it is done

This commit is contained in:
Anuken
2019-12-25 01:39:38 -05:00
parent 5b21873f3c
commit 514d4817c8
488 changed files with 4572 additions and 4574 deletions

View File

@@ -0,0 +1,233 @@
package mindustry.maps;
import arc.*;
import arc.struct.*;
import arc.files.*;
import arc.graphics.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.io.*;
import mindustry.maps.filters.*;
import mindustry.mod.Mods.*;
import mindustry.type.*;
import static mindustry.Vars.*;
public class Map implements Comparable<Map>, Publishable{
/** Whether this is a custom map. */
public final boolean custom;
/** Metadata. Author description, display name, etc. */
public final StringMap tags;
/** Base file of this map. File can be named anything at all. */
public final Fi file;
/** Format version. */
public final int version;
/** Whether this map is managed, e.g. downloaded from the Steam workshop.*/
public boolean workshop;
/** Map width/height, shorts. */
public int width, height;
/** Preview texture. */
public Texture texture;
/** Build that this map was created in. -1 = unknown or custom build. */
public int build;
/** All teams present on this map.*/
public IntSet teams = new IntSet();
/** Number of enemy spawns on this map.*/
public int spawns = 0;
/** Associated mod. If null, no mod is associated. */
public @Nullable LoadedMod mod;
public Map(Fi file, int width, int height, StringMap tags, boolean custom, int version, int build){
this.custom = custom;
this.tags = tags;
this.file = file;
this.width = width;
this.height = height;
this.version = version;
this.build = build;
}
public Map(Fi file, int width, int height, StringMap tags, boolean custom, int version){
this(file, width, height, tags, custom, version, -1);
}
public Map(Fi file, int width, int height, StringMap tags, boolean custom){
this(file, width, height, tags, custom, -1);
}
public Map(StringMap tags){
this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true);
}
public int getHightScore(){
return Core.settings.getInt("hiscore" + file.nameWithoutExtension(), 0);
}
public Texture safeTexture(){
return texture == null ? Core.assets.get("sprites/error.png") : texture;
}
public Fi previewFile(){
return Vars.mapPreviewDirectory.child((workshop ? file.parent().name() : file.nameWithoutExtension()) + ".png");
}
public Fi cacheFile(){
return Vars.mapPreviewDirectory.child(workshop ? file.parent().name() + "-workshop-cache.dat" : file.nameWithoutExtension() + "-cache.dat");
}
public void setHighScore(int score){
Core.settings.put("hiscore" + file.nameWithoutExtension(), score);
Vars.data.modified();
}
/** Returns the result of applying this map's rules to the specified gamemode.*/
public Rules applyRules(Gamemode mode){
//mode specific defaults have been applied
Rules out = new Rules();
mode.apply(out);
//now apply map-specific overrides
return rules(out);
}
/** This creates a new instance of Rules.*/
public Rules rules(){
return rules(new Rules());
}
public Rules rules(Rules base){
try{
Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}"));
if(result.spawns.isEmpty()) result.spawns = Vars.defaultWaves.get();
return result;
}catch(Exception e){
//error reading rules. ignore?
e.printStackTrace();
return new Rules();
}
}
/** Returns the generation filters that this map uses on load.*/
public Array<GenerateFilter> filters(){
if(tags.getInt("build", -1) < 83 && tags.getInt("build", -1) != -1 && tags.get("genfilters", "").isEmpty()){
return Array.with();
}
return maps.readFilters(tags.get("genfilters", ""));
}
public String author(){
return tag("author");
}
public String description(){
return tag("description");
}
public String name(){
return tag("name");
}
public String tag(String name){
return tags.containsKey(name) && !tags.get(name).trim().isEmpty() ? tags.get(name) : Core.bundle.get("unknown");
}
public boolean hasTag(String name){
return tags.containsKey(name);
}
@Override
public String getSteamID(){
return tags.get("steamid");
}
@Override
public void addSteamID(String id){
tags.put("steamid", id);
ui.editor.editor.getTags().put("steamid", id);
try{
ui.editor.save();
}catch(Exception e){
Log.err(e);
}
Events.fire(new MapPublishEvent());
}
@Override
public void removeSteamID(){
tags.remove("steamid");
ui.editor.editor.getTags().remove("steamid");
try{
ui.editor.save();
}catch(Exception e){
Log.err(e);
}
}
@Override
public String steamTitle(){
return name();
}
@Override
public String steamDescription(){
return description();
}
@Override
public String steamTag(){
return "map";
}
@Override
public Fi createSteamFolder(String id){
Fi mapFile = tmpDirectory.child("map_" + id).child("map.msav");
file.copyTo(mapFile);
return mapFile.parent();
}
@Override
public Fi createSteamPreview(String id){
return previewFile();
}
@Override
public Array<String> extraTags(){
Gamemode mode = Gamemode.attack.valid(this) ? Gamemode.attack : Gamemode.survival;
return Array.with(mode.name());
}
@Override
public boolean prePublish(){
tags.put("author", player.name);
ui.editor.editor.getTags().put("author", tags.get("author"));
ui.editor.save();
return true;
}
@Override
public int compareTo(Map map){
int work = -Boolean.compare(workshop, map.workshop);
if(work != 0) return work;
int type = -Boolean.compare(custom, map.custom);
if(type != 0) return type;
int modes = Boolean.compare(Gamemode.pvp.valid(this), Gamemode.pvp.valid(map));
if(modes != 0) return modes;
return name().compareTo(map.name());
}
@Override
public String toString(){
return "Map{" +
"file='" + file + '\'' +
", custom=" + custom +
", tags=" + tags +
'}';
}
}

View File

@@ -0,0 +1,10 @@
package mindustry.maps;
public class MapException extends RuntimeException{
public final Map map;
public MapException(Map map, String s){
super(s);
this.map = map;
}
}

View File

@@ -0,0 +1,57 @@
package mindustry.maps;
import arc.assets.*;
import arc.assets.loaders.*;
import arc.assets.loaders.resolvers.*;
import arc.struct.*;
import arc.files.*;
import arc.graphics.*;
import arc.util.*;
import mindustry.*;
import mindustry.ctype.Content;
public class MapPreviewLoader extends TextureLoader{
public MapPreviewLoader(){
super(new AbsoluteFileHandleResolver());
}
@Override
public void loadAsync(AssetManager manager, String fileName, Fi file, TextureParameter parameter){
try{
super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter);
}catch(Exception e){
Log.err(e);
MapPreviewParameter param = (MapPreviewParameter)parameter;
Vars.maps.queueNewPreview(param.map);
}
}
@Override
public Texture loadSync(AssetManager manager, String fileName, Fi file, TextureParameter parameter){
try{
return super.loadSync(manager, fileName, file, parameter);
}catch(Throwable e){
Log.err(e);
try{
return new Texture(file);
}catch(Throwable e2){
Log.err(e2);
return new Texture("sprites/error.png");
}
}
}
@Override
public Array<AssetDescriptor> getDependencies(String fileName, Fi file, TextureParameter parameter){
return Array.with(new AssetDescriptor<>("contentcreate", Content.class));
}
public static class MapPreviewParameter extends TextureParameter{
public Map map;
public MapPreviewParameter(Map map){
this.map = map;
}
}
}

