tModLoader v0.11.8.9
A mod to make and play Terraria mods
WorldIO.cs
Go to the documentation of this file.
1using Microsoft.Xna.Framework;
2using System;
3using System.Collections.Generic;
4using System.IO;
5using Terraria.ID;
6using Terraria.ModLoader.Default;
8using Terraria.Social;
9using Terraria.Utilities;
10
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) {
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}
static readonly Framework Framework
This serves as the central class from which item-related functions are carried out....
Definition: ItemLoader.cs:22
static ModItem GetItem(int type)
Gets the ModItem instance corresponding to the specified type. Returns null if no modded item has the...
Definition: ItemLoader.cs:76
Manages content added by mods. Liasons between mod content and Terraria's arrays and oversees the Loa...
Definition: ModContent.cs:27
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
Definition: Mod.cs:25
ModWorld GetModWorld(string name)
Gets the ModWorld instance with the given name from this mod.
int ItemType(string name)
Gets the internal ID / type of the ModItem corresponding to the name. Returns 0 if no ModItem with th...
virtual string Name
Stores the name of the mod. This name serves as the mod's identification, and also helps with saving ...
Definition: Mod.cs:42
int NPCType(string name)
Gets the type of the ModNPC of this mod with the given name. Returns 0 if no ModNPC with the given na...
Mod mod
Gets the mod.
Definition: ModItem.cs:37
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:29
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
Definition: ModLoader.cs:90
This class serves as a place for you to place all your properties and hooks for each NPC....
Definition: ModNPC.cs:15
string Name
The internal name of this NPC.
Definition: ModNPC.cs:37
Mod mod
The mod that added this ModNPC.
Definition: ModNPC.cs:29
A ModWorld instance represents an extension of a World. You can store fields in the ModWorld classes ...
Definition: ModWorld.cs:12
virtual void LoadLegacy(BinaryReader reader)
Allows you to load pre-v0.9 custom data you have saved for this world.
Definition: ModWorld.cs:58
This serves as the central class from which NPC-related functions are carried out....
Definition: NPCLoader.cs:20
static ModNPC GetNPC(int type)
Gets the ModNPC instance corresponding to the specified type.
Definition: NPCLoader.cs:93
This is where all ModWorld hooks are gathered and called.
Definition: WorldHooks.cs:13