Merge branch Anuken:master into pr-readwrite

This commit is contained in:
WayZer
2024-07-31 11:20:20 +08:00
246 changed files with 10693 additions and 6167 deletions

View File

@@ -66,11 +66,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Time.setDeltaProvider(() -> {
float result = Core.graphics.getDeltaTime() * 60f;
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, 60f / 10f);
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, maxDeltaClient);
});
UI.loadColors();
batch = new SortedSpriteBatch();
batch = new SpriteBatch();
assets = new AssetManager();
assets.setLoader(Texture.class, "." + mapExtension, new MapPreviewLoader());

View File

@@ -28,6 +28,7 @@ import mindustry.net.*;
import mindustry.service.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.meta.*;
import java.io.*;
@@ -105,8 +106,8 @@ public class Vars implements Loadable{
public static final float invasionGracePeriod = 20;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.1f;
/** land/launch animation duration */
public static final float coreLandDuration = 160f;
/** @deprecated see {@link CoreBlock#landDuration} instead! */
public static final @Deprecated float coreLandDuration = 160f;
/** size of tiles in units */
public static final int tilesize = 8;
/** size of one tile payload (^2) */
@@ -136,6 +137,13 @@ public class Vars implements Loadable{
Color.valueOf("4b5ef1"),
Color.valueOf("2cabfe"),
};
/** Icons available to the user for customization in certain dialogs. */
public static final String[] accessibleIcons = {
"effect", "power", "logic", "units", "liquid", "production", "defense", "turret", "distribution", "crafting",
"settings", "cancel", "zoom", "ok", "star", "home", "pencil", "up", "down", "left", "right",
"hammer", "warning", "tree", "admin", "map", "modePvp", "terrain",
"modeSurvival", "commandRally", "commandAttack",
};
/** maximum TCP packet size */
public static final int maxTcpSize = 900;
/** default server port */
@@ -146,6 +154,8 @@ public class Vars implements Loadable{
public static final int maxModSubtitleLength = 40;
/** multicast group for discovery.*/
public static final String multicastGroup = "227.2.7.7";
/** Maximum delta time. If the actual delta time (*60) between frames is higher than this number, the game will start to slow down. */
public static float maxDeltaClient = 6f, maxDeltaServer = 10f;
/** whether the graphical game client has loaded */
public static boolean clientLoaded = false;
/** max GL texture size */

View File

@@ -32,7 +32,6 @@ public class BaseBuilderAI{
private static int correct = 0, incorrect = 0;
private int lastX, lastY, lastW, lastH;
private boolean foundPath;
final TeamData data;
@@ -262,11 +261,6 @@ public class BaseBuilderAI{
data.plans.add(new BlockPlan(cx + tile.x, cy + tile.y, tile.rotation, tile.block.id, tile.config));
}
lastX = cx - 1;
lastY = cy - 1;
lastW = result.width + 2;
lastH = result.height + 2;
return true;
}
}

View File

@@ -130,13 +130,13 @@ public class BlockIndexer{
data.turretTree.remove(build);
}
//is no longer registered
build.wasDamaged = false;
//unregister damaged buildings
if(build.damaged() && damagedTiles[team.id] != null){
if(build.wasDamaged && damagedTiles[team.id] != null){
damagedTiles[team.id].remove(build);
}
//is no longer registered
build.wasDamaged = false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@ import mindustry.logic.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.defense.turrets.BaseTurret.*;
import mindustry.world.blocks.defense.turrets.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import mindustry.world.meta.*;
@@ -77,6 +78,7 @@ public class RtsAI{
}
public void update(){
if(timer.get(timeUpdate, 60f * 2f)){
assignSquads();
checkBuilding();
@@ -110,7 +112,8 @@ public class RtsAI{
for(var unit : data.units){
if(used.add(unit.id) && unit.isCommandable() && !unit.command().hasCommand() && !unit.command().isAttacking()){
squad.clear();
data.tree().intersect(unit.x - squadRadius/2f, unit.y - squadRadius/2f, squadRadius, squadRadius, squad);
float rad = squadRadius + unit.hitSize*1.5f;
data.tree().intersect(unit.x - rad/2f, unit.y - rad/2f, rad, rad, squad);
squad.truncate(data.team.rules().rtsMaxSquad);
@@ -244,7 +247,7 @@ public class RtsAI{
}
}
var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0);
var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0, units.first().isFlying());
if(build != null || anyDefend){
for(var unit : units){
@@ -267,7 +270,7 @@ public class RtsAI{
return anyDefend;
}
@Nullable Building findTarget(float x, float y, int total, float dps, float health, boolean checkWeight){
@Nullable Building findTarget(float x, float y, int total, float dps, float health, boolean checkWeight, boolean air){
if(total < data.team.rules().rtsMinSquad) return null;
//flag priority?
@@ -289,7 +292,7 @@ public class RtsAI{
targets.truncate(maxTargetsChecked);
for(var target : targets){
weights.put(target, estimateStats(x, y, target.x, target.y, dps, health));
weights.put(target, estimateStats(x, y, target.x, target.y, dps, health, air));
}
var result = targets.min(
@@ -311,12 +314,12 @@ public class RtsAI{
}
//TODO extremely slow especially with many squads.
float estimateStats(float fromX, float fromY, float x, float y, float selfDps, float selfHealth){
float estimateStats(float fromX, float fromY, float x, float y, float selfDps, float selfHealth, boolean air){
float[] health = {0f}, dps = {0f};
float extraRadius = 50f;
for(var turret : Vars.indexer.getEnemy(data.team, BlockFlag.turret)){
if(turret instanceof BaseTurretBuild t && Intersector.distanceSegmentPoint(fromX, fromY, x, y, t.x, t.y) <= t.range() + extraRadius){
if(turret instanceof BaseTurretBuild t && turret.block instanceof Turret tb && ((tb.targetAir && air) || (tb.targetGround && !air)) && Intersector.distanceSegmentPoint(fromX, fromY, x, y, t.x, t.y) <= t.range() + extraRadius){
health[0] += t.health;
dps[0] += t.estimateDps();
}

View File

@@ -12,30 +12,12 @@ import mindustry.async.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.world.blocks.environment.*;
public class UnitGroup{
public Seq<Unit> units = new Seq<>();
public int collisionLayer;
public volatile float[] positions, originalPositions;
public volatile boolean valid;
public long lastSpeedUpdate = -1;
public float minSpeed = 999999f;
public void updateMinSpeed(){
if(lastSpeedUpdate == Vars.state.updateId) return;
lastSpeedUpdate = Vars.state.updateId;
minSpeed = 999999f;
for(Unit unit : units){
//don't factor in the floor speed multiplier
Floor on = unit.isFlying() ? Blocks.air.asFloor() : unit.floorOn();
minSpeed = Math.min(unit.speed() / on.speedMultiplier, minSpeed);
}
if(Float.isInfinite(minSpeed) || Float.isNaN(minSpeed)) minSpeed = 999999f;
}
public void calculateFormation(Vec2 dest, int collisionLayer){
this.collisionLayer = collisionLayer;
@@ -58,8 +40,6 @@ public class UnitGroup{
unit.command().groupIndex = i;
}
updateMinSpeed();
//run on new thread to prevent stutter
Vars.mainExecutor.submit(() -> {
//unused space between circles that needs to be reached for compression to end
@@ -188,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

@@ -47,6 +47,8 @@ public class BuilderAI extends AIController{
if(target != null && shouldShoot()){
unit.lookAt(target);
}else if(!unit.type.flying){
unit.lookAt(unit.prefRotation());
}
unit.updateBuilding = true;
@@ -55,6 +57,8 @@ public class BuilderAI extends AIController{
following = assistFollowing;
}
boolean moving = false;
if(following != null){
retreatTimer = 0f;
//try to follow and mimic someone
@@ -83,6 +87,7 @@ public class BuilderAI extends AIController{
var core = unit.closestCore();
if(core != null && !unit.within(core, retreatDst)){
moveTo(core, retreatDst);
moving = true;
}
}
}
@@ -114,7 +119,8 @@ public class BuilderAI extends AIController{
if(valid){
//move toward the plan
moveTo(req.tile(), unit.type.buildRange - 20f);
moveTo(req.tile(), unit.type.buildRange - 20f, 20f);
moving = !unit.within(req.tile(), unit.type.buildRange - 10f);
}else{
//discard invalid plan
unit.plans.removeFirst();
@@ -124,6 +130,7 @@ public class BuilderAI extends AIController{
if(assistFollowing != null){
moveTo(assistFollowing, assistFollowing.type.hitSize + unit.type.hitSize/2f + 60f);
moving = !unit.within(assistFollowing, assistFollowing.type.hitSize + unit.type.hitSize/2f + 65f);
}
//follow someone and help them build
@@ -153,7 +160,7 @@ public class BuilderAI extends AIController{
float minDst = Float.MAX_VALUE;
Player closest = null;
for(var player : Groups.player){
if(player.unit().canBuild() && !player.dead() && player.team() == unit.team){
if(!player.dead() && player.isBuilder() && player.team() == unit.team){
float dst = player.dst2(unit);
if(dst < minDst){
closest = player;
@@ -186,6 +193,10 @@ public class BuilderAI extends AIController{
}
}
}
if(!unit.type.flying){
unit.updateBoosting(moving || unit.floorOn().isDuct || unit.floorOn().damageTaken > 0f);
}
}
protected boolean nearEnemy(int x, int y){

View File

@@ -4,7 +4,6 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.core.*;
import mindustry.entities.*;
@@ -35,7 +34,6 @@ public class CommandAI extends AIController{
protected boolean stopAtTarget, stopWhenInRange;
protected Vec2 lastTargetPos;
protected int pathId = -1;
protected boolean blockingUnit;
protected float timeSpentBlocked;
@@ -148,10 +146,6 @@ public class CommandAI extends AIController{
}
}
if(group != null){
group.updateMinSpeed();
}
if(!net.client() && command == UnitCommand.enterPayloadCommand && unit.buildOn() != null && (targetPos == null || (world.buildWorld(targetPos.x, targetPos.y) != null && world.buildWorld(targetPos.x, targetPos.y) == unit.buildOn()))){
var build = unit.buildOn();
tmpPayload.unit = unit;
@@ -209,6 +203,11 @@ 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;
if(targetPos != null){
boolean move = true, isFinalPoint = commandQueue.size == 0;
vecOut.set(targetPos);
@@ -225,6 +224,7 @@ public class CommandAI extends AIController{
}
if(unit.isGrounded() && stance != UnitStance.ram){
//TODO: blocking enable or disable?
if(timer.get(timerTarget3, avoidInterval)){
Vec2 dstPos = Tmp.v1.trns(unit.rotation, unit.hitSize/2f);
float max = unit.hitSize/2f;
@@ -252,8 +252,18 @@ public class CommandAI extends AIController{
timeSpentBlocked = 0f;
}
//if you've spent 3 seconds stuck, something is wrong, move regardless
move = Vars.controlPath.getPathPosition(unit, pathId, vecMovePos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
//if the unit is next to the target, stop asking the pathfinder how to get there, it's a waste of CPU
//TODO maybe stop moving too?
if(withinAttackRange){
move = true;
noFound[0] = false;
vecOut.set(vecMovePos);
}else{
move = controlPath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
}
//rare case where unit must be perfectly aligned (happens with 1-tile gaps)
alwaysArrive = vecOut.epsilonEquals(unit.tileX() * tilesize, unit.tileY() * tilesize);
//we've reached the final point if the returned coordinate is equal to the supplied input
isFinalPoint &= vecMovePos.epsilonEquals(vecOut, 4.1f);
@@ -270,18 +280,16 @@ public class CommandAI extends AIController{
vecOut.set(vecMovePos);
}
float engageRange = unit.type.range - 10f;
if(move){
if(unit.type.circleTarget && attackTarget != null){
target = attackTarget;
circleAttack(80f);
}else{
moveTo(vecOut,
attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram ? engageRange :
withinAttackRange ? engageRange :
unit.isGrounded() ? 0f :
attackTarget != null && stance != UnitStance.ram ? engageRange :
0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint);
attackTarget != null && stance != UnitStance.ram ? engageRange : 0f,
unit.isFlying() ? 40f : 100f, false, null, isFinalPoint || alwaysArrive);
}
}
@@ -363,11 +371,6 @@ public class CommandAI extends AIController{
}
}
@Override
public float prefSpeed(){
return group == null ? super.prefSpeed() : Math.min(group.minSpeed, unit.speed());
}
@Override
public boolean shouldFire(){
return stance != UnitStance.holdFire;
@@ -426,7 +429,6 @@ public class CommandAI extends AIController{
//this is an allocation, but it's relatively rarely called anyway, and outside mutations must be prevented
targetPos = lastTargetPos = pos.cpy();
attackTarget = null;
pathId = Vars.controlPath.nextTargetId();
this.stopWhenInRange = stopWhenInRange;
}
@@ -441,7 +443,6 @@ public class CommandAI extends AIController{
public void commandTarget(Teamc moveTo, boolean stopAtTarget){
attackTarget = moveTo;
this.stopAtTarget = stopAtTarget;
pathId = Vars.controlPath.nextTargetId();
}
/*

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

@@ -3,7 +3,6 @@ package mindustry.ai.types;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -41,8 +40,6 @@ public class LogicAI extends AIController{
public PosTeam posTarget = PosTeam.create();
private ObjectSet<Object> radars = new ObjectSet<>();
private float lastMoveX, lastMoveY;
private int lastPathId = 0;
// LogicAI state should not be reset after reading.
@Override
@@ -52,14 +49,6 @@ public class LogicAI extends AIController{
@Override
public void updateMovement(){
if(control == LUnitControl.pathfind){
if(!Mathf.equal(moveX, lastMoveX, 0.1f) || !Mathf.equal(moveY, lastMoveY, 0.1f)){
lastPathId ++;
lastMoveX = moveX;
lastMoveY = moveY;
}
}
if(targetTimer > 0f){
targetTimer -= Time.delta;
}else{
@@ -86,7 +75,7 @@ public class LogicAI extends AIController{
if(unit.isFlying()){
moveTo(Tmp.v1.set(moveX, moveY), 1f, 30f);
}else{
if(Vars.controlPath.getPathPosition(unit, lastPathId, Tmp.v2.set(moveX, moveY), Tmp.v1, null)){
if(controlPath.getPathPosition(unit, Tmp.v2.set(moveX, moveY), Tmp.v2, Tmp.v1, null)){
moveTo(Tmp.v1, 1f, Tmp.v2.epsilonEquals(Tmp.v1, 4.1f) ? 30f : 0f);
}
}

View File

@@ -164,8 +164,11 @@ public class SoundControl{
//this just fades out the last track to make way for ingame music
silence();
//play music at intervals
if(Time.timeSinceMillis(lastPlayed) > 1000 * musicInterval / 60f){
if(Core.settings.getBool("alwaysmusic")){
if(current == null){
playRandom();
}
}else if(Time.timeSinceMillis(lastPlayed) > 1000 * musicInterval / 60f){
//chance to play it per interval
if(Mathf.chance(musicChance)){
lastPlayed = Time.millis();
@@ -213,7 +216,9 @@ public class SoundControl{
/** Plays a random track.*/
public void playRandom(){
if(isDark()){
if(state.boss() != null){
playOnce(bossMusic.random(lastRandomPlayed));
}else if(isDark()){
playOnce(darkMusic.random(lastRandomPlayed));
}else{
playOnce(ambientMusic.random(lastRandomPlayed));

View File

@@ -1181,6 +1181,7 @@ public class Blocks{
rotate = true;
invertFlip = true;
group = BlockGroup.liquids;
itemCapacity = 0;
liquidCapacity = 50f;
@@ -1231,6 +1232,7 @@ public class Blocks{
}});
researchCostMultiplier = 1.1f;
itemCapacity = 0;
liquidCapacity = 40f;
consumePower(2f);
ambientSound = Sounds.extractLoop;
@@ -2554,8 +2556,8 @@ public class Blocks{
fluxReactor = new VariableReactor("flux-reactor"){{
requirements(Category.power, with(Items.graphite, 300, Items.carbide, 200, Items.oxide, 100, Items.silicon, 600, Items.surgeAlloy, 300));
powerProduction = 120f;
maxHeat = 140f;
powerProduction = 240f;
maxHeat = 150f;
consumeLiquid(Liquids.cyanogen, 9f / 60f);
liquidCapacity = 30f;
@@ -2782,6 +2784,7 @@ public class Blocks{
ambientSoundVolume = 0.06f;
hasLiquids = true;
boostScale = 1f / 9f;
itemCapacity = 0;
outputLiquid = new LiquidStack(Liquids.water, 30f / 60f);
consumePower(0.5f);
liquidCapacity = 60f;
@@ -4046,7 +4049,7 @@ public class Blocks{
researchCostMultiplier = 0.05f;
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
limitRange();
limitRange(12f);
}};
diffuse = new ItemTurret("diffuse"){{
@@ -4105,7 +4108,7 @@ public class Blocks{
rotateSpeed = 3f;
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
limitRange();
limitRange(25f);
}};
sublimate = new ContinuousLiquidTurret("sublimate"){{
@@ -4149,6 +4152,7 @@ public class Blocks{
liquidConsumed = 10f / 60f;
targetInterval = 5f;
newTargetInterval = 30f;
targetUnderBlocks = false;
float r = range = 130f;
@@ -4239,6 +4243,8 @@ public class Blocks{
shootY = 7f;
rotateSpeed = 1.4f;
minWarmup = 0.85f;
newTargetInterval = 40f;
shootWarmupSpeed = 0.07f;
coolant = consume(new ConsumeLiquid(Liquids.water, 30f / 60f));
@@ -4355,7 +4361,7 @@ public class Blocks{
coolant = consume(new ConsumeLiquid(Liquids.water, 20f / 60f));
coolantMultiplier = 2.5f;
limitRange(-5f);
limitRange(5f);
}};
afflict = new PowerTurret("afflict"){{
@@ -4376,6 +4382,7 @@ public class Blocks{
trailInterval = 3f;
trailParam = 4f;
pierceCap = 2;
buildingDamageMultiplier = 0.5f;
fragOnHit = false;
speed = 5f;
damage = 180f;
@@ -4459,6 +4466,8 @@ public class Blocks{
heatRequirement = 10f;
maxHeatEfficiency = 2f;
newTargetInterval = 40f;
inaccuracy = 1f;
shake = 2f;
shootY = 4;
@@ -4681,6 +4690,8 @@ public class Blocks{
shootSound = Sounds.missileLaunch;
minWarmup = 0.94f;
newTargetInterval = 40f;
unitSort = UnitSorts.strongest;
shootWarmupSpeed = 0.03f;
targetAir = false;
targetUnderBlocks = false;
@@ -5502,23 +5513,6 @@ public class Blocks{
);
}};
mechRefabricator = new Reconstructor("mech-refabricator"){{
requirements(Category.units, with(Items.beryllium, 250, Items.tungsten, 120, Items.silicon, 150));
regionSuffix = "-dark";
size = 3;
consumePower(2.5f);
consumeLiquid(Liquids.hydrogen, 3f / 60f);
consumeItems(with(Items.silicon, 50, Items.tungsten, 40));
constructTime = 60f * 45f;
researchCostMultiplier = 0.75f;
upgrades.addAll(
new UnitType[]{UnitTypes.merui, UnitTypes.cleroi}
);
}};
shipRefabricator = new Reconstructor("ship-refabricator"){{
requirements(Category.units, with(Items.beryllium, 200, Items.tungsten, 100, Items.silicon, 150, Items.oxide, 40));
regionSuffix = "-dark";
@@ -5537,6 +5531,23 @@ public class Blocks{
researchCost = with(Items.beryllium, 500, Items.tungsten, 200, Items.silicon, 300, Items.oxide, 80);
}};
mechRefabricator = new Reconstructor("mech-refabricator"){{
requirements(Category.units, with(Items.beryllium, 250, Items.tungsten, 120, Items.silicon, 150));
regionSuffix = "-dark";
size = 3;
consumePower(2.5f);
consumeLiquid(Liquids.hydrogen, 3f / 60f);
consumeItems(with(Items.silicon, 50, Items.tungsten, 40));
constructTime = 60f * 45f;
researchCostMultiplier = 0.75f;
upgrades.addAll(
new UnitType[]{UnitTypes.merui, UnitTypes.cleroi}
);
}};
//yes very silly name
primeRefabricator = new Reconstructor("prime-refabricator"){{
requirements(Category.units, with(Items.thorium, 250, Items.oxide, 200, Items.tungsten, 200, Items.silicon, 400));
@@ -5791,6 +5802,8 @@ public class Blocks{
heatOutput = 1000f;
warmupRate = 1000f;
regionRotated1 = 1;
itemCapacity = 0;
alwaysUnlocked = true;
ambientSound = Sounds.none;
}};

View File

@@ -37,7 +37,9 @@ public class Bullets{
damageLightningGround = damageLightning.copy();
damageLightningGround.collidesAir = false;
fireball = new FireBulletType(1f, 4);
fireball = new FireBulletType(1f, 4){{
hittable = false;
}};
spaceLiquid = new SpaceLiquidBulletType(){{
knockback = 0.7f;

View File

@@ -2434,12 +2434,9 @@ public class Fx{
shieldBreak = new Effect(40, e -> {
color(e.color);
stroke(3f * e.fout());
if(e.data instanceof Unit u){
var ab = (ForceFieldAbility)Structs.find(u.abilities, a -> a instanceof ForceFieldAbility);
if(ab != null){
Lines.poly(e.x, e.y, ab.sides, e.rotation + e.fin(), ab.rotation);
return;
}
if(e.data instanceof ForceFieldAbility ab){
Lines.poly(e.x, e.y, ab.sides, e.rotation + e.fin(), ab.rotation);
return;
}
Lines.poly(e.x, e.y, 6, e.rotation + e.fin());
@@ -2584,7 +2581,7 @@ public class Fx{
if(!(e.data instanceof Vec2[] vec)) return;
Draw.color(e.color);
Lines.stroke(1f);
Lines.stroke(2f);
if(vec.length == 2){
Lines.line(vec[0].x, vec[0].y, vec[1].x, vec[1].y);
@@ -2596,5 +2593,15 @@ public class Fx{
}
Draw.reset();
}),
debugRect = new Effect(90f, 1000000000000f, e -> {
if(!(e.data instanceof Rect rect)) return;
Draw.color(e.color);
Lines.stroke(2f);
Lines.rect(rect);
Draw.reset();
});
}

View File

@@ -322,7 +322,7 @@ public class UnitTypes{
speed = 0.55f;
hitSize = 8f;
health = 120f;
buildSpeed = 0.8f;
buildSpeed = 0.35f;
armor = 1f;
abilities.add(new RepairFieldAbility(10f, 60f * 4, 60f));
@@ -354,7 +354,7 @@ public class UnitTypes{
speed = 0.7f;
hitSize = 11f;
health = 320f;
buildSpeed = 0.9f;
buildSpeed = 0.5f;
armor = 4f;
riseSpeed = 0.07f;
@@ -408,7 +408,7 @@ public class UnitTypes{
mineTier = 3;
boostMultiplier = 2f;
health = 640f;
buildSpeed = 1.7f;
buildSpeed = 1.1f;
canBoost = true;
armor = 9f;
mechLandShake = 2f;
@@ -1318,6 +1318,7 @@ public class UnitTypes{
healPercent = 5.5f;
collidesTeam = true;
reflectable = false;
backColor = Pal.heal;
trailColor = Pal.heal;
}};
@@ -3894,7 +3895,7 @@ public class UnitTypes{
x = 43f * i / 4f;
particles = parts;
//visual only, the middle one does the actual suppressing
display = active = false;
active = false;
}});
}

View File

@@ -17,7 +17,7 @@ public class Weathers{
suspendParticles;
public static void load(){
snow = new ParticleWeather("snow"){{
snow = new ParticleWeather("snowing"){{
particleRegion = "particle";
sizeMax = 13f;
sizeMin = 2.6f;

View File

@@ -314,6 +314,14 @@ public class ContentLoader{
return getByName(ContentType.planet, name);
}
public Seq<Weather> weathers(){
return getBy(ContentType.weather);
}
public Weather weather(String name){
return getByName(ContentType.weather, name);
}
public Seq<UnitStance> unitStances(){
return getBy(ContentType.unitStance);
}

View File

@@ -30,6 +30,7 @@ import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.io.*;
@@ -191,43 +192,29 @@ public class Control implements ApplicationListener, Loadable{
Events.run(Trigger.newGame, () -> {
var core = player.bestCore();
if(core == null) return;
camera.position.set(core);
player.set(core);
float coreDelay = 0f;
if(!settings.getBool("skipcoreanimation") && !state.rules.pvp){
coreDelay = coreLandDuration;
coreDelay = core.landDuration();
//delay player respawn so animation can play.
player.deathTimer = Player.deathDelay - coreLandDuration;
player.deathTimer = Player.deathDelay - core.landDuration();
//TODO this sounds pretty bad due to conflict
if(settings.getInt("musicvol") > 0){
Musics.land.stop();
Musics.land.play();
Musics.land.setVolume(settings.getInt("musicvol") / 100f);
//TODO what to do if another core with different music is already playing?
Music music = core.landMusic();
music.stop();
music.play();
music.setVolume(settings.getInt("musicvol") / 100f);
}
app.post(() -> ui.hudfrag.showLand());
renderer.showLanding();
Time.run(coreLandDuration, () -> {
Fx.launch.at(core);
Effect.shake(5f, 5f, core);
core.thrusterTime = 1f;
if(state.isCampaign() && Vars.showSectorLandInfo && (state.rules.sector.preset == null || state.rules.sector.preset.showSectorLandInfo)){
ui.announce("[accent]" + state.rules.sector.name() + "\n" +
(state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " +
state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5);
}
});
renderer.showLanding(core);
}
if(state.isCampaign()){
//don't run when hosting, that doesn't really work.
if(state.rules.sector.planet.prebuildBase){
toBePlaced.clear();

View File

@@ -86,11 +86,12 @@ public class GameState{
}
public boolean isPaused(){
return is(State.paused);
return state == State.paused;
}
/** @return whether there is an unpaused game in progress. */
public boolean isPlaying(){
return (state == State.playing) || (state == State.paused && !isPaused());
return state == State.playing;
}
/** @return whether the current state is *not* the menu. */

View File

@@ -47,17 +47,7 @@ public class Logic implements ApplicationListener{
Events.on(BlockBuildEndEvent.class, event -> {
if(!event.breaking){
TeamData data = event.team.data();
Iterator<BlockPlan> it = data.plans.iterator();
var bounds = event.tile.block().bounds(event.tile.x, event.tile.y, Tmp.r1);
while(it.hasNext()){
BlockPlan b = it.next();
Block block = content.block(b.block);
if(bounds.overlaps(block.bounds(b.x, b.y, Tmp.r2))){
b.removed = true;
it.remove();
}
}
checkOverlappingPlans(event.team, event.tile);
if(event.team == state.rules.defaultTeam){
state.stats.placedBlockCount.increment(event.tile.block());
@@ -65,6 +55,12 @@ public class Logic implements ApplicationListener{
}
});
Events.on(PayloadDropEvent.class, e -> {
if(e.build != null){
checkOverlappingPlans(e.build.team, e.build.tile);
}
});
//when loading a 'damaged' sector, propagate the damage
Events.on(SaveLoadEvent.class, e -> {
if(state.isCampaign()){
@@ -211,6 +207,20 @@ public class Logic implements ApplicationListener{
});
}
private void checkOverlappingPlans(Team team, Tile tile){
TeamData data = team.data();
Iterator<BlockPlan> it = data.plans.iterator();
var bounds = tile.block().bounds(tile.x, tile.y, Tmp.r1);
while(it.hasNext()){
BlockPlan b = it.next();
Block block = content.block(b.block);
if(bounds.overlaps(block.bounds(b.x, b.y, Tmp.r2))){
b.removed = true;
it.remove();
}
}
}
/** Adds starting items, resets wave time, and sets state to playing. */
public void play(){
state.set(State.playing);
@@ -251,6 +261,7 @@ public class Logic implements ApplicationListener{
Groups.clear();
Time.clear();
Events.fire(new ResetEvent());
world.tiles = new Tiles(0, 0);
//save settings on reset
Core.settings.manualSave();
@@ -357,7 +368,8 @@ public class Logic implements ApplicationListener{
//map is over, no more world processor objective stuff
state.rules.disableWorldProcessors = true;
state.rules.objectives.clear();
Call.clearObjectives();
//save, just in case
if(!headless && !net.client()){
@@ -460,9 +472,6 @@ public class Logic implements ApplicationListener{
if(!state.isEditor()){
state.rules.objectives.update();
if(state.rules.objectives.checkChanged() && net.server()){
Call.setObjectives(state.rules.objectives);
}
}
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){

View File

@@ -340,15 +340,23 @@ public class NetClient implements ApplicationListener{
state.rules = rules;
}
//NOTE: avoid using this, runs into packet/buffer size limitations
@Remote(variants = Variant.both)
public static void setObjectives(MapObjectives executor){
state.rules.objectives = executor;
}
@Remote(called = Loc.server)
public static void objectiveCompleted(String[] flagsRemoved, String[] flagsAdded){
state.rules.objectiveFlags.removeAll(flagsRemoved);
state.rules.objectiveFlags.addAll(flagsAdded);
@Remote(variants = Variant.both, called = Loc.server)
public static void clearObjectives(){
state.rules.objectives.clear();
}
@Remote(variants = Variant.both, called = Loc.server)
public static void completeObjective(int index){
var obj = state.rules.objectives.get(index);
if(obj != null){
obj.done();
}
}
@Remote(variants = Variant.both)
@@ -470,7 +478,7 @@ public class NetClient implements ApplicationListener{
Log.warn("Block ID mismatch at @: @ != @. Skipping block snapshot.", tile, tile.build.block.id, block);
break;
}
tile.build.readAll(Reads.get(input), tile.build.version());
tile.build.readSync(Reads.get(input), tile.build.version());
}
}catch(Exception e){
Log.err(e);
@@ -614,21 +622,22 @@ public class NetClient implements ApplicationListener{
void sync(){
if(timer.get(0, playerSyncTime)){
Unit unit = player.dead() ? Nulls.unit : player.unit();
int uid = player.dead() ? -1 : unit.id;
boolean dead = player.dead();
Unit unit = dead ? null : player.unit();
int uid = dead || unit == null ? -1 : unit.id;
Call.clientSnapshot(
lastSent++,
uid,
player.dead(),
player.dead() ? player.x : unit.x, player.dead() ? player.y : unit.y,
player.unit().aimX(), player.unit().aimY(),
unit.rotation,
dead,
dead ? player.x : unit.x, dead ? player.y : unit.y,
dead ? 0f : unit.aimX(), dead ? 0f : unit.aimY(),
unit == null ? 0f : unit.rotation,
unit instanceof Mechc m ? m.baseRotation() : 0,
unit.vel.x, unit.vel.y,
player.unit().mineTile,
unit == null ? 0f : unit.vel.x, unit == null ? 0f : unit.vel.y,
dead ? null : unit.mineTile,
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
player.isBuilder() ? player.unit().plans : null,
player.isBuilder() && unit != null ? unit.plans : null,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width, Core.camera.height
);

View File

@@ -59,7 +59,7 @@ public class NetServer implements ApplicationListener{
count++;
}
}
return count;
return (float)count + Mathf.random(-0.1f, 0.1f); //if several have the same playercount pick random
});
return re == null ? null : re.team;
}
@@ -98,7 +98,7 @@ public class NetServer implements ApplicationListener{
private boolean closing = false, pvpAutoPaused = true;
private Interval timer = new Interval(10);
private IntSet buildHealthChanged = new IntSet();
/** Current kick session. */
public @Nullable VoteSession currentlyKicking = null;
/** Duration of a kick in seconds. */
@@ -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(){
@@ -203,7 +205,7 @@ public class NetServer implements ApplicationListener{
info.id = packet.uuid;
admins.save();
Call.infoMessage(con, "You are not whitelisted here.");
info("&lcDo &lywhitelist-add @&lc to whitelist the player &lb'@'", packet.uuid, packet.name);
info("&lcDo &lywhitelist add @&lc to whitelist the player &lb'@'", packet.uuid, packet.name);
con.kick(KickReason.whitelist);
return;
}
@@ -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){
@@ -542,10 +548,10 @@ public class NetServer implements ApplicationListener{
@Remote(targets = Loc.client, variants = Variant.one)
public static void requestDebugStatus(Player player){
int flags =
(player.con.hasDisconnected ? 1 : 0) |
(player.con.hasConnected ? 2 : 0) |
(player.isAdded() ? 4 : 0) |
(player.con.hasBegunConnecting ? 8 : 0);
(player.con.hasDisconnected ? 1 : 0) |
(player.con.hasConnected ? 2 : 0) |
(player.isAdded() ? 4 : 0) |
(player.con.hasBegunConnecting ? 8 : 0);
Call.debugStatusClient(player.con, flags, player.con.lastReceivedClientSnapshot, player.con.snapshotsSent);
Call.debugStatusClientUnreliable(player.con, flags, player.con.lastReceivedClientSnapshot, player.con.snapshotsSent);
@@ -583,24 +589,39 @@ 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);
}
@Remote(targets = Loc.client, unreliable = true)
public static void clientSnapshot(
Player player,
int snapshotID,
int unitID,
boolean dead,
float x, float y,
float pointerX, float pointerY,
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean chatting, boolean building,
@Nullable Queue<BuildPlan> plans,
float viewX, float viewY, float viewWidth, float viewHeight
Player player,
int snapshotID,
int unitID,
boolean dead,
float x, float y,
float pointerX, float pointerY,
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean chatting, boolean building,
@Nullable Queue<BuildPlan> plans,
float viewX, float viewY, float viewWidth, float viewHeight
){
NetConnection con = player.con;
if(con == null || snapshotID < con.lastReceivedClientSnapshot) return;
@@ -639,12 +660,11 @@ public class NetServer implements ApplicationListener{
player.shooting = shooting;
player.boosting = boosting;
player.unit().controlWeapons(shooting, shooting);
player.unit().aim(pointerX, pointerY);
@Nullable var unit = player.unit();
if(player.isBuilder()){
player.unit().clearBuilding();
player.unit().updateBuilding(building);
unit.clearBuilding();
unit.updateBuilding(building);
if(plans != null){
for(BuildPlan req : plans){
@@ -673,12 +693,12 @@ public class NetServer implements ApplicationListener{
}
}
player.unit().mineTile = mining;
con.rejectedRequests.clear();
if(!player.dead()){
Unit unit = player.unit();
unit.controlWeapons(shooting, shooting);
unit.aim(pointerX, pointerY);
unit.mineTile = mining;
long elapsed = Math.min(Time.timeSinceMillis(con.lastReceivedClientTime), 1500);
float maxSpeed = unit.speed();
@@ -893,7 +913,7 @@ public class NetServer implements ApplicationListener{
dataStream.writeInt(entity.pos());
dataStream.writeShort(entity.block.id);
entity.writeAll(Writes.get(dataStream));
entity.writeSync(Writes.get(dataStream));
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
@@ -1104,7 +1124,7 @@ public class NetServer implements ApplicationListener{
voted.put(admins.getInfo(player.uuid()).lastIP, d);
Call.sendMessage(Strings.format("[lightgray]@[lightgray] has voted on kicking[orange] @[lightgray].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
player.name, target.name, votes, votesRequired()));
player.name, target.name, votes, votesRequired()));
checkPass();
}

View File

@@ -1,6 +1,7 @@
package mindustry.core;
import arc.*;
import arc.filedialogs.*;
import arc.files.*;
import arc.func.*;
import arc.math.*;
@@ -141,7 +142,9 @@ public interface Platform{
* @param title The title of the native dialog
*/
default void showFileChooser(boolean open, String title, String extension, Cons<Fi> cons){
if(OS.isLinux && !OS.isAndroid){
if(OS.isWindows || OS.isMac){
showNativeFileChooser(open, title, cons, extension);
}else if(OS.isLinux && !OS.isAndroid){
showZenity(open, title, new String[]{extension}, cons, () -> defaultFileDialog(open, title, extension, cons));
}else{
defaultFileDialog(open, title, extension, cons);
@@ -223,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{
@@ -234,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

@@ -2,6 +2,7 @@ package mindustry.core;
import arc.*;
import arc.assets.loaders.TextureLoader.*;
import arc.audio.*;
import arc.files.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
@@ -13,7 +14,6 @@ import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
@@ -30,11 +30,6 @@ public class Renderer implements ApplicationListener{
/** These are global variables, for headless access. Cached. */
public static float laserOpacity = 0.5f, bridgeOpacity = 0.75f;
private static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f;
private static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
private static final float cloudAlpha = 0.81f;
private static final Interp landInterp = Interp.pow3;
public final BlockRenderer blocks = new BlockRenderer();
public final FogRenderer fog = new FogRenderer();
public final MinimapRenderer minimap = new MinimapRenderer();
@@ -55,18 +50,15 @@ public class Renderer implements ApplicationListener{
public TextureRegion[] bubbles = new TextureRegion[16], splashes = new TextureRegion[12];
public TextureRegion[][] fluidFrames;
//currently landing core, null if there are no cores or it has finished landing.
private @Nullable CoreBuild landCore;
private @Nullable CoreBlock launchCoreType;
private Color clearColor = new Color(0f, 0f, 0f, 1f);
private float
//seed for cloud visuals, 0-1
cloudSeed = 0f,
//target camera scale that is lerp-ed to
targetscale = Scl.scl(4),
//current actual camera scale
camerascale = targetscale,
//minimum camera zoom value for landing/launching; constant TODO make larger?
minZoomScl = Scl.scl(0.02f),
//starts at coreLandDuration, ends at 0. if positive, core is landing.
landTime,
//timer for core landing particles
@@ -113,10 +105,6 @@ public class Renderer implements ApplicationListener{
setupBloom();
}
Events.run(Trigger.newGame, () -> {
landCore = player.bestCore();
});
EnvRenderers.init();
for(int i = 0; i < bubbles.length; i++) bubbles[i] = atlas.find("bubble-" + i);
for(int i = 0; i < splashes.length; i++) splashes[i] = atlas.find("splash-" + i);
@@ -181,32 +169,26 @@ public class Renderer implements ApplicationListener{
enableEffects = settings.getBool("effects");
drawDisplays = !settings.getBool("hidedisplays");
drawLight = settings.getBool("drawlight", true);
pixelate = Core.settings.getBool("pixelate");
pixelate = settings.getBool("pixelate");
//don't bother drawing landing animation if core is null
if(landCore == null) landTime = 0f;
if(landTime > 0){
if(!state.isPaused()){
CoreBuild build = landCore == null ? player.bestCore() : landCore;
if(build != null){
build.updateLandParticles();
}
}
if(!state.isPaused()) landCore.updateLaunching();
if(!state.isPaused()){
landTime -= Time.delta;
}
float fin = landTime / coreLandDuration;
if(!launching) fin = 1f - fin;
camerascale = landInterp.apply(minZoomScl, Scl.scl(4f), fin);
weatherAlpha = 0f;
camerascale = landCore.zoomLaunching();
//snap camera to cutscene core regardless of player input
if(landCore != null){
camera.position.set(landCore);
}
if(!state.isPaused()) landTime -= Time.delta;
}else{
weatherAlpha = Mathf.lerpDelta(weatherAlpha, 1f, 0.08f);
}
if(landCore != null && landTime <= 0f){
landCore.endLaunch();
landCore = null;
}
camera.width = graphics.getWidth() / camerascale;
camera.height = graphics.getHeight() / camerascale;
@@ -304,7 +286,7 @@ public class Renderer implements ApplicationListener{
graphics.clear(clearColor);
Draw.reset();
if(Core.settings.getBool("animatedwater") || animateShields){
if(settings.getBool("animatedwater") || animateShields){
effectBuffer.resize(graphics.getWidth(), graphics.getHeight());
}
@@ -393,7 +375,10 @@ public class Renderer implements ApplicationListener{
Draw.draw(Layer.overlayUI, overlays::drawTop);
if(state.rules.fog) Draw.draw(Layer.fogOfWar, fog::drawFog);
Draw.draw(Layer.space, this::drawLanding);
Draw.draw(Layer.space, () -> {
if(landCore == null || landTime <= 0f) return;
landCore.drawLanding(launching && launchCoreType != null ? launchCoreType : (CoreBlock)landCore.block);
});
Events.fire(Trigger.drawOver);
blocks.drawBlocks();
@@ -481,61 +466,6 @@ public class Renderer implements ApplicationListener{
if(state.rules.customBackgroundCallback != null && customBackgrounds.containsKey(state.rules.customBackgroundCallback)){
customBackgrounds.get(state.rules.customBackgroundCallback).run();
}
}
void drawLanding(){
CoreBuild build = landCore == null ? player.bestCore() : landCore;
var clouds = assets.get("sprites/clouds.png", Texture.class);
if(landTime > 0 && build != null){
float fout = landTime / coreLandDuration;
if(launching) fout = 1f - fout;
float fin = 1f - fout;
float scl = Scl.scl(4f) / camerascale;
float pfin = Interp.pow3Out.apply(fin), pf = Interp.pow2In.apply(fout);
//draw particles
Draw.color(Pal.lightTrail);
Angles.randLenVectors(1, pfin, 100, 800f * scl * pfin, (ax, ay, ffin, ffout) -> {
Lines.stroke(scl * ffin * pf * 3f);
Lines.lineAngle(build.x + ax, build.y + ay, Mathf.angle(ax, ay), (ffin * 20 + 1f) * scl);
});
Draw.color();
CoreBlock block = launching && launchCoreType != null ? launchCoreType : (CoreBlock)build.block;
block.drawLanding(build, build.x, build.y);
Draw.color();
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
//accent tint indicating that the core was just constructed
if(launching){
float f = Mathf.clamp(1f - fout * 12f);
if(f > 0.001f){
Draw.mixcol(Pal.accent, f);
}
}
//draw clouds
if(state.rules.cloudColor.a > 0.0001f){
float scaling = cloudScaling;
float sscl = Math.max(1f + Mathf.clamp(fin + cfinOffset)* cfinScl, 0f) * camerascale;
Tmp.tr1.set(clouds);
Tmp.tr1.set(
(camera.position.x - camera.width/2f * sscl) / scaling,
(camera.position.y - camera.height/2f * sscl) / scaling,
(camera.position.x + camera.width/2f * sscl) / scaling,
(camera.position.y + camera.height/2f * sscl) / scaling);
Tmp.tr1.scroll(10f * cloudSeed, 10f * cloudSeed);
Draw.alpha(Mathf.sample(cloudAlphas, fin + calphaFinOffset) * cloudAlpha);
Draw.mixcol(state.rules.cloudColor, state.rules.cloudColor.a);
Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height);
Draw.reset();
}
}
}
public void scaleCamera(float amount){
@@ -580,6 +510,13 @@ public class Renderer implements ApplicationListener{
return landTime;
}
public float getLandTimeIn(){
if(landCore == null) return 0f;
float fin = landTime / landCore.landDuration();
if(!launching) fin = 1f - fin;
return fin;
}
public float getLandPTimer(){
return landPTimer;
}
@@ -588,25 +525,42 @@ public class Renderer implements ApplicationListener{
this.landPTimer = landPTimer;
}
@Deprecated
public void showLanding(){
launching = false;
camerascale = minZoomScl;
landTime = coreLandDuration;
cloudSeed = Mathf.random(1f);
var core = player.bestCore();
if(core != null) showLanding(core);
}
public void showLanding(CoreBuild landCore){
this.landCore = landCore;
launching = false;
landTime = landCore.landDuration();
landCore.beginLaunch(null);
camerascale = landCore.zoomLaunching();
}
@Deprecated
public void showLaunch(CoreBlock coreType){
Vars.ui.hudfrag.showLaunch();
Vars.control.input.config.hideConfig();
Vars.control.input.inv.hide();
launchCoreType = coreType;
var core = player.team().core();
if(core != null) showLaunch(core, coreType);
}
public void showLaunch(CoreBuild landCore, CoreBlock coreType){
control.input.config.hideConfig();
control.input.inv.hide();
this.landCore = landCore;
launching = true;
landCore = player.team().core();
cloudSeed = Mathf.random(1f);
landTime = coreLandDuration;
if(landCore != null){
Fx.coreLaunchConstruct.at(landCore.x, landCore.y, coreType.size);
}
landTime = landCore.landDuration();
launchCoreType = coreType;
Music music = landCore.launchMusic();
music.stop();
music.play();
music.setVolume(settings.getInt("musicvol") / 100f);
landCore.beginLaunch(coreType);
}
public void takeMapScreenshot(){
@@ -648,7 +602,7 @@ public class Renderer implements ApplicationListener{
Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png");
PixmapIO.writePng(file, fullPixmap);
fullPixmap.dispose();
app.post(() -> ui.showInfoFade(Core.bundle.format("screenshot", file.toString())));
app.post(() -> ui.showInfoFade(bundle.format("screenshot", file.toString())));
});
}

View File

@@ -43,6 +43,8 @@ public abstract class UnlockableContent extends MappableContent{
public TextureRegion uiIcon;
/** Icon of the full content. Unscaled.*/
public TextureRegion fullIcon;
/** Override for the full icon. Useful for mod content with duplicate icons. Overrides any other full icon.*/
public String fullOverride = "";
/** The tech tree node for this content, if applicable. Null if not part of a tech tree. */
public @Nullable TechNode techNode;
/** Tech nodes for all trees that this content is part of. */
@@ -62,11 +64,12 @@ public abstract class UnlockableContent extends MappableContent{
@Override
public void loadIcon(){
fullIcon =
Core.atlas.find(fullOverride,
Core.atlas.find(getContentType().name() + "-" + name + "-full",
Core.atlas.find(name + "-full",
Core.atlas.find(name,
Core.atlas.find(getContentType().name() + "-" + name,
Core.atlas.find(name + "1")))));
Core.atlas.find(name + "1"))))));
uiIcon = Core.atlas.find(getContentType().name() + "-" + name + "-ui", fullIcon);
}
@@ -115,6 +118,7 @@ public abstract class UnlockableContent extends MappableContent{
var result = Pixmaps.outline(base, outlineColor, outlineRadius);
Drawf.checkBleed(result);
packer.add(page, regName, result);
result.dispose();
}
}
}
@@ -126,6 +130,7 @@ public abstract class UnlockableContent extends MappableContent{
var result = Pixmaps.outline(base, outlineColor, outlineRadius);
Drawf.checkBleed(result);
packer.add(PageType.main, name, result);
result.dispose();
}
}
@@ -142,6 +147,11 @@ public abstract class UnlockableContent extends MappableContent{
return Fonts.getUnicodeStr(name);
}
public int emojiChar(){
return Fonts.getUnicode(name);
}
public boolean hasEmoji(){
return Fonts.hasUnicodeStr(name);
}

View File

@@ -74,7 +74,7 @@ public class MapEditor{
for(int i = 0; i < tiles.width * tiles.height; i++){
Tile tile = tiles.geti(i);
var build = tile.build;
if(build != null){
if(build != null && tile.isCenter()){
builds.add(build);
}
tiles.seti(i, new EditorTile(tile.x, tile.y, tile.floorID(), tile.overlayID(), build == null ? tile.blockID() : 0));

View File

@@ -24,7 +24,6 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
@@ -212,11 +211,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
margin(0);
update(() -> {
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
return;
}
if(Core.scene != null && Core.scene.getKeyboardFocus() == this){
if(hasKeyboard()){
doInput();
}
});

View File

@@ -14,16 +14,15 @@ import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class MapInfoDialog extends BaseDialog{
private final WaveInfoDialog waveInfo;
private final MapGenerateDialog generate;
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
private final MapObjectivesDialog objectives = new MapObjectivesDialog();
private final MapLocalesDialog locales = new MapLocalesDialog();
private WaveInfoDialog waveInfo = new WaveInfoDialog();
private MapGenerateDialog generate = new MapGenerateDialog(false);
private CustomRulesDialog ruleInfo = new CustomRulesDialog();
private MapObjectivesDialog objectives = new MapObjectivesDialog();
private MapLocalesDialog locales = new MapLocalesDialog();
private MapProcessorsDialog processors = new MapProcessorsDialog();
public MapInfoDialog(){
super("@editor.mapinfo");
this.waveInfo = new WaveInfoDialog();
this.generate = new MapGenerateDialog(false);
addCloseButton();
@@ -108,7 +107,12 @@ public class MapInfoDialog extends BaseDialog{
ui.showException(e);
}
hide();
}).marginLeft(10f).width(0f).colspan(2).center().growX();
}).marginLeft(10f);
r.button("@editor.worldprocessors", Icon.logic, style, () -> {
hide();
processors.show();
}).marginLeft(10f);
}).colspan(2).center();
name.change();

View File

@@ -39,7 +39,7 @@ public class MapLoadDialog extends BaseDialog{
ButtonGroup<Button> group = new ButtonGroup<>();
int i = 0;
int cols = Math.max((int)(Core.graphics.getWidth() / Scl.scl(250f)), 1);
int cols = Math.max((int)(Core.graphics.getWidth() / Scl.scl(300f)), 1);
Table table = new Table();
table.defaults().size(250f, 90f).pad(4f);
@@ -53,7 +53,7 @@ public class MapLoadDialog extends BaseDialog{
table.button(b -> {
b.add(new BorderImage(map.safeTexture(), 2f).setScaling(Scaling.fit)).padLeft(5f).size(16 * 4f);
b.add(map.name()).wrap().grow().labelAlign(Align.center).padLeft(5f);
}, Styles.squareTogglet, () -> selected = map).group(group).checked(b -> selected == map);
}, Styles.squareTogglet, () -> selected = map).group(group).margin(8f).checked(b -> selected == map);
if(++i % cols == 0) table.row();
}

View File

@@ -5,7 +5,6 @@ import arc.func.*;
import arc.graphics.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.Button.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.layout.*;
import arc.scene.utils.*;

View File

@@ -0,0 +1,155 @@
package mindustry.editor;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.logic.LogicBlock.*;
import static mindustry.Vars.*;
public class MapProcessorsDialog extends BaseDialog{
private IconSelectDialog iconSelect = new IconSelectDialog();
private TextField search;
private Seq<Building> processors = new Seq<>();
private Table list;
public MapProcessorsDialog(){
super("@editor.worldprocessors");
shown(this::setup);
addCloseButton();
buttons.button("@add", Icon.add, () -> {
boolean foundAny = false;
outer:
for(int y = 0; y < Vars.world.height(); y++){
for(int x = 0; x < Vars.world.width(); x++){
Tile tile = Vars.world.rawTile(x, y);
if(!tile.synthetic()){
foundAny = true;
tile.setNet(Blocks.worldProcessor, Team.sharded, 0);
if(ui.editor.isShown()){
Vars.editor.renderer.updatePoint(x, y);
}
break outer;
}
}
}
if(!foundAny){
ui.showErrorMessage("@editor.worldprocessors.nospace");
}else{
setup();
}
}).size(210f, 64f);
cont.top();
getCell(cont).grow();
cont.table(s -> {
s.image(Icon.zoom).padRight(8);
search = s.field(null, text -> rebuild()).growX().get();
search.setMessageText("@players.search");
}).width(440f).fillX().padBottom(4).row();
cont.pane(t -> {
list = t;
});
}
private void rebuild(){
list.clearChildren();
if(processors.isEmpty()){
list.add("@editor.worldprocessors.none");
}else{
Table t = list;
var text = search.getText().toLowerCase();
t.defaults().pad(4f);
float h = 50f;
for(var build : processors){
if(build instanceof LogicBuild log && (text.isEmpty() || (log.tag != null && log.tag.toLowerCase().contains(text)))){
t.button(log.iconTag == 0 ? Styles.none : new TextureRegionDrawable(Fonts.getLargeIcon(Fonts.unicodeToName(log.iconTag))), Styles.graySquarei, iconMed, () -> {
iconSelect.show(ic -> {
log.iconTag = (char)ic;
rebuild();
});
}).size(h);
t.button((log.tag == null ? "<no name>\n" : "[accent]" + log.tag + "\n") + "[lightgray][[" + log.tile.x + ", " + log.tile.y + "]", Styles.grayt, () -> {
//TODO: bug: if you edit name inside of the edit dialog, it won't show up in the list properly
log.showEditDialog(true);
}).size(Vars.mobile ? 390f : 450f, h).margin(10f).with(b -> {
b.getLabel().setAlignment(Align.left, Align.left);
});
t.button(Icon.pencil, Styles.graySquarei, Vars.iconMed, () -> {
ui.showTextInput("", "@editor.name", LogicBlock.maxNameLength, log.tag == null ? "" : log.tag, tag -> {
//bypass configuration and set it directly in case privileged checks mess things up
log.tag = tag;
setup();
});
}).size(h);
if(Vars.state.isGame() && state.isEditor()){
t.button(Icon.eyeSmall, Styles.graySquarei, Vars.iconMed, () -> {
hide();
control.input.config.showConfig(build);
control.input.panCamera(Tmp.v1.set(build));
}).size(h);
}
t.button(Icon.trash, Styles.graySquarei, iconMed, () -> {
ui.showConfirm("@editor.worldprocessors.delete.confirm", () -> {
boolean surrounded = true;
for(int i = 0; i < 4; i++){
Tile other = build.tile.nearby(i);
if(other != null && !(other.block().privileged || other.block().isStatic())){
surrounded = false;
break;
}
}
if(surrounded){
build.tile.setNet(build.tile.floor().wall instanceof StaticWall ? build.tile.floor().wall : Blocks.stoneWall);
}else{
build.tile.setNet(Blocks.air);
}
processors.remove(build);
rebuild();
});
}).size(h);
t.row();
}
}
}
}
private void setup(){
processors.clear();
//scan the entire world for processor (Groups.build can be empty, indexer is probably inaccurate)
Vars.world.tiles.eachTile(t -> {
if(t.isCenter() && t.block() == Blocks.worldProcessor){
processors.add(t.build);
}
});
rebuild();
}
}

View File

@@ -25,7 +25,6 @@ public class Damage{
private static final Rect rect = new Rect();
private static final Rect hitrect = new Rect();
private static final Vec2 vec = new Vec2(), seg1 = new Vec2(), seg2 = new Vec2();
private static final Seq<Unit> units = new Seq<>();
private static final IntSet collidedBlocks = new IntSet();
private static final IntFloatMap damages = new IntFloatMap();
private static final Seq<Collided> collided = new Seq<>();
@@ -41,6 +40,7 @@ public class Damage{
public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source){
applySuppression(team, x, y, range, reload, maxDelay, applyParticleChance, source, Pal.sapBullet);
}
public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source, Color effectColor){
builds.clear();
indexer.eachBlock(null, x, y, range, build -> build.team != team, build -> {
@@ -175,21 +175,23 @@ public class Damage{
distances.clear();
World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y), (x, y) -> {
//add distance to list so it can be processed
var build = world.build(x, y);
if(b.type.collidesGround && b.type.collidesTiles){
World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y), (x, y) -> {
//add distance to list so it can be processed
var build = world.build(x, y);
if(build != null && build.team != b.team && build.collide(b) && b.checkUnderBuild(build, x * tilesize, y * tilesize)){
distances.add(b.dst(build));
if(build != null && build.team != b.team && build.collide(b) && b.checkUnderBuild(build, x * tilesize, y * tilesize)){
distances.add(b.dst(build));
if(laser && build.absorbLasers()){
maxDst = Math.min(maxDst, b.dst(build));
return true;
if(laser && build.absorbLasers()){
maxDst = Math.min(maxDst, b.dst(build));
return true;
}
}
}
return false;
});
return false;
});
}
Units.nearbyEnemies(b.team, rect, u -> {
u.hitbox(hitrect);
@@ -243,11 +245,12 @@ public class Damage{
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){
length = findLength(hitter, length, laser, pierceCap);
hitter.fdata = length;
collidedBlocks.clear();
vec.trnsExact(angle, length);
if(hitter.type.collidesGround){
if(hitter.type.collidesGround && hitter.type.collidesTiles){
seg1.set(x, y);
seg2.set(seg1).add(vec);
World.raycastEachWorld(x, y, seg2.x, seg2.y, (cx, cy) -> {
@@ -293,7 +296,6 @@ public class Damage{
collided.each(c -> {
if(hitter.damage > 0 && (pierceCap <= 0 || collideCount[0] < pierceCap)){
if(c.target instanceof Unit u){
effect.at(c.x, c.y);
u.collision(hitter, c.x, c.y);
hitter.collision(u, c.x, c.y);
collideCount[0]++;
@@ -344,7 +346,6 @@ public class Damage{
Units.nearbyEnemies(team, rect.setCentered(x, y, 1f), u -> {
if(u.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround) && u.hittable()){
effect.at(x, y);
u.collision(hitter, x, y);
hitter.collision(u, x, y);
}

View File

@@ -247,4 +247,4 @@ public class EntityCollisions{
public interface SolidPred{
boolean solid(int x, int y);
}
}
}

View File

@@ -74,7 +74,7 @@ public class Puddles{
Puddle puddle = Puddle.create();
puddle.tile = tile;
puddle.liquid = liquid;
puddle.amount = amount;
puddle.amount = Math.min(amount, maxLiquid);
puddle.set(ax, ay);
register(puddle);
puddle.add();

View File

@@ -6,6 +6,7 @@ import mindustry.gen.*;
import mindustry.type.*;
public abstract class Ability implements Cloneable{
protected static final float descriptionWidth = 350f;
/** If false, this ability does not show in unit stats. */
public boolean display = true;
//the one and only data variable that is synced.
@@ -16,7 +17,16 @@ public abstract class Ability implements Cloneable{
public void death(Unit unit){}
public void init(UnitType type){}
public void displayBars(Unit unit, Table bars){}
public void addStats(Table t){}
public void addStats(Table t){
if(Core.bundle.has(getBundle() + ".description")){
t.add(Core.bundle.get(getBundle() + ".description")).wrap().width(descriptionWidth);
t.row();
}
}
public String abilityStat(String stat, Object... values){
return Core.bundle.format("ability.stat." + stat, values);
}
public Ability copy(){
try{
@@ -29,7 +39,11 @@ public abstract class Ability implements Cloneable{
/** @return localized ability name; mods should override this. */
public String localized(){
return Core.bundle.get(getBundle());
}
public String getBundle(){
var type = getClass();
return Core.bundle.get("ability." + (type.isAnonymousClass() ? type.getSuperclass() : type).getSimpleName().replace("Ability", "").toLowerCase());
return "ability." + (type.isAnonymousClass() ? type.getSuperclass() : type).getSimpleName().replace("Ability", "").toLowerCase();
}
}

View File

@@ -4,18 +4,27 @@ import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.Table;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.meta.*;
public class ArmorPlateAbility extends Ability{
public TextureRegion plateRegion;
public Color color = Color.valueOf("d1efff");
public TextureRegion shineRegion;
public String plateSuffix = "-armor";
public String shineSuffix = "-shine";
/** Color of the shine. If null, uses team color. */
public @Nullable Color color = null;
public float shineSpeed = 1f;
public float z = -1;
/** Whether to draw the plate region. */
public boolean drawPlate = true;
/** Whether to draw the shine over the plate region. */
public boolean drawShine = true;
public float healthMultiplier = 0.2f;
public float z = Layer.effect;
protected float warmup;
@@ -29,29 +38,45 @@ public class ArmorPlateAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Stat.healthMultiplier.localized() + ": [white]" + Math.round(healthMultiplier * 100f) + 100 + "%");
super.addStats(t);
t.add(abilityStat("damagereduction", Strings.autoFixed(-healthMultiplier * 100f, 1)));
}
@Override
public void draw(Unit unit){
if(!drawPlate && !drawShine) return;
if(warmup > 0.001f){
if(plateRegion == null){
plateRegion = Core.atlas.find(unit.type.name + "-armor", unit.type.region);
plateRegion = Core.atlas.find(unit.type.name + plateSuffix, unit.type.region);
shineRegion = Core.atlas.find(unit.type.name + shineSuffix, plateRegion);
}
Draw.draw(z <= 0 ? Draw.z() : z, () -> {
Shaders.armor.region = plateRegion;
Shaders.armor.progress = warmup;
Shaders.armor.time = -Time.time / 20f;
float pz = Draw.z();
if(z > 0) Draw.z(z);
Draw.rect(Shaders.armor.region, unit.x, unit.y, unit.rotation - 90f);
Draw.color(color);
Draw.shader(Shaders.armor);
Draw.rect(Shaders.armor.region, unit.x, unit.y, unit.rotation - 90f);
Draw.shader();
if(drawPlate){
Draw.alpha(warmup);
Draw.rect(plateRegion, unit.x, unit.y, unit.rotation - 90f);
Draw.alpha(1f);
}
Draw.reset();
});
if(drawShine){
Draw.draw(Draw.z(), () -> {
Shaders.armor.region = shineRegion;
Shaders.armor.progress = warmup;
Shaders.armor.time = -Time.time / 20f * shineSpeed;
Draw.color(color == null ? unit.team.color : color);
Draw.shader(Shaders.armor);
Draw.rect(shineRegion, unit.x, unit.y, unit.rotation - 90f);
Draw.shader();
Draw.reset();
});
}
Draw.z(pz);
}
}
}

View File

@@ -14,7 +14,6 @@ import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -53,23 +52,29 @@ public class EnergyFieldAbility extends Ability{
@Override
public void addStats(Table t){
t.add(Core.bundle.format("bullet.damage", damage));
if(displayHeal){
t.add(Core.bundle.get(getBundle() + ".healdescription")).wrap().width(descriptionWidth);
}else{
t.add(Core.bundle.get(getBundle() + ".description")).wrap().width(descriptionWidth);
}
t.row();
t.add("[lightgray]" + Stat.reload.localized() + ": [white]" + Strings.autoFixed(60f / reload, 2) + " " + StatUnit.perSecond.localized());
t.row();
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
t.row();
t.add(Core.bundle.format("ability.energyfield.maxtargets", maxTargets));
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
t.row();
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
t.row();
t.add(abilityStat("maxtargets", maxTargets));
t.row();
t.add(Core.bundle.format("bullet.damage", damage));
if(status != StatusEffects.none){
t.row();
t.add((status.hasEmoji() ? status.emoji() : "") + "[stat]" + status.localizedName);
}
if(displayHeal){
t.row();
t.add(Core.bundle.format("bullet.healpercent", Strings.autoFixed(healPercent, 2)));
t.row();
t.add(Core.bundle.format("ability.energyfield.sametypehealmultiplier", Math.round(sameTypeHealMult * 100f)));
}
if(status != StatusEffects.none){
t.row();
t.add(status.emoji() + " " + status.localizedName);
t.add(abilityStat("sametypehealmultiplier", (sameTypeHealMult < 1f ? "[negstat]" : "") + Strings.autoFixed(sameTypeHealMult * 100f, 2)));
}
}

View File

@@ -1,5 +1,6 @@
package mindustry.entities.abilities;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
@@ -12,7 +13,6 @@ import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -32,6 +32,7 @@ public class ForceFieldAbility extends Ability{
/** State. */
protected float radiusScale, alpha;
protected boolean wasBroken = true;
private static float realRad;
private static Unit paramUnit;
@@ -41,13 +42,6 @@ public class ForceFieldAbility extends Ability{
trait.absorb();
Fx.absorb.at(trait);
//break shield
if(paramUnit.shield <= trait.damage()){
paramUnit.shield -= paramField.cooldown * paramField.regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color, paramUnit);
}
paramUnit.shield -= trait.damage();
paramField.alpha = 1f;
}
@@ -73,18 +67,26 @@ public class ForceFieldAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Stat.health.localized() + ": [white]" + Math.round(max));
super.addStats(t);
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(radius / tilesize, 2)));
t.row();
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(radius / tilesize, 2) + " " + StatUnit.blocks.localized());
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
t.row();
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(regen * 60f, 2) + StatUnit.perSecond.localized());
t.row();
t.add("[lightgray]" + Stat.cooldownTime.localized() + ": [white]" + Strings.autoFixed(cooldown / 60f, 2) + " " + StatUnit.seconds.localized());
t.add(abilityStat("repairspeed", Strings.autoFixed(regen * 60f, 2)));
t.row();
t.add(abilityStat("cooldown", Strings.autoFixed(cooldown / 60f, 2)));
}
@Override
public void update(Unit unit){
if(unit.shield <= 0f && !wasBroken){
unit.shield -= cooldown * regen;
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.type.shieldColor(unit), this);
}
wasBroken = unit.shield <= 0f;
if(unit.shield < max){
unit.shield += Time.delta * regen;
}
@@ -108,7 +110,7 @@ public class ForceFieldAbility extends Ability{
checkRadius(unit);
if(unit.shield > 0){
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
Draw.color(unit.type.shieldColor(unit), Color.white, Mathf.clamp(alpha));
if(Vars.renderer.animateShields){
Draw.z(Layer.shields + 0.001f * alpha);

View File

@@ -1,6 +1,7 @@
package mindustry.entities.abilities;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.noise.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -16,6 +17,12 @@ public class LiquidExplodeAbility extends Ability{
public float radAmountScale = 5f, radScale = 1f;
public float noiseMag = 6.5f, noiseScl = 5f;
@Override
public void addStats(Table t){
super.addStats(t);
t.add((liquid.hasEmoji() ? liquid.emoji() : "") + "[stat]" + liquid.localizedName);
}
@Override
public void death(Unit unit){
//TODO what if noise is radial, so it looks like a splat?

View File

@@ -1,6 +1,7 @@
package mindustry.entities.abilities;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -17,6 +18,14 @@ public class LiquidRegenAbility extends Ability{
public float slurpEffectChance = 0.4f;
public Effect slurpEffect = Fx.heal;
@Override
public void addStats(Table t){
super.addStats(t);
t.add((liquid.hasEmoji() ? liquid.emoji() : "") + "[stat]" + liquid.localizedName);
t.row();
t.add(abilityStat("slurpheal", Strings.autoFixed(regenPerSlurp, 2)));
}
@Override
public void update(Unit unit){
//TODO timer?

View File

@@ -5,12 +5,15 @@ import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
public class MoveLightningAbility extends Ability{
/** Lightning damage */
public float damage = 35f;
@@ -63,7 +66,15 @@ public class MoveLightningAbility extends Ability{
this.maxSpeed = maxSpeed;
this.color = color;
}
@Override
public void addStats(Table t){
super.addStats(t);
t.add(abilityStat("minspeed", Strings.autoFixed(minSpeed * 60f / tilesize, 2)));
t.row();
t.add(Core.bundle.format("bullet.damage", damage));
}
@Override
public void update(Unit unit){
float scl = Mathf.clamp((unit.vel().len() - minSpeed) / (maxSpeed - minSpeed));

View File

@@ -1,10 +1,8 @@
package mindustry.entities.abilities;
import arc.Core;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
public class RegenAbility extends Ability{
/** Amount healed as percent per tick. */
@@ -14,13 +12,16 @@ public class RegenAbility extends Ability{
@Override
public void addStats(Table t){
if(amount > 0.01f){
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(amount * 60f, 2) + StatUnit.perSecond.localized());
t.row();
}
super.addStats(t);
if(percentAmount > 0.01f){
t.add(Core.bundle.format("bullet.healpercent", Strings.autoFixed(percentAmount * 60f, 2)) + StatUnit.perSecond.localized()); //stupid but works
boolean flat = amount >= 0.001f;
boolean percent = percentAmount >= 0.001f;
if(flat || percent){
t.add(abilityStat("regen",
(flat ? Strings.autoFixed(amount * 60f, 2) + (percent ? " [lightgray]+[stat] " : "") : "")
+ (percent ? Strings.autoFixed(percentAmount * 60f, 2) + "%" : "")
));
}
}

View File

@@ -1,13 +1,13 @@
package mindustry.entities.abilities;
import arc.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.*;
public class RepairFieldAbility extends Ability{
public float amount = 1, reload = 100, range = 60;
@@ -28,9 +28,10 @@ public class RepairFieldAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(amount * 60f / reload, 2) + StatUnit.perSecond.localized());
super.addStats(t);
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
t.row();
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
t.add(abilityStat("repairspeed", Strings.autoFixed(amount * 60f / reload, 2)));
}
@Override

View File

@@ -13,7 +13,6 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
public class ShieldArcAbility extends Ability{
private static Unit paramUnit;
@@ -32,7 +31,7 @@ public class ShieldArcAbility extends Ability{
if(paramField.data <= b.damage()){
paramField.data -= paramField.cooldown * paramField.regen;
Fx.arcShieldBreak.at(paramPos.x, paramPos.y, 0, paramUnit.team.color, paramUnit);
Fx.arcShieldBreak.at(paramPos.x, paramPos.y, 0, paramField.color == null ? paramUnit.type.shieldColor(paramUnit) : paramField.color, paramUnit);
}
paramField.data -= b.damage();
@@ -61,6 +60,8 @@ public class ShieldArcAbility extends Ability{
public boolean drawArc = true;
/** If not null, will be drawn on top. */
public @Nullable String region;
/** Color override of the shield. Uses unit shield colour by default. */
public @Nullable Color color;
/** If true, sprite position will be influenced by x/y. */
public boolean offsetRegion = false;
@@ -69,12 +70,12 @@ public class ShieldArcAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Stat.health.localized() + ": [white]" + Math.round(max));
super.addStats(t);
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
t.row();
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(regen * 60f, 2) + StatUnit.perSecond.localized());
t.row();
t.add("[lightgray]" + Stat.cooldownTime.localized() + ": [white]" + Strings.autoFixed(cooldown / 60f, 2) + " " + StatUnit.seconds.localized());
t.add(abilityStat("repairspeed", Strings.autoFixed(regen * 60f, 2)));
t.row();
t.add(abilityStat("cooldown", Strings.autoFixed(cooldown / 60f, 2)));
}
@Override
@@ -110,7 +111,7 @@ public class ShieldArcAbility extends Ability{
if(widthScale > 0.001f){
Draw.z(Layer.shields);
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
Draw.color(color == null ? unit.type.shieldColor(unit) : color, Color.white, Mathf.clamp(alpha));
var pos = paramPos.set(x, y).rotate(unit.rotation - 90f).add(unit);
if(!Vars.renderer.animateShields){

View File

@@ -1,14 +1,13 @@
package mindustry.entities.abilities;
import arc.Core;
import arc.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.*;
public class ShieldRegenFieldAbility extends Ability{
public float amount = 1, max = 100f, reload = 100, range = 60;
@@ -30,12 +29,12 @@ public class ShieldRegenFieldAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Core.bundle.get("waves.shields") + ": [white]" + Math.round(max)); //extremely stupid usage
super.addStats(t);
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
t.row();
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
t.row();
t.add("[lightgray]" + Stat.reload.localized() + ": [white]" + Strings.autoFixed(60f / reload, 2) + " " + StatUnit.perSecond.localized());
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
t.row();
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
}
@Override
@@ -49,13 +48,13 @@ public class ShieldRegenFieldAbility extends Ability{
if(other.shield < max){
other.shield = Math.min(other.shield + amount, max);
other.shieldAlpha = 1f; //TODO may not be necessary
applyEffect.at(unit.x, unit.y, 0f, unit.team.color, parentizeEffects ? other : null);
applyEffect.at(other.x, other.y, 0f, other.type.shieldColor(other), parentizeEffects ? other : null);
applied = true;
}
});
if(applied){
activeEffect.at(unit.x, unit.y, unit.team.color);
activeEffect.at(unit.x, unit.y, unit.type.shieldColor(unit));
}
timer = 0f;

View File

@@ -27,7 +27,8 @@ public class SpawnDeathAbility extends Ability{
@Override
public void addStats(Table t){
t.add((randAmount > 0 ? amount + "-" + (amount + randAmount) : amount) + " " + unit.emoji() + " " + unit.localizedName);
super.addStats(t);
t.add("[stat]" + (randAmount > 0 ? amount + "x-" + (amount + randAmount) : amount) + "x[] " + (unit.hasEmoji() ? unit.emoji() : "") + "[stat]" + unit.localizedName);
}
@Override

View File

@@ -1,15 +1,15 @@
package mindustry.entities.abilities;
import arc.*;
import arc.math.*;
import arc.scene.ui.layout.Table;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.meta.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.*;
public class StatusFieldAbility extends Ability{
public StatusEffect effect;
@@ -33,11 +33,12 @@ public class StatusFieldAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Stat.reload.localized() + ": [white]" + Strings.autoFixed(60f / reload, 2) + " " + StatUnit.perSecond.localized());
super.addStats(t);
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
t.row();
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
t.row();
t.add(effect.emoji() + " " + effect.localizedName);
t.add((effect.hasEmoji() ? effect.emoji() : "") + "[stat]" + effect.localizedName);
}
@Override

View File

@@ -1,12 +1,17 @@
package mindustry.entities.abilities;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import static mindustry.Vars.*;
public class SuppressionFieldAbility extends Ability{
protected static Rand rand = new Rand();
@@ -33,6 +38,19 @@ public class SuppressionFieldAbility extends Ability{
protected float timer;
@Override
public void init(UnitType type){
if(!active) display = false;
}
@Override
public void addStats(Table t){
super.addStats(t);
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
t.row();
t.add(abilityStat("duration", Strings.autoFixed(reload / 60f, 2)));
}
@Override
public void update(Unit unit){
if(!active) return;

View File

@@ -12,7 +12,6 @@ import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -36,9 +35,10 @@ public class UnitSpawnAbility extends Ability{
@Override
public void addStats(Table t){
t.add("[lightgray]" + Stat.buildTime.localized() + ": [white]" + Strings.autoFixed(spawnTime / 60f, 2) + " " + StatUnit.seconds.localized());
super.addStats(t);
t.add(abilityStat("buildtime", Strings.autoFixed(spawnTime / 60f, 2)));
t.row();
t.add(unit.emoji() + " " + unit.localizedName);
t.add((unit.hasEmoji() ? unit.emoji() : "") + "[stat]" + unit.localizedName);
}
@Override

View File

@@ -176,6 +176,8 @@ public class BulletType extends Content implements Cloneable{
public float fragLifeMin = 1f, fragLifeMax = 1f;
/** Random offset of frag bullets from the parent bullet. */
public float fragOffsetMin = 1f, fragOffsetMax = 7f;
/** How many times this bullet can release frag bullets, if pierce = true. */
public int pierceFragCap = -1;
/** Bullet that is created at a fixed interval. */
public @Nullable BulletType intervalBullet;
@@ -307,6 +309,8 @@ public class BulletType extends Content implements Cloneable{
/** Color of light emitted by this bullet. */
public Color lightColor = Pal.powerLight;
protected float cachedDps = -1;
public BulletType(float speed, float damage){
this.speed = speed;
this.damage = damage;
@@ -336,15 +340,20 @@ public class BulletType extends Content implements Cloneable{
/** @return estimated damage per shot. this can be very inaccurate. */
public float estimateDPS(){
if(cachedDps >= 0f) return cachedDps;
if(spawnUnit != null){
return spawnUnit.estimateDps();
}
float sum = damage + splashDamage*0.75f;
float sum = damage * (pierce ? pierceCap == -1 ? 2 : Mathf.clamp(pierceCap, 1, 2) : 1f) * splashDamage*0.75f;
if(fragBullet != null && fragBullet != this){
sum += fragBullet.estimateDPS() * fragBullets / 2f;
}
return sum;
for(var other : spawnBullets){
sum += other.estimateDPS();
}
return cachedDps = sum;
}
/** @return maximum distance the bullet this bullet type has can travel. */
@@ -509,12 +518,13 @@ public class BulletType extends Content implements Cloneable{
}
public void createFrags(Bullet b, float x, float y){
if(fragBullet != null && (fragOnAbsorb || !b.absorbed)){
if(fragBullet != null && (fragOnAbsorb || !b.absorbed) && !(b.frags >= pierceFragCap && pierceFragCap > 0)){
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(fragOffsetMin, fragOffsetMax);
float a = b.rotation() + Mathf.range(fragRandomSpread / 2) + fragAngle + ((i - fragBullets/2) * fragSpread);
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
}
b.frags++;
}
}

View File

@@ -49,6 +49,7 @@ public class ContinuousFlameBulletType extends ContinuousBulletType{
lifetime = 16f;
hitColor = colors[1].cpy().a(1f);
lightColor = hitColor;
lightOpacity = 0.7f;
laserAbsorb = false;
ammoMultiplier = 1f;
pierceArmor = true;
@@ -87,7 +88,7 @@ public class ContinuousFlameBulletType extends ContinuousBulletType{
}
Tmp.v1.trns(b.rotation(), realLength * 1.1f);
Drawf.light(b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, 0.7f);
Drawf.light(b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, lightOpacity);
Draw.reset();
}

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

@@ -118,6 +118,7 @@ public class PointLaserBulletType extends BulletType{
}
}
@Override
public void updateBulletInterval(Bullet b){
if(intervalBullet != null && b.time >= intervalDelay && b.timer.get(2, bulletInterval)){
float ang = b.rotation();

View File

@@ -40,7 +40,7 @@ abstract class BlockUnitComp implements Unitc{
@Replace
@Override
public TextureRegion icon(){
return tile.block.fullIcon;
return tile.block.uiIcon;
}
@Override

View File

@@ -138,7 +138,11 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
if(!(tile.build instanceof ConstructBuild cb)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
boolean hasAll = infinite || current.isRotation(team) ||
//derelict repair
(tile.team() == Team.derelict && tile.block() == current.block && tile.build != null && tile.block().allowDerelictRepair && state.rules.derelictRepair) ||
//make sure there's at least 1 item of each type first
!Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
if(hasAll){
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
@@ -290,10 +294,6 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
return plans.size == 0 ? null : plans.first();
}
public void draw(){
drawBuilding();
}
public void drawBuilding(){
//TODO make this more generic so it works with builder "weapons"
boolean active = activelyBuilding();

View File

@@ -59,6 +59,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Import float x, y, health, maxHealth;
@Import Team team;
@Import boolean dead;
transient Tile tile;
transient Block block;
@@ -87,6 +88,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient float optionalEfficiency;
/** The efficiency this block *would* have if shouldConsume() returned true. */
transient float potentialEfficiency;
/** Whether there are any consumers (aside from power) that have efficiency > 0. */
transient boolean shouldConsumePower;
transient float healSuppressionTime = -1f;
transient float lastHealTime = -120f * 10f;
@@ -259,6 +262,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
read(read, revision);
}
public void writeSync(Writes write){
writeAll(write);
}
public void readSync(Reads read, byte revision){
readAll(read, revision);
}
@CallSuper
public void write(Writes write){
//overriden by subclasses!
@@ -1310,7 +1321,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(value instanceof UnitType) type = UnitType.class;
if(builder != null && builder.isPlayer()){
lastAccessed = builder.getPlayer().coloredName();
updateLastAccess(builder.getPlayer());
}
if(block.configurations.containsKey(type)){
@@ -1324,6 +1335,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
public void updateLastAccess(Player player){
lastAccessed = player.coloredName();
}
/** Called when the block is tapped by the local player. */
public void tapped(){
@@ -1769,6 +1784,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(!block.hasConsumers || cheating()){
potentialEfficiency = enabled && productionValid() ? 1f : 0f;
efficiency = optionalEfficiency = shouldConsume() ? potentialEfficiency : 0f;
shouldConsumePower = true;
updateEfficiencyMultiplier();
return;
}
@@ -1776,6 +1792,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//disabled -> nothing works
if(!enabled){
potentialEfficiency = efficiency = optionalEfficiency = 0f;
shouldConsumePower = false;
return;
}
@@ -1785,10 +1802,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//assume efficiency is 1 for the calculations below
efficiency = optionalEfficiency = 1f;
shouldConsumePower = true;
//first pass: get the minimum efficiency of any consumer
for(var cons : block.nonOptionalConsumers){
minEfficiency = Math.min(minEfficiency, cons.efficiency(self()));
float result = cons.efficiency(self());
if(cons != block.consPower && result <= 0.0000001f){
shouldConsumePower = false;
}
minEfficiency = Math.min(minEfficiency, result);
}
//same for optionals
@@ -1911,6 +1935,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;
@@ -1977,9 +2002,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
switch(prop){
case health -> {
health = (float)Mathf.clamp(value, 0, maxHealth);
healthChanged();
if(health <= 0f && !dead()){
Call.buildDestroyed(self());
}else{
healthChanged();
}
}
case team -> {
@@ -2056,6 +2082,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Override
public void killed(){
dead = true;
Events.fire(new BlockDestroyEvent(tile));
block.destroySound.at(tile);
onDestroyed();

View File

@@ -45,6 +45,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
transient @Nullable Mover mover;
transient boolean absorbed, hit;
transient @Nullable Trail trail;
transient int frags;
@Override
public void getCollisions(Cons<QuadTree> consumer){

View File

@@ -4,6 +4,7 @@ import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.world.blocks.*;
@Component
abstract class ChildComp implements Posc, Rotc{
@@ -18,9 +19,14 @@ abstract class ChildComp implements Posc, Rotc{
if(parent != null){
offsetX = x - parent.getX();
offsetY = y - parent.getY();
if(rotWithParent && parent instanceof Rotc r){
offsetPos = -r.rotation();
offsetRot = rotation - r.rotation();
if(rotWithParent){
if(parent instanceof Rotc r){
offsetPos = -r.rotation();
offsetRot = rotation - r.rotation();
}else if(parent instanceof RotBlock rot){
offsetPos = -rot.buildRotation();
offsetRot = rotation - rot.buildRotation();
}
}
}
}
@@ -28,10 +34,16 @@ abstract class ChildComp implements Posc, Rotc{
@Override
public void update(){
if(parent != null){
if(rotWithParent && parent instanceof Rotc r){
x = parent.getX() + Angles.trnsx(r.rotation() + offsetPos, offsetX, offsetY);
y = parent.getY() + Angles.trnsy(r.rotation() + offsetPos, offsetX, offsetY);
rotation = r.rotation() + offsetRot;
if(rotWithParent){
if(parent instanceof Rotc r){
x = parent.getX() + Angles.trnsx(r.rotation() + offsetPos, offsetX, offsetY);
y = parent.getY() + Angles.trnsy(r.rotation() + offsetPos, offsetX, offsetY);
rotation = r.rotation() + offsetRot;
}else if(parent instanceof RotBlock rot){
x = parent.getX() + Angles.trnsx(rot.buildRotation() + offsetPos, offsetX, offsetY);
y = parent.getY() + Angles.trnsy(rot.buildRotation() + offsetPos, offsetX, offsetY);
rotation = rot.buildRotation() + offsetRot;
}
}else{
x = parent.getX() + offsetX;
y = parent.getY() + offsetY;

View File

@@ -35,10 +35,6 @@ abstract class EntityComp{
return ((Object)this) instanceof Unitc u && u.isPlayer() && !isLocal();
}
boolean isNull(){
return false;
}
/** Replaced with `this` after code generation. */
<T extends Entityc> T self(){
return (T)this;

View File

@@ -68,7 +68,7 @@ abstract class HitboxComp implements Posc, Sized, QuadTreeObject{
public void hitboxTile(Rect rect){
//tile hitboxes are never bigger than a tile, otherwise units get stuck
float size = Math.min(hitSize * 0.66f, 7.9f);
float size = Math.min(hitSize * 0.66f, 7.8f);
//TODO: better / more accurate version is
//float size = hitSize * 0.85f;
//- for tanks?

View File

@@ -24,6 +24,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Import float x, y, rotation, speedMultiplier;
@Import UnitType type;
@Import Team team;
@Import boolean disarmed;
transient Leg[] legs = {};
transient float totalLength;
@@ -191,7 +192,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
}
}
if(type.legSplashDamage > 0){
if(type.legSplashDamage > 0 && !disarmed){
Damage.damage(team, l.base.x, l.base.y, type.legSplashRange, type.legSplashDamage * state.rules.unitDamage(team), false, true);
}
}

View File

@@ -123,31 +123,4 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
}
}
}
@Override
public void draw(){
if(!mining()) return;
float focusLen = hitSize / 2f + Mathf.absin(Time.time, 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
float ex = mineTile.worldx() + Mathf.sin(Time.time + 48, swingScl, swingMag);
float ey = mineTile.worldy() + Mathf.sin(Time.time + 48, swingScl + 2f, swingMag);
Draw.z(Layer.flyingUnit + 0.1f);
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time, 0.5f, flashScl));
Drawf.laser(Core.atlas.find("minelaser"), Core.atlas.find("minelaser-end"), px, py, ex, ey, 0.75f);
if(isLocal()){
Lines.stroke(1f, Pal.accent);
Lines.poly(mineTile.worldx(), mineTile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time);
}
Draw.color();
}
}

View File

@@ -60,6 +60,11 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
}
}
@Override
public void destroy(){
if(Vars.state.rules.unitPayloadsExplode) payloads.each(Payload::destroyed);
}
float payloadUsed(){
return payloads.sumf(p -> p.size() * p.size());
}

View File

@@ -33,7 +33,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Import float x, y;
@ReadOnly Unit unit = Nulls.unit;
@ReadOnly @Nullable Unit unit;
transient @Nullable NetConnection con;
@ReadOnly Team team = Team.sharded;
@SyncLocal boolean typing, shooting, boosting;
@@ -47,13 +47,14 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
transient float deathTimer;
transient String lastText = "";
transient float textFadeTime;
transient Ratekeeper itemDepositRate = new Ratekeeper();
transient private Unit lastReadUnit = Nulls.unit;
transient private @Nullable Unit lastReadUnit;
transient private int wrongReadUnits;
transient @Nullable Unit justSwitchFrom, justSwitchTo;
public boolean isBuilder(){
return unit.canBuild();
return unit != null && unit.canBuild();
}
public @Nullable CoreBuild closestCore(){
@@ -72,7 +73,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
public TextureRegion icon(){
//display default icon for dead players
if(dead()) return core() == null ? UnitTypes.alpha.fullIcon : ((CoreBlock)bestCore().block).unitType.fullIcon;
if(dead()) return core() == null ? UnitTypes.alpha.uiIcon : ((CoreBlock)bestCore().block).unitType.uiIcon;
return unit.icon();
}
@@ -88,7 +89,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
x = y = 0f;
if(!dead()){
unit.resetController();
unit = Nulls.unit;
unit = null;
}
}
@@ -104,7 +105,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Replace
public float clipSize(){
return unit.isNull() ? 20 : unit.type.hitSize * 2f;
return unit == null ? 20 : unit.type.hitSize * 2f;
}
@Override
@@ -130,17 +131,18 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
unit = lastReadUnit;
unit(set);
lastReadUnit = unit;
unit.aim(mouseX, mouseY);
//this is only necessary when the thing being controlled isn't synced
unit.controlWeapons(shooting, shooting);
//extra precaution, necessary for non-synced things
unit.controller(this);
if(unit != null){
unit.aim(mouseX, mouseY);
//this is only necessary when the thing being controlled isn't synced
unit.controlWeapons(shooting, shooting);
//extra precaution, necessary for non-synced things
unit.controller(this);
}
}
@Override
public void update(){
if(!unit.isValid()){
if(unit != null && !unit.isValid()){
clearUnit();
}
@@ -180,42 +182,43 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Override
public void remove(){
//clear unit upon removal
if(!unit.isNull()){
if(unit != null){
clearUnit();
}
lastReadUnit = Nulls.unit;
lastReadUnit = null;
justSwitchTo = justSwitchFrom = null;
}
public void team(Team team){
this.team = team;
unit.team(team);
if(unit != null){
unit.team(team);
}
}
public void clearUnit(){
unit(Nulls.unit);
unit(null);
}
public Unit unit(){
public @Nullable Unit unit(){
return unit;
}
public void unit(Unit unit){
public void unit(@Nullable Unit unit){
//refuse to switch when the unit was just transitioned from
if(isLocal() && unit == justSwitchFrom && justSwitchFrom != null && justSwitchTo != null){
return;
}
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit == unit) return;
//save last command this unit had
if(unit.controller() instanceof CommandAI ai){
if(unit != null && unit.controller() instanceof CommandAI ai){
lastCommand = ai.command;
}
if(this.unit != Nulls.unit){
if(this.unit != null){
//un-control the old unit
this.unit.resetController();
//restore last command issued before it was controlled
@@ -224,7 +227,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
}
}
this.unit = unit;
if(unit != Nulls.unit){
if(unit != null){
unit.team(team);
unit.controller(this);
@@ -243,7 +246,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
}
boolean dead(){
return unit.isNull() || !unit.isValid();
return unit == null || !unit.isValid();
}
String ip(){
@@ -276,10 +279,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Override
public void draw(){
if(unit != null && unit.inFogTo(Vars.player.team())) return;
//??????
if(name == null) return;
if(unit == null || name == null || unit.inFogTo(Vars.player.team())) return;
Draw.z(Layer.playerName);
float z = Drawf.text();

View File

@@ -59,6 +59,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
amount -= Time.delta * (1f - liquid.viscosity) / (5f + addSpeed);
amount += accepting;
amount = Math.min(amount, maxLiquid);
accepting = 0f;
if(amount >= maxLiquid / 1.5f){

View File

@@ -61,7 +61,7 @@ abstract class ShieldComp implements Healthc, Posc{
}
if(hadShields && shield <= 0.0001f){
Fx.unitShieldBreak.at(x, y, 0, team.color, this);
Fx.unitShieldBreak.at(x, y, 0, type.shieldColor(self()), this);
}
}
}

View File

@@ -73,6 +73,7 @@ abstract class StatusComp implements Posc, Flyingc{
}
void clearStatuses(){
statuses.each(e -> e.effect.onRemoved(self()));
statuses.clear();
}
@@ -80,6 +81,7 @@ abstract class StatusComp implements Posc, Flyingc{
void unapply(StatusEffect effect){
statuses.remove(e -> {
if(e.effect == effect){
e.effect.onRemoved(self());
Pools.free(e);
return true;
}
@@ -189,6 +191,10 @@ abstract class StatusComp implements Posc, Flyingc{
entry.time = Math.max(entry.time - Time.delta, 0);
if(entry.effect == null || (entry.time <= 0 && !entry.effect.permanent)){
if(entry.effect != null){
entry.effect.onRemoved(self());
}
Pools.free(entry);
index --;
statuses.remove(index);

View File

@@ -18,7 +18,7 @@ import static mindustry.Vars.*;
@Component
abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec{
@Import float x, y, hitSize, rotation, speedMultiplier;
@Import boolean hovering;
@Import boolean hovering, disarmed;
@Import UnitType type;
@Import Team team;
@@ -51,7 +51,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
}
//calculate overlapping tiles so it slows down when going "over" walls
int r = Math.max(Math.round(hitSize * 0.6f / tilesize), 1);
int r = Math.max((int)(hitSize * 0.6f / tilesize), 0);
int solids = 0, total = (r*2+1)*(r*2+1);
for(int dx = -r; dx <= r; dx++){
@@ -62,7 +62,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
}
//TODO should this apply to the player team(s)? currently PvE due to balancing
if(type.crushDamage > 0 && (walked || deltaLen() >= 0.01f) && t != null && t.build != null && t.build.team != team
if(type.crushDamage > 0 && !disarmed && (walked || deltaLen() >= 0.01f) && t != null && t.build != null && t.build.team != team
//damage radius is 1 tile smaller to prevent it from just touching walls as it passes
&& Math.max(Math.abs(dx), Math.abs(dy)) <= r - 1){

View File

@@ -214,6 +214,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case ammoCapacity -> type.ammoCapacity;
case x -> World.conv(x);
case y -> World.conv(y);
case velocityX -> vel.x * 60f / tilesize;
case velocityY -> vel.y * 60f / tilesize;
case dead -> dead || !isAdded() ? 1 : 0;
case team -> team.id;
case shooting -> isShooting() ? 1 : 0;
@@ -282,6 +284,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
y = World.unconv((float)value);
if(!isLocal()) snapInterpolation();
}
case velocityX -> vel.x = (float)(value * tilesize / 60d);
case velocityY -> vel.y = (float)(value * tilesize / 60d);
case rotation -> rotation = (float)value;
case team -> {
if(!net.client()){
@@ -402,7 +406,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
}
/** @return pathfinder path type for calculating costs */
/** @return pathfinder path type for calculating costs. This is used for wave AI only. (TODO: remove) */
public int pathType(){
return Pathfinder.costGround;
}
@@ -666,9 +670,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
/** @return a preview icon for this unit. */
/** @return a preview UI icon for this unit. */
public TextureRegion icon(){
return type.fullIcon;
return type.uiIcon;
}
/** Actually destroys the unit, removing it and creating explosions. **/
@@ -734,7 +738,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return name of direct or indirect player controller. */
@Override
public @Nullable String getControllerName(){
if(isPlayer()) return getPlayer().name;
if(isPlayer()) return getPlayer().coloredName();
if(controller instanceof LogicAI ai && ai.controller != null) return ai.controller.lastAccessed;
return null;
}
@@ -788,6 +792,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
@Replace
public String toString(){
return "Unit#" + id() + ":" + type;
return "Unit#" + id() + ":" + type + " (" + x + ", " + y + ")";
}
}

View File

@@ -79,7 +79,7 @@ public class ParticleEffect extends Effect{
float x = rv.x, y = rv.y;
Lines.lineAngle(ox + x, oy + y, Mathf.angle(x, y), len, cap);
Drawf.light(ox + x, oy + y, len * lightScl, lightColor, lightOpacity * Draw.getColor().a);
Drawf.light(ox + x, oy + y, len * lightScl, lightColor, lightOpacity * Draw.getColorAlpha());
}
}else{
rand.setSeed(e.id);
@@ -88,8 +88,8 @@ public class ParticleEffect extends Effect{
rv.trns(realRotation + rand.range(cone), !randLength ? l : rand.random(l));
float x = rv.x, y = rv.y;
Draw.rect(tex, ox + x, oy + y, rad, rad, realRotation + offset + e.time * spin);
Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity * Draw.getColor().a);
Draw.rect(tex, ox + x, oy + y, rad, rad / tex.ratio(), realRotation + offset + e.time * spin);
Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity * Draw.getColorAlpha());
}
}
}

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());
}
@@ -341,7 +341,7 @@ public class AIController implements UnitController{
vec.setLength(speed * length);
}
//do not move when infinite vectors are used or if its zero.
//ignore invalid movement values
if(vec.isNaN() || vec.isInfinite() || vec.isZero()) return;
if(!unit.type.omniMovement && unit.type.rotateMoveFirst){

View File

@@ -39,6 +39,7 @@ public class EventType{
socketConfigChanged,
update,
unitCommandChange,
unitCommandPosition,
unitCommandAttack,
importMod,
draw,

View File

@@ -13,6 +13,7 @@ public enum Gamemode{
}, map -> map.spawns > 0),
sandbox(rules -> {
rules.infiniteResources = true;
rules.allowEditRules = true;
rules.waves = true;
rules.waveTimer = false;
}),

View File

@@ -39,8 +39,6 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
* @see #eachRunning(Cons)
*/
public Seq<MapObjective> all = new Seq<>(4);
/** @see #checkChanged() */
protected transient boolean changed;
static{
registerObjective(
@@ -126,21 +124,13 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
eachRunning(obj -> {
//objectives cannot get completed on the client, but they do try to update for timers and such
if(obj.update() && !net.client()){
obj.completed = true;
obj.done();
Call.completeObjective(all.indexOf(obj));
}
changed |= obj.changed;
obj.changed = false;
});
}
/** @return True if map rules should be synced. Reserved for {@link Vars#logic}; do not invoke directly! */
public boolean checkChanged(){
boolean has = changed;
changed = false;
return has;
public @Nullable MapObjective get(int index){
return index < 0 || index >= all.size ? null : all.get(index);
}
/** @return Whether there are any qualified objectives at all. */
@@ -149,7 +139,6 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
public void clear(){
if(all.size > 0) changed = true;
all.clear();
}
@@ -191,7 +180,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
/** Whether this objective has been done yet. This is internally set. */
private boolean completed;
/** Internal value. Do not modify! */
private transient boolean depFinished, changed;
private transient boolean depFinished;
/** @return True if this objective is done and should be removed from the executor. */
public abstract boolean update();
@@ -201,13 +190,9 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
/** Called once after {@link #update()} returns true, before this objective is removed. */
public void done(){
changed();
Call.objectiveCompleted(flagsRemoved, flagsAdded);
}
/** Notifies the executor that map rules should be synced. */
protected void changed(){
changed = true;
state.rules.objectiveFlags.removeAll(flagsRemoved);
state.rules.objectiveFlags.addAll(flagsAdded);
completed = true;
}
/** @return True if all {@link #parents} are completed, rendering this objective able to execute. */
@@ -866,7 +851,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
/** Displays a shape with an outline and color. */
public static class ShapeMarker extends PosMarker{
public float radius = 8f, rotation = 0f, stroke = 1f;
public float radius = 8f, rotation = 0f, stroke = 1f, startAngle = 0f, endAngle = 360f;
public boolean fill = false, outline = true;
public int sides = 4;
public Color color = Color.valueOf("ffd37f");
@@ -892,14 +877,18 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
if(!fill){
if(outline){
Lines.stroke((stroke + 2f) * scaleFactor, Pal.gray);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
}
Lines.stroke(stroke * scaleFactor, color);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
}else{
Draw.color(color);
Fill.poly(pos.x, pos.y, sides, radius * scaleFactor, rotation);
if (startAngle < endAngle){
Fill.arc(pos.x, pos.y, radius * scaleFactor, (endAngle - startAngle) / 360f, rotation + startAngle, sides);
}else{
Fill.arc(pos.x, pos.y, radius * scaleFactor, (startAngle - endAngle) / 360f, rotation + endAngle, sides);
}
}
Draw.reset();
@@ -916,12 +905,14 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
case rotation -> rotation = (float)p1;
case color -> color.fromDouble(p1);
case shape -> sides = (int)p1;
case arc -> startAngle = (float)p1;
}
}
if(!Double.isNaN(p2)){
switch(type){
case shape -> fill = !Mathf.equal((float)p2, 0f);
case arc -> endAngle = (float)p2;
}
}

View File

@@ -19,6 +19,8 @@ import mindustry.world.blocks.*;
* Does not store game state, just configuration.
*/
public class Rules{
/** Allows editing the rules in-game. Essentially a cheat mode toggle. */
public boolean allowEditRules = false;
/** Sandbox mode: Enables infinite resources, build range and build speed. */
public boolean infiniteResources;
/** Team-specific rules. */
@@ -29,6 +31,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. */
@@ -59,6 +63,8 @@ public class Rules{
public boolean unitAmmo = false;
/** EXPERIMENTAL! If true, blocks will update in units and share power. */
public boolean unitPayloadUpdate = false;
/** If true, units' payloads are destroy()ed when the unit is destroyed. */
public boolean unitPayloadsExplode = false;
/** Whether cores add to unit limit */
public boolean unitCapVariable = true;
/** If true, unit spawn points are shown. */
@@ -101,6 +107,8 @@ public class Rules{
public boolean cleanupDeadTeams = true;
/** If true, items can only be deposited in the core. */
public boolean onlyDepositCore = false;
/** Cooldown, in seconds, of item depositing for players. */
public float itemDepositCooldown = 0.5f;
/** If true, every enemy block in the radius of the (enemy) core is destroyed upon death. Used for campaign maps. */
public boolean coreDestroyClear = false;
/** If true, banned blocks are hidden from the build menu. */
@@ -199,6 +207,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

@@ -480,7 +480,7 @@ public class Drawf{
/** Draws a sprite that should be light-wise correct, when rotated. Provided sprite must be symmetrical in shape. */
public static void spinSprite(TextureRegion region, float x, float y, float r){
float a = Draw.getColor().a;
float a = Draw.getColorAlpha();
r = Mathf.mod(r, 90f);
Draw.rect(region, x, y, r);
Draw.alpha(r / 90f*a);

View File

@@ -70,7 +70,7 @@ public class LightRenderer{
float rot = Mathf.angleExact(x2 - x, y2 - y);
TextureRegion ledge = Core.atlas.find("circle-end"), lmid = Core.atlas.find("circle-mid");
float color = Draw.getColor().toFloatBits();
float color = Draw.getColorPacked();
float u = lmid.u;
float v = lmid.v2;
float u2 = lmid.u2;

View File

@@ -176,10 +176,7 @@ public class MinimapRenderer{
if(fullView && net.active()){
for(Player player : Groups.player){
if(!player.dead()){
float rx = player.x / (world.width() * tilesize) * w;
float ry = player.y / (world.height() * tilesize) * h;
drawLabel(x + rx, y + ry, player.name, player.color);
drawLabel(player.x, player.y, player.name, player.color);
}
}
}

View File

@@ -97,8 +97,12 @@ public class MultiPacker implements Disposable{
@Override
public void dispose(){
for(PixmapPacker packer : packers){
packer.dispose();
for(int i = 0; i < PageType.all.length; i ++){
var packer = packers[i];
//the UI packer's image is later used when merging with the font, don't dispose it
if(i != PageType.ui.ordinal()){
packer.forceDispose();
}
}
}

View File

@@ -129,9 +129,11 @@ public class OverlayRenderer{
Draw.mixcol(Pal.accent, 1f);
Draw.alpha(unitFade);
Building build = (select instanceof BlockUnitc b ? b.tile() : select instanceof Building b ? b : null);
TextureRegion region = build != null ? build.block.fullIcon : select instanceof Unit u ? u.icon() : Core.atlas.white();
TextureRegion region = build != null ? build.block.fullIcon : Core.atlas.white();
Draw.rect(region, select.getX(), select.getY(), select instanceof Unit u && !(select instanceof BlockUnitc) ? u.rotation - 90f : 0f);
if(!(select instanceof Unitc)){
Draw.rect(region, select.getX(), select.getY());
}
for(int i = 0; i < 4; i++){
float rot = i * 90f + 45f + (-Time.time) % 360f;
@@ -239,7 +241,9 @@ public class OverlayRenderer{
Draw.reset();
Building build = world.buildWorld(v.x, v.y);
if(input.canDropItem() && build != null && build.interactable(player.team()) && build.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0 && player.within(build, itemTransferRange)){
if(input.canDropItem() && build != null && build.interactable(player.team()) && build.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0 && player.within(build, itemTransferRange) &&
input.itemDepositCooldown <= 0f){
boolean invalid = (state.rules.onlyDepositCore && !(build instanceof CoreBuild));
Lines.stroke(3f, Pal.gray);
@@ -255,6 +259,13 @@ public class OverlayRenderer{
}
}
public void checkApplySelection(Unit u){
if(unitFade > 0.001f && lastSelect == u){
Color prev = Draw.getMixColor();
Draw.mixcol(prev.a > 0.001f ? prev.lerp(Pal.accent, unitFade) : Pal.accent, Math.max(unitFade, prev.a));
}
}
private static class CoreEdge{
float x1, y1, x2, y2;
Team t1, t2;

View File

@@ -474,6 +474,6 @@ public class Shaders{
}
public static Fi getShaderFi(String file){
return Core.files.internal("shaders/" + file);
return tree.get("shaders/" + file);
}
}

View File

@@ -65,6 +65,12 @@ public class Trail{
x2 = items[i + 3];
y2 = items[i + 4];
w2 = items[i + 5];
if(i == 0 && points.size >= (length - 1) * 3){
x1 = Mathf.lerp(x1, x2, counter);
y1 = Mathf.lerp(y1, y2, counter);
w1 = Mathf.lerp(w1, w2, counter);
}
}else{
x2 = lastX;
y2 = lastY;
@@ -97,13 +103,12 @@ public class Trail{
/** Removes the last point from the trail at intervals. */
public void shorten(){
if((counter += Time.delta) >= 1f){
if(points.size >= 3){
points.removeRange(0, 2);
}
int count = (int)(counter += Time.delta);
counter -= count;
counter %= 1f;
}
if(count > 0 && points.size > 0){
points.removeRange(0, Math.min(count * 3 - 1, points.size - 1));
}
}
/** Adds a new point to the trail at intervals. */
@@ -113,18 +118,27 @@ public class Trail{
/** Adds a new point to the trail at intervals. */
public void update(float x, float y, float width){
//TODO fix longer trails at low FPS
if((counter += Time.delta) >= 1f){
if(points.size > length*3){
points.removeRange(0, 2);
int count = (int)(counter += Time.delta);
counter -= count;
if(count > 0){
int toRemove = points.size + (count - 1 - length) * 3;
if(toRemove > 0 && points.size > 0){
points.removeRange(0, Math.min(toRemove - 1, points.size - 1));
}
points.add(x, y, width);
counter %= 1f;
//if lastX is -1, this trail has never updated, so only add one point - there is nothing to interpolate with
if(count == 1 || lastX == -1f){
points.add(x, y, width);
}else{
for(int i = 0; i < count; i++){
float f = (i + 1f) / count;
points.add(Mathf.lerp(lastX, x, f), Mathf.lerp(lastY, y, f), Mathf.lerp(lastW, width, f));
}
}
}
//update last position regardless, so it joins
//update last position regardless, so it joins at the origin
lastAngle = -Angles.angleRad(x, y, lastX, lastY);
lastX = x;
lastY = y;

View File

@@ -57,7 +57,7 @@ public class DesktopInput extends InputHandler{
public long lastCtrlGroupSelectMillis;
boolean showHint(){
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() &&
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() && !player.dead() &&
(!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore());
}
@@ -114,12 +114,12 @@ public class DesktopInput extends InputHandler{
//draw break selection
if(mode == breaking){
drawBreakSelection(selectX, selectY, cursorX, cursorY, !Core.input.keyDown(Binding.schematic_select) ? maxLength : Vars.maxSchematicSize, false);
drawBreakSelection(selectX, selectY, cursorX, cursorY, !(Core.input.keyDown(Binding.schematic_select) && schemX != -1 && schemY != -1) ? maxLength : Vars.maxSchematicSize, false);
}
if(!Core.scene.hasKeyboard() && mode != breaking){
if(Core.input.keyDown(Binding.schematic_select)){
if(Core.input.keyDown(Binding.schematic_select) && schemX != -1 && schemY != -1){
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
}else if(Core.input.keyDown(Binding.rebuild_select)){
drawRebuildSelection(schemX, schemY, cursorX, cursorY);
@@ -451,7 +451,7 @@ public class DesktopInput extends InputHandler{
cursorType = cursor.build.getCursor();
}
if(cursor.build != null && cursor.build.team == Team.derelict && Build.validPlace(cursor.block(), player.team(), cursor.build.tileX(), cursor.build.tileY(), cursor.build.rotation)){
if(cursor.build != null && player.team() != Team.derelict && cursor.build.team == Team.derelict && Build.validPlace(cursor.block(), player.team(), cursor.build.tileX(), cursor.build.tileY(), cursor.build.rotation)){
cursorType = ui.repairCursor;
}
@@ -568,11 +568,11 @@ public class DesktopInput extends InputHandler{
schematicY += shiftY;
}
if(Core.input.keyTap(Binding.deselect) && !isPlacing() && player.unit().plans.isEmpty() && !commandMode){
if(Core.input.keyTap(Binding.deselect) && !ui.minimapfrag.shown() && !isPlacing() && player.unit().plans.isEmpty() && !commandMode){
player.unit().mineTile = null;
}
if(Core.input.keyTap(Binding.clear_building)){
if(Core.input.keyTap(Binding.clear_building) && !player.dead()){
player.unit().clearBuilding();
}
@@ -594,7 +594,7 @@ public class DesktopInput extends InputHandler{
selectPlans.clear();
}
if( !Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
if(!Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
if(Core.input.keyRelease(Binding.schematic_select)){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
useSchematic(lastSchematic);

View File

@@ -83,6 +83,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean overrideLineRotation;
public int rotation;
public boolean droppingItem;
public float itemDepositCooldown;
public Group uiGroup;
public boolean isBuilding = true, buildWasAutoPaused = false, wasShooting = false;
public @Nullable UnitType controlledType;
@@ -115,7 +116,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
private WidgetGroup group = new WidgetGroup();
private final Eachable<BuildPlan> allPlans = cons -> {
player.unit().plans().each(cons);
if(!player.dead()){
player.unit().plans().each(cons);
}
selectPlans.each(cons);
linePlans.each(cons);
};
@@ -142,6 +145,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Events.on(ResetEvent.class, e -> {
logicCutscene = false;
itemDepositCooldown = 0f;
Arrays.fill(controlGroups, null);
});
}
@@ -234,9 +238,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public static void commandUnits(Player player, int[] unitIds, @Nullable Building buildTarget, @Nullable Unit unitTarget, @Nullable Vec2 posTarget, boolean queueCommand, boolean finalBatch){
if(player == null || unitIds == null) return;
//why did I ever think this was a good idea
if(unitTarget != null && unitTarget.isNull()) unitTarget = null;
if(net.server() && !netServer.admins.allowAction(player, ActionType.commandUnits, event -> {
event.unitIDs = unitIds;
})){
@@ -258,7 +259,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
if(teamTarget != null && teamTarget.team() != player.team() &&
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
anyCommandedTarget = true;
if(queueCommand){
@@ -280,7 +281,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(ai.commandQueue.size <= 0){
ai.group = null;
}
//remove when other player command
if(!headless && player != Vars.player){
control.input.selectedUnits.remove(unit);
@@ -311,18 +312,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
}
float minSpeed = 100000000f;
for(int i = 0; i < groups.length; i ++){
var group = groups[i];
if(group != null && group.units.size > 0){
group.calculateFormation(targetAsVec, i);
minSpeed = Math.min(group.minSpeed, minSpeed);
}
}
for(var group : groups){
if(group != null){
group.minSpeed = minSpeed;
}
}
}
@@ -399,7 +392,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(build == null || build.team() != player.team() || !build.block.commandable) continue;
build.onCommand(target);
build.lastAccessed = player.name;
build.updateLastAccess(player);
if(!state.isPaused() && player == Vars.player){
Fx.moveCommand.at(target);
@@ -412,7 +405,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Building build, Item item, int amount){
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, itemTransferRange) || player.dead()) return;
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, itemTransferRange) || player.dead() || amount <= 0) return;
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.withdrawItem, build.tile(), action -> {
@@ -431,6 +424,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(player == null || build == null || !player.within(build, itemTransferRange) || build.items == null || player.dead() || (state.rules.onlyDepositCore && !(build instanceof CoreBuild))) return;
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, build) ||
//to avoid rejecting deposit packets that happen to overlap due to packet speed differences, the actual cap is double the cooldown with 2 deposits.
(!player.isLocal() && !player.itemDepositRate.allow((long)(state.rules.itemDepositCooldown * 1000 * 2), 2)) ||
!netServer.admins.allowAction(player, ActionType.depositItem, build.tile, action -> {
action.itemAmount = player.unit().stack.amount;
action.item = player.unit().item();
@@ -600,11 +596,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(build == null) return;
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.rotate, build.tile(), action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
!netServer.admins.allowAction(player, ActionType.rotate, build.tile(), action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
throw new ValidateException(player, "Player cannot rotate a block.");
}
if(player != null) build.lastAccessed = player.name;
if(player != null) build.updateLastAccess(player);
int previous = build.rotation;
build.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4);
build.updateProximity();
@@ -619,7 +615,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(build == null) return;
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.configure, build.tile, action -> action.config = value))){
!netServer.admins.allowAction(player, ActionType.configure, build.tile, action -> action.config = value))){
if(player.con != null){
var packet = new TileConfigCallPacket(); //undo the config on the client
@@ -698,7 +694,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.unit(unit);
if(before != null && !before.isNull()){
if(before != null){
if(before.spawnedByCore){
unit.dockedType = before.type;
}else if(before.dockedType != null && before.dockedType.coreUnitDock){
@@ -804,6 +800,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
logicCutsceneZoom = -1f;
}
itemDepositCooldown -= Time.delta / 60f;
commandBuildings.removeAll(b -> !b.isValid());
if(!commandMode){
@@ -811,7 +809,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
playerPlanTree.clear();
player.unit().plans.each(playerPlanTree::insert);
if(!player.dead()){
player.unit().plans.each(playerPlanTree::insert);
}
player.typing = ui.chatfrag.shown();
@@ -823,7 +823,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.unit().updateBuilding(isBuilding);
}
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && !player.team().rules().infiniteAmmo && player.unit().ammo <= 0){
if(!player.dead() && player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && !player.team().rules().infiniteAmmo && player.unit().ammo <= 0){
player.unit().type.weapons.first().noAmmoSound.at(player.unit());
}
@@ -1012,6 +1012,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(attack != null){
Events.fire(Trigger.unitCommandAttack);
}else{
Events.fire(Trigger.unitCommandPosition);
}
int maxChunkSize = 200;
@@ -1179,7 +1181,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
var last = lastSchematic;
ui.showTextInput("@schematic.add", "@name", "", text -> {
ui.showTextInput("@schematic.add", "@name", 1000, "", text -> {
Schematic replacement = schematics.all().find(s -> s.name().equals(text));
if(replacement != null){
ui.showConfirm("@confirm", "@schematic.replace", () -> {
@@ -1661,7 +1663,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
boolean tryRepairDerelict(Tile selected){
if(selected != null && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict && Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){
if(selected != null && player.team() != Team.derelict && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict && Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){
player.unit().addBuild(new BuildPlan(selected.build.tileX(), selected.build.tileY(), selected.build.rotation, selected.block(), selected.build.config()));
return true;
}
@@ -1670,9 +1672,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
boolean canMine(Tile tile){
return !Core.scene.hasMouse()
&& player.unit().validMine(tile)
&& player.unit().acceptsItem(player.unit().getMineResult(tile))
&& !((!Core.settings.getBool("doubletapmine") && tile.floor().playerUnmineable) && tile.overlay().itemDrop == null);
&& player.unit().validMine(tile)
&& player.unit().acceptsItem(player.unit().getMineResult(tile))
&& !((!Core.settings.getBool("doubletapmine") && tile.floor().playerUnmineable) && tile.overlay().itemDrop == null);
}
/** Returns the tile at the specified MOUSE coordinates. */
@@ -1838,7 +1840,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean canShoot(){
return block == null && !onConfigurable() && !isDroppingItem() && !player.unit().activelyBuilding() &&
!(player.unit() instanceof Mechc && player.unit().isFlying()) && !player.unit().mining() && !commandMode;
!(player.unit() instanceof Mechc && player.unit().isFlying()) && !player.unit().mining() && !commandMode;
}
public boolean onConfigurable(){
@@ -1864,9 +1866,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
ItemStack stack = player.unit().stack;
if(build != null && build.acceptStack(stack.item, stack.amount, player.unit()) > 0 && build.interactable(player.team()) &&
build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){
if(!(state.rules.onlyDepositCore && !(build instanceof CoreBuild))){
build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){
if(!(state.rules.onlyDepositCore && !(build instanceof CoreBuild)) && itemDepositCooldown <= 0f){
Call.transferInventory(player, build);
itemDepositCooldown = state.rules.itemDepositCooldown;
}
}else{
Call.dropItem(player.angleTo(x, y));

View File

@@ -90,7 +90,7 @@ public class MobileInput extends InputHandler implements GestureListener{
void checkTargets(float x, float y){
Unit unit = Units.closestEnemy(player.team(), x, y, 20f, u -> !u.dead);
if(unit != null && player.unit().type.canAttack){
if(unit != null && !player.dead() && player.unit().type.canAttack){
player.unit().mineTile = null;
target = unit;
}else{
@@ -126,18 +126,21 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
for(var plan : player.unit().plans()){
Tile other = world.tile(plan.x, plan.y);
if(!player.dead()){
for(var plan : player.unit().plans()){
Tile other = world.tile(plan.x, plan.y);
if(other == null || plan.breaking) continue;
if(other == null || plan.breaking) continue;
r1.setSize(plan.block.size * tilesize);
r1.setCenter(other.worldx() + plan.block.offset, other.worldy() + plan.block.offset);
r1.setSize(plan.block.size * tilesize);
r1.setCenter(other.worldx() + plan.block.offset, other.worldy() + plan.block.offset);
if(r2.overlaps(r1)){
return true;
if(r2.overlaps(r1)){
return true;
}
}
}
return false;
}
@@ -263,7 +266,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
boolean showCancel(){
return (player.unit().isBuilding() || block != null || mode == breaking || !selectPlans.isEmpty()) && !hasSchem();
return !player.dead() && (player.unit().isBuilding() || block != null || mode == breaking || !selectPlans.isEmpty()) && !hasSchem();
}
boolean hasSchem(){
@@ -277,7 +280,9 @@ public class MobileInput extends InputHandler implements GestureListener{
t.visible(this::showCancel);
t.bottom().left();
t.button("@cancel", Icon.cancel, () -> {
player.unit().clearBuilding();
if(!player.dead()){
player.unit().clearBuilding();
}
selectPlans.clear();
mode = none;
block = null;
@@ -864,7 +869,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
if(player.shooting && (player.unit().activelyBuilding() || player.unit().mining())){
if(player.shooting && !player.dead() && (player.unit().activelyBuilding() || player.unit().mining())){
player.shooting = false;
}
}
@@ -1037,7 +1042,7 @@ public class MobileInput extends InputHandler implements GestureListener{
unit.movePref(movement);
//update shooting if not building + not mining
if(!player.unit().activelyBuilding() && player.unit().mineTile == null){
if(!unit.activelyBuilding() && unit.mineTile == null){
//autofire targeting
if(manualShooting){
@@ -1046,7 +1051,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else if(target == null){
player.shooting = false;
if(Core.settings.getBool("autotarget") && !(player.unit() instanceof BlockUnitUnit u && u.tile() instanceof ControlBlock c && !c.shouldAutoTarget())){
if(player.unit().type.canAttack){
if(unit.type.canAttack){
target = Units.closestTarget(unit.team, unit.x, unit.y, range, u -> u.checkTarget(type.targetAir, type.targetGround), u -> type.targetGround);
}

View File

@@ -279,7 +279,7 @@ public class TypeIO{
}
public static void writeUnit(Writes write, Unit unit){
write.b(unit == null || unit.isNull() ? 0 : unit instanceof BlockUnitc ? 1 : 2);
write.b(unit == null ? 0 : unit instanceof BlockUnitc ? 1 : 2);
//block units are special
if(unit instanceof BlockUnitc){
@@ -295,15 +295,14 @@ public class TypeIO{
byte type = read.b();
int id = read.i();
//nothing
if(type == 0) return Nulls.unit;
if(type == 0) return null;
if(type == 2){ //standard unit
Unit unit = Groups.unit.getByID(id);
return unit == null ? Nulls.unit : unit;
return Groups.unit.getByID(id);
}else if(type == 1){ //block
Building tile = world.build(id);
return tile instanceof ControlBlock cont ? cont.unit() : Nulls.unit;
return tile instanceof ControlBlock cont ? cont.unit() : null;
}
return Nulls.unit;
return null;
}
public static void writeCommand(Writes write, @Nullable UnitCommand command){
@@ -628,7 +627,7 @@ public class TypeIO{
}
public static KickReason readKick(Reads read){
return KickReason.values()[read.b()];
return KickReason.all[read.b()];
}
public static void writeMarkerControl(Writes write, LMarkerControl reason){
@@ -786,7 +785,7 @@ public class TypeIO{
}
public static AdminAction readAction(Reads read){
return AdminAction.values()[read.b()];
return AdminAction.all[read.b()];
}
public static void writeUnitType(Writes write, UnitType effect){
@@ -1038,14 +1037,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 +1063,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

@@ -7,10 +7,8 @@ import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.logic.LExecutor.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.legacy.*;
@@ -27,12 +25,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;
@@ -118,13 +115,14 @@ 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);
}
for(Weather weather : Vars.content.weathers()){
put("@" + weather.name, weather);
}
//store sensor constants
for(LAccess sensor : LAccess.all){
put("@" + sensor.name(), sensor);
@@ -168,31 +166,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;
}
}
@@ -213,84 +211,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

@@ -17,6 +17,7 @@ public enum LAccess{
powerNetOut,
ammo,
ammoCapacity,
currentAmmoType,
health,
maxHealth,
heat,
@@ -28,6 +29,8 @@ public enum LAccess{
rotation,
x,
y,
velocityX,
velocityY,
shootX,
shootY,
cameraX,
@@ -35,6 +38,7 @@ public enum LAccess{
cameraWidth,
cameraHeight,
size,
solid,
dead,
range,
shooting,
@@ -67,7 +71,7 @@ public enum LAccess{
all = values(),
senseable = Seq.select(all, t -> t.params.length <= 1).toArray(LAccess.class),
controls = Seq.select(all, t -> t.params.length > 0).toArray(LAccess.class),
settable = {x, y, rotation, speed, armor, health, team, flag, totalPower, payloadType};
settable = {x, y, velocityX, velocityY, rotation, speed, armor, health, shield, team, flag, totalPower, payloadType};
LAccess(String... params){
this.params = params;
@@ -78,5 +82,4 @@ public enum LAccess{
this.params = params;
isObj = obj;
}
}

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 +
'}';
}
}
}

View File

@@ -164,6 +164,12 @@ public class LCanvas extends Table{
this.statements.layout();
}
public void clearStatements(){
jumps.clear();
statements.clearChildren();
statements.layout();
}
StatementElem checkHovered(){
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e != null){

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ public enum LMarkerControl{
stroke("stroke"),
rotation("rotation"),
shape("sides", "fill", "outline"),
arc("start", "end"),
flushText("fetch"),
fontSize("size"),
textHeight("height"),

View File

@@ -16,7 +16,7 @@ import mindustry.logic.LCanvas.*;
import mindustry.logic.LExecutor.*;
import mindustry.ui.*;
import static mindustry.Vars.ui;
import static mindustry.Vars.*;
import static mindustry.logic.LCanvas.*;
/**

View File

@@ -260,6 +260,13 @@ public class LStatements{
}, 2, cell -> cell.size(165, 50)));
}, Styles.logict, () -> {}).size(165, 40).color(s.color).left().padLeft(2);
}
case translate, scale -> {
fields(s, "x", x, v -> x = v);
fields(s, "y", y, v -> y = v);
}
case rotate -> {
fields(s, "degrees", p1, v -> p1 = v);
}
}
}).expand().left();
}
@@ -274,8 +281,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
@@ -1038,7 +1045,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
@@ -1373,6 +1380,115 @@ public class LStatements{
}
}
@RegisterStatement("weathersense")
public static class WeatherSenseStatement extends LStatement{
public String to = "result";
public String weather = "@rain";
private transient TextField tfield;
@Override
public void build(Table table){
field(table, to, str -> to = str);
table.add(" = weather ");
row(table);
tfield = field(table, weather, str -> weather = str).padRight(0f).get();
table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> showSelectTable(b, (t, hide) -> {
t.row();
t.table(i -> {
i.left();
int c = 0;
for(Weather w : Vars.content.weathers()){
i.button(w.name, Styles.flatt, () -> {
weather = "@" + w.name;
tfield.setText(weather);
hide.run();
}).height(40f).uniformX().wrapLabel(false).growX();
if(++c % 2 == 0) i.row();
}
}).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
return new SenseWeatherI(builder.var(weather), builder.var(to));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
@RegisterStatement("weatherset")
public static class WeatherSetStatement extends LStatement{
public String weather = "@rain", state = "true";
private transient TextField tfield;
@Override
public void build(Table table){
table.add(" set weather ");
tfield = field(table, weather, str -> weather = str).padRight(0f).get();
table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> showSelectTable(b, (t, hide) -> {
t.row();
t.table(i -> {
i.left();
int c = 0;
for(Weather w : Vars.content.weathers()){
i.button(w.name, Styles.flatt, () -> {
weather = "@" + w.name;
tfield.setText(weather);
hide.run();
}).height(40f).uniformX().wrapLabel(false).growX();
if(++c % 2 == 0) i.row();
}
}).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
table.add(" state ");
fields(table, state, str -> state = str);
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
return new SetWeatherI(builder.var(weather), builder.var(state));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
@RegisterStatement("spawnwave")
public static class SpawnWaveStatement extends LStatement{
public String x = "10", y = "10", natural = "false";
@@ -1740,11 +1856,17 @@ public class LStatements{
fields(table, index, i -> index = i);
}
if(type == FetchType.buildCount || type == FetchType.build || type == FetchType.unitCount){
if(type == FetchType.buildCount || type == FetchType.build){
row(table);
fields(table, "block", extra, i -> extra = i);
}
if(type == FetchType.unitCount || type == FetchType.unit){
row(table);
fields(table, "unit", extra, i -> extra = i);
}
}
@Override
@@ -1789,6 +1911,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, reliable, str -> reliable = 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

@@ -17,6 +17,7 @@ import mindustry.logic.LExecutor.*;
import mindustry.logic.LStatements.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.blocks.logic.*;
import static mindustry.Vars.*;
import static mindustry.logic.LCanvas.*;
@@ -52,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 :
@@ -66,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" :
@@ -92,11 +93,29 @@ public class LogicDialog extends BaseDialog{
TextButtonStyle style = Styles.flatt;
t.defaults().size(280f, 60f).left();
if(privileged && executor != null && executor.build != null && !ui.editor.isShown()){
t.button("@editor.worldprocessors.editname", Icon.edit, style, () -> {
ui.showTextInput("", "@editor.name", LogicBlock.maxNameLength, executor.build.tag == null ? "" : executor.build.tag, tag -> {
if(privileged && executor != null && executor.build != null){
executor.build.configure(tag);
//just in case of privilege shenanigans...
executor.build.tag = tag;
}
});
dialog.hide();
}).marginLeft(12f).row();
}
t.button("@clear", Icon.cancel, style, () -> {
ui.showConfirm("@logic.clear.confirm", () -> canvas.clearStatements());
dialog.hide();
}).marginLeft(12f).row();
t.button("@schematic.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(canvas.save());
}).marginLeft(12f);
t.row();
}).marginLeft(12f).row();
t.button("@schematic.copy.import", Icon.download, style, () -> {
dialog.hide();
try{

View File

@@ -15,6 +15,7 @@ public enum LogicRule{
lighting,
ambientLight,
solarMultiplier,
dragMultiplier,
ban,
unban,

Some files were not shown because too many files have changed in this diff Show More