├── .gitignore
├── CHANGELOG.md
├── ItemStats.sln
├── ItemStats.sln.DotSettings.user
├── ItemStats
├── ItemStats.csproj
├── ItemStats.csproj.DotSettings
├── Properties
│ └── AssemblyInfo.cs
├── packages.config
└── src
│ ├── ContextProvider.cs
│ ├── Hooks.cs
│ ├── ItemStatDefinitions.cs
│ ├── ItemStatProvider.cs
│ ├── ItemStatsMod.cs
│ ├── Stat
│ ├── IStat.cs
│ ├── ItemStat.cs
│ ├── ItemStatDef.cs
│ └── StatContext.cs
│ ├── StatCalculation
│ ├── DefaultStatCalculationStrategy.cs
│ └── IStatCalculationStrategy.cs
│ ├── StatModification
│ ├── AbstractStatModifier.cs
│ ├── IStatModifier.cs
│ ├── Modifiers
│ │ ├── HealingIncreaseModifier.cs
│ │ └── LuckModifier.cs
│ └── StatModifiers.cs
│ └── ValueFormatters
│ ├── Colors.cs
│ ├── Extensions.cs
│ ├── IStatFormatter.cs
│ └── ModifierFormatter.cs
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.dll
2 | .idea/
3 | bin/
4 | obj/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes in 2.2.1
2 |
3 | * Fixed clover incorrectly calculating stats for items with >= 100% chance to proc
4 |
5 | * Fixed clover modifying the incorrect stat on Fresh Meat
6 |
7 | * Fixed incorrect percentage formatting for the new items
8 |
9 | # Changes in 2.2.0
10 |
11 | * Anniversary update support
12 |
13 | # Changes in 2.1.0
14 |
15 | * Fixed incorrect stats of Strides of Heresy
16 |
17 | * Fixed formatting issues
18 |
19 | * Luck stat modifier now shows purity
20 |
21 | * Added config option to hide detailed descriptions in item pickup popup
22 |
23 | # Changes in 2.0.0
24 |
25 | * Add custom item and stat modifier API
26 |
27 | * Support for new/updated items from the past few updates
28 |
29 | * Now shows healing bonuses from Rejuvenation Rack
30 |
31 | * Now shows item description on pickup
32 |
33 | # Changes in 1.5.0
34 |
35 | * Support for new/updated items from the Artifacts update
36 |
37 | * Added scaling gold information to Ghor's Tome
38 |
39 | * Fixed Genesis Loop recharge duration
40 |
41 | # Changes in 1.4.0
42 |
43 | * Support for new/updated items from the newest update
44 |
45 | # Changes in 1.3.1
46 |
47 | * Fixed the lunar ability replacement thingy from crashing when spectating in multiplayer
48 |
49 | * Updated the Tooth healing amount
50 |
51 | # Changes in 1.3
52 |
53 | * Support for new/updated items from the skills 2.0 update
54 |
55 | # Changes in 1.2.1
56 |
57 | * Tri-tip Dagger is now affected by clover
58 | * Halcyon Seed now has stats more accurate to the description
59 | * Missile proc chance is now affected by clover
60 | * Predatory Instincts now has proper attack speed stats
61 | * Fixed turtle hp stat
62 | * Aegis is now capped at 100% health
63 | * Kill health threshold for old guiliotine is fixed
64 | * Rej-rack no longer has proc chance lmao
65 |
66 | # Changes in 1.2.0
67 |
68 | * Support for new/updated items
69 | * Fixed a few missing/broken stats for pre-patch items
70 |
71 | # Changes in 1.1.1
72 |
73 | * Implemented frost relic
74 | * Fixed stun grenade fuckup
75 |
76 | # Changes in 1.1.0
77 |
78 | * Items that roll chance now show the clover bonus
79 | * Rusty key now shows proper loot chances for item groups
80 | * Less confusing stat text
81 |
--------------------------------------------------------------------------------
/ItemStats.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ItemStats", "ItemStats\ItemStats.csproj", "{35991648-A109-4E7A-A120-08C4FA3780F8}"
4 | EndProject
5 | Global
6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
7 | Debug|Any CPU = Debug|Any CPU
8 | Release|Any CPU = Release|Any CPU
9 | EndGlobalSection
10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
11 | {35991648-A109-4E7A-A120-08C4FA3780F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12 | {35991648-A109-4E7A-A120-08C4FA3780F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
13 | {35991648-A109-4E7A-A120-08C4FA3780F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
14 | {35991648-A109-4E7A-A120-08C4FA3780F8}.Release|Any CPU.Build.0 = Release|Any CPU
15 | EndGlobalSection
16 | EndGlobal
17 |
--------------------------------------------------------------------------------
/ItemStats.sln.DotSettings.user:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
4 | True
5 | 2
6 | <AssemblyExplorer>
7 | <Assembly Path="C:\Users\MAX\RiderProjects\ItemStats\ItemStatsMod\bin\Release\UnityEngine.dll" />
8 | <Assembly Path="D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\Assembly-CSharp.dll" />
9 | <Assembly Path="D:\SteamLibrary\steamapps\common\Risk of Rain 2\BepInEx\core\BepInEx.dll" />
10 | <Assembly Path="C:\Users\MAX\RiderProjects\ItemStats\ItemStats\bin\Release\R2API.dll" />
11 | <Assembly Path="D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\RoR2.dll" />
12 | </AssemblyExplorer>
13 |
14 |
--------------------------------------------------------------------------------
/ItemStats/ItemStats.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {35991648-A109-4E7A-A120-08C4FA3780F8}
8 | Library
9 | Properties
10 | ItemStats
11 | ItemStats
12 | v4.6.1
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 | ..\packages\Lib.Harmony.1.2.0.1\lib\net45\0Harmony.dll
37 | True
38 |
39 |
40 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\BepInEx\core\BepInEx.dll
41 |
42 |
43 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\com.unity.multiplayer-hlapi.Runtime.dll
44 |
45 |
46 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\BepInEx\plugins\MMHOOK\MMHOOK_RoR2.dll
47 |
48 |
49 | bin\Release\R2API.dll
50 |
51 |
52 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\Rewired_Core.dll
53 |
54 |
55 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\RoR2.dll
56 |
57 |
58 |
59 |
60 |
61 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\UnityEngine.dll
62 |
63 |
64 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\UnityEngine.CoreModule.dll
65 |
66 |
67 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\UnityEngine.Networking.dll
68 |
69 |
70 | D:\SteamLibrary\steamapps\common\Risk of Rain 2\Risk of Rain 2_Data\Managed\UnityEngine.UI.dll
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
108 |
--------------------------------------------------------------------------------
/ItemStats/ItemStats.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | CSharp80
--------------------------------------------------------------------------------
/ItemStats/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("ItemStats")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("ItemStats")]
12 | [assembly: AssemblyCopyright("Copyright ontrigger 2021")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("35991648-A109-4E7A-A120-08C4FA3780F8")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("2.2.1.0")]
35 | [assembly: AssemblyFileVersion("2.2.1.0")]
--------------------------------------------------------------------------------
/ItemStats/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ItemStats/src/ContextProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using ItemStats.ValueFormatters;
4 | using RoR2;
5 |
6 | namespace ItemStats
7 | {
8 | public static class ContextProvider
9 | {
10 | public static Dictionary GetPlayerIdToItemCountMap(ItemIndex index)
11 | {
12 | return LocalUserManager.readOnlyLocalUsersList
13 | .ToDictionary(user => user.id, user => user.cachedBody.CountItems(index));
14 | }
15 |
16 | public static IEnumerable GetPlayerBodiesExcept(int userId)
17 | {
18 | return LocalUserManager.readOnlyLocalUsersList
19 | .Where(user => user.id != userId)
20 | .Select(user => user.cachedBody);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/ItemStats/src/Hooks.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using R2API.Utils;
3 | using RoR2;
4 | using RoR2.UI;
5 | using GenericNotification = On.RoR2.UI.GenericNotification;
6 | using HUD = On.RoR2.UI.HUD;
7 | using ScoreboardStrip = On.RoR2.UI.ScoreboardStrip;
8 |
9 | namespace ItemStats
10 | {
11 | internal static class Hooks
12 | {
13 | // TODO: prevent memleak
14 | private static readonly Dictionary DisplayToMasterRef =
15 | new Dictionary();
16 |
17 | private static readonly Dictionary IconToMasterRef =
18 | new Dictionary();
19 |
20 | public static void Init()
21 | {
22 | HUD.Update += HudUpdateHook;
23 |
24 | ScoreboardStrip.SetMaster += ScoreboardSetMasterHook;
25 |
26 | GenericNotification.SetItem += SetNotificationItemHook;
27 |
28 | On.RoR2.UI.ItemInventoryDisplay.AllocateIcons += ItemDisplayAllocateIconsHook;
29 |
30 | On.RoR2.UI.ItemIcon.SetItemIndex += ItemIconSetItemIndexHook;
31 | }
32 |
33 | private static void ItemIconSetItemIndexHook(On.RoR2.UI.ItemIcon.orig_SetItemIndex orig, ItemIcon self,
34 | ItemIndex newIndex, int newCount)
35 | {
36 | orig(self, newIndex, newCount);
37 |
38 | var itemDef = ItemCatalog.GetItemDef(newIndex);
39 | if (self.tooltipProvider != null && itemDef != null)
40 | {
41 | var itemDescription = Language.GetString(itemDef.descriptionToken);
42 |
43 | IconToMasterRef.TryGetValue(self, out var master);
44 |
45 | // TODO: use a pool to reduce StatContext allocations
46 | itemDescription += ItemStatsMod.GetStatsForItem(newIndex, newCount, new StatContext(master));
47 |
48 | self.tooltipProvider.overrideBodyText = itemDescription;
49 | }
50 | }
51 |
52 | private static void ItemDisplayAllocateIconsHook(On.RoR2.UI.ItemInventoryDisplay.orig_AllocateIcons orig,
53 | ItemInventoryDisplay self, int count)
54 | {
55 | orig(self, count);
56 |
57 | var icons = self.GetFieldValue>("itemIcons");
58 |
59 | DisplayToMasterRef.TryGetValue(self, out var masterRef);
60 |
61 | // naive, but not worth improving as it is not called every frame
62 | icons.ForEach(i => IconToMasterRef[i] = masterRef);
63 | }
64 |
65 | private static void ScoreboardSetMasterHook(ScoreboardStrip.orig_SetMaster orig, RoR2.UI.ScoreboardStrip self,
66 | CharacterMaster master)
67 | {
68 | orig(self, master);
69 |
70 | if (master) DisplayToMasterRef[self.itemInventoryDisplay] = master;
71 | }
72 |
73 | private static void HudUpdateHook(HUD.orig_Update orig, RoR2.UI.HUD self)
74 | {
75 | orig(self);
76 |
77 | if (self.itemInventoryDisplay && self.targetMaster)
78 | DisplayToMasterRef[self.itemInventoryDisplay] = self.targetMaster;
79 | }
80 |
81 | private static void SetNotificationItemHook(GenericNotification.orig_SetItem orig,
82 | RoR2.UI.GenericNotification self,
83 | ItemDef itemDef)
84 | {
85 | orig(self, itemDef);
86 |
87 | if (ItemStatsMod.DetailedPickupDescriptions.Value)
88 | {
89 | self.descriptionText.token = itemDef.descriptionToken;
90 | }
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/ItemStats/src/ItemStatDefinitions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ItemStats.Stat;
3 | using ItemStats.ValueFormatters;
4 | using RoR2;
5 | using UnityEngine;
6 | namespace ItemStats
7 | {
8 | internal partial class ItemStatProvider
9 | {
10 | static ItemStatProvider()
11 | {
12 | CustomItemDefs = new Dictionary();
13 |
14 | ItemDefs = new Dictionary();
15 | }
16 |
17 | public static void Init()
18 | {
19 | ItemDefs[ItemCatalog.FindItemIndex("Bear")] = new ItemStatDef
20 | {
21 | Stats = new List
22 | {
23 | new ItemStat(
24 | (itemCount, ctx) => 1f - 1f / (0.15f * itemCount + 1f),
25 | (value, ctx) => $"Block Chance: {value.FormatPercentage()}"
26 | )
27 | }
28 | };
29 | ItemDefs[ItemCatalog.FindItemIndex("Hoof")] = new ItemStatDef
30 | {
31 | Stats = new List
32 | {
33 | new ItemStat(
34 | (itemCount, ctx) => itemCount * 0.14f,
35 | (value, ctx) => $"Movement Speed Increase: {value.FormatPercentage()}"
36 | )
37 | }
38 | };
39 | ItemDefs[ItemCatalog.FindItemIndex("Syringe")] = new ItemStatDef
40 | {
41 | Stats = new List
42 | {
43 | new ItemStat(
44 | (itemCount, ctx) => itemCount * 0.15f,
45 | (value, ctx) => $"Attack Speed Increase: {value.FormatPercentage()}"
46 | )
47 | }
48 | };
49 | ItemDefs[ItemCatalog.FindItemIndex("Mushroom")] = new ItemStatDef
50 | {
51 | Stats = new List
52 | {
53 | new ItemStat(
54 | (itemCount, ctx) => 0.0225f + 0.0225f * itemCount,
55 | (value, ctx) => $"Healing Per Second: {value.FormatPercentage(maxValue: 1f)}"
56 | ),
57 | new ItemStat(
58 | (itemCount, ctx) => 1.5f + 1.5f * itemCount,
59 | (value, ctx) => $"Area Increase: {value.FormatInt("m")}"
60 | )
61 | }
62 | };
63 | ItemDefs[ItemCatalog.FindItemIndex("CritGlasses")] = new ItemStatDef
64 | {
65 | Stats = new List
66 | {
67 | new ItemStat(
68 | (itemCount, ctx) => itemCount * 0.1f,
69 | (value, ctx) => $"Additional Crit Chance: {value.FormatPercentage(maxValue: 1f)}"
70 | )
71 | }
72 | };
73 | ItemDefs[ItemCatalog.FindItemIndex("Feather")] = new ItemStatDef
74 | {
75 | Stats = new List
76 | {
77 | new ItemStat(
78 | (itemCount, ctx) => itemCount,
79 | (value, ctx) => $"Total Additional Jumps: {value.FormatInt()}"
80 | )
81 | }
82 | };
83 | ItemDefs[ItemCatalog.FindItemIndex("Seed")] = new ItemStatDef
84 | {
85 | Stats = new List
86 | {
87 | new ItemStat(
88 | (itemCount, ctx) => itemCount,
89 | (value, ctx) => $"Total Heal: {value.FormatInt("HP")}"
90 | )
91 | }
92 | };
93 | ItemDefs[ItemCatalog.FindItemIndex("GhostOnKill")] = new ItemStatDef
94 | {
95 | Stats = new List
96 | {
97 | new ItemStat(
98 | (itemCount, ctx) => itemCount * 30f,
99 | (value, ctx) => $"Ghost Duration: {value.FormatInt("s")}"
100 | ),
101 | new ItemStat(
102 | (itemCount, ctx) => 0.07f,
103 | (value, ctx) => $"Proc Chance: {value.FormatPercentage()}"
104 | // StatModifiers.Luck
105 | )
106 | }
107 | };
108 | ItemDefs[ItemCatalog.FindItemIndex("Knurl")] = new ItemStatDef
109 | {
110 | Stats = new List
111 | {
112 | new ItemStat(
113 | (itemCount, ctx) => itemCount * 40f,
114 | (value, ctx) => $"Bonus Health: {value.FormatInt("HP")}"
115 | ),
116 | new ItemStat(
117 | (itemCount, ctx) => itemCount * 1.6f,
118 | (value, ctx) => $"Additional Regeneration: {value.FormatInt("HP/s")}"
119 | )
120 | }
121 | };
122 | ItemDefs[ItemCatalog.FindItemIndex("Clover")] = new ItemStatDef
123 | {
124 | Stats = new List
125 | {
126 | new ItemStat(
127 | (itemCount, ctx) => itemCount,
128 | (value, ctx) => $"Additional Rerolls: {value.FormatInt()}"
129 | )
130 | }
131 | };
132 | ItemDefs[ItemCatalog.FindItemIndex("Medkit")] = new ItemStatDef
133 | {
134 | Stats = new List
135 | {
136 | new ItemStat(
137 | (itemCount, ctx) =>
138 | 20f + 0.05f * itemCount * (ctx.Master != null ? ctx.Master.GetBody().maxHealth : 1),
139 | (value, ctx) =>
140 | {
141 | var statValue = ctx.Master != null
142 | ? $"{value.FormatInt("HP")}"
143 | : $"{value.FormatPercentage()}";
144 | return "Health Healed: " + statValue;
145 | })
146 | }
147 | };
148 | ItemDefs[ItemCatalog.FindItemIndex("Crowbar")] = new ItemStatDef
149 | {
150 | Stats = new List
151 | {
152 | new ItemStat(
153 | (itemCount, ctx) => 0.75f * itemCount,
154 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
155 | )
156 | }
157 | };
158 | ItemDefs[ItemCatalog.FindItemIndex("Tooth")] = new ItemStatDef
159 | {
160 | Stats = new List
161 | {
162 | new ItemStat(
163 | (itemCount, ctx) => 0.02f * itemCount,
164 | (value, ctx) => $"Heal Amount: {value.FormatPercentage()}"
165 | )
166 | }
167 | };
168 | ItemDefs[ItemCatalog.FindItemIndex("Talisman")] = new ItemStatDef
169 | {
170 | Stats = new List
171 | {
172 | new ItemStat(
173 | (itemCount, ctx) => 2f + itemCount * 2f,
174 | (value, ctx) => $"Cooldown Reduction: {value.FormatInt("s")}"
175 | )
176 | }
177 | };
178 | ItemDefs[ItemCatalog.FindItemIndex("Bandolier")] = new ItemStatDef
179 | {
180 | Stats = new List
181 | {
182 | new ItemStat(
183 | (itemCount, ctx) => 1f - 1f / Mathf.Pow(itemCount + 1, 0.33f),
184 | (value, ctx) => $"Drop Chance: {value.FormatPercentage()}"
185 | )
186 | }
187 | };
188 | ItemDefs[ItemCatalog.FindItemIndex("IceRing")] = new ItemStatDef
189 | {
190 | Stats = new List
191 | {
192 | new ItemStat(
193 | (itemCount, ctx) => 2.5f * itemCount,
194 | (value, ctx) => $"Ice Blast Damage: {value.FormatPercentage()}"
195 | ),
196 | new ItemStat(
197 | (itemCount, ctx) => 3f * itemCount,
198 | (value, ctx) => $"Ice Debuff Duration: {value.FormatInt("s")}"
199 | )
200 | }
201 | };
202 | ItemDefs[ItemCatalog.FindItemIndex("FireRing")] = new ItemStatDef
203 | {
204 | Stats = new List
205 | {
206 | new ItemStat(
207 | (itemCount, ctx) => 3f * itemCount,
208 | (value, ctx) => $"Fire Tornado Damage: {value.FormatPercentage()}"
209 | )
210 | }
211 | };
212 | ItemDefs[ItemCatalog.FindItemIndex("WarCryOnMultiKill")] = new ItemStatDef
213 | {
214 | Stats = new List
215 | {
216 | new ItemStat(
217 | (itemCount, ctx) => 2f + 4f * itemCount,
218 | (value, ctx) => $"Frenzy Duration: {value.FormatInt("s")}"
219 | )
220 | }
221 | };
222 | ItemDefs[ItemCatalog.FindItemIndex("SprintOutOfCombat")] = new ItemStatDef
223 | {
224 | Stats = new List
225 | {
226 | new ItemStat(
227 | (itemCount, ctx) => itemCount * 0.3f,
228 | (value, ctx) => $"Speed Increase: {value.FormatPercentage()}"
229 | )
230 | }
231 | };
232 | ItemDefs[ItemCatalog.FindItemIndex("StunChanceOnHit")] = new ItemStatDef
233 | {
234 | Stats = new List
235 | {
236 | new ItemStat(
237 | (itemCount, ctx) => 1f - 1f / (0.05f * itemCount + 1f),
238 | (value, ctx) => $"Stun Chance Increase: {value.FormatPercentage(maxValue: 1f)}"
239 | // StatModifiers.Luck
240 | )
241 | }
242 | };
243 | ItemDefs[ItemCatalog.FindItemIndex("WarCryOnCombat")] = new ItemStatDef
244 | {
245 | Stats = new List
246 | {
247 | new ItemStat(
248 | (itemCount, ctx) => 2f + 4f * itemCount,
249 | (value, ctx) => $"Frenzy Duration: {value.FormatInt()}"
250 | )
251 | }
252 | };
253 | ItemDefs[ItemCatalog.FindItemIndex("SecondarySkillMagazine")] = new ItemStatDef
254 | {
255 | Stats = new List
256 | {
257 | new ItemStat(
258 | (itemCount, ctx) => itemCount,
259 | (value, ctx) => $"Bonus Stock: {value.FormatInt()}"
260 | )
261 | }
262 | };
263 | ItemDefs[ItemCatalog.FindItemIndex("UtilitySkillMagazine")] = new ItemStatDef
264 | {
265 | Stats = new List
266 | {
267 | new ItemStat(
268 | (itemCount, ctx) => itemCount * 2f,
269 | (value, ctx) => $"Bonus Charges: {value.FormatInt()}"
270 | )
271 | }
272 | };
273 | ItemDefs[ItemCatalog.FindItemIndex("AutoCastEquipment")] = new ItemStatDef
274 | {
275 | Stats = new List
276 | {
277 | new ItemStat(
278 | (itemCount, ctx) => 1f - (0.5f * Mathf.Pow(0.85f, itemCount - 1)),
279 | (value, ctx) => $"Cooldown Decrease: {value.FormatPercentage()}"
280 | )
281 | }
282 | };
283 | ItemDefs[ItemCatalog.FindItemIndex("KillEliteFrenzy")] = new ItemStatDef
284 | {
285 | Stats = new List
286 | {
287 | new ItemStat(
288 | (itemCount, ctx) => itemCount * 4f,
289 | (value, ctx) => $"Frenzy Duration: {value.FormatInt("s")}"
290 | )
291 | }
292 | };
293 | ItemDefs[ItemCatalog.FindItemIndex("BossDamageBonus")] = new ItemStatDef
294 | {
295 | Stats = new List
296 | {
297 | new ItemStat(
298 | (itemCount, ctx) => 0.2f + 0.2f * (itemCount - 1),
299 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
300 | )
301 | }
302 | };
303 | ItemDefs[ItemCatalog.FindItemIndex("ExplodeOnDeath")] = new ItemStatDef
304 | {
305 | Stats = new List
306 | {
307 | new ItemStat(
308 | (itemCount, ctx) => 12f + 2.4f * (itemCount - 1f),
309 | (value, ctx) => $"Radius Increase: {value.FormatInt("m")}"
310 | ),
311 | new ItemStat(
312 | (itemCount, ctx) => 3.5f * (1f + (itemCount - 1) * 0.8f),
313 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
314 | )
315 | }
316 | };
317 | ItemDefs[ItemCatalog.FindItemIndex("HealWhileSafe")] = new ItemStatDef
318 | {
319 | Stats = new List
320 | {
321 | new ItemStat(
322 | (itemCount, ctx) => 3f * itemCount,
323 | (value, ctx) => $"Bonus Health Regen: {value.FormatInt("HP/s")}"
324 | )
325 | }
326 | };
327 | ItemDefs[ItemCatalog.FindItemIndex("IgniteOnKill")] = new ItemStatDef
328 | {
329 | Stats = new List
330 | {
331 | new ItemStat(
332 | (itemCount, ctx) => 8f + 4f * itemCount,
333 | (value, ctx) => $"Radius Increase: {value.FormatInt("m")}"
334 | ),
335 | new ItemStat(
336 | (itemCount, ctx) => 1.5f + 1.5f * itemCount,
337 | (value, ctx) => $"Duration Increase: {value.FormatPercentage()}"
338 | )
339 | }
340 | };
341 | ItemDefs[ItemCatalog.FindItemIndex("WardOnLevel")] = new ItemStatDef
342 | {
343 | Stats = new List
344 | {
345 | new ItemStat(
346 | (itemCount, ctx) => 8f + 8f * itemCount,
347 | (value, ctx) => $"Radius Increase: {value.FormatInt("m")}"
348 | )
349 | }
350 | };
351 | ItemDefs[ItemCatalog.FindItemIndex("NovaOnHeal")] = new ItemStatDef
352 | {
353 | Stats = new List
354 | {
355 | new ItemStat(
356 | (itemCount, ctx) => itemCount,
357 | (value, ctx) => $"Soul Energy: {value.FormatPercentage()}"
358 | )
359 | }
360 | };
361 | ItemDefs[ItemCatalog.FindItemIndex("HealOnCrit")] = new ItemStatDef
362 | {
363 | Stats = new List
364 | {
365 | new ItemStat(
366 | (itemCount, ctx) => 4f + itemCount * 4f,
367 | (value, ctx) => $"Health per Crit: {value.FormatInt("HP")}"
368 | )
369 | }
370 | };
371 | ItemDefs[ItemCatalog.FindItemIndex("BleedOnHit")] = new ItemStatDef
372 | {
373 | Stats = new List
374 | {
375 | new ItemStat(
376 | (itemCount, ctx) => 0.1f * itemCount,
377 | (value, ctx) => $"Bleed Chance Increase: {value.FormatPercentage(maxValue: 1f)}"
378 | // StatModifiers.Luck
379 | )
380 | }
381 | };
382 | ItemDefs[ItemCatalog.FindItemIndex("SlowOnHit")] = new ItemStatDef
383 | {
384 | Stats = new List
385 | {
386 | new ItemStat(
387 | (itemCount, ctx) => 2 * itemCount,
388 | (value, ctx) => $"Slow Duration: {value.FormatInt("s")}"
389 | )
390 | }
391 | };
392 | ItemDefs[ItemCatalog.FindItemIndex("EquipmentMagazine")] = new ItemStatDef
393 | {
394 | Stats = new List
395 | {
396 | new ItemStat(
397 | (itemCount, ctx) => itemCount,
398 | (value, ctx) => $"Bonus Charges: {value.FormatInt()}"
399 | ),
400 | new ItemStat(
401 | (itemCount, ctx) => 1 - Mathf.Pow(0.85f, itemCount),
402 | (value, ctx) => $"Cooldown Decrease: {value.FormatPercentage()}"
403 | )
404 | }
405 | };
406 | ItemDefs[ItemCatalog.FindItemIndex("GoldOnHit")] = new ItemStatDef
407 | {
408 | Stats = new List
409 | {
410 | new ItemStat(
411 | //TODO: make run a modifier
412 | (itemCount, ctx) => itemCount * 2f * Run.instance.difficultyCoefficient,
413 | (value, ctx) => $"Gold per Hit(*): {value.FormatInt()}"
414 | ),
415 | new ItemStat(
416 | (itemCount, ctx) => 0.3f,
417 | (value, ctx) => $"Proc Chance: {value.FormatPercentage()}"
418 | // modifiers: StatModifiers.Luck
419 | )
420 | }
421 | };
422 | ItemDefs[ItemCatalog.FindItemIndex("IncreaseHealing")] = new ItemStatDef
423 | {
424 | Stats = new List
425 | {
426 | new ItemStat(
427 | (itemCount, ctx) => itemCount,
428 | (value, ctx) => $"Healing Increase: {value.FormatPercentage()}"
429 | )
430 | }
431 | };
432 | ItemDefs[ItemCatalog.FindItemIndex("PersonalShield")] = new ItemStatDef
433 | {
434 | Stats = new List
435 | {
436 | new ItemStat(
437 | (itemCount, ctx) => 0.08f * itemCount,
438 | (value, ctx) => $"Shield Health Increase: {value.FormatPercentage()}"
439 | )
440 | }
441 | };
442 | ItemDefs[ItemCatalog.FindItemIndex("ChainLightning")] = new ItemStatDef
443 | {
444 | Stats = new List
445 | {
446 | new ItemStat(
447 | (itemCount, ctx) => itemCount * 2f,
448 | (value, ctx) => $"Total Bounces: {value.FormatInt()}"
449 | ),
450 | new ItemStat(
451 | (itemCount, ctx) => 20f + 2f * itemCount,
452 | (value, ctx) => $"Bounce Range: {value.FormatInt("m")}"
453 | ),
454 | new ItemStat(
455 | (itemCount, ctx) => 0.25f,
456 | (value, ctx) => $"Proc Chance: {value.FormatPercentage()}"
457 | // StatModifiers.Luck
458 | )
459 | }
460 | };
461 | ItemDefs[ItemCatalog.FindItemIndex("TreasureCache")] = new ItemStatDef
462 | {
463 | Stats = new List
464 | {
465 | new ItemStat(
466 | (itemCount, ctx) => itemCount,
467 | (value, ctx) => $"Unlockable Caches: {value}"
468 | // StatModifiers.TreasureCache
469 | ),
470 | }
471 | };
472 | ItemDefs[ItemCatalog.FindItemIndex("BounceNearby")] = new ItemStatDef
473 | {
474 | Stats = new List
475 | {
476 | new ItemStat(
477 | (itemCount, ctx) => 1f - 100f / (100f + 20f * itemCount),
478 | (value, ctx) => $"Hook Chance: {value.FormatPercentage()}"
479 | // modifiers: StatModifiers.Luck
480 | ),
481 | new ItemStat(
482 | (itemCount, ctx) => 5f + itemCount * 5f,
483 | (value, ctx) => $"Max Enemies Hooked: {value.FormatInt()}"
484 | )
485 | }
486 | };
487 | ItemDefs[ItemCatalog.FindItemIndex("SprintBonus")] = new ItemStatDef
488 | {
489 | Stats = new List
490 | {
491 | new ItemStat(
492 | (itemCount, ctx) => 0.25f * itemCount,
493 | (value, ctx) => $"Speed Increase: {value.FormatPercentage()}"
494 | )
495 | }
496 | };
497 | ItemDefs[ItemCatalog.FindItemIndex("SprintArmor")] = new ItemStatDef
498 | {
499 | Stats = new List
500 | {
501 | new ItemStat(
502 | (itemCount, ctx) => 30f * itemCount,
503 | (value, ctx) => $"Sprint Bonus Armor: {value.FormatInt()}"
504 | )
505 | }
506 | };
507 | ItemDefs[ItemCatalog.FindItemIndex("ShockNearby")] = new ItemStatDef
508 | {
509 | Stats = new List
510 | {
511 | new ItemStat(
512 | (itemCount, ctx) => 2f * itemCount,
513 | (value, ctx) => $"Total Bounces: {value.FormatInt()}"
514 | )
515 | }
516 | };
517 | ItemDefs[ItemCatalog.FindItemIndex("BeetleGland")] = new ItemStatDef
518 | {
519 | Stats = new List
520 | {
521 | new ItemStat(
522 | (itemCount, ctx) => itemCount,
523 | (value, ctx) => $"Total Guards: {value.FormatInt()}"
524 | )
525 | }
526 | };
527 | ItemDefs[ItemCatalog.FindItemIndex("ShieldOnly")] = new ItemStatDef
528 | {
529 | Stats = new List
530 | {
531 | new ItemStat(
532 | (itemCount, ctx) => 0.5f + (itemCount - 1) * 0.25f,
533 | (value, ctx) => $"Max Health Increase: {value.FormatPercentage()}"
534 | )
535 | }
536 | };
537 | ItemDefs[ItemCatalog.FindItemIndex("StickyBomb")] = new ItemStatDef
538 | {
539 | Stats = new List
540 | {
541 | new ItemStat(
542 | (itemCount, ctx) => 0.05f * itemCount,
543 | (value, ctx) => $"Proc Chance Increase: {value.FormatPercentage(maxValue: 1f)}"
544 | // StatModifiers.Luck
545 | )
546 | }
547 | };
548 | ItemDefs[ItemCatalog.FindItemIndex("RepeatHeal")] = new ItemStatDef
549 | {
550 | Stats = new List
551 | {
552 | //TODO: need to get masters maxhealth to get actual heal amount
553 | new ItemStat(
554 | (itemCount, ctx) => 0.1f / itemCount,
555 | (value, ctx) => $"Health Fraction/s: {value.FormatPercentage()}"
556 | ),
557 | new ItemStat(
558 | (itemCount, ctx) => itemCount,
559 | (value, ctx) => $"Healing per Heal Increase: {value.FormatPercentage()}"
560 | )
561 | }
562 | };
563 | ItemDefs[ItemCatalog.FindItemIndex("HeadHunter")] = new ItemStatDef
564 | {
565 | Stats = new List
566 | {
567 | new ItemStat(
568 | (itemCount, ctx) => 3f + 5f * itemCount,
569 | (value, ctx) => $"Empowerment Duration: {value.FormatInt("s")}"
570 | )
571 | }
572 | };
573 | ItemDefs[ItemCatalog.FindItemIndex("ExtraLife")] = new ItemStatDef
574 | {
575 | Stats = new List
576 | {
577 | new ItemStat(
578 | (itemCount, ctx) => itemCount,
579 | (value, ctx) => $"Extra Lives: {value.FormatInt()}"
580 | )
581 | }
582 | };
583 | ItemDefs[ItemCatalog.FindItemIndex("AlienHead")] = new ItemStatDef
584 | {
585 | Stats = new List
586 | {
587 | new ItemStat(
588 | (itemCount, ctx) => 1 - Mathf.Pow(0.75f, itemCount),
589 | (value, ctx) => $"Cooldown Reduction: {value.FormatPercentage(2)}"
590 | )
591 | }
592 | };
593 | ItemDefs[ItemCatalog.FindItemIndex("Firework")] = new ItemStatDef
594 | {
595 | Stats = new List
596 | {
597 | new ItemStat(
598 | (itemCount, ctx) => 4 + itemCount * 4,
599 | (value, ctx) => $"Firework Count: {value.FormatInt()}"
600 | )
601 | }
602 | };
603 | ItemDefs[ItemCatalog.FindItemIndex("Missile")] = new ItemStatDef
604 | {
605 | Stats = new List
606 | {
607 | new ItemStat(
608 | (itemCount, ctx) => 3 * itemCount,
609 | (value, ctx) => $"Missile Total Damage: {value.FormatPercentage()}"
610 | ),
611 | new ItemStat(
612 | (itemCount, ctx) => 0.1f,
613 | (value, ctx) => $"Proc Chance: {value.FormatPercentage()}"
614 | // StatModifiers.Luck
615 | )
616 | }
617 | };
618 | ItemDefs[ItemCatalog.FindItemIndex("Infusion")] = new ItemStatDef
619 | {
620 | Stats = new List
621 | {
622 | new ItemStat(
623 | (itemCount, ctx) => 100 * itemCount,
624 | (value, ctx) => $"Max Additional Health: {value.FormatInt("HP")}"
625 | ),
626 | new ItemStat(
627 | (itemCount, ctx) => itemCount,
628 | (value, ctx) => $"Health Gained Per Kill: {value.FormatInt("HP")}"
629 | )
630 | }
631 | };
632 | ItemDefs[ItemCatalog.FindItemIndex("AttackSpeedOnCrit")] = new ItemStatDef
633 | {
634 | Stats = new List
635 | {
636 | new ItemStat(
637 | (itemCount, ctx) => 0.12f + 0.24f * itemCount,
638 | (value, ctx) => $"Max Attack Speed: {value.FormatPercentage()}"
639 | ),
640 | new ItemStat(
641 | (itemCount, ctx) => 0.05f,
642 | (value, ctx) => $"Crit Chance Bonus: {value.FormatPercentage()}"
643 | )
644 | }
645 | };
646 | ItemDefs[ItemCatalog.FindItemIndex("Icicle")] = new ItemStatDef
647 | {
648 | Stats = new List
649 | {
650 | new ItemStat(
651 | (itemCount, ctx) => 3f + 3f * itemCount,
652 | (value, ctx) => $"Radius: {value.FormatInt("m")}"
653 | )
654 | }
655 | };
656 | ItemDefs[ItemCatalog.FindItemIndex("Behemoth")] = new ItemStatDef
657 | {
658 | Stats = new List
659 | {
660 | new ItemStat(
661 | (itemCount, ctx) => 1.5f + 2.5f * itemCount,
662 | (value, ctx) => $"Explosion Radius: {value.FormatInt("m", 1)}"
663 | )
664 | }
665 | };
666 | ItemDefs[ItemCatalog.FindItemIndex("BarrierOnKill")] = new ItemStatDef
667 | {
668 | Stats = new List
669 | {
670 | new ItemStat(
671 | (itemCount, ctx) => 15f * itemCount,
672 | (value, ctx) => $"Barrier Health: {value.FormatInt("HP")}"
673 | )
674 | }
675 | };
676 | ItemDefs[ItemCatalog.FindItemIndex("BarrierOnOverHeal")] = new ItemStatDef
677 | {
678 | Stats = new List
679 | {
680 | new ItemStat(
681 | (itemCount, ctx) => 0.5f * itemCount,
682 | (value, ctx) => $"Barrier From Overheal: {value.FormatPercentage()}"
683 | )
684 | }
685 | };
686 | ItemDefs[ItemCatalog.FindItemIndex("ExecuteLowHealthElite")] = new ItemStatDef
687 | {
688 | Stats = new List
689 | {
690 | new ItemStat(
691 | (itemCount, ctx) => 1 - 1 / (1 + 0.13f * itemCount),
692 | (value, ctx) => $"Kill Health Threshold: {value.FormatPercentage()}"
693 | )
694 | }
695 | };
696 | ItemDefs[ItemCatalog.FindItemIndex("EnergizedOnEquipmentUse")] = new ItemStatDef
697 | {
698 | Stats = new List
699 | {
700 | new ItemStat(
701 | (itemCount, ctx) => 8f + 4f * (itemCount - 1),
702 | (value, ctx) => $"Attack Speed Duration: {value.FormatInt("s")}"
703 | )
704 | }
705 | };
706 | ItemDefs[ItemCatalog.FindItemIndex("TitanGoldDuringTP")] = new ItemStatDef
707 | {
708 | Stats = new List
709 | {
710 | new ItemStat(
711 | (itemCount, ctx) => itemCount,
712 | (value, ctx) => $"Health Boost: {value.FormatPercentage()}"
713 | ),
714 | new ItemStat(
715 | (itemCount, ctx) => 0.5f + 0.5f * itemCount,
716 | (value, ctx) => $"Damage Boost: {value.FormatPercentage()}"
717 | )
718 | }
719 | };
720 | ItemDefs[ItemCatalog.FindItemIndex("SprintWisp")] = new ItemStatDef
721 | {
722 | Stats = new List
723 | {
724 | new ItemStat(
725 | (itemCount, ctx) => 3f * itemCount,
726 | (value, ctx) => $"Damage Boost: {value.FormatPercentage()}"
727 | )
728 | }
729 | };
730 | ItemDefs[ItemCatalog.FindItemIndex("Dagger")] = new ItemStatDef
731 | {
732 | Stats = new List
733 | {
734 | new ItemStat(
735 | (itemCount, ctx) => 1.5f * itemCount,
736 | (value, ctx) => $"Dagger Damage: {value.FormatPercentage()}"
737 | )
738 | }
739 | };
740 | ItemDefs[ItemCatalog.FindItemIndex("LunarUtilityReplacement")] = new ItemStatDef
741 | {
742 | Stats = new List
743 | {
744 | new ItemStat(
745 | (itemCount, ctx) => 3f * itemCount,
746 | (value, ctx) => $"Skill Duration: {value.FormatInt("s")}"
747 | ),
748 | new ItemStat(
749 | (itemCount, ctx) =>
750 | {
751 | var healthFraction = ctx.Master != null ? ctx.Master.GetBody().maxHealth : 1;
752 |
753 | // heal 1.3% of max HP per 5 times/s across 3 * itemCount seconds
754 | return healthFraction * 0.013f * 5 * (3f * itemCount);
755 | },
756 | (value, ctx) =>
757 | {
758 | var statValue = ctx.Master != null
759 | ? $"{value.FormatInt("HP")}"
760 | : $"{value.FormatPercentage()}";
761 | return "Health Healed: " + statValue;
762 | })
763 | }
764 | };
765 | ItemDefs[ItemCatalog.FindItemIndex("NearbyDamageBonus")] = new ItemStatDef
766 | {
767 | Stats = new List
768 | {
769 | new ItemStat(
770 | (itemCount, ctx) => 0.2f * itemCount,
771 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
772 | )
773 | }
774 | };
775 | ItemDefs[ItemCatalog.FindItemIndex("TPHealingNova")] = new ItemStatDef
776 | {
777 | Stats = new List
778 | {
779 | new ItemStat(
780 | (itemCount, ctx) => itemCount,
781 | (value, ctx) => $"Max Occurrences: {value.FormatInt()}"
782 | // StatModifiers.TpHealingNova
783 | )
784 | }
785 | };
786 | ItemDefs[ItemCatalog.FindItemIndex("ArmorReductionOnHit")] = new ItemStatDef
787 | {
788 | Stats = new List
789 | {
790 | new ItemStat(
791 | (itemCount, ctx) => 8f * itemCount,
792 | (value, ctx) => $"Duration: {value.FormatInt("s")}"
793 | )
794 | }
795 | };
796 | ItemDefs[ItemCatalog.FindItemIndex("Thorns")] = new ItemStatDef
797 | {
798 | Stats = new List
799 | {
800 | new ItemStat(
801 | (itemCount, ctx) => 5 + 2 * (itemCount - 1),
802 | (value, ctx) => $"Max Targets: {value.FormatInt()}"
803 | ),
804 | new ItemStat(
805 | (itemCount, ctx) => 25f + 10f * (itemCount - 1),
806 | (value, ctx) => $"Radius: {value.FormatInt("m")}"
807 | )
808 | }
809 | };
810 | ItemDefs[ItemCatalog.FindItemIndex("FlatHealth")] = new ItemStatDef
811 | {
812 | Stats = new List
813 | {
814 | new ItemStat(
815 | (itemCount, ctx) => 25 * itemCount,
816 | (value, ctx) => $"Health Increase: {value.FormatInt()}"
817 | )
818 | }
819 | };
820 | ItemDefs[ItemCatalog.FindItemIndex("Pearl")] = new ItemStatDef
821 | {
822 | Stats = new List
823 | {
824 | new ItemStat(
825 | (itemCount, ctx) => 0.1f * itemCount,
826 | (value, ctx) => $"Health Increase: {value.FormatPercentage()}"
827 | )
828 | }
829 | };
830 | ItemDefs[ItemCatalog.FindItemIndex("ShinyPearl")] = new ItemStatDef
831 | {
832 | Stats = new List
833 | {
834 | new ItemStat(
835 | (itemCount, ctx) => 0.1f * itemCount,
836 | (value, ctx) => $"Stat Increase: {value.FormatPercentage()}"
837 | )
838 | }
839 | };
840 | ItemDefs[ItemCatalog.FindItemIndex("BonusGoldPackOnKill")] = new ItemStatDef
841 | {
842 | Stats = new List
843 | {
844 | new ItemStat(
845 | (itemCount, ctx) => Run.instance.GetDifficultyScaledCost(25),
846 | (value, ctx) => $"per Drop: {value.FormatInt("$")}"
847 | ),
848 | new ItemStat(
849 | (itemCount, ctx) => 0.04f * itemCount,
850 | (value, ctx) => $"Drop Chance: {value.FormatPercentage()}"
851 | // StatModifiers.Luck
852 | )
853 | }
854 | };
855 | ItemDefs[ItemCatalog.FindItemIndex("LunarPrimaryReplacement")] = new ItemStatDef
856 | {
857 | Stats = new List
858 | {
859 | new ItemStat(
860 | (itemCount, ctx) => 12f * itemCount,
861 | (value, ctx) => $"Max Charges: {value.FormatInt()}"
862 | ),
863 | new ItemStat(
864 | (itemCount, ctx) => 2f * itemCount,
865 | (value, ctx) => $"Recharge Delay: {value.FormatInt("s")}"
866 | )
867 | }
868 | };
869 | ItemDefs[ItemCatalog.FindItemIndex("LaserTurbine")] = new ItemStatDef
870 | {
871 | Stats = new List
872 | {
873 | new ItemStat(
874 | (itemCount, ctx) => 3f * itemCount,
875 | (value, ctx) => $"Pierce Damage: {value.FormatPercentage()}"
876 | ),
877 | new ItemStat(
878 | (itemCount, ctx) => 10f * itemCount,
879 | (value, ctx) => $"Explosion Damage: {value.FormatPercentage()}"
880 | ),
881 | new ItemStat(
882 | (itemCount, ctx) => 3f * itemCount,
883 | (value, ctx) => $"On Return Damage: {value.FormatPercentage()}"
884 | )
885 | }
886 | };
887 | ItemDefs[ItemCatalog.FindItemIndex("NovaOnLowHealth")] = new ItemStatDef
888 | {
889 | Stats = new List
890 | {
891 | new ItemStat(
892 | (itemCount, ctx) => 30f / itemCount,
893 | (value, ctx) => $"Recharge Delay: {value.FormatInt("s")}"
894 | )
895 | }
896 | };
897 | ItemDefs[ItemCatalog.FindItemIndex("ArmorPlate")] = new ItemStatDef
898 | {
899 | Stats = new List
900 | {
901 | new ItemStat(
902 | (itemCount, ctx) => itemCount * 5,
903 | (value, ctx) => $"Reduced damage: {value.FormatInt()}"
904 | )
905 | }
906 | };
907 | ItemDefs[ItemCatalog.FindItemIndex("Squid")] = new ItemStatDef
908 | {
909 | Stats = new List
910 | {
911 | new ItemStat(
912 | (itemCount, ctx) => itemCount,
913 | (value, ctx) => $"Attack Speed: {value.FormatPercentage(0)}"
914 | )
915 | }
916 | };
917 | ItemDefs[ItemCatalog.FindItemIndex("DeathMark")] = new ItemStatDef
918 | {
919 | Stats = new List
920 | {
921 | new ItemStat(
922 | (itemCount, ctx) => itemCount * 0.5f,
923 | (value, ctx) => $"Increased Damage: {value.FormatPercentage()}"
924 | )
925 | }
926 | };
927 | ItemDefs[ItemCatalog.FindItemIndex("Plant")] = new ItemStatDef
928 | {
929 | Stats = new List
930 | {
931 | new ItemStat(
932 | (itemCount, ctx) => 3 + 1.5f * (itemCount - 1),
933 | (value, ctx) => $"Radius: {value.FormatInt("m", 1)}"
934 | )
935 | }
936 | };
937 | ItemDefs[ItemCatalog.FindItemIndex("FocusConvergence")] = new ItemStatDef
938 | {
939 | Stats = new List
940 | {
941 | new ItemStat(
942 | (itemCount, ctx) => 90f / (1f + 0.3f * Mathf.Min(itemCount, 3f)),
943 | (value, ctx) => $"Minimum Charge Time: {value.FormatInt("s")}"
944 | ),
945 | new ItemStat(
946 | (itemCount, ctx) => 1 / (2 * Mathf.Min(itemCount, 3f)),
947 | (value, ctx) => $"Zone Size: {value.FormatPercentage(2)}"
948 | )
949 | }
950 | };
951 | ItemDefs[ItemCatalog.FindItemIndex("DeathMark")] = new ItemStatDef
952 | {
953 | Stats = new List
954 | {
955 | new ItemStat(
956 | (itemCount, ctx) => 7f * itemCount,
957 | (value, ctx) => $"Debuff Duration: {value.FormatInt("s")}"
958 | )
959 | }
960 | };
961 | ItemDefs[ItemCatalog.FindItemIndex("Plant")] = new ItemStatDef
962 | {
963 | Stats = new List
964 | {
965 | new ItemStat(
966 | (itemCount, ctx) => 5f * itemCount,
967 | (value, ctx) => $"Healing Radius: {value.FormatInt("s")}"
968 | )
969 | }
970 | };
971 | ItemDefs[ItemCatalog.FindItemIndex("Squid")] = new ItemStatDef
972 | {
973 | Stats = new List
974 | {
975 | new ItemStat(
976 | (itemCount, ctx) => itemCount,
977 | (value, ctx) => $"Attack Speed: {value.FormatPercentage()}"
978 | )
979 | }
980 | };
981 | ItemDefs[ItemCatalog.FindItemIndex("CaptainDefenseMatrix")] = new ItemStatDef
982 | {
983 | Stats = new List
984 | {
985 | new ItemStat(
986 | (itemCount, ctx) => itemCount,
987 | (value, ctx) => $"Projectile Count: {value.FormatInt(" projectile(s)")}"
988 | )
989 | }
990 | };
991 | ItemDefs[ItemCatalog.FindItemIndex("CutHp")] = new ItemStatDef
992 | {
993 | Stats = new List
994 | {
995 | new ItemStat(
996 | (itemCount, ctx) => 1f / (itemCount + 1),
997 | (value, ctx) => $"Health Reduction: {value.FormatPercentage()}"
998 | )
999 | }
1000 | };
1001 | ItemDefs[ItemCatalog.FindItemIndex("Phasing")] = new ItemStatDef
1002 | {
1003 | Stats = new List
1004 | {
1005 | new ItemStat(
1006 | (itemCount, ctx) => 30 * Mathf.Pow(0.5f, itemCount - 1),
1007 | (value, ctx) => $"Cooldown: {value.FormatInt("s")}"
1008 | )
1009 | }
1010 | };
1011 | ItemDefs[ItemCatalog.FindItemIndex("FallBoots")] = new ItemStatDef
1012 | {
1013 | Stats = new List
1014 | {
1015 | new ItemStat(
1016 | (itemCount, ctx) => 10f * Mathf.Pow(0.5f, itemCount - 1),
1017 | (value, ctx) => $"Recharge Time: {value.FormatInt("s", 2)}"
1018 | )
1019 | }
1020 | };
1021 | ItemDefs[ItemCatalog.FindItemIndex("JumpBoost")] = new ItemStatDef
1022 | {
1023 | Stats = new List
1024 | {
1025 | new ItemStat(
1026 | (itemCount, ctx) => 10f * itemCount,
1027 | (value, ctx) => $"Boost Length: {value.FormatInt("m")}"
1028 | )
1029 | }
1030 | };
1031 | ItemDefs[ItemCatalog.FindItemIndex("LunarDagger")] = new ItemStatDef
1032 | {
1033 | Stats = new List
1034 | {
1035 | new ItemStat(
1036 | (itemCount, ctx) => Mathf.Pow(2f, itemCount),
1037 | (value, ctx) => $"Base Damage Increase: {value.FormatPercentage()}"
1038 | ),
1039 | new ItemStat(
1040 | (itemCount, ctx) => 1f / Mathf.Pow(2f, itemCount),
1041 | (value, ctx) => $"Max Health Reduction: {value.FormatPercentage()}"
1042 | )
1043 | }
1044 | };
1045 | ItemDefs[ItemCatalog.FindItemIndex("Incubator")] = new ItemStatDef
1046 | {
1047 | Stats = new List
1048 | {
1049 | new ItemStat(
1050 | (itemCount, ctx) => (7f + 1f * itemCount) / 100f,
1051 | (value, ctx) => $"Summon Chance: {value.FormatPercentage()}"
1052 | // StatModifiers.Luck
1053 | ),
1054 | new ItemStat(
1055 | (itemCount, ctx) => itemCount,
1056 | (value, ctx) => $"Base Health: {value.FormatPercentage()}"
1057 | )
1058 | }
1059 | };
1060 | ItemDefs[ItemCatalog.FindItemIndex("SiphonOnLowHealth")] = new ItemStatDef
1061 | {
1062 | Stats = new List
1063 | {
1064 | new ItemStat(
1065 | (itemCount, ctx) => itemCount,
1066 | (value, ctx) => $"Additional Enemies: {value.FormatInt()}"
1067 | )
1068 | }
1069 | };
1070 | ItemDefs[ItemCatalog.FindItemIndex("FireballsOnHit")] = new ItemStatDef
1071 | {
1072 | Stats = new List
1073 | {
1074 | new ItemStat(
1075 | (itemCount, ctx) => 3 * itemCount,
1076 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
1077 | ),
1078 | new ItemStat(
1079 | (itemCount, ctx) => 0.10f,
1080 | (value, ctx) => $"Proc Chance: {value.FormatPercentage()}"
1081 | )
1082 | }
1083 | };
1084 | ItemDefs[ItemCatalog.FindItemIndex("BleedOnHitAndExplode")] = new ItemStatDef
1085 | {
1086 | Stats = new List
1087 | {
1088 | new ItemStat(
1089 | (itemCount, ctx) => 4 * itemCount,
1090 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
1091 | ),
1092 | new ItemStat(
1093 | (itemCount, ctx) => 0.15f * itemCount,
1094 | (value, ctx) => $"Explosion Damage Increase: {value.FormatPercentage()}"
1095 | )
1096 | }
1097 | };
1098 | ItemDefs[ItemCatalog.FindItemIndex("MonstersOnShrineUse")] = new ItemStatDef
1099 | {
1100 | Stats = new List
1101 | {
1102 | new ItemStat(
1103 | (itemCount, ctx) => 0.4f * itemCount,
1104 | (value, ctx) => $"Enemy Difficulty Increase: {value.FormatPercentage()}"
1105 | ),
1106 | }
1107 | };
1108 | ItemDefs[ItemCatalog.FindItemIndex("RandomDamageZone")] = new ItemStatDef
1109 | {
1110 | Stats = new List
1111 | {
1112 | new ItemStat(
1113 | (itemCount, ctx) => 16f * Mathf.Pow(1.5f, itemCount - 1),
1114 | (value, ctx) => $"Radius Increase: {value.FormatInt("m")}"
1115 | ),
1116 | }
1117 | };
1118 | ItemDefs[ItemCatalog.FindItemIndex("LunarBadLuck")] = new ItemStatDef
1119 | {
1120 | Stats = new List
1121 | {
1122 | new ItemStat(
1123 | (itemCount, ctx) => itemCount,
1124 | (value, ctx) => $"Cooldown Reduction: {value.FormatInt("s")}"
1125 | ),
1126 | }
1127 | };
1128 | // Charged Perforator
1129 | ItemDefs[ItemCatalog.FindItemIndex("LightningStrikeOnHit")] = new ItemStatDef
1130 | {
1131 | Stats = new List
1132 | {
1133 | new ItemStat(
1134 | (itemCount, ctx) => 5f * itemCount,
1135 | (value, ctx) => $"Damage: {value.FormatPercentage()}"
1136 | ),
1137 | }
1138 | };
1139 | // Empathy Cores
1140 | ItemDefs[ItemCatalog.FindItemIndex("RoboBallBuddy")] = new ItemStatDef
1141 | {
1142 | Stats = new List
1143 | {
1144 | new ItemStat(
1145 | (itemCount, ctx) => 1f * itemCount,
1146 | (value, ctx) => $"Damage per Ally: {value.FormatPercentage()}"
1147 | ),
1148 | }
1149 | };
1150 | // Planula
1151 | ItemDefs[ItemCatalog.FindItemIndex("ParentEgg")] = new ItemStatDef
1152 | {
1153 | Stats = new List
1154 | {
1155 | new ItemStat(
1156 | (itemCount, ctx) => 15 * itemCount,
1157 | (value, ctx) => $"Heal Amount: {value.FormatInt(" HP")}"
1158 | ),
1159 | }
1160 | };
1161 | // Essence of Heresy
1162 | ItemDefs[ItemCatalog.FindItemIndex("LunarSpecialReplacement")] = new ItemStatDef
1163 | {
1164 | Stats = new List
1165 | {
1166 | new ItemStat(
1167 | (itemCount, ctx) => 10 * itemCount,
1168 | (value, ctx) => $"Effect Duration: {value.FormatInt("s")}"
1169 | ),
1170 | new ItemStat(
1171 | (itemCount, ctx) => 8 * itemCount,
1172 | (value, ctx) => $"Cooldown: {value.FormatInt("s")}"
1173 | ),
1174 | }
1175 | };
1176 | // Hooks of Heresy
1177 | ItemDefs[ItemCatalog.FindItemIndex("LunarSecondaryReplacement")] = new ItemStatDef
1178 | {
1179 | Stats = new List
1180 | {
1181 | new ItemStat(
1182 | (itemCount, ctx) => 3 * itemCount,
1183 | (value, ctx) => $"Root Duration: {value.FormatInt("s")}"
1184 | ),
1185 | new ItemStat(
1186 | (itemCount, ctx) => 5 * itemCount,
1187 | (value, ctx) => $"Cooldown: {value.FormatInt("s")}"
1188 | ),
1189 | }
1190 | };
1191 | // Hunter's Harpoon
1192 | ItemDefs[ItemCatalog.FindItemIndex("MoveSpeedOnKill")] = new ItemStatDef
1193 | {
1194 | Stats = new List
1195 | {
1196 | new ItemStat(
1197 | (itemCount, ctx) => 1f + (itemCount - 1f) * 0.5f,
1198 | (value, ctx) => $"Total Duration: {value.FormatInt("s", 1)}"
1199 | ),
1200 | }
1201 | };
1202 | // Symbiotic Scorpion
1203 | ItemDefs[ItemCatalog.FindItemIndex("PermanentDebuffOnHit")] = new ItemStatDef
1204 | {
1205 | Stats = new List
1206 | {
1207 | new ItemStat(
1208 | (itemCount, ctx) => 1f + (itemCount - 1f) * 0.5f,
1209 | (value, ctx) => $"Armor Reduction: {value.FormatInt()}"
1210 | ),
1211 | }
1212 | };
1213 | // Mocha
1214 | ItemDefs[ItemCatalog.FindItemIndex("AttackSpeedAndMoveSpeed")] = new ItemStatDef
1215 | {
1216 | Stats = new List
1217 | {
1218 | new ItemStat(
1219 | (itemCount, ctx) => itemCount * 0.075f,
1220 | (value, ctx) => $"Attack Speed Increase: {value.FormatPercentage()}"
1221 | ),
1222 | new ItemStat(
1223 | (itemCount, ctx) => itemCount * 0.07f,
1224 | (value, ctx) => $"Move Speed Increase {value.FormatPercentage()}"
1225 | ),
1226 | }
1227 | };
1228 | // Laser Scope
1229 | ItemDefs[ItemCatalog.FindItemIndex("CritDamage")] = new ItemStatDef
1230 | {
1231 | Stats = new List
1232 | {
1233 | new ItemStat(
1234 | (itemCount, ctx) => itemCount,
1235 | (value, ctx) => $"Additional Damage {value.FormatPercentage()}"
1236 | ),
1237 | }
1238 | };
1239 | // Safer Spaces
1240 | ItemDefs[ItemCatalog.FindItemIndex("BearVoid")] = new ItemStatDef
1241 | {
1242 | Stats = new List
1243 | {
1244 | new ItemStat(
1245 | (itemCount, ctx) => Mathf.CeilToInt(15f * Mathf.Pow(0.9f, itemCount)),
1246 | (value, ctx) => $"Recharge Duration {value.FormatInt("s", 1)}"
1247 | ),
1248 | },
1249 | };
1250 | // Weeping Fungus
1251 | ItemDefs[ItemCatalog.FindItemIndex("MushroomVoid")] = new ItemStatDef
1252 | {
1253 | Stats = new List
1254 | {
1255 | new ItemStat(
1256 | (itemCount, ctx) => 0.01f * itemCount,
1257 | (value, ctx) => $"Heal Amount: {value.FormatPercentage()}"
1258 | ),
1259 | },
1260 | AdditionalText = "Description is incorrect".SetColor("red")
1261 | };
1262 | // Benthic Bloom
1263 | ItemDefs[ItemCatalog.FindItemIndex("CloverVoid")] = new ItemStatDef
1264 | {
1265 | Stats = new List
1266 | {
1267 | new ItemStat(
1268 | (itemCount, ctx) => 3f * itemCount,
1269 | (value, ctx) => $"Items Upgraded: {value.FormatInt()}"
1270 | ),
1271 | },
1272 | };
1273 | // Ignition Tank
1274 | ItemDefs[ItemCatalog.FindItemIndex("StrengthenBurn")] = new ItemStatDef
1275 | {
1276 | Stats = new List
1277 | {
1278 | new ItemStat(
1279 | (itemCount, ctx) => 3f * itemCount,
1280 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
1281 | ),
1282 | },
1283 | };
1284 | // Needletick
1285 | ItemDefs[ItemCatalog.FindItemIndex("BleedOnHitVoid")] = new ItemStatDef
1286 | {
1287 | Stats = new List
1288 | {
1289 | new ItemStat(
1290 | (itemCount, ctx) => 0.1f * itemCount,
1291 | (value, ctx) => $"Collapse Chance: {value.FormatPercentage()}"
1292 | ),
1293 | },
1294 | };
1295 | // Lost Seer's Lenses
1296 | ItemDefs[ItemCatalog.FindItemIndex("CritGlassesVoid")] = new ItemStatDef
1297 | {
1298 | Stats = new List
1299 | {
1300 | new ItemStat(
1301 | (itemCount, ctx) => 0.05f * itemCount,
1302 | (value, ctx) => $"Instakill Chance: {value.FormatPercentage()}"
1303 | ),
1304 | },
1305 | };
1306 | // Tentabauble
1307 | ItemDefs[ItemCatalog.FindItemIndex("SlowOnHitVoid")] = new ItemStatDef
1308 | {
1309 | Stats = new List
1310 | {
1311 | new ItemStat(
1312 | (itemCount, ctx) => itemCount * 0.05f,
1313 | (value, ctx) => $"Root Chance: {value.FormatPercentage()}"
1314 | ),
1315 | new ItemStat(
1316 | (itemCount, ctx) => itemCount,
1317 | (value, ctx) => $"Root Duration: {value.FormatInt("s")}"
1318 | ),
1319 | },
1320 | };
1321 | // Plasma Shrimp
1322 | ItemDefs[ItemCatalog.FindItemIndex("Plasma Shrimp")] = new ItemStatDef
1323 | {
1324 | Stats = new List
1325 | {
1326 | new ItemStat(
1327 | (itemCount, ctx) => itemCount * 0.4f,
1328 | (value, ctx) => $"Total Damage: {value.FormatPercentage()}"
1329 | ),
1330 | new ItemStat(
1331 | (itemCount, ctx) => 1,
1332 | (value, ctx) => $"Missile Count: {value.FormatInt()}"
1333 | ),
1334 | },
1335 | };
1336 | // Polylute
1337 | ItemDefs[ItemCatalog.FindItemIndex("ChainLightningVoid")] = new ItemStatDef
1338 | {
1339 | Stats = new List
1340 | {
1341 | new ItemStat(
1342 | (itemCount, ctx) => itemCount * 3f,
1343 | (value, ctx) => $"Total Strikes: {value.FormatInt()}"
1344 | ),
1345 | },
1346 | };
1347 | // Lysate Cell
1348 | ItemDefs[ItemCatalog.FindItemIndex("EquipmentMagazineVoid")] = new ItemStatDef
1349 | {
1350 | Stats = new List
1351 | {
1352 | new ItemStat(
1353 | (itemCount, ctx) => itemCount,
1354 | (value, ctx) => $"Bonus Charges: {value.FormatInt()}"
1355 | ),
1356 | },
1357 | };
1358 | // Voidsent Flame
1359 | ItemDefs[ItemCatalog.FindItemIndex("ExplodeOnDeathVoid")] = new ItemStatDef
1360 | {
1361 | Stats = new List
1362 | {
1363 | new ItemStat(
1364 | (itemCount, ctx) => 3.5f * (1f + (itemCount - 1) * 0.8f),
1365 | (value, ctx) => $"Base Damage: {value.FormatPercentage()}"
1366 | ),
1367 | new ItemStat(
1368 | (itemCount, ctx) => 12f + 2.4f * (itemCount - 1f),
1369 | (value, ctx) => $"Radius: {value.FormatInt("m")}"
1370 | ),
1371 | },
1372 | };
1373 | // Delicate Watch
1374 | ItemDefs[ItemCatalog.FindItemIndex("FragileDamageBonus")] = new ItemStatDef
1375 | {
1376 | Stats = new List
1377 | {
1378 | new ItemStat(
1379 | (itemCount, ctx) => itemCount * 0.2f,
1380 | (value, ctx) => $"Damage Increase: {value.FormatPercentage()}"
1381 | ),
1382 | },
1383 | };
1384 | }
1385 | }
1386 | }
1387 |
--------------------------------------------------------------------------------
/ItemStats/src/ItemStatProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using RoR2;
3 |
4 | namespace ItemStats
5 | {
6 | internal static partial class ItemStatProvider
7 | {
8 | private static readonly Dictionary ItemDefs;
9 |
10 | private static readonly Dictionary CustomItemDefs;
11 |
12 | public static string ProvideStatsForItem(ItemIndex index, int count, StatContext context)
13 | {
14 | var itemStatDef = GetItemStatDef(index);
15 |
16 | return itemStatDef != null ? itemStatDef.ProcessItem(index, count, context) : "";
17 | }
18 |
19 | public static void AddCustomItemDef(ItemIndex idx, ItemStatDef customDef)
20 | {
21 | CustomItemDefs[idx] = customDef;
22 | }
23 |
24 | public static ItemStatDef GetItemStatDef(ItemIndex index)
25 | {
26 | if (!ItemDefs.TryGetValue(index, out var itemStatDef)) CustomItemDefs.TryGetValue(index, out itemStatDef);
27 |
28 | return itemStatDef;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/ItemStats/src/ItemStatsMod.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using BepInEx;
3 | using BepInEx.Configuration;
4 | using BepInEx.Logging;
5 | using ItemStats.StatModification;
6 | using R2API.Utils;
7 | using RoR2;
8 |
9 | namespace ItemStats
10 | {
11 | [BepInDependency("com.bepis.r2api")]
12 | [BepInPlugin("dev.ontrigger.itemstats", "ItemStats", "2.2.1")]
13 | [NetworkCompatibility(CompatibilityLevel.NoNeedForSync, VersionStrictness.DifferentModVersionsAreOk)]
14 | public class ItemStatsMod : BaseUnityPlugin
15 | {
16 | internal new static ManualLogSource Logger { get; private set; }
17 |
18 | public static ConfigEntry DetailedPickupDescriptions;
19 |
20 | private ItemStatsMod()
21 | {
22 | Logger = base.Logger;
23 | }
24 |
25 | public void Awake()
26 | {
27 | InitConfig();
28 |
29 | ItemCatalog.availability.CallWhenAvailable(() =>
30 | {
31 | ItemStatProvider.Init();
32 | StatModifiers.Init();
33 | Hooks.Init();
34 | });
35 | }
36 |
37 | private void InitConfig()
38 | {
39 | DetailedPickupDescriptions = Config.Bind(
40 | "Settings",
41 | "DetailedPickupDescriptions",
42 | true,
43 | "Toggle displaying full item descriptions in the pickup popup"
44 | );
45 | }
46 |
47 | public static void AddCustomItemStatDef(ItemIndex index, ItemStatDef customDef)
48 | {
49 | ItemStatProvider.AddCustomItemDef(index, customDef);
50 | }
51 |
52 | public static ItemStatDef GetItemStatDef(ItemIndex index)
53 | {
54 | return ItemStatProvider.GetItemStatDef(index);
55 | }
56 |
57 | public static string GetStatsForItem(ItemIndex index, int count, StatContext context)
58 | {
59 | return ItemStatProvider.ProvideStatsForItem(index, count, context);
60 | }
61 |
62 | public static void AddStatModifier(AbstractStatModifier modifier)
63 | {
64 | StatModifiers.AddStatModifier(modifier);
65 | }
66 |
67 | public static List GetModifiersForItemIndex(ItemIndex index)
68 | {
69 | return StatModifiers.GetModifiersForItemIndex(index);
70 | }
71 |
72 | public static List GetModifiersForItemDef(ItemStatDef itemStatDef)
73 | {
74 | return StatModifiers.GetModifiersForItemDef(itemStatDef);
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/ItemStats/src/Stat/IStat.cs:
--------------------------------------------------------------------------------
1 | namespace ItemStats.Stat
2 | {
3 | public interface IStat
4 | {
5 | float? GetInitialStat(float count, StatContext context);
6 |
7 | string Format(float statValue, StatContext context);
8 | }
9 | }
--------------------------------------------------------------------------------
/ItemStats/src/Stat/ItemStat.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ItemStats.Stat
4 | {
5 | public class ItemStat : IStat
6 | {
7 | public Func Formula { get; }
8 | public Func Formatter { get; }
9 |
10 | public ItemStat(Func formula, Func formatter)
11 | {
12 | Formula = formula;
13 | Formatter = formatter;
14 | }
15 |
16 | public float? GetInitialStat(float count, StatContext context)
17 | {
18 | try
19 | {
20 | return Formula(count, context);
21 | }
22 | catch (NullReferenceException e)
23 | {
24 | ItemStatsMod.Logger.LogError("Caught " + e);
25 | }
26 |
27 | return null;
28 | }
29 |
30 | public string Format(float statValue, StatContext context)
31 | {
32 | return Formatter(statValue, context);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/ItemStats/src/Stat/ItemStatDef.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ItemStats.Stat;
3 | using ItemStats.StatCalculation;
4 | using ItemStats.StatModification;
5 | using RoR2;
6 |
7 | namespace ItemStats
8 | {
9 | public class ItemStatDef
10 | {
11 | public IStatCalculationStrategy StatCalculationStrategy = new DefaultStatCalculationStrategy();
12 |
13 | public List Stats;
14 |
15 | // additional text that only appears on stat tooltip and not the logbook
16 | public string AdditionalText;
17 |
18 | public string ProcessItem(ItemIndex index, int count, StatContext context)
19 | {
20 | return StatCalculationStrategy.ProcessItem(this, index, count, context);
21 | }
22 | }
23 |
24 | public static class ItemStatDefExtensions
25 | {
26 | public static List GetStatModifiers(this ItemStatDef statDef)
27 | {
28 | return StatModifiers.GetModifiersForItemDef(statDef);
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/ItemStats/src/Stat/StatContext.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 | using RoR2;
3 |
4 | namespace ItemStats
5 | {
6 | public class StatContext
7 | {
8 | public StatContext([CanBeNull] CharacterMaster master)
9 | {
10 | Master = master;
11 | Inventory = master != null ? master.inventory : null;
12 | }
13 |
14 | [CanBeNull] public CharacterMaster Master { get; }
15 | [CanBeNull] public Inventory Inventory { get; }
16 | }
17 |
18 | public static class StatContextExtensions
19 | {
20 | public static int CountItems(this StatContext ctx, ItemIndex idx)
21 | {
22 | return ctx.Inventory != null ? ctx.Inventory.GetItemCount(idx) : 0;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatCalculation/DefaultStatCalculationStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using RoR2;
4 |
5 | namespace ItemStats.StatCalculation
6 | {
7 | public class DefaultStatCalculationStrategy : IStatCalculationStrategy
8 | {
9 | public string ProcessItem(ItemStatDef statDef, ItemIndex itemIndex, int count, StatContext context)
10 | {
11 | var fullStatText = new StringBuilder();
12 | fullStatText.AppendLine();
13 |
14 | if (statDef.AdditionalText != null)
15 | {
16 | fullStatText.AppendLine();
17 | fullStatText.Append(statDef.AdditionalText);
18 | fullStatText.AppendLine();
19 | }
20 |
21 | var statList = statDef.Stats;
22 | var modifierList = statDef.GetStatModifiers();
23 |
24 | for (var statIndex = 0; statIndex < statList.Count; statIndex++)
25 | {
26 | var stat = statList[statIndex];
27 | var m = stat.GetInitialStat(count, context);
28 | if (!m.HasValue) continue;
29 | var originalValue = m.Value;
30 |
31 | var lastLine = statIndex == statList.Count - 1;
32 |
33 | var modifiedValueSum = 0f;
34 | var formattedContributions = new StringBuilder();
35 |
36 | foreach (var statModifier in modifierList)
37 | {
38 | if (!statModifier.AffectsItem(itemIndex, statIndex)) continue;
39 |
40 | m = statModifier.ModifyValue(originalValue, itemIndex, statIndex, context);
41 |
42 | var modifierContribution = (float) m - originalValue;
43 |
44 | // skip modifiers that contrib less that 1% to the final value
45 | if (!ContributionSignificant(modifierContribution)) continue;
46 |
47 | formattedContributions.AppendLine();
48 | formattedContributions.Append(" ");
49 | formattedContributions.Append(
50 | statModifier.Format(modifierContribution, itemIndex, statIndex, context)
51 | );
52 |
53 | modifiedValueSum += modifierContribution;
54 | }
55 |
56 | var finalFormattedValue = stat.Format(originalValue + modifiedValueSum, context);
57 |
58 | // explicitly align left on the last line to fix the stack counter alignment
59 | var lastLineAlignment = lastLine ? "" : "";
60 |
61 | fullStatText.AppendLine();
62 | fullStatText.Append(lastLineAlignment + finalFormattedValue);
63 | fullStatText.Append(formattedContributions);
64 | }
65 |
66 |
67 | return fullStatText.Append($"
({count} stacks)").ToString();
68 | }
69 |
70 | private static bool ContributionSignificant(float contrib)
71 | {
72 | return Math.Round(Math.Abs(contrib), 4) > 0;
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatCalculation/IStatCalculationStrategy.cs:
--------------------------------------------------------------------------------
1 | using RoR2;
2 |
3 | namespace ItemStats.StatCalculation
4 | {
5 | public interface IStatCalculationStrategy
6 | {
7 | string ProcessItem(ItemStatDef statDef, ItemIndex itemIndex, int count, StatContext context);
8 | }
9 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatModification/AbstractStatModifier.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using RoR2;
5 |
6 | namespace ItemStats.StatModification
7 | {
8 | public abstract class AbstractStatModifier : IStatModifier
9 | {
10 | protected abstract Func ModifyValueFunc { get; }
11 |
12 | protected virtual Func ModifyItemCountFunc =>
13 | (itemCount, itemIndex, itemStatIndex, context) => itemCount;
14 |
15 | protected abstract Func FormatFunc { get; }
16 |
17 | public abstract Dictionary> AffectedItems { get; }
18 |
19 | public float ModifyValue(float result, ItemIndex itemIndex, int statIndex, StatContext context)
20 | {
21 | return ModifyValueFunc(result, itemIndex, statIndex, context);
22 | }
23 |
24 | public int ModifyItemCount(int count, ItemIndex itemIndex, int statIndex, StatContext context)
25 | {
26 | return ModifyItemCountFunc(count, itemIndex, statIndex, context);
27 | }
28 |
29 | public string Format(float result, ItemIndex itemIndex, int statIndex, StatContext context)
30 | {
31 | return FormatFunc(result, itemIndex, statIndex, context);
32 | }
33 |
34 | public bool AffectsItem(ItemIndex itemIndex, int statIndex)
35 | {
36 | if (AffectedItems.TryGetValue(itemIndex, out var affectedStats))
37 | {
38 | return affectedStats.Contains(statIndex);
39 | }
40 |
41 | ;
42 |
43 | return false;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatModification/IStatModifier.cs:
--------------------------------------------------------------------------------
1 | using RoR2;
2 |
3 | namespace ItemStats.StatModification
4 | {
5 | public interface IStatModifier
6 | {
7 | float ModifyValue(float result, ItemIndex itemIndex, int statIndex, StatContext context);
8 |
9 | int ModifyItemCount(int count, ItemIndex itemIndex, int statIndex, StatContext context);
10 |
11 | string Format(float result, ItemIndex itemIndex, int statIndex, StatContext context);
12 |
13 | bool AffectsItem(ItemIndex itemIndex, int statIndex);
14 | }
15 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatModification/Modifiers/HealingIncreaseModifier.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using ItemStats.ValueFormatters;
4 | using RoR2;
5 |
6 | namespace ItemStats.StatModification
7 | {
8 | public class HealingIncreaseModifier : AbstractStatModifier
9 | {
10 | protected override Func ModifyValueFunc =>
11 | (result, itemIndex, itemStatIndex, context) =>
12 | result * (1 + context.CountItems(ItemCatalog.FindItemIndex("IncreaseHealing")));
13 |
14 | protected override Func FormatFunc =>
15 | (result, itemIndex, itemStatIndex, ctx) =>
16 | {
17 | string formattedResult;
18 | if (itemIndex == ItemCatalog.FindItemIndex("Mushroom") || itemIndex == ItemCatalog.FindItemIndex("Tooth"))
19 | {
20 | formattedResult = result.FormatPercentage(signed: true, color: Colors.ModifierColor);
21 | }
22 | else
23 | {
24 | formattedResult = result.FormatInt(signed: true, color: Colors.ModifierColor, postfix: "HP");
25 | }
26 |
27 | return $"{formattedResult} from Rejuvenation Rack";
28 | };
29 |
30 | public override Dictionary> AffectedItems =>
31 | new Dictionary>
32 | {
33 | [ItemCatalog.FindItemIndex("Mushroom")] = new[] {0},
34 | [ItemCatalog.FindItemIndex("HealWhileSafe")] = new[] {0},
35 | [ItemCatalog.FindItemIndex("Medkit")] = new[] {0},
36 | [ItemCatalog.FindItemIndex("Tooth")] = new[] {0},
37 | [ItemCatalog.FindItemIndex("HealOnCrit")] = new[] {0},
38 | [ItemCatalog.FindItemIndex("Seed")] = new[] {0},
39 | [ItemCatalog.FindItemIndex("Knurl")] = new[] {1}
40 | };
41 | }
42 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatModification/Modifiers/LuckModifier.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using ItemStats.ValueFormatters;
5 | using RoR2;
6 | using UnityEngine;
7 |
8 | namespace ItemStats.StatModification
9 | {
10 | public class LuckModifier : AbstractStatModifier
11 | {
12 | protected override Func ModifyValueFunc =>
13 | (result, itemIndex, itemStatIndex, context) =>
14 | {
15 | // if chance is already >= 100% then return same value so
16 | // that there are no contribution stats
17 | if (result >= 1)
18 | {
19 | return result;
20 | }
21 |
22 | var cloverCount = context.CountItems(ItemCatalog.FindItemIndex("Clover"));
23 | var purityCount = context.CountItems(ItemCatalog.FindItemIndex("LunarBadLuck"));
24 |
25 | var luck = cloverCount - purityCount;
26 | if (luck > 0)
27 | {
28 | return 1 - Mathf.Pow(1 - result, 1 + luck);
29 | }
30 |
31 | return (float) Math.Round(Math.Pow(result, 1 + Math.Abs(luck)), 4);
32 | };
33 |
34 | protected override Func FormatFunc =>
35 | (result, itemIndex, itemStatIndex, ctx) =>
36 | {
37 | // TODO: pass the original value to be able to properly show clover and purity contribution
38 | var itemCount = ctx.CountItems(itemIndex);
39 | if (itemCount <= 0)
40 | {
41 | return $"{result.FormatPercentage(signed: true, color: Colors.ModifierColor)} from luck";
42 | }
43 |
44 | var itemStatDef = ItemStatsMod.GetItemStatDef(itemIndex);
45 | var itemStat = itemStatDef.Stats[itemStatIndex];
46 |
47 | // ReSharper disable once PossibleInvalidOperationException
48 | var originalValue = Mathf.Clamp01(itemStat.GetInitialStat(itemCount, ctx).Value);
49 |
50 | var cloverCount = ctx.CountItems(ItemCatalog.FindItemIndex("Clover"));
51 | var purityCount = ctx.CountItems(ItemCatalog.FindItemIndex("LunarBadLuck"));
52 |
53 | var cloverContribution = 1 - Mathf.Pow(1 - originalValue, 1 + cloverCount) - originalValue;
54 |
55 | var purityContribution =
56 | (float) Math.Round(Mathf.Pow(originalValue, 1 + purityCount), 3) - originalValue;
57 |
58 | var stringBuilder = new StringBuilder();
59 |
60 | if (cloverCount > 0)
61 | {
62 | stringBuilder
63 | .Append(cloverContribution.FormatPercentage(signed: true, color: Colors.ModifierColor))
64 | .Append(" from Clover");
65 |
66 | if (purityCount > 0) stringBuilder.AppendLine().Append(" ");
67 | }
68 |
69 | if (purityCount > 0)
70 | {
71 | stringBuilder
72 | .Append(purityContribution.FormatPercentage(signed: true, color: Colors.ModifierColor))
73 | .Append(" from Purity");
74 | }
75 |
76 | return stringBuilder.ToString();
77 | };
78 |
79 | public override Dictionary> AffectedItems =>
80 | new Dictionary>
81 | {
82 | [ItemCatalog.FindItemIndex("GhostOnKill")] = new[] {1},
83 | [ItemCatalog.FindItemIndex("StunChanceOnHit")] = new[] {0},
84 | [ItemCatalog.FindItemIndex("BleedOnHit")] = new[] {0},
85 | [ItemCatalog.FindItemIndex("GoldOnHit")] = new[] {1},
86 | [ItemCatalog.FindItemIndex("ChainLightning")] = new[] {2},
87 | [ItemCatalog.FindItemIndex("BounceNearby")] = new[] {0},
88 | [ItemCatalog.FindItemIndex("StickyBomb")] = new[] {0},
89 | [ItemCatalog.FindItemIndex("Missile")] = new[] {1},
90 | [ItemCatalog.FindItemIndex("BonusGoldPackOnKill")] = new[] {1},
91 | [ItemCatalog.FindItemIndex("Incubator")] = new[] {0},
92 | [ItemCatalog.FindItemIndex("FireballsOnHit")] = new[] {1}
93 | };
94 | }
95 | }
--------------------------------------------------------------------------------
/ItemStats/src/StatModification/StatModifiers.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using RoR2;
3 |
4 | namespace ItemStats.StatModification
5 | {
6 | internal static class StatModifiers
7 | {
8 | private static readonly Dictionary> ModifierDefs;
9 |
10 | static StatModifiers()
11 | {
12 | ModifierDefs = new Dictionary>();
13 | }
14 |
15 | public static void Init()
16 | {
17 | AddStatModifier(new LuckModifier());
18 | AddStatModifier(new HealingIncreaseModifier());
19 | }
20 |
21 | public static void AddStatModifier(AbstractStatModifier modifier)
22 | {
23 | foreach (var itemIndex in modifier.AffectedItems.Keys)
24 | {
25 | var itemStatDef = ItemStatProvider.GetItemStatDef(itemIndex);
26 | if (itemStatDef == null)
27 | {
28 | throw new KeyNotFoundException($"Affected ItemStatDef with ItemIndex ${itemIndex} not found");
29 | }
30 |
31 | if (ModifierDefs.TryGetValue(itemStatDef, out var existingEntry))
32 | {
33 | if (!existingEntry.Contains(modifier))
34 | {
35 | existingEntry.Add(modifier);
36 | }
37 | }
38 | else
39 | {
40 | ModifierDefs[itemStatDef] = new List {modifier};
41 | }
42 | }
43 | }
44 |
45 | public static List GetModifiersForItemIndex(ItemIndex itemIndex)
46 | {
47 | var itemStatDef = ItemStatProvider.GetItemStatDef(itemIndex);
48 | if (itemStatDef == null)
49 | {
50 | throw new KeyNotFoundException($"ItemStatDef with ItemIndex ${itemIndex} not found");
51 | }
52 |
53 | return GetModifiersForItemDef(itemStatDef);
54 | }
55 |
56 | public static List GetModifiersForItemDef(ItemStatDef itemStatDef)
57 | {
58 | ModifierDefs.TryGetValue(itemStatDef, out var existingEntry);
59 | return existingEntry ?? new List();
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/ItemStats/src/ValueFormatters/Colors.cs:
--------------------------------------------------------------------------------
1 | namespace ItemStats.ValueFormatters
2 | {
3 | public static class Colors
4 | {
5 | public const string Green = "\"green\"";
6 | public const string Blue = "\"blue\"";
7 | public const string Black = "\"black\"";
8 | public const string Orange = "\"orange\"";
9 | public const string Purple = "\"purple\"";
10 | public const string Red = "\"red\"";
11 | public const string White = "\"white\"";
12 | public const string Yellow = "\"yellow\"";
13 | public const string ModifierColor = "#FFB6C1";
14 | }
15 | }
--------------------------------------------------------------------------------
/ItemStats/src/ValueFormatters/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RoR2;
3 | using UnityEngine;
4 |
5 | namespace ItemStats.ValueFormatters
6 | {
7 | public static class Extensions
8 | {
9 | public static string WrapIn(this string str, string start, string end = "")
10 | {
11 | return String.Concat(start, str, end);
12 | }
13 |
14 | public static string SetColor(this string str, string color)
15 | {
16 | return str.WrapIn($"", "");
17 | }
18 |
19 | public static int CountItems(this CharacterBody body, ItemIndex index)
20 | {
21 | return body != null ? body.inventory.GetItemCount(index) : 0;
22 | }
23 |
24 | public static string FormatInt(
25 | this float value, string postfix = "",
26 | int decimals = 0, bool signed = false,
27 | string color = Colors.Green)
28 | {
29 | if (!signed) value = Mathf.Abs(value);
30 | var sign = signed && value > 0 ? "+" : "";
31 |
32 | return $"{sign}{Math.Round(value, decimals)}{postfix}".SetColor(color);
33 | }
34 |
35 | public static string FormatPercentage(
36 | this float value, int decimalPlaces = 1,
37 | float scale = 100f, float maxValue = float.MaxValue,
38 | bool signed = false, string color = Colors.Green)
39 | {
40 | // color light blue
41 | var maxStackMessage = value >= maxValue ? "(Max Stack)".SetColor("#ADD8E6") : "";
42 | value = Mathf.Min(value, maxValue);
43 | if (!signed) value = Mathf.Abs(value);
44 |
45 | // amount of ###
46 | var trailFormatStr = new string('#', decimalPlaces);
47 | var valueStr = Math.Round(value * scale, decimalPlaces).ToString($"0.{trailFormatStr}");
48 | valueStr += "%";
49 |
50 | var sign = signed && value > 0 ? "+" : "";
51 |
52 | return $"{sign}{valueStr}".SetColor(color) + $" {maxStackMessage}";
53 | }
54 |
55 | public static string FormatModifier(this float value, string statText = "", string color = "#FFB6C1")
56 | {
57 | var sign = value >= 0 ? "+" : "-";
58 | //TODO: turn color into a separate decorator and get rid of this
59 | var trailFormatStr = new string('#', 1);
60 | var valueStr = Math.Round(value * 100f, 1).ToString($"0.{trailFormatStr}");
61 | valueStr += "%";
62 |
63 | return " " + $"{sign}{valueStr} ".SetColor(color);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/ItemStats/src/ValueFormatters/IStatFormatter.cs:
--------------------------------------------------------------------------------
1 | namespace ItemStats.ValueFormatters
2 | {
3 | public interface IStatFormatter
4 | {
5 | string Format(float value);
6 | }
7 | }
--------------------------------------------------------------------------------
/ItemStats/src/ValueFormatters/ModifierFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ItemStats.ValueFormatters
4 | {
5 | public class ModifierFormatter : IStatFormatter
6 | {
7 | private readonly string _color;
8 | private readonly string _statText;
9 |
10 | public ModifierFormatter(string statText = "", string color = "#FFB6C1")
11 | {
12 | _statText = statText;
13 | _color = color;
14 | }
15 |
16 | public string Format(float value)
17 | {
18 | var sign = value >= 0 ? "+" : "-";
19 | //TODO: turn color into a separate decorator and get rid of this
20 | var trailFormatStr = new string('#', 1);
21 | var valueStr = Math.Round(value * 100f, 1).ToString($"0.{trailFormatStr}");
22 | valueStr += "%";
23 |
24 | return " " + $"{sign}{valueStr} ".SetColor(_color) + _statText;
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 ontrigger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ItemStats 2.2.1
2 | Provides current stack bonuses for an item in the tooltip
3 |
4 | 
5 |
6 | # Installation
7 | First thing you'll need is the BepInExPack. If you don't have it, get it [here](https://thunderstore.io/package/bbepis/BepInExPack/) and follow the installation instructions.
8 |
9 | Afterwards, get the zip file from releases and extract the ItemStats folder in BepInEx/plugins. That's it!
10 |
11 | # Custom Item API
12 | In order to use API, add a Bepin SoftDependency to your BaseUnityPlugin annotations.
13 | Make sure to check if ItemStats is loaded before trying to use it
14 |
15 | Then, create an ItemStatDef for your item (see ItemStatDefinitions.cs for examples).
16 |
17 | Use the R2API ItemApi Submodule to add your item and get its ItemIndex.
18 |
19 | After that, simply call `ItemStatsMod.AddCustomItemStatDef(myItemIndex, myItemStatDef)`
20 |
21 | To create an item stat modifier, extend from `AbstractStatModifier`. Check ItemStats.StatModification.Modifiers for examples
22 | Add an instance of the class with `ItemStatsMod.AddStatModifier(new MyStatModifier())`
23 |
24 | # Building
25 |
26 | You will first need the following libraries:
27 |
28 | * Assembly-CSharp (duh)
29 | * UnityEngine
30 | * UnityEngine.CoreModule
31 | * BepInEx
32 | * MMHOOK_Assembly-CSharp
33 | * R2API
34 |
35 | Open the solution, link the libraries in the Lib folder, DL the required NuGet package and compile.
36 |
37 | # Contributors
38 |
39 | * kylewill0725
40 | * orare
41 | * XuaTheGrate
42 |
--------------------------------------------------------------------------------