-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathItemManager.cs
2081 lines (1823 loc) · 82.7 KB
/
ItemManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using JetBrains.Annotations;
using UnityEngine;
namespace ItemManager;
[PublicAPI]
public enum CraftingTable
{
Disabled,
Inventory,
[InternalName("piece_workbench")] Workbench,
[InternalName("piece_cauldron")] Cauldron,
[InternalName("piece_MeadCauldron")] MeadCauldron,
[InternalName("forge")] Forge,
[InternalName("piece_artisanstation")] ArtisanTable,
[InternalName("piece_stonecutter")] StoneCutter,
[InternalName("piece_magetable")] MageTable,
[InternalName("piece_preptable")] PrepTable,
[InternalName("blackforge")] BlackForge,
Custom,
}
[PublicAPI]
public enum ConversionPiece
{
Disabled,
[InternalName("smelter")] Smelter,
[InternalName("charcoal_kiln")] CharcoalKiln,
[InternalName("blastfurnace")] BlastFurnace,
[InternalName("windmill")] Windmill,
[InternalName("piece_spinningwheel")] SpinningWheel,
[InternalName("eitrrefinery")] EitrRefinery,
Custom,
}
public class InternalName : Attribute
{
public readonly string internalName;
public InternalName(string internalName) => this.internalName = internalName;
}
[PublicAPI]
public class RequiredResourceList
{
public readonly List<Requirement> Requirements = new();
public bool Free = false; // If Requirements empty and Free is true, then it costs nothing. If Requirements empty and Free is false, then it won't be craftable.
public void Add(string itemName, int amount, int quality = 0) => Requirements.Add(new Requirement { itemName = itemName, amount = amount, quality = quality });
public void Add(string itemName, ConfigEntry<int> amountConfig, int quality = 0) => Requirements.Add(new Requirement { itemName = itemName, amountConfig = amountConfig, quality = quality });
}
[PublicAPI]
public class CraftingStationList
{
public readonly List<CraftingStationConfig> Stations = new();
public void Add(CraftingTable table, int level) => Stations.Add(new CraftingStationConfig { Table = table, level = level });
public void Add(string customTable, int level) => Stations.Add(new CraftingStationConfig { Table = CraftingTable.Custom, level = level, custom = customTable });
}
[PublicAPI]
public class ItemRecipe
{
public readonly RequiredResourceList RequiredItems = new();
public readonly RequiredResourceList RequiredUpgradeItems = new();
public readonly CraftingStationList Crafting = new();
public int CraftAmount = 1;
public bool RequireOnlyOneIngredient = false;
public float QualityResultAmountMultiplier = 1;
public ConfigEntryBase? RecipeIsActive = null;
}
[PublicAPI]
public class Trade
{
public Trader Trader = Trader.None;
public uint Price = 0;
public uint Stack = 1;
public string? RequiredGlobalKey = null;
}
[PublicAPI]
[Flags]
public enum Trader
{
None = 0,
Haldor = 1 << 0,
Hildir = 1 << 1,
}
public struct Requirement
{
public string itemName;
public int amount;
public ConfigEntry<int>? amountConfig;
[Description("Set to a non-zero value to apply the requirement only for a specific quality")]
public int quality;
}
public struct CraftingStationConfig
{
public CraftingTable Table;
public int level;
public string? custom;
}
[Flags]
public enum Configurability
{
Disabled = 0,
Recipe = 1,
Stats = 2,
Drop = 4,
Trader = 8,
Full = Recipe | Drop | Stats | Trader,
}
[PublicAPI]
public class DropTargets
{
public readonly List<DropTarget> Drops = new();
public void Add(string creatureName, float chance, int min = 1, int? max = null, bool levelMultiplier = true)
{
Drops.Add(new DropTarget { creature = creatureName, chance = chance, min = min, max = max ?? min, levelMultiplier = levelMultiplier });
}
}
public struct DropTarget
{
public string creature;
public int min;
public int max;
public float chance;
public bool levelMultiplier;
}
public enum Toggle
{
On = 1,
Off = 0,
}
[PublicAPI]
public class Item
{
private class ItemConfig
{
public ConfigEntry<string>? craft;
public ConfigEntry<string>? upgrade;
public ConfigEntry<CraftingTable> table = null!;
public ConfigEntry<int> tableLevel = null!;
public ConfigEntry<string> customTable = null!;
public ConfigEntry<int>? maximumTableLevel;
public ConfigEntry<Toggle> requireOneIngredient = null!;
public ConfigEntry<float> qualityResultAmountMultiplier = null!;
}
private class TraderConfig
{
public ConfigEntry<Trader> trader = null!;
public ConfigEntry<uint> price = null!;
public ConfigEntry<uint> stack = null!;
public ConfigEntry<string> requiredGlobalKey = null!;
}
private class RequirementQuality
{
public int quality;
}
private static readonly List<Item> registeredItems = new();
private static readonly Dictionary<ItemDrop, Item> itemDropMap = new();
private static Dictionary<Item, Dictionary<string, List<Recipe>>> activeRecipes = new();
private static Dictionary<Recipe, ConfigEntryBase?> hiddenCraftRecipes = new();
private static Dictionary<Recipe, ConfigEntryBase?> hiddenUpgradeRecipes = new();
private static Dictionary<Item, Dictionary<string, ItemConfig>> itemCraftConfigs = new();
private static Dictionary<Item, ConfigEntry<string>> itemDropConfigs = new();
private Dictionary<CharacterDrop, CharacterDrop.Drop> characterDrops = new();
private readonly Dictionary<ConfigEntryBase, Action> statsConfigs = new();
private static readonly ConditionalWeakTable<Piece.Requirement, RequirementQuality> requirementQuality = new();
public static Configurability DefaultConfigurability = Configurability.Full;
public Configurability? Configurable = null;
private Configurability configurability => Configurable ?? DefaultConfigurability;
private Configurability configurationVisible = Configurability.Full;
private TraderConfig? traderConfig;
public readonly GameObject Prefab;
[Description("Specifies the resources needed to craft the item.\nUse .Add to add resources with their internal ID and an amount.\nUse one .Add for each resource type the item should need.")]
public RequiredResourceList RequiredItems => this[""].RequiredItems;
[Description("Specifies the resources needed to upgrade the item.\nUse .Add to add resources with their internal ID and an amount. This amount will be multipled by the item quality level.\nUse one .Add for each resource type the upgrade should need.")]
public RequiredResourceList RequiredUpgradeItems => this[""].RequiredUpgradeItems;
[Description("Specifies the crafting station needed to craft the item.\nUse .Add to add a crafting station, using the CraftingTable enum and a minimum level for the crafting station.\nUse one .Add for each crafting station.")]
public CraftingStationList Crafting => this[""].Crafting;
[Description("Specifies a config entry which toggles whether a recipe is active.")]
public ConfigEntryBase? RecipeIsActive
{
get => this[""].RecipeIsActive;
set => this[""].RecipeIsActive = value;
}
[Description("Specifies the number of items that should be given to the player with a single craft of the item.\nDefaults to 1.")]
public int CraftAmount
{
get => this[""].CraftAmount;
set => this[""].CraftAmount = value;
}
[Description("Specifies the maximum required crafting station level to upgrade and repair the item.\nDefault is calculated from crafting station level and maximum quality.")]
public int MaximumRequiredStationLevel = int.MaxValue;
public bool RequireOnlyOneIngredient
{
get => this[""].RequireOnlyOneIngredient;
set => this[""].RequireOnlyOneIngredient = value;
}
public float QualityResultAmountMultiplier
{
get => this[""].QualityResultAmountMultiplier;
set => this[""].QualityResultAmountMultiplier = value;
}
[Description("Assigns the item as a drop item to a creature.\nUses a creature name, a drop chance and a minimum and maximum amount.")]
public readonly DropTargets DropsFrom = new();
[Description("Configures whether the item can be bought at the trader.\nDon't forget to set cost to something above 0 or the item will be sold for free.")]
public readonly Trade Trade = new();
internal List<Conversion> Conversions = new();
internal List<Smelter.ItemConversion> conversions = new();
public Dictionary<string, ItemRecipe> Recipes = new();
public ItemRecipe this[string name]
{
get
{
if (Recipes.TryGetValue(name, out ItemRecipe recipe))
{
return recipe;
}
return Recipes[name] = new ItemRecipe();
}
}
private LocalizeKey? _name;
public LocalizeKey Name
{
get
{
if (_name is { } name)
{
return name;
}
ItemDrop.ItemData.SharedData data = Prefab.GetComponent<ItemDrop>().m_itemData.m_shared;
if (data.m_name.StartsWith("$"))
{
_name = new LocalizeKey(data.m_name);
}
else
{
string key = "$item_" + Prefab.name.Replace(" ", "_");
_name = new LocalizeKey(key).English(data.m_name);
data.m_name = key;
}
return _name;
}
}
private LocalizeKey? _description;
public LocalizeKey Description
{
get
{
if (_description is { } description)
{
return description;
}
ItemDrop.ItemData.SharedData data = Prefab.GetComponent<ItemDrop>().m_itemData.m_shared;
if (data.m_description.StartsWith("$"))
{
_description = new LocalizeKey(data.m_description);
}
else
{
string key = "$itemdesc_" + Prefab.name.Replace(" ", "_");
_description = new LocalizeKey(key).English(data.m_description);
data.m_description = key;
}
return _description;
}
}
public Item(string assetBundleFileName, string prefabName, string folderName = "assets") : this(PrefabManager.RegisterAssetBundle(assetBundleFileName, folderName), prefabName)
{
}
public Item(AssetBundle bundle, string prefabName) : this(PrefabManager.RegisterPrefab(bundle, prefabName, true), true)
{
}
public Item(GameObject prefab, bool skipRegistering = false)
{
if (!skipRegistering)
{
PrefabManager.RegisterPrefab(prefab, true);
}
Prefab = prefab;
registeredItems.Add(this);
itemDropMap[Prefab.GetComponent<ItemDrop>()] = this;
Prefab.GetComponent<ItemDrop>().m_itemData.m_dropPrefab = Prefab;
}
public void ToggleConfigurationVisibility(Configurability visible)
{
void Toggle(ConfigEntryBase cfg, Configurability check)
{
foreach (object? tag in cfg.Description.Tags)
{
if (tag is ConfigurationManagerAttributes attrs)
{
attrs.Browsable = (visible & check) != 0 && (attrs.browsability is null || attrs.browsability());
}
}
}
void ToggleObj(object obj, Configurability check)
{
foreach (FieldInfo field in obj.GetType().GetFields())
{
if (field.GetValue(obj) is ConfigEntryBase cfg)
{
Toggle(cfg, check);
}
}
}
configurationVisible = visible;
if (itemDropConfigs.TryGetValue(this, out ConfigEntry<string> dropCfg))
{
Toggle(dropCfg, Configurability.Drop);
}
if (itemCraftConfigs.TryGetValue(this, out Dictionary<string, ItemConfig> craftCfgs))
{
foreach (ItemConfig craftCfg in craftCfgs.Values)
{
ToggleObj(craftCfg, Configurability.Recipe);
}
}
foreach (Conversion conversion in Conversions)
{
if (conversion.config is not null)
{
ToggleObj(conversion.config, Configurability.Recipe);
}
}
foreach (KeyValuePair<ConfigEntryBase, Action> cfg in statsConfigs)
{
Toggle(cfg.Key, Configurability.Stats);
if ((visible & Configurability.Stats) != 0)
{
cfg.Value();
}
}
reloadConfigDisplay();
}
private class ConfigurationManagerAttributes
{
[UsedImplicitly] public int? Order;
[UsedImplicitly] public bool? Browsable;
[UsedImplicitly] public string? Category;
[UsedImplicitly] public Action<ConfigEntryBase>? CustomDrawer;
public Func<bool>? browsability;
}
[PublicAPI]
public enum DamageModifier
{
Normal,
Resistant,
Weak,
Immune,
Ignore,
VeryResistant,
VeryWeak,
None,
}
private static object? configManager;
private delegate void setDmgFunc(ref HitData.DamageTypes dmg, float value);
internal static void reloadConfigDisplay()
{
if (configManager?.GetType().GetProperty("DisplayingWindow")!.GetValue(configManager) is true)
{
configManager.GetType().GetMethod("BuildSettingList")!.Invoke(configManager, Array.Empty<object>());
}
}
private void UpdateItemTableConfig(string recipeKey, CraftingTable table, string customTableValue)
{
if (activeRecipes.ContainsKey(this) && activeRecipes[this].TryGetValue(recipeKey, out List<Recipe> recipes))
{
recipes.First().m_enabled = table != CraftingTable.Disabled;
if (table is CraftingTable.Inventory or CraftingTable.Disabled)
{
recipes.First().m_craftingStation = null;
}
else if (table is CraftingTable.Custom)
{
recipes.First().m_craftingStation = ZNetScene.instance.GetPrefab(customTableValue)?.GetComponent<CraftingStation>();
}
else
{
recipes.First().m_craftingStation = ZNetScene.instance.GetPrefab(getInternalName(table)).GetComponent<CraftingStation>();
}
}
}
private void UpdateCraftConfig(string recipeKey, SerializedRequirements craftRequirements, SerializedRequirements upgradeRequirements)
{
if (ObjectDB.instance && activeRecipes.ContainsKey(this) && activeRecipes[this].TryGetValue(recipeKey, out List<Recipe> recipes))
{
foreach (Recipe recipe in recipes)
{
recipe.m_resources = SerializedRequirements.toPieceReqs(ObjectDB.instance, craftRequirements, upgradeRequirements);
}
}
}
internal static void Patch_FejdStartup()
{
Assembly? bepinexConfigManager = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "ConfigurationManager");
Type? configManagerType = bepinexConfigManager?.GetType("ConfigurationManager.ConfigurationManager");
if (DefaultConfigurability != Configurability.Disabled)
{
bool SaveOnConfigSet = plugin.Config.SaveOnConfigSet;
plugin.Config.SaveOnConfigSet = false;
foreach (Item item in registeredItems.Where(i => i.configurability != Configurability.Disabled))
{
string nameKey = item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name;
string englishName = new Regex(@"[=\n\t\\""\'\[\]]*").Replace(english.Localize(nameKey), "").Trim();
string localizedName = Localization.instance.Localize(nameKey).Trim();
int order = 0;
if ((item.configurability & Configurability.Recipe) != 0)
{
itemCraftConfigs[item] = new Dictionary<string, ItemConfig>();
foreach (string configKey in item.Recipes.Keys.DefaultIfEmpty(""))
{
string configSuffix = configKey == "" ? "" : $" ({configKey})";
if (item.Recipes.ContainsKey(configKey) && item.Recipes[configKey].Crafting.Stations.Count > 0)
{
ItemConfig cfg = itemCraftConfigs[item][configKey] = new ItemConfig();
List<ConfigurationManagerAttributes> hideWhenNoneAttributes = new();
cfg.table = config(englishName, "Crafting Station" + configSuffix, item.Recipes[configKey].Crafting.Stations.First().Table, new ConfigDescription($"Crafting station where {englishName} is available.", null, new ConfigurationManagerAttributes { Order = --order, Browsable = (item.configurationVisible & Configurability.Recipe) != 0, Category = localizedName }));
bool CustomTableBrowsability() => cfg.table.Value == CraftingTable.Custom;
ConfigurationManagerAttributes customTableAttributes = new() { Order = --order, browsability = CustomTableBrowsability, Browsable = CustomTableBrowsability() && (item.configurationVisible & Configurability.Recipe) != 0, Category = localizedName };
cfg.customTable = config(englishName, "Custom Crafting Station" + configSuffix, item.Recipes[configKey].Crafting.Stations.First().custom ?? "", new ConfigDescription("", null, customTableAttributes));
void TableConfigChanged(object o, EventArgs e)
{
item.UpdateItemTableConfig(configKey, cfg.table.Value, cfg.customTable.Value);
customTableAttributes.Browsable = cfg.table.Value == CraftingTable.Custom;
foreach (ConfigurationManagerAttributes attributes in hideWhenNoneAttributes)
{
attributes.Browsable = cfg.table.Value != CraftingTable.Disabled;
}
reloadConfigDisplay();
}
cfg.table.SettingChanged += TableConfigChanged;
cfg.customTable.SettingChanged += TableConfigChanged;
bool TableLevelBrowsability() => cfg.table.Value != CraftingTable.Disabled;
ConfigurationManagerAttributes tableLevelAttributes = new() { Order = --order, browsability = TableLevelBrowsability, Browsable = TableLevelBrowsability() && (item.configurationVisible & Configurability.Recipe) != 0, Category = localizedName };
hideWhenNoneAttributes.Add(tableLevelAttributes);
cfg.tableLevel = config(englishName, "Crafting Station Level" + configSuffix, item.Recipes[configKey].Crafting.Stations.First().level, new ConfigDescription($"Required crafting station level to craft {englishName}.", null, tableLevelAttributes));
cfg.tableLevel.SettingChanged += (_, _) =>
{
if (activeRecipes.ContainsKey(item) && activeRecipes[item].TryGetValue(configKey, out List<Recipe> recipes))
{
recipes.First().m_minStationLevel = cfg.tableLevel.Value;
}
};
if (item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxQuality > 1)
{
cfg.maximumTableLevel = config(englishName, "Maximum Crafting Station Level" + configSuffix, item.MaximumRequiredStationLevel == int.MaxValue ? item.Recipes[configKey].Crafting.Stations.First().level + item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxQuality - 1 : item.MaximumRequiredStationLevel, new ConfigDescription($"Maximum crafting station level to upgrade and repair {englishName}.", null, tableLevelAttributes));
}
bool QualityResultBrowsability() => cfg.requireOneIngredient.Value == Toggle.On;
cfg.requireOneIngredient = config(englishName, "Require only one resource" + configSuffix, item.Recipes[configKey].RequireOnlyOneIngredient ? Toggle.On : Toggle.Off, new ConfigDescription($"Whether only one of the ingredients is needed to craft {englishName}", null, new ConfigurationManagerAttributes { Order = --order, Category = localizedName }));
ConfigurationManagerAttributes qualityResultAttributes = new() { Order = --order, browsability = QualityResultBrowsability, Browsable = QualityResultBrowsability() && (item.configurationVisible & Configurability.Recipe) != 0, Category = localizedName };
cfg.requireOneIngredient.SettingChanged += (_, _) =>
{
if (activeRecipes.ContainsKey(item) && activeRecipes[item].TryGetValue(configKey, out List<Recipe> recipes))
{
foreach (Recipe recipe in recipes)
{
recipe.m_requireOnlyOneIngredient = cfg.requireOneIngredient.Value == Toggle.On;
}
}
qualityResultAttributes.Browsable = QualityResultBrowsability();
reloadConfigDisplay();
};
cfg.qualityResultAmountMultiplier = config(englishName, "Quality Multiplier" + configSuffix, item.Recipes[configKey].QualityResultAmountMultiplier, new ConfigDescription($"Multiplies the crafted amount based on the quality of the resources when crafting {englishName}. Only works, if Require Only One Resource is true.", null, qualityResultAttributes));
cfg.qualityResultAmountMultiplier.SettingChanged += (_, _) =>
{
if (activeRecipes.ContainsKey(item) && activeRecipes[item].TryGetValue(configKey, out List<Recipe> recipes))
{
foreach (Recipe recipe in recipes)
{
recipe.m_qualityResultAmountMultiplier = cfg.qualityResultAmountMultiplier.Value;
}
}
};
ConfigEntry<string> itemConfig(string name, string value, string desc, bool isUpgrade)
{
bool ItemBrowsability() => cfg.table.Value != CraftingTable.Disabled;
ConfigurationManagerAttributes attributes = new() { CustomDrawer = drawRequirementsConfigTable(item, isUpgrade), Order = --order, browsability = ItemBrowsability, Browsable = ItemBrowsability() && (item.configurationVisible & Configurability.Recipe) != 0, Category = localizedName };
hideWhenNoneAttributes.Add(attributes);
return config(englishName, name, value, new ConfigDescription(desc, null, attributes));
}
if ((!item.Recipes[configKey].RequiredItems.Free || item.Recipes[configKey].RequiredItems.Requirements.Count > 0) && item.Recipes[configKey].RequiredItems.Requirements.All(r => r.amountConfig is null))
{
cfg.craft = itemConfig("Crafting Costs" + configSuffix, new SerializedRequirements(item.Recipes[configKey].RequiredItems.Requirements).ToString(), $"Item costs to craft {englishName}", false);
}
if (item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxQuality > 1 && (!item.Recipes[configKey].RequiredUpgradeItems.Free || item.Recipes[configKey].RequiredUpgradeItems.Requirements.Count > 0) && item.Recipes[configKey].RequiredUpgradeItems.Requirements.All(r => r.amountConfig is null))
{
cfg.upgrade = itemConfig("Upgrading Costs" + configSuffix, new SerializedRequirements(item.Recipes[configKey].RequiredUpgradeItems.Requirements).ToString(), $"Item costs per level to upgrade {englishName}", true);
}
void ConfigChanged(object o, EventArgs e)
{
item.UpdateCraftConfig(configKey, new SerializedRequirements(cfg.craft?.Value ?? ""), new SerializedRequirements(cfg.upgrade?.Value ?? ""));
}
if (cfg.craft != null)
{
cfg.craft.SettingChanged += ConfigChanged;
}
if (cfg.upgrade != null)
{
cfg.upgrade.SettingChanged += ConfigChanged;
}
}
}
if ((item.configurability & Configurability.Drop) != 0)
{
ConfigEntry<string> dropConfig = itemDropConfigs[item] = config(englishName, "Drops from", new SerializedDrop(item.DropsFrom.Drops).ToString(), new ConfigDescription($"{englishName} drops from this creature.", null, new ConfigurationManagerAttributes { CustomDrawer = drawDropsConfigTable, Category = localizedName, Browsable = (item.configurationVisible & Configurability.Drop) != 0 }));
dropConfig.SettingChanged += (_, _) => item.UpdateCharacterDrop();
}
for (int i = 0; i < item.Conversions.Count; ++i)
{
string prefix = item.Conversions.Count > 1 ? $"{i + 1}. " : "";
Conversion conversion = item.Conversions[i];
conversion.config = new Conversion.ConversionConfig();
int index = i;
void UpdatePiece()
{
if (index >= item.conversions.Count || !ZNetScene.instance)
{
return;
}
string? newPieceName = conversion.config.piece.Value is not ConversionPiece.Disabled ? conversion.config.piece.Value == ConversionPiece.Custom ? conversion.config.customPiece.Value : getInternalName(conversion.config.piece.Value) : null;
string? activePiece = conversion.config.activePiece;
if (conversion.config.activePiece is not null)
{
Smelter smelter = ZNetScene.instance.GetPrefab(conversion.config.activePiece).GetComponent<Smelter>();
int removeIndex = smelter.m_conversion.IndexOf(item.conversions[index]);
if (removeIndex >= 0)
{
foreach (Smelter instantiatedSmelter in Resources.FindObjectsOfTypeAll<Smelter>())
{
if (Utils.GetPrefabName(instantiatedSmelter.gameObject) == activePiece)
{
instantiatedSmelter.m_conversion.RemoveAt(removeIndex);
}
}
}
conversion.config.activePiece = null;
}
if (item.conversions[index].m_from is not null && conversion.config.piece.Value is not ConversionPiece.Disabled)
{
if (ZNetScene.instance.GetPrefab(newPieceName)?.GetComponent<Smelter>() is not null)
{
conversion.config.activePiece = newPieceName;
foreach (Smelter instantiatedSmelter in Resources.FindObjectsOfTypeAll<Smelter>())
{
if (Utils.GetPrefabName(instantiatedSmelter.gameObject) == newPieceName)
{
instantiatedSmelter.m_conversion.Add(item.conversions[index]);
}
}
}
}
}
conversion.config.input = config(englishName, $"{prefix}Conversion Input Item", conversion.Input, new ConfigDescription($"Input item to create {englishName}", null, new ConfigurationManagerAttributes { Category = localizedName, Browsable = (item.configurationVisible & Configurability.Recipe) != 0 }));
conversion.config.input.SettingChanged += (_, _) =>
{
if (index < item.conversions.Count && ObjectDB.instance is { } objectDB)
{
ItemDrop? inputItem = SerializedRequirements.fetchByName(objectDB, conversion.config.input.Value);
item.conversions[index].m_from = inputItem;
UpdatePiece();
}
};
conversion.config.piece = config(englishName, $"{prefix}Conversion Piece", conversion.Piece, new ConfigDescription($"Conversion piece used to create {englishName}", null, new ConfigurationManagerAttributes { Category = localizedName, Browsable = (item.configurationVisible & Configurability.Recipe) != 0 }));
conversion.config.piece.SettingChanged += (_, _) => UpdatePiece();
conversion.config.customPiece = config(englishName, $"{prefix}Conversion Custom Piece", conversion.customPiece ?? "", new ConfigDescription($"Custom conversion piece to create {englishName}", null, new ConfigurationManagerAttributes { Category = localizedName, Browsable = (item.configurationVisible & Configurability.Recipe) != 0 }));
conversion.config.customPiece.SettingChanged += (_, _) => UpdatePiece();
}
}
if ((item.configurability & Configurability.Stats) != 0)
{
item.statsConfigs.Clear();
void statcfg<T>(string configName, string description, Func<ItemDrop.ItemData.SharedData, T> readDefault, Action<ItemDrop.ItemData.SharedData, T> setValue)
{
ItemDrop.ItemData.SharedData shared = item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared;
ConfigEntry<T> cfg = config(englishName, configName, readDefault(shared), new ConfigDescription(description, null, new ConfigurationManagerAttributes { Category = localizedName, Browsable = (item.configurationVisible & Configurability.Stats) != 0 }));
if ((item.configurationVisible & Configurability.Stats) != 0)
{
setValue(shared, cfg.Value);
}
void ApplyConfig() => item.ApplyToAllInstances(item => setValue(item.m_shared, cfg.Value));
item.statsConfigs.Add(cfg, ApplyConfig);
cfg.SettingChanged += (_, _) =>
{
if ((item.configurationVisible & Configurability.Stats) != 0)
{
ApplyConfig();
}
};
}
ItemDrop.ItemData.SharedData shared = item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared;
ItemDrop.ItemData.ItemType itemType = shared.m_itemType;
statcfg("Weight", $"Weight of {englishName}.", shared => shared.m_weight, (shared, value) => shared.m_weight = value);
statcfg("Trader Value", $"Trader value of {englishName}.", shared => shared.m_value, (shared, value) => shared.m_value = value);
if (itemType is ItemDrop.ItemData.ItemType.Bow or ItemDrop.ItemData.ItemType.Chest or ItemDrop.ItemData.ItemType.Hands or ItemDrop.ItemData.ItemType.Helmet or ItemDrop.ItemData.ItemType.Legs or ItemDrop.ItemData.ItemType.Shield or ItemDrop.ItemData.ItemType.Shoulder or ItemDrop.ItemData.ItemType.Tool or ItemDrop.ItemData.ItemType.OneHandedWeapon or ItemDrop.ItemData.ItemType.TwoHandedWeapon or ItemDrop.ItemData.ItemType.TwoHandedWeaponLeft)
{
statcfg("Durability", $"Durability of {englishName}.", shared => shared.m_maxDurability, (shared, value) => shared.m_maxDurability = value);
statcfg("Durability per Level", $"Durability gain per level of {englishName}.", shared => shared.m_durabilityPerLevel, (shared, value) => shared.m_durabilityPerLevel = value);
statcfg("Movement Speed Modifier", $"Movement speed modifier of {englishName}.", shared => shared.m_movementModifier, (shared, value) => shared.m_movementModifier = value);
}
if (itemType is ItemDrop.ItemData.ItemType.Bow or ItemDrop.ItemData.ItemType.Shield or ItemDrop.ItemData.ItemType.OneHandedWeapon or ItemDrop.ItemData.ItemType.TwoHandedWeapon or ItemDrop.ItemData.ItemType.TwoHandedWeaponLeft)
{
statcfg("Block Armor", $"Block armor of {englishName}.", shared => shared.m_blockPower, (shared, value) => shared.m_blockPower = value);
statcfg("Block Armor per Level", $"Block armor per level for {englishName}.", shared => shared.m_blockPowerPerLevel, (shared, value) => shared.m_blockPowerPerLevel = value);
statcfg("Block Force", $"Block force of {englishName}.", shared => shared.m_deflectionForce, (shared, value) => shared.m_deflectionForce = value);
statcfg("Block Force per Level", $"Block force per level for {englishName}.", shared => shared.m_deflectionForcePerLevel, (shared, value) => shared.m_deflectionForcePerLevel = value);
statcfg("Parry Bonus", $"Parry bonus of {englishName}.", shared => shared.m_timedBlockBonus, (shared, value) => shared.m_timedBlockBonus = value);
}
else if (itemType is ItemDrop.ItemData.ItemType.Chest or ItemDrop.ItemData.ItemType.Hands or ItemDrop.ItemData.ItemType.Helmet or ItemDrop.ItemData.ItemType.Legs or ItemDrop.ItemData.ItemType.Shoulder)
{
statcfg("Armor", $"Armor of {englishName}.", shared => shared.m_armor, (shared, value) => shared.m_armor = value);
statcfg("Armor per Level", $"Armor per level for {englishName}.", shared => shared.m_armorPerLevel, (shared, value) => shared.m_armorPerLevel = value);
}
if (shared.m_skillType is Skills.SkillType.Axes or Skills.SkillType.Pickaxes)
{
statcfg("Tool tier", $"Tool tier of {englishName}.", shared => shared.m_toolTier, (shared, value) => shared.m_toolTier = value);
}
if (itemType is ItemDrop.ItemData.ItemType.Shield or ItemDrop.ItemData.ItemType.Chest or ItemDrop.ItemData.ItemType.Hands or ItemDrop.ItemData.ItemType.Helmet or ItemDrop.ItemData.ItemType.Legs or ItemDrop.ItemData.ItemType.Shoulder)
{
Dictionary<HitData.DamageType, DamageModifier> modifiers = shared.m_damageModifiers.ToDictionary(d => d.m_type, d => (DamageModifier)(int)d.m_modifier);
foreach (HitData.DamageType damageType in ((HitData.DamageType[])Enum.GetValues(typeof(HitData.DamageType))).Except(new[] { HitData.DamageType.Chop, HitData.DamageType.Pickaxe, HitData.DamageType.Spirit, HitData.DamageType.Physical, HitData.DamageType.Elemental }))
{
statcfg($"{damageType.ToString()} Resistance", $"{damageType.ToString()} resistance of {englishName}.", _ => modifiers.TryGetValue(damageType, out DamageModifier modifier) ? modifier : DamageModifier.None, (shared, value) =>
{
HitData.DamageModPair modifier = new() { m_type = damageType, m_modifier = (HitData.DamageModifier)(int)value };
for (int i = 0; i < shared.m_damageModifiers.Count; ++i)
{
if (shared.m_damageModifiers[i].m_type == damageType)
{
if (value == DamageModifier.None)
{
shared.m_damageModifiers.RemoveAt(i);
}
else
{
shared.m_damageModifiers[i] = modifier;
}
return;
}
}
if (value != DamageModifier.None)
{
shared.m_damageModifiers.Add(modifier);
}
});
}
}
if (itemType is ItemDrop.ItemData.ItemType.Consumable && shared.m_food > 0)
{
statcfg("Health", $"Health value of {englishName}.", shared => shared.m_food, (shared, value) => shared.m_food = value);
statcfg("Stamina", $"Stamina value of {englishName}.", shared => shared.m_foodStamina, (shared, value) => shared.m_foodStamina = value);
statcfg("Eitr", $"Eitr value of {englishName}.", shared => shared.m_foodEitr, (shared, value) => shared.m_foodEitr = value);
statcfg("Duration", $"Duration of {englishName}.", shared => shared.m_foodBurnTime, (shared, value) => shared.m_foodBurnTime = value);
statcfg("Health Regen", $"Health regen value of {englishName}.", shared => shared.m_foodRegen, (shared, value) => shared.m_foodRegen = value);
}
if (shared.m_skillType is Skills.SkillType.BloodMagic)
{
statcfg("Health Cost", $"Health cost of {englishName}.", shared => shared.m_attack.m_attackHealth, (shared, value) => shared.m_attack.m_attackHealth = value);
statcfg("Health Cost Percentage", $"Health cost percentage of {englishName}.", shared => shared.m_attack.m_attackHealthPercentage, (shared, value) => shared.m_attack.m_attackHealthPercentage = value);
}
if (shared.m_skillType is Skills.SkillType.BloodMagic or Skills.SkillType.ElementalMagic)
{
statcfg("Eitr Cost", $"Eitr cost of {englishName}.", shared => shared.m_attack.m_attackEitr, (shared, value) => shared.m_attack.m_attackEitr = value);
}
if (itemType is ItemDrop.ItemData.ItemType.OneHandedWeapon or ItemDrop.ItemData.ItemType.TwoHandedWeapon or ItemDrop.ItemData.ItemType.TwoHandedWeaponLeft or ItemDrop.ItemData.ItemType.Bow)
{
statcfg("Knockback", $"Knockback of {englishName}.", shared => shared.m_attackForce, (shared, value) => shared.m_attackForce = value);
statcfg("Backstab Bonus", $"Backstab bonus of {englishName}.", shared => shared.m_backstabBonus, (shared, value) => shared.m_backstabBonus = value);
statcfg("Attack Stamina", $"Attack stamina of {englishName}.", shared => shared.m_attack.m_attackStamina, (shared, value) => shared.m_attack.m_attackStamina = value);
void SetDmg(string dmgType, Func<HitData.DamageTypes, float> readDmg, setDmgFunc setDmg)
{
statcfg($"{dmgType} Damage", $"{dmgType} damage dealt by {englishName}.", shared => readDmg(shared.m_damages), (shared, val) => setDmg(ref shared.m_damages, val));
statcfg($"{dmgType} Damage Per Level", $"{dmgType} damage dealt increase per level for {englishName}.", shared => readDmg(shared.m_damagesPerLevel), (shared, val) => setDmg(ref shared.m_damagesPerLevel, val));
}
SetDmg("True", dmg => dmg.m_damage, (ref HitData.DamageTypes dmg, float val) => dmg.m_damage = val);
SetDmg("Slash", dmg => dmg.m_slash, (ref HitData.DamageTypes dmg, float val) => dmg.m_slash = val);
SetDmg("Pierce", dmg => dmg.m_pierce, (ref HitData.DamageTypes dmg, float val) => dmg.m_pierce = val);
SetDmg("Blunt", dmg => dmg.m_blunt, (ref HitData.DamageTypes dmg, float val) => dmg.m_blunt = val);
SetDmg("Chop", dmg => dmg.m_chop, (ref HitData.DamageTypes dmg, float val) => dmg.m_chop = val);
SetDmg("Pickaxe", dmg => dmg.m_pickaxe, (ref HitData.DamageTypes dmg, float val) => dmg.m_pickaxe = val);
SetDmg("Fire", dmg => dmg.m_fire, (ref HitData.DamageTypes dmg, float val) => dmg.m_fire = val);
SetDmg("Poison", dmg => dmg.m_poison, (ref HitData.DamageTypes dmg, float val) => dmg.m_poison = val);
SetDmg("Frost", dmg => dmg.m_frost, (ref HitData.DamageTypes dmg, float val) => dmg.m_frost = val);
SetDmg("Lightning", dmg => dmg.m_lightning, (ref HitData.DamageTypes dmg, float val) => dmg.m_lightning = val);
SetDmg("Spirit", dmg => dmg.m_spirit, (ref HitData.DamageTypes dmg, float val) => dmg.m_spirit = val);
if (itemType is ItemDrop.ItemData.ItemType.Bow)
{
statcfg("Projectiles", $"Number of projectiles that {englishName} shoots at once.", shared => shared.m_attack.m_projectileBursts, (shared, value) => shared.m_attack.m_projectileBursts = value);
statcfg("Burst Interval", $"Time between the projectiles {englishName} shoots at once.", shared => shared.m_attack.m_burstInterval, (shared, value) => shared.m_attack.m_burstInterval = value);
statcfg("Minimum Accuracy", $"Minimum accuracy for {englishName}.", shared => shared.m_attack.m_projectileAccuracyMin, (shared, value) => shared.m_attack.m_projectileAccuracyMin = value);
statcfg("Accuracy", $"Accuracy for {englishName}.", shared => shared.m_attack.m_projectileAccuracy, (shared, value) => shared.m_attack.m_projectileAccuracy = value);
statcfg("Minimum Velocity", $"Minimum velocity for {englishName}.", shared => shared.m_attack.m_projectileVelMin, (shared, value) => shared.m_attack.m_projectileVelMin = value);
statcfg("Velocity", $"Velocity for {englishName}.", shared => shared.m_attack.m_projectileVel, (shared, value) => shared.m_attack.m_projectileVel = value);
statcfg("Maximum Draw Time", $"Time until {englishName} is fully drawn at skill level 0.", shared => shared.m_attack.m_drawDurationMin, (shared, value) => shared.m_attack.m_drawDurationMin = value);
statcfg("Stamina Drain", $"Stamina drain per second while drawing {englishName}.", shared => shared.m_attack.m_drawStaminaDrain, (shared, value) => shared.m_attack.m_drawStaminaDrain = value);
}
}
}
if ((item.configurability & Configurability.Trader) != 0)
{
List<ConfigurationManagerAttributes> traderAttributes = new();
bool TraderBrowsability() => item.traderConfig.trader.Value != 0;
item.traderConfig = new TraderConfig
{
trader = config(englishName, "Trader Selling", item.Trade.Trader, new ConfigDescription($"Which traders sell {englishName}.", null, new ConfigurationManagerAttributes { Order = --order, Browsable = (item.configurationVisible & Configurability.Trader) != 0, Category = localizedName })),
};
item.traderConfig.trader.SettingChanged += (_, _) =>
{
item.ReloadTraderConfiguration();
foreach (ConfigurationManagerAttributes attributes in traderAttributes)
{
attributes.Browsable = TraderBrowsability();
}
reloadConfigDisplay();
};
ConfigEntry<T> traderConfig<T>(string name, T value, string desc)
{
ConfigurationManagerAttributes attributes = new() { Order = --order, browsability = TraderBrowsability, Browsable = TraderBrowsability() && (item.configurationVisible & Configurability.Trader) != 0, Category = localizedName };
traderAttributes.Add(attributes);
ConfigEntry<T> cfg = config(englishName, name, value, new ConfigDescription(desc, null, attributes));
cfg.SettingChanged += (_, _) => item.ReloadTraderConfiguration();
return cfg;
}
item.traderConfig.price = traderConfig("Trader Price", item.Trade.Price, $"Price of {englishName} at the trader.");
item.traderConfig.stack = traderConfig("Trader Stack", item.Trade.Stack, $"Stack size of {englishName} in the trader. Also known as the number of items sold by a trader in one transaction.");
item.traderConfig.requiredGlobalKey = traderConfig("Trader Required Global Key", item.Trade.RequiredGlobalKey ?? "", $"Required global key to unlock {englishName} at the trader.");
if (item.traderConfig.trader.Value != 0)
{
PrefabManager.AddItemToTrader(item.Prefab, item.traderConfig.trader.Value, item.traderConfig.price.Value, item.traderConfig.stack.Value, item.traderConfig.requiredGlobalKey.Value);
}
}
else if (item.Trade.Trader != 0)
{
PrefabManager.AddItemToTrader(item.Prefab, item.Trade.Trader, item.Trade.Price, item.Trade.Stack, item.Trade.RequiredGlobalKey);
}
}
if (SaveOnConfigSet)
{
plugin.Config.SaveOnConfigSet = true;
plugin.Config.Save();
}
}
configManager = configManagerType == null ? null : BepInEx.Bootstrap.Chainloader.ManagerObject.GetComponent(configManagerType);
foreach (Item item in registeredItems)
{
foreach (KeyValuePair<string, ItemRecipe> kv in item.Recipes)
{
foreach (RequiredResourceList resourceList in new[] { kv.Value.RequiredItems, kv.Value.RequiredUpgradeItems })
{
for (int i = 0; i < resourceList.Requirements.Count; ++i)
{
if ((item.configurability & Configurability.Recipe) != 0 && resourceList.Requirements[i].amountConfig is { } amountCfg)
{
int resourceIndex = i;
void ConfigChanged(object o, EventArgs e)
{
if (ObjectDB.instance && activeRecipes.ContainsKey(item) && activeRecipes[item].TryGetValue(kv.Key, out List<Recipe> recipes))
{
foreach (Recipe recipe in recipes)
{
recipe.m_resources[resourceIndex].m_amount = amountCfg.Value;
}
}
}
amountCfg.SettingChanged += ConfigChanged;
}
}
}
}
item.InitializeNewRegisteredItem();
}
}
private void InitializeNewRegisteredItem()
{
foreach (KeyValuePair<string, ItemRecipe> kv in Recipes)
{
if (kv.Value.RecipeIsActive is { } enabledCfg)
{
void ConfigChanged(object o, EventArgs e)
{
if (ObjectDB.instance && activeRecipes.ContainsKey(this) && activeRecipes[this].TryGetValue(kv.Key, out List<Recipe> recipes))
{
foreach (Recipe recipe in recipes)
{
recipe.m_enabled = (int)enabledCfg.BoxedValue != 0;
}
}
}
enabledCfg.GetType().GetEvent(nameof(ConfigEntry<int>.SettingChanged)).AddEventHandler(enabledCfg, new EventHandler(ConfigChanged));
}
}
}
public void ReloadCraftingConfiguration()
{
if (ObjectDB.instance && ObjectDB.instance.GetItemPrefab(Prefab.name.GetStableHashCode()) is null)
{
registerRecipesInObjectDB(ObjectDB.instance);
ObjectDB.instance.m_items.Add(Prefab);
ObjectDB.instance.m_itemByHash.Add(Prefab.name.GetStableHashCode(), Prefab);
ZNetScene.instance.m_prefabs.Add(Prefab);
ZNetScene.instance.m_namedPrefabs.Add(Prefab.name.GetStableHashCode(), Prefab);
}
foreach (string configKey in Recipes.Keys.DefaultIfEmpty(""))
{
if (Recipes.TryGetValue(configKey, out ItemRecipe recipe) && recipe.Crafting.Stations.Count > 0)
{
UpdateItemTableConfig(configKey, recipe.Crafting.Stations.First().Table, recipe.Crafting.Stations.First().custom ?? "");
UpdateCraftConfig(configKey, new SerializedRequirements(recipe.RequiredItems.Requirements), new SerializedRequirements(recipe.RequiredUpgradeItems.Requirements));
// To be extended as needed
}
}
}
private void ReloadTraderConfiguration()
{
if (traderConfig!.trader.Value == 0)
{
PrefabManager.RemoveItemFromTrader(Prefab);
}
else
{
PrefabManager.AddItemToTrader(Prefab, traderConfig.trader.Value, traderConfig.price.Value, traderConfig.stack.Value, traderConfig.requiredGlobalKey.Value);
}
}
public static void ApplyToAllInstances(GameObject prefab, Action<ItemDrop.ItemData> callback)
{
callback(prefab.GetComponent<ItemDrop>().m_itemData);
string itemName = prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name;
Inventory[] inventories = Player.s_players.Select(p => p.GetInventory()).Concat(UnityEngine.Object.FindObjectsOfType<Container>().Select(c => c.GetInventory())).Where(c => c is not null).ToArray();
foreach (ItemDrop.ItemData itemdata in ObjectDB.instance.m_items.Select(p => p.GetComponent<ItemDrop>()).Where(c => c && c.GetComponent<ZNetView>()).Concat(ItemDrop.s_instances).Select(i => i.m_itemData).Concat(inventories.SelectMany(i => i.GetAllItems())))
{
if (itemdata.m_shared.m_name == itemName)
{
callback(itemdata);
}
}
}
public void ApplyToAllInstances(Action<ItemDrop.ItemData> callback) => ApplyToAllInstances(Prefab, callback);
private static string getInternalName<T>(T value) where T : struct => ((InternalName)typeof(T).GetMember(value.ToString())[0].GetCustomAttributes(typeof(InternalName)).First()).internalName;
private void registerRecipesInObjectDB(ObjectDB objectDB)
{
activeRecipes[this] = new Dictionary<string, List<Recipe>>();
itemCraftConfigs.TryGetValue(this, out Dictionary<string, ItemConfig> cfgs);
foreach (KeyValuePair<string, ItemRecipe> kv in Recipes)
{
List<Recipe> recipes = new();
foreach (CraftingStationConfig station in kv.Value.Crafting.Stations)
{
ItemConfig? cfg = cfgs?[kv.Key];
Recipe recipe = ScriptableObject.CreateInstance<Recipe>();
recipe.name = $"{Prefab.name}_Recipe_{station.Table.ToString()}";
recipe.m_amount = kv.Value.CraftAmount;
recipe.m_enabled = cfg is null ? (int)(kv.Value.RecipeIsActive?.BoxedValue ?? 1) != 0 : cfg.table.Value != CraftingTable.Disabled;
recipe.m_item = Prefab.GetComponent<ItemDrop>();
recipe.m_resources = SerializedRequirements.toPieceReqs(objectDB, cfg?.craft == null ? new SerializedRequirements(kv.Value.RequiredItems.Requirements) : new SerializedRequirements(cfg.craft.Value), cfg?.upgrade == null ? new SerializedRequirements(kv.Value.RequiredUpgradeItems.Requirements) : new SerializedRequirements(cfg.upgrade.Value));
if ((cfg == null || recipes.Count > 0 ? station.Table : cfg.table.Value) is CraftingTable.Inventory or CraftingTable.Disabled)
{
recipe.m_craftingStation = null;
}
else if ((cfg == null || recipes.Count > 0 ? station.Table : cfg.table.Value) is CraftingTable.Custom)
{
if (ZNetScene.instance.GetPrefab(cfg == null || recipes.Count > 0 ? station.custom : cfg.customTable.Value) is { } craftingTable)
{
recipe.m_craftingStation = craftingTable.GetComponent<CraftingStation>();
}
else
{
Debug.LogWarning($"Custom crafting station '{(cfg == null || recipes.Count > 0 ? station.custom : cfg.customTable.Value)}' does not exist");
}
}
else
{
recipe.m_craftingStation = ZNetScene.instance.GetPrefab(getInternalName(cfg == null || recipes.Count > 0 ? station.Table : cfg.table.Value)).GetComponent<CraftingStation>();
}
recipe.m_minStationLevel = cfg == null || recipes.Count > 0 ? station.level : cfg.tableLevel.Value;
recipe.m_requireOnlyOneIngredient = cfg == null ? kv.Value.RequireOnlyOneIngredient : cfg.requireOneIngredient.Value == Toggle.On;
recipe.m_qualityResultAmountMultiplier = cfg?.qualityResultAmountMultiplier.Value ?? kv.Value.QualityResultAmountMultiplier;
recipes.Add(recipe);
if (kv.Value.RequiredItems is { Free: false, Requirements.Count: 0 })
{