1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-11 08:15:35 +03:00

Implemented random map load filters

This commit is contained in:
Anuken 2019-07-25 21:27:16 -04:00
parent 4a638cba79
commit c4ceb89240
14 changed files with 230 additions and 169 deletions

1
.gitignore vendored
View File

@ -21,6 +21,7 @@ logs/
/test_files/
/annotations/build/
/desktop-sdl/build/
desktop-sdl/build/
/android/assets/mindustry-maps/
/android/assets/mindustry-saves/
/core/assets/gifexport/

View File

@ -187,6 +187,7 @@ editor.author = Author:
editor.description = Description:
editor.waves = Waves:
editor.rules = Rules:
editor.generation = Generation:
editor.ingame = Edit In-Game
editor.newmap = New Map
waves.title = Waves

View File

@ -1,25 +1,26 @@
package io.anuke.mindustry.core;
import io.anuke.annotations.Annotations.Nullable;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.IntArray;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.ai.*;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.game.EventType.TileChangeEvent;
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.editor.MapGenerateDialog.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.maps.generators.Generator;
import io.anuke.mindustry.type.Zone;
import io.anuke.mindustry.maps.filters.*;
import io.anuke.mindustry.maps.filters.GenerateFilter.*;
import io.anuke.mindustry.maps.generators.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.BlockPart;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.*;
@ -172,12 +173,6 @@ public class World implements ApplicationListener{
generating = true;
}
/** Call to signal the beginning of loading the map with a custom set of tiles. */
public void beginMapLoad(Tile[][] tiles){
this.tiles = tiles;
generating = true;
}
/**
* Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world.
* A WorldLoadEvent will be fire.
@ -226,9 +221,8 @@ public class World implements ApplicationListener{
}
public void loadMap(Map map){
try{
MapIO.loadMap(map);
SaveIO.load(map.file, new FilterContext(map));
}catch(Exception e){
Log.err(e);
if(!headless){
@ -315,50 +309,6 @@ public class World implements ApplicationListener{
}
}
/**
* Raycast, but with world coordinates.
*/
public Point2 raycastWorld(float x, float y, float x2, float y2){
return raycast(Math.round(x / tilesize), Math.round(y / tilesize),
Math.round(x2 / tilesize), Math.round(y2 / tilesize));
}
/**
* Input is in block coordinates, not world coordinates.
* @return null if no collisions found, block position otherwise.
*/
public Point2 raycast(int x0f, int y0f, int x1, int y1){
int x0 = x0f;
int y0 = y0f;
int dx = Math.abs(x1 - x0);
int dy = Math.abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx - dy;
int e2;
while(true){
if(!passable(x0, y0)){
return Tmp.g1.set(x0, y0);
}
if(x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if(e2 > -dy){
err = err - dy;
x0 = x0 + sx;
}
if(e2 < dx){
err = err + dx;
y0 = y0 + sy;
}
}
return null;
}
public void raycastEachWorld(float x0, float y0, float x1, float y1, Raycaster cons){
raycastEach(toTile(x0), toTile(y0), toTile(x1), toTile(y1), cons);
}
@ -489,7 +439,7 @@ public class World implements ApplicationListener{
boolean accept(int x, int y);
}
class Context implements WorldContext{
private class Context implements WorldContext{
@Override
public Tile tile(int x, int y){
return tiles[x][y];
@ -520,4 +470,45 @@ public class World implements ApplicationListener{
endMapLoad();
}
}
/** World context that applies filters after generation end. */
private class FilterContext extends Context{
final Map map;
FilterContext(Map map){
this.map = map;
}
@Override
public void end(){
Array<GenerateFilter> filters = map.filters();
if(!filters.isEmpty()){
//input for filter queries
GenerateInput input = new GenerateInput();
GenTile gtile = new GenTile();
for(GenerateFilter filter : filters){
input.begin(filter, width(), height(), (x, y) -> gtile.set(tiles[x][y]));
//actually apply the filter
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
Tile tile = rawTile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
tile.setFloor((Floor)input.floor);
tile.setOverlay(input.ore);
if(!tile.block().synthetic()){
tile.setBlock(input.block);
}
}
}
}
}
super.end();
}
}
}

