mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-12-06 02:40:23 -08:00
Unit command prototype
This commit is contained in:
parent
2fabd39ea1
commit
f9efbb6686
15 changed files with 146 additions and 20 deletions
35
core/src/mindustry/ai/types/CommandAI.java
Normal file
35
core/src/mindustry/ai/types/CommandAI.java
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class CommandAI extends AIController{
|
||||
public @Nullable Vec2 targetPos;
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
//TODO
|
||||
|
||||
if(targetPos != null){
|
||||
//if(unit.isFlying()){
|
||||
moveTo(targetPos, 5f);
|
||||
//}
|
||||
|
||||
if(unit.isFlying()){
|
||||
unit.lookAt(targetPos);
|
||||
}else{
|
||||
faceTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void commandPosition(Vec2 pos){
|
||||
targetPos = pos;
|
||||
}
|
||||
|
||||
public void commandTarget(Teamc moveTo){
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -3535,7 +3535,8 @@ public class Blocks{
|
|||
size = 3;
|
||||
}};
|
||||
|
||||
//TODO setup, sprite, balance...
|
||||
//TODO setup, sprite, balance... or just scrap it completely.
|
||||
if(false)
|
||||
droneCenter = new DroneCenter("drone-center"){{
|
||||
requirements(Category.units, with(Items.graphite, 10));
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,12 @@ public class Fx{
|
|||
}
|
||||
}),
|
||||
|
||||
moveCommand = new Effect(15, e -> {
|
||||
color(Pal.command);
|
||||
stroke(e.fout() * 5f);
|
||||
Lines.circle(e.x, e.y, 6f + e.fin() * 2f);
|
||||
}).layer(Layer.effect - 20f),
|
||||
|
||||
commandSend = new Effect(28, e -> {
|
||||
color(Pal.command);
|
||||
stroke(e.fout() * 2f);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import arc.*;
|
|||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
|
|
@ -114,11 +115,16 @@ public class Logic implements ApplicationListener{
|
|||
|
||||
if(state.isCampaign()){
|
||||
//enable building AI on campaign unless the preset disables it
|
||||
//TODO should be configurable, I don't want building AI everywhere.
|
||||
//TODO should be (more) configurable, I don't want building AI everywhere.
|
||||
if(state.getSector().planet.defaultAI && !(state.getSector().preset != null && !state.getSector().preset.useAI)){
|
||||
state.rules.waveTeam.rules().ai = true;
|
||||
}
|
||||
|
||||
//TODO unit commanding is not allowed on serpulo until I test it properly
|
||||
if(state.getSector().planet != Planets.serpulo){
|
||||
state.rules.unitCommand = true;
|
||||
}
|
||||
|
||||
state.rules.coreIncinerates = true;
|
||||
state.rules.waveTeam.rules().aiTier = state.getSector().threat * 0.8f;
|
||||
state.rules.waveTeam.rules().infiniteResources = true;
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ abstract class CommanderComp implements Entityc, Posc{
|
|||
//reset controlled units
|
||||
for(Unit unit : controlling){
|
||||
if(unit.controller().isBeingControlled(self())){
|
||||
unit.controller(unit.type.createController());
|
||||
unit.controller(unit.type.createController(unit));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||
textFadeTime = 0f;
|
||||
x = y = 0f;
|
||||
if(!dead()){
|
||||
unit.controller(unit.type.createController());
|
||||
unit.resetController();
|
||||
unit = Nulls.unit;
|
||||
}
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||
|
||||
if(this.unit != Nulls.unit){
|
||||
//un-control the old unit
|
||||
this.unit.controller(this.unit.type.createController());
|
||||
this.unit.resetController();
|
||||
}
|
||||
this.unit = unit;
|
||||
if(unit != Nulls.unit){
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||
}
|
||||
|
||||
public void resetController(){
|
||||
controller(type.createController());
|
||||
controller(type.createController(self()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -291,6 +291,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||
return controller instanceof AIController;
|
||||
}
|
||||
|
||||
public boolean isCommandable(){
|
||||
return controller instanceof CommandAI;
|
||||
}
|
||||
|
||||
public int count(){
|
||||
return team.data().countType(type);
|
||||
}
|
||||
|
|
@ -307,7 +311,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||
this.hitSize = type.hitSize;
|
||||
this.hovering = type.hovering;
|
||||
|
||||
if(controller == null) controller(type.createController());
|
||||
if(controller == null) controller(type.createController(self()));
|
||||
if(mounts().length != type.weapons.size) setupWeapons(type);
|
||||
if(abilities.length != type.abilities.size){
|
||||
abilities = new Ability[type.abilities.size];
|
||||
|
|
@ -328,7 +332,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||
public void afterRead(){
|
||||
afterSync();
|
||||
//reset controller state
|
||||
controller(type.createController());
|
||||
controller(type.createController(self()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ public class Rules{
|
|||
public boolean damageExplosions = true;
|
||||
/** Whether fire is enabled. */
|
||||
public boolean fire = true;
|
||||
/** Erekir-specific: If true, unit RTS controls can be used. */
|
||||
public boolean unitCommand = false;
|
||||
/** Whether units use and require ammo. */
|
||||
public boolean unitAmmo = false;
|
||||
/** EXPERIMENTAL! If true, blocks will update in units and share power. */
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import arc.struct.*;
|
|||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
|
@ -236,23 +235,19 @@ public class Teams{
|
|||
public UnitCommand command = UnitCommand.attack;
|
||||
|
||||
/** Quadtree for all buildings of this team. Null if not active. */
|
||||
@Nullable
|
||||
public QuadTree<Building> buildings;
|
||||
public @Nullable QuadTree<Building> buildings;
|
||||
/** Current unit cap. Do not modify externally. */
|
||||
public int unitCap;
|
||||
/** Total unit count. */
|
||||
public int unitCount;
|
||||
/** Counts for each type of unit. Do not access directly. */
|
||||
@Nullable
|
||||
public int[] typeCounts;
|
||||
public @Nullable int[] typeCounts;
|
||||
/** Quadtree for units of this team. Do not access directly. */
|
||||
@Nullable
|
||||
public QuadTree<Unit> tree;
|
||||
public @Nullable QuadTree<Unit> tree;
|
||||
/** Units of this team. Updated each frame. */
|
||||
public Seq<Unit> units = new Seq<>();
|
||||
/** Units of this team by type. Updated each frame. */
|
||||
@Nullable
|
||||
public Seq<Unit>[] unitsByType;
|
||||
public @Nullable Seq<Unit>[] unitsByType;
|
||||
|
||||
public TeamData(Team team){
|
||||
this.team = team;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public enum Binding implements KeyBind{
|
|||
pan(KeyCode.mouseForward),
|
||||
|
||||
boost(KeyCode.shiftLeft),
|
||||
commandMode(KeyCode.shiftLeft),
|
||||
control(KeyCode.controlLeft),
|
||||
respawn(KeyCode.v),
|
||||
select(KeyCode.mouseLeft),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import arc.scene.ui.*;
|
|||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
|
|
@ -114,6 +116,27 @@ public class DesktopInput extends InputHandler{
|
|||
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
|
||||
}
|
||||
|
||||
for(Unit unit : selectedUnits){
|
||||
CommandAI ai = (CommandAI)unit.controller();
|
||||
//draw target line
|
||||
if(ai.targetPos != null){
|
||||
Tmp.v1.set(ai.targetPos).sub(unit).setLength(unit.hitSize / 2f);
|
||||
|
||||
Drawf.dashLine(Pal.accent, unit.x + Tmp.v1.x, unit.y + Tmp.v1.y, ai.targetPos.x, ai.targetPos.y);
|
||||
}
|
||||
|
||||
Drawf.square(unit.x, unit.y, unit.hitSize / 1.4f + 1f);
|
||||
}
|
||||
|
||||
//draw command overlay UI
|
||||
if(commandMode){
|
||||
Unit sel = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||
|
||||
if(sel != null){
|
||||
Drawf.square(sel.x, sel.y, sel.hitSize / 1.4f + Mathf.absin(4f, 1f), selectedUnits.contains(sel) ? Pal.remove : Pal.accent);
|
||||
}
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
|
@ -220,11 +243,19 @@ public class DesktopInput extends InputHandler{
|
|||
Core.camera.position.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * panScale, -1, 1) * camSpeed;
|
||||
Core.camera.position.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * panScale, -1, 1) * camSpeed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
commandMode = input.keyDown(Binding.commandMode) && !locked && state.rules.unitCommand && block == null;
|
||||
shouldShoot = !scene.hasMouse() && !locked;
|
||||
|
||||
//TODO should selected units be cleared out of command mode?
|
||||
if(!commandMode){
|
||||
selectedUnits.clear();
|
||||
}
|
||||
|
||||
//validate commanding units
|
||||
selectedUnits.removeAll(u -> !u.isCommandable());
|
||||
|
||||
if(!scene.hasMouse() && !locked){
|
||||
if(Core.input.keyDown(Binding.control) && Core.input.keyTap(Binding.select)){
|
||||
Unit on = selectedUnit();
|
||||
|
|
@ -503,6 +534,26 @@ public class DesktopInput extends InputHandler{
|
|||
sreq = req;
|
||||
}else if(req != null && req.breaking){
|
||||
deleting = true;
|
||||
}else if(commandMode){
|
||||
Unit unit = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||
if(unit != null){
|
||||
if(selectedUnits.contains(unit)){
|
||||
selectedUnits.remove(unit);
|
||||
}else{
|
||||
selectedUnits.add(unit);
|
||||
}
|
||||
}else if(selectedUnits.size > 0){
|
||||
//move to location - TODO right click instead?
|
||||
|
||||
//TODO all this needs to be synced, done with packets, etc
|
||||
Vec2 target = input.mouseWorld().cpy();
|
||||
|
||||
for(var sel : selectedUnits){
|
||||
((CommandAI)sel.controller()).commandPosition(target);
|
||||
}
|
||||
|
||||
Fx.moveCommand.at(target);
|
||||
}
|
||||
}else if(selected != null){
|
||||
//only begin shooting if there's no cursor event
|
||||
if(!tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !tileTapped(selected.build) && !player.unit().activelyBuilding() && !droppingItem
|
||||
|
|
@ -520,7 +571,7 @@ public class DesktopInput extends InputHandler{
|
|||
}else if(Core.input.keyTap(Binding.deselect) && !selectRequests.isEmpty()){
|
||||
selectRequests.clear();
|
||||
lastSchematic = null;
|
||||
}else if(Core.input.keyTap(Binding.break_block) && !Core.scene.hasMouse() && player.isBuilder()){
|
||||
}else if(Core.input.keyTap(Binding.break_block) && !Core.scene.hasMouse() && player.isBuilder() && !commandMode){
|
||||
//is recalculated because setting the mode to breaking removes potential multiblock cursor offset
|
||||
deleting = false;
|
||||
mode = breaking;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
/** Maximum line length. */
|
||||
final static int maxLength = 100;
|
||||
final static Rect r1 = new Rect(), r2 = new Rect();
|
||||
final static Seq<Unit> tmpUnits = new Seq<>();
|
||||
|
||||
public final OverlayFragment frag = new OverlayFragment();
|
||||
|
||||
|
|
@ -71,6 +72,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
public Seq<BuildPlan> lineRequests = new Seq<>();
|
||||
public Seq<BuildPlan> selectRequests = new Seq<>();
|
||||
|
||||
//for RTS controls
|
||||
public Seq<Unit> selectedUnits = new Seq<>();
|
||||
public boolean commandMode = false;
|
||||
|
||||
private Seq<BuildPlan> plansOut = new Seq<>(BuildPlan.class);
|
||||
private QuadTree<BuildPlan> playerPlanTree = new QuadTree<>(new Rect());
|
||||
|
||||
|
|
@ -1183,6 +1188,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
return null;
|
||||
}
|
||||
|
||||
public @Nullable Unit selectedCommandUnit(float x, float y){
|
||||
var tree = player.team().data().tree();
|
||||
tmpUnits.clear();
|
||||
float rad = 4f;
|
||||
tree.intersect(x - rad/2f, y - rad/2f, rad, rad, tmpUnits);
|
||||
return tmpUnits.min(u -> u.isCommandable(), u -> u.dst(x, y) - u.hitSize/2f);
|
||||
}
|
||||
|
||||
public void remove(){
|
||||
Core.input.removeProcessor(this);
|
||||
frag.remove();
|
||||
|
|
@ -1225,7 +1238,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||
|
||||
public boolean canShoot(){
|
||||
return block == null && !onConfigurable() && !isDroppingItem() && !player.unit().activelyBuilding() &&
|
||||
!(player.unit() instanceof Mechc && player.unit().isFlying()) && !player.unit().mining();
|
||||
!(player.unit() instanceof Mechc && player.unit().isFlying()) && !player.unit().mining() && !commandMode;
|
||||
}
|
||||
|
||||
public boolean onConfigurable(){
|
||||
|
|
|
|||
|
|
@ -442,6 +442,7 @@ public class ErekirPlanetGenerator extends PlanetGenerator{
|
|||
//it is very hot
|
||||
state.rules.attributes.set(Attribute.heat, 0.8f);
|
||||
state.rules.environment = sector.planet.defaultEnv;
|
||||
state.rules.unitCommand = true;
|
||||
|
||||
//TODO remove slag and arkycite around core.
|
||||
Schematics.placeLaunchLoadout(spawnX, spawnY);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ public class UnitType extends UnlockableContent{
|
|||
public Prov<? extends Unit> constructor;
|
||||
/** The default AI controller to assign on creation. */
|
||||
public Prov<? extends UnitController> defaultController = () -> !flying ? new GroundAI() : new FlyingAI();
|
||||
/** Function that chooses AI controller based on unit entity. */
|
||||
public Func<Unit, ? extends UnitController> unitBasedDefaultController = u -> defaultController.get();
|
||||
|
||||
/** Environmental flags that are *all* required for this unit to function. 0 = any environment */
|
||||
public int envRequired = 0;
|
||||
|
|
@ -208,10 +210,16 @@ public class UnitType extends UnlockableContent{
|
|||
constructor = EntityMapping.map(this.name);
|
||||
}
|
||||
|
||||
/** @deprecated use the createController method instead */
|
||||
@Deprecated
|
||||
public UnitController createController(){
|
||||
return defaultController.get();
|
||||
}
|
||||
|
||||
public UnitController createController(Unit unit){
|
||||
return unitBasedDefaultController.get(unit);
|
||||
}
|
||||
|
||||
public Unit create(Team team){
|
||||
Unit unit = constructor.get();
|
||||
unit.team = team;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mindustry.type.unit;
|
||||
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
|
@ -12,7 +13,9 @@ public class ErekirUnitType extends UnitType{
|
|||
commandLimit = 0;
|
||||
outlineColor = Pal.darkOutline;
|
||||
envDisabled = Env.space;
|
||||
//TODO necessary, or not?
|
||||
defaultAI = false;
|
||||
coreUnitDock = true;
|
||||
unitBasedDefaultController = u -> !playerControllable || u.team.isAI() ? defaultController.get() : new CommandAI();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue