mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-05-28 17:06:34 +03:00
Added 'hold position' stance (closes Anuken/Mindustry-Suggestions/issues/6157)
This commit is contained in:
@@ -380,6 +380,7 @@ stance.shoot = Stance: Shoot
|
||||
stance.holdfire = Stance: Hold Fire
|
||||
stance.pursuetarget = Stance: Pursue Target
|
||||
stance.patrol = Stance: Patrol Path
|
||||
stance.holdposition = Stance: Hold Position
|
||||
stance.ram = Stance: Ram\n[lightgray]Straight line movement, no pathfinding
|
||||
stance.boost = Boost
|
||||
stance.mineauto = Automatic Mining
|
||||
@@ -1364,6 +1365,7 @@ keybind.unit_stance_pursue_target.name = Unit Stance: Pursue Target
|
||||
keybind.unit_stance_patrol.name = Unit Stance: Patrol
|
||||
keybind.unit_stance_ram.name = Unit Stance: Ram
|
||||
keybind.unit_stance_boost.name = Unit Stance: Boost
|
||||
keybind.unit_stance_hold_position.name = Unit Stance: Hold Position
|
||||
|
||||
keybind.unit_command_move.name = Unit Command: Move
|
||||
keybind.unit_command_repair.name = Unit Command: Repair
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.input.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.ctype.*;
|
||||
@@ -27,12 +28,14 @@ public class UnitCommand extends MappableContent{
|
||||
public boolean resetTarget = true;
|
||||
/** Whether to snap the command destination to ally buildings. */
|
||||
public boolean snapToBuilding = false;
|
||||
/** */
|
||||
/** If true, the unit will arrive at this command's exact endpoint. */
|
||||
public boolean exactArrival = false;
|
||||
/** If true, this command refreshes the list of stances when selected TODO: do not use, this will likely be removed later!*/
|
||||
public boolean refreshOnSelect = false;
|
||||
/** Key to press for this command. */
|
||||
public @Nullable KeyBind keybind = null;
|
||||
/** Extra stances that are available when this command is selected. These ignore incompatibleStances. */
|
||||
public Seq<UnitStance> extraStances = new Seq<>();
|
||||
|
||||
public UnitCommand(String name, String icon, Func<Unit, AIController> controller){
|
||||
super(name);
|
||||
|
||||
@@ -12,7 +12,7 @@ import mindustry.input.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class UnitStance extends MappableContent{
|
||||
public static UnitStance stop, holdFire, pursueTarget, patrol, ram, boost, mineAuto;
|
||||
public static UnitStance stop, holdFire, pursueTarget, patrol, ram, boost, holdPosition, mineAuto;
|
||||
|
||||
/** Name of UI icon (from Icon class). */
|
||||
public String icon;
|
||||
@@ -84,16 +84,22 @@ public class UnitStance extends MappableContent{
|
||||
stop = new UnitStance("stop", "cancel", Binding.cancelOrders, false);
|
||||
holdFire = new UnitStance("holdfire", "none", Binding.unitStanceHoldFire);
|
||||
pursueTarget = new UnitStance("pursuetarget", "right", Binding.unitStancePursueTarget);
|
||||
patrol = new UnitStance("patrol", "refresh", Binding.unitStancePatrol);
|
||||
patrol = new UnitStance("patrol", "refresh", Binding.unitStancePatrol){{
|
||||
incompatibleCommands.addAll(UnitCommand.repairCommand, UnitCommand.assistCommand, UnitCommand.rebuildCommand);
|
||||
}};
|
||||
ram = new UnitStance("ram", "rightOpen", Binding.unitStanceRam);
|
||||
boost = new UnitStance("boost", "up", Binding.unitStanceBoost){{
|
||||
incompatibleCommands.addAll(UnitCommand.rebuildCommand, UnitCommand.repairCommand, UnitCommand.assistCommand);
|
||||
}};
|
||||
holdPosition = new UnitStance("holdposition", "effect", Binding.unitStanceHoldPosition);
|
||||
mineAuto = new UnitStance("mineauto", "settings", null, false);
|
||||
|
||||
//Only vanilla items are supported for now
|
||||
for(Item item : Vars.content.items()){
|
||||
new ItemUnitStance(item);
|
||||
}
|
||||
|
||||
Seq.with(UnitCommand.repairCommand, UnitCommand.assistCommand, UnitCommand.rebuildCommand)
|
||||
.each(c -> c.extraStances.add(holdPosition));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.ai.types;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -61,6 +62,7 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
|
||||
boolean moving = false;
|
||||
boolean hold = hasStance(UnitStance.holdPosition);
|
||||
|
||||
if(following != null){
|
||||
retreatTimer = 0f;
|
||||
@@ -77,7 +79,7 @@ public class BuilderAI extends AIController{
|
||||
unit.plans.clear();
|
||||
unit.plans.addFirst(following.buildPlan());
|
||||
lastPlan = null;
|
||||
}else if(unit.buildPlan() == null || alwaysFlee){
|
||||
}else if((unit.buildPlan() == null || alwaysFlee) && !hold){
|
||||
//not following anyone or building
|
||||
if(timer.get(timerTarget4, 40)){
|
||||
enemy = target(unit.x, unit.y, fleeRange, true, true);
|
||||
@@ -121,10 +123,16 @@ public class BuilderAI extends AIController{
|
||||
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation)));
|
||||
|
||||
if(valid){
|
||||
float range = Math.min(unit.type.buildRange - unit.type.hitSize * 2f, buildRadius);
|
||||
//move toward the plan
|
||||
moveTo(req.tile(), range, 20f);
|
||||
moving = !unit.within(req.tile(), range);
|
||||
if(!hold){
|
||||
float range = Math.min(unit.type.buildRange - unit.type.hitSize * 2f, buildRadius);
|
||||
//move toward the plan
|
||||
moveTo(req.tile(), range, 20f);
|
||||
moving = !unit.within(req.tile(), range);
|
||||
}else if(!unit.within(req, unit.type.buildRange - tilesize) && !state.rules.infiniteResources){
|
||||
//discard the plan, it's too far away to reach while holding position. try the next one
|
||||
unit.plans.removeFirst();
|
||||
lastPlan = null;
|
||||
}
|
||||
}else{
|
||||
//discard invalid plan
|
||||
unit.plans.removeFirst();
|
||||
@@ -132,7 +140,7 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
}else{
|
||||
|
||||
if(assistFollowing != null){
|
||||
if(assistFollowing != null && !hold){
|
||||
moveTo(assistFollowing, assistFollowing.type.hitSize + unit.type.hitSize/2f + 60f);
|
||||
moving = !unit.within(assistFollowing, assistFollowing.type.hitSize + unit.type.hitSize/2f + 65f);
|
||||
}
|
||||
@@ -179,21 +187,43 @@ public class BuilderAI extends AIController{
|
||||
|
||||
//find new plan
|
||||
if(!onlyAssist && !unit.team.data().plans.isEmpty() && following == null && timer.get(timerTarget3, rebuildPeriod)){
|
||||
Queue<BlockPlan> blocks = unit.team.data().plans;
|
||||
BlockPlan block = blocks.first();
|
||||
var blocks = unit.team.data().plans;
|
||||
|
||||
//check if it's already been placed
|
||||
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block() == block.block){
|
||||
blocks.removeFirst();
|
||||
}else if(Build.validPlace(block.block, unit.team(), block.x, block.y, block.rotation) && (!alwaysFlee || !nearEnemy(block.x, block.y))){ //it's valid
|
||||
lastPlan = block;
|
||||
//add build plan
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, block.block, block.config));
|
||||
//shift build plan to tail so next unit builds something else
|
||||
blocks.addLast(blocks.removeFirst());
|
||||
|
||||
if(hold){
|
||||
//essentially build turret behavior (find first plan in range)
|
||||
for(int i = 0; i < blocks.size; i++){
|
||||
var block = blocks.get(i);
|
||||
if(state.rules.infiniteResources || unit.within(block.x * tilesize, block.y * tilesize, unit.type.buildRange)){
|
||||
var btype = block.block;
|
||||
|
||||
if(Build.validPlace(btype, unit.team(), block.x, block.y, block.rotation)){
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, block.block, block.config));
|
||||
//shift build plan to tail so next unit builds something else
|
||||
blocks.addLast(blocks.removeIndex(i));
|
||||
lastPlan = block;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//shift head of queue to tail, try something else next time
|
||||
blocks.addLast(blocks.removeFirst());
|
||||
BlockPlan block = blocks.first();
|
||||
|
||||
//check if it's already been placed
|
||||
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block() == block.block){
|
||||
blocks.removeFirst();
|
||||
}else if(Build.validPlace(block.block, unit.team(), block.x, block.y, block.rotation)
|
||||
&& (!alwaysFlee || !nearEnemy(block.x, block.y))){ //check if it's valid
|
||||
|
||||
lastPlan = block;
|
||||
//add build plan
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, block.block, block.config));
|
||||
//shift build plan to tail so next unit builds something else
|
||||
blocks.addLast(blocks.removeFirst());
|
||||
}else{
|
||||
//shift head of queue to tail, try something else next time
|
||||
blocks.addLast(blocks.removeFirst());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStance(@Nullable UnitStance stance){
|
||||
return stance != null && stances.get(stance.id);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -28,11 +29,15 @@ public class RepairAI extends AIController{
|
||||
unit.controlWeapons(false);
|
||||
}
|
||||
|
||||
boolean hold = hasStance(UnitStance.holdPosition);
|
||||
|
||||
if(target != null && target instanceof Building b && b.team == unit.team){
|
||||
if(unit.type.circleTarget){
|
||||
circleAttack(unit.type.circleTargetRadius);
|
||||
}else if(!target.within(unit, unit.type.range * 0.65f)){
|
||||
moveTo(target, unit.type.range * 0.65f);
|
||||
if(!hold){
|
||||
if(unit.type.circleTarget){
|
||||
circleAttack(unit.type.circleTargetRadius);
|
||||
}else if(!target.within(unit, unit.type.range * 0.65f)){
|
||||
moveTo(target, unit.type.range * 0.65f);
|
||||
}
|
||||
}
|
||||
|
||||
if(!unit.type.circleTarget){
|
||||
@@ -41,7 +46,7 @@ public class RepairAI extends AIController{
|
||||
}
|
||||
|
||||
//not repairing
|
||||
if(!(target instanceof Building)){
|
||||
if(!(target instanceof Building) && !hold){
|
||||
if(timer.get(timerTarget4, 40)){
|
||||
avoid = target(unit.x, unit.y, fleeRange, true, true);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
@@ -53,6 +54,13 @@ public class AIController implements UnitController{
|
||||
updateMovement();
|
||||
}
|
||||
|
||||
public boolean hasStance(@Nullable UnitStance stance){
|
||||
if(unit.controller() instanceof AIController ai){
|
||||
return ai.hasStance(stance);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Called when the parent CommandAI changes its stance. */
|
||||
public void stanceChanged(){}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public class Binding{
|
||||
unitStancePatrol = KeyBind.add("unit_stance_patrol", KeyCode.unset),
|
||||
unitStanceRam = KeyBind.add("unit_stance_ram", KeyCode.unset),
|
||||
unitStanceBoost = KeyBind.add("unit_stance_boost", KeyCode.unset),
|
||||
unitStanceHoldPosition = KeyBind.add("unit_stance_hold_position", KeyCode.unset),
|
||||
|
||||
unitCommandMove = KeyBind.add("unit_command_move", KeyCode.unset),
|
||||
unitCommandRepair = KeyBind.add("unit_command_repair", KeyCode.unset),
|
||||
|
||||
@@ -648,8 +648,12 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
/** Adds all available unit stances based on the unit's current state. This can change based on the command of the unit. */
|
||||
public void getUnitStances(Unit unit, Seq<UnitStance> out){
|
||||
if(!(unit.controller() instanceof CommandAI ai)) return;
|
||||
|
||||
var current = ai.currentCommand();
|
||||
|
||||
//return mining stances based on present items
|
||||
if(unit.controller() instanceof CommandAI ai && ai.currentCommand() == UnitCommand.mineCommand){
|
||||
if(current == UnitCommand.mineCommand){
|
||||
out.add(UnitStance.mineAuto);
|
||||
for(Item item : indexer.getAllPresentOres()){
|
||||
if(unit.canMine(item) && ((mineFloor && indexer.hasOre(item)) || (mineWalls && indexer.hasWallOre(item)))){
|
||||
@@ -660,13 +664,15 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
}
|
||||
}
|
||||
}else{
|
||||
var command = unit.controller() instanceof CommandAI ai ? ai.command : null;
|
||||
for(var stance : stances){
|
||||
if(stance.isCompatible(command)){
|
||||
if(stance.isCompatible(current)){
|
||||
out.add(stance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//there might be duplicates, but that shouldn't cause issues
|
||||
out.addAll(current.extraStances);
|
||||
}
|
||||
|
||||
public boolean allowStance(Unit unit, UnitStance stance){
|
||||
|
||||
@@ -35,7 +35,6 @@ task incrementConfig{
|
||||
dependencies{
|
||||
implementation project(":core")
|
||||
|
||||
implementation arcModule("natives:natives-ios")
|
||||
implementation arcModule("natives:natives-freetype-ios")
|
||||
implementation arcModule("backends:backend-robovm")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user