forked from Pathoschild/StardewMods
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathModEntry.cs
More file actions
247 lines (215 loc) · 9.5 KB
/
ModEntry.cs
File metadata and controls
247 lines (215 loc) · 9.5 KB
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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Pathoschild.Stardew.Automate.Framework;
using Pathoschild.Stardew.Automate.Framework.Models;
using Pathoschild.Stardew.Common;
using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;
namespace Pathoschild.Stardew.Automate
{
/// <summary>The mod entry point.</summary>
internal class ModEntry : Mod
{
/*********
** Properties
*********/
/// <summary>The mod configuration.</summary>
private ModConfig Config;
/// <summary>Constructs machine instances.</summary>
private readonly MachineFactory Factory = new MachineFactory();
/// <summary>The machines to process.</summary>
private readonly IDictionary<GameLocation, MachineGroup[]> MachineGroups = new Dictionary<GameLocation, MachineGroup[]>();
/// <summary>The locations that should be reloaded on the next update tick.</summary>
private readonly HashSet<GameLocation> ReloadQueue = new HashSet<GameLocation>();
/// <summary>The number of ticks until the next automation cycle.</summary>
private int AutomateCountdown;
/// <summary>The current overlay being displayed, if any.</summary>
private OverlayMenu CurrentOverlay;
/*********
** Public methods
*********/
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</param>
public override void Entry(IModHelper helper)
{
// read config
this.Config = helper.ReadConfig<ModConfig>();
// hook events
SaveEvents.AfterLoad += this.SaveEvents_AfterLoad;
LocationEvents.CurrentLocationChanged += this.LocationEvents_CurrentLocationChanged;
LocationEvents.LocationsChanged += this.LocationEvents_LocationsChanged;
LocationEvents.LocationObjectsChanged += this.LocationEvents_LocationObjectsChanged;
GameEvents.UpdateTick += this.GameEvents_UpdateTick;
// handle player interaction
InputEvents.ButtonPressed += this.InputEvents_ButtonPressed;
// log info
if (this.Config.VerboseLogging)
this.Monitor.Log($"Verbose logging is enabled. This is useful when troubleshooting but can impact performance. It should be disabled if you don't explicitly need it. You can delete {Path.Combine(this.Helper.DirectoryPath, "config.json")} and restart the game to disable it.", LogLevel.Warn);
this.VerboseLog($"Initialised with automation every {this.Config.AutomationInterval} ticks.");
}
/*********
** Private methods
*********/
/****
** Event handlers
****/
/// <summary>The method invoked when the player loads a save.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void SaveEvents_AfterLoad(object sender, EventArgs e)
{
// reset automation interval
this.AutomateCountdown = this.Config.AutomationInterval;
this.DisableOverlay();
}
/// <summary>The method invoked when the player warps to a new location.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void LocationEvents_CurrentLocationChanged(object sender, EventArgsCurrentLocationChanged e)
{
this.ResetOverlayIfShown();
}
/// <summary>The method invoked when a location is added or removed.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void LocationEvents_LocationsChanged(object sender, EventArgsGameLocationsChanged e)
{
this.VerboseLog("Location list changed, reloading all machines.");
try
{
this.MachineGroups.Clear();
foreach (GameLocation location in CommonHelper.GetLocations())
this.ReloadQueue.Add(location);
}
catch (Exception ex)
{
this.HandleError(ex, "updating locations");
}
}
/// <summary>The method invoked when an object is added or removed to a location.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void LocationEvents_LocationObjectsChanged(object sender, EventArgsLocationObjectsChanged e)
{
this.VerboseLog("Object list changed, reloading machines in current location.");
try
{
this.ReloadQueue.Add(Game1.currentLocation);
}
catch (Exception ex)
{
this.HandleError(ex, "updating the current location");
}
}
/// <summary>The method invoked when the in-game clock time changes.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void GameEvents_UpdateTick(object sender, EventArgs e)
{
if (!Context.IsWorldReady)
return;
try
{
// handle delay
this.AutomateCountdown--;
if (this.AutomateCountdown > 0)
return;
this.AutomateCountdown = this.Config.AutomationInterval;
// reload machines if needed
if (this.ReloadQueue.Any())
{
foreach (GameLocation location in this.ReloadQueue)
this.ReloadMachinesIn(location);
this.ReloadQueue.Clear();
this.ResetOverlayIfShown();
}
// process machines
foreach (MachineGroup group in this.GetAllMachineGroups())
group.Automate();
}
catch (Exception ex)
{
this.HandleError(ex, "processing machines");
}
}
/// <summary>The method invoked when the player presses a button.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void InputEvents_ButtonPressed(object sender, EventArgsInput e)
{
try
{
// toggle overlay
if (Context.IsPlayerFree && this.Config.Controls.ToggleOverlay.Contains(e.Button))
{
if (this.CurrentOverlay != null)
this.DisableOverlay();
else
this.EnableOverlay();
}
}
catch (Exception ex)
{
this.HandleError(ex, "handling key input");
}
}
/****
** Methods
****/
/// <summary>Get the machine groups in every location.</summary>
private IEnumerable<MachineGroup> GetAllMachineGroups()
{
foreach (KeyValuePair<GameLocation, MachineGroup[]> group in this.MachineGroups)
{
foreach (MachineGroup machineGroup in group.Value)
yield return machineGroup;
}
}
/// <summary>Reload the machines in a given location.</summary>
/// <param name="location">The location whose machines to reload.</param>
private void ReloadMachinesIn(GameLocation location)
{
this.VerboseLog($"Reloading machines in {location.Name}...");
this.MachineGroups[location] = this.Factory.GetActiveMachinesGroups(location, this.Helper.Reflection).ToArray();
}
/// <summary>Log an error and warn the user.</summary>
/// <param name="ex">The exception to handle.</param>
/// <param name="verb">The verb describing where the error occurred (e.g. "looking that up").</param>
private void HandleError(Exception ex, string verb)
{
this.Monitor.Log($"Something went wrong {verb}:\n{ex}", LogLevel.Error);
CommonHelper.ShowErrorMessage($"Huh. Something went wrong {verb}. The error log has the technical details.");
}
/// <summary>Log a trace message if verbose logging is enabled.</summary>
/// <param name="message">The message to log.</param>
private void VerboseLog(string message)
{
if (this.Config.VerboseLogging)
this.Monitor.Log(message, LogLevel.Trace);
}
/// <summary>Disable the overlay, if shown.</summary>
private void DisableOverlay()
{
this.CurrentOverlay?.Dispose();
this.CurrentOverlay = null;
}
/// <summary>Enable the overlay.</summary>
private void EnableOverlay()
{
if (this.CurrentOverlay == null)
this.CurrentOverlay = new OverlayMenu(this.Factory.GetMachineGroups(Game1.currentLocation, this.Helper.Reflection));
}
/// <summary>Reset the overlay if it's being shown.</summary>
private void ResetOverlayIfShown()
{
if (this.CurrentOverlay != null)
{
this.DisableOverlay();
this.EnableOverlay();
}
}
}
}