View File

@ -60,7 +60,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor = new MapEditor();
view = new MapView(editor);
infoDialog = new MapInfoDialog(editor);
generateDialog = new MapGenerateDialog(editor);
generateDialog = new MapGenerateDialog(editor, true);
menu = new FloatingDialog("$menu");
menu.addCloseButton();
@ -81,7 +81,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
t.row();
t.addImageTextButton("$editor.generate", "icon-editor", isize, () -> {
generateDialog.show();
generateDialog.show(generateDialog::applyToEditor);
menu.hide();
});

View File

@ -32,6 +32,7 @@ public class MapGenerateDialog extends FloatingDialog{
MirrorFilter::new
};
private final MapEditor editor;
private final boolean applied;
private Pixmap pixmap;
private Texture texture;
@ -46,19 +47,24 @@ public class MapGenerateDialog extends FloatingDialog{
private GenTile returnTile = new GenTile();
private GenTile[][] buffer1, buffer2;
private Consumer<Array<GenerateFilter>> applier;
public MapGenerateDialog(MapEditor editor){
/** @param applied whether or not to use the applied in-game mode. */
public MapGenerateDialog(MapEditor editor, boolean applied){
super("$editor.generate");
this.editor = editor;
this.applied = applied;
shown(this::setup);
addCloseButton();
buttons.addButton("$editor.apply", () -> {
ui.loadAnd(() -> {
apply();
hide();
});
}).size(160f, 64f);
if(applied){
buttons.addButton("$editor.apply", () -> {
ui.loadAnd(() -> {
apply();
hide();
});
}).size(160f, 64f);
}
buttons.addButton("$editor.randomize", () -> {
for(GenerateFilter filter : filters){
filter.randomize();
@ -67,6 +73,76 @@ public class MapGenerateDialog extends FloatingDialog{
}).size(160f, 64f);
buttons.addImageTextButton("$add", "icon-add", iconsize, this::showAdd).height(64f).width(140f);
if(!applied){
hidden(this::apply);
}
}
public void show(Array<GenerateFilter> filters, Consumer<Array<GenerateFilter>> applier){
this.filters = filters;
this.applier = applier;
show();
}
public void show(Consumer<Array<GenerateFilter>> applier){
show(this.filters, applier);
}
/** Applies the specified filters to the editor. */
public void applyToEditor(Array<GenerateFilter> filters){
//writeback buffer
GenTile[][] writeTiles = new GenTile[editor.width()][editor.height()];
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
writeTiles[x][y] = new GenTile();
}
}
for(GenerateFilter filter : filters){
input.begin(filter, editor.width(), editor.height(), (x, y) -> dset(editor.tile(x, y)));
//write to buffer
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation());
}
}
editor.load(() -> {
//read from buffer back into tiles
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
GenTile write = writeTiles[x][y];
tile.rotation(write.rotation);
tile.setFloor((Floor)content.block(write.floor));
tile.setBlock(content.block(write.block));
tile.setTeam(Team.all[write.team]);
tile.setOverlay(content.block(write.ore));
}
}
});
}
//reset undo stack as generation... messes things up
editor.load(editor::checkLinkedTiles);
editor.renderer().updateAll();
editor.clearOp();
}
public void addDefaultOres(Array<GenerateFilter> filters){
int index = 0;
for(Block block : new Block[]{Blocks.oreCopper, Blocks.oreCoal, Blocks.oreLead, Blocks.oreTitanium, Blocks.oreThorium}){
OreFilter filter = new OreFilter();
filter.threshold += index ++ * 0.025f;
filter.ore = block;
filters.add(filter);
}
}
void setup(){
@ -140,6 +216,7 @@ public class MapGenerateDialog extends FloatingDialog{
filterTable.top();
for(GenerateFilter filter : filters){
//main container
filterTable.table("button", c -> {
//icons to perform actions
@ -206,6 +283,9 @@ public class MapGenerateDialog extends FloatingDialog{
int i = 0;
for(Supplier<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
if(!applied && filter.buffered) continue;
selection.cont.addButton(filter.name(), () -> {
filters.add(filter);
rebuildFilters();
@ -216,14 +296,7 @@ public class MapGenerateDialog extends FloatingDialog{
}
selection.cont.addButton("Default Ores", () -> {
int index = 0;
for(Block block : new Block[]{Blocks.oreCopper, Blocks.oreCoal, Blocks.oreLead, Blocks.oreTitanium, Blocks.oreThorium}){
OreFilter filter = new OreFilter();
filter.threshold += index ++ * 0.02f;
filter.ore = block;
filters.add(filter);
}
addDefaultOres(filters);
rebuildFilters();
update();
selection.hide();
@ -253,48 +326,7 @@ public class MapGenerateDialog extends FloatingDialog{
texture = null;
}
//writeback buffer
GenTile[][] writeTiles = new GenTile[editor.width()][editor.height()];
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
writeTiles[x][y] = new GenTile();
}
}
for(GenerateFilter filter : filters){
input.begin(filter, editor.width(), editor.height(), (x, y) -> dset(editor.tile(x, y)));
//write to buffer
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation());
}
}
editor.load(() -> {
//read from buffer back into tiles
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
GenTile write = writeTiles[x][y];
tile.rotation(write.rotation);
tile.setFloor((Floor)content.block(write.floor));
tile.setBlock(content.block(write.block));
tile.setTeam(Team.all[write.team]);
tile.setOverlay(content.block(write.ore));
}
}
});
}
//reset undo stack as generation... messes things up
editor.load(editor::checkLinkedTiles);
editor.renderer().updateAll();
editor.clearOp();
applier.accept(filters);
}
void update(){
@ -371,7 +403,7 @@ public class MapGenerateDialog extends FloatingDialog{
public byte team, rotation;
public short block, floor, ore;
void set(Block floor, Block wall, Block ore, Team team, int rotation){
public void set(Block floor, Block wall, Block ore, Team team, int rotation){
this.floor = floor.id;
this.block = wall.id;
this.ore = ore.id;
@ -379,7 +411,7 @@ public class MapGenerateDialog extends FloatingDialog{
this.rotation = (byte)rotation;
}
void set(GenTile other){
public void set(GenTile other){
this.floor = other.floor;
this.block = other.block;
this.ore = other.ore;
@ -387,8 +419,9 @@ public class MapGenerateDialog extends FloatingDialog{
this.rotation = other.rotation;
}
void set(Tile other){
public GenTile set(Tile other){
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.rotation());
return this;
}
}
}

View File

@ -1,24 +1,25 @@
package io.anuke.mindustry.editor;
import io.anuke.arc.Core;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.arc.scene.ui.TextArea;
import io.anuke.arc.scene.ui.TextField;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.game.Rules;
import io.anuke.mindustry.ui.dialogs.CustomRulesDialog;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.ui.dialogs.*;
public class MapInfoDialog extends FloatingDialog{
private final MapEditor editor;
private final WaveInfoDialog waveInfo;
private final MapGenerateDialog generate;
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
public MapInfoDialog(MapEditor editor){
super("$editor.mapinfo");
this.editor = editor;
this.waveInfo = new WaveInfoDialog(editor);
this.generate = new MapGenerateDialog(editor, false);
addCloseButton();
@ -64,6 +65,13 @@ public class MapInfoDialog extends FloatingDialog{
t.add("$editor.waves").padRight(8).left();
t.addButton("$edit", waveInfo::show).left().width(200f);
t.row();
t.add("$editor.generation").padRight(8).left();
t.addButton("$edit",
() -> generate.show(JsonIO.read(Array.class, editor.getTags().get("genfilters", "{}")),
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)))
).left().width(200f);
name.change();
description.change();
author.change();

View File

@ -2,12 +2,14 @@ package io.anuke.mindustry.io;
import io.anuke.arc.collection.EnumSet;
import io.anuke.arc.collection.LongQueue;
import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.Json;
import io.anuke.arc.util.serialization.JsonValue;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.TeamData;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@SuppressWarnings("unchecked")
public class JsonIO{
@ -40,6 +42,37 @@ public class JsonIO{
}
});
//TODO extremely hacky and disgusting
for(Block block : Vars.content.blocks()){
Class type = block.getClass();
if(type.isAnonymousClass()) type = type.getSuperclass();
Log.info(type);
setSerializer(type, new Serializer<Block>(){
@Override
public void write(Json json, Block object, Class knownType){
json.writeValue(object.name);
}
@Override
public Block read(Json json, JsonValue jsonData, Class type){
return Vars.content.getByName(ContentType.block, jsonData.asString());
}
});
}
setSerializer(Block.class, new Serializer<Block>(){
@Override
public void write(Json json, Block object, Class knownType){
json.writeValue(object.name);
}
@Override
public Block read(Json json, JsonValue jsonData, Class type){
return Vars.content.getByName(ContentType.block, jsonData.asString());
}
});
setSerializer(TeamData.class, new Serializer<TeamData>(){
@Override
public void write(Json json, TeamData object, Class knownType){

View File

@ -7,6 +7,7 @@ import io.anuke.arc.graphics.Texture;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.JsonIO;
import io.anuke.mindustry.maps.filters.*;
public class Map implements Comparable<Map>{
/** Whether this is a custom map. */
@ -59,25 +60,18 @@ public class Map implements Comparable<Map>{
Vars.data.modified();
}
/** This creates a new instance.*/
/** This creates a new instance of Rules.*/
public Rules rules(){
Rules result = JsonIO.read(Rules.class, tags.get("rules", "{}"));
if(result.spawns.isEmpty()) result.spawns = Vars.defaultWaves.get();
return result;
}
/** Whether this map has a core of the enemy 'wave' team. Default: true.
* Used for checking Attack mode validity.
public boolean hasEnemyCore(){
return tags.get("enemycore", "true").equals("true");
/** Returns the generation filters that this map uses on load.*/
public Array<GenerateFilter> filters(){
return JsonIO.read(Array.class, tags.get("genfilters", "{}"));
}
/** Whether this map has a core of any team except the default player team. Default: true.
* Used for checking PvP mode validity.
public boolean hasOtherCores(){
return tags.get("othercore", "true").equals("true");
}*/
public String author(){
return tag("author");
}

View File

@ -13,6 +13,7 @@ public class BlendFilter extends GenerateFilter{
Block flooronto = Blocks.stone, floor = Blocks.ice;
{
buffered = true;
options(
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f),
new BlockOption("flooronto", () -> flooronto, b -> flooronto = b, floorsOnly),

View File

@ -10,6 +10,7 @@ public class DistortFilter extends GenerateFilter{
float scl = 40, mag = 5;
{
buffered = true;
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 400f),
new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f)

View File

@ -15,7 +15,8 @@ public abstract class GenerateFilter{
protected transient long seed;
protected transient GenerateInput in;
public FilterOption[] options;
public transient boolean buffered = false;
public transient FilterOption[] options;
public final void apply(GenerateInput in){
this.in = in;

View File

@ -13,6 +13,7 @@ public class MedianFilter extends GenerateFilter{
IntArray blocks = new IntArray(), floors = new IntArray();
{
buffered = true;
options(
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f),
new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f)

View File

@ -8,7 +8,7 @@ import static io.anuke.mindustry.maps.filters.FilterOption.BlockOption;
import static io.anuke.mindustry.maps.filters.FilterOption.oresOnly;
public class OreFilter extends GenerateFilter{
public float scl = 50, threshold = 0.72f, octaves = 3f, falloff = 0.4f;
public float scl = 45, threshold = 0.73f, octaves = 3f, falloff = 0.4f;
public Block ore = Blocks.oreCopper;
{

View File

@ -1,23 +1,19 @@
package io.anuke.mindustry.maps.generators;
import io.anuke.arc.collection.Array;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.util.Structs;
import io.anuke.arc.util.noise.Simplex;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Loadout;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.noise.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
import io.anuke.mindustry.world.blocks.storage.StorageBlock;
import io.anuke.mindustry.world.blocks.storage.*;
import static io.anuke.mindustry.Vars.defaultTeam;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public class MapGenerator extends Generator{
private Map map;
@ -82,7 +78,7 @@ public class MapGenerator extends Generator{
}
}
MapIO.loadMap(map);
SaveIO.load(map.file);
Array<Point2> players = new Array<>();
Array<Point2> enemies = new Array<>();