Partial merge
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.rawTile(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.tiles);
|
||||
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);
|
||||
}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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
core/src/mindustry/maps/filters/BlendFilter.java
Normal file
54
core/src/mindustry/maps/filters/BlendFilter.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
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;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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 boolean isBuffered(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@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.within(x, y, 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 arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.maps.filters.FilterOption.*;
|
||||
|
||||
public class ClearFilter extends GenerateFilter{
|
||||
protected Block block = Blocks.air;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(new BlockOption("block", () -> block, b -> block = b, wallsOnly));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(){
|
||||
|
||||
if(in.block == block){
|
||||
in.block = Blocks.air;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
core/src/mindustry/maps/filters/CoreSpawnFilter.java
Normal file
44
core/src/mindustry/maps/filters/CoreSpawnFilter.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.maps.filters.FilterOption.*;
|
||||
import io.anuke.mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
/** Selects X spawns from the core spawn pool.*/
|
||||
public class CoreSpawnFilter extends GenerateFilter{
|
||||
int amount = 1;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
new SliderOption("amount", () -> amount, f -> amount = (int)f, 1, 10).display()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Tiles tiles, GenerateInput in){
|
||||
IntArray spawns = new IntArray();
|
||||
for(Tile tile : tiles){
|
||||
if(tile.getTeam() == defaultTeam && tile.block() instanceof CoreBlock){
|
||||
spawns.add(tile.pos());
|
||||
}
|
||||
}
|
||||
|
||||
spawns.shuffle();
|
||||
|
||||
int used = Math.min(spawns.size, amount);
|
||||
for(int i = used; i < spawns.size; i++){
|
||||
Tile tile = tiles.getp(spawns.get(i));
|
||||
world.removeBlock(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPost(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
32
core/src/mindustry/maps/filters/DistortFilter.java
Normal file
32
core/src/mindustry/maps/filters/DistortFilter.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.maps.filters.FilterOption.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
public class DistortFilter extends GenerateFilter{
|
||||
float scl = 40, mag = 5;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
new SliderOption("scale", () -> scl, f -> scl = f, 1f, 200f),
|
||||
new SliderOption("mag", () -> mag, f -> mag = f, 0.5f, 100f)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuffered(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
41
core/src/mindustry/maps/filters/EnemySpawnFilter.java
Normal file
41
core/src/mindustry/maps/filters/EnemySpawnFilter.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.maps.filters.FilterOption.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
/** Selects X spawns from the spawn pool.*/
|
||||
public class EnemySpawnFilter extends GenerateFilter{
|
||||
int amount = 1;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
new SliderOption("amount", () -> amount, f -> amount = (int)f, 1, 10).display()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Tiles tiles, GenerateInput in){
|
||||
IntArray spawns = new IntArray();
|
||||
for(Tile tile : tiles){
|
||||
if(tile.overlay() == Blocks.spawn){
|
||||
spawns.add(tile.pos());
|
||||
}
|
||||
}
|
||||
|
||||
spawns.shuffle();
|
||||
|
||||
int used = Math.min(spawns.size, amount);
|
||||
for(int i = used; i < spawns.size; i++){
|
||||
Tile tile = tiles.getp(spawns.get(i));
|
||||
tile.clearOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPost(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
112
core/src/mindustry/maps/filters/FilterOption.java
Normal file
112
core/src/mindustry/maps/filters/FilterOption.java
Normal file
@@ -0,0 +1,112 @@
|
||||
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;
|
||||
|
||||
boolean display;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public SliderOption display(){
|
||||
display = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
if(!display){
|
||||
table.add("$filter.option." + name);
|
||||
}else{
|
||||
table.label(() -> Core.bundle.get("filter.option." + name) + ": " + (int)getter.get());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
core/src/mindustry/maps/filters/GenerateFilter.java
Normal file
120
core/src/mindustry/maps/filters/GenerateFilter.java
Normal file
@@ -0,0 +1,120 @@
|
||||
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.*;
|
||||
|
||||
public abstract class GenerateFilter{
|
||||
protected transient float o = (float)(Math.random() * 10000000.0);
|
||||
protected transient long seed;
|
||||
protected transient GenerateInput in;
|
||||
|
||||
public void apply(Tiles tiles, GenerateInput in){
|
||||
this.in = in;
|
||||
for(Tile tile : tiles){
|
||||
in.apply(tile.x, tile.y, tile.floor(), tile.block(), tile.overlay());
|
||||
apply();
|
||||
|
||||
tile.setFloor(in.floor.asFloor());
|
||||
tile.setOverlay(in.floor.asFloor().isLiquid ? Blocks.air : in.ore);
|
||||
|
||||
if(!tile.block().synthetic() && !in.block.synthetic()){
|
||||
tile.setBlock(in.block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final void apply(GenerateInput in){
|
||||
this.in = in;
|
||||
apply();
|
||||
}
|
||||
|
||||
/** @return a new array of options for configuring this filter */
|
||||
public abstract FilterOption[] options();
|
||||
|
||||
/** apply the actual filter on the input */
|
||||
protected 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);
|
||||
}
|
||||
|
||||
/** @return whether this filter needs a read/write buffer (e.g. not a 1:1 tile mapping). */
|
||||
public boolean isBuffered(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return whether this filter can *only* be used while generating the map, e.g. is not undoable. */
|
||||
public boolean isPost(){
|
||||
return false;
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
core/src/mindustry/maps/filters/MedianFilter.java
Normal file
53
core/src/mindustry/maps/filters/MedianFilter.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
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();
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f),
|
||||
new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuffered(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
89
core/src/mindustry/maps/filters/MirrorFilter.java
Normal file
89
core/src/mindustry/maps/filters/MirrorFilter.java
Normal file
@@ -0,0 +1,89 @@
|
||||
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 Vec2 v1 = new Vec2(), v2 = new Vec2(), v3 = new Vec2();
|
||||
|
||||
int angle = 45;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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);
|
||||
|
||||
Vec2 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<Vec2> 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(Vec2 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(Vec2 a, Vec2 b, Vec2 c){
|
||||
return ((b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x)) > 0;
|
||||
}
|
||||
}
|
||||
36
core/src/mindustry/maps/filters/NoiseFilter.java
Normal file
36
core/src/mindustry/maps/filters/NoiseFilter.java
Normal file
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
core/src/mindustry/maps/filters/OreFilter.java
Normal file
33
core/src/mindustry/maps/filters/OreFilter.java
Normal file
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
core/src/mindustry/maps/filters/OreMedianFilter.java
Normal file
62
core/src/mindustry/maps/filters/OreMedianFilter.java
Normal file
@@ -0,0 +1,62 @@
|
||||
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();
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 12f),
|
||||
new SliderOption("percentile", () -> percentile, f -> percentile = f, 0f, 1f)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuffered(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
35
core/src/mindustry/maps/filters/RandomItemFilter.java
Normal file
35
core/src/mindustry/maps/filters/RandomItemFilter.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package mindustry.maps.filters;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
|
||||
public class RandomItemFilter extends GenerateFilter{
|
||||
public Array<ItemStack> drops = new Array<>();
|
||||
public float chance = 0.3f;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return new FilterOption[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Tiles tiles, GenerateInput in){
|
||||
for(Tile tile : tiles){
|
||||
if(tile.block() instanceof StorageBlock && !(tile.block() instanceof CoreBlock)){
|
||||
for(ItemStack stack : drops){
|
||||
if(Mathf.chance(chance)){
|
||||
tile.entity.items.add(stack.item, Math.min(Mathf.random(stack.amount), tile.block().itemCapacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPost(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
43
core/src/mindustry/maps/filters/RiverNoiseFilter.java
Normal file
43
core/src/mindustry/maps/filters/RiverNoiseFilter.java
Normal file
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
core/src/mindustry/maps/filters/ScatterFilter.java
Normal file
39
core/src/mindustry/maps/filters/ScatterFilter.java
Normal file
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
core/src/mindustry/maps/filters/TerrainFilter.java
Normal file
43
core/src/mindustry/maps/filters/TerrainFilter.java
Normal file
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
@Override
|
||||
public FilterOption[] options(){
|
||||
return Structs.arr(
|
||||
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(Tiles tiles){
|
||||
int seed = Mathf.random(99999999);
|
||||
sim.setSeed(seed);
|
||||
sim2.setSeed(seed + 1);
|
||||
super.generate(tiles);
|
||||
}
|
||||
|
||||
public void ores(Tiles 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(Tiles 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(Tiles 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.getn(x, y);
|
||||
this.floor = floor;
|
||||
if(tile.block().solid){
|
||||
this.block = block;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void overlay(Tiles 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.getn(x, y).floor() == floor){
|
||||
ore = block;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void tech(Tiles 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(Tiles 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.getn(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(Tiles 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(Tiles tiles, Intc2 r){
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
floor = tiles.getn(x, y).floor();
|
||||
block = tiles.getn(x, y).block();
|
||||
ore = tiles.getn(x, y).overlay();
|
||||
r.get(x, y);
|
||||
tiles.set(x, y, new Tile(x, y, floor.id, ore.id, block.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void brush(Tiles tiles, Array<Tile> path, int rad){
|
||||
path.each(tile -> erase(tiles, tile.x, tile.y, rad));
|
||||
}
|
||||
|
||||
public void erase(Tiles 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.getn(wx, wy);
|
||||
other.setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Array<Tile> pathfind(Tiles tiles, int startX, int startY, int endX, int endY, TileHueristic th, DistanceHeuristic dh){
|
||||
Tile start = tiles.getn(startX, startY);
|
||||
Tile end = tiles.getn(endX, endY);
|
||||
GridBits closed = new GridBits(width, height);
|
||||
IntFloatMap costs = new IntFloatMap();
|
||||
PriorityQueue<Tile> queue = new PriorityQueue<>(tiles.width() * tiles.height()/4, (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.getn(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.getn(current.x + p.x, current.y + p.y);
|
||||
}
|
||||
|
||||
out.reverse();
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public void inverseFloodFill(Tiles 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.getn(x, y).cost = 2;
|
||||
for(Point2 point : Geometry.d4){
|
||||
int newx = x + point.x, newy = y + point.y;
|
||||
if(tiles.in(newx, newy)){
|
||||
Tile child = tiles.getn(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.getn(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(Tiles tiles);
|
||||
}
|
||||
84
core/src/mindustry/maps/generators/MapGenerator.java
Normal file
84
core/src/mindustry/maps/generators/MapGenerator.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package mindustry.maps.generators;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
//TODO:
|
||||
//- limited # of enemy spawns as filter
|
||||
//- spawn loadout selection as filter
|
||||
//- configure map loadout, make 1 core the default
|
||||
public class MapGenerator extends Generator{
|
||||
private Map map;
|
||||
private String mapName;
|
||||
|
||||
public MapGenerator(String mapName){
|
||||
this.mapName = mapName;
|
||||
}
|
||||
|
||||
public void removePrefix(String name){
|
||||
this.mapName = this.mapName.substring(name.length() + 1);
|
||||
}
|
||||
|
||||
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(Tiles tiles){
|
||||
tiles.fill();
|
||||
|
||||
SaveIO.load(map.file);
|
||||
|
||||
for(Tile tile : tiles){
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean anyCores = false;
|
||||
|
||||
for(Tile tile : tiles){
|
||||
if(tile.overlay() == Blocks.spawn){
|
||||
int rad = 10;
|
||||
Geometry.circle(tile.x, tile.y, tiles.width(), tiles.height(), rad, (wx, wy) -> {
|
||||
if(tile.overlay().itemDrop != null){
|
||||
tile.clearOverlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(tile.block() instanceof CoreBlock && tile.getTeam() == defaultTeam){
|
||||
schematics.placeLoadout(loadout, tile.x, tile.y);
|
||||
anyCores = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!anyCores){
|
||||
throw new IllegalArgumentException("All zone maps must have a core.");
|
||||
}
|
||||
|
||||
world.prepareTiles(tiles);
|
||||
world.setMap(map);
|
||||
}
|
||||
}
|
||||
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(Tiles 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.set(x, y, new Tile(x, y, floor.id, ore.id, block.id));
|
||||
}
|
||||
}
|
||||
|
||||
decorate(tiles);
|
||||
|
||||
world.setMap(new Map(new StringMap()));
|
||||
}
|
||||
|
||||
public abstract void decorate(Tiles 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.*;
|
||||
|
||||
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(Tiles 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.getn(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.getn(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(Tiles 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.getn(spawnX, spawnY), Blocks.sporerocks);
|
||||
|
||||
noise(tiles, Blocks.darksandTaintedWater, Blocks.duneRocks, 4, 0.7f, 120f, 0.64f);
|
||||
//scatter(tiles, Blocks.sporePine, Blocks.whiteTreeDead, 1f);
|
||||
|
||||
tiles.getn(endX, endY).setOverlay(Blocks.spawn);
|
||||
schematics.placeLoadout(loadout, spawnX, spawnY);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user