mirror of
https://github.com/Anuken/Mindustry.git
synced 2024-09-11 08:15:35 +03:00
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:
parent
3b1c8baca9
commit
695c19d0b0
@ -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
|
||||
jar{
|
||||
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
|
||||
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.produce = [accent]Obtain:\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.command = [accent]Command Units
|
||||
objective.nuclearlaunch = [accent]\u26A0 Nuclear launch detected: [lightgray]{0}
|
||||
|
||||
announce.nuclearstrike = [red]\u26A0 NUCLEAR STRIKE INBOUND \u26A0
|
||||
|
||||
loadout = Loadout
|
||||
|
@ -146,3 +146,4 @@ Alex25820
|
||||
KayAyeAre
|
||||
SMOLKEYS
|
||||
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;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.game.MapObjectives.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.MapObjectives.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
@ -115,102 +115,18 @@ public class SectorPresets{
|
||||
addStartingItems = true;
|
||||
alwaysUnlocked = true;
|
||||
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){{
|
||||
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){{
|
||||
difficulty = 4;
|
||||
|
||||
rules = r -> {
|
||||
r.objectives.addAll(
|
||||
new BuildCountObjective(Blocks.shipFabricator, 1),
|
||||
new UnitCountObjective(UnitTypes.elude, 1)
|
||||
);
|
||||
};
|
||||
}};
|
||||
|
||||
three = new SectorPreset("three", erekir, 36){{
|
||||
difficulty = 5;
|
||||
|
||||
captureWave = 9;
|
||||
}};
|
||||
|
||||
@ -220,47 +136,10 @@ public class SectorPresets{
|
||||
|
||||
split = new SectorPreset("split", erekir, 19){{ //TODO random sector, pick a better one
|
||||
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){{
|
||||
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
|
||||
|
@ -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)
|
||||
public static void sectorCapture(){
|
||||
//the sector has been conquered - waves get disabled
|
||||
@ -520,7 +474,10 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
//TODO objectives clientside???
|
||||
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){
|
||||
|
@ -329,6 +329,11 @@ public class NetClient implements ApplicationListener{
|
||||
state.rules = rules;
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void setObjectives(MapObjectives executor){
|
||||
state.rules.objectives = executor;
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void worldDataBegin(){
|
||||
Groups.clear();
|
||||
|
@ -77,7 +77,7 @@ public class MapInfoDialog extends BaseDialog{
|
||||
r.row();
|
||||
|
||||
r.button("@editor.objectives", Icon.info, style, () -> {
|
||||
objectives.show(state.rules.objectives);
|
||||
objectives.show(state.rules.objectives.all, state.rules.objectives.all::set);
|
||||
hide();
|
||||
}).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.geom.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.MapObjectives.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.type.*;
|
||||
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.*;
|
||||
|
||||
public class MapObjectives{
|
||||
public static Prov<MapObjective>[] allObjectiveTypes = new Prov[]{
|
||||
ResearchObjective::new, BuildCountObjective::new, UnitCountObjective::new, ItemObjective::new,
|
||||
CommandModeObjective::new, CoreItemObjective::new, DestroyCoreObjective::new, DestroyUnitsObjective::new,
|
||||
TimerObjective::new, FlagObjective::new, DestroyBlockObjective::new, ProduceObjective::new,
|
||||
DestroyBlocksObjective::new
|
||||
};
|
||||
/** 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 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. */
|
||||
public static class ResearchObjective extends MapObjective{
|
||||
public UnlockableContent content = Items.copper;
|
||||
public @Researchable UnlockableContent content = Items.copper;
|
||||
|
||||
public ResearchObjective(UnlockableContent content){
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public ResearchObjective(){
|
||||
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 boolean complete(){
|
||||
return content.unlocked();
|
||||
}
|
||||
}
|
||||
|
||||
/** Produce a specific piece of content in the tech tree (essentially research with different text). */
|
||||
public static class ProduceObjective extends MapObjective{
|
||||
public UnlockableContent content = Items.copper;
|
||||
public @Researchable UnlockableContent content = Items.copper;
|
||||
|
||||
public ProduceObjective(UnlockableContent content){
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public ProduceObjective(){
|
||||
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 boolean complete(){
|
||||
return content.unlocked();
|
||||
}
|
||||
}
|
||||
|
||||
/** Have a certain amount of item in your core. */
|
||||
@ -83,18 +324,17 @@ public class MapObjectives{
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public ItemObjective(){
|
||||
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 boolean complete(){
|
||||
return state.rules.defaultTeam.items().has(item, amount);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a certain item in your core (through a block, not manually.) */
|
||||
@ -107,23 +347,22 @@ public class MapObjectives{
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public CoreItemObjective(){
|
||||
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 boolean complete(){
|
||||
return state.stats.coreItemCount.get(item) >= amount;
|
||||
}
|
||||
}
|
||||
|
||||
/** Build a certain amount of a block. */
|
||||
public static class BuildCountObjective extends MapObjective{
|
||||
public Block block = Blocks.conveyor;
|
||||
public @Synthetic Block block = Blocks.conveyor;
|
||||
public int count = 1;
|
||||
|
||||
public BuildCountObjective(Block block, int count){
|
||||
@ -131,18 +370,17 @@ public class MapObjectives{
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public BuildCountObjective(){
|
||||
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, block.emoji(), block.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return state.stats.placedBlockCount.get(block, 0) >= count;
|
||||
}
|
||||
}
|
||||
|
||||
/** Produce a certain amount of a unit. */
|
||||
@ -155,18 +393,17 @@ public class MapObjectives{
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public UnitCountObjective(){
|
||||
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, unit.emoji(), unit.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return state.rules.defaultTeam.data().countType(unit) >= count;
|
||||
}
|
||||
}
|
||||
|
||||
/** Produce a certain amount of units. */
|
||||
@ -177,24 +414,24 @@ public class MapObjectives{
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public DestroyUnitsObjective(){
|
||||
public DestroyUnitsObjective(){}
|
||||
|
||||
@Override
|
||||
public boolean update(){
|
||||
return state.stats.enemyUnitsDestroyed >= count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.destroyunits", count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return state.stats.enemyUnitsDestroyed >= count;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TimerObjective extends MapObjective{
|
||||
public String text;
|
||||
public float countup;
|
||||
public float duration = 60f * 30f;
|
||||
public @Second float duration = 60f * 30f;
|
||||
|
||||
protected float countup;
|
||||
|
||||
public TimerObjective(String text, float duration){
|
||||
this.text = text;
|
||||
@ -205,13 +442,8 @@ public class MapObjectives{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return countup >= duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
countup += Time.delta;
|
||||
public boolean update(){
|
||||
return (countup += Time.delta) >= duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -243,28 +475,27 @@ public class MapObjectives{
|
||||
return Core.bundle.formatString(text, timeString.toString());
|
||||
}
|
||||
}
|
||||
return text;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DestroyBlockObjective extends MapObjective{
|
||||
public int x, y;
|
||||
public Point2 pos = new Point2();
|
||||
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){
|
||||
this.block = block;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.team = team;
|
||||
this.pos.set(x, y);
|
||||
}
|
||||
|
||||
public DestroyBlockObjective(){
|
||||
}
|
||||
public DestroyBlockObjective(){}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
var build = world.build(x, y);
|
||||
public boolean update(){
|
||||
var build = world.build(pos.x, pos.y);
|
||||
return build == null || build.team != team || build.block != block;
|
||||
}
|
||||
|
||||
@ -275,25 +506,22 @@ public class MapObjectives{
|
||||
}
|
||||
|
||||
public static class DestroyBlocksObjective extends MapObjective{
|
||||
public int[] positions = {};
|
||||
public @Unordered Point2[] positions = {};
|
||||
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.team = team;
|
||||
this.positions = positions;
|
||||
}
|
||||
|
||||
public DestroyBlocksObjective(){
|
||||
}
|
||||
public DestroyBlocksObjective(){}
|
||||
|
||||
public int progress(){
|
||||
int count = 0;
|
||||
for(var pos : positions){
|
||||
int x = Point2.x(pos), y = Point2.y(pos);
|
||||
|
||||
var build = world.build(x, y);
|
||||
var build = world.build(pos.x, pos.y);
|
||||
if(build == null || build.team != team || build.block != block){
|
||||
count ++;
|
||||
}
|
||||
@ -302,7 +530,7 @@ public class MapObjectives{
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
public boolean update(){
|
||||
return progress() >= positions.length;
|
||||
}
|
||||
|
||||
@ -314,16 +542,15 @@ public class MapObjectives{
|
||||
|
||||
/** 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 boolean complete(){
|
||||
return headless || control.input.selectedUnits.contains(u -> u.isCommandable() && u.command().hasCommand());
|
||||
}
|
||||
}
|
||||
|
||||
/** Wait until a logic flag is set. */
|
||||
@ -335,189 +562,138 @@ public class MapObjectives{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public FlagObjective(){
|
||||
public FlagObjective(){}
|
||||
|
||||
@Override
|
||||
public boolean update(){
|
||||
return state.rules.objectiveFlags.contains(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String 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). */
|
||||
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 boolean complete(){
|
||||
return state.rules.waveTeam.cores().size == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Base abstract class for any in-map objective. */
|
||||
public static abstract class MapObjective{
|
||||
public @Nullable String details;
|
||||
public String[] flagsAdded = {};
|
||||
public String[] flagsRemoved = {};
|
||||
public ObjectiveMarker[] markers = {};
|
||||
/** Marker used for drawing UI to indicate something along with an objective. */
|
||||
public static abstract class ObjectiveMarker{
|
||||
/** Makes sure markers are only added once. */
|
||||
private transient boolean wasAdded;
|
||||
|
||||
//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(){
|
||||
return getClass().getSimpleName().replace("Objective", "");
|
||||
}
|
||||
|
||||
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;
|
||||
String className = getClass().getSimpleName().replace("Marker", "");
|
||||
return Core.bundle == null ? className : Core.bundle.get("marker." + className.toLowerCase() + ".name", className);
|
||||
}
|
||||
}
|
||||
|
||||
/** Displays text above a shape. */
|
||||
public static class ShapeTextMarker extends ObjectiveMarker{
|
||||
public String text = "frog";
|
||||
public float x, y, fontSize = 1f, textHeight = 7f;
|
||||
public byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||
public @TilePos Vec2 pos = new Vec2();
|
||||
public float fontSize = 1f, textHeight = 7f;
|
||||
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||
|
||||
public float radius = 6f, rotation = 0f;
|
||||
public int sides = 4;
|
||||
public Color color = Color.valueOf("ffd37f");
|
||||
|
||||
//cached localized text
|
||||
// Cached localized text.
|
||||
private transient String fetchedText;
|
||||
|
||||
public ShapeTextMarker(String text, float x, float y){
|
||||
this.text = text;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
}
|
||||
|
||||
public ShapeTextMarker(String text, float x, float y, float radius){
|
||||
this.text = text;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public ShapeTextMarker(String text, float x, float y, float radius, float rotation){
|
||||
this.text = text;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
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.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
this.radius = radius;
|
||||
this.rotation = rotation;
|
||||
this.textHeight = textHeight;
|
||||
}
|
||||
|
||||
public ShapeTextMarker(){
|
||||
|
||||
}
|
||||
public ShapeTextMarker(){}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
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.poly(x, y, sides, radius + 1f, rotation);
|
||||
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
|
||||
Draw.reset();
|
||||
|
||||
if(fetchedText == null){
|
||||
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. */
|
||||
public static class MinimapMarker extends ObjectiveMarker{
|
||||
public int x, y;
|
||||
public Point2 pos = new Point2();
|
||||
public float radius = 5f, stroke = 11f;
|
||||
public Color color = Color.valueOf("f25555");
|
||||
|
||||
public MinimapMarker(int x, int y){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
}
|
||||
|
||||
public MinimapMarker(int x, int y, Color color){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public MinimapMarker(int x, int y, float radius, float stroke, Color color){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
this.stroke = stroke;
|
||||
this.radius = radius;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public MinimapMarker(){
|
||||
}
|
||||
public MinimapMarker(){}
|
||||
|
||||
@Override
|
||||
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 fin = Interp.pow2Out.apply((Time.globalTime / 100f) % 1f);
|
||||
@ -530,25 +706,23 @@ public class MapObjectives{
|
||||
|
||||
/** Displays a shape with an outline and color. */
|
||||
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 int sides = 4;
|
||||
public Color color = Color.valueOf("ffd37f");
|
||||
|
||||
public ShapeMarker(float x, float y){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
}
|
||||
|
||||
public ShapeMarker(float x, float y, float radius, float rotation){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
this.radius = radius;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
public ShapeMarker(){
|
||||
}
|
||||
public ShapeMarker(){}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
@ -558,14 +732,14 @@ public class MapObjectives{
|
||||
if(!fill){
|
||||
if(outline){
|
||||
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.poly(x, y, sides, radius + 1f, rotation);
|
||||
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
|
||||
}else{
|
||||
Draw.color(color);
|
||||
Fill.poly(x, y, sides, radius);
|
||||
Fill.poly(pos.x, pos.y, sides, radius);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
@ -575,27 +749,25 @@ public class MapObjectives{
|
||||
/** Displays text at a location. */
|
||||
public static class TextMarker extends ObjectiveMarker{
|
||||
public String text = "uwu";
|
||||
public float x, y, fontSize = 1f;
|
||||
public byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||
//cached localized text
|
||||
public @TilePos Vec2 pos = new Vec2();
|
||||
public float fontSize = 1f;
|
||||
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||
// Cached localized text.
|
||||
private transient String fetchedText;
|
||||
|
||||
public TextMarker(String text, float x, float y, float fontSize, byte flags){
|
||||
this.text = text;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.fontSize = fontSize;
|
||||
this.flags = flags;
|
||||
this.pos.set(x, y);
|
||||
}
|
||||
|
||||
public TextMarker(String text, float x, float y){
|
||||
this.text = text;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pos.set(x, y);
|
||||
}
|
||||
|
||||
public TextMarker(){
|
||||
}
|
||||
public TextMarker(){}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
@ -603,27 +775,37 @@ public class MapObjectives{
|
||||
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. */
|
||||
public static abstract class ObjectiveMarker{
|
||||
/** makes sure markers are only added once */
|
||||
public transient boolean wasAdded;
|
||||
/** For arrays or {@link Seq}s; does not create element rearrangement buttons. */
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface Unordered{}
|
||||
|
||||
//TODO localize
|
||||
public String typeName(){
|
||||
return getClass().getSimpleName().replace("Marker", "");
|
||||
}
|
||||
/** For {@code byte}; treats it as a world label flag. */
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface LabelFlag{}
|
||||
|
||||
/** Called in the overlay draw layer.*/
|
||||
public void draw(){}
|
||||
/** Called in the small & large map. */
|
||||
public void drawMinimap(MinimapRenderer minimap){}
|
||||
/** Add any UI elements necessary. */
|
||||
public void added(){}
|
||||
/** Remove any UI elements, if necessary. */
|
||||
public void removed(){}
|
||||
}
|
||||
/** 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 {@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 mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.MapObjectives.*;
|
||||
import mindustry.graphics.g3d.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.type.*;
|
||||
@ -124,9 +123,9 @@ public class Rules{
|
||||
public ObjectSet<String> researched = new ObjectSet<>();
|
||||
/** Block containing these items as requirements are hidden. */
|
||||
public ObjectSet<Item> hiddenBuildItems = Items.erekirOnlyItems.asSet();
|
||||
/** Campaign-only map objectives. */
|
||||
public Seq<MapObjective> objectives = new Seq<>();
|
||||
/** Flags set by objectives. Used in world processors. n*/
|
||||
/** In-map objective executor. */
|
||||
public MapObjectives objectives = new MapObjectives();
|
||||
/** Flags set by objectives. Used in world processors. */
|
||||
public ObjectSet<String> objectiveFlags = new ObjectSet<>();
|
||||
/** If true, fog of war is enabled. Enemy units and buildings are hidden unless in radar view. */
|
||||
public boolean fog = false;
|
||||
|
@ -190,12 +190,9 @@ public class MinimapRenderer{
|
||||
drawSpawns(x, y, w, h, scaling);
|
||||
}
|
||||
|
||||
if(state.rules.objectives.size > 0){
|
||||
var first = state.rules.objectives.first();
|
||||
for(var marker : first.markers){
|
||||
marker.drawMinimap(this);
|
||||
}
|
||||
}
|
||||
state.rules.objectives.eachRunning(obj -> {
|
||||
for(var marker : obj.markers) marker.drawMinimap(this);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
if(state.rules.objectives.size > 0){
|
||||
var first = state.rules.objectives.first();
|
||||
for(var marker : first.markers){
|
||||
marker.draw();
|
||||
}
|
||||
}
|
||||
//draw objective markers
|
||||
state.rules.objectives.eachRunning(obj -> {
|
||||
for(var marker : obj.markers) marker.draw();
|
||||
});
|
||||
|
||||
if(player.dead()) return; //dead players don't draw
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Json.*;
|
||||
@ -8,6 +9,7 @@ import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.MapObjectives.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
@ -24,9 +26,9 @@ public class JsonIO{
|
||||
|
||||
@Override
|
||||
public void writeValue(Object value, Class knownType, Class elementType){
|
||||
if(value instanceof MappableContent){
|
||||
if(value instanceof MappableContent c){
|
||||
try{
|
||||
getWriter().value(((MappableContent)value).name);
|
||||
getWriter().value(c.name);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -37,9 +39,7 @@ public class JsonIO{
|
||||
|
||||
@Override
|
||||
protected String convertToString(Object object){
|
||||
if(object instanceof MappableContent){
|
||||
return ((MappableContent)object).name;
|
||||
}
|
||||
if(object instanceof MappableContent c) return c.name;
|
||||
return super.convertToString(object);
|
||||
}
|
||||
};
|
||||
@ -69,6 +69,11 @@ public class JsonIO{
|
||||
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){
|
||||
json.setElementType(Rules.class, "spawns", SpawnGroup.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
|
||||
for(var filter : Maps.allFilterTypes){
|
||||
var i = filter.get();
|
||||
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{
|
||||
|
@ -546,6 +546,19 @@ public class TypeIO{
|
||||
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){
|
||||
if(v == null){
|
||||
write.f(Float.NaN);
|
||||
|
@ -775,13 +775,21 @@ public class HudFragment{
|
||||
builder.setLength(0);
|
||||
|
||||
//objectives override mission?
|
||||
if(state.rules.objectives.size > 0){
|
||||
var first = state.rules.objectives.first();
|
||||
String text = first.text();
|
||||
if(text != null){
|
||||
builder.append(text);
|
||||
return builder;
|
||||
if(state.rules.objectives.any()){
|
||||
boolean first = true;
|
||||
for(var obj : state.rules.objectives){
|
||||
if(!obj.qualified()) continue;
|
||||
|
||||
String text = obj.text();
|
||||
if(text != null){
|
||||
if(!first) builder.append('\n');
|
||||
builder.append(text);
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
//mission overrides everything
|
||||
@ -832,13 +840,24 @@ public class HudFragment{
|
||||
table.row();
|
||||
|
||||
table.clicked(() -> {
|
||||
if(state.rules.objectives.size > 0){
|
||||
var first = state.rules.objectives.first();
|
||||
var details = first.details();
|
||||
if(details != null){
|
||||
//TODO this could be much better.
|
||||
ui.showInfo(details);
|
||||
if(state.rules.objectives.any()){
|
||||
StringBuilder text = new StringBuilder();
|
||||
|
||||
boolean first = true;
|
||||
for(var obj : state.rules.objectives){
|
||||
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());
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user