/// <summary> /// Updates a particular vital according to regeneration rate /// </summary> /// <param name="vital">The vital stat to update (health/stamina/mana)</param> public void VitalHeartBeat(CreatureVital vital) { // Current and MaxValue are properties and include overhead in getting their values. We cache them so we only hit the overhead once. var vitalCurrent = vital.Current; var vitalMax = vital.MaxValue; if (vitalCurrent == vitalMax) { return; } if (vitalCurrent > vitalMax) { UpdateVital(vital, vitalMax); return; } if (vital.RegenRate == 0.0) { return; } // take attributes into consideration (strength, endurance) var attributeMod = GetAttributeMod(vital); // take stance into consideration (combat, crouch, sitting, sleeping) var stanceMod = GetStanceMod(vital); // take enchantments into consideration: // (regeneration / rejuvenation / mana renewal / etc.) var enchantmentMod = EnchantmentManager.GetRegenerationMod(vital); var augMod = 1.0f; if (this is Player player && player.AugmentationFasterRegen > 0) { augMod += player.AugmentationFasterRegen; } // cap rate? var currentTick = vital.RegenRate * attributeMod * stanceMod * enchantmentMod * augMod; // add in partially accumulated / rounded vitals from previous tick(s) var totalTick = currentTick + vital.PartialRegen; // accumulate partial vital rates between ticks var intTick = (int)totalTick; vital.PartialRegen = totalTick - intTick; if (intTick > 0) { UpdateVitalDelta(vital, intTick); if (vital.Vital == PropertyAttribute2nd.MaxHealth) { DamageHistory.OnHeal((uint)intTick); } } //Console.WriteLine($"VitalTick({vital.Vital.ToSentence()}): attributeMod={attributeMod}, stanceMod={stanceMod}, enchantmentMod={enchantmentMod}, regenRate={vital.RegenRate}, currentTick={currentTick}, totalTick={totalTick}, accumulated={vital.PartialRegen}"); }
/// <summary> /// Applies some amount of damage to this monster from source /// </summary> /// <param name="source">The attacker / source of damage</param> /// <param name="amount">The amount of damage rounded</param> public virtual uint TakeDamage(WorldObject source, DamageType damageType, float amount, bool crit = false) { var tryDamage = (int)Math.Round(amount); var damage = -UpdateVitalDelta(Health, -tryDamage); // TODO: update monster stamina? // source should only be null for combined DoT ticks from multiple sources if (source != null) { if (damage >= 0) { DamageHistory.Add(source, damageType, (uint)damage); } else { DamageHistory.OnHeal((uint)-damage); } } if (Health.Current <= 0) { OnDeath(DamageHistory.LastDamager, damageType, crit); Die(); } return((uint)Math.Max(0, damage)); }
public virtual void SetMaxVitals() { var missingHealth = Health.Missing; Health.Current = Health.MaxValue; Stamina.Current = Stamina.MaxValue; Mana.Current = Mana.MaxValue; DamageHistory.OnHeal(missingHealth); }
/// <summary> /// Updates a particular vital according to regeneration rate /// </summary> /// <param name="vital">The vital stat to update (health/stamina/mana)</param> public void VitalTick(CreatureVital vital) { if (vital.Current == vital.MaxValue) { return; } if (vital.Current > vital.MaxValue) { UpdateVital(vital, vital.MaxValue); return; } if (vital.RegenRate == 0.0) { return; } // take attributes into consideration (strength, endurance) var attributeMod = GetAttributeMod(vital); // take stance into consideration (combat, crouch, sitting, sleeping) var stanceMod = GetStanceMod(vital); // take enchantments into consideration: // (regeneration / rejuvenation / mana renewal / etc.) var enchantmentMod = EnchantmentManager.GetRegenerationMod(vital); // cap rate? var currentTick = vital.RegenRate * attributeMod * stanceMod * enchantmentMod; // add in partially accumulated / rounded vitals from previous tick(s) var totalTick = currentTick + vital.PartialRegen; // accumulate partial vital rates between ticks var intTick = (int)totalTick; vital.PartialRegen = totalTick - intTick; if (intTick > 0) { UpdateVitalDelta(vital, intTick); if (vital.Vital == PropertyAttribute2nd.MaxHealth) { DamageHistory.OnHeal((uint)intTick); } } //Console.WriteLine($"VitalTick({vital.Vital.ToSentence()}): attributeMod={attributeMod}, stanceMod={stanceMod}, enchantmentMod={enchantmentMod}, regenRate={vital.RegenRate}, currentTick={currentTick}, totalTick={totalTick}, accumulated={vital.PartialRegen}"); }
/// <summary> /// Method used to perform the animation, sound, and vital update on consumption of food or potions /// </summary> /// <param name="consumableName">Name of the consumable</param> /// <param name="sound">Either Sound.Eat1 or Sound.Drink1</param> /// <param name="buffType">ConsumableBuffType.Spell,ConsumableBuffType.Health,ConsumableBuffType.Stamina,ConsumableBuffType.Mana</param> /// <param name="boostAmount">Amount the Vital is boosted by; can be null, if buffType = ConsumableBuffType.Spell</param> /// <param name="spellDID">Id of the spell cast by the consumable; can be null, if buffType != ConsumableBuffType.Spell</param> public void ApplyConsumable(string consumableName, Sound sound, ConsumableBuffType buffType, uint?boostAmount, uint?spellDID) { MotionCommand motionCommand; if (sound == Sound.Eat1) { motionCommand = MotionCommand.Eat; } else { motionCommand = MotionCommand.Drink; } // start the eat/drink motion var motion = new Motion(MotionStance.NonCombat, motionCommand); EnqueueBroadcastMotion(motion); var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId); var animTime = motionTable.GetAnimationLength(CurrentMotionState.Stance, motionCommand, MotionCommand.Ready); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(animTime); actionChain.AddAction(this, () => { GameMessageSystemChat buffMessage; if (buffType == ConsumableBuffType.Spell) { bool result = false; uint spellId = spellDID ?? 0; if (spellId != 0) { result = CreateSingleSpell(spellId); } if (result) { var spell = new Server.Entity.Spell(spellId); buffMessage = new GameMessageSystemChat($"{consumableName} casts {spell.Name} on you.", ChatMessageType.Magic); } else { buffMessage = new GameMessageSystemChat($"Consuming {consumableName} attempted to apply a spell not yet fully implemented.", ChatMessageType.System); } } else { CreatureVital creatureVital; string vitalName; // Null check for safety if (boostAmount == null) { boostAmount = 0; } switch (buffType) { case ConsumableBuffType.Health: creatureVital = Health; vitalName = "Health"; break; case ConsumableBuffType.Mana: creatureVital = Mana; vitalName = "Mana"; break; default: creatureVital = Stamina; vitalName = "Stamina"; break; } var vitalChange = UpdateVitalDelta(creatureVital, (uint)boostAmount); if (vitalName == "Health") { DamageHistory.OnHeal((uint)vitalChange); if (Fellowship != null) { Fellowship.OnVitalUpdate(this); } } buffMessage = new GameMessageSystemChat($"You regain {vitalChange} {vitalName}.", ChatMessageType.Craft); } var soundEvent = new GameMessageSound(Guid, sound, 1.0f); EnqueueBroadcast(soundEvent); Session.Network.EnqueueSend(buffMessage); // return to original stance var returnStance = new Motion(CurrentMotionState.Stance); EnqueueBroadcastMotion(returnStance); }); actionChain.EnqueueChain(); }