Logic unit control

This commit is contained in:
Anuken 2020-10-05 15:42:37 -04:00
parent 8e49d73765
commit 7088ae89b3
37 changed files with 661 additions and 202 deletions

View file

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

View file

@ -14,6 +14,8 @@ mindustry.entities.comp.EffectStateComp=9
mindustry.entities.comp.FireComp=10
mindustry.entities.comp.LaunchCoreComp=11
mindustry.entities.comp.PlayerComp=12
mindustry.entities.comp.PosTeam=27
mindustry.entities.comp.PosTeamDef=28
mindustry.entities.comp.PuddleComp=13
mindustry.type.Weather.WeatherStateComp=14
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.output = Output
units.processorcontrol = [lightgray]Processor Controlled
bullet.damage = [stat]{0}[lightgray] damage
bullet.splashdamage = [stat]{0}[lightgray] area dmg ~[stat] {1}[lightgray] tiles
bullet.incendiary = [stat]incendiary

View file

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

View file

@ -0,0 +1,101 @@
package mindustry.ai.types;
import arc.struct.*;
import arc.util.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.logic.LExecutor.*;
import mindustry.logic.*;
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 = 15f * 60f;
public LUnitControl control = LUnitControl.stop;
public float moveX, moveY, moveRad;
public float itemTimer, controlTimer = logicControlTimeout, targetTimer;
//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){
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, 10f);
}
}
//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

