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(() -> {
logic.reset();
world.loadMap(map, rules);
var oldRules = state.rules;
rules.retainContentFields(oldRules);
state.rules = rules;
if(playtest) state.playtestingMap = map;
state.rules.sector = null;
+5
View File
@@ -65,6 +65,11 @@ public abstract class Content implements Comparable<Content>{
return !isVanilla();
}
/** @return whether this content is from a map data patch. */
public boolean isPatchContent(){
return minfo.mod == DataPatcher.dpMod;
}
@Override
public int compareTo(Content c){
return Integer.compare(id, c.id);
+17
View File
@@ -245,6 +245,23 @@ public class Rules{
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. */
public Gamemode mode(){
if(pvp){
+1 -2
View File
@@ -3,7 +3,6 @@ package mindustry.io;
import arc.struct.*;
import arc.struct.ObjectMap.*;
import arc.util.io.*;
import mindustry.world.*;
import java.io.*;
@@ -190,7 +189,7 @@ public abstract class SaveFileReader{
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;
+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?");
ver.read(stream, counter, context);
ver.read(stream, counter, new SaveReadState(context));
Events.fire(new SaveLoadEvent(context.isMap()));
}catch(Throwable 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
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, context));
if(version >= 12) readRegion("patches", stream, counter, this::readDataPatches);
public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, saveState));
if(version >= 12) readRegion("patches", stream, counter, in -> readDataPatches(in, saveState));
try{
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);
if(version >= 8) readRegion("markers", stream, counter, this::readMarkers);
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{
writeRegion("meta", stream, out -> writeMeta(out, options.extraTags));
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);
state.wave = map.getInt("wave");
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
state.tick = map.getFloat("tick");
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", "{}"));
if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
if(context.getSector() != null){
state.rules.sector = context.getSector();
if(state.rules.sector != null){
state.rules.sector.planet.applyRules(state.rules);
}
}
saveState.ruleString = map.get("rules", "{}");
//replace the default serpulo env with erekir
if(state.rules.planet == Planets.serpulo && state.rules.hasEnv(Env.scorching)){
state.rules.planet = Planets.erekir;
//for versions >= 13, rules are parsed after data patches are loaded
if(version < 13){
readRules(saveState);
}
if(!headless){
@@ -196,6 +190,25 @@ public abstract class SaveVersion extends SaveFileReader{
)) : 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{
//write world size
stream.writeShort(world.width());
@@ -527,7 +540,7 @@ public abstract class SaveVersion extends SaveFileReader{
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
int total = stream.readInt();
@@ -563,6 +576,9 @@ public abstract class SaveVersion extends SaveFileReader{
Events.fire(new DataPatchLoadEvent(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{
@@ -1,7 +1,7 @@
package mindustry.io.versions;
import arc.util.io.*;
import mindustry.world.*;
import mindustry.io.*;
import java.io.*;
@@ -15,12 +15,12 @@ public class LegacyRegionSaveVersion extends ShortChunkSaveVersion{
}
@Override
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, context));
public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, saveState));
readRegion("content", stream, counter, this::readContentHeader);
try{
readRegion("map", stream, counter, in -> readMap(in, context));
readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities);
}finally{
content.setTemporaryMapper(null);
+5 -6
View File
@@ -7,7 +7,6 @@ import arc.util.io.*;
import mindustry.game.EventType.*;
import mindustry.io.*;
import mindustry.mod.data.*;
import mindustry.world.*;
import java.io.*;
@@ -23,13 +22,13 @@ public class Save11 extends SaveVersion{
}
@Override
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, context));
public void read(DataInputStream stream, CounterInputStream counter, SaveReadState saveState) throws IOException{
readRegion("meta", stream, counter, in -> readMeta(in, saveState));
readRegion("content", stream, counter, this::readContentHeader);
try{
readRegion("patches", stream, counter, this::readDataPatches);
readRegion("map", stream, counter, in -> readMap(in, context));
readRegion("patches", stream, counter, in -> readDataPatches(in, saveState));
readRegion("map", stream, counter, in -> readMap(in, saveState.context));
readRegion("entities", stream, counter, this::readEntities);
readRegion("markers", stream, counter, this::readMarkers);
readRegion("custom", stream, counter, this::readCustomChunks);
@@ -40,7 +39,7 @@ public class Save11 extends SaveVersion{
//old, simplified string-only data patches
@Override
public void readDataPatches(DataInput stream) throws IOException{
public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
Seq<DataAsset> assets = new Seq<>();
int amount = stream.readUnsignedByte();
+1 -1
View File
@@ -19,7 +19,7 @@ public class Save12 extends SaveVersion{
}
@Override
public void readDataPatches(DataInput stream) throws IOException{
public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
stream.readInt(); //version - ignored for now
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){
try{
//this replacement is a MASSIVE hack but it fixes some incorrect overwriting of team-specific rules.
//may need to be tweaked later
Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}").replace("teams:{2:{infiniteAmmo:true}},", ""));
Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}"));
//replace the default serpulo env with erekir
if(result.planet == Planets.serpulo && result.hasEnv(Env.scorching)){
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. */
@SuppressWarnings("unchecked")
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 patchFormatVersion = 2;
@@ -37,10 +43,7 @@ public class DataPatcher{
private static final ObjectMap<String, ContentType> nameToType = new ObjectMap<>();
private static DataPatcher currentDataPatcher;
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 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.mapLocales));
SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags);
@@ -55,7 +58,6 @@ public class NetworkIO{
stream.writeInt(player.id);
player.write(new Writes(stream));
SaveIO.getSaveWriter().writeDataPatches(stream, false);
SaveIO.getSaveWriter().writeContentHeader(stream);
SaveIO.getSaveWriter().writeMap(stream);
SaveIO.getSaveWriter().writeTeamBlocks(stream);
@@ -70,6 +72,8 @@ public class NetworkIO{
try(DataInputStream stream = new DataInputStream(is)){
Time.clear();
SaveIO.getSaveWriter().readDataPatches(stream, new SaveReadState(world.context));
state.rules = JsonIO.read(Rules.class, stream.readUTF());
state.mapLocales = JsonIO.read(MapLocales.class, stream.readUTF());
state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream));
@@ -89,7 +93,6 @@ public class NetworkIO{
player.id = id;
player.add();
SaveIO.getSaveWriter().readDataPatches(stream);
SaveIO.getSaveWriter().readContentHeader(stream);
SaveIO.getSaveWriter().readMap(stream, world.context);
SaveIO.getSaveWriter().readTeamBlocks(stream);