Merge branch 'master' of https://github.com/Anuken/Mindustry into new-pathfinding

This commit is contained in:
Anuken
2024-04-15 13:08:48 -04:00
235 changed files with 10974 additions and 3792 deletions
+6 -1
View File
@@ -29,6 +29,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
private long nextFrame;
private long beginTime;
private long lastTargetFps = -1;
private boolean finished = false;
private LoadRenderer loader;
@@ -68,6 +69,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, 60f / 10f);
});
UI.loadColors();
batch = new SortedSpriteBatch();
assets = new AssetManager();
assets.setLoader(Texture.class, "." + mapExtension, new MapPreviewLoader());
@@ -200,9 +202,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void update(){
int targetfps = Core.settings.getInt("fpscap", 120);
boolean changed = lastTargetFps != targetfps && lastTargetFps != -1;
boolean limitFps = targetfps > 0 && targetfps <= 240;
if(limitFps){
lastTargetFps = targetfps;
if(limitFps && !changed){
nextFrame += (1000 * 1000000) / targetfps;
}else{
nextFrame = Time.nanos();
+3 -2
View File
@@ -28,6 +28,7 @@ import mindustry.net.*;
import mindustry.service.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.meta.*;
import java.io.*;
@@ -105,8 +106,8 @@ public class Vars implements Loadable{
public static final float invasionGracePeriod = 20;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.1f;
/** land/launch animation duration */
public static final float coreLandDuration = 160f;
/** @deprecated see {@link CoreBlock#landDuration} instead! */
public static final @Deprecated float coreLandDuration = 160f;
/** size of tiles in units */
public static final int tilesize = 8;
/** size of one tile payload (^2) */
+1 -8
View File
@@ -12,14 +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 float minSpeed = 999999f;
public void calculateFormation(Vec2 dest, int collisionLayer){
this.collisionLayer = collisionLayer;
@@ -33,20 +31,15 @@ public class UnitGroup{
cy /= units.size;
positions = new float[units.size * 2];
//all positions are relative to the center
for(int i = 0; i < units.size; i ++){
Unit unit = units.get(i);
positions[i * 2] = unit.x - cx;
positions[i * 2 + 1] = unit.y - cy;
unit.command().groupIndex = i;
//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;
//run on new thread to prevent stutter
Vars.mainExecutor.submit(() -> {
//unused space between circles that needs to be reached for compression to end
+9 -4
View File
@@ -30,6 +30,8 @@ public class CommandAI extends AIController{
public int groupIndex = 0;
/** All encountered unreachable buildings of this AI. Why a sequence? Because contains() is very rarely called on it. */
public IntSeq unreachableBuildings = new IntSeq(8);
/** ID of unit read as target. This is set up after reading. Do not access! */
public int readAttackTarget = -1;
protected boolean stopAtTarget, stopWhenInRange;
protected Vec2 lastTargetPos;
@@ -214,7 +216,7 @@ public class CommandAI extends AIController{
}
//TODO: should the unit stop when it finds a target?
if(stance == UnitStance.patrol && target != null && unit.within(target, unit.type.range - 2f)){
if(stance == UnitStance.patrol && target != null && unit.within(target, unit.type.range - 2f) && !unit.type.circleTarget){
move = false;
}
@@ -285,7 +287,7 @@ public class CommandAI extends AIController{
attackTarget = null;
}
if(unit.isFlying() && move){
if(unit.isFlying() && move && (attackTarget == null || !unit.within(attackTarget, unit.type.range))){
unit.lookAt(vecMovePos);
}else{
faceTarget();
@@ -351,8 +353,11 @@ public class CommandAI extends AIController{
}
@Override
public float prefSpeed(){
return group == null ? super.prefSpeed() : Math.min(group.minSpeed, unit.speed());
public void afterRead(Unit unit){
if(readAttackTarget != -1){
attackTarget = Groups.unit.getByID(readAttackTarget);
readAttackTarget = -1;
}
}
@Override
@@ -2,6 +2,8 @@ package mindustry.ai.types;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -29,6 +31,11 @@ public class MissileAI extends AIController{
}
}
@Override
public Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground && (!t.block.underBullets || (shooter != null && t == Vars.world.buildWorld(shooter.aimX, shooter.aimY))));
}
@Override
public boolean retarget(){
//more frequent retarget due to high speed. TODO won't this lag?
+9 -3
View File
@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
/** Controls playback of multiple audio tracks.*/
public class SoundControl{
public float finTime = 120f, foutTime = 120f, musicInterval = 3f * Time.toMinutes, musicChance = 0.6f, musicWaveChance = 0.46f;
public float finTime = 120f, foutTime = 120f, musicInterval = 3f * Time.toMinutes, musicChance = 0.8f, musicWaveChance = 0.46f;
/** normal, ambient music, plays at any time */
public Seq<Music> ambientMusic = Seq.with();
@@ -28,6 +28,7 @@ public class SoundControl{
protected Music lastRandomPlayed;
protected Interval timer = new Interval(4);
protected long lastPlayed;
protected @Nullable Music current;
protected float fade;
protected boolean silenced;
@@ -55,6 +56,10 @@ public class SoundControl{
}));
setupFilters();
Events.on(ResetEvent.class, e -> {
lastPlayed = Time.millis();
});
}
protected void setupFilters(){
@@ -146,7 +151,7 @@ public class SoundControl{
if(state.isMenu()){
silenced = false;
if(ui.planet.isShown()){
play(Musics.launch);
play(ui.planet.state.planet.launchMusic);
}else if(ui.editor.isShown()){
play(Musics.editor);
}else{
@@ -160,9 +165,10 @@ public class SoundControl{
silence();
//play music at intervals
if(timer.get(musicInterval)){
if(Time.timeSinceMillis(lastPlayed) > 1000 * musicInterval / 60f){
//chance to play it per interval
if(Mathf.chance(musicChance)){
lastPlayed = Time.millis();
playRandom();
}
}
+27 -20
View File
@@ -1181,6 +1181,7 @@ public class Blocks{
rotate = true;
invertFlip = true;
group = BlockGroup.liquids;
itemCapacity = 0;
liquidCapacity = 50f;
@@ -1231,6 +1232,7 @@ public class Blocks{
}});
researchCostMultiplier = 1.1f;
itemCapacity = 0;
liquidCapacity = 40f;
consumePower(2f);
ambientSound = Sounds.extractLoop;
@@ -2433,6 +2435,7 @@ public class Blocks{
itemDuration = 140f;
ambientSound = Sounds.pulse;
ambientSoundVolume = 0.07f;
liquidCapacity = 60f;
consumePower(25f);
consumeItem(Items.blastCompound);
@@ -2781,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;
@@ -4045,7 +4049,7 @@ public class Blocks{
researchCostMultiplier = 0.05f;
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
limitRange();
limitRange(12f);
}};
diffuse = new ItemTurret("diffuse"){{
@@ -4104,7 +4108,7 @@ public class Blocks{
rotateSpeed = 3f;
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
limitRange();
limitRange(16f);
}};
sublimate = new ContinuousLiquidTurret("sublimate"){{
@@ -4354,7 +4358,7 @@ public class Blocks{
coolant = consume(new ConsumeLiquid(Liquids.water, 20f / 60f));
coolantMultiplier = 2.5f;
limitRange(-5f);
limitRange(5f);
}};
afflict = new PowerTurret("afflict"){{
@@ -4566,6 +4570,7 @@ public class Blocks{
loopSoundVolume = 0.6f;
deathSound = Sounds.largeExplosion;
targetAir = false;
targetUnderBlocks = false;
fogRadius = 6f;
@@ -5500,23 +5505,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";
@@ -5535,6 +5523,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));
@@ -5789,6 +5794,8 @@ public class Blocks{
heatOutput = 1000f;
warmupRate = 1000f;
regionRotated1 = 1;
itemCapacity = 0;
alwaysUnlocked = true;
ambientSound = Sounds.none;
}};
+1 -1
View File
@@ -54,7 +54,7 @@ public class Liquids{
capPuddles = false;
spreadTarget = Liquids.water;
moveThroughBlocks = true;
incinerable = true;
incinerable = false;
blockReactive = false;
canStayOn.addAll(water, oil, cryofluid);
+13 -1
View File
@@ -1318,6 +1318,7 @@ public class UnitTypes{
healPercent = 5.5f;
collidesTeam = true;
reflectable = false;
backColor = Pal.heal;
trailColor = Pal.heal;
}};
@@ -1845,6 +1846,7 @@ public class UnitTypes{
armor = 3f;
buildSpeed = 1.5f;
rotateToBuilding = false;
weapons.add(new RepairBeamWeapon("repair-beam-weapon-center"){{
x = 0f;
@@ -1935,6 +1937,7 @@ public class UnitTypes{
abilities.add(new StatusFieldAbility(StatusEffects.overclock, 60f * 6, 60f * 6f, 60f));
buildSpeed = 2f;
rotateToBuilding = false;
weapons.add(new Weapon("plasma-mount-weapon"){{
@@ -2009,6 +2012,7 @@ public class UnitTypes{
trailScl = 2f;
buildSpeed = 2f;
rotateToBuilding = false;
weapons.add(new RepairBeamWeapon("repair-beam-weapon-center"){{
x = 11f;
@@ -2150,6 +2154,7 @@ public class UnitTypes{
trailScl = 3.2f;
buildSpeed = 3f;
rotateToBuilding = false;
abilities.add(new EnergyFieldAbility(40f, 65f, 180f){{
statusDuration = 60f * 6f;
@@ -2193,6 +2198,7 @@ public class UnitTypes{
trailScl = 3.5f;
buildSpeed = 3.5f;
rotateToBuilding = false;
for(float mountY : new float[]{-117/4f, 50/4f}){
for(float sign : Mathf.signs){
@@ -3889,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;
}});
}
@@ -4058,6 +4064,8 @@ public class UnitTypes{
isEnemy = false;
envDisabled = 0;
range = 60f;
faceTarget = true;
targetPriority = -2;
lowAltitude = false;
mineWalls = true;
@@ -4122,8 +4130,10 @@ public class UnitTypes{
isEnemy = false;
envDisabled = 0;
range = 60f;
targetPriority = -2;
lowAltitude = false;
faceTarget = true;
mineWalls = true;
mineFloor = false;
mineHardnessScaling = false;
@@ -4199,6 +4209,8 @@ public class UnitTypes{
isEnemy = false;
envDisabled = 0;
range = 65f;
faceTarget = true;
targetPriority = -2;
lowAltitude = false;
mineWalls = true;
+1 -1
View File
@@ -17,7 +17,7 @@ public class Weathers{
suspendParticles;
public static void load(){
snow = new ParticleWeather("snow"){{
snow = new ParticleWeather("snowing"){{
particleRegion = "particle";
sizeMax = 13f;
sizeMin = 2.6f;
@@ -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);
}
+23 -23
View File
@@ -30,6 +30,7 @@ import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.io.*;
@@ -53,7 +54,7 @@ public class Control implements ApplicationListener, Loadable{
private Interval timer = new Interval(2);
private boolean hiscore = false;
private boolean wasPaused = false;
private boolean wasPaused = false, backgroundPaused = false;
private Seq<Building> toBePlaced = new Seq<>(false);
public Control(){
@@ -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();
@@ -332,6 +319,13 @@ public class Control implements ApplicationListener, Loadable{
void createPlayer(){
player = Player.create();
player.name = Core.settings.getString("name");
String locale = Core.settings.getString("locale");
if(locale.equals("default")){
locale = Locale.getDefault().toString();
}
player.locale = locale;
player.color.set(Core.settings.getInt("color-0"));
if(mobile){
@@ -551,6 +545,7 @@ public class Control implements ApplicationListener, Loadable{
@Override
public void pause(){
if(settings.getBool("backgroundpause", true) && !net.active()){
backgroundPaused = true;
wasPaused = state.is(State.paused);
if(state.is(State.playing)) state.set(State.paused);
}
@@ -561,6 +556,7 @@ public class Control implements ApplicationListener, Loadable{
if(state.is(State.paused) && !wasPaused && settings.getBool("backgroundpause", true) && !net.active()){
state.set(State.playing);
}
backgroundPaused = false;
}
@Override
@@ -652,6 +648,10 @@ public class Control implements ApplicationListener, Loadable{
core.items.each((i, a) -> i.unlock());
}
if(backgroundPaused && settings.getBool("backgroundpause") && !net.active()){
state.set(State.paused);
}
//cannot launch while paused
if(state.isPaused() && renderer.isCutscene()){
state.set(State.playing);
+6 -5
View File
@@ -1,11 +1,9 @@
package mindustry.core;
import arc.*;
import arc.struct.*;
import arc.util.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.MapObjectives.*;
import mindustry.gen.*;
import mindustry.maps.*;
import mindustry.type.*;
@@ -35,7 +33,9 @@ public class GameState{
/** Statistics for this save/game. Displayed after game over. */
public GameStats stats = new GameStats();
/** Markers not linked to objectives. Controlled by world processors. */
public IntMap<ObjectiveMarker> markers = new IntMap<>();
public MapMarkers markers = new MapMarkers();
/** Locale-specific string bundles of current map */
public MapLocales mapLocales = new MapLocales();
/** Global attributes of the environment, calculated by weather. */
public Attributes envAttrs = new Attributes();
/** Team data. Gets reset every new game. */
@@ -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. */
+2 -4
View File
@@ -357,7 +357,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 +461,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){
+12 -14
View File
@@ -340,25 +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){
//clear old markers
for(var objective : state.rules.objectives){
for(var marker : objective.markers){
if(marker.wasAdded){
marker.removed();
marker.wasAdded = false;
}
}
}
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)
-1
View File
@@ -777,7 +777,6 @@ public class NetServer implements ApplicationListener{
}else{
NetClient.traceInfo(other, info);
}
info("&lc@ &fi&lk[&lb@&fi&lk]&fb has requested trace info of @ &fi&lk[&lb@&fi&lk]&fb.", player.plainName(), player.uuid(), other.plainName(), other.uuid());
}
case switchTeam -> {
if(params instanceof Team team){
+73 -105
View File
@@ -13,7 +13,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 +29,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();
@@ -46,7 +40,7 @@ public class Renderer implements ApplicationListener{
public @Nullable Bloom bloom;
public @Nullable FrameBuffer backgroundBuffer;
public FrameBuffer effectBuffer = new FrameBuffer();
public boolean animateShields, drawWeather = true, drawStatus, enableEffects, drawDisplays = true, drawLight = true;
public boolean animateShields, drawWeather = true, drawStatus, enableEffects, drawDisplays = true, drawLight = true, pixelate = false;
public float weatherAlpha;
/** minZoom = zooming out, maxZoom = zooming in */
public float minZoom = 1.5f, maxZoom = 6f;
@@ -55,18 +49,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 +104,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,31 +168,26 @@ public class Renderer implements ApplicationListener{
enableEffects = settings.getBool("effects");
drawDisplays = !settings.getBool("hidedisplays");
drawLight = settings.getBool("drawlight", true);
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;
@@ -227,7 +209,7 @@ public class Renderer implements ApplicationListener{
shakeIntensity = 0f;
}
if(pixelator.enabled()){
if(renderer.pixelate){
pixelator.drawPixelate();
}else{
draw();
@@ -303,7 +285,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());
}
@@ -318,7 +300,7 @@ public class Renderer implements ApplicationListener{
Events.fire(Trigger.draw);
MapPreviewLoader.checkPreviews();
if(pixelator.enabled()){
if(renderer.pixelate){
pixelator.register();
}
@@ -371,9 +353,31 @@ public class Renderer implements ApplicationListener{
});
}
float scaleFactor = 4f / renderer.getDisplayScale();
//draw objective markers
state.rules.objectives.eachRunning(obj -> {
for(var marker : obj.markers){
if(marker.world){
marker.draw(marker.autoscale ? scaleFactor : 1);
}
}
});
for(var marker : state.markers){
if(marker.world){
marker.draw(marker.autoscale ? scaleFactor : 1);
}
}
Draw.reset();
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();
@@ -461,61 +465,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){
@@ -560,6 +509,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;
}
@@ -568,25 +524,37 @@ 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;
landCore.beginLaunch(coreType);
}
public void takeMapScreenshot(){
@@ -628,7 +596,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())));
});
}
+1 -3
View File
@@ -101,8 +101,6 @@ public class UI implements ApplicationListener, Loadable{
@Override
public void loadSync(){
loadColors();
Fonts.outline.getData().markupEnabled = true;
Fonts.def.getData().markupEnabled = true;
Fonts.def.setOwnsTexture(false);
@@ -281,7 +279,7 @@ public class UI implements ApplicationListener, Loadable{
public void showTextInput(String titleText, String text, int textLength, String def, boolean numbers, boolean allowEmpty, Cons<String> confirmed, Runnable closed){
if(mobile){
var description = text;
var description = (text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text);
var empty = allowEmpty;
Core.input.getTextInput(new TextInput(){{
this.title = (titleText.startsWith("@") ? Core.bundle.get(titleText.substring(1)) : titleText);
@@ -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();
}
}
+14 -3
View File
@@ -74,7 +74,7 @@ public class MapEditor{
for(int i = 0; i < tiles.width * tiles.height; i++){
Tile tile = tiles.geti(i);
var build = tile.build;
if(build != null){
if(build != null && tile.isCenter()){
builds.add(build);
}
tiles.seti(i, new EditorTile(tile.x, tile.y, tile.floorID(), tile.overlayID(), build == null ? tile.blockID() : 0));
@@ -301,6 +301,14 @@ public class MapEditor{
if(previous.in(px, py)){
tiles.set(x, y, previous.getn(px, py));
Tile tile = tiles.getn(x, y);
Object config = null;
//fetch the old config first, configs can be relative to block position (tileX/tileY) before those are reassigned
if(tile.build != null && tile.isCenter()){
config = tile.build.config();
}
tile.x = (short)x;
tile.y = (short)y;
@@ -309,9 +317,12 @@ public class MapEditor{
tile.build.y = y * tilesize + tile.block().offset;
//shift links to account for map resize
Object config = tile.build.config();
if(config != null){
Object out = BuildPlan.pointConfig(tile.block(), config, p -> p.sub(offsetX, offsetY));
Object out = BuildPlan.pointConfig(tile.block(), config, p -> {
if(!tile.build.block.ignoreResizeConfig){
p.sub(offsetX, offsetY);
}
});
if(out != config){
tile.build.configureAny(out);
}
@@ -24,6 +24,7 @@ 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.*;
@@ -7,6 +7,7 @@ import mindustry.game.*;
import mindustry.gen.*;
import mindustry.io.*;
import mindustry.maps.filters.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
@@ -17,6 +18,7 @@ public class MapInfoDialog extends BaseDialog{
private final MapGenerateDialog generate;
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
private final MapObjectivesDialog objectives = new MapObjectivesDialog();
private final MapLocalesDialog locales = new MapLocalesDialog();
public MapInfoDialog(){
super("@editor.mapinfo");
@@ -94,6 +96,19 @@ public class MapInfoDialog extends BaseDialog{
});
hide();
}).marginLeft(10f);
r.row();
r.button("@editor.locales", Icon.fileText, style, () -> {
try{
MapLocales res = JsonIO.read(MapLocales.class, editor.tags.get("locales", "{}"));
locales.show(res);
}catch(Throwable e){
locales.show(new MapLocales());
ui.showException(e);
}
hide();
}).marginLeft(10f).width(0f).colspan(2).center().growX();
}).colspan(2).center();
name.change();
@@ -0,0 +1,774 @@
package mindustry.editor;
import arc.Core;
import arc.func.*;
import arc.graphics.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.layout.*;
import arc.scene.utils.*;
import arc.struct.*;
import mindustry.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class MapLocalesDialog extends BaseDialog{
/** Width of UI property card. */
private static final float cardWidth = 400f;
/** Style for filter options buttons */
private static final TextButtonStyle filterStyle = new TextButtonStyle(){{
up = down = checked = over = Tex.whitePane;
font = Fonts.outline;
fontColor = Color.lightGray;
overFontColor = Pal.accent;
disabledFontColor = Color.gray;
disabled = Styles.black;
}};
/** Icons for use in map locales dialog. */
private static final ContentType[] contentIcons = {ContentType.item, ContentType.block, ContentType.liquid, ContentType.status, ContentType.unit};
private MapLocales locales;
private MapLocales lastSaved;
private boolean saved = true;
private Table langs;
private Table main;
private Table propView;
private String selectedLocale;
private boolean applytoall = true;
private boolean collapsed = false;
private String searchString = "";
private boolean searchByValue = false;
private boolean showCorrect = true;
private boolean showMissing = true;
private boolean showSame = true;
public MapLocalesDialog(){
super("@editor.locales");
selectedLocale = MapLocales.currentLocale();
langs = new Table(Tex.button);
main = new Table();
propView = new Table();
buttons.add("").uniform();
buttons.table(t -> {
t.defaults().pad(3).center();
t.button("@back", Icon.left, () -> {
if(!saved) ui.showConfirm("@editor.locales", "@editor.savechanges", () -> {
editor.tags.put("locales", JsonIO.write(locales));
state.mapLocales = locales;
});
hide();
}).size(210f, 64f);
closeOnBack(() -> {
if(!saved) ui.showConfirm("@editor.locales", "@editor.savechanges", () -> {
editor.tags.put("locales", JsonIO.write(locales));
state.mapLocales = locales;
});
});
t.button("@editor.apply", Icon.ok, () -> {
editor.tags.put("locales", JsonIO.write(locales));
state.mapLocales = locales;
lastSaved = locales.copy();
saved = true;
}).size(210f, 64f).disabled(b -> saved);
t.button("@edit", Icon.edit, this::editDialog).size(210f, 64f);
}).growX();
resized(this::buildMain);
buttons.button("?", () -> ui.showInfo("@locales.info")).size(60f, 64f).uniform();
shown(this::setup);
}
public void show(MapLocales locales){
this.locales = locales;
lastSaved = locales.copy();
saved = true;
show();
}
private void setup(){
cont.clear();
buildTables();
cont.add(langs).left();
cont.table(t -> {
// search/collapse all/filter
t.table(a -> {
a.button(Icon.downOpen, Styles.emptyTogglei, () -> {
collapsed = !collapsed;
buildMain();
}).update(b -> {
b.replaceImage(new Image(collapsed ? Icon.upOpen : Icon.downOpen));
b.setChecked(collapsed);
}).size(35f);
a.button(Icon.filter, Styles.emptyi, () -> filterDialog(this::buildMain)).padLeft(10f).size(35f);
var field = a.field("", v -> {
searchString = v;
buildMain();
}).update(f -> f.setText(searchString)).maxTextLength(64).padLeft(10f).width(250f).update(f -> f.setMessageText(searchByValue ? "@locales.searchvalue": "@locales.searchname")).get();
a.button(Icon.cancel, Styles.emptyi, () -> {
searchString = "";
field.setText("");
buildMain();
}).padLeft(10f).size(35f);
}).row();
t.check("@locales.applytoall", applytoall, b -> applytoall = b).pad(10f).row();
t.add(main).center().grow().row();
}).pad(10f).grow();
// property addition
cont.table(Tex.button, t -> {
TextField name = t.field("name", s -> {}).maxTextLength(64).fillX().padTop(10f).get();
t.row();
TextField value = t.area("text", s -> {}).maxTextLength(1000).fillX().height(140f).get();
t.row();
t.button("@add", Icon.add, () -> {
if(applytoall){
for(var locale : locales.values()){
locale.put(name.getText(), value.getText());
}
}else{
locales.get(selectedLocale).put(name.getText(), value.getText());
}
saved = false;
buildMain();
}).padTop(10f).size(cardWidth, 50f).fillX().row();
}).right();
}
private void buildTables(){
if(!locales.containsKey(selectedLocale)){
locales.put(selectedLocale, new StringMap());
}
buildLocalesTable();
buildMain();
}
private void buildLocalesTable(){
langs.clear();
langs.pane(p -> {
for(var loc : Vars.locales){
String name = loc.toString();
if(locales.containsKey(name)){
p.button(loc.getDisplayName(Core.bundle.getLocale()), Styles.flatTogglet, () -> {
if(name.equals(selectedLocale)) return;
selectedLocale = name;
buildTables();
}).update(b -> b.setChecked(selectedLocale.equals(name))).width(200f).minHeight(50f);
p.button(Icon.edit, Styles.flati, () -> localeEditDialog(name)).size(50f);
p.button(Icon.trash, Styles.flati, () -> ui.showConfirm("@confirm", "@locales.deletelocale", () -> {
locales.remove(name);
selectedLocale = (locales.size != 0 ? locales.keys().next() : Core.settings.getString("locale"));
saved = false;
buildTables();
})).size(50f).row();
}
}
}).row();
langs.button("@add", Icon.add, this::addLocaleDialog).padTop(10f).width(250f);
}
private void buildMain(){
main.clear();
StringMap props = locales.get(selectedLocale);
main.image().color(Pal.gray).height(3f).growX().expandY().top().row();
main.pane(p -> {
int cols = Math.max(1, (int)((Core.graphics.getWidth() / Scl.scl() - 410f) / cardWidth) - 1);
if(props.size == 0){
main.add("@empty").center().row();
return;
}
p.defaults().top();
Table[] colTables = new Table[cols];
for(var i = 0; i < cols; i++){
colTables[i] = new Table();
}
int i = 0;
// To sort properties in alphabetic order
Seq<String> keys = props.keys().toSeq().sort();
for(var key : keys){
var comparsionString = (searchByValue ? props.get(key).toLowerCase() : key.toLowerCase());
if(!searchString.isEmpty() && !comparsionString.contains(searchString.toLowerCase())) continue;
PropertyStatus status = getPropertyStatus(key, props.get(key), selectedLocale, false);
if(status == PropertyStatus.correct && !showCorrect) continue;
if(status == PropertyStatus.missing && !showMissing) continue;
if(status == PropertyStatus.same && !showSame) continue;
colTables[i].table(Tex.whitePane, t -> {
boolean[] shown = {!collapsed};
String[] propKey = {key};
String[] propValue = {props.get(key)};
// collapse button
t.button(Icon.downOpen, Styles.emptyTogglei, () -> shown[0] = !shown[0]).update(b -> {
b.replaceImage(new Image(shown[0] ? Icon.upOpen : Icon.downOpen));
b.setChecked(shown[0]);
}).size(35f);
// property name field
t.field(propKey[0], (f, c) -> c != '=' && c != ':', v -> {
if(props.containsKey(v)){
t.setColor(Color.valueOf("f25555"));
return;
}
if(applytoall){
for(var bundle : locales.values()){
if(!bundle.containsKey(v)){
String value = bundle.get(propKey[0]);
if(value == null) continue;
bundle.remove(propKey[0]);
bundle.put(v, value);
}
}
}else{
if(!props.containsKey(v)){
props.remove(propKey[0]);
props.put(v, propValue[0]);
}
}
propKey[0] = v;
updateCard(t, v, propValue[0]);
saved = false;
}).maxTextLength(64).width(cardWidth - 125f);
// remove button
t.button(Icon.trash, Styles.emptyi, () -> {
if(applytoall){
for(var bundle : locales.values()){
bundle.remove(propKey[0]);
}
}else{
props.remove(propKey[0]);
}
saved = false;
buildMain();
}).size(35f);
// more actions
t.button(Icon.edit, Styles.emptyi, () -> propEditDialog(t, propKey[0], propValue[0])).size(35f).row();
// property value area
t.collapser(c -> c.area(propValue[0], v -> {
props.put(propKey[0], v);
updateCard(t, propKey[0], v);
saved = false;
}).maxTextLength(1000).height(140f).update(a -> {
propValue[0] = props.get(propKey[0]);
a.setText(props.get(propKey[0]));
}).growX(), () -> shown[0]).colspan(4).growX();
updateCard(t, propKey[0], propValue[0]);
}).top().width(cardWidth).pad(5f).row();
i = ++i % cols;
}
if(!colTables[0].hasChildren()){
main.add("@empty").center().row();
}else{
p.add(colTables);
}
}).growX().row();
main.image().color(Pal.gray).height(3f).growX().expandY().bottom().row();
}
private void updateCard(Table table, String propKey, String propValue){
updateCard(table, propKey, propValue, selectedLocale, false);
}
private void updateCard(Table table, String propKey, String propValue, String locale, boolean viewCard){
switch(getPropertyStatus(propKey, propValue, locale, viewCard)){
case missing -> table.setColor(Pal.accent);
case same -> table.setColor(Pal.techBlue);
case correct -> table.setColor(Pal.gray);
}
}
// Property statuses for main dialog and property view dialog are a bit different
private PropertyStatus getPropertyStatus(String propKey, String propValue, String locale, boolean forView){
if(forView && propValue == null) return PropertyStatus.missing;
for(var bundle : locales.entries()){
if(!forView && bundle.key.equals(selectedLocale)) continue;
if(forView && bundle.key.equals(locale)) continue;
StringMap props = bundle.value;
if(!props.containsKey(propKey)){
if(!forView) return PropertyStatus.missing;
}else{
if(props.get(propKey).equals(propValue)){
return PropertyStatus.same;
}
}
}
return PropertyStatus.correct;
}
private void addLocaleDialog(){
BaseDialog dialog = new BaseDialog("@add");
dialog.cont.pane(t -> {
for(var loc : Vars.locales){
String name = loc.toString();
if(!locales.containsKey(name)){
t.button(loc.getDisplayName(Core.bundle.getLocale()), Styles.flatTogglet, () -> {
if(name.equals(selectedLocale)) return;
locales.put(name, new StringMap());
selectedLocale = name;
saved = false;
buildTables();
dialog.hide();
}).update(b -> b.setChecked(selectedLocale.equals(name))).size(400f, 50f).row();
}
}
});
dialog.addCloseButton();
dialog.show();
}
private void propEditDialog(Table card, String key, String value){
BaseDialog dialog = new BaseDialog("@edit");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
t.defaults().size(450f, 60f).left();
t.button("@locales.addtoother", Icon.add, Styles.flatt, () -> {
for(var bundle : locales.values()){
if(!bundle.containsKey(key)){
bundle.put(key, value);
}
}
saved = false;
updateCard(card, key, value);
dialog.hide();
}).marginLeft(12f).row();
t.button("@locales.viewproperty", Icon.zoom, Styles.flatt, () -> {
viewPropertyDialog(key);
dialog.hide();
}).marginLeft(12f).row();
t.button("@locales.addicon", Icon.image, Styles.flatt, () -> {
addIconDialog(res -> {
locales.get(selectedLocale).put(key, value + res);
saved = false;
});
dialog.hide();
}).marginLeft(12f).row();
t.button("@locales.rollback", Icon.undo, Styles.flatt, () -> {
locales.get(selectedLocale).put(key, lastSaved.get(selectedLocale).get(key));
buildTables();
dialog.hide();
}).disabled(b -> {
if(!lastSaved.containsKey(selectedLocale)) return true;
StringMap savedMap = lastSaved.get(selectedLocale);
return !savedMap.containsKey(key) || savedMap.get(key).equals(locales.get(selectedLocale).get(key));
}).marginLeft(12f).row();
});
});
dialog.addCloseButton();
dialog.show();
}
private void localeEditDialog(String locale){
BaseDialog dialog = new BaseDialog("@edit");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
t.defaults().size(350f, 60f).left();
t.button("@waves.copy", Icon.copy, Styles.flatt, () -> {
Core.app.setClipboardText(writeLocale(locale));
ui.showInfoFade("@copied");
dialog.hide();
}).marginLeft(12f).row();
t.button("@waves.load", Icon.download, Styles.flatt, () -> {
locales.put(locale, readLocale(Core.app.getClipboardText()));
buildTables();
saved = false;
dialog.hide();
}).disabled(Core.app.getClipboardText() == null).marginLeft(12f).row();
});
});
dialog.addCloseButton();
dialog.show();
}
private void editDialog(){
BaseDialog dialog = new BaseDialog("@edit");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
t.defaults().size(450f, 60f).left();
t.button("@waves.copy", Icon.copy, Styles.flatt, () -> {
Core.app.setClipboardText(writeBundles());
ui.showInfoFade("@copied");
dialog.hide();
}).marginLeft(12f).row();
t.button("@waves.load", Icon.download, Styles.flatt, () -> {
locales = readBundles(Core.app.getClipboardText());
buildTables();
saved = false;
dialog.hide();
}).disabled(Core.app.getClipboardText() == null).marginLeft(12f).row();
t.button("@locales.rollback", Icon.undo, Styles.flatt, () -> {
locales = lastSaved.copy();
saved = true;
buildTables();
dialog.hide();
}).disabled(b -> saved).marginLeft(12f).row();
});
});
dialog.addCloseButton();
dialog.show();
}
private void viewPropertyDialog(String key){
BaseDialog dialog = new BaseDialog(Core.bundle.format("locales.viewing", key));
dialog.cont.table(t -> {
t.button(Icon.filter, Styles.emptyi, () -> filterDialog(() -> buildPropView(key))).size(35f);
var field = t.field(searchString, v -> {
searchString = v;
buildPropView(key);
}).update(f -> f.setText(searchString)).maxTextLength(64).padLeft(10f).width(250f).update(f -> f.setMessageText(searchByValue ? "@locales.searchvalue" : "@locales.searchlocale")).get();
t.button(Icon.cancel, Styles.emptyi, () -> {
searchString = "";
field.setText("");
buildPropView(key);
}).padLeft(10f).size(35f);
}).row();
buildPropView(key);
dialog.cont.add(propView).grow().center().row();
dialog.addCloseButton();
dialog.closeOnBack();
dialog.hidden(this::buildMain);
dialog.show();
}
private void buildPropView(String key){
propView.clear();
propView.image().color(Pal.gray).height(3f).fillX().top().row();
propView.pane(p -> {
int cols = Math.max(1, (int)((Core.graphics.getWidth() / Scl.scl() - 100f) / cardWidth));
if(cols == 0){
propView.add("@empty").center().row();
return;
}
p.defaults().top();
Table[] colTables = new Table[cols];
for(var i = 0; i < cols; i++){
colTables[i] = new Table();
}
int i = 0;
for(var loc : Vars.locales){
String name = loc.toString();
if(!locales.containsKey(name)) continue;
PropertyStatus status = getPropertyStatus(key, locales.get(name).get(key), name, true);
if(status == PropertyStatus.correct && !showCorrect) continue;
if(status == PropertyStatus.missing && !showMissing) continue;
if(status == PropertyStatus.same && !showSame) continue;
if(status != PropertyStatus.missing){
var comparsionString = (searchByValue ? locales.get(name).get(key).toLowerCase() : loc.getDisplayName(Core.bundle.getLocale()).toLowerCase());
if(!searchString.isEmpty() && !comparsionString.contains(searchString.toLowerCase())) continue;
}
colTables[i].table(Tex.whitePane, t -> {
t.add(loc.getDisplayName(Core.bundle.getLocale())).left().color(Pal.accent).row();
t.image().color(Pal.accent).fillX().row();
if(status == PropertyStatus.missing){
t.table(b ->
b.button("@add", Icon.add, () -> {
locales.get(name).put(key, "moai");
t.getCells().get(2).clearElement();
t.getCells().remove(2);
t.area(locales.get(name).get(key), v -> {
locales.get(name).put(key, v);
saved = false;
}).maxTextLength(1000).height(140f).growX().row();
}).size(160f, 50f)).height(140f).growX().row();
}else{
t.area(locales.get(name).get(key), v -> {
locales.get(name).put(key, v);
saved = false;
}).maxTextLength(1000).height(140f).growX().row();
}
}).update(t -> updateCard(t, key, locales.get(name).get(key), name, true)).top().width(cardWidth).pad(5f).row();
i = ++i % cols;
}
if(!colTables[0].hasChildren()){
propView.add("@empty").center().row();
}else{
p.add(colTables);
}
}).grow().row();
propView.image().color(Pal.gray).height(3f).fillX().bottom().row();
}
private void filterDialog(Runnable hidden){
BaseDialog dialog = new BaseDialog("@locales.filter");
dialog.cont.table(t -> {
t.add("@search").row();
t.table(b -> {
b.button("@locales.byname", Styles.togglet, () -> searchByValue = false).size(300f, 50f).checked(v -> !searchByValue);
b.button("@locales.byvalue", Styles.togglet, () -> searchByValue = true).padLeft(10f).size(300f, 50f).checked(v -> searchByValue);
}).padTop(5f);
}).row();
dialog.cont.button("@locales.showcorrect", Icon.ok, filterStyle, () -> showCorrect = !showCorrect).update(b -> {
((Image)b.getChildren().get(1)).setDrawable(showCorrect ? Icon.ok : Icon.cancel);
b.setChecked(showCorrect);
}).size(450f, 100f).color(Pal.gray).padTop(65f);
dialog.cont.row();
dialog.cont.button("@locales.showmissing", Icon.ok, filterStyle, () -> showMissing = !showMissing).update(b -> {
((Image)b.getChildren().get(1)).setDrawable(showMissing ? Icon.ok : Icon.cancel);
b.setChecked(showMissing);
}).size(450f, 100f).color(Pal.accent).padTop(65f);
dialog.cont.row();
dialog.cont.button("@locales.showsame", Icon.ok, filterStyle, () -> showSame = !showSame).update(b -> {
((Image)b.getChildren().get(1)).setDrawable(showSame ? Icon.ok : Icon.cancel);
b.setChecked(showSame);
}).size(450f, 100f).color(Pal.techBlue).padTop(65f);
dialog.buttons.button("@back", Icon.left, () -> {
hidden.run();
dialog.hide();
}).size(210f, 64f);
dialog.closeOnBack(hidden);
dialog.show();
}
private void addIconDialog(Cons<String> cons){
BaseDialog dialog = new BaseDialog("@locales.addicon");
Table icons = new Table();
TextField search = Elem.newField("", v -> iconsTable(icons, v.replace(" ", "").toLowerCase(), dialog, cons));
search.setMessageText("@search");
dialog.cont.table(t -> {
t.add(search).maxTextLength(64).padLeft(10f).width(250f);
t.button(Icon.cancel, Styles.emptyi, () -> {
search.setText("");
iconsTable(icons, "", dialog, cons);
}).padLeft(10f).size(35f);
}).row();
dialog.cont.pane(icons).scrollX(false);
dialog.resized(true, () -> iconsTable(icons, search.getText().replace(" ", "").toLowerCase(), dialog, cons));
dialog.addCloseButton();
dialog.closeOnBack();
dialog.setFillParent(true);
dialog.show();
}
private void iconsTable(Table table, String search, Dialog dialog, Cons<String> cons){
table.clear();
table.marginRight(19f).marginLeft(12f);
table.defaults().size(48f);
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
int i = 0;
var codes = new ObjectIntMap<>(Iconc.codes);
for(var name : codes.keys()){
if(!name.toLowerCase().contains(search)) codes.remove(name);
}
if(codes.size > 0) table.image().colspan(cols).growX().width(-1f).height(3f).color(Pal.accent).row();
for(var icon : codes){
String res = (char)icon.value + "";
table.button(Icon.icons.get(icon.key), Styles.flati, iconMed, () -> {
cons.get(res);
dialog.hide();
}).tooltip(icon.key);
if(++i % cols == 0) table.row();
}
for(ContentType ctype : contentIcons){
var all = content.getBy(ctype).<UnlockableContent>as().select(u -> u.localizedName.replace(" ", "").toLowerCase().contains(search) && u.uiIcon.found());
table.row();
if(all.size > 0) table.image().colspan(cols).growX().width(-1f).height(3f).color(Pal.accent).row();
i = 0;
for(UnlockableContent u : all){
table.button(new TextureRegionDrawable(u.uiIcon), Styles.flati, iconMed, () -> {
cons.get(u.emoji() + "");
dialog.hide();
}).tooltip(u.localizedName);
if(++i % cols == 0) table.row();
}
}
var teams = new Seq<>(Team.baseTeams);
teams = teams.select(u -> u.localized().toLowerCase().contains(search) && Core.atlas.has("team-" + u.name));
table.row();
if(teams.size > 0) table.image().colspan(cols).growX().width(-1f).height(3f).color(Pal.accent).row();
for(Team team : teams){
var region = Core.atlas.find("team-" + team.name);
table.button(new TextureRegionDrawable(region), Styles.flati, iconMed, () -> {
cons.get(team.emoji);
dialog.hide();
}).tooltip(team.localized());
if(++i % cols == 0) table.row();
}
}
private String writeBundles(){
StringBuilder data = new StringBuilder();
for(var locale : locales.keys()){
data.append(locale).append(":\n").append(writeLocale(locale));
}
return data.toString();
}
private String writeLocale(String key){
StringBuilder data = new StringBuilder();
if(!locales.containsKey(key)) return "";
for(var prop : locales.get(key).entries()){
// Convert \n in plain text to \\n, then convert newlines to \n
data.append(prop.key).append(" = ").append(prop.value
.replace("\\n", "\\\\n").replace("\n", "\\n")).append("\n");
}
return data.toString();
}
private MapLocales readBundles(String data){
MapLocales bundles = new MapLocales();
String currentLocale = "";
for(var line : data.split("\\r?\\n|\\r")){
if(line.endsWith(":") && !line.contains("=")){
currentLocale = line.substring(0, line.length() - 1);
bundles.put(currentLocale, new StringMap());
}else{
int sepIndex = line.indexOf(" = ");
if(sepIndex != -1 && !currentLocale.isEmpty()){
// Convert \n in file to newlines in text, then revert newlines with escape characters
bundles.get(currentLocale).put(line.substring(0, sepIndex), line.substring(sepIndex + 3)
.replace("\\n", "\n").replace("\\\n", "\\n"));
}
}
}
return bundles;
}
private StringMap readLocale(String data){
StringMap map = new StringMap();
for(var line : data.split("\\r?\\n|\\r")){
int sepIndex = line.indexOf(" = ");
if(sepIndex != -1){
// Convert \n in file to newlines in text, then revert newlines with escape characters
map.put(line.substring(0, sepIndex), line.substring(sepIndex + 3)
.replace("\\n", "\n").replace("\\\n", "\\n"));
}
}
return map;
}
private enum PropertyStatus{
correct,
missing,
same
}
}
@@ -246,6 +246,38 @@ public class MapObjectivesDialog extends BaseDialog{
show();
}});
setInterpreter(Vertices.class, float[].class, (cont, name, type, field, remover, indexer, get, set) -> cont.table(main -> {
float[] data = get.get();
name(cont, name, remover, indexer);
cont.table(t -> {
t.left().defaults().left();
String[] names = {"x", "y", "color", "u", "v"};
int stride = 6;
int vertices = data.length / stride;
for(int i = 0; i < vertices; i++){
int offset = i * stride;
t.table(row -> {
for(int j = 0; j < names.length; j++){
int index = offset + j;
if("color".equals(names[j])) {
getInterpreter(Color.class).build(row, names[j], new TypeInfo(Color.class), null, null, null, () -> new Color().abgr8888(data[index]), value -> data[index] = value.toFloatBits());
}else{
float scale = j <= 1 ? tilesize : 1;
getInterpreter(float.class).build(row, names[j], new TypeInfo(float.class), null, null, null, () -> data[index] / scale, value -> data[index] = value * scale);
}
row.add().pad(4);
}
}).row();
}
});
}));
// Types that use the default interpreter. It would be nice if all types could use it, but I don't know how to reliably prevent classes like [? extends Content] from using it.
for(var obj : MapObjectives.allObjectiveTypes) setInterpreter(obj.get().getClass(), defaultInterpreter());
for(var mark : MapObjectives.allMarkerTypes) setInterpreter(mark.get().getClass(), defaultInterpreter());
@@ -290,10 +322,12 @@ public class MapObjectivesDialog extends BaseDialog{
t.button(Icon.downOpen, Styles.emptyi, () -> indexer.get(false)).fill().padRight(4f);
}
t.button(Icon.add, Styles.emptyi, () -> getProvider(type.element.raw).get(type.element, res -> {
arr.add(res);
rebuild[0].run();
})).fill();
if(!field.isAnnotationPresent(Immutable.class)) {
t.button(Icon.add, Styles.emptyi, () -> getProvider(type.element.raw).get(type.element, res -> {
arr.add(res);
rebuild[0].run();
})).fill();
}
}).growX().height(46f).pad(0f, -10f, 0f, -10f).get();
main.row().table(Tex.button, t -> rebuild[0] = () -> {
@@ -312,10 +346,10 @@ public class MapObjectivesDialog extends BaseDialog{
getInterpreter((Class<Object>)arr.get(index).getClass()).build(
t, "", new TypeInfo(arr.get(index).getClass()),
field, () -> {
field, field == null || !field.isAnnotationPresent(Immutable.class) ? () -> {
arr.remove(index);
rebuild[0].run();
}, field == null || !field.isAnnotationPresent(Unordered.class) ? in -> {
} : null, field == null || !field.isAnnotationPresent(Unordered.class) ? in -> {
if(in && index > 0){
arr.swap(index, index - 1);
rebuild[0].run();
-2
View File
@@ -293,7 +293,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 +343,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);
}
+25 -18
View File
@@ -1,8 +1,6 @@
package mindustry.entities;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
@@ -14,13 +12,12 @@ import static mindustry.Vars.*;
public class Fires{
private static final float baseLifetime = 1000f;
private static final IntMap<Fire> map = new IntMap<>();
/** Start a fire on the tile. If there already is a fire there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null || !state.rules.fire || !state.rules.hasEnv(Env.oxygen)) return; //not clientside.
Fire fire = map.get(tile.pos());
Fire fire = get(tile);
if(fire == null){
fire = Fire.create();
@@ -28,48 +25,58 @@ public class Fires{
fire.lifetime = baseLifetime;
fire.set(tile.worldx(), tile.worldy());
fire.add();
map.put(tile.pos(), fire);
set(tile, fire);
}else{
fire.lifetime = baseLifetime;
fire.time = 0f;
}
}
public static Fire get(int x, int y){
return map.get(Point2.pack(x, y));
public static @Nullable Fire get(Tile tile){
return tile == null ? null : world.tiles.getFire(tile.array());
}
public static @Nullable Fire get(int x, int y){
return Structs.inBounds(x, y, world.width(), world.height()) ? world.tiles.getFire(world.packArray(x, y)) : null;
}
private static void set(Tile tile, Fire fire){
world.tiles.setFire(tile.array(), fire);
}
public static boolean has(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Point2.pack(x, y))){
if(!Structs.inBounds(x, y, world.width(), world.height())){
return false;
}
Fire fire = map.get(Point2.pack(x, y));
return fire.isAdded() && fire.fin() < 1f && fire.tile() != null && fire.tile().x == x && fire.tile().y == y;
Fire fire = get(x, y);
return fire != null && fire.isAdded() && fire.fin() < 1f && fire.tile != null && fire.tile.x == x && fire.tile.y == y;
}
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.pos())){
Fire fire = map.get(tile.pos());
fire.time(fire.time + intensity * Time.delta);
Fx.steam.at(fire);
if(fire.time >= fire.lifetime){
Events.fire(Trigger.fireExtinguish);
if(tile != null){
Fire fire = get(tile);
if(fire != null){
fire.time(fire.time + intensity * Time.delta);
Fx.steam.at(fire);
if(fire.time >= fire.lifetime){
Events.fire(Trigger.fireExtinguish);
}
}
}
}
public static void remove(Tile tile){
if(tile != null){
map.remove(tile.pos());
set(tile, null);
}
}
public static void register(Fire fire){
if(fire.tile != null){
map.put(fire.tile.pos(), fire);
set(fire.tile, fire);
}
}
}
+3 -3
View File
@@ -26,8 +26,8 @@ public class Puddles{
}
/** Returns the Puddle on the specified tile. May return null. */
public static Puddle get(Tile tile){
return world.tiles.puddle(tile.array());
public static @Nullable Puddle get(Tile tile){
return tile == null ? null : world.tiles.getPuddle(tile.array());
}
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount, boolean initial){
@@ -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();
+16 -3
View File
@@ -192,8 +192,18 @@ public class Units{
/** Returns the nearest enemy tile in a range. */
public static Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
return findEnemyTile(team, x, y, range, false, pred);
}
/** Returns the nearest enemy tile in a range. */
public static Building findEnemyTile(Team team, float x, float y, float range, boolean checkUnder, Boolf<Building> pred){
if(team == Team.derelict) return null;
if(checkUnder){
Building target = indexer.findEnemyTile(team, x, y, range, build -> !build.block.underBullets && pred.get(build));
if(target != null) return target;
}
return indexer.findEnemyTile(team, x, y, range, pred);
}
@@ -214,7 +224,10 @@ public class Units{
}
});
return buildResult;
var result = buildResult;
buildResult = null;
return result;
}
/** Iterates through all buildings in a range. */
@@ -240,7 +253,7 @@ public class Units{
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, tilePred);
return findEnemyTile(team, x, y, range, true, tilePred);
}
}
@@ -252,7 +265,7 @@ public class Units{
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, tilePred);
return findEnemyTile(team, x, y, range, true, tilePred);
}
}
@@ -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.*;
@@ -73,14 +73,14 @@ 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
@@ -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;
@@ -69,12 +68,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
@@ -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
@@ -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
@@ -174,6 +174,10 @@ public class BulletType extends Content implements Cloneable{
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
/** Random range of frag lifetime as a multiplier. */
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;
@@ -387,14 +391,14 @@ public class BulletType extends Content implements Cloneable{
if(entity instanceof Healthc h){
float damage = b.damage;
float shield = entity instanceof Shieldc s ? Math.max(s.shield(), 0f) : 0f;
if(maxDamageFraction > 0){
float cap = h.maxHealth() * maxDamageFraction;
if(entity instanceof Shieldc s){
cap += Math.max(s.shield(), 0f);
}
float cap = h.maxHealth() * maxDamageFraction + shield;
damage = Math.min(damage, cap);
//cap health to effective health for handlePierce to handle it properly
health = Math.min(health, cap);
}else{
health += shield;
}
if(pierceArmor){
h.damagePierce(damage);
@@ -507,12 +511,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(1f, 7f);
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();
}
@@ -43,8 +43,6 @@ public class RailBulletType extends BulletType{
if(b.damage > 0){
pierceEffect.at(x, y, b.rotation());
hitEffect.at(x, y);
}
//subtract health from each consecutive pierce
@@ -86,9 +86,9 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
buildAlpha = Mathf.lerpDelta(buildAlpha, activelyBuilding() ? 1f : 0f, 0.15f);
}
//validate regardless of whether building is enabled.
validatePlans();
if(!updateBuilding || !canBuild()){
validatePlans();
return;
}
@@ -99,19 +99,18 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
if(Float.isNaN(buildCounter) || Float.isInfinite(buildCounter)) buildCounter = 0f;
buildCounter = Math.min(buildCounter, 10f);
boolean instant = state.rules.instantBuild && state.rules.infiniteResources;
//random attempt to fix a freeze that only occurs on Android
int maxPerFrame = 10, count = 0;
int maxPerFrame = instant ? plans.size : 10, count = 0;
while(buildCounter >= 1 && count++ < maxPerFrame){
var core = core();
if((core == null && !infinite)) return;
while((buildCounter >= 1 || instant) && count++ < maxPerFrame && plans.size > 0){
buildCounter -= 1f;
validatePlans();
var core = core();
//nothing to build.
if(buildPlan() == null) return;
//find the next build plan
if(plans.size > 1){
int total = 0;
@@ -163,7 +162,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
}
//if there is no core to build with or no build entity, stop building!
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild entity)){
if(!(tile.build instanceof ConstructBuild entity)){
continue;
}
@@ -1310,7 +1310,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 +1324,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(){
@@ -1344,6 +1348,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return block.itemCapacity;
}
/** Called when a block begins (not finishes!) deconstruction. The building is still present at this point. */
public void onDeconstructed(@Nullable Unit builder){
//deposit non-incinerable liquid on ground
if(liquids != null && liquids.currentAmount() > 0 && (!liquids.current().incinerable || block.deconstructDropAllLiquid)){
float perCell = liquids.currentAmount() / (block.size * block.size) * 2f;
tile.getLinkedTiles(other -> Puddles.deposit(other, liquids.current(), perCell));
}
}
/** Called when the block is destroyed. The tile is still intact at this stage. */
public void onDestroyed(){
float explosiveness = block.baseExplosiveness;
@@ -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;
@@ -66,4 +66,9 @@ abstract class EntityComp{
void afterRead(){
}
/** Called after *all* entities are read. */
void afterAllRead(){
}
}
@@ -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());
}
@@ -183,6 +183,9 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
if(!unit.isNull()){
clearUnit();
}
lastReadUnit = Nulls.unit;
justSwitchTo = justSwitchFrom = null;
}
public void team(Team team){
@@ -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){
@@ -127,10 +127,10 @@ abstract class StatusComp implements Posc, Flyingc{
return entry;
}
/** Uses a dynamic status effect to override speed. */
/** Uses a dynamic status effect to override speed (in tiles/second). */
public void statusSpeed(float speed){
//type.speed should never be 0
applyDynamicStatus().speedMultiplier = speed / type.speed;
applyDynamicStatus().speedMultiplier = speed / (type.speed * 60f / tilesize);
}
/** Uses a dynamic status effect to change damage. */
@@ -92,7 +92,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
moveAt(Tmp.v2.trns(rotation, vec.len()));
if(!vec.isZero()){
rotation = Angles.moveToward(rotation, vec.angle(), type.rotateSpeed * Time.delta);
rotation = Angles.moveToward(rotation, vec.angle(), type.rotateSpeed * Time.delta * speedMultiplier);
}
}
@@ -110,7 +110,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return !type.flying && world.tiles.in(tileX, tileY) && type.pathCost.getCost(team.id, pathfinder.get(tileX, tileY)) == -1;
}
/** @return approx. square size of the physical hitbox for physics */
public float physicSize(){
return hitSize * 0.7f;
@@ -490,6 +489,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
@Override
public void afterAllRead(){
controller.afterRead(self());
}
@Override
public void add(){
team.data().updateCount(type, 1);
@@ -730,7 +734,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;
}
@@ -21,18 +21,32 @@ public class SoundEffect extends Effect{
public Effect effect;
public SoundEffect(){
startDelay = -1;
}
public SoundEffect(Sound sound, Effect effect){
this();
this.sound = sound;
this.effect = effect;
}
@Override
public void init(){
if(startDelay < 0){
startDelay = effect.startDelay;
}
}
@Override
public void create(float x, float y, float rotation, Color color, Object data){
if(!shouldCreate()) return;
sound.at(x, y, Mathf.random(minPitch, maxPitch), Mathf.random(minVolume, maxVolume));
if(startDelay > 0){
Time.run(startDelay, () -> sound.at(x, y, Mathf.random(minPitch, maxPitch), Mathf.random(minVolume, maxVolume)));
}else{
sound.at(x, y, Mathf.random(minPitch, maxPitch), Mathf.random(minVolume, maxVolume));
}
effect.create(x, y, rotation, color, data);
}
}
+12 -2
View File
@@ -85,8 +85,10 @@ public abstract class DrawPart{
/** Weapon heat, 1 when just fired, 0, when it has cooled down (duration depends on weapon) */
heat = p -> p.heat,
/** Lifetime fraction, 0 to 1. Only for missiles. */
life = p -> p.life;
life = p -> p.life,
/** Current unscaled value of Time.time. */
time = p -> Time.time;
float get(PartParams p);
static PartProgress constant(float value){
@@ -167,6 +169,14 @@ public abstract class DrawPart{
default PartProgress absin(float scl, float mag){
return p -> get(p) + Mathf.absin(scl, mag);
}
default PartProgress mod(float amount){
return p -> Mathf.mod(get(p), amount);
}
default PartProgress loop(float time){
return p -> Mathf.mod(get(p)/time, 1);
}
default PartProgress apply(PartProgress other, PartFunc func){
return p -> func.get(get(p), other.get(p));
@@ -55,6 +55,11 @@ public class AIController implements UnitController{
return false;
}
@Override
public void afterRead(Unit unit){
}
@Override
public boolean isLogicControllable(){
return true;
@@ -222,7 +227,7 @@ public class AIController implements UnitController{
}
public Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground);
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground && (unit.type.targetUnderBlocks || !t.block.underBullets));
}
public boolean retarget(){
@@ -272,7 +277,7 @@ public class AIController implements UnitController{
vec.setLength(prefSpeed());
unit.moveAt(vec);
unit.movePref(vec);
}
public void circle(Position target, float circleLength){
@@ -290,7 +295,7 @@ public class AIController implements UnitController{
vec.setLength(speed);
unit.moveAt(vec);
unit.movePref(vec);
}
public void moveTo(Position target, float circleLength){
@@ -27,6 +27,10 @@ public interface UnitController{
}
default void afterRead(Unit unit){
}
default boolean isBeingControlled(Unit player){
return false;
}
@@ -40,6 +40,8 @@ public class WeaponMount{
public int totalShots;
/** counter for which barrel bullets have been fired from; used for alternating patterns */
public int barrelCounter;
/** Last aim length of weapon. Only used for point lasers. */
public float lastLength;
/** current bullet for continuous weapons */
public @Nullable Bullet bullet;
/** sound loop for continuous weapons */
+1
View File
@@ -39,6 +39,7 @@ public class EventType{
socketConfigChanged,
update,
unitCommandChange,
unitCommandPosition,
unitCommandAttack,
importMod,
draw,
+1
View File
@@ -35,6 +35,7 @@ public enum Gamemode{
}, map -> map.teams.size > 1),
editor(true, rules -> {
rules.infiniteResources = true;
rules.instantBuild = true;
rules.editor = true;
rules.waves = false;
rules.waveTimer = false;
+72
View File
@@ -0,0 +1,72 @@
package mindustry.game;
import arc.struct.*;
import arc.util.*;
import mindustry.game.MapObjectives.*;
import mindustry.io.*;
import java.io.*;
import java.util.*;
public class MapMarkers implements Iterable<ObjectiveMarker>{
/** Maps marker unique ID to marker. */
private IntMap<ObjectiveMarker> map = new IntMap<>();
/** Sequential list of markers. This allows for faster iteration than a map. */
private Seq<ObjectiveMarker> all = new Seq<>(false);
public void add(int id, ObjectiveMarker marker){
if(marker == null) return;
var prev = map.put(id, marker);
if(prev != null){
all.set(prev.arrayIndex, marker);
}else{
all.add(marker);
marker.arrayIndex = all.size - 1;
}
}
public void remove(int id){
var prev = map.remove(id);
if(prev != null){
if(all.size > prev.arrayIndex + 1){ //there needs to be something above the index to replace it with
all.remove(prev.arrayIndex);
//update its index
all.get(prev.arrayIndex).arrayIndex = prev.arrayIndex;
}else{
//no sense updating the index of the replaced element when it was not replaced
all.remove(prev.arrayIndex);
}
}
}
public @Nullable ObjectiveMarker get(int id){
return map.get(id);
}
public boolean has(int id){
return get(id) != null;
}
public int size(){
return all.size;
}
public void write(DataOutput stream) throws IOException{
JsonIO.writeBytes(map, ObjectiveMarker.class, (DataOutputStream)stream);
}
public void read(DataInput stream) throws IOException{
all.clear();
map = JsonIO.readBytes(IntMap.class, ObjectiveMarker.class, (DataInputStream)stream);
for(var entry : map.entries()){
all.add(entry.value);
entry.value.arrayIndex = all.size - 1;
}
}
@Override
public Iterator<ObjectiveMarker> iterator(){
return all.iterator();
}
}
+456 -199
View File
@@ -39,8 +39,6 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
* @see #eachRunning(Cons)
*/
public Seq<MapObjective> all = new Seq<>(4);
/** @see #checkChanged() */
protected transient boolean changed;
static{
registerObjective(
@@ -61,11 +59,15 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
registerMarker(
ShapeTextMarker::new,
MinimapMarker::new,
PointMarker::new,
ShapeMarker::new,
TextMarker::new,
LineMarker::new
LineMarker::new,
TextureMarker::new,
QuadMarker::new
);
registerLegacyMarker("Minimap", PointMarker::new);
}
@SafeVarargs
@@ -95,6 +97,15 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
}
public static void registerLegacyMarker(String name, Prov<? extends ObjectiveMarker> prov) {
Class<?> type = prov.get().getClass();
markerNameToType.put(name, prov);
markerNameToType.put(Strings.camelize(name), prov);
JsonIO.classTag(Strings.camelize(name), type);
JsonIO.classTag(name, type);
}
/** Adds all given objectives to the executor as root objectives. */
public void add(MapObjective... objectives){
for(var objective : objectives) flatten(objective);
@@ -111,36 +122,15 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
/** Updates all objectives this executor contains. */
public void update(){
eachRunning(obj -> {
for(var marker : obj.markers){
if(!marker.wasAdded){
marker.wasAdded = true;
marker.added();
}
}
//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();
for(var marker : obj.markers){
if(marker.wasAdded){
marker.removed();
marker.wasAdded = false;
}
}
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. */
@@ -483,6 +468,14 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
timeString.append(s);
if(text.startsWith("@")){
if(state.mapLocales.containsProperty(text.substring(1))){
try{
return state.mapLocales.getFormatted(text.substring(1), timeString.toString());
}catch(IllegalArgumentException e){
//illegal text.
text = "";
}
}
return Core.bundle.format(text.substring(1), timeString.toString());
}else{
try{
@@ -589,9 +582,17 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
return state.rules.objectiveFlags.contains(flag);
}
@Nullable
@Override
public String text(){
return text != null && text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text;
if(text == null) return null;
if(text.startsWith("@")){
if(state.mapLocales.containsProperty(text.substring(1))) return state.mapLocales.getProperty(text.substring(1));
return Core.bundle.get(text.substring(1));
}else{
return text;
}
}
}
@@ -608,30 +609,38 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
}
/** Marker used for drawing UI to indicate something along with an objective. */
/** Marker used for drawing various content to indicate something along with an objective. Mostly used as UI overlay. */
public static abstract class ObjectiveMarker{
/** Makes sure markers are only added once. */
public transient boolean wasAdded;
//** Hides the marker, used by world processors */
public boolean hidden = false;
/** Internal use only! Do not access. */
public transient int arrayIndex;
/** Called in the overlay draw layer.*/
public void draw(){}
/** Called in the small and large map. */
public void drawMinimap(MinimapRenderer minimap){}
/** Add any UI elements necessary. */
public void added(){}
/** Remove any UI elements, if necessary. */
public void removed(){}
/** Control marker with world processor code*/
/** Whether to display marker in the world. */
public boolean world = true;
/** Whether to display marker on minimap. */
public boolean minimap = false;
/** Whether to scale marker corresponding to player's zoom level. */
public boolean autoscale = false;
/** On which z-sorting layer is marker drawn. */
protected float drawLayer = Layer.overlayUI;
public void draw(float scaleFactor){}
/** Control marker with world processor code. Ignores NaN (null) values. */
public void control(LMarkerControl type, double p1, double p2, double p3){
if(Double.isNaN(p1)) return;
switch(type){
case toggleVisibility -> hidden = !hidden;
case setVisibility -> hidden = ((Math.abs(p1) < 1e-5));
case world -> world = !Mathf.equal((float)p1, 0f);
case minimap -> minimap = !Mathf.equal((float)p1, 0f);
case autoscale -> autoscale = !Mathf.equal((float)p1, 0f);
case drawLayer -> drawLayer = (float)p1;
}
}
public void setText(String text, boolean fetch){}
public void setTexture(String textureName){}
/** @return The localized type-name of this objective, defaulting to the class simple name without the "Marker" prefix. */
public String typeName(){
String className = getClass().getSimpleName().replace("Marker", "");
@@ -639,19 +648,52 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
public static String fetchText(String text){
return text.startsWith("@") ?
//on mobile, try ${text}.mobile first for mobile-specific hints.
mobile ? Core.bundle.get(text.substring(1) + ".mobile", Core.bundle.get(text.substring(1))) :
Core.bundle.get(text.substring(1)) :
text;
if(text == null) return "";
if(text.startsWith("@")){
String key = text.substring(1);
if(mobile){
return state.mapLocales.containsProperty(key + ".mobile") ?
state.mapLocales.getProperty(key + ".mobile") :
Core.bundle.get(key + ".mobile", Core.bundle.get(key));
}else{
return state.mapLocales.containsProperty(key) ?
state.mapLocales.getProperty(key) :
Core.bundle.get(key);
}
}else{
return text;
}
}
}
/** A marker that has a position in the world in world coordinates. */
public static abstract class PosMarker extends ObjectiveMarker{
/** Position of marker, in world coordinates */
public @TilePos Vec2 pos = new Vec2();
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
if(type == LMarkerControl.pos){
pos.x = (float)p1 * tilesize;
}
}
if(!Double.isNaN(p2)){
if(type == LMarkerControl.pos){
pos.y = (float)p2 * tilesize;
}
}
}
}
/** Displays text above a shape. */
public static class ShapeTextMarker extends ObjectiveMarker{
public static class ShapeTextMarker extends PosMarker{
public @Multiline String text = "frog";
public @TilePos Vec2 pos = new Vec2();
public float fontSize = 1f, textHeight = 7f;
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
@@ -691,16 +733,15 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
public ShapeTextMarker(){}
@Override
public void draw(){
if(hidden) return;
public void draw(float scaleFactor){
//in case some idiot decides to make 9999999 sides and freeze the game
int sides = Math.min(this.sides, 200);
int sides = Math.min(this.sides, 300);
Lines.stroke(3f, Pal.gray);
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
Lines.stroke(1f, color);
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
Draw.z(drawLayer);
Lines.stroke(3f * scaleFactor, Pal.gray);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
Lines.stroke(scaleFactor, color);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation);
Draw.reset();
if(fetchedText == null){
@@ -708,42 +749,43 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
// font size cannot be 0
if(Math.abs(fontSize) < 1e-5) return;
if(Mathf.equal(fontSize, 0f)) return;
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius + textHeight, Draw.z(), flags, fontSize);
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius * scaleFactor + textHeight * scaleFactor, drawLayer, flags, fontSize * scaleFactor);
}
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
switch(type){
case x -> pos.x = (float)p1 * tilesize;
case y -> pos.y = (float)p1 * tilesize;
case pos -> pos.set((float)p1 * tilesize, (float)p2 * tilesize);
case fontSize -> fontSize = (float)p1;
case textHeight -> textHeight = (float)p1;
case labelBackground -> {
if((Math.abs(p1) >= 1e-5)){
flags |= WorldLabel.flagBackground;
}else{
flags &= ~WorldLabel.flagBackground;
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case fontSize -> fontSize = (float)p1;
case textHeight -> textHeight = (float)p1;
case labelFlags -> {
if(!Mathf.equal((float)p1, 0f)){
flags |= WorldLabel.flagBackground;
}else{
flags &= ~WorldLabel.flagBackground;
}
}
case radius -> radius = (float)p1;
case rotation -> rotation = (float)p1;
case color -> color.fromDouble(p1);
case shape -> sides = (int)p1;
}
}
if(!Double.isNaN(p2)){
switch(type){
case labelFlags -> {
if(!Mathf.equal((float)p2, 0f)){
flags |= WorldLabel.flagOutline;
}else{
flags &= ~WorldLabel.flagOutline;
}
}
}
case labelOutline -> {
if((Math.abs(p1) >= 1e-5)){
flags |= WorldLabel.flagOutline;
}else{
flags &= ~WorldLabel.flagOutline;
}
}
case labelFlags -> {
flags = ((Math.abs(p1) >= 1e-5) ? WorldLabel.flagBackground : 0);
if((Math.abs(p2) >= 1e-5)) flags |= WorldLabel.flagOutline;
}
case radius -> radius = (float)p1;
case rotation -> rotation = (float)p1;
case shapeSides -> sides = (int)p1;
case color -> color.set(Tmp.c1.fromDouble(p1));
default -> super.control(type, p1, p2, p3);
}
}
@@ -758,62 +800,58 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
}
/** Displays a circle on the minimap. */
public static class MinimapMarker extends ObjectiveMarker{
public Point2 pos = new Point2();
/** Displays a circle in the world. */
public static class PointMarker extends PosMarker{
public float radius = 5f, stroke = 11f;
public Color color = Color.valueOf("f25555");
public MinimapMarker(int x, int y){
public PointMarker(int x, int y){
this.pos.set(x, y);
}
public MinimapMarker(int x, int y, Color color){
public PointMarker(int x, int y, Color color){
this.pos.set(x, y);
this.color = color;
}
public MinimapMarker(int x, int y, float radius, float stroke, Color color){
public PointMarker(int x, int y, float radius, float stroke, Color color){
this.pos.set(x, y);
this.stroke = stroke;
this.radius = radius;
this.color = color;
}
public MinimapMarker(){}
public PointMarker(){}
@Override
public void drawMinimap(MinimapRenderer minimap){
if(hidden) return;
minimap.transform(Tmp.v1.set(pos.x * tilesize, pos.y * tilesize));
float rad = minimap.scale(radius * tilesize);
public void draw(float scaleFactor){
float rad = radius * tilesize * scaleFactor;
float fin = Interp.pow2Out.apply((Time.globalTime / 100f) % 1f);
Draw.z(drawLayer);
Lines.stroke(Scl.scl((1f - fin) * stroke + 0.1f), color);
Lines.circle(Tmp.v1.x, Tmp.v1.y, rad * fin);
Lines.circle(pos.x, pos.y, rad * fin);
Draw.reset();
}
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
switch(type){
case x -> pos.x = (int)p1;
case y -> pos.y = (int)p1;
case pos -> pos.set((int)p1, (int)p2);
case radius -> radius = (float)p1;
case stroke -> stroke = (float)p1;
case color -> color.set(Tmp.c1.fromDouble(p1));
default -> super.control(type, p1, p2, p3);
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case radius -> radius = (float)p1;
case stroke -> stroke = (float)p1;
case color -> color.fromDouble(p1);
}
}
}
}
/** Displays a shape with an outline and color. */
public static class ShapeMarker extends ObjectiveMarker{
public @TilePos Vec2 pos = new Vec2();
public float radius = 8f, rotation = 0f, stroke = 1f;
public static class ShapeMarker extends PosMarker{
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");
@@ -831,23 +869,26 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
public ShapeMarker(){}
@Override
public void draw(){
if(hidden) return;
public void draw(float scaleFactor){
//in case some idiot decides to make 9999999 sides and freeze the game
int sides = Math.min(this.sides, 200);
Draw.z(drawLayer);
if(!fill){
if(outline){
Lines.stroke(stroke + 2f, Pal.gray);
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
Lines.stroke((stroke + 2f) * scaleFactor, Pal.gray);
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
}
Lines.stroke(stroke, color);
Lines.poly(pos.x, pos.y, sides, radius + 1f, rotation);
Lines.stroke(stroke * scaleFactor, color);
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, 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();
@@ -855,31 +896,37 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
switch(type){
case x -> pos.x = (float)p1 * tilesize;
case y -> pos.y = (float)p1 * tilesize;
case pos -> pos.set((float)p1 * tilesize, (float)p2 * tilesize);
case radius -> radius = (float)p1;
case rotation -> rotation = (float)p1;
case stroke -> stroke = (float)p1;
case shapeSides -> sides = (int)p1;
case shapeFill -> fill = (Math.abs(p1) >= 1e-5);
case shapeOutline -> outline = (Math.abs(p1) >= 1e-5);
case setShape -> {
sides = (int)p1;
fill = (Math.abs(p2) >= 1e-5);
outline = (Math.abs(p3) >= 1e-5);
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case radius -> radius = (float)p1;
case stroke -> stroke = (float)p1;
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;
}
}
if(!Double.isNaN(p3)){
if(type == LMarkerControl.shape){
outline = !Mathf.equal((float)p3, 0f);
}
case color -> color.set(Tmp.c1.fromDouble(p1));
default -> super.control(type, p1, p2, p3);
}
}
}
/** Displays text at a location. */
public static class TextMarker extends ObjectiveMarker{
public static class TextMarker extends PosMarker{
public @Multiline String text = "uwu";
public @TilePos Vec2 pos = new Vec2();
public float fontSize = 1f;
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
// Cached localized text.
@@ -900,43 +947,44 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
public TextMarker(){}
@Override
public void draw(){
public void draw(float scaleFactor){
// font size cannot be 0
if(hidden || Math.abs(fontSize) < 1e-5) return;
if(Mathf.equal(fontSize, 0f)) return;
if(fetchedText == null){
fetchedText = fetchText(text);
}
WorldLabel.drawAt(fetchedText, pos.x, pos.y, Draw.z(), flags, fontSize);
WorldLabel.drawAt(fetchedText, pos.x, pos.y, drawLayer, flags, fontSize * scaleFactor);
}
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
switch(type){
case x -> pos.x = (float)p1 * tilesize;
case y -> pos.y = (float)p1 * tilesize;
case pos -> pos.set((float)p1 * tilesize, (float)p2 * tilesize);
case fontSize -> fontSize = (float)p1;
case labelBackground -> {
if((Math.abs(p1) >= 1e-5)){
flags |= WorldLabel.flagBackground;
}else{
flags &= ~WorldLabel.flagBackground;
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case fontSize -> fontSize = (float)p1;
case labelFlags -> {
if(!Mathf.equal((float)p1, 0f)){
flags |= WorldLabel.flagBackground;
}else{
flags &= ~WorldLabel.flagBackground;
}
}
}
case labelOutline -> {
if((Math.abs(p1) >= 1e-5)){
flags |= WorldLabel.flagOutline;
}else{
flags &= ~WorldLabel.flagOutline;
}
if(!Double.isNaN(p2)){
switch(type){
case labelFlags -> {
if(!Mathf.equal((float)p2, 0f)){
flags |= WorldLabel.flagOutline;
}else{
flags &= ~WorldLabel.flagOutline;
}
}
}
case labelFlags -> {
flags = ((Math.abs(p1) >= 1e-5) ? WorldLabel.flagBackground : 0);
if((Math.abs(p2) >= 1e-5)) flags |= WorldLabel.flagOutline;
}
default -> super.control(type, p1, p2, p3);
}
}
@@ -952,52 +1000,251 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
/** Displays a line from pos1 to pos2. */
public static class LineMarker extends ObjectiveMarker{
public @TilePos Vec2 pos1 = new Vec2(), pos2 = new Vec2();
public static class LineMarker extends PosMarker{
public @TilePos Vec2 endPos = new Vec2();
public float stroke = 1f;
public boolean outline = true;
public Color color = Color.valueOf("ffd37f");
public Color color1 = Color.valueOf("ffd37f");
public Color color2 = Color.valueOf("ffd37f");
public LineMarker(String text, float x1, float y1, float x2, float y2, float stroke){
public LineMarker(float x1, float y1, float x2, float y2, float stroke){
this.stroke = stroke;
this.pos1.set(x1, y1);
this.pos2.set(x2, y2);
this.pos.set(x1, y1);
this.endPos.set(x2, y2);
}
public LineMarker(String text, float x1, float y1, float x2, float y2){
this.pos1.set(x1, y1);
this.pos2.set(x2, y2);
public LineMarker(float x1, float y1, float x2, float y2){
this.pos.set(x1, y1);
this.endPos.set(x2, y2);
}
public LineMarker(){}
@Override
public void draw(float scaleFactor){
Draw.z(drawLayer);
if(outline){
Lines.stroke((stroke + 2f) * scaleFactor, Pal.gray);
Lines.line(pos.x, pos.y, endPos.x, endPos.y);
}
Lines.stroke(stroke * scaleFactor, Color.white);
Lines.line(pos.x, pos.y, color1, endPos.x, endPos.y, color2);
}
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
switch(type){
case x -> pos1.x = (float)p1 * tilesize;
case y -> pos1.y = (float)p1 * tilesize;
case pos -> pos1.set((float)p1 * tilesize, (float)p2 * tilesize);
case endX -> pos2.x = (float)p1 * tilesize;
case endY -> pos2.y = (float)p1 * tilesize;
case endPos -> pos2.set((float)p1 * tilesize, (float)p2 * tilesize);
case stroke -> stroke = (float)p1;
case shapeOutline -> outline = ((Math.abs(p1) >= 1e-5));
case color -> color.set(Tmp.c1.fromDouble(p1));
default -> super.control(type, p1, p2, p3);
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case endPos -> endPos.x = (float)p1 * tilesize;
case stroke -> stroke = (float)p1;
case color -> color1.set(color2.fromDouble(p1));
}
}
if(!Double.isNaN(p2)){
switch(type){
case endPos -> endPos.y = (float)p2 * tilesize;
}
}
if(!Double.isNaN(p1) && !Double.isNaN(p2)){
switch (type){
case posi -> ((int)p1 == 0 ? pos : (int)p1 == 1 ? endPos : Tmp.v1).x = (float)p2 * tilesize;
case colori -> ((int)p1 == 0 ? color1 : (int)p1 == 1 ? color2 : Tmp.c1).fromDouble(p2);
}
}
if(!Double.isNaN(p1) && !Double.isNaN(p3)){
switch(type){
case posi -> ((int)p1 == 0 ? pos : (int)p1 == 1 ? endPos : Tmp.v1).y = (float)p3 * tilesize;
}
}
}
}
/** Displays a texture with specified name. */
public static class TextureMarker extends PosMarker{
public float rotation = 0f, width = 0f, height = 0f; // Zero width/height scales marker to original texture's size
public String textureName = "";
public Color color = Color.white.cpy();
private transient TextureRegion fetchedRegion;
public TextureMarker(String textureName, float x, float y, float width, float height){
this.textureName = textureName;
this.pos.set(x, y);
this.width = width;
this.height = height;
}
public TextureMarker(String textureName, float x, float y){
this.textureName = textureName;
this.pos.set(x, y);
}
public TextureMarker(){}
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case rotation -> rotation = (float)p1;
case textureSize -> width = (float)p1 * tilesize;
case color -> color.fromDouble(p1);
}
}
if(!Double.isNaN(p2)){
switch(type){
case textureSize -> height = (float)p2 * tilesize;
}
}
}
@Override
public void draw(){
if(hidden) return;
public void draw(float scaleFactor){
if(textureName.isEmpty()) return;
if(outline){
Lines.stroke(stroke + 2f, Pal.gray);
Lines.line(pos1.x, pos1.y, pos2.x, pos2.y);
if(fetchedRegion == null) setTexture(textureName);
// Zero width/height scales marker to original texture's size
if(Mathf.equal(width, 0f)) width = fetchedRegion.width * fetchedRegion.scl() * Draw.xscl;
if(Mathf.equal(height, 0f)) height = fetchedRegion.height * fetchedRegion.scl() * Draw.yscl;
Draw.z(drawLayer);
Draw.color(color);
Draw.rect(fetchedRegion, pos.x, pos.y, width * scaleFactor, height * scaleFactor, rotation);
}
@Override
public void setTexture(String textureName){
this.textureName = textureName;
if(fetchedRegion == null) fetchedRegion = new TextureRegion();
lookupRegion(textureName, fetchedRegion);
}
}
public static class QuadMarker extends ObjectiveMarker{
public String textureName = "white";
public @Vertices float[] vertices = new float[24];
private boolean mapRegion = true;
private transient TextureRegion fetchedRegion;
public QuadMarker() {
for(int i = 0; i < 4; i++){
vertices[i * 6 + 2] = Color.white.toFloatBits();
vertices[i * 6 + 5] = Color.clearFloatBits;
}
}
@Override
public void draw(float scaleFactor){
if(fetchedRegion == null) setTexture(textureName);
Draw.z(drawLayer);
Draw.vert(fetchedRegion.texture, vertices, 0, vertices.length);
}
@Override
public void control(LMarkerControl type, double p1, double p2, double p3){
super.control(type, p1, p2, p3);
if(!Double.isNaN(p1)){
switch(type){
case color -> {
float col = Tmp.c1.fromDouble(p1).toFloatBits();
for(int i = 0; i < 4; i++) vertices[i * 6 + 2] = col;
}
case pos -> vertices[0] = (float)p1 * tilesize;
case posi -> setPos((int)p1, p2, p3);
case uvi -> setUv((int)p1, p2, p3);
}
}
Lines.stroke(stroke, color);
Lines.line(pos1.x, pos1.y, pos2.x, pos2.y);
if(!Double.isNaN(p2)){
switch(type){
case pos -> vertices[1] = (float)p1 * tilesize;
}
}
if(!Double.isNaN(p1) && !Double.isNaN(p2)){
switch(type){
case colori -> setColor((int)p1, p2);
}
}
}
@Override
public void setTexture(String textureName){
this.textureName = textureName;
boolean firstUpdate = fetchedRegion == null;
if(fetchedRegion == null) fetchedRegion = new TextureRegion();
Tmp.tr1.set(fetchedRegion);
lookupRegion(textureName, fetchedRegion);
if(firstUpdate){
if(mapRegion){
mapRegion = false;
// possibly from the editor, we need to clamp the values
for(int i = 0; i < 4; i++){
vertices[i * 6 + 3] = Mathf.map(Mathf.clamp(vertices[i * 6 + 3]), fetchedRegion.u, fetchedRegion.u2);
vertices[i * 6 + 4] = Mathf.map(1 - Mathf.clamp(vertices[i * 6 + 4]), fetchedRegion.v, fetchedRegion.v2);
}
}
}else{
for(int i = 0; i < 4; i++){
vertices[i * 6 + 3] = Mathf.map(vertices[i * 6 + 3], Tmp.tr1.u, Tmp.tr1.u2, fetchedRegion.u, fetchedRegion.u2);
vertices[i * 6 + 4] = Mathf.map(vertices[i * 6 + 4], Tmp.tr1.v, Tmp.tr1.v2, fetchedRegion.v, fetchedRegion.v2);
}
}
}
private void setPos(int i, double x, double y){
if(i >= 0 && i < 4){
if(!Double.isNaN(x)) vertices[i * 6] = (float)x * tilesize;
if(!Double.isNaN(y)) vertices[i * 6 + 1] = (float)y * tilesize;
}
}
private void setColor(int i, double c){
if(i >= 0 && i < 4){
vertices[i * 6 + 2] = Tmp.c1.fromDouble(c).toFloatBits();
}
}
private void setUv(int i, double u, double v){
if(i >= 0 && i < 4){
if(fetchedRegion == null) setTexture(textureName);
if(!Double.isNaN(u)) vertices[i * 6 + 3] = Mathf.map(Mathf.clamp((float)u), fetchedRegion.u, fetchedRegion.u2);
if(!Double.isNaN(v)) vertices[i * 6 + 4] = Mathf.map(1 - Mathf.clamp((float)v), fetchedRegion.v, fetchedRegion.v2);
}
}
}
private static void lookupRegion(String name, TextureRegion out){
TextureRegion region = Core.atlas.find(name);
if(region.found()){
out.set(region);
}else{
if(Core.assets.isLoaded(name, Texture.class)){
out.set(Core.assets.get(name, Texture.class));
}else{
out.set(Core.atlas.find("error"));
}
}
}
@@ -1006,6 +1253,16 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
@Retention(RUNTIME)
public @interface Unordered{}
/** For arrays or {@link Seq}s; does not add the new and delete buttons */
@Target(FIELD)
@Retention(RUNTIME)
public @interface Immutable{}
/** For {@code float[]}; treats it as an array of vertices. */
@Target(FIELD)
@Retention(RUNTIME)
public @interface Vertices{}
/** For {@code byte}; treats it as a world label flag. */
@Target(FIELD)
@Retention(RUNTIME)
+6
View File
@@ -59,6 +59,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. */
@@ -105,6 +107,10 @@ public class Rules{
public boolean coreDestroyClear = false;
/** If true, banned blocks are hidden from the build menu. */
public boolean hideBannedBlocks = false;
/** If true, most blocks (including environmental walls) can be deconstructed. This is only meant to be used internally in sandbox/test maps. */
public boolean allowEnvironmentDeconstruct = false;
/** If true, buildings will be constructed instantly, with no limit on blocks placed per second. This is highly experimental and may cause lag! */
public boolean instantBuild = false;
/** If true, bannedBlocks becomes a whitelist. */
public boolean blockWhitelist = false;
/** If true, bannedUnits becomes a whitelist. */
+1 -1
View File
@@ -31,7 +31,7 @@ public class Schematic implements Publishable, Comparable<Schematic>{
}
public float powerProduction(){
return tiles.sumf(s -> s.block instanceof PowerGenerator p ? p.powerProduction : 0f);
return tiles.sumf(s -> s.block instanceof PowerGenerator p ? p.getDisplayedPowerProduction() : 0f);
}
public float powerConsumption(){
+1 -1
View File
@@ -31,7 +31,7 @@ public class Team implements Comparable<Team>{
derelict = new Team(0, "derelict", Color.valueOf("4d4e58")),
sharded = new Team(1, "sharded", Pal.accent.cpy(), Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
crux = new Team(2, "crux", Color.valueOf("f25555"), Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
malis = new Team(3, "malis", Color.valueOf("a27ce5"), Color.valueOf("c195fb"), Color.valueOf("665c9f"), Color.valueOf("484988")),
malis = new Team(3, "malis", Color.valueOf("a27ce5"), Color.valueOf("c7a4f5"), Color.valueOf("896fd6"), Color.valueOf("504cba")),
//TODO temporarily no palettes for these teams.
green = new Team(4, "green", Color.valueOf("54d67d")),//Color.valueOf("96f58c"), Color.valueOf("54d67d"), Color.valueOf("28785c")),
+1 -1
View File
@@ -347,7 +347,7 @@ public class Teams{
}
for(var build : builds){
if(build.within(x, y, range) && !cores.contains(c -> c.within(x, y, range))){
if(build.within(x, y, range) && !cores.contains(c -> c.within(build, range))){
//TODO GPU driver bugs?
build.kill();
//Time.run(Mathf.random(0f, 60f * 6f), build::kill);
+1 -1
View File
@@ -223,7 +223,7 @@ public class Drawf{
/** Sets Draw.z to the text layer, and returns the previous layer. */
public static float text(){
float z = Draw.z();
if(renderer.pixelator.enabled()){
if(renderer.pixelate){
Draw.z(Layer.endPixeled);
}
@@ -146,26 +146,37 @@ public class MinimapRenderer{
rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize);
Tmp.m2.set(Draw.trans());
float scaleFactor;
var trans = Tmp.m1.idt();
trans.translate(lastX, lastY);
if(!worldSpace){
trans.scl(Tmp.v1.set(scaleFactor = lastW / rect.width, lastH / rect.height));
trans.translate(-rect.x, -rect.y);
}else{
trans.scl(Tmp.v1.set(scaleFactor = lastW / world.unitWidth(), lastH / world.unitHeight()));
}
trans.translate(tilesize / 2f, tilesize / 2f);
Draw.trans(trans);
scaleFactor = 1f / scaleFactor;
for(Unit unit : units){
if(unit.inFogTo(player.team()) || !unit.type.drawMinimap) continue;
float rx = !fullView ? (unit.x - rect.x) / rect.width * w : unit.x / (world.width() * tilesize) * w;
float ry = !fullView ? (unit.y - rect.y) / rect.width * h : unit.y / (world.height() * tilesize) * h;
float scale = Scl.scl(1f) * tilesize * 3;
var region = unit.icon();
Draw.mixcol(unit.team.color, 1f);
float scale = Scl.scl(1f) / 2f * scaling * 32f;
var region = unit.icon();
Draw.rect(region, x + rx, y + ry, scale, scale * (float)region.height / region.width, unit.rotation() - 90);
Draw.rect(region, unit.x, unit.y, scale, scale * (float)region.height / region.width, unit.rotation() - 90);
Draw.reset();
}
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);
}
}
}
@@ -186,15 +197,14 @@ public class MinimapRenderer{
//crisp pixels
dynamicTex.setFilter(TextureFilter.nearest);
if(worldSpace){
region.set(0f, 0f, 1f, 1f);
}
Tmp.tr1.set(dynamicTex);
Tmp.tr1.set(region.u, 1f - region.v, region.u2, 1f - region.v2);
Tmp.tr1.set(0f, 1f, 1f, 0f);
float wf = world.width() * tilesize;
float hf = world.height() * tilesize;
Draw.color(state.rules.dynamicColor, 0.5f);
Draw.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h);
Draw.rect(Tmp.tr1, wf / 2, hf / 2, wf, hf);
if(state.rules.staticFog){
staticTex.setFilter(TextureFilter.nearest);
@@ -202,7 +212,7 @@ public class MinimapRenderer{
Tmp.tr1.texture = staticTex;
//must be black to fit with borders
Draw.color(0f, 0f, 0f, 1f);
Draw.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h);
Draw.rect(Tmp.tr1, wf / 2, hf / 2, wf, hf);
}
Draw.color();
@@ -211,23 +221,21 @@ public class MinimapRenderer{
//TODO might be useful in the standard minimap too
if(fullView){
drawSpawns(x, y, w, h, scaling);
drawSpawns();
if(!mobile){
//draw bounds for camera - not drawn on mobile because you can't shift it by tapping anyway
Rect r = Core.camera.bounds(Tmp.r1);
Vec2 bot = transform(Tmp.v1.set(r.x, r.y));
Vec2 top = transform(Tmp.v2.set(r.x + r.width, r.y + r.height));
Lines.stroke(Scl.scl(3f));
Lines.stroke(Scl.scl(3f) * scaleFactor);
Draw.color(Pal.accent);
Lines.rect(bot.x,bot.y, top.x - bot.x, top.y - bot.y);
Lines.rect(r.x, r.y, r.width, r.height);
Draw.reset();
}
}
LongSeq indicators = control.indicators.list();
float fin = ((Time.globalTime / 30f) % 1f);
float rad = scale(fin * 5f + tilesize - 2f);
float rad = fin * 5f + tilesize - 2f;
Lines.stroke(Scl.scl((1f - fin) * 4f + 0.5f));
for(int i = 0; i < indicators.size; i++){
@@ -244,27 +252,32 @@ public class MinimapRenderer{
offset = build.block.offset / tilesize;
}
Vec2 v = transform(Tmp.v1.set((ix + 0.5f + offset) * tilesize, (iy + 0.5f + offset) * tilesize));
Draw.color(Color.orange, Color.scarlet, Mathf.clamp(time / 70f));
Lines.square(v.x, v.y, rad);
Lines.square((ix + 0.5f + offset) * tilesize, (iy + 0.5f + offset) * tilesize, rad);
}
Draw.reset();
//TODO autoscale markers
state.rules.objectives.eachRunning(obj -> {
for(var marker : obj.markers){
marker.drawMinimap(this);
if(marker.minimap){
marker.draw(1);
}
}
});
for(var marker : state.markers.values()){
if(marker != null) marker.drawMinimap(this);
for(var marker : state.markers){
if(marker.minimap){
marker.draw(1);
}
}
Draw.trans(Tmp.m2);
}
public void drawSpawns(float x, float y, float w, float h, float scaling){
public void drawSpawns(){
if(!state.rules.showSpawns || !state.hasSpawns() || !state.rules.waves) return;
TextureRegion icon = Icon.units.getRegion();
@@ -273,36 +286,21 @@ public class MinimapRenderer{
Draw.color(state.rules.waveTeam.color, Tmp.c2.set(state.rules.waveTeam.color).value(1.2f), Mathf.absin(Time.time, 16f, 1f));
float rad = scale(state.rules.dropZoneRadius);
float rad = state.rules.dropZoneRadius;
float curve = Mathf.curve(Time.time % 240f, 120f, 240f);
for(Tile tile : spawner.getSpawns()){
float tx = ((tile.x + 0.5f) / world.width()) * w;
float ty = ((tile.y + 0.5f) / world.height()) * h;
float tx = tile.worldx();
float ty = tile.worldy();
Draw.rect(icon, x + tx, y + ty, icon.width, icon.height);
Lines.circle(x + tx, y + ty, rad);
if(curve > 0f) Lines.circle(x + tx, y + ty, rad * Interp.pow3Out.apply(curve));
Draw.rect(icon, tx, ty, icon.width, icon.height);
Lines.circle(tx, ty, rad);
if(curve > 0f) Lines.circle(tx, ty, rad * Interp.pow3Out.apply(curve));
}
Draw.reset();
}
//TODO horrible code, everywhere.
public Vec2 transform(Vec2 position){
if(!worldSpace){
position.sub(rect.x, rect.y).scl(lastW / rect.width, lastH / rect.height);
}else{
position.scl(lastW / world.unitWidth(), lastH / world.unitHeight());
}
return position.add(lastX, lastY);
}
public float scale(float radius){
return worldSpace ? (radius / (baseSize / 2f)) * 5f * lastScl : lastW / rect.width * radius;
}
public @Nullable TextureRegion getRegion(){
if(texture == null) return null;
+6 -2
View File
@@ -97,8 +97,12 @@ public class MultiPacker implements Disposable{
@Override
public void dispose(){
for(PixmapPacker packer : packers){
packer.dispose();
for(int i = 0; i < PageType.all.length; i ++){
var packer = packers[i];
//the UI packer's image is later used when merging with the font, don't dispose it
if(i != PageType.ui.ordinal()){
packer.forceDispose();
}
}
}
@@ -113,15 +113,6 @@ public class OverlayRenderer{
}
}
//draw objective markers
state.rules.objectives.eachRunning(obj -> {
for(var marker : obj.markers) marker.draw();
});
for(var marker : state.markers.values()){
if(marker != null) marker.draw();
}
if(player.dead()) return; //dead players don't draw
InputHandler input = control.input;
+1 -1
View File
@@ -58,7 +58,7 @@ public class Pixelator implements Disposable{
}
public boolean enabled(){
return Core.settings.getBool("pixelate");
return renderer.pixelate;
}
@Override
+11 -4
View File
@@ -242,10 +242,17 @@ public class Shaders{
@Override
public void apply(){
setUniformf("u_progress", progress);
setUniformf("u_uv", region.u, region.v);
setUniformf("u_uv2", region.u2, region.v2);
setUniformf("u_time", time);
setUniformf("u_texsize", region.texture.width, region.texture.height);
if(region.texture == null){
setUniformf("u_uv", 0f, 0f);
setUniformf("u_uv2", 1f, 1f);
setUniformf("u_texsize", 1, 1);
}else{
setUniformf("u_uv", region.u, region.v);
setUniformf("u_uv2", region.u2, region.v2);
setUniformf("u_texsize", region.texture.width, region.texture.height);
}
}
}
@@ -467,6 +474,6 @@ public class Shaders{
}
public static Fi getShaderFi(String file){
return Core.files.internal("shaders/" + file);
return tree.get("shaders/" + file);
}
}
+27 -13
View File
@@ -65,6 +65,12 @@ public class Trail{
x2 = items[i + 3];
y2 = items[i + 4];
w2 = items[i + 5];
if(i == 0 && points.size >= (length - 1) * 3){
x1 = Mathf.lerp(x1, x2, counter);
y1 = Mathf.lerp(y1, y2, counter);
w1 = Mathf.lerp(w1, w2, counter);
}
}else{
x2 = lastX;
y2 = lastY;
@@ -97,12 +103,11 @@ 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(points.size + ((count - 1) * 3) > length * 3 && points.size > 0){
points.removeRange(0, Math.min(3 * count - 1, points.size - 1));
}
}
@@ -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;
@@ -203,17 +203,7 @@ public class PlanetRenderer implements Disposable{
}
public void setPlane(Sector sector){
float rotation = -sector.planet.getRotation();
float length = 0.01f;
projector.setPlane(
//origin on sector position
Tmp.v33.set(sector.tile.v).setLength((outlineRad + length) * sector.planet.radius).rotate(Vec3.Y, rotation).add(sector.planet.position),
//face up
sector.plane.project(Tmp.v32.set(sector.tile.v).add(Vec3.Y)).sub(sector.tile.v, sector.planet.radius).rotate(Vec3.Y, rotation).nor(),
//right vector
Tmp.v31.set(Tmp.v32).rotate(Vec3.Y, -rotation).add(sector.tile.v).rotate(sector.tile.v, 90).sub(sector.tile.v).rotate(Vec3.Y, rotation).nor()
);
sector.planet.setPlane(sector, projector);
}
public void fill(Sector sector, Color color, float offset){
+2 -2
View File
@@ -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,7 +568,7 @@ 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;
}
+21 -13
View File
@@ -311,18 +311,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 +391,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);
@@ -604,7 +596,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
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();
@@ -615,7 +607,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void tileConfig(@Nullable Player player, Building build, @Nullable Object value){
if(build == null && net.server()) throw new ValidateException(player, "building is null");
if(build == null) return;
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.configure, build.tile, action -> action.config = value))){
@@ -652,7 +646,13 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
if(player.team() == build.team && build.canControlSelect(player.unit())){
var before = player.unit();
build.onControlSelect(player.unit());
if(!before.dead && before.spawnedByCore && !before.isPlayer()){
Call.unitDespawn(before);
}
}
}
@@ -697,6 +697,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//direct dock transfer???
unit.dockedType = before.dockedType;
}
if(before.spawnedByCore && !before.isPlayer()){
Call.unitDespawn(before);
}
}
Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
@@ -1000,6 +1004,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(attack != null){
Events.fire(Trigger.unitCommandAttack);
}else{
Events.fire(Trigger.unitCommandPosition);
}
int maxChunkSize = 200;
@@ -1167,7 +1173,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", () -> {
@@ -1649,7 +1655,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;
}
@@ -1961,11 +1967,13 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
var end = world.build(endX, endY);
if(diagonal && (block == null || block.allowDiagonal)){
if(block != null && start instanceof ChainedBuilding && end instanceof ChainedBuilding
&& block.canReplace(end.block) && block.canReplace(start.block)){
&& block.canReplace(end.block) && block.canReplace(start.block)){
points = Placement.upgradeLine(startX, startY, endX, endY);
}else{
points = Placement.pathfindLine(block != null && block.conveyorPlacement, startX, startY, endX, endY);
}
}else if(block != null && block.allowRectanglePlacement){
points = Placement.normalizeRectangle(startX, startY, endX, endY, block.size);
}else{
points = Placement.normalizeLine(startX, startY, endX, endY);
}
+6 -2
View File
@@ -743,6 +743,11 @@ public class MobileInput extends InputHandler implements GestureListener{
queueCommandMode = false;
}
//cannot rebuild and place at the same time
if(block != null){
rebuildMode = false;
}
if(player.dead()){
mode = none;
manualShooting = false;
@@ -766,7 +771,7 @@ public class MobileInput extends InputHandler implements GestureListener{
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
}
if(!Core.settings.getBool("keyboard") && !locked){
if(!Core.settings.getBool("keyboard") && !locked && !scene.hasKeyboard()){
//move camera around
float camSpeed = 6f;
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta * camSpeed));
@@ -967,7 +972,6 @@ public class MobileInput extends InputHandler implements GestureListener{
boolean allowHealing = type.canHeal;
boolean validHealTarget = allowHealing && target instanceof Building b && b.isValid() && target.team() == unit.team && b.damaged() && target.within(unit, type.range);
boolean boosted = (unit instanceof Mechc && unit.isFlying());
//reset target if:
// - in the editor, or...
// - it's both an invalid standard target and an invalid heal target
+67 -28
View File
@@ -58,6 +58,22 @@ public class Placement{
return points;
}
/** Normalize two points into a rectangle. */
public static Seq<Point2> normalizeRectangle(int startX, int startY, int endX, int endY, int blockSize){
Pools.freeAll(points);
points.clear();
int minX = Math.min(startX, endX), minY = Math.min(startY, endY), maxX = Math.max(startX, endX), maxY = Math.max(startY, endY);
for(int y = 0; y <= maxY - minY; y += blockSize){
for(int x = 0; x <= maxX - minX; x += blockSize){
points.add(Pools.obtain(Point2.class, Point2::new).set(startX + x * Mathf.sign(endX - startX), startY + y * Mathf.sign(endY - startY)));
}
}
return points;
}
public static Seq<Point2> upgradeLine(int startX, int startY, int endX, int endY){
closed.clear();
Pools.freeAll(points);
@@ -113,6 +129,10 @@ public class Placement{
}
public static void calculateBridges(Seq<BuildPlan> plans, ItemBridge bridge){
calculateBridges(plans, bridge, t -> false);
}
public static void calculateBridges(Seq<BuildPlan> plans, ItemBridge bridge, Boolf<Block> avoid){
if(isSidePlace(plans) || plans.size == 0) return;
//check for orthogonal placement + unlocked state
@@ -120,11 +140,11 @@ public class Placement{
return;
}
Boolf<BuildPlan> placeable = plan -> (plan.placeable(player.team())) ||
(plan.tile() != null && plan.tile().block() == plan.block); //don't count the same block as inaccessible
Boolf<BuildPlan> placeable = plan ->
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
!(plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
var result = plans1.clear();
var team = player.team();
var rotated = plans.first().tile() != null && plans.first().tile().absoluteRelativeTo(plans.peek().x, plans.peek().y) == Mathf.mod(plans.first().rotation + 2, 4);
outer:
@@ -134,6 +154,7 @@ public class Placement{
//gap found
if(i < plans.size - 1 && placeable.get(cur) && !placeable.get(plans.get(i + 1))){
boolean wereSame = true;
//find the closest valid position within range
for(int j = i + 1; j < plans.size; j++){
@@ -147,18 +168,29 @@ public class Placement{
}
i = j;
continue outer;
}else if(other.placeable(team)){
//found a link, assign bridges
cur.block = bridge;
other.block = bridge;
if(rotated){
other.config = new Point2(cur.x - other.x, cur.y - other.y);
}else{
cur.config = new Point2(other.x - cur.x, other.y - cur.y);
}
}else if(placeable.get(other)){
i = j;
continue outer;
if(wereSame){
//the gap is fake, it's just conveyors that can be replaced with junctions
i ++;
continue outer;
}else{
//found a link, assign bridges
cur.block = bridge;
other.block = bridge;
if(rotated){
other.config = new Point2(cur.x - other.x, cur.y - other.y);
}else{
cur.config = new Point2(other.x - cur.x, other.y - cur.y);
}
i = j;
continue outer;
}
}
if(other.tile() != null && !avoid.get(other.tile().block())){
wereSame = false;
}
}
@@ -175,7 +207,7 @@ public class Placement{
plans.set(result);
}
public static void calculateBridges(Seq<BuildPlan> plans, DirectionBridge bridge, boolean hasJunction, Boolf<Block> same){
public static void calculateBridges(Seq<BuildPlan> plans, DirectionBridge bridge, boolean hasJunction, Boolf<Block> avoid){
if(isSidePlace(plans) || plans.size == 0) return;
//check for orthogonal placement + unlocked state
@@ -183,13 +215,9 @@ public class Placement{
return;
}
Boolf<BuildPlan> rotated = plan -> plan.build() != null && same.get(plan.build().block) && plan.rotation != plan.build().rotation;
//TODO for chains of ducts, do not count consecutives in a different rotation as 'placeable'
Boolf<BuildPlan> placeable = plan ->
!(!hasJunction && rotated.get(plan)) &&
(plan.placeable(player.team()) ||
(plan.tile() != null && same.get(plan.tile().block()))); //don't count the same block as inaccessible
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
!(plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
var result = plans1.clear();
@@ -199,10 +227,11 @@ public class Placement{
result.add(cur);
//gap found
if(i < plans.size - 1 && placeable.get(cur) && (!placeable.get(plans.get(i + 1)) || (hasJunction && rotated.get(plans.get(i + 1)) && i < plans.size - 2 && !placeable.get(plans.get(i + 2))))){
if(i < plans.size - 1 && placeable.get(cur) && !placeable.get(plans.get(i + 1))){
boolean wereSame = true;
//find the closest valid position within range
for(int j = i + 2; j < plans.size; j++){
for(int j = i + 1; j < plans.size; j++){
var other = plans.get(j);
//out of range now, set to current position and keep scanning forward for next occurrence
@@ -214,12 +243,22 @@ public class Placement{
i = j;
continue outer;
}else if(placeable.get(other)){
//found a link, assign bridges
cur.block = bridge;
other.block = bridge;
i = j;
continue outer;
if(wereSame && hasJunction){
//the gap is fake, it's just conveyors that can be replaced with junctions
i ++;
continue outer;
}else{
//found a link, assign bridges
cur.block = bridge;
other.block = bridge;
i = j;
continue outer;
}
}
if(other.tile() != null && !avoid.get(other.tile().block())){
wereSame = false;
}
}
+6 -1
View File
@@ -56,8 +56,13 @@ public class SaveIO{
}
public static boolean isSaveValid(Fi file){
return isSaveFileValid(file) || isSaveFileValid(backupFileFor(file));
}
private static boolean isSaveFileValid(Fi file){
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(file.read(bufferSize)))){
return isSaveValid(stream);
getMeta(stream);
return true;
}catch(Throwable e){
return false;
}
+12 -4
View File
@@ -6,17 +6,16 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.game.MapObjectives.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.maps.Map;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -141,6 +140,7 @@ public abstract class SaveVersion extends SaveFileReader{
"wavetime", state.wavetime,
"stats", JsonIO.write(state.stats),
"rules", JsonIO.write(state.rules),
"locales", JsonIO.write(state.mapLocales),
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
"controlGroups", headless || control == null ? "null" : JsonIO.write(control.input.controlGroups),
"width", world.width(),
@@ -160,6 +160,7 @@ public abstract class SaveVersion extends SaveFileReader{
state.tick = map.getFloat("tick");
state.stats = JsonIO.read(GameStats.class, map.get("stats", "{}"));
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
state.mapLocales = JsonIO.read(MapLocales.class, map.get("locales", "{}"));
if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
lastReadBuild = map.getInt("build", -1);
@@ -400,11 +401,11 @@ public abstract class SaveVersion extends SaveFileReader{
}
public void writeMarkers(DataOutput stream) throws IOException{
JsonIO.writeBytes(Vars.state.markers, ObjectiveMarker.class, (DataOutputStream)stream);
state.markers.write(stream);
}
public void readMarkers(DataInput stream) throws IOException{
Vars.state.markers = JsonIO.readBytes(IntMap.class, ObjectiveMarker.class, (DataInputStream)stream);
state.markers.read(stream);
}
public void readTeamBlocks(DataInput stream) throws IOException{
@@ -434,6 +435,8 @@ public abstract class SaveVersion extends SaveFileReader{
//entityMapping is null in older save versions, so use the default
var mapping = this.entityMapping == null ? EntityMapping.idMap : this.entityMapping;
Seq<Entityc> entities = new Seq<>();
int amount = stream.readInt();
for(int j = 0; j < amount; j++){
readChunk(stream, true, in -> {
@@ -446,12 +449,17 @@ public abstract class SaveVersion extends SaveFileReader{
int id = in.readInt();
Entityc entity = (Entityc)mapping[typeid].get();
entities.add(entity);
EntityGroup.checkNextId(id);
entity.id(id);
entity.read(Reads.get(in));
entity.add();
});
}
for(var e : entities){
e.afterAllRead();
}
}
public void readEntityMapping(DataInput stream) throws IOException{
+2 -1
View File
@@ -563,13 +563,14 @@ public class TypeIO{
ai.targetPos = null;
}
ai.setupLastPos();
ai.readAttackTarget = -1;
if(hasAttack){
byte entityType = read.b();
if(entityType == 1){
ai.attackTarget = world.build(read.i());
}else{
ai.attackTarget = Groups.unit.getByID(read.i());
ai.attackTarget = Groups.unit.getByID(ai.readAttackTarget = read.i());
}
}else{
ai.attackTarget = null;
+103 -27
View File
@@ -27,44 +27,63 @@ 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, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam;
private static int 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 Seq<VarEntry> varEntries = new Seq<>();
private IntSet privilegedIds = new IntSet();
private UnlockableContent[][] logicIdToContent;
private int[][] contentIdToLogicId;
public void init(){
put("the end", null);
putEntryOnly("sectionProcessor");
putEntryOnly("@this");
putEntryOnly("@thisx");
putEntryOnly("@thisy");
putEntryOnly("@links");
putEntryOnly("@ipt");
putEntryOnly("sectionGeneral");
put("the end", null, false, true);
//add default constants
put("false", 0);
put("true", 1);
put("null", null);
putEntry("false", 0);
putEntry("true", 1);
put("null", null, false, true);
//math
put("@pi", Mathf.PI);
put("π", Mathf.PI); //for the "cool" kids
put("@e", Mathf.E);
put("@degToRad", Mathf.degRad);
put("@radToDeg", Mathf.radDeg);
putEntry("@pi", Mathf.PI);
put("π", Mathf.PI, false, true); //for the "cool" kids
putEntry("@e", Mathf.E);
putEntry("@degToRad", Mathf.degRad);
putEntry("@radToDeg", Mathf.radDeg);
putEntryOnly("sectionMap");
//time
varTime = put("@time", 0);
varTick = put("@tick", 0);
varSecond = put("@second", 0);
varMinute = put("@minute", 0);
varWave = put("@waveNumber", 0);
varWaveTime = put("@waveTime", 0);
varTime = putEntry("@time", 0);
varTick = putEntry("@tick", 0);
varSecond = putEntry("@second", 0);
varMinute = putEntry("@minute", 0);
varWave = putEntry("@waveNumber", 0);
varWaveTime = putEntry("@waveTime", 0);
varServer = put("@server", 0, true);
varClient = put("@client", 0, true);
varMapW = putEntry("@mapw", 0);
varMapH = putEntry("@maph", 0);
putEntryOnly("sectionNetwork");
varServer = putEntry("@server", 0, true);
varClient = putEntry("@client", 0, true);
//privileged desynced client variables
varClientLocale = put("@clientLocale", null, true);
varClientUnit = put("@clientUnit", null, true);
varClientName = put("@clientName", null, true);
varClientTeam = put("@clientTeam", 0, true);
varClientLocale = putEntry("@clientLocale", null, true);
varClientUnit = putEntry("@clientUnit", null, true);
varClientName = putEntry("@clientName", null, true);
varClientTeam = putEntry("@clientTeam", 0, true);
varClientMobile = putEntry("@clientMobile", 0, true);
//special enums
put("@ctrlProcessor", ctrlProcessor);
@@ -106,6 +125,10 @@ public class GlobalVars{
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);
@@ -114,6 +137,8 @@ public class GlobalVars{
logicIdToContent = new UnlockableContent[ContentType.all.length][];
contentIdToLogicId = new int[ContentType.all.length][];
putEntryOnly("sectionLookup");
Fi ids = Core.files.internal("logicids.dat");
if(ids.exists()){
//read logic ID mapping data (generated in ImagePacker)
@@ -124,7 +149,7 @@ public class GlobalVars{
contentIdToLogicId[ctype.ordinal()] = new int[Vars.content.getBy(ctype).size];
//store count constants
put("@" + ctype.name() + "Count", amount);
putEntry("@" + ctype.name() + "Count", amount);
for(int i = 0; i < amount; i++){
String name = in.readUTF();
@@ -158,6 +183,9 @@ public class GlobalVars{
vars.items[varWave].numval = state.wave;
vars.items[varWaveTime].numval = state.wavetime / 60f;
vars.items[varMapW].numval = world.width();
vars.items[varMapH].numval = world.height();
//network
vars.items[varServer].numval = (net.server() || !net.active()) ? 1 : 0;
vars.items[varClient].numval = net.client() ? 1 : 0;
@@ -168,9 +196,14 @@ public class GlobalVars{
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;
}
}
public Seq<VarEntry> getEntries(){
return varEntries;
}
/** @return a piece of content based on its logic ID. This is not equivalent to content ID. */
public @Nullable Content lookupContent(ContentType type, int id){
var arr = logicIdToContent[type.ordinal()];
@@ -183,14 +216,18 @@ public class GlobalVars{
return arr != null && content.id >= 0 && content.id < arr.length ? arr[content.id] : -1;
}
/** @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 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.
*/
public int get(String name){
return namesToIds.get(name, -1);
}
/** @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 ID. ID is not bound checked and must be positive.
* 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];
@@ -203,6 +240,11 @@ public class GlobalVars{
/** Adds a constant value by name. */
public int 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)
Log.debug("Failed to add global logic variable '@', as it already exists.", name);
@@ -222,10 +264,44 @@ public class GlobalVars{
namesToIds.put(name, index);
if(privileged) privilegedIds.add(index);
vars.add(var);
if(!hidden){
varEntries.add(new VarEntry(index, name, "", "", privileged));
}
return index;
}
public int put(String name, Object value){
return put(name, value, false);
}
public int putEntry(String name, Object value){
return put(name, value, false, false);
}
public int 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));
}
/** 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;
this.name = name;
this.description = description;
this.icon = icon;
this.privileged = privileged;
}
public VarEntry(){
}
}
}
@@ -0,0 +1,61 @@
package mindustry.logic;
import arc.*;
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.*;
public class GlobalVarsDialog extends BaseDialog{
public GlobalVarsDialog(){
super("@logic.globals");
addCloseButton();
shown(this::setup);
onResize(this::setup);
}
void setup(){
float prefWidth = Math.min(Core.graphics.getWidth() * 0.9f / Scl.scl(1f) - 240f, 600f);
cont.clearChildren();
cont.pane(t -> {
t.margin(10f).marginRight(16f);
t.defaults().fillX().fillY();
for(var entry : Vars.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();
t.image(Tex.whiteui).height(4f).color(color).colspan(4).padBottom(8f).row();
}else{
Color varColor = Pal.gray;
float stub = 8f, mul = 0.5f, pad = 4;
String desc = entry.description;
if(desc == null || desc.isEmpty()){
desc = Core.bundle.get("lglobal." + entry.name, "");
}
String fdesc = desc;
t.add(new Image(Tex.whiteui, varColor.cpy().mul(mul))).width(stub);
t.stack(new Image(Tex.whiteui, varColor), new Label(" " + entry.name + " ", Styles.outlineLabel)).padRight(pad);
t.add(new Image(Tex.whiteui, Pal.gray.cpy().mul(mul))).width(stub);
t.table(Tex.pane, out -> out.add(fdesc).style(Styles.outlineLabel).width(prefWidth).padLeft(2).padRight(2).wrap()).padRight(pad);
t.row();
t.add().fillX().colspan(4).height(4).row();
}
}
}).grow();
}
}
+1 -1
View File
@@ -67,7 +67,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, rotation, speed, armor, health, shield, team, flag, totalPower, payloadType};
LAccess(String... params){
this.params = params;
+255 -34
View File
@@ -13,12 +13,14 @@ import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.MapObjectives.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.logic.LogicFx.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.logic.*;
@@ -61,7 +63,11 @@ public class LExecutor{
public boolean privileged = false;
//yes, this is a minor memory leak, but it's probably not significant enough to matter
protected IntFloatMap unitTimeouts = new IntFloatMap();
protected static IntFloatMap unitTimeouts = new IntFloatMap();
static{
Events.on(ResetEvent.class, e -> unitTimeouts.clear());
}
boolean timeoutDone(Unit unit, float delay){
return Time.time >= unitTimeouts.get(unit.id) + delay;
@@ -161,11 +167,23 @@ public class LExecutor{
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : v.numval;
}
/** Get num value from variable, convert null to NaN to handle it differently in some instructions */
public double numOrNan(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : Double.NaN : invalid(v.numval) ? 0 : v.numval;
}
public float numf(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : (float)v.numval;
}
/** Get float value from variable, convert null to NaN to handle it differently in some instructions */
public float numfOrNan(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : Float.NaN : invalid(v.numval) ? 0 : (float)v.numval;
}
public int numi(int index){
return (int)num(index);
}
@@ -688,7 +706,7 @@ public class LExecutor{
int address = exec.numi(position);
Building from = exec.building(target);
if(from instanceof MemoryBuild mem && (exec.privileged || from.team == exec.team) && address >= 0 && address < mem.memory.length){
if(from instanceof MemoryBuild mem && (exec.privileged || (from.team == exec.team && !mem.block.privileged)) && address >= 0 && address < mem.memory.length){
mem.memory[address] = exec.num(value);
}
}
@@ -945,12 +963,6 @@ public class LExecutor{
//graphics on headless servers are useless.
if(Vars.headless || exec.graphicsBuffer.size >= maxGraphicsBuffer) return;
int num1 = exec.numi(p1);
if(type == LogicDisplay.commandImage){
num1 = exec.obj(p1) instanceof UnlockableContent u ? u.iconId : 0;
}
//explicitly unpack colorPack, it's pre-processed here
if(type == LogicDisplay.commandColorPack){
double packed = exec.num(x);
@@ -962,9 +974,68 @@ public class LExecutor{
a = ((value & 0x000000ff));
exec.graphicsBuffer.add(DisplayCmd.get(LogicDisplay.commandColor, pack(r), pack(g), pack(b), pack(a), 0, 0));
}else if(type == LogicDisplay.commandPrint){
CharSequence str = exec.textBuffer;
if(str.length() > 0){
var data = Fonts.logic.getData();
int advance = (int)data.spaceXadvance, lineHeight = (int)data.lineHeight;
int xOffset, yOffset;
int align = p1; //p1 is not a variable, it's a raw align value. what a massive hack
int maxWidth = 0, lines = 1, lineWidth = 0;
for(int i = 0; i < str.length(); i++){
char next = str.charAt(i);
if(next == '\n'){
maxWidth = Math.max(maxWidth, lineWidth);
lineWidth = 0;
lines ++;
}else{
lineWidth ++;
}
}
maxWidth = Math.max(maxWidth, lineWidth);
float
width = maxWidth * advance,
height = lines * lineHeight,
ha = ((Align.isLeft(align) ? -1f : 0f) + 1f + (Align.isRight(align) ? 1f : 0f))/2f,
va = ((Align.isBottom(align) ? -1f : 0f) + 1f + (Align.isTop(align) ? 1f : 0f))/2f;
xOffset = -(int)(width * ha);
yOffset = -(int)(height * va) + (lines - 1) * lineHeight;
int curX = exec.numi(x), curY = exec.numi(y);
for(int i = 0; i < str.length(); i++){
char next = str.charAt(i);
if(next == '\n'){
//move Y down when newline is encountered
curY -= lineHeight;
curX = exec.numi(x); //reset
continue;
}
if(Fonts.logic.getData().hasGlyph(next)){
exec.graphicsBuffer.add(DisplayCmd.get(LogicDisplay.commandPrint, packSign(curX + xOffset), packSign(curY + yOffset), next, 0, 0, 0));
}
curX += advance;
}
exec.textBuffer.setLength(0);
}
}else{
int num1 = exec.numi(p1), xval = packSign(exec.numi(x)), yval = packSign(exec.numi(y));
if(type == LogicDisplay.commandImage){
num1 = exec.obj(p1) instanceof UnlockableContent u ? u.iconId : 0;
}else if(type == LogicDisplay.commandScale){
xval = packSign((int)(exec.numf(x) / LogicDisplay.scaleStep));
yval = packSign((int)(exec.numf(y) / LogicDisplay.scaleStep));
}
//add graphics calls, cap graphics buffer size
exec.graphicsBuffer.add(DisplayCmd.get(type, packSign(exec.numi(x)), packSign(exec.numi(y)), packSign(num1), packSign(exec.numi(p2)), packSign(exec.numi(p3)), packSign(exec.numi(p4))));
exec.graphicsBuffer.add(DisplayCmd.get(type, xval, yval, packSign(num1), packSign(exec.numi(p2)), packSign(exec.numi(p3)), packSign(exec.numi(p4))));
}
}
@@ -1048,6 +1119,55 @@ public class LExecutor{
}
}
public static class FormatI implements LInstruction{
public int value;
public FormatI(int value){
this.value = value;
}
FormatI(){}
@Override
public void run(LExecutor exec){
if(exec.textBuffer.length() >= maxTextBuffer) return;
int placeholderIndex = -1;
int placeholderNumber = 10;
for(int i = 0; i < exec.textBuffer.length(); i++){
if(exec.textBuffer.charAt(i) == '{' && exec.textBuffer.length() - i > 2){
char numChar = exec.textBuffer.charAt(i + 1);
if(numChar >= '0' && numChar <= '9' && exec.textBuffer.charAt(i + 2) == '}'){
if(numChar - '0' < placeholderNumber){
placeholderNumber = numChar - '0';
placeholderIndex = i;
}
}
}
}
if(placeholderIndex == -1) return;
//this should avoid any garbage allocation
Var v = exec.var(value);
if(v.isobj && value != 0){
String strValue = PrintI.toString(v.objval);
exec.textBuffer.replace(placeholderIndex, placeholderIndex + 3, strValue);
}else{
//display integer version when possible
if(Math.abs(v.numval - (long)v.numval) < 0.00001){
exec.textBuffer.replace(placeholderIndex, placeholderIndex + 3, (long)v.numval + "");
}else{
exec.textBuffer.replace(placeholderIndex, placeholderIndex + 3, v.numval + "");
}
}
}
}
public static class PrintFlushI implements LInstruction{
public int target;
@@ -1113,7 +1233,6 @@ public class LExecutor{
public int value;
public float curTime;
public long frameId;
public WaitI(int value){
this.value = value;
@@ -1130,11 +1249,7 @@ public class LExecutor{
//skip back to self.
exec.var(varCounter).numval --;
exec.yield = true;
}
if(state.updateId != frameId){
curTime += Time.delta / 60f;
frameId = state.updateId;
}
}
}
@@ -1248,25 +1363,40 @@ public class LExecutor{
TeamData data = t.data();
switch(type){
case unit -> exec.setobj(result, i < 0 || i >= data.units.size ? null : data.units.get(i));
case unit -> {
UnitType type = exec.obj(extra) instanceof UnitType u ? u : null;
if(type == null){
exec.setobj(result, i < 0 || i >= data.units.size ? null : data.units.get(i));
}else{
var units = data.unitCache(type);
exec.setobj(result, units == null || i < 0 || i >= units.size ? null : units.get(i));
}
}
case player -> exec.setobj(result, i < 0 || i >= data.players.size || data.players.get(i).unit().isNull() ? null : data.players.get(i).unit());
case core -> exec.setobj(result, i < 0 || i >= data.cores.size ? null : data.cores.get(i));
case build -> {
Block block = exec.obj(extra) instanceof Block b ? b : null;
if(block == null){
exec.setobj(result, null);
exec.setobj(result, i < 0 || i >= data.buildings.size ? null : data.buildings.get(i));
}else{
var builds = data.getBuildings(block);
exec.setobj(result, i < 0 || i >= builds.size ? null : builds.get(i));
}
}
case unitCount -> exec.setnum(result, data.units.size);
case unitCount -> {
UnitType type = exec.obj(extra) instanceof UnitType u ? u : null;
if(type == null){
exec.setnum(result, data.units.size);
}else{
exec.setnum(result, data.unitCache(type) == null ? 0 : data.unitCache(type).size);
}
}
case coreCount -> exec.setnum(result, data.cores.size);
case playerCount -> exec.setnum(result, data.players.size);
case buildCount -> {
Block block = exec.obj(extra) instanceof Block b ? b : null;
if(block == null){
exec.setobj(result, null);
exec.setnum(result, data.buildings.size);
}else{
exec.setnum(result, data.getBuildings(block).size);
}
@@ -1389,6 +1519,47 @@ public class LExecutor{
}
}
public static class SenseWeatherI implements LInstruction{
public int type, to;
public SenseWeatherI(int type, int to){
this.type = type;
this.to = to;
}
@Override
public void run(LExecutor exec){
exec.setbool(to, exec.obj(type) instanceof Weather weather && weather.isActive());
}
}
public static class SetWeatherI implements LInstruction{
public int type, state;
public SetWeatherI(int type, int state){
this.type = type;
this.state = state;
}
@Override
public void run(LExecutor exec){
if(exec.obj(type) instanceof Weather weather){
if(exec.bool(state)){
if(!weather.isActive()){ //Create is not already active
Tmp.v1.setToRandomDirection();
Call.createWeather(weather, 1f, WeatherState.fadeTime, Tmp.v1.x, Tmp.v1.y);
}else{
weather.instance().life(WeatherState.fadeTime);
}
}else{
if(weather.isActive() && weather.instance().life > WeatherState.fadeTime){
weather.instance().life(WeatherState.fadeTime);
}
}
}
}
}
public static class ApplyEffectI implements LInstruction{
public boolean clear;
public String effect;
@@ -1556,7 +1727,7 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
//set default to succes
//set default to success
exec.setnum(outSuccess, 1);
if(headless && type != MessageType.mission) {
exec.textBuffer.setLength(0);
@@ -1853,7 +2024,7 @@ public class LExecutor{
}
public static class SetMarkerI implements LInstruction{
public LMarkerControl type = LMarkerControl.x;
public LMarkerControl type = LMarkerControl.pos;
public int id, p1, p2, p3;
public SetMarkerI(LMarkerControl type, int id, int p1, int p2, int p3){
@@ -1875,13 +2046,18 @@ public class LExecutor{
var marker = state.markers.get(exec.numi(id));
if(marker == null) return;
if(type == LMarkerControl.text){
marker.setText((exec.obj(p1) != null ? exec.obj(p1).toString() : "null"), false);
}else if(type == LMarkerControl.flushText){
marker.setText(exec.textBuffer.toString(), true);
if(type == LMarkerControl.flushText){
marker.setText(exec.textBuffer.toString(), exec.bool(p1));
exec.textBuffer.setLength(0);
}else if(type == LMarkerControl.texture){
if(exec.bool(p1)){
marker.setTexture(exec.textBuffer.toString());
exec.textBuffer.setLength(0);
}else{
marker.setTexture(PrintI.toString(exec.obj(p2)));
}
}else{
marker.control(type, exec.num(p1), exec.num(p2), exec.num(p3));
marker.control(type, exec.numOrNan(p1), exec.numOrNan(p2), exec.numOrNan(p3));
}
}
}
@@ -1909,12 +2085,12 @@ public class LExecutor{
public void run(LExecutor exec){
var cons = MapObjectives.markerNameToType.get(type);
if(cons != null && state.markers.size < maxMarkers){
if(cons != null && state.markers.size() < maxMarkers){
int mid = exec.numi(id);
if(exec.bool(replace) || !state.markers.containsKey(mid)){
if(exec.bool(replace) || !state.markers.has(mid)){
var marker = cons.get();
marker.control(LMarkerControl.pos, exec.num(x), exec.num(y), 0);
state.markers.put(mid, marker);
state.markers.add(mid, marker);
}
}
}
@@ -1922,7 +2098,12 @@ public class LExecutor{
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void createMarker(int id, ObjectiveMarker marker){
state.markers.put(id, marker);
state.markers.add(id, marker);
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void removeMarker(int id){
state.markers.remove(id);
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
@@ -1934,13 +2115,53 @@ public class LExecutor{
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void updateMarkerText(int id, LMarkerControl type, String text){
public static void updateMarkerText(int id, LMarkerControl type, boolean fetch, String text){
var marker = state.markers.get(id);
if(marker != null){
if(type == LMarkerControl.text){
marker.setText(text, true);
}else if(type == LMarkerControl.flushText){
marker.setText(text, false);
if(type == LMarkerControl.flushText){
marker.setText(text, fetch);
}
}
}
@Remote(called = Loc.server, variants = Variant.both, unreliable = true)
public static void updateMarkerTexture(int id, String textureName){
var marker = state.markers.get(id);
if(marker != null){
marker.setTexture(textureName);
}
}
public static class LocalePrintI implements LInstruction{
public int name;
public LocalePrintI(int name){
this.name = name;
}
public LocalePrintI(){
}
@Override
public void run(LExecutor exec){
if(exec.textBuffer.length() >= maxTextBuffer) return;
//this should avoid any garbage allocation
Var v = exec.var(name);
if(v.isobj){
String name = PrintI.toString(v.objval);
String strValue;
if(mobile){
strValue = state.mapLocales.containsProperty(name + ".mobile") ?
state.mapLocales.getProperty(name + ".mobile") :
state.mapLocales.getProperty(name);
}else{
strValue = state.mapLocales.getProperty(name);
}
exec.textBuffer.append(strValue);
}
}
}
+16 -18
View File
@@ -2,29 +2,27 @@ package mindustry.logic;
public enum LMarkerControl{
remove,
setVisibility("true/false"),
toggleVisibility,
text("text"),
flushText,
x("x"),
y("y"),
world("true/false"),
minimap("true/false"),
autoscale("true/false"),
pos("x", "y"),
endX("x"),
endY("y"),
endPos("x", "y"),
fontSize("size"),
textHeight("height"),
labelBackground("true/false"),
labelOutline("true/false"),
labelFlags("background", "outline"),
drawLayer("layer"),
color("color"),
radius("radius"),
stroke("stroke"),
rotation("rotation"),
shapeSides("sides"),
shapeFill("true/false"),
shapeOutline("true/false"),
setShape("sides", "fill", "outline"),
color("color");
shape("sides", "fill", "outline"),
arc("start", "end"),
flushText("fetch"),
fontSize("size"),
textHeight("height"),
labelFlags("background", "outline"),
texture("printFlush", "name"),
textureSize("width", "height"),
posi("index", "x", "y"),
uvi("index", "x", "y"),
colori("index", "color");
public final String[] params;
+20
View File
@@ -2,6 +2,7 @@ package mindustry.logic;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
@@ -10,10 +11,12 @@ import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.LCanvas.*;
import mindustry.logic.LExecutor.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
import static mindustry.logic.LCanvas.*;
/**
@@ -120,6 +123,23 @@ public abstract class LStatement{
return result[0];
}
/** Adds color edit button */
protected Cell<Button> col(Table table, String value, Cons<Color> setter){
return table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> {
Color current = Pal.accent.cpy();
if(value.startsWith("%")){
try{
current = Color.valueOf(value.substring(1));
}catch(Exception ignored){}
}
ui.picker.show(current, setter);
});
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
}
protected Cell<TextField> fields(Table table, String value, Cons<String> setter){
return field(table, value, setter).width(85f);
}
+242 -26
View File
@@ -6,6 +6,7 @@ import arc.graphics.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
@@ -121,6 +122,20 @@ public class LStatements{
@RegisterStatement("draw")
public static class DrawStatement extends LStatement{
static final String[] aligns = {"center", "top", "bottom", "left", "right", "topLeft", "topRight", "bottomLeft", "bottomRight"};
//yes, boxing Integer is gross but this is easier to construct and Integers <128 don't allocate anyway
static final ObjectMap<String, Integer> nameToAlign = ObjectMap.of(
"center", Align.center,
"top", Align.top,
"bottom", Align.bottom,
"left", Align.left,
"right", Align.right,
"topLeft", Align.topLeft,
"topRight", Align.topRight,
"bottomLeft", Align.bottomLeft,
"bottomRight", Align.bottomRight
);
public GraphicsType type = GraphicsType.clear;
public String x = "0", y = "0", p1 = "0", p2 = "0", p3 = "0", p4 = "0";
@@ -147,6 +162,11 @@ public class LStatements{
p2 = "32";
p3 = "0";
}
if(type == GraphicsType.print){
p1 = "bottomLeft";
}
rebuild(table);
}, 2, cell -> cell.size(100, 50)));
}, Styles.logict, () -> {}).size(90, 40).color(table.color).left().padLeft(2);
@@ -174,6 +194,10 @@ public class LStatements{
}
case col -> {
fields(s, "color", x, v -> x = v).width(144f);
col(s, x, res -> {
x = "%" + res.toString().substring(0, res.a >= 1f ? 6 : 8);
build(table);
});
}
case stroke -> {
s.add().width(4);
@@ -221,14 +245,28 @@ public class LStatements{
row(s);
fields(s, "rotation", p3, v -> p3 = v);
}
//TODO
/*
case character -> {
case print -> {
fields(s, "x", x, v -> x = v);
fields(s, "y", y, v -> y = v);
row(s);
fields(s, "char", p1, v -> p1 = v);
}*/
s.add("align ");
s.button(b -> {
b.label(() -> nameToAlign.containsKey(p1) ? p1 : "bottomLeft");
b.clicked(() -> showSelect(b, aligns, p1, t -> {
p1 = t;
}, 2, cell -> cell.size(165, 50)));
}, Styles.logict, () -> {}).size(165, 40).color(s.color).left().padLeft(2);
}
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();
}
@@ -243,7 +281,8 @@ public class LStatements{
@Override
public LInstruction build(LAssembler builder){
return new DrawI((byte)type.ordinal(), 0, builder.var(x), builder.var(y), builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
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));
}
@Override
@@ -273,6 +312,27 @@ public class LStatements{
}
}
@RegisterStatement("format")
public static class FormatStatement extends LStatement{
public String value = "\"frog\"";
@Override
public void build(Table table){
field(table, value, str -> value = str).width(0f).growX().padRight(3);
}
@Override
public LInstruction build(LAssembler builder){
return new FormatI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.io;
}
}
@RegisterStatement("drawflush")
public static class DrawFlushStatement extends LStatement{
public String target = "display1";
@@ -1320,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";
@@ -1554,22 +1723,10 @@ public class LStatements{
if(entry.color){
fields(table, "color", color, str -> color = str).width(120f);
table.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> {
Color current = Pal.accent.cpy();
if(color.startsWith("%")){
try{
current = Color.valueOf(color.substring(1));
}catch(Exception ignored){}
}
ui.picker.show(current, result -> {
color = "%" + result.toString().substring(0, result.a >= 1f ? 6 : 8);
build(table);
});
});
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
col(table, color, res -> {
color = "%" + res.toString().substring(0, res.a >= 1f ? 6 : 8);
build(table);
});
}
row(table);
@@ -1704,6 +1861,12 @@ public class LStatements{
fields(table, "block", extra, i -> extra = i);
}
if(type == FetchType.unitCount || type == FetchType.unit){
row(table);
fields(table, "unit", extra, i -> extra = i);
}
}
@Override
@@ -1925,7 +2088,7 @@ public class LStatements{
@RegisterStatement("setmarker")
public static class SetMarkerStatement extends LStatement{
public LMarkerControl type = LMarkerControl.x;
public LMarkerControl type = LMarkerControl.pos;
public String id = "0", p1 = "0", p2 = "0", p3 = "0";
@Override
@@ -1943,7 +2106,7 @@ public class LStatements{
b.clicked(() -> showSelect(b, LMarkerControl.all, type, t -> {
type = t;
rebuild(table);
}, 2, cell -> cell.size(140, 50)));
}, 3, cell -> cell.size(140, 50)));
}, Styles.logict, () -> {}).size(190, 40).color(table.color).left().padLeft(2);
row(table);
@@ -1958,7 +2121,35 @@ public class LStatements{
table.table(t -> {
t.setColor(table.color);
fields(t, type.params[i], i == 0 ? p1 : i == 1 ? p2 : p3, i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : v -> p3 = v).width(100f);
String value = i == 0 ? p1 : i == 1 ? p2 : p3;
Cons<String> setter = i == 0 ? v -> p1 = v : i == 1 ? v -> p2 = v : v -> p3 = v;
fields(t, type.params[i], value, setter).width(100f);
if(type == LMarkerControl.color || (type == LMarkerControl.colori && i == 1)){
col(t, value, res -> {
setter.get("%" + res.toString().substring(0, res.a >= 1f ? 6 : 8));
build(table);
});
}else if(type == LMarkerControl.drawLayer){
t.button(b -> {
b.image(Icon.pencilSmall);
b.clicked(() -> showSelectTable(b, (o, hide) -> {
o.row();
o.table(s -> {
s.left();
for(var field : Layer.class.getFields()){
float layer = Reflect.get(field);
s.button(field.getName() + " = " + layer, Styles.logicTogglet, () -> {
p1 = Float.toString(layer);
rebuild(table);
hide.run();
}).size(240f, 40f).row();
}
}).width(240f).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
}
});
if(i == 0) row(table);
@@ -1984,7 +2175,7 @@ public class LStatements{
@RegisterStatement("makemarker")
public static class MakeMarkerStatement extends LStatement{
public String id = "0", type = "shape", x = "0", y = "0", replace = "true";
public String type = "shape", id = "0", x = "0", y = "0", replace = "true";
@Override
public void build(Table table){
@@ -2027,4 +2218,29 @@ public class LStatements{
return LCategory.world;
}
}
@RegisterStatement("localeprint")
public static class LocalePrintStatement extends LStatement{
public String value = "\"name\"";
@Override
public void build(Table table){
field(table, value, str -> value = str).width(0f).growX().padRight(3);
}
@Override
public boolean privileged(){
return true;
}
@Override
public LInstruction build(LAssembler builder){
return new LocalePrintI(builder.var(value));
}
@Override
public LCategory category(){
return LCategory.world;
}
}
}
+5 -2
View File
@@ -26,6 +26,7 @@ public class LogicDialog extends BaseDialog{
Cons<String> consumer = s -> {};
boolean privileged;
@Nullable LExecutor executor;
GlobalVarsDialog globalsDialog = new GlobalVarsDialog();
public LogicDialog(){
super("logic");
@@ -51,7 +52,7 @@ public class LogicDialog extends BaseDialog{
add(buttons).growX().name("canvas");
}
private Color typeColor(Var s, Color color){
public static Color typeColor(Var s, Color color){
return color.set(
!s.isobj ? Pal.place :
s.objval == null ? Color.darkGray :
@@ -65,7 +66,7 @@ public class LogicDialog extends BaseDialog{
);
}
private String typeName(Var s){
public static String typeName(Var s){
return
!s.isobj ? "number" :
s.objval == null ? "null" :
@@ -178,6 +179,8 @@ public class LogicDialog extends BaseDialog{
});
dialog.addCloseButton();
dialog.buttons.button("@logic.globals", Icon.list, () -> globalsDialog.show()).size(210f, 64f);
dialog.show();
}).name("variables").disabled(b -> executor == null || executor.vars.length == 0);
+6
View File
@@ -57,6 +57,12 @@ public class LogicFx{
return map.get(name);
}
/** Adds an effect entry to the map. */
public static void add(String name, EffectEntry entry){
entry.name = name;
map.put(name, entry);
}
public static String[] all(){
return map.orderedKeys().toArray(String.class);
}
@@ -47,20 +47,20 @@ public class BaseGenerator{
if(bases.cores.isEmpty()) return;
Mathf.rand.setSeed(sector.id);
Mathf.rand.nextDouble();
float bracketRange = 0.17f;
float baseChance = Mathf.lerp(0.7f, 2.1f, difficulty);
int wallAngle = 70; //180 for full coverage
double resourceChance = 0.5 * baseChance;
double nonResourceChance = 0.002 * baseChance;
BasePart coreschem = bases.cores.getFrac(difficulty);
int passes = difficulty < 0.4 ? 1 : difficulty < 0.8 ? 3 : 5;
Block wall = getDifficultyWall(1, difficulty), wallLarge = getDifficultyWall(2, difficulty);
for(Tile tile : cores){
tile.clearOverlay();
Schematics.placeLoadout(coreschem.schematic, tile.x, tile.y, team, false);
Schematics.placeLoadout(bases.cores.getFrac((difficulty + Mathf.rand.range(0.4f)) / 1.4f).schematic, tile.x, tile.y, team, false);
//fill core with every type of item (even non-material)
Building entity = tile.build;
@@ -81,7 +81,7 @@ public class BaseGenerator{
tryPlace(parts.getFrac(difficulty + Mathf.range(bracketRange)), tile.x, tile.y, team);
}
}else if(Mathf.chance(nonResourceChance)){
tryPlace(bases.parts.getFrac(Mathf.random(1f)), tile.x, tile.y, team);
tryPlace(bases.parts.getFrac(Mathf.rand.random(1f)), tile.x, tile.y, team);
}
});
}
@@ -235,7 +235,7 @@ public class BaseGenerator{
set(placed, item);
}
Tile rand = world.tiles.getc(ex + Mathf.range(1), ey + Mathf.range(1));
Tile rand = world.tiles.getc(ex + Mathf.rand.range(1), ey + Mathf.rand.range(1));
if(rand.floor().hasSurface()){
//random ores nearby to make it look more natural
set(rand, item);
+36 -15
View File
@@ -2,6 +2,7 @@ package mindustry.mod;
import arc.*;
import arc.assets.*;
import arc.assets.loaders.MusicLoader.*;
import arc.assets.loaders.SoundLoader.*;
import arc.audio.*;
import arc.files.*;
@@ -60,7 +61,6 @@ public class ContentParser{
ObjectMap<Class<?>, ContentType> contentTypes = new ObjectMap<>();
ObjectSet<Class<?>> implicitNullable = ObjectSet.with(TextureRegion.class, TextureRegion[].class, TextureRegion[][].class, TextureRegion[][][].class);
ObjectMap<String, AssetDescriptor<?>> sounds = new ObjectMap<>();
Seq<ParseListener> listeners = new Seq<>();
ObjectMap<Class<?>, FieldParser> classParsers = new ObjectMap<>(){{
@@ -271,18 +271,15 @@ public class ContentParser{
return new Vec3(data.getFloat("x", 0f), data.getFloat("y", 0f), data.getFloat("z", 0f));
});
put(Sound.class, (type, data) -> {
if(fieldOpt(Sounds.class, data) != null) return fieldOpt(Sounds.class, data);
if(Vars.headless) return new Sound();
if(data.isArray()) return new RandomSound(parser.readValue(Sound[].class, data));
String name = "sounds/" + data.asString();
String path = Vars.tree.get(name + ".ogg").exists() ? name + ".ogg" : name + ".mp3";
var field = fieldOpt(Sounds.class, data);
return field != null ? field : Vars.tree.loadSound(data.asString());
});
put(Music.class, (type, data) -> {
var field = fieldOpt(Musics.class, data);
if(sounds.containsKey(path)) return ((SoundParameter)sounds.get(path).params).sound;
var sound = new Sound();
AssetDescriptor<?> desc = Core.assets.load(path, Sound.class, new SoundParameter(sound));
desc.errored = Throwable::printStackTrace;
sounds.put(path, desc);
return sound;
return field != null ? field : Vars.tree.loadMusic(data.asString());
});
put(Objectives.Objective.class, (type, data) -> {
if(data.isString()){
@@ -449,6 +446,17 @@ public class ContentParser{
if(value.has("consumes") && value.get("consumes").isObject()){
for(JsonValue child : value.get("consumes")){
switch(child.name){
case "remove" -> {
String[] values = child.isString() ? new String[]{child.asString()} : child.asStringArray();
for(String type : values){
Class<?> consumeType = resolve("Consume" + Strings.capitalize(type), Consume.class);
if(consumeType != Consume.class){
block.removeConsumers(b -> consumeType.isAssignableFrom(b.getClass()));
}else{
Log.warn("Unknown consumer type '@' (Class: @) in consume: remove.", type, "Consume" + Strings.capitalize(type));
}
}
}
case "item" -> block.consumeItem(find(ContentType.item, child.asString()));
case "itemCharged" -> block.consume((Consume)parser.readValue(ConsumeItemCharged.class, child));
case "itemFlammable" -> block.consume((Consume)parser.readValue(ConsumeItemFlammable.class, child));
@@ -995,7 +1003,6 @@ public class ContentParser{
throw new RuntimeException(e);
}
}
Object fieldOpt(Class<?> type, JsonValue value){
try{
return type.getField(value.asString()).get(null);
@@ -1047,7 +1054,21 @@ public class ContentParser{
}
Field field = metadata.field;
try{
field.set(object, parser.readValue(field.getType(), metadata.elementType, child, metadata.keyType));
boolean mergeMap = ObjectMap.class.isAssignableFrom(field.getType()) && child.has("add") && child.get("add").isBoolean() && child.getBoolean("add", false);
if(mergeMap){
child.remove("add");
}
Object readField = parser.readValue(field.getType(), metadata.elementType, child, metadata.keyType);
//if a map has add: true, add its contents to the map instead
if(mergeMap && field.get(object) instanceof ObjectMap<?,?> baseMap){
baseMap.putAll((ObjectMap)readField);
}else{
field.set(object, readField);
}
}catch(IllegalAccessException ex){
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")", ex);
}catch(SerializationException ex){
@@ -1095,8 +1116,8 @@ public class ContentParser{
}
//all items have a produce requirement unless already specified
if(object instanceof Item i && !node.objectives.contains(o -> o instanceof Produce p && p.content == i)){
node.objectives.add(new Produce(i));
if((unlock instanceof Item || unlock instanceof Liquid) && !node.objectives.contains(o -> o instanceof Produce p && p.content == unlock)){
node.objectives.add(new Produce(unlock));
}
//remove old node from parent
+6 -1
View File
@@ -6,6 +6,11 @@ import mindustry.*;
public abstract class Mod{
/** @return the folder where configuration files for this mod should go.*/
public Fi getConfigFolder(){
return Vars.mods.getConfigFolder(this);
}
/** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/
public Fi getConfig(){
return Vars.mods.getConfig(this);
@@ -26,7 +31,7 @@ public abstract class Mod{
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
/** Register any commands to be used on the client side, e.g. sent from an in-game player. */
public void registerClientCommands(CommandHandler handler){
}
+32 -9
View File
@@ -62,12 +62,18 @@ public class Mods implements Loadable{
return mainLoader;
}
/** Returns a file named 'config.json' in a special folder for the specified plugin.
/** @return the folder where configuration files for this mod should go. The folder may not exist yet; call mkdirs() before writing to it.
* Call this in init(). */
public Fi getConfig(Mod mod){
public Fi getConfigFolder(Mod mod){
ModMeta load = metas.get(mod.getClass());
if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!");
return modDirectory.child(load.name).child("config.json");
return modDirectory.child(load.name);
}
/** @return a file named 'config.json' in the config folder for the specified mod.
* Call this in init(). */
public Fi getConfig(Mod mod){
return getConfigFolder(mod).child("config.json");
}
/** Returns a list of files per mod subdirectory. */
@@ -218,7 +224,10 @@ public class Mods implements Loadable{
}
//this returns a *runnable* which actually packs the resulting pixmap; this has to be done synchronously outside the method
return () -> {
String fullName = (prefix ? mod.name + "-" : "") + baseName;
//don't prefix with mod name if it's already prefixed by a category, e.g. `block-modname-content-full`.
int hyphen = baseName.indexOf('-');
String fullName = ((prefix && !(hyphen != -1 && baseName.substring(hyphen + 1).startsWith(mod.name + "-"))) ? mod.name + "-" : "") + baseName;
packer.add(getPage(file), fullName, new PixmapRegion(pix));
if(textureScale != 1.0f){
textureResize.put(fullName, textureScale);
@@ -358,7 +367,24 @@ public class Mods implements Loadable{
Log.debug("Time to generate icons: @", Time.elapsed());
//dispose old atlas data
Core.atlas = packer.flush(filter, new TextureAtlas());
Core.atlas = packer.flush(filter, new TextureAtlas(){
PixmapRegion fake = new PixmapRegion(new Pixmap(1, 1));
boolean didWarn = false;
@Override
public PixmapRegion getPixmap(AtlasRegion region){
var other = super.getPixmap(region);
if(other.pixmap.isDisposed()){
if(!didWarn){
Log.err(new RuntimeException("Calling getPixmap outside of createIcons is not supported! This will be a crash in the future."));
didWarn = true;
}
return fake;
}
return other;
}
});
textureResize.each(e -> Core.atlas.find(e.key).scale = e.value);
@@ -856,7 +882,7 @@ public class Mods implements Loadable{
return false;
// If dependency present, resolve it, or if it's not required, ignore it
}else if(context.dependencies.containsKey(dependency.name)){
if(!context.ordered.contains(dependency.name) && !resolve(dependency.name, context) && dependency.required){
if(((!context.ordered.contains(dependency.name) && !resolve(dependency.name, context)) || !Core.settings.getBool("mod-" + dependency.name + "-enabled", true)) && dependency.required){
context.invalid.put(element, ModState.incompleteDependencies);
return false;
}
@@ -1180,8 +1206,6 @@ public class Mods implements Loadable{
public boolean hidden;
/** If true, this mod should be loaded as a Java class mod. This is technically optional, but highly recommended. */
public boolean java;
/** If true, -outline regions for units are kept when packing. Only use if you know exactly what you are doing. */
public boolean keepOutlines;
/** To rescale textures with a different size. Represents the size in pixels of the sprite of a 1x1 block. */
public float texturescale = 1.0f;
/** If true, bleeding is skipped and no content icons are generated. */
@@ -1229,7 +1253,6 @@ public class Mods implements Loadable{
", softDependencies=" + softDependencies +
", hidden=" + hidden +
", java=" + java +
", keepOutlines=" + keepOutlines +
", texturescale=" + texturescale +
", pregenerated=" + pregenerated +
'}';
@@ -577,6 +577,10 @@ public class Administration{
changed.run();
}
public boolean isDefault(){
return Structs.eq(get(), defaultValue);
}
private static boolean debug(){
return Config.debug.bool();
}
+10 -2
View File
@@ -5,6 +5,7 @@ import arc.func.*;
import arc.math.*;
import arc.net.*;
import arc.net.FrameworkMessage.*;
import arc.net.Server.*;
import arc.net.dns.*;
import arc.struct.*;
import arc.util.*;
@@ -93,7 +94,9 @@ public class ArcNetProvider implements NetProvider{
server.setMulticast(multicastGroup, multicastPort);
server.setDiscoveryHandler((address, handler) -> {
ByteBuffer buffer = NetworkIO.writeServerData();
int length = buffer.position();
buffer.position(0);
buffer.limit(length);
handler.respond(buffer);
});
@@ -161,6 +164,11 @@ public class ArcNetProvider implements NetProvider{
server.setConnectFilter(connectFilter);
}
@Override
public @Nullable ServerConnectFilter getConnectFilter(){
return server.getConnectFilter();
}
private static boolean isLocal(InetAddress addr){
if(addr.isAnyLocalAddress() || addr.isLoopbackAddress()) return true;
@@ -327,7 +335,7 @@ public class ArcNetProvider implements NetProvider{
@Override
public void sendStream(Streamable stream){
connection.addListener(new InputStreamSender(stream.stream, 512){
connection.addListener(new InputStreamSender(stream.stream, 1024){
int id;
@Override
@@ -446,7 +454,7 @@ public class ArcNetProvider implements NetProvider{
byteBuffer.put((byte)-2); //code for framework message
writeFramework(byteBuffer, msg);
}else{
if(!(o instanceof Packet pack)) throw new RuntimeException("All sent objects must implement be Packets! Class: " + o.getClass());
if(!(o instanceof Packet pack)) throw new RuntimeException("All sent objects must extend Packet! Class: " + o.getClass());
byte id = Net.getPacketId(pack);
byteBuffer.put(id);
+14
View File
@@ -3,6 +3,7 @@ package mindustry.net;
import arc.*;
import arc.func.*;
import arc.net.*;
import arc.net.Server.*;
import arc.struct.*;
import arc.util.*;
import mindustry.game.EventType.*;
@@ -325,6 +326,15 @@ public class Net{
}
}
/** Sets a connection filter by IP address. If the filter returns {@code false}, the connection will be closed. Server only. */
public void setConnectFilter(@Nullable ServerConnectFilter filter){
provider.setConnectFilter(filter);
}
public @Nullable ServerConnectFilter getConnectFilter(){
return provider.getConnectFilter();
}
/**
* Pings a host in a pooled thread. If an error occurred, failed() should be called with the exception.
* If the port is the default mindustry port, SRV records are checked too.
@@ -401,5 +411,9 @@ public class Net{
/** Sets a connection filter by IP address. If the filter returns {@code false}, the connection will be closed. */
default void setConnectFilter(Server.ServerConnectFilter connectFilter){}
default @Nullable ServerConnectFilter getConnectFilter(){
return null;
}
}
}

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