Better wave timer / Sprite tweaks / Core wave spawns
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.6 KiB |
BIN
core/assets/maps/saltFlats.msav
Normal file
Before Width: | Height: | Size: 701 B After Width: | Height: | Size: 705 B |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 170 KiB |
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 214 KiB |
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 401 KiB |
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 283 KiB |
@ -2,6 +2,7 @@ package io.anuke.mindustry.ai;
|
|||||||
|
|
||||||
import io.anuke.arc.Events;
|
import io.anuke.arc.Events;
|
||||||
import io.anuke.arc.collection.Array;
|
import io.anuke.arc.collection.Array;
|
||||||
|
import io.anuke.arc.function.PositionConsumer;
|
||||||
import io.anuke.arc.math.Angles;
|
import io.anuke.arc.math.Angles;
|
||||||
import io.anuke.arc.math.Mathf;
|
import io.anuke.arc.math.Mathf;
|
||||||
import io.anuke.arc.util.Time;
|
import io.anuke.arc.util.Time;
|
||||||
@ -19,6 +20,8 @@ import io.anuke.mindustry.world.Tile;
|
|||||||
import static io.anuke.mindustry.Vars.*;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
public class WaveSpawner{
|
public class WaveSpawner{
|
||||||
|
private static final float margin = 40f, coreMargin = tilesize * 3; //how far away from the edge flying units spawn
|
||||||
|
|
||||||
private Array<FlyerSpawn> flySpawns = new Array<>();
|
private Array<FlyerSpawn> flySpawns = new Array<>();
|
||||||
private Array<Tile> groundSpawns = new Array<>();
|
private Array<Tile> groundSpawns = new Array<>();
|
||||||
private boolean spawning = false;
|
private boolean spawning = false;
|
||||||
@ -46,28 +49,20 @@ public class WaveSpawner{
|
|||||||
for(SpawnGroup group : state.rules.spawns){
|
for(SpawnGroup group : state.rules.spawns){
|
||||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
int spawned = group.getUnitsSpawned(state.wave - 1);
|
||||||
|
|
||||||
float spawnX, spawnY;
|
|
||||||
float spread;
|
|
||||||
|
|
||||||
if(group.type.isFlying){
|
if(group.type.isFlying){
|
||||||
for(FlyerSpawn spawn : flySpawns){
|
float spread = margin / 1.5f;
|
||||||
float margin = 40f; //how far away from the edge flying units spawn
|
|
||||||
float trns = (world.width() + world.height()) * tilesize;
|
|
||||||
spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(spawn.angle, trns), -margin, world.width() * tilesize + margin);
|
|
||||||
spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(spawn.angle, trns), -margin, world.height() * tilesize + margin);
|
|
||||||
spread = margin / 1.5f;
|
|
||||||
|
|
||||||
|
eachFlyerSpawn((spawnX, spawnY) -> {
|
||||||
for(int i = 0; i < spawned; i++){
|
for(int i = 0; i < spawned; i++){
|
||||||
BaseUnit unit = group.createUnit(waveTeam);
|
BaseUnit unit = group.createUnit(waveTeam);
|
||||||
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
|
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
|
||||||
unit.add();
|
unit.add();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}else{
|
}else{
|
||||||
for(Tile spawn : groundSpawns){
|
float spread = tilesize * 2;
|
||||||
spawnX = spawn.worldx();
|
|
||||||
spawnY = spawn.worldy();
|
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
|
||||||
spread = tilesize * 2;
|
|
||||||
|
|
||||||
for(int i = 0; i < spawned; i++){
|
for(int i = 0; i < spawned; i++){
|
||||||
Tmp.v1.rnd(spread);
|
Tmp.v1.rnd(spread);
|
||||||
@ -75,18 +70,49 @@ public class WaveSpawner{
|
|||||||
BaseUnit unit = group.createUnit(waveTeam);
|
BaseUnit unit = group.createUnit(waveTeam);
|
||||||
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
||||||
|
|
||||||
Time.run(Math.min(i * 5, 60 * 2), () -> shockwave(unit));
|
Time.run(Math.min(i * 5, 60 * 2), () -> spawnEffect(unit));
|
||||||
}
|
}
|
||||||
Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawn.x * tilesize, spawn.y * tilesize, state.rules.dropZoneRadius));
|
|
||||||
//would be interesting to see player structures survive this without hacks
|
if(doShockwave){
|
||||||
Time.run(40f, () -> Damage.damage(waveTeam, spawn.x * tilesize, spawn.y * tilesize, state.rules.dropZoneRadius, 99999999f, true));
|
Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawnX, spawnY, state.rules.dropZoneRadius));
|
||||||
}
|
Time.run(40f, () -> Damage.damage(waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Time.runTask(121f, () -> spawning = false);
|
Time.runTask(121f, () -> spawning = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void eachGroundSpawn(SpawnConsumer cons){
|
||||||
|
for(Tile spawn : groundSpawns){
|
||||||
|
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(state.rules.attackMode && state.teams.isActive(waveTeam) && !state.teams.get(defaultTeam).cores.isEmpty()){
|
||||||
|
Tile firstCore = state.teams.get(defaultTeam).cores.first();
|
||||||
|
for(Tile core : state.teams.get(waveTeam).cores){
|
||||||
|
Tmp.v1.set(firstCore).sub(core.worldx(), core.worldy()).limit(coreMargin + core.block().size*tilesize);
|
||||||
|
cons.accept(core.worldx() + Tmp.v1.x, core.worldy() + Tmp.v1.y, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void eachFlyerSpawn(PositionConsumer cons){
|
||||||
|
for(FlyerSpawn spawn : flySpawns){
|
||||||
|
float trns = (world.width() + world.height()) * tilesize;
|
||||||
|
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(spawn.angle, trns), -margin, world.width() * tilesize + margin);
|
||||||
|
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(spawn.angle, trns), -margin, world.height() * tilesize + margin);
|
||||||
|
cons.accept(spawnX, spawnY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(state.rules.attackMode && state.teams.isActive(waveTeam)){
|
||||||
|
for(Tile core : state.teams.get(waveTeam).cores){
|
||||||
|
cons.accept(core.worldx(), core.worldy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isSpawning(){
|
public boolean isSpawning(){
|
||||||
return spawning && !Net.client();
|
return spawning && !Net.client();
|
||||||
}
|
}
|
||||||
@ -114,7 +140,7 @@ public class WaveSpawner{
|
|||||||
flySpawns.add(fspawn);
|
flySpawns.add(fspawn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shockwave(BaseUnit unit){
|
private void spawnEffect(BaseUnit unit){
|
||||||
Effects.effect(Fx.unitSpawn, unit.x, unit.y, 0f, unit);
|
Effects.effect(Fx.unitSpawn, unit.x, unit.y, 0f, unit);
|
||||||
Time.run(30f, () -> {
|
Time.run(30f, () -> {
|
||||||
unit.add();
|
unit.add();
|
||||||
@ -122,11 +148,11 @@ public class WaveSpawner{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface SpawnConsumer{
|
||||||
|
void accept(float x, float y, boolean shockwave);
|
||||||
|
}
|
||||||
|
|
||||||
private class FlyerSpawn{
|
private class FlyerSpawn{
|
||||||
float angle;
|
float angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GroundSpawn{
|
|
||||||
int x, y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -80,20 +80,24 @@ public class Zones implements ContentList{
|
|||||||
}};
|
}};
|
||||||
}};
|
}};
|
||||||
|
|
||||||
//to be implemented as an attack map
|
saltFlats = new Zone("saltFlats", new MapGenerator("saltFlats")){{
|
||||||
/*
|
baseLaunchCost = ItemStack.with(Items.copper, -100);
|
||||||
saltFlats = new Zone("saltFlats", new DesertWastesGenerator(260, 260)){{
|
startingItems = ItemStack.list(Items.copper, 100);
|
||||||
startingItems = ItemStack.list(Items.copper, 200);
|
alwaysUnlocked = true;
|
||||||
conditionWave = 10;
|
conditionWave = 5;
|
||||||
zoneRequirements = ZoneRequirement.with(desertWastes, 25);
|
launchPeriod = 5;
|
||||||
blockRequirements = new Block[]{Blocks.router};
|
zoneRequirements = ZoneRequirement.with(desertWastes, 60);
|
||||||
resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.sand};
|
resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal};
|
||||||
rules = () -> new Rules(){{
|
rules = () -> new Rules(){{
|
||||||
waves = true;
|
waves = true;
|
||||||
waveTimer = true;
|
waveTimer = true;
|
||||||
waveSpacing = 60 * 60 * 1.5f;
|
attackMode = true;
|
||||||
|
waveSpacing = 60 * 60;
|
||||||
|
buildCostMultiplier = 0.5f;
|
||||||
|
unitBuildSpeedMultiplier = 0.5f;
|
||||||
|
enemyCheat = true;
|
||||||
}};
|
}};
|
||||||
}};*/
|
}};
|
||||||
|
|
||||||
craters = new Zone("craters", new MapGenerator("craters", 1).dist(0).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{
|
craters = new Zone("craters", new MapGenerator("craters", 1).dist(0).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{
|
||||||
startingItems = ItemStack.list(Items.copper, 200);
|
startingItems = ItemStack.list(Items.copper, 200);
|
||||||
|
@ -21,7 +21,8 @@ import io.anuke.mindustry.Vars;
|
|||||||
import io.anuke.mindustry.core.GameState.State;
|
import io.anuke.mindustry.core.GameState.State;
|
||||||
import io.anuke.mindustry.core.Platform;
|
import io.anuke.mindustry.core.Platform;
|
||||||
import io.anuke.mindustry.game.*;
|
import io.anuke.mindustry.game.*;
|
||||||
import io.anuke.mindustry.io.*;
|
import io.anuke.mindustry.io.JsonIO;
|
||||||
|
import io.anuke.mindustry.io.MapIO;
|
||||||
import io.anuke.mindustry.maps.Map;
|
import io.anuke.mindustry.maps.Map;
|
||||||
import io.anuke.mindustry.ui.dialogs.FileChooser;
|
import io.anuke.mindustry.ui.dialogs.FileChooser;
|
||||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||||
@ -267,7 +268,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
|||||||
}else{
|
}else{
|
||||||
Map map = world.maps.all().find(m -> m.name().equals(name));
|
Map map = world.maps.all().find(m -> m.name().equals(name));
|
||||||
if(map != null && !map.custom){
|
if(map != null && !map.custom){
|
||||||
ui.showError("$editor.save.overwrite");
|
handleSaveBuiltin(map);
|
||||||
}else{
|
}else{
|
||||||
world.maps.saveMap(editor.getTags());
|
world.maps.saveMap(editor.getTags());
|
||||||
ui.showInfoFade("$editor.saved");
|
ui.showInfoFade("$editor.saved");
|
||||||
@ -278,6 +279,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
|||||||
saved = true;
|
saved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Called when a built-in map save is attempted.*/
|
||||||
|
protected void handleSaveBuiltin(Map map){
|
||||||
|
ui.showError("$editor.save.overwrite");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Argument format:
|
* Argument format:
|
||||||
* 0) button name
|
* 0) button name
|
||||||
|
@ -18,7 +18,7 @@ import static io.anuke.mindustry.Vars.*;
|
|||||||
|
|
||||||
public class Maps implements Disposable{
|
public class Maps implements Disposable{
|
||||||
/** List of all built-in maps. Filenames only. */
|
/** List of all built-in maps. Filenames only. */
|
||||||
private static final String[] defaultMapNames = {"fortress", "labyrinth", "islands"};
|
private static String[] defaultMapNames = {"fortress", "labyrinth", "islands"};
|
||||||
/** All maps stored in an ordered array. */
|
/** All maps stored in an ordered array. */
|
||||||
private Array<Map> maps = new Array<>();
|
private Array<Map> maps = new Array<>();
|
||||||
/** Serializer for meta. */
|
/** Serializer for meta. */
|
||||||
|
@ -16,6 +16,7 @@ import io.anuke.mindustry.world.blocks.*;
|
|||||||
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
|
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
|
||||||
import io.anuke.mindustry.world.blocks.storage.StorageBlock;
|
import io.anuke.mindustry.world.blocks.storage.StorageBlock;
|
||||||
|
|
||||||
|
import static io.anuke.mindustry.Vars.defaultTeam;
|
||||||
import static io.anuke.mindustry.Vars.world;
|
import static io.anuke.mindustry.Vars.world;
|
||||||
|
|
||||||
public class MapGenerator extends Generator{
|
public class MapGenerator extends Generator{
|
||||||
@ -79,7 +80,7 @@ public class MapGenerator extends Generator{
|
|||||||
|
|
||||||
for(int x = 0; x < width; x++){
|
for(int x = 0; x < width; x++){
|
||||||
for(int y = 0; y < height; y++){
|
for(int y = 0; y < height; y++){
|
||||||
if(tiles[x][y].block() instanceof CoreBlock){
|
if(tiles[x][y].block() instanceof CoreBlock && tiles[x][y].getTeam() == defaultTeam){
|
||||||
players.add(new Point2(x, y));
|
players.add(new Point2(x, y));
|
||||||
tiles[x][y].setBlock(Blocks.air);
|
tiles[x][y].setBlock(Blocks.air);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package io.anuke.mindustry.ui;
|
|||||||
|
|
||||||
|
|
||||||
import io.anuke.arc.Core;
|
import io.anuke.arc.Core;
|
||||||
|
import io.anuke.arc.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A low-garbage way to format bundle strings.
|
* A low-garbage way to format bundle strings.
|
||||||
@ -10,15 +11,21 @@ public class IntFormat{
|
|||||||
private final StringBuilder builder = new StringBuilder();
|
private final StringBuilder builder = new StringBuilder();
|
||||||
private final String text;
|
private final String text;
|
||||||
private int lastValue = Integer.MIN_VALUE;
|
private int lastValue = Integer.MIN_VALUE;
|
||||||
|
private Function<Integer, String> converter = String::valueOf;
|
||||||
|
|
||||||
public IntFormat(String text){
|
public IntFormat(String text){
|
||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IntFormat(String text, Function<Integer, String> converter){
|
||||||
|
this.text = text;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
public CharSequence get(int value){
|
public CharSequence get(int value){
|
||||||
if(lastValue != value){
|
if(lastValue != value){
|
||||||
builder.setLength(0);
|
builder.setLength(0);
|
||||||
builder.append(Core.bundle.format(text, value));
|
builder.append(Core.bundle.format(text, converter.get(value)));
|
||||||
}
|
}
|
||||||
lastValue = value;
|
lastValue = value;
|
||||||
return builder;
|
return builder;
|
||||||
|
@ -534,11 +534,27 @@ public class HudFragment extends Fragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addWaveTable(TextButton table){
|
private void addWaveTable(TextButton table){
|
||||||
|
StringBuilder ibuild = new StringBuilder();
|
||||||
|
|
||||||
IntFormat wavef = new IntFormat("wave");
|
IntFormat wavef = new IntFormat("wave");
|
||||||
IntFormat enemyf = new IntFormat("wave.enemy");
|
IntFormat enemyf = new IntFormat("wave.enemy");
|
||||||
IntFormat enemiesf = new IntFormat("wave.enemies");
|
IntFormat enemiesf = new IntFormat("wave.enemies");
|
||||||
IntFormat waitingf = new IntFormat("wave.waiting");
|
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
|
||||||
|
ibuild.setLength(0);
|
||||||
|
int m = i/60;
|
||||||
|
int s = i % 60;
|
||||||
|
if(m <= 0){
|
||||||
|
ibuild.append(s);
|
||||||
|
}else{
|
||||||
|
ibuild.append(m);
|
||||||
|
ibuild.append(":");
|
||||||
|
if(s < 10){
|
||||||
|
ibuild.append("0");
|
||||||
|
}
|
||||||
|
ibuild.append(s);
|
||||||
|
}
|
||||||
|
return ibuild.toString();
|
||||||
|
});
|
||||||
|
|
||||||
table.clearChildren();
|
table.clearChildren();
|
||||||
table.touchable(Touchable.enabled);
|
table.touchable(Touchable.enabled);
|
||||||
|
@ -45,14 +45,14 @@ public class ZoneTests{
|
|||||||
if(tile.drop() != null){
|
if(tile.drop() != null){
|
||||||
resources.add(tile.drop());
|
resources.add(tile.drop());
|
||||||
}
|
}
|
||||||
if(tile.block() instanceof CoreBlock){
|
if(tile.block() instanceof CoreBlock && tile.getTeam() == defaultTeam){
|
||||||
hasSpawnPoint = true;
|
hasSpawnPoint = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(hasSpawnPoint, "Zone \"" + zone.name + "\" has no spawn points.");
|
assertTrue(hasSpawnPoint, "Zone \"" + zone.name + "\" has no spawn points.");
|
||||||
assertTrue(world.spawner.countSpawns() > 0, "Zone \"" + zone.name + "\" has no enemy spawn points: " + world.spawner.countSpawns());
|
assertTrue(world.spawner.countSpawns() > 0 || (zone.rules.get().attackMode && !state.teams.get(waveTeam).cores.isEmpty()), "Zone \"" + zone.name + "\" has no enemy spawn points: " + world.spawner.countSpawns());
|
||||||
|
|
||||||
for(Item item : resources){
|
for(Item item : resources){
|
||||||
assertTrue(Structs.contains(zone.resources, item), "Zone \"" + zone.name + "\" is missing item in resource list: \"" + item.name + "\"");
|
assertTrue(Structs.contains(zone.resources, item), "Zone \"" + zone.name + "\" is missing item in resource list: \"" + item.name + "\"");
|
||||||
|
@ -70,7 +70,7 @@ public class Generators{
|
|||||||
|
|
||||||
ImagePacker.generate("block-icons", () -> {
|
ImagePacker.generate("block-icons", () -> {
|
||||||
Image colors = new Image(content.blocks().size, 1);
|
Image colors = new Image(content.blocks().size, 1);
|
||||||
Color outlineColor = Color.valueOf("404049");
|
Color outlineColor = Color.valueOf("4d4e58");
|
||||||
|
|
||||||
for(Block block : content.blocks()){
|
for(Block block : content.blocks()){
|
||||||
TextureRegion[] regions = block.getGeneratedIcons();
|
TextureRegion[] regions = block.getGeneratedIcons();
|
||||||
|