View File

@@ -0,0 +1,504 @@
package mindustry.maps;
import arc.*;
import arc.assets.*;
import arc.assets.loaders.*;
import arc.struct.*;
import arc.struct.IntSet.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.async.*;
import arc.util.io.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.io.*;
import mindustry.maps.MapPreviewLoader.*;
import mindustry.maps.filters.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import java.io.*;
import static mindustry.Vars.*;
public class Maps{
/** List of all built-in maps. Filenames only. */
private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "veins", "glacier"};
/** All maps stored in an ordered array. */
private Array<Map> maps = new Array<>();
/** Serializer for meta. */
private Json json = new Json();
private ShuffleMode shuffleMode = ShuffleMode.all;
private @Nullable MapProvider shuffler;
private AsyncExecutor executor = new AsyncExecutor(2);
private ObjectSet<Map> previewList = new ObjectSet<>();
public ShuffleMode getShuffleMode(){
return shuffleMode;
}
public void setShuffleMode(ShuffleMode mode){
this.shuffleMode = mode;
}
/** Set the provider for the map(s) to be played on. Will override the default shuffle mode setting.*/
public void setMapProvider(MapProvider provider){
this.shuffler = provider;
}
/** @return the next map to shuffle to. May be null, in which case the server should be stopped. */
public @Nullable Map getNextMap(@Nullable Map previous){
return shuffler != null ? shuffler.next(previous) : shuffleMode.next(previous);
}
/** Returns a list of all maps, including custom ones. */
public Array<Map> all(){
return maps;
}
/** Returns a list of only custom maps. */
public Array<Map> customMaps(){
return maps.select(m -> m.custom);
}
/** Returns a list of only default maps. */
public Array<Map> defaultMaps(){
return maps.select(m -> !m.custom);
}
public Map byName(String name){
return maps.find(m -> m.name().equals(name));
}
public Maps(){
Events.on(ClientLoadEvent.class, event -> {
maps.sort();
});
Events.on(ContentReloadEvent.class, event -> {
reload();
for(Map map : maps){
try{
map.texture = map.previewFile().exists() ? new Texture(map.previewFile()) : new Texture(MapIO.generatePreview(map));
readCache(map);
}catch(Exception e){
e.printStackTrace();
}
}
});
if(Core.assets != null){
((CustomLoader) Core.assets.getLoader(Content.class)).loaded = this::createAllPreviews;
}
}
/**
* Loads a map from the map folder and returns it. Should only be used for zone maps.
* Does not add this map to the map list.
*/
public Map loadInternalMap(String name){
Fi file = tree.get("maps/" + name + "." + mapExtension);
try{
return MapIO.createMap(file, false);
}catch(IOException e){
throw new RuntimeException(e);
}
}
/** Load all maps. Should be called at application start. */
public void load(){
//defaults; must work
try{
for(String name : defaultMapNames){
Fi file = Core.files.internal("maps/" + name + "." + mapExtension);
loadMap(file, false);
}
}catch(IOException e){
throw new RuntimeException(e);
}
//custom
for(Fi file : customMapDirectory.list()){
try{
if(file.extension().equalsIgnoreCase(mapExtension)){
loadMap(file, true);
}
}catch(Exception e){
Log.err("Failed to load custom map file '{0}'!", file);
Log.err(e);
}
}
//workshop
for(Fi file : platform.getWorkshopContent(Map.class)){
try{
Map map = loadMap(file, false);
map.workshop = true;
map.tags.put("steamid", file.parent().name());
}catch(Exception e){
Log.err("Failed to load workshop map file '{0}'!", file);
Log.err(e);
}
}
//mod
mods.listFiles("maps", (mod, file) -> {
try{
Map map = loadMap(file, false);
map.mod = mod;
}catch(Exception e){
Log.err("Failed to load mod map file '{0}'!", file);
Log.err(e);
}
});
}
public void reload(){
for(Map map : maps){
if(map.texture != null){
map.texture.dispose();
map.texture = null;
}
}
maps.clear();
load();
}
/**
* Save a custom map to the directory. This updates all values and stored data necessary.
* The tags are copied to prevent mutation later.
*/
public Map saveMap(ObjectMap<String, String> baseTags){
try{
StringMap tags = new StringMap(baseTags);
String name = tags.get("name");
if(name == null) throw new IllegalArgumentException("Can't save a map with no name. How did this happen?");
Fi file;
//find map with the same exact display name
Map other = maps.find(m -> m.name().equals(name));
if(other != null){
//dispose of map if it's already there
if(other.texture != null){
other.texture.dispose();
other.texture = null;
}
maps.remove(other);
file = other.file;
}else{
file = findFile();
}
//create map, write it, etc etc etc
Map map = new Map(file, world.width(), world.height(), tags, true);
MapIO.writeMap(file, map);
if(!headless){
//reset attributes
map.teams.clear();
map.spawns = 0;
for(int x = 0; x < map.width; x++){
for(int y = 0; y < map.height; y++){
Tile tile = world.getTiles()[x][y];
if(tile.block() instanceof CoreBlock){
map.teams.add(tile.getTeamID());
}
if(tile.overlay() == Blocks.spawn){
map.spawns ++;
}
}
}
if(Core.assets.isLoaded(map.previewFile().path() + "." + mapExtension)){
Core.assets.unload(map.previewFile().path() + "." + mapExtension);
}
Pixmap pix = MapIO.generatePreview(world.getTiles());
executor.submit(() -> map.previewFile().writePNG(pix));
writeCache(map);
map.texture = new Texture(pix);
}
maps.add(map);
maps.sort();
return map;
}catch(IOException e){
throw new RuntimeException(e);
}
}
/** Import a map, then save it. This updates all values and stored data necessary. */
public void importMap(Fi file) throws IOException{
Fi dest = findFile();
file.copyTo(dest);
Map map = loadMap(dest, true);
Exception[] error = {null};
createNewPreview(map, e -> {
maps.remove(map);
try{
map.file.delete();
}catch(Throwable ignored){
}
error[0] = e;
});
if(error[0] != null){
throw new IOException(error[0]);
}
}
/** Attempts to run the following code;
* catches any errors and attempts to display them in a readable way.*/
public void tryCatchMapError(UnsafeRunnable run){
try{
run.run();
}catch(Throwable e){
Log.err(e);
if("Outdated legacy map format".equals(e.getMessage())){
ui.showErrorMessage("$editor.errornot");
}else if(e.getMessage() != null && e.getMessage().contains("Incorrect header!")){
ui.showErrorMessage("$editor.errorheader");
}else{
ui.showException("$editor.errorload", e);
}
}
}
/** Removes a map completely. */
public void removeMap(Map map){
if(map.texture != null){
map.texture.dispose();
map.texture = null;
}
maps.remove(map);
map.file.delete();
}
/** Reads JSON of filters, returning a new default array if not found.*/
@SuppressWarnings("unchecked")
public Array<GenerateFilter> readFilters(String str){
if(str == null || str.isEmpty()){
//create default filters list
Array<GenerateFilter> filters = Array.with(
new ScatterFilter(){{
flooronto = Blocks.stone;
block = Blocks.rock;
}},
new ScatterFilter(){{
flooronto = Blocks.shale;
block = Blocks.shaleBoulder;
}},
new ScatterFilter(){{
flooronto = Blocks.snow;
block = Blocks.snowrock;
}},
new ScatterFilter(){{
flooronto = Blocks.ice;
block = Blocks.snowrock;
}},
new ScatterFilter(){{
flooronto = Blocks.sand;
block = Blocks.sandBoulder;
}}
);
addDefaultOres(filters);
return filters;
}else{
try{
return JsonIO.read(Array.class, str.replace("mindustrz", "mindustry"));
}catch(Exception e){
e.printStackTrace();
return readFilters("");
}
}
}
public void addDefaultOres(Array<GenerateFilter> filters){
int index = 0;
for(Block block : new Block[]{Blocks.oreCopper, Blocks.oreLead, Blocks.oreCoal, Blocks.oreTitanium, Blocks.oreThorium}){
OreFilter filter = new OreFilter();
filter.threshold += index ++ * 0.018f;
filter.scl += index/2.1f;
filter.ore = block;
filters.add(filter);
}
}
public String writeWaves(Array<SpawnGroup> groups){
if(groups == null){
return "[]";
}
StringWriter buffer = new StringWriter();
json.setWriter(buffer);
json.writeArrayStart();
for(int i = 0; i < groups.size; i++){
json.writeObjectStart(SpawnGroup.class, SpawnGroup.class);
groups.get(i).write(json);
json.writeObjectEnd();
}
json.writeArrayEnd();
return buffer.toString();
}
public Array<SpawnGroup> readWaves(String str){
return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str));
}
public void loadPreviews(){
for(Map map : maps){
//try to load preview
if(map.previewFile().exists()){
//this may fail, but calls queueNewPreview
Core.assets.load(new AssetDescriptor<>(map.previewFile().path() + "." + mapExtension, Texture.class, new MapPreviewParameter(map))).loaded = t -> map.texture = (Texture)t;
try{
readCache(map);
}catch(Exception e){
e.printStackTrace();
queueNewPreview(map);
}
}else{
queueNewPreview(map);
}
}
}
private void createAllPreviews(){
Core.app.post(() -> {
for(Map map : previewList){
createNewPreview(map, e -> Core.app.post(() -> map.texture = Core.assets.get("sprites/error.png")));
}
previewList.clear();
});
}
public void queueNewPreview(Map map){
Core.app.post(() -> previewList.add(map));
}
private void createNewPreview(Map map, Cons<Exception> failed){
try{
//if it's here, then the preview failed to load or doesn't exist, make it
//this has to be done synchronously!
Pixmap pix = MapIO.generatePreview(map);
map.texture = new Texture(pix);
executor.submit(() -> {
try{
map.previewFile().writePNG(pix);
writeCache(map);
}catch(Exception e){
e.printStackTrace();
}
});
}catch(Exception e){
failed.get(e);
Log.err("Failed to generate preview!", e);
}
}
private void writeCache(Map map) throws IOException{
try(DataOutputStream stream = new DataOutputStream(map.cacheFile().write(false, Streams.DEFAULT_BUFFER_SIZE))){
stream.write(0);
stream.writeInt(map.spawns);
stream.write(map.teams.size);
IntSetIterator iter = map.teams.iterator();
while(iter.hasNext){
stream.write(iter.next());
}
}
}
private void readCache(Map map) throws IOException{
try(DataInputStream stream = new DataInputStream(map.cacheFile().read(Streams.DEFAULT_BUFFER_SIZE))){
stream.read(); //version
map.spawns = stream.readInt();
int teamsize = stream.readByte();
for(int i = 0; i < teamsize; i++){
map.teams.add(stream.read());
}
}
}
/** Find a new filename to put a map to. */
private Fi findFile(){
//find a map name that isn't used.
int i = maps.size;
while(customMapDirectory.child("map_" + i + "." + mapExtension).exists()){
i++;
}
return customMapDirectory.child("map_" + i + "." + mapExtension);
}
private Map loadMap(Fi file, boolean custom) throws IOException{
Map map = MapIO.createMap(file, custom);
if(map.name() == null){
throw new IOException("Map name cannot be empty! File: " + file);
}
maps.add(map);
maps.sort();
return map;
}
public interface MapProvider{
@Nullable Map next(@Nullable Map previous);
}
public enum ShuffleMode implements MapProvider{
none(map -> null),
all(prev -> {
Array<Map> maps = Array.withArrays(Vars.maps.defaultMaps(), Vars.maps.customMaps());
maps.shuffle();
return maps.find(m -> m != prev || maps.size == 1);
}),
custom(prev -> {
Array<Map> maps = Array.withArrays(Vars.maps.customMaps().isEmpty() ? Vars.maps.defaultMaps() : Vars.maps.customMaps());
maps.shuffle();
return maps.find(m -> m != prev || maps.size == 1);
}),
builtin(prev -> {
Array<Map> maps = Array.withArrays(Vars.maps.defaultMaps());
maps.shuffle();
return maps.find(m -> m != prev || maps.size == 1);
});
private final MapProvider provider;
ShuffleMode(MapProvider provider){
this.provider = provider;
}
@Override
public Map next(@Nullable Map previous){
return provider.next(previous);
}
}
}

