Merged Unit & Builder components / Cleanup

This commit is contained in:
Anuken 2020-11-17 19:40:18 -05:00
parent 8280166485
commit 527be41e32
38 changed files with 152 additions and 125 deletions

View file

@ -1,10 +1,13 @@
#Maps entity names to IDs. Autogenerated.
alpha=0
arkyid=29
atrax=1
beta=30
block=2
corvus=24
flare=3
gamma=31
mace=4
mega=5
mindustry.entities.comp.BuildingComp=6
@ -26,6 +29,8 @@ oct=26
poly=18
pulsar=19
quad=23
quasar=32
risso=20
spiroct=21
toxopid=33
vela=25

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View file

@ -1279,6 +1279,7 @@ liquid.slag.description = Refined in separators into constituent metals, or spra
liquid.oil.description = Used in advanced material production and as incendiary ammunition.
liquid.cryofluid.description = Used as coolant in reactors, turrets and factories.
block.resupply-point.description = Resupplies nearby units with copper ammunition. Not compatible with units that require battery power.
block.armored-conveyor.description = Moves items forward. Does not accept inputs from the sides.
block.illuminator.description = Emits light.
block.message.description = Stores a message for communication between allies.

View file

@ -14,21 +14,20 @@ import static mindustry.Vars.*;
public class BuilderAI extends AIController{
float buildRadius = 1500;
boolean found = false;
@Nullable Builderc following;
@Nullable Unit following;
@Override
public void updateMovement(){
Builderc builder = (Builderc)unit;
if(builder.moving()){
builder.lookAt(builder.vel().angle());
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
if(target != null && shouldShoot()){
unit.lookAt(target);
}
builder.updateBuilding(true);
unit.updateBuilding = true;
if(following != null){
//try to follow and mimic someone
@ -36,18 +35,18 @@ public class BuilderAI extends AIController{
//validate follower
if(!following.isValid() || !following.activelyBuilding()){
following = null;
builder.plans().clear();
unit.plans.clear();
return;
}
//set to follower's first build plan, whatever that is
builder.plans().clear();
builder.plans().addFirst(following.buildPlan());
unit.plans.clear();
unit.plans.addFirst(following.buildPlan());
}
if(builder.buildPlan() != null){
if(unit.buildPlan() != null){
//approach request if building
BuildPlan req = builder.buildPlan();
BuildPlan req = unit.buildPlan();
boolean valid =
(req.tile().build instanceof ConstructBuild && req.tile().<ConstructBuild>bc().cblock == req.block) ||
@ -60,7 +59,7 @@ public class BuilderAI extends AIController{
moveTo(req.tile(), buildingRange - 20f);
}else{
//discard invalid request
builder.plans().removeFirst();
unit.plans.removeFirst();
}
}else{
@ -71,8 +70,8 @@ public class BuilderAI extends AIController{
Units.nearby(unit.team, unit.x, unit.y, buildRadius, u -> {
if(found) return;
if(u instanceof Builderc b && u != unit && b.activelyBuilding()){
BuildPlan plan = b.buildPlan();
if(u.canBuild() && u != unit && u.activelyBuilding()){
BuildPlan plan = u.buildPlan();
Building build = world.build(plan.x, plan.y);
if(build instanceof ConstructBuild cons){
@ -80,7 +79,7 @@ public class BuilderAI extends AIController{
//make sure you can reach the request in time
if(dist / unit.speed() < cons.buildCost * 0.9f){
following = b;
following = u;
found = true;
}
}
@ -98,7 +97,7 @@ public class BuilderAI extends AIController{
blocks.removeFirst();
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
//add build request.
builder.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
//shift build plan to tail so next unit builds something else.
blocks.addLast(blocks.removeFirst());
}else{

View file

@ -59,13 +59,13 @@ public class FormationAI extends AIController implements FormationMember{
unit.moveAt(realtarget.sub(unit).limit(speed));
}
if(unit instanceof Minerc mine && leader instanceof Minerc com){
if(com.mineTile() != null && mine.validMine(com.mineTile())){
mine.mineTile(com.mineTile());
if(unit.canMine() && leader.canMine()){
if(leader.mineTile != null && unit.validMine(leader.mineTile)){
unit.mineTile(leader.mineTile);
CoreBuild core = unit.team.core();
if(core != null && com.mineTile().drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(com.mineTile().drop())){
if(core != null && leader.mineTile.drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(leader.mineTile.drop())){
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
@ -73,13 +73,13 @@ public class FormationAI extends AIController implements FormationMember{
}
}
}else{
mine.mineTile(null);
unit.mineTile(null);
}
}
if(unit instanceof Builderc build && leader instanceof Builderc com && com.activelyBuilding()){
build.clearBuilding();
build.addBuild(com.buildPlan());
if(unit.canBuild() && leader.canBuild() && leader.activelyBuilding()){
unit.clearBuilding();
leader.addBuild(unit.buildPlan());
}
}

View file

@ -92,9 +92,7 @@ public class LogicAI extends AIController{
}
}
case stop -> {
if(unit instanceof Builderc build){
build.clearBuilding();
}
unit.clearBuilding();
}
}

View file

@ -17,21 +17,21 @@ public class MinerAI extends AIController{
protected void updateMovement(){
Building core = unit.closestCore();
if(!(unit instanceof Minerc miner) || core == null) return;
if(!(unit.canMine()) || core == null) return;
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type.range)){
miner.mineTile(null);
if(unit.mineTile != null && !unit.mineTile.within(unit, unit.type.range)){
unit.mineTile(null);
}
if(mining){
if(timer.get(timerTarget2, 60 * 4) || targetItem == null){
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && unit.canMine(i), i -> core.items.get(i));
}
//core full of the target item, do nothing
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
unit.clearItem();
miner.mineTile(null);
unit.mineTile(null);
return;
}
@ -47,7 +47,7 @@ public class MinerAI extends AIController{
moveTo(ore, unit.type.range / 2f, 20f);
if(unit.within(ore, unit.type.range)){
miner.mineTile(ore);
unit.mineTile = ore;
}
if(ore.block() != Blocks.air){
@ -56,7 +56,7 @@ public class MinerAI extends AIController{
}
}
}else{
miner.mineTile(null);
unit.mineTile = null;
if(unit.stack.amount == 0){
mining = true;

View file

@ -17,11 +17,13 @@ import static mindustry.Vars.*;
public class UnitTypes implements ContentList{
//region definitions
//(the wall of shame - should fix the legacy stuff eventually...)
//mech
public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, scepter, reign;
//mech + builder + miner
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class}) UnitType nova, pulsar, quasar;
//mech
public static @EntityDef(value = {Unitc.class, Mechc.class}, legacy = true) UnitType nova, pulsar, quasar;
//mech
public static @EntityDef({Unitc.class, Mechc.class}) UnitType vela;
@ -29,29 +31,29 @@ public class UnitTypes implements ContentList{
//legs
public static @EntityDef({Unitc.class, Legsc.class}) UnitType corvus, atrax;
//legs + building
public static @EntityDef({Unitc.class, Legsc.class, Builderc.class}) UnitType spiroct, arkyid, toxopid;
//legs
public static @EntityDef(value = {Unitc.class, Legsc.class}, legacy = true) UnitType spiroct, arkyid, toxopid;
//air (no special traits)
//air
public static @EntityDef({Unitc.class}) UnitType flare, eclipse, horizon, zenith, antumbra;
//air, legacy mining
//air
public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType mono;
//air + building + mining
public static @EntityDef({Unitc.class, Builderc.class}) UnitType poly;
//air
public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType poly;
//air + building + mining + payload
public static @EntityDef({Unitc.class, Builderc.class, Payloadc.class}) UnitType mega;
//air + payload
public static @EntityDef({Unitc.class, Payloadc.class}) UnitType mega;
//air + building + payload
public static @EntityDef(value = {Unitc.class, Builderc.class, Payloadc.class}, legacy = true) UnitType quad;
//air + payload
public static @EntityDef(value = {Unitc.class, Payloadc.class}, legacy = true) UnitType quad;
//air + building + payload
public static @EntityDef({Unitc.class, Builderc.class, Payloadc.class, AmmoDistributec.class}) UnitType oct;
//air + payload + ammo distribution
public static @EntityDef({Unitc.class, Payloadc.class, AmmoDistributec.class}) UnitType oct;
//air + building + mining
public static @EntityDef({Unitc.class, Builderc.class}) UnitType alpha, beta, gamma;
//air
public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType alpha, beta, gamma;
//water
public static @EntityDef({Unitc.class, WaterMovec.class}) UnitType risso, minke, bryde, sei, omura;
@ -470,7 +472,6 @@ public class UnitTypes implements ContentList{
mineTier = 1;
hitSize = 29f;
health = 18000f;
buildSpeed = 1.7f;
armor = 9f;
landShake = 1.5f;
rotateSpeed = 1.5f;
@ -700,6 +701,7 @@ public class UnitTypes implements ContentList{
rippleScale = 2f;
legSpeed = 0.2f;
ammoType = AmmoTypes.power;
buildSpeed = 1f;
legSplashDamage = 32;
legSplashRange = 30;
@ -802,6 +804,7 @@ public class UnitTypes implements ContentList{
rippleScale = 3f;
legSpeed = 0.19f;
ammoType = AmmoTypes.powerHigh;
buildSpeed = 1f;
legSplashDamage = 80;
legSplashRange = 60;

View file

@ -571,13 +571,13 @@ public class NetClient implements ApplicationListener{
BuildPlan[] requests = null;
if(player.isBuilder()){
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.builder().plans().size, 10);
int usedRequests = Math.min(player.unit().plans().size, 10);
int totalLength = 0;
//prevent buffer overflow by checking config length
for(int i = 0; i < usedRequests; i++){
BuildPlan plan = player.builder().plans().get(i);
BuildPlan plan = player.unit().plans().get(i);
if(plan.config instanceof byte[] b){
int length = b.length;
totalLength += length;
@ -591,7 +591,7 @@ public class NetClient implements ApplicationListener{
requests = new BuildPlan[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.builder().plans().get(i);
requests[i] = player.unit().plans().get(i);
}
}
@ -607,7 +607,7 @@ public class NetClient implements ApplicationListener{
unit.rotation,
unit instanceof Mechc m ? m.baseRotation() : 0,
unit.vel.x, unit.vel.y,
player.unit().mineTile(),
player.unit().mineTile,
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,

View file

@ -600,8 +600,8 @@ public class NetServer implements ApplicationListener{
player.unit().aim(pointerX, pointerY);
if(player.isBuilder()){
player.builder().clearBuilding();
player.builder().updateBuilding(building);
player.unit().clearBuilding();
player.unit().updateBuilding(building);
if(requests != null){
for(BuildPlan req : requests){
@ -625,7 +625,7 @@ public class NetServer implements ApplicationListener{
con.rejectedRequests.add(req);
continue;
}
player.builder().plans().addLast(req);
player.unit().plans().addLast(req);
}
}
}

View file

@ -13,6 +13,7 @@ import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
@ -22,17 +23,22 @@ import java.util.*;
import static mindustry.Vars.*;
@Component
abstract class BuilderComp implements Unitc{
abstract class BuilderComp implements Posc, Teamc, Rotc{
static final Vec2[] vecs = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
@Import float x, y, rotation;
@Import UnitType type;
@SyncLocal Queue<BuildPlan> plans = new Queue<>(1);
@SyncLocal transient boolean updateBuilding = true;
public boolean canBuild(){
return type.buildSpeed > 0;
}
@Override
public void update(){
if(!updateBuilding) return;
if(!updateBuilding || !canBuild()) return;
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange;
boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources;
@ -102,9 +108,9 @@ abstract class BuilderComp implements Unitc{
ConstructBuild entity = tile.bc();
if(current.breaking){
entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier);
entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * state.rules.buildSpeedMultiplier);
}else{
entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.config);
entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * state.rules.buildSpeedMultiplier, current.config);
}
current.stuck = Mathf.equal(current.progress, entity.progress);
@ -161,6 +167,8 @@ abstract class BuilderComp implements Unitc{
/** Add another build requests to the queue, if it doesn't exist there yet. */
void addBuild(BuildPlan place, boolean tail){
if(!canBuild()) return;
BuildPlan replace = null;
for(BuildPlan request : plans){
if(request.x == place.x && request.y == place.y){
@ -196,9 +204,8 @@ abstract class BuilderComp implements Unitc{
return plans.size == 0 ? null : plans.first();
}
@Override
public void draw(){
if(!isBuilding() || !updateBuilding) return;
if(!isBuilding() || !updateBuilding || !canBuild()) return;
//TODO check correctness
Draw.z(Layer.flyingUnit);

View file

@ -1122,7 +1122,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Returns whether or not a hand cursor should be shown over this block. */
public Cursor getCursor(){
return block.configurable ? SystemCursor.hand : SystemCursor.arrow;
return block.configurable && team == player.team() ? SystemCursor.hand : SystemCursor.arrow;
}
/**

View file

@ -32,7 +32,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
}
boolean mining(){
return mineTile != null && !(((Object)this) instanceof Builderc b && b.activelyBuilding());
return mineTile != null && !this.<Unit>self().activelyBuilding();
}
public boolean validMine(Tile tile, boolean checkDst){
@ -44,6 +44,10 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
return validMine(tile, true);
}
public boolean canMine(){
return type.mineSpeed > 0;
}
@Override
public void update(){
Building core = closestCore();

View file

@ -48,7 +48,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
transient float textFadeTime;
public boolean isBuilder(){
return unit instanceof Builderc;
return unit.canBuild();
}
public @Nullable CoreBuild closestCore(){
@ -160,10 +160,6 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return unit;
}
public Builderc builder(){
return !(unit instanceof Builderc) ? Nulls.builder : (Builderc)unit;
}
public void unit(Unit unit){
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit == unit) return;

View file

@ -31,7 +31,7 @@ import mindustry.world.blocks.payloads.*;
import static mindustry.Vars.*;
@Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Commanderc, Displayable, Senseable, Ranged, Minerc{
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Commanderc, Displayable, Senseable, Ranged, Minerc, Builderc{
@Import boolean hovering, dead;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo, minFormationSpeed;
@ -88,8 +88,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return where the unit wants to look at. */
public float prefRotation(){
if(this instanceof Builderc builder && builder.activelyBuilding()){
return angleTo(builder.buildPlan());
if(activelyBuilding()){
return angleTo(buildPlan());
}else if(mineTile() != null){
return angleTo(mineTile());
}else if(moving()){

View file

@ -28,7 +28,7 @@ public class OverlayRenderer{
if(player.dead()) return;
if(player.isBuilder()){
player.builder().drawBuildRequests();
player.unit().drawBuildRequests();
}
input.drawBottom();

View file

@ -59,7 +59,7 @@ public class DesktopInput extends InputHandler{
group.fill(t -> {
t.bottom();
t.visible(() -> {
t.color.a = Mathf.lerpDelta(t.color.a, player.builder().isBuilding() ? 1f : 0f, 0.15f);
t.color.a = Mathf.lerpDelta(t.color.a, player.unit().isBuilding() ? 1f : 0f, 0.15f);
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectRequests.isEmpty() && t.color.a > 0.01f;
});
@ -290,7 +290,7 @@ public class DesktopInput extends InputHandler{
cursorType = cursor.build.getCursor();
}
if(isPlacing() || !selectRequests.isEmpty()){
if((isPlacing() && player.isBuilder()) || !selectRequests.isEmpty()){
cursorType = SystemCursor.hand;
}
@ -366,7 +366,7 @@ public class DesktopInput extends InputHandler{
int rawCursorX = World.toTile(Core.input.mouseWorld().x), rawCursorY = World.toTile(Core.input.mouseWorld().y);
// automatically pause building if the current build queue is empty
if(Core.settings.getBool("buildautopause") && isBuilding && !player.builder().isBuilding()){
if(Core.settings.getBool("buildautopause") && isBuilding && !player.unit().isBuilding()){
isBuilding = false;
buildWasAutoPaused = true;
}
@ -388,7 +388,7 @@ public class DesktopInput extends InputHandler{
}
if(Core.input.keyTap(Binding.clear_building)){
player.builder().clearBuilding();
player.unit().clearBuilding();
}
if(Core.input.keyTap(Binding.schematic_select) && !Core.scene.hasKeyboard() && mode != breaking){
@ -480,8 +480,8 @@ public class DesktopInput extends InputHandler{
deleting = true;
}else if(selected != null){
//only begin shooting if there's no cursor event
if(!tileTapped(selected.build) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !player.builder().activelyBuilding() && !droppingItem &&
!tryBeginMine(selected) && player.unit().mineTile() == null && !Core.scene.hasKeyboard()){
if(!tileTapped(selected.build) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !player.unit().activelyBuilding() && !droppingItem &&
!tryBeginMine(selected) && player.unit().mineTile == null && !Core.scene.hasKeyboard()){
player.shooting = shouldShoot;
}
}else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine
@ -506,7 +506,7 @@ public class DesktopInput extends InputHandler{
if(Core.input.keyDown(Binding.select) && mode == none && !isPlacing() && deleting){
BuildPlan req = getRequest(cursorX, cursorY);
if(req != null && req.breaking){
player.builder().plans().remove(req);
player.unit().plans().remove(req);
}
}else{
deleting = false;
@ -547,7 +547,7 @@ public class DesktopInput extends InputHandler{
if(sreq != null){
if(getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null){
player.builder().plans().remove(sreq, true);
player.unit().plans().remove(sreq, true);
}
sreq = null;
}

View file

@ -169,7 +169,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(variants = Variant.one)
public static void removeQueueBlock(int x, int y, boolean breaking){
player.builder().removeBuild(x, y, breaking);
player.unit().removeBuild(x, y, breaking);
}
@Remote(targets = Loc.both, called = Loc.server)
@ -383,7 +383,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public Eachable<BuildPlan> allRequests(){
return cons -> {
for(BuildPlan request : player.builder().plans()) cons.get(request);
for(BuildPlan request : player.unit().plans()) cons.get(request);
for(BuildPlan request : selectRequests) cons.get(request);
for(BuildPlan request : lineRequests) cons.get(request);
};
@ -401,7 +401,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.typing = ui.chatfrag.shown();
if(player.isBuilder()){
player.builder().updateBuilding(isBuilding);
player.unit().updateBuilding(isBuilding);
}
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && player.unit().ammo <= 0){
@ -653,7 +653,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
return r2.overlaps(r1);
};
for(BuildPlan req : player.builder().plans()){
for(BuildPlan req : player.unit().plans()){
if(test.get(req)) return req;
}
@ -678,7 +678,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Draw.color(Pal.remove);
Lines.stroke(1f);
for(BuildPlan req : player.builder().plans()){
for(BuildPlan req : player.unit().plans()){
if(req.breaking) continue;
if(req.bounds(Tmp.r2).overlaps(Tmp.r1)){
drawBreaking(req);
@ -740,7 +740,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
for(BuildPlan req : requests){
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
BuildPlan copy = req.copy();
player.builder().addBuild(copy);
player.unit().addBuild(copy);
}
}
}
@ -804,7 +804,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//remove build requests
Tmp.r1.set(result.x * tilesize, result.y * tilesize, (result.x2 - result.x) * tilesize, (result.y2 - result.y) * tilesize);
Iterator<BuildPlan> it = player.builder().plans().iterator();
Iterator<BuildPlan> it = player.unit().plans().iterator();
while(it.hasNext()){
BuildPlan req = it.next();
if(!req.breaking && req.bounds(Tmp.r2).overlaps(Tmp.r1)){
@ -1044,7 +1044,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public boolean canShoot(){
return block == null && !onConfigurable() && !isDroppingItem() && !player.builder().activelyBuilding() &&
return block == null && !onConfigurable() && !isDroppingItem() && !player.unit().activelyBuilding() &&
!(player.unit() instanceof Mechc && player.unit().isFlying());
}
@ -1090,7 +1090,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public boolean validPlace(int x, int y, Block type, int rotation, BuildPlan ignore){
for(BuildPlan req : player.builder().plans()){
for(BuildPlan req : player.unit().plans()){
if(req != ignore
&& !req.breaking
&& req.block.bounds(req.x, req.y, Tmp.r1).overlaps(type.bounds(x, y, Tmp.r2))
@ -1108,15 +1108,15 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public void placeBlock(int x, int y, Block block, int rotation){
BuildPlan req = getRequest(x, y);
if(req != null){
player.builder().plans().remove(req);
player.unit().plans().remove(req);
}
player.builder().addBuild(new BuildPlan(x, y, rotation, block, block.nextConfig()));
player.unit().addBuild(new BuildPlan(x, y, rotation, block, block.nextConfig()));
}
public void breakBlock(int x, int y){
Tile tile = world.tile(x, y);
if(tile != null && tile.build != null) tile = tile.build.tile;
player.builder().addBuild(new BuildPlan(tile.x, tile.y));
player.unit().addBuild(new BuildPlan(tile.x, tile.y));
}
public void drawArrow(Block block, int x, int y, int rotation){

View file

@ -114,7 +114,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
for(BuildPlan req : player.builder().plans()){
for(BuildPlan req : player.unit().plans()){
Tile other = world.tile(req.x, req.y);
if(other == null || req.breaking) continue;
@ -219,10 +219,10 @@ public class MobileInput extends InputHandler implements GestureListener{
BuildPlan copy = request.copy();
if(other == null){
player.builder().addBuild(copy);
player.unit().addBuild(copy);
}else if(!other.breaking && other.x == request.x && other.y == request.y && other.block.size == request.block.size){
player.builder().plans().remove(other);
player.builder().addBuild(copy);
player.unit().plans().remove(other);
player.unit().addBuild(copy);
}
}
@ -245,10 +245,10 @@ public class MobileInput extends InputHandler implements GestureListener{
Boolp schem = () -> lastSchematic != null && !selectRequests.isEmpty();
group.fill(t -> {
t.visible(() -> (player.builder().isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get());
t.visible(() -> (player.unit().isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get());
t.bottom().left();
t.button("@cancel", Icon.cancel, () -> {
player.builder().clearBuilding();
player.unit().clearBuilding();
selectRequests.clear();
mode = none;
block = null;
@ -914,7 +914,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//update shooting if not building + not mining
if(!player.builder().isBuilding() && player.unit().mineTile() == null){
if(!player.unit().isBuilding() && player.unit().mineTile == null){
//autofire targeting
if(manualShooting){

View file

@ -321,13 +321,8 @@ public class LExecutor{
((LogicAI)unit.controller()).controller = exec.building(varThis);
//clear old state
if(unit instanceof Minerc miner){
miner.mineTile(null);
}
if(unit instanceof Builderc builder){
builder.clearBuilding();
}
unit.mineTile = null;
unit.clearBuilding();
return (LogicAI)unit.controller();
}
@ -357,12 +352,8 @@ public class LExecutor{
//stop mining/building
if(type == LUnitControl.stop){
if(unit instanceof Minerc miner){
miner.mineTile(null);
}
if(unit instanceof Builderc build){
build.clearBuilding();
}
unit.mineTile = null;
unit.clearBuilding();
}
}
case within -> {
@ -390,8 +381,8 @@ public class LExecutor{
}
case mine -> {
Tile tile = world.tileWorld(x1, y1);
if(unit instanceof Minerc miner){
miner.mineTile(miner.validMine(tile) ? tile : null);
if(unit.canMine()){
unit.mineTile = unit.validMine(tile) ? tile : null;
}
}
case payDrop -> {
@ -432,12 +423,12 @@ public class LExecutor{
}
}
case build -> {
if(unit instanceof Builderc builder && exec.obj(p3) instanceof Block block){
if(unit.canBuild() && exec.obj(p3) instanceof Block block){
int x = World.toTile(x1), y = World.toTile(y1);
int rot = exec.numi(p4);
//reset state of last request when necessary
if(ai.plan.x != x || ai.plan.y != y || ai.plan.block != block || builder.plans().isEmpty()){
if(ai.plan.x != x || ai.plan.y != y || ai.plan.block != block || unit.plans.isEmpty()){
ai.plan.progress = 0;
ai.plan.initialized = false;
ai.plan.stuck = false;
@ -446,11 +437,11 @@ public class LExecutor{
ai.plan.set(x, y, rot, block);
ai.plan.config = exec.obj(p5) instanceof Content c ? c : null;
builder.clearBuilding();
unit.clearBuilding();
if(ai.plan.tile() != null){
builder.updateBuilding(true);
builder.addBuild(ai.plan);
unit.updateBuilding = true;
unit.addBuild(ai.plan);
}
}
}

View file

@ -80,7 +80,7 @@ public class UnitType extends UnlockableContent{
public int ammoCapacity = -1;
public AmmoType ammoType = AmmoTypes.copper;
public int mineTier = -1;
public float buildSpeed = 1f, mineSpeed = 1f;
public float buildSpeed = -1f, mineSpeed = 1f;
public Sound mineSound = Sounds.minebeam;
public float mineSoundVolume = 0.6f;
@ -225,7 +225,7 @@ public class UnitType extends UnlockableContent{
stats.addPercent(Stat.mineSpeed, mineSpeed);
stats.add(Stat.mineTier, new BlockFilterValue(b -> b instanceof Floor f && f.itemDrop != null && f.itemDrop.hardness <= mineTier && !f.playerUnmineable));
}
if(inst instanceof Builderc){
if(buildSpeed > 0){
stats.addPercent(Stat.buildSpeed, buildSpeed);
}
if(inst instanceof Payloadc){

View file

@ -102,7 +102,7 @@ public class PlacementFragment extends Fragment{
Block tryRecipe = tile == null ? null : tile.block instanceof ConstructBlock ? ((ConstructBuild)tile).cblock : tile.block;
Object tryConfig = tile == null ? null : tile.config();
for(BuildPlan req : player.builder().plans()){
for(BuildPlan req : player.unit().plans()){
if(!req.breaking && req.block.bounds(req.x, req.y, Tmp.r1).contains(Core.input.mouseWorld())){
tryRecipe = req.block;
tryConfig = req.config;

View file

@ -179,7 +179,7 @@ public class ConstructBlock extends Block{
if(control.input.buildWasAutoPaused && !control.input.isBuilding && player.isBuilder()){
control.input.isBuilding = true;
}
player.builder().addBuild(new BuildPlan(tile.x, tile.y, rotation, cblock, lastConfig), false);
player.unit().addBuild(new BuildPlan(tile.x, tile.y, rotation, cblock, lastConfig), false);
}
}

View file

@ -57,6 +57,7 @@ public interface Payload{
return (T)payload;
}else if(type == payloadUnit){
byte id = read.b();
if(EntityMapping.map(id) == null) throw new RuntimeException("No type with ID " + id + " found.");
Unit unit = (Unit)EntityMapping.map(id).get();
unit.read(read);
return (T)new UnitPayload(unit);

View file

@ -433,6 +433,15 @@ public class ApplicationTests{
assertEquals(256, world.height());
}
@Test
void load114Save(){
resetWorld();
SaveIO.load(Core.files.internal("114.msav"));
assertEquals(500, world.width());
assertEquals(500, world.height());
}
@Test
void arrayIterators(){
Seq<String> arr = Seq.with("a", "b" , "c", "d", "e", "f");

Binary file not shown.