Files
Mindustry/core/src/mindustry/entities/comp/BuilderComp.java
2022-02-22 16:17:22 -05:00

330 lines
11 KiB
Java

package mindustry.entities.comp;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.struct.Queue;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
import java.util.*;
import static mindustry.Vars.*;
@Component
abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
@Import float x, y, rotation, buildSpeedMultiplier;
@Import UnitType type;
@Import Team team;
@SyncLocal Queue<BuildPlan> plans = new Queue<>(1);
@SyncLocal boolean updateBuilding = true;
private transient float buildCounter;
private transient BuildPlan lastActive;
private transient int lastSize;
transient float buildAlpha = 0f;
public boolean canBuild(){
return type.buildSpeed > 0 && buildSpeedMultiplier > 0;
}
@Override
public void update(){
updateBuildLogic();
}
public void updateBuildLogic(){
if(!headless){
//visual activity update
if(lastActive != null && buildAlpha <= 0.01f){
lastActive = null;
}
buildAlpha = Mathf.lerpDelta(buildAlpha, activelyBuilding() ? 1f : 0f, 0.15f);
}
if(!updateBuilding || !canBuild()) return;
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : type.buildRange;
boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources;
buildCounter += Time.delta;
while(buildCounter >= 1){
buildCounter -= 1f;
Iterator<BuildPlan> it = plans.iterator();
while(it.hasNext()){
BuildPlan plan = it.next();
Tile tile = world.tile(plan.x, plan.y);
if(tile == null || (plan.breaking && tile.block() == Blocks.air) || (!plan.breaking && ((tile.build != null && tile.build.rotation == plan.rotation) || !plan.block.rotate) && tile.block() == plan.block)){
it.remove();
}
}
var core = core();
//nothing to build.
if(buildPlan() == null) continue;
//find the next build plan
if(plans.size > 1){
int total = 0;
BuildPlan plan;
while((!within((plan = buildPlan()).tile(), finalPlaceDst) || shouldSkip(plan, core)) && total < plans.size){
plans.removeFirst();
plans.addLast(plan);
total++;
}
}
BuildPlan current = buildPlan();
Tile tile = current.tile();
lastActive = current;
buildAlpha = 1f;
if(current.breaking) lastSize = tile.block().size;
if(!within(tile, finalPlaceDst)) continue;
if(!headless){
Vars.control.sound.loop(Sounds.build, tile, 0.51f);
}
if(!(tile.build instanceof ConstructBuild cb)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
if(hasAll){
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
}else{
current.stuck = true;
}
}else if(!current.initialized && current.breaking && Build.validBreak(team, current.x, current.y)){
Call.beginBreak(self(), team, current.x, current.y);
}else{
plans.removeFirst();
continue;
}
}else if((tile.team() != team && tile.team() != Team.derelict) || (!current.breaking && (cb.current != current.block || cb.tile != current.tile()))){
plans.removeFirst();
continue;
}
if(tile.build instanceof ConstructBuild && !current.initialized){
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team, self(), current.breaking)));
current.initialized = true;
}
//if there is no core to build with or no build entity, stop building!
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild entity)){
continue;
}
float bs = 1f / entity.buildCost * Time.delta * type.buildSpeed * buildSpeedMultiplier * state.rules.buildSpeed(team);
//otherwise, update it.
if(current.breaking){
entity.deconstruct(self(), core, bs);
}else{
entity.construct(self(), core, bs, current.config);
}
current.stuck = Mathf.equal(current.progress, entity.progress);
current.progress = entity.progress;
}
}
/** Draw all current build plans. Does not draw the beam effect, only the positions. */
void drawBuildPlans(){
Boolf<BuildPlan> skip = plan -> plan.progress > 0.01f || (buildPlan() == plan && plan.initialized && (within(plan.x * tilesize, plan.y * tilesize, type.buildRange) || state.isEditor()));
for(int i = 0; i < 2; i++){
for(BuildPlan plan : plans){
if(skip.get(plan)) continue;
if(i == 0){
drawPlan(plan, 1f);
}else{
drawPlanTop(plan, 1f);
}
}
}
Draw.reset();
}
void drawPlan(BuildPlan plan, float alpha){
plan.animScale = 1f;
if(plan.breaking){
control.input.drawBreaking(plan);
}else{
plan.block.drawPlan(plan, control.input.allPlans(),
Build.validPlace(plan.block, team, plan.x, plan.y, plan.rotation) || control.input.planMatches(plan),
alpha);
}
}
void drawPlanTop(BuildPlan plan, float alpha){
if(!plan.breaking){
Draw.reset();
Draw.mixcol(Color.white, 0.24f + Mathf.absin(Time.globalTime, 6f, 0.28f));
Draw.alpha(alpha);
plan.block.drawPlanConfigTop(plan, plans);
}
}
/** @return whether this plan should be skipped, in favor of the next one. */
boolean shouldSkip(BuildPlan plan, @Nullable Building core){
//plans that you have at least *started* are considered
if(state.rules.infiniteResources || team.rules().infiniteResources || plan.breaking || core == null || plan.isRotation(team) || (isBuilding() && !within(plans.last(), type.buildRange))) return false;
return (plan.stuck && !core.items.has(plan.block.requirements)) || (Structs.contains(plan.block.requirements, i -> !core.items.has(i.item, Math.min(i.amount, 15)) && Mathf.round(i.amount * state.rules.buildCostMultiplier) > 0) && !plan.initialized);
}
void removeBuild(int x, int y, boolean breaking){
//remove matching plan
int idx = plans.indexOf(req -> req.breaking == breaking && req.x == x && req.y == y);
if(idx != -1){
plans.removeIndex(idx);
}
}
/** Return whether this builder's place queue contains items. */
boolean isBuilding(){
return plans.size != 0;
}
/** Clears the placement queue. */
void clearBuilding(){
plans.clear();
}
/** Add another build plans to the tail of the queue, if it doesn't exist there yet. */
void addBuild(BuildPlan place){
addBuild(place, true);
}
/** Add another build plans to the queue, if it doesn't exist there yet. */
void addBuild(BuildPlan place, boolean tail){
if(!canBuild()) return;
BuildPlan replace = null;
for(BuildPlan plan : plans){
if(plan.x == place.x && plan.y == place.y){
replace = plan;
break;
}
}
if(replace != null){
plans.remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.build instanceof ConstructBuild cons){
place.progress = cons.progress;
}
if(tail){
plans.addLast(place);
}else{
plans.addFirst(place);
}
}
boolean activelyBuilding(){
//not actively building when not near the build plan
if(isBuilding()){
if(!state.isEditor() && !within(buildPlan(), state.rules.infiniteResources ? Float.MAX_VALUE : type.buildRange)){
return false;
}
}
return isBuilding() && updateBuilding;
}
/** @return the build plan currently active, or the one at the top of the queue.*/
@Nullable BuildPlan buildPlan(){
return plans.size == 0 ? null : plans.first();
}
public void draw(){
drawBuilding();
}
public void drawBuilding(){
//TODO make this more generic so it works with builder "weapons"
boolean active = activelyBuilding();
if(!active && lastActive == null) return;
Draw.z(Layer.flyingUnit);
BuildPlan plan = active ? buildPlan() : lastActive;
Tile tile = plan.tile();
var core = team.core();
if(tile == null || !within(plan, state.rules.infiniteResources ? Float.MAX_VALUE : type.buildRange)){
return;
}
//draw remote plans.
if(core != null && active && !isLocal() && !(tile.block() instanceof ConstructBlock)){
Draw.z(Layer.plans - 1f);
drawPlan(plan, 0.5f);
drawPlanTop(plan, 0.5f);
Draw.z(Layer.flyingUnit);
}
if(type.drawBuildBeam){
float focusLen = type.buildBeamOffset + Mathf.absin(Time.time, 3f, 0.6f);
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
drawBuildingBeam(px, py);
}
}
public void drawBuildingBeam(float px, float py){
boolean active = activelyBuilding();
if(!active && lastActive == null) return;
Draw.z(Layer.flyingUnit);
BuildPlan plan = active ? buildPlan() : lastActive;
Tile tile = world.tile(plan.x, plan.y);
if(tile == null || !within(plan, state.rules.infiniteResources ? Float.MAX_VALUE : type.buildRange)){
return;
}
int size = plan.breaking ? active ? tile.block().size : lastSize : plan.block.size;
float tx = plan.drawx(), ty = plan.drawy();
Lines.stroke(1f, plan.breaking ? Pal.remove : Pal.accent);
Draw.z(Layer.buildBeam);
Draw.alpha(buildAlpha);
if(!active && !(tile.build instanceof ConstructBuild)){
Fill.square(plan.drawx(), plan.drawy(), size * tilesize/2f);
}
Drawf.buildBeam(px, py, tx, ty, Vars.tilesize * size / 2f);
Fill.square(px, py, 1.8f + Mathf.absin(Time.time, 2.2f, 1.1f), rotation + 45);
Draw.reset();
Draw.z(Layer.flyingUnit);
}
}