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> /// Directly grants XP to the player, without the XP modifier /// </summary> /// <param name="amount">The amount of XP to grant to the player</param> /// <param name="xpType">The source of the XP being granted</param> /// <param name="shareable">If TRUE, this XP can be shared with fellowship members</param> public void GrantXP(long amount, XpType xpType, ShareType shareType = ShareType.All) { if (Fellowship != null && Fellowship.ShareXP && shareType.HasFlag(ShareType.Fellowship)) { // this will divy up the XP, and re-call this function // with ShareType.Fellowship removed Fellowship.SplitXp((ulong)amount, xpType, shareType, this); return; } UpdateXpAndLevel(amount, xpType); // for passing XP up the allegiance chain, // this function is only called at the very beginning, to start the process. if (shareType.HasFlag(ShareType.Allegiance)) { UpdateXpAllegiance(amount); } // only certain types of XP are granted to items if (xpType == XpType.Kill || xpType == XpType.Quest) { GrantItemXP(amount); } }
/// <summary> /// Directly grants XP to the player, without the XP modifier /// </summary> /// <param name="amount">The amount of XP to grant to the player</param> /// <param name="passup">The source of the XP being granted</param> /// <param name="shareable">If TRUE, this XP can be shared with fellowship members</param> public void GrantXP(long amount, XpType xpType, bool shareable = true) { if (shareable && Fellowship != null && Fellowship.ShareXP && Fellowship.ShareableMembers.ContainsKey(Guid.Full)) { // this will divy up the XP, and re-call this function // with shareable = false Fellowship.SplitXp((ulong)amount, xpType, this); return; } UpdateXpAndLevel(amount); // for passing XP up the allegiance chain, // this function is only called at the very beginning, to start the process. if (xpType != XpType.Allegiance) { UpdateXpAllegiance(amount); } // only certain types of XP are granted to items if (xpType == XpType.Kill || xpType == XpType.Quest) { GrantItemXP(amount); } }
/// <summary> /// Directly raises the available XP by a specified amount /// For debugging purposes only. Normal XP progression should use EarnXP above. /// </summary> /// <param name="amount">The amount of XP to grant to the player</param> /// <param name="passup">If TRUE, additional XP is passed up the allegiance chain</param> public void GrantXP(long amount, bool sharable = true, bool fixedAmount = false, bool message = true) { if (sharable) { if (Fellowship != null && Fellowship.ShareXP && Fellowship.SharableMembers.Contains(this)) { Fellowship.SplitXp((ulong)amount, fixedAmount); } else { UpdateXpAndLevel(amount); } UpdateXpAllegiance(amount); } else { UpdateXpAndLevel(amount); } if (message) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{amount:N0} experience granted.", ChatMessageType.Advancement)); } }
/// <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 FellowshipQuit(bool disband) { if (Fellowship == null) { return; } Fellowship.QuitFellowship(this, disband); Fellowship = null; }
/// <summary> /// Directly grants luminance to the player, without any additional luminance modifiers /// </summary> public void GrantLuminance(long amount, XpType xpType, ShareType shareType = ShareType.All) { if (Fellowship != null && Fellowship.ShareXP && shareType.HasFlag(ShareType.Fellowship)) { // this will divy up the luminance, and re-call this function // with ShareType.Fellowship removed Fellowship.SplitLuminance((ulong)amount, xpType, shareType, this); } else { AddLuminance(amount, xpType); } }
// todo: Figure out if this is the best place to do this, and whether there are concurrency issues associated with it. public void FellowshipCreate(string fellowshipName, bool shareXP) { // An Olthoi player cannot create a fellowship if (IsOlthoiPlayer) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OlthoiCannotJoinFellowship)); return; } Fellowship = new Fellowship(this, fellowshipName, shareXP); Session.Network.EnqueueSend(new GameEventFellowshipFullUpdate(Session)); Session.Network.EnqueueSend(new GameEventFellowshipFellowUpdateDone(Session)); }
public GameMessageFellowshipFullUpdate(Session session) : base(GameMessageOpcode.GameEvent, GameMessageGroup.Group09) { // This is a naive, bare-bones implementation of 0x02BE, FullFellowshipUpdate. // 0x02BE is fairly complicated, so the following code is at least valuable as an example of a valid server response. // todo: The current implementation has race conditions, // and there are questions that must be answered before it can be fixed. // We need to figure out who "owns" the fellowship data. // Does everyone get a turn to read from and modify the fellowship data, and if so, how is this managed? // Currently, creating and leaving a fellowship is supported. // Any other fellowship function is not yet supported. Fellowship fellowship = session.Player.Fellowship; Writer.Write(session.Player.Guid.Full); Writer.Write(session.GameEventSequence++); Writer.Write((uint)GameEvent.GameEventType.FellowshipFullUpdate); // the current number of fellowship members Writer.Write((UInt16)fellowship.FellowshipMembers.Count); // todo: figure out what these two bytes are for Writer.Write((byte)0x10); Writer.Write((byte)0x00); // --- FellowInfo --- ActionChain fellowChain = new ActionChain(); foreach (Player fellow in fellowship.FellowshipMembers) { // Write data associated with each fellowship member WriteFellow(fellow); } Writer.WriteString16L(fellowship.FellowshipName); Writer.Write(fellowship.FellowshipLeaderGuid); Writer.Write(Convert.ToUInt32(fellowship.ShareXP)); Writer.Write(Convert.ToUInt32(fellowship.Open)); // todo: fellows departed? Writer.Write(0u); Writer.Write(0u); // End of meaningful data? Writer.Write((uint)0x00200000); Writer.Write((uint)0x00200000); }
public List <Player> GetFellowshipTargets() { if (Fellowship != null) { return(Fellowship.GetFellowshipMembers().Values.ToList()); } else { return new List <Player>() { this } }; }
/// <summary> /// Called when a player dies, in conjunction with Die() /// </summary> /// <param name="lastDamager">The last damager that landed the death blow</param> /// <param name="damageType">The damage type for the death message</param> public override DeathMessage OnDeath(WorldObject lastDamager, DamageType damageType, bool criticalHit = false) { var deathMessage = base.OnDeath(lastDamager, damageType, criticalHit); lastDamager.EmoteManager.OnKill(this); var playerMsg = string.Format(deathMessage.Victim, Name, lastDamager.Name); var msgYourDeath = new GameEventYourDeath(Session, playerMsg); Session.Network.EnqueueSend(msgYourDeath); // broadcast to nearby players var nearbyMsg = string.Format(deathMessage.Broadcast, Name, lastDamager.Name); var broadcastMsg = new GameMessageSystemChat(nearbyMsg, ChatMessageType.Broadcast); var excludePlayers = new List <Player>(); if (lastDamager is Player lastDamagerPlayer) { excludePlayers.Add(lastDamagerPlayer); } var nearbyPlayers = EnqueueBroadcast(excludePlayers, false, broadcastMsg); excludePlayers.AddRange(nearbyPlayers); excludePlayers.Add(this); // exclude self if (Fellowship != null) { Fellowship.OnDeath(this); } // if the player's lifestone is in a different landblock, also broadcast their demise to that landblock if (Sanctuary != null && Location.Landblock != Sanctuary.Landblock) { // ActionBroadcastKill might not work if other players around lifestone aren't aware of this player yet... // this existing broadcast method is also based on the current visible objects to the player, // and the player hasn't entered portal space or teleported back to the lifestone yet, so this doesn't work //ActionBroadcastKill(nearbyMsg, Guid, lastDamager.Guid); // instead, we get all of the players in the lifestone landblock + adjacent landblocks, // and possibly limit that to some radius around the landblock? var lifestoneBlock = LandblockManager.GetLandblock(new LandblockId(Sanctuary.Landblock << 16 | 0xFFFF), true); lifestoneBlock.EnqueueBroadcast(excludePlayers, true, broadcastMsg); } return(deathMessage); }
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); } } }
public void EarnXP(long amount, bool sharable = true, bool fixedAmount = false) { if (sharable) { if (Fellowship != null && Fellowship.ShareXP) { Fellowship.SplitXp((ulong)amount, fixedAmount); } else { UpdateXpAndLevel(amount); } } else { UpdateXpAndLevel(amount); } }
public static void HandleFellowship(Session session, NetworkObject networkObject) { FellowshipAction action = (FellowshipAction)NetworkObjectField.ReadIntField(networkObject.GetField(0)); FellowshipObject fellowshipInfo = new FellowshipObject(); fellowshipInfo.FromNetworkObject(networkObject.GetField(1).ReadObject()); switch (action) { case FellowshipAction.Join: { Fellowship fellowship = FellowshipManager.GetFellowship(fellowshipInfo); fellowship.AddMember(session); break; } case FellowshipAction.Leave: { Fellowship fellowship = FellowshipManager.GetFellowship(fellowshipInfo); fellowship?.RemoveMember(session); break; } } }
/// <summary> /// Directly grants XP to the player, without the XP modifier /// </summary> /// <param name="amount">The amount of XP to grant to the player</param> /// <param name="xpType">The source of the XP being granted</param> /// <param name="shareable">If TRUE, this XP can be shared with fellowship members</param> public void GrantXP(long amount, XpType xpType, ShareType shareType = ShareType.All) { if (IsOlthoiPlayer) { if (HasVitae) { UpdateXpVitae(amount); } return; } if (Fellowship != null && Fellowship.ShareXP && shareType.HasFlag(ShareType.Fellowship)) { // this will divy up the XP, and re-call this function // with ShareType.Fellowship removed Fellowship.SplitXp((ulong)amount, xpType, shareType, this); return; } // Make sure UpdateXpAndLevel is done on this players thread EnqueueAction(new ActionEventDelegate(() => UpdateXpAndLevel(amount, xpType))); // for passing XP up the allegiance chain, // this function is only called at the very beginning, to start the process. if (shareType.HasFlag(ShareType.Allegiance)) { UpdateXpAllegiance(amount); } // only certain types of XP are granted to items if (xpType == XpType.Kill || xpType == XpType.Quest) { GrantItemXP(amount); } }
/// <summary> /// Called when a player dies, in conjunction with Die() /// </summary> /// <param name="lastDamager">The last damager that landed the death blow</param> /// <param name="damageType">The damage type for the death message</param> public override DeathMessage OnDeath(WorldObject lastDamager, DamageType damageType, bool criticalHit = false) { if (DamageHistory.TopDamager is Player pkPlayer) { if (IsPKDeath(pkPlayer)) { pkPlayer.PkTimestamp = Time.GetUnixTime(); pkPlayer.PlayerKillsPk++; var globalPKDe = $"{lastDamager.Name} has defeated {Name}!"; if ((Location.Cell & 0xFFFF) < 0x100) { globalPKDe += $" The kill occured at {Location.GetMapCoordStr()}"; } globalPKDe += "\n[PKDe]"; PlayerManager.BroadcastToAll(new GameMessageSystemChat(globalPKDe, ChatMessageType.Broadcast)); } else if (IsPKLiteDeath(pkPlayer)) { pkPlayer.PlayerKillsPkl++; } } var deathMessage = base.OnDeath(lastDamager, damageType, criticalHit); if (lastDamager != null) { lastDamager.EmoteManager.OnKill(this); } var playerMsg = ""; if (lastDamager != null) { playerMsg = string.Format(deathMessage.Victim, Name, lastDamager.Name); } else { playerMsg = deathMessage.Victim; } var msgYourDeath = new GameEventYourDeath(Session, playerMsg); Session.Network.EnqueueSend(msgYourDeath); // broadcast to nearby players var nearbyMsg = ""; if (lastDamager != null) { nearbyMsg = string.Format(deathMessage.Broadcast, Name, lastDamager.Name); } else { nearbyMsg = deathMessage.Broadcast; } var broadcastMsg = new GameMessageSystemChat(nearbyMsg, ChatMessageType.Broadcast); log.Debug("[CORPSE] " + nearbyMsg); var excludePlayers = new List <Player>(); if (lastDamager is Player lastDamagerPlayer) { excludePlayers.Add(lastDamagerPlayer); } var nearbyPlayers = EnqueueBroadcast(excludePlayers, false, broadcastMsg); excludePlayers.AddRange(nearbyPlayers); excludePlayers.Add(this); // exclude self if (Fellowship != null) { Fellowship.OnDeath(this); } // if the player's lifestone is in a different landblock, also broadcast their demise to that landblock if (Sanctuary != null && Location.Landblock != Sanctuary.Landblock) { // ActionBroadcastKill might not work if other players around lifestone aren't aware of this player yet... // this existing broadcast method is also based on the current visible objects to the player, // and the player hasn't entered portal space or teleported back to the lifestone yet, so this doesn't work //ActionBroadcastKill(nearbyMsg, Guid, lastDamager.Guid); // instead, we get all of the players in the lifestone landblock + adjacent landblocks, // and possibly limit that to some radius around the landblock? var lifestoneBlock = LandblockManager.GetLandblock(new LandblockId(Sanctuary.Landblock << 16 | 0xFFFF), true); lifestoneBlock.EnqueueBroadcast(excludePlayers, true, broadcastMsg); } return(deathMessage); }
/// <summary> /// Constructs a new QuestManager for a Fellowship /// </summary> public QuestManager(Fellowship fellowship) { Fellowship = fellowship; }
public void FellowshipQuit(bool disband) { Fellowship.QuitFellowship(this, disband); Fellowship = null; }
/// <summary> /// Determines if the player has advanced a level /// </summary> private void CheckForLevelup() { var xpTable = DatManager.PortalDat.XpTable; var maxLevel = GetMaxLevel(); if (Level >= maxLevel) { return; } var startingLevel = Level; bool creditEarned = false; // increases until the correct level is found while ((ulong)(TotalExperience ?? 0) >= xpTable.CharacterLevelXPList[(Level ?? 0) + 1]) { Level++; // increase the skill credits if the chart allows this level to grant a credit if (xpTable.CharacterLevelSkillCreditList[Level ?? 0] > 0) { AvailableSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0]; TotalSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0]; creditEarned = true; } // break if we reach max if (Level == maxLevel) { PlayParticleEffect(PlayScript.WeddingBliss, Guid); break; } } if (Level > startingLevel) { var message = (Level == maxLevel) ? $"You have reached the maximum level of {Level}!" : $"You are now level {Level}!"; message += (AvailableSkillCredits > 0) ? $"\nYou have {AvailableExperience:#,###0} experience points and {AvailableSkillCredits} skill credits available to raise skills and attributes." : $"\nYou have {AvailableExperience:#,###0} experience points available to raise skills and attributes."; var levelUp = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.Level, Level ?? 1); var currentCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0); if (Level != maxLevel && !creditEarned) { var nextLevelWithCredits = 0; for (int i = (Level ?? 0) + 1; i <= maxLevel; i++) { if (xpTable.CharacterLevelSkillCreditList[i] > 0) { nextLevelWithCredits = i; break; } } message += $"\nYou will earn another skill credit at level {nextLevelWithCredits}."; } if (Fellowship != null) { Fellowship.OnFellowLevelUp(this); } if (AllegianceNode != null) { AllegianceNode.OnLevelUp(); } Session.Network.EnqueueSend(levelUp); SetMaxVitals(); // play level up effect PlayParticleEffect(PlayScript.LevelUp, Guid); Session.Network.EnqueueSend(new GameMessageSystemChat(message, ChatMessageType.Advancement), currentCredits); } }
/// <summary> /// Called when a player dies, in conjunction with Die() /// </summary> /// <param name="lastDamager">The last damager that landed the death blow</param> /// <param name="damageType">The damage type for the death message</param> public override DeathMessage OnDeath(DamageHistoryInfo lastDamager, DamageType damageType, bool criticalHit = false) { var topDamager = DamageHistory.GetTopDamager(false); HandlePKDeathBroadcast(lastDamager, topDamager); var deathMessage = base.OnDeath(lastDamager, damageType, criticalHit); var lastDamagerObj = lastDamager?.TryGetAttacker(); if (lastDamagerObj != null) { lastDamagerObj.EmoteManager.OnKill(this); } var playerMsg = ""; if (lastDamager != null) { playerMsg = string.Format(deathMessage.Victim, Name, lastDamager.Name); } else { playerMsg = deathMessage.Victim; } var msgYourDeath = new GameEventVictimNotification(Session, playerMsg); Session.Network.EnqueueSend(msgYourDeath); // broadcast to nearby players var nearbyMsg = ""; if (lastDamager != null) { nearbyMsg = string.Format(deathMessage.Broadcast, Name, lastDamager.Name); } else { nearbyMsg = deathMessage.Broadcast; } var broadcastMsg = new GameMessagePlayerKilled(nearbyMsg, Guid, lastDamager?.Guid ?? ObjectGuid.Invalid); log.Debug("[CORPSE] " + nearbyMsg); var excludePlayers = new List <Player>(); var nearbyPlayers = EnqueueBroadcast(excludePlayers, false, broadcastMsg); excludePlayers.AddRange(nearbyPlayers); if (Fellowship != null) { Fellowship.OnDeath(this); } // if the player's lifestone is in a different landblock, also broadcast their demise to that landblock if (PropertyManager.GetBool("lifestone_broadcast_death").Item&& Sanctuary != null && Location.Landblock != Sanctuary.Landblock) { // ActionBroadcastKill might not work if other players around lifestone aren't aware of this player yet... // this existing broadcast method is also based on the current visible objects to the player, // and the player hasn't entered portal space or teleported back to the lifestone yet, so this doesn't work //ActionBroadcastKill(nearbyMsg, Guid, lastDamager.Guid); // instead, we get all of the players in the lifestone landblock + adjacent landblocks, // and possibly limit that to some radius around the landblock? var lifestoneBlock = LandblockManager.GetLandblock(new LandblockId(Sanctuary.Landblock << 16 | 0xFFFF), true); lifestoneBlock.EnqueueBroadcast(excludePlayers, true, Sanctuary, LocalBroadcastRangeSq, broadcastMsg); } return(deathMessage); }
/// <summary> /// Determines if the player has advanced a level /// </summary> private void CheckForLevelup() { var xpTable = DatManager.PortalDat.XpTable; var maxLevel = GetMaxLevel(); if (Level >= maxLevel) { return; } var startingLevel = Level; bool creditEarned = false; // if level is not under 275(false) do normal stuff, otherwise if over 274(true) do custom function. // increases until the correct level is found while ((ulong)(TotalExperience ?? 0) >= ((Level < 275) ? xpTable.CharacterLevelXPList[(Level ?? 0) + 1] : (ulong)Catch275(false, startingLevel))) { Level++; // increase the skill credits if the chart allows this level to grant a credit // gotta limit this to levels 275 and under otherwise causes crash. if (Level <= 275) { if (xpTable.CharacterLevelSkillCreditList[Level ?? 0] > 0) { AvailableSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0]; TotalSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0]; creditEarned = true; } } // if a player is level 300 or higher and level is also divisible by 30 evenly give a skill credit. if (Level > 275) { if (Level >= 300 && Level % 30 == 0) { AvailableSkillCredits += 1; TotalSkillCredits += 1; creditEarned = true; } } // break if we reach max if (Level == maxLevel) { PlayParticleEffect(PlayScript.WeddingBliss, Guid); break; } } if (Level > startingLevel) { var message = (Level == maxLevel) ? $"You have reached the maximum level of {Level}!" : $"You are now level {Level}!"; message += (AvailableSkillCredits > 0) ? $"\nYou have {AvailableExperience:#,###0} experience points and {AvailableSkillCredits} skill credits available to raise skills and attributes." : $"\nYou have {AvailableExperience:#,###0} experience points available to raise skills and attributes."; var levelUp = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.Level, Level ?? 1); var currentCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0); // 275 and under code. if (Level != maxLevel && !creditEarned && Level <= 275) { var nextLevelWithCredits = 0; for (int i = (Level ?? 0) + 1; i <= maxLevel; i++) { if (xpTable.CharacterLevelSkillCreditList[i] > 0) { nextLevelWithCredits = i; break; } } message += $"\nYou will earn another skill credit at level {nextLevelWithCredits}."; } if (Fellowship != null) { Fellowship.OnFellowLevelUp(this); } if (AllegianceNode != null) { AllegianceNode.OnLevelUp(); } Session.Network.EnqueueSend(levelUp); SetMaxVitals(); // play level up effect PlayParticleEffect(PlayScript.LevelUp, Guid); Session.Network.EnqueueSend(new GameMessageSystemChat(message, ChatMessageType.Advancement), currentCredits); } }
// todo: Figure out if this is the best place to do this, and whether there are concurrency issues associated with it. public void FellowshipCreate(string fellowshipName, bool shareXP) { Fellowship = new Fellowship(this, fellowshipName, shareXP); }
/// <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(); }