Merge branch 'master' into do-you-hear-the-voices-too

This commit is contained in:
Mythril382
2024-06-17 02:06:46 +08:00
committed by GitHub
67 changed files with 1295 additions and 1168 deletions

View File

@@ -1099,6 +1099,7 @@ public class ControlPathfinder implements Runnable{
destY = World.toTile(mainDestination.y),
actualDestX = World.toTile(destination.x),
actualDestY = World.toTile(destination.y),
actualDestPos = actualDestX + actualDestY * wwidth,
destPos = destX + destY * wwidth;
PathRequest request = unitRequests.get(unit);
@@ -1156,8 +1157,10 @@ public class ControlPathfinder implements Runnable{
int i = 0;
boolean recalc = false;
if(packedPos == actualDestPos){
request.lastTargetTile = tileOn;
//TODO last pos can change if the flowfield changes.
if(initialTileOn.pos() != request.lastTile || request.lastTargetTile == null){
}else if(initialTileOn.pos() != request.lastTile || request.lastTargetTile == null){
boolean anyNearSolid = false;
//find the next tile until one near a solid block is discovered
@@ -1181,9 +1184,13 @@ public class ControlPathfinder implements Runnable{
anyNearSolid = true;
}
if((value == 0 || otherCost < value) && otherCost != impassable && (otherCost != 0 || packed == destPos) && (current == null || otherCost < minCost) && passable(unit.team.id, cost, packed)){
if((value == 0 || otherCost < value) && otherCost != impassable && ((otherCost != 0 && (current == null || otherCost < minCost)) || packed == actualDestPos || packed == destPos) && passable(unit.team.id, cost, packed)){
current = other;
minCost = otherCost;
//no need to keep searching.
if(packed == destPos || packed == actualDestPos){
break;
}
}
}
@@ -1205,7 +1212,9 @@ public class ControlPathfinder implements Runnable{
tileOn = current;
any = true;
if(current.array() == destPos){
int a = current.array();
if(a == destPos || a == actualDestPos){
break;
}
}
@@ -1216,13 +1225,13 @@ public class ControlPathfinder implements Runnable{
}
request.lastTargetTile = any ? tileOn : null;
if(showDebug && tileOn != null){
if(showDebug && tileOn != null && Core.graphics.getFrameId() % 30 == 0){
Fx.placeBlock.at(tileOn.worldx(), tileOn.worldy(), 1);
}
}
if(request.lastTargetTile != null){
if(showDebug){
if(showDebug && Core.graphics.getFrameId() % 30 == 0){
Fx.breakBlock.at(request.lastTargetTile.worldx(), request.lastTargetTile.worldy(), 1);
}
out.set(request.lastTargetTile);
@@ -1325,6 +1334,30 @@ public class ControlPathfinder implements Runnable{
return 0;
}
/** @return 0 if nothing was hit, otherwise the packed coordinates. This is an internal function and will likely be moved - do not use!*/
public static int raycastFastAvoid(int team, PathCost type, int x1, int y1, int x2, int y2){
int ww = world.width(), wh = world.height();
int x = x1, dx = Math.abs(x2 - x), sx = x < x2 ? 1 : -1;
int y = y1, dy = Math.abs(y2 - y), sy = y < y2 ? 1 : -1;
int err = dx - dy;
while(x >= 0 && y >= 0 && x < ww && y < wh){
if(avoid(team, type, x + y * wwidth)) return Point2.pack(x, y);
if(x == x2 && y == y2) return 0;
//no diagonals
if(2 * err + dy > dx - 2 * err){
err -= dy;
x += sx;
}else{
err += dx;
y += sy;
}
}
return 0;
}
private static boolean overlap(int team, PathCost type, int x, int y, float startX, float startY, float endX, float endY, float rectSize){
if(x < 0 || y < 0 || x >= wwidth || y >= wheight) return false;
if(!nearPassable(team, type, x + y * wwidth)){

View File

@@ -168,9 +168,9 @@ public class UnitGroup{
Unit unit = units.get(index);
PathCost cost = unit.type.pathCost;
int res = ControlPathfinder.raycastFast(unit.team.id, cost, World.toTile(dest.x), World.toTile(dest.y), World.toTile(x), World.toTile(y));
int res = ControlPathfinder.raycastFastAvoid(unit.team.id, cost, World.toTile(dest.x), World.toTile(dest.y), World.toTile(x), World.toTile(y));
//collision found, make th destination the point right before the collision
//collision found, make the destination the point right before the collision
if(res != 0){
v1.set(Point2.x(res) * Vars.tilesize - dest.x, Point2.y(res) * Vars.tilesize - dest.y);
v1.setLength(Math.max(v1.len() - Vars.tilesize - 4f, 0));

View File

@@ -152,14 +152,21 @@ public class WaveSpawner{
}
private void eachFlyerSpawn(int filterPos, Floatc2 cons){
boolean airUseSpawns = state.rules.airUseSpawns;
for(Tile tile : spawns){
if(filterPos != -1 && filterPos != tile.pos()) continue;
float angle = Angles.angle(world.width() / 2f, world.height() / 2f, tile.x, tile.y);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(angle, trns), -margin, world.height() * tilesize + margin);
cons.get(spawnX, spawnY);
if(!airUseSpawns){
float angle = Angles.angle(world.width() / 2f, world.height() / 2f, tile.x, tile.y);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(angle, trns), -margin, world.height() * tilesize + margin);
cons.get(spawnX, spawnY);
}else{
cons.get(tile.worldx(), tile.worldy());
}
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam)){

View File

@@ -39,7 +39,7 @@ public class FlyingFollowAI extends FlyingAI{
@Override
public void updateVisuals(){
if(unit.isFlying()){
unit.wobble();
if(unit.type.wobble) unit.wobble();
if(!shouldFaceTarget()){
unit.lookAt(unit.prefRotation());

View File

@@ -117,6 +117,8 @@ public class NetServer implements ApplicationListener{
private DataOutputStream dataStream = new DataOutputStream(syncStream);
/** Packet handlers for custom types of messages. */
private ObjectMap<String, Seq<Cons2<Player, String>>> customPacketHandlers = new ObjectMap<>();
/** Packet handlers for logic client data */
private ObjectMap<String, Seq<Cons2<Player, Object>>> logicClientDataHandlers = new ObjectMap<>();
public NetServer(){
@@ -515,6 +517,10 @@ public class NetServer implements ApplicationListener{
return customPacketHandlers.get(type, Seq::new);
}
public void addLogicDataHandler(String type, Cons2<Player, Object> handler){
logicClientDataHandlers.get(type, Seq::new).add(handler);
}
public static void onDisconnect(Player player, String reason){
//singleplayer multiplayer weirdness
if(player.con == null){
@@ -583,6 +589,21 @@ public class NetServer implements ApplicationListener{
serverPacketReliable(player, type, contents);
}
@Remote(targets = Loc.client)
public static void clientLogicDataReliable(Player player, String channel, Object value){
Seq<Cons2<Player, Object>> handlers = netServer.logicClientDataHandlers.get(channel);
if(handlers != null){
for(Cons2<Player, Object> handler : handlers){
handler.get(player, value);
}
}
}
@Remote(targets = Loc.client, unreliable = true)
public static void clientLogicDataUnreliable(Player player, String channel, Object value){
clientLogicDataReliable(player, channel, value);
}
private static boolean invalid(float f){
return Float.isInfinite(f) || Float.isNaN(f);
}

View File

@@ -143,49 +143,7 @@ public interface Platform{
*/
default void showFileChooser(boolean open, String title, String extension, Cons<Fi> cons){
if(OS.isWindows || OS.isMac){
String formatted = (title.startsWith("@") ? Core.bundle.get(title.substring(1)) : title).replaceAll("\"", "'");
//native file dialog
Threads.daemon(() -> {
try{
FileDialogs.loadNatives();
String result;
//on MacOS, .msav is not properly recognized until I put garbage into the array?
String[] extensions = OS.isMac && open ? new String[]{"", "*." + extension} : new String[]{"*." + extension};
if(open){
result = FileDialogs.openFileDialog(formatted, FileChooser.getLastDirectory().absolutePath(), extensions, "." + extension + " files", false);
}else{
result = FileDialogs.saveFileDialog(formatted, FileChooser.getLastDirectory().child("file." + extension).absolutePath(), extensions, "." + extension + " files");
}
if(result == null) return;
if(result.length() > 1 && result.contains("\n")){
result = result.split("\n")[0];
}
//cancelled selection, ignore result
if(result.isEmpty() || result.equals("\n")) return;
if(result.endsWith("\n")) result = result.substring(0, result.length() - 1);
if(result.contains("\n")) throw new IOException("invalid input: \"" + result + "\"");
Fi file = Core.files.absolute(result);
Core.app.post(() -> {
FileChooser.setLastDirectory(file.isDirectory() ? file : file.parent());
if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
cons.get(file);
}
});
}catch(Throwable error){
Log.err("Failure to execute native file chooser", error);
Core.app.post(() -> defaultFileDialog(open, title, extension, cons));
}
});
showNativeFileChooser(open, title, cons, extension);
}else if(OS.isLinux && !OS.isAndroid){
showZenity(open, title, new String[]{extension}, cons, () -> defaultFileDialog(open, title, extension, cons));
}else{
@@ -268,6 +226,8 @@ public interface Platform{
default void showMultiFileChooser(Cons<Fi> cons, String... extensions){
if(mobile){
showFileChooser(true, extensions[0], cons);
}else if(OS.isWindows || OS.isMac){
showNativeFileChooser(true, "@open", cons, extensions);
}else if(OS.isLinux && !OS.isAndroid){
showZenity(true, "@open", extensions, cons, () -> defaultMultiFileChooser(cons, extensions));
}else{
@@ -279,6 +239,68 @@ public interface Platform{
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
}
default void showNativeFileChooser(boolean open, String title, Cons<Fi> cons, String... shownExtensions){
String formatted = (title.startsWith("@") ? Core.bundle.get(title.substring(1)) : title).replaceAll("\"", "'");
//this should never happen unless someone is being dumb with the parameters
String[] ext = shownExtensions == null || shownExtensions.length == 0 ? new String[]{""} : shownExtensions;
//native file dialog
Threads.daemon(() -> {
try{
FileDialogs.loadNatives();
String result;
String[] patterns = new String[ext.length];
for(int i = 0; i < ext.length; i++){
patterns[i] = "*." + ext[i];
}
//on MacOS, .msav is not properly recognized until I put garbage into the array?
if(patterns.length == 1 && OS.isMac && open){
patterns = new String[]{"", "*." + ext[0]};
}
if(open){
result = FileDialogs.openFileDialog(formatted, FileChooser.getLastDirectory().absolutePath(), patterns, "." + ext[0] + " files", false);
}else{
result = FileDialogs.saveFileDialog(formatted, FileChooser.getLastDirectory().child("file." + ext[0]).absolutePath(), patterns, "." + ext[0] + " files");
}
if(result == null) return;
if(result.length() > 1 && result.contains("\n")){
result = result.split("\n")[0];
}
//cancelled selection, ignore result
if(result.isEmpty() || result.equals("\n")) return;
if(result.endsWith("\n")) result = result.substring(0, result.length() - 1);
if(result.contains("\n")) throw new IOException("invalid input: \"" + result + "\"");
Fi file = Core.files.absolute(result);
Core.app.post(() -> {
FileChooser.setLastDirectory(file.isDirectory() ? file : file.parent());
if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + ext[0]));
}else{
cons.get(file);
}
});
}catch(Throwable error){
Log.err("Failure to execute native file chooser", error);
Core.app.post(() -> {
if(ext.length > 1){
defaultMultiFileChooser(cons, ext);
}else{
defaultFileDialog(open, title, ext[0], cons);
}
});
}
});
}
/** Hide the app. Android only. */
default void hide(){
}

View File

@@ -10,31 +10,24 @@ import mindustry.world.blocks.distribution.MassDriver.*;
import static mindustry.Vars.*;
public class MassDriverBolt extends BulletType{
public class MassDriverBolt extends BasicBulletType{
public MassDriverBolt(){
super(1f, 75);
collidesTiles = false;
lifetime = 1f;
width = 11f;
height = 13f;
shrinkY = 0f;
sprite = "shell";
despawnEffect = Fx.smeltsmoke;
hitEffect = Fx.hitBulletBig;
}
@Override
public void draw(Bullet b){
float w = 11f, h = 13f;
Draw.color(Pal.bulletYellowBack);
Draw.rect("shell-back", b.x, b.y, w, h, b.rotation() + 90);
Draw.color(Pal.bulletYellow);
Draw.rect("shell", b.x, b.y, w, h, b.rotation() + 90);
Draw.reset();
}
@Override
public void update(Bullet b){
super.update(b);
//data MUST be an instance of DriverBulletData
if(!(b.data() instanceof DriverBulletData data)){
hit(b);

View File

@@ -1915,6 +1915,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
case y -> World.conv(y);
case color -> Color.toDoubleBits(team.color.r, team.color.g, team.color.b, 1f);
case dead -> !isValid() ? 1 : 0;
case solid -> block.solid || checkSolid() ? 1 : 0;
case team -> team.id;
case health -> health;
case maxHealth -> maxHealth;

View File

@@ -83,7 +83,7 @@ public class AIController implements UnitController{
public void updateVisuals(){
if(unit.isFlying()){
unit.wobble();
if(unit.type.wobble) unit.wobble();
unit.lookAt(unit.prefRotation());
}

View File

@@ -29,6 +29,8 @@ public class Rules{
public boolean waveSending = true;
/** Whether waves are spawnable at all. */
public boolean waves;
/** Whether air units spawn at spawns instead of the edge of the map */
public boolean airUseSpawns = false;
/** Whether the game objective is PvP. Note that this enables automatic hosting. */
public boolean pvp;
/** Whether is waiting for players enabled in PvP. */
@@ -201,6 +203,8 @@ public class Rules{
public @Nullable PlanetParams planetBackground;
/** Rules from this planet are applied. If it's {@code sun}, mixed tech is enabled. */
public Planet planet = Planets.serpulo;
/** If the `data` instruction is allowed for world processors */
public boolean allowLogicData = false;
/** Copies this ruleset exactly. Not efficient at all, do not use often. */
public Rules copy(){

View File

@@ -1038,14 +1038,19 @@ public class TypeIO{
}
}
public interface Boxed<T> {
T unbox();
}
/** Represents a building that has not been resolved yet. */
public static class BuildingBox{
public static class BuildingBox implements Boxed<Building>{
public int pos;
public BuildingBox(int pos){
this.pos = pos;
}
@Override
public Building unbox(){
return world.build(pos);
}
@@ -1059,13 +1064,14 @@ public class TypeIO{
}
/** Represents a unit that has not been resolved yet. TODO unimplemented / unused*/
public static class UnitBox{
public static class UnitBox implements Boxed<Unit>{
public int id;
public UnitBox(int id){
this.id = id;
}
@Override
public Unit unbox(){
return Groups.unit.getByID(id);
}

View File

@@ -8,11 +8,9 @@ import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.game.*;
import mindustry.logic.LExecutor.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.legacy.*;
@@ -29,12 +27,11 @@ public class GlobalVars{
public static final Rand rand = new Rand();
//non-constants that depend on state
private static int varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
private static LVar varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
private ObjectIntMap<String> namesToIds = new ObjectIntMap<>();
private Seq<Var> vars = new Seq<>(Var.class);
private ObjectMap<String, LVar> vars = new ObjectMap<>();
private Seq<VarEntry> varEntries = new Seq<>();
private IntSet privilegedIds = new IntSet();
private ObjectSet<String> privilegedNames = new ObjectSet<>();
private UnlockableContent[][] logicIdToContent;
private int[][] contentIdToLogicId;
@@ -131,9 +128,6 @@ public class GlobalVars{
put("@color" + Strings.capitalize(entry.key), entry.value.toDoubleBits());
}
//used as a special value for any environmental solid block
put("@solid", Blocks.stoneWall);
for(UnitType type : Vars.content.units()){
put("@" + type.name, type);
}
@@ -185,31 +179,31 @@ public class GlobalVars{
public void update(){
//set up time; note that @time is now only updated once every invocation and directly based off of @tick.
//having time be based off of user system time was a very bad idea.
vars.items[varTime].numval = state.tick / 60.0 * 1000.0;
vars.items[varTick].numval = state.tick;
varTime.numval = state.tick / 60.0 * 1000.0;
varTick.numval = state.tick;
//shorthands for seconds/minutes spent in save
vars.items[varSecond].numval = state.tick / 60f;
vars.items[varMinute].numval = state.tick / 60f / 60f;
varSecond.numval = state.tick / 60f;
varMinute.numval = state.tick / 60f / 60f;
//wave state
vars.items[varWave].numval = state.wave;
vars.items[varWaveTime].numval = state.wavetime / 60f;
varWave.numval = state.wave;
varWaveTime.numval = state.wavetime / 60f;
vars.items[varMapW].numval = world.width();
vars.items[varMapH].numval = world.height();
varMapW.numval = world.width();
varMapH.numval = world.height();
//network
vars.items[varServer].numval = (net.server() || !net.active()) ? 1 : 0;
vars.items[varClient].numval = net.client() ? 1 : 0;
varServer.numval = (net.server() || !net.active()) ? 1 : 0;
varClient.numval = net.client() ? 1 : 0;
//client
if(!net.server() && player != null){
vars.items[varClientLocale].objval = player.locale();
vars.items[varClientUnit].objval = player.unit();
vars.items[varClientName].objval = player.name();
vars.items[varClientTeam].numval = player.team().id;
vars.items[varClientMobile].numval = mobile ? 1 : 0;
varClientLocale.objval = player.locale();
varClientUnit.objval = player.unit();
varClientName.objval = player.name();
varClientTeam.numval = player.team().id;
varClientMobile.numval = mobile ? 1 : 0;
}
}
@@ -230,84 +224,81 @@ public class GlobalVars{
}
/**
* @return a constant ID > 0 if there is a constant with this name, otherwise -1.
* Attempt to get privileged variable id from non-privileged logic executor returns null constant id.
* @return a constant variable if there is a constant with this name, otherwise null.
* Attempt to get privileged variable from non-privileged logic executor returns null constant.
*/
public int get(String name){
return namesToIds.get(name, -1);
public LVar get(String name){
return vars.get(name);
}
/**
* @return a constant variable by ID. ID is not bound checked and must be positive.
* Attempt to get privileged variable from non-privileged logic executor returns null constant
* @return a constant variable by name
* Attempt to get privileged variable from non-privileged logic executor returns null constant.
*/
public Var get(int id, boolean privileged){
if(!privileged && privilegedIds.contains(id)) return vars.get(namesToIds.get("null"));
return vars.items[id];
public LVar get(String name, boolean privileged){
if(!privileged && privilegedNames.contains(name)) return vars.get("null");
return vars.get(name);
}
/** Sets a global variable by an ID returned from put(). */
public void set(int id, double value){
get(id, true).numval = value;
/** Sets a global variable by name. */
public void set(String name, double value){
get(name, true).numval = value;
}
/** Adds a constant value by name. */
public int put(String name, Object value, boolean privileged){
public LVar put(String name, Object value, boolean privileged){
return put(name, value, privileged, true);
}
/** Adds a constant value by name. */
public int put(String name, Object value, boolean privileged, boolean hidden){
int existingIdx = namesToIds.get(name, -1);
if(existingIdx != -1){ //don't overwrite existing vars (see #6910)
public LVar put(String name, Object value, boolean privileged, boolean hidden){
LVar existingVar = vars.get(name);
if(existingVar != null){ //don't overwrite existing vars (see #6910)
Log.debug("Failed to add global logic variable '@', as it already exists.", name);
return existingIdx;
return existingVar;
}
Var var = new Var(name);
LVar var = new LVar(name);
var.constant = true;
if(value instanceof Number num){
var.isobj = false;
var.numval = num.doubleValue();
}else{
var.isobj = true;
var.objval = value;
}
int index = vars.size;
namesToIds.put(name, index);
if(privileged) privilegedIds.add(index);
vars.add(var);
vars.put(name, var);
if(privileged) privilegedNames.add(name);
if(!hidden){
varEntries.add(new VarEntry(index, name, "", "", privileged));
varEntries.add(new VarEntry(name, "", "", privileged));
}
return index;
return var;
}
public int put(String name, Object value){
public LVar put(String name, Object value){
return put(name, value, false);
}
public int putEntry(String name, Object value){
public LVar putEntry(String name, Object value){
return put(name, value, false, false);
}
public int putEntry(String name, Object value, boolean privileged){
public LVar putEntry(String name, Object value, boolean privileged){
return put(name, value, privileged, false);
}
public void putEntryOnly(String name){
varEntries.add(new VarEntry(0, name, "", "", false));
varEntries.add(new VarEntry(name, "", "", false));
}
/** An entry that describes a variable for documentation purposes. This is *only* used inside UI for global variables. */
public static class VarEntry{
public int id;
public String name, description, icon;
public boolean privileged;
public VarEntry(int id, String name, String description, String icon, boolean privileged){
this.id = id;
public VarEntry(String name, String description, String icon, boolean privileged){
this.name = name;
this.description = description;
this.icon = icon;

View File

@@ -5,12 +5,13 @@ import arc.graphics.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class GlobalVarsDialog extends BaseDialog{
public GlobalVarsDialog(){
@@ -28,8 +29,7 @@ public class GlobalVarsDialog extends BaseDialog{
cont.pane(t -> {
t.margin(10f).marginRight(16f);
t.defaults().fillX().fillY();
for(var entry : Vars.logicVars.getEntries()){
for(var entry : logicVars.getEntries()){
if(entry.name.startsWith("section")){
Color color = Pal.accent;
t.add("@lglobal." + entry.name).fillX().center().labelAlign(Align.center).colspan(4).color(color).padTop(4f).padBottom(2f).row();

View File

@@ -35,6 +35,7 @@ public enum LAccess{
cameraWidth,
cameraHeight,
size,
solid,
dead,
range,
shooting,

View File

@@ -14,16 +14,15 @@ public class LAssembler{
private static final int invalidNum = Integer.MIN_VALUE;
private int lastVar;
private boolean privileged;
/** Maps names to variable IDs. */
public ObjectMap<String, BVar> vars = new ObjectMap<>();
/** Maps names to variable. */
public OrderedMap<String, LVar> vars = new OrderedMap<>();
/** All instructions to be executed. */
public LInstruction[] instructions;
public LAssembler(){
//instruction counter
putVar("@counter").value = 0;
putVar("@counter");
//currently controlled unit
putConst("@unit", null);
//reference to self
@@ -57,20 +56,17 @@ public class LAssembler{
return new LParser(text, privileged).parse();
}
/** @return a variable ID by name.
/** @return a variable by name.
* This may be a constant variable referring to a number or object. */
public int var(String symbol){
int constId = Vars.logicVars.get(symbol);
if(constId > 0){
//global constants are *negated* and stored separately
return -constId;
}
public LVar var(String symbol){
LVar constVar = Vars.logicVars.get(symbol);
if(constVar != null) return constVar;
symbol = symbol.trim();
//string case
if(!symbol.isEmpty() && symbol.charAt(0) == '\"' && symbol.charAt(symbol.length() - 1) == '\"'){
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n"));
}
//remove spaces for non-strings
@@ -79,10 +75,10 @@ public class LAssembler{
double value = parseDouble(symbol);
if(value == invalidNum){
return putVar(symbol).id;
return putVar(symbol);
}else{
//this creates a hidden const variable with the specified value
return putConst("___" + value, value).id;
return putConst("___" + value, value);
}
}
@@ -106,48 +102,34 @@ public class LAssembler{
}
/** Adds a constant value by name. */
public BVar putConst(String name, Object value){
BVar var = putVar(name);
public LVar putConst(String name, Object value){
LVar var = putVar(name);
if(value instanceof Number number){
var.isobj = false;
var.numval = number.doubleValue();
var.objval = null;
}else{
var.isobj = true;
var.objval = value;
}
var.constant = true;
var.value = value;
return var;
}
/** Registers a variable name mapping. */
public BVar putVar(String name){
public LVar putVar(String name){
if(vars.containsKey(name)){
return vars.get(name);
}else{
BVar var = new BVar(lastVar++);
LVar var = new LVar(name);
vars.put(name, var);
return var;
}
}
@Nullable
public BVar getVar(String name){
public LVar getVar(String name){
return vars.get(name);
}
/** A variable "builder". */
public static class BVar{
public int id;
public boolean constant;
public Object value;
public BVar(int id){
this.id = id;
}
BVar(){}
@Override
public String toString(){
return "BVar{" +
"id=" + id +
", constant=" + constant +
", value=" + value +
'}';
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -282,8 +282,8 @@ public class LStatements{
@Override
public LInstruction build(LAssembler builder){
return new DrawI((byte)type.ordinal(), 0, builder.var(x), builder.var(y),
type == GraphicsType.print ? nameToAlign.get(p1, Align.bottomLeft) : builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
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));
}
@Override
@@ -1046,7 +1046,7 @@ public class LStatements{
@Override
public LInstruction build(LAssembler builder){
return new RadarI(target1, target2, target3, sort, LExecutor.varUnit, builder.var(sortOrder), builder.var(output));
return new RadarI(target1, target2, target3, sort, builder.var("@unit"), builder.var(sortOrder), builder.var(output));
}
@Override
@@ -1912,6 +1912,42 @@ public class LStatements{
}
}
@RegisterStatement("clientdata")
public static class ClientDataStatement extends LStatement{
public String channel = "\"frog\"", value = "\"bar\"", reliable = "0";
@Override
public void build(Table table){
table.add("send ");
fields(table, value, str -> value = str);
table.add(" on ");
fields(table, channel, str -> channel = str);
table.add(", reliable ");
fields(table, channel, str -> channel = str);
}
@Override
public boolean hidden(){
return true;
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
if(!state.rules.allowLogicData) return null;
return new ClientDataI(builder.var(channel), builder.var(value), builder.var(reliable));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
@RegisterStatement("getflag")
public static class GetFlagStatement extends LStatement{
public String result = "result", flag = "\"flag\"";

View File

@@ -0,0 +1,108 @@
package mindustry.logic;
import arc.util.*;
import mindustry.game.*;
import mindustry.gen.*;
public class LVar{
public final String name;
public int id;
public boolean isobj, constant;
public Object objval;
public double numval;
//ms timestamp for when this was last synced; used in the sync instruction
public long syncTime;
public LVar(String name){
this(name, -1);
}
public LVar(String name, int id){
this(name, id, false);
}
public LVar(String name, int id, boolean constant){
this.name = name;
this.id = id;
this.constant = constant;
}
public @Nullable Building building(){
return isobj && objval instanceof Building building ? building : null;
}
public @Nullable Object obj(){
return isobj ? objval : null;
}
public @Nullable Team team(){
if(isobj){
return objval instanceof Team t ? t : null;
}else{
int t = (int)numval;
if(t < 0 || t >= Team.all.length) return null;
return Team.all[t];
}
}
public boolean bool(){
return isobj ? objval != null : Math.abs(numval) >= 0.00001;
}
public double num(){
return isobj ? objval != null ? 1 : 0 : invalid(numval) ? 0 : numval;
}
/** Get num value from variable, convert null to NaN to handle it differently in some instructions */
public double numOrNan(){
return isobj ? objval != null ? 1 : Double.NaN : invalid(numval) ? 0 : numval;
}
public float numf(){
return isobj ? objval != null ? 1 : 0 : invalid(numval) ? 0 : (float)numval;
}
/** Get float value from variable, convert null to NaN to handle it differently in some instructions */
public float numfOrNan(){
return isobj ? objval != null ? 1 : Float.NaN : invalid(numval) ? 0 : (float)numval;
}
public int numi(){
return (int)num();
}
public void setbool(boolean value){
setnum(value ? 1 : 0);
}
public void setnum(double value){
if(constant) return;
if(invalid(value)){
objval = null;
isobj = true;
}else{
numval = value;
objval = null;
isobj = false;
}
}
public void setobj(Object value){
if(constant) return;
objval = value;
isobj = true;
}
public void setconst(Object value){
objval = value;
isobj = true;
}
public static boolean invalid(double d){
return Double.isNaN(d) || Double.isInfinite(d);
}
}

View File

@@ -53,7 +53,7 @@ public class LogicDialog extends BaseDialog{
add(buttons).growX().name("canvas");
}
public static Color typeColor(Var s, Color color){
public static Color typeColor(LVar s, Color color){
return color.set(
!s.isobj ? Pal.place :
s.objval == null ? Color.darkGray :
@@ -67,7 +67,7 @@ public class LogicDialog extends BaseDialog{
);
}
public static String typeName(Var s){
public static String typeName(LVar s){
return
!s.isobj ? "number" :
s.objval == null ? "null" :

View File

@@ -954,6 +954,8 @@ public class ContentParser{
case "min" -> base.min(parser.readValue(PartProgress.class, data.get("other")));
case "sin" -> base.sin(data.has("offset") ? data.getFloat("offset") : 0f, data.getFloat("scl"), data.getFloat("mag"));
case "absin" -> base.absin(data.getFloat("scl"), data.getFloat("mag"));
case "mod" -> base.mod(data.getFloat("amount"));
case "loop" -> base.loop(data.getFloat("time"));
case "curve" -> data.has("interp") ? base.curve(parser.readValue(Interp.class, data.get("interp"))) : base.curve(data.getFloat("offset"), data.getFloat("duration"));
default -> throw new RuntimeException("Unknown operation '" + op + "', check PartProgress class for a list of methods.");
};

View File

@@ -141,8 +141,10 @@ public class UnitType extends UnlockableContent implements Senseable{
/** if true, this unit counts as an enemy in the wave counter (usually false for support-only units) */
public boolean isEnemy = true,
/** If true, the unit is always at elevation 1. */
/** if true, the unit is always at elevation 1 */
flying = false,
/** whether this flying unit should wobble around */
wobble = true,
/** whether this unit tries to attack air units */
targetAir = true,
/** whether this unit tries to attack ground units */

View File

@@ -20,11 +20,10 @@ import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.gen.*;
import java.util.*;
import java.io.*;
public class Fonts{
private static final String mainFont = "fonts/font.woff";
@@ -33,15 +32,9 @@ public class Fonts{
private static IntMap<String> unicodeToName = new IntMap<>();
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
private static ObjectMap<String, TextureRegion> largeIcons = new ObjectMap<>();
private static TextureRegion[] iconTable;
private static int lastCid;
public static Font def, outline, icon, iconLarge, tech, logic;
public static TextureRegion logicIcon(int id){
return iconTable[id];
}
public static int getUnicode(String content){
return unicodeIcons.get(content, 0);
}
@@ -117,9 +110,9 @@ public class Fonts{
Texture uitex = Core.atlas.find("logo").texture;
int size = (int)(Fonts.def.getData().lineHeight/Fonts.def.getData().scaleY);
try(Scanner scan = new Scanner(Core.files.internal("icons/icons.properties").read(512))){
while(scan.hasNextLine()){
String line = scan.nextLine();
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
String line;
while((line = reader.readLine()) != null){
String[] split = line.split("=");
String[] nametex = split[1].split("\\|");
String character = split[0], texture = nametex[1];
@@ -154,32 +147,21 @@ public class Fonts{
glyph.page = 0;
fonts.each(f -> f.getData().setGlyph(ch, glyph));
}
}catch(IOException e){
throw new RuntimeException(e);
}
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));
iconTable = new TextureRegion[512];
iconTable[0] = Core.atlas.find("error");
lastCid = 1;
Vars.content.each(c -> {
if(c instanceof UnlockableContent u){
TextureRegion region = Core.atlas.find(u.name + "-icon-logic");
if(region.found()){
iconTable[u.iconId = lastCid++] = region;
}
}
});
for(Team team : Team.baseTeams){
team.emoji = stringIcons.get(team.name, "");
}
}
public static void loadContentIconsHeadless(){
try(Scanner scan = new Scanner(Core.files.internal("icons/icons.properties").read(512))){
while(scan.hasNextLine()){
String line = scan.nextLine();
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
String line;
while((line = reader.readLine()) != null){
String[] split = line.split("=");
String[] nametex = split[1].split("\\|");
String character = split[0];
@@ -188,6 +170,8 @@ public class Fonts{
unicodeIcons.put(nametex[0], ch);
stringIcons.put(nametex[0], ((char)ch) + "");
}
}catch(IOException e){
throw new RuntimeException(e);
}
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));

View File

@@ -215,6 +215,7 @@ public class CustomRulesDialog extends BaseDialog{
check("@rules.wavesending", b -> rules.waveSending = b, () -> rules.waveSending, () -> rules.waves);
check("@rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer, () -> rules.waves);
check("@rules.waitForWaveToEnd", b -> rules.waitEnemies = b, () -> rules.waitEnemies, () -> rules.waves && rules.waveTimer);
check("@rules.airUseSpawns", b -> rules.airUseSpawns = b, () -> rules.airUseSpawns, () -> rules.waves);
numberi("@rules.wavelimit", f -> rules.winWave = f, () -> rules.winWave, () -> rules.waves, 0, Integer.MAX_VALUE);
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> rules.waves && rules.waveTimer, 1, Float.MAX_VALUE);
//this is experimental, because it's not clear that 0 makes it default.

View File

@@ -519,6 +519,10 @@ public class Block extends UnlockableContent implements Senseable{
return rotate;
}
public boolean rotatedOutput(int fromX, int fromY, Tile destination){
return rotatedOutput(fromX, fromY);
}
public boolean synthetic(){
return update || destructible;
}
@@ -1403,6 +1407,7 @@ public class Block extends UnlockableContent implements Senseable{
return switch(sensor){
case color -> mapColor.toDoubleBits();
case health, maxHealth -> health;
case solid -> solid ? 1 : 0;
case size -> size;
case itemCapacity -> itemCapacity;
case liquidCapacity -> liquidCapacity;

View File

@@ -206,7 +206,7 @@ public interface Autotiler{
//block is facing the other
Point2.equals(tile.x + Geometry.d4(rotation).x, tile.y + Geometry.d4(rotation).y, otherx, othery) ||
//does not output to rotated direction
!otherblock.rotatedOutput(otherx, othery) ||
!otherblock.rotatedOutput(otherx, othery, tile) ||
//other block is facing this one
Point2.equals(otherx + Geometry.d4(otherrot).x, othery + Geometry.d4(otherrot).y, tile.x, tile.y);
}

View File

@@ -20,8 +20,6 @@ import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.io.TypeIO.*;
import mindustry.logic.*;
import mindustry.logic.LAssembler.*;
import mindustry.logic.LExecutor.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.ConstructBlock.*;
@@ -381,12 +379,14 @@ public class LogicBlock extends Block{
if(keep){
//store any older variables
for(Var var : executor.vars){
for(LVar var : executor.vars){
boolean unit = var.name.equals("@unit");
if(!var.constant || unit){
BVar dest = asm.getVar(var.name);
LVar dest = asm.getVar(var.name);
if(dest != null && (!dest.constant || unit)){
dest.value = var.isobj ? var.objval : var.numval;
dest.isobj = var.isobj;
dest.objval = var.objval;
dest.numval = var.numval;
}
}
}
@@ -397,7 +397,7 @@ public class LogicBlock extends Block{
assemble.get(asm);
}
asm.getVar("@this").value = this;
asm.getVar("@this").setconst(this);
asm.putConst("@thisx", World.conv(x));
asm.putConst("@thisy", World.conv(y));
@@ -682,17 +682,17 @@ public class LogicBlock extends Block{
write.b(compressed);
//write only the non-constant variables
int count = Structs.count(executor.vars, v -> (!v.constant || v == executor.vars[LExecutor.varUnit]) && !(v.isobj && v.objval == null));
int count = Structs.count(executor.vars, v -> (!v.constant || v == executor.unit) && !(v.isobj && v.objval == null));
write.i(count);
for(int i = 0; i < executor.vars.length; i++){
Var v = executor.vars[i];
LVar v = executor.vars[i];
//null is the default variable value, so waste no time serializing that
if(v.isobj && v.objval == null) continue;
//skip constants
if(v.constant && i != LExecutor.varUnit) continue;
if(v.constant && v != executor.unit) continue;
//write the name and the object value
write.str(v.name);
@@ -751,13 +751,9 @@ public class LogicBlock extends Block{
loadBlock = () -> updateCode(code, false, asm -> {
//load up the variables that were stored
for(int i = 0; i < varcount; i++){
BVar dest = asm.getVar(names[i]);
if(dest != null && (!dest.constant || dest.id == LExecutor.varUnit)){
dest.value =
values[i] instanceof BuildingBox box ? box.unbox() :
values[i] instanceof UnitBox box ? box.unbox() :
values[i];
LVar var = asm.getVar(names[i]);
if(var.objval instanceof Boxed<?> boxed){
var.objval = boxed.unbox();
}
}
});

View File

@@ -10,6 +10,7 @@ import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
@@ -120,8 +121,10 @@ public class LogicDisplay extends Block{
case commandColor -> Draw.color(this.color = Color.toFloatBits(x, y, p1, p2));
case commandStroke -> Lines.stroke(this.stroke = x);
case commandImage -> {
var icon = Fonts.logicIcon(p1);
Draw.rect(Fonts.logicIcon(p1), x, y, p2, p2 / icon.ratio(), p3);
if(p4 >= 0 && p4 < ContentType.all.length && Vars.content.getByID(ContentType.all[p4], p1) instanceof UnlockableContent u){
var icon = u.fullIcon;
Draw.rect(icon, x, y, p2, p2 / icon.ratio(), p3);
}
}
case commandPrint -> {
var glyph = Fonts.logic.getData().getGlyph((char)p1);

View File

@@ -13,6 +13,7 @@ import mindustry.gen.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.liquid.Conduit.*;
import mindustry.world.draw.*;
import mindustry.world.meta.*;
@@ -91,8 +92,17 @@ public class GenericCrafter extends Block{
}
@Override
public boolean rotatedOutput(int x, int y){
return false;
public boolean rotatedOutput(int fromX, int fromY, Tile destination){
if(!(destination.build instanceof ConduitBuild)) return false;
Building crafter = world.build(fromX, fromY);
if(crafter == null) return false;
int relative = Mathf.mod(crafter.relativeTo(destination) - crafter.rotation, 4);
for(int dir : liquidOutputDirections){
if(dir == -1 || dir == relative) return false;
}
return true;
}
@Override