View File

@@ -0,0 +1,48 @@
package mindustry.maps.filters;
import arc.math.*;
import mindustry.content.*;
import mindustry.maps.filters.FilterOption.*;
import mindustry.world.*;
import static mindustry.maps.filters.FilterOption.*;
public class BlendFilter extends GenerateFilter{
float radius = 2f;
Block block = Blocks.stone, floor = Blocks.ice, ignore = Blocks.air;
{
buffered = true;
options(
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f),
new BlockOption("block", () -> block, b -> block = b, anyOptional),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("ignore", () -> ignore, b -> ignore = b, floorsOptional)
);
}
@Override
public void apply(){
if(in.floor == block || block == Blocks.air || in.floor == ignore) return;
int rad = (int)radius;
boolean found = false;
outer:
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
if(Mathf.dst2(x, y) > rad*rad) continue;
Tile tile = in.tile(in.x + x, in.y + y);
if(tile.floor() == block || tile.block() == block || tile.overlay() == block){
found = true;
break outer;
}
}
}
if(found){
in.floor = floor;
}
}
}

View File

@@ -0,0 +1,24 @@
package mindustry.maps.filters;
import mindustry.content.*;
import mindustry.world.*;
import static mindustry.maps.filters.FilterOption.*;
public class ClearFilter extends GenerateFilter{
protected Block block = Blocks.air;
{
options(
new BlockOption("block", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
if(in.block == block){
in.block = Blocks.air;
}
}
}

View File

@@ -0,0 +1,26 @@
package mindustry.maps.filters;
import mindustry.maps.filters.FilterOption.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
public class DistortFilter extends GenerateFilter{
float scl = 40, mag = 5;
{
buffered = true;
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 200f),
new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f)
);
}
@Override
public void apply(){
Tile tile = in.tile(in.x + noise(in.x, in.y, scl, mag) - mag / 2f, in.y + noise(in.x, in.y + o, scl, mag) - mag / 2f);
in.floor = tile.floor();
if(!tile.block().synthetic() && !in.block.synthetic()) in.block = tile.block();
if(!((Floor)in.floor).isLiquid) in.ore = tile.overlay();
}
}

