From 86c2fe880585bab8824356d7ed2e11a41df2b5e0 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 15 Oct 2020 13:44:20 -0400 Subject: [PATCH] too many things to list --- core/assets/bundles/bundle.properties | 3 +- core/assets/maps/craters.msav | Bin 11971 -> 11955 bytes core/src/mindustry/Vars.java | 2 - core/src/mindustry/ai/Pathfinder.java | 4 +- core/src/mindustry/ai/WaveSpawner.java | 16 +- core/src/mindustry/ai/types/FormationAI.java | 25 ++ core/src/mindustry/ai/types/MinerAI.java | 4 +- core/src/mindustry/ai/types/RepairAI.java | 6 +- core/src/mindustry/content/Blocks.java | 2 +- core/src/mindustry/content/Bullets.java | 2 +- core/src/mindustry/core/Logic.java | 65 +++- .../mindustry/ctype/UnlockableContent.java | 9 +- core/src/mindustry/editor/WaveGraph.java | 2 +- .../mindustry/entities/bullet/BulletType.java | 9 + .../bullet/ContinuousLaserBulletType.java | 6 + .../src/mindustry/entities/comp/UnitComp.java | 2 + .../mindustry/entities/comp/WeaponsComp.java | 2 +- core/src/mindustry/game/DefaultWaves.java | 4 +- core/src/mindustry/game/Rules.java | 2 + core/src/mindustry/game/SectorInfo.java | 8 + core/src/mindustry/game/SpawnGroup.java | 11 +- core/src/mindustry/game/Team.java | 6 +- core/src/mindustry/game/Universe.java | 30 +- core/src/mindustry/io/TypeIO.java | 10 + core/src/mindustry/logic/LAccess.java | 2 + core/src/mindustry/logic/LAssembler.java | 1 + core/src/mindustry/logic/LCanvas.java | 2 +- core/src/mindustry/logic/LExecutor.java | 8 +- core/src/mindustry/maps/SectorDamage.java | 331 +++++++++++++++++- core/src/mindustry/mod/Mods.java | 2 +- core/src/mindustry/net/NetworkIO.java | 16 + core/src/mindustry/type/Sector.java | 28 +- core/src/mindustry/type/UnitType.java | 15 +- core/src/mindustry/ui/dialogs/HostDialog.java | 12 +- core/src/mindustry/ui/dialogs/JoinDialog.java | 12 +- .../ui/dialogs/LaunchLoadoutDialog.java | 12 +- .../mindustry/ui/dialogs/PausedDialog.java | 19 +- .../mindustry/ui/dialogs/PlanetDialog.java | 14 +- .../mindustry/ui/dialogs/ResearchDialog.java | 11 +- .../ui/dialogs/SettingsMenuDialog.java | 1 - .../mindustry/ui/fragments/HudFragment.java | 2 +- .../ui/fragments/MinimapFragment.java | 10 +- .../world/blocks/defense/ForceProjector.java | 8 +- .../world/blocks/defense/MendProjector.java | 8 +- .../blocks/defense/OverdriveProjector.java | 8 +- .../blocks/defense/turrets/ItemTurret.java | 1 - .../blocks/defense/turrets/LiquidTurret.java | 5 +- .../blocks/defense/turrets/PowerTurret.java | 10 + .../world/blocks/defense/turrets/Turret.java | 3 + .../world/blocks/logic/SwitchBlock.java | 5 + gradle.properties | 2 +- tests/src/test/java/SectorTests.java | 4 +- 52 files changed, 665 insertions(+), 117 deletions(-) diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 4a854f5c71..1253de6a13 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -478,7 +478,7 @@ requirement.research = Research {0} requirement.capture = Capture {0} bestwave = [lightgray]Best Wave: {0} launch.text = Launch -campaign.multiplayer = While playing multiplayer in campaign, you can only research using items from [accent]your[] sectors, [scarlet]not[] the host's sector that you are on right now.\n\nTo get items to [accent]your[] sectors in multiplayer, use a [accent]launch pad[]. +research.multiplayer = Only the host can research items. uncover = Uncover configure = Configure Loadout #TODO @@ -695,7 +695,6 @@ setting.linear.name = Linear Filtering setting.hints.name = Hints setting.flow.name = Display Resource Flow Rate setting.buildautopause.name = Auto-Pause Building -setting.mapcenter.name = Auto Center Map To Player setting.animatedwater.name = Animated Fluids setting.animatedshields.name = Animated Shields setting.antialias.name = Antialias[lightgray] (requires restart)[] diff --git a/core/assets/maps/craters.msav b/core/assets/maps/craters.msav index b10b354bf009d65f7a94e7eda8c95064d58f0599..04c1aa73d36f10b689c7335883863b6ac7412757 100644 GIT binary patch delta 11429 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 delta 11487 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 F>XHl_#ee_+ diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index 37ba22d3cb..6fdc1418dd 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -86,8 +86,6 @@ public class Vars implements Loadable{ public static final float logicItemTransferRange = 45f; /** duration of time between turns in ticks */ public static final float turnDuration = 2 * Time.toMinutes; - /** turns needed to destroy a sector completely */ - public static final float sectorDestructionTurns = 2f; /** min armor fraction damage; e.g. 0.05 = at least 5% damage */ public static final float minArmorDamage = 0.1f; /** launch animation duration */ diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index 4a951d3448..0d8624baa3 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -450,7 +450,7 @@ public class Pathfinder implements Runnable{ * Data for a flow field to some set of destinations. * Concrete subclasses must specify a way to fetch costs and destinations. * */ - static abstract class Flowfield{ + public static abstract class Flowfield{ /** Refresh rate in milliseconds. Return any number <= 0 to disable. */ protected int refreshRate; /** Team this path is for. Set before using. */ @@ -459,7 +459,7 @@ public class Pathfinder implements Runnable{ protected PathCost cost = costTypes.get(costGround); /** costs of getting to a specific tile */ - int[][] weights; + public int[][] weights; /** search IDs of each position - the highest, most recent search is prioritized and overwritten */ int[][] searches; /** search frontier, these are Pos objects */ diff --git a/core/src/mindustry/ai/WaveSpawner.java b/core/src/mindustry/ai/WaveSpawner.java index 9cecf32ac1..9a1ffb9d1d 100644 --- a/core/src/mindustry/ai/WaveSpawner.java +++ b/core/src/mindustry/ai/WaveSpawner.java @@ -23,11 +23,21 @@ public class WaveSpawner{ private Seq spawns = new Seq<>(); private boolean spawning = false; private boolean any = false; + private Tile firstSpawn = null; public WaveSpawner(){ Events.on(WorldLoadEvent.class, e -> reset()); } + @Nullable + public Tile getFirstSpawn(){ + firstSpawn = null; + eachGroundSpawn((cx, cy) -> { + firstSpawn = world.tile(cx, cy); + }); + return firstSpawn; + } + public int countSpawns(){ return spawns.size; } @@ -47,7 +57,7 @@ public class WaveSpawner{ for(SpawnGroup group : state.rules.spawns){ if(group.type == null) continue; - int spawned = group.getUnitsSpawned(state.wave - 1); + int spawned = group.getSpawned(state.wave - 1); if(group.type.flying){ float spread = margin / 1.5f; @@ -89,6 +99,10 @@ public class WaveSpawner{ Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true)); } + public void eachGroundSpawn(Intc2 cons){ + eachGroundSpawn((x, y, shock) -> cons.get(world.toTile(x), world.toTile(y))); + } + private void eachGroundSpawn(SpawnConsumer cons){ for(Tile spawn : spawns){ cons.accept(spawn.worldx(), spawn.worldy(), true); diff --git a/core/src/mindustry/ai/types/FormationAI.java b/core/src/mindustry/ai/types/FormationAI.java index cbe12a565e..d774719cab 100644 --- a/core/src/mindustry/ai/types/FormationAI.java +++ b/core/src/mindustry/ai/types/FormationAI.java @@ -7,6 +7,7 @@ import mindustry.ai.formations.*; import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.type.*; +import mindustry.world.blocks.storage.CoreBlock.*; public class FormationAI extends AIController implements FormationMember{ public Unit leader; @@ -57,6 +58,30 @@ public class FormationAI extends AIController implements FormationMember{ }else{ unit.moveAt(realtarget.sub(unit).limit(type.speed)); } + + if(unit instanceof Minerc mine && leader instanceof Minerc com){ + if(mine.validMine(com.mineTile())){ + mine.mineTile(com.mineTile()); + + CoreBuild core = unit.team.core(); + + if(core != null && com.mineTile().drop() != null && unit.within(core, unit.type().range) && !unit.acceptsItem(com.mineTile().drop())){ + if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){ + Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core); + + unit.clearItem(); + } + } + }else{ + mine.mineTile(null); + } + + } + + if(unit instanceof Builderc build && leader instanceof Builderc com && com.activelyBuilding()){ + build.clearBuilding(); + build.addBuild(com.buildPlan()); + } } @Override diff --git a/core/src/mindustry/ai/types/MinerAI.java b/core/src/mindustry/ai/types/MinerAI.java index a03481768f..d19b8e2c0a 100644 --- a/core/src/mindustry/ai/types/MinerAI.java +++ b/core/src/mindustry/ai/types/MinerAI.java @@ -24,7 +24,9 @@ public class MinerAI extends AIController{ } if(mining){ - targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i)); + if(timer.get(timerTarget2, 60 * 4) || targetItem == null){ + targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i)); + } //core full of the target item, do nothing if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){ diff --git a/core/src/mindustry/ai/types/RepairAI.java b/core/src/mindustry/ai/types/RepairAI.java index 5f021ae828..cb60a3bfc9 100644 --- a/core/src/mindustry/ai/types/RepairAI.java +++ b/core/src/mindustry/ai/types/RepairAI.java @@ -23,7 +23,7 @@ public class RepairAI extends AIController{ } if(target != null){ - if(!target.within(unit, unit.type().range * 0.65f)){ + if(!target.within(unit, unit.type().range * 0.65f) && target instanceof Building){ moveTo(target, unit.type().range * 0.65f); } @@ -33,12 +33,14 @@ public class RepairAI extends AIController{ @Override protected void updateTargeting(){ - target = Units.findDamagedTile(unit.team, unit.x, unit.y); + Building target = Units.findDamagedTile(unit.team, unit.x, unit.y); if(target instanceof ConstructBuild) target = null; if(target == null){ super.updateTargeting(); + }else{ + this.target = target; } } diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 05ed572f7e..d18b011bea 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -1713,7 +1713,7 @@ public class Blocks implements ContentList{ despawnEffect = Fx.instBomb; trailSpacing = 20f; damage = 1350; - tileDamageMultiplier = 0.5f; + tileDamageMultiplier = 0.3f; speed = brange; hitShake = 6f; ammoMultiplier = 1f; diff --git a/core/src/mindustry/content/Bullets.java b/core/src/mindustry/content/Bullets.java index 221dc44e8d..e2d42c0ef0 100644 --- a/core/src/mindustry/content/Bullets.java +++ b/core/src/mindustry/content/Bullets.java @@ -510,7 +510,7 @@ public class Bullets implements ContentList{ speed = 4f; knockback = 1.3f; puddleSize = 8f; - damage = 6f; + damage = 5f; drag = 0.001f; ammoMultiplier = 2f; statusDuration = 60f * 4f; diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index df536552df..4d95b22820 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -5,10 +5,12 @@ import arc.math.*; import arc.util.*; import mindustry.annotations.Annotations.*; import mindustry.core.GameState.*; +import mindustry.ctype.*; import mindustry.game.EventType.*; import mindustry.game.*; import mindustry.game.Teams.*; import mindustry.gen.*; +import mindustry.maps.*; import mindustry.type.*; import mindustry.type.Weather.*; import mindustry.world.*; @@ -84,20 +86,41 @@ public class Logic implements ApplicationListener{ Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack)); //when loading a 'damaged' sector, propagate the damage - Events.on(WorldLoadEvent.class, e -> { + Events.on(SaveLoadEvent.class, e -> { if(state.isCampaign()){ - long seconds = state.rules.sector.getSecondsPassed(); CoreBuild core = state.rules.defaultTeam.core(); - //THE WAVES NEVER END - state.rules.waves = true; - //apply fractional damage based on how many turns have passed for this sector - //float turnsPassed = seconds / (turnDuration / 60f); + //how much wave time has passed + int wavesPassed = state.rules.sector.getWavesPassed(); - //TODO sector damage disabled for now - //if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){ - // SectorDamage.apply(turnsPassed / sectorDestructionTurns); - //} + //reset passed waves + state.rules.sector.setWavesPassed(0); + + //wave has passed, remove all enemies, they are assumed to be dead + if(wavesPassed > 0){ + Groups.unit.each(u -> { + if(u.team == state.rules.waveTeam){ + u.remove(); + } + }); + } + + if(wavesPassed > 0){ + //simulate wave counter moving forward + state.wave += wavesPassed; + state.wavetime = state.rules.waveSpacing; + } + + //reset damage display + state.rules.sector.setDamage(0f); + + //simulate damage if applicable + if(wavesPassed > 0){ + SectorDamage.applyCalculatedDamage(wavesPassed); + } + + //waves depend on attack status. + state.rules.waves = state.rules.sector.isUnderAttack(); //add resources based on turns passed if(state.rules.sector.save != null && core != null){ @@ -121,7 +144,9 @@ public class Logic implements ApplicationListener{ state.rules.sector.setSecondsPassed(0); } + }); + Events.on(WorldLoadEvent.class, e -> { //enable infinite ammo for wave team by default state.rules.waveTeam.rules().infiniteAmmo = true; @@ -129,6 +154,13 @@ public class Logic implements ApplicationListener{ Core.settings.manualSave(); }); + //sync research + Events.on(ResearchEvent.class, e -> { + if(net.server()){ + Call.researched(e.content); + } + }); + } /** Adds starting items, resets wave time, and sets state to playing. */ @@ -199,8 +231,6 @@ public class Logic implements ApplicationListener{ state.rules.waves = false; } - //TODO capturing is disabled - /* //if there's a "win" wave and no enemies are present, win automatically if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){ //the sector has been conquered - waves get disabled @@ -213,7 +243,7 @@ public class Logic implements ApplicationListener{ if(!headless){ control.saves.saveSector(state.rules.sector); } - }*/ + } }else{ if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){ state.gameOver = true; @@ -266,6 +296,15 @@ public class Logic implements ApplicationListener{ netClient.setQuiet(); } + //called when the remote server researches something + @Remote + public static void researched(Content content){ + if(!(content instanceof UnlockableContent u)) return; + + state.rules.researched.add(u.name); + ui.hudfrag.showUnlock(u); + } + @Override public void dispose(){ //save the settings before quitting diff --git a/core/src/mindustry/ctype/UnlockableContent.java b/core/src/mindustry/ctype/UnlockableContent.java index f6e6d0a2dc..21b72374ec 100644 --- a/core/src/mindustry/ctype/UnlockableContent.java +++ b/core/src/mindustry/ctype/UnlockableContent.java @@ -95,16 +95,17 @@ public abstract class UnlockableContent extends MappableContent{ } } - public final boolean unlocked(){ + public boolean unlocked(){ + if(net.client()) return state.rules.researched.contains(name); return unlocked || alwaysUnlocked; } /** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */ - public final boolean unlockedNow(){ - return unlocked || alwaysUnlocked || !state.isCampaign(); + public boolean unlockedNow(){ + return unlocked() || !state.isCampaign(); } - public final boolean locked(){ + public boolean locked(){ return !unlocked(); } } diff --git a/core/src/mindustry/editor/WaveGraph.java b/core/src/mindustry/editor/WaveGraph.java index 14b2416085..2436c62c2a 100644 --- a/core/src/mindustry/editor/WaveGraph.java +++ b/core/src/mindustry/editor/WaveGraph.java @@ -154,7 +154,7 @@ public class WaveGraph extends Table{ int sum = 0; for(SpawnGroup spawn : groups){ - int spawned = spawn.getUnitsSpawned(i); + int spawned = spawn.getSpawned(i); values[index][spawn.type.id] += spawned; if(spawned > 0){ used.add(spawn.type); diff --git a/core/src/mindustry/entities/bullet/BulletType.java b/core/src/mindustry/entities/bullet/BulletType.java index 0c3a605f1b..aafbb42504 100644 --- a/core/src/mindustry/entities/bullet/BulletType.java +++ b/core/src/mindustry/entities/bullet/BulletType.java @@ -137,6 +137,15 @@ public abstract class BulletType extends Content{ this(1f, 1f); } + /** @return estimated damage per shot. this can be very inaccurate. */ + public float estimateDPS(){ + float sum = damage + splashDamage*0.75f; + if(fragBullet != null && fragBullet != this){ + sum += fragBullet.estimateDPS() * fragBullets / 2f; + } + return sum; + } + /** Returns maximum distance the bullet this bullet type has can travel. */ public float range(){ return Math.max(speed * lifetime * (1f - drag), range); diff --git a/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java b/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java index a862671040..4c52105b26 100644 --- a/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java +++ b/core/src/mindustry/entities/bullet/ContinuousLaserBulletType.java @@ -44,6 +44,12 @@ public class ContinuousLaserBulletType extends BulletType{ this(0); } + @Override + public float estimateDPS(){ + //assume firing duration is about 100 by default, may not be accurate there's no way of knowing in this method + return damage * 100f / 5f; + } + @Override public float range(){ return length; diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index 1cf0f7e216..605e2d1d1e 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -88,6 +88,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I case rotation -> rotation; case health -> health; case maxHealth -> maxHealth; + case ammo -> state.rules.unitAmmo ? type.ammoCapacity : ammo; + case ammoCapacity -> type.ammoCapacity; case x -> x; case y -> y; case team -> team.id; diff --git a/core/src/mindustry/entities/comp/WeaponsComp.java b/core/src/mindustry/entities/comp/WeaponsComp.java index d891dbc1e4..dec0105c14 100644 --- a/core/src/mindustry/entities/comp/WeaponsComp.java +++ b/core/src/mindustry/entities/comp/WeaponsComp.java @@ -166,7 +166,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{ Weapon weapon = mount.weapon; float baseX = this.x, baseY = this.y; - boolean delay = weapon.firstShotDelay > 0f; + boolean delay = weapon.firstShotDelay + weapon.shotDelay > 0f; (delay ? weapon.chargeSound : weapon.shootSound).at(x, y, Mathf.random(0.8f, 1.0f)); diff --git a/core/src/mindustry/game/DefaultWaves.java b/core/src/mindustry/game/DefaultWaves.java index 0ce0d3a6a2..12d1d28dfe 100644 --- a/core/src/mindustry/game/DefaultWaves.java +++ b/core/src/mindustry/game/DefaultWaves.java @@ -290,7 +290,7 @@ public class DefaultWaves{ begin = f; end = f + next >= cap ? never : f + next; max = 14; - unitScaling = rand.random(1f, 2f); + unitScaling = rand.random(1f, 3f); shields = shieldAmount; shieldScaling = shieldsPerWave; spacing = space; @@ -329,7 +329,7 @@ public class DefaultWaves{ while(step <= cap){ createProgression.get(step); - step += (int)(rand.random(12, 25) * Mathf.lerp(1f, 0.4f, difficulty)); + step += (int)(rand.random(13, 25) * Mathf.lerp(1f, 0.5f, difficulty)); } int bossWave = (int)(rand.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty)); diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java index a101fe8592..329e69c92a 100644 --- a/core/src/mindustry/game/Rules.java +++ b/core/src/mindustry/game/Rules.java @@ -82,6 +82,8 @@ public class Rules{ public Seq weather = new Seq<>(1); /** Blocks that cannot be placed. */ public ObjectSet bannedBlocks = new ObjectSet<>(); + /** Unlocked content names. Only used in multiplayer when the campaign is enabled. */ + public ObjectSet researched = new ObjectSet<>(); /** Whether ambient lighting is enabled. */ public boolean lighting = false; /** Whether enemy lighting is visible. diff --git a/core/src/mindustry/game/SectorInfo.java b/core/src/mindustry/game/SectorInfo.java index b66dc5bf95..8e7f73a256 100644 --- a/core/src/mindustry/game/SectorInfo.java +++ b/core/src/mindustry/game/SectorInfo.java @@ -5,6 +5,7 @@ import arc.struct.*; import arc.util.*; import mindustry.content.*; import mindustry.ctype.*; +import mindustry.maps.*; import mindustry.type.*; import mindustry.world.*; import mindustry.world.blocks.storage.CoreBlock.*; @@ -38,6 +39,10 @@ public class SectorInfo{ public @Nullable Sector destination; /** Resources known to occur at this sector. */ public Seq resources = new Seq<>(); + + /** Special variables for simulation. */ + public float sumHealth, sumRps, sumDps, waveHealthBase, waveHealthSlope, waveDpsBase, waveDpsSlope; + /** Time spent at this sector. Do not use unless you know what you're doing. */ public transient float internalTimeSpent; @@ -99,6 +104,9 @@ public class SectorInfo{ //update sector's internal time spent counter state.rules.sector.setTimeSpent(internalTimeSpent); + state.rules.sector.setUnderAttack(state.rules.waves); + + SectorDamage.writeParameters(this); } /** Update averages of various stats, updates some special sector logic. diff --git a/core/src/mindustry/game/SpawnGroup.java b/core/src/mindustry/game/SpawnGroup.java index f9ae7c74d3..92ba6811ea 100644 --- a/core/src/mindustry/game/SpawnGroup.java +++ b/core/src/mindustry/game/SpawnGroup.java @@ -52,8 +52,8 @@ public class SpawnGroup implements Serializable{ //serialization use only } - /** Returns the amount of units spawned on a specific wave. */ - public int getUnitsSpawned(int wave){ + /** @return amount of units spawned on a specific wave. */ + public int getSpawned(int wave){ if(spacing == 0) spacing = 1; if(wave < begin || wave > end || (wave - begin) % spacing != 0){ return 0; @@ -61,6 +61,11 @@ public class SpawnGroup implements Serializable{ return Math.min(unitAmount + (int)(((wave - begin) / spacing) / unitScaling), max); } + /** @return amount of shields each unit has at a specific wave. */ + public float getShield(int wave){ + return Math.max(shields + shieldScaling*(wave - begin), 0); + } + /** * Creates a unit, and assigns correct values based on this group's data. * This method does not add() the unit. @@ -76,7 +81,7 @@ public class SpawnGroup implements Serializable{ unit.addItem(items.item, items.amount); } - unit.shield(Math.max(shields + shieldScaling*(wave - begin), 0)); + unit.shield = getShield(wave); return unit; } diff --git a/core/src/mindustry/game/Team.java b/core/src/mindustry/game/Team.java index 1234c7b2d7..cbed06f4e7 100644 --- a/core/src/mindustry/game/Team.java +++ b/core/src/mindustry/game/Team.java @@ -31,9 +31,9 @@ public class Team implements Comparable{ 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")), - green = new Team(3, "green", Color.valueOf("4dd98b")), - purple = new Team(4, "purple", Color.valueOf("9a4bdf")), - blue = new Team(5, "blue", Color.royal.cpy()); + green = new Team(3, "green", Color.valueOf("54d67d")), + purple = new Team(4, "purple", Color.valueOf("995bb0")), + blue = new Team(5, "blue", Color.valueOf("5a4deb")); static{ Mathf.rand.setSeed(8); diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java index 892b93b731..82b53ce604 100644 --- a/core/src/mindustry/game/Universe.java +++ b/core/src/mindustry/game/Universe.java @@ -6,6 +6,7 @@ import arc.struct.*; import arc.util.*; import mindustry.content.*; import mindustry.game.EventType.*; +import mindustry.maps.*; import mindustry.type.*; import mindustry.world.blocks.storage.*; @@ -55,7 +56,7 @@ public class Universe{ /** @return sectors attacked on the current planet, minus the ones that are being played on right now. */ public Seq getAttacked(Planet planet){ - return planet.sectors.select(s -> s.hasWaves() && s.hasBase() && !s.isBeingPlayed() && s.getSecondsPassed() > 1); + return planet.sectors.select(s -> s.isUnderAttack() && s.hasBase() && !s.isBeingPlayed() && s.getWavesPassed() > 0); } /** Update planet rotations, global time and relevant state. */ @@ -138,11 +139,23 @@ public class Universe{ //increment seconds passed for this sector by the time that just passed with this turn if(!sector.isBeingPlayed()){ - sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed); + int secPassed = sector.getSecondsPassed() + actuallyPassed; + + sector.setSecondsPassed(secPassed); + + boolean attacked = sector.isUnderAttack(); + + int wavesPassed = (int)(secPassed*60f / sector.save.meta.rules.waveSpacing); + float damage = attacked ? SectorDamage.getDamage(sector.save.meta.secinfo, sector.save.meta.rules.waveSpacing, sector.save.meta.wave, wavesPassed) : 0f; + + if(attacked){ + sector.setWavesPassed(wavesPassed); + } + + sector.setDamage(damage); - //TODO sector damage disabled for now //check if the sector has been attacked too many times... - /*if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){ + if(attacked && damage >= 0.999f){ //fire event for losing the sector Events.fire(new SectorLoseEvent(sector)); @@ -152,7 +165,14 @@ public class Universe{ //clear recieved sector.setExtraItems(new ItemSeq()); sector.save = null; - }*/ + sector.setDamage(0f); + }else if(attacked && wavesPassed > 0 && sector.save.meta.wave + wavesPassed >= sector.save.meta.rules.winWave && !sector.hasEnemyBase()){ + //autocapture the sector + sector.setUnderAttack(false); + + //fire the event + Events.fire(new SectorCaptureEvent(state.rules.sector)); + } } //export to another sector diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index 669cd65d53..bba4ffeb0a 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -450,6 +450,16 @@ public class TypeIO{ return color.set(read.i()); } + public static void writeContent(Writes write, Content cont){ + write.b(cont.getContentType().ordinal()); + write.s(cont.id); + } + + public static Content readContent(Reads read){ + byte id = read.b(); + return content.getByID(ContentType.all[id], read.s()); + } + public static void writeLiquid(Writes write, Liquid liquid){ write.s(liquid == null ? -1 : liquid.id); } diff --git a/core/src/mindustry/logic/LAccess.java b/core/src/mindustry/logic/LAccess.java index 5dc881a778..0d678554d1 100644 --- a/core/src/mindustry/logic/LAccess.java +++ b/core/src/mindustry/logic/LAccess.java @@ -15,6 +15,8 @@ public enum LAccess{ powerNetCapacity, powerNetIn, powerNetOut, + ammo, + ammoCapacity, health, maxHealth, heat, diff --git a/core/src/mindustry/logic/LAssembler.java b/core/src/mindustry/logic/LAssembler.java index 4f72d121aa..f353a8c38c 100644 --- a/core/src/mindustry/logic/LAssembler.java +++ b/core/src/mindustry/logic/LAssembler.java @@ -191,6 +191,7 @@ public class LAssembler{ try{ double value = Double.parseDouble(symbol); + if(Double.isNaN(value) || Double.isInfinite(value)) value = 0; //this creates a hidden const variable with the specified value String key = "___" + value; return putConst(key, value).id; diff --git a/core/src/mindustry/logic/LCanvas.java b/core/src/mindustry/logic/LCanvas.java index ed98bee8ff..e16aab6621 100644 --- a/core/src/mindustry/logic/LCanvas.java +++ b/core/src/mindustry/logic/LCanvas.java @@ -305,7 +305,7 @@ public class LCanvas extends Table{ statements.finishLayout(); } }); - }).growX(); + }).growX().height(38); row(); diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index a8c5de8a9d..fee780e145 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -110,12 +110,12 @@ public class LExecutor{ public double num(int index){ Var v = vars[index]; - return v.isobj ? v.objval != null ? 1 : 0 : v.numval; + return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : v.numval; } public float numf(int index){ Var v = vars[index]; - return v.isobj ? v.objval != null ? 1 : 0 : (float)v.numval; + return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : (float)v.numval; } public int numi(int index){ @@ -129,7 +129,7 @@ public class LExecutor{ public void setnum(int index, double value){ Var v = vars[index]; if(v.constant) return; - v.numval = value; + v.numval = Double.isNaN(value) || Double.isInfinite(value) ? 0 : 0; v.objval = null; v.isobj = false; } @@ -737,7 +737,7 @@ public class LExecutor{ v.objval = f.objval; v.isobj = true; }else{ - v.numval = f.numval; + v.numval = Double.isNaN(f.numval) || Double.isInfinite(f.numval) ? 0 : f.numval; v.isobj = false; } } diff --git a/core/src/mindustry/maps/SectorDamage.java b/core/src/mindustry/maps/SectorDamage.java index 5f4e267d74..b8afb42688 100644 --- a/core/src/mindustry/maps/SectorDamage.java +++ b/core/src/mindustry/maps/SectorDamage.java @@ -3,11 +3,18 @@ package mindustry.maps; import arc.math.*; import arc.math.geom.*; import arc.struct.*; +import arc.util.*; import mindustry.ai.*; import mindustry.content.*; import mindustry.entities.*; +import mindustry.entities.abilities.*; +import mindustry.game.*; import mindustry.gen.*; +import mindustry.logic.*; import mindustry.world.*; +import mindustry.world.blocks.defense.*; +import mindustry.world.blocks.defense.turrets.*; +import mindustry.world.blocks.defense.turrets.Turret.*; import mindustry.world.blocks.storage.*; import static mindustry.Vars.*; @@ -15,6 +22,264 @@ import static mindustry.Vars.*; public class SectorDamage{ //direct damage is for testing only private static final boolean direct = false, rubble = true; + private static final int maxWavesSimulated = 50; + + /** @return calculated capture progress of the enemy */ + public static float getDamage(SectorInfo info, float waveSpace, int wave, int wavesPassed){ + float health = info.sumHealth; + + //this approach is O(n), it simulates every wave passing. + //other approaches can assume all the waves come as one, but that's not as fair. + if(wavesPassed > 0){ + int waveBegin = wave; + int waveEnd = wave + wavesPassed; + + //do not simulate every single wave if there's too many + if(wavesPassed > maxWavesSimulated){ + waveBegin = waveEnd - maxWavesSimulated; + } + + for(int i = waveBegin; i <= waveEnd; i++){ + + float efficiency = health / info.sumHealth; + float dps = info.sumDps * efficiency; + float rps = info.sumRps * efficiency; + + float enemyDps = info.waveDpsBase + info.waveDpsSlope * (i); + float enemyHealth = info.waveHealthBase + info.waveHealthSlope * (i); + + //happens due to certain regressions + if(enemyHealth < 0 || enemyDps < 0) continue; + + //calculate time to destroy both sides + float timeDestroyEnemy = dps <= 0.0001f ? Float.POSITIVE_INFINITY : enemyHealth / dps; //if dps == 0, this is infinity + float timeDestroyBase = health / (enemyDps - rps); //if regen > enemyDps this is negative + + //sector is lost, enemy took too long. + if(timeDestroyEnemy > timeDestroyBase){ + health = 0f; + break; + } + + //otherwise, the enemy shoots for timeDestroyEnemy seconds, so calculate damage taken + float damageTaken = timeDestroyEnemy * (enemyDps - rps); + + //damage the base. + health -= damageTaken; + + //regen health after wave. + health = Math.min(health + rps / 60f * waveSpace, info.sumHealth); + } + } + + return 1f - Mathf.clamp(health / info.sumHealth); + } + + /** Applies wave damage based on sector parameters. */ + public static void applyCalculatedDamage(int wavesPassed){ + //calculate base damage fraction + float damage = getDamage(state.secinfo, state.rules.waveSpacing, state.wave, wavesPassed); + + //scaled damage has a power component to make it seem a little more realistic (as systems fail, enemy capturing gets easier and easier) + float scaled = Mathf.pow(damage, 1.5f); + + //apply damage to units + float unitDamage = damage * state.secinfo.sumHealth; + Tile spawn = spawner.getFirstSpawn(); + + //damage only units near the spawn point + if(spawn != null){ + Seq allies = new Seq<>(); + for(Unit ally : Groups.unit){ + if(ally.team == state.rules.defaultTeam && ally.within(spawn, state.rules.dropZoneRadius * 2.5f)){ + allies.add(ally); + } + } + + allies.sort(u -> u.dst2(spawn)); + + //damage units one by one, not uniformly + for(var u : allies){ + if(u.health < unitDamage){ + u.remove(); + unitDamage -= u.health; + }else{ + u.health -= unitDamage; + break; + } + } + } + + //finally apply scaled damage + apply(scaled); + } + + /** Calculates damage simulation parameters before a game is saved. */ + public static void writeParameters(SectorInfo info){ + Building core = state.rules.defaultTeam.core(); + Seq spawns = new Seq<>(); + spawner.eachGroundSpawn((x, y) -> spawns.add(world.tile(x, y))); + + if(core == null || spawns.isEmpty()) return; + + Tile start = spawns.first(); + + Time.mark(); + var field = pathfinder.getField(state.rules.waveTeam, Pathfinder.costGround, Pathfinder.fieldCore); + Seq path = new Seq<>(); + boolean found = false; + + if(field != null && field.weights != null){ + int[][] weights = field.weights; + int count = 0; + Tile current = start; + while(count < world.width() * world.height()){ + int minCost = Integer.MAX_VALUE; + int cx = current.x, cy = current.y; + for(Point2 p : Geometry.d4){ + int nx = cx + p.x, ny = cy + p.y; + + Tile other = world.tile(nx, ny); + if(other != null && weights[nx][ny] < minCost && weights[nx][ny] != -1){ + minCost = weights[nx][ny]; + current = other; + } + } + + path.add(current); + + if(current.build == core){ + found = true; + break; + } + + count ++; + } + } + + if(!found){ + path = Astar.pathfind(start, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid())); + } + + //create sparse tile array for fast range query + int sparseSkip = 6; + //TODO if this is slow, use a quadtree + Seq sparse = new Seq<>(path.size / sparseSkip + 1); + + for(int i = 0; i < path.size; i++){ + if(i % sparseSkip == 0){ + sparse.add(path.get(i)); + } + } + + //regen is in health per second + //dps is per second + float sumHealth = 0f, sumRps = 0f, sumDps = 0f; + float totalPathBuild = 0; + + //first, calculate the total health of blocks in the path + + for(Tile t : path){ + int radius = 2; + + //radius is square. + for(int dx = -radius; dx <= radius; dx++){ + for(int dy = -radius; dy <= radius; dy++){ + int wx = dx + t.x, wy = dy + t.y; + if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height()){ + Tile tile = world.rawTile(wx, wy); + + if(tile.build != null && tile.team() == state.rules.defaultTeam){ + //health is divided by block size, because multiblocks are counted multiple times. + sumHealth += tile.build.health / tile.block().size; + totalPathBuild += 1f / tile.block().size; + } + } + } + } + } + + float avgHealth = totalPathBuild <= 1 ? sumHealth : sumHealth / totalPathBuild; + + //block dps + regen + extra health/shields + for(Building build : Groups.build){ + float e = build.efficiency(); + if(e > 0.08f){ + if(build.team == state.rules.defaultTeam && build instanceof Ranged ranged && sparse.contains(t -> t.within(build, ranged.range()))){ + if(build.block instanceof Turret t && build instanceof TurretBuild b && b.hasAmmo()){ + sumDps += t.shots / t.reloadTime * 60f * b.peekAmmo().estimateDPS() * e; + } + + if(build.block instanceof MendProjector m){ + sumRps += m.healPercent / m.reload * avgHealth * 60f / 100f * e; + } + + if(build.block instanceof ForceProjector f){ + sumHealth += f.breakage * e; + sumRps += 1f * e; + } + } + } + } + + float curEnemyHealth = 0f, curEnemyDps = 0f; + + //unit regen + health + dps + for(Unit unit : Groups.unit){ + //skip player + if(unit.isPlayer()) continue; + + if(unit.team == state.rules.defaultTeam){ + sumHealth += unit.health + unit.shield; + sumDps += unit.type().dpsEstimate; + if(unit.abilities.find(a -> a instanceof HealFieldAbility) instanceof HealFieldAbility h){ + sumRps += h.amount / h.reload * 60f; + } + }else{ + curEnemyDps += unit.type().dpsEstimate; + curEnemyHealth += unit.health; + } + } + + //calculate DPS and health for the next few waves and store in list + var reg = new LinearRegression(); + Seq waveDps = new Seq<>(), waveHealth = new Seq<>(); + + for(int wave = state.wave, i = 0; i < 3; wave += (1 + i++)){ + float sumWaveDps = 0f, sumWaveHealth = 0f; + + //first wave has to take into account current dps + if(wave == state.wave){ + sumWaveDps += curEnemyDps; + sumWaveHealth += curEnemyHealth; + } + + for(SpawnGroup group : state.rules.spawns){ + int spawned = group.getSpawned(wave); + if(spawned <= 0) continue; + sumWaveHealth += spawned * (group.getShield(wave) + group.type.health); + sumWaveDps += spawned * group.type.dpsEstimate; + } + waveDps.add(new Vec2(wave, sumWaveDps)); + waveHealth.add(new Vec2(wave, sumWaveHealth)); + } + + //calculate linear regression of the wave data and store it + reg.calculate(waveHealth); + info.waveHealthBase = reg.intercept; + info.waveHealthSlope = reg.slope; + + reg.calculate(waveDps); + info.waveDpsBase = reg.intercept; + info.waveDpsSlope = reg.slope; + + info.sumHealth = sumHealth; + info.sumDps = sumDps; + info.sumRps = sumRps; + + //finally, find an equation to put it all together and produce a 0-1 number + //due to the way most defenses are structured, this number will likely need a ^4 power or so + } public static void apply(float fraction){ Tiles tiles = world.tiles; @@ -35,22 +300,62 @@ public class SectorDamage{ if(core != null && !frontier.isEmpty()){ for(Tile spawner : frontier){ //find path from spawn to core - //TODO this is broken Seq path = Astar.pathfind(spawner, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid())); - int amount = (int)(path.size * fraction); - for(int i = 0; i < amount; i++){ - Tile t = path.get(i); - Geometry.circle(t.x, t.y, tiles.width, tiles.height, 5, (cx, cy) -> { - Tile other = tiles.getn(cx, cy); - //just remove all the buildings in the way - as long as they're not cores! - if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){ - if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){ - Effect.rubble(other.build.x, other.build.y, other.block().size); - } + Seq removal = new Seq<>(); - other.remove(); + int radius = 3; + + //only penetrate a certain % by health, not by distance + float totalHealth = path.sumf(t -> { + float s = 0; + for(int dx = -radius; dx <= radius; dx++){ + for(int dy = -radius; dy <= radius; dy++){ + int wx = dx + t.x, wy = dy + t.y; + if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height() && Mathf.within(dx, dy, radius)){ + Tile other = world.rawTile(wx, wy); + s += other.team() == state.rules.defaultTeam ? other.build.health / other.block().size : 0f; + } } - }); + } + return s; + }); + float targetHealth = totalHealth * fraction; + float healthCount = 0; + + out: + for(int i = 0; i < path.size && healthCount < targetHealth; i++){ + Tile t = path.get(i); + + for(int dx = -radius; dx <= radius; dx++){ + for(int dy = -radius; dy <= radius; dy++){ + int wx = dx + t.x, wy = dy + t.y; + if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height() && Mathf.within(dx, dy, radius)){ + Tile other = world.rawTile(wx, wy); + + //just remove all the buildings in the way - as long as they're not cores + if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){ + if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){ + Effect.rubble(other.build.x, other.build.y, other.block().size); + } + + //since the whole block is removed, count the whole health + healthCount += other.build.health; + + removal.add(other.build); + + if(healthCount >= targetHealth){ + break out; + } + } + } + } + } + } + + for(Building r : removal){ + if(r.tile.build == r){ + r.tile.remove(); + } } } } diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index 12c33c7480..a596f3e7ae 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -629,7 +629,7 @@ public class Mods implements Loadable{ } //make sure the main class exists before loading it; if it doesn't just don't put it there - if(mainFile.exists()){ + if(mainFile.exists() && Core.settings.getBool("mod-" + meta.name.toLowerCase().replace(" ", "-") + "-enabled", true)){ //mobile versions don't support class mods if(ios){ throw new IllegalArgumentException("Java class mods are not supported on iOS."); diff --git a/core/src/mindustry/net/NetworkIO.java b/core/src/mindustry/net/NetworkIO.java index 3a32d5cb6b..acaf9f9640 100644 --- a/core/src/mindustry/net/NetworkIO.java +++ b/core/src/mindustry/net/NetworkIO.java @@ -3,7 +3,9 @@ package mindustry.net; import arc.*; import arc.util.*; import arc.util.io.*; +import mindustry.content.*; import mindustry.core.*; +import mindustry.ctype.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.io.*; @@ -21,6 +23,18 @@ public class NetworkIO{ public static void writeWorld(Player player, OutputStream os){ try(DataOutputStream stream = new DataOutputStream(os)){ + //write all researched content to rules if hosting + if(state.isCampaign()){ + state.rules.researched.clear(); + for(ContentType type : ContentType.all){ + for(Content c : content.getBy(type)){ + if(c instanceof UnlockableContent u && u.unlocked() && TechTree.get(u) != null){ + state.rules.researched.add(u.name); + } + } + } + } + stream.writeUTF(JsonIO.write(state.rules)); SaveIO.getSaveWriter().writeStringMap(stream, state.map.tags); @@ -44,6 +58,8 @@ public class NetworkIO{ state.rules = JsonIO.read(Rules.class, stream.readUTF()); state.map = new Map(SaveIO.getSaveWriter().readStringMap(stream)); + Log.info("READ RULES: @", state.rules.researched); + state.wave = stream.readInt(); state.wavetime = stream.readFloat(); diff --git a/core/src/mindustry/type/Sector.java b/core/src/mindustry/type/Sector.java index 4c1df28945..f692293309 100644 --- a/core/src/mindustry/type/Sector.java +++ b/core/src/mindustry/type/Sector.java @@ -103,9 +103,22 @@ public class Sector{ return save != null && !save.meta.rules.waves; } - /** @return whether waves are present - if true, any bases here will be attacked. */ - public boolean hasWaves(){ - return save != null && save.meta.rules.waves; + /** @return whether waves are present - if true, any bases here will be attacked. + * only applicable to sectors with active player bases. */ + public boolean isUnderAttack(){ + return hasBase() && Core.settings.getBool(key("under-attack"), true); + } + + public void setUnderAttack(boolean underAttack){ + Core.settings.put(key("under-attack"), underAttack); + } + + public void setWavesPassed(int waves){ + put("waves-passed", waves); + } + + public int getWavesPassed(){ + return Core.settings.getInt(key("waves-passed"), 0); } public boolean hasSave(){ @@ -238,6 +251,15 @@ public class Sector{ return Core.settings.getInt(key("spawn-position"), Point2.pack(world.width() / 2, world.height() / 2)); } + /** @return sector damage from enemy, 0 to 1 */ + public float getDamage(){ + return Core.settings.getFloat(key("damage"), 0f); + } + + public void setDamage(float damage){ + put("damage", damage); + } + /** @return time spent in this sector this turn in ticks. */ public float getTimeSpent(){ //return currently counting time spent if being played on diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 661e735fa7..68fdd6837e 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -79,6 +79,8 @@ public class UnitType extends UnlockableContent{ public int mineTier = -1; public float buildSpeed = 1f, mineSpeed = 1f; + /** This is a VERY ROUGH estimate of unit DPS. */ + public float dpsEstimate = -1; public float clipSize = -1; public boolean canDrown = true; public float engineOffset = 5f, engineSize = 2.5f; @@ -266,6 +268,17 @@ public class UnitType extends UnlockableContent{ ammoCapacity = Math.max(1, (int)(shotsPerSecond * targetSeconds)); } + + //calculate estimated DPS for one target based on weapons + if(dpsEstimate < 0){ + dpsEstimate = weapons.sumf(w -> (w.bullet.estimateDPS() / w.reload) * w.shots * 60f); + + //suicide enemy + if(weapons.contains(w -> w.bullet.killShooter)){ + //scale down DPS to be insignificant + dpsEstimate /= 100f; + } + } } @CallSuper @@ -436,7 +449,7 @@ public class UnitType extends UnlockableContent{ applyColor(unit); //draw back items - if(unit.hasItem() && unit.itemTime > 0.01f){ + if(unit.item() != null && unit.itemTime > 0.01f){ float size = (itemSize + Mathf.absin(Time.time(), 5f, 1f)) * unit.itemTime; Draw.mixcol(Pal.accent, Mathf.absin(Time.time(), 5f, 0.5f)); diff --git a/core/src/mindustry/ui/dialogs/HostDialog.java b/core/src/mindustry/ui/dialogs/HostDialog.java index 25300fc0b1..1282d47b7d 100644 --- a/core/src/mindustry/ui/dialogs/HostDialog.java +++ b/core/src/mindustry/ui/dialogs/HostDialog.java @@ -76,13 +76,15 @@ public class HostDialog extends BaseDialog{ platform.updateLobby(); }); })); + + if(Version.modifier.contains("beta") || Version.modifier.contains("alpha")){ + Core.settings.put("publichost", false); + platform.updateLobby(); + Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta")); + } } - if(Version.modifier.contains("beta")){ - Core.settings.put("publichost", false); - platform.updateLobby(); - Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta")); - } + }catch(IOException e){ ui.showException("@server.error", e); } diff --git a/core/src/mindustry/ui/dialogs/JoinDialog.java b/core/src/mindustry/ui/dialogs/JoinDialog.java index 1c80418708..aa4d9c22c8 100644 --- a/core/src/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/mindustry/ui/dialogs/JoinDialog.java @@ -367,8 +367,10 @@ public class JoinDialog extends BaseDialog{ local.row(); - TextButton button = local.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version)) - .width(w).pad(5f).get(); + TextButton button = local.button("", Styles.cleart, () -> { + Events.fire(new ClientPreConnectEvent(host)); + safeConnect(host.address, host.port, host.version); + }).width(w).pad(5f).get(); button.clearChildren(); buildServer(host, button); } @@ -379,8 +381,10 @@ public class JoinDialog extends BaseDialog{ global.row(); - TextButton button = global.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version)) - .width(w).pad(5f).get(); + TextButton button = global.button("", Styles.cleart, () -> { + Events.fire(new ClientPreConnectEvent(host)); + safeConnect(host.address, host.port, host.version); + }).width(w).pad(5f).get(); button.clearChildren(); buildServer(host, button); } diff --git a/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java b/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java index acb240d7c9..1ba7a0084b 100644 --- a/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java +++ b/core/src/mindustry/ui/dialogs/LaunchLoadoutDialog.java @@ -2,6 +2,7 @@ package mindustry.ui.dialogs; import arc.*; import arc.func.*; +import arc.input.*; import arc.scene.ui.*; import arc.scene.ui.layout.*; import arc.struct.*; @@ -33,7 +34,14 @@ public class LaunchLoadoutDialog extends BaseDialog{ cont.clear(); buttons.clear(); - addCloseButton(); + buttons.defaults().size(160f, 64f); + buttons.button("@back", Icon.left, this::hide); + + keyDown(key -> { + if(key == KeyCode.escape || key == KeyCode.back){ + Core.app.post(this::hide); + } + }); //updates sum requirements Runnable update = () -> { @@ -79,7 +87,7 @@ public class LaunchLoadoutDialog extends BaseDialog{ update.run(); rebuildItems.run(); }); - }); + }).width(204); buttons.button("@launch.text", Icon.ok, () -> { universe.updateLoadout(core, selected); diff --git a/core/src/mindustry/ui/dialogs/PausedDialog.java b/core/src/mindustry/ui/dialogs/PausedDialog.java index f09e2fe185..f06c09ce30 100644 --- a/core/src/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/mindustry/ui/dialogs/PausedDialog.java @@ -34,12 +34,11 @@ public class PausedDialog extends BaseDialog{ }); if(!mobile){ - //TODO localize - //TODO capturing is disabled, remove? - //cont.label(() -> state.getSector() == null ? "" : - //("[lightgray]Next turn in [accent]" + state.getSector().displayTimeRemaining() + - // (state.rules.winWave > 0 && !state.getSector().isCaptured() ? "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture" : ""))) - // .visible(() -> state.getSector() != null).colspan(2); + //TODO localize + move to other wave menu + cont.label(() -> state.getSector() == null || state.rules.winWave <= 0 || state.getSector().isCaptured() ? "" : + (state.rules.winWave > 0 && !state.getSector().isCaptured() ? + (state.wave >= state.rules.winWave ? "\n[lightgray]Defeat remaining enemies to capture" : "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture") : "")) + .visible(() -> state.getSector() != null).colspan(2); cont.row(); float dw = 220f; @@ -89,7 +88,7 @@ public class PausedDialog extends BaseDialog{ cont.buttonRow("@launchcore", Icon.up, () -> { hide(); ui.planet.showLaunch(state.getSector(), player.team().core()); - }).disabled(b -> player.team().core() == null); + }).disabled(b -> player.team().core() == null || net.client()); cont.row(); @@ -101,7 +100,11 @@ public class PausedDialog extends BaseDialog{ cont.row(); } - cont.buttonRow("@hostserver.mobile", Icon.host, ui.host::show).disabled(b -> net.active()); + if(state.isCampaign() && net.active()){ + cont.buttonRow("@research", Icon.tree, ui.research::show); + }else{ + cont.buttonRow("@hostserver.mobile", Icon.host, ui.host::show).disabled(b -> net.active()); + } cont.buttonRow("@quit", Icon.exit, this::showQuitConfirm).update(s -> { s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "@save.quit" : "@quit"); diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 4d24bbe8b8..d730d455d9 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -217,9 +217,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ public void renderProjections(){ if(hovered != null){ planets.drawPlane(hovered, () -> { - Draw.color(Color.white, Pal.accent, Mathf.absin(5f, 1f)); + Draw.color(hovered.isUnderAttack() ? Pal.remove : Color.white, Pal.accent, Mathf.absin(5f, 1f)); - TextureRegion icon = hovered.locked() && !canSelect(hovered) ? Icon.lock.getRegion() : null; + TextureRegion icon = hovered.locked() && !canSelect(hovered) ? Icon.lock.getRegion() : hovered.isUnderAttack() ? Icon.warning.getRegion() : null; if(icon != null){ Draw.rect(icon, 0, 0); @@ -355,20 +355,18 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ stable.add("[accent]" + (sector.preset == null ? sector.id : sector.preset.localizedName)).row(); stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row(); stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row(); - if(sector.hasWaves() || sector.hasEnemyBase()){ + if(sector.isUnderAttack() || sector.hasEnemyBase()){ stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row(); } - //TODO sector damage is disabled, remove when finalized - /* - if(sector.hasBase() && sector.hasWaves()){ + if(sector.isUnderAttack()){ //TODO localize when finalized //these mechanics are likely to change and as such are not added to the bundle stable.add("[scarlet]Under attack!"); stable.row(); - stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s)\nuntil destruction"); + stable.add("[accent]" + (int)(sector.getDamage() * 100) + "% damaged"); stable.row(); - }*/ + } if(sector.save != null){ stable.add("@sectors.resources").row(); diff --git a/core/src/mindustry/ui/dialogs/ResearchDialog.java b/core/src/mindustry/ui/dialogs/ResearchDialog.java index 9c5e6fd3ed..bb6841dcbb 100644 --- a/core/src/mindustry/ui/dialogs/ResearchDialog.java +++ b/core/src/mindustry/ui/dialogs/ResearchDialog.java @@ -164,13 +164,10 @@ public class ResearchDialog extends BaseDialog{ @Override public Dialog show(){ - Core.app.post(() -> { - if(net.client()){ - //TODO make this not display every time - //TODO rework this in the future - ui.showInfo("@campaign.multiplayer"); - } - }); + if(net.client()){ + ui.showInfo("@research.multiplayer"); + return null; + } return super.show(); } diff --git a/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java index 8dd9753fda..190c4109f2 100644 --- a/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -250,7 +250,6 @@ public class SettingsMenuDialog extends SettingsDialog{ if(!mobile){ game.checkPref("buildautopause", false); } - game.checkPref("mapcenter", true); if(steam){ game.sliderPref("playerlimit", 16, 2, 32, i -> { diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java index f56d507c4d..44c39795be 100644 --- a/core/src/mindustry/ui/fragments/HudFragment.java +++ b/core/src/mindustry/ui/fragments/HudFragment.java @@ -54,7 +54,7 @@ public class HudFragment extends Fragment{ outer: for(int i = state.wave - 1; i <= state.wave + max; i++){ for(SpawnGroup group : state.rules.spawns){ - if(group.effect == StatusEffects.boss && group.getUnitsSpawned(i) > 0){ + if(group.effect == StatusEffects.boss && group.getSpawned(i) > 0){ int diff = (i + 2) - state.wave; //increments at which to warn about incoming guardian diff --git a/core/src/mindustry/ui/fragments/MinimapFragment.java b/core/src/mindustry/ui/fragments/MinimapFragment.java index 43ad3163b0..5acf3bc6d3 100644 --- a/core/src/mindustry/ui/fragments/MinimapFragment.java +++ b/core/src/mindustry/ui/fragments/MinimapFragment.java @@ -111,12 +111,10 @@ public class MinimapFragment extends Fragment{ } public void toggle(){ - if(Core.settings.getBool("mapcenter")){ - float size = baseSize * zoom * world.width(); - float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width; - panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom; - pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom; - } + float size = baseSize * zoom * world.width(); + float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width; + panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom; + pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom; shown = !shown; } } diff --git a/core/src/mindustry/world/blocks/defense/ForceProjector.java b/core/src/mindustry/world/blocks/defense/ForceProjector.java index b508e90965..3b90533745 100644 --- a/core/src/mindustry/world/blocks/defense/ForceProjector.java +++ b/core/src/mindustry/world/blocks/defense/ForceProjector.java @@ -12,6 +12,7 @@ import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.logic.*; import mindustry.world.*; import mindustry.world.consumers.*; import mindustry.world.meta.*; @@ -80,11 +81,16 @@ public class ForceProjector extends Block{ Draw.color(); } - public class ForceBuild extends Building{ + public class ForceBuild extends Building implements Ranged{ public boolean broken = true; public float buildup, radscl, hit, warmup, phaseHeat; public ForceDraw drawer; + @Override + public float range(){ + return realRadius(); + } + @Override public void created(){ super.created(); diff --git a/core/src/mindustry/world/blocks/defense/MendProjector.java b/core/src/mindustry/world/blocks/defense/MendProjector.java index a2efa6e8b0..2bc50d6d7a 100644 --- a/core/src/mindustry/world/blocks/defense/MendProjector.java +++ b/core/src/mindustry/world/blocks/defense/MendProjector.java @@ -9,6 +9,7 @@ import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.logic.*; import mindustry.world.*; import mindustry.world.meta.*; @@ -55,11 +56,16 @@ public class MendProjector extends Block{ Drawf.dashCircle(x * tilesize + offset, y * tilesize + offset, range, Pal.accent); } - public class MendBuild extends Building{ + public class MendBuild extends Building implements Ranged{ float heat; float charge = Mathf.random(reload); float phaseHeat; + @Override + public float range(){ + return range; + } + @Override public void updateTile(){ heat = Mathf.lerpDelta(heat, consValid() || cheating() ? 1f : 0f, 0.08f); diff --git a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java index 56010c7c0f..ae3c10f3fd 100644 --- a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java +++ b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java @@ -8,6 +8,7 @@ import arc.util.io.*; import mindustry.annotations.Annotations.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.logic.*; import mindustry.world.*; import mindustry.world.meta.*; @@ -60,11 +61,16 @@ public class OverdriveProjector extends Block{ } } - public class OverdriveBuild extends Building{ + public class OverdriveBuild extends Building implements Ranged{ float heat; float charge = Mathf.random(reload); float phaseHeat; + @Override + public float range(){ + return range; + } + @Override public void drawLight(){ Drawf.light(team, x, y, 50f * efficiency(), baseColor, 0.7f * efficiency()); diff --git a/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java b/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java index ea94a670b9..521e6dc19b 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/ItemTurret.java @@ -19,7 +19,6 @@ import mindustry.world.meta.values.*; import static mindustry.Vars.*; public class ItemTurret extends Turret{ - public int maxAmmo = 30; public ObjectMap ammoTypes = new ObjectMap<>(); public ItemTurret(String name){ diff --git a/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java b/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java index e34d221d53..11d6213aa5 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/LiquidTurret.java @@ -8,6 +8,7 @@ import mindustry.entities.bullet.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; +import mindustry.world.*; import mindustry.world.consumers.*; import mindustry.world.meta.*; import mindustry.world.meta.values.*; @@ -83,7 +84,9 @@ public class LiquidTurret extends Turret{ int tr = (int)(range / tilesize); for(int x = -tr; x <= tr; x++){ for(int y = -tr; y <= tr; y++){ - if(Fires.has(x + tile.x, y + tile.y)){ + Tile other = world.tileWorld(x + tile.x, y + tile.y); + //do not extinguish fires on other team blocks + if(other != null && Fires.has(x + tile.x, y + tile.y) && (other.build == null || other.team() == team)){ target = Fires.get(x + tile.x, y + tile.y); return; } diff --git a/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java b/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java index f8b49195da..85e5243e2f 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java @@ -1,6 +1,7 @@ package mindustry.world.blocks.defense.turrets; import mindustry.entities.bullet.*; +import mindustry.logic.*; import mindustry.world.meta.*; public class PowerTurret extends Turret{ @@ -33,6 +34,15 @@ public class PowerTurret extends Turret{ super.updateTile(); } + @Override + public double sense(LAccess sensor){ + return switch(sensor){ + case ammo -> power.status; + case ammoCapacity -> 1; + default -> super.sense(sensor); + }; + } + @Override public BulletType useAmmo(){ //nothing used directly diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java index 411ee782ec..4199c4b3fb 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java @@ -41,6 +41,7 @@ public abstract class Turret extends Block{ public Effect ammoUseEffect = Fx.none; public Sound shootSound = Sounds.shoot; + public int maxAmmo = 30; public int ammoPerShot = 1; public float ammoEjectBack = 1f; public float range = 50f; @@ -192,6 +193,8 @@ public abstract class Turret extends Block{ @Override public double sense(LAccess sensor){ return switch(sensor){ + case ammo -> totalAmmo; + case ammoCapacity -> maxAmmo; case rotation -> rotation; case shootX -> targetPos.x; case shootY -> targetPos.y; diff --git a/core/src/mindustry/world/blocks/logic/SwitchBlock.java b/core/src/mindustry/world/blocks/logic/SwitchBlock.java index bca47a306a..d27cebb1b3 100644 --- a/core/src/mindustry/world/blocks/logic/SwitchBlock.java +++ b/core/src/mindustry/world/blocks/logic/SwitchBlock.java @@ -37,6 +37,11 @@ public class SwitchBlock extends Block{ } } + @Override + public Boolean config(){ + return enabled; + } + @Override public byte version(){ return 1; diff --git a/gradle.properties b/gradle.properties index 5a37b2f0ab..0cde4e83bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=dfcb9ab4b9f9bb977ed3cff4b8a16c22e076368a +archash=171cba8dda6deed931b998a0eb368d61f68ea38c diff --git a/tests/src/test/java/SectorTests.java b/tests/src/test/java/SectorTests.java index 4110670eed..6f07e77313 100644 --- a/tests/src/test/java/SectorTests.java +++ b/tests/src/test/java/SectorTests.java @@ -66,7 +66,7 @@ public class SectorTests{ outer: for(int i = 1; i <= 1000; i++){ for(SpawnGroup spawn : spawns){ - if(spawn.effect == StatusEffects.boss && spawn.getUnitsSpawned(i) > 0){ + if(spawn.effect == StatusEffects.boss && spawn.getSpawned(i) > 0){ bossWave = i; break outer; } @@ -84,7 +84,7 @@ public class SectorTests{ for(int i = 1; i <= bossWave; i++){ int total = 0; for(SpawnGroup spawn : spawns){ - total += spawn.getUnitsSpawned(i); + total += spawn.getSpawned(i); } assertNotEquals(0, total, "Sector " + zone + " has no spawned enemies at wave " + i);