From bdbc8ab6d22ec5766e6ce4947af1be75faf55f8b Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 16 May 2020 16:11:53 -0400 Subject: [PATCH] Many campaign changes --- core/src/mindustry/core/Control.java | 12 +++ core/src/mindustry/core/Logic.java | 31 +++++++ core/src/mindustry/core/World.java | 4 + core/src/mindustry/entities/Damage.java | 2 +- core/src/mindustry/game/Universe.java | 30 +++++-- core/src/mindustry/io/SaveVersion.java | 3 +- core/src/mindustry/maps/SectorDamage.java | 82 +++++++++++++++++++ .../maps/generators/BaseGenerator.java | 31 +++++++ .../maps/planet/TODOPlanetGenerator.java | 3 +- core/src/mindustry/type/Sector.java | 53 +++++++++++- .../mindustry/ui/dialogs/PlanetDialog.java | 11 ++- gradle.properties | 2 +- 12 files changed, 250 insertions(+), 14 deletions(-) create mode 100644 core/src/mindustry/maps/SectorDamage.java diff --git a/core/src/mindustry/core/Control.java b/core/src/mindustry/core/Control.java index 87670aae59..4bdd36626f 100644 --- a/core/src/mindustry/core/Control.java +++ b/core/src/mindustry/core/Control.java @@ -266,11 +266,23 @@ public class Control implements ApplicationListener, Loadable{ ui.planet.hide(); SaveSlot slot = sector.save; if(slot != null && !clearSectors){ + try{ net.reset(); slot.load(); state.rules.sector = sector; + + //if there is no base, simulate a new game and place the right loadout at the spawn position + if(state.rules.defaultTeam.cores().isEmpty()){ + Tile spawn = world.tile(sector.getSpawnPosition()); + //TODO PLACE CORRECT LOADOUT + Schematics.placeLoadout(Loadouts.advancedShard, spawn.x, spawn.y); + + Events.fire(Trigger.newGame); + } + state.set(State.playing); + }catch(SaveException e){ Log.err(e); sector.save = null; diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index b63e88e61a..9b01512338 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -11,6 +11,7 @@ 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.*; @@ -94,6 +95,22 @@ public class Logic implements ApplicationListener{ }); Events.on(LaunchItemEvent.class, e -> state.stats.handleItemExport(e.stack)); + + //when loading a 'damaged' sector, propagate the damage + Events.on(WorldLoadEvent.class, e -> { + if(state.isCampaign() && state.rules.sector.hasWaves() && state.rules.sector.getTurnsPassed() > 0){ + SectorDamage.apply(state.rules.sector.getTurnsPassed()); + state.rules.sector.setTurnsPassed(0); + } + }); + + //TODO this should be in the same place as launch handling code + Events.on(GameOverEvent.class, e -> { + //simulate a turn on a normal non-launch gameover + if(state.isCampaign() && !state.launched){ + universe.runTurn(); + } + }); } /** Handles the event of content being used by either the player or some block. */ @@ -204,6 +221,7 @@ public class Logic implements ApplicationListener{ ui.hudfrag.showLaunch(); } + //TODO core launch effect for(Tilec tile : state.teams.playerCores()){ Fx.launch.at(tile); } @@ -213,6 +231,9 @@ public class Logic implements ApplicationListener{ //state.getSector().setLaunched(); } + Sector sector = state.rules.sector; + + //TODO what about containers, do they get launched too? Time.runTask(30f, () -> { for(Tilec entity : state.teams.playerCores()){ for(Item item : content.items()){ @@ -223,6 +244,16 @@ public class Logic implements ApplicationListener{ } state.launched = true; state.gameOver = true; + + //save over the data w/o the cores + sector.save.save(); + //TODO mark sector as not containing any cores + + //run a turn, since launching takes up a turn + universe.runTurn(); + //TODO needs extra damage to prevent player from landing immediately afterwards + sector.setTurnsPassed(sector.getTurnsPassed() + 1); + Events.fire(new LaunchEvent()); //manually fire game over event now Events.fire(new GameOverEvent(state.rules.defaultTeam)); diff --git a/core/src/mindustry/core/World.java b/core/src/mindustry/core/World.java index 2ed26d6dde..1e9ba226ad 100644 --- a/core/src/mindustry/core/World.java +++ b/core/src/mindustry/core/World.java @@ -215,6 +215,10 @@ public class World{ int size = sector.getSize(); loadGenerator(size, size, tiles -> sector.planet.generator.generate(tiles, sector)); + + if(state.rules.defaultTeam.core() != null){ + sector.setSpawnPosition(state.rules.defaultTeam.core().pos()); + } } private void setSectorRules(Sector sector){ diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index e6066023fb..89a7cfad0d 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -34,7 +34,7 @@ public class Damage{ } for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){ - Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, -1f, Mathf.random(360f), 1, 1)); + Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1)); } int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30); diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java index 7742e53530..6fbb442f2a 100644 --- a/core/src/mindustry/game/Universe.java +++ b/core/src/mindustry/game/Universe.java @@ -55,9 +55,7 @@ public class Universe{ turnCounter += Time.delta(); if(turnCounter >= turnDuration){ - turn ++; - turnCounter = 0; - onTurn(); + runTurn(); } if(state.hasSector()){ @@ -78,7 +76,7 @@ public class Universe{ for(Sector sector : planet.sectors){ //ignore the current sector if the player is in it right now - if(sector.hasSave() && (state.isMenu() || sector != state.rules.sector)){ + if(sector.hasSave() && !sector.isBeingPlayed()){ SaveMeta meta = sector.save.meta; for(Entry entry : meta.exportRates){ @@ -94,8 +92,28 @@ public class Universe{ return exports; } - private void onTurn(){ - //TODO run waves on hostile sectors, damage them + public void runTurns(int amount){ + for(int i = 0; i < amount; i++){ + runTurn(); + } + } + + /** Runs a turn once. Resets turn counter. */ + public void runTurn(){ + turn ++; + turnCounter = 0; + + //TODO EVENTS + + //increment turns passed for sectors with waves + //TODO a turn passing may break the core; detect this, send an event and mark the sector as having no base! + for(Planet planet : content.planets()){ + for(Sector sector : planet.sectors){ + if(!sector.isBeingPlayed() && sector.hasSave() && sector.hasWaves()){ + sector.setTurnsPassed(sector.getTurnsPassed() + 1); + } + } + } //calculate passive item generation int[] exports = getTotalExports(); diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java index ff20c77a6e..0635b12ab6 100644 --- a/core/src/mindustry/io/SaveVersion.java +++ b/core/src/mindustry/io/SaveVersion.java @@ -83,7 +83,8 @@ public abstract class SaveVersion extends SaveFileReader{ "width", world.width(), "height", world.height(), "viewpos", Tmp.v1.set(player == null ? Vec2.ZERO : player).toString(), - "controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name + "controlledType", headless || control.input.controlledType == null ? "null" : control.input.controlledType.name, + "nocores", state.rules.defaultTeam.cores().isEmpty() ).merge(tags)); } diff --git a/core/src/mindustry/maps/SectorDamage.java b/core/src/mindustry/maps/SectorDamage.java new file mode 100644 index 0000000000..4ba3201f53 --- /dev/null +++ b/core/src/mindustry/maps/SectorDamage.java @@ -0,0 +1,82 @@ +package mindustry.maps; + +import arc.math.*; +import arc.math.geom.*; +import arc.struct.*; +import mindustry.content.*; +import mindustry.entities.*; +import mindustry.world.*; +import mindustry.world.blocks.storage.*; + +import static mindustry.Vars.*; + +public class SectorDamage{ + //direct damage is for testing only + private static final boolean direct = false; + + //TODO amount of damage could be related to wave spacing + public static void apply(float turns){ + Tiles tiles = world.tiles; + + Queue frontier = new Queue<>(); + float[][] values = new float[tiles.width][tiles.height]; + float damage = turns*50; + + //phase one: find all spawnpoints + for(Tile tile : tiles){ + if((tile.block() instanceof CoreBlock && tile.team() == state.rules.waveTeam) || tile.overlay() == Blocks.spawn){ + frontier.add(tile); + values[tile.x][tile.y] = damage; + } + } + + float falloff = (damage) / (Math.max(tiles.width, tiles.height) * Mathf.sqrt2); + int peak = 0; + + //phase two: propagate the damage + while(!frontier.isEmpty()){ + peak = Math.max(peak, frontier.size); + Tile tile = frontier.removeFirst(); + float currDamage = values[tile.x][tile.y] - falloff; + + for(int i = 0; i < 4; i++){ + int cx = tile.x + Geometry.d4x[i], cy = tile.y + Geometry.d4y[i]; + + //propagate to new tiles + if(tiles.in(cx, cy) && values[cx][cy] < currDamage){ + Tile other = tiles.getn(cx, cy); + float resultDamage = currDamage; + + //damage the tile if it's not friendly + if(other.entity != null && other.team() != state.rules.waveTeam){ + resultDamage -= other.entity.health(); + + if(direct){ + other.entity.damage(currDamage); + }else{ //indirect damage happens at game load time + other.entity.health(other.entity.health() - currDamage); + + //remove the block when destroyed + if(other.entity.health() < 0){ + if(!other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){ + Effects.rubble(other.entity.x(), other.entity.y(), other.block().size); + } + + other.remove(); + } + } + + }else if(other.solid() && !other.synthetic()){ //skip damage propagation through solid blocks + continue; + } + + if(resultDamage > 0 && values[cx][cy] < resultDamage){ + frontier.addLast(other); + values[cx][cy] = resultDamage; + } + } + } + } + + } +} diff --git a/core/src/mindustry/maps/generators/BaseGenerator.java b/core/src/mindustry/maps/generators/BaseGenerator.java index 278167baf4..78a90d4207 100644 --- a/core/src/mindustry/maps/generators/BaseGenerator.java +++ b/core/src/mindustry/maps/generators/BaseGenerator.java @@ -1,5 +1,6 @@ package mindustry.maps.generators; +import arc.math.geom.*; import arc.struct.*; import mindustry.content.*; import mindustry.game.*; @@ -9,10 +10,40 @@ import mindustry.world.*; public class BaseGenerator{ public void generate(Tiles tiles, Array cores, Tile spawn, Team team, Sector sector){ + //algorithm idea: make it spawn prefab turret setups and route resources to them with conveyors from chunks of ore drill setups + + GridBits used = new GridBits(tiles.width, tiles.height); + Queue frontier = new Queue<>(); + for(Tile tile : cores){ + frontier.add(tile); + } + + int count = 2000; + int total = 0; + + while(total++ < count){ + Tile tile = frontier.removeFirst(); + for(int i = 0; i < 4; i++){ + int cx = tile.x + Geometry.d4x[i], cy = tile.y + Geometry.d4y[i]; + if(tiles.in(cx, cy) && !used.get(cx, cy)){ + Tile other = tiles.getn(cx, cy); + + if(!other.solid()){ + frontier.addLast(other); + } + used.set(cx, cy); + } + } + } + + for(Tile tile : frontier){ + tile.setBlock(Blocks.copperWall, team); + } for(Tile tile : cores){ tile.clearOverlay(); tile.setBlock(Blocks.coreShard, team); + } diff --git a/core/src/mindustry/maps/planet/TODOPlanetGenerator.java b/core/src/mindustry/maps/planet/TODOPlanetGenerator.java index ac5f1cb158..3528b2380a 100644 --- a/core/src/mindustry/maps/planet/TODOPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/TODOPlanetGenerator.java @@ -260,9 +260,10 @@ public class TODOPlanetGenerator extends PlanetGenerator{ } }); + //TODO PLACE CORRECT LOADOUT Schematics.placeLoadout(Loadouts.advancedShard, spawn.x, spawn.y); - if(sector.hostility > 0.02f){ + if(sector.hasEnemyBase()){ new BaseGenerator().generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector); state.rules.attackMode = true; diff --git a/core/src/mindustry/type/Sector.java b/core/src/mindustry/type/Sector.java index 531fa5e213..4e2cfa5693 100644 --- a/core/src/mindustry/type/Sector.java +++ b/core/src/mindustry/type/Sector.java @@ -1,5 +1,6 @@ package mindustry.type; +import arc.*; import arc.math.geom.*; import arc.util.ArcAnnotate.*; import arc.util.*; @@ -11,6 +12,8 @@ import mindustry.game.Saves.*; import mindustry.graphics.g3d.PlanetGrid.*; import mindustry.world.*; +import static mindustry.Vars.world; + /** A small section of a planet. */ public class Sector{ public final SectorRect rect; @@ -24,7 +27,7 @@ public class Sector{ public @Nullable SaveSlot save; public boolean unlocked; - /** */ + /** Sector enemy hostility from 0 to 1 */ public float hostility; //TODO implement a dynamic (?) launch period @@ -39,13 +42,33 @@ public class Sector{ this.data = data; } + /** @return whether the player has a base here. */ + public boolean hasBase(){ + return save != null && !save.meta.tags.getBool("nocores"); + } + + /** @return whether the enemy has a generated base here. */ + public boolean hasEnemyBase(){ + return hostility >= 0.02f && (save == null || save.meta.rules.waves); + } + + public boolean isBeingPlayed(){ + //after the launch dialog, a sector is no longer considered being played + return Vars.state.isGame() && Vars.state.rules.sector == this && !Vars.state.launched && !Vars.state.gameOver; + } + + /** @return whether waves are present, e.g. any bases here will be attacked. */ + public boolean hasWaves(){ + return save != null && save.meta.rules.waves; + } + public boolean hasSave(){ return save != null; } public void generate(){ //TODO use simplex and a seed - hostility = Math.max(Noise.snoise3(tile.v.x, tile.v.y, tile.v.z, 1f, 0.5f), 0); + hostility = Math.max(Noise.snoise3(tile.v.x, tile.v.y, tile.v.z, 0.5f, 0.5f), 0); } public boolean locked(){ @@ -74,6 +97,32 @@ public class Sector{ return false; } + public void setSpawnPosition(int position){ + put("spawn-position", position); + } + + /** Only valid after this sector has been landed on once. */ + //TODO move to sector data? + public int getSpawnPosition(){ + return Core.settings.getInt(key("spawn-position"), Point2.pack(world.width() / 2, world.height() / 2)); + } + + public void setTurnsPassed(int number){ + put("turns-passed", number); + } + + public int getTurnsPassed(){ + return Core.settings.getInt(key("turns-passed")); + } + + private String key(String key){ + return planet.name + "-s-" + id + "-" + key; + } + + private void put(String key, Object value){ + Core.settings.put(key(key), value); + } + /** Projects this sector onto a 4-corner square for use in map gen. * Allocates a new object. Do not call in the main loop. */ private SectorRect makeRect(){ diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 7d37387d8e..77ab26a621 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -281,9 +281,9 @@ public class PlanetDialog extends FloatingDialog{ if(selectAlpha > 0.01f){ float stroke = 0.026f; - if(sec.save != null){ + if(sec.hasBase()){ drawSelection(sec, Tmp.c1.set(Team.sharded.color).a(selectAlpha), stroke, -0.01f); - }else if(sec.hostility >= 0.02f){ + }else if(sec.hasEnemyBase()){ drawSelection(sec, Tmp.c1.set(Team.crux.color).a(selectAlpha), stroke, -0.02f); } } @@ -380,6 +380,13 @@ public class PlanetDialog extends FloatingDialog{ }); } + //disaply how many turns this sector has been attacked + if(selected.getTurnsPassed() > 0){ + stable.row(); + + stable.add("[scarlet]" + Iconc.warning + " " + selected.getTurnsPassed() + "x attacks"); + } + stable.row(); stable.button("Launch", Styles.transt, () -> { diff --git a/gradle.properties b/gradle.properties index fe545b57db..bef09563e2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=811e22ca49b8e5ad1cffb36a74199c1bec73ee87 +archash=4f93ba3aec02c10cb016f6fb2f5a80d5d7ba49e1