View File

@@ -0,0 +1,101 @@
package mindustry.maps.filters;
import arc.*;
import arc.func.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.ui.Cicon;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import static mindustry.Vars.*;
public abstract class FilterOption{
public static final Boolf<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(mindustry.ui.Cicon.full));
public static final Boolf<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && !headless && Core.atlas.isFound(b.icon(mindustry.ui.Cicon.full));
public static final Boolf<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(mindustry.ui.Cicon.full)));
public static final Boolf<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && !headless && Core.atlas.isFound(b.icon(mindustry.ui.Cicon.full)));
public static final Boolf<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(mindustry.ui.Cicon.full)));
public static final Boolf<Block> oresOnly = b -> b instanceof OverlayFloor && !headless && Core.atlas.isFound(b.icon(mindustry.ui.Cicon.full));
public static final Boolf<Block> anyOptional = b -> floorsOnly.get(b) || wallsOnly.get(b) || oresOnly.get(b) || b == Blocks.air;
public abstract void build(Table table);
public Runnable changed = () -> {};
static class SliderOption extends FilterOption{
final String name;
final Floatp getter;
final Floatc setter;
final float min, max, step;
SliderOption(String name, Floatp getter, Floatc setter, float min, float max){
this(name, getter, setter, min, max, (max - min) / 200);
}
SliderOption(String name, Floatp getter, Floatc setter, float min, float max, float step){
this.name = name;
this.getter = getter;
this.setter = setter;
this.min = min;
this.max = max;
this.step = step;
}
@Override
public void build(Table table){
table.add("$filter.option." + name);
table.row();
Slider slider = table.addSlider(min, max, step, setter).growX().get();
slider.setValue(getter.get());
if(updateEditorOnChange){
slider.changed(changed);
}else{
slider.released(changed);
}
}
}
static class BlockOption extends FilterOption{
final String name;
final Prov<Block> supplier;
final Cons<Block> consumer;
final Boolf<Block> filter;
BlockOption(String name, Prov<Block> supplier, Cons<Block> consumer, Boolf<Block> filter){
this.name = name;
this.supplier = supplier;
this.consumer = consumer;
this.filter = filter;
}
@Override
public void build(Table table){
table.addButton(b -> b.addImage(supplier.get().icon(mindustry.ui.Cicon.small)).update(i -> ((TextureRegionDrawable)i.getDrawable())
.setRegion(supplier.get() == Blocks.air ? Core.atlas.find("icon-none") : supplier.get().icon(mindustry.ui.Cicon.small))).size(8 * 3), () -> {
FloatingDialog dialog = new FloatingDialog("");
dialog.setFillParent(false);
int i = 0;
for(Block block : Vars.content.blocks()){
if(!filter.get(block)) continue;
dialog.cont.addImage(block == Blocks.air ? Core.atlas.find("icon-none-small") : block.icon(Cicon.medium)).size(8 * 4).pad(3).get().clicked(() -> {
consumer.get(block);
dialog.hide();
changed.run();
});
if(++i % 10 == 0) dialog.cont.row();
}
dialog.show();
}).pad(4).margin(12f);
table.add("$filter.option." + name);
}
}
}

View File

@@ -0,0 +1,105 @@
package mindustry.maps.filters;
import arc.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.content.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
public abstract class GenerateFilter{
protected transient float o = (float)(Math.random() * 10000000.0);
protected transient long seed;
protected transient GenerateInput in;
public transient boolean buffered = false;
public transient FilterOption[] options;
public final void apply(GenerateInput in){
this.in = in;
apply();
//remove extra ores on liquids
if(((Floor)in.floor).isLiquid){
in.ore = Blocks.air;
}
}
/** sets up the options; this is necessary since the constructor can't access subclass variables. */
protected void options(FilterOption... options){
this.options = options;
}
/** apply the actual filter on the input */
protected abstract void apply();
/** draw any additional guides */
public void draw(Image image){}
/** localized display name */
public String name(){
return Core.bundle.get("filter." + getClass().getSimpleName().toLowerCase().replace("filter", ""), getClass().getSimpleName().replace("Filter", ""));
}
/** set the seed to a random number */
public void randomize(){
seed = Mathf.random(99999999);
}
//utility generation functions
protected float noise(float x, float y, float scl, float mag){
return (float)in.noise.octaveNoise2D(1f, 0f, 1f / scl, x + o, y + o) * mag;
}
protected float noise(float x, float y, float scl, float mag, float octaves, float persistence){
return (float)in.noise.octaveNoise2D(octaves, persistence, 1f / scl, x + o, y + o) * mag;
}
protected float rnoise(float x, float y, float scl, float mag){
return in.pnoise.getValue((int)(x + o), (int)(y + o), 1f / scl) * mag;
}
protected float chance(){
return Mathf.randomSeed(Pack.longInt(in.x, in.y + (int)seed));
}
/** an input for generating at a certain coordinate. should only be instantiated once. */
public static class GenerateInput{
/** input size parameters */
public int x, y, width, height;
/** output parameters */
public Block floor, block, ore;
Simplex noise = new Simplex();
RidgedPerlin pnoise = new RidgedPerlin(0, 1);
TileProvider buffer;
public void apply(int x, int y, Block floor, Block block, Block ore){
this.floor = floor;
this.block = block;
this.ore = ore;
this.x = x;
this.y = y;
}
public void begin(GenerateFilter filter, int width, int height, TileProvider buffer){
this.buffer = buffer;
this.width = width;
this.height = height;
noise.setSeed(filter.seed);
pnoise.setSeed((int)(filter.seed + 1));
}
Tile tile(float x, float y){
return buffer.get(Mathf.clamp((int)x, 0, width - 1), Mathf.clamp((int)y, 0, height - 1));
}
public interface TileProvider{
Tile get(int x, int y);
}
}
}

View File

@@ -0,0 +1,47 @@
package mindustry.maps.filters;
import arc.struct.*;
import arc.math.*;
import mindustry.maps.filters.FilterOption.*;
import mindustry.world.*;
import static mindustry.Vars.content;
public class MedianFilter extends GenerateFilter{
float radius = 2;
float percentile = 0.5f;
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)
);
}
@Override
public void apply(){
int rad = (int)radius;
blocks.clear();
floors.clear();
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
if(Mathf.dst2(x, y) > rad*rad) continue;
Tile tile = in.tile(in.x + x, in.y + y);
blocks.add(tile.block().id);
floors.add(tile.floor().id);
}
}
floors.sort();
blocks.sort();
int index = Math.min((int)(floors.size * percentile), floors.size - 1);
int floor = floors.get(index), block = blocks.get(index);
in.floor = content.block(floor);
if(!content.block(block).synthetic() && !in.block.synthetic()) in.block = content.block(block);
}
}

