1using Microsoft.Xna.Framework;
3using System.Collections.Generic;
6using Terraria.ModLoader.Default;
9using Terraria.Utilities;
13 internal static class WorldIO
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);
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()
35 var stream =
new MemoryStream();
36 TagIO.ToStream(tag, stream);
37 var data = stream.ToArray();
38 FileUtilities.Write(path, data, data.Length, isCloudSave);
41 internal static void Load(
string path,
bool isCloudSave) {
42 customDataFail =
null;
43 path = Path.ChangeExtension(path,
".twld");
44 if (!FileUtilities.Exists(path, isCloudSave))
47 var buf = FileUtilities.ReadAllBytes(path, isCloudSave);
48 if (buf[0] != 0x1F || buf[1] != 0x8B) {
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"));
59 TileIO.LoadTileEntities(tag.GetList<TagCompound>(
"tileEntities"));
65 LoadNPCKillCounts(tag.GetList<TagCompound>(
"killCounts"));
66 LoadAnglerQuest(tag.GetCompound(
"anglerQuest"));
67 LoadTownManager(tag.GetList<TagCompound>(
"townManager"));
69 LoadModData(tag.GetList<TagCompound>(
"modData"));
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];
84 var itemTagList = PlayerIO.SaveInventory(chest.item);
85 if (itemTagList ==
null)
88 list.Add(
new TagCompound {
89 [
"items"] = itemTagList,
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);
103 chest = Chest.CreateChest(x, y);
105 PlayerIO.LoadInventory(Main.chest[chest].item, tag.GetList<TagCompound>(
"items"));
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)) {
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
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
141 internal static void LoadNPCs(IList<TagCompound> list) {
146 foreach (TagCompound tag
in list) {
148 int type = mod?.
NPCType(tag.GetString(
"name")) ?? 0;
150 while (nextFreeNPC < 200 && Main.npc[nextFreeNPC].active) {
153 if (nextFreeNPC >= 200) {
156 NPC npc = Main.npc[nextFreeNPC];
157 npc.SetDefaults(type);
158 npc.position.X = tag.GetFloat(
"x");
159 npc.position.Y = tag.GetFloat(
"y");
161 npc.GivenName = tag.GetString(
"displayName");
162 npc.homeless = tag.GetBool(
"homeless");
163 npc.homeTileX = tag.GetInt(
"homeTileX");
164 npc.homeTileY = tag.GetInt(
"homeTileY");
168 ModContent.GetInstance<MysteryWorld>().mysteryNPCs.Add(tag);
173 internal static List<TagCompound> SaveNPCKillCounts() {
174 var list =
new List<TagCompound>();
176 if (NPC.killCount[type] <= 0)
179 list.Add(
new TagCompound {
182 [
"count"] = NPC.killCount[type]
188 internal static void LoadNPCKillCounts(IList<TagCompound> list) {
189 foreach (var tag
in list) {
191 int type = mod?.
NPCType(tag.GetString(
"name")) ?? 0;
193 NPC.killCount[type] = tag.GetInt(
"count");
196 ModContent.GetInstance<MysteryWorld>().mysteryKillCounts.Add(tag);
201 internal static TagCompound SaveAnglerQuest() {
202 if (Main.anglerQuest <
ItemLoader.vanillaQuestFishCount)
205 int type = Main.anglerQuestItemNetIDs[Main.anglerQuest];
208 return new TagCompound {
210 [
"itemName"] = modItem.Name
214 internal static void LoadAnglerQuest(TagCompound tag) {
216 if (!tag.ContainsKey(
"mod")) {
220 int type = mod?.
ItemType(tag.GetString(
"itemName")) ?? 0;
222 for (
int k = 0; k < Main.anglerQuestItemNetIDs.Length; k++) {
223 if (Main.anglerQuestItemNetIDs[k] == type) {
224 Main.anglerQuest = k;
229 Main.AnglerQuestSwap();
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) {
237 TagCompound tag =
new TagCompound {
240 [
"x"] = pair.Item2.X,
249 internal static void LoadTownManager(IList<TagCompound> list) {
253 foreach (TagCompound tag
in list) {
255 int type = mod?.
NPCType(tag.GetString(
"name")) ?? 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;
264 internal static List<TagCompound> SaveModData() {
265 var list =
new List<TagCompound>();
267 var data = modWorld.Save();
271 list.Add(
new TagCompound {
272 [
"mod"] = modWorld.mod.Name,
273 [
"name"] = modWorld.Name,
280 internal static void LoadModData(IList<TagCompound> list) {
281 foreach (var tag
in list) {
283 var modWorld = mod?.
GetModWorld(tag.GetString(
"name"));
284 if (modWorld !=
null) {
286 if (tag.ContainsKey(
"legacyData"))
289 modWorld.Load(tag.GetCompound(
"data"));
293 "Error in reading custom world data for " + mod.Name, e);
297 ModContent.GetInstance<MysteryWorld>().data.Add(tag);
303 foreach (var modWorld
in WorldHooks.NetWorlds)
304 writer.SafeWrite(w => modWorld.NetSend(w));
307 public static void ReceiveModData(
BinaryReader reader) {
308 foreach (var modWorld
in WorldHooks.NetWorlds) {
310 reader.SafeRead(r => modWorld.NetReceive(r));
317 Logging.tML.Error($
"Above IOException error caused by {modWorld.Name} from the {modWorld.mod.Name} mod.");
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])) {
333 private static void LoadLegacy(
byte[] buffer) {
334 const int numByteFlags = 1;
335 using (MemoryStream stream =
new MemoryStream(buffer)) {
337 byte limit = reader.ReadByte();
341 byte[] flags = reader.ReadBytes(limit);
342 if (flags.Length < numByteFlags) {
343 Array.Resize(ref flags, numByteFlags);
346 LoadLegacyModWorld(flags, reader);
356 private static void LoadLegacyModWorld(
byte[] flags,
BinaryReader reader) {
357 if (flags.Length == 0) {
360 if ((flags[0] & 1) == 1) {
361 LoadLegacyChests(reader);
363 if ((flags[0] & 2) == 2) {
364 TileIO.LoadLegacyTiles(reader);
366 if ((flags[0] & 4) == 4) {
367 LoadLegacyNPCKillCounts(reader);
369 if ((flags[0] & 8) == 8) {
370 TileIO.ReadContainers(reader);
372 if ((flags[0] & 16) == 16) {
373 LoadLegacyAnglerQuest(reader);
375 if ((flags[0] & 32) == 32) {
376 LoadLegacyModData(reader);
380 private static void LoadLegacyChests(
BinaryReader reader) {
381 short count = reader.ReadInt16();
382 for (
int k = 0; k < count; k++) {
383 LoadLegacyChest(reader);
387 private static void LoadLegacyChest(
BinaryReader reader) {
388 int x = reader.ReadInt32();
389 int y = reader.ReadInt32();
390 int chest = Chest.FindChest(x, y);
392 chest = Chest.CreateChest(x, y);
395 ItemIO.LoadLegacyInventory(Main.chest[chest].item, reader,
true);
398 ItemIO.LoadLegacyInventory(
new Item[40], reader,
true);
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();
409 int type = mod ==
null ? 0 : mod.
NPCType(name);
411 NPC.killCount[type] = count;
416 private static void LoadLegacyAnglerQuest(
BinaryReader reader) {
417 string modName = reader.ReadString();
418 string name = reader.ReadString();
426 for (
int k = 0; k < Main.anglerQuestItemNetIDs.Length; k++) {
427 if (Main.anglerQuestItemNetIDs[k] == type) {
428 Main.anglerQuest = k;
435 Main.AnglerQuestSwap();
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());
447 if (modWorld !=
null) {
448 using (MemoryStream stream =
new MemoryStream(data)) {
455 "Error in reading custom world data for " + mod.
Name, e);
461 var tag =
new TagCompound {
464 [
"legacyData"] = data
466 ModContent.GetInstance<MysteryWorld>().data.Add(tag);
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);
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);
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);
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);
500 if (FileUtilities.Exists(path +
".bak", cloudSave)) {
501 FileUtilities.Copy(path +
".bak", path, cloudSave,
true);
502 FileUtilities.Delete(path +
".bak", cloudSave);
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);
511 if (FileUtilities.Exists(path +
".bad", cloudSave)) {
512 FileUtilities.Copy(path +
".bad", path, cloudSave,
true);
513 FileUtilities.Delete(path +
".bad", cloudSave);
518 internal static void EraseWorld(
string path,
bool cloudSave) {
519 path = Path.ChangeExtension(path,
".twld");
522 FileOperationAPIWrapper.MoveToRecycleBin(path);
523 FileOperationAPIWrapper.MoveToRecycleBin(path +
".bak");
526 File.Delete(path +
".bak");
529 else if (SocialAPI.Cloud !=
null) {
530 SocialAPI.Cloud.Delete(path);
static readonly Framework Framework
This serves as the central class from which item-related functions are carried out....
static ModItem GetItem(int type)
Gets the ModItem instance corresponding to the specified type. Returns null if no modded item has the...
Manages content added by mods. Liasons between mod content and Terraria's arrays and oversees the Loa...
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
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 ...
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...
This serves as the central class which loads mods. It contains many static fields and methods related...
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
This class serves as a place for you to place all your properties and hooks for each NPC....
string Name
The internal name of this NPC.
Mod mod
The mod that added this ModNPC.
A ModWorld instance represents an extension of a World. You can store fields in the ModWorld classes ...
virtual void LoadLegacy(BinaryReader reader)
Allows you to load pre-v0.9 custom data you have saved for this world.
This serves as the central class from which NPC-related functions are carried out....
static ModNPC GetNPC(int type)
Gets the ModNPC instance corresponding to the specified type.
This is where all ModWorld hooks are gathered and called.