mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-12-06 02:40:23 -08:00
Content patch import dialog & server support
This commit is contained in:
parent
9f7817f70e
commit
9cc3105518
16 changed files with 373 additions and 28 deletions
|
|
@ -470,6 +470,13 @@ editor.rules = Rules
|
|||
editor.generation = Generation
|
||||
editor.objectives = Objectives
|
||||
editor.locales = Locale Bundles
|
||||
editor.patches = Content Patches
|
||||
editor.patch: Patchset: {0}
|
||||
editor.patches.none = [lightgray]No patchsets loaded.
|
||||
editor.patches.errors = Patchset Errors
|
||||
editor.patches.importerror = Failed to import patchset
|
||||
editor.patches.delete.confirm = Are you sure you want to delete this patchset?
|
||||
editor.patch.fields = {0} fields
|
||||
editor.worldprocessors = World Processors
|
||||
editor.worldprocessors.editname = Edit Name
|
||||
editor.worldprocessors.none = [lightgray]No world processor blocks found!\nAdd one in the map editor, or use the \ue813 Add button below.
|
||||
|
|
|
|||
BIN
core/assets/fonts/monospace.woff
Normal file
BIN
core/assets/fonts/monospace.woff
Normal file
Binary file not shown.
|
|
@ -43,12 +43,12 @@ public class GameState{
|
|||
public Attributes envAttrs = new Attributes();
|
||||
/** Team data. Gets reset every new game. */
|
||||
public Teams teams = new Teams();
|
||||
/** Handles JSON edits of game content. */
|
||||
public ContentPatcher patcher = new ContentPatcher();
|
||||
/** Number of enemies in the game; only used clientside in servers. */
|
||||
public int enemies;
|
||||
/** Map being playtested (not edited!) */
|
||||
public @Nullable Map playtestingMap;
|
||||
/** Null if not content patches have been applied. */
|
||||
public @Nullable ContentPatcher patcher;
|
||||
/** Current game state. */
|
||||
private State state = State.menu;
|
||||
|
||||
|
|
|
|||
|
|
@ -260,10 +260,7 @@ public class Logic implements ApplicationListener{
|
|||
|
||||
public void reset(){
|
||||
State prev = state.getState();
|
||||
if(state.patcher != null){
|
||||
state.patcher.unapply();
|
||||
state.patcher = null;
|
||||
}
|
||||
state.patcher.unapply();
|
||||
//recreate gamestate - sets state to menu
|
||||
state = new GameState();
|
||||
//fire change event, since it was technically changed
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public class MapInfoDialog extends BaseDialog{
|
|||
private MapObjectivesDialog objectives = new MapObjectivesDialog();
|
||||
private MapLocalesDialog locales = new MapLocalesDialog();
|
||||
private MapProcessorsDialog processors = new MapProcessorsDialog();
|
||||
private MapPatchesDialog patches = new MapPatchesDialog();
|
||||
|
||||
public MapInfoDialog(){
|
||||
super("@editor.mapinfo");
|
||||
|
|
@ -33,7 +34,7 @@ public class MapInfoDialog extends BaseDialog{
|
|||
cont.clear();
|
||||
|
||||
ObjectMap<String, String> tags = editor.tags;
|
||||
|
||||
|
||||
cont.pane(t -> {
|
||||
t.add("@editor.mapname").padRight(8).left();
|
||||
t.defaults().padTop(15);
|
||||
|
|
@ -113,6 +114,16 @@ public class MapInfoDialog extends BaseDialog{
|
|||
hide();
|
||||
processors.show();
|
||||
}).marginLeft(10f);
|
||||
|
||||
r.row();
|
||||
|
||||
r.button("@editor.patches", Icon.file, style, () -> {
|
||||
hide();
|
||||
patches.show();
|
||||
}).marginLeft(10f);
|
||||
|
||||
//empty space
|
||||
r.add().marginLeft(10f);
|
||||
}).colspan(2).center();
|
||||
|
||||
name.change();
|
||||
|
|
|
|||
156
core/src/mindustry/editor/MapPatchesDialog.java
Normal file
156
core/src/mindustry/editor/MapPatchesDialog.java
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.serialization.*;
|
||||
import mindustry.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapPatchesDialog extends BaseDialog{
|
||||
private Table list;
|
||||
|
||||
public MapPatchesDialog(){
|
||||
super("@editor.patches");
|
||||
|
||||
shown(this::setup);
|
||||
|
||||
addCloseButton();
|
||||
buttons.button("@add", Icon.add, () -> showImport(this::addPatch)).size(210f, 64f);
|
||||
|
||||
cont.top();
|
||||
getCell(cont).grow();
|
||||
|
||||
cont.pane(t -> list = t);
|
||||
}
|
||||
|
||||
private void setup(){
|
||||
list.clearChildren();
|
||||
var patches = state.patcher.patches;
|
||||
|
||||
if(patches.isEmpty()){
|
||||
list.add("@editor.patches.none");
|
||||
}else{
|
||||
Table t = list;
|
||||
|
||||
t.defaults().pad(4f);
|
||||
float h = 50f;
|
||||
for(var patch : patches){
|
||||
int fields = countFields(patch.json);
|
||||
|
||||
if(patch.warnings.size > 0){
|
||||
t.button(Icon.warning, Styles.graySquarei, iconMed, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@editor.patches.errors");
|
||||
dialog.cont.top().pane(p -> {
|
||||
p.top();
|
||||
|
||||
for(var warning : patch.warnings){
|
||||
p.table(Styles.grayPanel, in -> {
|
||||
in.add(warning, Styles.monoLabel).grow().wrap();
|
||||
}).margin(6f).growX().pad(3f).row();
|
||||
}
|
||||
}).grow();
|
||||
dialog.addCloseButton();
|
||||
dialog.show();
|
||||
}).size(h);
|
||||
}else{
|
||||
t.add().size(h);
|
||||
}
|
||||
|
||||
t.button((patch.name.isEmpty() ? "<unnamed>\n" : "[accent]" + patch.name + "\n") + "[lightgray][[" + Core.bundle.format("editor.patch.fields", fields) + "]", Styles.grayt, () -> {
|
||||
BaseDialog dialog = new BaseDialog(Core.bundle.format("editor.patch", patch.name.isEmpty() ? "<unnamed>" : patch.name));
|
||||
dialog.cont.top().pane(p -> {
|
||||
p.top();
|
||||
p.table(Styles.grayPanel, in -> {
|
||||
in.add(patch.patch.replaceAll("\t", " "), Styles.monoLabel).grow().wrap().left().labelAlign(Align.left);
|
||||
}).margin(6f).growX().pad(5f).row();
|
||||
}).grow();
|
||||
dialog.addCloseButton();
|
||||
dialog.show();
|
||||
}).size(mobile ? 390f : 450f, h).margin(10f).with(b -> {
|
||||
b.getLabel().setAlignment(Align.left, Align.left);
|
||||
});
|
||||
|
||||
t.button(Icon.refresh, Styles.graySquarei, Vars.iconMed, () -> {
|
||||
showImport(str -> addPatch(str, patches.indexOf(patch)));
|
||||
}).size(h);
|
||||
|
||||
t.button(Icon.trash, Styles.graySquarei, iconMed, () -> {
|
||||
ui.showConfirm("@editor.patches.delete.confirm", () -> {
|
||||
patches.remove(patch);
|
||||
setup();
|
||||
});
|
||||
}).size(h);
|
||||
|
||||
t.row();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showImport(Cons<String> handler){
|
||||
BaseDialog dialog = new BaseDialog("@editor.import");
|
||||
dialog.cont.pane(p -> {
|
||||
p.margin(10f);
|
||||
p.table(Tex.button, t -> {
|
||||
TextButtonStyle style = Styles.flatt;
|
||||
t.defaults().size(280f, 60f).left();
|
||||
t.row();
|
||||
t.button("@schematic.copy.import", Icon.copy, style, () -> {
|
||||
dialog.hide();
|
||||
handler.get(Core.app.getClipboardText());
|
||||
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null);
|
||||
t.row();
|
||||
t.button("@schematic.importfile", Icon.download, style, () -> platform.showMultiFileChooser(file -> {
|
||||
dialog.hide();
|
||||
handler.get(file.readString());
|
||||
}, "json", "hjson", "json5")).marginLeft(12f);
|
||||
t.row();
|
||||
});
|
||||
});
|
||||
|
||||
dialog.addCloseButton();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
void addPatch(String patch){
|
||||
addPatch(patch, -1);
|
||||
}
|
||||
|
||||
void addPatch(String patch, int replaceIndex){
|
||||
var oldPatches = state.patcher.patches.copy();
|
||||
try{
|
||||
Jval.read(patch); //validation
|
||||
Seq<String> patches = state.patcher.patches.map(p -> p.patch);
|
||||
if(replaceIndex == -1){
|
||||
patches.add(patch);
|
||||
}else{
|
||||
patches.set(replaceIndex, patch);
|
||||
}
|
||||
state.patcher.apply(patches);
|
||||
|
||||
setup();
|
||||
}catch(Exception e){
|
||||
state.patcher.patches.set(oldPatches);
|
||||
ui.showException("@editor.patches.importerror", e);
|
||||
}
|
||||
}
|
||||
|
||||
int countFields(JsonValue value){
|
||||
if(value.isObject() || value.isArray()){
|
||||
int sum = 0;
|
||||
for(var child : value){
|
||||
sum += countFields(child);
|
||||
}
|
||||
return Math.max(sum, 1);
|
||||
}else{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package mindustry.game;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
|
|
@ -102,6 +103,15 @@ public class EventType{
|
|||
/** Called when a game begins and the world tiles are initiated. About to updates tile proximity and sets up physics for the world(Before WorldLoadEvent) */
|
||||
public static class WorldLoadEndEvent{}
|
||||
|
||||
/** Called when a save loads custom patches. {@link #patches} can be modified in the event handler. */
|
||||
public static class ContentPatchLoadEvent{
|
||||
public final Seq<String> patches;
|
||||
|
||||
public ContentPatchLoadEvent(Seq<String> patches){
|
||||
this.patches = patches;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveLoadEvent{
|
||||
public final boolean isMap;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class SaveIO{
|
|||
/** Save format header. */
|
||||
public static final byte[] header = {'M', 'S', 'A', 'V'};
|
||||
public static final IntMap<SaveVersion> versions = new IntMap<>();
|
||||
public static final Seq<SaveVersion> versionArray = Seq.with(new Save1(), new Save2(), new Save3(), new Save4(), new Save5(), new Save6(), new Save7(), new Save8(), new Save9(), new Save10());
|
||||
public static final Seq<SaveVersion> versionArray = Seq.with(new Save1(), new Save2(), new Save3(), new Save4(), new Save5(), new Save6(), new Save7(), new Save8(), new Save9(), new Save10(), new Save11());
|
||||
|
||||
static{
|
||||
for(SaveVersion version : versionArray){
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import mindustry.core.*;
|
|||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.maps.Map;
|
||||
|
|
@ -67,6 +68,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
readRegion("content", stream, counter, this::readContentHeader);
|
||||
|
||||
try{
|
||||
if(version >= 11) readRegion("patches", stream, counter, this::readContentPatches);
|
||||
readRegion("map", stream, counter, in -> readMap(in, context));
|
||||
readRegion("entities", stream, counter, this::readEntities);
|
||||
if(version >= 8) readRegion("markers", stream, counter, this::readMarkers);
|
||||
|
|
@ -79,6 +81,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
public void write(DataOutputStream stream, StringMap extraTags) throws IOException{
|
||||
writeRegion("meta", stream, out -> writeMeta(out, extraTags));
|
||||
writeRegion("content", stream, this::writeContentHeader);
|
||||
writeRegion("patches", stream, this::writeContentPatches);
|
||||
writeRegion("map", stream, this::writeMap);
|
||||
writeRegion("entities", stream, this::writeEntities);
|
||||
writeRegion("markers", stream, this::writeMarkers);
|
||||
|
|
@ -502,8 +505,46 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
readWorldEntities(stream, mapping);
|
||||
}
|
||||
|
||||
public void readContentPatches(DataInput stream) throws IOException{
|
||||
Seq<String> patches = new Seq<>();
|
||||
|
||||
int amount = stream.readUnsignedByte();
|
||||
if(amount > 0){
|
||||
for(int i = 0; i < amount; i++){
|
||||
int len = stream.readInt();
|
||||
byte[] bytes = new byte[len];
|
||||
stream.readFully(bytes);
|
||||
patches.add(new String(bytes, Strings.utf8));
|
||||
}
|
||||
}
|
||||
|
||||
Events.fire(new ContentPatchLoadEvent(patches));
|
||||
|
||||
if(patches.size > 0){
|
||||
try{
|
||||
state.patcher.apply(patches);
|
||||
}catch(Throwable e){
|
||||
Log.err("Failed to apply patches: " + patches, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeContentPatches(DataOutput stream) throws IOException{
|
||||
if(state.patcher.patches.size > 0){
|
||||
var patches = state.patcher.patches;
|
||||
stream.writeByte(patches.size);
|
||||
for(var patchset : patches){
|
||||
byte[] bytes = patchset.patch.getBytes(Strings.utf8);
|
||||
stream.writeInt(bytes.length);
|
||||
stream.write(bytes);
|
||||
}
|
||||
}else{
|
||||
stream.writeByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void readContentHeader(DataInput stream) throws IOException{
|
||||
byte mapped = stream.readByte();
|
||||
int mapped = stream.readUnsignedByte();
|
||||
|
||||
MappableContent[][] map = new MappableContent[ContentType.all.length][0];
|
||||
|
||||
|
|
@ -520,6 +561,21 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||
}
|
||||
|
||||
content.setTemporaryMapper(map);
|
||||
|
||||
//HACK: versions below 11 don't read the patch chunk, which means the event for reading patches is never triggered.
|
||||
//manually fire the event here for older versions.
|
||||
if(version < 11){
|
||||
Seq<String> patches = new Seq<>();
|
||||
Events.fire(new ContentPatchLoadEvent(patches));
|
||||
|
||||
if(patches.size > 0){
|
||||
try{
|
||||
state.patcher.apply(patches);
|
||||
}catch(Throwable e){
|
||||
Log.err("Failed to apply patches: " + patches, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeContentHeader(DataOutput stream) throws IOException{
|
||||
|
|
|
|||
11
core/src/mindustry/io/versions/Save11.java
Normal file
11
core/src/mindustry/io/versions/Save11.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.io.*;
|
||||
|
||||
/** Adds patches in content header. */
|
||||
public class Save11 extends SaveVersion{
|
||||
|
||||
public Save11(){
|
||||
super(11);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import java.lang.reflect.*;
|
|||
import java.util.*;
|
||||
|
||||
/** The current implementation is awful. Consider it a proof of concept. */
|
||||
//TODO block consumer support
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ContentPatcher{
|
||||
private static final Object root = new Object();
|
||||
|
|
@ -25,6 +26,10 @@ public class ContentPatcher{
|
|||
private ObjectSet<PatchRecord> usedpatches = new ObjectSet<>();
|
||||
private Seq<Runnable> resetters = new Seq<>();
|
||||
private Seq<Runnable> afterCallbacks = new Seq<>();
|
||||
private @Nullable PatchSet currentlyApplying;
|
||||
|
||||
/** Currently active patches. Note that apply() should be called after modification. */
|
||||
public Seq<PatchSet> patches = new Seq<>();
|
||||
|
||||
static{
|
||||
for(var type : ContentType.all){
|
||||
|
|
@ -32,22 +37,37 @@ public class ContentPatcher{
|
|||
}
|
||||
}
|
||||
|
||||
public void apply(String patch) throws Exception{
|
||||
/** Applies the specified patches. If patches were already applied, the previous ones are un-applied - they do not stack! */
|
||||
public void apply(Seq<String> patchArray) throws Exception{
|
||||
if(applied){
|
||||
unapply();
|
||||
applied = false;
|
||||
}
|
||||
json = Vars.mods.getContentParser().getJson();
|
||||
|
||||
applied = true;
|
||||
contentLoader = Vars.content.copy();
|
||||
patches.clear();
|
||||
|
||||
try{
|
||||
JsonValue value = json.fromJson(null, Jval.read(patch).toString(Jformat.plain));
|
||||
for(var child : value){
|
||||
assign(root, child.name, child, null, null, null);
|
||||
for(String patch : patchArray){
|
||||
try{
|
||||
JsonValue value = json.fromJson(null, Jval.read(patch).toString(Jformat.plain));
|
||||
PatchSet set = new PatchSet(patch, value);
|
||||
patches.add(set);
|
||||
currentlyApplying = set;
|
||||
|
||||
value.remove("name"); //patchsets can have a name, ignore it if present
|
||||
for(var child : value){
|
||||
assign(root, child.name, child, null, null, null);
|
||||
}
|
||||
currentlyApplying = null;
|
||||
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to apply patch: " + patch, e);
|
||||
}
|
||||
|
||||
afterCallbacks.each(Runnable::run);
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to apply patch: " + patch, e);
|
||||
}
|
||||
|
||||
afterCallbacks.each(Runnable::run);
|
||||
}
|
||||
|
||||
public void unapply(){
|
||||
|
|
@ -69,6 +89,7 @@ public class ContentPatcher{
|
|||
//this should never throw an exception
|
||||
afterCallbacks.each(Runnable::run);
|
||||
afterCallbacks.clear();
|
||||
usedpatches.clear();
|
||||
}
|
||||
|
||||
void assign(Object object, String field, Object value, @Nullable FieldData metadata, @Nullable Object parentObject, @Nullable String parentField) throws Exception{
|
||||
|
|
@ -168,6 +189,10 @@ public class ContentPatcher{
|
|||
assignValue(object, field, metadata, () -> Array.get(fobj, i), val -> Array.set(fobj, i, val), value, false);
|
||||
}
|
||||
}
|
||||
}else if(object instanceof ObjectSet set && prefix == '+'){
|
||||
modifiedField(parentObject, parentField, set.copy());
|
||||
|
||||
assignValue(object, field, metadata, () -> null, val -> set.add(val), value, false);
|
||||
}else if(object instanceof ObjectMap map){
|
||||
if(metadata == null){
|
||||
warn("ObjectMap cannot be parsed without metadata: @.@", parentObject, parentField);
|
||||
|
|
@ -182,7 +207,13 @@ public class ContentPatcher{
|
|||
var copy = map.copy();
|
||||
reset(() -> map.set(copy));
|
||||
|
||||
assignValue(object, field, new FieldData(metadata.elementType, null, null), () -> map.get(key), val -> map.put(key, val), value, false);
|
||||
if(value instanceof JsonValue jval && jval.isString() && (jval.asString().equals("-"))){
|
||||
//removal syntax:
|
||||
//"value": "-"
|
||||
map.remove(key);
|
||||
}else{
|
||||
assignValue(object, field, new FieldData(metadata.elementType, null, null), () -> map.get(key), val -> map.put(key, val), value, false);
|
||||
}
|
||||
}else{
|
||||
Class<?> actualType = object.getClass();
|
||||
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
|
||||
|
|
@ -193,9 +224,15 @@ public class ContentPatcher{
|
|||
if(checkField(fdata.field)) return;
|
||||
|
||||
var fobj = object;
|
||||
assignValue(object, field, new FieldData(fdata), () -> Reflect.get(fobj, fdata.field), fv -> Reflect.set(fobj, fdata.field, fv), value, true);
|
||||
assignValue(object, field, new FieldData(fdata), () -> Reflect.get(fobj, fdata.field), fv -> {
|
||||
if(fv == null && !fdata.field.isAnnotationPresent(Nullable.class)){
|
||||
warn("Field '@' cannot be null.", fdata.field);
|
||||
return;
|
||||
}
|
||||
Reflect.set(fobj, fdata.field, fv);
|
||||
}, value, true);
|
||||
}else{
|
||||
warn("Unknown field: '@' for '@'", field, actualType.getName());
|
||||
warn("Unknown field: '@' for class '@'", field, actualType.getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -322,9 +359,12 @@ public class ContentPatcher{
|
|||
return json.fromJson(type, string);
|
||||
}
|
||||
|
||||
//TODO crash?
|
||||
void warn(String error, Object... fmt){
|
||||
Log.warn(error, fmt);
|
||||
String formatted = Strings.format(error, fmt);
|
||||
if(currentlyApplying != null){
|
||||
currentlyApplying.warnings.add(formatted);
|
||||
}
|
||||
Log.warn("[ContentPatcher] " + formatted);
|
||||
}
|
||||
|
||||
void after(Runnable run){
|
||||
|
|
@ -343,6 +383,19 @@ public class ContentPatcher{
|
|||
return ((Object[])object).clone();
|
||||
}
|
||||
|
||||
public static class PatchSet{
|
||||
public String patch;
|
||||
public JsonValue json;
|
||||
public String name;
|
||||
public Seq<String> warnings = new Seq<>();
|
||||
|
||||
public PatchSet(String patch, JsonValue json){
|
||||
this.patch = patch;
|
||||
this.json = json;
|
||||
name = json.getString("name", "");
|
||||
}
|
||||
}
|
||||
|
||||
private static class FieldData{
|
||||
Class type, elementType, keyType;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ public class NetworkIO{
|
|||
player.write(new Writes(stream));
|
||||
|
||||
SaveIO.getSaveWriter().writeContentHeader(stream);
|
||||
SaveIO.getSaveWriter().writeContentPatches(stream);
|
||||
SaveIO.getSaveWriter().writeMap(stream);
|
||||
SaveIO.getSaveWriter().writeTeamBlocks(stream);
|
||||
SaveIO.getSaveWriter().writeMarkers(stream);
|
||||
|
|
@ -84,6 +85,7 @@ public class NetworkIO{
|
|||
player.add();
|
||||
|
||||
SaveIO.getSaveWriter().readContentHeader(stream);
|
||||
SaveIO.getSaveWriter().readContentPatches(stream);
|
||||
SaveIO.getSaveWriter().readMap(stream, world.context);
|
||||
SaveIO.getSaveWriter().readTeamBlocks(stream);
|
||||
SaveIO.getSaveWriter().readMarkers(stream);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class Fonts{
|
|||
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
|
||||
private static ObjectMap<String, TextureRegion> largeIcons = new ObjectMap<>();
|
||||
|
||||
public static Font def, outline, icon, iconLarge, tech, logic;
|
||||
public static Font def, outline, icon, iconLarge, tech, logic, monospace;
|
||||
|
||||
public static int getUnicode(String content){
|
||||
return unicodeIcons.get(content, 0);
|
||||
|
|
@ -66,6 +66,13 @@ public class Fonts{
|
|||
|
||||
Core.assets.load("default", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.def = f;
|
||||
|
||||
Core.assets.load("monospace", Font.class, new FreeTypeFontLoaderParameter("fonts/monospace.woff", new FreeTypeFontParameter(){{
|
||||
size = 16;
|
||||
incremental = true;
|
||||
//most people will never see the monospace font, so don't pre-bake anything
|
||||
characters = "\u0000 ";
|
||||
}})).loaded = f -> Fonts.monospace = f;
|
||||
|
||||
Core.assets.load("icon", Font.class, new FreeTypeFontLoaderParameter("fonts/icon.ttf", new FreeTypeFontParameter(){{
|
||||
size = 30;
|
||||
incremental = true;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public class Styles{
|
|||
|
||||
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane, noBarPane;
|
||||
public static SliderStyle defaultSlider;
|
||||
public static LabelStyle defaultLabel, outlineLabel, techLabel;
|
||||
public static LabelStyle defaultLabel, outlineLabel, techLabel, monoLabel;
|
||||
public static TextFieldStyle defaultField, nodeField, areaField, nodeArea;
|
||||
public static CheckBoxStyle defaultCheck;
|
||||
public static DialogStyle defaultDialog, fullDialog;
|
||||
|
|
@ -380,6 +380,10 @@ public class Styles{
|
|||
font = Fonts.tech;
|
||||
fontColor = Color.white;
|
||||
}};
|
||||
monoLabel = new LabelStyle(){{
|
||||
font = Fonts.monospace;
|
||||
fontColor = Color.white;
|
||||
}};
|
||||
|
||||
defaultField = new TextFieldStyle(){{
|
||||
font = Fonts.def;
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ org.gradle.caching=true
|
|||
org.gradle.internal.http.socketTimeout=100000
|
||||
org.gradle.internal.http.connectionTimeout=100000
|
||||
android.enableR8.fullMode=false
|
||||
archash=7a3d906e1b
|
||||
archash=c8f3bd901b
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import arc.util.CommandHandler.*;
|
|||
import arc.util.Timer.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.JsonValue.*;
|
||||
import arc.util.serialization.Jval.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.core.*;
|
||||
|
|
@ -72,6 +73,8 @@ public class ServerControl implements ApplicationListener{
|
|||
private PrintWriter socketOutput;
|
||||
private String suggested;
|
||||
private boolean autoPaused = false;
|
||||
private Fi patchDirectory;
|
||||
private Seq<String> contentPatches = new Seq<>();
|
||||
|
||||
public Cons<GameOverEvent> gameOverListener = event -> {
|
||||
if(state.rules.waves){
|
||||
|
|
@ -191,13 +194,17 @@ public class ServerControl implements ApplicationListener{
|
|||
}
|
||||
});
|
||||
|
||||
customMapDirectory.mkdirs();
|
||||
|
||||
if(Version.build == -1){
|
||||
warn("&lyYour server is running a custom build, which means that client checking is disabled.");
|
||||
warn("&lyIt is highly advised to specify which version you're using by building with gradle args &lb&fb-Pbuildversion=&lr<build>");
|
||||
}
|
||||
|
||||
customMapDirectory.mkdirs();
|
||||
|
||||
patchDirectory = dataDirectory.child("patches");
|
||||
patchDirectory.mkdirs();
|
||||
loadPatchFiles();
|
||||
|
||||
//set up default shuffle mode
|
||||
try{
|
||||
maps.setShuffleMode(ShuffleMode.valueOf(Core.settings.getString("shufflemode")));
|
||||
|
|
@ -314,6 +321,30 @@ public class ServerControl implements ApplicationListener{
|
|||
|
||||
info("Server loaded. Type @ for help.", "'help'");
|
||||
});
|
||||
|
||||
Events.on(ContentPatchLoadEvent.class, event -> {
|
||||
//NOTE: if patches change, and an older save is loaded, the patches will be applied twice; the old ones won't be removed.
|
||||
for(String patch : contentPatches){
|
||||
event.patches.addUnique(patch);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void loadPatchFiles(){
|
||||
contentPatches.clear();
|
||||
Seq<Fi> patches = patchDirectory.findAll(f -> f.extEquals("json") || f.extEquals("hjson") || f.extEquals("json5")).sort();
|
||||
|
||||
for(Fi patch : patches){
|
||||
try{
|
||||
contentPatches.add(Jval.read(patch.readString()).toString(Jformat.plain));
|
||||
}catch(Throwable e){
|
||||
Log.err("Invalid patch file: " + patch.name(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if(contentPatches.size > 0){
|
||||
Log.info("Loaded @ content patch files.", contentPatches.size);
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerCommands(){
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue