Even More Modifiers  1.0.0.0
A mod for rolling various bonus stats on items
/home/travis/build/Jofairden/EvenMoreModifiers/LootModItem.cs
Go to the documentation of this file.
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.Globalization;
5 using System.IO;
6 using System.Linq;
7 using Loot.Api.Core;
8 using Loot.Api.Ext;
9 using Loot.Api.Graphics;
10 using Loot.Hacks;
11 using Loot.IO;
12 using Terraria;
13 using Terraria.ModLoader;
14 using Terraria.ModLoader.IO;
15 
16 namespace Loot
17 {
21  public sealed class LootModItem : GlobalItem
22  {
23  private const int SAVE_VERSION = 14;
24 
25  public static LootModItem GetInfo(Item item) => item.GetGlobalItem<LootModItem>();
26  public static List<Modifier> GetActivePool(Item item) => GetInfo(item).Modifiers?.Modifiers ?? new List<Modifier>();
27 
28  public override bool InstancePerEntity => true;
29  public override bool CloneNewInstances => true;
30 
31  public ModifierRarity Rarity { get; internal set; } // the current rarity
32  public FiniteModifierPool Modifiers { get; internal set; } // the current pool of mods.
33 
34  public bool HasRolled { get; internal set; } // has rolled a pool
35  public bool SealedModifiers { get; internal set; } // are modifiers unchangeable
36 
37  // Non saved
38  public bool JustTinkerModified { get; internal set; } // is just tinker modified: e.g. armor hacked
39  public bool SlottedInUI { get; internal set; } // is currently in cube UI slot
40 
45  public bool IsActivated { get; internal set; }
46 
47  private void InvalidateRolls()
48  {
49  Rarity = mod.GetNullModifierRarity();
50  Modifiers = mod.GetNullModifierPool();
51  }
52 
57  //public ModifierPool RollNewPool(ModifierContext ctx, RollingStrategyProperties rollingStrategyProperties = null)
58  //{
59  // if (rollingStrategyProperties == null)
60  // {
61  // rollingStrategyProperties = new RollingStrategyProperties();
62  // }
63 
64  // HasRolled = true;
65  // bool noForce = true;
66 
67  // // Custom rarity provided
68  // if (rollingStrategyProperties.OverrideRollModifierRarity != null)
69  // {
70  // Rarity = rollingStrategyProperties.OverrideRollModifierRarity.Invoke();
71  // }
72  // else if (rollingStrategyProperties.ForceModifierRarity != null)
73  // {
74  // Rarity = rollingStrategyProperties.ForceModifierRarity;
75  // }
76  // else if (Rarity == null || Rarity.Type == 0)
77  // {
78  // Rarity = mod.GetModifierRarity<CommonRarity>();
79  // }
80 
81  // ctx.Rarity = Rarity;
82 
83  // // Upgrade rarity
84  // if (rollingStrategyProperties.CanUpgradeRarity(ctx)
85  // && Main.rand.NextFloat() <= (Rarity.UpgradeChance ?? 0f))
86  // {
87  // var newRarity = Rarity.Upgrade;
88  // var newFromLoader = ModUtils.GetModifierRarity(newRarity);
89  // if (newRarity != null && newFromLoader != null)
90  // {
91  // Rarity = newFromLoader;
92  // }
93  // }
94  // // Downgrade rarity
95  // else if (rollingStrategyProperties.CanDowngradeRarity(ctx)
96  // && Main.rand.NextFloat() <= (Rarity.DowngradeChance ?? 0f))
97  // {
98  // var newRarity = Rarity.Downgrade;
99  // var newFromLoader = ModUtils.GetModifierRarity(newRarity);
100  // if (newRarity != null && newFromLoader != null)
101  // {
102  // Rarity = newFromLoader;
103  // }
104  // }
105 
106  // ctx.Rarity = Rarity;
107 
108  // // Custom pool provided
109  // if (rollingStrategyProperties.OverrideRollModifierPool != null)
110  // {
111  // ModifierPool = rollingStrategyProperties.OverrideRollModifierPool.Invoke();
112  // noForce = !ModifierPool?._CanRoll(ctx) ?? true;
113  // }
114 
115  // // No behavior provided
116  // if (noForce)
117  // {
118  // // A pool is forced to roll
119  // if (rollingStrategyProperties.ForceModifierPool != null)
120  // {
121  // ModifierPool = ModUtils.GetModifierPool(rollingStrategyProperties.ForceModifierPool.GetType());
122  // noForce = !ModifierPool?._CanRoll(ctx) ?? true;
123  // }
124 
125  // // No pool forced to roll or it's not valid
126  // if (noForce)
127  // {
128  // // Try rolling a predefined (weighted) pool
129  // bool rollPredefinedPool = Main.rand.NextFloat() <= rollingStrategyProperties.RollPredefinedPoolChance;
130  // noForce = !rollPredefinedPool;
131 
132  // if (rollPredefinedPool)
133  // {
134  // // GetWeightedPool already checks _CanRoll
135  // ModifierPool = ContentLoader.ModifierPool.GetWeightedPool(ctx);
136  // noForce = ModifierPool == null || !ModifierPool._CanRoll(ctx);
137  // }
138 
139  // // Roll from all modifiers
140  // if (noForce)
141  // {
142  // ModifierPool = mod.GetAllModifiersPool();
143  // if (!ModifierPool._CanRoll(ctx))
144  // {
145  // InvalidateRolls();
146  // return null;
147  // }
148  // }
149  // }
150  // }
151 
152  // if (ctx.Strategy != null)
153  // {
154  // // Attempt rolling modifiers
155  // if (!ctx.Strategy.Roll(ctx, new RollingStrategyContext(ctx.Item, rollingStrategyProperties)))
156  // {
157  // InvalidateRolls();
158  // }
159  // }
160 
161  // ctx.Item.GetGlobalItem<GraphicsGlobalItem>().NeedsUpdate = true;
162  // return ModifierPool;
163  //}
164 
165  public override GlobalItem Clone(Item item, Item itemClone)
166  {
167  LootModItem clone = (LootModItem)base.Clone(item, itemClone);
168  clone.Rarity = (ModifierRarity)Rarity?.Clone() ?? mod.GetNullModifierRarity();
169  clone.Modifiers = (FiniteModifierPool)Modifiers?.Clone() ?? mod.GetNullModifierPool();
170  // there is no need to apply here, we already cloned the item which stats are already modified by its pool
171  return clone;
172  }
173 
174  public override void Load(Item item, TagCompound tag)
175  {
176  // enforce illegitimate rolls to go away (needed for earliest versions saves)
177  if (!item.IsModifierRollableItem())
178  {
179  InvalidateRolls();
180  }
181  else if (tag.ContainsKey("SaveVersion"))
182  {
183  int saveVersion = tag.GetInt("SaveVersion");
184  if (saveVersion < 14)
185  {
186  InvalidateRolls();
187  }
188  else
189  {
190  Rarity = ModifierRarityIO.Load(item, tag.GetCompound("ModifierRarity"));
191  Modifiers = ModifierPoolIO.Load(item, tag.GetCompound("ModifierPool"));
192  SealedModifiers = tag.GetBool("SealedModifiers");
193  HasRolled = tag.GetBool("HasRolled");
194  Modifiers.Apply(item);
196  }
197  }
198  }
199 
200  public override TagCompound Save(Item item)
201  {
202  TagCompound tag = new TagCompound
203  {
204  // SaveVersion saved since SaveVersion 2, version 1 not present
205  {"SaveVersion", SAVE_VERSION},
206  {"SealedModifiers", SealedModifiers},
207  {"HasRolled", HasRolled},
208  {"ModifierRarity", ModifierRarityIO.Save(item, Rarity)},
209  {"ModifierPool", ModifierPoolIO.Save(item, Modifiers)}
210  };
211 
212  return tag;
213  }
214 
215  public override bool NeedsSaving(Item item)
216  => Modifiers != null || HasRolled;
217 
218  public override void NetReceive(Item item, BinaryReader reader)
219  {
220  bool hasPool = reader.ReadBoolean();
221 
222  if (hasPool)
223  {
224  Rarity = ModifierRarityIO.NetReceive(item, reader);
225  Modifiers = ModifierPoolIO.NetReceive(item, reader);
226  }
227 
228  HasRolled = reader.ReadBoolean();
229  SealedModifiers = reader.ReadBoolean();
230  JustTinkerModified = reader.ReadBoolean();
231 
232  item.UpdateModifiers(Modifiers.Modifiers);
233  }
234 
235  public override void NetSend(Item item, BinaryWriter writer)
236  {
237  bool hasPool = Modifiers != null;
238  writer.Write(hasPool);
239 
240  if (hasPool)
241  {
242  ModifierRarityIO.NetSend(Rarity, item, writer);
243  ModifierPoolIO.NetSend(Modifiers, item, writer);
244  }
245 
246  writer.Write(HasRolled);
247  writer.Write(SealedModifiers);
248 
249  writer.Write(JustTinkerModified);
250  }
251 
252  //public override void OnCraft(Item item, Recipe recipe)
253  //{
254  // ModifierPool pool = GetInfo(item).ModifierPool;
255  // if (!HasRolled && pool == null)
256  // {
257  // ModifierContext ctx = new ModifierContext
258  // {
259  // Method = ModifierContextMethod.OnCraft,
260  // Item = item,
261  // Player = Main.LocalPlayer,
262  // Recipe = recipe
263  // };
264 
265  // pool = RollNewPool(ctx);
266  // pool?.ApplyModifiers(item);
267  // }
268 
269  // base.OnCraft(item, recipe);
270  //}
271 
272  //public override bool OnPickup(Item item, Player player)
273  //{
274  // ModifierPool pool = GetInfo(item).ModifierPool;
275  // if (!HasRolled && pool == null)
276  // {
277  // ModifierContext ctx = new ModifierContext
278  // {
279  // Method = ModifierContextMethod.OnPickup,
280  // Item = item,
281  // Player = player,
282  // Strategy = RollingUtils.Strategies.Normal
283  // };
284 
285  // pool = RollNewPool(ctx);
286  // pool?.ApplyModifiers(item);
287  // }
288 
289  // return base.OnPickup(item, player);
290  //}
291 
292  //public override void PostReforge(Item item)
293  //{
294  // if (!SealedModifiers)
295  // {
296  // ModifierContext ctx = new ModifierContext
297  // {
298  // Method = ModifierContextMethod.OnReforge,
299  // Item = item,
300  // Player = Main.LocalPlayer,
301  // Strategy = RollingUtils.Strategies.Normal
302  // };
303 
304  // ModifierPool pool = RollNewPool(ctx);
305  // pool?.ApplyModifiers(item);
306  // }
307  //}
308 
309  private string GetPrefixNormString(float cpStat, float rStat, ref double num, ref Color? color)
310  {
311  //float num19 = (float)Main.mouseTextColor / 255f;
312  //patch file: num20
313  float defColorVal = Main.mouseTextColor / 255f;
314  int alphaColor = Main.mouseTextColor;
315 
316  if (cpStat == 0f && rStat != 0f)
317  {
318  num = 1;
319  if (rStat > 0f)
320  {
321  color = new Color((byte)(120f * defColorVal), (byte)(190f * defColorVal), (byte)(120f * defColorVal), alphaColor);
322  return "+" + rStat.ToString(CultureInfo.InvariantCulture); /* + Lang.tip[39].Value;*/
323  }
324 
325  color = new Color((byte)(190f * defColorVal), (byte)(120f * defColorVal), (byte)(120f * defColorVal), alphaColor);
326  return rStat.ToString(CultureInfo.InvariantCulture); /* + Lang.tip[39].Value;*/
327  }
328 
329  double diffStat = rStat - cpStat;
330  diffStat = diffStat / cpStat * 100.0;
331  diffStat = Math.Round(diffStat);
332  num = diffStat;
333 
334  // for some reason - is handled automatically, but + is not
335  if (diffStat > 0.0)
336  {
337  color = new Color((byte)(120f * defColorVal), (byte)(190f * defColorVal), (byte)(120f * defColorVal), alphaColor);
338  return "+" + diffStat.ToString(CultureInfo.InvariantCulture); /* + Lang.tip[39].Value;*/
339  }
340 
341  color = new Color((byte)(190f * defColorVal), (byte)(120f * defColorVal), (byte)(120f * defColorVal), alphaColor);
342  return diffStat.ToString(CultureInfo.InvariantCulture); /* + Lang.tip[39].Value;*/
343  //if (num12 < 0.0)
344  //{
345  // array3[num4] = true;
346  //}
347  }
348 
352  public override void ModifyTooltips(Item item, List<TooltipLine> tooltips)
353  {
354  var pool = Modifiers;
355  if (pool != null)
356  {
357  // the following part, recalculate the vanilla prefix tooltips
358  // this is because our mods modify the stats, which was never intended by vanilla, causing the differences to be innacurate and bugged
359 
360  // RECALC START
361  var vanillaTooltips = tooltips.Where(x => x.mod.Equals("Terraria")).ToArray();
362  var baseItem = new Item();
363  baseItem.netDefaults(item.netID);
364 
365  // the item with just the modifiers applied
366  // var poolItem = baseItem.CloneWithModdedDataFrom(item);
367  // GetItemInfo(poolItem)?.ModifierPool.ApplyModifiers(poolItem);
368 
369  // the item with just the prefix applied
370  var prefixItem = baseItem.Clone();
371  prefixItem.Prefix(item.prefix);
372 
373  try
374  {
375  foreach (var tooltipLine in vanillaTooltips)
376  {
377  double outNumber = 0d;
378  string newTooltipLine = tooltipLine.text;
379  Color? newColor = tooltipLine.overrideColor;
380  string tooltipEndText =
381  new string(tooltipLine.text
382  .Reverse()
383  .TakeWhile(x => !char.IsDigit(x))
384  .Reverse()
385  .ToArray());
386 
387  //private string[] _prefixTooltipLines = {
388  // "PrefixDamage", "PrefixSpeed", "PrefixCritChance", "PrefixUseMana", "PrefixSize",
389  // "PrefixShootSpeed", "PrefixKnockback", "PrefixAccDefense", "PrefixAccMaxMana",
390  // "PrefixAccCritChance", "PrefixAccDamage", "PrefixAccMoveSpeed", "PrefixAccMeleeSpeed"
391  // };
392 
393  if (tooltipLine.Name.Equals("PrefixDamage"))
394  {
395  newTooltipLine = baseItem.damage > 0
396  ? GetPrefixNormString(baseItem.damage, prefixItem.damage, ref outNumber, ref newColor)
397  : GetPrefixNormString(prefixItem.damage, baseItem.damage, ref outNumber, ref newColor);
398  }
399  else if (tooltipLine.Name.Equals("PrefixSpeed"))
400  {
401  newTooltipLine = baseItem.useAnimation <= 0
402  ? GetPrefixNormString(baseItem.useAnimation, prefixItem.useAnimation, ref outNumber, ref newColor)
403  : GetPrefixNormString(prefixItem.useAnimation, baseItem.useAnimation, ref outNumber, ref newColor);
404  }
405  else if (tooltipLine.Name.Equals("PrefixCritChance"))
406  {
407  outNumber = prefixItem.crit - baseItem.crit;
408  float defColorVal = Main.mouseTextColor / 255f;
409  int alphaColor = Main.mouseTextColor;
410  newTooltipLine = "";
411  if (outNumber >= 0)
412  {
413  newTooltipLine += "+";
414  newColor = new Color((byte)(120f * defColorVal), (byte)(190f * defColorVal), (byte)(120f * defColorVal), alphaColor);
415  }
416  else
417  {
418  newColor = new Color((byte)(190f * defColorVal), (byte)(120f * defColorVal), (byte)(120f * defColorVal), alphaColor);
419  }
420 
421  newTooltipLine += outNumber.ToString(CultureInfo.InvariantCulture);
422  }
423  else if (tooltipLine.Name.Equals("PrefixUseMana"))
424  {
425  if (baseItem.mana != 0)
426  {
427  float defColorVal = Main.mouseTextColor / 255f;
428  int alphaColor = Main.mouseTextColor;
429  newTooltipLine = GetPrefixNormString(baseItem.mana, prefixItem.mana, ref outNumber, ref newColor);
430  newColor = prefixItem.mana < baseItem.mana
431  ? new Color((byte)(120f * defColorVal), (byte)(190f * defColorVal), (byte)(120f * defColorVal), alphaColor)
432  : new Color((byte)(190f * defColorVal), (byte)(120f * defColorVal), (byte)(120f * defColorVal), alphaColor);
433  }
434  }
435  else if (tooltipLine.Name.Equals("PrefixSize"))
436  {
437  newTooltipLine = baseItem.scale > 0
438  ? GetPrefixNormString(baseItem.scale, prefixItem.scale, ref outNumber, ref newColor)
439  : GetPrefixNormString(prefixItem.scale, baseItem.scale, ref outNumber, ref newColor);
440  }
441  else if (tooltipLine.Name.Equals("PrefixShootSpeed"))
442  {
443  newTooltipLine = baseItem.shootSpeed > 0
444  ? GetPrefixNormString(baseItem.shootSpeed, prefixItem.shootSpeed, ref outNumber, ref newColor)
445  : GetPrefixNormString(prefixItem.shootSpeed, baseItem.shootSpeed, ref outNumber, ref newColor);
446  }
447  else if (tooltipLine.Name.Equals("PrefixKnockback"))
448  {
449  newTooltipLine = baseItem.knockBack > 0
450  ? GetPrefixNormString(baseItem.knockBack, prefixItem.knockBack, ref outNumber, ref newColor)
451  : GetPrefixNormString(prefixItem.knockBack, baseItem.knockBack, ref outNumber, ref newColor);
452  }
453  else
454  {
455  continue;
456  }
457 
458  int ttlI = tooltips.FindIndex(x => x.mod.Equals(tooltipLine.mod) && x.Name.Equals(tooltipLine.Name));
459  if (ttlI == -1)
460  {
461  continue;
462  }
463 
464  if (outNumber == 0d)
465  {
466  tooltips.RemoveAt(ttlI);
467  }
468  else
469  {
470  tooltips[ttlI].text = $"{newTooltipLine}{tooltipEndText}";
471  tooltips[ttlI].overrideColor = newColor;
472  }
473  }
474  }
475  catch (Exception e)
476  {
477  Loot.Logger.Error(
478  $"A problem occurred during modification of the item's tooltip." +
479  $"\nItem in question: {item.AffixName()}",
480  e);
481  }
482  // RECALC END
483 
484  // Modifies the tooltips, to insert generic mods data
485  int i = tooltips.FindIndex(x => x.mod == "Terraria" && x.Name == "ItemName");
486  if (i != -1)
487  {
488  var namelayer = tooltips[i];
489 
490  if (Rarity.ItemPrefix != null)
491  {
492  namelayer.text = $"{Rarity.ItemPrefix} {namelayer.text}";
493  }
494 
495  if (Rarity.ItemSuffix != null)
496  {
497  namelayer.text = $"{namelayer.text} {Rarity.ItemSuffix}";
498  }
499 
500  if (Rarity.OverrideNameColor != null)
501  {
502  namelayer.overrideColor = Rarity.OverrideNameColor;
503  }
504 
505  tooltips[i] = namelayer;
506  }
507 
508 
509  CheatedItemHackGlobalItem cheatedItemHackGlobalItem = CheatedItemHackGlobalItem.GetInfo(item);
510  bool isVanityIgnored = cheatedItemHackGlobalItem.ShouldBeIgnored(item, Main.LocalPlayer);
511  Color? inactiveColor = isVanityIgnored ? (Color?)Color.DarkSlateGray : null;
512  // Insert modifier rarity
513 
514  if (!(Rarity is NullModifierRarity))
515  {
516  i = tooltips.Count;
517  tooltips.Insert(i, new TooltipLine(mod, "Loot: Modifier:Rarity", $"[{Rarity.RarityName}]{(isVanityIgnored ? " [IGNORED]" : "")}")
518  {
519  overrideColor = inactiveColor ?? Rarity.Color * Main.inventoryScale
520  });
521  }
522 
523  i = tooltips.Count - 1;
524 
525  foreach (var modifier in pool.Modifiers)
526  {
527  foreach (var tt in modifier.GetTooltip().Build())
528  {
529  tooltips.Insert(++i, new TooltipLine(mod, $"Loot: Modifier:Line:{i}", $"{modifier.GetFormattedUniqueName()} {tt.Text}".TrimStart())
530  {
531  overrideColor = inactiveColor ?? (tt.Color ?? Color.White) * Main.inventoryScale
532  });
533  }
534  }
535 
536  if (SealedModifiers)
537  {
538  var ttl = new TooltipLine(mod, "Loot: Modifier:Sealed", "Modifiers cannot be changed")
539  {
540  overrideColor = inactiveColor ?? Color.Cyan
541  };
542  tooltips.Insert(++i, ttl);
543  }
544 
545  foreach (var e in pool.Modifiers)
546  {
547  e.ModifyTooltips(item, tooltips);
548  }
549  }
550  }
551  }
552 }
static void UpdateGraphicsEntities(List< Modifier > modifiers, Item item)
virtual void NetReceive(Item item, BinaryReader reader)
Allows the modder to do custom NetReceive
This class is responsible for handling ShaderEntity and GlowmaskEntity that come from the Modifiers o...
virtual void NetSend(Item item, BinaryWriter writer)
Allows modder to do custom NetSend here
override bool CloneNewInstances
Definition: LootModItem.cs:29
string GetPrefixNormString(float cpStat, float rStat, ref double num, ref Color?color)
Definition: LootModItem.cs:309
override TagCompound Save(Item item)
Definition: LootModItem.cs:200
Can detect if an item was activated via cheats
Defines an item that may be modified by modifiers from mods
Definition: LootModItem.cs:21
virtual void Load(Item item, TagCompound tag)
Allows modder to do custom loading here Use the given TC to pull data you saved using Save(Item...
override void Load(Item item, TagCompound tag)
Definition: LootModItem.cs:174
Defines the rarity of a modifier
virtual void Load(Item item, TagCompound tag)
Allows modder to do custom loading here Use the given TagCompound to pull data you saved using Save(I...
override void ModifyTooltips(Item item, List< TooltipLine > tooltips)
Will modify vanilla tooltips to add additional information for the affected item&#39;s modifiers ...
Definition: LootModItem.cs:352
static List< Modifier > GetActivePool(Item item)
readonly List< Modifier > Modifiers
Definition: ModifierPool.cs:19
override bool NeedsSaving(Item item)
static LootModItem GetInfo(Item item)
ModifierRarity Rarity
Definition: LootModItem.cs:31
static CheatedItemHackGlobalItem GetInfo(Item item)
bool IsActivated
Keeps track of if the particular item was activated (&#39;delegated&#39;) Specific usecase see CursedEffect a...
Definition: LootModItem.cs:45
FiniteModifierPool Modifiers
Definition: LootModItem.cs:32
bool ShouldBeIgnored(Item item, Player player)
override bool InstancePerEntity
Definition: LootModItem.cs:28
override void NetReceive(Item item, BinaryReader reader)
Definition: LootModItem.cs:218
override GlobalItem Clone(Item item, Item itemClone)
Attempts to roll new modifiers Has a set chance to hit a predefined pool of modifiers ...
Definition: LootModItem.cs:165
const int SAVE_VERSION
Definition: LootModItem.cs:23
bool JustTinkerModified
Definition: LootModItem.cs:38
void InvalidateRolls()
Definition: LootModItem.cs:47
Defines a "Null" rarity which represents no rarity safely Cannot be rolled normally ...
virtual void NetReceive(Item item, BinaryReader reader)
Allows the modder to do custom NetReceive
override void NetSend(Item item, BinaryWriter writer)
Definition: LootModItem.cs:235