public void Player_Tick(double currentUnixTime) { actionQueue.RunActions(); if (nextAgeUpdateTime <= currentUnixTime) { nextAgeUpdateTime = currentUnixTime + ageUpdateInterval; if (initialAgeTime == DateTime.MinValue) { initialAge = Age ?? 1; initialAgeTime = DateTime.UtcNow; } Age = initialAge + (int)(DateTime.UtcNow - initialAgeTime).TotalSeconds; // In retail, this is sent every 7 seconds. If you adjust ageUpdateInterval from 7, you'll need to re-add logic to send this every 7s (if you want to match retail) Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.Age, Age ?? 1)); } if (FellowVitalUpdate && Fellowship != null) { Fellowship.OnVitalUpdate(this); FellowVitalUpdate = false; } }
/// <summary> /// Applies damages to a player from a physical damage source /// </summary> public void TakeDamage(WorldObject source, DamageType damageType, float _amount, BodyPart bodyPart, bool crit = false) { if (Invincible ?? false) { return; } // check lifestone protection if (UnderLifestoneProtection) { HandleLifestoneProtection(); return; } var amount = (uint)Math.Round(_amount); var percent = (float)amount / Health.MaxValue; // update health var damageTaken = (uint)-UpdateVitalDelta(Health, (int)-amount); DamageHistory.Add(source, damageType, damageTaken); // update stamina UpdateVitalDelta(Stamina, -1); if (Fellowship != null) { Fellowship.OnVitalUpdate(this); } if (Health.Current == 0) { OnDeath(source, damageType, crit); Die(); return; } var damageLocation = (DamageLocation)BodyParts.Indices[bodyPart]; // send network messages var creature = source as Creature; var hotspot = source as Hotspot; if (creature != null) { var text = new GameEventDefenderNotification(Session, creature.Name, damageType, percent, amount, damageLocation, crit, AttackConditions.None); Session.Network.EnqueueSend(text); var hitSound = new GameMessageSound(Guid, GetHitSound(source, bodyPart), 1.0f); var splatter = new GameMessageScript(Guid, (PlayScript)Enum.Parse(typeof(PlayScript), "Splatter" + creature.GetSplatterHeight() + creature.GetSplatterDir(this))); EnqueueBroadcast(hitSound, splatter); } if (percent >= 0.1f) { EnqueueBroadcast(new GameMessageSound(Guid, Sound.Wound1, 1.0f)); } }
/// <summary> /// Simplified player take damage function, only called for DoTs currently /// </summary> public override void TakeDamageOverTime(float _amount, DamageType damageType) { if (Invincible ?? false || IsDead) { return; } // check lifestone protection if (UnderLifestoneProtection) { HandleLifestoneProtection(); return; } var amount = (uint)Math.Round(_amount); var percent = (float)amount / Health.MaxValue; // update health var damageTaken = (uint)-UpdateVitalDelta(Health, (int)-amount); // update stamina UpdateVitalDelta(Stamina, -1); if (Fellowship != null) { Fellowship.OnVitalUpdate(this); } // send damage text message if (PropertyManager.GetBool("show_dot_messages").Item) { var nether = damageType == DamageType.Nether ? "nether " : ""; var text = new GameMessageSystemChat($"You receive {amount} points of periodic {nether}damage.", ChatMessageType.Combat); Session.Network.EnqueueSend(text); } // splatter effects //var splatter = new GameMessageScript(Guid, (PlayScript)Enum.Parse(typeof(PlayScript), "Splatter" + creature.GetSplatterHeight() + creature.GetSplatterDir(this))); // not sent in retail, but great visual indicator? var splatter = new GameMessageScript(Guid, damageType == DamageType.Nether ? ACE.Entity.Enum.PlayScript.HealthDownVoid : ACE.Entity.Enum.PlayScript.DirtyFightingDamageOverTime); EnqueueBroadcast(splatter); if (Health.Current <= 0) { // since damage over time is possibly combined from multiple sources, // sending a message to the last damager here could be tricky.. OnDeath(null, damageType, false); Die(); return; } if (percent >= 0.1f) { EnqueueBroadcast(new GameMessageSound(Guid, Sound.Wound1, 1.0f)); } }
public void Player_Tick(double currentUnixTime) { actionQueue.RunActions(); if (nextAgeUpdateTime <= currentUnixTime) { nextAgeUpdateTime = currentUnixTime + ageUpdateInterval; if (initialAgeTime == DateTime.MinValue) { initialAge = Age ?? 1; initialAgeTime = DateTime.UtcNow; } Age = initialAge + (int)(DateTime.UtcNow - initialAgeTime).TotalSeconds; // In retail, this is sent every 7 seconds. If you adjust ageUpdateInterval from 7, you'll need to re-add logic to send this every 7s (if you want to match retail) Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.Age, Age ?? 1)); } if (FellowVitalUpdate && Fellowship != null) { Fellowship.OnVitalUpdate(this); FellowVitalUpdate = false; } if (House != null && PropertyManager.GetBool("house_rent_enabled").Item) { if (houseRentWarnTimestamp > 0 && currentUnixTime > houseRentWarnTimestamp) { HouseManager.GetHouse(House.Guid.Full, (house) => { if (house != null && house.HouseStatus == HouseStatus.Active && !house.SlumLord.IsRentPaid()) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Warning! You have not paid your maintenance costs for the last {(house.IsApartment ? "90" : "30")} day maintenance period. Please pay these costs by this deadline or you will lose your house, and all your items within it.", ChatMessageType.Broadcast)); } }); houseRentWarnTimestamp = Time.GetFutureUnixTime(houseRentWarnInterval); } else if (houseRentWarnTimestamp == 0) { houseRentWarnTimestamp = Time.GetFutureUnixTime(houseRentWarnInterval); } } }
/// <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(); }