View File

@@ -0,0 +1,86 @@
package mindustry.maps.filters;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.graphics.*;
import mindustry.maps.filters.FilterOption.*;
import mindustry.world.*;
public class MirrorFilter extends GenerateFilter{
private final Vector2 v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2();
int angle = 45;
{
options(new SliderOption("angle", () -> angle, f -> angle = (int)f, 0, 360, 45));
}
@Override
protected void apply(){
v1.trnsExact(angle - 90, 1f);
v2.set(v1).scl(-1f);
v1.add(in.width/2f - 0.5f, in.height/2f - 0.5f);
v2.add(in.width/2f - 0.5f, in.height/2f - 0.5f);
v3.set(in.x, in.y);
if(!left(v1, v2, v3)){
mirror(v3, v1.x, v1.y, v2.x, v2.y);
Tile tile = in.tile(v3.x, v3.y);
in.floor = tile.floor();
if(!tile.block().synthetic()){
in.block = tile.block();
}
in.ore = tile.overlay();
}
}
@Override
public void draw(Image image){
super.draw(image);
Vector2 vsize = Scaling.fit.apply(image.getDrawable().getMinWidth(), image.getDrawable().getMinHeight(), image.getWidth(), image.getHeight());
float imageWidth = Math.max(vsize.x, vsize.y);
float imageHeight = Math.max(vsize.y, vsize.x);
float size = Math.max(image.getWidth() *2, image.getHeight()*2);
Cons<Vector2> clamper = v ->
v.clamp(
image.getX() + image.getWidth()/2f - imageWidth/2f,
image.getX() + image.getWidth()/2f + imageWidth/2f,
image.getY() + image.getHeight()/2f - imageHeight/2f,
image.getY() + image.getHeight()/2f + imageHeight/2f);
clamper.get(Tmp.v1.trns(angle - 90, size).add(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()));
clamper.get(Tmp.v2.set(Tmp.v1).sub(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()).rotate(180f).add(image.getWidth()/2f + image.getX(), image.getHeight()/2f + image.getY()));
Lines.stroke(Scl.scl(3f), Pal.accent);
Lines.line(Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y);
Draw.reset();
}
void mirror(Vector2 p, float x0, float y0, float x1, float y1){
//special case: uneven map mirrored at 45 degree angle
if(in.width != in.height && angle % 90 != 0){
p.x = (p.x - in.width/2f) * -1 + in.width/2f;
p.y = (p.y - in.height/2f) * -1 + in.height/2f;
}else{
float dx = x1 - x0;
float dy = y1 - y0;
float a = (dx * dx - dy * dy) / (dx * dx + dy * dy);
float b = 2 * dx * dy / (dx * dx + dy * dy);
p.set((a * (p.x - x0) + b * (p.y - y0) + x0), (b * (p.x - x0) - a * (p.y - y0) + y0));
}
}
boolean left(Vector2 a, Vector2 b, Vector2 c){
return ((b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x)) > 0;
}
}

View File

@@ -0,0 +1,35 @@
package mindustry.maps.filters;
import mindustry.content.Blocks;
import mindustry.maps.filters.FilterOption.BlockOption;
import mindustry.maps.filters.FilterOption.SliderOption;
import mindustry.world.Block;
import static mindustry.maps.filters.FilterOption.floorsOnly;
import static mindustry.maps.filters.FilterOption.wallsOnly;
public class NoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0.5f, octaves = 3f, falloff = 0.5f;
Block floor = Blocks.stone, block = Blocks.rocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("wall", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, 1f, octaves, falloff);
if(noise > threshold){
in.floor = floor;
if(wallsOnly.get(in.block)) in.block = block;
}
}
}

View File

@@ -0,0 +1,32 @@
package mindustry.maps.filters;
import mindustry.content.Blocks;
import mindustry.maps.filters.FilterOption.SliderOption;
import mindustry.world.Block;
import static mindustry.maps.filters.FilterOption.BlockOption;
import static mindustry.maps.filters.FilterOption.oresOnly;
public class OreFilter extends GenerateFilter{
public float scl = 23, threshold = 0.81f, octaves = 2f, falloff = 0.3f;
public Block ore = Blocks.oreCopper;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("ore", () -> ore, b -> ore = b, oresOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, 1f, octaves, falloff);
if(noise > threshold && in.ore != Blocks.spawn){
in.ore = ore;
}
}
}

View File

@@ -0,0 +1,57 @@
package mindustry.maps.filters;
import arc.struct.*;
import arc.math.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.maps.filters.FilterOption.*;
import mindustry.world.*;
public class OreMedianFilter extends GenerateFilter{
public float radius = 2;
public float percentile = 0.5f;
private IntArray blocks = new IntArray();
{
buffered = true;
options(
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f),
new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f)
);
}
@Override
public void apply(){
if(in.ore == Blocks.spawn) return;
int cx = (in.x / 2) * 2;
int cy = (in.y / 2) * 2;
if(in.ore != Blocks.air){
if(!(in.tile(cx + 1, cy).overlay() == in.ore && in.tile(cx, cy).overlay() == in.ore && in.tile(cx + 1, cy + 1).overlay() == in.ore && in.tile(cx, cy + 1).overlay() == in.ore &&
!in.tile(cx + 1, cy).block().isStatic() && !in.tile(cx, cy).block().isStatic() && !in.tile(cx + 1, cy + 1).block().isStatic() && !in.tile(cx, cy + 1).block().isStatic())){
in.ore = Blocks.air;
}
}
int rad = (int)radius;
blocks.clear();
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
if(Mathf.dst2(x, y) > rad*rad) continue;
Tile tile = in.tile(in.x + x, in.y + y);
if(tile.overlay() != Blocks.spawn)
blocks.add(tile.overlay().id);
}
}
blocks.sort();
int index = Math.min((int)(blocks.size * percentile), blocks.size - 1);
int overlay = blocks.get(index);
in.ore = Vars.content.block(overlay);
}
}

View File

@@ -0,0 +1,42 @@
package mindustry.maps.filters;
import mindustry.content.Blocks;
import mindustry.maps.filters.FilterOption.BlockOption;
import mindustry.maps.filters.FilterOption.SliderOption;
import mindustry.world.Block;
import static mindustry.maps.filters.FilterOption.floorsOnly;
import static mindustry.maps.filters.FilterOption.wallsOnly;
public class RiverNoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0f, threshold2 = 0.1f;
Block floor = Blocks.water, floor2 = Blocks.deepwater, block = Blocks.sandRocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, -1f, 0.3f),
new SliderOption("threshold2", () -> threshold2, f -> threshold2 = f, -1f, 0.3f),
new BlockOption("block", () -> block, b -> block = b, wallsOnly),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("floor2", () -> floor2, b -> floor2 = b, floorsOnly)
);
}
@Override
public void apply(){
float noise = rnoise(in.x, in.y, scl, 1f);
if(noise >= threshold){
in.floor = floor;
if(in.block.solid){
in.block = block;
}
if(noise >= threshold2){
in.floor = floor2;
}
}
}
}

