diff --git a/CoreBase/Network/BaseServer.cs b/CoreBase/Network/BaseServer.cs
index a748c8d8d4..1641e25e12 100644
--- a/CoreBase/Network/BaseServer.cs
+++ b/CoreBase/Network/BaseServer.cs
@@ -169,7 +169,7 @@ void FreeBufferPosition()
catch (SocketException e)
{
if (log.IsDebugEnabled)
- log.Error($"Socket exception on UDP receive (Code: {e.SocketErrorCode})");
+ log.Debug($"Socket exception on UDP receive (Code: {e.SocketErrorCode})");
}
catch (Exception e)
{
diff --git a/CoreDatabase/Tables/DbCoreCharacter.cs b/CoreDatabase/Tables/DbCoreCharacter.cs
index e612f657ad..11069a5e76 100644
--- a/CoreDatabase/Tables/DbCoreCharacter.cs
+++ b/CoreDatabase/Tables/DbCoreCharacter.cs
@@ -63,14 +63,14 @@ public class DbCoreCharacter : DataObject
private int m_mithril;
private int m_currentModel;
- private int m_constitution = 0;
- private int m_dexterity = 0;
- private int m_strength = 0;
- private int m_quickness = 0;
- private int m_intelligence = 0;
- private int m_piety = 0;
- private int m_empathy = 0;
- private int m_charisma = 0;
+ private int m_constitution;
+ private int m_dexterity;
+ private int m_strength;
+ private int m_quickness;
+ private int m_intelligence;
+ private int m_piety;
+ private int m_empathy;
+ private int m_charisma;
//This needs to be uint and ushort!
private int m_xpos;
@@ -130,19 +130,19 @@ public class DbCoreCharacter : DataObject
private bool m_isLevelRespecUsed;
private int m_respecBought; // /respec buy
private bool m_safetyFlag;
- private int m_craftingPrimarySkill = 0;
+ private int m_craftingPrimarySkill;
private bool m_cancelStyle;
private bool m_isAnonymous;
- private byte m_customisationStep = 1;
+ private byte m_customisationStep;
- private byte m_eyesize = 0;
- private byte m_lipsize = 0;
- private byte m_eyecolor = 0;
- private byte m_hairColor = 0;
- private byte m_facetype = 0;
- private byte m_hairstyle = 0;
- private byte m_moodtype = 0;
+ private byte m_eyesize;
+ private byte m_lipsize;
+ private byte m_eyecolor;
+ private byte m_hairColor;
+ private byte m_facetype;
+ private byte m_hairstyle;
+ private byte m_moodtype;
private bool m_gainXP;
private bool m_gainRP;
@@ -167,15 +167,15 @@ public class DbCoreCharacter : DataObject
private bool m_mlGranted;
// Should this player stats be ignored when tabulating statistics?
- private bool m_ignoreStatistics = false;
+ private bool m_ignoreStatistics;
// What should the Herald display of this character?
- private byte m_notDisplayedInHerald = 0;
+ private byte m_notDisplayedInHerald;
// Should we hide the detailed specialization of this player in the APIs?
private bool m_hideSpecializationAPI;
- private byte m_activeSaddleBags = 0;
+ private byte m_activeSaddleBags;
private DateTime m_lastLevelUp;
@@ -188,46 +188,28 @@ public class DbCoreCharacter : DataObject
private bool m_receiveROG; // toggle receiving ROGs for the player
private bool m_boosted; // set to true if player has used a free level/rr NPC
+ // Controls automation
+ private ushort _automaticBackupStyleId;
+
///
/// Create the character row in table
///
public DbCoreCharacter()
- {
- m_creationDate = DateTime.Now;
- m_concentration = 100;
- m_exp = 0;
- m_bntyPts = 0;
- m_realmPts = 0;
-
- m_lastPlayed = DateTime.Now; // Prevent /played crash.
- m_playedTime = 0; // /played startup
- m_deathTime = long.MinValue;
- m_respecAmountAllSkill = 0;
- m_respecAmountSingleSkill = 0;
- m_respecAmountRealmSkill = 0;
- m_respecAmountDOL = 0;
- m_respecBought = 0;
-
- m_isLevelRespecUsed = true;
- m_safetyFlag = true;
- m_craftingPrimarySkill = 0;
- m_usedLevelCommand = false;
- m_spellQueue = true;
- m_gainXP = true;
- m_gainRP = true;
- m_autoloot = true;
- m_showXFireInfo = false;
- m_noHelp = false;
- m_showGuildLogins = false;
- m_roleplay = false;
- m_ignoreStatistics = false;
+ {
+ m_creationDate = DateTime.Now;
+ m_concentration = 100;
+ m_lastPlayed = DateTime.Now; // Prevent /played crash.
+ m_deathTime = long.MinValue;
+ m_isLevelRespecUsed = true;
+ m_customisationStep = 1;
+ m_safetyFlag = true;
+ m_spellQueue = true;
+ m_gainXP = true;
+ m_gainRP = true;
+ m_autoloot = true;
m_lastLevelUp = DateTime.Now;
- m_playedTimeSinceLevel = 0;
m_receiveROG = true;
- m_hardcore = false;
- m_hardcoreCompleted = false;
- m_boosted = false;
- }
+ }
///
/// Gets/sets if this character has xp in a gravestone
@@ -2249,6 +2231,17 @@ public bool isBoosted
}
}
+ [DataElement(AllowDbNull = false)]
+ public ushort AutomaticBackupStyleId
+ {
+ get => _automaticBackupStyleId;
+ set
+ {
+ Dirty = true;
+ _automaticBackupStyleId = value;
+ }
+ }
+
///
/// Do we ignore all statistics for this player?
///
diff --git a/GameServer/ECS-Components/Actions/NpcAttackAction.cs b/GameServer/ECS-Components/Actions/NpcAttackAction.cs
index 1fb3bf6d35..47e29f2cb9 100644
--- a/GameServer/ECS-Components/Actions/NpcAttackAction.cs
+++ b/GameServer/ECS-Components/Actions/NpcAttackAction.cs
@@ -91,7 +91,7 @@ protected override bool PrepareMeleeAttack()
}
}
- _combatStyle = StyleComponent.NPCGetStyleToUse();
+ _combatStyle = StyleComponent.GetStyleToUse();
if (!base.PrepareMeleeAttack())
return false;
diff --git a/GameServer/ECS-Components/NpcStyleComponent.cs b/GameServer/ECS-Components/NpcStyleComponent.cs
new file mode 100644
index 0000000000..9221af98c0
--- /dev/null
+++ b/GameServer/ECS-Components/NpcStyleComponent.cs
@@ -0,0 +1,86 @@
+using DOL.GS.ServerProperties;
+using DOL.GS.Styles;
+
+namespace DOL.GS
+{
+ public class NpcStyleComponent : StyleComponent
+ {
+ private GameNPC _npcOwner;
+
+ public NpcStyleComponent(GameNPC npcOwner) : base(npcOwner)
+ {
+ _npcOwner = npcOwner;
+ }
+
+ public override Style GetStyleToUse()
+ {
+ if (_npcOwner.Styles == null || _npcOwner.Styles.Count < 1 || _npcOwner.TargetObject == null)
+ return null;
+
+ AttackData lastAttackData = _npcOwner.attackComponent.attackAction.LastAttackData;
+
+ // Chain and defensive styles are excluded from the chance roll because they would almost never happen otherwise.
+ // For example, an NPC blocks 10% of the time, so the default 20% style chance effectively means the defensive
+ // style would only actually occur during 2% of of a mob's attacks. In comparison, a style chain would only happen
+ // 0.4% of the time.
+ if (_npcOwner.StylesChain != null && _npcOwner.StylesChain.Count > 0)
+ {
+ foreach (Style style in _npcOwner.StylesChain)
+ {
+ if (StyleProcessor.CanUseStyle(lastAttackData, _npcOwner, style, _npcOwner.ActiveWeapon))
+ return style;
+ }
+ }
+
+ if (_npcOwner.StylesDefensive != null && _npcOwner.StylesDefensive.Count > 0)
+ {
+ foreach (Style style in _npcOwner.StylesDefensive)
+ {
+ if (StyleProcessor.CanUseStyle(lastAttackData, _npcOwner, style, _npcOwner.ActiveWeapon) && _npcOwner.CheckStyleStun(style)) // Make sure we don't spam stun styles like Brutalize
+ return style;
+ }
+ }
+
+ if (Util.Chance(Properties.GAMENPC_CHANCES_TO_STYLE))
+ {
+ // All of the remaining lists are randomly picked from,
+ // as this creates more variety with each combat result.
+ // For example, a mob with both Pincer and Ice Storm
+ // styles could potentially use one or the other with
+ // each attack roll that succeeds.
+
+ // First, check positional styles (in order of back, side, front)
+ // in case the defender is facing another direction
+ if (_npcOwner.StylesBack != null && _npcOwner.StylesBack.Count > 0)
+ {
+ Style style = _npcOwner.StylesBack[Util.Random(0, _npcOwner.StylesBack.Count - 1)];
+
+ if (StyleProcessor.CanUseStyle(lastAttackData, _npcOwner, style, _npcOwner.ActiveWeapon))
+ return style;
+ }
+
+ if (_npcOwner.StylesSide != null && _npcOwner.StylesSide.Count > 0)
+ {
+ Style style = _npcOwner.StylesSide[Util.Random(0, _npcOwner.StylesSide.Count - 1)];
+
+ if (StyleProcessor.CanUseStyle(lastAttackData, _npcOwner, style, _npcOwner.ActiveWeapon))
+ return style;
+ }
+
+ if (_npcOwner.StylesFront != null && _npcOwner.StylesFront.Count > 0)
+ {
+ Style style = _npcOwner.StylesFront[Util.Random(0, _npcOwner.StylesFront.Count - 1)];
+
+ if (StyleProcessor.CanUseStyle(lastAttackData, _npcOwner, style, _npcOwner.ActiveWeapon))
+ return style;
+ }
+
+ // Pick a random anytime style
+ if (_npcOwner.StylesAnytime != null && _npcOwner.StylesAnytime.Count > 0)
+ return _npcOwner.StylesAnytime[Util.Random(0, _npcOwner.StylesAnytime.Count - 1)];
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/GameServer/ECS-Components/PlayerStyleComponent.cs b/GameServer/ECS-Components/PlayerStyleComponent.cs
new file mode 100644
index 0000000000..cda51306f3
--- /dev/null
+++ b/GameServer/ECS-Components/PlayerStyleComponent.cs
@@ -0,0 +1,208 @@
+using System.Collections.Generic;
+using DOL.Database;
+using DOL.GS.PacketHandler;
+using DOL.GS.Styles;
+using DOL.Language;
+
+namespace DOL.GS
+{
+ public class PlayerStyleComponent : StyleComponent
+ {
+ private GamePlayer _playerOwner;
+ private bool _awaitingBackupInput;
+ private Style _automaticBackupStyle;
+
+ public override bool CancelStyle
+ {
+ get => _playerOwner.DBCharacter != null && _playerOwner.DBCharacter.CancelStyle;
+ set
+ {
+ if (_playerOwner.DBCharacter != null)
+ _playerOwner.DBCharacter.CancelStyle = value;
+ }
+ }
+
+ public override bool AwaitingBackupInput
+ {
+ get => _awaitingBackupInput;
+ set => _awaitingBackupInput = value;
+ }
+
+ public override Style AutomaticBackupStyle
+ {
+ get => _automaticBackupStyle;
+ set => _automaticBackupStyle = value;
+ }
+
+ public PlayerStyleComponent(GamePlayer playerOwner) : base(playerOwner)
+ {
+ _playerOwner = playerOwner;
+ }
+
+ public void OnPlayerLoadFromDatabase()
+ {
+ DbCoreCharacter dbCharacter = _playerOwner.DBCharacter;
+
+ if (dbCharacter == null)
+ return;
+
+ AutomaticBackupStyle = SkillBase.GetStyleByID(dbCharacter.AutomaticBackupStyleId, _playerOwner.CharacterClass.ID);
+ }
+
+ public void OnPlayerSaveIntoDatabase()
+ {
+ DbCoreCharacter dbCharacter = _playerOwner.DBCharacter;
+
+ if (dbCharacter == null || AutomaticBackupStyle.ID == dbCharacter.AutomaticBackupStyleId)
+ return;
+
+ dbCharacter.AutomaticBackupStyleId = (ushort) AutomaticBackupStyle.ID;
+ }
+
+ public override Style GetStyleToUse()
+ {
+ if (NextCombatStyle == null)
+ return null;
+
+ // If the player no longer access to the style.
+ if (AutomaticBackupStyle != null && _playerOwner.GetBaseSpecLevel(AutomaticBackupStyle.Spec) < AutomaticBackupStyle.SpecLevelRequirement)
+ {
+ _playerOwner.Out.SendMessage($"{AutomaticBackupStyle.Name} is no longer a valid backup style for your spec level and has been cleared.", eChatType.CT_System, eChatLoc.CL_SystemWindow);
+ AutomaticBackupStyle = null;
+ }
+
+ AttackData lastAttackData = Owner.attackComponent.attackAction.LastAttackData;
+ DbInventoryItem weapon = NextCombatStyle.WeaponTypeRequirement == (int) eObjectType.Shield ? Owner.Inventory.GetItem(eInventorySlot.LeftHandWeapon) : Owner.ActiveWeapon;
+
+ //determine which style will actually be used
+ Style styleToUse;
+
+ if (StyleProcessor.CanUseStyle(lastAttackData, Owner, NextCombatStyle, weapon))
+ styleToUse = NextCombatStyle;
+ else if (NextCombatBackupStyle != null)
+ styleToUse = NextCombatBackupStyle;
+ else if (AutomaticBackupStyle != null)
+ {
+ StyleProcessor.TryToUseStyle(Owner, AutomaticBackupStyle);
+ styleToUse = NextCombatBackupStyle; // `NextCombatBackupStyle` became `AutomaticBackupStyle` if `TryToUse` succeeded.
+ }
+ else
+ styleToUse = NextCombatStyle; // Not sure why.
+
+ return styleToUse;
+ }
+
+ public override void DelveWeaponStyle(List delveInfo, Style style)
+ {
+ StyleProcessor.DelveWeaponStyle(delveInfo, style, _playerOwner);
+ }
+
+ public override void AddStyle(Style style, bool notify)
+ {
+ lock (lockStyleList)
+ {
+ if (m_styles.TryGetValue(style.ID, out Style existingStyle))
+ {
+ existingStyle.Level = style.Level;
+ return;
+ }
+
+ m_styles.Add(style.ID, style);
+
+ if (!notify)
+ return;
+
+ _playerOwner.Out.SendMessage(LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.YouLearn", style.Name), eChatType.CT_System, eChatLoc.CL_SystemWindow);
+ string message = null;
+
+ if (style.OpeningRequirementType is Style.eOpening.Offensive)
+ {
+ switch (style.AttackResultRequirement)
+ {
+ case Style.eAttackResultRequirement.Style:
+ case Style.eAttackResultRequirement.Hit: // TODO: make own message for hit after styles DB is updated
+ {
+ Style reqStyle = SkillBase.GetStyleByID(style.OpeningRequirementValue, _playerOwner.CharacterClass.ID);
+
+ if (reqStyle == null)
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterStyle", "(style " + style.OpeningRequirementValue + " not found)");
+ else
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterStyle", reqStyle.Name);
+
+ break;
+ }
+ case Style.eAttackResultRequirement.Miss:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterMissed");
+ break;
+ }
+ case Style.eAttackResultRequirement.Parry:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterParried");
+ break;
+ }
+ case Style.eAttackResultRequirement.Block:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterBlocked");
+ break;
+ }
+ case Style.eAttackResultRequirement.Evade:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterEvaded");
+ break;
+ }
+ case Style.eAttackResultRequirement.Fumble:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.AfterFumbles");
+ break;
+ }
+ }
+ }
+ else if (style.OpeningRequirementType is Style.eOpening.Defensive)
+ {
+ switch (style.AttackResultRequirement)
+ {
+ case Style.eAttackResultRequirement.Miss:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetMisses");
+ break;
+ }
+ case Style.eAttackResultRequirement.Hit:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetHits");
+ break;
+ }
+ case Style.eAttackResultRequirement.Parry:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetParried");
+ break;
+ }
+ case Style.eAttackResultRequirement.Block:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetBlocked");
+ break;
+ }
+ case Style.eAttackResultRequirement.Evade:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetEvaded");
+ break;
+ }
+ case Style.eAttackResultRequirement.Fumble:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetFumbles");
+ break;
+ }
+ case Style.eAttackResultRequirement.Style:
+ {
+ message = LanguageMgr.GetTranslation(_playerOwner.Client.Account.Language, "GamePlayer.RefreshSpec.TargetStyle");
+ break;
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(message))
+ _playerOwner.Out.SendMessage(message, eChatType.CT_System, eChatLoc.CL_SystemWindow);
+ }
+ }
+ }
+}
diff --git a/GameServer/ECS-Components/StyleComponent.cs b/GameServer/ECS-Components/StyleComponent.cs
index 39ce6ab480..4b11a01360 100644
--- a/GameServer/ECS-Components/StyleComponent.cs
+++ b/GameServer/ECS-Components/StyleComponent.cs
@@ -1,23 +1,46 @@
-using System.Collections;
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using DOL.Database;
-using DOL.GS.PacketHandler;
-using DOL.GS.ServerProperties;
using DOL.GS.Styles;
-using DOL.Language;
namespace DOL.GS
{
public class StyleComponent
{
- private GameLiving _owner;
+ public GameLiving Owner { get; }
- public bool AwaitingBackupInput = false;
+ public virtual bool CancelStyle
+ {
+ get => false;
+ set { }
+ }
+
+ public virtual bool AwaitingBackupInput
+ {
+ get => false;
+ set { }
+ }
+
+ public virtual Style AutomaticBackupStyle
+ {
+ get => null;
+ set { }
+ }
+
+ protected StyleComponent(GameLiving owner)
+ {
+ Owner = owner;
+ }
- public StyleComponent(GameLiving owner)
+ public static StyleComponent Create(GameLiving owner)
{
- _owner = owner;
+ if (owner is GameNPC npcOwner)
+ return new NpcStyleComponent(npcOwner);
+ else if (owner is GamePlayer playerOwner)
+ return new PlayerStyleComponent(playerOwner);
+ else
+ return new StyleComponent(owner);
}
///
@@ -44,45 +67,35 @@ public IList GetStyleList()
return list;
}
- ///
- /// Holds the style that this living should use next
- ///
- protected Style m_nextCombatStyle;
- ///
- /// Holds the backup style for the style that the living should use next
- ///
- protected Style m_nextCombatBackupStyle;
- ///
- /// Holds the time at which the style was set
- ///
- protected long m_nextCombatStyleTime;
-
- //if automatic backup styles are enabled, this is the one that will be used
- public Style AutomaticBackupStyle { get; set; }
+ protected Style _nextCombatStyle;
+ protected Style _nextCombatBackupStyle;
+ protected long _nextCombatStyleTime;
///
/// Gets or Sets the next combat style to use
///
public Style NextCombatStyle
{
- get { return m_nextCombatStyle; }
- set { m_nextCombatStyle = value; }
+ get { return _nextCombatStyle; }
+ set { _nextCombatStyle = value; }
}
+
///
/// Gets or Sets the next combat backup style to use
///
public Style NextCombatBackupStyle
{
- get { return m_nextCombatBackupStyle; }
- set { m_nextCombatBackupStyle = value; }
+ get { return _nextCombatBackupStyle; }
+ set { _nextCombatBackupStyle = value; }
}
+
///
/// Gets or Sets the time at which the style was set
///
public long NextCombatStyleTime
{
- get { return m_nextCombatStyleTime; }
- set { m_nextCombatStyleTime = value; }
+ get { return _nextCombatStyleTime; }
+ set { _nextCombatStyleTime = value; }
}
///
@@ -90,137 +103,23 @@ public long NextCombatStyleTime
///
protected bool m_cancelStyle;
- ///
- /// Gets or Sets the cancel style flag
- /// (delegate to PlayerCharacter)
- ///
- public bool CancelStyle
- {
- get => _owner is GamePlayer player && player.DBCharacter != null && player.DBCharacter.CancelStyle;
- set
- {
- if (_owner is GamePlayer player && player.DBCharacter != null)
- player.DBCharacter.CancelStyle = value;
- }
- }
-
public void ExecuteWeaponStyle(Style style)
{
- StyleProcessor.TryToUseStyle(_owner, style);
+ StyleProcessor.TryToUseStyle(Owner, style);
}
///
/// Decides which style living will use in this moment
///
/// Style to use or null if none
- public Style GetStyleToUse()
+ public virtual Style GetStyleToUse()
{
- if (NextCombatStyle == null)
- return null;
-
- AttackData lastAttackData = _owner.attackComponent.attackAction.LastAttackData;
- DbInventoryItem weapon = NextCombatStyle.WeaponTypeRequirement == (int) eObjectType.Shield ? _owner.Inventory.GetItem(eInventorySlot.LeftHandWeapon) : _owner.ActiveWeapon;
-
- //if they've cached a style and then respecced to no longer have access, remove it
- if (AutomaticBackupStyle != null && _owner is GamePlayer player && player.GetBaseSpecLevel(AutomaticBackupStyle.Spec) < AutomaticBackupStyle.SpecLevelRequirement)
- {
- player.Out.SendMessage($"{AutomaticBackupStyle.Name} is no longer a valid backup style for your spec level and has been cleared.", eChatType.CT_System, eChatLoc.CL_SystemWindow);
- AutomaticBackupStyle = null;
- }
-
- //determine which style will actually be used
- Style styleToUse;
-
- if (StyleProcessor.CanUseStyle(lastAttackData, _owner, NextCombatStyle, weapon))
- styleToUse = NextCombatStyle;
- else if (NextCombatBackupStyle != null)
- styleToUse = NextCombatBackupStyle;
- else if (AutomaticBackupStyle != null)
- {
- StyleProcessor.TryToUseStyle(_owner, AutomaticBackupStyle);
- styleToUse = NextCombatBackupStyle; // `NextCombatBackupStyle` became `AutomaticBackupStyle` if `TryToUse` succeeded.
- }
- else
- styleToUse = NextCombatStyle; // Not sure why.
-
- return styleToUse;
- }
-
- ///
- /// Picks a style, prioritizing reactives and chains over positionals and anytimes
- ///
- /// Selected style
- public Style NPCGetStyleToUse()
- {
- var p = _owner as GameNPC;
- if (p.Styles == null || p.Styles.Count < 1 || p.TargetObject == null)
- return null;
-
- AttackData lastAttackData = p.attackComponent.attackAction.LastAttackData;
-
- // Chain and defensive styles are excluded from the chance roll because they would almost never happen otherwise.
- // For example, an NPC blocks 10% of the time, so the default 20% style chance effectively means the defensive
- // style would only actually occur during 2% of of a mob's attacks. In comparison, a style chain would only happen
- // 0.4% of the time.
- if (p.StylesChain != null && p.StylesChain.Count > 0)
- foreach (Style s in p.StylesChain)
- if (StyleProcessor.CanUseStyle(lastAttackData, p, s, p.ActiveWeapon))
- return s;
-
- if (p.StylesDefensive != null && p.StylesDefensive.Count > 0)
- foreach (Style s in p.StylesDefensive)
- if (StyleProcessor.CanUseStyle(lastAttackData, p, s, p.ActiveWeapon)
- && p.CheckStyleStun(s)) // Make sure we don't spam stun styles like Brutalize
- return s;
-
- if (Util.Chance(Properties.GAMENPC_CHANCES_TO_STYLE))
- {
- // All of the remaining lists are randomly picked from,
- // as this creates more variety with each combat result.
- // For example, a mob with both Pincer and Ice Storm
- // styles could potentially use one or the other with
- // each attack roll that succeeds.
-
- // First, check positional styles (in order of back, side, front)
- // in case the defender is facing another direction
- if (p.StylesBack != null && p.StylesBack.Count > 0)
- {
- Style s = p.StylesBack[Util.Random(0, p.StylesBack.Count - 1)];
- if (StyleProcessor.CanUseStyle(lastAttackData, p, s, p.ActiveWeapon))
- return s;
- }
-
- if (p.StylesSide != null && p.StylesSide.Count > 0)
- {
- Style s = p.StylesSide[Util.Random(0, p.StylesSide.Count - 1)];
- if (StyleProcessor.CanUseStyle(lastAttackData, p, s, p.ActiveWeapon))
- return s;
- }
-
- if (p.StylesFront != null && p.StylesFront.Count > 0)
- {
- Style s = p.StylesFront[Util.Random(0, p.StylesFront.Count - 1)];
- if (StyleProcessor.CanUseStyle(lastAttackData, p, s, p.ActiveWeapon))
- return s;
- }
-
- // Pick a random anytime style
- if (p.StylesAnytime != null && p.StylesAnytime.Count > 0)
- return p.StylesAnytime[Util.Random(0, p.StylesAnytime.Count - 1)];
- }
-
- return null;
+ throw new NotImplementedException();
}
- ///
- /// Delve a weapon style for this player
- ///
- ///
- ///
- ///
- public void DelveWeaponStyle(List delveInfo, Style style)
+ public virtual void DelveWeaponStyle(List delveInfo, Style style)
{
- StyleProcessor.DelveWeaponStyle(delveInfo, style, _owner as GamePlayer);
+ throw new NotImplementedException();
}
public void RemoveAllStyles()
@@ -231,92 +130,9 @@ public void RemoveAllStyles()
}
}
- public void AddStyle(Style style, bool notify)
+ public virtual void AddStyle(Style style, bool notify)
{
- var p = _owner as GamePlayer;
-
- lock (lockStyleList)
- {
- if (m_styles.TryGetValue(style.ID, out Style existingStyle))
- {
- existingStyle.Level = style.Level;
- return;
- }
-
- m_styles.Add(style.ID, style);
-
- if (!notify)
- return;
-
-
- p.Out.SendMessage(LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.YouLearn", style.Name), eChatType.CT_System, eChatLoc.CL_SystemWindow);
-
- string message = null;
-
- if (Style.eOpening.Offensive == style.OpeningRequirementType)
- {
- switch (style.AttackResultRequirement)
- {
- case Style.eAttackResultRequirement.Style:
- case Style.eAttackResultRequirement.Hit: // TODO: make own message for hit after styles DB is updated
-
- Style reqStyle = SkillBase.GetStyleByID(style.OpeningRequirementValue, p.CharacterClass.ID);
-
- if (reqStyle == null)
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterStyle", "(style " + style.OpeningRequirementValue + " not found)");
-
- else
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterStyle", reqStyle.Name);
-
- break;
- case Style.eAttackResultRequirement.Miss:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterMissed");
- break;
- case Style.eAttackResultRequirement.Parry:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterParried");
- break;
- case Style.eAttackResultRequirement.Block:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterBlocked");
- break;
- case Style.eAttackResultRequirement.Evade:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterEvaded");
- break;
- case Style.eAttackResultRequirement.Fumble:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.AfterFumbles");
- break;
- }
- }
- else if (Style.eOpening.Defensive == style.OpeningRequirementType)
- {
- switch (style.AttackResultRequirement)
- {
- case Style.eAttackResultRequirement.Miss:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetMisses");
- break;
- case Style.eAttackResultRequirement.Hit:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetHits");
- break;
- case Style.eAttackResultRequirement.Parry:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetParried");
- break;
- case Style.eAttackResultRequirement.Block:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetBlocked");
- break;
- case Style.eAttackResultRequirement.Evade:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetEvaded");
- break;
- case Style.eAttackResultRequirement.Fumble:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetFumbles");
- break;
- case Style.eAttackResultRequirement.Style:
- message = LanguageMgr.GetTranslation(p.Client.Account.Language, "GamePlayer.RefreshSpec.TargetStyle");
- break;
- }
- }
-
- if (!string.IsNullOrEmpty(message))
- p.Out.SendMessage(message, eChatType.CT_System, eChatLoc.CL_SystemWindow);
- }
+ throw new NotImplementedException();
}
}
}
diff --git a/GameServer/gameobjects/GameLiving.cs b/GameServer/gameobjects/GameLiving.cs
index faed6c089a..a2817736a8 100644
--- a/GameServer/gameobjects/GameLiving.cs
+++ b/GameServer/gameobjects/GameLiving.cs
@@ -4010,7 +4010,7 @@ public GameLiving() : base()
{
attackComponent = new AttackComponent(this);
rangeAttackComponent = new RangeAttackComponent(this);
- styleComponent = new StyleComponent(this);
+ styleComponent = StyleComponent.Create(this);
castingComponent = CastingComponent.Create(this);
effectListComponent = new EffectListComponent(this);
movementComponent = MovementComponent.Create(this);
diff --git a/GameServer/gameobjects/GameNPC.cs b/GameServer/gameobjects/GameNPC.cs
index a95b5c5534..c46105d23b 100644
--- a/GameServer/gameobjects/GameNPC.cs
+++ b/GameServer/gameobjects/GameNPC.cs
@@ -4368,6 +4368,7 @@ protected virtual void BroadcastLoot(ArrayList droplist)
public override eGender Gender { get; set; }
public new NpcMovementComponent movementComponent;
+ public new NpcStyleComponent styleComponent;
public GameNPC Copy()
{
@@ -4489,8 +4490,8 @@ public GameNPC Copy(GameNPC copyTarget)
public GameNPC(ABrain defaultBrain) : base()
{
- if (movementComponent == null)
- movementComponent = base.movementComponent as NpcMovementComponent;
+ movementComponent ??= base.movementComponent as NpcMovementComponent;
+ styleComponent ??= base.styleComponent as NpcStyleComponent;
Level = 1;
m_health = MaxHealth;
diff --git a/GameServer/gameobjects/GamePlayer.cs b/GameServer/gameobjects/GamePlayer.cs
index b6c4e9bd45..a07b3165a1 100644
--- a/GameServer/gameobjects/GamePlayer.cs
+++ b/GameServer/gameobjects/GamePlayer.cs
@@ -42,6 +42,7 @@ public class GamePlayer : GameLiving
private readonly object m_LockObject = new();
public new PlayerMovementComponent movementComponent;
+ public new PlayerStyleComponent styleComponent;
public override eGameObjectType GameObjectType => eGameObjectType.PLAYER;
public ChainedActions ChainedActions { get; }
@@ -11417,6 +11418,8 @@ public override void LoadFromDatabase(DataObject obj)
// check the account for the Muted flag
if (Client.Account.IsMuted)
IsMuted = true;
+
+ styleComponent.OnPlayerLoadFromDatabase();
}
///
@@ -11510,6 +11513,7 @@ public override void SaveIntoDatabase()
DBCharacter.Direction = loc.Heading;
}
}
+ styleComponent.OnPlayerSaveIntoDatabase();
GameServer.Database.SaveObject(DBCharacter);
Inventory.SaveIntoDatabase(InternalID);
@@ -14542,8 +14546,8 @@ private GamePlayer(ICharacterClass charClass) : base()
/// The character for this player
public GamePlayer(GameClient client, DbCoreCharacter dbChar) : base()
{
- if (movementComponent == null)
- movementComponent = base.movementComponent as PlayerMovementComponent;
+ movementComponent ??= base.movementComponent as PlayerMovementComponent;
+ styleComponent ??= base.styleComponent as PlayerStyleComponent;
IsJumping = false;
m_steed = new WeakRef(null);