Terraria ModLoader  0.11.7.8
A mod to make and play Terraria mods
WorldIO.cs
Go to the documentation of this file.
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Collections.Generic;
4 using System.IO;
5 using Terraria.ID;
6 using Terraria.ModLoader.Default;
8 using Terraria.Social;
9 using Terraria.Utilities;
10 
11 namespace Terraria.ModLoader.IO
12 {
13  internal static class WorldIO
14  {
15  public static CustomModDataException customDataFail;
16 
17  //add near end of Terraria.IO.WorldFile.saveWorld before releasing locks
18  internal static void Save(string path, bool isCloudSave) {
19  path = Path.ChangeExtension(path, ".twld");
20  if (FileUtilities.Exists(path, isCloudSave))
21  FileUtilities.Copy(path, path + ".bak", isCloudSave);
22 
23  var tag = new TagCompound {
24  ["chests"] = SaveChests(),
25  ["tiles"] = TileIO.SaveTiles(),
26  ["containers"] = TileIO.SaveContainers(),
27  ["npcs"] = SaveNPCs(),
28  ["tileEntities"] = TileIO.SaveTileEntities(),
29  ["killCounts"] = SaveNPCKillCounts(),
30  ["anglerQuest"] = SaveAnglerQuest(),
31  ["townManager"] = SaveTownManager(),
32  ["modData"] = SaveModData()
33  };
34 
35  var stream = new MemoryStream();
36  TagIO.ToStream(tag, stream);
37  var data = stream.ToArray();
38  FileUtilities.Write(path, data, data.Length, isCloudSave);
39  }
40  //add near end of Terraria.IO.WorldFile.loadWorld before setting failure and success
41  internal static void Load(string path, bool isCloudSave) {
42  customDataFail = null;
43  path = Path.ChangeExtension(path, ".twld");
44  if (!FileUtilities.Exists(path, isCloudSave))
45  return;
46 
47  var buf = FileUtilities.ReadAllBytes(path, isCloudSave);
48  if (buf[0] != 0x1F || buf[1] != 0x8B) {
49  LoadLegacy(buf);
50  return;
51  }
52 
53  var tag = TagIO.FromStream(new MemoryStream(buf));
54  LoadChests(tag.GetList<TagCompound>("chests"));
55  TileIO.LoadTiles(tag.GetCompound("tiles"));
56  TileIO.LoadContainers(tag.GetCompound("containers"));
57  LoadNPCs(tag.GetList<TagCompound>("npcs"));
58  try {
59  TileIO.LoadTileEntities(tag.GetList<TagCompound>("tileEntities"));
60  }
61  catch (CustomModDataException e) {
62  customDataFail = e;
63  throw;
64  }
65  LoadNPCKillCounts(tag.GetList<TagCompound>("killCounts"));
66  LoadAnglerQuest(tag.GetCompound("anglerQuest"));
67  LoadTownManager(tag.GetList<TagCompound>("townManager"));
68  try {
69  LoadModData(tag.GetList<TagCompound>("modData"));
70  }
71  catch (CustomModDataException e) {
72  customDataFail = e;
73  throw;
74  }
75  }
76 
77  internal static List<TagCompound> SaveChests() {
78  var list = new List<TagCompound>();
79  for (int k = 0; k < 1000; k++) {
80  var chest = Main.chest[k];
81  if (chest == null)
82  continue;
83 
84  var itemTagList = PlayerIO.SaveInventory(chest.item);
85  if (itemTagList == null) //doesn't need mod saving
86  continue;
87 
88  list.Add(new TagCompound {
89  ["items"] = itemTagList,
90  ["x"] = chest.x,
91  ["y"] = chest.y
92  });
93  }
94  return list;
95  }
96 
97  internal static void LoadChests(IList<TagCompound> list) {
98  foreach (var tag in list) {
99  int x = tag.GetInt("x");
100  int y = tag.GetInt("y");
101  int chest = Chest.FindChest(x, y);
102  if (chest < 0)
103  chest = Chest.CreateChest(x, y);
104  if (chest >= 0)
105  PlayerIO.LoadInventory(Main.chest[chest].item, tag.GetList<TagCompound>("items"));
106  }
107  }
108 
109  internal static List<TagCompound> SaveNPCs() {
110  var list = new List<TagCompound>();
111  for (int k = 0; k < Main.npc.Length; k++) {
112  NPC npc = Main.npc[k];
113  if (npc.active && NPCLoader.IsModNPC(npc)) {
114  if (npc.townNPC) {
115  TagCompound tag = new TagCompound {
116  ["mod"] = npc.modNPC.mod.Name,
117  ["name"] = npc.modNPC.Name,
118  ["displayName"] = npc.GivenName,
119  ["x"] = npc.position.X,
120  ["y"] = npc.position.Y,
121  ["homeless"] = npc.homeless,
122  ["homeTileX"] = npc.homeTileX,
123  ["homeTileY"] = npc.homeTileY
124  };
125  list.Add(tag);
126  }
127  else if (NPCID.Sets.SavesAndLoads[npc.type]) {
128  TagCompound tag = new TagCompound {
129  ["mod"] = npc.modNPC.mod.Name,
130  ["name"] = npc.modNPC.Name,
131  ["x"] = npc.position.X,
132  ["y"] = npc.position.Y
133  };
134  list.Add(tag);
135  }
136  }
137  }
138  return list;
139  }
140 
141  internal static void LoadNPCs(IList<TagCompound> list) {
142  if (list == null) {
143  return;
144  }
145  int nextFreeNPC = 0;
146  foreach (TagCompound tag in list) {
147  Mod mod = ModLoader.GetMod(tag.GetString("mod"));
148  int type = mod?.NPCType(tag.GetString("name")) ?? 0;
149  if (type > 0) {
150  while (nextFreeNPC < 200 && Main.npc[nextFreeNPC].active) {
151  nextFreeNPC++;
152  }
153  if (nextFreeNPC >= 200) {
154  break;
155  }
156  NPC npc = Main.npc[nextFreeNPC];
157  npc.SetDefaults(type);
158  npc.position.X = tag.GetFloat("x");
159  npc.position.Y = tag.GetFloat("y");
160  if (npc.townNPC) {
161  npc.GivenName = tag.GetString("displayName");
162  npc.homeless = tag.GetBool("homeless");
163  npc.homeTileX = tag.GetInt("homeTileX");
164  npc.homeTileY = tag.GetInt("homeTileY");
165  }
166  }
167  else {
168  ModContent.GetInstance<MysteryWorld>().mysteryNPCs.Add(tag);
169  }
170  }
171  }
172 
173  internal static List<TagCompound> SaveNPCKillCounts() {
174  var list = new List<TagCompound>();
175  for (int type = NPCID.Count; type < NPCLoader.NPCCount; type++) {
176  if (NPC.killCount[type] <= 0)
177  continue;
178 
179  list.Add(new TagCompound {
180  ["mod"] = NPCLoader.GetNPC(type).mod.Name,
181  ["name"] = NPCLoader.GetNPC(type).Name,
182  ["count"] = NPC.killCount[type]
183  });
184  }
185  return list;
186  }
187 
188  internal static void LoadNPCKillCounts(IList<TagCompound> list) {
189  foreach (var tag in list) {
190  Mod mod = ModLoader.GetMod(tag.GetString("mod"));
191  int type = mod?.NPCType(tag.GetString("name")) ?? 0;
192  if (type > 0) {
193  NPC.killCount[type] = tag.GetInt("count");
194  }
195  else {
196  ModContent.GetInstance<MysteryWorld>().mysteryKillCounts.Add(tag);
197  }
198  }
199  }
200 
201  internal static TagCompound SaveAnglerQuest() {
202  if (Main.anglerQuest < ItemLoader.vanillaQuestFishCount)
203  return null;
204 
205  int type = Main.anglerQuestItemNetIDs[Main.anglerQuest];
206  var modItem = ItemLoader.GetItem(type);
207 
208  return new TagCompound {
209  ["mod"] = modItem.mod.Name,
210  ["itemName"] = modItem.Name
211  };
212  }
213 
214  internal static void LoadAnglerQuest(TagCompound tag) {
215  // Don't try to load modded angler quest item if there isn't one
216  if (!tag.ContainsKey("mod")) {
217  return;
218  }
219  var mod = ModLoader.GetMod(tag.GetString("mod"));
220  int type = mod?.ItemType(tag.GetString("itemName")) ?? 0;
221  if (type > 0) {
222  for (int k = 0; k < Main.anglerQuestItemNetIDs.Length; k++) {
223  if (Main.anglerQuestItemNetIDs[k] == type) {
224  Main.anglerQuest = k;
225  return;
226  }
227  }
228  }
229  Main.AnglerQuestSwap();
230  }
231 
232  internal static List<TagCompound> SaveTownManager() {
233  var list = new List<TagCompound>();
234  foreach (Tuple<int, Point> pair in WorldGen.TownManager._roomLocationPairs) {
235  if (pair.Item1 >= NPCID.Count) {
236  ModNPC npc = NPCLoader.GetNPC(pair.Item1);
237  TagCompound tag = new TagCompound {
238  ["mod"] = npc.mod.Name,
239  ["name"] = npc.Name,
240  ["x"] = pair.Item2.X,
241  ["y"] = pair.Item2.Y
242  };
243  list.Add(tag);
244  }
245  }
246  return list;
247  }
248 
249  internal static void LoadTownManager(IList<TagCompound> list) {
250  if (list == null) {
251  return;
252  }
253  foreach (TagCompound tag in list) {
254  Mod mod = ModLoader.GetMod(tag.GetString("mod"));
255  int type = mod?.NPCType(tag.GetString("name")) ?? 0;
256  if (type > 0) {
257  Point location = new Point(tag.GetInt("x"), tag.GetInt("y"));
258  WorldGen.TownManager._roomLocationPairs.Add(Tuple.Create(type, location));
259  WorldGen.TownManager._hasRoom[type] = true;
260  }
261  }
262  }
263 
264  internal static List<TagCompound> SaveModData() {
265  var list = new List<TagCompound>();
266  foreach (var modWorld in WorldHooks.worlds) {
267  var data = modWorld.Save();
268  if (data == null)
269  continue;
270 
271  list.Add(new TagCompound {
272  ["mod"] = modWorld.mod.Name,
273  ["name"] = modWorld.Name,
274  ["data"] = data
275  });
276  }
277  return list;
278  }
279 
280  internal static void LoadModData(IList<TagCompound> list) {
281  foreach (var tag in list) {
282  var mod = ModLoader.GetMod(tag.GetString("mod"));
283  var modWorld = mod?.GetModWorld(tag.GetString("name"));
284  if (modWorld != null) {
285  try {
286  if (tag.ContainsKey("legacyData"))
287  modWorld.LoadLegacy(new BinaryReader(new MemoryStream(tag.GetByteArray("legacyData"))));
288  else
289  modWorld.Load(tag.GetCompound("data"));
290  }
291  catch (Exception e) {
292  throw new CustomModDataException(mod,
293  "Error in reading custom world data for " + mod.Name, e);
294  }
295  }
296  else {
297  ModContent.GetInstance<MysteryWorld>().data.Add(tag);
298  }
299  }
300  }
301 
302  public static void SendModData(BinaryWriter writer) {
303  foreach (var modWorld in WorldHooks.NetWorlds)
304  writer.SafeWrite(w => modWorld.NetSend(w));
305  }
306 
307  public static void ReceiveModData(BinaryReader reader) {
308  foreach (var modWorld in WorldHooks.NetWorlds) {
309  try {
310  reader.SafeRead(r => modWorld.NetReceive(r));
311  }
312  catch (IOException e) {
313  if (FrameworkVersion.Framework == Framework.Mono) {
314  Logging.tML.Error(e);
315  }
316 
317  Logging.tML.Error($"Above IOException error caused by {modWorld.Name} from the {modWorld.mod.Name} mod.");
318  }
319  }
320  }
321 
322  public static void ValidateSigns() {
323  for (int i = 0; i < Main.sign.Length; i++) {
324  if (Main.sign[i] != null) {
325  Tile tile = Main.tile[Main.sign[i].x, Main.sign[i].y];
326  if (!(tile.active() && Main.tileSign[(int)tile.type])) {
327  Main.sign[i] = null;
328  }
329  }
330  }
331  }
332 
333  private static void LoadLegacy(byte[] buffer) {
334  const int numByteFlags = 1;
335  using (MemoryStream stream = new MemoryStream(buffer)) {
336  using (BinaryReader reader = new BinaryReader(stream)) {
337  byte limit = reader.ReadByte();
338  if (limit == 0) {
339  return;
340  }
341  byte[] flags = reader.ReadBytes(limit);
342  if (flags.Length < numByteFlags) {
343  Array.Resize(ref flags, numByteFlags);
344  }
345  try {
346  LoadLegacyModWorld(flags, reader);
347  }
348  catch (CustomModDataException e) {
349  customDataFail = e;
350  throw;
351  }
352  }
353  }
354  }
355 
356  private static void LoadLegacyModWorld(byte[] flags, BinaryReader reader) {
357  if (flags.Length == 0) {
358  return;
359  }
360  if ((flags[0] & 1) == 1) {
361  LoadLegacyChests(reader);
362  }
363  if ((flags[0] & 2) == 2) {
364  TileIO.LoadLegacyTiles(reader);
365  }
366  if ((flags[0] & 4) == 4) {
367  LoadLegacyNPCKillCounts(reader);
368  }
369  if ((flags[0] & 8) == 8) {
370  TileIO.ReadContainers(reader);
371  }
372  if ((flags[0] & 16) == 16) {
373  LoadLegacyAnglerQuest(reader);
374  }
375  if ((flags[0] & 32) == 32) {
376  LoadLegacyModData(reader);
377  }
378  }
379 
380  private static void LoadLegacyChests(BinaryReader reader) {
381  short count = reader.ReadInt16();
382  for (int k = 0; k < count; k++) {
383  LoadLegacyChest(reader);
384  }
385  }
386 
387  private static void LoadLegacyChest(BinaryReader reader) {
388  int x = reader.ReadInt32();
389  int y = reader.ReadInt32();
390  int chest = Chest.FindChest(x, y);
391  if (chest < 0) {
392  chest = Chest.CreateChest(x, y);
393  }
394  if (chest >= 0) {
395  ItemIO.LoadLegacyInventory(Main.chest[chest].item, reader, true);
396  }
397  else {
398  ItemIO.LoadLegacyInventory(new Item[40], reader, true);
399  }
400  }
401 
402  private static void LoadLegacyNPCKillCounts(BinaryReader reader) {
403  ushort numCounts = reader.ReadUInt16();
404  for (ushort k = 0; k < numCounts; k++) {
405  string modName = reader.ReadString();
406  string name = reader.ReadString();
407  int count = reader.ReadInt32();
408  Mod mod = ModLoader.GetMod(modName);
409  int type = mod == null ? 0 : mod.NPCType(name);
410  if (type > 0) {
411  NPC.killCount[type] = count;
412  }
413  }
414  }
415 
416  private static void LoadLegacyAnglerQuest(BinaryReader reader) {
417  string modName = reader.ReadString();
418  string name = reader.ReadString();
419  Mod mod = ModLoader.GetMod(modName);
420  int type = 0;
421  if (mod != null) {
422  type = mod.ItemType(name);
423  }
424  bool flag = false;
425  if (type > 0) {
426  for (int k = 0; k < Main.anglerQuestItemNetIDs.Length; k++) {
427  if (Main.anglerQuestItemNetIDs[k] == type) {
428  Main.anglerQuest = k;
429  flag = true;
430  break;
431  }
432  }
433  }
434  if (!flag) {
435  Main.AnglerQuestSwap();
436  }
437  }
438 
439  private static void LoadLegacyModData(BinaryReader reader) {
440  int count = reader.ReadUInt16();
441  for (int k = 0; k < count; k++) {
442  string modName = reader.ReadString();
443  string name = reader.ReadString();
444  byte[] data = reader.ReadBytes(reader.ReadUInt16());
445  Mod mod = ModLoader.GetMod(modName);
446  ModWorld modWorld = mod == null ? null : mod.GetModWorld(name);
447  if (modWorld != null) {
448  using (MemoryStream stream = new MemoryStream(data)) {
449  using (BinaryReader customReader = new BinaryReader(stream)) {
450  try {
451  modWorld.LoadLegacy(customReader);
452  }
453  catch (Exception e) {
454  throw new CustomModDataException(mod,
455  "Error in reading custom world data for " + mod.Name, e);
456  }
457  }
458  }
459  }
460  else {
461  var tag = new TagCompound {
462  ["mod"] = modName,
463  ["name"] = name,
464  ["legacyData"] = data
465  };
466  ModContent.GetInstance<MysteryWorld>().data.Add(tag);
467  }
468  }
469  }
470 
471  //add to end of Terraria.IO.WorldFileData.MoveToCloud
472  internal static void MoveToCloud(string localPath, string cloudPath) {
473  localPath = Path.ChangeExtension(localPath, ".twld");
474  cloudPath = Path.ChangeExtension(cloudPath, ".twld");
475  if (File.Exists(localPath)) {
476  FileUtilities.MoveToCloud(localPath, cloudPath);
477  }
478  }
479  //add to end of Terraria.IO.WorldFileData.MoveToLocal
480  internal static void MoveToLocal(string cloudPath, string localPath) {
481  cloudPath = Path.ChangeExtension(cloudPath, ".twld");
482  localPath = Path.ChangeExtension(localPath, ".twld");
483  if (FileUtilities.Exists(cloudPath, true)) {
484  FileUtilities.MoveToLocal(cloudPath, localPath);
485  }
486  }
487  //in Terraria.Main.DrawMenu in menuMode == 200 add after moving .bak file
488  internal static void LoadBackup(string path, bool cloudSave) {
489  path = Path.ChangeExtension(path, ".twld");
490  if (FileUtilities.Exists(path + ".bak", cloudSave)) {
491  FileUtilities.Move(path + ".bak", path, cloudSave, true);
492  }
493  }
494  //in Terraria.WorldGen.do_playWorldCallback add this after moving .bak file
495  internal static void LoadDedServBackup(string path, bool cloudSave) {
496  path = Path.ChangeExtension(path, ".twld");
497  if (FileUtilities.Exists(path, cloudSave)) {
498  FileUtilities.Copy(path, path + ".bad", cloudSave, true);
499  }
500  if (FileUtilities.Exists(path + ".bak", cloudSave)) {
501  FileUtilities.Copy(path + ".bak", path, cloudSave, true);
502  FileUtilities.Delete(path + ".bak", cloudSave);
503  }
504  }
505  //in Terraria.WorldGen.do_playWorldCallback add this after returning .bak file
506  internal static void RevertDedServBackup(string path, bool cloudSave) {
507  path = Path.ChangeExtension(path, ".twld");
508  if (FileUtilities.Exists(path, cloudSave)) {
509  FileUtilities.Copy(path, path + ".bak", cloudSave, true);
510  }
511  if (FileUtilities.Exists(path + ".bad", cloudSave)) {
512  FileUtilities.Copy(path + ".bad", path, cloudSave, true);
513  FileUtilities.Delete(path + ".bad", cloudSave);
514  }
515  }
516  //in Terraria.Main.EraseWorld before reloading worlds add
517  // WorldIO.EraseWorld(Main.WorldList[i].Path, Main.WorldList[i].IsCloudSave);
518  internal static void EraseWorld(string path, bool cloudSave) {
519  path = Path.ChangeExtension(path, ".twld");
520  if (!cloudSave) {
521 #if WINDOWS
522  FileOperationAPIWrapper.MoveToRecycleBin(path);
523  FileOperationAPIWrapper.MoveToRecycleBin(path + ".bak");
524 #else
525  File.Delete(path);
526  File.Delete(path + ".bak");
527 #endif
528  }
529  else if (SocialAPI.Cloud != null) {
530  SocialAPI.Cloud.Delete(path);
531  }
532  }
533  }
534 }