-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathModEntry.cs
345 lines (316 loc) · 15.6 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
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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ionic.Zip;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Objects;
using StardewValley.TerrainFeatures;
namespace Entoarox.SeasonalImmersion
{
/// <summary>The mod entry class.</summary>
public class ModEntry : Mod
{
/*********
** Fields
*********/
private static readonly BlendState BlendAlpha = new BlendState
{
ColorWriteChannels = ColorWriteChannels.Alpha,
AlphaDestinationBlend = Blend.Zero,
ColorDestinationBlend = Blend.Zero,
AlphaSourceBlend = Blend.One,
ColorSourceBlend = Blend.One
};
private static readonly BlendState BlendColor = new BlendState
{
ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue,
AlphaDestinationBlend = Blend.Zero,
ColorDestinationBlend = Blend.Zero,
AlphaSourceBlend = Blend.SourceAlpha,
ColorSourceBlend = Blend.SourceAlpha
};
private static readonly string[] Seasons = { "spring", "summer", "fall", "winter" };
private static string FilePath;
private ModConfig Config;
private ContentMode Mode = 0;
private ZipFile Zip;
/*********
** Accessors
*********/
internal static Dictionary<string, Dictionary<string, Texture2D>> SeasonTextures;
/*********
** 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)
{
this.Config = helper.ReadConfig<ModConfig>();
ModEntry.FilePath = helper.DirectoryPath;
helper.Events.GameLoop.GameLaunched += this.OnGameLaunched;
}
/*********
** Protected methods
*********/
/// <summary>Raised after the game is launched, right before the first update tick. This happens once per game session (unrelated to loading saves). All mods are loaded and initialised at this point, so this is a good time to set up mod integrations.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event data.</param>
private void OnGameLaunched(object sender, GameLaunchedEventArgs e)
{
try
{
this.VerboseLog("Loading Seasonal Immersion ContentPack...");
this.LoadContent();
}
catch (Exception ex)
{
this.Monitor.Log($"Could not load ContentPack\n{ex.Message}\n{ex.StackTrace}", LogLevel.Error);
}
this.Helper.Content.AssetLoaders.Add(new SeasonalTextureLoader());
}
private Texture2D PreMultiply(Texture2D texture)
{
try
{
RenderTarget2D result = new RenderTarget2D(Game1.graphics.GraphicsDevice, texture.Width, texture.Height);
Game1.graphics.GraphicsDevice.SetRenderTarget(result);
Game1.graphics.GraphicsDevice.Clear(Color.Black);
Game1.spriteBatch.Begin(SpriteSortMode.Immediate, ModEntry.BlendColor);
Game1.spriteBatch.Draw(texture, texture.Bounds, Color.White);
Game1.spriteBatch.End();
Game1.spriteBatch.Begin(SpriteSortMode.Immediate, ModEntry.BlendAlpha);
Game1.spriteBatch.Draw(texture, texture.Bounds, Color.White);
Game1.spriteBatch.End();
Game1.graphics.GraphicsDevice.SetRenderTarget(null);
MemoryStream stream = new MemoryStream();
result.SaveAsPng(stream, texture.Width, texture.Height);
return Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
}
catch (Exception err)
{
this.Monitor.Log($"Failed to PreMultiply texture, alpha will not work properly\n{err.Message}\n{err.StackTrace}", LogLevel.Warn);
return texture;
}
}
private void LoadContent()
{
if (ModEntry.SeasonTextures != null)
this.Monitor.Log("SeasonalImmersionMod::Entry has already been called previously, this shouldnt be happening!", LogLevel.Warn);
this.VerboseLog("Attempting to resolve content pack...");
if (Directory.Exists(Path.Combine(ModEntry.FilePath, "ContentPack")))
this.Mode = ContentMode.Directory;
else if (File.Exists(Path.Combine(ModEntry.FilePath, "ContentPack.zip")))
{
try
{
this.Zip = new ZipFile(Path.Combine(ModEntry.FilePath, "ContentPack.zip"));
this.Mode = ContentMode.Zipped;
}
catch (Exception ex)
{
this.Monitor.Log($"Was unable to reference ContentPack.zip file, using internal content pack as a fallback.\n{ex.Message}\n{ex.StackTrace}", LogLevel.Error);
this.Mode = ContentMode.Internal;
}
}
else
this.Mode = ContentMode.Internal;
Stream stream = this.GetStream("manifest.json");
if (stream == null)
{
switch (this.Mode)
{
case ContentMode.Directory:
this.Monitor.Log("Found `ContentPack` directory but the `manifest.json` file is missing, falling back to internal.", LogLevel.Error);
this.Mode = ContentMode.Internal;
stream = this.GetStream("manifest.json");
break;
case ContentMode.Zipped:
this.Monitor.Log("Found `ContentPack.zip` file but the `manifest.json` file is missing, falling back to internal.", LogLevel.Error);
this.Mode = ContentMode.Internal;
stream = this.GetStream("manifest.json");
break;
}
}
if (stream == null && this.Mode == ContentMode.Internal)
{
this.Monitor.Log("Attempted to use internal ContentPack but could not resolve manifest, disabling mod.", LogLevel.Error);
return;
}
this.VerboseLog($"Content pack resolved to mode: {this.Mode}");
ModEntry.SeasonTextures = new Dictionary<string, Dictionary<string, Texture2D>>();
ContentPackManifest manifest = JsonConvert.DeserializeObject<ContentPackManifest>(new StreamReader(stream).ReadToEnd(), new VersionConverter());
this.Monitor.Log($"Using the `{manifest.Name}` content pack, version {manifest.Version} by {manifest.Author}", LogLevel.Info);
// Resolve content dir cause CA messes all stuffs up...
List<string> Files;
if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "Resources", Game1.content.RootDirectory, "XACT", "FarmerSounds.xgs")))
Files = new List<string>(Directory.EnumerateFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "Resources", Game1.content.RootDirectory, "Buildings")).Where(a => Path.GetExtension(a).Equals(".xnb")));
else
Files = new List<string>(Directory.EnumerateFiles(Path.Combine(Game1.content.RootDirectory, "Buildings")).Where(a => Path.GetExtension(a).Equals(".xnb")));
Files.AddRange(new[]
{
"Flooring.xnb",
"Craftables.xnb",
"Craftables_outdoor.xnb",
"Craftables_indoor.xnb"
});
foreach (string file in Files)
{
Dictionary<string, Texture2D> textures = new Dictionary<string, Texture2D>();
string name = Path.GetFileNameWithoutExtension(file);
this.VerboseLog($"Checking if file is seasonal: {name}");
int count = 0;
foreach (string season in ModEntry.Seasons)
{
Texture2D tex = this.GetTexture(Path.Combine(season, name + ".png"));
if (tex == null)
continue;
count++;
textures.Add(season, tex);
}
if (count != 4)
{
if (count > 0)
this.Monitor.Log($"Skipping file due to the textures being incomplete: {file}", LogLevel.Warn);
else
this.VerboseLog($"Skipping file due to there being no textures for it found: {file}");
continue;
}
this.VerboseLog($"Making file seasonal: {file}");
ModEntry.SeasonTextures.Add(name, textures);
}
this.Helper.Events.Player.Warped += this.OnWarped;
this.Helper.Events.GameLoop.DayStarted += this.OnDayStarted;
this.Monitor.Log($"ContentPack processed, found [{ModEntry.SeasonTextures.Count}] seasonal files", LogLevel.Info);
}
private Stream GetStream(string file)
{
try
{
switch (this.Mode)
{
case ContentMode.Directory:
if (!File.Exists(Path.Combine(ModEntry.FilePath, "ContentPack", file)))
{
this.VerboseLog($"Skipping file due to being unable to find it in the ContentPack directory: {file}");
return null;
}
return new FileStream(Path.Combine(ModEntry.FilePath, "ContentPack", file), FileMode.Open);
case ContentMode.Zipped:
if (!this.Zip.ContainsEntry(file))
{
this.VerboseLog($"Skipping file due to being unable to find it in the ContentPack.zip: {file}");
return null;
}
MemoryStream memoryStream = new MemoryStream();
this.Zip[file].Extract(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
case ContentMode.Internal:
Stream manifestStream = this.GetType().Assembly.GetManifestResourceStream("Entoarox.SeasonalImmersion.ContentPack." + file.Replace(Path.DirectorySeparatorChar, '.'));
if (manifestStream == null)
{
this.VerboseLog($"Skipping file due to being unable to find it in the bundled resource pack: {file}");
return null;
}
return manifestStream;
default:
this.Monitor.Log($"Skipping file due to the content pack location being unknown: {file}", LogLevel.Error);
return null;
}
}
catch (Exception ex)
{
this.Monitor.Log($"Skipping file due to a unknown error: {file}\n{ex}", LogLevel.Error);
return null;
}
}
private Texture2D GetTexture(string file)
{
Stream stream = this.GetStream(file);
if (stream == null)
return null;
try
{
return this.PreMultiply(Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream));
}
catch (Exception ex)
{
this.Monitor.Log($"Skipping texture due to a unknown error: {file}\n{ex}", LogLevel.Warn);
return null;
}
}
private void UpdateTextures()
{
if (ModEntry.SeasonTextures.Count == 0)
return;
try
{
this.VerboseLog("Attempting to update seasonal textures...");
// Check houses/greenhouse
if (ModEntry.SeasonTextures.ContainsKey("houses"))
this.Helper.Reflection.GetField<Texture2D>(typeof(Farm), nameof(Farm.houseTextures)).SetValue(ModEntry.SeasonTextures["houses"][Game1.currentSeason]);
// Check flooring
if (ModEntry.SeasonTextures.ContainsKey("Flooring"))
Flooring.floorsTexture = ModEntry.SeasonTextures["Flooring"][Game1.currentSeason];
// Check furniture
if (ModEntry.SeasonTextures.ContainsKey("furniture"))
Furniture.furnitureTexture = ModEntry.SeasonTextures["furniture"][Game1.currentSeason];
// Check outdoor craftables
if (Game1.currentLocation.IsOutdoors && ModEntry.SeasonTextures.ContainsKey("Craftables_outdoor"))
{
this.Helper.Content.InvalidateCache("TileSheets\\Craftables_outdoor");
Game1.bigCraftableSpriteSheet = Game1.content.Load<Texture2D>("TileSheets\\Craftables_outdoor");
}
// Check indoor craftables
else if (!Game1.currentLocation.IsOutdoors && ModEntry.SeasonTextures.ContainsKey("Craftables_indoor"))
{
this.Helper.Content.InvalidateCache("TileSheets\\Craftables_indoor");
Game1.bigCraftableSpriteSheet = Game1.content.Load<Texture2D>("TileSheets\\Craftables_indoor");
}
// Reset big craftables
else
{
this.Helper.Content.InvalidateCache("TileSheets\\Craftables");
Game1.bigCraftableSpriteSheet = Game1.content.Load<Texture2D>("TileSheets\\Craftables");
}
// Loop buildings
foreach (Building building in Game1.getFarm().buildings)
if (ModEntry.SeasonTextures.ContainsKey(building.buildingType.Value))
building.texture = new Lazy<Texture2D>(() => ModEntry.SeasonTextures[building.buildingType.Value][Game1.currentSeason]);
}
catch (Exception ex)
{
this.Monitor.Log($"Failed to update seasonal textures\n{ex}", LogLevel.Error);
}
}
/// <summary>Raised after the game begins a new day (including when the player loads a save).</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event data.</param>
private void OnDayStarted(object sender, DayStartedEventArgs e)
{
if (Game1.dayOfMonth == 1)
this.UpdateTextures();
}
/// <summary>Raised after a player warps to a new location. NOTE: this event is currently only raised for the current player.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event data.</param>
private void OnWarped(object sender, WarpedEventArgs e)
{
if (e.NewLocation.Name == "Farm" || (e.OldLocation != null && e.OldLocation.IsOutdoors != e.NewLocation.IsOutdoors))
this.UpdateTextures();
}
private void VerboseLog(string message)
{
if (this.Config.VerboseLog)
this.Monitor.Log(message, LogLevel.Trace);
}
}
}