Better unknown field warnings for patcher

This commit is contained in:
Anuken
2025-10-25 11:39:58 -04:00
parent 472cacbb3b
commit 53c534eaa8
4 changed files with 59 additions and 29 deletions

View File

@@ -59,6 +59,8 @@ public class ContentParser{
ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
Seq<ParseListener> listeners = new Seq<>();
/** If false, arbitrary class names cannot be resolved with Class.forName. */
boolean allowClassResolution = true;
ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<>(){{
put(Effect.class, (type, data) -> {
@@ -458,7 +460,7 @@ public class ContentParser{
if(consumeType != Consume.class){
block.removeConsumers(b -> consumeType.isAssignableFrom(b.getClass()));
}else{
Log.warn("Unknown consumer type '@' (Class: @) in consume: remove.", type, "Consume" + Strings.capitalize(type));
warn("Unknown consumer type '@' (Class: @) in consume: remove.", type, "Consume" + Strings.capitalize(type));
}
}
}
@@ -503,7 +505,7 @@ public class ContentParser{
if(locate(ContentType.block, name) != null){
if(value.has("type")){
Log.warn("Warning: '" + currentMod.name + "-" + name + "' re-declares a type. This will be interpreted as a new block. If you wish to override a vanilla block, omit the 'type' section, as vanilla block `type`s cannot be changed.");
warn("Warning: '" + currentMod.name + "-" + name + "' re-declares a type. This will be interpreted as a new block. If you wish to override a vanilla block, omit the 'type' section, as vanilla block `type`s cannot be changed.");
block = make(resolve(value.getString("type", ""), Block.class), mod + "-" + name);
}else{
block = locate(ContentType.block, name);
@@ -1122,7 +1124,7 @@ public class ContentParser{
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
if(metadata == null){
if(ignoreUnknownFields){
Log.warn("[@]: Ignoring unknown field: @ (@)", currentContent == null ? null : currentContent.minfo.sourceFile.name(), child.name, type.getSimpleName());
warn("@Unknown field '@' for class '@'", currentContent == null ? "" : "[" + currentContent.minfo.sourceFile.name() + "]: ", child.name, type.getSimpleName());
continue;
}else{
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
@@ -1241,7 +1243,7 @@ public class ContentParser{
TechNode parent = TechTree.all.find(t -> t.content.name.equals(researchName) || t.content.name.equals(currentMod.name + "-" + researchName) || t.content.name.equals(SaveVersion.mapFallback(researchName)));
if(parent == null){
Log.warn("Content '" + researchName + "' isn't in the tech tree, but '" + unlock.name + "' requires it to be researched.");
warn("Content '" + researchName + "' isn't in the tech tree, but '" + unlock.name + "' requires it to be researched.");
}else{
//add this node to the parent
if(!parent.children.contains(node)){
@@ -1252,7 +1254,7 @@ public class ContentParser{
node.planet = parent.planet;
}
}else{
Log.warn(unlock.name + " is not a root node, and does not have a `parent: ` property. Ignoring.");
warn(unlock.name + " is not a root node, and does not have a `parent: ` property. Ignoring.");
}
}
});
@@ -1279,7 +1281,7 @@ public class ContentParser{
if(out != null) return (Class<T>)out;
//try to resolve it as a raw class name
if(base.indexOf('.') != -1){
if(base.indexOf('.') != -1 && allowClassResolution){
try{
return (Class<T>)Class.forName(base);
}catch(Exception ignored){
@@ -1291,12 +1293,21 @@ public class ContentParser{
}
if(def != null){
if(warn) Log.warn("[@] No type '" + base + "' found, defaulting to type '" + def.getSimpleName() + "'", currentContent == null && currentMod != null ? currentMod.name : "");
if(warn) warn("[@] No type '" + base + "' found, defaulting to type '" + def.getSimpleName() + "'", currentContent == null && currentMod != null ? currentMod.name : "");
return def;
}
throw new IllegalArgumentException("Type not found: " + base);
}
void warn(String string, Object... format){
Log.warn(string, format);
}
public Json getJson(){
checkInit();
return parser;
}
private interface FieldParser{
Object parse(Class<?> type, JsonValue value) throws Exception;
}
@@ -1305,11 +1316,6 @@ public class ContentParser{
T parse(String mod, String name, JsonValue value) throws Exception;
}
public Json getJson(){
checkInit();
return parser;
}
//intermediate class for parsing
static class UnitReq{
public Block block;

View File

@@ -23,8 +23,8 @@ import java.util.*;
public class ContentPatcher{
private static final Object root = new Object();
private static final ObjectMap<String, ContentType> nameToType = new ObjectMap<>();
private static ContentParser parser = createParser();
private Json json;
private boolean applied;
private ContentLoader contentLoader;
private ObjectSet<Object> usedpatches = new ObjectSet<>();
@@ -41,13 +41,27 @@ public class ContentPatcher{
}
}
static ContentParser createParser(){
ContentParser cont = new ContentParser(){
@Override
void warn(String string, Object... format){
//forward warnings to the current patcher - this is a bit hacky, but I do not want to re-initialize the parser every time
if(Vars.state.patcher != null){
Vars.state.patcher.warn(string, format);
}
}
};
cont.allowClassResolution = false;
return cont;
}
/** 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();
@@ -55,7 +69,7 @@ public class ContentPatcher{
for(String patch : patchArray){
try{
JsonValue value = json.fromJson(null, Jval.read(patch).toString(Jformat.plain));
JsonValue value = parser.getJson().fromJson(null, Jval.read(patch).toString(Jformat.plain));
PatchSet set = new PatchSet(patch, value);
patches.add(set);
currentlyApplying = set;
@@ -267,7 +281,7 @@ public class ContentPatcher{
Class<?> actualType = object.getClass();
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
var fields = json.getFields(actualType);
var fields = parser.getJson().getFields(actualType);
var fdata = fields.get(field);
if(fdata != null){
if(checkField(fdata.field)) return;
@@ -292,13 +306,13 @@ public class ContentPatcher{
after(bl::reinitializeConsumers);
try{
Vars.mods.getContentParser().readBlockConsumers(bl, jsv);
parser.readBlockConsumers(bl, jsv);
}catch(Throwable e){
Log.err(e);
warn("Failed to read consumers for '@': @", bl, Strings.getSimpleMessage(e));
}
}else{
warn("Unknown field: '@' for class '@'", field, actualType.getSimpleName());
warn("Unknown field '@' for class '@'", field, actualType.getSimpleName());
}
}
}
@@ -316,16 +330,16 @@ public class ContentPatcher{
if(modify) modifiedField(object, field, getter.get());
//HACK: listen for creation of objects once
Vars.mods.getContentParser().listeners.add((type, jsonData, result) -> created(result, object));
parser.listeners.add((type, jsonData, result) -> created(result, object));
try{
setter.get(json.readValue(metadata.type, metadata.elementType, jsv));
setter.get(parser.getJson().readValue(metadata.type, metadata.elementType, jsv));
}catch(Throwable e){
warn("Failed to read value @.@ = @: @ (type = @ elementType = @)\n@", object, field, value, e.getMessage(), metadata.type, metadata.elementType, Strings.getStackTrace(e));
}
Vars.mods.getContentParser().listeners.pop();
parser.listeners.pop();
}else{
//assign each field manually
var childFields = json.getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass());
var childFields = parser.getJson().getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass());
for(var child : jsv){
if(child.name != null){
assign(prevValue, child.name, child, !childFields.containsKey(child.name) ? null : new FieldData(childFields.get(child.name)), object, field);
@@ -383,7 +397,7 @@ public class ContentPatcher{
Class<?> actualType = object.getClass();
if(actualType.isAnonymousClass()) actualType = actualType.getSuperclass();
var fields = json.getFields(actualType);
var fields = parser.getJson().getFields(actualType);
var fdata = fields.get(field);
if(fdata != null){
if(checkField(fdata.field)) return null;
@@ -407,7 +421,7 @@ public class ContentPatcher{
void modifiedField(Object target, String field, Object value){
if(!applied || target == null) return;
var fields = json.getFields(target.getClass());
var fields = parser.getJson().getFields(target.getClass());
var meta = fields.get(field);
if(meta != null){
@@ -431,7 +445,7 @@ public class ContentPatcher{
}
Object convertKeyType(String string, Class<?> type){
return json.fromJson(type, string);
return parser.getJson().fromJson(type, string);
}
void warn(String error, Object... fmt){

View File

@@ -971,10 +971,6 @@ public class Mods implements Loadable{
}
}
public ContentParser getContentParser(){
return parser;
}
/** @return the mods that the client is missing.
* The inputted array is changed to contain the extra mods that the client has but the server doesn't.*/
public Seq<String> getIncompatibility(Seq<String> out){