View File

@@ -0,0 +1,38 @@
package mindustry.maps.filters;
import mindustry.content.Blocks;
import mindustry.maps.filters.FilterOption.BlockOption;
import mindustry.maps.filters.FilterOption.SliderOption;
import mindustry.world.Block;
import static mindustry.maps.filters.FilterOption.*;
public class ScatterFilter extends GenerateFilter{
protected float chance = 0.014f;
protected Block flooronto = Blocks.air, floor = Blocks.air, block = Blocks.air;
{
options(
new SliderOption("chance", () -> chance, f -> chance = f, 0f, 1f),
new BlockOption("flooronto", () -> flooronto, b -> flooronto = b, floorsOptional),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOptional),
new BlockOption("block", () -> block, b -> block = b, wallsOresOptional)
);
}
@Override
public void apply(){
if(block != Blocks.air && (in.floor == flooronto || flooronto == Blocks.air) && in.block == Blocks.air && chance() <= chance){
if(!block.isOverlay()){
in.block = block;
}else{
in.ore = block;
}
}
if(floor != Blocks.air && (in.floor == flooronto || flooronto == Blocks.air) && chance() <= chance){
in.floor = floor;
}
}
}

View File

@@ -0,0 +1,42 @@
package mindustry.maps.filters;
import arc.math.Mathf;
import mindustry.content.Blocks;
import mindustry.maps.filters.FilterOption.BlockOption;
import mindustry.maps.filters.FilterOption.SliderOption;
import mindustry.world.Block;
import static mindustry.maps.filters.FilterOption.floorsOnly;
import static mindustry.maps.filters.FilterOption.wallsOnly;
public class TerrainFilter extends GenerateFilter{
float scl = 40, threshold = 0.9f, octaves = 3f, falloff = 0.5f, magnitude = 1f, circleScl = 2.1f;
Block floor = Blocks.stone, block = Blocks.rocks;
{
options(
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 500f),
new SliderOption("mag", () -> magnitude, f -> magnitude = f, 0f, 2f),
new SliderOption("threshold", () -> threshold, f -> threshold = f, 0f, 1f),
new SliderOption("circle-scale", () -> circleScl, f -> circleScl = f, 0f, 3f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("wall", () -> block, b -> block = b, wallsOnly)
);
}
@Override
public void apply(){
float noise = noise(in.x, in.y, scl, magnitude, octaves, falloff) + Mathf.dst((float)in.x / in.width, (float)in.y / in.height, 0.5f, 0.5f) * circleScl;
in.floor = floor;
in.ore = Blocks.air;
if(noise >= threshold){
in.block = block;
}else{
in.block = Blocks.air;
}
}
}

View File

@@ -0,0 +1,262 @@
package mindustry.maps.generators;
import arc.struct.*;
import arc.func.Intc2;
import arc.math.Mathf;
import arc.math.geom.Geometry;
import arc.math.geom.Point2;
import arc.util.Structs;
import arc.util.noise.Simplex;
import mindustry.content.Blocks;
import mindustry.world.*;
import mindustry.world.blocks.Floor;
import java.util.PriorityQueue;
public abstract class BasicGenerator extends RandomGenerator{
protected static final DistanceHeuristic manhattan = (x1, y1, x2, y2) -> Math.abs(x1 - x2) + Math.abs(y1 - y2);
protected Array<Block> ores;
protected Simplex sim = new Simplex();
protected Simplex sim2 = new Simplex();
public BasicGenerator(int width, int height, Block... ores){
super(width, height);
this.ores = Array.with(ores);
}
@Override
public void generate(Tile[][] tiles){
int seed = Mathf.random(99999999);
sim.setSeed(seed);
sim2.setSeed(seed + 1);
super.generate(tiles);
}
public void ores(Tile[][] tiles){
pass(tiles, (x, y) -> {
if(ores != null){
int offsetX = x - 4, offsetY = y + 23;
for(int i = ores.size - 1; i >= 0; i--){
Block entry = ores.get(i);
if(Math.abs(0.5f - sim.octaveNoise2D(2, 0.7, 1f / (40 + i * 2), offsetX, offsetY + i*999)) > 0.26f &&
Math.abs(0.5f - sim2.octaveNoise2D(1, 1, 1f / (30 + i * 4), offsetX, offsetY - i*999)) > 0.37f){
ore = entry;
break;
}
}
}
});
}
public void terrain(Tile[][] tiles, Block dst, float scl, float mag, float cmag){
pass(tiles, (x, y) -> {
double rocks = sim.octaveNoise2D(5, 0.5, 1f / scl, x, y) * mag
+ Mathf.dst((float)x / width, (float)y / height, 0.5f, 0.5f) * cmag;
double edgeDist = Math.min(x, Math.min(y, Math.min(Math.abs(x - (width - 1)), Math.abs(y - (height - 1)))));
double transition = 5;
if(edgeDist < transition){
rocks += (transition - edgeDist) / transition / 1.5;
}
if(rocks > 0.9){
block =dst;
}
});
}
public void noise(Tile[][] tiles, Block floor, Block block, int octaves, float falloff, float scl, float threshold){
sim.setSeed(Mathf.random(99999));
pass(tiles, (x, y) -> {
if(sim.octaveNoise2D(octaves, falloff, 1f / scl, x, y) > threshold){
Tile tile = tiles[x][y];
this.floor = floor;
if(tile.block().solid){
this.block = block;
}
}
});
}
public void overlay(Tile[][] tiles, Block floor, Block block, float chance, int octaves, float falloff, float scl, float threshold){
sim.setSeed(Mathf.random(99999));
pass(tiles, (x, y) -> {
if(sim.octaveNoise2D(octaves, falloff, 1f / scl, x, y) > threshold && Mathf.chance(chance) && tiles[x][y].floor() == floor){
ore = block;
}
});
}
public void tech(Tile[][] tiles){
Block[] blocks = {Blocks.darkPanel3};
int secSize = 20;
pass(tiles, (x, y) -> {
int mx = x % secSize, my = y % secSize;
int sclx = x / secSize, scly = y / secSize;
if(noise(sclx, scly, 10f, 1f) > 0.63f && (mx == 0 || my == 0 || mx == secSize - 1 || my == secSize - 1)){
if(Mathf.chance(noise(x + 0x231523, y, 40f, 1f))){
floor = Structs.random(blocks);
if(Mathf.dst(mx, my, secSize/2, secSize/2) > secSize/2f + 2){
floor = Blocks.darkPanel4;
}
}
if(block.solid && Mathf.chance(0.7)){
block = Blocks.darkMetal;
}
}
});
}
public void distort(Tile[][] tiles, float scl, float mag){
Block[][] blocks = new Block[width][height];
Floor[][] floors = new Floor[width][height];
each((x, y) -> {
float cx = x + noise(x, y, scl, mag) - mag / 2f, cy = y + noise(x, y + 1525215f, scl, mag) - mag / 2f;
Tile other = tiles[Mathf.clamp((int)cx, 0, width-1)][Mathf.clamp((int)cy, 0, height-1)];
blocks[x][y] = other.block();
floors[x][y] = other.floor();
});
pass(tiles, (x, y) -> {
floor = floors[x][y];
block = blocks[x][y];
});
}
public void scatter(Tile[][] tiles, Block target, Block dst, float chance){
pass(tiles, (x, y) -> {
if(!Mathf.chance(chance)) return;
if(floor == target){
floor = dst;
}else if(block == target){
block = dst;
}
});
}
public void each(Intc2 r){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
r.get(x, y);
}
}
}
protected float noise(float x, float y, float scl, float mag){
return (float)sim2.octaveNoise2D(1f, 0f, 1f / scl, x + 0x361266f, y + 0x251259f) * mag;
}
public void pass(Tile[][] tiles, Intc2 r){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
floor = tiles[x][y].floor();
block = tiles[x][y].block();
ore = tiles[x][y].overlay();
r.get(x, y);
tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id);
}
}
}
public void brush(Tile[][] tiles, Array<Tile> path, int rad){
path.each(tile -> erase(tiles, tile.x, tile.y, rad));
}
public void erase(Tile[][] tiles, int cx, int cy, int rad){
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
int wx = cx + x, wy = cy + y;
if(Structs.inBounds(wx, wy, width, height) && Mathf.dst(x, y, 0, 0) <= rad){
Tile other = tiles[wx][wy];
other.setBlock(Blocks.air);
}
}
}
}
public Array<Tile> pathfind(Tile[][] tiles, int startX, int startY, int endX, int endY, TileHueristic th, DistanceHeuristic dh){
Tile start = tiles[startX][startY];
Tile end = tiles[endX][endY];
GridBits closed = new GridBits(width, height);
IntFloatMap costs = new IntFloatMap();
PriorityQueue<Tile> queue = new PriorityQueue<>(tiles.length * tiles[0].length / 2, (a, b) -> Float.compare(costs.get(a.pos(), 0f) + dh.cost(a.x, a.y, end.x, end.y), costs.get(b.pos(), 0f) + dh.cost(b.x, b.y, end.x, end.y)));
queue.add(start);
boolean found = false;
while(!queue.isEmpty()){
Tile next = queue.poll();
float baseCost = costs.get(next.pos(), 0f);
if(next == end){
found = true;
break;
}
closed.set(next.x, next.y);
for(Point2 point : Geometry.d4){
int newx = next.x + point.x, newy = next.y + point.y;
if(Structs.inBounds(newx, newy, width, height)){
Tile child = tiles[newx][newy];
if(!closed.get(child.x, child.y)){
closed.set(child.x, child.y);
child.rotation(child.relativeTo(next.x, next.y));
costs.put(child.pos(), th.cost(child) + baseCost);
queue.add(child);
}
}
}
}
Array<Tile> out = new Array<>();
if(!found) return out;
Tile current = end;
while(current != start){
out.add(current);
Point2 p = Geometry.d4(current.rotation());
current = tiles[current.x + p.x][current.y + p.y];
}
out.reverse();
return out;
}
public void inverseFloodFill(Tile[][] tiles, Tile start, Block block){
IntArray arr = new IntArray();
arr.add(start.pos());
while(!arr.isEmpty()){
int i = arr.pop();
int x = Pos.x(i), y = Pos.y(i);
tiles[x][y].cost = 2;
for(Point2 point : Geometry.d4){
int newx = x + point.x, newy = y + point.y;
if(Structs.inBounds(newx, newy, width, height)){
Tile child = tiles[newx][newy];
if(child.block() == Blocks.air && child.cost != 2){
child.cost = 2;
arr.add(child.pos());
}
}
}
}
for(int x = 0; x < width; x ++){
for(int y = 0; y < height; y++){
Tile tile = tiles[x][y];
if(tile.cost != 2 && tile.block() == Blocks.air){
tile.setBlock(block);
}
}
}
}
public interface DistanceHeuristic{
float cost(int x1, int y1, int x2, int y2);
}
public interface TileHueristic{
float cost(Tile tile);
}
}

