-
Notifications
You must be signed in to change notification settings - Fork 37
/
ModEntry.cs
230 lines (212 loc) · 10.5 KB
/
ModEntry.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
using System;
using System.Collections.Generic;
using System.Reflection;
using Entoarox.Framework;
using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;
using StardewValley.Menus;
using SDVObject = StardewValley.Object;
namespace Entoarox.ShopExpander
{
/// <summary>The mod entry class.</summary>
internal class ModEntry : Mod
{
/*********
** Fields
*********/
private static readonly Dictionary<string, SObject> AddedObjects = new Dictionary<string, SObject>();
private static readonly Dictionary<string, Item> ReplacementStacks = new Dictionary<string, Item>();
private ModConfig Config;
private bool EventsActive;
private byte SkippedTicks;
private readonly List<string> AffectedShops = new List<string>();
/*********
** Public methods
*********/
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public override void Entry(IModHelper helper)
{
helper.Events.Display.MenuChanged += this.OnMenuChanged;
helper.Events.GameLoop.UpdateTicked += this.OnUpdateTicked;
}
/*********
** Protected methods
*********/
/// <summary>Raised after the game state is updated (≈60 times per second).</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnUpdateTicked(object sender, EventArgs e)
{
if (this.SkippedTicks > 1)
{
this.Config = this.Helper.ReadConfig<ModConfig>();
this.Helper.Events.GameLoop.UpdateTicked -= this.OnUpdateTicked;
foreach (Reference obj in this.Config.Objects)
{
try
{
this.GenerateObject(obj.Owner, obj.Item, obj.Amount, obj.Conditions);
}
catch (Exception err)
{
this.Monitor.Log($"Object failed to generate: {obj}", LogLevel.Error, err);
}
}
}
else
this.SkippedTicks++;
}
private void GenerateObject(string owner, int replacement, int stackAmount, string requirements)
{
if (owner == "???")
{
this.Monitor.Log("Attempt to add a object to a shop owned by `???`, this cant be done because `???` means the owner is unknown!", LogLevel.Error);
return;
}
SDVObject stack = new SDVObject(replacement, stackAmount);
if (stack.salePrice() == 0)
{
this.Monitor.Log("Unable to add item to shop, it has no value: " + replacement, LogLevel.Error);
return;
}
SObject obj = new SObject(stack, stackAmount)
{
targetedShop = owner,
requirements = requirements
};
if (!this.AffectedShops.Contains(owner))
this.AffectedShops.Add(owner);
if (!ModEntry.AddedObjects.ContainsKey(obj.Name))
{
ModEntry.AddedObjects.Add(obj.Name, obj);
ModEntry.ReplacementStacks.Add(obj.Name, stack);
}
}
/// <summary>Raised after items are added or removed to a player's inventory.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnInventoryChanged(object sender, InventoryChangedEventArgs e)
{
// If the inventory changes while this even is hooked, we need to check if any SObject instances are in it, so we can replace them
if (e.IsLocalPlayer)
{
for (int c = 0; c < Game1.player.Items.Count; c++)
{
if (Game1.player.Items[c] is SObject obj)
{
this.Monitor.Log($"Reverting object: {obj.Name}:{obj.Stack}", LogLevel.Trace);
Game1.player.Items[c] = obj.Revert();
}
}
}
}
// Add a modified "stack" item to the shop
private void AddItem(ShopMenu menu, SObject item, string location)
{
// Check that makes sure only the items that the current shop is supposed to sell are added
if (location != item.targetedShop)
{
this.Monitor.Log("Item(" + item.Name + ':' + item.stackAmount + '*' + item.maximumStackSize() + "){Location=false}", LogLevel.Trace);
return;
}
if (!string.IsNullOrEmpty(item.requirements) && !this.Helper.Conditions().ValidateConditions(item.requirements))
{
this.Monitor.Log("Item(" + item.Name + ':' + item.stackAmount + '*' + item.maximumStackSize() + "){Location=true,Condition=false}", LogLevel.Trace);
return;
}
if (item.stackAmount == 1)
{
this.Monitor.Log("Item(" + item.Name + ':' + item.stackAmount + '*' + item.maximumStackSize() + "){Location=true,Condition=true,Stack=false}", LogLevel.Trace);
Item reverted = item.Revert();
menu.forSale.Add(reverted);
menu.itemPriceAndStock.Add(reverted, new int[2] { reverted.salePrice(), int.MaxValue });
}
else
{
this.Monitor.Log("Item(" + item.Name + ':' + item.stackAmount + '*' + item.maximumStackSize() + "){Location=true,Condition=true,Stack=true}", LogLevel.Trace);
menu.forSale.Add(item);
menu.itemPriceAndStock.Add(item, new int[2] { item.salePrice(), int.MaxValue });
}
}
/// <summary>Raised after a game menu is opened, closed, or replaced.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnMenuChanged(object sender, MenuChangedEventArgs e)
{
// when the menu closes, remove the hook for the inventory changed event
if (e.OldMenu is ShopMenu && this.EventsActive)
{
this.Helper.Events.Player.InventoryChanged -= this.OnInventoryChanged;
}
// check if the current menu is that for a shop
if (e.NewMenu is ShopMenu menu)
{
// When it is a shop menu, I need to perform some logic to identify the HatMouse, Traveler or ClintUpgrade shops as I cannot simply use their owner for that
this.Monitor.Log("Shop Menu active, checking for expansion", LogLevel.Trace);
string shopOwner = "???";
// There are by default two shops in the forest, and neither has a owner, so we need to manually resolve the shop owner
if (menu.portraitPerson != null)
{
shopOwner = menu.portraitPerson.Name;
// Clint has two shops, we need to check if this is the tool upgrade shop and modify the owner if that is the case
if (shopOwner == "Clint" && menu.potraitPersonDialogue == "I can upgrade your tools with more power. You'll have to leave them with me for a few days, though.")
shopOwner = "ClintUpgrade";
}
else
{
switch (Game1.currentLocation.Name)
{
case "Forest":
if (menu.potraitPersonDialogue == "Hiyo, poke. Did you bring coins? Gud. Me sell hats.")
shopOwner = "HatMouse";
else
{
// The merchant is a bit harder to determine then the mouse
List<string> matches = new List<string>
{
"I've got a little bit of everything. Take a look!",
"I smuggled these goods out of the Gotoro Empire. Why do you think they're so expensive?",
"I'll have new items every week, so make sure to come back!",
"Beautiful country you have here. One of my favorite stops. The pig likes it, too.",
"Let me see... Oh! I've got just what you need: "
};
// We only set the owner if it actually is the traveler, so custom unowned shops will simply remain unidentified
if (matches.Contains(menu.potraitPersonDialogue) || menu.potraitPersonDialogue.Substring(0, matches[4].Length) == matches[4])
shopOwner = "Traveler";
}
break;
case "Hospital":
shopOwner = "Hospital";
break;
case "Club":
shopOwner = "MisterQi";
break;
case "JojaMart":
shopOwner = "Joja";
break;
}
}
if (this.AffectedShops.Contains(shopOwner))
{
this.Monitor.Log($"Shop owned by `{shopOwner}` gets modified, doing so now", LogLevel.Trace);
// Register to inventory changes so we can immediately replace bought stacks
this.EventsActive = true;
this.Helper.Events.Player.InventoryChanged += this.OnInventoryChanged;
// Add our custom items to the shop
foreach (string key in ModEntry.AddedObjects.Keys)
this.AddItem(menu, ModEntry.AddedObjects[key], shopOwner);
// Use reflection to set the changed values
}
else
{
if (shopOwner.Equals("???"))
this.Monitor.Log("The shop owner could not be resolved, skipping shop", LogLevel.Trace);
else
this.Monitor.Log($"The shop owned by `{shopOwner}` is not on the list, ignoring it");
}
}
}
}
}