Tree-like objective node structure (#7152)
* overall structure * overall layout * field interpreter * less bloated UI * scroll pan't * strip off the 'Marker' suffix * e * all (hopefully all...) interpreters finished. * onset * two, four * i don't understand how icon mappings work. * separate remover and indexer * some cleanups * untested mobile support * contrib * ok anuke * fix conflicts 2 * hidden
This commit is contained in:
@@ -365,6 +365,13 @@ project(":core"){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gradle.taskGraph.whenReady{
|
||||||
|
//these are completely unnecessary
|
||||||
|
tasks.kaptGenerateStubsKotlin.onlyIf{ false }
|
||||||
|
tasks.compileKotlin.onlyIf{ false }
|
||||||
|
tasks.inspectClassesForKotlinIC.onlyIf{ false }
|
||||||
|
}
|
||||||
|
|
||||||
//comp** classes are only used for code generation
|
//comp** classes are only used for code generation
|
||||||
jar{
|
jar{
|
||||||
exclude("mindustry/entities/comp/**")
|
exclude("mindustry/entities/comp/**")
|
||||||
|
|||||||
BIN
core/assets-raw/sprites/ui/button-edge-down-1.9.png
Normal file
BIN
core/assets-raw/sprites/ui/button-edge-down-1.9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 250 B |
BIN
core/assets-raw/sprites/ui/button-edge-down-3.9.png
Normal file
BIN
core/assets-raw/sprites/ui/button-edge-down-3.9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 218 B |
BIN
core/assets-raw/sprites/ui/button-edge-over-1.9.png
Normal file
BIN
core/assets-raw/sprites/ui/button-edge-over-1.9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 258 B |
BIN
core/assets-raw/sprites/ui/button-edge-over-3.9.png
Normal file
BIN
core/assets-raw/sprites/ui/button-edge-over-3.9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 228 B |
BIN
core/assets-raw/sprites/ui/button-select-trans.9.png
Normal file
BIN
core/assets-raw/sprites/ui/button-select-trans.9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 163 B |
@@ -586,6 +586,28 @@ map.multiplayer = Only the host can view sectors.
|
|||||||
uncover = Uncover
|
uncover = Uncover
|
||||||
configure = Configure Loadout
|
configure = Configure Loadout
|
||||||
|
|
||||||
|
objective.research.name = Research
|
||||||
|
objective.produce.name = Obtain
|
||||||
|
objective.item.name = Obtain Item
|
||||||
|
objective.coreitem.name = Core Item
|
||||||
|
objective.buildcount.name = Build Count
|
||||||
|
objective.unitcount.name = Unit Count
|
||||||
|
objective.destroyunits.name = Destroy Units
|
||||||
|
objective.timer.name = Timer
|
||||||
|
objective.destroyblock.name = Destroy Block
|
||||||
|
objective.destroyblocks.name = Destroy Blocks
|
||||||
|
objective.destroycore.name = Destroy Core
|
||||||
|
objective.commandmode.name = Command Mode
|
||||||
|
objective.flag.name = Flag
|
||||||
|
|
||||||
|
marker.shapetext.name = Shape Text
|
||||||
|
marker.minimap.name = Minimap
|
||||||
|
marker.shape.name = Shape
|
||||||
|
marker.text.name = Text
|
||||||
|
|
||||||
|
marker.background = Background
|
||||||
|
marker.outline = Outline
|
||||||
|
|
||||||
objective.research = [accent]Research:\n[]{0}[lightgray]{1}
|
objective.research = [accent]Research:\n[]{0}[lightgray]{1}
|
||||||
objective.produce = [accent]Obtain:\n[]{0}[lightgray]{1}
|
objective.produce = [accent]Obtain:\n[]{0}[lightgray]{1}
|
||||||
objective.destroyblock = [accent]Destroy:\n[]{0}[lightgray]{1}
|
objective.destroyblock = [accent]Destroy:\n[]{0}[lightgray]{1}
|
||||||
@@ -599,6 +621,7 @@ objective.enemiesapproaching = [accent]Enemies approaching in [lightgray]{0}[]
|
|||||||
objective.destroycore = [accent]Destroy Enemy Core
|
objective.destroycore = [accent]Destroy Enemy Core
|
||||||
objective.command = [accent]Command Units
|
objective.command = [accent]Command Units
|
||||||
objective.nuclearlaunch = [accent]\u26A0 Nuclear launch detected: [lightgray]{0}
|
objective.nuclearlaunch = [accent]\u26A0 Nuclear launch detected: [lightgray]{0}
|
||||||
|
|
||||||
announce.nuclearstrike = [red]\u26A0 NUCLEAR STRIKE INBOUND \u26A0
|
announce.nuclearstrike = [red]\u26A0 NUCLEAR STRIKE INBOUND \u26A0
|
||||||
|
|
||||||
loadout = Loadout
|
loadout = Loadout
|
||||||
|
|||||||
@@ -146,3 +146,4 @@ Alex25820
|
|||||||
KayAyeAre
|
KayAyeAre
|
||||||
SMOLKEYS
|
SMOLKEYS
|
||||||
1stvaliduser(SUS)
|
1stvaliduser(SUS)
|
||||||
|
GlennFolker
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,8 @@
|
|||||||
package mindustry.content;
|
package mindustry.content;
|
||||||
|
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import mindustry.game.MapObjectives.*;
|
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
|
import mindustry.game.MapObjectives.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
|
|
||||||
@@ -115,102 +115,18 @@ public class SectorPresets{
|
|||||||
addStartingItems = true;
|
addStartingItems = true;
|
||||||
alwaysUnlocked = true;
|
alwaysUnlocked = true;
|
||||||
difficulty = 1;
|
difficulty = 1;
|
||||||
|
|
||||||
rules = r -> {
|
|
||||||
r.objectives.addAll(
|
|
||||||
new ItemObjective(Items.beryllium, 15).withMarkers(
|
|
||||||
new ShapeTextMarker("Click to mine [accent]resources[] from walls.", 290f * 8f, 106f * 8f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.turbineCondenser, 1).withMarkers(
|
|
||||||
new ShapeTextMarker("Open the tech tree.\nResearch, then place a [accent]turbine condenser[] on the vent.\nThis will generate [accent]power[].", 289f * 8f, 116f * 8f, 8f * 2.6f, 0f, 9f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.plasmaBore, 1).withMarkers(
|
|
||||||
new ShapeTextMarker("Research and place a [accent]plasma bore[]. \nThis automatically mines resources from walls.", 293.5f * 8f, 113.5f * 8f, 4f * 2.6f, 45f, 60f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.beamNode, 1).withMarkers(
|
|
||||||
new ShapeTextMarker("To [accent]power[] the plasma bore, research and place a [accent]beam node[].\nConnect the turbine condenser to the plasma bore.", 294f * 8f, 116f * 8f)
|
|
||||||
),
|
|
||||||
new CoreItemObjective(Items.beryllium, 5).withMarkers(
|
|
||||||
new TextMarker("Research and place [accent]ducts[] to move the mined resources\nfrom the plasma bore to the core.", 285f * 8f, 108f * 8f)
|
|
||||||
),
|
|
||||||
new CoreItemObjective(Items.beryllium, 200).withMarkers(
|
|
||||||
new TextMarker("Expand the mining operation.\nPlace more Plasma Bores and use beam nodes and ducts to support them.\nMine 200 beryllium.", 280f * 8f, 118f * 8f)
|
|
||||||
),
|
|
||||||
new CoreItemObjective(Items.graphite, 100).withMarkers(
|
|
||||||
new TextMarker("More complex blocks require [accent]graphite[].\nSet up plasma bores to mine graphite.", 261f * 8f, 108f * 8f)
|
|
||||||
),
|
|
||||||
new ResearchObjective(Blocks.siliconArcFurnace).withMarkers(
|
|
||||||
new TextMarker("Begin researching [accent]factories[].\nResearch the [accent]cliff crusher[] and [accent]silicon arc furnace[].", 268f * 8f, 101f * 8f)
|
|
||||||
),
|
|
||||||
new CoreItemObjective(Items.silicon, 50).withMarkers(
|
|
||||||
new TextMarker("The arc furnace needs [accent]sand[] and [accent]graphite[] to create [accent]silicon[].\n[accent]Power[] is also required.", 268f * 8f, 101f * 8f),
|
|
||||||
new TextMarker("Use [accent]cliff crushers[] to mine sand.", 262f * 8f, 88f * 8f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.tankFabricator, 1).withMarkers(
|
|
||||||
new TextMarker("Use [accent]units[] to explore the map, defend buildings, and go on the offensive.\n Research and place a [accent]tank fabricator[].", 258f * 8f, 116f * 8f)
|
|
||||||
),
|
|
||||||
new UnitCountObjective(UnitTypes.stell, 1).withMarkers(
|
|
||||||
new TextMarker("Produce a unit.\nUse the \"?\" button to see selected factory requirements.", 258f * 8f, 116f * 8f)
|
|
||||||
),
|
|
||||||
new CommandModeObjective().withMarkers(
|
|
||||||
new TextMarker("Hold [accent]shift[] to enter [accent]command mode[].\n[accent]Left-click and drag[] to select units.\n[accent]Right-click[] to order selected units to move or attack.", 258f * 8f, 116f * 8f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.breach, 1).withMarkers(
|
|
||||||
new TextMarker("Units are effective, but [accent]turrets[] provide better defensive capabilities if used effectively.\n Place a [accent]Breach[] turret.\nTurrets require [accent]ammo[].", 258f * 8f, 114f * 8f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.berylliumWall, 6).withMarkers(
|
|
||||||
new TextMarker("[accent]Walls[] can prevent oncoming damage from reaching buildings.\nPlace some [accent]beryllium walls[] around the turret.", 276f * 8f, 133f * 8f)
|
|
||||||
),
|
|
||||||
new TimerObjective("@objective.enemiesapproaching",30 * 60).withMarkers(
|
|
||||||
new TextMarker("Enemy incoming, prepare to defend.", 276f * 8f, 133f * 8f)
|
|
||||||
).withFlags("defStart"),
|
|
||||||
new DestroyUnitsObjective(2).withFlags("defDone"),
|
|
||||||
new DestroyBlockObjective(Blocks.coreBastion , 288, 198, Team.malis).withMarkers(
|
|
||||||
new TextMarker("The enemy is vulnerable. Counter-attack.", 276f * 8f, 133f * 8f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.coreBastion, 1).withMarkers(
|
|
||||||
new ShapeTextMarker("New cores can be placed on [accent]core tiles[].\nNew cores function as forward bases and share a resource inventory with other cores.\nPlace a core.", 287.5f * 8f, 197.5f * 8f, 9f * 2.6f, 0f, 12f)
|
|
||||||
),
|
|
||||||
new TimerObjective("[accent]Set up defenses:[lightgray] {0}", 120 * 60).withMarkers(
|
|
||||||
new TextMarker("The enemy will be able to detect you in 2 minutes.\nSet up defenses, mining, and production.", 288f * 8f, 202f * 8f)
|
|
||||||
).withFlags("openMap")
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
two = new SectorPreset("two", erekir, 88){{
|
two = new SectorPreset("two", erekir, 88){{
|
||||||
difficulty = 3;
|
difficulty = 3;
|
||||||
|
|
||||||
rules = r -> {
|
|
||||||
r.objectives.addAll(
|
|
||||||
new TimerObjective("[lightgray]Enemy detection:[] [accent]{0}", 7 * 60 * 60).withMarkers(
|
|
||||||
new TextMarker("The enemy will begin constructing units in 7 minutes.", 276f * 8f, 164f * 8f)
|
|
||||||
).withFlags("beginBuilding"),
|
|
||||||
new ProduceObjective(Items.tungsten).withMarkers(
|
|
||||||
new ShapeTextMarker("Tungsten can be mined using an [accent]impact drill[].\nThis structure requires [accent]water[] and [accent]power[].", 220f * 8f, 181f * 8f)
|
|
||||||
),
|
|
||||||
new DestroyBlockObjective(Blocks.largeShieldProjector, 210, 278, Team.malis).withMarkers(
|
|
||||||
new TextMarker("The enemy is protected by shields.\nAn experimental shield breaker module has been detected in this sector.\nFind and activate it using tungsten.", 276f * 8f, 164f * 8f),
|
|
||||||
new MinimapMarker(23, 137, Pal.accent)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
lake = new SectorPreset("lake", erekir, 41){{
|
lake = new SectorPreset("lake", erekir, 41){{
|
||||||
difficulty = 4;
|
difficulty = 4;
|
||||||
|
|
||||||
rules = r -> {
|
|
||||||
r.objectives.addAll(
|
|
||||||
new BuildCountObjective(Blocks.shipFabricator, 1),
|
|
||||||
new UnitCountObjective(UnitTypes.elude, 1)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
three = new SectorPreset("three", erekir, 36){{
|
three = new SectorPreset("three", erekir, 36){{
|
||||||
difficulty = 5;
|
difficulty = 5;
|
||||||
|
|
||||||
captureWave = 9;
|
captureWave = 9;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -220,47 +136,10 @@ public class SectorPresets{
|
|||||||
|
|
||||||
split = new SectorPreset("split", erekir, 19){{ //TODO random sector, pick a better one
|
split = new SectorPreset("split", erekir, 19){{ //TODO random sector, pick a better one
|
||||||
difficulty = 5;
|
difficulty = 5;
|
||||||
|
|
||||||
rules = r -> {
|
|
||||||
r.objectives.addAll(
|
|
||||||
new CoreItemObjective(Items.tungsten, 100).withMarkers(
|
|
||||||
new TextMarker("Some blocks can be picked up by the core unit.\nPick up this [accent]container[] and place it onto the [accent]payload loader[].\n(Default keys are [ and ] to pick up and drop)", 347 * 8f, 445f * 8f),
|
|
||||||
new TextMarker("You must acquire some tungsten to build units.", 293 * 8f, 417 * 8f)
|
|
||||||
),
|
|
||||||
new BuildCountObjective(Blocks.payloadMassDriver, 2).withMarkers(
|
|
||||||
new TextMarker("Units must be transported to the other side of the wall.\nPlace two [accent]Payload Mass Drivers[], one on each side of the wall.\nSet up the link by pressing one of them, then selecting the other.", 293 * 8f, 417 * 8f)
|
|
||||||
),
|
|
||||||
new DestroyCoreObjective().withMarkers(
|
|
||||||
new TextMarker("Similar to the container, units can also be transported using a [accent]Payload Mass Driver[].\nPlace a unit fabricator adjacent to a mass driver to load them, then send them across the wall to attack the enemy base.", 293 * 8f, 417 * 8f)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
four = new SectorPreset("four", erekir, 29){{
|
four = new SectorPreset("four", erekir, 29){{
|
||||||
difficulty = 6;
|
difficulty = 6;
|
||||||
|
|
||||||
rules = r -> {
|
|
||||||
float rad = 52f;
|
|
||||||
r.objectives.addAll(
|
|
||||||
new DestroyBlocksObjective(Blocks.coreBastion, Team.malis, Point2.pack(290,501), Point2.pack(158,496))
|
|
||||||
.withFlags("nukeannounce"),
|
|
||||||
new TimerObjective("@objective.nuclearlaunch", 8 * 60 * 60).withMarkers(
|
|
||||||
new MinimapMarker(338, 377, rad, 14f, Pal.remove),
|
|
||||||
new ShapeMarker(338 * 8, 377 * 8f){{
|
|
||||||
radius = rad * 8f;
|
|
||||||
fill = true;
|
|
||||||
color = Pal.remove.cpy().mul(0.8f).a(0.3f);
|
|
||||||
sides = 90;
|
|
||||||
}},
|
|
||||||
new ShapeMarker(338 * 8, 377 * 8f){{
|
|
||||||
radius = rad * 8f;
|
|
||||||
color = Pal.remove;
|
|
||||||
sides = 90;
|
|
||||||
}}
|
|
||||||
).withFlags("nuke1")
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|||||||
@@ -328,52 +328,6 @@ public class Logic implements ApplicationListener{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateObjectives(){
|
|
||||||
//update objectives; do not get completed clientside
|
|
||||||
if(state.rules.objectives.size > 0){
|
|
||||||
var first = state.rules.objectives.first();
|
|
||||||
first.update();
|
|
||||||
|
|
||||||
//initialize markers
|
|
||||||
for(var marker : first.markers){
|
|
||||||
if(!marker.wasAdded){
|
|
||||||
marker.wasAdded = true;
|
|
||||||
marker.added();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean completed = false;
|
|
||||||
|
|
||||||
//multiple objectives can be updated in the same frame
|
|
||||||
while(!net.client() && first != null && first.complete()){
|
|
||||||
state.rules.objectives.remove(0);
|
|
||||||
first.completed();
|
|
||||||
//apply flags.
|
|
||||||
state.rules.objectiveFlags.removeAll(first.flagsRemoved);
|
|
||||||
state.rules.objectiveFlags.addAll(first.flagsAdded);
|
|
||||||
if(!headless){
|
|
||||||
//delete markers
|
|
||||||
for(var marker : first.markers){
|
|
||||||
if(marker.wasAdded){
|
|
||||||
marker.removed();
|
|
||||||
marker.wasAdded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
first = state.rules.objectives.firstOpt();
|
|
||||||
completed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(completed){
|
|
||||||
//TODO call packet for this?
|
|
||||||
if(net.server()){
|
|
||||||
Call.setRules(state.rules);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Remote(called = Loc.server)
|
@Remote(called = Loc.server)
|
||||||
public static void sectorCapture(){
|
public static void sectorCapture(){
|
||||||
//the sector has been conquered - waves get disabled
|
//the sector has been conquered - waves get disabled
|
||||||
@@ -520,7 +474,10 @@ public class Logic implements ApplicationListener{
|
|||||||
|
|
||||||
//TODO objectives clientside???
|
//TODO objectives clientside???
|
||||||
if(!state.isEditor()){
|
if(!state.isEditor()){
|
||||||
updateObjectives();
|
state.rules.objectives.update();
|
||||||
|
if(state.rules.objectives.checkChanged() && net.server()){
|
||||||
|
Call.setObjectives(state.rules.objectives);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){
|
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){
|
||||||
|
|||||||
@@ -329,6 +329,11 @@ public class NetClient implements ApplicationListener{
|
|||||||
state.rules = rules;
|
state.rules = rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Remote(variants = Variant.both)
|
||||||
|
public static void setObjectives(MapObjectives executor){
|
||||||
|
state.rules.objectives = executor;
|
||||||
|
}
|
||||||
|
|
||||||
@Remote(variants = Variant.both)
|
@Remote(variants = Variant.both)
|
||||||
public static void worldDataBegin(){
|
public static void worldDataBegin(){
|
||||||
Groups.clear();
|
Groups.clear();
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class MapInfoDialog extends BaseDialog{
|
|||||||
r.row();
|
r.row();
|
||||||
|
|
||||||
r.button("@editor.objectives", Icon.info, style, () -> {
|
r.button("@editor.objectives", Icon.info, style, () -> {
|
||||||
objectives.show(state.rules.objectives);
|
objectives.show(state.rules.objectives.all, state.rules.objectives.all::set);
|
||||||
hide();
|
hide();
|
||||||
}).marginLeft(10f);
|
}).marginLeft(10f);
|
||||||
|
|
||||||
|
|||||||
570
core/src/mindustry/editor/MapObjectivesCanvas.java
Normal file
570
core/src/mindustry/editor/MapObjectivesCanvas.java
Normal file
@@ -0,0 +1,570 @@
|
|||||||
|
package mindustry.editor;
|
||||||
|
|
||||||
|
import arc.*;
|
||||||
|
import arc.graphics.*;
|
||||||
|
import arc.graphics.g2d.*;
|
||||||
|
import arc.input.*;
|
||||||
|
import arc.math.*;
|
||||||
|
import arc.math.geom.*;
|
||||||
|
import arc.scene.event.*;
|
||||||
|
import arc.scene.ui.*;
|
||||||
|
import arc.scene.ui.ImageButton.*;
|
||||||
|
import arc.scene.ui.layout.*;
|
||||||
|
import arc.struct.*;
|
||||||
|
import arc.util.*;
|
||||||
|
import mindustry.editor.MapObjectivesCanvas.ObjectiveTilemap.ObjectiveTile.*;
|
||||||
|
import mindustry.editor.MapObjectivesDialog.*;
|
||||||
|
import mindustry.game.MapObjectives.*;
|
||||||
|
import mindustry.gen.*;
|
||||||
|
import mindustry.graphics.*;
|
||||||
|
import mindustry.ui.*;
|
||||||
|
import mindustry.ui.dialogs.*;
|
||||||
|
|
||||||
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class MapObjectivesCanvas extends WidgetGroup{
|
||||||
|
public static final int
|
||||||
|
objWidth = 5, objHeight = 2,
|
||||||
|
bounds = 100;
|
||||||
|
|
||||||
|
public static final float unitSize = 48f;
|
||||||
|
|
||||||
|
public Seq<MapObjective> objectives = new Seq<>();
|
||||||
|
public ObjectiveTilemap tilemap;
|
||||||
|
|
||||||
|
protected MapObjective query;
|
||||||
|
|
||||||
|
private boolean pressed;
|
||||||
|
private long visualPressed;
|
||||||
|
private int queryX = -objWidth, queryY = -objHeight;
|
||||||
|
|
||||||
|
public MapObjectivesCanvas(){
|
||||||
|
setFillParent(true);
|
||||||
|
addChild(tilemap = new ObjectiveTilemap());
|
||||||
|
|
||||||
|
addCaptureListener(new InputListener(){
|
||||||
|
@Override
|
||||||
|
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(query != null && button == KeyCode.mouseRight){
|
||||||
|
stopQuery();
|
||||||
|
|
||||||
|
event.stop();
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addCaptureListener(new ElementGestureListener(){
|
||||||
|
int pressPointer = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY){
|
||||||
|
if(tilemap.moving != null || tilemap.connecting != null) return;
|
||||||
|
tilemap.x = Mathf.clamp(tilemap.x + deltaX, -bounds * unitSize + width, 0f);
|
||||||
|
tilemap.y = Mathf.clamp(tilemap.y + deltaY, -bounds * unitSize + height, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tap(InputEvent event, float x, float y, int count, KeyCode button){
|
||||||
|
if(query == null) return;
|
||||||
|
|
||||||
|
Vec2 pos = localToDescendantCoordinates(tilemap, Tmp.v1.set(x, y));
|
||||||
|
queryX = Mathf.round((pos.x - objWidth * unitSize / 2f) / unitSize);
|
||||||
|
queryY = Mathf.floor((pos.y - unitSize) / unitSize);
|
||||||
|
|
||||||
|
// In mobile, placing the query is done in a separate button.
|
||||||
|
if(!mobile) placeQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(pressPointer != -1) return;
|
||||||
|
pressPointer = pointer;
|
||||||
|
pressed = true;
|
||||||
|
visualPressed = Time.millis() + 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(pointer == pressPointer){
|
||||||
|
pressPointer = -1;
|
||||||
|
pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearObjectives(){
|
||||||
|
stopQuery();
|
||||||
|
tilemap.clearTiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void stopQuery(){
|
||||||
|
if(query == null) return;
|
||||||
|
query = null;
|
||||||
|
|
||||||
|
Core.graphics.restoreCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void query(MapObjective obj){
|
||||||
|
stopQuery();
|
||||||
|
query = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void placeQuery(){
|
||||||
|
if(isQuerying() && tilemap.createTile(queryX, queryY, query)){
|
||||||
|
objectives.add(query);
|
||||||
|
stopQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQuerying(){
|
||||||
|
return query != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisualPressed(){
|
||||||
|
return pressed || visualPressed > Time.millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ObjectiveTilemap extends WidgetGroup{
|
||||||
|
protected final GridBits grid = new GridBits(bounds, bounds);
|
||||||
|
|
||||||
|
/** The connector button that is being pressed. */
|
||||||
|
protected @Nullable Connector connecting;
|
||||||
|
/** The current tile that is being moved. */
|
||||||
|
protected @Nullable ObjectiveTile moving;
|
||||||
|
|
||||||
|
public ObjectiveTilemap(){
|
||||||
|
setTransform(false);
|
||||||
|
setSize(getPrefWidth(), getPrefHeight());
|
||||||
|
touchable(() -> isQuerying() ? Touchable.disabled : Touchable.childrenOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(){
|
||||||
|
validate();
|
||||||
|
int minX = Math.max(Mathf.floor((x - 1f) / unitSize), 0), minY = Math.max(Mathf.floor((y - 1f) / unitSize), 0),
|
||||||
|
maxX = Math.min(Mathf.ceil((x + width + 1f) / unitSize), bounds), maxY = Math.min(Mathf.ceil((y + height + 1f) / unitSize), bounds);
|
||||||
|
float progX = x % unitSize, progY = y % unitSize;
|
||||||
|
|
||||||
|
Lines.stroke(2f);
|
||||||
|
Draw.color(Pal.gray, parentAlpha);
|
||||||
|
|
||||||
|
for(int x = minX; x <= maxX; x++) Lines.line(progX + x * unitSize, minY * unitSize, progX + x * unitSize, maxY * unitSize);
|
||||||
|
for(int y = minY; y <= maxY; y++) Lines.line(minX * unitSize, progY + y * unitSize, maxX * unitSize, progY + y * unitSize);
|
||||||
|
|
||||||
|
if(isQuerying()){
|
||||||
|
int tx, ty;
|
||||||
|
if(mobile){
|
||||||
|
tx = queryX;
|
||||||
|
ty = queryY;
|
||||||
|
}else{
|
||||||
|
Vec2 pos = screenToLocalCoordinates(Core.input.mouse());
|
||||||
|
tx = Mathf.round((pos.x - objWidth * unitSize / 2f) / unitSize);
|
||||||
|
ty = Mathf.floor((pos.y - unitSize) / unitSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lines.stroke(4f);
|
||||||
|
Draw.color(
|
||||||
|
isVisualPressed() ? Pal.metalGrayDark : validPlace(tx, ty) ? Pal.accent : Pal.remove,
|
||||||
|
parentAlpha * (inPlaceBounds(tx, ty) ? 1f : Mathf.absin(3f, 1f))
|
||||||
|
);
|
||||||
|
|
||||||
|
Lines.rect(x + tx * unitSize, y + ty * unitSize, objWidth * unitSize, objHeight * unitSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(moving != null){
|
||||||
|
int tx, ty;
|
||||||
|
float x = this.x + (tx = Mathf.round(moving.x / unitSize)) * unitSize;
|
||||||
|
float y = this.y + (ty = Mathf.round(moving.y / unitSize)) * unitSize;
|
||||||
|
|
||||||
|
Draw.color(
|
||||||
|
validMove(moving, tx, ty) ? Pal.accent : Pal.remove,
|
||||||
|
0.5f * parentAlpha * (inPlaceBounds(tx, ty) ? 1f : Mathf.absin(3f, 1f))
|
||||||
|
);
|
||||||
|
|
||||||
|
Fill.crect(x, y, objWidth * unitSize, objHeight * unitSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Draw.reset();
|
||||||
|
super.draw();
|
||||||
|
|
||||||
|
Draw.reset();
|
||||||
|
Seq<ObjectiveTile> tiles = getChildren().as();
|
||||||
|
|
||||||
|
Connector conTarget = null;
|
||||||
|
if(connecting != null){
|
||||||
|
Vec2 pos = connecting.localToAscendantCoordinates(this, Tmp.v1.set(connecting.pointX, connecting.pointY));
|
||||||
|
if(hit(pos.x, pos.y, true) instanceof Connector con && connecting.canConnectTo(con)) conTarget = con;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean removing = false;
|
||||||
|
for(var tile : tiles){
|
||||||
|
for(var parent : tile.obj.parents){
|
||||||
|
var parentTile = tiles.find(t -> t.obj == parent);
|
||||||
|
|
||||||
|
Connector
|
||||||
|
conFrom = parentTile.conChildren,
|
||||||
|
conTo = tile.conParent;
|
||||||
|
|
||||||
|
if(conTarget != null && (
|
||||||
|
(connecting.findParent && connecting == conTo && conTarget == conFrom) ||
|
||||||
|
(!connecting.findParent && connecting == conFrom && conTarget == conTo)
|
||||||
|
)){
|
||||||
|
removing = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2
|
||||||
|
from = conFrom.localToAscendantCoordinates(this, Tmp.v1.set(conFrom.getWidth() / 2f, conFrom.getHeight() / 2f)).add(x, y),
|
||||||
|
to = conTo.localToAscendantCoordinates(this, Tmp.v2.set(conTo.getWidth() / 2f, conTo.getHeight() / 2f)).add(x, y);
|
||||||
|
|
||||||
|
drawCurve(false, from.x, from.y, to.x, to.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(connecting != null){
|
||||||
|
Vec2
|
||||||
|
mouse = (conTarget == null
|
||||||
|
? connecting.localToAscendantCoordinates(this, Tmp.v1.set(connecting.pointX, connecting.pointY))
|
||||||
|
: conTarget.localToAscendantCoordinates(this, Tmp.v1.set(conTarget.getWidth() / 2f, conTarget.getHeight() / 2f))
|
||||||
|
).add(x, y),
|
||||||
|
anchor = connecting.localToAscendantCoordinates(this, Tmp.v2.set(connecting.getWidth() / 2f, connecting.getHeight() / 2f)).add(x, y);
|
||||||
|
|
||||||
|
Vec2
|
||||||
|
from = connecting.findParent ? mouse : anchor,
|
||||||
|
to = connecting.findParent ? anchor : mouse;
|
||||||
|
|
||||||
|
drawCurve(removing, from.x, from.y, to.x, to.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Draw.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void drawCurve(boolean remove, float x1, float y1, float x2, float y2){
|
||||||
|
Lines.stroke(4f);
|
||||||
|
Draw.color(remove ? Pal.remove : Pal.accent, parentAlpha);
|
||||||
|
|
||||||
|
float dist = Math.abs(x1 - x2) / 2f;
|
||||||
|
Lines.curve(x1, y1, x1 + dist, y1, x2 - dist, y2, x2, y2, Math.max(4, (int) (Mathf.dst(x1, y1, x2, y2) / 4f)));
|
||||||
|
|
||||||
|
Draw.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean inPlaceBounds(int x, int y){
|
||||||
|
return Structs.inBounds(x, y, bounds - objWidth + 1, bounds - objHeight + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validPlace(int x, int y){
|
||||||
|
if(!inPlaceBounds(x, y)) return false;
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
if(occupied(x + tx, y + ty)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validMove(ObjectiveTile tile, int newX, int newY){
|
||||||
|
if(!inPlaceBounds(newX, newY)) return false;
|
||||||
|
|
||||||
|
int x = tile.tx, y = tile.ty;
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
grid.set(x + tx, y + ty, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean valid = validPlace(newX, newY);
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
grid.set(x + tx, y + ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean occupied(int x, int y){
|
||||||
|
return grid.get(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean createTile(MapObjective obj){
|
||||||
|
return createTile(obj.editorX, obj.editorY, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean createTile(int x, int y, MapObjective obj){
|
||||||
|
if(!validPlace(x, y)) return false;
|
||||||
|
|
||||||
|
ObjectiveTile tile = new ObjectiveTile(obj, x, y);
|
||||||
|
tile.pack();
|
||||||
|
|
||||||
|
addChild(tile);
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
grid.set(x + tx, y + ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean moveTile(ObjectiveTile tile, int newX, int newY){
|
||||||
|
if(!validMove(tile, newX, newY)) return false;
|
||||||
|
|
||||||
|
int x = tile.tx, y = tile.ty;
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
grid.set(x + tx, y + ty, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.pos(newX, newY);
|
||||||
|
|
||||||
|
x = newX;
|
||||||
|
y = newY;
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
grid.set(x + tx, y + ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTile(ObjectiveTile tile){
|
||||||
|
if(!tile.isDescendantOf(this)) return;
|
||||||
|
tile.remove();
|
||||||
|
|
||||||
|
int x = tile.tx, y = tile.ty;
|
||||||
|
for(int tx = 0; tx < objWidth; tx++){
|
||||||
|
for(int ty = 0; ty < objHeight; ty++){
|
||||||
|
grid.set(x + tx, y + ty, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearTiles(){
|
||||||
|
clearChildren();
|
||||||
|
grid.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getPrefWidth(){
|
||||||
|
return bounds * unitSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getPrefHeight(){
|
||||||
|
return bounds * unitSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ObjectiveTile extends Table{
|
||||||
|
public final MapObjective obj;
|
||||||
|
public int tx, ty;
|
||||||
|
|
||||||
|
public final Mover mover;
|
||||||
|
public final Connector conParent, conChildren;
|
||||||
|
|
||||||
|
public ObjectiveTile(MapObjective obj, int x, int y){
|
||||||
|
this.obj = obj;
|
||||||
|
setTransform(false);
|
||||||
|
setClip(false);
|
||||||
|
|
||||||
|
add(conParent = new Connector(true)).size(unitSize);
|
||||||
|
add(new ImageButton(Icon.move, new ImageButtonStyle(){{
|
||||||
|
up = Tex.whiteui;
|
||||||
|
imageUpColor = Color.black;
|
||||||
|
}})).color(Pal.accent).height(unitSize).growX().get().addCaptureListener(mover = new Mover());
|
||||||
|
add(conChildren = new Connector(false)).size(unitSize);
|
||||||
|
|
||||||
|
row().table(Tex.buttonSelectTrans, t -> {
|
||||||
|
t.labelWrap(obj.typeName()).grow()
|
||||||
|
.color(Pal.accent).align(Align.left).padLeft(6f)
|
||||||
|
.ellipsis(true).get().setAlignment(Align.left);
|
||||||
|
|
||||||
|
t.table(b -> {
|
||||||
|
b.right().defaults().size(32f).pad((unitSize - 32f) / 2f - 4f);
|
||||||
|
b.button(Icon.pencilSmall, () -> {
|
||||||
|
BaseDialog dialog = new BaseDialog("@editor.objectives");
|
||||||
|
dialog.cont.pane(Styles.noBarPane, list -> list.top().table(e -> {
|
||||||
|
e.margin(0f);
|
||||||
|
MapObjectivesDialog.getInterpreter((Class<MapObjective>)obj.getClass()).build(
|
||||||
|
e, obj.typeName(), new TypeInfo(obj.getClass()),
|
||||||
|
null, null, null,
|
||||||
|
() -> obj,
|
||||||
|
res -> {}
|
||||||
|
);
|
||||||
|
}).width(400f).fillY()).grow();
|
||||||
|
|
||||||
|
dialog.addCloseButton();
|
||||||
|
dialog.show();
|
||||||
|
});
|
||||||
|
b.button(Icon.trashSmall, () -> removeTile(this));
|
||||||
|
}).growY().fillX();
|
||||||
|
}).grow().colspan(3);
|
||||||
|
|
||||||
|
setSize(getPrefWidth(), getPrefHeight());
|
||||||
|
pos(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pos(int x, int y){
|
||||||
|
tx = obj.editorX = x;
|
||||||
|
ty = obj.editorY = y;
|
||||||
|
this.x = x * unitSize;
|
||||||
|
this.y = y * unitSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getPrefWidth(){
|
||||||
|
return objWidth * unitSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getPrefHeight(){
|
||||||
|
return objHeight * unitSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(){
|
||||||
|
if(super.remove()){
|
||||||
|
obj.parents.clear();
|
||||||
|
|
||||||
|
var it = objectives.iterator();
|
||||||
|
while(it.hasNext()){
|
||||||
|
var next = it.next();
|
||||||
|
if(next == obj){
|
||||||
|
it.remove();
|
||||||
|
}else{
|
||||||
|
next.parents.remove(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Mover extends InputListener{
|
||||||
|
public int prevX, prevY;
|
||||||
|
public float lastX, lastY;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(moving != null) return false;
|
||||||
|
moving = ObjectiveTile.this;
|
||||||
|
moving.toFront();
|
||||||
|
|
||||||
|
prevX = moving.tx;
|
||||||
|
prevY = moving.ty;
|
||||||
|
|
||||||
|
// Convert to world pos first because the button gets dragged too.
|
||||||
|
Vec2 pos = event.listenerActor.localToStageCoordinates(Tmp.v1.set(x, y));
|
||||||
|
lastX = pos.x;
|
||||||
|
lastY = pos.y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchDragged(InputEvent event, float x, float y, int pointer){
|
||||||
|
Vec2 pos = event.listenerActor.localToStageCoordinates(Tmp.v1.set(x, y));
|
||||||
|
|
||||||
|
moving.moveBy(pos.x - lastX, pos.y - lastY);
|
||||||
|
lastX = pos.x;
|
||||||
|
lastY = pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(!moveTile(moving,
|
||||||
|
Mathf.round(moving.x / unitSize),
|
||||||
|
Mathf.round(moving.y / unitSize)
|
||||||
|
)) moving.pos(prevX, prevY);
|
||||||
|
moving = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Connector extends Button{
|
||||||
|
public float pointX, pointY;
|
||||||
|
public final boolean findParent;
|
||||||
|
|
||||||
|
public Connector(boolean findParent){
|
||||||
|
super(new ButtonStyle(){{
|
||||||
|
down = findParent ? Tex.buttonEdgeDown1 : Tex.buttonEdgeDown3;
|
||||||
|
up = findParent ? Tex.buttonEdge1 : Tex.buttonEdge3;
|
||||||
|
over = findParent ? Tex.buttonEdgeOver1 : Tex.buttonEdgeOver3;
|
||||||
|
}});
|
||||||
|
|
||||||
|
this.findParent = findParent;
|
||||||
|
|
||||||
|
clearChildren();
|
||||||
|
addCaptureListener(new InputListener(){
|
||||||
|
int conPointer = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(conPointer != -1) return false;
|
||||||
|
conPointer = pointer;
|
||||||
|
|
||||||
|
if(connecting != null) return false;
|
||||||
|
connecting = Connector.this;
|
||||||
|
|
||||||
|
pointX = x;
|
||||||
|
pointY = y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchDragged(InputEvent event, float x, float y, int pointer){
|
||||||
|
if(conPointer != pointer) return;
|
||||||
|
pointX = x;
|
||||||
|
pointY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||||
|
if(conPointer != pointer || connecting != Connector.this) return;
|
||||||
|
conPointer = -1;
|
||||||
|
|
||||||
|
Vec2 pos = Connector.this.localToAscendantCoordinates(ObjectiveTilemap.this, Tmp.v1.set(x, y));
|
||||||
|
if(ObjectiveTilemap.this.hit(pos.x, pos.y, true) instanceof Connector con && con.canConnectTo(Connector.this)){
|
||||||
|
if(findParent){
|
||||||
|
if(!obj.parents.remove(con.tile().obj)) obj.parents.add(con.tile().obj);
|
||||||
|
}else{
|
||||||
|
if(!con.tile().obj.parents.remove(obj)) con.tile().obj.parents.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connecting = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canConnectTo(Connector other){
|
||||||
|
return
|
||||||
|
findParent != other.findParent &&
|
||||||
|
tile() != other.tile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectiveTile tile(){
|
||||||
|
return ObjectiveTile.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPressed(){
|
||||||
|
return super.isPressed() || connecting == this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOver(){
|
||||||
|
return super.isOver() && (connecting == null || connecting.canConnectTo(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -7,70 +7,311 @@ import arc.graphics.g2d.*;
|
|||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.scene.ui.layout.*;
|
import arc.scene.ui.layout.*;
|
||||||
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
|
import mindustry.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
|
import mindustry.game.MapObjectives.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
|
import mindustry.io.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.*;
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class MapObjectives{
|
/** Handles and executes in-map objectives. */
|
||||||
public static Prov<MapObjective>[] allObjectiveTypes = new Prov[]{
|
public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObjective>{
|
||||||
ResearchObjective::new, BuildCountObjective::new, UnitCountObjective::new, ItemObjective::new,
|
public static final Seq<Prov<? extends MapObjective>> allObjectiveTypes = new Seq<>();
|
||||||
CommandModeObjective::new, CoreItemObjective::new, DestroyCoreObjective::new, DestroyUnitsObjective::new,
|
public static final Seq<Prov<? extends ObjectiveMarker>> allMarkerTypes = new Seq<>();
|
||||||
TimerObjective::new, FlagObjective::new, DestroyBlockObjective::new, ProduceObjective::new,
|
|
||||||
DestroyBlocksObjective::new
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Prov<ObjectiveMarker>[] allMarkerTypes = new Prov[]{
|
/**
|
||||||
TextMarker::new, ShapeMarker::new, ShapeTextMarker::new, MinimapMarker::new
|
* All objectives the executor contains. Do not modify directly, ever!
|
||||||
};
|
* @see #eachRunning(Cons)
|
||||||
|
*/
|
||||||
|
public Seq<MapObjective> all = new Seq<>(4);
|
||||||
|
/** @see #checkChanged() */
|
||||||
|
protected transient boolean changed;
|
||||||
|
|
||||||
|
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,
|
||||||
|
MinimapMarker::new,
|
||||||
|
ShapeMarker::new,
|
||||||
|
TextMarker::new
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static void registerObjective(Prov<? extends MapObjective>... providers){
|
||||||
|
for(var prov : providers){
|
||||||
|
allObjectiveTypes.add(prov);
|
||||||
|
|
||||||
|
Class<? extends MapObjective> type = prov.get().getClass();
|
||||||
|
JsonIO.classTag(type.getSimpleName().replace("Objective", ""), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static void registerMarker(Prov<? extends ObjectiveMarker>... providers){
|
||||||
|
for(var prov : providers){
|
||||||
|
allMarkerTypes.add(prov);
|
||||||
|
|
||||||
|
Class<? extends ObjectiveMarker> type = prov.get().getClass();
|
||||||
|
JsonIO.classTag(type.getSimpleName().replace("Marker", ""), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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(){
|
||||||
|
//TODO am i doing this correctly
|
||||||
|
if(net.client()) return;
|
||||||
|
eachRunning(obj -> {
|
||||||
|
for(var marker : obj.markers){
|
||||||
|
if(!marker.wasAdded){
|
||||||
|
marker.wasAdded = true;
|
||||||
|
marker.added();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(obj.update()){
|
||||||
|
obj.completed = true;
|
||||||
|
obj.done();
|
||||||
|
for(var marker : obj.markers){
|
||||||
|
if(marker.wasAdded){
|
||||||
|
marker.removed();
|
||||||
|
marker.wasAdded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changed |= obj.changed;
|
||||||
|
obj.changed = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return True if map rules should be synced. Reserved for {@link Vars#logic}; do not invoke directly! */
|
||||||
|
public boolean checkChanged(){
|
||||||
|
boolean has = changed;
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
return has;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Whether there are any qualified objectives at all. */
|
||||||
|
public boolean any(){
|
||||||
|
return all.count(MapObjective::qualified) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 @Nullable String details;
|
||||||
|
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 = -1, editorY = -1;
|
||||||
|
|
||||||
|
/** Whether this objective has been done yet. This is internally set. */
|
||||||
|
private boolean completed;
|
||||||
|
/** Internal value. Do not modify! */
|
||||||
|
private transient boolean depFinished, changed;
|
||||||
|
|
||||||
|
/** @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(){
|
||||||
|
changed();
|
||||||
|
state.rules.objectiveFlags.removeAll(flagsRemoved);
|
||||||
|
state.rules.objectiveFlags.addAll(flagsAdded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Notifies the executor that map rules should be synced. */
|
||||||
|
protected void changed(){
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return True if all {@link #parents} are completed, rendering this objective able to execute. */
|
||||||
|
public final boolean dependencyFinished(){
|
||||||
|
if(depFinished) return true;
|
||||||
|
|
||||||
|
boolean f = true;
|
||||||
|
for(var parent : parents){
|
||||||
|
if(!parent.isCompleted()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return f && (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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Research a specific piece of content in the tech tree. */
|
/** Research a specific piece of content in the tech tree. */
|
||||||
public static class ResearchObjective extends MapObjective{
|
public static class ResearchObjective extends MapObjective{
|
||||||
public UnlockableContent content = Items.copper;
|
public @Researchable UnlockableContent content = Items.copper;
|
||||||
|
|
||||||
public ResearchObjective(UnlockableContent content){
|
public ResearchObjective(UnlockableContent content){
|
||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResearchObjective(){
|
public ResearchObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return content.unlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.research", content.emoji(), content.localizedName);
|
return Core.bundle.format("objective.research", content.emoji(), content.localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return content.unlocked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Produce a specific piece of content in the tech tree (essentially research with different text). */
|
/** Produce a specific piece of content in the tech tree (essentially research with different text). */
|
||||||
public static class ProduceObjective extends MapObjective{
|
public static class ProduceObjective extends MapObjective{
|
||||||
public UnlockableContent content = Items.copper;
|
public @Researchable UnlockableContent content = Items.copper;
|
||||||
|
|
||||||
public ProduceObjective(UnlockableContent content){
|
public ProduceObjective(UnlockableContent content){
|
||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProduceObjective(){
|
public ProduceObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return content.unlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.produce", content.emoji(), content.localizedName);
|
return Core.bundle.format("objective.produce", content.emoji(), content.localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return content.unlocked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Have a certain amount of item in your core. */
|
/** Have a certain amount of item in your core. */
|
||||||
@@ -83,18 +324,17 @@ public class MapObjectives{
|
|||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemObjective(){
|
public ItemObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.rules.defaultTeam.items().has(item, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.item", state.rules.defaultTeam.items().get(item), amount, item.emoji(), item.localizedName);
|
return Core.bundle.format("objective.item", state.rules.defaultTeam.items().get(item), amount, item.emoji(), item.localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.rules.defaultTeam.items().has(item, amount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a certain item in your core (through a block, not manually.) */
|
/** Get a certain item in your core (through a block, not manually.) */
|
||||||
@@ -107,23 +347,22 @@ public class MapObjectives{
|
|||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CoreItemObjective(){
|
public CoreItemObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.stats.coreItemCount.get(item) >= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.coreitem", state.stats.coreItemCount.get(item), amount, item.emoji(), item.localizedName);
|
return Core.bundle.format("objective.coreitem", state.stats.coreItemCount.get(item), amount, item.emoji(), item.localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.stats.coreItemCount.get(item) >= amount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Build a certain amount of a block. */
|
/** Build a certain amount of a block. */
|
||||||
public static class BuildCountObjective extends MapObjective{
|
public static class BuildCountObjective extends MapObjective{
|
||||||
public Block block = Blocks.conveyor;
|
public @Synthetic Block block = Blocks.conveyor;
|
||||||
public int count = 1;
|
public int count = 1;
|
||||||
|
|
||||||
public BuildCountObjective(Block block, int count){
|
public BuildCountObjective(Block block, int count){
|
||||||
@@ -131,18 +370,17 @@ public class MapObjectives{
|
|||||||
this.count = count;
|
this.count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuildCountObjective(){
|
public BuildCountObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.stats.placedBlockCount.get(block, 0) >= count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.build", count, block.emoji(), block.localizedName);
|
return Core.bundle.format("objective.build", count, block.emoji(), block.localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.stats.placedBlockCount.get(block, 0) >= count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Produce a certain amount of a unit. */
|
/** Produce a certain amount of a unit. */
|
||||||
@@ -155,18 +393,17 @@ public class MapObjectives{
|
|||||||
this.count = count;
|
this.count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnitCountObjective(){
|
public UnitCountObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.rules.defaultTeam.data().countType(unit) >= count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.buildunit", count, unit.emoji(), unit.localizedName);
|
return Core.bundle.format("objective.buildunit", count, unit.emoji(), unit.localizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.rules.defaultTeam.data().countType(unit) >= count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Produce a certain amount of units. */
|
/** Produce a certain amount of units. */
|
||||||
@@ -177,24 +414,24 @@ public class MapObjectives{
|
|||||||
this.count = count;
|
this.count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DestroyUnitsObjective(){
|
public DestroyUnitsObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.stats.enemyUnitsDestroyed >= count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.format("objective.destroyunits", count);
|
return Core.bundle.format("objective.destroyunits", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.stats.enemyUnitsDestroyed >= count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TimerObjective extends MapObjective{
|
public static class TimerObjective extends MapObjective{
|
||||||
public String text;
|
public String text;
|
||||||
public float countup;
|
public @Second float duration = 60f * 30f;
|
||||||
public float duration = 60f * 30f;
|
|
||||||
|
protected float countup;
|
||||||
|
|
||||||
public TimerObjective(String text, float duration){
|
public TimerObjective(String text, float duration){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
@@ -205,13 +442,8 @@ public class MapObjectives{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean complete(){
|
public boolean update(){
|
||||||
return countup >= duration;
|
return (countup += Time.delta) >= duration;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(){
|
|
||||||
countup += Time.delta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -243,28 +475,27 @@ public class MapObjectives{
|
|||||||
return Core.bundle.formatString(text, timeString.toString());
|
return Core.bundle.formatString(text, timeString.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text;
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DestroyBlockObjective extends MapObjective{
|
public static class DestroyBlockObjective extends MapObjective{
|
||||||
public int x, y;
|
public Point2 pos = new Point2();
|
||||||
public Team team = Team.crux;
|
public Team team = Team.crux;
|
||||||
public Block block = Blocks.router;
|
public @Synthetic Block block = Blocks.router;
|
||||||
|
|
||||||
public DestroyBlockObjective(Block block, int x, int y, Team team){
|
public DestroyBlockObjective(Block block, int x, int y, Team team){
|
||||||
this.block = block;
|
this.block = block;
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.team = team;
|
this.team = team;
|
||||||
|
this.pos.set(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DestroyBlockObjective(){
|
public DestroyBlockObjective(){}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean complete(){
|
public boolean update(){
|
||||||
var build = world.build(x, y);
|
var build = world.build(pos.x, pos.y);
|
||||||
return build == null || build.team != team || build.block != block;
|
return build == null || build.team != team || build.block != block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,25 +506,22 @@ public class MapObjectives{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DestroyBlocksObjective extends MapObjective{
|
public static class DestroyBlocksObjective extends MapObjective{
|
||||||
public int[] positions = {};
|
public @Unordered Point2[] positions = {};
|
||||||
public Team team = Team.crux;
|
public Team team = Team.crux;
|
||||||
public Block block = Blocks.router;
|
public @Synthetic Block block = Blocks.router;
|
||||||
|
|
||||||
public DestroyBlocksObjective(Block block, Team team, int... positions){
|
public DestroyBlocksObjective(Block block, Team team, Point2... positions){
|
||||||
this.block = block;
|
this.block = block;
|
||||||
this.team = team;
|
this.team = team;
|
||||||
this.positions = positions;
|
this.positions = positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DestroyBlocksObjective(){
|
public DestroyBlocksObjective(){}
|
||||||
}
|
|
||||||
|
|
||||||
public int progress(){
|
public int progress(){
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for(var pos : positions){
|
for(var pos : positions){
|
||||||
int x = Point2.x(pos), y = Point2.y(pos);
|
var build = world.build(pos.x, pos.y);
|
||||||
|
|
||||||
var build = world.build(x, y);
|
|
||||||
if(build == null || build.team != team || build.block != block){
|
if(build == null || build.team != team || build.block != block){
|
||||||
count ++;
|
count ++;
|
||||||
}
|
}
|
||||||
@@ -302,7 +530,7 @@ public class MapObjectives{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean complete(){
|
public boolean update(){
|
||||||
return progress() >= positions.length;
|
return progress() >= positions.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,16 +542,15 @@ public class MapObjectives{
|
|||||||
|
|
||||||
/** Command any unit to do anything. Always compete in headless mode. */
|
/** Command any unit to do anything. Always compete in headless mode. */
|
||||||
public static class CommandModeObjective extends MapObjective{
|
public static class CommandModeObjective extends MapObjective{
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return headless || control.input.selectedUnits.contains(u -> u.isCommandable() && u.command().hasCommand());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.get("objective.command");
|
return Core.bundle.get("objective.command");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return headless || control.input.selectedUnits.contains(u -> u.isCommandable() && u.command().hasCommand());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wait until a logic flag is set. */
|
/** Wait until a logic flag is set. */
|
||||||
@@ -335,189 +562,138 @@ public class MapObjectives{
|
|||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FlagObjective(){
|
public FlagObjective(){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.rules.objectiveFlags.contains(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return text != null && text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
|
return text != null && text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.rules.objectiveFlags.contains(flag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Destroy all enemy core(s). */
|
/** Destroy all enemy core(s). */
|
||||||
public static class DestroyCoreObjective extends MapObjective{
|
public static class DestroyCoreObjective extends MapObjective{
|
||||||
|
@Override
|
||||||
|
public boolean update(){
|
||||||
|
return state.rules.waveTeam.cores().size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String text(){
|
public String text(){
|
||||||
return Core.bundle.get("objective.destroycore");
|
return Core.bundle.get("objective.destroycore");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean complete(){
|
|
||||||
return state.rules.waveTeam.cores().size == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Base abstract class for any in-map objective. */
|
/** Marker used for drawing UI to indicate something along with an objective. */
|
||||||
public static abstract class MapObjective{
|
public static abstract class ObjectiveMarker{
|
||||||
public @Nullable String details;
|
/** Makes sure markers are only added once. */
|
||||||
public String[] flagsAdded = {};
|
private transient boolean wasAdded;
|
||||||
public String[] flagsRemoved = {};
|
|
||||||
public ObjectiveMarker[] markers = {};
|
|
||||||
|
|
||||||
//TODO localize
|
/** Called in the overlay draw layer.*/
|
||||||
|
public void draw(){}
|
||||||
|
/** Called in the small and large map. */
|
||||||
|
public void drawMinimap(MinimapRenderer minimap){}
|
||||||
|
/** Add any UI elements necessary. */
|
||||||
|
public void added(){}
|
||||||
|
/** Remove any UI elements, if necessary. */
|
||||||
|
public void removed(){}
|
||||||
|
|
||||||
|
/** @return The localized type-name of this objective, defaulting to the class simple name without the "Marker" prefix. */
|
||||||
public String typeName(){
|
public String typeName(){
|
||||||
return getClass().getSimpleName().replace("Objective", "");
|
String className = getClass().getSimpleName().replace("Marker", "");
|
||||||
}
|
return Core.bundle == null ? className : Core.bundle.get("marker." + className.toLowerCase() + ".name", className);
|
||||||
|
|
||||||
public MapObjective withFlags(String... flags){
|
|
||||||
this.flagsAdded = flags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MapObjective withFlagsRemoved(String... flags){
|
|
||||||
this.flagsRemoved = flags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MapObjective withMarkers(ObjectiveMarker... markers){
|
|
||||||
this.markers = markers;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MapObjective withDetails(String details){
|
|
||||||
this.details = details;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean complete(){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called immediately after this objective is completed and removed from the rules. */
|
|
||||||
public void completed(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reset internal state, if any. */
|
|
||||||
public void reset(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Basic mission display text. If null, falls back to standard text. */
|
|
||||||
public @Nullable String text(){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Details that appear upon click. */
|
|
||||||
public @Nullable String details(){
|
|
||||||
return details;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays text above a shape. */
|
/** Displays text above a shape. */
|
||||||
public static class ShapeTextMarker extends ObjectiveMarker{
|
public static class ShapeTextMarker extends ObjectiveMarker{
|
||||||
public String text = "frog";
|
public String text = "frog";
|
||||||
public float x, y, fontSize = 1f, textHeight = 7f;
|
public @TilePos Vec2 pos = new Vec2();
|
||||||
public byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
public float fontSize = 1f, textHeight = 7f;
|
||||||
|
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||||
|
|
||||||
public float radius = 6f, rotation = 0f;
|
public float radius = 6f, rotation = 0f;
|
||||||
public int sides = 4;
|
public int sides = 4;
|
||||||
public Color color = Color.valueOf("ffd37f");
|
public Color color = Color.valueOf("ffd37f");
|
||||||
|
|
||||||
//cached localized text
|
// Cached localized text.
|
||||||
private transient String fetchedText;
|
private transient String fetchedText;
|
||||||
|
|
||||||
public ShapeTextMarker(String text, float x, float y){
|
public ShapeTextMarker(String text, float x, float y){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeTextMarker(String text, float x, float y, float radius){
|
public ShapeTextMarker(String text, float x, float y, float radius){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeTextMarker(String text, float x, float y, float radius, float rotation){
|
public ShapeTextMarker(String text, float x, float y, float radius, float rotation){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeTextMarker(String text, float x, float y, float radius, float rotation, float textHeight){
|
public ShapeTextMarker(String text, float x, float y, float radius, float rotation, float textHeight){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
this.textHeight = textHeight;
|
this.textHeight = textHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeTextMarker(){
|
public ShapeTextMarker(){}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(){
|
public void draw(){
|
||||||
Lines.stroke(3f, Pal.gray);
|
Lines.stroke(3f, Pal.gray);
|
||||||
Lines.poly(x, y, sides, radius + 1f, rotation);
|
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
|
||||||
Lines.stroke(1f, color);
|
Lines.stroke(1f, color);
|
||||||
Lines.poly(x, y, sides, radius + 1f, rotation);
|
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
|
||||||
Draw.reset();
|
Draw.reset();
|
||||||
|
|
||||||
if(fetchedText == null){
|
if(fetchedText == null){
|
||||||
fetchedText = text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
|
fetchedText = text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldLabel.drawAt(text, x, y + radius + textHeight, Draw.z(), flags, fontSize);
|
WorldLabel.drawAt(text, pos.x, pos.y + radius + textHeight, Draw.z(), flags, fontSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays a circle on the minimap. */
|
/** Displays a circle on the minimap. */
|
||||||
public static class MinimapMarker extends ObjectiveMarker{
|
public static class MinimapMarker extends ObjectiveMarker{
|
||||||
public int x, y;
|
public Point2 pos = new Point2();
|
||||||
public float radius = 5f, stroke = 11f;
|
public float radius = 5f, stroke = 11f;
|
||||||
public Color color = Color.valueOf("f25555");
|
public Color color = Color.valueOf("f25555");
|
||||||
|
|
||||||
public MinimapMarker(int x, int y){
|
public MinimapMarker(int x, int y){
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinimapMarker(int x, int y, Color color){
|
public MinimapMarker(int x, int y, Color color){
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinimapMarker(int x, int y, float radius, float stroke, Color color){
|
public MinimapMarker(int x, int y, float radius, float stroke, Color color){
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
this.stroke = stroke;
|
this.stroke = stroke;
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinimapMarker(){
|
public MinimapMarker(){}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawMinimap(MinimapRenderer minimap){
|
public void drawMinimap(MinimapRenderer minimap){
|
||||||
minimap.transform(Tmp.v1.set(x * tilesize, y * tilesize));
|
minimap.transform(Tmp.v1.set(pos.x * tilesize, pos.y * tilesize));
|
||||||
|
|
||||||
float rad = minimap.scale(radius * tilesize);
|
float rad = minimap.scale(radius * tilesize);
|
||||||
float fin = Interp.pow2Out.apply((Time.globalTime / 100f) % 1f);
|
float fin = Interp.pow2Out.apply((Time.globalTime / 100f) % 1f);
|
||||||
@@ -530,25 +706,23 @@ public class MapObjectives{
|
|||||||
|
|
||||||
/** Displays a shape with an outline and color. */
|
/** Displays a shape with an outline and color. */
|
||||||
public static class ShapeMarker extends ObjectiveMarker{
|
public static class ShapeMarker extends ObjectiveMarker{
|
||||||
public float x, y, radius = 8f, rotation = 0f, stroke = 1f;
|
public @TilePos Vec2 pos = new Vec2();
|
||||||
|
public float radius = 8f, rotation = 0f, stroke = 1f;
|
||||||
public boolean fill = false, outline = true;
|
public boolean fill = false, outline = true;
|
||||||
public int sides = 4;
|
public int sides = 4;
|
||||||
public Color color = Color.valueOf("ffd37f");
|
public Color color = Color.valueOf("ffd37f");
|
||||||
|
|
||||||
public ShapeMarker(float x, float y){
|
public ShapeMarker(float x, float y){
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeMarker(float x, float y, float radius, float rotation){
|
public ShapeMarker(float x, float y, float radius, float rotation){
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
this.radius = radius;
|
this.radius = radius;
|
||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapeMarker(){
|
public ShapeMarker(){}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(){
|
public void draw(){
|
||||||
@@ -558,14 +732,14 @@ public class MapObjectives{
|
|||||||
if(!fill){
|
if(!fill){
|
||||||
if(outline){
|
if(outline){
|
||||||
Lines.stroke(stroke + 2f, Pal.gray);
|
Lines.stroke(stroke + 2f, Pal.gray);
|
||||||
Lines.poly(x, y, sides, radius + 1f, rotation);
|
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lines.stroke(stroke, color);
|
Lines.stroke(stroke, color);
|
||||||
Lines.poly(x, y, sides, radius + 1f, rotation);
|
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
|
||||||
}else{
|
}else{
|
||||||
Draw.color(color);
|
Draw.color(color);
|
||||||
Fill.poly(x, y, sides, radius);
|
Fill.poly(pos.x, pos.y, sides, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
Draw.reset();
|
Draw.reset();
|
||||||
@@ -575,27 +749,25 @@ public class MapObjectives{
|
|||||||
/** Displays text at a location. */
|
/** Displays text at a location. */
|
||||||
public static class TextMarker extends ObjectiveMarker{
|
public static class TextMarker extends ObjectiveMarker{
|
||||||
public String text = "uwu";
|
public String text = "uwu";
|
||||||
public float x, y, fontSize = 1f;
|
public @TilePos Vec2 pos = new Vec2();
|
||||||
public byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
public float fontSize = 1f;
|
||||||
//cached localized text
|
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||||
|
// Cached localized text.
|
||||||
private transient String fetchedText;
|
private transient String fetchedText;
|
||||||
|
|
||||||
public TextMarker(String text, float x, float y, float fontSize, byte flags){
|
public TextMarker(String text, float x, float y, float fontSize, byte flags){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.fontSize = fontSize;
|
this.fontSize = fontSize;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
this.pos.set(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextMarker(String text, float x, float y){
|
public TextMarker(String text, float x, float y){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
this.pos.set(x, y);
|
||||||
this.y = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextMarker(){
|
public TextMarker(){}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(){
|
public void draw(){
|
||||||
@@ -603,27 +775,37 @@ public class MapObjectives{
|
|||||||
fetchedText = text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
|
fetchedText = text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldLabel.drawAt(fetchedText, x, y, Draw.z(), flags, fontSize);
|
WorldLabel.drawAt(fetchedText, pos.x, pos.y, Draw.z(), flags, fontSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Marker used for drawing UI to indicate something along with an objective. */
|
/** For arrays or {@link Seq}s; does not create element rearrangement buttons. */
|
||||||
public static abstract class ObjectiveMarker{
|
@Target(FIELD)
|
||||||
/** makes sure markers are only added once */
|
@Retention(RUNTIME)
|
||||||
public transient boolean wasAdded;
|
public @interface Unordered{}
|
||||||
|
|
||||||
//TODO localize
|
/** For {@code byte}; treats it as a world label flag. */
|
||||||
public String typeName(){
|
@Target(FIELD)
|
||||||
return getClass().getSimpleName().replace("Marker", "");
|
@Retention(RUNTIME)
|
||||||
}
|
public @interface LabelFlag{}
|
||||||
|
|
||||||
/** Called in the overlay draw layer.*/
|
/** For {@link UnlockableContent}; filters all un-researchable content. */
|
||||||
public void draw(){}
|
@Target(FIELD)
|
||||||
/** Called in the small & large map. */
|
@Retention(RUNTIME)
|
||||||
public void drawMinimap(MinimapRenderer minimap){}
|
public @interface Researchable{}
|
||||||
/** Add any UI elements necessary. */
|
|
||||||
public void added(){}
|
/** For {@link Block}; filters all un-buildable blocks. */
|
||||||
/** Remove any UI elements, if necessary. */
|
@Target(FIELD)
|
||||||
public void removed(){}
|
@Retention(RUNTIME)
|
||||||
}
|
public @interface Synthetic{}
|
||||||
|
|
||||||
|
/** 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{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import arc.util.serialization.*;
|
|||||||
import arc.util.serialization.Json.*;
|
import arc.util.serialization.Json.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.game.MapObjectives.*;
|
|
||||||
import mindustry.graphics.g3d.*;
|
import mindustry.graphics.g3d.*;
|
||||||
import mindustry.io.*;
|
import mindustry.io.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
@@ -124,9 +123,9 @@ public class Rules{
|
|||||||
public ObjectSet<String> researched = new ObjectSet<>();
|
public ObjectSet<String> researched = new ObjectSet<>();
|
||||||
/** Block containing these items as requirements are hidden. */
|
/** Block containing these items as requirements are hidden. */
|
||||||
public ObjectSet<Item> hiddenBuildItems = Items.erekirOnlyItems.asSet();
|
public ObjectSet<Item> hiddenBuildItems = Items.erekirOnlyItems.asSet();
|
||||||
/** Campaign-only map objectives. */
|
/** In-map objective executor. */
|
||||||
public Seq<MapObjective> objectives = new Seq<>();
|
public MapObjectives objectives = new MapObjectives();
|
||||||
/** Flags set by objectives. Used in world processors. n*/
|
/** Flags set by objectives. Used in world processors. */
|
||||||
public ObjectSet<String> objectiveFlags = new ObjectSet<>();
|
public ObjectSet<String> objectiveFlags = new ObjectSet<>();
|
||||||
/** If true, fog of war is enabled. Enemy units and buildings are hidden unless in radar view. */
|
/** If true, fog of war is enabled. Enemy units and buildings are hidden unless in radar view. */
|
||||||
public boolean fog = false;
|
public boolean fog = false;
|
||||||
|
|||||||
@@ -190,12 +190,9 @@ public class MinimapRenderer{
|
|||||||
drawSpawns(x, y, w, h, scaling);
|
drawSpawns(x, y, w, h, scaling);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(state.rules.objectives.size > 0){
|
state.rules.objectives.eachRunning(obj -> {
|
||||||
var first = state.rules.objectives.first();
|
for(var marker : obj.markers) marker.drawMinimap(this);
|
||||||
for(var marker : first.markers){
|
});
|
||||||
marker.drawMinimap(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawSpawns(float x, float y, float w, float h, float scaling){
|
public void drawSpawns(float x, float y, float w, float h, float scaling){
|
||||||
|
|||||||
@@ -113,13 +113,10 @@ public class OverlayRenderer{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//draw objective markers, if any
|
//draw objective markers
|
||||||
if(state.rules.objectives.size > 0){
|
state.rules.objectives.eachRunning(obj -> {
|
||||||
var first = state.rules.objectives.first();
|
for(var marker : obj.markers) marker.draw();
|
||||||
for(var marker : first.markers){
|
});
|
||||||
marker.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(player.dead()) return; //dead players don't draw
|
if(player.dead()) return; //dead players don't draw
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package mindustry.io;
|
package mindustry.io;
|
||||||
|
|
||||||
import arc.graphics.*;
|
import arc.graphics.*;
|
||||||
|
import arc.math.geom.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import arc.util.serialization.*;
|
import arc.util.serialization.*;
|
||||||
import arc.util.serialization.Json.*;
|
import arc.util.serialization.Json.*;
|
||||||
@@ -8,6 +9,7 @@ import mindustry.*;
|
|||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
|
import mindustry.game.MapObjectives.*;
|
||||||
import mindustry.maps.*;
|
import mindustry.maps.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
@@ -24,9 +26,9 @@ public class JsonIO{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeValue(Object value, Class knownType, Class elementType){
|
public void writeValue(Object value, Class knownType, Class elementType){
|
||||||
if(value instanceof MappableContent){
|
if(value instanceof MappableContent c){
|
||||||
try{
|
try{
|
||||||
getWriter().value(((MappableContent)value).name);
|
getWriter().value(c.name);
|
||||||
}catch(IOException e){
|
}catch(IOException e){
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -37,9 +39,7 @@ public class JsonIO{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String convertToString(Object object){
|
protected String convertToString(Object object){
|
||||||
if(object instanceof MappableContent){
|
if(object instanceof MappableContent c) return c.name;
|
||||||
return ((MappableContent)object).name;
|
|
||||||
}
|
|
||||||
return super.convertToString(object);
|
return super.convertToString(object);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -69,6 +69,11 @@ public class JsonIO{
|
|||||||
return json.prettyPrint(in);
|
return json.prettyPrint(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void classTag(String tag, Class<?> type){
|
||||||
|
json.addClassTag(tag, type);
|
||||||
|
jsonBase.addClassTag(tag, type);
|
||||||
|
}
|
||||||
|
|
||||||
static void apply(Json json){
|
static void apply(Json json){
|
||||||
json.setElementType(Rules.class, "spawns", SpawnGroup.class);
|
json.setElementType(Rules.class, "spawns", SpawnGroup.class);
|
||||||
json.setElementType(Rules.class, "loadout", ItemStack.class);
|
json.setElementType(Rules.class, "loadout", ItemStack.class);
|
||||||
@@ -255,23 +260,61 @@ public class JsonIO{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
json.setSerializer(MapObjectives.class, new Serializer<>(){
|
||||||
|
@Override
|
||||||
|
public void write(Json json, MapObjectives exec, Class knownType){
|
||||||
|
json.writeArrayStart();
|
||||||
|
for(var obj : exec){
|
||||||
|
json.writeObjectStart(obj.getClass().isAnonymousClass() ? obj.getClass().getSuperclass() : obj.getClass(), null);
|
||||||
|
json.writeFields(obj);
|
||||||
|
|
||||||
|
json.writeArrayStart("parents");
|
||||||
|
for(var parent : obj.parents){
|
||||||
|
json.writeValue(exec.all.indexOf(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
json.writeArrayEnd();
|
||||||
|
|
||||||
|
json.writeValue("editorPos", Point2.pack(obj.editorX, obj.editorY));
|
||||||
|
json.writeObjectEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
json.writeArrayEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapObjectives read(Json json, JsonValue data, Class type){
|
||||||
|
var exec = new MapObjectives();
|
||||||
|
// First iteration to instantiate the objectives.
|
||||||
|
for(var value = data.child; value != null; value = value.next){
|
||||||
|
MapObjective obj = json.readValue(MapObjective.class, value);
|
||||||
|
|
||||||
|
int pos = value.getInt("editorPos");
|
||||||
|
obj.editorX = Point2.x(pos);
|
||||||
|
obj.editorY = Point2.y(pos);
|
||||||
|
|
||||||
|
exec.all.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second iteration to map the parents.
|
||||||
|
int i = 0;
|
||||||
|
for(var value = data.child; value != null; value = value.next, i++){
|
||||||
|
for(var parent = value.get("parents").child; parent != null; parent = parent.next){
|
||||||
|
exec.all.get(i).parents.add(exec.all.get(parent.asInt()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//use short names for all filter types
|
//use short names for all filter types
|
||||||
for(var filter : Maps.allFilterTypes){
|
for(var filter : Maps.allFilterTypes){
|
||||||
var i = filter.get();
|
var i = filter.get();
|
||||||
json.addClassTag(Strings.camelize(i.getClass().getSimpleName().replace("Filter", "")), i.getClass());
|
json.addClassTag(Strings.camelize(i.getClass().getSimpleName().replace("Filter", "")), i.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
//use short names for all objective types
|
|
||||||
for(var obj : MapObjectives.allObjectiveTypes){
|
|
||||||
var i = obj.get();
|
|
||||||
json.addClassTag(Strings.camelize(i.getClass().getSimpleName().replace("Objective", "")), i.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
//use short names for all marker types
|
|
||||||
for(var obj : MapObjectives.allMarkerTypes){
|
|
||||||
var i = obj.get();
|
|
||||||
json.addClassTag(Strings.camelize(i.getClass().getSimpleName().replace("Marker", "")), i.getClass());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class CustomJson extends Json{
|
static class CustomJson extends Json{
|
||||||
|
|||||||
@@ -546,6 +546,19 @@ public class TypeIO{
|
|||||||
return JsonIO.read(Rules.class, string);
|
return JsonIO.read(Rules.class, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeObjectives(Writes write, MapObjectives executor){
|
||||||
|
String string = JsonIO.write(executor);
|
||||||
|
byte[] bytes = string.getBytes(charset);
|
||||||
|
write.i(bytes.length);
|
||||||
|
write.b(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MapObjectives readObjectives(Reads read){
|
||||||
|
int length = read.i();
|
||||||
|
String string = new String(read.b(new byte[length]), charset);
|
||||||
|
return JsonIO.read(MapObjectives.class, string);
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeVecNullable(Writes write, @Nullable Vec2 v){
|
public static void writeVecNullable(Writes write, @Nullable Vec2 v){
|
||||||
if(v == null){
|
if(v == null){
|
||||||
write.f(Float.NaN);
|
write.f(Float.NaN);
|
||||||
|
|||||||
@@ -775,13 +775,21 @@ public class HudFragment{
|
|||||||
builder.setLength(0);
|
builder.setLength(0);
|
||||||
|
|
||||||
//objectives override mission?
|
//objectives override mission?
|
||||||
if(state.rules.objectives.size > 0){
|
if(state.rules.objectives.any()){
|
||||||
var first = state.rules.objectives.first();
|
boolean first = true;
|
||||||
String text = first.text();
|
for(var obj : state.rules.objectives){
|
||||||
if(text != null){
|
if(!obj.qualified()) continue;
|
||||||
builder.append(text);
|
|
||||||
return builder;
|
String text = obj.text();
|
||||||
|
if(text != null){
|
||||||
|
if(!first) builder.append('\n');
|
||||||
|
builder.append(text);
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
//mission overrides everything
|
//mission overrides everything
|
||||||
@@ -832,13 +840,24 @@ public class HudFragment{
|
|||||||
table.row();
|
table.row();
|
||||||
|
|
||||||
table.clicked(() -> {
|
table.clicked(() -> {
|
||||||
if(state.rules.objectives.size > 0){
|
if(state.rules.objectives.any()){
|
||||||
var first = state.rules.objectives.first();
|
StringBuilder text = new StringBuilder();
|
||||||
var details = first.details();
|
|
||||||
if(details != null){
|
boolean first = true;
|
||||||
//TODO this could be much better.
|
for(var obj : state.rules.objectives){
|
||||||
ui.showInfo(details);
|
if(!obj.qualified()) continue;
|
||||||
|
|
||||||
|
String details = obj.details();
|
||||||
|
if(details != null){
|
||||||
|
if(!first) text.append('\n');
|
||||||
|
text.append(details);
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO this, as said before, could be much better.
|
||||||
|
ui.showInfo(text.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user