mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-01-28 07:22:21 -08:00
Commandable blocks
This commit is contained in:
parent
49a39d42e7
commit
9f3af412f0
12 changed files with 177 additions and 16 deletions
|
|
@ -545,6 +545,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||
|
||||
}
|
||||
|
||||
/** Called when this building receives a position command. Requires a commandable block. */
|
||||
public void onCommand(Vec2 target){
|
||||
|
||||
}
|
||||
|
||||
/** @return the position that this block points to for commands, or null. */
|
||||
public @Nullable Vec2 getCommandPosition(){
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleUnitPayload(Unit unit, Cons<Payload> grabber){
|
||||
Fx.spawn.at(unit);
|
||||
|
||||
|
|
|
|||
|
|
@ -281,6 +281,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||
return controller instanceof CommandAI;
|
||||
}
|
||||
|
||||
public CommandAI command(){
|
||||
if(controller instanceof CommandAI ai){
|
||||
return ai;
|
||||
}else{
|
||||
throw new IllegalArgumentException("Unit cannot be commanded - check isCommandable() first.");
|
||||
}
|
||||
}
|
||||
|
||||
public int count(){
|
||||
return team.data().countType(type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ import static mindustry.Vars.*;
|
|||
|
||||
public class AIController implements UnitController{
|
||||
protected static final Vec2 vec = new Vec2();
|
||||
protected static final float rotateBackTimer = 60f * 5f;
|
||||
protected static final int timerTarget = 0, timerTarget2 = 1, timerTarget3 = 2, timerTarget4 = 3;
|
||||
|
||||
protected Unit unit;
|
||||
protected Interval timer = new Interval(4);
|
||||
protected AIController fallback;
|
||||
protected float noTargetTime;
|
||||
|
||||
/** main target that is being faced */
|
||||
protected Teamc target;
|
||||
|
|
@ -128,8 +130,12 @@ public class AIController implements UnitController{
|
|||
target = findMainTarget(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
|
||||
}
|
||||
|
||||
noTargetTime += Time.delta;
|
||||
|
||||
if(invalid(target)){
|
||||
target = null;
|
||||
}else{
|
||||
noTargetTime = 0f;
|
||||
}
|
||||
|
||||
unit.isShooting = false;
|
||||
|
|
@ -168,6 +174,13 @@ public class AIController implements UnitController{
|
|||
|
||||
unit.isShooting |= (mount.shoot = mount.rotate = shoot);
|
||||
|
||||
if(mount.target == null && !shoot && !Angles.within(mount.rotation, 0f, 0.01f) && noTargetTime >= rotateBackTimer){
|
||||
mount.rotate = true;
|
||||
Tmp.v1.trns(unit.rotation, 5f);
|
||||
mount.aimX = mountX + Tmp.v1.x;
|
||||
mount.aimY = mountY + Tmp.v1.y;
|
||||
}
|
||||
|
||||
if(shoot){
|
||||
unit.aimX = mount.aimX;
|
||||
unit.aimY = mount.aimY;
|
||||
|
|
|
|||
|
|
@ -118,6 +118,13 @@ public class Drawf{
|
|||
Draw.z(pz);
|
||||
}
|
||||
|
||||
public static void limitLine(Position start, Position dest, float len1, float len2){
|
||||
Tmp.v1.set(dest).sub(start).setLength(len1);
|
||||
Tmp.v2.set(Tmp.v1).scl(-1f).setLength(len2);
|
||||
|
||||
Drawf.line(Pal.accent, start.getX() + Tmp.v1.x, start.getY() + Tmp.v1.y, dest.getX() + Tmp.v2.x, dest.getY() + Tmp.v2.y);
|
||||
}
|
||||
|
||||
public static void dashLineDst(Color color, float x, float y, float x2, float y2){
|
||||
dashLine(color, x, y, x2, y2, (int)(Mathf.dst(x, y, x2, y2) / tilesize * 1.6f));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,17 +117,12 @@ public class DesktopInput extends InputHandler{
|
|||
if(commandMode){
|
||||
|
||||
//draw command overlay UI
|
||||
|
||||
for(Unit unit : selectedUnits){
|
||||
CommandAI ai = (CommandAI)unit.controller();
|
||||
CommandAI ai = unit.command();
|
||||
//draw target line
|
||||
if(ai.targetPos != null){
|
||||
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||
|
||||
Tmp.v1.set(lineDest).sub(unit).setLength(unit.hitSize / 2f);
|
||||
Tmp.v2.set(Tmp.v1).scl(-1f).setLength(3.5f);
|
||||
|
||||
Drawf.line(Pal.accent, unit.x + Tmp.v1.x, unit.y + Tmp.v1.y, lineDest.getX() + Tmp.v2.x, lineDest.getY() + Tmp.v2.y);
|
||||
Drawf.limitLine(unit, lineDest, unit.hitSize / 2f, 3.5f);
|
||||
|
||||
if(ai.attackTarget == null){
|
||||
Drawf.square(lineDest.getX(), lineDest.getY(), 3.5f);
|
||||
|
|
@ -141,6 +136,16 @@ public class DesktopInput extends InputHandler{
|
|||
}
|
||||
}
|
||||
|
||||
if(commandBuild != null){
|
||||
Drawf.square(commandBuild.x, commandBuild.y, commandBuild.hitSize() / 1.4f + 1f);
|
||||
var cpos = commandBuild.getCommandPosition();
|
||||
|
||||
if(cpos != null){
|
||||
Drawf.limitLine(commandBuild, cpos, commandBuild.hitSize() / 2f, 3.5f);
|
||||
Drawf.square(cpos.x, cpos.y, 3.5f);
|
||||
}
|
||||
}
|
||||
|
||||
if(commandMode && !commandRect){
|
||||
Unit sel = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||
|
||||
|
|
@ -564,6 +569,7 @@ public class DesktopInput extends InputHandler{
|
|||
selectedUnits.clear();
|
||||
}
|
||||
selectedUnits.addAll(units);
|
||||
commandBuild = null;
|
||||
}
|
||||
commandRect = false;
|
||||
}
|
||||
|
|
@ -698,6 +704,7 @@ public class DesktopInput extends InputHandler{
|
|||
//click: select a single unit
|
||||
if(button == KeyCode.mouseLeft){
|
||||
Unit unit = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||
Building build = world.buildWorld(input.mouseWorldX(), input.mouseWorldY());
|
||||
if(unit != null){
|
||||
if(selectedUnits.contains(unit)){
|
||||
selectedUnits.remove(unit);
|
||||
|
|
@ -705,16 +712,24 @@ public class DesktopInput extends InputHandler{
|
|||
selectedUnits.clear();
|
||||
selectedUnits.add(unit);
|
||||
}
|
||||
commandBuild = null;
|
||||
}else{
|
||||
//deselect
|
||||
selectedUnits.clear();
|
||||
|
||||
if(build != null && build.team == player.team() && build.block.commandable){
|
||||
commandBuild = (commandBuild == build ? null : build);
|
||||
}else{
|
||||
commandBuild = null;
|
||||
}
|
||||
}
|
||||
}else if(button == KeyCode.mouseRight){
|
||||
//right click: move to position
|
||||
|
||||
//move to location - TODO right click instead?
|
||||
Vec2 target = input.mouseWorld().cpy();
|
||||
|
||||
if(selectedUnits.size > 0){
|
||||
//move to location - TODO right click instead?
|
||||
Vec2 target = input.mouseWorld().cpy();
|
||||
|
||||
Teamc attack = world.buildWorld(target.x, target.y);
|
||||
|
||||
|
|
@ -729,6 +744,10 @@ public class DesktopInput extends InputHandler{
|
|||
|
||||
Call.commandUnits(player, ids, attack instanceof Building b ? b : null, attack instanceof Unit u ? u : null, target);
|
||||
}
|
||||
|
||||
if(commandBuild != null){
|
||||
Call.commandBuilding(player, commandBuild, target);
|
||||
}
|
||||
}
|
||||
|
||||
return super.tap(x, y, count, button);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
|
||||
//for RTS controls
|
||||
public Seq<Unit> selectedUnits = new Seq<>();
|
||||
public @Nullable Building commandBuild;
|
||||
public boolean commandMode = false;
|
||||
public boolean commandRect = false;
|
||||
public boolean tappedOne = false;
|
||||
|
|
@ -228,6 +229,20 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||
public static void commandBuilding(Player player, Building build, Vec2 target){
|
||||
if(player == null || build == null || build.team != player.team() || !build.block.commandable || target == null) return;
|
||||
|
||||
if(net.server() && !netServer.admins.allowAction(player, ActionType.commandBuilding, event -> {
|
||||
event.tile = build.tile;
|
||||
})){
|
||||
throw new ValidateException(player, "Player cannot command building.");
|
||||
}
|
||||
|
||||
build.onCommand(target);
|
||||
Fx.moveCommand.at(target);
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||
public static void requestItem(Player player, Building build, Item item, int amount){
|
||||
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, buildingRange) || player.dead()) return;
|
||||
|
|
@ -1062,12 +1077,16 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
if(build == null){
|
||||
frag.inv.hide();
|
||||
frag.config.hideConfig();
|
||||
commandBuild = null;
|
||||
return false;
|
||||
}
|
||||
boolean consumed = false, showedInventory = false;
|
||||
|
||||
//check if tapped block is configurable
|
||||
if(build.block.configurable && build.interactable(player.team())){
|
||||
//select building for commanding
|
||||
if(build.block.commandable && commandMode){
|
||||
//TODO handled in tap.
|
||||
consumed = true;
|
||||
}else if(build.block.configurable && build.interactable(player.team())){ //check if tapped block is configurable
|
||||
consumed = true;
|
||||
if((!frag.config.isShown() && build.shouldShowConfigure(player)) //if the config fragment is hidden, show
|
||||
//alternatively, the current selected block can 'agree' to switch config tiles
|
||||
|
|
|
|||
|
|
@ -486,6 +486,21 @@ public class TypeIO{
|
|||
return JsonIO.read(Rules.class, string);
|
||||
}
|
||||
|
||||
public static void writeVecNullable(Writes write, @Nullable Vec2 v){
|
||||
if(v == null){
|
||||
write.f(Float.NaN);
|
||||
write.f(Float.NaN);
|
||||
}else{
|
||||
write.f(v.x);
|
||||
write.f(v.y);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable Vec2 readVecNullable(Reads read){
|
||||
float x = read.f(), y = read.f();
|
||||
return Float.isNaN(x) || Float.isNaN(y) ? null : new Vec2(x, y);
|
||||
}
|
||||
|
||||
public static void writeVec2(Writes write, Vec2 v){
|
||||
if(v == null){
|
||||
write.f(0);
|
||||
|
|
|
|||
|
|
@ -653,7 +653,7 @@ public class Administration{
|
|||
}
|
||||
|
||||
public enum ActionType{
|
||||
breakBlock, placeBlock, rotate, configure, withdrawItem, depositItem, control, buildSelect, command, removePlanned, commandUnits
|
||||
breakBlock, placeBlock, rotate, configure, withdrawItem, depositItem, control, buildSelect, command, removePlanned, commandUnits, commandBuilding
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,8 @@ public class Block extends UnlockableContent implements Senseable{
|
|||
public int unitCapModifier = 0;
|
||||
/** Whether the block can be tapped and selected to configure. */
|
||||
public boolean configurable;
|
||||
/** If true, this building can be selected like a unit when commanding. */
|
||||
public boolean commandable;
|
||||
/** If true, the building inventory can be shown with the config. */
|
||||
public boolean allowConfigInventory = true;
|
||||
/** If true, this block can be configured by logic. */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mindustry.world.blocks.units;
|
|||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
|
|
@ -13,6 +14,7 @@ import mindustry.entities.units.*;
|
|||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
|
@ -31,6 +33,7 @@ public class Reconstructor extends UnitBlock{
|
|||
super(name);
|
||||
regionRotated1 = 1;
|
||||
regionRotated2 = 2;
|
||||
commandable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -108,11 +111,22 @@ public class Reconstructor extends UnitBlock{
|
|||
}
|
||||
|
||||
public class ReconstructorBuild extends UnitBuild{
|
||||
public @Nullable Vec2 commandPos;
|
||||
|
||||
public float fraction(){
|
||||
return progress / constructTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getCommandPosition(){
|
||||
return commandPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(Vec2 target){
|
||||
commandPos = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptUnitPayload(Unit unit){
|
||||
return hasUpgrade(unit.type) && !upgrade(unit.type).isBanned();
|
||||
|
|
@ -213,6 +227,9 @@ public class Reconstructor extends UnitBlock{
|
|||
//upgrade the unit
|
||||
if(progress >= constructTime){
|
||||
payload.unit = upgrade(payload.unit.type).create(payload.unit.team());
|
||||
if(commandPos != null && payload.unit.isCommandable()){
|
||||
payload.unit.command().commandPosition(commandPos);
|
||||
}
|
||||
progress %= 1f;
|
||||
Effect.shake(2f, 3f, this);
|
||||
Fx.producesmoke.at(this);
|
||||
|
|
@ -261,7 +278,7 @@ public class Reconstructor extends UnitBlock{
|
|||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -269,16 +286,20 @@ public class Reconstructor extends UnitBlock{
|
|||
super.write(write);
|
||||
|
||||
write.f(progress);
|
||||
TypeIO.writeVecNullable(write, commandPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Reads read, byte revision){
|
||||
super.read(read, revision);
|
||||
|
||||
if(revision == 1){
|
||||
if(revision >= 1){
|
||||
progress = read.f();
|
||||
}
|
||||
|
||||
if(revision >= 2){
|
||||
commandPos = TypeIO.readVecNullable(read);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import mindustry.entities.units.*;
|
|||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
|
@ -52,6 +53,7 @@ public class UnitAssembler extends PayloadBlock{
|
|||
regionRotated1 = 1;
|
||||
sync = true;
|
||||
group = BlockGroup.units;
|
||||
commandable = true;
|
||||
}
|
||||
|
||||
public Rect getRect(Rect rect, float x, float y, int rotation){
|
||||
|
|
@ -190,6 +192,7 @@ public class UnitAssembler extends PayloadBlock{
|
|||
//holds drone IDs that have been sent, but not synced yet - add to list as soon as possible
|
||||
protected IntSeq whenSyncedUnits = new IntSeq();
|
||||
|
||||
public @Nullable Vec2 commandPos;
|
||||
public Seq<Unit> units = new Seq<>();
|
||||
public Seq<UnitAssemblerModuleBuild> modules = new Seq<>();
|
||||
public PayloadSeq blocks = new PayloadSeq();
|
||||
|
|
@ -398,6 +401,9 @@ public class UnitAssembler extends PayloadBlock{
|
|||
|
||||
if(!net.client()){
|
||||
var unit = plan.unit.create(team);
|
||||
if(unit != null && unit.isCommandable()){
|
||||
unit.command().commandPosition(commandPos);
|
||||
}
|
||||
unit.set(spawn.x + Mathf.range(0.001f), spawn.y + Mathf.range(0.001f));
|
||||
unit.rotation = 90f;
|
||||
unit.add();
|
||||
|
|
@ -546,6 +552,21 @@ public class UnitAssembler extends PayloadBlock{
|
|||
plan.requirements.contains(b -> b.item == payload.content() && blocks.get(payload.content()) < b.amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getCommandPosition(){
|
||||
return commandPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(Vec2 target){
|
||||
commandPos = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
|
|
@ -557,6 +578,7 @@ public class UnitAssembler extends PayloadBlock{
|
|||
}
|
||||
|
||||
blocks.write(write);
|
||||
TypeIO.writeVecNullable(write, commandPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -571,6 +593,9 @@ public class UnitAssembler extends PayloadBlock{
|
|||
whenSyncedUnits.clear();
|
||||
|
||||
blocks.read(read);
|
||||
if(revision >= 1){
|
||||
commandPos = TypeIO.readVecNullable(read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import arc.*;
|
|||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
|
|
@ -15,6 +16,7 @@ import mindustry.entities.units.*;
|
|||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
|
@ -39,6 +41,7 @@ public class UnitFactory extends UnitBlock{
|
|||
outputsPayload = true;
|
||||
rotate = true;
|
||||
regionRotated1 = 1;
|
||||
commandable = true;
|
||||
|
||||
config(Integer.class, (UnitFactoryBuild tile, Integer i) -> {
|
||||
if(!configurable) return;
|
||||
|
|
@ -143,12 +146,23 @@ public class UnitFactory extends UnitBlock{
|
|||
}
|
||||
|
||||
public class UnitFactoryBuild extends UnitBuild{
|
||||
public @Nullable Vec2 commandPos;
|
||||
public int currentPlan = -1;
|
||||
|
||||
public float fraction(){
|
||||
return currentPlan == -1 ? 0 : progress / plans.get(currentPlan).time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getCommandPosition(){
|
||||
return commandPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommand(Vec2 target){
|
||||
commandPos = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object senseObject(LAccess sensor){
|
||||
if(sensor == LAccess.config) return currentPlan == -1 ? null : plans.get(currentPlan).unit;
|
||||
|
|
@ -252,7 +266,11 @@ public class UnitFactory extends UnitBlock{
|
|||
if(progress >= plan.time && consValid()){
|
||||
progress %= 1f;
|
||||
|
||||
payload = new UnitPayload(plan.unit.create(team));
|
||||
Unit unit = plan.unit.create(team);
|
||||
if(commandPos != null && unit.isCommandable()){
|
||||
unit.command().commandPosition(commandPos);
|
||||
}
|
||||
payload = new UnitPayload(unit);
|
||||
payVector.setZero();
|
||||
consume();
|
||||
Events.fire(new UnitCreateEvent(payload.unit, this));
|
||||
|
|
@ -287,7 +305,7 @@ public class UnitFactory extends UnitBlock{
|
|||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -295,6 +313,7 @@ public class UnitFactory extends UnitBlock{
|
|||
super.write(write);
|
||||
write.f(progress);
|
||||
write.s(currentPlan);
|
||||
TypeIO.writeVecNullable(write, commandPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -302,6 +321,9 @@ public class UnitFactory extends UnitBlock{
|
|||
super.read(read, revision);
|
||||
progress = read.f();
|
||||
currentPlan = read.s();
|
||||
if(revision >= 2){
|
||||
commandPos = TypeIO.readVecNullable(read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue