From 45da5787560e95445ea1c151734369df3833b8eb Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 20 Jun 2018 20:23:43 -0400 Subject: [PATCH] Implemented dynamic ground unit spawning --- .../src/io/anuke/mindustry/ai/Pathfinder.java | 8 +- .../io/anuke/mindustry/ai/WaveSpawner.java | 110 ++++++++++++++++-- .../io/anuke/mindustry/content/UnitTypes.java | 2 +- .../src/io/anuke/mindustry/entities/Unit.java | 2 +- .../mindustry/entities/units/GroundUnit.java | 3 +- .../mindustry/ui/fragments/DebugFragment.java | 3 + .../anuke/mindustry/world/mapgen/ProcGen.java | 10 +- 7 files changed, 121 insertions(+), 17 deletions(-) diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java index b9f20de87f..fc03f07839 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfinder.java +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -79,8 +79,12 @@ public class Pathfinder { return paths[Team.red.ordinal()].weights[x][y]; } + public float getValueforTeam(Team team, int x, int y){ + return paths == null ? 0 : paths[team.ordinal()].weights[x][y]; + } + private boolean passable(Tile tile, Team team){ - return (tile.getWallID() == 0 && !(tile.floor().isLiquid && (tile.floor().damageTaken > 0 || tile.floor().drownTime > 0))) + return (tile.getWallID() == 0 && tile.cliffs == 0 && !(tile.floor().isLiquid && (tile.floor().damageTaken > 0 || tile.floor().drownTime > 0))) || (tile.breakable() && (tile.getTeam() != team)) || !tile.solid(); } @@ -173,6 +177,8 @@ public class Pathfinder { createFor(data.team); } + state.spawner.checkAllQuadrants(); + Log.info("Elapsed calculation time: {0}", Timers.elapsed()); } diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java index 80c709ae4c..e8e39aad34 100644 --- a/core/src/io/anuke/mindustry/ai/WaveSpawner.java +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -1,19 +1,25 @@ package io.anuke.mindustry.ai; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Bits; +import com.badlogic.gdx.utils.IntArray; import io.anuke.mindustry.content.AmmoTypes; import io.anuke.mindustry.content.UnitTypes; import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.entities.units.Squad; import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.world.Tile; import io.anuke.ucore.core.Events; import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; public class WaveSpawner { - private static final int quadsize = 15; + private static final int quadsize = 4; + + private Bits quadrants; + private IntArray tmpArray = new IntArray(); private Array flySpawns = new Array<>(); private Array groundSpawns = new Array<>(); @@ -23,24 +29,45 @@ public class WaveSpawner { } public void spawnEnemies(){ - int spawned = 10; - int groundGroups = Math.min(1 + state.wave / 20, 4); - int flyGroups = Math.min(1 + state.wave / 20, 4); + int spawned = Math.min(state.wave, 6); + int groundGroups = 1 + state.wave / 20; + int flyGroups = 1 + state.wave / 20; //add extra groups if necessary for (int i = 0; i < groundGroups - groundSpawns.size; i++) { GroundSpawn spawn = new GroundSpawn(); + findLocation(spawn); + groundSpawns.add(spawn); } for (int i = 0; i < flyGroups - flySpawns.size; i++) { FlyerSpawn spawn = new FlyerSpawn(); - spawn.angle = Mathf.random(360f); - + findLocation(spawn); flySpawns.add(spawn); } - for(GroundSpawn spawn : groundSpawns){ + if(state.wave % 20 == 0){ + for(FlyerSpawn spawn : flySpawns) findLocation(spawn); + for(GroundSpawn spawn : groundSpawns) findLocation(spawn); + } + for(GroundSpawn spawn : groundSpawns){ + checkQuadrant(spawn.x, spawn.y); + if(!getQuad(spawn.x, spawn.y)){ + findLocation(spawn); + } + + Squad squad = new Squad(); + + for(int i = 0; i < spawned; i ++){ + BaseUnit unit = UnitTypes.scout.create(Team.red); + unit.inventory.addAmmo(AmmoTypes.bulletIron); + unit.setWave(); + unit.setSquad(squad); + unit.set(spawn.x * quadsize * tilesize + quadsize * tilesize/2f + Mathf.range(quadsize*tilesize/3f), + spawn.y * quadsize * tilesize + quadsize * tilesize/2f + Mathf.range(quadsize*tilesize/3)); + unit.add(); + } } for(FlyerSpawn spawn : flySpawns){ @@ -62,11 +89,26 @@ public class WaveSpawner { } } - public void calculateSpawn(){ + public void checkAllQuadrants(){ + for(int x = 0; x < quadWidth(); x ++){ + for(int y = 0; y < quadHeight(); y ++){ + checkQuadrant(x, y); + } + } + } + + private void checkQuadrant(int quadx, int quady){ + setQuad(quadx, quady, true); - for(int x = 0; x < world.width(); x += quadsize){ - for(int y = 0; y < world.height(); y += quadsize){ - //TODO quadrant operations, etc + outer: + for (int x = quadx * quadsize; x < world.width() && x < (quadx + 1)*quadsize; x++) { + for (int y = quady * quadsize; y < world.height() && y < (quady + 1)*quadsize; y++) { + Tile tile = world.tile(x, y); + + if(tile.solid() || world.pathfinder().getValueforTeam(Team.red, x, y) == Float.MAX_VALUE){ + setQuad(quadx, quady, false); + break outer; + } } } } @@ -74,9 +116,53 @@ public class WaveSpawner { private void reset(){ flySpawns.clear(); groundSpawns.clear(); + quadrants = new Bits(quadWidth() * quadHeight()); + } + + private boolean getQuad(int quadx, int quady){ + return quadrants.get(quadx + quady * quadWidth()); + } + + private void setQuad(int quadx, int quady, boolean valid){ + if(valid){ + quadrants.set(quadx + quady * quadWidth()); + }else{ + quadrants.clear(quadx + quady * quadWidth()); + } + } + + private void findLocation(GroundSpawn spawn){ + spawn.x = -1; + spawn.y = -1; + + int shellWidth = quadWidth()*2 + quadHeight() * 2 * 6; + shellWidth = Math.min(quadWidth() * quadHeight() / 4, shellWidth); + + Mathf.traverseSpiral(quadWidth(), quadHeight(), Mathf.random(shellWidth), (x, y) -> { + if(getQuad(x, y)){ + spawn.x = x; + spawn.y = y; + return true; + } + + return false; + }); + } + + private void findLocation(FlyerSpawn spawn){ + spawn.angle = Mathf.random(360f); + } + + private int quadWidth(){ + return Mathf.ceil(world.width() / (float)quadsize); + } + + private int quadHeight(){ + return Mathf.ceil(world.height() / (float)quadsize); } private class FlyerSpawn{ + //square angle float angle; FlyerSpawn(){ @@ -85,6 +171,8 @@ public class WaveSpawner { } private class GroundSpawn{ + //quadrant spawn coordinates + int x, y; GroundSpawn(){ diff --git a/core/src/io/anuke/mindustry/content/UnitTypes.java b/core/src/io/anuke/mindustry/content/UnitTypes.java index e7a8af4dd4..c342734ee6 100644 --- a/core/src/io/anuke/mindustry/content/UnitTypes.java +++ b/core/src/io/anuke/mindustry/content/UnitTypes.java @@ -24,7 +24,7 @@ public class UnitTypes implements ContentList { scout = new UnitType("scout", team -> new Scout(scout, team)){{ maxVelocity = 1.1f; - speed = 0.1f; + speed = 0.2f; drag = 0.4f; range = 40f; setAmmo(AmmoTypes.bulletIron); diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index ef77f766c0..968b52eef6 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -251,7 +251,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ drownTime = Mathf.clamp(drownTime); if(drownTime >= 1f){ - damage(health + 1, false); + damage(health + 1); } float px = x, py = y; diff --git a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java index de7044d1a2..df577c613d 100644 --- a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java @@ -111,6 +111,7 @@ public abstract class GroundUnit extends BaseUnit { protected void moveToCore(){ Tile tile = world.tileWorld(x, y); + if(tile == null) return; Tile targetTile = world.pathfinder().getTargetTile(team, tile); if(tile == targetTile) return; @@ -150,7 +151,7 @@ public abstract class GroundUnit extends BaseUnit { } //TODO move toward resupply point - if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ + if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ state.set(attack); } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java index 5b9b281c75..d3973e5358 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/DebugFragment.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.ui.fragments; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; +import io.anuke.mindustry.content.AmmoTypes; import io.anuke.mindustry.content.UnitTypes; import io.anuke.mindustry.content.bullets.TurretBullets; import io.anuke.mindustry.entities.Player; @@ -79,6 +80,8 @@ public class DebugFragment implements Fragment { new button("spawng", () ->{ BaseUnit unit = UnitTypes.scout.create(player.getTeam()); unit.set(player.x, player.y); + unit.inventory.addAmmo(AmmoTypes.bulletIron); + unit.setWave(); unit.add(); }); row(); diff --git a/core/src/io/anuke/mindustry/world/mapgen/ProcGen.java b/core/src/io/anuke/mindustry/world/mapgen/ProcGen.java index 8f4d04813d..2245f40232 100644 --- a/core/src/io/anuke/mindustry/world/mapgen/ProcGen.java +++ b/core/src/io/anuke/mindustry/world/mapgen/ProcGen.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.world.mapgen; +import com.badlogic.gdx.math.Vector2; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.content.blocks.StorageBlocks; import io.anuke.mindustry.game.Team; @@ -30,11 +31,16 @@ public class ProcGen { double r = sim2.octaveNoise2D(1, 0.6, 1f/70, x, y); double elevation = sim.octaveNoise2D(3, 0.5, 1f/70, x, y) * 4 - 1.2; double edgeDist = Math.max(data.width()/2, data.height()/2) - Math.max(Math.abs(x - data.width()/2), Math.abs(y - data.height()/2)); + double dst = Vector2.dst(data.width()/2, data.height()/2, x, y); - double border = 10; + double border = 14; if(edgeDist < border){ - elevation += (border - edgeDist)/4.0; + elevation += (border - edgeDist)/6.0; + } + + if(dst < 20){ + elevation = 0; } if(r > 0.9){