View File

@@ -0,0 +1,23 @@
package mindustry.maps.generators;
import mindustry.game.*;
import mindustry.world.*;
public abstract class Generator{
public int width, height;
protected Schematic loadout;
public Generator(int width, int height){
this.width = width;
this.height = height;
}
public Generator(){
}
public void init(Schematic loadout){
this.loadout = loadout;
}
public abstract void generate(Tile[][] tiles);
}

View File

@@ -0,0 +1,170 @@
package mindustry.maps.generators;
import arc.struct.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.storage.*;
import static mindustry.Vars.*;
public class MapGenerator extends Generator{
private Map map;
private String mapName;
private Array<Decoration> decorations = Array.with(new Decoration(Blocks.stone, Blocks.rock, 0.003f));
/**
* The amount of final enemy spawns used. -1 to use everything in the map.
* This amount of enemy spawns is selected randomly from the map.
*/
public int enemySpawns = -1;
/** Whether floor is distorted along with blocks. */
public boolean distortFloor = false;
public MapGenerator(String mapName){
this.mapName = mapName;
}
public MapGenerator(String mapName, int enemySpawns){
this.mapName = mapName;
this.enemySpawns = enemySpawns;
}
public MapGenerator decor(Decoration... decor){
this.decorations.addAll(decor);
return this;
}
public void removePrefix(String name){
this.mapName = this.mapName.substring(name.length() + 1);
}
{
decor(new Decoration(Blocks.snow, Blocks.snowrock, 0.01), new Decoration(Blocks.ignarock, Blocks.pebbles, 0.03f));
}
public Map getMap(){
return map;
}
@Override
public void init(Schematic loadout){
this.loadout = loadout;
map = maps.loadInternalMap(mapName);
width = map.width;
height = map.height;
}
@Override
public void generate(Tile[][] tiles){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
tiles[x][y] = new Tile(x, y);
}
}
SaveIO.load(map.file);
Array<Point2> players = new Array<>();
Array<Point2> enemies = new Array<>();
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
if(tiles[x][y].block() instanceof CoreBlock && tiles[x][y].getTeam() == defaultTeam){
players.add(new Point2(x, y));
tiles[x][y].setBlock(Blocks.air);
}
if(tiles[x][y].overlay() == Blocks.spawn && enemySpawns != -1){
enemies.add(new Point2(x, y));
tiles[x][y].setOverlay(Blocks.air);
}
if(tiles[x][y].block() instanceof BlockPart){
tiles[x][y].setBlock(Blocks.air);
}
}
}
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
Tile tile = tiles[x][y];
for(Decoration decor : decorations){
if(x > 0 && y > 0 && (tiles[x - 1][y].block() == decor.wall || tiles[x][y - 1].block() == decor.wall)){
continue;
}
if(tile.block() == Blocks.air && !(decor.wall instanceof Floor) && tile.floor() == decor.floor && Mathf.chance(decor.chance)){
tile.setBlock(decor.wall);
}else if(tile.floor() == decor.floor && decor.wall.isOverlay() && Mathf.chance(decor.chance)){
tile.setOverlay(decor.wall);
}else if(tile.floor() == decor.floor && decor.wall.isFloor() && !decor.wall.isOverlay() && Mathf.chance(decor.chance)){
tile.setFloor((Floor)decor.wall);
}
}
if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock) && world.getZone() != null){
for(Item item : world.getZone().resources){
if(Mathf.chance(0.3)){
tile.entity.items.add(item, Math.min(Mathf.random(500), tile.block().itemCapacity));
}
}
}
}
}
if(enemySpawns != -1){
if(enemySpawns > enemies.size){
throw new IllegalArgumentException("Enemy spawn pool greater than map spawn number for map: " + mapName);
}
enemies.shuffle();
for(int i = 0; i < enemySpawns; i++){
Point2 point = enemies.get(i);
tiles[point.x][point.y].setOverlay(Blocks.spawn);
int rad = 10, frad = 12;
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
int wx = x + point.x, wy = y + point.y;
double dst = Mathf.dst(x, y);
if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){
Tile tile = tiles[wx][wy];
if(tile.overlay() != Blocks.spawn){
tile.clearOverlay();
}
}
}
}
}
}
Point2 core = players.random();
if(core == null){
throw new IllegalArgumentException("All zone maps must have a core.");
}
schematics.placeLoadout(loadout, core.x, core.y);
world.prepareTiles(tiles);
world.setMap(map);
}
public static class Decoration{
public final Block floor;
public final Block wall;
public final double chance;
public Decoration(Block floor, Block wall, double chance){
this.floor = floor;
this.wall = wall;
this.chance = chance;
}
}
}

