it is done
This commit is contained in:
233
core/src/mindustry/maps/Map.java
Normal file
233
core/src/mindustry/maps/Map.java
Normal 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
10
core/src/mindustry/maps/MapException.java
Normal file
10
core/src/mindustry/maps/MapException.java
Normal 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;
|
||||
}
|
||||
}
|
||||
57
core/src/mindustry/maps/MapPreviewLoader.java
Normal file
57
core/src/mindustry/maps/MapPreviewLoader.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
504
core/src/mindustry/maps/Maps.java
Normal file
504
core/src/mindustry/maps/Maps.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
core/src/mindustry/maps/filters/BlendFilter.java
Normal file
48
core/src/mindustry/maps/filters/BlendFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
core/src/mindustry/maps/filters/ClearFilter.java
Normal file
24
core/src/mindustry/maps/filters/ClearFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
core/src/mindustry/maps/filters/DistortFilter.java
Normal file
26
core/src/mindustry/maps/filters/DistortFilter.java
Normal 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();
|
||||
}
|
||||
}
|
||||
101
core/src/mindustry/maps/filters/FilterOption.java
Normal file
101
core/src/mindustry/maps/filters/FilterOption.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
105
core/src/mindustry/maps/filters/GenerateFilter.java
Normal file
105
core/src/mindustry/maps/filters/GenerateFilter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
core/src/mindustry/maps/filters/MedianFilter.java
Normal file
47
core/src/mindustry/maps/filters/MedianFilter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
86
core/src/mindustry/maps/filters/MirrorFilter.java
Normal file
86
core/src/mindustry/maps/filters/MirrorFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
35
core/src/mindustry/maps/filters/NoiseFilter.java
Normal file
35
core/src/mindustry/maps/filters/NoiseFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
core/src/mindustry/maps/filters/OreFilter.java
Normal file
32
core/src/mindustry/maps/filters/OreFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
core/src/mindustry/maps/filters/OreMedianFilter.java
Normal file
57
core/src/mindustry/maps/filters/OreMedianFilter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
42
core/src/mindustry/maps/filters/RiverNoiseFilter.java
Normal file
42
core/src/mindustry/maps/filters/RiverNoiseFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
core/src/mindustry/maps/filters/ScatterFilter.java
Normal file
38
core/src/mindustry/maps/filters/ScatterFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
core/src/mindustry/maps/filters/TerrainFilter.java
Normal file
42
core/src/mindustry/maps/filters/TerrainFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
262
core/src/mindustry/maps/generators/BasicGenerator.java
Normal file
262
core/src/mindustry/maps/generators/BasicGenerator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
23
core/src/mindustry/maps/generators/Generator.java
Normal file
23
core/src/mindustry/maps/generators/Generator.java
Normal 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);
|
||||
}
|
||||
170
core/src/mindustry/maps/generators/MapGenerator.java
Normal file
170
core/src/mindustry/maps/generators/MapGenerator.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
core/src/mindustry/maps/generators/RandomGenerator.java
Normal file
44
core/src/mindustry/maps/generators/RandomGenerator.java
Normal 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);
|
||||
}
|
||||
49
core/src/mindustry/maps/zonegen/DesertWastesGenerator.java
Normal file
49
core/src/mindustry/maps/zonegen/DesertWastesGenerator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
45
core/src/mindustry/maps/zonegen/OvergrowthGenerator.java
Normal file
45
core/src/mindustry/maps/zonegen/OvergrowthGenerator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user