Merge branch 'master' into healbullets-bullets

This commit is contained in:
genNAowl
2020-10-05 16:04:29 -07:00
committed by GitHub
43 changed files with 735 additions and 224 deletions

View File

@@ -43,9 +43,9 @@ public class LogicStatementProcessor extends BaseProcessor{
String name = c.annotation(RegisterStatement.class).value(); String name = c.annotation(RegisterStatement.class).value();
if(beganWrite){ if(beganWrite){
writer.nextControlFlow("else if(obj instanceof $T)", c.mirror()); writer.nextControlFlow("else if(obj.getClass() == $T.class)", c.mirror());
}else{ }else{
writer.beginControlFlow("if(obj instanceof $T)", c.mirror()); writer.beginControlFlow("if(obj.getClass() == $T.class)", c.mirror());
beganWrite = true; beganWrite = true;
} }
@@ -53,6 +53,7 @@ public class LogicStatementProcessor extends BaseProcessor{
writer.addStatement("out.append($S)", name); writer.addStatement("out.append($S)", name);
Seq<Svar> fields = c.fields(); Seq<Svar> fields = c.fields();
fields.addAll(c.superclass().fields());
String readSt = "if(tokens[0].equals($S))"; String readSt = "if(tokens[0].equals($S))";
if(beganRead){ if(beganRead){

View File

@@ -14,6 +14,8 @@ mindustry.entities.comp.EffectStateComp=9
mindustry.entities.comp.FireComp=10 mindustry.entities.comp.FireComp=10
mindustry.entities.comp.LaunchCoreComp=11 mindustry.entities.comp.LaunchCoreComp=11
mindustry.entities.comp.PlayerComp=12 mindustry.entities.comp.PlayerComp=12
mindustry.entities.comp.PosTeam=27
mindustry.entities.comp.PosTeamDef=28
mindustry.entities.comp.PuddleComp=13 mindustry.entities.comp.PuddleComp=13
mindustry.type.Weather.WeatherStateComp=14 mindustry.type.Weather.WeatherStateComp=14
mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15 mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15

View File

@@ -0,0 +1 @@
{fields:[{name:team,type:mindustry.game.Team},{name:x,type:float},{name:y,type:float}]}

View File

@@ -638,6 +638,8 @@ bar.progress = Build Progress
bar.input = Input bar.input = Input
bar.output = Output bar.output = Output
units.processorcontrol = [lightgray]Processor Controlled
bullet.damage = [stat]{0}[lightgray] damage bullet.damage = [stat]{0}[lightgray] damage
bullet.splashdamage = [stat]{0}[lightgray] area dmg ~[stat] {1}[lightgray] tiles bullet.splashdamage = [stat]{0}[lightgray] area dmg ~[stat] {1}[lightgray] tiles
bullet.incendiary = [stat]incendiary bullet.incendiary = [stat]incendiary

View File

@@ -82,6 +82,8 @@ public class Vars implements Loadable{
public static final float buildingRange = 220f; public static final float buildingRange = 220f;
/** range for moving items */ /** range for moving items */
public static final float itemTransferRange = 220f; public static final float itemTransferRange = 220f;
/** range for moving items for logic units */
public static final float logicItemTransferRange = 45f;
/** duration of time between turns in ticks */ /** duration of time between turns in ticks */
public static final float turnDuration = 20 * Time.toMinutes; public static final float turnDuration = 20 * Time.toMinutes;
/** turns needed to destroy a sector completely */ /** turns needed to destroy a sector completely */
@@ -188,7 +190,6 @@ public class Vars implements Loadable{
public static Schematics schematics; public static Schematics schematics;
public static BeControl becontrol; public static BeControl becontrol;
public static AsyncCore asyncCore; public static AsyncCore asyncCore;
public static TeamIndexProcess teamIndex;
public static BaseRegistry bases; public static BaseRegistry bases;
public static Universe universe; public static Universe universe;

View File

@@ -192,7 +192,7 @@ public class BlockIndexer{
if(other == null) continue; if(other == null) continue;
if(other.team == team && pred.get(other) && intSet.add(other.pos())){ if((team == null || other.team == team) && pred.get(other) && intSet.add(other.pos())){
cons.get(other); cons.get(other);
any = true; any = true;
} }

View File

@@ -34,14 +34,14 @@ public class GroundAI extends AIController{
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false; if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
} }
if(move) moveTo(Pathfinder.fieldCore); if(move) pathfind(Pathfinder.fieldCore);
} }
if(command() == UnitCommand.rally){ if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false); Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){ if(target != null && !unit.within(target, 70f)){
moveTo(Pathfinder.fieldRally); pathfind(Pathfinder.fieldRally);
} }
} }
@@ -72,16 +72,4 @@ public class GroundAI extends AIController{
} }
}*/ }*/
} }
protected void moveTo(int pathTarget){
int costType = unit.pathType();
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}
} }

View File

@@ -0,0 +1,129 @@
package mindustry.ai.types;
import arc.struct.*;
import arc.util.*;
import mindustry.ai.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.logic.LExecutor.*;
import mindustry.logic.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LogicAI extends AIController{
/** Minimum delay between item transfers. */
public static final float transferDelay = 60f * 2f;
/** Time after which the unit resets its controlled and reverts to a normal unit. */
public static final float logicControlTimeout = 10f * 60f;
public LUnitControl control = LUnitControl.stop;
public float moveX, moveY, moveRad;
public float itemTimer, controlTimer = logicControlTimeout, targetTimer;
public Building controller;
//type of aiming to use
public LUnitControl aimControl = LUnitControl.stop;
//main target set for shootP
public Teamc mainTarget;
//whether to shoot at all
public boolean shoot;
//target shoot positions for manual aiming
public PosTeam posTarget = PosTeam.create();
private ObjectSet<RadarI> radars = new ObjectSet<>();
@Override
protected void updateMovement(){
if(itemTimer > 0){
itemTimer -= Time.delta;
}
if(targetTimer > 0f){
targetTimer -= Time.delta;
}else{
radars.clear();
targetTimer = 30f;
}
//timeout when not controlled by logic for a while
if(controlTimer > 0 && controller != null && controller.isValid()){
controlTimer -= Time.delta;
}else{
unit.resetController();
return;
}
switch(control){
case move -> {
moveTo(Tmp.v1.set(moveX, moveY), 1f, 30f);
}
case approach -> {
moveTo(Tmp.v1.set(moveX, moveY), moveRad, 1f);
}
case pathfind -> {
Building core = unit.closestEnemyCore();
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
if(move) pathfind(Pathfinder.fieldCore);
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
}
}
}
}
//look where moving if there's nothing to aim at
if(!shoot){
if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
}else if(unit.hasWeapons()){ //if there is, look at the object
unit.lookAt(unit.mounts[0].aimX, unit.mounts[0].aimY);
}
}
public boolean checkTargetTimer(RadarI radar){
return radars.add(radar);
}
//always retarget
@Override
protected boolean retarget(){
return true;
}
@Override
protected boolean invalid(Teamc target){
return false;
}
@Override
protected boolean shouldShoot(){
return shoot;
}
//always aim for the main target
@Override
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
return switch(aimControl){
case target -> posTarget;
case targetp -> mainTarget;
default -> null;
};
}
}

View File

