This commit is contained in:
Anuken
2026-07-01 01:36:58 -04:00
parent 5ae986a6dd
commit a4178e4c18
13 changed files with 95 additions and 40 deletions
+2
View File
@@ -401,6 +401,8 @@ public class Control implements ApplicationListener, Loadable{
ui.loadAnd(() -> { ui.loadAnd(() -> {
logic.reset(); logic.reset();
world.loadMap(map, rules); world.loadMap(map, rules);
var oldRules = state.rules;
rules.retainContentFields(oldRules);
state.rules = rules; state.rules = rules;
if(playtest) state.playtestingMap = map; if(playtest) state.playtestingMap = map;
state.rules.sector = null; state.rules.sector = null;
+5
View File
@@ -65,6 +65,11 @@ public abstract class Content implements Comparable<Content>{
return !isVanilla(); return !isVanilla();
} }
/** @return whether this content is from a map data patch. */
public boolean isPatchContent(){
return minfo.mod == DataPatcher.dpMod;
}
@Override @Override
public int compareTo(Content c){ public int compareTo(Content c){
return Integer.compare(id, c.id); return Integer.compare(id, c.id);
+17
View File
@@ -245,6 +245,23 @@ public class Rules{
return JsonIO.copy(this); return JsonIO.copy(this);
} }
/**
* When a map is played, it uses rules from the rules dialog, which cannot contain patched content, since it doesn't exist at that point in time.
* This means that any existing rules containing patched content will contain garbage or empty data.
* This function copies original map rule data from {@param source} (obtained after map load) that may contain new content into this ruleset.
* */
public void retainContentFields(Rules source){
//these fields can't be modified in the custom rules anyway, so force-overwriting them is fine
spawns = source.spawns;
objectives = source.objectives;
weather = source.weather;
//TODO: this overwrites banned blocks/units and loadouts if someone set it in custom rules when playing; there isn't a good way to avoid this
if(Seq.with(source.bannedBlocks).contains(Content::isPatchContent)) bannedBlocks = source.bannedBlocks;
if(Seq.with(source.bannedUnits).contains(Content::isPatchContent)) bannedUnits = source.bannedUnits;
if(source.loadout.contains(i -> i.item.isPatchContent())) loadout = source.loadout;
}
/** Returns the gamemode that best fits these rules. */ /** Returns the gamemode that best fits these rules. */
public Gamemode mode(){ public Gamemode mode(){
if(pvp){ if(pvp){
+1 -2
View File
@@ -3,7 +3,6 @@ package mindustry.io;
import arc.struct.*; import arc.struct.*;
import arc.struct.ObjectMap.*; import arc.struct.ObjectMap.*;
import arc.util.io.*; import arc.util.io.*;
import mindustry.world.*;
import java.io.*; import java.io.*;
@@ -190,7 +189,7 @@ public abstract class SaveFileReader{
return map; return map;
} }
public abstract void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException; public abstract void read(DataInputStream stream, CounterInputStream counter, SaveReadState state) throws IOException;
public abstract void write(DataOutputStream stream, SaveOptions options) throws IOException; public abstract void write(DataOutputStream stream, SaveOptions options) throws IOException;
+1 -1
View File
@@ -169,7 +169,7 @@ public class SaveIO{
if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?"); if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?");
ver.read(stream, counter, context); ver.read(stream, counter, new SaveReadState(context));
Events.fire(new SaveLoadEvent(context.isMap())); Events.fire(new SaveLoadEvent(context.isMap()));
}catch(Throwable e){ }catch(Throwable e){
throw new SaveException(e); throw new SaveException(e);
+13
View File
@@ -0,0 +1,13 @@
package mindustry.io;
import arc.util.*;
import mindustry.world.*;
public class SaveReadState{
public final WorldContext context;
public @Nullable String ruleString;
public SaveReadState(WorldContext context){
this.context = context;
}
}
+33 -17
View File
@@ -60,13 +60,13 @@ public abstract class SaveVersion extends SaveFileReader{
} }
@Override @Override
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{ public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, context)); readRegion("meta", stream, counter, in -> readMeta(in, saveState));
if(version >= 12) readRegion("patches", stream, counter, this::readDataPatches); if(version >= 12) readRegion("patches", stream, counter, in -> readDataPatches(in, saveState));
try{ try{
readRegion("content", stream, counter, this::readContentHeader); readRegion("content", stream, counter, this::readContentHeader);
readRegion("map", stream, counter, in -> readMap(in, context)); readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities); readRegion("entities", stream, counter, this::readEntities);
if(version >= 8) readRegion("markers", stream, counter, this::readMarkers); if(version >= 8) readRegion("markers", stream, counter, this::readMarkers);
readRegion("custom", stream, counter, this::readCustomChunks); readRegion("custom", stream, counter, this::readCustomChunks);
@@ -75,6 +75,7 @@ public abstract class SaveVersion extends SaveFileReader{
} }
} }
@Override
public void write(DataOutputStream stream, SaveOptions options) throws IOException{ public void write(DataOutputStream stream, SaveOptions options) throws IOException{
writeRegion("meta", stream, out -> writeMeta(out, options.extraTags)); writeRegion("meta", stream, out -> writeMeta(out, options.extraTags));
writeRegion("patches", stream, out -> writeDataPatches(out, options.embedAssets)); writeRegion("patches", stream, out -> writeDataPatches(out, options.embedAssets));
@@ -148,27 +149,20 @@ public abstract class SaveVersion extends SaveFileReader{
))); )));
} }
public void readMeta(DataInput stream, WorldContext context) throws IOException{ public void readMeta(DataInput stream, SaveReadState saveState) throws IOException{
StringMap map = readStringMap(stream); StringMap map = readStringMap(stream);
state.wave = map.getInt("wave"); state.wave = map.getInt("wave");
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing); state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
state.tick = map.getFloat("tick"); state.tick = map.getFloat("tick");
state.stats = JsonIO.read(GameStats.class, map.get("stats", "{}")); state.stats = JsonIO.read(GameStats.class, map.get("stats", "{}"));
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
state.mapLocales = JsonIO.read(MapLocales.class, map.get("locales", "{}")); state.mapLocales = JsonIO.read(MapLocales.class, map.get("locales", "{}"));
if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
if(context.getSector() != null){ saveState.ruleString = map.get("rules", "{}");
state.rules.sector = context.getSector();
if(state.rules.sector != null){
state.rules.sector.planet.applyRules(state.rules);
}
}
//replace the default serpulo env with erekir //for versions >= 13, rules are parsed after data patches are loaded
if(state.rules.planet == Planets.serpulo && state.rules.hasEnv(Env.scorching)){ if(version < 13){
state.rules.planet = Planets.erekir; readRules(saveState);
} }
if(!headless){ if(!headless){
@@ -196,6 +190,25 @@ public abstract class SaveVersion extends SaveFileReader{
)) : worldmap; )) : worldmap;
} }
public void readRules(SaveReadState saveState){
if(saveState.ruleString == null) return; //in NetworkIO, rules are null, not read here
state.rules = JsonIO.read(Rules.class, saveState.ruleString);
if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
if(saveState.context.getSector() != null){
state.rules.sector = saveState.context.getSector();
if(state.rules.sector != null){
state.rules.sector.planet.applyRules(state.rules);
}
}
//replace the default serpulo env with erekir
if(state.rules.planet == Planets.serpulo && state.rules.hasEnv(Env.scorching)){
state.rules.planet = Planets.erekir;
}
}
public void writeMap(DataOutput stream) throws IOException{ public void writeMap(DataOutput stream) throws IOException{
//write world size //write world size
stream.writeShort(world.width()); stream.writeShort(world.width());
@@ -527,7 +540,7 @@ public abstract class SaveVersion extends SaveFileReader{
readWorldEntities(stream, mapping); readWorldEntities(stream, mapping);
} }
public void readDataPatches(DataInput stream) throws IOException{ public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
stream.readInt(); //version - ignored for now stream.readInt(); //version - ignored for now
int total = stream.readInt(); int total = stream.readInt();
@@ -563,6 +576,9 @@ public abstract class SaveVersion extends SaveFileReader{
Events.fire(new DataPatchLoadEvent(assets)); Events.fire(new DataPatchLoadEvent(assets));
state.data.load(assets); state.data.load(assets);
//now that patches are loaded, the rules can actually be read
readRules(saveState);
} }
public void writeDataPatches(DataOutput stream, boolean forceEmbed) throws IOException{ public void writeDataPatches(DataOutput stream, boolean forceEmbed) throws IOException{
@@ -1,7 +1,7 @@
package mindustry.io.versions; package mindustry.io.versions;
import arc.util.io.*; import arc.util.io.*;
import mindustry.world.*; import mindustry.io.*;
import java.io.*; import java.io.*;
@@ -15,12 +15,12 @@ public class LegacyRegionSaveVersion extends ShortChunkSaveVersion{
} }
@Override @Override
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{ public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, context)); readRegion("meta", stream, counter, in -> readMeta(in, saveState));
readRegion("content", stream, counter, this::readContentHeader); readRegion("content", stream, counter, this::readContentHeader);
try{ try{
readRegion("map", stream, counter, in -> readMap(in, context)); readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities); readRegion("entities", stream, counter, this::readEntities);
}finally{ }finally{
content.setTemporaryMapper(null); content.setTemporaryMapper(null);
+5 -6
View File
@@ -7,7 +7,6 @@ import arc.util.io.*;
import mindustry.game.EventType.*; import mindustry.game.EventType.*;
import mindustry.io.*; import mindustry.io.*;
import mindustry.mod.data.*; import mindustry.mod.data.*;
import mindustry.world.*;
import java.io.*; import java.io.*;
@@ -23,13 +22,13 @@ public class Save11 extends SaveVersion{
} }
@Override @Override
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{ public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, context)); readRegion("meta", stream, counter, in -> readMeta(in, saveState));
readRegion("content", stream, counter, this::readContentHeader); readRegion("content", stream, counter, this::readContentHeader);
try{ try{
readRegion("patches", stream, counter, this::readDataPatches); readRegion("patches", stream, counter, in -> readDataPatches(in, saveState));
readRegion("map", stream, counter, in -> readMap(in, context)); readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities); readRegion("entities", stream, counter, this::readEntities);
readRegion("markers", stream, counter, this::readMarkers); readRegion("markers", stream, counter, this::readMarkers);
readRegion("custom", stream, counter, this::readCustomChunks); readRegion("custom", stream, counter, this::readCustomChunks);
@@ -40,7 +39,7 @@ public class Save11 extends SaveVersion{
//old, simplified string-only data patches //old, simplified string-only data patches
@Override @Override
public void readDataPatches(DataInput stream) throws IOException{ public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
Seq<DataAsset> assets = new Seq<>(); Seq<DataAsset> assets = new Seq<>();
int amount = stream.readUnsignedByte(); int amount = stream.readUnsignedByte();
+1 -1
View File
@@ -19,7 +19,7 @@ public class Save12 extends SaveVersion{
} }
@Override @Override
public void readDataPatches(DataInput stream) throws IOException{ public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
stream.readInt(); //version - ignored for now stream.readInt(); //version - ignored for now
Seq<DataAsset> assets = new Seq<>(); Seq<DataAsset> assets = new Seq<>();
+1 -3
View File
@@ -100,9 +100,7 @@ public class Map implements Comparable<Map>, Publishable{
public Rules rules(Rules base){ public Rules rules(Rules base){
try{ try{
//this replacement is a MASSIVE hack but it fixes some incorrect overwriting of team-specific rules. Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}"));
//may need to be tweaked later
Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}").replace("teams:{2:{infiniteAmmo:true}},", ""));
//replace the default serpulo env with erekir //replace the default serpulo env with erekir
if(result.planet == Planets.serpulo && result.hasEnv(Env.scorching)){ if(result.planet == Planets.serpulo && result.hasEnv(Env.scorching)){
result.planet = Planets.erekir; result.planet = Planets.erekir;
+7 -4
View File
@@ -29,6 +29,12 @@ import java.util.*;
/** The current implementation is awful. Consider it a proof of concept. */ /** The current implementation is awful. Consider it a proof of concept. */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class DataPatcher{ public class DataPatcher{
private static ModMeta dpModMeta = new ModMeta(){{
name = internalName = "dp";
}};
public static final LoadedMod dpMod = new LoadedMod(new Fi("dp"), new Fi(""), null, null, dpModMeta);
public static final int maxImageSize = 2000; public static final int maxImageSize = 2000;
public static final int patchFormatVersion = 2; public static final int patchFormatVersion = 2;
@@ -37,10 +43,7 @@ public class DataPatcher{
private static final ObjectMap<String, ContentType> nameToType = new ObjectMap<>(); private static final ObjectMap<String, ContentType> nameToType = new ObjectMap<>();
private static DataPatcher currentDataPatcher; private static DataPatcher currentDataPatcher;
private static ContentParser parser = createParser(); private static ContentParser parser = createParser();
private static ModMeta dpModMeta = new ModMeta(){{
name = internalName = "dp";
}};
private static LoadedMod dpMod = new LoadedMod(new Fi("dp"), new Fi(""), null, null, dpModMeta);
private boolean applied; private boolean applied;
private ContentLoader contentLoader; private ContentLoader contentLoader;
+5 -2
View File
@@ -42,6 +42,9 @@ public class NetworkIO{
} }
} }
//data patches must be first, as rules can involve patched content
SaveIO.getSaveWriter().writeDataPatches(stream, false);
stream.writeUTF(JsonIO.write(state.rules)); stream.writeUTF(JsonIO.write(state.rules));
stream.writeUTF(JsonIO.write(state.mapLocales)); stream.writeUTF(JsonIO.write(state.mapLocales));
SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags); SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags);
@@ -55,7 +58,6 @@ public class NetworkIO{
stream.writeInt(player.id); stream.writeInt(player.id);
player.write(new Writes(stream)); player.write(new Writes(stream));
SaveIO.getSaveWriter().writeDataPatches(stream, false);
SaveIO.getSaveWriter().writeContentHeader(stream); SaveIO.getSaveWriter().writeContentHeader(stream);
SaveIO.getSaveWriter().writeMap(stream); SaveIO.getSaveWriter().writeMap(stream);
SaveIO.getSaveWriter().writeTeamBlocks(stream); SaveIO.getSaveWriter().writeTeamBlocks(stream);
@@ -70,6 +72,8 @@ public class NetworkIO{
try(DataInputStream stream = new DataInputStream(is)){ try(DataInputStream stream = new DataInputStream(is)){
Time.clear(); Time.clear();
SaveIO.getSaveWriter().readDataPatches(stream, new SaveReadState(world.context));
state.rules = JsonIO.read(Rules.class, stream.readUTF()); state.rules = JsonIO.read(Rules.class, stream.readUTF());
state.mapLocales = JsonIO.read(MapLocales.class, stream.readUTF()); state.mapLocales = JsonIO.read(MapLocales.class, stream.readUTF());
state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream)); state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream));
@@ -89,7 +93,6 @@ public class NetworkIO{
player.id = id; player.id = id;
player.add(); player.add();
SaveIO.getSaveWriter().readDataPatches(stream);
SaveIO.getSaveWriter().readContentHeader(stream); SaveIO.getSaveWriter().readContentHeader(stream);
SaveIO.getSaveWriter().readMap(stream, world.context); SaveIO.getSaveWriter().readMap(stream, world.context);
SaveIO.getSaveWriter().readTeamBlocks(stream); SaveIO.getSaveWriter().readTeamBlocks(stream);