@ -2,7 +2,6 @@ package mindustry.async;
import arc.*;
import arc.struct.*;
import mindustry.*;
import mindustry.game.EventType.*;
import java.util.concurrent.*;
@ -12,8 +11,7 @@ import static mindustry.Vars.*;
public class AsyncCore{
//all processes to be executed each frame
private final Seq<AsyncProcess> processes = Seq.with(
new PhysicsProcess(),
Vars.teamIndex = new TeamIndexProcess()
new PhysicsProcess()
);
//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
nova = new UnitType("nova"){{
itemCapacity = 60;
canBoost = true;
boostMultiplier = 1.5f;
speed = 0.55f;
@ -293,7 +292,6 @@ public class UnitTypes implements ContentList{
}};
pulsar = new UnitType("pulsar"){{
itemCapacity = 60;
canBoost = true;
boostMultiplier = 1.5f;
speed = 0.65f;
@ -340,7 +338,6 @@ public class UnitTypes implements ContentList{
mineTier = 1;
hitSize = 12f;
boostMultiplier = 2f;
itemCapacity = 80;
health = 650f;
buildSpeed = 1.7f;
canBoost = true;
@ -445,7 +442,6 @@ public class UnitTypes implements ContentList{
corvus = new UnitType("corvus"){{
mineTier = 1;
hitSize = 29f;
itemCapacity = 80;
health = 18000f;
buildSpeed = 1.7f;
armor = 9f;
@ -545,7 +541,6 @@ public class UnitTypes implements ContentList{
}};
atrax = new UnitType("atrax"){{
itemCapacity = 80;
speed = 0.5f;
drag = 0.4f;
hitSize = 10f;
@ -1134,7 +1129,6 @@ public class UnitTypes implements ContentList{
health = 100;
engineSize = 1.8f;
engineOffset = 5.7f;
itemCapacity = 30;
range = 50f;
isCounted = false;
@ -1153,7 +1147,6 @@ public class UnitTypes implements ContentList{
rotateSpeed = 15f;
accel = 0.1f;
range = 70f;
itemCapacity = 70;
health = 400;
buildSpeed = 0.5f;
engineOffset = 6.5f;

View file

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

View file

@ -65,7 +65,7 @@ public class Units{
/** @return whether a new instance of a unit of this team can be created. */
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){
@ -284,7 +284,7 @@ public class Units{
/** Iterates over all units in a rectangle. */
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. */
@ -316,7 +316,7 @@ public class Units{
//inactive teams have no cache, check everything
//TODO cache all teams with units OR blocks
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);
}
}

View file

@ -31,12 +31,12 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
public void getCollisions(Cons<QuadTree> consumer){
if(team.active()){
for(Team team : team.enemies()){
consumer.get(teamIndex.tree(team));
consumer.get(team.data().tree());
}
}else{
for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){
consumer.get(teamIndex.tree(other));
if(other != team && team.data().unitCount > 0){
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.*;
@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 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;
boolean spawnedByCore;
//TODO mark as non-transient when done
transient double flag;
transient Seq<Ability> abilities = new Seq<>(0);
private transient float resupplyTime = Mathf.random(10f);
@ -63,6 +66,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type.hasWeapons();
}
@Override
public float range(){
return type.range;
}
@ -76,6 +80,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
public double sense(LAccess sensor){
return switch(sensor){
case totalItems -> stack().amount;
case itemCapacity -> type.itemCapacity;
case rotation -> rotation;
case health -> health;
case maxHealth -> maxHealth;
@ -85,6 +90,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case shooting -> isShooting() ? 1 : 0;
case shootX -> aimX();
case shootY -> aimY();
case flag -> flag;
default -> 0;
};
}
@ -93,6 +99,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
public Object senseObject(LAccess sensor){
return switch(sensor){
case type -> type;
case name -> controller instanceof Player p ? p.name : null;
default -> noSensed;
};
@ -182,7 +189,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
public int count(){
return teamIndex.countType(team, type);
return team.data().countType(type);
}
public int cap(){
@ -224,13 +231,13 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//check if over unit cap
if(count() > cap() && !spawnedByCore && !dead){
Call.unitCapDeath(self());
teamIndex.updateCount(team, type, -1);
team.data().updateCount(type, -1);
}
}
@Override
public void remove(){
teamIndex.updateCount(team, type, -1);
team.data().updateCount(type, -1);
controller.removed(self());
}

View file

@ -63,6 +63,10 @@ public class AIController implements UnitController{
}
}
protected boolean invalid(Teamc target){
return Units.invalidateTarget(target, unit.team, unit.x, unit.y);
}
protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
@ -73,7 +77,7 @@ public class AIController implements UnitController{
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;
}
@ -99,13 +103,11 @@ public class AIController implements UnitController{
boolean shoot = false;
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);
mount.aimX = to.x;
mount.aimY = to.y;
}
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
}
mount.shoot = shoot;
@ -113,6 +115,10 @@ public class AIController implements UnitController{
}
}
protected boolean shouldShoot(){
return true;
}
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));
return target == null ? null : target.build;
@ -157,11 +163,15 @@ public class AIController implements UnitController{
}
protected void moveTo(Position target, float circleLength){
moveTo(target, circleLength, 100f);
}
protected void moveTo(Position target, float circleLength, float smooth){
if(target == null) return;
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);
if(length < -0.5f){

View file

@ -2,15 +2,20 @@ package mindustry.game;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.Queue;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
import static mindustry.Vars.*;
/** Class for various team-based utilities. */
@ -67,9 +72,7 @@ public class Teams{
/** Returns team data by type. */
public TeamData get(Team team){
if(map[team.id] == null){
map[team.id] = new TeamData(team);
}
if(map[team.id] == null) map[team.id] = new TeamData(team);
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(){
if(state.rules.waves && !active.contains(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 Team team;
public final BaseAI ai;
@ -160,11 +218,48 @@ public class Teams{
/** Target items to mine. */
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){
this.team = team;
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(){
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){
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.table(Styles.black6, b -> {
b.defaults().left();

View file

@ -77,6 +77,19 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
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)
public static void transferItemToUnit(Item item, float x, float y, Itemsc to){
if(to == null) return;
@ -92,6 +105,16 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
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){
Fx.itemTransfer.at(x, y, amount, item.color, to);
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)
public static void removeQueueBlock(int x, int y, boolean breaking){
player.builder().removeBuild(x, y, breaking);

View file

@ -26,6 +26,8 @@ public enum LAccess{
shooting,
team,
type,
flag,
name,
//values with parameters are considered controllable
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 static final LAccess[]
all = values(),
senseable = Seq.select(all, t -> t.parameters.length <= 1).toArray(LAccess.class),
controls = Seq.select(all, t -> t.parameters.length > 0).toArray(LAccess.class);
senseable = Seq.select(all, t -> t.params.length <= 1).toArray(LAccess.class),
controls = Seq.select(all, t -> t.params.length > 0).toArray(LAccess.class);
LAccess(String... parameters){
this.parameters = parameters;
LAccess(String... params){
this.params = params;
isObj = false;
}
LAccess(boolean obj, String... parameters){
this.parameters = parameters;
LAccess(boolean obj, String... params){
this.params = params;
isObj = obj;
}
}

View file

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

View file

@ -7,7 +7,8 @@ public enum LCategory{
blocks(Pal.accentBack),
control(Color.cyan.cpy().shiftSaturation(-0.6f).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;

View file

@ -4,10 +4,13 @@ import arc.struct.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.*;
import mindustry.ai.types.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.logic.LogicDisplay.*;
import mindustry.world.blocks.logic.MemoryBlock.*;
import mindustry.world.blocks.logic.MessageBlock.*;
@ -23,7 +26,8 @@ public class LExecutor{
//special variables
public static final int
varCounter = 0,
varTime = 1;
varTime = 1,
varUnit = 2;
public static final int
maxGraphicsBuffer = 256,
@ -36,6 +40,7 @@ public class LExecutor{
public LongSeq graphicsBuffer = new LongSeq();
public StringBuilder textBuffer = new StringBuilder();
public Building[] links = {};
public Team team = Team.derelict;
public boolean initialized(){
return instructions != null && vars != null && instructions.length > 0;
@ -102,6 +107,11 @@ public class LExecutor{
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){
return (int)num(index);
}
@ -121,6 +131,12 @@ public class LExecutor{
v.isobj = true;
}
public void setconst(int index, Object value){
Var v = vars[index];
v.objval = value;
v.isobj = true;
}
//endregion
public static class Var{
@ -142,6 +158,153 @@ public class LExecutor{
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());
//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 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. */
public static class ControlI implements LInstruction{
public int target;
@ -311,16 +474,20 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
Building target = exec.building(radar);
Object base = exec.obj(radar);
int sortDir = exec.bool(sortOrder) ? 1 : -1;
LogicAI ai = null;
if(target instanceof Ranged){
float range = ((Ranged)target).range();
if(base instanceof Ranged r && r.team() == exec.team &&
(base instanceof Building || (ai = UnitControlI.checkLogicAI(exec, base)) != null)){ //must be a building or a controllable unit
float range = r.range();
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
boolean enemies = target1 == RadarTarget.enemy || target2 == RadarTarget.enemy || target3 == RadarTarget.enemy;
@ -328,11 +495,11 @@ public class LExecutor{
bestValue = 0;
if(enemies){
for(Team enemy : state.teams.enemiesOf(target.team)){
find(target, range, sortDir, enemy);
for(Team enemy : state.teams.enemiesOf(r.team())){
find(r, range, sortDir, enemy);
}
}else{
find(target, range, sortDir, target.team);
find(r, range, sortDir, r.team());
}
lastTarget = targeted = best;
@ -346,14 +513,14 @@ public class LExecutor{
}
}
void find(Building b, float range, int sortDir, Team team){
Units.nearby(team, b.x, b.y, range, u -> {
void find(Ranged b, float range, int sortDir, Team team){
Units.nearby(team, b.x(), b.y(), range, u -> {
if(!u.within(b, range)) return;
boolean valid =
target1.func.get(b.team, u) &&
target2.func.get(b.team, u) &&
target3.func.get(b.team, u);
target1.func.get(b.team(), u) &&
target2.func.get(b.team(), u) &&
target3.func.get(b.team(), u);
if(!valid) return;

View file

@ -31,6 +31,10 @@ public abstract class LStatement{
return read.size == 0 ? null : read.first();
}
public boolean hidden(){
return false;
}
//protected methods are only for internal UI layout utilities
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();
}
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();
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){

View file

@ -347,9 +347,9 @@ public class LStatements{
//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.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);
}
@ -376,11 +376,13 @@ public class LStatements{
public void build(Table table){
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++){
int fi = i;
@ -420,6 +422,10 @@ public class LStatements{
fields(table, output, v -> output = v);
}
public boolean buildFrom(){
return true;
}
@Override
public LCategory category(){
return LCategory.blocks;
@ -694,4 +700,95 @@ public class LStatements{
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,20 @@
package mindustry.logic;
public enum LUnitControl{
stop,
move("x", "y"),
approach("x", "y", "radius"),
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;
for(Prov<LStatement> prov : LogicIO.allStatements){
LStatement example = prov.get();
if(example instanceof InvalidStatement) continue;
if(example instanceof InvalidStatement || example.hidden()) continue;
TextButtonStyle style = new TextButtonStyle(Styles.cleart);
style.fontColor = example.category().color;

View file

@ -1,6 +1,7 @@
package mindustry.logic;
import arc.math.*;
import arc.util.*;
public enum LogicOp{
add("+", (a, b) -> a + b),
@ -9,8 +10,8 @@ public enum LogicOp{
div("/", (a, b) -> a / b),
idiv("//", (a, b) -> Math.floor(a / b)),
mod("%", (a, b) -> a % b),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> a == b ? 1 : 0),
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1, (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) -> !Structs.eq(a, b) ? 1 : 0),
lessThan("<", (a, b) -> a < b ? 1 : 0),
lessThanEq("<=", (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();
}
}).growX();
if(unit.controller() instanceof LogicAI){
table.row();
table.add(Blocks.microProcessor.emoji() + " " + Core.bundle.get("units.processorcontrol")).growX().left();
}
table.row();
}
@ -206,7 +211,7 @@ public class UnitType extends UnlockableContent{
singleTarget = weapons.size <= 1;
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

View file

@ -14,12 +14,8 @@ import arc.scene.ui.layout.Stack;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.*;
@ -42,29 +38,6 @@ public class BlockInventoryFragment extends Fragment{
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
public void build(Group parent){
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(() -> {

View file

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

View file

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

View file

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

View file

@ -331,6 +331,7 @@ public class LogicBlock extends Block{
@Override
public void updateTile(){
executor.team = team;
//check for previously invalid links to add after configuration
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);
switch(type){
case commandClear: Core.graphics.clear(x/255f, y/255f, p1/255f, 1f); break;
case commandLine: Lines.line(x, y, p1, p2); break;
case commandRect: Fill.crect(x, y, p1, p2); break;
case commandLineRect: Lines.rect(x, y, p1, p2); break;
case commandPoly: Fill.poly(x, y, Math.min(p1, maxSides), p2, p3); break;
case commandLinePoly: Lines.poly(x, y, Math.min(p1, maxSides), p2, p3); break;
case commandTriangle: Fill.tri(x, y, p1, p2, p3, p4); break;
case commandColor: this.color = Color.toFloatBits(x, y, p1, p2); Draw.color(this.color); break;
case commandStroke: this.stroke = x; Lines.stroke(x); break;
case commandClear -> Core.graphics.clear(x / 255f, y / 255f, p1 / 255f, 1f);
case commandLine -> Lines.line(x, y, p1, p2);
case commandRect -> Fill.crect(x, y, p1, p2);
case commandLineRect -> Lines.rect(x, y, p1, p2);
case commandPoly -> Fill.poly(x, y, Math.min(p1, maxSides), p2, p3);
case commandLinePoly -> Lines.poly(x, y, Math.min(p1, maxSides), p2, p3);
case commandTriangle -> Fill.tri(x, y, p1, p2, p3, p4);
case commandColor -> Draw.color(this.color = Color.toFloatBits(x, y, p1, p2));
case commandStroke -> Lines.stroke(this.stroke = x);
}
}

View file

@ -50,11 +50,11 @@ public class Reconstructor extends UnitBlock{
() -> e.unit() == null ? "[lightgray]" + Iconc.cancel :
Core.bundle.format("bar.unitcap",
Fonts.getUnicodeStr(e.unit().name),
teamIndex.countType(e.team, e.unit()),
e.team.data().countType(e.unit()),
Units.getCap(e.team)
),
() -> 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.meta.*;
import static mindustry.Vars.*;
public class UnitFactory extends UnitBlock{
public int[] capacities;
@ -71,11 +69,11 @@ public class UnitFactory extends UnitBlock{
() -> e.unit() == null ? "[lightgray]" + Iconc.cancel :
Core.bundle.format("bar.unitcap",
Fonts.getUnicodeStr(e.unit().name),
teamIndex.countType(e.team, e.unit()),
e.team.data().countType(e.unit()),
Units.getCap(e.team)
),
() -> 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)
));
}