@@ -67,10 +67,10 @@ public class SuicideAI extends GroundAI{
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false); Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){ if(target != null && !unit.within(target, 70f)){
moveTo(Pathfinder.fieldRally); pathfind(Pathfinder.fieldRally);
} }
}else if(command() == UnitCommand.attack && core != null){ }else if(command() == UnitCommand.attack && core != null){
moveTo(Pathfinder.fieldCore); pathfind(Pathfinder.fieldCore);
} }
if(unit.moving()) unit.lookAt(unit.vel().angle()); if(unit.moving()) unit.lookAt(unit.vel().angle());

View File

@@ -2,7 +2,6 @@ package mindustry.async;
import arc.*; import arc.*;
import arc.struct.*; import arc.struct.*;
import mindustry.*;
import mindustry.game.EventType.*; import mindustry.game.EventType.*;
import java.util.concurrent.*; import java.util.concurrent.*;
@@ -12,8 +11,7 @@ import static mindustry.Vars.*;
public class AsyncCore{ public class AsyncCore{
//all processes to be executed each frame //all processes to be executed each frame
private final Seq<AsyncProcess> processes = Seq.with( private final Seq<AsyncProcess> processes = Seq.with(
new PhysicsProcess(), new PhysicsProcess()
Vars.teamIndex = new TeamIndexProcess()
); );
//futures to be awaited //futures to be awaited

View File

@@ -1,82 +0,0 @@
package mindustry.async;
import arc.math.geom.*;
import mindustry.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.payloads.*;
import java.util.*;
/** Creates quadtrees per unit team. */
public class TeamIndexProcess implements AsyncProcess{
private QuadTree<Unit>[] trees = new QuadTree[Team.all.length];
private int[] counts = new int[Team.all.length];
private int[][] typeCounts = new int[Team.all.length][0];
public QuadTree<Unit> tree(Team team){
if(trees[team.id] == null) trees[team.id] = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
return trees[team.id];
}
public int count(Team team){
return counts[team.id];
}
public int countType(Team team, UnitType type){
return typeCounts[team.id].length <= type.id ? 0 : typeCounts[team.id][type.id];
}
public void updateCount(Team team, UnitType type, int amount){
counts[team.id] = Math.max(amount + counts[team.id], 0);
if(typeCounts[team.id].length <= type.id){
typeCounts[team.id] = new int[Vars.content.units().size];
}
typeCounts[team.id][type.id] = Math.max(amount + typeCounts[team.id][type.id], 0);
}
private void count(Unit unit){
updateCount(unit.team, unit.type(), 1);
if(unit instanceof Payloadc){
((Payloadc)unit).payloads().each(p -> {
if(p instanceof UnitPayload){
count(((UnitPayload)p).unit);
}
});
}
}
@Override
public void reset(){
counts = new int[Team.all.length];
trees = new QuadTree[Team.all.length];
}
@Override
public void begin(){
for(Team team : Team.all){
if(trees[team.id] != null){
trees[team.id].clear();
}
Arrays.fill(typeCounts[team.id], 0);
}
Arrays.fill(counts, 0);
for(Unit unit : Groups.unit){
tree(unit.team).insert(unit);
count(unit);
}
}
@Override
public boolean shouldProcess(){
return false;
}
}

View File

@@ -266,7 +266,6 @@ public class UnitTypes implements ContentList{
//region ground support //region ground support
nova = new UnitType("nova"){{ nova = new UnitType("nova"){{
itemCapacity = 60;
canBoost = true; canBoost = true;
boostMultiplier = 1.5f; boostMultiplier = 1.5f;
speed = 0.55f; speed = 0.55f;
@@ -293,7 +292,6 @@ public class UnitTypes implements ContentList{
}}; }};
pulsar = new UnitType("pulsar"){{ pulsar = new UnitType("pulsar"){{
itemCapacity = 60;
canBoost = true; canBoost = true;
boostMultiplier = 1.5f; boostMultiplier = 1.5f;
speed = 0.65f; speed = 0.65f;
@@ -340,7 +338,6 @@ public class UnitTypes implements ContentList{
mineTier = 1; mineTier = 1;
hitSize = 12f; hitSize = 12f;
boostMultiplier = 2f; boostMultiplier = 2f;
itemCapacity = 80;
health = 650f; health = 650f;
buildSpeed = 1.7f; buildSpeed = 1.7f;
canBoost = true; canBoost = true;
@@ -397,7 +394,7 @@ public class UnitTypes implements ContentList{
engineSize = 6f; engineSize = 6f;
lowAltitude = true; lowAltitude = true;
health = 6500f; health = 7000f;
armor = 7f; armor = 7f;
canBoost = true; canBoost = true;
landShake = 4f; landShake = 4f;
@@ -445,7 +442,6 @@ public class UnitTypes implements ContentList{
corvus = new UnitType("corvus"){{ corvus = new UnitType("corvus"){{
mineTier = 1; mineTier = 1;
hitSize = 29f; hitSize = 29f;
itemCapacity = 80;
health = 18000f; health = 18000f;
buildSpeed = 1.7f; buildSpeed = 1.7f;
armor = 9f; armor = 9f;
@@ -545,7 +541,6 @@ public class UnitTypes implements ContentList{
}}; }};
atrax = new UnitType("atrax"){{ atrax = new UnitType("atrax"){{
itemCapacity = 80;
speed = 0.5f; speed = 0.5f;
drag = 0.4f; drag = 0.4f;
hitSize = 10f; hitSize = 10f;
@@ -1134,7 +1129,6 @@ public class UnitTypes implements ContentList{
health = 100; health = 100;
engineSize = 1.8f; engineSize = 1.8f;
engineOffset = 5.7f; engineOffset = 5.7f;
itemCapacity = 30;
range = 50f; range = 50f;
isCounted = false; isCounted = false;
@@ -1153,7 +1147,6 @@ public class UnitTypes implements ContentList{
rotateSpeed = 15f; rotateSpeed = 15f;
accel = 0.1f; accel = 0.1f;
range = 70f; range = 70f;
itemCapacity = 70;
health = 400; health = 400;
buildSpeed = 0.5f; buildSpeed = 0.5f;
engineOffset = 6.5f; engineOffset = 6.5f;

View File

@@ -283,6 +283,8 @@ public class Logic implements ApplicationListener{
} }
if(!state.isPaused()){ if(!state.isPaused()){
state.teams.updateTeamStats();
if(state.isCampaign()){ if(state.isCampaign()){
state.secinfo.update(); state.secinfo.update();
} }

View File

@@ -63,7 +63,9 @@ public class Renderer implements ApplicationListener{
Color.white.set(1f, 1f, 1f, 1f); Color.white.set(1f, 1f, 1f, 1f);
Gl.clear(Gl.stencilBufferBit); Gl.clear(Gl.stencilBufferBit);
camerascale = Mathf.lerpDelta(camerascale, targetscale, 0.1f); float dest = Mathf.round(targetscale, 0.5f);
camerascale = Mathf.lerpDelta(camerascale, dest, 0.1f);
if(Mathf.within(camerascale, dest, 0.001f)) camerascale = dest;
laserOpacity = Core.settings.getInt("lasersopacity") / 100f; laserOpacity = Core.settings.getInt("lasersopacity") / 100f;
if(landTime > 0){ if(landTime > 0){
@@ -303,6 +305,10 @@ public class Renderer implements ApplicationListener{
targetscale = Mathf.clamp(targetscale, minScale(), Math.round(s * 6)); targetscale = Mathf.clamp(targetscale, minScale(), Math.round(s * 6));
} }
public float getDisplayScale(){
return camerascale;
}
public float minScale(){ public float minScale(){
return Scl.scl(1.5f); return Scl.scl(1.5f);
} }

View File

@@ -65,7 +65,7 @@ public class Units{
/** @return whether a new instance of a unit of this team can be created. */ /** @return whether a new instance of a unit of this team can be created. */
public static boolean canCreate(Team team, UnitType type){ public static boolean canCreate(Team team, UnitType type){
return teamIndex.countType(team, type) < getCap(team); return team.data().countType(type) < getCap(team);
} }
public static int getCap(Team team){ public static int getCap(Team team){
@@ -284,7 +284,7 @@ public class Units{
/** Iterates over all units in a rectangle. */ /** Iterates over all units in a rectangle. */
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){ public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
teamIndex.tree(team).intersect(x, y, width, height, cons); team.data().tree().intersect(x, y, width, height, cons);
} }
/** Iterates over all units in a circle around this position. */ /** Iterates over all units in a circle around this position. */
@@ -316,7 +316,7 @@ public class Units{
//inactive teams have no cache, check everything //inactive teams have no cache, check everything
//TODO cache all teams with units OR blocks //TODO cache all teams with units OR blocks
for(Team other : Team.all){ for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){ if(other != team && other.data().unitCount > 0){
nearby(other, x, y, width, height, cons); nearby(other, x, y, width, height, cons);
} }
} }

View File

@@ -146,6 +146,9 @@ public abstract class BulletType extends Content{
} }
public void hitTile(Bullet b, Building tile, float initialHealth){ public void hitTile(Bullet b, Building tile, float initialHealth){
if(status == StatusEffects.burning) {
Fires.create(tile.tile);
}
hit(b); hit(b);
if(healPercent > 0f && tile.team == b.team && !(tile.block instanceof ConstructBlock)){ if(healPercent > 0f && tile.team == b.team && !(tile.block instanceof ConstructBlock)){
@@ -193,11 +196,16 @@ public abstract class BulletType extends Content{
if(status != StatusEffects.none){ if(status != StatusEffects.none){
Damage.status(b.team, x, y, splashDamageRadius, status, statusDuration, collidesAir, collidesGround); Damage.status(b.team, x, y, splashDamageRadius, status, statusDuration, collidesAir, collidesGround);
} }
if(healPercent > 0f) { if(healPercent > 0f) {
indexer.eachBlock(b.team, x, y, splashDamageRadius, other -> other.damaged(), other -> { indexer.eachBlock(b.team, x, y, splashDamageRadius, other -> other.damaged(), other -> {
Fx.healBlockFull.at(other.x, other.y, other.block.size, Pal.heal); Fx.healBlockFull.at(other.x, other.y, other.block.size, Pal.heal);
other.heal(healPercent / 100f * other.maxHealth()); other.heal(healPercent / 100f * other.maxHealth());
}
if(status == StatusEffects.burning) {
indexer.eachBlock(null, x, y, splashDamageRadius, other -> other.team != b.team, other -> {
Fires.create(other.tile);
}); });
} }
} }

View File

@@ -31,12 +31,12 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
public void getCollisions(Cons<QuadTree> consumer){ public void getCollisions(Cons<QuadTree> consumer){
if(team.active()){ if(team.active()){
for(Team team : team.enemies()){ for(Team team : team.enemies()){
consumer.get(teamIndex.tree(team)); consumer.get(team.data().tree());
} }
}else{ }else{
for(Team other : Team.all){ for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){ if(other != team && team.data().unitCount > 0){
consumer.get(teamIndex.tree(other)); consumer.get(team.data().tree());
} }
} }
} }

View File

@@ -0,0 +1,9 @@
package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
//dummy target definition
@EntityDef(value = Teamc.class, genio = false, isFinal = false)
public class PosTeamDef{
}

View File

@@ -27,7 +27,7 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
@Component(base = true) @Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Senseable{ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Senseable, Ranged{
@Import boolean hovering, dead; @Import boolean hovering, dead;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo; @Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo;
@@ -38,6 +38,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
private UnitType type; private UnitType type;
boolean spawnedByCore; boolean spawnedByCore;
//TODO mark as non-transient when done
transient double flag;
transient Seq<Ability> abilities = new Seq<>(0); transient Seq<Ability> abilities = new Seq<>(0);
private transient float resupplyTime = Mathf.random(10f); private transient float resupplyTime = Mathf.random(10f);
@@ -63,6 +66,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type.hasWeapons(); return type.hasWeapons();
} }
@Override
public float range(){ public float range(){
return type.range; return type.range;
} }
@@ -76,6 +80,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
public double sense(LAccess sensor){ public double sense(LAccess sensor){
return switch(sensor){ return switch(sensor){
case totalItems -> stack().amount; case totalItems -> stack().amount;
case itemCapacity -> type.itemCapacity;
case rotation -> rotation; case rotation -> rotation;
case health -> health; case health -> health;
case maxHealth -> maxHealth; case maxHealth -> maxHealth;
@@ -85,6 +90,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case shooting -> isShooting() ? 1 : 0; case shooting -> isShooting() ? 1 : 0;
case shootX -> aimX(); case shootX -> aimX();
case shootY -> aimY(); case shootY -> aimY();
case flag -> flag;
default -> 0; default -> 0;
}; };
} }
@@ -93,6 +99,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
public Object senseObject(LAccess sensor){ public Object senseObject(LAccess sensor){
return switch(sensor){ return switch(sensor){
case type -> type; case type -> type;
case name -> controller instanceof Player p ? p.name : null;
default -> noSensed; default -> noSensed;
}; };
@@ -182,7 +189,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
} }
public int count(){ public int count(){
return teamIndex.countType(team, type); return team.data().countType(type);
} }
public int cap(){ public int cap(){
@@ -224,13 +231,13 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//check if over unit cap //check if over unit cap
if(count() > cap() && !spawnedByCore && !dead){ if(count() > cap() && !spawnedByCore && !dead){
Call.unitCapDeath(self()); Call.unitCapDeath(self());
teamIndex.updateCount(team, type, -1); team.data().updateCount(type, -1);
} }
} }
@Override @Override
public void remove(){ public void remove(){
teamIndex.updateCount(team, type, -1); team.data().updateCount(type, -1);
controller.removed(self()); controller.removed(self());
} }

View File

@@ -4,6 +4,7 @@ import arc.math.*;
import arc.math.geom.*; import arc.math.geom.*;
import arc.util.*; import arc.util.*;
import mindustry.*; import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.*; import mindustry.entities.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.type.*; import mindustry.type.*;
@@ -63,6 +64,23 @@ public class AIController implements UnitController{
} }
} }
protected boolean invalid(Teamc target){
return Units.invalidateTarget(target, unit.team, unit.x, unit.y);
}
protected void pathfind(int pathTarget){
int costType = unit.pathType();
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}
protected void updateWeapons(){ protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length]; if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
@@ -73,7 +91,7 @@ public class AIController implements UnitController{
target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround); target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
} }
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y)){ if(invalid(target)){
target = null; target = null;
} }
@@ -99,13 +117,11 @@ public class AIController implements UnitController{
boolean shoot = false; boolean shoot = false;
if(targets[i] != null){ if(targets[i] != null){
shoot = targets[i].within(mountX, mountY, weapon.bullet.range()); shoot = targets[i].within(mountX, mountY, weapon.bullet.range()) && shouldShoot();
if(shoot){ Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed); mount.aimX = to.x;
mount.aimX = to.x; mount.aimY = to.y;
mount.aimY = to.y;
}
} }
mount.shoot = shoot; mount.shoot = shoot;
@@ -113,6 +129,10 @@ public class AIController implements UnitController{
} }
} }
protected boolean shouldShoot(){
return true;
}
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){ protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag)); Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
return target == null ? null : target.build; return target == null ? null : target.build;
@@ -157,11 +177,15 @@ public class AIController implements UnitController{
} }
protected void moveTo(Position target, float circleLength){ protected void moveTo(Position target, float circleLength){
moveTo(target, circleLength, 100f);
}
protected void moveTo(Position target, float circleLength, float smooth){
if(target == null) return; if(target == null) return;
vec.set(target).sub(unit); vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f); float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / smooth, -1f, 1f);
vec.setLength(unit.type().speed * length); vec.setLength(unit.type().speed * length);
if(length < -0.5f){ if(length < -0.5f){

View File

@@ -2,15 +2,20 @@ package mindustry.game;
import arc.func.*; import arc.func.*;
import arc.math.geom.*; import arc.math.geom.*;
import arc.struct.Queue;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import mindustry.*;
import mindustry.ai.*; import mindustry.ai.*;
import mindustry.content.*; import mindustry.content.*;
import mindustry.entities.units.*; import mindustry.entities.units.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.type.*; import mindustry.type.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.storage.CoreBlock.*; import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
/** Class for various team-based utilities. */ /** Class for various team-based utilities. */
@@ -67,9 +72,7 @@ public class Teams{
/** Returns team data by type. */ /** Returns team data by type. */
public TeamData get(Team team){ public TeamData get(Team team){
if(map[team.id] == null){ if(map[team.id] == null) map[team.id] = new TeamData(team);
map[team.id] = new TeamData(team);
}
return map[team.id]; return map[team.id];
} }
@@ -129,6 +132,61 @@ public class Teams{
} }
} }
private void count(Unit unit){
unit.team.data().updateCount(unit.type(), 1);
if(unit instanceof Payloadc){
((Payloadc)unit).payloads().each(p -> {
if(p instanceof UnitPayload){
count(((UnitPayload)p).unit);
}
});
}
}
public void updateTeamStats(){
for(Team team : Team.all){
TeamData data = team.data();
data.unitCount = 0;
data.units.clear();
if(data.tree != null){
data.tree.clear();
}
if(data.typeCounts != null){
Arrays.fill(data.typeCounts, 0);
}
//clear old unit records
if(data.unitsByType != null){
for(int i = 0; i < data.unitsByType.length; i++){
if(data.unitsByType[i] != null){
data.unitsByType[i].clear();
}
}
}
}
for(Unit unit : Groups.unit){
TeamData data = unit.team.data();
data.tree().insert(unit);
data.units.add(unit);
if(data.unitsByType == null || data.unitsByType.length <= unit.type().id){
data.unitsByType = new Seq[content.units().size];
}
if(data.unitsByType[unit.type().id] == null){
data.unitsByType[unit.type().id] = new Seq<>();
}
data.unitsByType[unit.type().id].add(unit);
count(unit);
}
}
private void updateEnemies(){ private void updateEnemies(){
if(state.rules.waves && !active.contains(get(state.rules.waveTeam))){ if(state.rules.waves && !active.contains(get(state.rules.waveTeam))){
active.add(get(state.rules.waveTeam)); active.add(get(state.rules.waveTeam));
@@ -147,7 +205,7 @@ public class Teams{
} }
} }
public class TeamData{ public static class TeamData{
public final Seq<CoreBuild> cores = new Seq<>(); public final Seq<CoreBuild> cores = new Seq<>();
public final Team team; public final Team team;
public final BaseAI ai; public final BaseAI ai;
@@ -160,11 +218,48 @@ public class Teams{
/** Target items to mine. */ /** Target items to mine. */
public Seq<Item> mineItems = Seq.with(Items.copper, Items.lead, Items.titanium, Items.thorium); public Seq<Item> mineItems = Seq.with(Items.copper, Items.lead, Items.titanium, Items.thorium);
/** Total unit count. */
public int unitCount;
/** Counts for each type of unit. Do not access directly. */
@Nullable
public int[] typeCounts;
/** Quadtree for units of this type. Do not access directly. */
@Nullable
public 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 TeamData(Team team){ public TeamData(Team team){
this.team = team; this.team = team;
this.ai = new BaseAI(this); this.ai = new BaseAI(this);
} }
@Nullable
public Seq<Unit> unitCache(UnitType type){
if(unitsByType == null || unitsByType.length <= type.id || unitsByType[type.id] == null) return null;
return unitsByType[type.id];
}
public void updateCount(UnitType type, int amount){
unitCount = Math.max(amount + unitCount, 0);
if(typeCounts == null || typeCounts.length <= type.id){
typeCounts = new int[Vars.content.units().size];
}
typeCounts [type.id] = Math.max(amount + typeCounts [type.id], 0);
}
public QuadTree<Unit> tree(){
if(tree == null) tree = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
return tree;
}
public int countType(UnitType type){
return typeCounts == null || typeCounts.length <= type.id ? 0 : typeCounts[type.id];
}
public boolean active(){ public boolean active(){
return (team == state.rules.waveTeam && state.rules.waves) || cores.size > 0; return (team == state.rules.waveTeam && state.rules.waves) || cores.size > 0;
} }

View File

@@ -48,7 +48,7 @@ public class DesktopInput extends InputHandler{
public void buildUI(Group group){ public void buildUI(Group group){
group.fill(t -> { group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && ui.hudfrag.shown() && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty())); t.visible(() -> Core.settings.getBool("hints") && ui.hudfrag.shown && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty()));
t.bottom(); t.bottom();
t.table(Styles.black6, b -> { t.table(Styles.black6, b -> {
b.defaults().left(); b.defaults().left();

View File

@@ -77,6 +77,19 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
createItemTransfer(item, 1, x, y, to, null); createItemTransfer(item, 1, x, y, to, null);
} }
@Remote(called = Loc.server, unreliable = true)
public static void takeItems(Building build, Item item, int amount, Unit to){
if(to == null || build == null) return;
int removed = build.removeStack(item, Math.min(player.unit().maxAccepted(item), amount));
if(removed == 0) return;
to.addItem(item, removed);
for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){
Time.run(j * 3f, () -> Call.transferItemEffect(item, build.x, build.y, to));
}
}
@Remote(called = Loc.server, unreliable = true) @Remote(called = Loc.server, unreliable = true)
public static void transferItemToUnit(Item item, float x, float y, Itemsc to){ public static void transferItemToUnit(Item item, float x, float y, Itemsc to){
if(to == null) return; if(to == null) return;
@@ -92,6 +105,16 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
build.items.add(item, amount); build.items.add(item, amount);
} }
@Remote(called = Loc.server, unreliable = true)
public static void transferItemTo(Unit unit, Item item, int amount, float x, float y, Building build){
if(build == null || build.items == null) return;
unit.stack.amount = Math.max(unit.stack.amount - amount, 0);
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Time.run(i * 3, () -> createItemTransfer(item, amount, x, y, build, () -> {}));
}
build.handleStack(item, amount, unit);
}
public static void createItemTransfer(Item item, int amount, float x, float y, Position to, Runnable done){ public static void createItemTransfer(Item item, int amount, float x, float y, Position to, Runnable done){
Fx.itemTransfer.at(x, y, amount, item.color, to); Fx.itemTransfer.at(x, y, amount, item.color, to);
if(done != null){ if(done != null){
@@ -99,6 +122,29 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
} }
} }
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Building tile, Item item, int amount){
if(player == null || tile == null || !tile.interactable(player.team()) || !player.within(tile, buildingRange)) return;
amount = Math.min(player.unit().maxAccepted(item), amount);
int fa = amount;
if(amount == 0) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.withdrawItem, tile.tile(), action -> {
action.item = item;
action.itemAmount = fa;
}))) throw new ValidateException(player, "Player cannot request items.");
int removed = tile.removeStack(item, amount);
player.unit().addItem(item, removed);
Events.fire(new WithdrawEvent(tile, player, item, amount));
for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){
Time.run(j * 3f, () -> Call.transferItemEffect(item, tile.x, tile.y, player.unit()));
}
}
@Remote(variants = Variant.one) @Remote(variants = Variant.one)
public static void removeQueueBlock(int x, int y, boolean breaking){ public static void removeQueueBlock(int x, int y, boolean breaking){
player.builder().removeBuild(x, y, breaking); player.builder().removeBuild(x, y, breaking);

View File

@@ -26,6 +26,8 @@ public enum LAccess{
shooting, shooting,
team, team,
type, type,
flag,
name,
//values with parameters are considered controllable //values with parameters are considered controllable
enabled("to"), //"to" is standard for single parameter access enabled("to"), //"to" is standard for single parameter access
@@ -34,21 +36,21 @@ public enum LAccess{
; ;
public final String[] parameters; public final String[] params;
public final boolean isObj; public final boolean isObj;
public static final LAccess[] public static final LAccess[]
all = values(), all = values(),
senseable = Seq.select(all, t -> t.parameters.length <= 1).toArray(LAccess.class), senseable = Seq.select(all, t -> t.params.length <= 1).toArray(LAccess.class),
controls = Seq.select(all, t -> t.parameters.length > 0).toArray(LAccess.class); controls = Seq.select(all, t -> t.params.length > 0).toArray(LAccess.class);
LAccess(String... parameters){ LAccess(String... params){
this.parameters = parameters; this.params = params;
isObj = false; isObj = false;
} }
LAccess(boolean obj, String... parameters){ LAccess(boolean obj, String... params){
this.parameters = parameters; this.params = params;
isObj = obj; isObj = obj;
} }
} }

View File

@@ -21,8 +21,14 @@ public class LAssembler{
LInstruction[] instructions; LInstruction[] instructions;
public LAssembler(){ public LAssembler(){
//instruction counter
putVar("@counter").value = 0; putVar("@counter").value = 0;
//unix timestamp
putConst("@time", 0); putConst("@time", 0);
//currently controlled unit
putConst("@unit", null);
//reference to self
putConst("@this", null);
//add default constants //add default constants
putConst("false", 0); putConst("false", 0);
@@ -45,6 +51,10 @@ public class LAssembler{
} }
} }
for(UnitType type : Vars.content.units()){
putConst("@" + type.name, type);
}
//store sensor constants //store sensor constants
for(LAccess sensor : LAccess.all){ for(LAccess sensor : LAccess.all){

View File

@@ -7,7 +7,8 @@ public enum LCategory{
blocks(Pal.accentBack), blocks(Pal.accentBack),
control(Color.cyan.cpy().shiftSaturation(-0.6f).mul(0.7f)), control(Color.cyan.cpy().shiftSaturation(-0.6f).mul(0.7f)),
operations(Pal.place.cpy().shiftSaturation(-0.5f).mul(0.7f)), operations(Pal.place.cpy().shiftSaturation(-0.5f).mul(0.7f)),
io(Pal.remove.cpy().shiftSaturation(-0.5f).mul(0.7f)); io(Pal.remove.cpy().shiftSaturation(-0.5f).mul(0.7f)),
units(Pal.bulletYellowBack.cpy().shiftSaturation(-0.3f).mul(0.8f));
public final Color color; public final Color color;

View File

@@ -4,10 +4,13 @@ import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.noise.*; import arc.util.noise.*;
import mindustry.*; import mindustry.*;
import mindustry.ai.types.*;
import mindustry.ctype.*; import mindustry.ctype.*;
import mindustry.entities.*; import mindustry.entities.*;
import mindustry.game.*; import mindustry.game.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.logic.LogicDisplay.*; import mindustry.world.blocks.logic.LogicDisplay.*;
import mindustry.world.blocks.logic.MemoryBlock.*; import mindustry.world.blocks.logic.MemoryBlock.*;
import mindustry.world.blocks.logic.MessageBlock.*; import mindustry.world.blocks.logic.MessageBlock.*;
@@ -23,7 +26,9 @@ public class LExecutor{
//special variables //special variables
public static final int public static final int
varCounter = 0, varCounter = 0,
varTime = 1; varTime = 1,
varUnit = 2,
varThis = 3;
public static final int public static final int
maxGraphicsBuffer = 256, maxGraphicsBuffer = 256,
@@ -36,6 +41,7 @@ public class LExecutor{
public LongSeq graphicsBuffer = new LongSeq(); public LongSeq graphicsBuffer = new LongSeq();
public StringBuilder textBuffer = new StringBuilder(); public StringBuilder textBuffer = new StringBuilder();
public Building[] links = {}; public Building[] links = {};
public Team team = Team.derelict;
public boolean initialized(){ public boolean initialized(){
return instructions != null && vars != null && instructions.length > 0; return instructions != null && vars != null && instructions.length > 0;
@@ -102,6 +108,11 @@ public class LExecutor{
return v.isobj ? v.objval != null ? 1 : 0 : v.numval; return v.isobj ? v.objval != null ? 1 : 0 : v.numval;
} }
public float numf(int index){
Var v = vars[index];
return v.isobj ? v.objval != null ? 1 : 0 : (float)v.numval;
}
public int numi(int index){ public int numi(int index){
return (int)num(index); return (int)num(index);
} }
@@ -121,6 +132,12 @@ public class LExecutor{
v.isobj = true; v.isobj = true;
} }
public void setconst(int index, Object value){
Var v = vars[index];
v.objval = value;
v.isobj = true;
}
//endregion //endregion
public static class Var{ public static class Var{
@@ -142,6 +159,157 @@ public class LExecutor{
void run(LExecutor exec); void run(LExecutor exec);
} }
/** Binds the processor to a unit based on some filters. */
public static class UnitBindI implements LInstruction{
public int type;
//iteration index
private int index;
public UnitBindI(int type){
this.type = type;
}
public UnitBindI(){
}
@Override
public void run(LExecutor exec){
Object typeObj = exec.obj(type);
UnitType type = typeObj instanceof UnitType t ? t : null;
Seq<Unit> seq = type == null ? exec.team.data().units : exec.team.data().unitCache(type);
if(seq != null && seq.any()){
index %= seq.size;
if(index < seq.size){
//bind to the next unit
exec.setconst(varUnit, seq.get(index));
}
index ++;
}else{
//no units of this type found
exec.setconst(varUnit, null);
}
}
}
/** Controls the unit based on some parameters. */
public static class UnitControlI implements LInstruction{
public LUnitControl type = LUnitControl.move;
public int p1, p2, p3, p4;
public UnitControlI(LUnitControl type, int p1, int p2, int p3, int p4){
this.type = type;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.p4 = p4;
}
public UnitControlI(){
}
/** Checks is a unit is valid for logic AI control, and returns the controller. */
@Nullable
public static LogicAI checkLogicAI(LExecutor exec, Object unitObj){
if(unitObj instanceof Unit unit && exec.obj(varUnit) == unit && unit.team == exec.team && !unit.isPlayer() && !(unit.controller() instanceof FormationAI)){
if(!(unit.controller() instanceof LogicAI)){
unit.controller(new LogicAI());
((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();
}
return (LogicAI)unit.controller();
}
return (LogicAI)unit.controller();
}
return null;
}
@Override
public void run(LExecutor exec){
Object unitObj = exec.obj(varUnit);
LogicAI ai = checkLogicAI(exec, unitObj);
//only control standard AI units
if(unitObj instanceof Unit unit && ai != null){
ai.controlTimer = LogicAI.logicControlTimeout;
switch(type){
case move, stop, approach -> {
ai.control = type;
ai.moveX = exec.numf(p1);
ai.moveY = exec.numf(p2);
if(type == LUnitControl.approach){
ai.moveRad = exec.numf(p3);
}
}
case pathfind -> {
ai.control = type;
}
case target -> {
ai.posTarget.set(exec.numf(p1), exec.numf(p2));
ai.aimControl = type;
ai.mainTarget = null;
ai.shoot = exec.bool(p3);
}
case targetp -> {
ai.aimControl = type;
ai.mainTarget = exec.obj(p1) instanceof Teamc t ? t : null;
ai.shoot = exec.bool(p2);
}
case flag -> {
unit.flag = exec.num(p1);
}
case mine -> {
Tile tile = world.tileWorld(exec.numf(p1), exec.numf(p2));
if(unit instanceof Minerc miner){
miner.mineTile(tile);
}
}
case itemDrop -> {
if(ai.itemTimer > 0) return;
Building build = exec.building(p1);
int amount = exec.numi(p2);
int dropped = Math.min(unit.stack.amount, amount);
if(build != null && dropped > 0 && unit.within(build, logicItemTransferRange)){
int accepted = build.acceptStack(unit.item(), dropped, unit);
if(accepted > 0){
Call.transferItemTo(unit, unit.item(), accepted, unit.x, unit.y, build);
ai.itemTimer = LogicAI.transferDelay;
}
}
}
case itemTake -> {
if(ai.itemTimer > 0) return;
Building build = exec.building(p1);
int amount = exec.numi(p3);
if(build != null && exec.obj(p2) instanceof Item item && unit.within(build, logicItemTransferRange)){
int taken = Math.min(build.items.get(item), Math.min(amount, unit.maxAccepted(item)));
if(taken > 0){
Call.takeItems(build, item, taken, unit);
ai.itemTimer = LogicAI.transferDelay;
}
}
}
default -> {}
}
}
}
}
/** Controls a building's state. */ /** Controls a building's state. */
public static class ControlI implements LInstruction{ public static class ControlI implements LInstruction{
public int target; public int target;
@@ -311,16 +479,20 @@ public class LExecutor{
@Override @Override
public void run(LExecutor exec){ public void run(LExecutor exec){
Building target = exec.building(radar); Object base = exec.obj(radar);
int sortDir = exec.bool(sortOrder) ? 1 : -1; int sortDir = exec.bool(sortOrder) ? 1 : -1;
LogicAI ai = null;
if(target instanceof Ranged){ if(base instanceof Ranged r && r.team() == exec.team &&
float range = ((Ranged)target).range(); (base instanceof Building || (ai = UnitControlI.checkLogicAI(exec, base)) != null)){ //must be a building or a controllable unit
float range = r.range();
Healthc targeted; Healthc targeted;
if(timer.get(30f)){ //timers update on a fixed 30 tick interval
//units update on a special timer per controller instance
if((base instanceof Building && timer.get(30f)) || (ai != null && ai.checkTargetTimer(this))){
//if any of the targets involve enemies //if any of the targets involve enemies
boolean enemies = target1 == RadarTarget.enemy || target2 == RadarTarget.enemy || target3 == RadarTarget.enemy; boolean enemies = target1 == RadarTarget.enemy || target2 == RadarTarget.enemy || target3 == RadarTarget.enemy;
@@ -328,11 +500,11 @@ public class LExecutor{
bestValue = 0; bestValue = 0;
if(enemies){ if(enemies){
for(Team enemy : state.teams.enemiesOf(target.team)){ for(Team enemy : state.teams.enemiesOf(r.team())){
find(target, range, sortDir, enemy); find(r, range, sortDir, enemy);
} }
}else{ }else{
find(target, range, sortDir, target.team); find(r, range, sortDir, r.team());
} }
lastTarget = targeted = best; lastTarget = targeted = best;
@@ -346,14 +518,14 @@ public class LExecutor{
} }
} }
void find(Building b, float range, int sortDir, Team team){ void find(Ranged b, float range, int sortDir, Team team){
Units.nearby(team, b.x, b.y, range, u -> { Units.nearby(team, b.x(), b.y(), range, u -> {
if(!u.within(b, range)) return; if(!u.within(b, range)) return;
boolean valid = boolean valid =
target1.func.get(b.team, u) && target1.func.get(b.team(), u) &&
target2.func.get(b.team, u) && target2.func.get(b.team(), u) &&
target3.func.get(b.team, u); target3.func.get(b.team(), u);
if(!valid) return; if(!valid) return;

View File

@@ -31,6 +31,10 @@ public abstract class LStatement{
return read.size == 0 ? null : read.first(); return read.size == 0 ? null : read.first();
} }
public boolean hidden(){
return false;
}
//protected methods are only for internal UI layout utilities //protected methods are only for internal UI layout utilities
protected Cell<TextField> field(Table table, String value, Cons<String> setter){ protected Cell<TextField> field(Table table, String value, Cons<String> setter){
@@ -38,9 +42,9 @@ public abstract class LStatement{
.size(144f, 40f).pad(2f).color(table.color).addInputDialog(); .size(144f, 40f).pad(2f).color(table.color).addInputDialog();
} }
protected void fields(Table table, String desc, String value, Cons<String> setter){ protected Cell<TextField> fields(Table table, String desc, String value, Cons<String> setter){
table.add(desc).padLeft(10).left(); table.add(desc).padLeft(10).left();
field(table, value, setter).width(85f).padRight(10).left(); return field(table, value, setter).width(85f).padRight(10).left();
} }
protected void fields(Table table, String value, Cons<String> setter){ protected void fields(Table table, String value, Cons<String> setter){

View File

@@ -347,9 +347,9 @@ public class LStatements{
//Q: why don't you just use arrays for this? //Q: why don't you just use arrays for this?
//A: arrays aren't as easy to serialize so the code generator doesn't handle them //A: arrays aren't as easy to serialize so the code generator doesn't handle them
int c = 0; int c = 0;
for(int i = 0; i < type.parameters.length; i++){ for(int i = 0; i < type.params.length; i++){
fields(table, type.parameters[i], i == 0 ? p1 : i == 1 ? p2 : i == 2 ? p3 : p4, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : i == 2 ? v -> p3 = v : v -> p4 = v); fields(table, type.params[i], i == 0 ? p1 : i == 1 ? p2 : i == 2 ? p3 : p4, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : i == 2 ? v -> p3 = v : v -> p4 = v);
if(++c % 2 == 0) row(table); if(++c % 2 == 0) row(table);
} }
@@ -376,11 +376,13 @@ public class LStatements{
public void build(Table table){ public void build(Table table){
table.defaults().left(); table.defaults().left();
table.add(" from "); if(buildFrom()){
table.add(" from ");
fields(table, radar, v -> radar = v); fields(table, radar, v -> radar = v);
row(table); row(table);
}
for(int i = 0; i < 3; i++){ for(int i = 0; i < 3; i++){
int fi = i; int fi = i;
@@ -420,6 +422,10 @@ public class LStatements{
fields(table, output, v -> output = v); fields(table, output, v -> output = v);
} }
public boolean buildFrom(){
return true;
}
@Override @Override
public LCategory category(){ public LCategory category(){
return LCategory.blocks; return LCategory.blocks;
@@ -694,4 +700,95 @@ public class LStatements{
return LCategory.control; return LCategory.control;
} }
} }
@RegisterStatement("ubind")
public static class UnitBindStatement extends LStatement{
public String type = "@mono";
@Override
public void build(Table table){
table.add(" type ");
field(table, type, str -> type = str);
}
@Override
public LCategory category(){
return LCategory.units;
}
@Override
public LInstruction build(LAssembler builder){
return new UnitBindI(builder.var(type));
}
}
@RegisterStatement("ucontrol")
public static class UnitControlStatement extends LStatement{
public LUnitControl type = LUnitControl.move;
public String p1 = "0", p2 = "0", p3 = "0", p4 = "0";
@Override
public void build(Table table){
rebuild(table);
}
void rebuild(Table table){
table.clearChildren();
table.left();
table.add(" ");
table.button(b -> {
b.label(() -> type.name());
b.clicked(() -> showSelect(b, LUnitControl.all, type, t -> {
type = t;
rebuild(table);
}, 2, cell -> cell.size(120, 50)));
}, Styles.logict, () -> {}).size(120, 40).color(table.color).left().padLeft(2);
row(table);
//Q: why don't you just use arrays for this?
//A: arrays aren't as easy to serialize so the code generator doesn't handle them
int c = 0;
for(int i = 0; i < type.params.length; i++){
fields(table, type.params[i], i == 0 ? p1 : i == 1 ? p2 : i == 2 ? p3 : p4, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : i == 2 ? v -> p3 = v : v -> p4 = v).width(110f);
if(++c % 2 == 0) row(table);
}
}
@Override
public LCategory category(){
return LCategory.units;
}
@Override
public LInstruction build(LAssembler builder){
return new UnitControlI(type, builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
}
}
@RegisterStatement("uradar")
public static class UnitRadarStatement extends RadarStatement{
@Override
public boolean buildFrom(){
//do not build the "from" section
return false;
}
@Override
public LCategory category(){
return LCategory.units;
}
@Override
public LInstruction build(LAssembler builder){
return new RadarI(target1, target2, target3, sort, LExecutor.varUnit, builder.var(sortOrder), builder.var(output));
}
}
} }

View File

@@ -0,0 +1,21 @@
package mindustry.logic;
public enum LUnitControl{
stop,
move("x", "y"),
approach("x", "y", "radius"),
pathfind(),
target("x", "y", "shoot"),
targetp("unit", "shoot"),
itemDrop("to", "amount"),
itemTake("from", "item", "amount"),
mine("x", "y"),
flag("value");
public final String[] params;
public static final LUnitControl[] all = values();
LUnitControl(String... params){
this.params = params;
}
}

View File

@@ -60,7 +60,7 @@ public class LogicDialog extends BaseDialog{
int i = 0; int i = 0;
for(Prov<LStatement> prov : LogicIO.allStatements){ for(Prov<LStatement> prov : LogicIO.allStatements){
LStatement example = prov.get(); LStatement example = prov.get();
if(example instanceof InvalidStatement) continue; if(example instanceof InvalidStatement || example.hidden()) continue;
TextButtonStyle style = new TextButtonStyle(Styles.cleart); TextButtonStyle style = new TextButtonStyle(Styles.cleart);
style.fontColor = example.category().color; style.fontColor = example.category().color;

View File

@@ -1,6 +1,7 @@
package mindustry.logic; package mindustry.logic;
import arc.math.*; import arc.math.*;
import arc.util.*;
public enum LogicOp{ public enum LogicOp{
add("+", (a, b) -> a + b), add("+", (a, b) -> a + b),
@@ -9,8 +10,8 @@ public enum LogicOp{
div("/", (a, b) -> a / b), div("/", (a, b) -> a / b),
idiv("//", (a, b) -> Math.floor(a / b)), idiv("//", (a, b) -> Math.floor(a / b)),
mod("%", (a, b) -> a % b), mod("%", (a, b) -> a % b),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> a == b ? 1 : 0), equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> Structs.eq(a, b) ? 1 : 0),
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1, (a, b) -> a != b ? 1 : 0), notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1, (a, b) -> !Structs.eq(a, b) ? 1 : 0),
lessThan("<", (a, b) -> a < b ? 1 : 0), lessThan("<", (a, b) -> a < b ? 1 : 0),
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0), lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
greaterThan(">", (a, b) -> a > b ? 1 : 0), greaterThan(">", (a, b) -> a > b ? 1 : 0),

View File

@@ -163,6 +163,11 @@ public class UnitType extends UnlockableContent{
bars.row(); bars.row();
} }
}).growX(); }).growX();
if(unit.controller() instanceof LogicAI){
table.row();
table.add(Blocks.microProcessor.emoji() + " " + Core.bundle.get("units.processorcontrol")).growX().left();
}
table.row(); table.row();
} }
@@ -206,7 +211,7 @@ public class UnitType extends UnlockableContent{
singleTarget = weapons.size <= 1; singleTarget = weapons.size <= 1;
if(itemCapacity < 0){ if(itemCapacity < 0){
itemCapacity = Math.max(Mathf.round(hitSize * 7, 20), 20); itemCapacity = Math.max(Mathf.round(hitSize * 4, 10), 10);
} }
//set up default range //set up default range

View File

@@ -14,12 +14,8 @@ import arc.scene.ui.layout.Stack;
import arc.scene.ui.layout.*; import arc.scene.ui.layout.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.game.EventType.*; import mindustry.game.EventType.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.type.*; import mindustry.type.*;
import mindustry.ui.*; import mindustry.ui.*;
@@ -42,29 +38,6 @@ public class BlockInventoryFragment extends Fragment{
Events.on(WorldLoadEvent.class, e -> hide()); Events.on(WorldLoadEvent.class, e -> hide());
} }
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Building tile, Item item, int amount){
if(player == null || tile == null || !tile.interactable(player.team()) || !player.within(tile, buildingRange)) return;
amount = Math.min(player.unit().maxAccepted(item), amount);
int fa = amount;
if(amount == 0) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.withdrawItem, tile.tile(), action -> {
action.item = item;
action.itemAmount = fa;
}))) throw new ValidateException(player, "Player cannot request items.");
int removed = tile.removeStack(item, amount);
player.unit().addItem(item, removed);
Events.fire(new WithdrawEvent(tile, player, item, amount));
for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){
Time.run(j * 3f, () -> Call.transferItemEffect(item, tile.x, tile.y, player.unit()));
}
}
@Override @Override
public void build(Group parent){ public void build(Group parent){
table.name = "inventory"; table.name = "inventory";

View File

@@ -57,7 +57,7 @@ public class ChatFragment extends Table{
} }
} }
return net.active() && ui.hudfrag.shown(); return net.active() && ui.hudfrag.shown;
}); });
update(() -> { update(() -> {

View File

@@ -32,16 +32,16 @@ public class HudFragment extends Fragment{
private static final float dsize = 65f; private static final float dsize = 65f;
public final PlacementFragment blockfrag = new PlacementFragment(); public final PlacementFragment blockfrag = new PlacementFragment();
public boolean shown = true;
private ImageButton flip; private ImageButton flip;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private boolean shown = true;
private CoreItemsDisplay coreItems = new CoreItemsDisplay(); private CoreItemsDisplay coreItems = new CoreItemsDisplay();
private String hudText = ""; private String hudText = "";
private boolean showHudText; private boolean showHudText;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private long lastToast; private long lastToast;
@Override @Override
@@ -420,10 +420,6 @@ public class HudFragment extends Fragment{
}); });
} }
public boolean shown(){
return shown;
}
/** Show unlock notification for a new recipe. */ /** Show unlock notification for a new recipe. */
public void showUnlock(UnlockableContent content){ public void showUnlock(UnlockableContent content){
//some content may not have icons... yet //some content may not have icons... yet

View File

@@ -192,7 +192,7 @@ public class PlacementFragment extends Fragment{
public void build(Group parent){ public void build(Group parent){
parent.fill(full -> { parent.fill(full -> {
toggler = full; toggler = full;
full.bottom().right().visible(() -> ui.hudfrag.shown()); full.bottom().right().visible(() -> ui.hudfrag.shown);
full.table(frame -> { full.table(frame -> {

View File

@@ -28,7 +28,7 @@ public class Router extends Block{
@Override @Override
public void updateTile(){ public void updateTile(){
if(lastItem == null && items.any()){ if(lastItem == null && items.any()){
items.clear(); lastItem = items.first();
} }
if(lastItem != null){ if(lastItem != null){

View File

@@ -304,7 +304,7 @@ public class LogicBlock extends Block{
assemble.get(asm); assemble.get(asm);
} }
asm.putConst("@this", this); asm.getVar("@this").value = this;
asm.putConst("@thisx", x); asm.putConst("@thisx", x);
asm.putConst("@thisy", y); asm.putConst("@thisy", y);
@@ -331,6 +331,7 @@ public class LogicBlock extends Block{
@Override @Override
public void updateTile(){ public void updateTile(){
executor.team = team;
//check for previously invalid links to add after configuration //check for previously invalid links to add after configuration
boolean changed = false; boolean changed = false;

View File

@@ -75,15 +75,15 @@ public class LogicDisplay extends Block{
p1 = DisplayCmd.p1(c), p2 = DisplayCmd.p2(c), p3 = DisplayCmd.p3(c), p4 = DisplayCmd.p4(c); p1 = DisplayCmd.p1(c), p2 = DisplayCmd.p2(c), p3 = DisplayCmd.p3(c), p4 = DisplayCmd.p4(c);
switch(type){ switch(type){
case commandClear: Core.graphics.clear(x/255f, y/255f, p1/255f, 1f); break; case commandClear -> Core.graphics.clear(x / 255f, y / 255f, p1 / 255f, 1f);
case commandLine: Lines.line(x, y, p1, p2); break; case commandLine -> Lines.line(x, y, p1, p2);
case commandRect: Fill.crect(x, y, p1, p2); break; case commandRect -> Fill.crect(x, y, p1, p2);
case commandLineRect: Lines.rect(x, y, p1, p2); break; case commandLineRect -> Lines.rect(x, y, p1, p2);
case commandPoly: Fill.poly(x, y, Math.min(p1, maxSides), p2, p3); break; case commandPoly -> Fill.poly(x, y, Math.min(p1, maxSides), p2, p3);
case commandLinePoly: Lines.poly(x, y, Math.min(p1, maxSides), p2, p3); break; case commandLinePoly -> Lines.poly(x, y, Math.min(p1, maxSides), p2, p3);
case commandTriangle: Fill.tri(x, y, p1, p2, p3, p4); break; case commandTriangle -> Fill.tri(x, y, p1, p2, p3, p4);
case commandColor: this.color = Color.toFloatBits(x, y, p1, p2); Draw.color(this.color); break; case commandColor -> Draw.color(this.color = Color.toFloatBits(x, y, p1, p2));
case commandStroke: this.stroke = x; Lines.stroke(x); break; case commandStroke -> Lines.stroke(this.stroke = x);
} }
} }

View File

@@ -50,11 +50,11 @@ public class Reconstructor extends UnitBlock{
() -> e.unit() == null ? "[lightgray]" + Iconc.cancel : () -> e.unit() == null ? "[lightgray]" + Iconc.cancel :
Core.bundle.format("bar.unitcap", Core.bundle.format("bar.unitcap",
Fonts.getUnicodeStr(e.unit().name), Fonts.getUnicodeStr(e.unit().name),
teamIndex.countType(e.team, e.unit()), e.team.data().countType(e.unit()),
Units.getCap(e.team) Units.getCap(e.team)
), ),
() -> Pal.power, () -> Pal.power,
() -> e.unit() == null ? 0f : (float)teamIndex.countType(e.team, e.unit()) / Units.getCap(e.team) () -> e.unit() == null ? 0f : (float)e.team.data().countType(e.unit()) / Units.getCap(e.team)
)); ));
} }

View File

@@ -21,8 +21,6 @@ import mindustry.world.blocks.payloads.*;
import mindustry.world.consumers.*; import mindustry.world.consumers.*;
import mindustry.world.meta.*; import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class UnitFactory extends UnitBlock{ public class UnitFactory extends UnitBlock{
public int[] capacities; public int[] capacities;
@@ -71,11 +69,11 @@ public class UnitFactory extends UnitBlock{
() -> e.unit() == null ? "[lightgray]" + Iconc.cancel : () -> e.unit() == null ? "[lightgray]" + Iconc.cancel :
Core.bundle.format("bar.unitcap", Core.bundle.format("bar.unitcap",
Fonts.getUnicodeStr(e.unit().name), Fonts.getUnicodeStr(e.unit().name),
teamIndex.countType(e.team, e.unit()), e.team.data().countType(e.unit()),
Units.getCap(e.team) Units.getCap(e.team)
), ),
() -> Pal.power, () -> Pal.power,
() -> e.unit() == null ? 0f : (float)teamIndex.countType(e.team, e.unit()) / Units.getCap(e.team) () -> e.unit() == null ? 0f : (float)e.team.data().countType(e.unit()) / Units.getCap(e.team)
)); ));
} }

View File

@@ -1,3 +1,3 @@
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m org.gradle.jvmargs=-Xms256m -Xmx1024m
archash=6fca97cecdf28a696b2c19097f70960485a62743 archash=86e6ad07a2bfc727f8772c3e9c5f9d2761580879