mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-03-10 17:01:11 -07:00
1516 lines
50 KiB
Java
1516 lines
50 KiB
Java
package mindustry.game;
|
|
|
|
import arc.*;
|
|
import arc.func.*;
|
|
import arc.graphics.*;
|
|
import arc.graphics.Texture.*;
|
|
import arc.graphics.g2d.*;
|
|
import arc.math.*;
|
|
import arc.math.geom.*;
|
|
import arc.scene.ui.layout.*;
|
|
import arc.struct.*;
|
|
import arc.util.*;
|
|
import arc.util.serialization.*;
|
|
import arc.util.serialization.Json.*;
|
|
import mindustry.*;
|
|
import mindustry.content.*;
|
|
import mindustry.core.*;
|
|
import mindustry.ctype.*;
|
|
import mindustry.game.MapObjectives.*;
|
|
import mindustry.gen.*;
|
|
import mindustry.graphics.*;
|
|
import mindustry.io.*;
|
|
import mindustry.logic.*;
|
|
import mindustry.type.*;
|
|
import mindustry.world.*;
|
|
import mindustry.world.blocks.logic.CanvasBlock.*;
|
|
import mindustry.world.blocks.logic.LogicDisplay.*;
|
|
|
|
import java.lang.annotation.*;
|
|
import java.util.*;
|
|
|
|
import static java.lang.annotation.ElementType.*;
|
|
import static java.lang.annotation.RetentionPolicy.*;
|
|
import static mindustry.Vars.*;
|
|
|
|
/** Handles and executes in-map objectives. */
|
|
public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObjective>{
|
|
public static final Seq<Prov<? extends MapObjective>> allObjectiveTypes = new Seq<>();
|
|
public static final Seq<Prov<? extends ObjectiveMarker>> allMarkerTypes = new Seq<>();
|
|
public static final ObjectMap<String, Prov<? extends ObjectiveMarker>> markerNameToType = new ObjectMap<>();
|
|
public static final Seq<String> allMarkerTypeNames = new Seq<>();
|
|
|
|
/**
|
|
* All objectives the executor contains. Do not modify directly, ever!
|
|
* @see #eachRunning(Cons)
|
|
*/
|
|
public Seq<MapObjective> all = new Seq<>(4);
|
|
|
|
static{
|
|
registerObjective(
|
|
ResearchObjective::new,
|
|
ProduceObjective::new,
|
|
ItemObjective::new,
|
|
CoreItemObjective::new,
|
|
BuildCountObjective::new,
|
|
UnitCountObjective::new,
|
|
DestroyUnitsObjective::new,
|
|
TimerObjective::new,
|
|
DestroyBlockObjective::new,
|
|
DestroyBlocksObjective::new,
|
|
DestroyCoreObjective::new,
|
|
CommandModeObjective::new,
|
|
FlagObjective::new
|
|
);
|
|
|
|
registerMarker(
|
|
ShapeTextMarker::new,
|
|
PointMarker::new,
|
|
ShapeMarker::new,
|
|
TextMarker::new,
|
|
LineMarker::new,
|
|
TextureMarker::new,
|
|
QuadMarker::new
|
|
);
|
|
|
|
registerLegacyMarker("Minimap", PointMarker::new);
|
|
}
|
|
|
|
@SafeVarargs
|
|
public static void registerObjective(Prov<? extends MapObjective>... providers){
|
|
for(var prov : providers){
|
|
allObjectiveTypes.add(prov);
|
|
|
|
Class<? extends MapObjective> type = prov.get().getClass();
|
|
String name = type.getSimpleName().replace("Objective", "");
|
|
JsonIO.classTag(Strings.camelize(name), type);
|
|
JsonIO.classTag(name, type);
|
|
}
|
|
}
|
|
|
|
@SafeVarargs
|
|
public static void registerMarker(Prov<? extends ObjectiveMarker>... providers){
|
|
for(var prov : providers){
|
|
allMarkerTypes.add(prov);
|
|
|
|
Class<? extends ObjectiveMarker> type = prov.get().getClass();
|
|
String name = type.getSimpleName().replace("Marker", "");
|
|
allMarkerTypeNames.add(Strings.camelize(name));
|
|
markerNameToType.put(name, prov);
|
|
markerNameToType.put(Strings.camelize(name), prov);
|
|
JsonIO.classTag(Strings.camelize(name), type);
|
|
JsonIO.classTag(name, type);
|
|
}
|
|
}
|
|
|
|
public static void registerLegacyMarker(String name, Prov<? extends ObjectiveMarker> prov){
|
|
Class<?> type = prov.get().getClass();
|
|
|
|
markerNameToType.put(name, prov);
|
|
markerNameToType.put(Strings.camelize(name), prov);
|
|
JsonIO.classTag(Strings.camelize(name), type);
|
|
JsonIO.classTag(name, type);
|
|
}
|
|
|
|
public MapObjectives(Seq<MapObjective> all){
|
|
this.all.addAll(all);
|
|
}
|
|
|
|
public MapObjectives(){
|
|
}
|
|
|
|
/** Adds all given objectives to the executor as root objectives. */
|
|
public void add(MapObjective... objectives){
|
|
for(var objective : objectives) flatten(objective);
|
|
}
|
|
|
|
/** Recursively adds the objective and its children. */
|
|
private void flatten(MapObjective objective){
|
|
for(var child : objective.children) flatten(child);
|
|
|
|
objective.children.clear();
|
|
all.add(objective);
|
|
}
|
|
|
|
/** Updates all objectives this executor contains. */
|
|
public void update(){
|
|
eachRunning(obj -> {
|
|
//objectives cannot get completed on the client, but they do try to update for timers and such
|
|
if(obj.update() && !net.client()){
|
|
Call.completeObjective(all.indexOf(obj));
|
|
}
|
|
});
|
|
}
|
|
|
|
public @Nullable MapObjective get(int index){
|
|
return index < 0 || index >= all.size ? null : all.get(index);
|
|
}
|
|
|
|
/** @return Whether there are any qualified objectives at all. */
|
|
public boolean any(){
|
|
return all.count(MapObjective::qualified) > 0;
|
|
}
|
|
|
|
public void clear(){
|
|
all.clear();
|
|
}
|
|
|
|
/** Iterates over all qualified in-map objectives. */
|
|
public void eachRunning(Cons<MapObjective> cons){
|
|
all.each(MapObjective::qualified, cons);
|
|
}
|
|
|
|
/** Iterates over all qualified in-map objectives, with a filter. */
|
|
public <T extends MapObjective> void eachRunning(Boolf<? super MapObjective> pred, Cons<T> cons){
|
|
all.each(obj -> obj.qualified() && pred.get(obj), cons);
|
|
}
|
|
|
|
@Override
|
|
public Iterator<MapObjective> iterator(){
|
|
return all.iterator();
|
|
}
|
|
|
|
@Override
|
|
public void each(Cons<? super MapObjective> cons){
|
|
all.each(cons);
|
|
}
|
|
|
|
/** Base abstract class for any in-map objective. */
|
|
public static abstract class MapObjective{
|
|
public boolean hidden;
|
|
public @Nullable @Multiline String details;
|
|
public @Nullable @LogicCode String completionLogicCode;
|
|
public @Unordered String[] flagsAdded = {};
|
|
public @Unordered String[] flagsRemoved = {};
|
|
public ObjectiveMarker[] markers = {};
|
|
|
|
/** The parents of this objective. All parents must be done in order for this to be updated. */
|
|
public transient Seq<MapObjective> parents = new Seq<>(2);
|
|
/** Temporary container to store references since this class is static. Will immediately be flattened. */
|
|
private transient final Seq<MapObjective> children = new Seq<>(2);
|
|
|
|
/** For the objectives UI dialog. Do not modify directly! */
|
|
public transient int editorX = -999, editorY = -999;
|
|
|
|
/** Whether this objective has been done yet. This is internally set. */
|
|
private boolean completed;
|
|
/** Internal value. Do not modify! */
|
|
private transient boolean depFinished;
|
|
|
|
/** @return True if this objective is done and should be removed from the executor. */
|
|
public abstract boolean update();
|
|
|
|
/** Reset internal state, if any. */
|
|
public void reset(){}
|
|
|
|
/** Called once after {@link #update()} returns true, before this objective is removed. */
|
|
public void done(){
|
|
state.rules.objectiveFlags.removeAll(flagsRemoved);
|
|
state.rules.objectiveFlags.addAll(flagsAdded);
|
|
completed = true;
|
|
|
|
if(completionLogicCode != null && !completionLogicCode.isEmpty()){
|
|
LExecutor.runLogicScript(completionLogicCode);
|
|
}
|
|
}
|
|
|
|
/** @return true if all {@link #parents} are completed, rendering this objective able to execute. */
|
|
public final boolean dependencyFinished(){
|
|
if(depFinished) return true;
|
|
|
|
for(var parent : parents){
|
|
if(!parent.isCompleted()) return false;
|
|
}
|
|
|
|
return depFinished = true;
|
|
}
|
|
|
|
/** @return true if this objective is done (practically, has been removed from the executor). */
|
|
public final boolean isCompleted(){
|
|
return completed;
|
|
}
|
|
|
|
/** @return Whether this objective should run at all. */
|
|
public boolean qualified(){
|
|
return !completed && dependencyFinished();
|
|
}
|
|
|
|
/** @return This objective, with the given child's parents added with this, for chaining operations. */
|
|
public MapObjective child(MapObjective child){
|
|
child.parents.add(this);
|
|
children.add(child);
|
|
return this;
|
|
}
|
|
|
|
/** @return This objective, with the given parent added to this objective's parents, for chaining operations. */
|
|
public MapObjective parent(MapObjective parent){
|
|
parents.add(parent);
|
|
return this;
|
|
}
|
|
|
|
/** @return This objective, with the details message assigned to, for chaining operations. */
|
|
public MapObjective details(String details){
|
|
this.details = details;
|
|
return this;
|
|
}
|
|
|
|
/** @return This objective, with the added-flags assigned to, for chaining operations. */
|
|
public MapObjective flagsAdded(String... flagsAdded){
|
|
this.flagsAdded = flagsAdded;
|
|
return this;
|
|
}
|
|
|
|
/** @return This objective, with the removed-flags assigned to, for chaining operations. */
|
|
public MapObjective flagsRemoved(String... flagsRemoved){
|
|
this.flagsRemoved = flagsRemoved;
|
|
return this;
|
|
}
|
|
|
|
/** @return This objective, with the markers assigned to, for chaining operations. */
|
|
public MapObjective markers(ObjectiveMarker... markers){
|
|
this.markers = markers;
|
|
return this;
|
|
}
|
|
|
|
/** @return Basic mission display text. If null, falls back to standard text. */
|
|
public @Nullable String text(){
|
|
return null;
|
|
}
|
|
|
|
/** @return Details that appear upon click. */
|
|
public @Nullable String details(){
|
|
return details;
|
|
}
|
|
|
|
/** @return The localized type-name of this objective, defaulting to the class simple name without the "Objective" prefix. */
|
|
public String typeName(){
|
|
String className = getClass().getSimpleName().replace("Objective", "");
|
|
return Core.bundle == null ? className : Core.bundle.get("objective." + className.toLowerCase() + ".name", className);
|
|
}
|
|
|
|
/** Validate fields after reading to make sure none of them are null. */
|
|
public void validate(){
|
|
|
|
}
|
|
}
|
|
|
|
/** Research a specific piece of content in the tech tree. */
|
|
public static class ResearchObjective extends MapObjective{
|
|
public @Researchable UnlockableContent content = Items.copper;
|
|
|
|
public ResearchObjective(UnlockableContent content){
|
|
this.content = content;
|
|
}
|
|
|
|
public ResearchObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return content.unlocked();
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.research", content.emoji(), content.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(content == null) content = Items.copper;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "research: " + content;
|
|
}
|
|
}
|
|
|
|
/** Produce a specific piece of content in the tech tree (essentially research with different text). */
|
|
public static class ProduceObjective extends MapObjective{
|
|
public @Researchable UnlockableContent content = Items.copper;
|
|
|
|
public ProduceObjective(UnlockableContent content){
|
|
this.content = content;
|
|
}
|
|
|
|
public ProduceObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return content.unlocked();
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.produce", content.emoji(), content.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(content == null) content = Items.copper;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "produce: " + content;
|
|
}
|
|
}
|
|
|
|
/** Have a certain amount of item in your core. */
|
|
public static class ItemObjective extends MapObjective{
|
|
public Item item = Items.copper;
|
|
public int amount = 1;
|
|
|
|
public ItemObjective(Item item, int amount){
|
|
this.item = item;
|
|
this.amount = amount;
|
|
}
|
|
|
|
public ItemObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return state.rules.defaultTeam.items().has(item, amount);
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.item", state.rules.defaultTeam.items().get(item), amount, item.emoji(), item.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(item == null) item = Items.copper;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "item: " + item + " x" + amount;
|
|
}
|
|
}
|
|
|
|
/** Get a certain item in your core (through a block, not manually.) */
|
|
public static class CoreItemObjective extends MapObjective{
|
|
public Item item = Items.copper;
|
|
public int amount = 2;
|
|
|
|
public CoreItemObjective(Item item, int amount){
|
|
this.item = item;
|
|
this.amount = amount;
|
|
}
|
|
|
|
public CoreItemObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return state.stats.coreItemCount.get(item) >= amount;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.coreitem", state.stats.coreItemCount.get(item), amount, item.emoji(), item.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(item == null) item = Items.copper;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "coreItem: " + item + " x" + amount;
|
|
}
|
|
}
|
|
|
|
/** Build a certain amount of a block. */
|
|
public static class BuildCountObjective extends MapObjective{
|
|
public @Synthetic Block block = Blocks.conveyor;
|
|
public int count = 1;
|
|
|
|
public BuildCountObjective(Block block, int count){
|
|
this.block = block;
|
|
this.count = count;
|
|
}
|
|
|
|
public BuildCountObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return state.stats.placedBlockCount.get(block, 0) >= count;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.build", count - state.stats.placedBlockCount.get(block, 0), block.emoji(), block.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(block == null) block = Blocks.conveyor;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "buildCount: " + block + " x" + count;
|
|
}
|
|
}
|
|
|
|
/** Produce a certain amount of a unit. */
|
|
public static class UnitCountObjective extends MapObjective{
|
|
public UnitType unit = UnitTypes.dagger;
|
|
public int count = 1;
|
|
|
|
public UnitCountObjective(UnitType unit, int count){
|
|
this.unit = unit;
|
|
this.count = count;
|
|
}
|
|
|
|
public UnitCountObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return state.rules.defaultTeam.data().countType(unit) >= count;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.buildunit", count - state.rules.defaultTeam.data().countType(unit), unit.emoji(), unit.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(unit == null) unit = UnitTypes.dagger;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "unitCount: " + unit + " x" + count;
|
|
}
|
|
}
|
|
|
|
/** Produce a certain amount of units. */
|
|
public static class DestroyUnitsObjective extends MapObjective{
|
|
public int count = 1;
|
|
|
|
public DestroyUnitsObjective(int count){
|
|
this.count = count;
|
|
}
|
|
|
|
public DestroyUnitsObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return state.stats.enemyUnitsDestroyed >= count;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.destroyunits", count - state.stats.enemyUnitsDestroyed);
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "destroyUnits: " + count;
|
|
}
|
|
}
|
|
|
|
public static class TimerObjective extends MapObjective{
|
|
public @Multiline String text;
|
|
public @Second float duration = 60f * 30f;
|
|
|
|
protected float countup;
|
|
|
|
public TimerObjective(String text, float duration){
|
|
this.text = text;
|
|
this.duration = duration;
|
|
}
|
|
|
|
public TimerObjective(){
|
|
}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return (countup += Time.delta) >= duration * state.rules.objectiveTimerMultiplier;
|
|
}
|
|
|
|
@Override
|
|
public void reset(){
|
|
countup = 0f;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public String text(){
|
|
if(text != null){
|
|
int i = (int)((duration * state.rules.objectiveTimerMultiplier - countup) / 60f);
|
|
StringBuilder timeString = new StringBuilder();
|
|
|
|
int m = i / 60;
|
|
int s = i % 60;
|
|
if(m > 0){
|
|
timeString.append(m);
|
|
timeString.append(":");
|
|
if(s < 10){
|
|
timeString.append("0");
|
|
}
|
|
}
|
|
timeString.append(s);
|
|
|
|
if(text.startsWith("@")){
|
|
if(state.mapLocales.containsProperty(text.substring(1))){
|
|
try{
|
|
return state.mapLocales.getFormatted(text.substring(1), timeString.toString());
|
|
}catch(IllegalArgumentException e){
|
|
//illegal text.
|
|
text = "";
|
|
}
|
|
}
|
|
return Core.bundle.format(text.substring(1), timeString.toString());
|
|
}else{
|
|
try{
|
|
return Core.bundle.formatString(text, timeString.toString());
|
|
}catch(IllegalArgumentException e){
|
|
//illegal text.
|
|
text = "";
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "timer: " + duration;
|
|
}
|
|
}
|
|
|
|
public static class DestroyBlockObjective extends MapObjective{
|
|
public Point2 pos = new Point2();
|
|
public Team team = Team.crux;
|
|
public @Synthetic Block block = Blocks.router;
|
|
|
|
public DestroyBlockObjective(Block block, int x, int y, Team team){
|
|
this.block = block;
|
|
this.team = team;
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public DestroyBlockObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
var build = world.build(pos.x, pos.y);
|
|
return build == null || build.team != team || build.block != block;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.destroyblock", block.emoji(), block.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(block == null) block = Blocks.router;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "destroyBlock: " + block + ":" + team + " " + pos;
|
|
}
|
|
}
|
|
|
|
public static class DestroyBlocksObjective extends MapObjective{
|
|
public @Unordered Point2[] positions = {};
|
|
public Team team = Team.crux;
|
|
public @Synthetic Block block = Blocks.router;
|
|
|
|
public DestroyBlocksObjective(Block block, Team team, Point2... positions){
|
|
this.block = block;
|
|
this.team = team;
|
|
this.positions = positions;
|
|
}
|
|
|
|
public DestroyBlocksObjective(){}
|
|
|
|
public int progress(){
|
|
int count = 0;
|
|
for(var pos : positions){
|
|
var build = world.build(pos.x, pos.y);
|
|
if(build == null || build.team != team || build.block != block){
|
|
count ++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return progress() >= positions.length;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.format("objective.destroyblocks", progress(), positions.length, block.emoji(), block.localizedName);
|
|
}
|
|
|
|
@Override
|
|
public void validate(){
|
|
if(block == null) block = Blocks.router;
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "destroyBlocks: " + block + ":" + team + " " + Arrays.toString(positions);
|
|
}
|
|
}
|
|
|
|
/** Command any unit to do anything. Always compete in headless mode. */
|
|
public static class CommandModeObjective extends MapObjective{
|
|
@Override
|
|
public boolean update(){
|
|
return headless || control.input.selectedUnits.contains(u -> u.isCommandable() && u.command().hasCommand());
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.get("objective.command");
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "commandMode";
|
|
}
|
|
}
|
|
|
|
/** Wait until a logic flag is set. */
|
|
public static class FlagObjective extends MapObjective{
|
|
public String flag = "flag";
|
|
public @Multiline String text;
|
|
|
|
public FlagObjective(String flag, String text){
|
|
this.flag = flag;
|
|
this.text = text;
|
|
}
|
|
|
|
public FlagObjective(){}
|
|
|
|
@Override
|
|
public boolean update(){
|
|
return state.rules.objectiveFlags.contains(flag);
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public String text(){
|
|
if(text == null) return null;
|
|
|
|
if(text.startsWith("@")){
|
|
if(state.mapLocales.containsProperty(text.substring(1))) return state.mapLocales.getProperty(text.substring(1));
|
|
return Core.bundle.get(text.substring(1));
|
|
}else{
|
|
return text;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "flag: " + flag;
|
|
}
|
|
}
|
|
|
|
/** Destroy all enemy core(s). */
|
|
public static class DestroyCoreObjective extends MapObjective{
|
|
@Override
|
|
public boolean update(){
|
|
return state.rules.waveTeam.cores().size == 0;
|
|
}
|
|
|
|
@Override
|
|
public String text(){
|
|
return Core.bundle.get("objective.destroycore");
|
|
}
|
|
|
|
@Override
|
|
public String toString(){
|
|
return "destroyCore";
|
|
}
|
|
}
|
|
|
|
/** Marker used for drawing various content to indicate something along with an objective. Mostly used as UI overlay. */
|
|
public static abstract class ObjectiveMarker implements JsonSerializable{
|
|
/** Internal use only! Do not access. */
|
|
public transient int arrayIndex;
|
|
|
|
/** Whether to display marker in the world. */
|
|
public boolean world = true;
|
|
/** Whether to display marker on minimap. */
|
|
public boolean minimap = false;
|
|
/** Whether to scale marker corresponding to player's zoom level. */
|
|
public boolean autoscale = false;
|
|
/** On which z-sorting layer is marker drawn. */
|
|
protected float drawLayer = Layer.overlayUI;
|
|
|
|
public void draw(float scaleFactor){}
|
|
|
|
/** Control marker with world processor code. Ignores NaN (null) values. */
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
if(Double.isNaN(p1)) return;
|
|
|
|
switch(type){
|
|
case world -> world = !Mathf.equal((float)p1, 0f);
|
|
case minimap -> minimap = !Mathf.equal((float)p1, 0f);
|
|
case autoscale -> autoscale = !Mathf.equal((float)p1, 0f);
|
|
case drawLayer -> drawLayer = (float)p1;
|
|
}
|
|
}
|
|
|
|
public void setText(String text, boolean fetch){}
|
|
|
|
public void setTexture(Object texture){}
|
|
|
|
/** @return The localized type-name of this objective, defaulting to the class simple name without the "Marker" prefix. */
|
|
public String typeName(){
|
|
String className = getClass().getSimpleName().replace("Marker", "");
|
|
return Core.bundle == null ? className : Core.bundle.get("marker." + className.toLowerCase() + ".name", className);
|
|
}
|
|
|
|
public static String fetchText(String text){
|
|
if(text == null) return "";
|
|
|
|
if(text.startsWith("@")){
|
|
String key = text.substring(1);
|
|
|
|
String out;
|
|
if(mobile){
|
|
out =
|
|
state.mapLocales.containsProperty(key + ".mobile") ?
|
|
state.mapLocales.getProperty(key + ".mobile") :
|
|
state.mapLocales.containsProperty(key) ?
|
|
state.mapLocales.getProperty(key) :
|
|
Core.bundle.get(key + ".mobile", Core.bundle.get(key));
|
|
}else{
|
|
out =
|
|
state.mapLocales.containsProperty(key) ?
|
|
state.mapLocales.getProperty(key) :
|
|
Core.bundle.get(key);
|
|
}
|
|
return UI.formatIcons(out);
|
|
}else{
|
|
return UI.formatIcons(text);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void write(Json json){
|
|
json.writeFields(this);
|
|
}
|
|
|
|
@Override
|
|
public void read(Json json, JsonValue jsonData){
|
|
json.readFields(this, jsonData);
|
|
if(jsonData.has("textureName")) setTexture(jsonData.getString("textureName"));
|
|
}
|
|
}
|
|
|
|
/** A marker that has a position in the world in world coordinates. */
|
|
public static abstract class PosMarker extends ObjectiveMarker{
|
|
/** Position of marker, in world coordinates */
|
|
public @TilePos Vec2 pos = new Vec2();
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
if(type == LMarkerControl.pos){
|
|
pos.x = (float)p1 * tilesize;
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
if(type == LMarkerControl.pos){
|
|
pos.y = (float)p2 * tilesize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Displays text above a shape. */
|
|
public static class ShapeTextMarker extends PosMarker{
|
|
public @Multiline String text = "frog";
|
|
public float fontSize = 1f, textHeight = 7f;
|
|
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
|
public @Alignment int textAlign = Align.center;
|
|
public @Alignment(ver = false) int lineAlign = Align.center;
|
|
|
|
public float radius = 6f, rotation = 0f;
|
|
public int sides = 4;
|
|
public Color color = Color.valueOf("ffd37f");
|
|
|
|
// Cached localized text.
|
|
private transient String fetchedText;
|
|
|
|
public ShapeTextMarker(String text, float x, float y){
|
|
this.text = text;
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public ShapeTextMarker(String text, float x, float y, float radius){
|
|
this.text = text;
|
|
this.pos.set(x, y);
|
|
this.radius = radius;
|
|
}
|
|
|
|
public ShapeTextMarker(String text, float x, float y, float radius, float rotation){
|
|
this.text = text;
|
|
this.pos.set(x, y);
|
|
this.radius = radius;
|
|
this.rotation = rotation;
|
|
}
|
|
|
|
public ShapeTextMarker(String text, float x, float y, float radius, float rotation, float textHeight){
|
|
this.text = text;
|
|
this.pos.set(x, y);
|
|
this.radius = radius;
|
|
this.rotation = rotation;
|
|
this.textHeight = textHeight;
|
|
}
|
|
|
|
public ShapeTextMarker(String text, float x, float y, float radius, float rotation, float textHeight, int textAlign){
|
|
this.text = text;
|
|
this.pos.set(x, y);
|
|
this.radius = radius;
|
|
this.rotation = rotation;
|
|
this.textHeight = textHeight;
|
|
this.textAlign = textAlign;
|
|
}
|
|
|
|
public ShapeTextMarker(){}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
//in case some idiot decides to make 9999999 sides and freeze the game
|
|
int sides = Math.min(this.sides, 300);
|
|
|
|
Draw.z(drawLayer);
|
|
Lines.stroke(3f * scaleFactor, Pal.gray);
|
|
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
|
|
Lines.stroke(scaleFactor, color);
|
|
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
|
|
Draw.reset();
|
|
|
|
if(fetchedText == null){
|
|
fetchedText = fetchText(text);
|
|
}
|
|
|
|
// font size cannot be 0
|
|
if(Mathf.equal(fontSize, 0f)) return;
|
|
|
|
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius * scaleFactor + textHeight * scaleFactor, drawLayer, flags, fontSize * scaleFactor, textAlign, lineAlign);
|
|
}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case fontSize -> fontSize = (float)p1;
|
|
case textHeight -> textHeight = (float)p1;
|
|
case textAlign -> textAlign = (int)p1;
|
|
case lineAlign -> lineAlign = (int)p1;
|
|
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
|
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
|
case radius -> radius = (float)p1;
|
|
case rotation -> rotation = (float)p1;
|
|
case color -> color.fromDouble(p1);
|
|
case shape -> sides = (int)p1;
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
switch(type){
|
|
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p2, 0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setText(String text, boolean fetch){
|
|
this.text = text;
|
|
if(fetch){
|
|
fetchedText = fetchText(this.text);
|
|
}else{
|
|
fetchedText = this.text;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Displays a circle in the world. */
|
|
public static class PointMarker extends PosMarker{
|
|
public float radius = 5f, stroke = 11f;
|
|
public Color color = Color.valueOf("f25555");
|
|
|
|
public PointMarker(int x, int y){
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public PointMarker(int x, int y, Color color){
|
|
this.pos.set(x, y);
|
|
this.color = color;
|
|
}
|
|
|
|
public PointMarker(int x, int y, float radius, float stroke, Color color){
|
|
this.pos.set(x, y);
|
|
this.stroke = stroke;
|
|
this.radius = radius;
|
|
this.color = color;
|
|
}
|
|
|
|
public PointMarker(){}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
float rad = radius * tilesize * scaleFactor;
|
|
float fin = Interp.pow2Out.apply((Time.globalTime / 100f) % 1f);
|
|
|
|
Draw.z(drawLayer);
|
|
Lines.stroke(Scl.scl((1f - fin) * stroke + 0.1f), color);
|
|
Lines.circle(pos.x, pos.y, rad * fin);
|
|
|
|
Draw.reset();
|
|
}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case radius -> radius = (float)p1;
|
|
case stroke -> stroke = (float)p1;
|
|
case color -> color.fromDouble(p1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Displays a shape with an outline and color. */
|
|
public static class ShapeMarker extends PosMarker{
|
|
public float radius = 8f, rotation = 0f, stroke = 1f, startAngle = 0f, endAngle = 360f;
|
|
public boolean fill = false, outline = true;
|
|
public int sides = 4;
|
|
public Color color = Color.valueOf("ffd37f");
|
|
|
|
public ShapeMarker(float x, float y){
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public ShapeMarker(float x, float y, float radius, float rotation){
|
|
this.pos.set(x, y);
|
|
this.radius = radius;
|
|
this.rotation = rotation;
|
|
}
|
|
|
|
public ShapeMarker(){}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
//in case some idiot decides to make 9999999 sides and freeze the game
|
|
int sides = Math.min(this.sides, 200);
|
|
|
|
Draw.z(drawLayer);
|
|
if(!fill){
|
|
if(outline){
|
|
Lines.stroke((stroke + 2f) * scaleFactor, Pal.gray);
|
|
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
|
|
}
|
|
|
|
Lines.stroke(stroke * scaleFactor, color);
|
|
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
|
|
}else{
|
|
Draw.color(color);
|
|
if(startAngle < endAngle){
|
|
Fill.arc(pos.x, pos.y, radius * scaleFactor, (endAngle - startAngle) / 360f, rotation + startAngle, sides);
|
|
}else{
|
|
Fill.arc(pos.x, pos.y, radius * scaleFactor, (startAngle - endAngle) / 360f, rotation + endAngle, sides);
|
|
}
|
|
}
|
|
|
|
Draw.reset();
|
|
}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case radius -> radius = (float)p1;
|
|
case stroke -> stroke = (float)p1;
|
|
case outline -> outline = !Mathf.equal((float)p1, 0f);
|
|
case rotation -> rotation = (float)p1;
|
|
case color -> color.fromDouble(p1);
|
|
case shape -> sides = (int)p1;
|
|
case arc -> startAngle = (float)p1;
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
switch(type){
|
|
case shape -> fill = !Mathf.equal((float)p2, 0f);
|
|
case arc -> endAngle = (float)p2;
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p3)){
|
|
if(type == LMarkerControl.shape){
|
|
outline = !Mathf.equal((float)p3, 0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Displays text at a location. */
|
|
public static class TextMarker extends PosMarker{
|
|
public @Multiline String text = "uwu";
|
|
public float fontSize = 1f;
|
|
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
|
public @Alignment int textAlign = Align.center;
|
|
public @Alignment(ver = false) int lineAlign = Align.center;
|
|
|
|
// Cached localized text.
|
|
private transient String fetchedText;
|
|
|
|
public TextMarker(String text, float x, float y, float fontSize, byte flags){
|
|
this.text = text;
|
|
this.fontSize = fontSize;
|
|
this.flags = flags;
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public TextMarker(String text, float x, float y){
|
|
this.text = text;
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public TextMarker(){}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
// font size cannot be 0
|
|
if(Mathf.equal(fontSize, 0f)) return;
|
|
|
|
if(fetchedText == null){
|
|
fetchedText = fetchText(text);
|
|
}
|
|
|
|
WorldLabel.drawAt(fetchedText, pos.x, pos.y, drawLayer, flags, fontSize * scaleFactor, textAlign, lineAlign);
|
|
}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case fontSize -> fontSize = (float)p1;
|
|
case textAlign -> textAlign = (int)p1;
|
|
case lineAlign -> lineAlign = (int)p1;
|
|
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
|
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
switch(type){
|
|
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p2, 0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setText(String text, boolean fetch){
|
|
this.text = text;
|
|
if(fetch){
|
|
fetchedText = fetchText(this.text);
|
|
}else{
|
|
fetchedText = this.text;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Displays a line from pos1 to pos2. */
|
|
public static class LineMarker extends PosMarker{
|
|
public @TilePos Vec2 endPos = new Vec2();
|
|
public float stroke = 1f;
|
|
public boolean outline = true;
|
|
public Color color1 = Color.valueOf("ffd37f");
|
|
public Color color2 = Color.valueOf("ffd37f");
|
|
|
|
public LineMarker(float x1, float y1, float x2, float y2, float stroke){
|
|
this.stroke = stroke;
|
|
this.pos.set(x1, y1);
|
|
this.endPos.set(x2, y2);
|
|
}
|
|
|
|
public LineMarker(float x1, float y1, float x2, float y2){
|
|
this.pos.set(x1, y1);
|
|
this.endPos.set(x2, y2);
|
|
}
|
|
|
|
public LineMarker(){}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
Draw.z(drawLayer);
|
|
if(outline){
|
|
Lines.stroke((stroke + 2f) * scaleFactor, Pal.gray);
|
|
Lines.line(pos.x, pos.y, endPos.x, endPos.y);
|
|
}
|
|
|
|
Lines.stroke(stroke * scaleFactor, Color.white);
|
|
Lines.line(pos.x, pos.y, color1, endPos.x, endPos.y, color2);
|
|
}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case endPos -> endPos.x = (float)p1 * tilesize;
|
|
case stroke -> stroke = (float)p1;
|
|
case color -> color1.set(color2.fromDouble(p1));
|
|
case outline -> outline = !Mathf.equal((float)p1, 0f);
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
switch(type){
|
|
case endPos -> endPos.y = (float)p2 * tilesize;
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p1) && !Double.isNaN(p2)){
|
|
switch(type){
|
|
case posi -> ((int)p1 == 0 ? pos : (int)p1 == 1 ? endPos : Tmp.v1).x = (float)p2 * tilesize;
|
|
case colori -> ((int)p1 == 0 ? color1 : (int)p1 == 1 ? color2 : Tmp.c1).fromDouble(p2);
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p1) && !Double.isNaN(p3)){
|
|
switch(type){
|
|
case posi -> ((int)p1 == 0 ? pos : (int)p1 == 1 ? endPos : Tmp.v1).y = (float)p3 * tilesize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Displays a texture with specified name. */
|
|
public static class TextureMarker extends PosMarker{
|
|
public float rotation = 0f, width = 0f, height = 0f; // Zero width/height scales marker to original texture's size
|
|
public final TextureHolder texture = new TextureHolder();
|
|
public Color color = Color.white.cpy();
|
|
|
|
private transient TextureRegion fetchedRegion;
|
|
|
|
public TextureMarker(Object texture, float x, float y, float width, float height){
|
|
this.texture.value = texture;
|
|
this.pos.set(x, y);
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
public TextureMarker(Object texture, float x, float y){
|
|
this.texture.value = texture;
|
|
this.pos.set(x, y);
|
|
}
|
|
|
|
public TextureMarker(){}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case rotation -> rotation = (float)p1;
|
|
case textureSize -> width = (float)p1 * tilesize;
|
|
case color -> color.fromDouble(p1);
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
switch(type){
|
|
case textureSize -> height = (float)p2 * tilesize;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
if(fetchedRegion == null) setTexture(texture.value);
|
|
prepareTexture(this, texture.value);
|
|
|
|
// Zero width/height scales marker to original texture's size
|
|
if(Mathf.equal(width, 0f)) width = fetchedRegion.width * fetchedRegion.scl() * Draw.xscl;
|
|
if(Mathf.equal(height, 0f)) height = fetchedRegion.height * fetchedRegion.scl() * Draw.yscl;
|
|
|
|
Draw.z(drawLayer);
|
|
Draw.color(color);
|
|
Draw.rect(fetchedRegion, pos.x, pos.y, width * scaleFactor, height * scaleFactor, rotation);
|
|
}
|
|
|
|
@Override
|
|
public void setTexture(Object texture){
|
|
this.texture.value = texture;
|
|
|
|
if(headless) return;
|
|
if(fetchedRegion == null) fetchedRegion = new TextureRegion();
|
|
lookupRegion(texture, fetchedRegion);
|
|
}
|
|
}
|
|
|
|
public static class QuadMarker extends ObjectiveMarker{
|
|
public final TextureHolder texture = new TextureHolder();
|
|
public @Vertices float[] vertices = new float[24];
|
|
private boolean mapRegion = true;
|
|
|
|
private transient @Nullable TextureRegion fetchedRegion;
|
|
|
|
public QuadMarker(){
|
|
for(int i = 0; i < 4; i++){
|
|
vertices[i * 6 + 2] = Color.white.toFloatBits();
|
|
vertices[i * 6 + 5] = Color.clearFloatBits;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void draw(float scaleFactor){
|
|
if(fetchedRegion == null) setTexture(texture.value);
|
|
prepareTexture(this, texture.value);
|
|
|
|
Draw.z(drawLayer);
|
|
Draw.vert(fetchedRegion.texture, vertices, 0, vertices.length);
|
|
}
|
|
|
|
@Override
|
|
public void control(LMarkerControl type, double p1, double p2, double p3){
|
|
super.control(type, p1, p2, p3);
|
|
|
|
if(!Double.isNaN(p1)){
|
|
switch(type){
|
|
case color -> {
|
|
float col = Tmp.c1.fromDouble(p1).toFloatBits();
|
|
for(int i = 0; i < 4; i++) vertices[i * 6 + 2] = col;
|
|
}
|
|
case pos -> vertices[0] = (float)p1 * tilesize;
|
|
case posi -> setPos((int)p1, p2, p3);
|
|
case uvi -> setUv((int)p1, p2, p3);
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p2)){
|
|
switch(type){
|
|
case pos -> vertices[1] = (float)p1 * tilesize;
|
|
}
|
|
}
|
|
|
|
if(!Double.isNaN(p1) && !Double.isNaN(p2)){
|
|
switch(type){
|
|
case colori -> setColor((int)p1, p2);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setTexture(Object texture){
|
|
this.texture.value = texture;
|
|
if(headless) return;
|
|
|
|
boolean firstUpdate = fetchedRegion == null;
|
|
|
|
if(firstUpdate) fetchedRegion = new TextureRegion();
|
|
Tmp.tr1.set(fetchedRegion);
|
|
|
|
lookupRegion(texture, fetchedRegion);
|
|
|
|
if(firstUpdate){
|
|
if(mapRegion){
|
|
mapRegion = false;
|
|
for(int i = 0; i < 4; i++){
|
|
setUv(i, vertices[i * 6 + 3], vertices[i * 6 + 4]);
|
|
}
|
|
}
|
|
}else{
|
|
for(int i = 0; i < 4; i++){
|
|
setUv(i, unmap(vertices[i * 6 + 3], Tmp.tr1.u, Tmp.tr1.u2), 1 - unmap(vertices[i * 6 + 4], Tmp.tr1.v, Tmp.tr1.v2));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static float unmap(float x, float from, float to){
|
|
if(Mathf.equal(from, to)) return x;
|
|
return (x - from) / (to - from);
|
|
}
|
|
|
|
private void setPos(int i, double x, double y){
|
|
if(i >= 0 && i < 4){
|
|
if(!Double.isNaN(x)) vertices[i * 6] = (float)x * tilesize;
|
|
if(!Double.isNaN(y)) vertices[i * 6 + 1] = (float)y * tilesize;
|
|
}
|
|
}
|
|
|
|
private void setColor(int i, double c){
|
|
if(i >= 0 && i < 4){
|
|
vertices[i * 6 + 2] = Tmp.c1.fromDouble(c).toFloatBits();
|
|
}
|
|
}
|
|
|
|
private void setUv(int i, double u, double v){
|
|
if(headless) return;
|
|
|
|
if(i >= 0 && i < 4){
|
|
if(fetchedRegion == null) setTexture(texture);
|
|
|
|
if(!Double.isNaN(u)){
|
|
boolean clampU = fetchedRegion.texture.getUWrap() != TextureWrap.mirroredRepeat && fetchedRegion.texture.getUWrap() != TextureWrap.repeat;
|
|
vertices[i * 6 + 3] = Mathf.map(clampU ? Mathf.clamp((float)u) : (float)u, fetchedRegion.u, fetchedRegion.u2);
|
|
}
|
|
if(!Double.isNaN(v)){
|
|
boolean clampV = fetchedRegion.texture.getVWrap() != TextureWrap.mirroredRepeat && fetchedRegion.texture.getVWrap() != TextureWrap.repeat;
|
|
vertices[i * 6 + 4] = Mathf.map(clampV ? 1 - Mathf.clamp((float)v) : 1 - (float)v, fetchedRegion.v, fetchedRegion.v2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void lookupRegion(Object texture, TextureRegion out){
|
|
if(texture instanceof String name){
|
|
TextureRegion region = Core.atlas.find(name);
|
|
if(region.found()){
|
|
out.set(region);
|
|
}else if(Core.assets.isLoaded(name, Texture.class)){
|
|
out.set(Core.assets.get(name, Texture.class));
|
|
}else{
|
|
out.set(Core.atlas.find("error"));
|
|
}
|
|
}else if(texture instanceof UnlockableContent u){
|
|
out.set(u.fullIcon);
|
|
}else if(texture instanceof LogicDisplayBuild d && d.isAdded()){
|
|
d.ensureBuffer();
|
|
d.getBufferRegion(out);
|
|
}else if(texture instanceof CanvasBuild c && c.isAdded()){
|
|
c.updateTexture();
|
|
if(c.texture != null) out.set(c.texture);
|
|
}else{
|
|
out.set(Core.atlas.find("error"));
|
|
}
|
|
}
|
|
|
|
private static void prepareTexture(ObjectiveMarker marker, Object texture){
|
|
if(texture instanceof LogicDisplayBuild d && d.isAdded()){
|
|
if(d.buffer == null || d.buffer.isDisposed()){
|
|
marker.setTexture("error");
|
|
}else{
|
|
d.processCommands();
|
|
}
|
|
}else if(texture instanceof CanvasBuild c && c.isAdded()){
|
|
if(c.texture == null || c.texture.isDisposed()){
|
|
marker.setTexture("error");
|
|
}else{
|
|
c.updateTexture();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class TextureHolder implements JsonSerializable{
|
|
public Object value = "white";
|
|
|
|
@Override
|
|
public void write(Json json){
|
|
if(value instanceof String s){
|
|
json.writeValue("string", s);
|
|
}else if(value instanceof UnlockableContent c){
|
|
json.writeValue("content", c.name);
|
|
}else if(value instanceof Building b){
|
|
json.writeValue("building", b.pos());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void read(Json json, JsonValue jsonData){
|
|
if(jsonData.has("string")){
|
|
value = jsonData.get("string").asString();
|
|
}else if(jsonData.has("content")){
|
|
value = content.byName(jsonData.get("content").asString());
|
|
}else if(jsonData.has("building")){
|
|
value = world.build(jsonData.get("building").asInt());
|
|
}else{
|
|
value = "white";
|
|
}
|
|
}
|
|
}
|
|
|
|
/** For arrays or {@link Seq}s; does not create element rearrangement buttons. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Unordered{}
|
|
|
|
/** For arrays or {@link Seq}s; does not add the new and delete buttons */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Immutable{}
|
|
|
|
/** For {@code float[]}; treats it as an array of vertices. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Vertices{}
|
|
|
|
/** For {@code byte}; treats it as a world label flag. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface LabelFlag{}
|
|
|
|
/** For {@code int}; treats it as an alignment from {@link Align} */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Alignment{
|
|
boolean hor() default true;
|
|
boolean ver() default true;
|
|
}
|
|
|
|
/** For {@link UnlockableContent}; filters all un-researchable content. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Researchable{}
|
|
|
|
/** For {@link Block}; filters all un-buildable blocks. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Synthetic{}
|
|
|
|
/** For {@link String}; indicates that a text area should be used. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Multiline{}
|
|
|
|
/** For {@link String}; indicates that text corresponds to logic code. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface LogicCode{}
|
|
|
|
/** For {@code float}; multiplies the UI input by 60. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface Second{}
|
|
|
|
/** For {@code float} or similar data structures, such as {@link Vec2}; multiplies the UI input by {@link Vars#tilesize}. */
|
|
@Target(FIELD)
|
|
@Retention(RUNTIME)
|
|
public @interface TilePos{}
|
|
|
|
}
|