Merge branch Anuken:master into pr-readwrite
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());
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import mindustry.net.*;
|
||||
import mindustry.service.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -105,8 +106,8 @@ public class Vars implements Loadable{
|
||||
public static final float invasionGracePeriod = 20;
|
||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||
public static final float minArmorDamage = 0.1f;
|
||||
/** land/launch animation duration */
|
||||
public static final float coreLandDuration = 160f;
|
||||
/** @deprecated see {@link CoreBlock#landDuration} instead! */
|
||||
public static final @Deprecated float coreLandDuration = 160f;
|
||||
/** size of tiles in units */
|
||||
public static final int tilesize = 8;
|
||||
/** size of one tile payload (^2) */
|
||||
@@ -136,6 +137,13 @@ public class Vars implements Loadable{
|
||||
Color.valueOf("4b5ef1"),
|
||||
Color.valueOf("2cabfe"),
|
||||
};
|
||||
/** Icons available to the user for customization in certain dialogs. */
|
||||
public static final String[] accessibleIcons = {
|
||||
"effect", "power", "logic", "units", "liquid", "production", "defense", "turret", "distribution", "crafting",
|
||||
"settings", "cancel", "zoom", "ok", "star", "home", "pencil", "up", "down", "left", "right",
|
||||
"hammer", "warning", "tree", "admin", "map", "modePvp", "terrain",
|
||||
"modeSurvival", "commandRally", "commandAttack",
|
||||
};
|
||||
/** maximum TCP packet size */
|
||||
public static final int maxTcpSize = 900;
|
||||
/** default server port */
|
||||
@@ -146,6 +154,8 @@ public class Vars implements Loadable{
|
||||
public static final int maxModSubtitleLength = 40;
|
||||
/** multicast group for discovery.*/
|
||||
public static final String multicastGroup = "227.2.7.7";
|
||||
/** Maximum delta time. If the actual delta time (*60) between frames is higher than this number, the game will start to slow down. */
|
||||
public static float maxDeltaClient = 6f, maxDeltaServer = 10f;
|
||||
/** whether the graphical game client has loaded */
|
||||
public static boolean clientLoaded = false;
|
||||
/** max GL texture size */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -12,30 +12,12 @@ import mindustry.async.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
public class UnitGroup{
|
||||
public Seq<Unit> units = new Seq<>();
|
||||
public int collisionLayer;
|
||||
public volatile float[] positions, originalPositions;
|
||||
public volatile boolean valid;
|
||||
public long lastSpeedUpdate = -1;
|
||||
public float minSpeed = 999999f;
|
||||
|
||||
public void updateMinSpeed(){
|
||||
if(lastSpeedUpdate == Vars.state.updateId) return;
|
||||
|
||||
lastSpeedUpdate = Vars.state.updateId;
|
||||
minSpeed = 999999f;
|
||||
|
||||
for(Unit unit : units){
|
||||
//don't factor in the floor speed multiplier
|
||||
Floor on = unit.isFlying() ? Blocks.air.asFloor() : unit.floorOn();
|
||||
minSpeed = Math.min(unit.speed() / on.speedMultiplier, minSpeed);
|
||||
}
|
||||
|
||||
if(Float.isInfinite(minSpeed) || Float.isNaN(minSpeed)) minSpeed = 999999f;
|
||||
}
|
||||
|
||||
public void calculateFormation(Vec2 dest, int collisionLayer){
|
||||
this.collisionLayer = collisionLayer;
|
||||
@@ -58,8 +40,6 @@ public class UnitGroup{
|
||||
unit.command().groupIndex = i;
|
||||
}
|
||||
|
||||
updateMinSpeed();
|
||||
|
||||
//run on new thread to prevent stutter
|
||||
Vars.mainExecutor.submit(() -> {
|
||||
//unused space between circles that needs to be reached for compression to end
|
||||
@@ -188,9 +168,9 @@ public class UnitGroup{
|
||||
Unit unit = units.get(index);
|
||||
|
||||
PathCost cost = unit.type.pathCost;
|
||||
int res = ControlPathfinder.raycastFast(unit.team.id, cost, World.toTile(dest.x), World.toTile(dest.y), World.toTile(x), World.toTile(y));
|
||||
int res = ControlPathfinder.raycastFastAvoid(unit.team.id, cost, World.toTile(dest.x), World.toTile(dest.y), World.toTile(x), World.toTile(y));
|
||||
|
||||
//collision found, make th destination the point right before the collision
|
||||
//collision found, make the destination the point right before the collision
|
||||
if(res != 0){
|
||||
v1.set(Point2.x(res) * Vars.tilesize - dest.x, Point2.y(res) * Vars.tilesize - dest.y);
|
||||
v1.setLength(Math.max(v1.len() - Vars.tilesize - 4f, 0));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -148,10 +146,6 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
if(group != null){
|
||||
group.updateMinSpeed();
|
||||
}
|
||||
|
||||
if(!net.client() && command == UnitCommand.enterPayloadCommand && unit.buildOn() != null && (targetPos == null || (world.buildWorld(targetPos.x, targetPos.y) != null && world.buildWorld(targetPos.x, targetPos.y) == unit.buildOn()))){
|
||||
var build = unit.buildOn();
|
||||
tmpPayload.unit = unit;
|
||||
@@ -209,6 +203,11 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
boolean alwaysArrive = false;
|
||||
|
||||
float engageRange = unit.type.range - 10f;
|
||||
boolean withinAttackRange = attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram;
|
||||
|
||||
if(targetPos != null){
|
||||
boolean move = true, isFinalPoint = commandQueue.size == 0;
|
||||
vecOut.set(targetPos);
|
||||
@@ -225,6 +224,7 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
|
||||
if(unit.isGrounded() && stance != UnitStance.ram){
|
||||
//TODO: blocking enable or disable?
|
||||
if(timer.get(timerTarget3, avoidInterval)){
|
||||
Vec2 dstPos = Tmp.v1.trns(unit.rotation, unit.hitSize/2f);
|
||||
float max = unit.hitSize/2f;
|
||||
@@ -252,8 +252,18 @@ public class CommandAI extends AIController{
|
||||
timeSpentBlocked = 0f;
|
||||
}
|
||||
|
||||
//if you've spent 3 seconds stuck, something is wrong, move regardless
|
||||
move = Vars.controlPath.getPathPosition(unit, pathId, vecMovePos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
|
||||
//if the unit is next to the target, stop asking the pathfinder how to get there, it's a waste of CPU
|
||||
//TODO maybe stop moving too?
|
||||
if(withinAttackRange){
|
||||
move = true;
|
||||
noFound[0] = false;
|
||||
vecOut.set(vecMovePos);
|
||||
}else{
|
||||
move = controlPath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
|
||||
}
|
||||
|
||||
//rare case where unit must be perfectly aligned (happens with 1-tile gaps)
|
||||
alwaysArrive = vecOut.epsilonEquals(unit.tileX() * tilesize, unit.tileY() * tilesize);
|
||||
//we've reached the final point if the returned coordinate is equal to the supplied input
|
||||
isFinalPoint &= vecMovePos.epsilonEquals(vecOut, 4.1f);
|
||||
|
||||
@@ -270,18 +280,16 @@ public class CommandAI extends AIController{
|
||||
vecOut.set(vecMovePos);
|
||||
}
|
||||
|
||||
float engageRange = unit.type.range - 10f;
|
||||
|
||||
if(move){
|
||||
if(unit.type.circleTarget && attackTarget != null){
|
||||
target = attackTarget;
|
||||
circleAttack(80f);
|
||||
}else{
|
||||
moveTo(vecOut,
|
||||
attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram ? engageRange :
|
||||
withinAttackRange ? engageRange :
|
||||
unit.isGrounded() ? 0f :
|
||||
attackTarget != null && stance != UnitStance.ram ? engageRange :
|
||||
0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint);
|
||||
attackTarget != null && stance != UnitStance.ram ? engageRange : 0f,
|
||||
unit.isFlying() ? 40f : 100f, false, null, isFinalPoint || alwaysArrive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,11 +371,6 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float prefSpeed(){
|
||||
return group == null ? super.prefSpeed() : Math.min(group.minSpeed, unit.speed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFire(){
|
||||
return stance != UnitStance.holdFire;
|
||||
@@ -426,7 +429,6 @@ public class CommandAI extends AIController{
|
||||
//this is an allocation, but it's relatively rarely called anyway, and outside mutations must be prevented
|
||||
targetPos = lastTargetPos = pos.cpy();
|
||||
attackTarget = null;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
this.stopWhenInRange = stopWhenInRange;
|
||||
}
|
||||
|
||||
@@ -441,7 +443,6 @@ public class CommandAI extends AIController{
|
||||
public void commandTarget(Teamc moveTo, boolean stopAtTarget){
|
||||
attackTarget = moveTo;
|
||||
this.stopAtTarget = stopAtTarget;
|
||||
pathId = Vars.controlPath.nextTargetId();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1181,6 +1181,7 @@ public class Blocks{
|
||||
rotate = true;
|
||||
invertFlip = true;
|
||||
group = BlockGroup.liquids;
|
||||
itemCapacity = 0;
|
||||
|
||||
liquidCapacity = 50f;
|
||||
|
||||
@@ -1231,6 +1232,7 @@ public class Blocks{
|
||||
}});
|
||||
|
||||
researchCostMultiplier = 1.1f;
|
||||
itemCapacity = 0;
|
||||
liquidCapacity = 40f;
|
||||
consumePower(2f);
|
||||
ambientSound = Sounds.extractLoop;
|
||||
@@ -2554,8 +2556,8 @@ public class Blocks{
|
||||
|
||||
fluxReactor = new VariableReactor("flux-reactor"){{
|
||||
requirements(Category.power, with(Items.graphite, 300, Items.carbide, 200, Items.oxide, 100, Items.silicon, 600, Items.surgeAlloy, 300));
|
||||
powerProduction = 120f;
|
||||
maxHeat = 140f;
|
||||
powerProduction = 240f;
|
||||
maxHeat = 150f;
|
||||
|
||||
consumeLiquid(Liquids.cyanogen, 9f / 60f);
|
||||
liquidCapacity = 30f;
|
||||
@@ -2782,6 +2784,7 @@ public class Blocks{
|
||||
ambientSoundVolume = 0.06f;
|
||||
hasLiquids = true;
|
||||
boostScale = 1f / 9f;
|
||||
itemCapacity = 0;
|
||||
outputLiquid = new LiquidStack(Liquids.water, 30f / 60f);
|
||||
consumePower(0.5f);
|
||||
liquidCapacity = 60f;
|
||||
@@ -4046,7 +4049,7 @@ public class Blocks{
|
||||
researchCostMultiplier = 0.05f;
|
||||
|
||||
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
|
||||
limitRange();
|
||||
limitRange(12f);
|
||||
}};
|
||||
|
||||
diffuse = new ItemTurret("diffuse"){{
|
||||
@@ -4105,7 +4108,7 @@ public class Blocks{
|
||||
rotateSpeed = 3f;
|
||||
|
||||
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
|
||||
limitRange();
|
||||
limitRange(25f);
|
||||
}};
|
||||
|
||||
sublimate = new ContinuousLiquidTurret("sublimate"){{
|
||||
@@ -4149,6 +4152,7 @@ public class Blocks{
|
||||
|
||||
liquidConsumed = 10f / 60f;
|
||||
targetInterval = 5f;
|
||||
newTargetInterval = 30f;
|
||||
targetUnderBlocks = false;
|
||||
|
||||
float r = range = 130f;
|
||||
@@ -4239,6 +4243,8 @@ public class Blocks{
|
||||
shootY = 7f;
|
||||
rotateSpeed = 1.4f;
|
||||
minWarmup = 0.85f;
|
||||
|
||||
newTargetInterval = 40f;
|
||||
shootWarmupSpeed = 0.07f;
|
||||
|
||||
coolant = consume(new ConsumeLiquid(Liquids.water, 30f / 60f));
|
||||
@@ -4355,7 +4361,7 @@ public class Blocks{
|
||||
coolant = consume(new ConsumeLiquid(Liquids.water, 20f / 60f));
|
||||
coolantMultiplier = 2.5f;
|
||||
|
||||
limitRange(-5f);
|
||||
limitRange(5f);
|
||||
}};
|
||||
|
||||
afflict = new PowerTurret("afflict"){{
|
||||
@@ -4376,6 +4382,7 @@ public class Blocks{
|
||||
trailInterval = 3f;
|
||||
trailParam = 4f;
|
||||
pierceCap = 2;
|
||||
buildingDamageMultiplier = 0.5f;
|
||||
fragOnHit = false;
|
||||
speed = 5f;
|
||||
damage = 180f;
|
||||
@@ -4459,6 +4466,8 @@ public class Blocks{
|
||||
heatRequirement = 10f;
|
||||
maxHeatEfficiency = 2f;
|
||||
|
||||
newTargetInterval = 40f;
|
||||
|
||||
inaccuracy = 1f;
|
||||
shake = 2f;
|
||||
shootY = 4;
|
||||
@@ -4681,6 +4690,8 @@ public class Blocks{
|
||||
shootSound = Sounds.missileLaunch;
|
||||
|
||||
minWarmup = 0.94f;
|
||||
newTargetInterval = 40f;
|
||||
unitSort = UnitSorts.strongest;
|
||||
shootWarmupSpeed = 0.03f;
|
||||
targetAir = false;
|
||||
targetUnderBlocks = false;
|
||||
@@ -5502,23 +5513,6 @@ public class Blocks{
|
||||
);
|
||||
}};
|
||||
|
||||
mechRefabricator = new Reconstructor("mech-refabricator"){{
|
||||
requirements(Category.units, with(Items.beryllium, 250, Items.tungsten, 120, Items.silicon, 150));
|
||||
regionSuffix = "-dark";
|
||||
|
||||
size = 3;
|
||||
consumePower(2.5f);
|
||||
consumeLiquid(Liquids.hydrogen, 3f / 60f);
|
||||
consumeItems(with(Items.silicon, 50, Items.tungsten, 40));
|
||||
|
||||
constructTime = 60f * 45f;
|
||||
researchCostMultiplier = 0.75f;
|
||||
|
||||
upgrades.addAll(
|
||||
new UnitType[]{UnitTypes.merui, UnitTypes.cleroi}
|
||||
);
|
||||
}};
|
||||
|
||||
shipRefabricator = new Reconstructor("ship-refabricator"){{
|
||||
requirements(Category.units, with(Items.beryllium, 200, Items.tungsten, 100, Items.silicon, 150, Items.oxide, 40));
|
||||
regionSuffix = "-dark";
|
||||
@@ -5537,6 +5531,23 @@ public class Blocks{
|
||||
researchCost = with(Items.beryllium, 500, Items.tungsten, 200, Items.silicon, 300, Items.oxide, 80);
|
||||
}};
|
||||
|
||||
mechRefabricator = new Reconstructor("mech-refabricator"){{
|
||||
requirements(Category.units, with(Items.beryllium, 250, Items.tungsten, 120, Items.silicon, 150));
|
||||
regionSuffix = "-dark";
|
||||
|
||||
size = 3;
|
||||
consumePower(2.5f);
|
||||
consumeLiquid(Liquids.hydrogen, 3f / 60f);
|
||||
consumeItems(with(Items.silicon, 50, Items.tungsten, 40));
|
||||
|
||||
constructTime = 60f * 45f;
|
||||
researchCostMultiplier = 0.75f;
|
||||
|
||||
upgrades.addAll(
|
||||
new UnitType[]{UnitTypes.merui, UnitTypes.cleroi}
|
||||
);
|
||||
}};
|
||||
|
||||
//yes very silly name
|
||||
primeRefabricator = new Reconstructor("prime-refabricator"){{
|
||||
requirements(Category.units, with(Items.thorium, 250, Items.oxide, 200, Items.tungsten, 200, Items.silicon, 400));
|
||||
@@ -5791,6 +5802,8 @@ public class Blocks{
|
||||
heatOutput = 1000f;
|
||||
warmupRate = 1000f;
|
||||
regionRotated1 = 1;
|
||||
itemCapacity = 0;
|
||||
alwaysUnlocked = true;
|
||||
ambientSound = Sounds.none;
|
||||
}};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -1318,6 +1318,7 @@ public class UnitTypes{
|
||||
|
||||
healPercent = 5.5f;
|
||||
collidesTeam = true;
|
||||
reflectable = false;
|
||||
backColor = Pal.heal;
|
||||
trailColor = Pal.heal;
|
||||
}};
|
||||
@@ -3894,7 +3895,7 @@ public class UnitTypes{
|
||||
x = 43f * i / 4f;
|
||||
particles = parts;
|
||||
//visual only, the middle one does the actual suppressing
|
||||
display = active = false;
|
||||
active = false;
|
||||
}});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Weathers{
|
||||
suspendParticles;
|
||||
|
||||
public static void load(){
|
||||
snow = new ParticleWeather("snow"){{
|
||||
snow = new ParticleWeather("snowing"){{
|
||||
particleRegion = "particle";
|
||||
sizeMax = 13f;
|
||||
sizeMin = 2.6f;
|
||||
|
||||
@@ -314,6 +314,14 @@ public class ContentLoader{
|
||||
return getByName(ContentType.planet, name);
|
||||
}
|
||||
|
||||
public Seq<Weather> weathers(){
|
||||
return getBy(ContentType.weather);
|
||||
}
|
||||
|
||||
public Weather weather(String name){
|
||||
return getByName(ContentType.weather, name);
|
||||
}
|
||||
|
||||
public Seq<UnitStance> unitStances(){
|
||||
return getBy(ContentType.unitStance);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import mindustry.net.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -191,43 +192,29 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
Events.run(Trigger.newGame, () -> {
|
||||
var core = player.bestCore();
|
||||
|
||||
if(core == null) return;
|
||||
|
||||
camera.position.set(core);
|
||||
player.set(core);
|
||||
|
||||
float coreDelay = 0f;
|
||||
|
||||
if(!settings.getBool("skipcoreanimation") && !state.rules.pvp){
|
||||
coreDelay = coreLandDuration;
|
||||
coreDelay = core.landDuration();
|
||||
//delay player respawn so animation can play.
|
||||
player.deathTimer = Player.deathDelay - coreLandDuration;
|
||||
player.deathTimer = Player.deathDelay - core.landDuration();
|
||||
//TODO this sounds pretty bad due to conflict
|
||||
if(settings.getInt("musicvol") > 0){
|
||||
Musics.land.stop();
|
||||
Musics.land.play();
|
||||
Musics.land.setVolume(settings.getInt("musicvol") / 100f);
|
||||
//TODO what to do if another core with different music is already playing?
|
||||
Music music = core.landMusic();
|
||||
music.stop();
|
||||
music.play();
|
||||
music.setVolume(settings.getInt("musicvol") / 100f);
|
||||
}
|
||||
|
||||
app.post(() -> ui.hudfrag.showLand());
|
||||
renderer.showLanding();
|
||||
|
||||
Time.run(coreLandDuration, () -> {
|
||||
Fx.launch.at(core);
|
||||
Effect.shake(5f, 5f, core);
|
||||
core.thrusterTime = 1f;
|
||||
|
||||
if(state.isCampaign() && Vars.showSectorLandInfo && (state.rules.sector.preset == null || state.rules.sector.preset.showSectorLandInfo)){
|
||||
ui.announce("[accent]" + state.rules.sector.name() + "\n" +
|
||||
(state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " +
|
||||
state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5);
|
||||
}
|
||||
});
|
||||
renderer.showLanding(core);
|
||||
}
|
||||
|
||||
if(state.isCampaign()){
|
||||
|
||||
//don't run when hosting, that doesn't really work.
|
||||
if(state.rules.sector.planet.prebuildBase){
|
||||
toBePlaced.clear();
|
||||
|
||||
@@ -86,11 +86,12 @@ public class GameState{
|
||||
}
|
||||
|
||||
public boolean isPaused(){
|
||||
return is(State.paused);
|
||||
return state == State.paused;
|
||||
}
|
||||
|
||||
/** @return whether there is an unpaused game in progress. */
|
||||
public boolean isPlaying(){
|
||||
return (state == State.playing) || (state == State.paused && !isPaused());
|
||||
return state == State.playing;
|
||||
}
|
||||
|
||||
/** @return whether the current state is *not* the menu. */
|
||||
|
||||
@@ -47,17 +47,7 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
Events.on(BlockBuildEndEvent.class, event -> {
|
||||
if(!event.breaking){
|
||||
TeamData data = event.team.data();
|
||||
Iterator<BlockPlan> it = data.plans.iterator();
|
||||
var bounds = event.tile.block().bounds(event.tile.x, event.tile.y, Tmp.r1);
|
||||
while(it.hasNext()){
|
||||
BlockPlan b = it.next();
|
||||
Block block = content.block(b.block);
|
||||
if(bounds.overlaps(block.bounds(b.x, b.y, Tmp.r2))){
|
||||
b.removed = true;
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
checkOverlappingPlans(event.team, event.tile);
|
||||
|
||||
if(event.team == state.rules.defaultTeam){
|
||||
state.stats.placedBlockCount.increment(event.tile.block());
|
||||
@@ -65,6 +55,12 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(PayloadDropEvent.class, e -> {
|
||||
if(e.build != null){
|
||||
checkOverlappingPlans(e.build.team, e.build.tile);
|
||||
}
|
||||
});
|
||||
|
||||
//when loading a 'damaged' sector, propagate the damage
|
||||
Events.on(SaveLoadEvent.class, e -> {
|
||||
if(state.isCampaign()){
|
||||
@@ -211,6 +207,20 @@ public class Logic implements ApplicationListener{
|
||||
});
|
||||
}
|
||||
|
||||
private void checkOverlappingPlans(Team team, Tile tile){
|
||||
TeamData data = team.data();
|
||||
Iterator<BlockPlan> it = data.plans.iterator();
|
||||
var bounds = tile.block().bounds(tile.x, tile.y, Tmp.r1);
|
||||
while(it.hasNext()){
|
||||
BlockPlan b = it.next();
|
||||
Block block = content.block(b.block);
|
||||
if(bounds.overlaps(block.bounds(b.x, b.y, Tmp.r2))){
|
||||
b.removed = true;
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds starting items, resets wave time, and sets state to playing. */
|
||||
public void play(){
|
||||
state.set(State.playing);
|
||||
@@ -251,6 +261,7 @@ public class Logic implements ApplicationListener{
|
||||
Groups.clear();
|
||||
Time.clear();
|
||||
Events.fire(new ResetEvent());
|
||||
world.tiles = new Tiles(0, 0);
|
||||
|
||||
//save settings on reset
|
||||
Core.settings.manualSave();
|
||||
@@ -357,7 +368,8 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
//map is over, no more world processor objective stuff
|
||||
state.rules.disableWorldProcessors = true;
|
||||
state.rules.objectives.clear();
|
||||
|
||||
Call.clearObjectives();
|
||||
|
||||
//save, just in case
|
||||
if(!headless && !net.client()){
|
||||
@@ -460,9 +472,6 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
if(!state.isEditor()){
|
||||
state.rules.objectives.update();
|
||||
if(state.rules.objectives.checkChanged() && net.server()){
|
||||
Call.setObjectives(state.rules.objectives);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){
|
||||
|
||||
@@ -340,15 +340,23 @@ public class NetClient implements ApplicationListener{
|
||||
state.rules = rules;
|
||||
}
|
||||
|
||||
//NOTE: avoid using this, runs into packet/buffer size limitations
|
||||
@Remote(variants = Variant.both)
|
||||
public static void setObjectives(MapObjectives executor){
|
||||
state.rules.objectives = executor;
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
public static void objectiveCompleted(String[] flagsRemoved, String[] flagsAdded){
|
||||
state.rules.objectiveFlags.removeAll(flagsRemoved);
|
||||
state.rules.objectiveFlags.addAll(flagsAdded);
|
||||
@Remote(variants = Variant.both, called = Loc.server)
|
||||
public static void clearObjectives(){
|
||||
state.rules.objectives.clear();
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both, called = Loc.server)
|
||||
public static void completeObjective(int index){
|
||||
var obj = state.rules.objectives.get(index);
|
||||
if(obj != null){
|
||||
obj.done();
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
@@ -470,7 +478,7 @@ public class NetClient implements ApplicationListener{
|
||||
Log.warn("Block ID mismatch at @: @ != @. Skipping block snapshot.", tile, tile.build.block.id, block);
|
||||
break;
|
||||
}
|
||||
tile.build.readAll(Reads.get(input), tile.build.version());
|
||||
tile.build.readSync(Reads.get(input), tile.build.version());
|
||||
}
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
@@ -614,21 +622,22 @@ public class NetClient implements ApplicationListener{
|
||||
|
||||
void sync(){
|
||||
if(timer.get(0, playerSyncTime)){
|
||||
Unit unit = player.dead() ? Nulls.unit : player.unit();
|
||||
int uid = player.dead() ? -1 : unit.id;
|
||||
boolean dead = player.dead();
|
||||
Unit unit = dead ? null : player.unit();
|
||||
int uid = dead || unit == null ? -1 : unit.id;
|
||||
|
||||
Call.clientSnapshot(
|
||||
lastSent++,
|
||||
uid,
|
||||
player.dead(),
|
||||
player.dead() ? player.x : unit.x, player.dead() ? player.y : unit.y,
|
||||
player.unit().aimX(), player.unit().aimY(),
|
||||
unit.rotation,
|
||||
dead,
|
||||
dead ? player.x : unit.x, dead ? player.y : unit.y,
|
||||
dead ? 0f : unit.aimX(), dead ? 0f : unit.aimY(),
|
||||
unit == null ? 0f : unit.rotation,
|
||||
unit instanceof Mechc m ? m.baseRotation() : 0,
|
||||
unit.vel.x, unit.vel.y,
|
||||
player.unit().mineTile,
|
||||
unit == null ? 0f : unit.vel.x, unit == null ? 0f : unit.vel.y,
|
||||
dead ? null : unit.mineTile,
|
||||
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
|
||||
player.isBuilder() ? player.unit().plans : null,
|
||||
player.isBuilder() && unit != null ? unit.plans : null,
|
||||
Core.camera.position.x, Core.camera.position.y,
|
||||
Core.camera.width, Core.camera.height
|
||||
);
|
||||
|
||||
@@ -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.*;
|
||||
@@ -13,7 +14,6 @@ import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
@@ -30,11 +30,6 @@ public class Renderer implements ApplicationListener{
|
||||
/** These are global variables, for headless access. Cached. */
|
||||
public static float laserOpacity = 0.5f, bridgeOpacity = 0.75f;
|
||||
|
||||
private static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f;
|
||||
private static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
|
||||
private static final float cloudAlpha = 0.81f;
|
||||
private static final Interp landInterp = Interp.pow3;
|
||||
|
||||
public final BlockRenderer blocks = new BlockRenderer();
|
||||
public final FogRenderer fog = new FogRenderer();
|
||||
public final MinimapRenderer minimap = new MinimapRenderer();
|
||||
@@ -55,18 +50,15 @@ public class Renderer implements ApplicationListener{
|
||||
public TextureRegion[] bubbles = new TextureRegion[16], splashes = new TextureRegion[12];
|
||||
public TextureRegion[][] fluidFrames;
|
||||
|
||||
//currently landing core, null if there are no cores or it has finished landing.
|
||||
private @Nullable CoreBuild landCore;
|
||||
private @Nullable CoreBlock launchCoreType;
|
||||
private Color clearColor = new Color(0f, 0f, 0f, 1f);
|
||||
private float
|
||||
//seed for cloud visuals, 0-1
|
||||
cloudSeed = 0f,
|
||||
//target camera scale that is lerp-ed to
|
||||
targetscale = Scl.scl(4),
|
||||
//current actual camera scale
|
||||
camerascale = targetscale,
|
||||
//minimum camera zoom value for landing/launching; constant TODO make larger?
|
||||
minZoomScl = Scl.scl(0.02f),
|
||||
//starts at coreLandDuration, ends at 0. if positive, core is landing.
|
||||
landTime,
|
||||
//timer for core landing particles
|
||||
@@ -113,10 +105,6 @@ public class Renderer implements ApplicationListener{
|
||||
setupBloom();
|
||||
}
|
||||
|
||||
Events.run(Trigger.newGame, () -> {
|
||||
landCore = player.bestCore();
|
||||
});
|
||||
|
||||
EnvRenderers.init();
|
||||
for(int i = 0; i < bubbles.length; i++) bubbles[i] = atlas.find("bubble-" + i);
|
||||
for(int i = 0; i < splashes.length; i++) splashes[i] = atlas.find("splash-" + i);
|
||||
@@ -181,32 +169,26 @@ public class Renderer implements ApplicationListener{
|
||||
enableEffects = settings.getBool("effects");
|
||||
drawDisplays = !settings.getBool("hidedisplays");
|
||||
drawLight = settings.getBool("drawlight", true);
|
||||
pixelate = Core.settings.getBool("pixelate");
|
||||
pixelate = settings.getBool("pixelate");
|
||||
|
||||
//don't bother drawing landing animation if core is null
|
||||
if(landCore == null) landTime = 0f;
|
||||
if(landTime > 0){
|
||||
if(!state.isPaused()){
|
||||
CoreBuild build = landCore == null ? player.bestCore() : landCore;
|
||||
if(build != null){
|
||||
build.updateLandParticles();
|
||||
}
|
||||
}
|
||||
if(!state.isPaused()) landCore.updateLaunching();
|
||||
|
||||
if(!state.isPaused()){
|
||||
landTime -= Time.delta;
|
||||
}
|
||||
float fin = landTime / coreLandDuration;
|
||||
if(!launching) fin = 1f - fin;
|
||||
camerascale = landInterp.apply(minZoomScl, Scl.scl(4f), fin);
|
||||
weatherAlpha = 0f;
|
||||
camerascale = landCore.zoomLaunching();
|
||||
|
||||
//snap camera to cutscene core regardless of player input
|
||||
if(landCore != null){
|
||||
camera.position.set(landCore);
|
||||
}
|
||||
if(!state.isPaused()) landTime -= Time.delta;
|
||||
}else{
|
||||
weatherAlpha = Mathf.lerpDelta(weatherAlpha, 1f, 0.08f);
|
||||
}
|
||||
|
||||
if(landCore != null && landTime <= 0f){
|
||||
landCore.endLaunch();
|
||||
landCore = null;
|
||||
}
|
||||
|
||||
camera.width = graphics.getWidth() / camerascale;
|
||||
camera.height = graphics.getHeight() / camerascale;
|
||||
|
||||
@@ -304,7 +286,7 @@ public class Renderer implements ApplicationListener{
|
||||
graphics.clear(clearColor);
|
||||
Draw.reset();
|
||||
|
||||
if(Core.settings.getBool("animatedwater") || animateShields){
|
||||
if(settings.getBool("animatedwater") || animateShields){
|
||||
effectBuffer.resize(graphics.getWidth(), graphics.getHeight());
|
||||
}
|
||||
|
||||
@@ -393,7 +375,10 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
Draw.draw(Layer.overlayUI, overlays::drawTop);
|
||||
if(state.rules.fog) Draw.draw(Layer.fogOfWar, fog::drawFog);
|
||||
Draw.draw(Layer.space, this::drawLanding);
|
||||
Draw.draw(Layer.space, () -> {
|
||||
if(landCore == null || landTime <= 0f) return;
|
||||
landCore.drawLanding(launching && launchCoreType != null ? launchCoreType : (CoreBlock)landCore.block);
|
||||
});
|
||||
|
||||
Events.fire(Trigger.drawOver);
|
||||
blocks.drawBlocks();
|
||||
@@ -481,61 +466,6 @@ public class Renderer implements ApplicationListener{
|
||||
if(state.rules.customBackgroundCallback != null && customBackgrounds.containsKey(state.rules.customBackgroundCallback)){
|
||||
customBackgrounds.get(state.rules.customBackgroundCallback).run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void drawLanding(){
|
||||
CoreBuild build = landCore == null ? player.bestCore() : landCore;
|
||||
var clouds = assets.get("sprites/clouds.png", Texture.class);
|
||||
if(landTime > 0 && build != null){
|
||||
float fout = landTime / coreLandDuration;
|
||||
if(launching) fout = 1f - fout;
|
||||
float fin = 1f - fout;
|
||||
float scl = Scl.scl(4f) / camerascale;
|
||||
float pfin = Interp.pow3Out.apply(fin), pf = Interp.pow2In.apply(fout);
|
||||
|
||||
//draw particles
|
||||
Draw.color(Pal.lightTrail);
|
||||
Angles.randLenVectors(1, pfin, 100, 800f * scl * pfin, (ax, ay, ffin, ffout) -> {
|
||||
Lines.stroke(scl * ffin * pf * 3f);
|
||||
Lines.lineAngle(build.x + ax, build.y + ay, Mathf.angle(ax, ay), (ffin * 20 + 1f) * scl);
|
||||
});
|
||||
Draw.color();
|
||||
|
||||
CoreBlock block = launching && launchCoreType != null ? launchCoreType : (CoreBlock)build.block;
|
||||
block.drawLanding(build, build.x, build.y);
|
||||
|
||||
Draw.color();
|
||||
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
|
||||
|
||||
//accent tint indicating that the core was just constructed
|
||||
if(launching){
|
||||
float f = Mathf.clamp(1f - fout * 12f);
|
||||
if(f > 0.001f){
|
||||
Draw.mixcol(Pal.accent, f);
|
||||
}
|
||||
}
|
||||
|
||||
//draw clouds
|
||||
if(state.rules.cloudColor.a > 0.0001f){
|
||||
float scaling = cloudScaling;
|
||||
float sscl = Math.max(1f + Mathf.clamp(fin + cfinOffset)* cfinScl, 0f) * camerascale;
|
||||
|
||||
Tmp.tr1.set(clouds);
|
||||
Tmp.tr1.set(
|
||||
(camera.position.x - camera.width/2f * sscl) / scaling,
|
||||
(camera.position.y - camera.height/2f * sscl) / scaling,
|
||||
(camera.position.x + camera.width/2f * sscl) / scaling,
|
||||
(camera.position.y + camera.height/2f * sscl) / scaling);
|
||||
|
||||
Tmp.tr1.scroll(10f * cloudSeed, 10f * cloudSeed);
|
||||
|
||||
Draw.alpha(Mathf.sample(cloudAlphas, fin + calphaFinOffset) * cloudAlpha);
|
||||
Draw.mixcol(state.rules.cloudColor, state.rules.cloudColor.a);
|
||||
Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void scaleCamera(float amount){
|
||||
@@ -580,6 +510,13 @@ public class Renderer implements ApplicationListener{
|
||||
return landTime;
|
||||
}
|
||||
|
||||
public float getLandTimeIn(){
|
||||
if(landCore == null) return 0f;
|
||||
float fin = landTime / landCore.landDuration();
|
||||
if(!launching) fin = 1f - fin;
|
||||
return fin;
|
||||
}
|
||||
|
||||
public float getLandPTimer(){
|
||||
return landPTimer;
|
||||
}
|
||||
@@ -588,25 +525,42 @@ public class Renderer implements ApplicationListener{
|
||||
this.landPTimer = landPTimer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void showLanding(){
|
||||
launching = false;
|
||||
camerascale = minZoomScl;
|
||||
landTime = coreLandDuration;
|
||||
cloudSeed = Mathf.random(1f);
|
||||
var core = player.bestCore();
|
||||
if(core != null) showLanding(core);
|
||||
}
|
||||
|
||||
public void showLanding(CoreBuild landCore){
|
||||
this.landCore = landCore;
|
||||
launching = false;
|
||||
landTime = landCore.landDuration();
|
||||
|
||||
landCore.beginLaunch(null);
|
||||
camerascale = landCore.zoomLaunching();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void showLaunch(CoreBlock coreType){
|
||||
Vars.ui.hudfrag.showLaunch();
|
||||
Vars.control.input.config.hideConfig();
|
||||
Vars.control.input.inv.hide();
|
||||
launchCoreType = coreType;
|
||||
var core = player.team().core();
|
||||
if(core != null) showLaunch(core, coreType);
|
||||
}
|
||||
|
||||
public void showLaunch(CoreBuild landCore, CoreBlock coreType){
|
||||
control.input.config.hideConfig();
|
||||
control.input.inv.hide();
|
||||
|
||||
this.landCore = landCore;
|
||||
launching = true;
|
||||
landCore = player.team().core();
|
||||
cloudSeed = Mathf.random(1f);
|
||||
landTime = coreLandDuration;
|
||||
if(landCore != null){
|
||||
Fx.coreLaunchConstruct.at(landCore.x, landCore.y, coreType.size);
|
||||
}
|
||||
landTime = landCore.landDuration();
|
||||
launchCoreType = coreType;
|
||||
|
||||
Music music = landCore.launchMusic();
|
||||
music.stop();
|
||||
music.play();
|
||||
music.setVolume(settings.getInt("musicvol") / 100f);
|
||||
|
||||
landCore.beginLaunch(coreType);
|
||||
}
|
||||
|
||||
public void takeMapScreenshot(){
|
||||
@@ -648,7 +602,7 @@ public class Renderer implements ApplicationListener{
|
||||
Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png");
|
||||
PixmapIO.writePng(file, fullPixmap);
|
||||
fullPixmap.dispose();
|
||||
app.post(() -> ui.showInfoFade(Core.bundle.format("screenshot", file.toString())));
|
||||
app.post(() -> ui.showInfoFade(bundle.format("screenshot", file.toString())));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
public TextureRegion uiIcon;
|
||||
/** Icon of the full content. Unscaled.*/
|
||||
public TextureRegion fullIcon;
|
||||
/** Override for the full icon. Useful for mod content with duplicate icons. Overrides any other full icon.*/
|
||||
public String fullOverride = "";
|
||||
/** The tech tree node for this content, if applicable. Null if not part of a tech tree. */
|
||||
public @Nullable TechNode techNode;
|
||||
/** Tech nodes for all trees that this content is part of. */
|
||||
@@ -62,11 +64,12 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
@Override
|
||||
public void loadIcon(){
|
||||
fullIcon =
|
||||
Core.atlas.find(fullOverride,
|
||||
Core.atlas.find(getContentType().name() + "-" + name + "-full",
|
||||
Core.atlas.find(name + "-full",
|
||||
Core.atlas.find(name,
|
||||
Core.atlas.find(getContentType().name() + "-" + name,
|
||||
Core.atlas.find(name + "1")))));
|
||||
Core.atlas.find(name + "1"))))));
|
||||
|
||||
uiIcon = Core.atlas.find(getContentType().name() + "-" + name + "-ui", fullIcon);
|
||||
}
|
||||
@@ -115,6 +118,7 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
var result = Pixmaps.outline(base, outlineColor, outlineRadius);
|
||||
Drawf.checkBleed(result);
|
||||
packer.add(page, regName, result);
|
||||
result.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +130,7 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
var result = Pixmaps.outline(base, outlineColor, outlineRadius);
|
||||
Drawf.checkBleed(result);
|
||||
packer.add(PageType.main, name, result);
|
||||
result.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +147,11 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
return Fonts.getUnicodeStr(name);
|
||||
}
|
||||
|
||||
public int emojiChar(){
|
||||
return Fonts.getUnicode(name);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasEmoji(){
|
||||
return Fonts.hasUnicodeStr(name);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class MapEditor{
|
||||
for(int i = 0; i < tiles.width * tiles.height; i++){
|
||||
Tile tile = tiles.geti(i);
|
||||
var build = tile.build;
|
||||
if(build != null){
|
||||
if(build != null && tile.isCenter()){
|
||||
builds.add(build);
|
||||
}
|
||||
tiles.seti(i, new EditorTile(tile.x, tile.y, tile.floorID(), tile.overlayID(), build == null ? tile.blockID() : 0));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.Button.*;
|
||||
import arc.scene.ui.TextButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.scene.utils.*;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ public class Damage{
|
||||
private static final Rect rect = new Rect();
|
||||
private static final Rect hitrect = new Rect();
|
||||
private static final Vec2 vec = new Vec2(), seg1 = new Vec2(), seg2 = new Vec2();
|
||||
private static final Seq<Unit> units = new Seq<>();
|
||||
private static final IntSet collidedBlocks = new IntSet();
|
||||
private static final IntFloatMap damages = new IntFloatMap();
|
||||
private static final Seq<Collided> collided = new Seq<>();
|
||||
@@ -41,6 +40,7 @@ public class Damage{
|
||||
public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source){
|
||||
applySuppression(team, x, y, range, reload, maxDelay, applyParticleChance, source, Pal.sapBullet);
|
||||
}
|
||||
|
||||
public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source, Color effectColor){
|
||||
builds.clear();
|
||||
indexer.eachBlock(null, x, y, range, build -> build.team != team, build -> {
|
||||
@@ -175,21 +175,23 @@ public class Damage{
|
||||
|
||||
distances.clear();
|
||||
|
||||
World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y), (x, y) -> {
|
||||
//add distance to list so it can be processed
|
||||
var build = world.build(x, y);
|
||||
if(b.type.collidesGround && b.type.collidesTiles){
|
||||
World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y), (x, y) -> {
|
||||
//add distance to list so it can be processed
|
||||
var build = world.build(x, y);
|
||||
|
||||
if(build != null && build.team != b.team && build.collide(b) && b.checkUnderBuild(build, x * tilesize, y * tilesize)){
|
||||
distances.add(b.dst(build));
|
||||
if(build != null && build.team != b.team && build.collide(b) && b.checkUnderBuild(build, x * tilesize, y * tilesize)){
|
||||
distances.add(b.dst(build));
|
||||
|
||||
if(laser && build.absorbLasers()){
|
||||
maxDst = Math.min(maxDst, b.dst(build));
|
||||
return true;
|
||||
if(laser && build.absorbLasers()){
|
||||
maxDst = Math.min(maxDst, b.dst(build));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
Units.nearbyEnemies(b.team, rect, u -> {
|
||||
u.hitbox(hitrect);
|
||||
@@ -243,11 +245,12 @@ public class Damage{
|
||||
*/
|
||||
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){
|
||||
length = findLength(hitter, length, laser, pierceCap);
|
||||
hitter.fdata = length;
|
||||
|
||||
collidedBlocks.clear();
|
||||
vec.trnsExact(angle, length);
|
||||
|
||||
if(hitter.type.collidesGround){
|
||||
if(hitter.type.collidesGround && hitter.type.collidesTiles){
|
||||
seg1.set(x, y);
|
||||
seg2.set(seg1).add(vec);
|
||||
World.raycastEachWorld(x, y, seg2.x, seg2.y, (cx, cy) -> {
|
||||
@@ -293,7 +296,6 @@ public class Damage{
|
||||
collided.each(c -> {
|
||||
if(hitter.damage > 0 && (pierceCap <= 0 || collideCount[0] < pierceCap)){
|
||||
if(c.target instanceof Unit u){
|
||||
effect.at(c.x, c.y);
|
||||
u.collision(hitter, c.x, c.y);
|
||||
hitter.collision(u, c.x, c.y);
|
||||
collideCount[0]++;
|
||||
@@ -344,7 +346,6 @@ public class Damage{
|
||||
|
||||
Units.nearbyEnemies(team, rect.setCentered(x, y, 1f), u -> {
|
||||
if(u.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround) && u.hittable()){
|
||||
effect.at(x, y);
|
||||
u.collision(hitter, x, y);
|
||||
hitter.collision(u, x, y);
|
||||
}
|
||||
|
||||
@@ -247,4 +247,4 @@ public class EntityCollisions{
|
||||
public interface SolidPred{
|
||||
boolean solid(int x, int y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class Puddles{
|
||||
Puddle puddle = Puddle.create();
|
||||
puddle.tile = tile;
|
||||
puddle.liquid = liquid;
|
||||
puddle.amount = amount;
|
||||
puddle.amount = Math.min(amount, maxLiquid);
|
||||
puddle.set(ax, ay);
|
||||
register(puddle);
|
||||
puddle.add();
|
||||
|
||||
@@ -6,6 +6,7 @@ import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public abstract class Ability implements Cloneable{
|
||||
protected static final float descriptionWidth = 350f;
|
||||
/** If false, this ability does not show in unit stats. */
|
||||
public boolean display = true;
|
||||
//the one and only data variable that is synced.
|
||||
@@ -16,7 +17,16 @@ public abstract class Ability implements Cloneable{
|
||||
public void death(Unit unit){}
|
||||
public void init(UnitType type){}
|
||||
public void displayBars(Unit unit, Table bars){}
|
||||
public void addStats(Table t){}
|
||||
public void addStats(Table t){
|
||||
if(Core.bundle.has(getBundle() + ".description")){
|
||||
t.add(Core.bundle.get(getBundle() + ".description")).wrap().width(descriptionWidth);
|
||||
t.row();
|
||||
}
|
||||
}
|
||||
|
||||
public String abilityStat(String stat, Object... values){
|
||||
return Core.bundle.format("ability.stat." + stat, values);
|
||||
}
|
||||
|
||||
public Ability copy(){
|
||||
try{
|
||||
@@ -29,7 +39,11 @@ public abstract class Ability implements Cloneable{
|
||||
|
||||
/** @return localized ability name; mods should override this. */
|
||||
public String localized(){
|
||||
return Core.bundle.get(getBundle());
|
||||
}
|
||||
|
||||
public String getBundle(){
|
||||
var type = getClass();
|
||||
return Core.bundle.get("ability." + (type.isAnonymousClass() ? type.getSuperclass() : type).getSimpleName().replace("Ability", "").toLowerCase());
|
||||
return "ability." + (type.isAnonymousClass() ? type.getSuperclass() : type).getSimpleName().replace("Ability", "").toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,27 @@ import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.Table;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class ArmorPlateAbility extends Ability{
|
||||
public TextureRegion plateRegion;
|
||||
public Color color = Color.valueOf("d1efff");
|
||||
public TextureRegion shineRegion;
|
||||
public String plateSuffix = "-armor";
|
||||
public String shineSuffix = "-shine";
|
||||
/** Color of the shine. If null, uses team color. */
|
||||
public @Nullable Color color = null;
|
||||
public float shineSpeed = 1f;
|
||||
public float z = -1;
|
||||
|
||||
/** Whether to draw the plate region. */
|
||||
public boolean drawPlate = true;
|
||||
/** Whether to draw the shine over the plate region. */
|
||||
public boolean drawShine = true;
|
||||
|
||||
public float healthMultiplier = 0.2f;
|
||||
public float z = Layer.effect;
|
||||
|
||||
protected float warmup;
|
||||
|
||||
@@ -29,29 +38,45 @@ public class ArmorPlateAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Stat.healthMultiplier.localized() + ": [white]" + Math.round(healthMultiplier * 100f) + 100 + "%");
|
||||
super.addStats(t);
|
||||
t.add(abilityStat("damagereduction", Strings.autoFixed(-healthMultiplier * 100f, 1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Unit unit){
|
||||
if(!drawPlate && !drawShine) return;
|
||||
|
||||
if(warmup > 0.001f){
|
||||
if(plateRegion == null){
|
||||
plateRegion = Core.atlas.find(unit.type.name + "-armor", unit.type.region);
|
||||
plateRegion = Core.atlas.find(unit.type.name + plateSuffix, unit.type.region);
|
||||
shineRegion = Core.atlas.find(unit.type.name + shineSuffix, plateRegion);
|
||||
}
|
||||
|
||||
Draw.draw(z <= 0 ? Draw.z() : z, () -> {
|
||||
Shaders.armor.region = plateRegion;
|
||||
Shaders.armor.progress = warmup;
|
||||
Shaders.armor.time = -Time.time / 20f;
|
||||
float pz = Draw.z();
|
||||
if(z > 0) Draw.z(z);
|
||||
|
||||
Draw.rect(Shaders.armor.region, unit.x, unit.y, unit.rotation - 90f);
|
||||
Draw.color(color);
|
||||
Draw.shader(Shaders.armor);
|
||||
Draw.rect(Shaders.armor.region, unit.x, unit.y, unit.rotation - 90f);
|
||||
Draw.shader();
|
||||
if(drawPlate){
|
||||
Draw.alpha(warmup);
|
||||
Draw.rect(plateRegion, unit.x, unit.y, unit.rotation - 90f);
|
||||
Draw.alpha(1f);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
});
|
||||
if(drawShine){
|
||||
Draw.draw(Draw.z(), () -> {
|
||||
Shaders.armor.region = shineRegion;
|
||||
Shaders.armor.progress = warmup;
|
||||
Shaders.armor.time = -Time.time / 20f * shineSpeed;
|
||||
|
||||
Draw.color(color == null ? unit.team.color : color);
|
||||
Draw.shader(Shaders.armor);
|
||||
Draw.rect(shineRegion, unit.x, unit.y, unit.rotation - 90f);
|
||||
Draw.shader();
|
||||
|
||||
Draw.reset();
|
||||
});
|
||||
}
|
||||
|
||||
Draw.z(pz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -53,23 +52,29 @@ public class EnergyFieldAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add(Core.bundle.format("bullet.damage", damage));
|
||||
if(displayHeal){
|
||||
t.add(Core.bundle.get(getBundle() + ".healdescription")).wrap().width(descriptionWidth);
|
||||
}else{
|
||||
t.add(Core.bundle.get(getBundle() + ".description")).wrap().width(descriptionWidth);
|
||||
}
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.reload.localized() + ": [white]" + Strings.autoFixed(60f / reload, 2) + " " + StatUnit.perSecond.localized());
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
|
||||
t.row();
|
||||
t.add(Core.bundle.format("ability.energyfield.maxtargets", maxTargets));
|
||||
|
||||
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("maxtargets", maxTargets));
|
||||
t.row();
|
||||
t.add(Core.bundle.format("bullet.damage", damage));
|
||||
if(status != StatusEffects.none){
|
||||
t.row();
|
||||
t.add((status.hasEmoji() ? status.emoji() : "") + "[stat]" + status.localizedName);
|
||||
}
|
||||
if(displayHeal){
|
||||
t.row();
|
||||
t.add(Core.bundle.format("bullet.healpercent", Strings.autoFixed(healPercent, 2)));
|
||||
t.row();
|
||||
t.add(Core.bundle.format("ability.energyfield.sametypehealmultiplier", Math.round(sameTypeHealMult * 100f)));
|
||||
}
|
||||
if(status != StatusEffects.none){
|
||||
t.row();
|
||||
t.add(status.emoji() + " " + status.localizedName);
|
||||
t.add(abilityStat("sametypehealmultiplier", (sameTypeHealMult < 1f ? "[negstat]" : "") + Strings.autoFixed(sameTypeHealMult * 100f, 2)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
@@ -12,7 +13,6 @@ import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -32,6 +32,7 @@ public class ForceFieldAbility extends Ability{
|
||||
|
||||
/** State. */
|
||||
protected float radiusScale, alpha;
|
||||
protected boolean wasBroken = true;
|
||||
|
||||
private static float realRad;
|
||||
private static Unit paramUnit;
|
||||
@@ -41,13 +42,6 @@ public class ForceFieldAbility extends Ability{
|
||||
trait.absorb();
|
||||
Fx.absorb.at(trait);
|
||||
|
||||
//break shield
|
||||
if(paramUnit.shield <= trait.damage()){
|
||||
paramUnit.shield -= paramField.cooldown * paramField.regen;
|
||||
|
||||
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color, paramUnit);
|
||||
}
|
||||
|
||||
paramUnit.shield -= trait.damage();
|
||||
paramField.alpha = 1f;
|
||||
}
|
||||
@@ -73,18 +67,26 @@ public class ForceFieldAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Stat.health.localized() + ": [white]" + Math.round(max));
|
||||
super.addStats(t);
|
||||
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(radius / tilesize, 2)));
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(radius / tilesize, 2) + " " + StatUnit.blocks.localized());
|
||||
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(regen * 60f, 2) + StatUnit.perSecond.localized());
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.cooldownTime.localized() + ": [white]" + Strings.autoFixed(cooldown / 60f, 2) + " " + StatUnit.seconds.localized());
|
||||
t.add(abilityStat("repairspeed", Strings.autoFixed(regen * 60f, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("cooldown", Strings.autoFixed(cooldown / 60f, 2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
if(unit.shield <= 0f && !wasBroken){
|
||||
unit.shield -= cooldown * regen;
|
||||
|
||||
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.type.shieldColor(unit), this);
|
||||
}
|
||||
|
||||
wasBroken = unit.shield <= 0f;
|
||||
|
||||
if(unit.shield < max){
|
||||
unit.shield += Time.delta * regen;
|
||||
}
|
||||
@@ -108,7 +110,7 @@ public class ForceFieldAbility extends Ability{
|
||||
checkRadius(unit);
|
||||
|
||||
if(unit.shield > 0){
|
||||
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
|
||||
Draw.color(unit.type.shieldColor(unit), Color.white, Mathf.clamp(alpha));
|
||||
|
||||
if(Vars.renderer.animateShields){
|
||||
Draw.z(Layer.shields + 0.001f * alpha);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.noise.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -16,6 +17,12 @@ public class LiquidExplodeAbility extends Ability{
|
||||
public float radAmountScale = 5f, radScale = 1f;
|
||||
public float noiseMag = 6.5f, noiseScl = 5f;
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
super.addStats(t);
|
||||
t.add((liquid.hasEmoji() ? liquid.emoji() : "") + "[stat]" + liquid.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void death(Unit unit){
|
||||
//TODO what if noise is radial, so it looks like a splat?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -17,6 +18,14 @@ public class LiquidRegenAbility extends Ability{
|
||||
public float slurpEffectChance = 0.4f;
|
||||
public Effect slurpEffect = Fx.heal;
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
super.addStats(t);
|
||||
t.add((liquid.hasEmoji() ? liquid.emoji() : "") + "[stat]" + liquid.localizedName);
|
||||
t.row();
|
||||
t.add(abilityStat("slurpheal", Strings.autoFixed(regenPerSlurp, 2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
//TODO timer?
|
||||
|
||||
@@ -5,12 +5,15 @@ import arc.audio.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MoveLightningAbility extends Ability{
|
||||
/** Lightning damage */
|
||||
public float damage = 35f;
|
||||
@@ -63,7 +66,15 @@ public class MoveLightningAbility extends Ability{
|
||||
this.maxSpeed = maxSpeed;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
super.addStats(t);
|
||||
t.add(abilityStat("minspeed", Strings.autoFixed(minSpeed * 60f / tilesize, 2)));
|
||||
t.row();
|
||||
t.add(Core.bundle.format("bullet.damage", damage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
float scl = Mathf.clamp((unit.vel().len() - minSpeed) / (maxSpeed - minSpeed));
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.Core;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class RegenAbility extends Ability{
|
||||
/** Amount healed as percent per tick. */
|
||||
@@ -14,13 +12,16 @@ public class RegenAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
if(amount > 0.01f){
|
||||
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(amount * 60f, 2) + StatUnit.perSecond.localized());
|
||||
t.row();
|
||||
}
|
||||
super.addStats(t);
|
||||
|
||||
if(percentAmount > 0.01f){
|
||||
t.add(Core.bundle.format("bullet.healpercent", Strings.autoFixed(percentAmount * 60f, 2)) + StatUnit.perSecond.localized()); //stupid but works
|
||||
boolean flat = amount >= 0.001f;
|
||||
boolean percent = percentAmount >= 0.001f;
|
||||
|
||||
if(flat || percent){
|
||||
t.add(abilityStat("regen",
|
||||
(flat ? Strings.autoFixed(amount * 60f, 2) + (percent ? " [lightgray]+[stat] " : "") : "")
|
||||
+ (percent ? Strings.autoFixed(percentAmount * 60f, 2) + "%" : "")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class RepairFieldAbility extends Ability{
|
||||
public float amount = 1, reload = 100, range = 60;
|
||||
@@ -28,9 +28,10 @@ public class RepairFieldAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(amount * 60f / reload, 2) + StatUnit.perSecond.localized());
|
||||
super.addStats(t);
|
||||
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
|
||||
t.add(abilityStat("repairspeed", Strings.autoFixed(amount * 60f / reload, 2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,6 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class ShieldArcAbility extends Ability{
|
||||
private static Unit paramUnit;
|
||||
@@ -32,7 +31,7 @@ public class ShieldArcAbility extends Ability{
|
||||
if(paramField.data <= b.damage()){
|
||||
paramField.data -= paramField.cooldown * paramField.regen;
|
||||
|
||||
Fx.arcShieldBreak.at(paramPos.x, paramPos.y, 0, paramUnit.team.color, paramUnit);
|
||||
Fx.arcShieldBreak.at(paramPos.x, paramPos.y, 0, paramField.color == null ? paramUnit.type.shieldColor(paramUnit) : paramField.color, paramUnit);
|
||||
}
|
||||
|
||||
paramField.data -= b.damage();
|
||||
@@ -61,6 +60,8 @@ public class ShieldArcAbility extends Ability{
|
||||
public boolean drawArc = true;
|
||||
/** If not null, will be drawn on top. */
|
||||
public @Nullable String region;
|
||||
/** Color override of the shield. Uses unit shield colour by default. */
|
||||
public @Nullable Color color;
|
||||
/** If true, sprite position will be influenced by x/y. */
|
||||
public boolean offsetRegion = false;
|
||||
|
||||
@@ -69,12 +70,12 @@ public class ShieldArcAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Stat.health.localized() + ": [white]" + Math.round(max));
|
||||
super.addStats(t);
|
||||
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.repairSpeed.localized() + ": [white]" + Strings.autoFixed(regen * 60f, 2) + StatUnit.perSecond.localized());
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.cooldownTime.localized() + ": [white]" + Strings.autoFixed(cooldown / 60f, 2) + " " + StatUnit.seconds.localized());
|
||||
t.add(abilityStat("repairspeed", Strings.autoFixed(regen * 60f, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("cooldown", Strings.autoFixed(cooldown / 60f, 2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,7 +111,7 @@ public class ShieldArcAbility extends Ability{
|
||||
if(widthScale > 0.001f){
|
||||
Draw.z(Layer.shields);
|
||||
|
||||
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
|
||||
Draw.color(color == null ? unit.type.shieldColor(unit) : color, Color.white, Mathf.clamp(alpha));
|
||||
var pos = paramPos.set(x, y).rotate(unit.rotation - 90f).add(unit);
|
||||
|
||||
if(!Vars.renderer.animateShields){
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.Core;
|
||||
import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ShieldRegenFieldAbility extends Ability{
|
||||
public float amount = 1, max = 100f, reload = 100, range = 60;
|
||||
@@ -30,12 +29,12 @@ public class ShieldRegenFieldAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Core.bundle.get("waves.shields") + ": [white]" + Math.round(max)); //extremely stupid usage
|
||||
super.addStats(t);
|
||||
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.reload.localized() + ": [white]" + Strings.autoFixed(60f / reload, 2) + " " + StatUnit.perSecond.localized());
|
||||
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,13 +48,13 @@ public class ShieldRegenFieldAbility extends Ability{
|
||||
if(other.shield < max){
|
||||
other.shield = Math.min(other.shield + amount, max);
|
||||
other.shieldAlpha = 1f; //TODO may not be necessary
|
||||
applyEffect.at(unit.x, unit.y, 0f, unit.team.color, parentizeEffects ? other : null);
|
||||
applyEffect.at(other.x, other.y, 0f, other.type.shieldColor(other), parentizeEffects ? other : null);
|
||||
applied = true;
|
||||
}
|
||||
});
|
||||
|
||||
if(applied){
|
||||
activeEffect.at(unit.x, unit.y, unit.team.color);
|
||||
activeEffect.at(unit.x, unit.y, unit.type.shieldColor(unit));
|
||||
}
|
||||
|
||||
timer = 0f;
|
||||
|
||||
@@ -27,7 +27,8 @@ public class SpawnDeathAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add((randAmount > 0 ? amount + "-" + (amount + randAmount) : amount) + " " + unit.emoji() + " " + unit.localizedName);
|
||||
super.addStats(t);
|
||||
t.add("[stat]" + (randAmount > 0 ? amount + "x-" + (amount + randAmount) : amount) + "x[] " + (unit.hasEmoji() ? unit.emoji() : "") + "[stat]" + unit.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.Table;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class StatusFieldAbility extends Ability{
|
||||
public StatusEffect effect;
|
||||
@@ -33,11 +33,12 @@ public class StatusFieldAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Stat.reload.localized() + ": [white]" + Strings.autoFixed(60f / reload, 2) + " " + StatUnit.perSecond.localized());
|
||||
super.addStats(t);
|
||||
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
|
||||
t.row();
|
||||
t.add("[lightgray]" + Stat.shootRange.localized() + ": [white]" + Strings.autoFixed(range / tilesize, 2) + " " + StatUnit.blocks.localized());
|
||||
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
|
||||
t.row();
|
||||
t.add(effect.emoji() + " " + effect.localizedName);
|
||||
t.add((effect.hasEmoji() ? effect.emoji() : "") + "[stat]" + effect.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class SuppressionFieldAbility extends Ability{
|
||||
protected static Rand rand = new Rand();
|
||||
@@ -33,6 +38,19 @@ public class SuppressionFieldAbility extends Ability{
|
||||
|
||||
protected float timer;
|
||||
|
||||
@Override
|
||||
public void init(UnitType type){
|
||||
if(!active) display = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
super.addStats(t);
|
||||
t.add(Core.bundle.format("bullet.range", Strings.autoFixed(range / tilesize, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("duration", Strings.autoFixed(reload / 60f, 2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
if(!active) return;
|
||||
|
||||
@@ -12,7 +12,6 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -36,9 +35,10 @@ public class UnitSpawnAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void addStats(Table t){
|
||||
t.add("[lightgray]" + Stat.buildTime.localized() + ": [white]" + Strings.autoFixed(spawnTime / 60f, 2) + " " + StatUnit.seconds.localized());
|
||||
super.addStats(t);
|
||||
t.add(abilityStat("buildtime", Strings.autoFixed(spawnTime / 60f, 2)));
|
||||
t.row();
|
||||
t.add(unit.emoji() + " " + unit.localizedName);
|
||||
t.add((unit.hasEmoji() ? unit.emoji() : "") + "[stat]" + unit.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -176,6 +176,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float fragLifeMin = 1f, fragLifeMax = 1f;
|
||||
/** Random offset of frag bullets from the parent bullet. */
|
||||
public float fragOffsetMin = 1f, fragOffsetMax = 7f;
|
||||
/** How many times this bullet can release frag bullets, if pierce = true. */
|
||||
public int pierceFragCap = -1;
|
||||
|
||||
/** Bullet that is created at a fixed interval. */
|
||||
public @Nullable BulletType intervalBullet;
|
||||
@@ -307,6 +309,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
/** Color of light emitted by this bullet. */
|
||||
public Color lightColor = Pal.powerLight;
|
||||
|
||||
protected float cachedDps = -1;
|
||||
|
||||
public BulletType(float speed, float damage){
|
||||
this.speed = speed;
|
||||
this.damage = damage;
|
||||
@@ -336,15 +340,20 @@ public class BulletType extends Content implements Cloneable{
|
||||
|
||||
/** @return estimated damage per shot. this can be very inaccurate. */
|
||||
public float estimateDPS(){
|
||||
if(cachedDps >= 0f) return cachedDps;
|
||||
|
||||
if(spawnUnit != null){
|
||||
return spawnUnit.estimateDps();
|
||||
}
|
||||
|
||||
float sum = damage + splashDamage*0.75f;
|
||||
float sum = damage * (pierce ? pierceCap == -1 ? 2 : Mathf.clamp(pierceCap, 1, 2) : 1f) * splashDamage*0.75f;
|
||||
if(fragBullet != null && fragBullet != this){
|
||||
sum += fragBullet.estimateDPS() * fragBullets / 2f;
|
||||
}
|
||||
return sum;
|
||||
for(var other : spawnBullets){
|
||||
sum += other.estimateDPS();
|
||||
}
|
||||
return cachedDps = sum;
|
||||
}
|
||||
|
||||
/** @return maximum distance the bullet this bullet type has can travel. */
|
||||
@@ -509,12 +518,13 @@ public class BulletType extends Content implements Cloneable{
|
||||
}
|
||||
|
||||
public void createFrags(Bullet b, float x, float y){
|
||||
if(fragBullet != null && (fragOnAbsorb || !b.absorbed)){
|
||||
if(fragBullet != null && (fragOnAbsorb || !b.absorbed) && !(b.frags >= pierceFragCap && pierceFragCap > 0)){
|
||||
for(int i = 0; i < fragBullets; i++){
|
||||
float len = Mathf.random(fragOffsetMin, fragOffsetMax);
|
||||
float a = b.rotation() + Mathf.range(fragRandomSpread / 2) + fragAngle + ((i - fragBullets/2) * fragSpread);
|
||||
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
|
||||
}
|
||||
b.frags++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ public class ContinuousFlameBulletType extends ContinuousBulletType{
|
||||
lifetime = 16f;
|
||||
hitColor = colors[1].cpy().a(1f);
|
||||
lightColor = hitColor;
|
||||
lightOpacity = 0.7f;
|
||||
laserAbsorb = false;
|
||||
ammoMultiplier = 1f;
|
||||
pierceArmor = true;
|
||||
@@ -87,7 +88,7 @@ public class ContinuousFlameBulletType extends ContinuousBulletType{
|
||||
}
|
||||
|
||||
Tmp.v1.trns(b.rotation(), realLength * 1.1f);
|
||||
Drawf.light(b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, 0.7f);
|
||||
Drawf.light(b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, lightOpacity);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -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!
|
||||
@@ -1310,7 +1321,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
if(value instanceof UnitType) type = UnitType.class;
|
||||
|
||||
if(builder != null && builder.isPlayer()){
|
||||
lastAccessed = builder.getPlayer().coloredName();
|
||||
updateLastAccess(builder.getPlayer());
|
||||
}
|
||||
|
||||
if(block.configurations.containsKey(type)){
|
||||
@@ -1324,6 +1335,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
public void updateLastAccess(Player player){
|
||||
lastAccessed = player.coloredName();
|
||||
}
|
||||
|
||||
/** Called when the block is tapped by the local player. */
|
||||
public void tapped(){
|
||||
|
||||
@@ -1769,6 +1784,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
if(!block.hasConsumers || cheating()){
|
||||
potentialEfficiency = enabled && productionValid() ? 1f : 0f;
|
||||
efficiency = optionalEfficiency = shouldConsume() ? potentialEfficiency : 0f;
|
||||
shouldConsumePower = true;
|
||||
updateEfficiencyMultiplier();
|
||||
return;
|
||||
}
|
||||
@@ -1776,6 +1792,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//disabled -> nothing works
|
||||
if(!enabled){
|
||||
potentialEfficiency = efficiency = optionalEfficiency = 0f;
|
||||
shouldConsumePower = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1785,10 +1802,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
//assume efficiency is 1 for the calculations below
|
||||
efficiency = optionalEfficiency = 1f;
|
||||
shouldConsumePower = true;
|
||||
|
||||
//first pass: get the minimum efficiency of any consumer
|
||||
for(var cons : block.nonOptionalConsumers){
|
||||
minEfficiency = Math.min(minEfficiency, cons.efficiency(self()));
|
||||
float result = cons.efficiency(self());
|
||||
|
||||
if(cons != block.consPower && result <= 0.0000001f){
|
||||
shouldConsumePower = false;
|
||||
}
|
||||
|
||||
minEfficiency = Math.min(minEfficiency, result);
|
||||
}
|
||||
|
||||
//same for optionals
|
||||
@@ -1911,6 +1935,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
case y -> World.conv(y);
|
||||
case color -> Color.toDoubleBits(team.color.r, team.color.g, team.color.b, 1f);
|
||||
case dead -> !isValid() ? 1 : 0;
|
||||
case solid -> block.solid || checkSolid() ? 1 : 0;
|
||||
case team -> team.id;
|
||||
case health -> health;
|
||||
case maxHealth -> maxHealth;
|
||||
@@ -1977,9 +2002,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
switch(prop){
|
||||
case health -> {
|
||||
health = (float)Mathf.clamp(value, 0, maxHealth);
|
||||
healthChanged();
|
||||
if(health <= 0f && !dead()){
|
||||
Call.buildDestroyed(self());
|
||||
}else{
|
||||
healthChanged();
|
||||
}
|
||||
}
|
||||
case team -> {
|
||||
@@ -2056,6 +2082,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
@Override
|
||||
public void killed(){
|
||||
dead = true;
|
||||
Events.fire(new BlockDestroyEvent(tile));
|
||||
block.destroySound.at(tile);
|
||||
onDestroyed();
|
||||
|
||||
@@ -45,6 +45,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
transient @Nullable Mover mover;
|
||||
transient boolean absorbed, hit;
|
||||
transient @Nullable Trail trail;
|
||||
transient int frags;
|
||||
|
||||
@Override
|
||||
public void getCollisions(Cons<QuadTree> consumer){
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
@Component
|
||||
abstract class ChildComp implements Posc, Rotc{
|
||||
@@ -18,9 +19,14 @@ abstract class ChildComp implements Posc, Rotc{
|
||||
if(parent != null){
|
||||
offsetX = x - parent.getX();
|
||||
offsetY = y - parent.getY();
|
||||
if(rotWithParent && parent instanceof Rotc r){
|
||||
offsetPos = -r.rotation();
|
||||
offsetRot = rotation - r.rotation();
|
||||
if(rotWithParent){
|
||||
if(parent instanceof Rotc r){
|
||||
offsetPos = -r.rotation();
|
||||
offsetRot = rotation - r.rotation();
|
||||
}else if(parent instanceof RotBlock rot){
|
||||
offsetPos = -rot.buildRotation();
|
||||
offsetRot = rotation - rot.buildRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,10 +34,16 @@ abstract class ChildComp implements Posc, Rotc{
|
||||
@Override
|
||||
public void update(){
|
||||
if(parent != null){
|
||||
if(rotWithParent && parent instanceof Rotc r){
|
||||
x = parent.getX() + Angles.trnsx(r.rotation() + offsetPos, offsetX, offsetY);
|
||||
y = parent.getY() + Angles.trnsy(r.rotation() + offsetPos, offsetX, offsetY);
|
||||
rotation = r.rotation() + offsetRot;
|
||||
if(rotWithParent){
|
||||
if(parent instanceof Rotc r){
|
||||
x = parent.getX() + Angles.trnsx(r.rotation() + offsetPos, offsetX, offsetY);
|
||||
y = parent.getY() + Angles.trnsy(r.rotation() + offsetPos, offsetX, offsetY);
|
||||
rotation = r.rotation() + offsetRot;
|
||||
}else if(parent instanceof RotBlock rot){
|
||||
x = parent.getX() + Angles.trnsx(rot.buildRotation() + offsetPos, offsetX, offsetY);
|
||||
y = parent.getY() + Angles.trnsy(rot.buildRotation() + offsetPos, offsetX, offsetY);
|
||||
rotation = rot.buildRotation() + offsetRot;
|
||||
}
|
||||
}else{
|
||||
x = parent.getX() + offsetX;
|
||||
y = parent.getY() + offsetY;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(){
|
||||
if(Vars.state.rules.unitPayloadsExplode) payloads.each(Payload::destroyed);
|
||||
}
|
||||
|
||||
float payloadUsed(){
|
||||
return payloads.sumf(p -> p.size() * p.size());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -59,6 +59,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
|
||||
|
||||
amount -= Time.delta * (1f - liquid.viscosity) / (5f + addSpeed);
|
||||
amount += accepting;
|
||||
amount = Math.min(amount, maxLiquid);
|
||||
accepting = 0f;
|
||||
|
||||
if(amount >= maxLiquid / 1.5f){
|
||||
|
||||
@@ -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. **/
|
||||
@@ -734,7 +738,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
/** @return name of direct or indirect player controller. */
|
||||
@Override
|
||||
public @Nullable String getControllerName(){
|
||||
if(isPlayer()) return getPlayer().name;
|
||||
if(isPlayer()) return getPlayer().coloredName();
|
||||
if(controller instanceof LogicAI ai && ai.controller != null) return ai.controller.lastAccessed;
|
||||
return null;
|
||||
}
|
||||
@@ -788,6 +792,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
@Override
|
||||
@Replace
|
||||
public String toString(){
|
||||
return "Unit#" + id() + ":" + type;
|
||||
return "Unit#" + id() + ":" + type + " (" + x + ", " + y + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -39,6 +39,7 @@ public class EventType{
|
||||
socketConfigChanged,
|
||||
update,
|
||||
unitCommandChange,
|
||||
unitCommandPosition,
|
||||
unitCommandAttack,
|
||||
importMod,
|
||||
draw,
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
|
||||
@@ -39,8 +39,6 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
* @see #eachRunning(Cons)
|
||||
*/
|
||||
public Seq<MapObjective> all = new Seq<>(4);
|
||||
/** @see #checkChanged() */
|
||||
protected transient boolean changed;
|
||||
|
||||
static{
|
||||
registerObjective(
|
||||
@@ -126,21 +124,13 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
eachRunning(obj -> {
|
||||
//objectives cannot get completed on the client, but they do try to update for timers and such
|
||||
if(obj.update() && !net.client()){
|
||||
obj.completed = true;
|
||||
obj.done();
|
||||
Call.completeObjective(all.indexOf(obj));
|
||||
}
|
||||
|
||||
changed |= obj.changed;
|
||||
obj.changed = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** @return True if map rules should be synced. Reserved for {@link Vars#logic}; do not invoke directly! */
|
||||
public boolean checkChanged(){
|
||||
boolean has = changed;
|
||||
changed = false;
|
||||
|
||||
return has;
|
||||
public @Nullable MapObjective get(int index){
|
||||
return index < 0 || index >= all.size ? null : all.get(index);
|
||||
}
|
||||
|
||||
/** @return Whether there are any qualified objectives at all. */
|
||||
@@ -149,7 +139,6 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
if(all.size > 0) changed = true;
|
||||
all.clear();
|
||||
}
|
||||
|
||||
@@ -191,7 +180,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
/** Whether this objective has been done yet. This is internally set. */
|
||||
private boolean completed;
|
||||
/** Internal value. Do not modify! */
|
||||
private transient boolean depFinished, changed;
|
||||
private transient boolean depFinished;
|
||||
|
||||
/** @return True if this objective is done and should be removed from the executor. */
|
||||
public abstract boolean update();
|
||||
@@ -201,13 +190,9 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
|
||||
/** Called once after {@link #update()} returns true, before this objective is removed. */
|
||||
public void done(){
|
||||
changed();
|
||||
Call.objectiveCompleted(flagsRemoved, flagsAdded);
|
||||
}
|
||||
|
||||
/** Notifies the executor that map rules should be synced. */
|
||||
protected void changed(){
|
||||
changed = true;
|
||||
state.rules.objectiveFlags.removeAll(flagsRemoved);
|
||||
state.rules.objectiveFlags.addAll(flagsAdded);
|
||||
completed = true;
|
||||
}
|
||||
|
||||
/** @return True if all {@link #parents} are completed, rendering this objective able to execute. */
|
||||
@@ -866,7 +851,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
|
||||
/** Displays a shape with an outline and color. */
|
||||
public static class ShapeMarker extends PosMarker{
|
||||
public float radius = 8f, rotation = 0f, stroke = 1f;
|
||||
public float radius = 8f, rotation = 0f, stroke = 1f, startAngle = 0f, endAngle = 360f;
|
||||
public boolean fill = false, outline = true;
|
||||
public int sides = 4;
|
||||
public Color color = Color.valueOf("ffd37f");
|
||||
@@ -892,14 +877,18 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
if(!fill){
|
||||
if(outline){
|
||||
Lines.stroke((stroke + 2f) * scaleFactor, Pal.gray);
|
||||
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
|
||||
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
|
||||
}
|
||||
|
||||
Lines.stroke(stroke * scaleFactor, color);
|
||||
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
|
||||
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
|
||||
}else{
|
||||
Draw.color(color);
|
||||
Fill.poly(pos.x, pos.y, sides, radius * scaleFactor, rotation);
|
||||
if (startAngle < endAngle){
|
||||
Fill.arc(pos.x, pos.y, radius * scaleFactor, (endAngle - startAngle) / 360f, rotation + startAngle, sides);
|
||||
}else{
|
||||
Fill.arc(pos.x, pos.y, radius * scaleFactor, (startAngle - endAngle) / 360f, rotation + endAngle, sides);
|
||||
}
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
@@ -916,12 +905,14 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
case rotation -> rotation = (float)p1;
|
||||
case color -> color.fromDouble(p1);
|
||||
case shape -> sides = (int)p1;
|
||||
case arc -> startAngle = (float)p1;
|
||||
}
|
||||
}
|
||||
|
||||
if(!Double.isNaN(p2)){
|
||||
switch(type){
|
||||
case shape -> fill = !Mathf.equal((float)p2, 0f);
|
||||
case arc -> endAngle = (float)p2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import mindustry.world.blocks.*;
|
||||
* Does not store game state, just configuration.
|
||||
*/
|
||||
public class Rules{
|
||||
/** Allows editing the rules in-game. Essentially a cheat mode toggle. */
|
||||
public boolean allowEditRules = false;
|
||||
/** Sandbox mode: Enables infinite resources, build range and build speed. */
|
||||
public boolean infiniteResources;
|
||||
/** Team-specific rules. */
|
||||
@@ -29,6 +31,8 @@ public class Rules{
|
||||
public boolean waveSending = true;
|
||||
/** Whether waves are spawnable at all. */
|
||||
public boolean waves;
|
||||
/** Whether air units spawn at spawns instead of the edge of the map */
|
||||
public boolean airUseSpawns = false;
|
||||
/** Whether the game objective is PvP. Note that this enables automatic hosting. */
|
||||
public boolean pvp;
|
||||
/** Whether is waiting for players enabled in PvP. */
|
||||
@@ -59,6 +63,8 @@ public class Rules{
|
||||
public boolean unitAmmo = false;
|
||||
/** EXPERIMENTAL! If true, blocks will update in units and share power. */
|
||||
public boolean unitPayloadUpdate = false;
|
||||
/** If true, units' payloads are destroy()ed when the unit is destroyed. */
|
||||
public boolean unitPayloadsExplode = false;
|
||||
/** Whether cores add to unit limit */
|
||||
public boolean unitCapVariable = true;
|
||||
/** If true, unit spawn points are shown. */
|
||||
@@ -101,6 +107,8 @@ public class Rules{
|
||||
public boolean cleanupDeadTeams = true;
|
||||
/** If true, items can only be deposited in the core. */
|
||||
public boolean onlyDepositCore = false;
|
||||
/** Cooldown, in seconds, of item depositing for players. */
|
||||
public float itemDepositCooldown = 0.5f;
|
||||
/** If true, every enemy block in the radius of the (enemy) core is destroyed upon death. Used for campaign maps. */
|
||||
public boolean coreDestroyClear = false;
|
||||
/** If true, banned blocks are hidden from the build menu. */
|
||||
@@ -199,6 +207,8 @@ public class Rules{
|
||||
public @Nullable PlanetParams planetBackground;
|
||||
/** Rules from this planet are applied. If it's {@code sun}, mixed tech is enabled. */
|
||||
public Planet planet = Planets.serpulo;
|
||||
/** If the `data` instruction is allowed for world processors */
|
||||
public boolean allowLogicData = false;
|
||||
|
||||
/** Copies this ruleset exactly. Not efficient at all, do not use often. */
|
||||
public Rules copy(){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -176,10 +176,7 @@ public class MinimapRenderer{
|
||||
if(fullView && net.active()){
|
||||
for(Player player : Groups.player){
|
||||
if(!player.dead()){
|
||||
float rx = player.x / (world.width() * tilesize) * w;
|
||||
float ry = player.y / (world.height() * tilesize) * h;
|
||||
|
||||
drawLabel(x + rx, y + ry, player.name, player.color);
|
||||
drawLabel(player.x, player.y, player.name, player.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,12 @@ public class MultiPacker implements Disposable{
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
for(PixmapPacker packer : packers){
|
||||
packer.dispose();
|
||||
for(int i = 0; i < PageType.all.length; i ++){
|
||||
var packer = packers[i];
|
||||
//the UI packer's image is later used when merging with the font, don't dispose it
|
||||
if(i != PageType.ui.ordinal()){
|
||||
packer.forceDispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -474,6 +474,6 @@ public class Shaders{
|
||||
}
|
||||
|
||||
public static Fi getShaderFi(String file){
|
||||
return Core.files.internal("shaders/" + file);
|
||||
return tree.get("shaders/" + file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ public class Trail{
|
||||
x2 = items[i + 3];
|
||||
y2 = items[i + 4];
|
||||
w2 = items[i + 5];
|
||||
|
||||
if(i == 0 && points.size >= (length - 1) * 3){
|
||||
x1 = Mathf.lerp(x1, x2, counter);
|
||||
y1 = Mathf.lerp(y1, y2, counter);
|
||||
w1 = Mathf.lerp(w1, w2, counter);
|
||||
}
|
||||
}else{
|
||||
x2 = lastX;
|
||||
y2 = lastY;
|
||||
@@ -97,13 +103,12 @@ public class Trail{
|
||||
|
||||
/** Removes the last point from the trail at intervals. */
|
||||
public void shorten(){
|
||||
if((counter += Time.delta) >= 1f){
|
||||
if(points.size >= 3){
|
||||
points.removeRange(0, 2);
|
||||
}
|
||||
int count = (int)(counter += Time.delta);
|
||||
counter -= count;
|
||||
|
||||
counter %= 1f;
|
||||
}
|
||||
if(count > 0 && points.size > 0){
|
||||
points.removeRange(0, Math.min(count * 3 - 1, points.size - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a new point to the trail at intervals. */
|
||||
@@ -113,18 +118,27 @@ public class Trail{
|
||||
|
||||
/** Adds a new point to the trail at intervals. */
|
||||
public void update(float x, float y, float width){
|
||||
//TODO fix longer trails at low FPS
|
||||
if((counter += Time.delta) >= 1f){
|
||||
if(points.size > length*3){
|
||||
points.removeRange(0, 2);
|
||||
int count = (int)(counter += Time.delta);
|
||||
counter -= count;
|
||||
|
||||
if(count > 0){
|
||||
int toRemove = points.size + (count - 1 - length) * 3;
|
||||
if(toRemove > 0 && points.size > 0){
|
||||
points.removeRange(0, Math.min(toRemove - 1, points.size - 1));
|
||||
}
|
||||
|
||||
points.add(x, y, width);
|
||||
|
||||
counter %= 1f;
|
||||
//if lastX is -1, this trail has never updated, so only add one point - there is nothing to interpolate with
|
||||
if(count == 1 || lastX == -1f){
|
||||
points.add(x, y, width);
|
||||
}else{
|
||||
for(int i = 0; i < count; i++){
|
||||
float f = (i + 1f) / count;
|
||||
points.add(Mathf.lerp(lastX, x, f), Mathf.lerp(lastY, y, f), Mathf.lerp(lastW, width, f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update last position regardless, so it joins
|
||||
//update last position regardless, so it joins at the origin
|
||||
lastAngle = -Angles.angleRad(x, y, lastX, lastY);
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class DesktopInput extends InputHandler{
|
||||
public long lastCtrlGroupSelectMillis;
|
||||
|
||||
boolean showHint(){
|
||||
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() &&
|
||||
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() && !player.dead() &&
|
||||
(!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore());
|
||||
}
|
||||
|
||||
@@ -114,12 +114,12 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
//draw break selection
|
||||
if(mode == breaking){
|
||||
drawBreakSelection(selectX, selectY, cursorX, cursorY, !Core.input.keyDown(Binding.schematic_select) ? maxLength : Vars.maxSchematicSize, false);
|
||||
drawBreakSelection(selectX, selectY, cursorX, cursorY, !(Core.input.keyDown(Binding.schematic_select) && schemX != -1 && schemY != -1) ? maxLength : Vars.maxSchematicSize, false);
|
||||
}
|
||||
|
||||
if(!Core.scene.hasKeyboard() && mode != breaking){
|
||||
|
||||
if(Core.input.keyDown(Binding.schematic_select)){
|
||||
if(Core.input.keyDown(Binding.schematic_select) && schemX != -1 && schemY != -1){
|
||||
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
|
||||
}else if(Core.input.keyDown(Binding.rebuild_select)){
|
||||
drawRebuildSelection(schemX, schemY, cursorX, cursorY);
|
||||
@@ -451,7 +451,7 @@ public class DesktopInput extends InputHandler{
|
||||
cursorType = cursor.build.getCursor();
|
||||
}
|
||||
|
||||
if(cursor.build != null && cursor.build.team == Team.derelict && Build.validPlace(cursor.block(), player.team(), cursor.build.tileX(), cursor.build.tileY(), cursor.build.rotation)){
|
||||
if(cursor.build != null && player.team() != Team.derelict && cursor.build.team == Team.derelict && Build.validPlace(cursor.block(), player.team(), cursor.build.tileX(), cursor.build.tileY(), cursor.build.rotation)){
|
||||
cursorType = ui.repairCursor;
|
||||
}
|
||||
|
||||
@@ -568,11 +568,11 @@ public class DesktopInput extends InputHandler{
|
||||
schematicY += shiftY;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.deselect) && !isPlacing() && player.unit().plans.isEmpty() && !commandMode){
|
||||
if(Core.input.keyTap(Binding.deselect) && !ui.minimapfrag.shown() && !isPlacing() && player.unit().plans.isEmpty() && !commandMode){
|
||||
player.unit().mineTile = null;
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.clear_building)){
|
||||
if(Core.input.keyTap(Binding.clear_building) && !player.dead()){
|
||||
player.unit().clearBuilding();
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ public class DesktopInput extends InputHandler{
|
||||
selectPlans.clear();
|
||||
}
|
||||
|
||||
if( !Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
|
||||
if(!Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
|
||||
if(Core.input.keyRelease(Binding.schematic_select)){
|
||||
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
|
||||
useSchematic(lastSchematic);
|
||||
|
||||
@@ -83,6 +83,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
public boolean overrideLineRotation;
|
||||
public int rotation;
|
||||
public boolean droppingItem;
|
||||
public float itemDepositCooldown;
|
||||
public Group uiGroup;
|
||||
public boolean isBuilding = true, buildWasAutoPaused = false, wasShooting = false;
|
||||
public @Nullable UnitType controlledType;
|
||||
@@ -115,7 +116,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
private WidgetGroup group = new WidgetGroup();
|
||||
|
||||
private final Eachable<BuildPlan> allPlans = cons -> {
|
||||
player.unit().plans().each(cons);
|
||||
if(!player.dead()){
|
||||
player.unit().plans().each(cons);
|
||||
}
|
||||
selectPlans.each(cons);
|
||||
linePlans.each(cons);
|
||||
};
|
||||
@@ -142,6 +145,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
logicCutscene = false;
|
||||
itemDepositCooldown = 0f;
|
||||
Arrays.fill(controlGroups, null);
|
||||
});
|
||||
}
|
||||
@@ -234,9 +238,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
public static void commandUnits(Player player, int[] unitIds, @Nullable Building buildTarget, @Nullable Unit unitTarget, @Nullable Vec2 posTarget, boolean queueCommand, boolean finalBatch){
|
||||
if(player == null || unitIds == null) return;
|
||||
|
||||
//why did I ever think this was a good idea
|
||||
if(unitTarget != null && unitTarget.isNull()) unitTarget = null;
|
||||
|
||||
if(net.server() && !netServer.admins.allowAction(player, ActionType.commandUnits, event -> {
|
||||
event.unitIDs = unitIds;
|
||||
})){
|
||||
@@ -258,7 +259,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
if(teamTarget != null && teamTarget.team() != player.team() &&
|
||||
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
|
||||
!(teamTarget instanceof Unit u && !unit.canTarget(u)) && !(teamTarget instanceof Building && !unit.type.targetGround)){
|
||||
|
||||
anyCommandedTarget = true;
|
||||
if(queueCommand){
|
||||
@@ -280,7 +281,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(ai.commandQueue.size <= 0){
|
||||
ai.group = null;
|
||||
}
|
||||
|
||||
|
||||
//remove when other player command
|
||||
if(!headless && player != Vars.player){
|
||||
control.input.selectedUnits.remove(unit);
|
||||
@@ -311,18 +312,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
float minSpeed = 100000000f;
|
||||
for(int i = 0; i < groups.length; i ++){
|
||||
var group = groups[i];
|
||||
if(group != null && group.units.size > 0){
|
||||
group.calculateFormation(targetAsVec, i);
|
||||
minSpeed = Math.min(group.minSpeed, minSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
for(var group : groups){
|
||||
if(group != null){
|
||||
group.minSpeed = minSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,7 +392,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(build == null || build.team() != player.team() || !build.block.commandable) continue;
|
||||
|
||||
build.onCommand(target);
|
||||
build.lastAccessed = player.name;
|
||||
build.updateLastAccess(player);
|
||||
|
||||
if(!state.isPaused() && player == Vars.player){
|
||||
Fx.moveCommand.at(target);
|
||||
@@ -412,7 +405,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
@Remote(called = Loc.server, targets = Loc.both, forward = true)
|
||||
public static void requestItem(Player player, Building build, Item item, int amount){
|
||||
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, itemTransferRange) || player.dead()) return;
|
||||
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, itemTransferRange) || player.dead() || amount <= 0) return;
|
||||
|
||||
if(net.server() && (!Units.canInteract(player, build) ||
|
||||
!netServer.admins.allowAction(player, ActionType.withdrawItem, build.tile(), action -> {
|
||||
@@ -431,6 +424,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(player == null || build == null || !player.within(build, itemTransferRange) || build.items == null || player.dead() || (state.rules.onlyDepositCore && !(build instanceof CoreBuild))) return;
|
||||
|
||||
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, build) ||
|
||||
//to avoid rejecting deposit packets that happen to overlap due to packet speed differences, the actual cap is double the cooldown with 2 deposits.
|
||||
(!player.isLocal() && !player.itemDepositRate.allow((long)(state.rules.itemDepositCooldown * 1000 * 2), 2)) ||
|
||||
|
||||
!netServer.admins.allowAction(player, ActionType.depositItem, build.tile, action -> {
|
||||
action.itemAmount = player.unit().stack.amount;
|
||||
action.item = player.unit().item();
|
||||
@@ -600,11 +596,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(build == null) return;
|
||||
|
||||
if(net.server() && (!Units.canInteract(player, build) ||
|
||||
!netServer.admins.allowAction(player, ActionType.rotate, build.tile(), action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
|
||||
!netServer.admins.allowAction(player, ActionType.rotate, build.tile(), action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
|
||||
throw new ValidateException(player, "Player cannot rotate a block.");
|
||||
}
|
||||
|
||||
if(player != null) build.lastAccessed = player.name;
|
||||
if(player != null) build.updateLastAccess(player);
|
||||
int previous = build.rotation;
|
||||
build.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4);
|
||||
build.updateProximity();
|
||||
@@ -619,7 +615,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(build == null) return;
|
||||
|
||||
if(net.server() && (!Units.canInteract(player, build) ||
|
||||
!netServer.admins.allowAction(player, ActionType.configure, build.tile, action -> action.config = value))){
|
||||
!netServer.admins.allowAction(player, ActionType.configure, build.tile, action -> action.config = value))){
|
||||
|
||||
if(player.con != null){
|
||||
var packet = new TileConfigCallPacket(); //undo the config on the client
|
||||
@@ -698,7 +694,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
player.unit(unit);
|
||||
|
||||
if(before != null && !before.isNull()){
|
||||
if(before != null){
|
||||
if(before.spawnedByCore){
|
||||
unit.dockedType = before.type;
|
||||
}else if(before.dockedType != null && before.dockedType.coreUnitDock){
|
||||
@@ -804,6 +800,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
logicCutsceneZoom = -1f;
|
||||
}
|
||||
|
||||
itemDepositCooldown -= Time.delta / 60f;
|
||||
|
||||
commandBuildings.removeAll(b -> !b.isValid());
|
||||
|
||||
if(!commandMode){
|
||||
@@ -811,7 +809,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
playerPlanTree.clear();
|
||||
player.unit().plans.each(playerPlanTree::insert);
|
||||
if(!player.dead()){
|
||||
player.unit().plans.each(playerPlanTree::insert);
|
||||
}
|
||||
|
||||
player.typing = ui.chatfrag.shown();
|
||||
|
||||
@@ -823,7 +823,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
player.unit().updateBuilding(isBuilding);
|
||||
}
|
||||
|
||||
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && !player.team().rules().infiniteAmmo && player.unit().ammo <= 0){
|
||||
if(!player.dead() && player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && !player.team().rules().infiniteAmmo && player.unit().ammo <= 0){
|
||||
player.unit().type.weapons.first().noAmmoSound.at(player.unit());
|
||||
}
|
||||
|
||||
@@ -1012,6 +1012,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
if(attack != null){
|
||||
Events.fire(Trigger.unitCommandAttack);
|
||||
}else{
|
||||
Events.fire(Trigger.unitCommandPosition);
|
||||
}
|
||||
|
||||
int maxChunkSize = 200;
|
||||
@@ -1179,7 +1181,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
var last = lastSchematic;
|
||||
|
||||
ui.showTextInput("@schematic.add", "@name", "", text -> {
|
||||
ui.showTextInput("@schematic.add", "@name", 1000, "", text -> {
|
||||
Schematic replacement = schematics.all().find(s -> s.name().equals(text));
|
||||
if(replacement != null){
|
||||
ui.showConfirm("@confirm", "@schematic.replace", () -> {
|
||||
@@ -1661,7 +1663,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
boolean tryRepairDerelict(Tile selected){
|
||||
if(selected != null && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict && Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){
|
||||
if(selected != null && player.team() != Team.derelict && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict && Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){
|
||||
player.unit().addBuild(new BuildPlan(selected.build.tileX(), selected.build.tileY(), selected.build.rotation, selected.block(), selected.build.config()));
|
||||
return true;
|
||||
}
|
||||
@@ -1670,9 +1672,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
boolean canMine(Tile tile){
|
||||
return !Core.scene.hasMouse()
|
||||
&& player.unit().validMine(tile)
|
||||
&& player.unit().acceptsItem(player.unit().getMineResult(tile))
|
||||
&& !((!Core.settings.getBool("doubletapmine") && tile.floor().playerUnmineable) && tile.overlay().itemDrop == null);
|
||||
&& player.unit().validMine(tile)
|
||||
&& player.unit().acceptsItem(player.unit().getMineResult(tile))
|
||||
&& !((!Core.settings.getBool("doubletapmine") && tile.floor().playerUnmineable) && tile.overlay().itemDrop == null);
|
||||
}
|
||||
|
||||
/** Returns the tile at the specified MOUSE coordinates. */
|
||||
@@ -1838,7 +1840,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
public boolean canShoot(){
|
||||
return block == null && !onConfigurable() && !isDroppingItem() && !player.unit().activelyBuilding() &&
|
||||
!(player.unit() instanceof Mechc && player.unit().isFlying()) && !player.unit().mining() && !commandMode;
|
||||
!(player.unit() instanceof Mechc && player.unit().isFlying()) && !player.unit().mining() && !commandMode;
|
||||
}
|
||||
|
||||
public boolean onConfigurable(){
|
||||
@@ -1864,9 +1866,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
ItemStack stack = player.unit().stack;
|
||||
|
||||
if(build != null && build.acceptStack(stack.item, stack.amount, player.unit()) > 0 && build.interactable(player.team()) &&
|
||||
build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){
|
||||
if(!(state.rules.onlyDepositCore && !(build instanceof CoreBuild))){
|
||||
build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){
|
||||
|
||||
if(!(state.rules.onlyDepositCore && !(build instanceof CoreBuild)) && itemDepositCooldown <= 0f){
|
||||
Call.transferInventory(player, build);
|
||||
itemDepositCooldown = state.rules.itemDepositCooldown;
|
||||
}
|
||||
}else{
|
||||
Call.dropItem(player.angleTo(x, y));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.logic.LExecutor.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.legacy.*;
|
||||
@@ -27,12 +25,11 @@ public class GlobalVars{
|
||||
public static final Rand rand = new Rand();
|
||||
|
||||
//non-constants that depend on state
|
||||
private static int varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
|
||||
private static LVar varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
|
||||
|
||||
private ObjectIntMap<String> namesToIds = new ObjectIntMap<>();
|
||||
private Seq<Var> vars = new Seq<>(Var.class);
|
||||
private ObjectMap<String, LVar> vars = new ObjectMap<>();
|
||||
private Seq<VarEntry> varEntries = new Seq<>();
|
||||
private IntSet privilegedIds = new IntSet();
|
||||
private ObjectSet<String> privilegedNames = new ObjectSet<>();
|
||||
private UnlockableContent[][] logicIdToContent;
|
||||
private int[][] contentIdToLogicId;
|
||||
|
||||
@@ -118,13 +115,14 @@ public class GlobalVars{
|
||||
put("@color" + Strings.capitalize(entry.key), entry.value.toDoubleBits());
|
||||
}
|
||||
|
||||
//used as a special value for any environmental solid block
|
||||
put("@solid", Blocks.stoneWall);
|
||||
|
||||
for(UnitType type : Vars.content.units()){
|
||||
put("@" + type.name, type);
|
||||
}
|
||||
|
||||
for(Weather weather : Vars.content.weathers()){
|
||||
put("@" + weather.name, weather);
|
||||
}
|
||||
|
||||
//store sensor constants
|
||||
for(LAccess sensor : LAccess.all){
|
||||
put("@" + sensor.name(), sensor);
|
||||
@@ -168,31 +166,31 @@ public class GlobalVars{
|
||||
public void update(){
|
||||
//set up time; note that @time is now only updated once every invocation and directly based off of @tick.
|
||||
//having time be based off of user system time was a very bad idea.
|
||||
vars.items[varTime].numval = state.tick / 60.0 * 1000.0;
|
||||
vars.items[varTick].numval = state.tick;
|
||||
varTime.numval = state.tick / 60.0 * 1000.0;
|
||||
varTick.numval = state.tick;
|
||||
|
||||
//shorthands for seconds/minutes spent in save
|
||||
vars.items[varSecond].numval = state.tick / 60f;
|
||||
vars.items[varMinute].numval = state.tick / 60f / 60f;
|
||||
varSecond.numval = state.tick / 60f;
|
||||
varMinute.numval = state.tick / 60f / 60f;
|
||||
|
||||
//wave state
|
||||
vars.items[varWave].numval = state.wave;
|
||||
vars.items[varWaveTime].numval = state.wavetime / 60f;
|
||||
varWave.numval = state.wave;
|
||||
varWaveTime.numval = state.wavetime / 60f;
|
||||
|
||||
vars.items[varMapW].numval = world.width();
|
||||
vars.items[varMapH].numval = world.height();
|
||||
varMapW.numval = world.width();
|
||||
varMapH.numval = world.height();
|
||||
|
||||
//network
|
||||
vars.items[varServer].numval = (net.server() || !net.active()) ? 1 : 0;
|
||||
vars.items[varClient].numval = net.client() ? 1 : 0;
|
||||
varServer.numval = (net.server() || !net.active()) ? 1 : 0;
|
||||
varClient.numval = net.client() ? 1 : 0;
|
||||
|
||||
//client
|
||||
if(!net.server() && player != null){
|
||||
vars.items[varClientLocale].objval = player.locale();
|
||||
vars.items[varClientUnit].objval = player.unit();
|
||||
vars.items[varClientName].objval = player.name();
|
||||
vars.items[varClientTeam].numval = player.team().id;
|
||||
vars.items[varClientMobile].numval = mobile ? 1 : 0;
|
||||
varClientLocale.objval = player.locale();
|
||||
varClientUnit.objval = player.unit();
|
||||
varClientName.objval = player.name();
|
||||
varClientTeam.numval = player.team().id;
|
||||
varClientMobile.numval = mobile ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,84 +211,81 @@ public class GlobalVars{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a constant ID > 0 if there is a constant with this name, otherwise -1.
|
||||
* Attempt to get privileged variable id from non-privileged logic executor returns null constant id.
|
||||
* @return a constant variable if there is a constant with this name, otherwise null.
|
||||
* Attempt to get privileged variable from non-privileged logic executor returns null constant.
|
||||
*/
|
||||
public int get(String name){
|
||||
return namesToIds.get(name, -1);
|
||||
public LVar get(String name){
|
||||
return vars.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a constant variable by ID. ID is not bound checked and must be positive.
|
||||
* Attempt to get privileged variable from non-privileged logic executor returns null constant
|
||||
* @return a constant variable by name
|
||||
* Attempt to get privileged variable from non-privileged logic executor returns null constant.
|
||||
*/
|
||||
public Var get(int id, boolean privileged){
|
||||
if(!privileged && privilegedIds.contains(id)) return vars.get(namesToIds.get("null"));
|
||||
return vars.items[id];
|
||||
public LVar get(String name, boolean privileged){
|
||||
if(!privileged && privilegedNames.contains(name)) return vars.get("null");
|
||||
return vars.get(name);
|
||||
}
|
||||
|
||||
/** Sets a global variable by an ID returned from put(). */
|
||||
public void set(int id, double value){
|
||||
get(id, true).numval = value;
|
||||
/** Sets a global variable by name. */
|
||||
public void set(String name, double value){
|
||||
get(name, true).numval = value;
|
||||
}
|
||||
|
||||
/** Adds a constant value by name. */
|
||||
public int put(String name, Object value, boolean privileged){
|
||||
public LVar put(String name, Object value, boolean privileged){
|
||||
return put(name, value, privileged, true);
|
||||
}
|
||||
|
||||
/** Adds a constant value by name. */
|
||||
public int put(String name, Object value, boolean privileged, boolean hidden){
|
||||
int existingIdx = namesToIds.get(name, -1);
|
||||
if(existingIdx != -1){ //don't overwrite existing vars (see #6910)
|
||||
public LVar put(String name, Object value, boolean privileged, boolean hidden){
|
||||
LVar existingVar = vars.get(name);
|
||||
if(existingVar != null){ //don't overwrite existing vars (see #6910)
|
||||
Log.debug("Failed to add global logic variable '@', as it already exists.", name);
|
||||
return existingIdx;
|
||||
return existingVar;
|
||||
}
|
||||
|
||||
Var var = new Var(name);
|
||||
LVar var = new LVar(name);
|
||||
var.constant = true;
|
||||
if(value instanceof Number num){
|
||||
var.isobj = false;
|
||||
var.numval = num.doubleValue();
|
||||
}else{
|
||||
var.isobj = true;
|
||||
var.objval = value;
|
||||
}
|
||||
|
||||
int index = vars.size;
|
||||
namesToIds.put(name, index);
|
||||
if(privileged) privilegedIds.add(index);
|
||||
vars.add(var);
|
||||
vars.put(name, var);
|
||||
if(privileged) privilegedNames.add(name);
|
||||
|
||||
if(!hidden){
|
||||
varEntries.add(new VarEntry(index, name, "", "", privileged));
|
||||
varEntries.add(new VarEntry(name, "", "", privileged));
|
||||
}
|
||||
return index;
|
||||
return var;
|
||||
}
|
||||
|
||||
public int put(String name, Object value){
|
||||
public LVar put(String name, Object value){
|
||||
return put(name, value, false);
|
||||
}
|
||||
|
||||
public int putEntry(String name, Object value){
|
||||
public LVar putEntry(String name, Object value){
|
||||
return put(name, value, false, false);
|
||||
}
|
||||
|
||||
public int putEntry(String name, Object value, boolean privileged){
|
||||
public LVar putEntry(String name, Object value, boolean privileged){
|
||||
return put(name, value, privileged, false);
|
||||
}
|
||||
|
||||
public void putEntryOnly(String name){
|
||||
varEntries.add(new VarEntry(0, name, "", "", false));
|
||||
varEntries.add(new VarEntry(name, "", "", false));
|
||||
}
|
||||
|
||||
/** An entry that describes a variable for documentation purposes. This is *only* used inside UI for global variables. */
|
||||
public static class VarEntry{
|
||||
public int id;
|
||||
public String name, description, icon;
|
||||
public boolean privileged;
|
||||
|
||||
public VarEntry(int id, String name, String description, String icon, boolean privileged){
|
||||
this.id = id;
|
||||
public VarEntry(String name, String description, String icon, boolean privileged){
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.icon = icon;
|
||||
|
||||
@@ -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, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,16 +14,15 @@ public class LAssembler{
|
||||
|
||||
private static final int invalidNum = Integer.MIN_VALUE;
|
||||
|
||||
private int lastVar;
|
||||
private boolean privileged;
|
||||
/** Maps names to variable IDs. */
|
||||
public ObjectMap<String, BVar> vars = new ObjectMap<>();
|
||||
/** Maps names to variable. */
|
||||
public OrderedMap<String, LVar> vars = new OrderedMap<>();
|
||||
/** All instructions to be executed. */
|
||||
public LInstruction[] instructions;
|
||||
|
||||
public LAssembler(){
|
||||
//instruction counter
|
||||
putVar("@counter").value = 0;
|
||||
putVar("@counter");
|
||||
//currently controlled unit
|
||||
putConst("@unit", null);
|
||||
//reference to self
|
||||
@@ -57,20 +56,17 @@ public class LAssembler{
|
||||
return new LParser(text, privileged).parse();
|
||||
}
|
||||
|
||||
/** @return a variable ID by name.
|
||||
/** @return a variable by name.
|
||||
* This may be a constant variable referring to a number or object. */
|
||||
public int var(String symbol){
|
||||
int constId = Vars.logicVars.get(symbol);
|
||||
if(constId > 0){
|
||||
//global constants are *negated* and stored separately
|
||||
return -constId;
|
||||
}
|
||||
public LVar var(String symbol){
|
||||
LVar constVar = Vars.logicVars.get(symbol);
|
||||
if(constVar != null) return constVar;
|
||||
|
||||
symbol = symbol.trim();
|
||||
|
||||
//string case
|
||||
if(!symbol.isEmpty() && symbol.charAt(0) == '\"' && symbol.charAt(symbol.length() - 1) == '\"'){
|
||||
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
|
||||
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n"));
|
||||
}
|
||||
|
||||
//remove spaces for non-strings
|
||||
@@ -79,10 +75,10 @@ public class LAssembler{
|
||||
double value = parseDouble(symbol);
|
||||
|
||||
if(value == invalidNum){
|
||||
return putVar(symbol).id;
|
||||
return putVar(symbol);
|
||||
}else{
|
||||
//this creates a hidden const variable with the specified value
|
||||
return putConst("___" + value, value).id;
|
||||
return putConst("___" + value, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,48 +102,34 @@ public class LAssembler{
|
||||
}
|
||||
|
||||
/** Adds a constant value by name. */
|
||||
public BVar putConst(String name, Object value){
|
||||
BVar var = putVar(name);
|
||||
public LVar putConst(String name, Object value){
|
||||
LVar var = putVar(name);
|
||||
if(value instanceof Number number){
|
||||
var.isobj = false;
|
||||
var.numval = number.doubleValue();
|
||||
var.objval = null;
|
||||
}else{
|
||||
var.isobj = true;
|
||||
var.objval = value;
|
||||
}
|
||||
var.constant = true;
|
||||
var.value = value;
|
||||
return var;
|
||||
}
|
||||
|
||||
/** Registers a variable name mapping. */
|
||||
public BVar putVar(String name){
|
||||
public LVar putVar(String name){
|
||||
if(vars.containsKey(name)){
|
||||
return vars.get(name);
|
||||
}else{
|
||||
BVar var = new BVar(lastVar++);
|
||||
LVar var = new LVar(name);
|
||||
vars.put(name, var);
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BVar getVar(String name){
|
||||
public LVar getVar(String name){
|
||||
return vars.get(name);
|
||||
}
|
||||
|
||||
/** A variable "builder". */
|
||||
public static class BVar{
|
||||
public int id;
|
||||
public boolean constant;
|
||||
public Object value;
|
||||
|
||||
public BVar(int id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
BVar(){}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "BVar{" +
|
||||
"id=" + id +
|
||||
", constant=" + constant +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,12 @@ public class LCanvas extends Table{
|
||||
this.statements.layout();
|
||||
}
|
||||
|
||||
public void clearStatements(){
|
||||
jumps.clear();
|
||||
statements.clearChildren();
|
||||
statements.layout();
|
||||
}
|
||||
|
||||
StatementElem checkHovered(){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
if(e != null){
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ public enum LMarkerControl{
|
||||
stroke("stroke"),
|
||||
rotation("rotation"),
|
||||
shape("sides", "fill", "outline"),
|
||||
arc("start", "end"),
|
||||
flushText("fetch"),
|
||||
fontSize("size"),
|
||||
textHeight("height"),
|
||||
|
||||
@@ -16,7 +16,7 @@ import mindustry.logic.LCanvas.*;
|
||||
import mindustry.logic.LExecutor.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.ui;
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.logic.LCanvas.*;
|
||||
|
||||
/**
|
||||
|
||||
@@ -260,6 +260,13 @@ public class LStatements{
|
||||
}, 2, cell -> cell.size(165, 50)));
|
||||
}, Styles.logict, () -> {}).size(165, 40).color(s.color).left().padLeft(2);
|
||||
}
|
||||
case translate, scale -> {
|
||||
fields(s, "x", x, v -> x = v);
|
||||
fields(s, "y", y, v -> y = v);
|
||||
}
|
||||
case rotate -> {
|
||||
fields(s, "degrees", p1, v -> p1 = v);
|
||||
}
|
||||
}
|
||||
}).expand().left();
|
||||
}
|
||||
@@ -274,8 +281,8 @@ public class LStatements{
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new DrawI((byte)type.ordinal(), 0, builder.var(x), builder.var(y),
|
||||
type == GraphicsType.print ? nameToAlign.get(p1, Align.bottomLeft) : builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
||||
return new DrawI((byte)type.ordinal(), builder.var(x), builder.var(y),
|
||||
type == GraphicsType.print ? new LVar(p1, nameToAlign.get(p1, Align.bottomLeft), true) : builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1038,7 +1045,7 @@ public class LStatements{
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new RadarI(target1, target2, target3, sort, LExecutor.varUnit, builder.var(sortOrder), builder.var(output));
|
||||
return new RadarI(target1, target2, target3, sort, builder.var("@unit"), builder.var(sortOrder), builder.var(output));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1373,6 +1380,115 @@ public class LStatements{
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("weathersense")
|
||||
public static class WeatherSenseStatement extends LStatement{
|
||||
public String to = "result";
|
||||
public String weather = "@rain";
|
||||
|
||||
private transient TextField tfield;
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
field(table, to, str -> to = str);
|
||||
|
||||
table.add(" = weather ");
|
||||
|
||||
row(table);
|
||||
|
||||
tfield = field(table, weather, str -> weather = str).padRight(0f).get();
|
||||
|
||||
table.button(b -> {
|
||||
b.image(Icon.pencilSmall);
|
||||
|
||||
b.clicked(() -> showSelectTable(b, (t, hide) -> {
|
||||
t.row();
|
||||
t.table(i -> {
|
||||
i.left();
|
||||
int c = 0;
|
||||
for(Weather w : Vars.content.weathers()){
|
||||
i.button(w.name, Styles.flatt, () -> {
|
||||
weather = "@" + w.name;
|
||||
tfield.setText(weather);
|
||||
hide.run();
|
||||
}).height(40f).uniformX().wrapLabel(false).growX();
|
||||
|
||||
if(++c % 2 == 0) i.row();
|
||||
}
|
||||
}).left();
|
||||
}));
|
||||
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean privileged(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new SenseWeatherI(builder.var(weather), builder.var(to));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.world;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("weatherset")
|
||||
public static class WeatherSetStatement extends LStatement{
|
||||
public String weather = "@rain", state = "true";
|
||||
|
||||
private transient TextField tfield;
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
table.add(" set weather ");
|
||||
|
||||
tfield = field(table, weather, str -> weather = str).padRight(0f).get();
|
||||
|
||||
table.button(b -> {
|
||||
b.image(Icon.pencilSmall);
|
||||
|
||||
b.clicked(() -> showSelectTable(b, (t, hide) -> {
|
||||
t.row();
|
||||
t.table(i -> {
|
||||
i.left();
|
||||
int c = 0;
|
||||
for(Weather w : Vars.content.weathers()){
|
||||
i.button(w.name, Styles.flatt, () -> {
|
||||
weather = "@" + w.name;
|
||||
tfield.setText(weather);
|
||||
hide.run();
|
||||
}).height(40f).uniformX().wrapLabel(false).growX();
|
||||
|
||||
if(++c % 2 == 0) i.row();
|
||||
}
|
||||
}).left();
|
||||
}));
|
||||
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
|
||||
|
||||
table.add(" state ");
|
||||
|
||||
fields(table, state, str -> state = str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean privileged(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new SetWeatherI(builder.var(weather), builder.var(state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.world;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("spawnwave")
|
||||
public static class SpawnWaveStatement extends LStatement{
|
||||
public String x = "10", y = "10", natural = "false";
|
||||
@@ -1740,11 +1856,17 @@ public class LStatements{
|
||||
fields(table, index, i -> index = i);
|
||||
}
|
||||
|
||||
if(type == FetchType.buildCount || type == FetchType.build || type == FetchType.unitCount){
|
||||
if(type == FetchType.buildCount || type == FetchType.build){
|
||||
row(table);
|
||||
|
||||
fields(table, "block", extra, i -> extra = i);
|
||||
}
|
||||
|
||||
if(type == FetchType.unitCount || type == FetchType.unit){
|
||||
row(table);
|
||||
|
||||
fields(table, "unit", extra, i -> extra = i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1789,6 +1911,42 @@ public class LStatements{
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("clientdata")
|
||||
public static class ClientDataStatement extends LStatement{
|
||||
public String channel = "\"frog\"", value = "\"bar\"", reliable = "0";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
table.add("send ");
|
||||
fields(table, value, str -> value = str);
|
||||
table.add(" on ");
|
||||
fields(table, channel, str -> channel = str);
|
||||
table.add(", reliable ");
|
||||
fields(table, reliable, str -> reliable = str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hidden(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean privileged(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
if(!state.rules.allowLogicData) return null;
|
||||
return new ClientDataI(builder.var(channel), builder.var(value), builder.var(reliable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.world;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("getflag")
|
||||
public static class GetFlagStatement extends LStatement{
|
||||
public String result = "result", flag = "\"flag\"";
|
||||
|
||||
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,
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user