Merge branch 'master' of https://github.com/Anuken/Mindustry into crux-floor
# Conflicts: # gradle.properties
This commit is contained in:
@@ -202,6 +202,10 @@ public class Vars implements Loadable{
|
||||
/** Whether to draw shadows of blocks at map edges and static blocks.
|
||||
* Do not change unless you know exactly what you are doing.*/
|
||||
public static boolean enableDarkness = true;
|
||||
/** Whether to draw debug lines for collisions. */
|
||||
public static boolean drawDebugHitboxes = false;
|
||||
/** Whether to draw avoidance fields. */
|
||||
public static boolean debugDrawAvoidance = false;
|
||||
/** application data directory, equivalent to {@link Settings#getDataDirectory()} */
|
||||
public static Fi dataDirectory;
|
||||
/** data subdirectory used for screenshots */
|
||||
@@ -255,6 +259,7 @@ public class Vars implements Loadable{
|
||||
public static BaseRegistry bases;
|
||||
public static GlobalVars logicVars;
|
||||
public static MapEditor editor;
|
||||
public static AvoidanceProcess avoidance;
|
||||
public static GameService service = new GameService();
|
||||
|
||||
public static Universe universe;
|
||||
|
||||
@@ -8,17 +8,26 @@ import mindustry.type.*;
|
||||
|
||||
public class ItemUnitStance extends UnitStance{
|
||||
private static ObjectMap<Item, ItemUnitStance> itemToStance = new ObjectMap<>();
|
||||
private static Seq<ItemUnitStance> all = new Seq<>();
|
||||
|
||||
public final Item item;
|
||||
|
||||
public ItemUnitStance(Item item){
|
||||
super("item-" + item.name, "item-" + item.name, null);
|
||||
this.item = item;
|
||||
|
||||
incompatibleStances.add(UnitStance.mineAuto).addAll(UnitStance.mineAuto.incompatibleStances);
|
||||
|
||||
itemToStance.put(item, this);
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public static @Nullable ItemUnitStance getByItem(Item item){
|
||||
return itemToStance.get(item);
|
||||
return item == null ? null : itemToStance.get(item);
|
||||
}
|
||||
|
||||
public static Seq<ItemUnitStance> all(){
|
||||
return all;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -121,7 +121,6 @@ public class Pathfinder implements Runnable{
|
||||
mainList = new Seq<>();
|
||||
clearCache();
|
||||
|
||||
|
||||
for(int i = 0; i < tiles.length; i++){
|
||||
Tile tile = world.tiles.geti(i);
|
||||
tiles[i] = packTile(tile);
|
||||
@@ -253,7 +252,7 @@ public class Pathfinder implements Runnable{
|
||||
nearSolid,
|
||||
nearLegSolid,
|
||||
tile.floor().isDeep(),
|
||||
tile.floor().damageTaken > 0.00001f,
|
||||
tile.floor().damages(),
|
||||
allDeep,
|
||||
nearDeep,
|
||||
tile.block().teamPassable
|
||||
@@ -359,6 +358,11 @@ public class Pathfinder implements Runnable{
|
||||
|
||||
/** Gets next tile to travel to. Main thread only. */
|
||||
public @Nullable Tile getTargetTile(Tile tile, Flowfield path, boolean diagonals){
|
||||
return getTargetTile(tile, path, diagonals, 0);
|
||||
}
|
||||
|
||||
/** Gets next tile to travel to. Main thread only. */
|
||||
public @Nullable Tile getTargetTile(Tile tile, Flowfield path, boolean diagonals, int avoidanceId){
|
||||
if(tile == null) return null;
|
||||
|
||||
//uninitialized flowfields are not applicable
|
||||
@@ -390,6 +394,7 @@ public class Pathfinder implements Runnable{
|
||||
int value = values[apos];
|
||||
|
||||
var points = diagonals ? Geometry.d8 : Geometry.d4;
|
||||
int[] avoid = avoidanceId <= 0 ? null : avoidance.getAvoidance();
|
||||
|
||||
Tile current = null;
|
||||
int tl = 0;
|
||||
@@ -400,11 +405,13 @@ public class Pathfinder implements Runnable{
|
||||
if(other == null) continue;
|
||||
|
||||
int packed = dx/res + dy/res * ww;
|
||||
int avoidance = avoid == null ? 0 : avoid[packed] > Integer.MAX_VALUE - avoidanceId ? 1 : 0;
|
||||
int cost = values[packed] + avoidance;
|
||||
|
||||
if(values[packed] < value && (current == null || values[packed] < tl) && path.passable(packed) &&
|
||||
if(cost < value && avoidance == 0 && (current == null || cost < tl) && path.passable(packed) &&
|
||||
!(point.x != 0 && point.y != 0 && (!path.passable(((tile.x + point.x)/res + tile.y/res*ww)) || !path.passable((tile.x/res + (tile.y + point.y)/res*ww))))){ //diagonal corner trap
|
||||
current = other;
|
||||
tl = values[packed];
|
||||
tl = cost;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,6 +662,11 @@ public class Pathfinder implements Runnable{
|
||||
return pathfinder.getTargetTile(from, this);
|
||||
}
|
||||
|
||||
/** @return the next tile to travel to for this flowfield. Main thread only. */
|
||||
public @Nullable Tile getNextTile(Tile from, int unitAvoidanceId){
|
||||
return pathfinder.getTargetTile(from, this, true, unitAvoidanceId);
|
||||
}
|
||||
|
||||
public boolean hasCompleteWeights(){
|
||||
return hasComplete && completeWeights != null;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package mindustry.ai;
|
||||
import arc.*;
|
||||
import arc.input.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ctype.*;
|
||||
@@ -11,17 +12,39 @@ import mindustry.input.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class UnitStance extends MappableContent{
|
||||
public static UnitStance stop, shoot, holdFire, pursueTarget, patrol, ram, mineAuto;
|
||||
public static UnitStance stop, holdFire, pursueTarget, patrol, ram, mineAuto;
|
||||
|
||||
/** Name of UI icon (from Icon class). */
|
||||
public String icon;
|
||||
/** Key to press for this stance. */
|
||||
public @Nullable KeyBind keybind;
|
||||
/** Stances that are mutually exclusive to this stance. This is used for convenience, for writing only! */
|
||||
public Seq<UnitStance> incompatibleStances = new Seq<>();
|
||||
/** Incompatible stances as a bitset for easier operations. This is where incompatibility is actually stored. */
|
||||
public Bits incompatibleBits = new Bits(32);
|
||||
/** If true, this stance can be toggled on or off. */
|
||||
public boolean toggle = true;
|
||||
|
||||
public UnitStance(String name, String icon, KeyBind keybind){
|
||||
public UnitStance(String name, String icon, KeyBind keybind, boolean toggle){
|
||||
super(name);
|
||||
this.icon = icon;
|
||||
this.keybind = keybind;
|
||||
this.toggle = toggle;
|
||||
}
|
||||
|
||||
public UnitStance(String name, String icon, KeyBind keybind){
|
||||
this(name, icon, keybind, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
|
||||
for(var stance : incompatibleStances){
|
||||
if(stance == this) continue;
|
||||
incompatibleBits.set(stance.id);
|
||||
stance.incompatibleBits.set(id);
|
||||
}
|
||||
}
|
||||
|
||||
public String localized(){
|
||||
@@ -47,13 +70,12 @@ public class UnitStance extends MappableContent{
|
||||
}
|
||||
|
||||
public static void loadAll(){
|
||||
stop = new UnitStance("stop", "cancel", Binding.cancelOrders);
|
||||
shoot = new UnitStance("shoot", "commandAttack", Binding.unitStanceShoot);
|
||||
stop = new UnitStance("stop", "cancel", Binding.cancelOrders, false);
|
||||
holdFire = new UnitStance("holdfire", "none", Binding.unitStanceHoldFire);
|
||||
pursueTarget = new UnitStance("pursuetarget", "right", Binding.unitStancePursueTarget);
|
||||
patrol = new UnitStance("patrol", "refresh", Binding.unitStancePatrol);
|
||||
ram = new UnitStance("ram", "rightOpen", Binding.unitStanceRam);
|
||||
mineAuto = new UnitStance("mineauto", "settings", null);
|
||||
mineAuto = new UnitStance("mineauto", "settings", null, false);
|
||||
|
||||
//Only vanilla items are supported for now
|
||||
for(Item item : Vars.content.items()){
|
||||
|
||||
@@ -40,10 +40,10 @@ public class CommandAI extends AIController{
|
||||
protected float payloadPickupCooldown;
|
||||
protected int transferState = transferStateNone;
|
||||
|
||||
/** Stance, usually related to firing mode. */
|
||||
public UnitStance stance = UnitStance.shoot;
|
||||
/** Current command this unit is following. */
|
||||
public UnitCommand command;
|
||||
/** Stance, usually related to firing mode. Each bit is a stance ID. */
|
||||
public Bits stances = new Bits(content.unitStances().size);
|
||||
/** Current controller instance based on command. */
|
||||
protected @Nullable AIController commandController;
|
||||
/** Last command type assigned. Used for detecting command changes. */
|
||||
@@ -63,6 +63,38 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasStance(@Nullable UnitStance stance){
|
||||
return stance != null && stances.get(stance.id);
|
||||
}
|
||||
|
||||
public void setStance(UnitStance stance, boolean enabled){
|
||||
if(enabled){
|
||||
setStance(stance);
|
||||
}else{
|
||||
disableStance(stance);
|
||||
}
|
||||
}
|
||||
|
||||
public void setStance(UnitStance stance){
|
||||
//this happens when an older save reads the default "shoot" stance, or any other removed stance
|
||||
if(stance == UnitStance.stop) return;
|
||||
|
||||
stances.andNot(stance.incompatibleBits);
|
||||
stances.set(stance.id);
|
||||
stanceChanged();
|
||||
}
|
||||
|
||||
public void disableStance(UnitStance stance){
|
||||
stances.clear(stance.id);
|
||||
stanceChanged();
|
||||
}
|
||||
|
||||
public void stanceChanged(){
|
||||
if(commandController != null && !(commandController instanceof CommandAI)){
|
||||
commandController.stanceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
if(command == null){
|
||||
@@ -82,21 +114,18 @@ public class CommandAI extends AIController{
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
//this should not be possible
|
||||
if(stance == UnitStance.stop) stance = UnitStance.shoot;
|
||||
|
||||
//fix incorrect stance when mining
|
||||
if(command == UnitCommand.mineCommand && stance != UnitStance.mineAuto && !(stance instanceof ItemUnitStance)){
|
||||
stance = UnitStance.mineAuto;
|
||||
if(command == UnitCommand.mineCommand && !hasStance(UnitStance.mineAuto) && !ItemUnitStance.all().contains(this::hasStance)){
|
||||
setStance(UnitStance.mineAuto);
|
||||
}
|
||||
|
||||
//pursue the target if relevant
|
||||
if(stance == UnitStance.pursueTarget && target != null && attackTarget == null && targetPos == null){
|
||||
if(hasStance(UnitStance.pursueTarget) && !hasStance(UnitStance.patrol) && target != null && attackTarget == null && targetPos == null){
|
||||
commandTarget(target, false);
|
||||
}
|
||||
|
||||
//pursue the target for patrol, keeping the current position
|
||||
if(stance == UnitStance.patrol && target != null && attackTarget == null){
|
||||
if(hasStance(UnitStance.patrol) && hasStance(UnitStance.pursueTarget) && target != null && attackTarget == null){
|
||||
//commanding a target overwrites targetPos, so add it to the queue
|
||||
if(targetPos != null){
|
||||
commandQueue.add(targetPos.cpy());
|
||||
@@ -207,6 +236,8 @@ public class CommandAI extends AIController{
|
||||
finishPath();
|
||||
}
|
||||
|
||||
boolean ramming = hasStance(UnitStance.ram);
|
||||
|
||||
if(attackTarget != null){
|
||||
if(targetPos == null){
|
||||
targetPos = new Vec2();
|
||||
@@ -214,7 +245,7 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
targetPos.set(attackTarget);
|
||||
|
||||
if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.type.pathCostId != ControlPathfinder.costIdLegs && stance != UnitStance.ram){
|
||||
if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.type.pathCostId != ControlPathfinder.costIdLegs && !ramming){
|
||||
Tile best = build.findClosestEdge(unit, Tile::solid);
|
||||
if(best != null){
|
||||
targetPos.set(best);
|
||||
@@ -225,7 +256,7 @@ public class CommandAI extends AIController{
|
||||
boolean alwaysArrive = false;
|
||||
|
||||
float engageRange = unit.type.range - 10f;
|
||||
boolean withinAttackRange = attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram;
|
||||
boolean withinAttackRange = attackTarget != null && unit.within(attackTarget, engageRange) && !ramming;
|
||||
|
||||
if(targetPos != null){
|
||||
boolean move = true, isFinalPoint = commandQueue.size == 0;
|
||||
@@ -239,16 +270,17 @@ public class CommandAI extends AIController{
|
||||
|
||||
Building targetBuild = world.buildWorld(targetPos.x, targetPos.y);
|
||||
|
||||
|
||||
//TODO: should the unit stop when it finds a target?
|
||||
if(
|
||||
(stance == UnitStance.patrol && target != null && unit.within(target, unit.type.range - 2f) && !unit.type.circleTarget) ||
|
||||
(hasStance(UnitStance.patrol) && !hasStance(UnitStance.pursueTarget) && target != null && unit.within(target, unit.type.range - 2f) && !unit.type.circleTarget) ||
|
||||
(command == UnitCommand.enterPayloadCommand && unit.within(targetPos, 4f) || (targetBuild != null && unit.within(targetBuild, targetBuild.block.size * tilesize/2f * 0.9f))) ||
|
||||
(command == UnitCommand.loopPayloadCommand && unit.within(targetPos, 10f))
|
||||
){
|
||||
move = false;
|
||||
}
|
||||
|
||||
if(unit.isGrounded() && stance != UnitStance.ram){
|
||||
if(unit.isGrounded() && !ramming){
|
||||
//TODO: blocking enable or disable?
|
||||
if(timer.get(timerTarget3, avoidInterval)){
|
||||
Vec2 dstPos = Tmp.v1.trns(unit.rotation, unit.hitSize/2f);
|
||||
@@ -284,7 +316,7 @@ public class CommandAI extends AIController{
|
||||
noFound[0] = false;
|
||||
vecOut.set(vecMovePos);
|
||||
}else{
|
||||
move = controlPath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
|
||||
move &= controlPath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
|
||||
|
||||
//TODO: what to do when there's a target and it can't be reached?
|
||||
/*
|
||||
@@ -320,7 +352,7 @@ public class CommandAI extends AIController{
|
||||
moveTo(vecOut,
|
||||
withinAttackRange ? engageRange :
|
||||
unit.isGrounded() ? 0f :
|
||||
attackTarget != null && stance != UnitStance.ram ? engageRange : 0f,
|
||||
attackTarget != null && !ramming ? engageRange : 0f,
|
||||
unit.isFlying() ? 40f : 100f, false, null, isFinalPoint || alwaysArrive);
|
||||
}
|
||||
}
|
||||
@@ -412,7 +444,7 @@ public class CommandAI extends AIController{
|
||||
commandPosition(position);
|
||||
}
|
||||
|
||||
if(prev != null && (stance == UnitStance.patrol || command == UnitCommand.loopPayloadCommand)){
|
||||
if(prev != null && (hasStance(UnitStance.patrol) || command == UnitCommand.loopPayloadCommand)){
|
||||
commandQueue.add(prev.cpy());
|
||||
}
|
||||
|
||||
@@ -454,7 +486,7 @@ public class CommandAI extends AIController{
|
||||
|
||||
@Override
|
||||
public boolean shouldFire(){
|
||||
return stance != UnitStance.holdFire;
|
||||
return !hasStance(UnitStance.holdFire);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
@@ -9,11 +12,19 @@ import mindustry.world.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class GroundAI extends AIController{
|
||||
float stuckTime = 0f;
|
||||
float stuckX = -999f, stuckY = -999f;
|
||||
|
||||
static final float stuckRange = tilesize * 1.5f;
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
|
||||
//if it hasn't moved the stuck range in twice the time it should have taken, it's stuck
|
||||
float stuckThreshold = Math.max(1f, stuckRange * 2f / unit.type.speed);
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
boolean moved = false;
|
||||
|
||||
if(core != null && unit.within(core, unit.range() / 1.3f + core.block.size * tilesize / 2f)){
|
||||
target = core;
|
||||
@@ -38,7 +49,9 @@ public class GroundAI extends AIController{
|
||||
move = false;
|
||||
}
|
||||
|
||||
if(move) pathfind(Pathfinder.fieldCore);
|
||||
moved = move;
|
||||
|
||||
if(move) pathfind(Pathfinder.fieldCore, true, stuckTime >= stuckThreshold);
|
||||
}
|
||||
|
||||
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){
|
||||
@@ -46,5 +59,28 @@ public class GroundAI extends AIController{
|
||||
}
|
||||
|
||||
faceTarget();
|
||||
|
||||
if(moved){
|
||||
|
||||
if(unit.within(stuckX, stuckY, stuckRange)){
|
||||
stuckTime += Time.delta;
|
||||
if(stuckTime - Time.delta < stuckThreshold && stuckTime >= stuckThreshold){
|
||||
float radius = unit.hitSize * Vars.unitCollisionRadiusScale * 2f;
|
||||
Units.nearby(unit.team, unit.x, unit.y, radius, other -> {
|
||||
if(other != unit && other.controller() instanceof GroundAI ai && other.within(unit.x, unit.y, radius + other.hitSize * unitCollisionRadiusScale)){
|
||||
ai.stuckX = other.x;
|
||||
ai.stuckY = other.y;
|
||||
ai.stuckTime = Math.max(1f, stuckRange * 2f / other.type.speed) + 1f;
|
||||
}
|
||||
});
|
||||
}
|
||||
}else{
|
||||
stuckX = unit.x;
|
||||
stuckY = unit.y;
|
||||
stuckTime = 0f;
|
||||
}
|
||||
}else{
|
||||
stuckTime = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,14 @@ public class MinerAI extends AIController{
|
||||
public Item targetItem;
|
||||
public Tile ore;
|
||||
|
||||
@Override
|
||||
public void stanceChanged(){
|
||||
if(targetItem != null && unit.controller() instanceof CommandAI ai && !ai.hasStance(UnitStance.mineAuto) && !ai.hasStance(ItemUnitStance.getByItem(targetItem))){
|
||||
mining = false;
|
||||
targetItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
Building core = unit.closestCore();
|
||||
@@ -23,13 +31,15 @@ public class MinerAI extends AIController{
|
||||
unit.mineTile(null);
|
||||
}
|
||||
|
||||
Item autoItem = unit.controller() instanceof CommandAI ai && ai.stance instanceof ItemUnitStance stance ? stance.item : null;
|
||||
CommandAI ai = unit.controller() instanceof CommandAI a ? a : null;
|
||||
|
||||
if(mining){
|
||||
if(autoItem != null){
|
||||
targetItem = autoItem;
|
||||
}else if(timer.get(timerTarget2, 60 * 4) || targetItem == null){
|
||||
targetItem = unit.type.mineItems.min(i -> indexer.hasOre(i) && unit.canMine(i), i -> core.items.get(i));
|
||||
if(timer.get(timerTarget2, 60 * 4) || targetItem == null){
|
||||
if(ai != null && !ai.hasStance(UnitStance.mineAuto)){
|
||||
targetItem = content.items().min(i -> indexer.hasOre(i) && unit.canMine(i) && ai.hasStance(ItemUnitStance.getByItem(i)), i -> core.items.get(i));
|
||||
}else{
|
||||
targetItem = unit.type.mineItems.min(i -> indexer.hasOre(i) && unit.canMine(i), i -> core.items.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
//core full of the target item, do nothing
|
||||
|
||||
@@ -12,7 +12,8 @@ import static mindustry.Vars.*;
|
||||
public class AsyncCore{
|
||||
//all processes to be executed each frame
|
||||
public final Seq<AsyncProcess> processes = Seq.with(
|
||||
new PhysicsProcess()
|
||||
new PhysicsProcess(),
|
||||
avoidance = new AvoidanceProcess()
|
||||
);
|
||||
|
||||
//futures to be awaited
|
||||
|
||||
112
core/src/mindustry/async/AvoidanceProcess.java
Normal file
112
core/src/mindustry/async/AvoidanceProcess.java
Normal file
@@ -0,0 +1,112 @@
|
||||
package mindustry.async;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class AvoidanceProcess implements AsyncProcess{
|
||||
/** cached world size */
|
||||
static int wwidth, wheight;
|
||||
|
||||
@Nullable int[] buffer1, buffer2;
|
||||
volatile boolean swap;
|
||||
|
||||
IntSeq requests = new IntSeq();
|
||||
|
||||
@Nullable int[] avoidance;
|
||||
boolean modified;
|
||||
boolean active;
|
||||
|
||||
public @Nullable int[] getAvoidance(){
|
||||
if(!active){
|
||||
//lazily initialize and begin processing after this first request
|
||||
buffer1 = new int[wwidth * wheight];
|
||||
buffer2 = new int[wwidth * wheight];
|
||||
active = true;
|
||||
}
|
||||
return avoidance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
wwidth = Vars.world.width();
|
||||
wheight = Vars.world.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(){
|
||||
buffer1 = buffer2 = avoidance = null;
|
||||
swap = false;
|
||||
modified = false;
|
||||
active = false;
|
||||
requests.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
if(!active) return;
|
||||
|
||||
requests.clear();
|
||||
|
||||
avoidance = !swap ? buffer1 : buffer2;
|
||||
|
||||
for(var team : state.teams.present){
|
||||
//only do avoidance if it's relevant to the team
|
||||
if(team.team.isAI() && !team.team.rules().rtsAi){
|
||||
for(var unit : team.units){
|
||||
if(unit.collisionLayer() == PhysicsProcess.layerGround){
|
||||
//scaling is oversized 2x because units need to avoid things that are at their origin tile
|
||||
float scaling = 2f;
|
||||
requests.add(Point2.pack(unit.tileX(), unit.tileY()), Float.floatToRawIntBits(unit.hitSize * unitCollisionRadiusScale / tilesize * scaling), unit.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(){
|
||||
//double buffering; one buffer is always valid (not being updated)
|
||||
var buffer = swap ? buffer1 : buffer2;
|
||||
swap = !swap;
|
||||
|
||||
if(buffer == null) return;
|
||||
//technically, this is wrong, and will lead to flickering avoidance when all units are gone, but this doesn't matter because it's not being queried either way
|
||||
if(modified){
|
||||
Arrays.fill(buffer, 0);
|
||||
}
|
||||
|
||||
modified = requests.size > 0;
|
||||
|
||||
int total = requests.size;
|
||||
int[] items = requests.items;
|
||||
for(int i = 0; i < total; i += 3){
|
||||
int point = items[i], id = items[i + 2];
|
||||
int rx = Point2.x(point), ry = Point2.y(point);
|
||||
float rad = Float.intBitsToFloat(items[i + 1]);
|
||||
float rad2 = rad * rad;
|
||||
|
||||
int r = Math.max(1, Mathf.ceil(rad));
|
||||
|
||||
for(int dx = -r; dx <= r; dx++){
|
||||
for(int dy = -r; dy <= r; dy++){
|
||||
int x = dx + rx, y = dy + ry;
|
||||
if(x >= 0 && y >= 0 && x < wwidth && y < wheight && (dx*dx + dy*dy) <= rad2){
|
||||
buffer[x + y * wwidth] = Math.max(buffer[x + y * wwidth], Integer.MAX_VALUE - id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldProcess(){
|
||||
return active;
|
||||
}
|
||||
}
|
||||
@@ -6264,7 +6264,7 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
tankAssembler = new UnitAssembler("tank-assembler"){{
|
||||
requirements(Category.units, with(Items.thorium, 500, Items.oxide, 150, Items.carbide, 80, Items.silicon, 500));
|
||||
requirements(Category.units, with(Items.thorium, 500, Items.oxide, 150, Items.carbide, 80, Items.silicon, 650));
|
||||
regionSuffix = "-dark";
|
||||
size = 5;
|
||||
plans.add(
|
||||
@@ -6274,12 +6274,12 @@ public class Blocks{
|
||||
areaSize = 13;
|
||||
researchCostMultiplier = 0.4f;
|
||||
|
||||
consumePower(3f);
|
||||
consumePower(2.5f);
|
||||
consumeLiquid(Liquids.cyanogen, 9f / 60f);
|
||||
}};
|
||||
|
||||
shipAssembler = new UnitAssembler("ship-assembler"){{
|
||||
requirements(Category.units, with(Items.carbide, 100, Items.oxide, 200, Items.tungsten, 500, Items.silicon, 800, Items.thorium, 400));
|
||||
requirements(Category.units, with(Items.carbide, 100, Items.oxide, 200, Items.tungsten, 550, Items.silicon, 900, Items.thorium, 400));
|
||||
regionSuffix = "-dark";
|
||||
size = 5;
|
||||
plans.add(
|
||||
@@ -6288,12 +6288,12 @@ public class Blocks{
|
||||
);
|
||||
areaSize = 13;
|
||||
|
||||
consumePower(3f);
|
||||
consumePower(2.5f);
|
||||
consumeLiquid(Liquids.cyanogen, 12f / 60f);
|
||||
}};
|
||||
|
||||
mechAssembler = new UnitAssembler("mech-assembler"){{
|
||||
requirements(Category.units, with(Items.carbide, 200, Items.thorium, 600, Items.oxide, 200, Items.tungsten, 500, Items.silicon, 900));
|
||||
requirements(Category.units, with(Items.carbide, 200, Items.thorium, 600, Items.oxide, 200, Items.tungsten, 550, Items.silicon, 1000));
|
||||
regionSuffix = "-dark";
|
||||
size = 5;
|
||||
//TODO different reqs
|
||||
@@ -6303,14 +6303,14 @@ public class Blocks{
|
||||
);
|
||||
areaSize = 13;
|
||||
|
||||
consumePower(3.5f);
|
||||
consumePower(3f);
|
||||
consumeLiquid(Liquids.cyanogen, 12f / 60f);
|
||||
}};
|
||||
|
||||
//TODO requirements / only accept inputs
|
||||
basicAssemblerModule = new UnitAssemblerModule("basic-assembler-module"){{
|
||||
requirements(Category.units, with(Items.carbide, 300, Items.thorium, 500, Items.oxide, 200, Items.phaseFabric, 400));
|
||||
consumePower(4f);
|
||||
requirements(Category.units, with(Items.carbide, 300, Items.thorium, 500, Items.oxide, 250, Items.phaseFabric, 400));
|
||||
consumePower(3.5f);
|
||||
regionSuffix = "-dark";
|
||||
researchCostMultiplier = 0.75f;
|
||||
|
||||
@@ -6360,7 +6360,7 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
payloadMassDriver = new PayloadMassDriver("payload-mass-driver"){{
|
||||
requirements(Category.units, with(Items.tungsten, 120, Items.silicon, 120, Items.graphite, 50));
|
||||
requirements(Category.units, with(Items.tungsten, 40, Items.silicon, 50, Items.graphite, 20));
|
||||
regionSuffix = "-dark";
|
||||
size = 3;
|
||||
reload = 130f;
|
||||
@@ -6372,13 +6372,13 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
largePayloadMassDriver = new PayloadMassDriver("large-payload-mass-driver"){{
|
||||
requirements(Category.units, with(Items.thorium, 200, Items.tungsten, 200, Items.silicon, 200, Items.graphite, 100, Items.oxide, 30));
|
||||
requirements(Category.units, with(Items.phaseFabric, 20, Items.tungsten, 200, Items.silicon, 200, Items.graphite, 100, Items.oxide, 30));
|
||||
regionSuffix = "-dark";
|
||||
size = 5;
|
||||
reload = 130f;
|
||||
chargeTime = 100f;
|
||||
range = 1100f;
|
||||
maxPayloadSize = 3.5f;
|
||||
range = 2100f;
|
||||
maxPayloadSize = 4f;
|
||||
consumePower(3f);
|
||||
}};
|
||||
|
||||
@@ -6388,24 +6388,24 @@ public class Blocks{
|
||||
itemCapacity = 100;
|
||||
consumePower(1f);
|
||||
size = 3;
|
||||
deconstructSpeed = 1f;
|
||||
deconstructSpeed = 3f;
|
||||
}};
|
||||
|
||||
deconstructor = new PayloadDeconstructor("deconstructor"){{
|
||||
requirements(Category.units, with(Items.beryllium, 250, Items.oxide, 100, Items.silicon, 250, Items.carbide, 250));
|
||||
requirements(Category.units, with(Items.beryllium, 250, Items.oxide, 100, Items.silicon, 250, Items.carbide, 50));
|
||||
regionSuffix = "-dark";
|
||||
itemCapacity = 250;
|
||||
consumePower(3f);
|
||||
size = 5;
|
||||
deconstructSpeed = 2f;
|
||||
deconstructSpeed = 6f;
|
||||
}};
|
||||
|
||||
constructor = new Constructor("constructor"){{
|
||||
requirements(Category.units, with(Items.silicon, 100, Items.beryllium, 150, Items.tungsten, 80));
|
||||
requirements(Category.units, with(Items.silicon, 50, Items.beryllium, 75, Items.tungsten, 40));
|
||||
regionSuffix = "-dark";
|
||||
hasPower = true;
|
||||
buildSpeed = 0.6f;
|
||||
consumePower(2f);
|
||||
consumePower(2.5f);
|
||||
size = 3;
|
||||
//TODO expand this list
|
||||
filter = Seq.with(Blocks.tungstenWallLarge, Blocks.berylliumWallLarge, Blocks.carbideWallLarge, Blocks.reinforcedSurgeWallLarge, Blocks.reinforcedLiquidContainer, Blocks.reinforcedContainer, Blocks.beamNode);
|
||||
@@ -6413,7 +6413,7 @@ public class Blocks{
|
||||
|
||||
//yes this block is pretty much useless
|
||||
largeConstructor = new Constructor("large-constructor"){{
|
||||
requirements(Category.units, with(Items.silicon, 150, Items.oxide, 150, Items.tungsten, 200, Items.phaseFabric, 40));
|
||||
requirements(Category.units, with(Items.silicon, 150, Items.oxide, 100, Items.tungsten, 200, Items.thorium, 80));
|
||||
regionSuffix = "-dark";
|
||||
hasPower = true;
|
||||
buildSpeed = 0.75f;
|
||||
@@ -6421,11 +6421,11 @@ public class Blocks{
|
||||
minBlockSize = 3;
|
||||
size = 5;
|
||||
|
||||
consumePower(2f);
|
||||
consumePower(3f);
|
||||
}};
|
||||
|
||||
payloadLoader = new PayloadLoader("payload-loader"){{
|
||||
requirements(Category.units, with(Items.graphite, 50, Items.silicon, 50, Items.tungsten, 80));
|
||||
requirements(Category.units, with(Items.graphite, 80, Items.silicon, 160, Items.tungsten, 90));
|
||||
regionSuffix = "-dark";
|
||||
hasPower = true;
|
||||
consumePower(2f);
|
||||
@@ -6434,7 +6434,7 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
payloadUnloader = new PayloadUnloader("payload-unloader"){{
|
||||
requirements(Category.units, with(Items.graphite, 50, Items.silicon, 50, Items.tungsten, 30));
|
||||
requirements(Category.units, with(Items.graphite, 140, Items.silicon, 220, Items.tungsten, 180));
|
||||
regionSuffix = "-dark";
|
||||
hasPower = true;
|
||||
consumePower(2f);
|
||||
|
||||
@@ -415,6 +415,10 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
Groups.draw.draw(Drawc::draw);
|
||||
|
||||
if(drawDebugHitboxes){
|
||||
DebugCollisionRenderer.draw();
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
Draw.flush();
|
||||
Draw.sort(false);
|
||||
|
||||
@@ -95,6 +95,10 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
uiIcon = Core.atlas.find(getContentType().name() + "-" + name + "-ui", fullIcon);
|
||||
}
|
||||
|
||||
public boolean isBanned(){
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isOnPlanet(@Nullable Planet planet){
|
||||
return planet == null || planet == Planets.sun || shownPlanets.isEmpty() || shownPlanets.contains(planet);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import mindustry.game.MapObjectives.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
@@ -280,6 +281,15 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
});
|
||||
}));
|
||||
|
||||
setInterpreter(Alignment.class, int.class, (cont, name, type, field, remover, indexer, get, set) -> {
|
||||
Alignment align = field.getAnnotation(Alignment.class);
|
||||
name(cont, name, remover, indexer);
|
||||
cont.button(b -> {
|
||||
b.label(() -> LStatement.alignToName.get(get.get(), "center"));
|
||||
b.clicked(() -> LStatement.showAlignSelect(b, get.get(), set::get, align.hor(), align.ver()));
|
||||
}, () -> {});
|
||||
});
|
||||
|
||||
// Types that use the default interpreter. It would be nice if all types could use it, but I don't know how to reliably prevent classes like [? extends Content] from using it.
|
||||
for(var obj : MapObjectives.allObjectiveTypes) setInterpreter(obj.get().getClass(), defaultInterpreter());
|
||||
for(var mark : MapObjectives.allMarkerTypes) setInterpreter(mark.get().getClass(), defaultInterpreter());
|
||||
|
||||
@@ -59,7 +59,7 @@ public class Fires{
|
||||
if(tile != null){
|
||||
Fire fire = get(tile);
|
||||
if(fire != null){
|
||||
fire.time(fire.time + intensity * Time.delta);
|
||||
fire.time += intensity * Time.delta;
|
||||
Fx.steam.at(fire);
|
||||
if(fire.time >= fire.lifetime){
|
||||
Events.fire(Trigger.fireExtinguish);
|
||||
|
||||
@@ -110,7 +110,7 @@ public class Puddles{
|
||||
}
|
||||
|
||||
public static void register(Puddle puddle){
|
||||
world.tiles.setPuddle(puddle.tile().array(), puddle);
|
||||
world.tiles.setPuddle(puddle.tile.array(), puddle);
|
||||
}
|
||||
|
||||
/** Reacts two liquids together at a location. */
|
||||
|
||||
@@ -438,17 +438,19 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return heat;
|
||||
}
|
||||
|
||||
/** Sets the time scale of the building to the given intensity, unless it's above that value */
|
||||
public void applyBoost(float intensity, float duration){
|
||||
//do not refresh time scale when getting a weaker intensity
|
||||
//do not refresh time scale when getting a lower intensity
|
||||
if(intensity >= this.timeScale - 0.001f){
|
||||
timeScaleDuration = Math.max(timeScaleDuration, duration);
|
||||
}
|
||||
timeScale = Math.max(timeScale, intensity);
|
||||
}
|
||||
|
||||
/** Sets the time scale of the building to the given intensity, unless it's below that value */
|
||||
public void applySlowdown(float intensity, float duration){
|
||||
//do not refresh time scale when getting a weaker intensity
|
||||
if(intensity <= this.timeScale - 0.001f){
|
||||
//do not refresh time scale when getting a higher intensity
|
||||
if(intensity <= this.timeScale + 0.001f){
|
||||
timeScaleDuration = Math.max(timeScaleDuration, duration);
|
||||
}
|
||||
timeScale = Math.min(timeScale, intensity);
|
||||
@@ -1215,11 +1217,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
public void drawItemSelection(@Nullable UnlockableContent selection){
|
||||
if(selection != null){
|
||||
float dx = x - block.size * tilesize/2f, dy = y + block.size * tilesize/2f, s = iconSmall / 4f;
|
||||
float dx = x - block.size * tilesize/2f, dy = y + block.size * tilesize/2f, s = iconSmall / 4f * selection.fullIcon.ratio(), h = iconSmall / 4f;
|
||||
Draw.mixcol(Color.darkGray, 1f);
|
||||
Draw.rect(selection.fullIcon, dx, dy - 1, s, s);
|
||||
Draw.rect(selection.fullIcon, dx, dy - 1, s, h);
|
||||
Draw.reset();
|
||||
Draw.rect(selection.fullIcon, dx, dy, s, s);
|
||||
Draw.rect(selection.fullIcon, dx, dy, s, h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import mindustry.world.meta.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@EntityDef(value = {Firec.class}, pooled = true)
|
||||
@Component(base = true)
|
||||
@Component(base = true, genInterface = false)
|
||||
abstract class FireComp implements Timedc, Posc, Syncc, Drawc{
|
||||
public static final int frames = 40, duration = 90;
|
||||
|
||||
@@ -88,7 +88,7 @@ abstract class FireComp implements Timedc, Posc, Syncc, Drawc{
|
||||
//apply damage to nearby units & building
|
||||
if((damageTimer += Time.delta) >= damageDelay){
|
||||
damageTimer = 0f;
|
||||
Puddlec p = Puddles.get(tile);
|
||||
Puddle p = Puddles.get(tile);
|
||||
puddleFlammability = p != null ? p.getFlammability() / 3f : 0;
|
||||
|
||||
if(damage){
|
||||
|
||||
@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
|
||||
import static mindustry.entities.Puddles.*;
|
||||
|
||||
@EntityDef(value = {Puddlec.class}, pooled = true)
|
||||
@Component(base = true)
|
||||
@Component(base = true, genInterface = false)
|
||||
abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
|
||||
private static final Rect rect = new Rect(), rect2 = new Rect();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import mindustry.ui.*;
|
||||
|
||||
/** Component/entity for labels in world space. Useful for servers. Does not save in files - create only on world load. */
|
||||
@EntityDef(value = {WorldLabelc.class}, serialize = false)
|
||||
@Component(base = true)
|
||||
@Component(base = true, genInterface = false)
|
||||
public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
|
||||
@Import int id;
|
||||
@Import float x, y;
|
||||
@@ -31,10 +31,10 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
drawAt(text, x, y, z, flags, fontSize);
|
||||
drawAt(text, x, y, z, flags, fontSize, Align.center, Align.center);
|
||||
}
|
||||
|
||||
public static void drawAt(String text, float x, float y, float layer, int flags, float fontSize){
|
||||
public static void drawAt(String text, float x, float y, float layer, int flags, float fontSize, int align, int lineAlign){
|
||||
Draw.z(layer);
|
||||
float z = Drawf.text();
|
||||
|
||||
@@ -46,14 +46,32 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
|
||||
font.getData().setScale(0.25f / Scl.scl(1f) * fontSize);
|
||||
layout.setText(font, text);
|
||||
|
||||
int border = (flags & flagBackground) != 0 ? 1 : 0;
|
||||
|
||||
if(Align.isBottom(align)){
|
||||
y += layout.height + border * 1.5f;
|
||||
}else if(Align.isTop(align)){
|
||||
y -= border * 1.5f;
|
||||
}else{
|
||||
y += layout.height / 2;
|
||||
}
|
||||
|
||||
if(Align.isLeft(align)){
|
||||
x += layout.width / 2 + border;
|
||||
}else if(Align.isRight(align)){
|
||||
x -= layout.width / 2 + border;
|
||||
}
|
||||
|
||||
if((flags & flagBackground) != 0){
|
||||
Draw.color(0f, 0f, 0f, 0.3f);
|
||||
Fill.rect(x, y - layout.height / 2, layout.width + 2, layout.height + 3);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
float tx = Align.isLeft(lineAlign) ? -layout.width * 0.5f : Align.isRight(lineAlign) ? layout.width * 0.5f : 0;
|
||||
|
||||
font.setColor(Color.white);
|
||||
font.draw(text, x, y, 0, Align.center, false);
|
||||
font.draw(text, x + tx, y, 0, lineAlign, false);
|
||||
|
||||
Draw.reset();
|
||||
Pools.free(layout);
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -51,6 +52,9 @@ public class AIController implements UnitController{
|
||||
updateMovement();
|
||||
}
|
||||
|
||||
/** Called when the parent CommandAI changes its stance. */
|
||||
public void stanceChanged(){}
|
||||
|
||||
/**
|
||||
* @return whether controller state should not be reset after reading.
|
||||
* Do not override unless you know exactly what you are doing.
|
||||
@@ -129,11 +133,15 @@ public class AIController implements UnitController{
|
||||
}
|
||||
|
||||
public void pathfind(int pathTarget, boolean stopAtTargetTile){
|
||||
pathfind(pathTarget, stopAtTargetTile, false);
|
||||
}
|
||||
|
||||
public void pathfind(int pathTarget, boolean stopAtTargetTile, boolean avoidance){
|
||||
int costType = unit.type.flowfieldPathType;
|
||||
|
||||
Tile tile = unit.tileOn();
|
||||
if(tile == null) return;
|
||||
Tile targetTile = pathfinder.getField(unit.team, costType, pathTarget).getNextTile(tile);
|
||||
Tile targetTile = pathfinder.getField(unit.team, costType, pathTarget).getNextTile(tile, avoidance && unit.collisionLayer() == PhysicsProcess.layerGround ? unit.id : 0);
|
||||
|
||||
if((tile == targetTile && stopAtTargetTile) || !unit.canPass(targetTile.x, targetTile.y)) return;
|
||||
|
||||
|
||||
@@ -757,6 +757,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public @Multiline String text = "frog";
|
||||
public float fontSize = 1f, textHeight = 7f;
|
||||
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||
public @Alignment int textAlign = Align.center;
|
||||
public @Alignment(ver = false) int lineAlign = Align.center;
|
||||
|
||||
public float radius = 6f, rotation = 0f;
|
||||
public int sides = 4;
|
||||
@@ -791,6 +793,15 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
this.textHeight = textHeight;
|
||||
}
|
||||
|
||||
public ShapeTextMarker(String text, float x, float y, float radius, float rotation, float textHeight, int textAlign){
|
||||
this.text = text;
|
||||
this.pos.set(x, y);
|
||||
this.radius = radius;
|
||||
this.rotation = rotation;
|
||||
this.textHeight = textHeight;
|
||||
this.textAlign = textAlign;
|
||||
}
|
||||
|
||||
public ShapeTextMarker(){}
|
||||
|
||||
@Override
|
||||
@@ -812,7 +823,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
// font size cannot be 0
|
||||
if(Mathf.equal(fontSize, 0f)) return;
|
||||
|
||||
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius * scaleFactor + textHeight * scaleFactor, drawLayer, flags, fontSize * scaleFactor);
|
||||
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius * scaleFactor + textHeight * scaleFactor, drawLayer, flags, fontSize * scaleFactor, textAlign, lineAlign);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -823,6 +834,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
switch(type){
|
||||
case fontSize -> fontSize = (float)p1;
|
||||
case textHeight -> textHeight = (float)p1;
|
||||
case textAlign -> textAlign = (int)p1;
|
||||
case lineAlign -> lineAlign = (int)p1;
|
||||
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
||||
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
||||
case radius -> radius = (float)p1;
|
||||
@@ -980,6 +993,9 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public @Multiline String text = "uwu";
|
||||
public float fontSize = 1f;
|
||||
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||
public @Alignment int textAlign = Align.center;
|
||||
public @Alignment(ver = false) int lineAlign = Align.center;
|
||||
|
||||
// Cached localized text.
|
||||
private transient String fetchedText;
|
||||
|
||||
@@ -1006,7 +1022,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
fetchedText = fetchText(text);
|
||||
}
|
||||
|
||||
WorldLabel.drawAt(fetchedText, pos.x, pos.y, drawLayer, flags, fontSize * scaleFactor);
|
||||
WorldLabel.drawAt(fetchedText, pos.x, pos.y, drawLayer, flags, fontSize * scaleFactor, textAlign, lineAlign);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1016,6 +1032,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
if(!Double.isNaN(p1)){
|
||||
switch(type){
|
||||
case fontSize -> fontSize = (float)p1;
|
||||
case textAlign -> textAlign = (int)p1;
|
||||
case lineAlign -> lineAlign = (int)p1;
|
||||
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
||||
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
||||
}
|
||||
@@ -1317,6 +1335,14 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
@Retention(RUNTIME)
|
||||
public @interface LabelFlag{}
|
||||
|
||||
/** For {@code int}; treats it as an alignment from {@link Align} */
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface Alignment{
|
||||
boolean hor() default true;
|
||||
boolean ver() default true;
|
||||
}
|
||||
|
||||
/** For {@link UnlockableContent}; filters all un-researchable content. */
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
@@ -1341,4 +1367,5 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface TilePos{}
|
||||
|
||||
}
|
||||
|
||||
93
core/src/mindustry/graphics/DebugCollisionRenderer.java
Normal file
93
core/src/mindustry/graphics/DebugCollisionRenderer.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package mindustry.graphics;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class DebugCollisionRenderer{
|
||||
static final float[] edges = {
|
||||
1, -1,
|
||||
1, 1,
|
||||
-1, 1,
|
||||
-1, -1,
|
||||
};
|
||||
|
||||
public static void draw(){
|
||||
Rect rect = camera.bounds(new Rect());
|
||||
Draw.draw(Layer.overlayUI, () -> {
|
||||
//hitboxes
|
||||
Draw.color(Color.green, 0.3f);
|
||||
Groups.draw.each(d -> {
|
||||
if(d instanceof Hitboxc h && rect.overlaps(Tmp.r1.setCentered(d.x(), d.y(), d.clipSize()))){
|
||||
Fill.square(d.x(), d.y(), h.hitSize()/2f);
|
||||
}
|
||||
});
|
||||
|
||||
//tile hitboxes for units
|
||||
Lines.stroke(0.4f, Color.magenta);
|
||||
|
||||
int rx = Mathf.clamp((int)(Core.camera.width / tilesize / 2) + 1, 0, world.width()/2);
|
||||
int ry = Mathf.clamp((int)(Core.camera.height / tilesize / 2) + 1, 0, world.height()/2);
|
||||
|
||||
for(int x = -rx; x <= rx; x++){
|
||||
for(int y = -ry; y <= ry; y++){
|
||||
int wx = World.toTile(Core.camera.position.x) + x;
|
||||
int wy = World.toTile(Core.camera.position.y) + y;
|
||||
Tile tile = world.tile(wx, wy);
|
||||
if(tile != null && tile.solid()){
|
||||
for(int i = 0; i < 4; i++){
|
||||
Tile other = tile.nearby(i);
|
||||
if(other == null || !other.solid()){
|
||||
Lines.line(
|
||||
wx * tilesize + edges[i*2] * tilesize/2f,
|
||||
wy * tilesize + edges[i*2+1] * tilesize/2f,
|
||||
wx * tilesize + edges[((i + 1) % 4)*2] * tilesize/2f,
|
||||
wy * tilesize + edges[((i + 1) % 4)*2+1] * tilesize/2f
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(debugDrawAvoidance && tile != null){
|
||||
int[] avoid = avoidance.getAvoidance();
|
||||
if(avoid != null && avoid[tile.array()] != 0){
|
||||
Draw.color(0f, 1f, 1f, 0.25f);
|
||||
Fill.square(tile.worldx(), tile.worldy(), 4f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Groups.draw.each(d -> {
|
||||
if(d instanceof Unit u && rect.overlaps(Tmp.r1.setCentered(u.x, u.y, d.clipSize())) && !u.isFlying()){
|
||||
u.hitboxTile(Tmp.r1);
|
||||
|
||||
Lines.rect(Tmp.r1);
|
||||
}
|
||||
});
|
||||
|
||||
//physics hitboxes
|
||||
Lines.stroke(0.5f);
|
||||
Draw.color(Color.red, 0.5f);
|
||||
Groups.draw.each(d -> {
|
||||
if(d instanceof Unit u && rect.overlaps(Tmp.r1.setCentered(u.x, u.y, u.clipSize()))){
|
||||
Lines.circle(u.x, u.y, u.hitSize * unitCollisionRadiusScale);
|
||||
}
|
||||
});
|
||||
Draw.reset();
|
||||
|
||||
});
|
||||
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ public class Binding{
|
||||
schematicFlipY = KeyBind.add("schematic_flip_y", KeyCode.x),
|
||||
schematicMenu = KeyBind.add("schematic_menu", KeyCode.t),
|
||||
|
||||
|
||||
commandMode = KeyBind.add("command_mode", KeyCode.shiftLeft, "command"),
|
||||
commandQueue = KeyBind.add("command_queue", KeyCode.mouseMiddle),
|
||||
createControlGroup = KeyBind.add("create_control_group", KeyCode.controlLeft),
|
||||
@@ -103,7 +102,8 @@ public class Binding{
|
||||
chatHistoryNext = KeyBind.add("chat_history_next", KeyCode.down),
|
||||
chatScroll = KeyBind.add("chat_scroll", new Axis(KeyCode.scroll)),
|
||||
chatMode = KeyBind.add("chat_mode", KeyCode.tab),
|
||||
console = KeyBind.add("console", KeyCode.f8)
|
||||
console = KeyBind.add("console", KeyCode.f8),
|
||||
debugHitboxes = KeyBind.add("debug_hitboxes", KeyCode.unset)
|
||||
;
|
||||
|
||||
//dummy static class initializer
|
||||
|
||||
@@ -234,6 +234,10 @@ public class DesktopInput extends InputHandler{
|
||||
boolean detached = settings.getBool("detach-camera", false);
|
||||
|
||||
if(!scene.hasField() && !scene.hasDialog()){
|
||||
if(input.keyTap(Binding.debugHitboxes)){
|
||||
drawDebugHitboxes = !drawDebugHitboxes;
|
||||
}
|
||||
|
||||
if(input.keyTap(Binding.detachCamera)){
|
||||
settings.put("detach-camera", detached = !detached);
|
||||
if(!detached){
|
||||
|
||||
@@ -372,15 +372,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
//make sure its current stance is valid with its current command
|
||||
stancesOut.clear();
|
||||
unit.type.getUnitStances(unit, stancesOut);
|
||||
if(stancesOut.size > 0 && !stancesOut.contains(ai.stance)){
|
||||
ai.stance = stancesOut.first();
|
||||
for(var stance : content.unitStances()){
|
||||
//disable stances that the unit does not support anymore (TODO: this is slow!)
|
||||
if(ai.hasStance(stance) && !stancesOut.contains(stance)){
|
||||
ai.disableStance(stance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||
public static void setUnitStance(Player player, int[] unitIds, UnitStance stance){
|
||||
public static void setUnitStance(Player player, int[] unitIds, UnitStance stance, boolean enable){
|
||||
if(player == null || unitIds == null || stance == null) return;
|
||||
|
||||
if(net.server() && !netServer.admins.allowAction(player, ActionType.commandUnits, event -> {
|
||||
@@ -395,7 +398,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(stance == UnitStance.stop){ //not a real stance, just cancels orders
|
||||
ai.clearCommands();
|
||||
}else if(unit.type.allowStance(unit, stance)){
|
||||
ai.stance = stance;
|
||||
//if toggle is not allowed, the stance will always be set to true when pressed
|
||||
ai.setStance(stance, !stance.toggle || enable);
|
||||
}
|
||||
unit.lastCommanded = player.coloredName();
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ public class TypeIO{
|
||||
public static UnitStance readStance(Reads read){
|
||||
int val = read.ub();
|
||||
//never returns null
|
||||
return val == 255 || val >= content.unitStances().size ? UnitStance.shoot : content.unitStance(val);
|
||||
return val == 255 || val >= content.unitStances().size ? UnitStance.stop : content.unitStance(val);
|
||||
}
|
||||
|
||||
public static void writeEntity(Writes write, Entityc entity){
|
||||
@@ -523,7 +523,7 @@ public class TypeIO{
|
||||
write.b(3);
|
||||
write.i(logic.controller.pos());
|
||||
}else if(control instanceof CommandAI ai){
|
||||
write.b(8);
|
||||
write.b(9);
|
||||
write.bool(ai.attackTarget != null);
|
||||
write.bool(ai.targetPos != null);
|
||||
|
||||
@@ -559,7 +559,16 @@ public class TypeIO{
|
||||
}
|
||||
}
|
||||
|
||||
writeStance(write, ai.stance);
|
||||
int count = content.unitStances().count(ai::hasStance);
|
||||
|
||||
write.b(count);
|
||||
|
||||
for(var stance : content.unitStances()){
|
||||
if(ai.hasStance(stance)){
|
||||
writeStance(write, stance);
|
||||
}
|
||||
}
|
||||
|
||||
}else if(control instanceof AssemblerAI){ //hate
|
||||
write.b(5);
|
||||
}else{
|
||||
@@ -591,8 +600,8 @@ public class TypeIO{
|
||||
out.controller = world.build(pos);
|
||||
return out;
|
||||
}
|
||||
//type 4 is the old CommandAI with no commandIndex, type 6 is the new one with the index as a single byte, type 7 is the one with the command queue, 8 adds a stance
|
||||
}else if(type == 4 || type == 6 || type == 7 || type == 8){
|
||||
//type 4 is the old CommandAI with no commandIndex, type 6 is the new one with the index as a single byte, type 7 is the one with the command queue, 8 adds a stance, 9 adds multiple stances
|
||||
}else if(type == 4 || type == 6 || type == 7 || type == 8 || type == 9){
|
||||
CommandAI ai = prev instanceof CommandAI pai ? pai : new CommandAI();
|
||||
|
||||
boolean hasAttack = read.bool(), hasPos = read.bool();
|
||||
@@ -616,14 +625,14 @@ public class TypeIO{
|
||||
ai.attackTarget = null;
|
||||
}
|
||||
|
||||
if(type == 6 || type == 7 || type == 8){
|
||||
if(type == 6 || type == 7 || type == 8 || type == 9){
|
||||
byte id = read.b();
|
||||
ai.command = id < 0 ? null : content.unitCommand(id);
|
||||
if(ai.command == null) ai.command = UnitCommand.moveCommand;
|
||||
}
|
||||
|
||||
//command queue only in type 7/8
|
||||
if(type == 7 || type == 8){
|
||||
if(type == 7 || type == 8 || type == 9){
|
||||
ai.commandQueue.clear();
|
||||
int length = read.ub();
|
||||
for(int i = 0; i < length; i++){
|
||||
@@ -646,7 +655,12 @@ public class TypeIO{
|
||||
}
|
||||
|
||||
if(type == 8){
|
||||
ai.stance = readStance(read);
|
||||
ai.setStance(readStance(read));
|
||||
}else if(type == 9){
|
||||
int stances = read.ub();
|
||||
for(int i = 0; i < stances; i++){
|
||||
ai.setStance(readStance(read));
|
||||
}
|
||||
}
|
||||
|
||||
return ai;
|
||||
|
||||
@@ -28,6 +28,17 @@ public enum ConditionOp{
|
||||
this.objFunction = objFunction;
|
||||
}
|
||||
|
||||
public boolean test(LVar va, LVar vb){
|
||||
if(this == ConditionOp.strictEqual){
|
||||
return va.isobj == vb.isobj && ((va.isobj && va.objval == vb.objval) || (!va.isobj && va.numval == vb.numval));
|
||||
}
|
||||
if(objFunction != null && va.isobj && vb.isobj){
|
||||
//use object function if both are objects
|
||||
return objFunction.get(va.obj(), vb.obj());
|
||||
}
|
||||
return function.get(va.num(), vb.num());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return symbol;
|
||||
|
||||
@@ -147,6 +147,8 @@ public class GlobalVars{
|
||||
put("@" + sensor.name(), sensor);
|
||||
}
|
||||
|
||||
LStatement.nameToAlign.each((name, align) -> put("@" + name, align));
|
||||
|
||||
logicIdToContent = new UnlockableContent[ContentType.all.length][];
|
||||
contentIdToLogicId = new int[ContentType.all.length][];
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public enum LAccess{
|
||||
cameraHeight,
|
||||
displayWidth,
|
||||
displayHeight,
|
||||
bufferUsage,
|
||||
bufferSize,
|
||||
operations,
|
||||
size,
|
||||
solid,
|
||||
|
||||
@@ -25,10 +25,8 @@ import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.logic.*;
|
||||
import mindustry.world.blocks.logic.CanvasBlock.*;
|
||||
import mindustry.world.blocks.logic.LogicBlock.*;
|
||||
import mindustry.world.blocks.logic.LogicDisplay.*;
|
||||
import mindustry.world.blocks.logic.MemoryBlock.*;
|
||||
import mindustry.world.blocks.logic.MessageBlock.*;
|
||||
import mindustry.world.blocks.payloads.*;
|
||||
import mindustry.world.meta.*;
|
||||
@@ -516,14 +514,14 @@ public class LExecutor{
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
Object obj = target.obj();
|
||||
if(obj instanceof Building b && (exec.privileged || (b.team == exec.team && exec.linkIds.contains(b.id)))){
|
||||
if(obj instanceof Building b && (exec.privileged || (exec.build != null && exec.build.validLink(b)))){
|
||||
|
||||
if(type == LAccess.enabled && !p1.bool()){
|
||||
b.lastDisabler = exec.build;
|
||||
}
|
||||
|
||||
if(type == LAccess.enabled && p1.bool()){
|
||||
b.noSleep();
|
||||
if(type == LAccess.enabled){
|
||||
if(p1.bool()){
|
||||
b.noSleep();
|
||||
}else{
|
||||
b.lastDisabler = exec.build;
|
||||
}
|
||||
}
|
||||
|
||||
if(type.isObj && p1.isobj){
|
||||
@@ -568,22 +566,15 @@ public class LExecutor{
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
int address = position.numi();
|
||||
Building from = target.building();
|
||||
|
||||
if(from instanceof MemoryBuild mem && (exec.privileged || (from.team == exec.team && !mem.block.privileged))){
|
||||
output.setnum(address < 0 || address >= mem.memory.length ? 0 : mem.memory[address]);
|
||||
}else if(from instanceof LogicBuild logic && (exec.privileged || (from.team == exec.team && !from.block.privileged)) && position.isobj && position.objval instanceof String name){
|
||||
LVar fromVar = logic.executor.optionalVar(name);
|
||||
if(fromVar != null && !output.constant){
|
||||
output.objval = fromVar.objval;
|
||||
output.numval = fromVar.numval;
|
||||
output.isobj = fromVar.isobj;
|
||||
Object targetObj = target.obj();
|
||||
if(targetObj instanceof LReadable read){
|
||||
if(!read.readable(exec)) return;
|
||||
read.read(position, output);
|
||||
}else{
|
||||
int address = position.numi();
|
||||
if(targetObj instanceof CharSequence str){
|
||||
output.setnum(address < 0 || address >= str.length() ? Double.NaN : (int)str.charAt(address));
|
||||
}
|
||||
}else if(target.isobj && target.objval instanceof CharSequence str){
|
||||
output.setnum(address < 0 || address >= str.length() ? Double.NaN : (int)str.charAt(address));
|
||||
}else if(from instanceof CanvasBuild canvas && (exec.privileged || (from.team == exec.team))){
|
||||
output.setnum(canvas.getPixel(address));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,20 +593,10 @@ public class LExecutor{
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
int address = position.numi();
|
||||
Building from = target.building();
|
||||
|
||||
if(from instanceof MemoryBuild mem && (exec.privileged || (from.team == exec.team && !mem.block.privileged)) && address >= 0 && address < mem.memory.length){
|
||||
mem.memory[address] = value.num();
|
||||
}else if(from instanceof LogicBuild logic && (exec.privileged || (from.team == exec.team && !from.block.privileged)) && position.isobj && position.objval instanceof String name){
|
||||
LVar toVar = logic.executor.optionalVar(name);
|
||||
if(toVar != null && !toVar.constant){
|
||||
toVar.objval = value.objval;
|
||||
toVar.numval = value.numval;
|
||||
toVar.isobj = value.isobj;
|
||||
}
|
||||
}else if(from instanceof CanvasBuild canvas && (exec.privileged || (from.team == exec.team))){
|
||||
canvas.setPixel(address, value.numi());
|
||||
Object targetObj = target.obj();
|
||||
if(targetObj instanceof LWritable write){
|
||||
if(!write.writable(exec)) return;
|
||||
write.write(position, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -658,7 +639,7 @@ public class LExecutor{
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(target instanceof CharSequence seq && sense == LAccess.size){
|
||||
if(target instanceof CharSequence seq && (sense == LAccess.size || sense == LAccess.bufferSize)){
|
||||
to.setnum(seq.length());
|
||||
return;
|
||||
}
|
||||
@@ -785,15 +766,7 @@ public class LExecutor{
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
if(!to.constant){
|
||||
if(from.isobj){
|
||||
to.objval = from.objval;
|
||||
to.isobj = true;
|
||||
}else{
|
||||
to.numval = LVar.invalid(from.numval) ? 0 : from.numval;
|
||||
to.isobj = false;
|
||||
}
|
||||
}
|
||||
if(!to.constant) to.set(from);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -829,6 +802,28 @@ public class LExecutor{
|
||||
}
|
||||
}
|
||||
|
||||
public static class SelectI implements LInstruction{
|
||||
public ConditionOp op = ConditionOp.notEqual;
|
||||
public LVar result, comp0, comp1, a, b;
|
||||
|
||||
public SelectI(ConditionOp op, LVar result, LVar comp0, LVar comp1, LVar a, LVar b){
|
||||
this.op = op;
|
||||
this.result = result;
|
||||
this.comp0 = comp0;
|
||||
this.comp1 = comp1;
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public SelectI(){}
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
if(result.constant) return;
|
||||
result.set(op.test(comp0, comp1) ? a : b);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EndI implements LInstruction{
|
||||
|
||||
@Override
|
||||
@@ -883,7 +878,7 @@ public class LExecutor{
|
||||
int advance = (int)data.spaceXadvance, lineHeight = (int)data.lineHeight;
|
||||
|
||||
int xOffset, yOffset;
|
||||
int align = p1.id; //p1 is not a variable, it's a raw align value. what a massive hack
|
||||
int align = p1.numi();
|
||||
|
||||
int maxWidth = 0, lines = 1, lineWidth = 0;
|
||||
for(int i = 0; i < str.length(); i++){
|
||||
@@ -1134,23 +1129,8 @@ public class LExecutor{
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
if(address != -1){
|
||||
LVar va = value;
|
||||
LVar vb = compare;
|
||||
boolean cmp;
|
||||
|
||||
if(op == ConditionOp.strictEqual){
|
||||
cmp = va.isobj == vb.isobj && ((va.isobj && va.objval == vb.objval) || (!va.isobj && va.numval == vb.numval));
|
||||
}else if(op.objFunction != null && va.isobj && vb.isobj){
|
||||
//use object function if both are objects
|
||||
cmp = op.objFunction.get(value.obj(), compare.obj());
|
||||
}else{
|
||||
cmp = op.function.get(value.num(), compare.num());
|
||||
}
|
||||
|
||||
if(cmp){
|
||||
exec.counter.numval = address;
|
||||
}
|
||||
if(address != -1 && op.test(value, compare)){
|
||||
exec.counter.numval = address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ public enum LMarkerControl{
|
||||
flushText("fetch"),
|
||||
fontSize("size"),
|
||||
textHeight("height"),
|
||||
textAlign("align"),
|
||||
lineAlign("align"),
|
||||
labelFlags("background", "outline"),
|
||||
texture("printFlush", "name"),
|
||||
textureSize("width", "height"),
|
||||
|
||||
6
core/src/mindustry/logic/LReadable.java
Normal file
6
core/src/mindustry/logic/LReadable.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package mindustry.logic;
|
||||
|
||||
public interface LReadable{
|
||||
boolean readable(LExecutor exec);
|
||||
void read(LVar position, LVar output);
|
||||
}
|
||||
@@ -23,6 +23,25 @@ import static mindustry.logic.LCanvas.*;
|
||||
* A statement is an intermediate representation of an instruction, to be used mostly in UI.
|
||||
* Contains all relevant variable information. */
|
||||
public abstract class LStatement{
|
||||
|
||||
private static final String[] aligns = {"topLeft", "top", "topRight", "left", "center", "right", "bottomLeft", "bottom", "bottomRight"};
|
||||
public static final ObjectMap<String, Integer> nameToAlign = ObjectMap.of(
|
||||
"center", Align.center,
|
||||
"top", Align.top,
|
||||
"bottom", Align.bottom,
|
||||
"left", Align.left,
|
||||
"right", Align.right,
|
||||
"topLeft", Align.topLeft,
|
||||
"topRight", Align.topRight,
|
||||
"bottomLeft", Align.bottomLeft,
|
||||
"bottomRight", Align.bottomRight
|
||||
);
|
||||
public static final IntMap<String> alignToName = new IntMap<>();
|
||||
|
||||
static {
|
||||
nameToAlign.each((k, v) -> alignToName.put(v, k));
|
||||
}
|
||||
|
||||
public transient @Nullable StatementElem elem;
|
||||
|
||||
public abstract void build(Table table);
|
||||
@@ -175,7 +194,36 @@ public abstract class LStatement{
|
||||
showSelect(b, values, current, getter, 4, c -> {});
|
||||
}
|
||||
|
||||
protected void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
|
||||
protected void fieldAlignSelect(Table t, Prov<String> get, Cons<String> set, boolean hor, boolean ver) {
|
||||
t.button(b -> {
|
||||
b.image(Icon.pencilSmall);
|
||||
b.clicked(() -> {
|
||||
var current = get.get();
|
||||
showAlignSelect(b, current.startsWith("@") ? nameToAlign.get(current.substring(1), -1) : -1, align -> set.get("@" + alignToName.get(align)), hor, ver);
|
||||
});
|
||||
}, Styles.logict, () -> {}).size(40f).color(t.color).left().padLeft(-10);
|
||||
}
|
||||
|
||||
public static void showAlignSelect(Button b, int current, Intc setter, boolean hor, boolean ver) {
|
||||
showSelectTable(b, (t, hide) -> {
|
||||
t.defaults().size(150f, 40f);
|
||||
|
||||
int i = 0;
|
||||
for(String align : aligns){
|
||||
int val = nameToAlign.get(align);
|
||||
if(!hor && !Align.isCenterHorizontal(val)) continue;
|
||||
if(!ver && !Align.isCenterVertical(val)) continue;
|
||||
t.button(align, Styles.logicTogglet, () -> {
|
||||
setter.get(val);
|
||||
hide.run();
|
||||
}).checked(current == nameToAlign.get(align)).grow();
|
||||
|
||||
if (++i % 3 == 0) t.row();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
|
||||
Table t = new Table(Tex.paneSolid){
|
||||
@Override
|
||||
public float getPrefHeight(){
|
||||
|
||||
@@ -6,7 +6,6 @@ import arc.graphics.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
@@ -123,19 +122,6 @@ public class LStatements{
|
||||
|
||||
@RegisterStatement("draw")
|
||||
public static class DrawStatement extends LStatement{
|
||||
static final String[] aligns = {"center", "top", "bottom", "left", "right", "topLeft", "topRight", "bottomLeft", "bottomRight"};
|
||||
//yes, boxing Integer is gross but this is easier to construct and Integers <128 don't allocate anyway
|
||||
static final ObjectMap<String, Integer> nameToAlign = ObjectMap.of(
|
||||
"center", Align.center,
|
||||
"top", Align.top,
|
||||
"bottom", Align.bottom,
|
||||
"left", Align.left,
|
||||
"right", Align.right,
|
||||
"topLeft", Align.topLeft,
|
||||
"topRight", Align.topRight,
|
||||
"bottomLeft", Align.bottomLeft,
|
||||
"bottomRight", Align.bottomRight
|
||||
);
|
||||
|
||||
public GraphicsType type = GraphicsType.clear;
|
||||
public String x = "0", y = "0", p1 = "0", p2 = "0", p3 = "0", p4 = "0";
|
||||
@@ -165,7 +151,7 @@ public class LStatements{
|
||||
}
|
||||
|
||||
if(type == GraphicsType.print){
|
||||
p1 = "bottomLeft";
|
||||
p1 = "@bottomLeft";
|
||||
}
|
||||
|
||||
rebuild(table);
|
||||
@@ -253,13 +239,11 @@ public class LStatements{
|
||||
row(s);
|
||||
|
||||
s.add("align ");
|
||||
|
||||
s.button(b -> {
|
||||
b.label(() -> nameToAlign.containsKey(p1) ? p1 : "bottomLeft");
|
||||
b.clicked(() -> showSelect(b, aligns, p1, t -> {
|
||||
p1 = t;
|
||||
}, 2, cell -> cell.size(165, 50)));
|
||||
}, Styles.logict, () -> {}).size(165, 40).color(s.color).left().padLeft(2);
|
||||
fields(s, "align", p1, v -> p1 = v);
|
||||
fieldAlignSelect(s, () -> p1, v -> {
|
||||
p1 = v;
|
||||
rebuild(table);
|
||||
}, true, true);
|
||||
}
|
||||
case translate, scale -> {
|
||||
fields(s, "x", x, v -> x = v);
|
||||
@@ -278,12 +262,15 @@ public class LStatements{
|
||||
if(type == GraphicsType.color && p2.equals("0")){
|
||||
p2 = "255";
|
||||
}
|
||||
|
||||
if(type == GraphicsType.print && nameToAlign.get(p1) != null){
|
||||
p1 = "@" + p1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new DrawI((byte)type.ordinal(), builder.var(x), builder.var(y),
|
||||
type == GraphicsType.print ? new LVar(p1, nameToAlign.get(p1, Align.bottomLeft), true) : builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
||||
return new DrawI((byte)type.ordinal(), builder.var(x), builder.var(y), builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -793,6 +780,55 @@ public class LStatements{
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("select")
|
||||
public static class SelectStatement extends LStatement{
|
||||
public String result = "result";
|
||||
public ConditionOp op = ConditionOp.notEqual;
|
||||
public String comp0 = "x", comp1 = "false", a = "a", b = "b";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
rebuild(table);
|
||||
}
|
||||
|
||||
private void rebuild(Table table){
|
||||
table.clearChildren();
|
||||
table.left();
|
||||
|
||||
table.table(t -> {
|
||||
t.setColor(table.color);
|
||||
|
||||
field(t, result, str -> result = str);
|
||||
t.add(" = if ");
|
||||
|
||||
JumpStatement.addOp(this, t, op, o -> {
|
||||
op = o;
|
||||
rebuild(table);
|
||||
}, comp0, str -> comp0 = str, comp1, str -> comp1 = str);
|
||||
}).left();
|
||||
|
||||
table.row();
|
||||
table.table(t -> {
|
||||
t.setColor(table.color);
|
||||
|
||||
t.add("then ");
|
||||
field(t, a, str -> a = str);
|
||||
t.add(" else ");
|
||||
field(t, b, str -> b = str);
|
||||
}).left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new SelectI(op, builder.var(result), builder.var(comp0), builder.var(comp1), builder.var(a), builder.var(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.operation;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("wait")
|
||||
public static class WaitStatement extends LStatement{
|
||||
public String value = "0.5";
|
||||
@@ -984,17 +1020,22 @@ public class LStatements{
|
||||
table.clearChildren();
|
||||
table.setColor(last);
|
||||
|
||||
if(op != ConditionOp.always) field(table, value, str -> value = str);
|
||||
addOp(this, table, op, o -> {
|
||||
op = o;
|
||||
rebuild(table);
|
||||
}, value, str -> value = str, compare, str -> compare = str);
|
||||
}
|
||||
|
||||
table.button(b -> {
|
||||
b.label(() -> op.symbol);
|
||||
b.clicked(() -> showSelect(b, ConditionOp.all, op, o -> {
|
||||
op = o;
|
||||
rebuild(table);
|
||||
}));
|
||||
}, Styles.logict, () -> {}).size(op == ConditionOp.always ? 80f : 48f, 40f).pad(4f).color(table.color);
|
||||
public static void addOp(LStatement st, Table t, ConditionOp op, Cons<ConditionOp> getter, String comp0, Cons<String> set0, String comp1, Cons<String> set2){
|
||||
if(op != ConditionOp.always) st.field(t, comp0, set0);
|
||||
|
||||
if(op != ConditionOp.always) field(table, compare, str -> compare = str);
|
||||
t.button(b -> {
|
||||
b.add(op.symbol);
|
||||
b.clicked(() -> st.showSelect(b, ConditionOp.all, op, getter));
|
||||
}, Styles.logict, () -> {
|
||||
}).size(op == ConditionOp.always ? 80f : 48f, 40f).pad(4f).color(t.color);
|
||||
|
||||
if(op != ConditionOp.always) st.field(t, comp1, set2);
|
||||
}
|
||||
|
||||
//elements need separate conversion logic
|
||||
@@ -2345,6 +2386,11 @@ public class LStatements{
|
||||
}).width(240f).left();
|
||||
}));
|
||||
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
|
||||
}else if(type == LMarkerControl.textAlign || type == LMarkerControl.lineAlign){
|
||||
fieldAlignSelect(t, () -> p1, v -> {
|
||||
p1 = v;
|
||||
rebuild(table);
|
||||
}, true, type != LMarkerControl.lineAlign);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -101,6 +101,12 @@ public class LVar{
|
||||
isobj = true;
|
||||
}
|
||||
|
||||
public void set(LVar other){
|
||||
isobj = other.isobj;
|
||||
objval = other.objval;
|
||||
numval = other.numval;
|
||||
}
|
||||
|
||||
public static boolean invalid(double d){
|
||||
return Double.isNaN(d) || Double.isInfinite(d);
|
||||
}
|
||||
|
||||
6
core/src/mindustry/logic/LWritable.java
Normal file
6
core/src/mindustry/logic/LWritable.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package mindustry.logic;
|
||||
|
||||
public interface LWritable{
|
||||
boolean writable(LExecutor exec);
|
||||
void write(LVar position, LVar value);
|
||||
}
|
||||
@@ -798,7 +798,7 @@ public class Mods implements Loadable{
|
||||
|
||||
public void reload(){
|
||||
newImports.each(this::updateDependencies);
|
||||
newImports.remove(m -> m.missingDependencies.isEmpty() && m.softDependencies.isEmpty());
|
||||
newImports.removeAll(m -> m.missingDependencies.isEmpty() && m.softDependencies.isEmpty());
|
||||
|
||||
if(newImports.any()){
|
||||
checkDependencies(newImports, newImports.contains(m -> m.softDependencies.any()));
|
||||
|
||||
@@ -700,6 +700,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
return (envEnabled & env) != 0 && (envDisabled & env) == 0 && (envRequired == 0 || (envRequired & env) == envRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBanned(){
|
||||
return state.rules.isBanned(this);
|
||||
}
|
||||
@@ -1038,7 +1039,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
if(stances.size == 0){
|
||||
if(canAttack){
|
||||
stances.addAll(UnitStance.stop, UnitStance.shoot, UnitStance.holdFire, UnitStance.pursueTarget, UnitStance.patrol);
|
||||
stances.addAll(UnitStance.stop, UnitStance.holdFire, UnitStance.pursueTarget, UnitStance.patrol);
|
||||
if(!flying){
|
||||
stances.add(UnitStance.ram);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
@@ -95,7 +94,7 @@ public class DatabaseDialog extends BaseDialog{
|
||||
|
||||
if(++i % 10 == 0) t.row();
|
||||
}
|
||||
}).row();;
|
||||
}).row();
|
||||
|
||||
for(int j = 0; j < allContent.length; j++){
|
||||
ContentType type = ContentType.all[j];
|
||||
@@ -106,6 +105,11 @@ public class DatabaseDialog extends BaseDialog{
|
||||
|
||||
if(array.size == 0) continue;
|
||||
|
||||
//sorting only makes sense when in-game; otherwise, banned blocks can't exist
|
||||
if(state.isGame()){
|
||||
array.sort(Structs.comps(Structs.comparingBool(UnlockableContent::isBanned), Structs.comparingInt(u -> u.id)));
|
||||
}
|
||||
|
||||
all.add("@content." + type.name() + ".name").growX().left().color(Pal.accent);
|
||||
all.row();
|
||||
all.image().growX().pad(5).padLeft(0).padRight(0).height(3).color(Pal.accent);
|
||||
@@ -116,13 +120,11 @@ public class DatabaseDialog extends BaseDialog{
|
||||
int cols = (int)Mathf.clamp((Core.graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 12), 1, 22);
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < array.size; i++){
|
||||
UnlockableContent unlock = array.get(i);
|
||||
|
||||
for(var unlock : array){
|
||||
Image image = unlocked(unlock) ? new Image(new TextureRegionDrawable(unlock.uiIcon), mobile ? Color.white : Color.lightGray).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
|
||||
|
||||
//banned cross
|
||||
if(state.isGame() && (unlock instanceof UnitType u && u.isBanned() || unlock instanceof Block b && state.rules.isBanned(b))){
|
||||
if(state.isGame() && unlock.isBanned()){
|
||||
list.stack(image, new Image(Icon.cancel){{
|
||||
setColor(Color.scarlet);
|
||||
touchable = Touchable.disabled;
|
||||
|
||||
@@ -1274,7 +1274,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
if(sector.isAttacked()){
|
||||
addSurvivedInfo(sector, stable, false);
|
||||
}else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(Sector::hasEnemyBase)){
|
||||
}else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(s -> s.hasEnemyBase() && (s.preset == null || !s.preset.requireUnlock))){
|
||||
stable.add("@sectors.vulnerable");
|
||||
stable.row();
|
||||
}else if(!sector.hasBase() && sector.hasEnemyBase()){
|
||||
|
||||
@@ -474,6 +474,9 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
}
|
||||
graphics.checkPref("minimap", !mobile);
|
||||
graphics.checkPref("smoothcamera", true);
|
||||
if(!mobile){
|
||||
graphics.checkPref("detach-camera", false);
|
||||
}
|
||||
graphics.checkPref("position", false);
|
||||
if(!mobile){
|
||||
graphics.checkPref("mouseposition", false);
|
||||
|
||||
@@ -581,7 +581,7 @@ public class PlacementFragment{
|
||||
for(var stance : stances){
|
||||
|
||||
coms.button(stance.getIcon(), Styles.clearNoneTogglei, () -> {
|
||||
Call.setUnitStance(player, units.mapInt(un -> un.id, un -> un.type.allowStance(un, stance)).toArray(), stance);
|
||||
Call.setUnitStance(player, units.mapInt(un -> un.id, un -> un.type.allowStance(un, stance)).toArray(), stance, !activeStances.get(stance.id));
|
||||
}).checked(i -> activeStances.get(stance.id)).size(50f).tooltip(stance.localized(), true);
|
||||
|
||||
if(++scol % 6 == 0) coms.row();
|
||||
@@ -604,7 +604,7 @@ public class PlacementFragment{
|
||||
for(var unit : control.input.selectedUnits){
|
||||
if(unit.controller() instanceof CommandAI cmd){
|
||||
activeCommands.set(cmd.command.id);
|
||||
activeStances.set(cmd.stance.id);
|
||||
activeStances.set(cmd.stances);
|
||||
}
|
||||
|
||||
stancesOut.clear();
|
||||
@@ -631,7 +631,7 @@ public class PlacementFragment{
|
||||
for(UnitStance stance : stances){
|
||||
//first stance must always be the stop stance
|
||||
if(stance.keybind != null && Core.input.keyTap(stance.keybind)){
|
||||
Call.setUnitStance(player, control.input.selectedUnits.mapInt(un -> un.id, un -> un.type.allowStance(un, stance)).toArray(), stance);
|
||||
Call.setUnitStance(player, control.input.selectedUnits.mapInt(un -> un.id, un -> un.type.allowStance(un, stance)).toArray(), stance, !activeStances.get(stance.id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -923,11 +923,16 @@ public class Block extends UnlockableContent implements Senseable{
|
||||
}
|
||||
|
||||
public boolean isVisible(){
|
||||
return !isHidden() && (state.rules.editor || (!state.rules.hideBannedBlocks || !state.rules.isBanned(this)));
|
||||
return !isHidden() && (state.rules.editor || (!state.rules.hideBannedBlocks || !isBanned()));
|
||||
}
|
||||
|
||||
public boolean isPlaceable(){
|
||||
return isVisible() && (!state.rules.isBanned(this) || state.rules.editor) && supportsEnv(state.rules.env);
|
||||
return isVisible() && (!isBanned() || state.rules.editor) && supportsEnv(state.rules.env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBanned(){
|
||||
return state.rules.isBanned(this);
|
||||
}
|
||||
|
||||
/** @return whether this block supports a specific environment. */
|
||||
|
||||
@@ -94,7 +94,7 @@ public class LiquidTurret extends Turret{
|
||||
var fire = Fires.get(x + tx, y + ty);
|
||||
float dst = fire == null ? 0 : dst2(fire);
|
||||
//do not extinguish fires on other team blocks
|
||||
if(other != null && fire != null && Fires.has(other.x, other.y) && dst <= range * range && (result == null || dst < mindst) && (other.build == null || other.team() == team)){
|
||||
if(other != null && fire != null && other.build != this && Fires.has(other.x, other.y) && dst <= range * range && (result == null || dst < mindst) && (other.build == null || other.team() == team)){
|
||||
result = fire;
|
||||
mindst = dst;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,7 @@ public class DirectionalUnloader extends Block{
|
||||
front.handleItem(this, item);
|
||||
back.items.remove(item, 1);
|
||||
back.itemTaken(item);
|
||||
offset ++;
|
||||
offset %= itemc;
|
||||
offset = item.id + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,6 +376,11 @@ public class Floor extends Block{
|
||||
return (other.realBlendId(otherTile) > realBlendId(tile) || edges(tile.x, tile.y) == null);
|
||||
}
|
||||
|
||||
/** @return whether being on this floor can damage a unit */
|
||||
public boolean damages(){
|
||||
return damageTaken > 0f || (status != null && status.damage > 0f);
|
||||
}
|
||||
|
||||
public static class UpdateRenderState{
|
||||
public Tile tile;
|
||||
public Floor floor;
|
||||
|
||||
@@ -146,7 +146,7 @@ public class CanvasBlock extends Block{
|
||||
return result;
|
||||
}
|
||||
|
||||
public class CanvasBuild extends Building{
|
||||
public class CanvasBuild extends Building implements LReadable, LWritable{
|
||||
public @Nullable Texture texture;
|
||||
public byte[] data = new byte[Mathf.ceil(canvasSize * canvasSize * bitsPerPixel / 8f)];
|
||||
public int blending;
|
||||
@@ -226,6 +226,25 @@ public class CanvasBlock extends Block{
|
||||
}
|
||||
}
|
||||
|
||||
public boolean readable(LExecutor exec){
|
||||
return exec.privileged || this.team == exec.team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(LVar position, LVar output){
|
||||
output.setnum(getPixel(position.numi()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writable(LExecutor exec){
|
||||
return exec.privileged || this.team == exec.team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(LVar position, LVar value){
|
||||
setPixel(position.numi(), value.numi());
|
||||
}
|
||||
|
||||
boolean blends(Tile other){
|
||||
return other != null && other.build != null && other.build.block == block && other.build.tileX() == other.x && other.build.tileY() == other.y;
|
||||
}
|
||||
|
||||
@@ -82,17 +82,11 @@ public class LogicBlock extends Block{
|
||||
int x = lbuild.tileX(), y = lbuild.tileY();
|
||||
|
||||
LogicLink link = entity.links.find(l -> l.x == x && l.y == y);
|
||||
String bname = getLinkName(lbuild.block);
|
||||
|
||||
if(link != null){
|
||||
link.active = !link.active;
|
||||
//find a name when the base name differs (new block type)
|
||||
if(!link.name.startsWith(bname)){
|
||||
link.name = "";
|
||||
link.name = entity.findLinkName(lbuild.block);
|
||||
}
|
||||
entity.links.remove(link);
|
||||
//disable when unlinking
|
||||
if(!link.active && lbuild.block.autoResetEnabled && lbuild.lastDisabler == entity){
|
||||
if(lbuild.block.autoResetEnabled && lbuild.lastDisabler == entity){
|
||||
lbuild.enabled = true;
|
||||
}
|
||||
}else{
|
||||
@@ -148,12 +142,8 @@ public class LogicBlock extends Block{
|
||||
stream.writeInt(bytes.length);
|
||||
stream.write(bytes);
|
||||
|
||||
int actives = links.count(l -> l.active);
|
||||
|
||||
stream.writeInt(actives);
|
||||
stream.writeInt(links.size);
|
||||
for(LogicLink link : links){
|
||||
if(!link.active) continue;
|
||||
|
||||
stream.writeUTF(link.name);
|
||||
stream.writeShort(link.x);
|
||||
stream.writeShort(link.y);
|
||||
@@ -207,11 +197,7 @@ public class LogicBlock extends Block{
|
||||
String name = stream.readUTF();
|
||||
short x = stream.readShort(), y = stream.readShort();
|
||||
|
||||
Tmp.p2.set((int)(offset / (tilesize/2)), (int)(offset / (tilesize/2)));
|
||||
transformer.get(Tmp.p1.set(x * 2, y * 2).sub(Tmp.p2));
|
||||
Tmp.p1.add(Tmp.p2);
|
||||
Tmp.p1.x /= 2;
|
||||
Tmp.p1.y /= 2;
|
||||
transformer.get(Tmp.p1.set(x, y));
|
||||
links.add(new LogicLink(Tmp.p1.x, Tmp.p1.y, name, true));
|
||||
}
|
||||
|
||||
@@ -224,7 +210,7 @@ public class LogicBlock extends Block{
|
||||
}
|
||||
|
||||
public static class LogicLink{
|
||||
public boolean active = true, valid;
|
||||
public boolean valid;
|
||||
public int x, y;
|
||||
public String name;
|
||||
public Building lastBuild;
|
||||
@@ -237,13 +223,11 @@ public class LogicBlock extends Block{
|
||||
}
|
||||
|
||||
public LogicLink copy(){
|
||||
LogicLink out = new LogicLink(x, y, name, valid);
|
||||
out.active = active;
|
||||
return out;
|
||||
return new LogicLink(x, y, name, valid);
|
||||
}
|
||||
}
|
||||
|
||||
public class LogicBuild extends Building implements Ranged{
|
||||
public class LogicBuild extends Building implements Ranged, LReadable, LWritable{
|
||||
/** logic "source code" as list of asm statements */
|
||||
public String code = "";
|
||||
public LExecutor executor = new LExecutor();
|
||||
@@ -357,18 +341,19 @@ public class LogicBlock extends Block{
|
||||
|
||||
//store connections
|
||||
for(LogicLink link : links){
|
||||
if(link.active && (link.valid = validLink(world.build(link.x, link.y)))){
|
||||
link.valid = validLink(world.build(link.x, link.y));
|
||||
if(link.valid){
|
||||
asm.putConst(link.name, world.build(link.x, link.y));
|
||||
}
|
||||
}
|
||||
|
||||
//store link objects
|
||||
executor.links = new Building[links.count(l -> l.valid && l.active)];
|
||||
executor.links = new Building[links.count(l -> l.valid)];
|
||||
executor.linkIds.clear();
|
||||
|
||||
int index = 0;
|
||||
for(LogicLink link : links){
|
||||
if(link.active && link.valid){
|
||||
if(link.valid){
|
||||
Building build = world.build(link.x, link.y);
|
||||
executor.links[index ++] = build;
|
||||
if(build != null) executor.linkIds.add(build.id);
|
||||
@@ -387,9 +372,7 @@ public class LogicBlock extends Block{
|
||||
if(!var.constant){
|
||||
LVar dest = asm.getVar(var.name);
|
||||
if(dest != null && !dest.constant){
|
||||
dest.isobj = var.isobj;
|
||||
dest.objval = var.objval;
|
||||
dest.numval = var.numval;
|
||||
dest.set(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,8 +480,6 @@ public class LogicBlock extends Block{
|
||||
for(int i = 0; i < links.size; i++){
|
||||
LogicLink l = links.get(i);
|
||||
|
||||
if(!l.active) continue;
|
||||
|
||||
var cur = world.build(l.x, l.y);
|
||||
|
||||
boolean valid = validLink(cur);
|
||||
@@ -551,6 +532,38 @@ public class LogicBlock extends Block{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readable(LExecutor exec){
|
||||
return exec.privileged || (this.team == exec.team && !this.block.privileged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(LVar position, LVar output){
|
||||
if(position.isobj && position.objval instanceof String varName){
|
||||
LVar ret = executor.optionalVar(varName);
|
||||
if(ret == null){
|
||||
output.setnum(Double.NaN);
|
||||
return;
|
||||
}
|
||||
if(output.constant) return;
|
||||
output.set(ret);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writable(LExecutor exec){
|
||||
return exec.privileged || (this.team == exec.team && !this.block.privileged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(LVar position, LVar value){
|
||||
if(position.isobj && position.objval instanceof String varName){
|
||||
LVar at = executor.optionalVar(varName);
|
||||
if(at == null || at.constant) return;
|
||||
at.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] config(){
|
||||
return compress(code, relativeConnections());
|
||||
@@ -577,7 +590,7 @@ public class LogicBlock extends Block{
|
||||
|
||||
for(LogicLink l : links){
|
||||
Building build = world.build(l.x, l.y);
|
||||
if(l.active && validLink(build)){
|
||||
if(validLink(build)){
|
||||
Drawf.square(build.x, build.y, build.block.size * tilesize / 2f + 1f, Pal.place);
|
||||
}
|
||||
}
|
||||
@@ -585,7 +598,7 @@ public class LogicBlock extends Block{
|
||||
//draw top text on separate layer
|
||||
for(LogicLink l : links){
|
||||
Building build = world.build(l.x, l.y);
|
||||
if(l.active && validLink(build)){
|
||||
if(validLink(build)){
|
||||
build.block.drawPlaceText(l.name, build.tileX(), build.tileY(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public class LogicDisplay extends Block{
|
||||
public double sense(LAccess sensor){
|
||||
return switch(sensor){
|
||||
case displayWidth, displayHeight -> displaySize;
|
||||
case bufferUsage -> commands.size;
|
||||
case bufferSize -> commands.size;
|
||||
case operations -> operations;
|
||||
default -> super.sense(sensor);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ public class MemoryBlock extends Block{
|
||||
return accessible();
|
||||
}
|
||||
|
||||
public class MemoryBuild extends Building{
|
||||
public class MemoryBuild extends Building implements LReadable, LWritable{
|
||||
public double[] memory = new double[memoryCapacity];
|
||||
|
||||
//massive byte size means picking up causes sync issues
|
||||
@@ -56,6 +56,29 @@ public class MemoryBlock extends Block{
|
||||
return accessible();
|
||||
}
|
||||
|
||||
public boolean readable(LExecutor exec){
|
||||
return exec.privileged || (this.team == exec.team && !this.block.privileged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(LVar position, LVar output){
|
||||
int address = position.numi();
|
||||
//Return null when out of bounds. (instead of 0)
|
||||
output.setnum(address < 0 || address >= memory.length ? Double.NaN : memory[address]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writable(LExecutor exec){
|
||||
return exec.privileged || (this.team == exec.team && !this.block.privileged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(LVar position, LVar value){
|
||||
int address = position.numi();
|
||||
if(address < 0 || address >= memory.length) return;
|
||||
memory[address] = value.num();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
return switch(sensor){
|
||||
|
||||
@@ -14,6 +14,7 @@ import arc.util.io.*;
|
||||
import arc.util.pooling.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
@@ -67,7 +68,7 @@ public class MessageBlock extends Block{
|
||||
return accessible();
|
||||
}
|
||||
|
||||
public class MessageBuild extends Building{
|
||||
public class MessageBuild extends Building implements LReadable{
|
||||
public StringBuilder message = new StringBuilder();
|
||||
|
||||
@Override
|
||||
@@ -165,6 +166,25 @@ public class MessageBlock extends Block{
|
||||
return !accessible() ? SystemCursor.arrow : super.getCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readable(LExecutor exec){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(LVar position, LVar output){
|
||||
int address = position.numi();
|
||||
output.setnum(address < 0 || address >= message.length() ? Double.NaN : message.charAt(address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
return switch(sensor){
|
||||
case bufferSize -> message.length();
|
||||
default -> super.sense(sensor);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(float damage){
|
||||
if(privileged) return;
|
||||
|
||||
@@ -228,7 +228,7 @@ public class PayloadLoader extends PayloadBlock{
|
||||
return payload != null && (
|
||||
exporting ||
|
||||
(payload.block().hasLiquids && liquids.currentAmount() >= 0.1f && payload.build.liquids.currentAmount() >= payload.block().liquidCapacity - 0.001f) ||
|
||||
(payload.block().hasItems && items.any() && payload.block().separateItemCapacity && content.items().contains(i -> payload.build.items.get(i) >= payload.block().itemCapacity)) ||
|
||||
(payload.block().hasItems && items.any() && payload.block().separateItemCapacity && content.items().contains(i -> (payload.build.items.get(i) >= payload.block().itemCapacity) && items.has(i))) ||
|
||||
(hasBattery() && payload.build.power.status >= 0.999999999f));
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ public class Drill extends Block{
|
||||
|
||||
@Override
|
||||
public boolean shouldConsume(){
|
||||
return items.total() < itemCapacity && enabled;
|
||||
return items.total() < itemCapacity && enabled && dominantItem != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,7 +33,7 @@ public class Incinerator extends Block{
|
||||
|
||||
@Override
|
||||
public BlockStatus status(){
|
||||
return heat > 0.5f ? BlockStatus.active : BlockStatus.noInput;
|
||||
return !enabled ? BlockStatus.logicDisable : heat > 0.5f ? BlockStatus.active : BlockStatus.noInput;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ItemIncinerator extends Block{
|
||||
|
||||
@Override
|
||||
public BlockStatus status(){
|
||||
return efficiency > 0 ? BlockStatus.active : BlockStatus.noInput;
|
||||
return !enabled ? BlockStatus.logicDisable : efficiency > 0 ? BlockStatus.active : BlockStatus.noInput;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -220,6 +220,14 @@ public class UnitFactory extends UnitBlock{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawSelect(){
|
||||
super.drawSelect();
|
||||
if(plans.size > 1 && currentPlan != -1 && currentPlan < plans.size){
|
||||
drawItemSelection(plans.get(currentPlan).unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getCommandPosition(){
|
||||
return commandPos;
|
||||
|
||||
Reference in New Issue
Block a user