Skip to content

Commit

Permalink
Allow specifying widget data in stock config
Browse files Browse the repository at this point in the history
  • Loading branch information
cam72cam committed Dec 9, 2023
1 parent 3dc5e12 commit 8224792
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public StockModel(DEFINITION def) throws Exception {
};
this.base = ModelState.construct(settings -> settings.add(animators).add(interiorLit));

ComponentProvider provider = new ComponentProvider(this, def.internal_model_scale);
ComponentProvider provider = new ComponentProvider(this, def.internal_model_scale, def.widgetConfig);
initStates();
parseControllable(provider, def);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import cam72cam.immersiverailroading.library.ModelComponentType;
import cam72cam.immersiverailroading.library.ModelComponentType.ModelPosition;
import cam72cam.immersiverailroading.model.StockModel;
import cam72cam.immersiverailroading.util.DataBlock;

import java.util.*;
import java.util.regex.Matcher;
Expand All @@ -12,12 +13,14 @@
public class ComponentProvider {
public final StockModel<?, ?> model;
private final Set<String> groups;
public Map<String, DataBlock> widgetConfig;
private final List<ModelComponent> components;
public double internal_model_scale;

public ComponentProvider(StockModel<?, ?> model, double internal_model_scale) {
public ComponentProvider(StockModel<?, ?> model, double internal_model_scale, Map<String, DataBlock> widgetConfig) {
this.model = model;
this.groups = new HashSet<>(model.groups());
this.widgetConfig = widgetConfig;
this.components = new ArrayList<>();
this.internal_model_scale = internal_model_scale;
}
Expand Down
166 changes: 105 additions & 61 deletions src/main/java/cam72cam/immersiverailroading/model/part/Control.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cam72cam.immersiverailroading.model.part;

import cam72cam.immersiverailroading.ConfigGraphics;
import cam72cam.immersiverailroading.ConfigSound;
import cam72cam.immersiverailroading.entity.EntityMoveableRollingStock;
import cam72cam.immersiverailroading.entity.EntityRollingStock;
import cam72cam.immersiverailroading.library.ModelComponentType;
Expand All @@ -10,6 +9,7 @@
import cam72cam.immersiverailroading.model.components.ComponentProvider;
import cam72cam.immersiverailroading.model.components.ModelComponent;
import cam72cam.immersiverailroading.registry.EntityRollingStockDefinition.ControlSoundsDefinition;
import cam72cam.immersiverailroading.util.DataBlock;
import cam72cam.mod.MinecraftClient;
import cam72cam.mod.ModCore;
import cam72cam.mod.entity.Player;
Expand All @@ -18,15 +18,14 @@
import cam72cam.mod.model.obj.OBJGroup;
import cam72cam.mod.render.GlobalRender;
import cam72cam.mod.render.opengl.RenderState;
import cam72cam.mod.resource.Identifier;
import cam72cam.mod.sound.ISound;
import cam72cam.mod.text.TextColor;
import cam72cam.mod.util.Axis;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.text.WordUtils;
import util.Matrix4;

import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -52,84 +51,129 @@ public class Control<T extends EntityMoveableRollingStock> extends Interactable<
private final Map<Axis, Float> scaleRot = new HashMap<>();

public static <T extends EntityMoveableRollingStock> List<Control<T>> get(ComponentProvider provider, ModelState state, ModelComponentType type, ModelPosition pos) {
return provider.parseAll(type, pos).stream().map(part1 -> new Control<T>(part1, state, provider.internal_model_scale)).collect(Collectors.toList());
return provider.parseAll(type, pos).stream().map(part1 -> new Control<T>(part1, state, provider.internal_model_scale, provider.widgetConfig)).collect(Collectors.toList());
}

public static <T extends EntityMoveableRollingStock> List<Control<T>> get(ComponentProvider provider, ModelState state, ModelComponentType type) {
return provider.parseAll(type).stream().map(part1 -> new Control<T>(part1, state, provider.internal_model_scale)).collect(Collectors.toList());
return provider.parseAll(type).stream().map(part1 -> new Control<T>(part1, state, provider.internal_model_scale, provider.widgetConfig)).collect(Collectors.toList());
}

public Control(ModelComponent part, ModelState state, double internal_model_scale) {
public Control(ModelComponent part, ModelState state, double internal_model_scale, Map<String, DataBlock> widgetConfig) {
super(part);
this.controlGroup = part.modelIDs.stream().map(group -> {
Matcher matcher = Pattern.compile("_CG_([^_]+)").matcher(group);
return matcher.find() ? matcher.group(1) : null;
}).filter(Objects::nonNull).findFirst().orElse(part.key);
this.label = part.modelIDs.stream().map(group -> {
Matcher matcher = Pattern.compile("_LABEL_([^_]+)").matcher(group);
return matcher.find() ? matcher.group(1).replaceAll("\\^", " ") : null;
}).filter(Objects::nonNull).findFirst().orElse(null);
this.toggle = part.modelIDs.stream().anyMatch(g -> g.contains("_TOGGLE_") || g.startsWith("TOGGLE_") || g.endsWith("_TOGGLE"));
this.press = part.modelIDs.stream().anyMatch(g -> g.contains("_PRESS_") || g.startsWith("PRESS_") || g.endsWith("_PRESS"));
this.global = part.modelIDs.stream().anyMatch(g -> g.contains("_GLOBAL_") || g.startsWith("GLOBAL_") || g.endsWith("_GLOBAL"));
this.invert = part.modelIDs.stream().anyMatch(g -> g.contains("_INVERT_") || g.startsWith("INVERT_") || g.endsWith("_INVERT"));
this.hide = part.modelIDs.stream().anyMatch(g -> g.contains("_HIDE_") || g.startsWith("HIDE_") || g.endsWith("_HIDE"));
this.noInteract = part.modelIDs.stream().anyMatch(g ->
g.contains("_NOINTERACT_") || g.startsWith("NOINTERACT_") || g.endsWith("_NOINTERACT") ||
g.contains("_NOTOUCH_") || g.startsWith("NOTOUCH_") || g.endsWith("_NOTOUCH")
);
this.offset = part.modelIDs.stream().map(group -> {
Matcher matcher = Pattern.compile("_OFFSET_([^_]+)").matcher(group);
return matcher.find() ? Float.parseFloat(matcher.group(1)) : null;
}).filter(Objects::nonNull).findFirst().orElse(0f);

// This is terrible
String rotpat = part.pos != null && !part.type.regex.contains("#POS#") ?
part.type.regex.replaceAll("#ID#", part.pos + "_" + part.id + "_ROT") :
part.pos != null ?
part.type.regex.replaceAll("#POS#", part.pos.toString()).replaceAll("#ID#", part.id + "_ROT") :
part.type.regex.replaceAll("#ID#", part.id + "_ROT");
OBJGroup rot = part.groups().stream()
.filter(g -> Pattern.matches(rotpat, g.name))
.findFirst().orElse(null);
if (rot != null && rot.normal != null) {
this.rotationPoint = rot.max.add(rot.min).scale(0.5);
String[] split = rot.name.split("_");
int idx = ArrayUtils.indexOf(split, "ROT");
if (idx != ArrayUtils.INDEX_NOT_FOUND) {
String degrees = split[idx + 1];
try {
rotationDegrees = Float.parseFloat(degrees);
} catch (NumberFormatException e) {
ModCore.error("Unable to parse rotation point '%s': %s", rot.name, e);
}
}
// This is worse...
String name = rotpat.replace("_ROT", "").replaceAll("\\.\\*", "");

DataBlock config = widgetConfig.containsKey(name) ? widgetConfig.get(name) : new DataBlock() {
@Override
public Map<String, Value> getValueMap() { return Collections.emptyMap(); }
@Override
public Map<String, List<Value>> getValuesMap() { return Collections.emptyMap(); }
@Override
public Map<String, DataBlock> getBlockMap() { return Collections.emptyMap(); }
@Override
public Map<String, List<DataBlock>> getBlocksMap() { return Collections.emptyMap(); }
};

this.controlGroup = config.getValue("CG").asString(part.modelIDs.stream().map(group -> {
Matcher matcher = Pattern.compile("_CG_([^_]+)").matcher(group);
return matcher.find() ? matcher.group(1) : null;
}).filter(Objects::nonNull).findFirst().orElse(part.key));
this.label = config.getValue("LABEL").asString(part.modelIDs.stream().map(group -> {
Matcher matcher = Pattern.compile("_LABEL_([^_]+)").matcher(group);
return matcher.find() ? matcher.group(1).replaceAll("\\^", " ") : null;
}).filter(Objects::nonNull).findFirst().orElse(null));

rotations.put(Axis.X, (float) rot.normal.x);
rotations.put(Axis.Y, (float) rot.normal.y);
rotations.put(Axis.Z, (float) rot.normal.z);
Predicate<String> hasKey = s -> config.getValue(s).asBoolean(part.modelIDs.stream().anyMatch(g -> g.contains("_" + s + "_") || g.startsWith(s + "_") || g.endsWith("_" + s)));
this.toggle = hasKey.test("TOGGLE");
this.press = hasKey.test("PRESS");
this.global = hasKey.test("GLOBAL");
this.invert = hasKey.test("INVERT");
this.hide = hasKey.test("HIDE");
this.noInteract = hasKey.test("NOTOUCH") || hasKey.test("NOINTERACT");

List<Vec3d> nonRotGroups = part.groups().stream().filter(g -> !g.name.contains("_ROT")).map(g -> g.max.add(g.min).scale(0.5)).collect(Collectors.toList());
this.center = nonRotGroups.isEmpty() ? part.center : nonRotGroups.stream().reduce(Vec3d.ZERO, Vec3d::add).scale(1.0/nonRotGroups.size());
} else {
this.offset = config.getValue("OFFSET").asFloat(part.modelIDs.stream().map(group -> {
Matcher matcher = Pattern.compile("_OFFSET_([^_]+)").matcher(group);
return matcher.find() ? Float.parseFloat(matcher.group(1)) : null;
}).filter(Objects::nonNull).findFirst().orElse(0f));

DataBlock rotBlock = config.getBlock("ROT");
if (rotBlock != null) {
this.rotationDegrees = rotBlock.getValue("DEGREES").asFloat();
DataBlock point = rotBlock.getBlock("POINT");
this.rotationPoint = new Vec3d(
point.getValue("X").asDouble(),
point.getValue("Y").asDouble(),
point.getValue("Z").asDouble()
);
DataBlock axis = rotBlock.getBlock("AXIS");
this.rotations.put(Axis.X, axis.getValue("X").asFloat());
this.rotations.put(Axis.Y, axis.getValue("Y").asFloat());
this.rotations.put(Axis.Z, axis.getValue("Z").asFloat());
this.center = part.center;
} else {
OBJGroup rot = part.groups().stream()
.filter(g -> Pattern.matches(rotpat, g.name))
.findFirst().orElse(null);
if (rot != null && rot.normal != null) {
this.rotationPoint = rot.max.add(rot.min).scale(0.5);
String[] split = rot.name.split("_");
int idx = ArrayUtils.indexOf(split, "ROT");
if (idx != ArrayUtils.INDEX_NOT_FOUND) {
String degrees = split[idx + 1];
try {
rotationDegrees = Float.parseFloat(degrees);
} catch (NumberFormatException e) {
ModCore.error("Unable to parse rotation point '%s': %s", rot.name, e);
}
}

rotations.put(Axis.X, (float) rot.normal.x);
rotations.put(Axis.Y, (float) rot.normal.y);
rotations.put(Axis.Z, (float) rot.normal.z);

List<Vec3d> nonRotGroups = part.groups().stream().filter(g -> !g.name.contains("_ROT")).map(g -> g.max.add(g.min).scale(0.5)).collect(Collectors.toList());
this.center = nonRotGroups.isEmpty() ? part.center : nonRotGroups.stream().reduce(Vec3d.ZERO, Vec3d::add).scale(1.0 / nonRotGroups.size());
} else {
this.center = part.center;
}
}

Pattern pattern = Pattern.compile("TL_([^_]*)_([^_])");
for (String modelID : part.modelIDs) {
Matcher matcher = pattern.matcher(modelID);
while (matcher.find()) {
translations.put(Axis.valueOf(matcher.group(2)), Float.parseFloat(matcher.group(1)) * (float)internal_model_scale);
DataBlock tl = config.getBlock("TL");
if (tl != null) {
tl.getValueMap().forEach((k, v) -> translations.put(Axis.valueOf(k), v.asFloat()));
} else {
Pattern pattern = Pattern.compile("TL_([^_]*)_([^_])");
for (String modelID : part.modelIDs) {
Matcher matcher = pattern.matcher(modelID);
while (matcher.find()) {
translations.put(Axis.valueOf(matcher.group(2)), Float.parseFloat(matcher.group(1)) * (float) internal_model_scale);
}
}
}
pattern = Pattern.compile("SCALE_([^_]*)_([^_]+)");
for (String modelID : part.modelIDs) {
Matcher matcher = pattern.matcher(modelID);
while (matcher.find()) {
if (matcher.group(2).startsWith("R")) {
scaleRot.put(Axis.valueOf(matcher.group(2).substring(1)), Float.parseFloat(matcher.group(1)));
} else {
scales.put(Axis.valueOf(matcher.group(2)), Float.parseFloat(matcher.group(1)));
DataBlock scale = config.getBlock("SCALE");
if (scale != null) {
scale.getValueMap().forEach((k, v) -> scales.put(Axis.valueOf(k), v.asFloat()));
DataBlock r = scale.getBlock("R");
if (r != null) {
r.getValueMap().forEach((k, v) -> scaleRot.put(Axis.valueOf(k), v.asFloat()));
}
} else {
Pattern pattern = Pattern.compile("SCALE_([^_]*)_([^_]+)");
for (String modelID : part.modelIDs) {
Matcher matcher = pattern.matcher(modelID);
while (matcher.find()) {
if (matcher.group(2).startsWith("R")) {
scaleRot.put(Axis.valueOf(matcher.group(2).substring(1)), Float.parseFloat(matcher.group(1)));
} else {
scales.put(Axis.valueOf(matcher.group(2)), Float.parseFloat(matcher.group(1)));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import cam72cam.immersiverailroading.model.ModelState;
import cam72cam.immersiverailroading.model.components.ComponentProvider;
import cam72cam.immersiverailroading.model.components.ModelComponent;
import cam72cam.immersiverailroading.util.DataBlock;
import cam72cam.mod.entity.Player;
import cam72cam.mod.entity.boundingbox.IBoundingBox;
import cam72cam.mod.math.Vec3d;
Expand All @@ -30,15 +31,15 @@ public enum Types {
public final Types type;

public static <T extends EntityMoveableRollingStock> List<Door<T>> get(ComponentProvider provider, ModelState state) {
return provider.parseAll(ModelComponentType.DOOR_X).stream().map(p -> new Door<T>(p, state, provider.internal_model_scale)).collect(Collectors.toList());
return provider.parseAll(ModelComponentType.DOOR_X).stream().map(p -> new Door<T>(p, state, provider.internal_model_scale, provider.widgetConfig)).collect(Collectors.toList());
}

public static <T extends EntityMoveableRollingStock> List<Door<T>> get(ComponentProvider provider, ModelState state, ModelPosition pos) {
return provider.parseAll(ModelComponentType.DOOR_X, pos).stream().map(p -> new Door<T>(p, state, provider.internal_model_scale)).collect(Collectors.toList());
return provider.parseAll(ModelComponentType.DOOR_X, pos).stream().map(p -> new Door<T>(p, state, provider.internal_model_scale, provider.widgetConfig)).collect(Collectors.toList());
}

public Door(ModelComponent part, ModelState state, double internal_model_scale) {
super(part, state, internal_model_scale);
public Door(ModelComponent part, ModelState state, double internal_model_scale, Map<String, DataBlock> widgetConfig) {
super(part, state, internal_model_scale, widgetConfig);
type = part.modelIDs.stream().anyMatch(g -> g.contains("EXTERNAL")) ? Types.EXTERNAL :
part.modelIDs.stream().anyMatch(g -> g.contains("CONNECTING")) ? Types.CONNECTING :
Types.INTERNAL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import cam72cam.immersiverailroading.model.ModelState;
import cam72cam.immersiverailroading.model.components.ComponentProvider;
import cam72cam.immersiverailroading.model.components.ModelComponent;
import cam72cam.immersiverailroading.util.DataBlock;

import java.util.HashMap;
import java.util.List;
Expand All @@ -24,15 +25,15 @@ public class Readout<T extends EntityMoveableRollingStock> extends Control<T> {
private final float rangeMax;

public static <T extends EntityMoveableRollingStock> List<Readout<T>> getReadouts(ComponentProvider provider, ModelState state, ModelComponentType type, Readouts value) {
return provider.parseAll(type).stream().map(p -> new Readout<T>(p, value::getValue, state, provider.internal_model_scale)).collect(Collectors.toList());
return provider.parseAll(type).stream().map(p -> new Readout<T>(p, value::getValue, state, provider.internal_model_scale, provider.widgetConfig)).collect(Collectors.toList());
}

public static <T extends EntityMoveableRollingStock> List<Readout<T>> getReadouts(ComponentProvider provider, ModelState state, ModelComponentType type, ModelPosition pos, Readouts value) {
return provider.parseAll(type, pos).stream().map(p -> new Readout<T>(p, value::getValue, state, provider.internal_model_scale)).collect(Collectors.toList());
return provider.parseAll(type, pos).stream().map(p -> new Readout<T>(p, value::getValue, state, provider.internal_model_scale, provider.widgetConfig)).collect(Collectors.toList());
}

public Readout(ModelComponent part, Function<T, Float> position, ModelState state, double internal_model_scale) {
super(part, state, internal_model_scale);
public Readout(ModelComponent part, Function<T, Float> position, ModelState state, double internal_model_scale, Map<String, DataBlock> widgetConfig) {
super(part, state, internal_model_scale, widgetConfig);
this.position = position;

float min = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public abstract class EntityRollingStockDefinition {

public List<AnimationDefinition> animations;
public Map<String, Float> cgDefaults;
public Map<String, DataBlock> widgetConfig;

public static class SoundDefinition {
public final Identifier start;
Expand Down Expand Up @@ -552,6 +553,11 @@ public void loadData(DataBlock data) throws Exception {
this.cgDefaults.put(key, block.getValue("default").asFloat(0))
);
}
this.widgetConfig = Collections.emptyMap();
DataBlock widgets = data.getBlock("widgets");
if (widgets != null) {
widgetConfig = widgets.getBlockMap();
}
}

public List<ModelComponent> getComponents(ModelComponentType name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,37 @@ particles = # Optional: Configure general particle emitter settings
# Control defaults
# controls:
# CG_NAME =
# default = 0.0
# default = 0.0
# Widget Specs (TODO Document)
#widgets =
# WIDGET_NAME_1 =
# CG = "SOME_CONTROL_GROUP"
# LABEL = "Some Label"
# TOGGLE = true
# PRESS = true
# INVERT = true
# HIDE = true
# NOINTERACT = true
# OFFSET = 0.5
# ROT =
# DEGREES = 90
# POINT =
# X = 1
# Y = 0
# Z = 0
# AXIS =
# X = 1
# Y = 0
# Z = 0
# TL =
# X = 10
# Y = 20
# Z = 30
# SCALE =
# X = 1
# Y = 1
# Z = 1
# R =
# X = 1
# Y = 1
# Z = 1

0 comments on commit 8224792

Please sign in to comment.