diff --git a/Assets/AffixIcons/AggravatorAffix.png b/Assets/AffixIcons/AggravatorAffix.png new file mode 100644 index 00000000..12d73295 Binary files /dev/null and b/Assets/AffixIcons/AggravatorAffix.png differ diff --git a/Assets/AffixIcons/DamageAffix.png b/Assets/AffixIcons/DamageAffix.png new file mode 100644 index 00000000..5bc6cfe6 Binary files /dev/null and b/Assets/AffixIcons/DamageAffix.png differ diff --git a/Assets/AffixIcons/DoubleLife.png b/Assets/AffixIcons/DoubleLife.png new file mode 100644 index 00000000..e7e3eff7 Binary files /dev/null and b/Assets/AffixIcons/DoubleLife.png differ diff --git a/Assets/AffixIcons/FastAffix.png b/Assets/AffixIcons/FastAffix.png new file mode 100644 index 00000000..f7dd24c9 Binary files /dev/null and b/Assets/AffixIcons/FastAffix.png differ diff --git a/Assets/AffixIcons/SiphonerAffix.png b/Assets/AffixIcons/SiphonerAffix.png new file mode 100644 index 00000000..f3664b1d Binary files /dev/null and b/Assets/AffixIcons/SiphonerAffix.png differ diff --git a/Common/Buffs/ChilledFunctionality.cs b/Common/Buffs/ChilledFunctionality.cs index 4a1c1690..7fc60fab 100644 --- a/Common/Buffs/ChilledFunctionality.cs +++ b/Common/Buffs/ChilledFunctionality.cs @@ -9,7 +9,7 @@ public override void Update(int type, NPC npc, ref int buffIndex) { if (type == BuffID.Chilled) { - npc.GetGlobalNPC().SlowDown += 0.2f; + npc.GetGlobalNPC().SpeedModifier += 0.2f; if (Main.rand.NextBool(20)) { diff --git a/Common/Systems/Affixes/Affix.cs b/Common/Systems/Affixes/Affix.cs index f0e7e44a..345e62bc 100644 --- a/Common/Systems/Affixes/Affix.cs +++ b/Common/Systems/Affixes/Affix.cs @@ -273,6 +273,8 @@ public void Load(Mod mod) continue; case MobAffix mobAffix: _mobAffixes.Add(mobAffix); + + MobAffix.MobAffixIconsByAffixName[mobAffix.GetType().AssemblyQualifiedName] = ModContent.Request(mobAffix.TexturePath); break; } } diff --git a/Common/Systems/Affixes/MobAffix.cs b/Common/Systems/Affixes/MobAffix.cs index a2ae3aee..810bfc00 100644 --- a/Common/Systems/Affixes/MobAffix.cs +++ b/Common/Systems/Affixes/MobAffix.cs @@ -1,13 +1,30 @@ using PathOfTerraria.Common.Enums; +using ReLogic.Content; +using System.Collections.Generic; +using Terraria.Localization; using Terraria.ModLoader.IO; namespace PathOfTerraria.Common.Systems.Affixes; internal abstract class MobAffix : Affix { + public static readonly Dictionary> MobAffixIconsByAffixName = []; + + public Asset Icon => MobAffixIconsByAffixName[GetType().AssemblyQualifiedName]; + public virtual ItemRarity MinimumRarity => ItemRarity.Magic; public virtual bool Allowed => true; - + + /// + /// Texture path that points to the icon that shows over an NPC. + /// + public virtual string TexturePath => Mod.Name + "/Assets/AffixIcons/" + GetType().Name; + + /// + /// Text for the affix's prefix, such as the Strong in "Strong Blue Slime". + /// + public virtual LocalizedText Prefix => this.GetLocalization("Prefix"); + // would prefer ProgressionLock, but then you'd have to write !Main.moonlordDowned // but its mainly for progression public virtual float DropQuantityFlat => 0; @@ -16,24 +33,30 @@ internal abstract class MobAffix : Affix public virtual float DropRarityMultiplier => 1f; /// - /// after the rarity buff has been applied + /// Runs after the rarity buff has been applied. /// public virtual void PostRarity(NPC npc) { } /// - /// before the rarity buff has been applied + /// Runs before the rarity buff has been applied. /// public virtual void PreRarity(NPC npc) { } - public virtual bool PreAi(NPC npc) { return true; } - public virtual void Ai(NPC npc) { } - public virtual void PostAi(NPC npc) { } + public virtual bool PreAI(NPC npc) { return true; } + public virtual void AI(NPC npc) { } + public virtual void PostAI(NPC npc) { } public virtual bool PreDraw(NPC npc, SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor) { return true; } public virtual void OnKill(NPC npc) { } public virtual bool PreKill(NPC npc) { return true; } + internal override void CreateLocalization() + { + // Populate prefix, don't store + this.GetLocalization("Prefix"); + } + /// - /// Generates an affix from a tag, used on load to re-populate affixes + /// Generates an affix from a tag, used on load to re-populate affixes. /// /// /// diff --git a/Common/Systems/Affixes/MobAffixIconDrawing.cs b/Common/Systems/Affixes/MobAffixIconDrawing.cs new file mode 100644 index 00000000..38b792b3 --- /dev/null +++ b/Common/Systems/Affixes/MobAffixIconDrawing.cs @@ -0,0 +1,35 @@ +using PathOfTerraria.Common.Systems.MobSystem; +using System.Collections.Generic; + +namespace PathOfTerraria.Common.Systems.Affixes; + +internal class MobAffixIconDrawing : GlobalNPC +{ + public override bool InstancePerEntity => true; + + private float _hoverStrength = 0; + + public override void PostDraw(NPC npc, SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor) + { + List affixes = npc.GetGlobalNPC().Affixes; + + if (affixes.Count == 0) + { + return; + } + + _hoverStrength = MathHelper.Lerp(_hoverStrength, npc.Hitbox.Contains(Main.MouseWorld.ToPoint()) ? 1 : 0, 0.1f); + + float offset = affixes.Count / 2f - 0.5f; + float scale = MathHelper.Lerp(1f, 1.5f, _hoverStrength); + float opacity = MathHelper.Lerp(0.6f, 1f, _hoverStrength); + + for (int i = 0; i < affixes.Count; i++) + { + MobAffix affix = affixes[i]; + + Vector2 position = npc.Center - screenPos + new Vector2((i * 20 - offset * 20) * scale, -npc.height / 2 - 12); + spriteBatch.Draw(affix.Icon.Value, position, null, drawColor * opacity, 0f, affix.Icon.Size() / 2f, scale, SpriteEffects.None, 0); + } + } +} diff --git a/Common/Systems/Affixes/MobTypes/AggravatorAffix.cs b/Common/Systems/Affixes/MobTypes/AggravatorAffix.cs new file mode 100644 index 00000000..ab835ef8 --- /dev/null +++ b/Common/Systems/Affixes/MobTypes/AggravatorAffix.cs @@ -0,0 +1,16 @@ + +namespace PathOfTerraria.Common.Systems.Affixes.MobTypes; + +internal class AggravatorAffix : MobAffix +{ + public override bool PreAI(NPC npc) + { + if (npc.life < npc.lifeMax / 2) + { + npc.GetGlobalNPC().ExtraAISpeed += 0.3f; + npc.defense = npc.defDefense + 10; + } + + return true; + } +} \ No newline at end of file diff --git a/Common/Systems/Affixes/MobTypes/DamageAffix.cs b/Common/Systems/Affixes/MobTypes/DamageAffix.cs new file mode 100644 index 00000000..5e6cc005 --- /dev/null +++ b/Common/Systems/Affixes/MobTypes/DamageAffix.cs @@ -0,0 +1,9 @@ +namespace PathOfTerraria.Common.Systems.Affixes.MobTypes; + +internal class DamageAffix : MobAffix +{ + public override void PostRarity(NPC npc) + { + npc.damage = (int)(npc.damage * 1.5f); + } +} \ No newline at end of file diff --git a/Common/Systems/Affixes/MobTypes/FastAffix.cs b/Common/Systems/Affixes/MobTypes/FastAffix.cs new file mode 100644 index 00000000..32483f8d --- /dev/null +++ b/Common/Systems/Affixes/MobTypes/FastAffix.cs @@ -0,0 +1,11 @@ + +namespace PathOfTerraria.Common.Systems.Affixes.MobTypes; + +internal class FastAffix : MobAffix +{ + public override bool PreAI(NPC npc) + { + npc.GetGlobalNPC().ExtraAISpeed += 0.3f; + return true; + } +} \ No newline at end of file diff --git a/Common/Systems/Affixes/MobTypes/LifeAffixes.cs b/Common/Systems/Affixes/MobTypes/LifeAffixes.cs index b10a4821..e85cb911 100644 --- a/Common/Systems/Affixes/MobTypes/LifeAffixes.cs +++ b/Common/Systems/Affixes/MobTypes/LifeAffixes.cs @@ -1,13 +1,10 @@ namespace PathOfTerraria.Common.Systems.Affixes.MobTypes; -internal class LifeAffixes +internal class DoubleLife : MobAffix { - internal class DoubleLife : MobAffix + public override void PostRarity(NPC npc) { - public override void PostRarity(NPC npc) - { - npc.lifeMax *= 2; - npc.life = npc.lifeMax; - } + npc.lifeMax *= 2; + npc.life = npc.lifeMax; } } \ No newline at end of file diff --git a/Common/Systems/Affixes/MobTypes/SiphonerAffix.cs b/Common/Systems/Affixes/MobTypes/SiphonerAffix.cs new file mode 100644 index 00000000..4dc39b42 --- /dev/null +++ b/Common/Systems/Affixes/MobTypes/SiphonerAffix.cs @@ -0,0 +1,20 @@ +using PathOfTerraria.Common.Enums; +using PathOfTerraria.Common.Systems.MobSystem; + +namespace PathOfTerraria.Common.Systems.Affixes.MobTypes; + +internal class SiphonerAffix : MobAffix +{ + public override ItemRarity MinimumRarity => ItemRarity.Rare; + + public class SiphonerNPC : GlobalNPC + { + public override void OnHitPlayer(NPC npc, Player target, Player.HurtInfo hurtInfo) + { + if (npc.GetGlobalNPC().HasAffix()) + { + target.CheckMana(hurtInfo.Damage / 2, true); + } + } + } +} \ No newline at end of file diff --git a/Common/Systems/MobSystem/MobAPRGSystem.cs b/Common/Systems/MobSystem/ArpgNPC.cs similarity index 80% rename from Common/Systems/MobSystem/MobAPRGSystem.cs rename to Common/Systems/MobSystem/ArpgNPC.cs index 153de025..a42259dc 100644 --- a/Common/Systems/MobSystem/MobAPRGSystem.cs +++ b/Common/Systems/MobSystem/ArpgNPC.cs @@ -9,23 +9,22 @@ using PathOfTerraria.Common.Systems.ModPlayers; using PathOfTerraria.Core.Items; using SubworldLibrary; -using Terraria; using Terraria.ID; using Terraria.Localization; using Terraria.ModLoader.IO; namespace PathOfTerraria.Common.Systems.MobSystem; -internal class MobAprgSystem : GlobalNPC +internal class ArpgNPC : GlobalNPC { public override bool InstancePerEntity => true; public int? Experience; public ItemRarity Rarity = ItemRarity.Normal; + public List Affixes = []; private readonly Player _lastPlayerHit = null; - private List _affixes = []; private bool _synced = false; // should somehow work together with magic find (that i assume we will have) to increase rarity / if its a unique @@ -34,8 +33,8 @@ private float DropRarity get { float dropRarity = 0; - _affixes.ForEach(a => dropRarity += a.DropRarityFlat); - _affixes.ForEach(a => dropRarity *= a.DropRarityMultiplier); + Affixes.ForEach(a => dropRarity += a.DropRarityFlat); + Affixes.ForEach(a => dropRarity *= a.DropRarityMultiplier); return dropRarity; // rounds down iirc } } @@ -54,8 +53,8 @@ private float DropRarity get { float dropQuantity = 1; - _affixes.ForEach(a => dropQuantity += a.DropQuantityFlat); - _affixes.ForEach(a => dropQuantity *= a.DropQuantityMultiplier); + Affixes.ForEach(a => dropQuantity += a.DropQuantityFlat); + Affixes.ForEach(a => dropQuantity *= a.DropQuantityMultiplier); return dropQuantity; } } @@ -63,30 +62,30 @@ private float DropRarity public override bool PreAI(NPC npc) { bool doRunNormalAi = true; - _affixes.ForEach(a => doRunNormalAi = doRunNormalAi && a.PreAi(npc)); + Affixes.ForEach(a => doRunNormalAi = doRunNormalAi && a.PreAI(npc)); return doRunNormalAi; } public override void AI(NPC npc) { - _affixes.ForEach(a => a.Ai(npc)); + Affixes.ForEach(a => a.AI(npc)); } public override void PostAI(NPC npc) { - _affixes.ForEach(a => a.PostAi(npc)); + Affixes.ForEach(a => a.PostAI(npc)); } public override bool PreDraw(NPC npc, SpriteBatch spriteBatch, Vector2 screenPos, Color drawColor) { bool doDraw = true; - _affixes.ForEach(a => doDraw = doDraw && a.PreDraw(npc, spriteBatch, screenPos, drawColor)); + Affixes.ForEach(a => doDraw = doDraw && a.PreDraw(npc, spriteBatch, screenPos, drawColor)); return doDraw; } public override void OnKill(NPC npc) { - _affixes.ForEach(a => a.OnKill(npc)); + Affixes.ForEach(a => a.OnKill(npc)); if (npc.lifeMax <= 5 || npc.SpawnedFromStatue || npc.boss) { @@ -124,11 +123,11 @@ public override void OnKill(NPC npc) public override bool PreKill(NPC npc) { bool doKill = true; - _affixes.ForEach(a => doKill = doKill && a.PreKill(npc)); + Affixes.ForEach(a => doKill = doKill && a.PreKill(npc)); return doKill; } - public override void SetDefaultsFromNetId(NPC npc) + public override void SetDefaults(NPC npc) { //We only want to trigger these changes on hostile non-boss, non Eater of Worlds mobs in-game if (npc.friendly || npc.boss || Main.gameMenu || npc.type is NPCID.EaterofWorldsBody or NPCID.EaterofWorldsHead or NPCID.EaterofWorldsTail) @@ -161,18 +160,14 @@ public override void SetDefaultsFromNetId(NPC npc) } } - public void ApplyRarity(NPC npc) + public override void SetDefaultsFromNetId(NPC npc) { - // npc.TypeName uses only the netID, which is not set by SetDefaults...for some reason. - // This works the same, just using type if netID isn't helpful. - string typeName = NPCLoader.ModifyTypeName(npc, Lang.GetNPCNameValue(npc.netID == 0 ? npc.type : npc.netID)); + SetName(npc); + } - npc.GivenName = Rarity switch - { - ItemRarity.Magic or ItemRarity.Rare => $"{Language.GetTextValue($"Mods.{PoTMod.ModName}.Misc.RarityNames." + Enum.GetName(Rarity))} {typeName}", - ItemRarity.Unique => "UNIQUE MOB", - _ => typeName - }; + public void ApplyRarity(NPC npc) + { + string typeName = SetName(npc); if (MobRegistry.TryGetMobData(npc.type, out MobData mobData)) { @@ -202,14 +197,14 @@ public void ApplyRarity(NPC npc) } List possible = AffixHandler.GetAffixes(Rarity); - _affixes = Rarity switch + + Affixes = Rarity switch { - ItemRarity.Magic => Affix.GenerateAffixes(possible, PoTItemHelper.GetAffixCount(Rarity)), - ItemRarity.Rare => Affix.GenerateAffixes(possible, PoTItemHelper.GetAffixCount(Rarity)), + ItemRarity.Magic or ItemRarity.Rare => Affix.GenerateAffixes(possible, PoTItemHelper.GetMaxMobAffixCounts(Rarity)), _ => [] }; - _affixes.ForEach(a => a.PreRarity(npc)); + Affixes.ForEach(a => a.PreRarity(npc)); switch (Rarity) { @@ -233,10 +228,43 @@ public void ApplyRarity(NPC npc) throw new InvalidOperationException("Invalid rarity!"); } - _affixes.ForEach(a => a.PostRarity(npc)); + SetName(npc); + + Affixes.ForEach(a => a.PostRarity(npc)); _synced = true; } + private string SetName(NPC npc) + { + // npc.TypeName uses only the netID, which is not set by SetDefaults...for some reason. + // This works the same, just using type if netID isn't helpful. + string typeName = NPCLoader.ModifyTypeName(npc, Lang.GetNPCNameValue(npc.netID == 0 ? npc.type : npc.netID)); + + npc.GivenName = Rarity switch + { + ItemRarity.Magic or ItemRarity.Rare => $"{Language.GetTextValue($"Mods.{PoTMod.ModName}.Misc.RarityNames." + Enum.GetName(Rarity))} {typeName}", + ItemRarity.Unique => "UNIQUE MOB", + _ => typeName + }; + + npc.GivenName += "\n" + GetAffixPrefixes(npc); + + return typeName; + } + + private static string GetAffixPrefixes(NPC npc) + { + List affixes = npc.GetGlobalNPC().Affixes; + string prefix = ""; + + foreach (MobAffix affix in affixes) + { + prefix += affix.Prefix + " - "; + } + + return prefix; + } + public override void SendExtraAI(NPC npc, BitWriter bitWriter, BinaryWriter binaryWriter) { binaryWriter.Write((byte)Rarity); @@ -259,4 +287,17 @@ public override void ReceiveExtraAI(NPC npc, BitReader bitReader, BinaryReader b ApplyRarity(npc); } } + + public bool HasAffix() where T : MobAffix + { + foreach (MobAffix affix in Affixes) + { + if (affix.GetType() == typeof(T)) + { + return true; + } + } + + return false; + } } diff --git a/Common/Systems/MobSystem/MobExperienceGlobalNPC.cs b/Common/Systems/MobSystem/MobExperienceGlobalNPC.cs index 64b3f8f2..44039498 100644 --- a/Common/Systems/MobSystem/MobExperienceGlobalNPC.cs +++ b/Common/Systems/MobSystem/MobExperienceGlobalNPC.cs @@ -16,7 +16,7 @@ public override void OnKill(NPC npc) return; } - MobAprgSystem npcSystem = npc.GetGlobalNPC(); + ArpgNPC npcSystem = npc.GetGlobalNPC(); if (npcSystem is null) { diff --git a/Common/Systems/SlowDownNPC.cs b/Common/Systems/SlowDownNPC.cs index 5e707c10..9b11a7e2 100644 --- a/Common/Systems/SlowDownNPC.cs +++ b/Common/Systems/SlowDownNPC.cs @@ -1,19 +1,25 @@ namespace PathOfTerraria.Common.Systems; +/// +/// Used to make NPCs move slower by using the value.
+/// Does not modify behaviour speed; as such, enemies like the Eye of Cthulhu which do an action for a given amount of time will not be modified,
+/// but their speed in doing said action (like charging) will be reduced. Functionally, this means things like charges will be much shorter.
+/// If you want to speed up an NPC, use instead. These can be used in conjunction without issue. +///
internal class SlowDownNPC : GlobalNPC { public override bool InstancePerEntity => true; - public float SlowDown = 0; + public float SpeedModifier = 0; public override void ResetEffects(NPC npc) { - SlowDown = 0; + SpeedModifier = 0; } public override void AI(NPC npc) { - SlowDown = Math.Clamp(SlowDown, 0, 1); - npc.position -= npc.velocity * SlowDown; + SpeedModifier = Math.Clamp(SpeedModifier, 0, 1); + npc.position -= npc.velocity * SpeedModifier; } } diff --git a/Common/Systems/SpeedUpNPC.cs b/Common/Systems/SpeedUpNPC.cs new file mode 100644 index 00000000..183ddbb0 --- /dev/null +++ b/Common/Systems/SpeedUpNPC.cs @@ -0,0 +1,49 @@ +namespace PathOfTerraria.Common.Systems; + +/// +/// Used to make NPCs behave faster by using the value.
+/// This necessarily includes both behaviour speed (how fast the NPC does an action) and movement speed, which are inexorably tied.
+/// If you want to slow down an NPC, use instead. These can be used in conjunction without issue. +///
+public class SpeedUpNPC : GlobalNPC +{ + public override bool InstancePerEntity => true; + + public float ExtraAISpeed = 0f; + + private float _extraAITimer = 0; + private bool _boosting = false; + + public override void ResetEffects(NPC npc) + { + _boosting = false; + ExtraAISpeed = 0; + } + + public override void PostAI(NPC npc) + { + if (_boosting) + { + return; + } + + if (ExtraAISpeed == 0) + { + _extraAITimer = 0; + return; + } + + _extraAITimer += ExtraAISpeed; + Vector2 oldPosition = npc.position; + + while (_extraAITimer >= 1f) + { + _boosting = true; + npc.AI(); + _boosting = false; + _extraAITimer--; + } + + npc.position = oldPosition; + } +} diff --git a/Content/Buffs/RootedDebuff.cs b/Content/Buffs/RootedDebuff.cs index ca09ae76..3f2fb488 100644 --- a/Content/Buffs/RootedDebuff.cs +++ b/Content/Buffs/RootedDebuff.cs @@ -30,7 +30,7 @@ public override void SetStaticDefaults() public override void Update(NPC npc, ref int buffIndex) { - npc.GetGlobalNPC().SlowDown = 1; + npc.GetGlobalNPC().SpeedModifier = 1; npc.GetGlobalNPC().RootedImmuneTime = 3 * 60; } diff --git a/Content/Passives/BuffPassives.cs b/Content/Passives/BuffPassives.cs index b337c491..d339bfcf 100644 --- a/Content/Passives/BuffPassives.cs +++ b/Content/Passives/BuffPassives.cs @@ -22,7 +22,7 @@ public override void Update(int type, NPC npc, ref int buffIndex) multiplier += chillPower * 0.1f; } - npc.GetGlobalNPC().SlowDown += 0.1f * multiplier; + npc.GetGlobalNPC().SpeedModifier += 0.1f * multiplier; } } } diff --git a/Core/Items/PoTItemHelper.cs b/Core/Items/PoTItemHelper.cs index 4f4a8a63..37d17bb2 100644 --- a/Core/Items/PoTItemHelper.cs +++ b/Core/Items/PoTItemHelper.cs @@ -187,6 +187,21 @@ public static int GetMaxAffixCounts(ItemRarity rarity) }; } + /// + /// Gets the max amount of affixes a mob can have based on the rarity. + /// + /// Rarity of the mob. + /// How many affixes the mob can have. + public static int GetMaxMobAffixCounts(ItemRarity rarity) + { + return rarity switch + { + ItemRarity.Magic => 2, + ItemRarity.Rare => 4, + _ => 0 + }; + } + public static bool HasMaxAffixesForRarity(Item item) { PoTInstanceItemData data = item.GetInstanceData(); diff --git a/Localization/en-US/Mods.PathOfTerraria.Affixes.hjson b/Localization/en-US/Mods.PathOfTerraria.Affixes.hjson index aceb8e4e..730c02f2 100644 --- a/Localization/en-US/Mods.PathOfTerraria.Affixes.hjson +++ b/Localization/en-US/Mods.PathOfTerraria.Affixes.hjson @@ -55,4 +55,9 @@ IncreaseBlockAffix.Description: "{1}{0}% increased block chance" StrengthItemAffix.Description: "{1}{0} strength" DexterityItemAffix.Description: "{1}{0} dexterity" IntelligenceItemAffix.Description: "{1}{0} intelligence" +DoubleLife.Prefix: Hearty +SiphonerAffix.Prefix: Siphoning +FastAffix.Prefix: Swift +AggravatorAffix.Prefix: Aggravating +DamageAffix.Prefix: Harmful ChanceToApplyRootedGearAffix.Description: "{1}{0}% chance to apply Rooted"