View File

@@ -0,0 +1,44 @@
package mindustry.maps.generators;
import arc.struct.StringMap;
import mindustry.content.Blocks;
import mindustry.maps.Map;
import mindustry.world.Block;
import mindustry.world.Tile;
import static mindustry.Vars.world;
public abstract class RandomGenerator extends Generator{
protected Block floor;
protected Block block;
protected Block ore;
public RandomGenerator(int width, int height){
super(width, height);
}
@Override
public void generate(Tile[][] tiles){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
floor = Blocks.air;
block = Blocks.air;
ore = Blocks.air;
generate(x, y);
tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id);
}
}
decorate(tiles);
world.setMap(new Map(new StringMap()));
}
public abstract void decorate(Tile[][] tiles);
/**
* Sets {@link #floor} and {@link #block} to the correct values as output.
* Before this method is called, both are set to {@link Blocks#air} as defaults.
*/
public abstract void generate(int x, int y);
}

View File

@@ -0,0 +1,49 @@
package mindustry.maps.zonegen;
import arc.math.Mathf;
import mindustry.content.Blocks;
import mindustry.maps.generators.BasicGenerator;
import mindustry.world.Tile;
import static mindustry.Vars.schematics;
public class DesertWastesGenerator extends BasicGenerator{
public DesertWastesGenerator(int width, int height){
super(width, height, Blocks.oreCopper, Blocks.oreLead, Blocks.oreCoal, Blocks.oreCopper);
}
@Override
public void generate(int x, int y){
floor = Blocks.sand;
}
@Override
public void decorate(Tile[][] tiles){
ores(tiles);
terrain(tiles, Blocks.sandRocks, 60f, 1.5f, 0.9f);
int rand = 40;
int border = 25;
int spawnX = Mathf.clamp(30 + Mathf.range(rand), border, width - border), spawnY = Mathf.clamp(30 + Mathf.range(rand), border, height - border);
int endX = Mathf.clamp(width - 30 + Mathf.range(rand), border, width - border), endY = Mathf.clamp(height - 30 + Mathf.range(rand), border, height - border);
brush(tiles, pathfind(tiles, spawnX, spawnY, endX, endY, tile -> tile.solid() ? 5f : 0f, manhattan), 6);
brush(tiles, pathfind(tiles, spawnX, spawnY, endX, endY, tile -> tile.solid() ? 4f : 0f + (float)sim.octaveNoise2D(1, 1, 1f / 40f, tile.x, tile.y) * 20, manhattan), 5);
erase(tiles, endX, endY, 10);
erase(tiles, spawnX, spawnY, 20);
distort(tiles, 20f, 4f);
inverseFloodFill(tiles, tiles[spawnX][spawnY], Blocks.sandRocks);
noise(tiles, Blocks.salt, Blocks.saltRocks, 5, 0.6f, 200f, 0.55f);
noise(tiles, Blocks.darksand, Blocks.duneRocks, 5, 0.7f, 120f, 0.5f);
tech(tiles);
overlay(tiles, Blocks.sand, Blocks.pebbles, 0.15f, 5, 0.8f, 30f, 0.62f);
//scatter(tiles, Blocks.sandRocks, Blocks.creeptree, 1f);
tiles[endX][endY].setOverlay(Blocks.spawn);
schematics.placeLoadout(loadout, spawnX, spawnY);
}
}

View File

@@ -0,0 +1,45 @@
package mindustry.maps.zonegen;
import arc.math.Mathf;
import mindustry.content.Blocks;
import mindustry.maps.generators.BasicGenerator;
import mindustry.world.Tile;
import static mindustry.Vars.schematics;
public class OvergrowthGenerator extends BasicGenerator{
public OvergrowthGenerator(int width, int height){
super(width, height, Blocks.oreCopper, Blocks.oreLead, Blocks.oreCoal, Blocks.oreCopper);
}
@Override
public void generate(int x, int y){
floor = Blocks.moss;
}
@Override
public void decorate(Tile[][] tiles){
ores(tiles);
terrain(tiles, Blocks.sporePine, 70f, 1.4f, 1f);
int rand = 40;
int border = 25;
int spawnX = Mathf.clamp(30 + Mathf.range(rand), border, width - border), spawnY = Mathf.clamp(30 + Mathf.range(rand), border, height - border);
int endX = Mathf.clamp(width - 30 + Mathf.range(rand), border, width - border), endY = Mathf.clamp(height - 30 + Mathf.range(rand), border, height - border);
brush(tiles, pathfind(tiles, spawnX, spawnY, endX, endY, tile -> (tile.solid() ? 5f : 0f) + (float)sim.octaveNoise2D(1, 1, 1f / 50f, tile.x, tile.y) * 50, manhattan), 6);
brush(tiles, pathfind(tiles, spawnX, spawnY, endX, endY, tile -> (tile.solid() ? 4f : 0f) + (float)sim.octaveNoise2D(1, 1, 1f / 90f, tile.x+999, tile.y) * 70, manhattan), 5);
erase(tiles, endX, endY, 10);
erase(tiles, spawnX, spawnY, 20);
distort(tiles, 20f, 4f);
inverseFloodFill(tiles, tiles[spawnX][spawnY], Blocks.sporerocks);
noise(tiles, Blocks.darksandTaintedWater, Blocks.duneRocks, 4, 0.7f, 120f, 0.64f);
//scatter(tiles, Blocks.sporePine, Blocks.whiteTreeDead, 1f);
tiles[endX][endY].setOverlay(Blocks.spawn);
schematics.placeLoadout(loadout, spawnX, spawnY);
}
}