Allow setting attack mode wave unit spawn position

This commit is contained in:
Anuken
2025-05-05 20:50:43 -04:00
parent 317f9878dc
commit 16cb530b99
9 changed files with 143 additions and 29 deletions

View File

@@ -15,6 +15,7 @@ import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
@@ -125,39 +126,46 @@ public class WaveSpawner{
}
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
if(state.rules.wavesSpawnAtCores && state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
Building firstCore = state.teams.playerCores().first();
for(Building core : state.rules.waveTeam.cores()){
for(CoreBuild core : state.rules.waveTeam.cores()){
if(filterPos != -1 && filterPos != core.pos()) continue;
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block.size * tilesize /2f * Mathf.sqrt2);
if(core.commandPos != null){
cons.accept(core.commandPos.x, core.commandPos.y, false);
}else{
boolean valid = false;
boolean valid = false;
int steps = 0;
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block.size * tilesize /2f * Mathf.sqrt2);
//keep moving forward until the max step amount is reached
while(steps++ < maxSteps){
int tx = World.toTile(core.x + Tmp.v1.x), ty = World.toTile(core.y + Tmp.v1.y);
any = false;
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
if(world.solid(x, y)){
any = true;
int steps = 0;
//keep moving forward until the max step amount is reached
while(steps++ < maxSteps){
int tx = World.toTile(core.x + Tmp.v1.x), ty = World.toTile(core.y + Tmp.v1.y);
any = false;
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
if(world.solid(x, y)){
any = true;
}
});
//nothing is in the way, spawn it
if(!any){
valid = true;
break;
}else{
//make the vector longer
Tmp.v1.setLength(Tmp.v1.len() + tilesize*1.1f);
}
});
}
//nothing is in the way, spawn it
if(!any){
valid = true;
break;
}else{
//make the vector longer
Tmp.v1.setLength(Tmp.v1.len() + tilesize*1.1f);
if(valid){
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
}
}
if(valid){
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
}
}
}
}

View File

