Merge branch 'master' into mod-dependencies
This commit is contained in:
@@ -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());
|
||||
|
||||
|
||||
@@ -137,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 */
|
||||
@@ -147,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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -168,9 +168,9 @@ public class UnitGroup{
|
||||
Unit unit = units.get(index);
|
||||
|
||||
PathCost cost = unit.type.pathCost;
|
||||
int res = ControlPathfinder.raycastFast(unit.team.id, cost, World.toTile(dest.x), World.toTile(dest.y), World.toTile(x), World.toTile(y));
|
||||
int res = ControlPathfinder.raycastFastAvoid(unit.team.id, cost, World.toTile(dest.x), World.toTile(dest.y), World.toTile(x), World.toTile(y));
|
||||
|
||||
//collision found, make th destination the point right before the collision
|
||||
//collision found, make the destination the point right before the collision
|
||||
if(res != 0){
|
||||
v1.set(Point2.x(res) * Vars.tilesize - dest.x, Point2.y(res) * Vars.tilesize - dest.y);
|
||||
v1.setLength(Math.max(v1.len() - Vars.tilesize - 4f, 0));
|
||||
|
||||
@@ -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)){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -205,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);
|
||||
@@ -221,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;
|
||||
@@ -248,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);
|
||||
|
||||
@@ -266,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,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;
|
||||
}
|
||||
|
||||
@@ -432,7 +443,6 @@ public class CommandAI extends AIController{
|
||||
public void commandTarget(Teamc moveTo, boolean stopAtTarget){
|
||||
attackTarget = moveTo;
|
||||
this.stopAtTarget = stopAtTarget;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -2556,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;
|
||||
@@ -4108,7 +4108,7 @@ public class Blocks{
|
||||
rotateSpeed = 3f;
|
||||
|
||||
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
|
||||
limitRange(16f);
|
||||
limitRange(25f);
|
||||
}};
|
||||
|
||||
sublimate = new ContinuousLiquidTurret("sublimate"){{
|
||||
@@ -4152,6 +4152,7 @@ public class Blocks{
|
||||
|
||||
liquidConsumed = 10f / 60f;
|
||||
targetInterval = 5f;
|
||||
newTargetInterval = 30f;
|
||||
targetUnderBlocks = false;
|
||||
|
||||
float r = range = 130f;
|
||||
@@ -4242,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));
|
||||
@@ -4379,6 +4382,7 @@ public class Blocks{
|
||||
trailInterval = 3f;
|
||||
trailParam = 4f;
|
||||
pierceCap = 2;
|
||||
buildingDamageMultiplier = 0.5f;
|
||||
fragOnHit = false;
|
||||
speed = 5f;
|
||||
damage = 180f;
|
||||
@@ -4462,6 +4466,8 @@ public class Blocks{
|
||||
heatRequirement = 10f;
|
||||
maxHeatEfficiency = 2f;
|
||||
|
||||
newTargetInterval = 40f;
|
||||
|
||||
inaccuracy = 1f;
|
||||
shake = 2f;
|
||||
shootY = 4;
|
||||
@@ -4684,6 +4690,8 @@ public class Blocks{
|
||||
shootSound = Sounds.missileLaunch;
|
||||
|
||||
minWarmup = 0.94f;
|
||||
newTargetInterval = 40f;
|
||||
unitSort = UnitSorts.strongest;
|
||||
shootWarmupSpeed = 0.03f;
|
||||
targetAir = false;
|
||||
targetUnderBlocks = false;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -171,6 +171,13 @@ public class ContentLoader{
|
||||
|
||||
public void handleMappableContent(MappableContent content){
|
||||
if(contentNameMap[content.getContentType().ordinal()].containsKey(content.name)){
|
||||
var list = contentMap[content.getContentType().ordinal()];
|
||||
|
||||
//this method is only called when registering content, and after handleContent.
|
||||
//If this is the last registered content, and it is invalid, make sure to remove it from the list to prevent invalid stuff from being registered
|
||||
if(list.size > 0 && list.peek() == content){
|
||||
list.pop();
|
||||
}
|
||||
throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')");
|
||||
}
|
||||
if(currentMod != null){
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -478,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);
|
||||
@@ -622,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
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(){
|
||||
}
|
||||
|
||||
@@ -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.*;
|
||||
@@ -554,6 +555,11 @@ public class Renderer implements ApplicationListener{
|
||||
landTime = landCore.landDuration();
|
||||
launchCoreType = coreType;
|
||||
|
||||
Music music = landCore.launchMusic();
|
||||
music.stop();
|
||||
music.play();
|
||||
music.setVolume(settings.getInt("musicvol") / 100f);
|
||||
|
||||
landCore.beginLaunch(coreType);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
Core.scene.draw();
|
||||
|
||||
if(Core.input.keyTap(KeyCode.mouseLeft) && Core.scene.hasField()){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(!(e instanceof TextField)){
|
||||
Core.scene.setKeyboardFocus(null);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
@Override
|
||||
public void loadIcon(){
|
||||
fullIcon =
|
||||
Core.atlas.find(fullOverride,
|
||||
Core.atlas.find(fullOverride == null ? "" : fullOverride,
|
||||
Core.atlas.find(getContentType().name() + "-" + name + "-full",
|
||||
Core.atlas.find(name + "-full",
|
||||
Core.atlas.find(name,
|
||||
@@ -147,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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
155
core/src/mindustry/editor/MapProcessorsDialog.java
Normal file
155
core/src/mindustry/editor/MapProcessorsDialog.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -329,7 +329,7 @@ public class MapView extends Element implements GestureListener{
|
||||
return Core.scene != null && Core.scene.getKeyboardFocus() != null
|
||||
&& Core.scene.getKeyboardFocus().isDescendantOf(ui.editor)
|
||||
&& ui.editor.isShown() && tool == EditorTool.zoom &&
|
||||
Core.scene.hit(Core.input.mouse().x, Core.input.mouse().y, true) == this;
|
||||
Core.scene.getHoverElement() == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -247,4 +247,4 @@ public class EntityCollisions{
|
||||
public interface SolidPred{
|
||||
boolean solid(int x, int y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -85,6 +79,14 @@ public class ForceFieldAbility extends Ability{
|
||||
|
||||
@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);
|
||||
|
||||
@@ -31,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();
|
||||
@@ -60,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;
|
||||
|
||||
@@ -109,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){
|
||||
|
||||
@@ -48,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;
|
||||
|
||||
@@ -309,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;
|
||||
@@ -338,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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -40,7 +40,7 @@ abstract class BlockUnitComp implements Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public TextureRegion icon(){
|
||||
return tile.block.fullIcon;
|
||||
return tile.block.uiIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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!
|
||||
@@ -1359,6 +1370,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Called when the block is destroyed. The tile is still intact at this stage. */
|
||||
public void onDestroyed(){
|
||||
if(sound != null){
|
||||
sound.stop();
|
||||
}
|
||||
|
||||
float explosiveness = block.baseExplosiveness;
|
||||
float flammability = 0f;
|
||||
float power = 0f;
|
||||
@@ -1773,6 +1788,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;
|
||||
}
|
||||
@@ -1780,6 +1796,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//disabled -> nothing works
|
||||
if(!enabled){
|
||||
potentialEfficiency = efficiency = optionalEfficiency = 0f;
|
||||
shouldConsumePower = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1789,10 +1806,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
|
||||
@@ -1915,6 +1939,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;
|
||||
@@ -1981,9 +2006,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 -> {
|
||||
@@ -2060,6 +2086,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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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){
|
||||
|
||||
|
||||
@@ -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. **/
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class RegionPart extends DrawPart{
|
||||
public String suffix = "";
|
||||
/** Overrides suffix if set. */
|
||||
public @Nullable String name;
|
||||
public TextureRegion heat;
|
||||
public TextureRegion heat, light;
|
||||
public TextureRegion[] regions = {};
|
||||
public TextureRegion[] outlines = {};
|
||||
|
||||
@@ -133,7 +133,7 @@ public class RegionPart extends DrawPart{
|
||||
float hprog = heatProgress.getClamp(params);
|
||||
heatColor.write(Tmp.c1).a(hprog * heatColor.a);
|
||||
Drawf.additive(heat, Tmp.c1, rx, ry, rot, turretShading ? turretHeatLayer : Draw.z() + heatLayerOffset);
|
||||
if(heatLight) Drawf.light(rx, ry, heat, rot, Tmp.c1, heatLightOpacity * hprog);
|
||||
if(heatLight) Drawf.light(rx, ry, light.found() ? light : heat, rot, Tmp.c1, heatLightOpacity * hprog);
|
||||
}
|
||||
|
||||
Draw.xscl *= sign;
|
||||
@@ -187,6 +187,7 @@ public class RegionPart extends DrawPart{
|
||||
}
|
||||
|
||||
heat = Core.atlas.find(realName + "-heat");
|
||||
light = Core.atlas.find(realName + "-light");
|
||||
for(var child : children){
|
||||
child.turretShading = turretShading;
|
||||
child.load(name);
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
|
||||
@@ -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. */
|
||||
@@ -103,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. */
|
||||
@@ -201,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(){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -106,9 +106,9 @@ public class Trail{
|
||||
int count = (int)(counter += Time.delta);
|
||||
counter -= count;
|
||||
|
||||
if(points.size + ((count - 1) * 3) > length * 3 && points.size > 0){
|
||||
points.removeRange(0, Math.min(3 * count - 1, points.size - 1));
|
||||
}
|
||||
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. */
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -108,18 +108,22 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
@Override
|
||||
public void drawTop(){
|
||||
if(cursorType != SystemCursor.arrow && scene.hasMouse()){
|
||||
graphics.cursor(cursorType = SystemCursor.arrow);
|
||||
}
|
||||
|
||||
Lines.stroke(1f);
|
||||
int cursorX = tileX(Core.input.mouseX());
|
||||
int cursorY = tileY(Core.input.mouseY());
|
||||
|
||||
//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);
|
||||
@@ -446,6 +450,8 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
||||
|
||||
cursorType = SystemCursor.arrow;
|
||||
|
||||
if(cursor != null){
|
||||
if(cursor.build != null && cursor.build.interactable(player.team())){
|
||||
cursorType = cursor.build.getCursor();
|
||||
@@ -498,9 +504,9 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
if(!Core.scene.hasMouse()){
|
||||
Core.graphics.cursor(cursorType);
|
||||
}else{
|
||||
cursorType = SystemCursor.arrow;
|
||||
}
|
||||
|
||||
cursorType = SystemCursor.arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -572,7 +578,7 @@ public class DesktopInput extends InputHandler{
|
||||
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 +600,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);
|
||||
@@ -668,7 +674,7 @@ public class DesktopInput extends InputHandler{
|
||||
lastLineY = cursorY;
|
||||
mode = placing;
|
||||
updateLine(selectX, selectY);
|
||||
}else if(plan != null && !plan.breaking && mode == none && !plan.initialized){
|
||||
}else if(plan != null && !plan.breaking && mode == none && !plan.initialized && plan.progress <= 0f){
|
||||
splan = plan;
|
||||
}else if(plan != null && plan.breaking){
|
||||
deleting = true;
|
||||
|
||||
@@ -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);
|
||||
@@ -404,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 -> {
|
||||
@@ -423,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();
|
||||
@@ -592,7 +596,7 @@ 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.");
|
||||
}
|
||||
|
||||
@@ -611,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
|
||||
@@ -690,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){
|
||||
@@ -796,6 +800,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
logicCutsceneZoom = -1f;
|
||||
}
|
||||
|
||||
itemDepositCooldown -= Time.delta / 60f;
|
||||
|
||||
commandBuildings.removeAll(b -> !b.isValid());
|
||||
|
||||
if(!commandMode){
|
||||
@@ -803,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();
|
||||
|
||||
@@ -815,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());
|
||||
}
|
||||
|
||||
@@ -1664,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. */
|
||||
@@ -1832,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(){
|
||||
@@ -1858,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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.*;
|
||||
import arc.audio.*;
|
||||
import arc.files.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.logic.LExecutor.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.legacy.*;
|
||||
@@ -27,15 +27,16 @@ 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, varWait, 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;
|
||||
|
||||
|
||||
public static final Seq<String> soundNames = new Seq<>();
|
||||
|
||||
public void init(){
|
||||
putEntryOnly("sectionProcessor");
|
||||
|
||||
@@ -72,6 +73,7 @@ public class GlobalVars{
|
||||
|
||||
varMapW = putEntry("@mapw", 0);
|
||||
varMapH = putEntry("@maph", 0);
|
||||
varWait = putEntry("@wait", null);
|
||||
|
||||
putEntryOnly("sectionNetwork");
|
||||
|
||||
@@ -89,6 +91,17 @@ public class GlobalVars{
|
||||
put("@ctrlProcessor", ctrlProcessor);
|
||||
put("@ctrlPlayer", ctrlPlayer);
|
||||
put("@ctrlCommand", ctrlCommand);
|
||||
|
||||
//sounds
|
||||
if(Core.assets != null){
|
||||
for(Sound sound : Core.assets.getAll(Sound.class, new Seq<>(Sound.class))){
|
||||
if(sound != Sounds.none && sound != Sounds.swish && sound.file != null){
|
||||
String name = sound.file.nameWithoutExtension();
|
||||
soundNames.add(name);
|
||||
put("@sfx-" + name, Sounds.getSoundId(sound));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//store base content
|
||||
|
||||
@@ -118,9 +131,6 @@ public class GlobalVars{
|
||||
put("@color" + Strings.capitalize(entry.key), entry.value.toDoubleBits());
|
||||
}
|
||||
|
||||
//used as a special value for any environmental solid block
|
||||
put("@solid", Blocks.stoneWall);
|
||||
|
||||
for(UnitType type : Vars.content.units()){
|
||||
put("@" + type.name, type);
|
||||
}
|
||||
@@ -172,34 +182,38 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
public LVar waitVar(){
|
||||
return varWait;
|
||||
}
|
||||
|
||||
public Seq<VarEntry> getEntries(){
|
||||
return varEntries;
|
||||
}
|
||||
@@ -217,84 +231,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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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, shield, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,20 +10,18 @@ import mindustry.logic.LExecutor.*;
|
||||
/** "Compiles" a sequence of statements into instructions. */
|
||||
public class LAssembler{
|
||||
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
|
||||
public static final int maxTokenLength = 36;
|
||||
|
||||
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 +55,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, privileged);
|
||||
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 +74,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 +101,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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +164,14 @@ 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);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(e != null){
|
||||
while(e != null && !(e instanceof StatementElem)){
|
||||
e = e.parent;
|
||||
@@ -251,7 +257,7 @@ public class LCanvas extends Table{
|
||||
}
|
||||
}
|
||||
|
||||
invalidateHierarchy();
|
||||
if(parent != null) parent.invalidateHierarchy();//don't invalid self
|
||||
|
||||
if(parent != null && parent instanceof Table){
|
||||
setCullingArea(parent.getCullingArea());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -103,7 +103,7 @@ public abstract class LStatement{
|
||||
|
||||
protected Cell<TextField> field(Table table, String value, Cons<String> setter){
|
||||
return table.field(value, Styles.nodeField, s -> setter.get(sanitize(s)))
|
||||
.size(144f, 40f).pad(2f).color(table.color).maxTextLength(LAssembler.maxTokenLength);
|
||||
.size(144f, 40f).pad(2f).color(table.color);
|
||||
}
|
||||
|
||||
protected Cell<TextField> fields(Table table, String desc, String value, Cons<String> setter){
|
||||
|
||||
@@ -281,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
|
||||
@@ -1045,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
|
||||
@@ -1594,7 +1594,7 @@ public class LStatements{
|
||||
@RegisterStatement("message")
|
||||
public static class FlushMessageStatement extends LStatement{
|
||||
public MessageType type = MessageType.announce;
|
||||
public String duration = "3", outSuccess = "success";
|
||||
public String duration = "3", outSuccess = "@wait";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
@@ -1616,9 +1616,11 @@ public class LStatements{
|
||||
case announce, toast -> {
|
||||
table.add(" for ");
|
||||
fields(table, duration, str -> duration = str);
|
||||
table.add(" secs ");
|
||||
table.add(" sec ");
|
||||
}
|
||||
}
|
||||
row(table);
|
||||
|
||||
table.add(" success ");
|
||||
fields(table, outSuccess, str -> outSuccess = str);
|
||||
}
|
||||
@@ -1911,6 +1913,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\"";
|
||||
@@ -2085,6 +2123,74 @@ public class LStatements{
|
||||
return LCategory.world;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("playsound")
|
||||
public static class PlaySoundStatement extends LStatement{
|
||||
public boolean positional;
|
||||
public String id = "@sfx-pew", volume = "1", pitch = "1", pan = "0", x = "@thisx", y = "@thisy", limit = "true";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
rebuild(table);
|
||||
}
|
||||
|
||||
void rebuild(Table table){
|
||||
table.clearChildren();
|
||||
|
||||
table.button(positional ? "positional" : "global", Styles.logict, () -> {
|
||||
positional = !positional;
|
||||
rebuild(table);
|
||||
}).size(160f, 40f).pad(4f).color(table.color);
|
||||
|
||||
row(table);
|
||||
|
||||
field(table, id, str -> id = str).padRight(0f).get();
|
||||
|
||||
table.button(b -> {
|
||||
b.image(Icon.pencilSmall);
|
||||
|
||||
String soundName = id.startsWith("@sfx-") ? id.substring(5) : id;
|
||||
b.clicked(() -> showSelect(b, GlobalVars.soundNames.toArray(String.class), soundName, t -> {
|
||||
id = "@sfx-" + t;
|
||||
rebuild(table);
|
||||
}, 2, cell -> cell.size(160, 50)));
|
||||
}, Styles.logict, () -> {}).size(40).color(table.color).left().padLeft(-1);
|
||||
|
||||
row(table);
|
||||
|
||||
fieldst(table, "volume", volume, str -> volume = str);
|
||||
fieldst(table, "pitch", pitch, str -> pitch = str);
|
||||
|
||||
table.row();
|
||||
|
||||
if(positional){
|
||||
fieldst(table, "x", x, str -> x = str);
|
||||
|
||||
fieldst(table, "y", y, str -> y = str);
|
||||
}else{
|
||||
fieldst(table, "pan", pan, str -> pan = str);
|
||||
}
|
||||
|
||||
table.row();
|
||||
|
||||
fieldst(table, "limit", limit, str -> limit = str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean privileged(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new PlaySoundI(positional, builder.var(id), builder.var(volume), builder.var(pitch), builder.var(pan), builder.var(x), builder.var(y), builder.var(limit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.world;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("setmarker")
|
||||
public static class SetMarkerStatement extends LStatement{
|
||||
|
||||
108
core/src/mindustry/logic/LVar.java
Normal file
108
core/src/mindustry/logic/LVar.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
@@ -15,6 +15,7 @@ public enum LogicRule{
|
||||
lighting,
|
||||
ambientLight,
|
||||
solarMultiplier,
|
||||
dragMultiplier,
|
||||
ban,
|
||||
unban,
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class Maps{
|
||||
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
|
||||
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
|
||||
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new,
|
||||
EnemySpawnFilter::new, SpawnPathFilter::new
|
||||
EnemySpawnFilter::new, SpawnPathFilter::new, LogicFilter::new
|
||||
};
|
||||
|
||||
/** List of all built-in maps. Filenames only. */
|
||||
|
||||
@@ -27,7 +27,7 @@ public class BlendFilter extends GenerateFilter{
|
||||
|
||||
@Override
|
||||
public char icon(){
|
||||
return Iconc.blockSand;
|
||||
return Iconc.blockSandFloor;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
72
core/src/mindustry/maps/filters/LogicFilter.java
Normal file
72
core/src/mindustry/maps/filters/LogicFilter.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.maps.filters.FilterOption.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LogicFilter extends GenerateFilter{
|
||||
/** max available execution for logic filter */
|
||||
public static int maxInstructionsExecution = 500 * 500 * 25;
|
||||
public String code;
|
||||
public boolean loop;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return new FilterOption[]{
|
||||
new FilterOption(){
|
||||
final String name;
|
||||
{
|
||||
name = "code";
|
||||
}
|
||||
@Override
|
||||
public void build(Table table){
|
||||
table.button(b -> b.image(Icon.pencil).size(iconSmall), () -> {
|
||||
ui.logic.show(code, null, true, code -> LogicFilter.this.code = code);
|
||||
}).pad(4).margin(12f);
|
||||
|
||||
table.add("@filter.option." + name);
|
||||
}
|
||||
},
|
||||
new ToggleOption("loop", () -> loop, f -> loop = f)
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Tiles tiles, GenerateInput in){
|
||||
LExecutor executor = new LExecutor();
|
||||
executor.privileged = true;
|
||||
|
||||
try{
|
||||
//assembler has no variables, all the standard ones are null
|
||||
executor.load(LAssembler.assemble(code, true));
|
||||
}catch(Throwable ignored){
|
||||
//if loading code
|
||||
return;
|
||||
}
|
||||
|
||||
//this updates map width/height global variables
|
||||
logicVars.update();
|
||||
|
||||
//NOTE: all tile operations will call setNet for tiles, but that should have no overhead during world loading
|
||||
//executions are limited to prevent infinite generation
|
||||
for(int i = 1; i < maxInstructionsExecution; i++){
|
||||
if(!loop && (executor.counter.numval >= executor.instructions.length || executor.counter.numval < 0)) break;
|
||||
executor.runOnce();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char icon(){
|
||||
return Iconc.blockMicroProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPost(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class SpawnPathFilter extends GenerateFilter{
|
||||
|
||||
@Override
|
||||
public char icon(){
|
||||
return Iconc.blockCommandCenter;
|
||||
return Iconc.blockCoreZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -146,6 +146,11 @@ public class ContentParser{
|
||||
readFields(result, data);
|
||||
return result;
|
||||
});
|
||||
put(MassDriverBolt.class, (type, data) -> {
|
||||
MassDriverBolt result = (MassDriverBolt)make(MassDriverBolt.class);
|
||||
readFields(result, data);
|
||||
return result;
|
||||
});
|
||||
put(AmmoType.class, (type, data) -> {
|
||||
//string -> item
|
||||
//if liquid ammo support is added, this should scan for liquids as well
|
||||
@@ -322,6 +327,20 @@ public class ContentParser{
|
||||
readFields(consume, data);
|
||||
return consume;
|
||||
});
|
||||
put(Team.class, (type, data) -> {
|
||||
if(data.isString()){
|
||||
Team out = Structs.find(Team.baseTeams, t -> t.name.equals(data.asString()));
|
||||
if(out == null) throw new IllegalArgumentException("Unknown team: " + data.asString());
|
||||
return out;
|
||||
}else if(data.isNumber()){
|
||||
if(data.asInt() >= Team.all.length || data.asInt() < 0){
|
||||
throw new IllegalArgumentException("Unknown team: " + data.asString());
|
||||
}
|
||||
return Team.get(data.asInt());
|
||||
}else{
|
||||
throw new IllegalArgumentException("Unknown team: " + data.asString() + ". Team must either be a string or a number.");
|
||||
}
|
||||
});
|
||||
}};
|
||||
/** Stores things that need to be parsed fully, e.g. reading fields of content.
|
||||
* This is done to accommodate binding of content names first.*/
|
||||
@@ -462,6 +481,7 @@ public class ContentParser{
|
||||
case "itemFlammable" -> block.consume((Consume)parser.readValue(ConsumeItemFlammable.class, child));
|
||||
case "itemRadioactive" -> block.consume((Consume)parser.readValue(ConsumeItemRadioactive.class, child));
|
||||
case "itemExplosive" -> block.consume((Consume)parser.readValue(ConsumeItemExplosive.class, child));
|
||||
case "itemList" -> block.consume((Consume)parser.readValue(ConsumeItemList.class, child));
|
||||
case "itemExplode" -> block.consume((Consume)parser.readValue(ConsumeItemExplode.class, child));
|
||||
case "items" -> block.consume(child.isArray() ?
|
||||
new ConsumeItems(parser.readValue(ItemStack[].class, child)) :
|
||||
@@ -668,6 +688,27 @@ public class ContentParser{
|
||||
currentContent = planet;
|
||||
read(() -> readFields(planet, value));
|
||||
return planet;
|
||||
},
|
||||
ContentType.team, (TypeParser<TeamEntry>)(mod, name, value) -> {
|
||||
TeamEntry entry;
|
||||
Team team;
|
||||
if(value.has("team")){
|
||||
team = (Team)classParsers.get(Team.class).parse(Team.class, value.get("team"));
|
||||
}else{
|
||||
throw new RuntimeException("Team field missing.");
|
||||
}
|
||||
value.remove("team");
|
||||
|
||||
if(locate(ContentType.team, name) != null){
|
||||
entry = locate(ContentType.team, name);
|
||||
readBundle(ContentType.team, name, value);
|
||||
}else{
|
||||
readBundle(ContentType.team, name, value);
|
||||
entry = new TeamEntry(mod + "-" + name, team);
|
||||
}
|
||||
currentContent = entry;
|
||||
read(() -> readFields(entry, value));
|
||||
return entry;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -949,6 +990,8 @@ public class ContentParser{
|
||||
case "min" -> base.min(parser.readValue(PartProgress.class, data.get("other")));
|
||||
case "sin" -> base.sin(data.has("offset") ? data.getFloat("offset") : 0f, data.getFloat("scl"), data.getFloat("mag"));
|
||||
case "absin" -> base.absin(data.getFloat("scl"), data.getFloat("mag"));
|
||||
case "mod" -> base.mod(data.getFloat("amount"));
|
||||
case "loop" -> base.loop(data.getFloat("time"));
|
||||
case "curve" -> data.has("interp") ? base.curve(parser.readValue(Interp.class, data.get("interp"))) : base.curve(data.getFloat("offset"), data.getFloat("duration"));
|
||||
default -> throw new RuntimeException("Unknown operation '" + op + "', check PartProgress class for a list of methods.");
|
||||
};
|
||||
@@ -1054,17 +1097,25 @@ public class ContentParser{
|
||||
}
|
||||
Field field = metadata.field;
|
||||
try{
|
||||
boolean mergeMap = ObjectMap.class.isAssignableFrom(field.getType()) && child.has("add") && child.get("add").isBoolean() && child.getBoolean("add", false);
|
||||
boolean isMap = ObjectMap.class.isAssignableFrom(field.getType()) || ObjectIntMap.class.isAssignableFrom(field.getType()) || ObjectFloatMap.class.isAssignableFrom(field.getType());
|
||||
boolean mergeMap = isMap && child.has("add") && child.get("add").isBoolean() && child.getBoolean("add", false);
|
||||
|
||||
if(mergeMap){
|
||||
child.remove("add");
|
||||
}
|
||||
|
||||
Object readField = parser.readValue(field.getType(), metadata.elementType, child, metadata.keyType);
|
||||
Object fieldObj = field.get(object);
|
||||
|
||||
//if a map has add: true, add its contents to the map instead
|
||||
if(mergeMap && field.get(object) instanceof ObjectMap<?,?> baseMap){
|
||||
baseMap.putAll((ObjectMap)readField);
|
||||
if(mergeMap && (fieldObj instanceof ObjectMap<?,?> || fieldObj instanceof ObjectIntMap<?> || fieldObj instanceof ObjectFloatMap<?>)){
|
||||
if(field.get(object) instanceof ObjectMap<?,?> baseMap){
|
||||
baseMap.putAll((ObjectMap)readField);
|
||||
}else if(field.get(object) instanceof ObjectIntMap<?> baseMap){
|
||||
baseMap.putAll((ObjectIntMap)readField);
|
||||
}else if(field.get(object) instanceof ObjectFloatMap<?> baseMap){
|
||||
baseMap.putAll((ObjectFloatMap)readField);
|
||||
}
|
||||
}else{
|
||||
field.set(object, readField);
|
||||
}
|
||||
|
||||
@@ -871,6 +871,11 @@ public class Mods implements Loadable{
|
||||
Seq<LoadRun> runs = new Seq<>();
|
||||
|
||||
for(LoadedMod mod : orderedMods()){
|
||||
Seq<LoadRun> unorderedContent = new Seq<>();
|
||||
ObjectMap<String, LoadRun> orderedContent = new ObjectMap<>();
|
||||
String[] contentOrder = mod.meta.contentOrder;
|
||||
ObjectSet<String> orderSet = contentOrder == null ? null : ObjectSet.with(contentOrder);
|
||||
|
||||
if(mod.root.child("content").exists()){
|
||||
Fi contentRoot = mod.root.child("content");
|
||||
for(ContentType type : ContentType.all){
|
||||
@@ -878,15 +883,34 @@ public class Mods implements Loadable{
|
||||
Fi folder = contentRoot.child(lower + (lower.endsWith("s") ? "" : "s"));
|
||||
if(folder.exists()){
|
||||
for(Fi file : folder.findAll(f -> f.extension().equals("json") || f.extension().equals("hjson"))){
|
||||
runs.add(new LoadRun(type, file, mod));
|
||||
|
||||
//if this is part of the ordered content, put it aside to be dealt with later
|
||||
if(orderSet != null && orderSet.contains(file.nameWithoutExtension())){
|
||||
orderedContent.put(file.nameWithoutExtension(), new LoadRun(type, file, mod));
|
||||
}else{
|
||||
unorderedContent.add(new LoadRun(type, file, mod));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ordered content will be loaded first, if it exists
|
||||
if(contentOrder != null){
|
||||
for(String contentName : contentOrder){
|
||||
LoadRun run = orderedContent.get(contentName);
|
||||
if(run != null){
|
||||
runs.add(run);
|
||||
}else{
|
||||
Log.warn("Cannot find content defined in contentOrder: @", contentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//unordered content is sorted alphabetically per mod
|
||||
runs.addAll(unorderedContent.sort());
|
||||
}
|
||||
|
||||
//make sure mod content is in proper order
|
||||
runs.sort();
|
||||
for(LoadRun l : runs){
|
||||
Content current = content.getLastAdded();
|
||||
try{
|
||||
@@ -1357,6 +1381,8 @@ public class Mods implements Loadable{
|
||||
public float texturescale = 1.0f;
|
||||
/** If true, bleeding is skipped and no content icons are generated. */
|
||||
public boolean pregenerated;
|
||||
/** If set, load the mod content in this order by content names */
|
||||
public String[] contentOrder;
|
||||
|
||||
public String displayName(){
|
||||
//useless, kept for legacy reasons
|
||||
|
||||
@@ -17,6 +17,8 @@ public class Packets{
|
||||
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch,
|
||||
whitelist, playerLimit, serverRestarting;
|
||||
|
||||
public static final KickReason[] all = values();
|
||||
|
||||
public final boolean quiet;
|
||||
|
||||
KickReason(){
|
||||
@@ -38,7 +40,9 @@ public class Packets{
|
||||
}
|
||||
|
||||
public enum AdminAction{
|
||||
kick, ban, trace, wave, switchTeam
|
||||
kick, ban, trace, wave, switchTeam;
|
||||
|
||||
public static final AdminAction[] all = values();
|
||||
}
|
||||
|
||||
/** Generic client connection event. */
|
||||
|
||||
@@ -291,7 +291,7 @@ public class GameService{
|
||||
});
|
||||
|
||||
Events.on(SectorLaunchLoadoutEvent.class, e -> {
|
||||
if(!schematics.isDefaultLoadout(e.loadout)){
|
||||
if(e.sector.planet == Planets.serpulo && !schematics.isDefaultLoadout(e.loadout)){
|
||||
launchCoreSchematic.complete();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ public class StatusEffect extends UnlockableContent{
|
||||
boolean reacts = false;
|
||||
|
||||
for(var e : opposites.toSeq().sort()){
|
||||
stats.add(Stat.opposites, e.emoji() + "" + e);
|
||||
stats.add(Stat.opposites, e.emoji() + e);
|
||||
}
|
||||
|
||||
if(reactive){
|
||||
@@ -142,6 +142,11 @@ public class StatusEffect extends UnlockableContent{
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when status effect is removed. */
|
||||
public void onRemoved(Unit unit){
|
||||
|
||||
}
|
||||
|
||||
protected void trans(StatusEffect effect, TransitionHandler handler){
|
||||
transitions.put(effect, handler);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,15 @@ import mindustry.game.*;
|
||||
public class TeamEntry extends UnlockableContent{
|
||||
public final Team team;
|
||||
|
||||
public TeamEntry(Team team){
|
||||
super(team.name);
|
||||
public TeamEntry(String name, Team team){
|
||||
super(name);
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
public TeamEntry(Team team){
|
||||
this(team.name, team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayExtra(Table table){
|
||||
table.add("@team." + name + ".log").pad(6).padTop(20).width(400f).wrap().fillX();
|
||||
|
||||
@@ -141,8 +141,10 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
/** if true, this unit counts as an enemy in the wave counter (usually false for support-only units) */
|
||||
public boolean isEnemy = true,
|
||||
/** If true, the unit is always at elevation 1. */
|
||||
/** if true, the unit is always at elevation 1 */
|
||||
flying = false,
|
||||
/** whether this flying unit should wobble around */
|
||||
wobble = true,
|
||||
/** whether this unit tries to attack air units */
|
||||
targetAir = true,
|
||||
/** whether this unit tries to attack ground units */
|
||||
@@ -257,6 +259,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
public Color healColor = Pal.heal;
|
||||
/** Color of light that this unit produces when lighting is enabled in the map. */
|
||||
public Color lightColor = Pal.powerLight;
|
||||
/** override for unit shield colour. */
|
||||
public @Nullable Color shieldColor;
|
||||
/** sound played when this unit explodes (*not* when it is shot down) */
|
||||
public Sound deathSound = Sounds.bang;
|
||||
/** sound played on loop when this unit is around. */
|
||||
@@ -288,6 +292,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
/** Function used for calculating cost of moving with ControlPathfinder. Does not affect "normal" flow field pathfinding. */
|
||||
public @Nullable PathCost pathCost;
|
||||
/** ID for path cost, to be used in the control path finder. This is the value that actually matters; do not assign manually. Set in init(). */
|
||||
public int pathCostId;
|
||||
/** A sample of the unit that this type creates. Do not modify! */
|
||||
public @Nullable Unit sample;
|
||||
|
||||
@@ -427,10 +433,13 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
//(undocumented, you shouldn't need to use these, and if you do just check how they're drawn and copy that)
|
||||
public TextureRegion baseRegion, legRegion, region, previewRegion, shadowRegion, cellRegion, itemCircleRegion,
|
||||
softShadowRegion, jointRegion, footRegion, legBaseRegion, baseJointRegion, outlineRegion, treadRegion;
|
||||
softShadowRegion, jointRegion, footRegion, legBaseRegion, baseJointRegion, outlineRegion, treadRegion,
|
||||
mineLaserRegion, mineLaserEndRegion;
|
||||
public TextureRegion[] wreckRegions, segmentRegions, segmentOutlineRegions;
|
||||
public TextureRegion[][] treadRegions;
|
||||
|
||||
//INTERNAL REQUIREMENTS
|
||||
|
||||
protected float buildTime = -1f;
|
||||
protected @Nullable ItemStack[] totalRequirements, cachedRequirements, firstRequirements;
|
||||
|
||||
@@ -691,6 +700,9 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
ControlPathfinder.costGround;
|
||||
}
|
||||
|
||||
pathCostId = ControlPathfinder.costTypes.indexOf(pathCost);
|
||||
if(pathCostId == -1) pathCostId = 0;
|
||||
|
||||
if(flying){
|
||||
envEnabled |= Env.space;
|
||||
}
|
||||
@@ -816,6 +828,10 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
if(canBoost){
|
||||
cmds.add(UnitCommand.boostCommand);
|
||||
|
||||
if(buildSpeed > 0f){
|
||||
cmds.add(UnitCommand.rebuildCommand, UnitCommand.assistCommand);
|
||||
}
|
||||
}
|
||||
|
||||
//healing, mining and building is only supported for flying units; pathfinding to ambiguously reachable locations is hard.
|
||||
@@ -842,7 +858,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
if(stances.length == 0){
|
||||
if(canAttack){
|
||||
Seq<UnitStance> seq = Seq.with(UnitStance.stop, UnitStance.shoot, UnitStance.holdFire, UnitStance.pursueTarget, UnitStance.patrol);
|
||||
if(crushDamage > 0){
|
||||
if(!flying){
|
||||
seq.add(UnitStance.ram);
|
||||
}
|
||||
stances = seq.toArray(UnitStance.class);
|
||||
@@ -910,6 +926,9 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
legBaseRegion = Core.atlas.find(name + "-leg-base", name + "-leg");
|
||||
baseRegion = Core.atlas.find(name + "-base");
|
||||
cellRegion = Core.atlas.find(name + "-cell", Core.atlas.find("power-cell"));
|
||||
|
||||
mineLaserRegion = Core.atlas.find("minelaser");
|
||||
mineLaserEndRegion = Core.atlas.find("minelaser-end");
|
||||
//when linear filtering is on, it's acceptable to use the relatively low-res 'particle' region
|
||||
softShadowRegion =
|
||||
squareShape ? Core.atlas.find("square-shadow") :
|
||||
@@ -1186,6 +1205,10 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
public void draw(Unit unit){
|
||||
if(unit.inFogTo(Vars.player.team())) return;
|
||||
|
||||
unit.drawBuilding();
|
||||
|
||||
drawMining(unit);
|
||||
|
||||
boolean isPayload = !unit.isAdded();
|
||||
|
||||
Mechc mech = unit instanceof Mechc ? (Mechc)unit : null;
|
||||
@@ -1259,9 +1282,9 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
for(int i = 0; i < parts.size; i++){
|
||||
var part = parts.get(i);
|
||||
|
||||
WeaponMount first = unit.mounts.length > part.weaponIndex ? unit.mounts[part.weaponIndex] : null;
|
||||
if(first != null){
|
||||
DrawPart.params.set(first.warmup, first.reload / weapons.first().reload, first.smoothReload, first.heat, first.recoil, first.charge, unit.x, unit.y, unit.rotation);
|
||||
WeaponMount mount = unit.mounts.length > part.weaponIndex ? unit.mounts[part.weaponIndex] : null;
|
||||
if(mount != null){
|
||||
DrawPart.params.set(mount.warmup, mount.reload / mount.weapon.reload, mount.smoothReload, mount.heat, mount.recoil, mount.charge, unit.x, unit.y, unit.rotation);
|
||||
}else{
|
||||
DrawPart.params.set(0f, 0f, 0f, 0f, 0f, 0f, unit.x, unit.y, unit.rotation);
|
||||
}
|
||||
@@ -1288,6 +1311,38 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
//...where do I put this
|
||||
public Color shieldColor(Unit unit){
|
||||
return shieldColor == null ? unit.team.color : shieldColor;
|
||||
}
|
||||
|
||||
|
||||
public void drawMining(Unit unit){
|
||||
if(!unit.mining()) return;
|
||||
float focusLen = unit.hitSize / 2f + Mathf.absin(Time.time, 1.1f, 0.5f);
|
||||
float swingScl = 12f, swingMag = tilesize / 8f;
|
||||
float flashScl = 0.3f;
|
||||
|
||||
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
|
||||
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
|
||||
|
||||
float ex = unit.mineTile.worldx() + Mathf.sin(Time.time + 48, swingScl, swingMag);
|
||||
float ey = unit.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(mineLaserRegion, mineLaserEndRegion, px, py, ex, ey, 0.75f);
|
||||
|
||||
if(unit.isLocal()){
|
||||
Lines.stroke(1f, Pal.accent);
|
||||
Lines.poly(unit.mineTile.worldx(), unit.mineTile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time);
|
||||
}
|
||||
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
public <T extends Unit & Payloadc> void drawPayload(T unit){
|
||||
if(unit.hasPayload()){
|
||||
@@ -1302,7 +1357,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
float radius = unit.hitSize() * 1.3f;
|
||||
Fill.light(unit.x, unit.y, Lines.circleVertices(radius), radius,
|
||||
Color.clear,
|
||||
Tmp.c2.set(unit.team.color).lerp(Color.white, Mathf.clamp(unit.hitTime() / 2f)).a(0.7f * alpha)
|
||||
Tmp.c2.set(unit.type.shieldColor(unit)).lerp(Color.white, Mathf.clamp(unit.hitTime() / 2f)).a(0.7f * alpha)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1465,6 +1520,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
}
|
||||
|
||||
public <T extends Unit & Tankc> void drawTank(T unit){
|
||||
applyColor(unit);
|
||||
Draw.rect(treadRegion, unit.x, unit.y, unit.rotation - 90);
|
||||
|
||||
if(treadRegion.found()){
|
||||
@@ -1636,6 +1692,10 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
if(unit.drownTime > 0 && unit.lastDrownFloor != null){
|
||||
Draw.mixcol(Tmp.c1.set(unit.lastDrownFloor.mapColor).mul(0.83f), unit.drownTime * 0.9f);
|
||||
}
|
||||
//this is horribly scuffed.
|
||||
if(renderer != null && renderer.overlays != null){
|
||||
renderer.overlays.checkApplySelection(unit);
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
@@ -161,7 +161,7 @@ public class Weather extends UnlockableContent{
|
||||
Core.camera.bounds(Tmp.r2);
|
||||
int total = (int)(Tmp.r1.area() / density * intensity);
|
||||
Lines.stroke(stroke);
|
||||
float alpha = Draw.getColor().a;
|
||||
float alpha = Draw.getColorAlpha();
|
||||
Draw.color(color);
|
||||
|
||||
for(int i = 0; i < total; i++){
|
||||
|
||||
@@ -20,27 +20,21 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Fonts{
|
||||
private static final String mainFont = "fonts/font.woff";
|
||||
private static final ObjectSet<String> unscaled = ObjectSet.with("iconLarge");
|
||||
private static ObjectIntMap<String> unicodeIcons = new ObjectIntMap<>();
|
||||
private static IntMap<String> unicodeToName = new IntMap<>();
|
||||
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
|
||||
private static ObjectMap<String, TextureRegion> largeIcons = new ObjectMap<>();
|
||||
private static TextureRegion[] iconTable;
|
||||
private static int lastCid;
|
||||
|
||||
public static Font def, outline, icon, iconLarge, tech, logic;
|
||||
|
||||
public static TextureRegion logicIcon(int id){
|
||||
return iconTable[id];
|
||||
}
|
||||
|
||||
public static int getUnicode(String content){
|
||||
return unicodeIcons.get(content, 0);
|
||||
}
|
||||
@@ -95,12 +89,16 @@ public class Fonts{
|
||||
}})).loaded = f -> Fonts.logic = f;
|
||||
}
|
||||
|
||||
public static @Nullable String unicodeToName(int unicode){
|
||||
return unicodeToName.get(unicode, () -> Iconc.codeToName.get(unicode));
|
||||
}
|
||||
|
||||
public static TextureRegion getLargeIcon(String name){
|
||||
return largeIcons.get(name, () -> {
|
||||
var region = new TextureRegion();
|
||||
int code = Iconc.codes.get(name, '\uF8D4');
|
||||
var glyph = iconLarge.getData().getGlyph((char)code);
|
||||
if(glyph == null) return Core.atlas.find("error");
|
||||
if(glyph == null) return Core.atlas.find(name);
|
||||
region.set(iconLarge.getRegion().texture);
|
||||
region.set(glyph.u, glyph.v2, glyph.u2, glyph.v);
|
||||
return region;
|
||||
@@ -112,9 +110,9 @@ public class Fonts{
|
||||
Texture uitex = Core.atlas.find("logo").texture;
|
||||
int size = (int)(Fonts.def.getData().lineHeight/Fonts.def.getData().scaleY);
|
||||
|
||||
try(Scanner scan = new Scanner(Core.files.internal("icons/icons.properties").read(512))){
|
||||
while(scan.hasNextLine()){
|
||||
String line = scan.nextLine();
|
||||
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
String[] split = line.split("=");
|
||||
String[] nametex = split[1].split("\\|");
|
||||
String character = split[0], texture = nametex[1];
|
||||
@@ -127,6 +125,7 @@ public class Fonts{
|
||||
|
||||
unicodeIcons.put(nametex[0], ch);
|
||||
stringIcons.put(nametex[0], ((char)ch) + "");
|
||||
unicodeToName.put(ch, texture);
|
||||
|
||||
Vec2 out = Scaling.fit.apply(region.width, region.height, size, size);
|
||||
|
||||
@@ -148,32 +147,21 @@ public class Fonts{
|
||||
glyph.page = 0;
|
||||
fonts.each(f -> f.getData().setGlyph(ch, glyph));
|
||||
}
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));
|
||||
|
||||
iconTable = new TextureRegion[512];
|
||||
iconTable[0] = Core.atlas.find("error");
|
||||
lastCid = 1;
|
||||
|
||||
Vars.content.each(c -> {
|
||||
if(c instanceof UnlockableContent u){
|
||||
TextureRegion region = Core.atlas.find(u.name + "-icon-logic");
|
||||
if(region.found()){
|
||||
iconTable[u.iconId = lastCid++] = region;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
team.emoji = stringIcons.get(team.name, "");
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadContentIconsHeadless(){
|
||||
try(Scanner scan = new Scanner(Core.files.internal("icons/icons.properties").read(512))){
|
||||
while(scan.hasNextLine()){
|
||||
String line = scan.nextLine();
|
||||
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
String[] split = line.split("=");
|
||||
String[] nametex = split[1].split("\\|");
|
||||
String character = split[0];
|
||||
@@ -182,6 +170,8 @@ public class Fonts{
|
||||
unicodeIcons.put(nametex[0], ch);
|
||||
stringIcons.put(nametex[0], ((char)ch) + "");
|
||||
}
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));
|
||||
|
||||
@@ -3,9 +3,11 @@ package mindustry.ui;
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -20,6 +22,22 @@ public class Minimap extends Table{
|
||||
add(new Element(){
|
||||
{
|
||||
setSize(Scl.scl(140f));
|
||||
|
||||
addListener(new ClickListener(KeyCode.mouseRight){
|
||||
@Override
|
||||
public void clicked(InputEvent event, float cx, float cy){
|
||||
var region = renderer.minimap.getRegion();
|
||||
if(region == null) return;
|
||||
|
||||
float
|
||||
sx = (cx - x) / width,
|
||||
sy = (cy - y) / height,
|
||||
scaledX = Mathf.lerp(region.u, region.u2, sx) * world.width() * tilesize,
|
||||
scaledY = Mathf.lerp(1f - region.v2, 1f - region.v, sy) * world.height() * tilesize;
|
||||
|
||||
control.input.panCamera(Tmp.v1.set(scaledX, scaledY));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,7 +111,7 @@ public class Minimap extends Table{
|
||||
|
||||
update(() -> {
|
||||
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(e != null && e.isDescendantOf(this)){
|
||||
requestScroll();
|
||||
}else if(hasScroll()){
|
||||
|
||||
@@ -73,6 +73,8 @@ public class Styles{
|
||||
geni,
|
||||
/** Gray, toggleable, no background. */
|
||||
grayi,
|
||||
/** Gray square background, standard behavior. Equivalent to grayt. */
|
||||
graySquarei,
|
||||
/** Flat, square, black background. */
|
||||
flati,
|
||||
/** Square border. */
|
||||
@@ -288,6 +290,14 @@ public class Styles{
|
||||
imageUpColor = Color.lightGray;
|
||||
imageDownColor = Color.white;
|
||||
}};
|
||||
graySquarei = new ImageButtonStyle(){{
|
||||
imageUpColor = Color.white;
|
||||
imageDownColor = Color.lightGray;
|
||||
|
||||
over = flatOver;
|
||||
down = flatOver;
|
||||
up = grayPanel;
|
||||
}};
|
||||
flati = new ImageButtonStyle(){{
|
||||
down = flatOver;
|
||||
up = black;
|
||||
|
||||
@@ -30,16 +30,24 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
private Table main;
|
||||
private Prov<Rules> resetter;
|
||||
private LoadoutDialog loadoutDialog;
|
||||
|
||||
public boolean showRuleEditRule;
|
||||
public Seq<Table> categories;
|
||||
public Table current;
|
||||
public Seq<String> categoryNames;
|
||||
public String currentName;
|
||||
public String currentName = "";
|
||||
public String ruleSearch = "";
|
||||
public Seq<Runnable> additionalSetup; // for modding to easily add new rules
|
||||
|
||||
public CustomRulesDialog(){
|
||||
this(false);
|
||||
}
|
||||
|
||||
public CustomRulesDialog(boolean showRuleEditRule){
|
||||
super("@mode.custom");
|
||||
|
||||
this.showRuleEditRule = showRuleEditRule;
|
||||
|
||||
loadoutDialog = new LoadoutDialog();
|
||||
|
||||
setFillParent(true);
|
||||
@@ -49,8 +57,6 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
additionalSetup = new Seq<>();
|
||||
categories = new Seq<>();
|
||||
categoryNames = new Seq<>();
|
||||
currentName = "";
|
||||
ruleSearch = "";
|
||||
|
||||
buttons.button("@edit", Icon.pencil, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@waves.edit");
|
||||
@@ -209,12 +215,12 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
main.left().defaults().fillX().left();
|
||||
main.row();
|
||||
|
||||
|
||||
category("waves");
|
||||
check("@rules.waves", b -> rules.waves = b, () -> rules.waves);
|
||||
check("@rules.wavesending", b -> rules.waveSending = b, () -> rules.waveSending, () -> rules.waves);
|
||||
check("@rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer, () -> rules.waves);
|
||||
check("@rules.waitForWaveToEnd", b -> rules.waitEnemies = b, () -> rules.waitEnemies, () -> rules.waves && rules.waveTimer);
|
||||
check("@rules.airUseSpawns", b -> rules.airUseSpawns = b, () -> rules.airUseSpawns, () -> rules.waves);
|
||||
numberi("@rules.wavelimit", f -> rules.winWave = f, () -> rules.winWave, () -> rules.waves, 0, Integer.MAX_VALUE);
|
||||
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> rules.waves && rules.waveTimer, 1, Float.MAX_VALUE);
|
||||
//this is experimental, because it's not clear that 0 makes it default.
|
||||
@@ -351,6 +357,10 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
|
||||
|
||||
category("teams");
|
||||
//not sure where else to put this
|
||||
if(showRuleEditRule){
|
||||
check("@rules.allowedit", b -> rules.allowEditRules = b, () -> rules.allowEditRules);
|
||||
}
|
||||
team("@rules.playerteam", t -> rules.defaultTeam = t, () -> rules.defaultTeam);
|
||||
team("@rules.enemyteam", t -> rules.waveTeam = t, () -> rules.waveTeam);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public class DatabaseDialog extends BaseDialog{
|
||||
|
||||
void rebuild(){
|
||||
all.clear();
|
||||
var text = search.getText();
|
||||
var text = search.getText().toLowerCase();
|
||||
|
||||
Seq<Content>[] allContent = Vars.content.getContentMap();
|
||||
|
||||
@@ -54,7 +54,7 @@ public class DatabaseDialog extends BaseDialog{
|
||||
|
||||
Seq<UnlockableContent> array = allContent[j]
|
||||
.select(c -> c instanceof UnlockableContent u && !u.isHidden() &&
|
||||
(text.isEmpty() || u.localizedName.toLowerCase().contains(text.toLowerCase()))).as();
|
||||
(text.isEmpty() || u.localizedName.toLowerCase().contains(text))).as();
|
||||
if(array.size == 0) continue;
|
||||
|
||||
all.add("@content." + type.name() + ".name").growX().left().color(Pal.accent);
|
||||
|
||||
74
core/src/mindustry/ui/dialogs/IconSelectDialog.java
Normal file
74
core/src/mindustry/ui/dialogs/IconSelectDialog.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class IconSelectDialog extends Dialog{
|
||||
private Intc consumer = i -> Log.info("you have mere seconds");
|
||||
|
||||
public IconSelectDialog(){
|
||||
closeOnBack();
|
||||
setFillParent(true);
|
||||
|
||||
cont.pane(t -> {
|
||||
resized(true, () -> {
|
||||
t.clearChildren();
|
||||
t.marginRight(19f);
|
||||
t.defaults().size(48f);
|
||||
|
||||
t.button(Icon.none, Styles.flati, () -> {
|
||||
hide();
|
||||
consumer.get(0);
|
||||
});
|
||||
|
||||
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
|
||||
|
||||
int i = 1;
|
||||
for(var key : accessibleIcons){
|
||||
var value = Icon.icons.get(key);
|
||||
|
||||
t.button(value, Styles.flati, () -> {
|
||||
hide();
|
||||
consumer.get(Iconc.codes.get(key));
|
||||
});
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
|
||||
for(ContentType ctype : defaultContentIcons){
|
||||
t.row();
|
||||
t.image().colspan(cols).growX().width(Float.NEGATIVE_INFINITY).height(3f).color(Pal.accent);
|
||||
t.row();
|
||||
|
||||
i = 0;
|
||||
for(UnlockableContent u : content.getBy(ctype).<UnlockableContent>as()){
|
||||
if(!u.isHidden() && u.unlocked()){
|
||||
t.button(new TextureRegionDrawable(u.uiIcon), Styles.flati, iconMed, () -> {
|
||||
hide();
|
||||
consumer.get(u.emojiChar());
|
||||
});
|
||||
|
||||
if(++i % cols == 0) t.row();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
|
||||
}
|
||||
|
||||
public void show(Intc listener){
|
||||
consumer = listener;
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,6 @@ public class KeybindDialog extends Dialog{
|
||||
@Override
|
||||
public boolean keyDown(InputEvent event, KeyCode keycode){
|
||||
rebindDialog.hide();
|
||||
if(keycode == KeyCode.escape) return false;
|
||||
rebind(section, name, keycode);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import static mindustry.Vars.*;
|
||||
public class MapPlayDialog extends BaseDialog{
|
||||
public @Nullable Runnable playListener;
|
||||
|
||||
CustomRulesDialog dialog = new CustomRulesDialog();
|
||||
CustomRulesDialog dialog = new CustomRulesDialog(true);
|
||||
Rules rules;
|
||||
Gamemode selectedGamemode = Gamemode.survival;
|
||||
Map lastMap;
|
||||
@@ -102,7 +102,7 @@ public class MapPlayDialog extends BaseDialog{
|
||||
ScrollPane pane = new ScrollPane(table);
|
||||
pane.setFadeScrollBars(false);
|
||||
table.row();
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
for(Gamemode mode : Gamemode.all){
|
||||
if(mode.hidden) continue;
|
||||
table.labelWrap("[accent]" + mode + ":[] [lightgray]" + mode.description()).width(400f);
|
||||
table.row();
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.*;
|
||||
import mindustry.editor.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class PausedDialog extends BaseDialog{
|
||||
private MapProcessorsDialog processors = new MapProcessorsDialog();
|
||||
private SaveDialog save = new SaveDialog();
|
||||
private LoadDialog load = new LoadDialog();
|
||||
private boolean wasClient = false;
|
||||
private CustomRulesDialog rulesDialog = new CustomRulesDialog();
|
||||
|
||||
public PausedDialog(){
|
||||
super("@menu");
|
||||
shouldPause = true;
|
||||
|
||||
clearChildren();
|
||||
add(titleTable).growX().row();
|
||||
|
||||
stack(cont, new Table(t -> {
|
||||
t.bottom().left();
|
||||
t.button(Icon.book, () -> {
|
||||
Rules toEdit = Vars.state.rules.copy();
|
||||
rulesDialog.show(toEdit, () -> state.rules.copy());
|
||||
rulesDialog.hidden(() -> {
|
||||
//apply rule changes only once it is hidden
|
||||
Vars.state.rules = toEdit;
|
||||
Call.setRules(toEdit);
|
||||
});
|
||||
}).size(70f).tooltip("@customize").visible(() -> state.rules.allowEditRules && (net.server() || !net.active()));
|
||||
})).grow().row();
|
||||
|
||||
shown(this::rebuild);
|
||||
|
||||
addCloseListener();
|
||||
@@ -49,13 +70,22 @@ public class PausedDialog extends BaseDialog{
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.button("@hostserver", Icon.host, () -> {
|
||||
//the button runs out of space when the editor button is added, so use the mobile text
|
||||
cont.button(state.isEditor() ? "@hostserver.mobile" : "@hostserver", Icon.host, () -> {
|
||||
if(net.server() && steam){
|
||||
platform.inviteFriends();
|
||||
}else{
|
||||
ui.host.show();
|
||||
}
|
||||
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(2).width(dw * 2 + 10f).update(e -> e.setText(net.server() && steam ? "@invitefriends" : "@hostserver"));
|
||||
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(state.isEditor() ? 1 : 2).width(state.isEditor() ? dw : dw * 2 + 10f)
|
||||
.update(e -> e.setText(net.server() && steam ? "@invitefriends" : state.isEditor() ? "@hostserver.mobile" : "@hostserver"));
|
||||
|
||||
if(state.isEditor()){
|
||||
cont.button("@editor.worldprocessors", Icon.logic, () -> {
|
||||
hide();
|
||||
processors.show();
|
||||
});
|
||||
}
|
||||
|
||||
cont.row();
|
||||
|
||||
@@ -119,7 +149,7 @@ public class PausedDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
public void runExitSave(){
|
||||
wasClient = net.client();
|
||||
boolean wasClient = net.client();
|
||||
if(net.client()) netClient.disconnectQuietly();
|
||||
|
||||
if(state.isEditor() && !wasClient){
|
||||
|
||||
@@ -43,13 +43,6 @@ import static mindustry.graphics.g3d.PlanetRenderer.*;
|
||||
import static mindustry.ui.dialogs.PlanetDialog.Mode.*;
|
||||
|
||||
public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
static final String[] defaultIcons = {
|
||||
"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",
|
||||
};
|
||||
|
||||
//if true, enables launching anywhere for testing
|
||||
public static boolean debugSelect = false;
|
||||
public static float sectorShowDuration = 60f * 2.4f;
|
||||
@@ -594,7 +587,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
@Override
|
||||
public void act(float delta){
|
||||
if(scene.getDialog() == PlanetDialog.this && !scene.hit(input.mouseX(), input.mouseY(), true).isDescendantOf(e -> e instanceof ScrollPane)){
|
||||
if(scene.getDialog() == PlanetDialog.this && (scene.getHoverElement() == null || !scene.getHoverElement().isDescendantOf(e -> e instanceof ScrollPane))){
|
||||
scene.setScrollFocus(PlanetDialog.this);
|
||||
}
|
||||
|
||||
@@ -998,6 +991,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
sector.save = null;
|
||||
}
|
||||
updateSelected();
|
||||
rebuildList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1053,6 +1047,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
Icon.icons.get(sector.info.icon + "Small");
|
||||
|
||||
title.button(icon == null ? Icon.noneSmall : icon, Styles.clearNonei, iconSmall, () -> {
|
||||
//TODO use IconSelectDialog
|
||||
new Dialog(""){{
|
||||
closeOnBack();
|
||||
setFillParent(true);
|
||||
@@ -1079,7 +1074,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
|
||||
|
||||
int i = 1;
|
||||
for(var key : defaultIcons){
|
||||
for(var key : accessibleIcons){
|
||||
var value = Icon.icons.get(key);
|
||||
|
||||
t.button(value, Styles.squareTogglei, () -> {
|
||||
|
||||
@@ -472,7 +472,7 @@ public class ResearchDialog extends BaseDialog{
|
||||
|
||||
if(mobile){
|
||||
tapped(() -> {
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(e == this){
|
||||
hoverNode = null;
|
||||
rebuild();
|
||||
|
||||
@@ -398,6 +398,7 @@ public class SchematicsDialog extends BaseDialog{
|
||||
closeOnBack();
|
||||
setFillParent(true);
|
||||
|
||||
//TODO: use IconSelectDialog
|
||||
cont.pane(t -> {
|
||||
resized(true, () -> {
|
||||
t.clearChildren();
|
||||
@@ -407,7 +408,7 @@ public class SchematicsDialog extends BaseDialog{
|
||||
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
|
||||
|
||||
int i = 0;
|
||||
for(String icon : PlanetDialog.defaultIcons){
|
||||
for(String icon : accessibleIcons){
|
||||
String out = (char)Iconc.codes.get(icon) + "";
|
||||
if(tags.contains(out)) continue;
|
||||
|
||||
|
||||
@@ -297,6 +297,7 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void addSettings(){
|
||||
sound.checkPref("alwaysmusic", false);
|
||||
sound.sliderPref("musicvol", 100, 0, 100, 1, i -> i + "%");
|
||||
sound.sliderPref("sfxvol", 100, 0, 100, 1, i -> i + "%");
|
||||
sound.sliderPref("ambientvol", 100, 0, 100, 1, i -> i + "%");
|
||||
|
||||
@@ -67,7 +67,7 @@ public class BlockConfigFragment{
|
||||
}
|
||||
|
||||
public boolean hasConfigMouse(){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.graphics.getHeight() - Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
return e != null && (e == table || e.isDescendantOf(table));
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user