@@ -176,10 +176,67 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.hide();
sectorGenDialog.show();
}).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.row();
menu.cont.row();
//this is gated behind a property, because it's (1) not useful to most people, (2) confusing and (3) may crash or otherwise bug out
if(OS.hasProp("mindustry.editor.simulate.button")){
menu.cont.button("Simulate", Icon.logic, () -> {
menu.hide();
BaseDialog dialog = new BaseDialog("Simulate");
int[] seconds = {60 * 1};
dialog.cont.add("Seconds: ");
dialog.cont.field(seconds[0] + "", text -> seconds[0] = Strings.parseInt(text, 1)).valid(s -> Strings.parseInt(s, 9999999) < 10f * 60f);
dialog.addCloseButton();
dialog.buttons.button("@ok", Icon.ok, () -> {
ui.loadAnd(() -> {
float deltaScl = 2f;
int steps = Mathf.ceil(seconds[0] * 60f / deltaScl);
float oldDelta = Time.delta;
Time.delta = deltaScl;
Seq<Building> builds = new Seq<>();
Time.clear();
world.tiles.eachTile(t -> {
if(t.build != null && t.isCenter() && t.block().update && t.build.allowUpdate()){
builds.add(t.build);
t.build.updateProximity();
}
});
for(int i = 0; i < steps; i++){
Time.update();
for(var build : builds){
build.update();
}
Groups.powerGraph.update();
}
//spawned units will cause havoc, so clear them
Groups.unit.clear();
Time.clear();
Time.delta = oldDelta;
});
dialog.hide();
}).size(210f, 64f);
dialog.show();
}).size(swidth * 2f + 10, 60f);
menu.cont.row();
}
menu.cont.button("@quit", Icon.exit, () -> {
tryExit();
menu.hide();

View File

@@ -1315,6 +1315,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
public boolean isCommandable(){
return block.commandable;
}
/** @return whether this building is in a payload */
public boolean isPayload(){
return tile == emptyTile;

View File

@@ -34,6 +34,8 @@ public class Rules{
public boolean waves;
/** Whether air units spawn at spawns instead of the edge of the map */
public boolean airUseSpawns = false;
/** If true, units spawn at enemy cores in attack maps with waves enabled. */
public boolean wavesSpawnAtCores = true;
/** Whether the game objective is PvP. Note that this enables automatic hosting. */
public boolean pvp;
/** Whether is waiting for players enabled in PvP. */

View File

@@ -326,7 +326,7 @@ public class DesktopInput extends InputHandler{
selectedUnits.clear();
commandBuildings.clear();
for(var build : player.team().data().buildings){
if(build.block.commandable){
if(build.isCommandable()){
commandBuildings.add(build);
}
}

View File

@@ -155,6 +155,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Events.on(ResetEvent.class, e -> {
logicCutscene = false;
commandBuildings.clear();
selectedUnits.clear();
itemDepositCooldown = 0f;
Arrays.fill(controlGroups, null);
lastUnit = null;
@@ -413,7 +415,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
for(int pos : buildings){
var build = world.build(pos);
if(build == null || build.team() != player.team() || !build.block.commandable) continue;
if(build == null || build.team() != player.team() || !build.isCommandable()) continue;
build.onCommand(target);
build.updateLastAccess(player);
@@ -839,7 +841,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
itemDepositCooldown -= Time.delta / 60f;
commandBuildings.removeAll(b -> !b.isValid());
commandBuildings.removeAll(b -> !b.isValid() || !b.isCommandable() || b.team != player.team());
if(!commandMode){
commandRect = false;
@@ -1025,7 +1027,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//deselect
selectedUnits.clear();
if(build != null && build.team == player.team() && build.block.commandable){
if(build != null && build.team == player.team() && build.isCommandable()){
if(commandBuildings.contains(build)){
commandBuildings.remove(build);
}else{
@@ -1742,7 +1744,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
boolean consumed = false, showedInventory = false;
//select building for commanding
if(build.block.commandable && commandMode){
if(build.isCommandable() && commandMode){
//TODO handled in tap.
consumed = true;
}else if(build.block.configurable && build.interactable(player.team())){ //check if tapped block is configurable

View File

@@ -151,6 +151,7 @@ public class CustomRulesDialog extends BaseDialog{
check("@rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer, () -> rules.waves);
check("@rules.waitForWaveToEnd", b -> rules.waitEnemies = b, () -> rules.waitEnemies, () -> rules.waves && rules.waveTimer);
check("@rules.randomwaveai", b -> rules.randomWaveAI = b, () -> rules.randomWaveAI, () -> rules.waves);
check("@rules.wavespawnatcores", b -> rules.wavesSpawnAtCores = b, () -> rules.wavesSpawnAtCores, () -> rules.waves);
check("@rules.airUseSpawns", b -> rules.airUseSpawns = b, () -> rules.airUseSpawns, () -> rules.waves);
numberi("@rules.wavelimit", f -> rules.winWave = f, () -> rules.winWave, () -> rules.waves, 0, Integer.MAX_VALUE);
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> rules.waves && rules.waveTimer, 1, Float.MAX_VALUE);

View File

@@ -13,6 +13,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
@@ -23,6 +24,7 @@ import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
@@ -71,6 +73,7 @@ public class CoreBlock extends StorageBlock{
unitCapModifier = 10;
drawDisabled = false;
canOverdrive = false;
commandable = true;
envEnabled |= Env.space;
//support everything
@@ -247,9 +250,25 @@ public class CoreBlock extends StorageBlock{
public Team lastDamage = Team.derelict;
public float iframes = -1f;
public float thrusterTime = 0f;
public @Nullable Vec2 commandPos;
protected float cloudSeed, landParticleTimer;
@Override
public boolean isCommandable(){
return team != state.rules.defaultTeam && state.rules.attackMode;
}
@Override
public Vec2 getCommandPosition(){
return commandPos;
}
@Override
public void onCommand(Vec2 target){
commandPos = target;
}
@Override
public void draw(){
//draw thrusters when just landed
@@ -806,5 +825,24 @@ public class CoreBlock extends StorageBlock{
noEffect = false;
}
}
@Override
public byte version(){
return 1;
}
@Override
public void write(Writes write){
super.write(write);
TypeIO.writeVecNullable(write, commandPos);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
if(revision >= 1){
commandPos = TypeIO.readVecNullable(read);
}
}
}
}