Merging of map saves
This commit is contained in:
@@ -25,6 +25,8 @@ import java.util.Locale;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Vars{
|
||||
/** Whether to load locales.*/
|
||||
public static boolean loadLocales = true;
|
||||
/** IO buffer size. */
|
||||
public static final int bufferSize = 8192;
|
||||
/** global charset */
|
||||
@@ -104,8 +106,10 @@ public class Vars{
|
||||
public static FileHandle customMapDirectory;
|
||||
/** data subdirectory used for saves */
|
||||
public static FileHandle saveDirectory;
|
||||
/** old map file extension, for conversion */
|
||||
public static final String oldMapExtension = "mmap";
|
||||
/** map file extension */
|
||||
public static final String mapExtension = "mmap";
|
||||
public static final String mapExtension = "msav";
|
||||
/** save file extension */
|
||||
public static final String saveExtension = "msav";
|
||||
|
||||
@@ -141,19 +145,22 @@ public class Vars{
|
||||
public static void init(){
|
||||
Serialization.init();
|
||||
|
||||
//load locales
|
||||
String[] stra = Core.files.internal("locales").readString().split("\n");
|
||||
locales = new Locale[stra.length];
|
||||
for(int i = 0; i < locales.length; i++){
|
||||
String code = stra[i];
|
||||
if(code.contains("_")){
|
||||
locales[i] = new Locale(code.split("_")[0], code.split("_")[1]);
|
||||
}else{
|
||||
locales[i] = new Locale(code);
|
||||
if(loadLocales){
|
||||
//load locales
|
||||
String[] stra = Core.files.internal("locales").readString().split("\n");
|
||||
locales = new Locale[stra.length];
|
||||
for(int i = 0; i < locales.length; i++){
|
||||
String code = stra[i];
|
||||
if(code.contains("_")){
|
||||
locales[i] = new Locale(code.split("_")[0], code.split("_")[1]);
|
||||
}else{
|
||||
locales[i] = new Locale(code);
|
||||
}
|
||||
}
|
||||
|
||||
Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
|
||||
Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER));
|
||||
Version.init();
|
||||
|
||||
content = new ContentLoader();
|
||||
|
||||
@@ -171,12 +171,10 @@ public class BlockIndexer{
|
||||
|
||||
for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx++){
|
||||
for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty++){
|
||||
Tile other = world.tile(tx, ty);
|
||||
Tile other = world.ltile(tx, ty);
|
||||
|
||||
if(other == null) continue;
|
||||
|
||||
other = other.target();
|
||||
|
||||
if(other.entity == null || other.getTeam() != team || !pred.test(other) || !other.block().targetable)
|
||||
continue;
|
||||
|
||||
@@ -293,7 +291,7 @@ public class BlockIndexer{
|
||||
outer:
|
||||
for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){
|
||||
for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){
|
||||
Tile result = world.tile(x, y).target();
|
||||
Tile result = world.ltile(x, y);
|
||||
//when a targetable block is found, mark this quadrant as occupied and stop searching
|
||||
if(result.entity != null && result.getTeam() == data.team){
|
||||
structQuadrants[data.team.ordinal()].set(index);
|
||||
|
||||
@@ -83,7 +83,7 @@ public class Pathfinder{
|
||||
}
|
||||
|
||||
private boolean passable(Tile tile, Team team){
|
||||
return (!tile.solid()) || (tile.breakable() && (tile.target().getTeam() != team));
|
||||
return (!tile.solid()) || (tile.breakable() && (tile.getTeam() != team));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,10 +2,10 @@ package io.anuke.mindustry.ai;
|
||||
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.IntArray;
|
||||
import io.anuke.arc.math.Angles;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.Tmp;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.content.Fx;
|
||||
import io.anuke.mindustry.entities.Damage;
|
||||
@@ -14,48 +14,25 @@ import io.anuke.mindustry.entities.type.BaseUnit;
|
||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.mindustry.world.Pos;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class WaveSpawner{
|
||||
private Array<FlyerSpawn> flySpawns = new Array<>();
|
||||
private Array<GroundSpawn> groundSpawns = new Array<>();
|
||||
private IntArray loadedSpawns = new IntArray();
|
||||
private boolean spawning = false;
|
||||
|
||||
public WaveSpawner(){
|
||||
Events.on(WorldLoadEvent.class, e -> reset());
|
||||
}
|
||||
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeInt(groundSpawns.size);
|
||||
for(GroundSpawn spawn : groundSpawns){
|
||||
stream.writeInt(Pos.get(spawn.x, spawn.y));
|
||||
}
|
||||
}
|
||||
|
||||
public void read(DataInput stream) throws IOException{
|
||||
flySpawns.clear();
|
||||
groundSpawns.clear();
|
||||
loadedSpawns.clear();
|
||||
|
||||
int amount = stream.readInt();
|
||||
|
||||
for(int i = 0; i < amount; i++){
|
||||
loadedSpawns.add(stream.readInt());
|
||||
}
|
||||
}
|
||||
|
||||
public int countSpawns(){
|
||||
return groundSpawns.size;
|
||||
}
|
||||
|
||||
/** @return true if the player is near a ground spawn point. */
|
||||
public boolean playerNear(){
|
||||
return groundSpawns.count(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius) > 0;
|
||||
return groundSpawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius);
|
||||
}
|
||||
|
||||
public void spawnEnemies(){
|
||||
@@ -117,21 +94,11 @@ public class WaveSpawner{
|
||||
for(int x = 0; x < world.width(); x++){
|
||||
for(int y = 0; y < world.height(); y++){
|
||||
|
||||
if(world.tile(x, y).block() == Blocks.spawn){
|
||||
if(world.tile(x, y).overlay() == Blocks.spawn){
|
||||
addSpawns(x, y);
|
||||
|
||||
//hide spawnpoints, they have served their purpose
|
||||
world.tile(x, y).setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < loadedSpawns.size; i++){
|
||||
int pos = loadedSpawns.get(i);
|
||||
addSpawns(Pos.x(pos), Pos.y(pos));
|
||||
}
|
||||
|
||||
loadedSpawns.clear();
|
||||
}
|
||||
|
||||
private void addSpawns(int x, int y){
|
||||
|
||||
@@ -30,7 +30,7 @@ public class Blocks implements ContentList{
|
||||
public static Block
|
||||
|
||||
//environment
|
||||
air, part, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
|
||||
air, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
|
||||
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
|
||||
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, grass, salt,
|
||||
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
|
||||
@@ -88,15 +88,9 @@ public class Blocks implements ContentList{
|
||||
hasShadow = false;
|
||||
}
|
||||
|
||||
public void draw(Tile tile){
|
||||
}
|
||||
|
||||
public void load(){
|
||||
}
|
||||
|
||||
public void init(){
|
||||
}
|
||||
|
||||
public void draw(Tile tile){}
|
||||
public void load(){}
|
||||
public void init(){}
|
||||
public boolean isHidden(){
|
||||
return true;
|
||||
}
|
||||
@@ -109,14 +103,24 @@ public class Blocks implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
part = new BlockPart();
|
||||
//create special blockpart variants
|
||||
for(int dx = 0; dx < BlockPart.maxSize; dx++){
|
||||
for(int dy = 0; dy < BlockPart.maxSize; dy++){
|
||||
int fx = dx - BlockPart.maxSize/2, fy = dy - BlockPart.maxSize/2;
|
||||
if(fx != 0 || fy != 0){
|
||||
new BlockPart(fx, fy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spawn = new Block("spawn");
|
||||
spawn = new OverlayFloor("spawn"){
|
||||
public void draw(Tile tile){}
|
||||
};
|
||||
|
||||
//Registers build blocks from size 1-6
|
||||
//Registers build blocks
|
||||
//no reference is needed here since they can be looked up by name later
|
||||
for(int i = 1; i <= 6; i++){
|
||||
new BuildBlock("build" + i);
|
||||
for(int i = 1; i <= BuildBlock.maxSize; i++){
|
||||
new BuildBlock(i);
|
||||
}
|
||||
|
||||
deepwater = new Floor("deepwater"){{
|
||||
@@ -556,7 +560,7 @@ public class Blocks implements ContentList{
|
||||
drawer = tile -> {
|
||||
LiquidModule mod = tile.entity.liquids;
|
||||
|
||||
int rotation = rotate ? tile.getRotation() * 90 : 0;
|
||||
int rotation = rotate ? tile.rotation() * 90 : 0;
|
||||
|
||||
Draw.rect(reg(bottomRegion), tile.drawx(), tile.drawy(), rotation);
|
||||
|
||||
|
||||
@@ -398,9 +398,9 @@ public class Bullets implements ContentList{
|
||||
@Override
|
||||
public void hitTile(Bullet b, Tile tile){
|
||||
super.hit(b);
|
||||
tile = tile.target();
|
||||
tile = tile.link();
|
||||
|
||||
if(tile != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
|
||||
if(tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
|
||||
Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size);
|
||||
tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth());
|
||||
}
|
||||
@@ -424,7 +424,6 @@ public class Bullets implements ContentList{
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
//TODO add color to the bullet depending on the color of the flame it came from
|
||||
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.GRAY, b.fin());
|
||||
Fill.circle(b.x, b.y, 3f * b.fout());
|
||||
Draw.reset();
|
||||
|
||||
@@ -75,8 +75,6 @@ public class StatusEffects implements ContentList{
|
||||
armorMultiplier = 3f;
|
||||
damageMultiplier = 3f;
|
||||
speedMultiplier = 1.1f;
|
||||
//TODO custom effect
|
||||
//effect = Fx.overdriven;
|
||||
}};
|
||||
|
||||
shocked = new StatusEffect();
|
||||
|
||||
@@ -154,20 +154,6 @@ public class Zones implements ContentList{
|
||||
}};
|
||||
}};
|
||||
|
||||
/*
|
||||
crags = new Zone("crags", new MapGenerator("groundZero", 1)){{ //TODO implement
|
||||
baseLaunchCost = ItemStack.with(Items.copper, 300);
|
||||
startingItems = ItemStack.with(Items.copper, 200);
|
||||
conditionWave = 15;
|
||||
zoneRequirements = new Zone[]{frozenForest};
|
||||
blockRequirements = new Block[]{Blocks.copperWall};
|
||||
rules = () -> new Rules(){{
|
||||
waves = true;]
|
||||
waveTimer = true;
|
||||
waveSpacing = 60 * 80;
|
||||
}};
|
||||
}};*/
|
||||
|
||||
stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2)
|
||||
.dist(0f, false)
|
||||
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
|
||||
|
||||
@@ -90,17 +90,12 @@ public class ContentLoader{
|
||||
for(Array<Content> arr : contentMap){
|
||||
for(int i = 0; i < arr.size; i++){
|
||||
int id = arr.get(i).id;
|
||||
if(id < 0) id += 256;
|
||||
if(id != i){
|
||||
throw new IllegalArgumentException("Out-of-order IDs for content '" + arr.get(i) + "' (expected " + i + " but got " + id + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(blocks().size >= 256){
|
||||
throw new ImpendingDoomException("THE TIME HAS COME. More than 256 blocks have been created.");
|
||||
}
|
||||
|
||||
if(verbose){
|
||||
Log.info("--- CONTENT INFO ---");
|
||||
for(int k = 0; k < contentMap.length; k++){
|
||||
@@ -129,7 +124,7 @@ public class ContentLoader{
|
||||
/** Loads block colors. */
|
||||
public void loadColors(){
|
||||
Pixmap pixmap = new Pixmap(files.internal("sprites/block_colors.png"));
|
||||
for(int i = 0; i < 256; i++){
|
||||
for(int i = 0; i < pixmap.getWidth(); i++){
|
||||
if(blocks().size > i){
|
||||
int color = pixmap.getPixel(i, 0);
|
||||
|
||||
@@ -170,8 +165,6 @@ public class ContentLoader{
|
||||
}
|
||||
|
||||
public <T extends Content> T getByID(ContentType type, int id){
|
||||
//offset negative values by 256, as they are probably a product of byte overflow
|
||||
if(id < 0) id += 256;
|
||||
|
||||
if(temporaryMapper != null && temporaryMapper[type.ordinal()] != null && temporaryMapper[type.ordinal()].length != 0){
|
||||
if(temporaryMapper[type.ordinal()].length <= id || temporaryMapper[type.ordinal()][id] == null){
|
||||
@@ -243,10 +236,4 @@ public class ContentLoader{
|
||||
TypeTrait.registerType(Bullet.class, Bullet::new);
|
||||
TypeTrait.registerType(Lightning.class, Lightning::new);
|
||||
}
|
||||
|
||||
private class ImpendingDoomException extends RuntimeException{
|
||||
ImpendingDoomException(String s){
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import io.anuke.mindustry.gen.Call;
|
||||
import io.anuke.mindustry.input.*;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.*;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
@@ -120,7 +120,7 @@ public class Control implements ApplicationListener{
|
||||
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
|
||||
//the restart dialog can show info for any number of scenarios
|
||||
Call.onGameOver(event.winner);
|
||||
if(state.rules.zone != -1){
|
||||
if(state.rules.zone != null){
|
||||
//remove zone save on game over
|
||||
if(saves.getZoneSlot() != null){
|
||||
saves.getZoneSlot().delete();
|
||||
@@ -207,6 +207,23 @@ public class Control implements ApplicationListener{
|
||||
});
|
||||
}
|
||||
|
||||
public void playZone(Zone zone){
|
||||
ui.loadAnd(() -> {
|
||||
logic.reset();
|
||||
world.loadGenerator(zone.generator);
|
||||
state.rules = zone.rules.get();
|
||||
state.rules.zone = zone;
|
||||
for(Tile core : state.teams.get(defaultTeam).cores){
|
||||
for(ItemStack stack : zone.getStartingItems()){
|
||||
core.entity.items.add(stack.item, stack.amount);
|
||||
}
|
||||
}
|
||||
state.set(State.playing);
|
||||
control.saves.zoneSave();
|
||||
logic.play();
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isHighScore(){
|
||||
return hiscore;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ public class GameState{
|
||||
state = astate;
|
||||
}
|
||||
|
||||
public boolean isEditor(){
|
||||
return rules.editor;
|
||||
}
|
||||
|
||||
public boolean isPaused(){
|
||||
return (is(State.paused) && !Net.active()) || (gameOver && !Net.active());
|
||||
}
|
||||
|
||||
@@ -58,12 +58,6 @@ public class Logic implements ApplicationListener{
|
||||
public void play(){
|
||||
state.set(State.playing);
|
||||
state.wavetime = state.rules.waveSpacing * 2; //grace period of 2x wave time before game starts
|
||||
|
||||
//sometimes a map has no waves defined, they're defined in the zone rules
|
||||
if(world.getMap().getWaves() != DefaultWaves.get() || !world.isZone()){
|
||||
state.rules.spawns = world.getMap().getWaves();
|
||||
}
|
||||
|
||||
Events.fire(new PlayEvent());
|
||||
}
|
||||
|
||||
@@ -165,15 +159,17 @@ public class Logic implements ApplicationListener{
|
||||
Entities.update(groundEffectGroup);
|
||||
}
|
||||
|
||||
for(EntityGroup group : unitGroups){
|
||||
Entities.update(group);
|
||||
}
|
||||
if(!state.isEditor()){
|
||||
for(EntityGroup group : unitGroups){
|
||||
Entities.update(group);
|
||||
}
|
||||
|
||||
Entities.update(puddleGroup);
|
||||
Entities.update(shieldGroup);
|
||||
Entities.update(bulletGroup);
|
||||
Entities.update(tileGroup);
|
||||
Entities.update(fireGroup);
|
||||
Entities.update(puddleGroup);
|
||||
Entities.update(shieldGroup);
|
||||
Entities.update(bulletGroup);
|
||||
Entities.update(tileGroup);
|
||||
Entities.update(fireGroup);
|
||||
}
|
||||
Entities.update(playerGroup);
|
||||
|
||||
//effect group only contains item transfers in the headless version, update it!
|
||||
@@ -181,19 +177,21 @@ public class Logic implements ApplicationListener{
|
||||
Entities.update(effectGroup);
|
||||
}
|
||||
|
||||
for(EntityGroup group : unitGroups){
|
||||
if(group.isEmpty()) continue;
|
||||
if(!state.isEditor()){
|
||||
|
||||
collisions.collideGroups(bulletGroup, group);
|
||||
for(EntityGroup group : unitGroups){
|
||||
if(group.isEmpty()) continue;
|
||||
collisions.collideGroups(bulletGroup, group);
|
||||
}
|
||||
|
||||
collisions.collideGroups(bulletGroup, playerGroup);
|
||||
collisions.collideGroups(playerGroup, playerGroup);
|
||||
}
|
||||
|
||||
collisions.collideGroups(bulletGroup, playerGroup);
|
||||
collisions.collideGroups(playerGroup, playerGroup);
|
||||
|
||||
world.pathfinder.update();
|
||||
}
|
||||
|
||||
if(!Net.client() && !world.isInvalidMap()){
|
||||
if(!Net.client() && !world.isInvalidMap() && !state.isEditor()){
|
||||
checkGameOver();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public class NetClient implements ApplicationListener{
|
||||
}
|
||||
|
||||
//called on all clients
|
||||
@Remote(called = Loc.server, targets = Loc.server)
|
||||
@Remote(called = Loc.server, targets = Loc.server, variants = Variant.both)
|
||||
public static void sendMessage(String message, String sender, Player playersender){
|
||||
if(Vars.ui != null){
|
||||
Vars.ui.chatfrag.addMessage(message, sender);
|
||||
|
||||
@@ -12,8 +12,7 @@ import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.Rectangle;
|
||||
import io.anuke.arc.math.geom.Vector2;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.io.ByteBufferOutput;
|
||||
import io.anuke.arc.util.io.ReusableByteOutStream;
|
||||
import io.anuke.arc.util.io.*;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.entities.Entities;
|
||||
@@ -213,7 +212,7 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
public void sendWorldData(Player player, int clientID){
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream def = new DeflaterOutputStream(stream);
|
||||
DeflaterOutputStream def = new FastDeflaterOutputStream(stream);
|
||||
NetworkIO.writeWorld(player, def);
|
||||
WorldStream data = new WorldStream();
|
||||
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
||||
@@ -290,7 +289,7 @@ public class NetServer implements ApplicationListener{
|
||||
//auto-skip done requests
|
||||
if(req.breaking && tile.block() == Blocks.air){
|
||||
continue;
|
||||
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.getRotation() == req.rotation)){
|
||||
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){
|
||||
continue;
|
||||
}
|
||||
player.getPlaceQueue().addLast(req);
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.anuke.mindustry.core;
|
||||
|
||||
import io.anuke.annotations.Annotations.Nullable;
|
||||
import io.anuke.arc.*;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.IntArray;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.Geometry;
|
||||
@@ -18,8 +17,9 @@ import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.maps.*;
|
||||
import io.anuke.mindustry.maps.generators.Generator;
|
||||
import io.anuke.mindustry.type.*;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
@@ -28,17 +28,22 @@ public class World implements ApplicationListener{
|
||||
public final BlockIndexer indexer = new BlockIndexer();
|
||||
public final WaveSpawner spawner = new WaveSpawner();
|
||||
public final Pathfinder pathfinder = new Pathfinder();
|
||||
public final Context context = new Context();
|
||||
|
||||
private Map currentMap;
|
||||
private Tile[][] tiles;
|
||||
|
||||
private Array<Tile> tempTiles = new Array<>();
|
||||
private boolean generating, invalidMap;
|
||||
|
||||
public World(){
|
||||
maps.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
maps.loadLegacyMaps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
maps.dispose();
|
||||
@@ -97,6 +102,12 @@ public class World implements ApplicationListener{
|
||||
return tiles[x][y];
|
||||
}
|
||||
|
||||
public @Nullable Tile ltile(int x, int y){
|
||||
Tile tile = tile(x, y);
|
||||
if(tile == null) return null;
|
||||
return tile.block().linked(tile);
|
||||
}
|
||||
|
||||
public Tile rawTile(int x, int y){
|
||||
return tiles[x][y];
|
||||
}
|
||||
@@ -105,6 +116,10 @@ public class World implements ApplicationListener{
|
||||
return tile(Math.round(x / tilesize), Math.round(y / tilesize));
|
||||
}
|
||||
|
||||
public @Nullable Tile ltileWorld(float x, float y){
|
||||
return ltile(Math.round(x / tilesize), Math.round(y / tilesize));
|
||||
}
|
||||
|
||||
public int toTile(float coord){
|
||||
return Math.round(coord / tilesize);
|
||||
}
|
||||
@@ -160,6 +175,8 @@ public class World implements ApplicationListener{
|
||||
* A WorldLoadEvent will be fire.
|
||||
*/
|
||||
public void endMapLoad(){
|
||||
prepareTiles(tiles);
|
||||
|
||||
for(int x = 0; x < tiles.length; x++){
|
||||
for(int y = 0; y < tiles[0].length; y++){
|
||||
Tile tile = tiles[x][y];
|
||||
@@ -188,24 +205,7 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
|
||||
public Zone getZone(){
|
||||
return content.getByID(ContentType.zone, state.rules.zone);
|
||||
}
|
||||
|
||||
public void playZone(Zone zone){
|
||||
ui.loadAnd(() -> {
|
||||
logic.reset();
|
||||
state.rules = zone.rules.get();
|
||||
state.rules.zone = zone.id;
|
||||
loadGenerator(zone.generator);
|
||||
for(Tile core : state.teams.get(defaultTeam).cores){
|
||||
for(ItemStack stack : zone.getStartingItems()){
|
||||
core.entity.items.add(stack.item, stack.amount);
|
||||
}
|
||||
}
|
||||
state.set(State.playing);
|
||||
control.saves.zoneSave();
|
||||
logic.play();
|
||||
});
|
||||
return state.rules.zone;
|
||||
}
|
||||
|
||||
public void loadGenerator(Generator generator){
|
||||
@@ -213,24 +213,14 @@ public class World implements ApplicationListener{
|
||||
|
||||
createTiles(generator.width, generator.height);
|
||||
generator.generate(tiles);
|
||||
prepareTiles(tiles);
|
||||
|
||||
endMapLoad();
|
||||
}
|
||||
|
||||
public void loadMap(Map map){
|
||||
beginMapLoad();
|
||||
this.currentMap = map;
|
||||
|
||||
try{
|
||||
createTiles(map.width, map.height);
|
||||
for(int x = 0; x < map.width; x++){
|
||||
for(int y = 0; y < map.height; y++){
|
||||
tiles[x][y] = new Tile(x, y);
|
||||
}
|
||||
}
|
||||
MapIO.readTiles(map, tiles);
|
||||
prepareTiles(tiles);
|
||||
MapIO.loadMap(map);
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
if(!headless){
|
||||
@@ -242,7 +232,7 @@ public class World implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
endMapLoad();
|
||||
this.currentMap = map;
|
||||
|
||||
invalidMap = false;
|
||||
|
||||
@@ -289,16 +279,7 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
|
||||
public void removeBlock(Tile tile){
|
||||
if(!tile.block().isMultiblock() && !tile.isLinked()){
|
||||
tile.setBlock(Blocks.air);
|
||||
}else{
|
||||
Tile target = tile.target();
|
||||
Array<Tile> removals = target.getLinkedTiles(tempTiles);
|
||||
for(Tile toremove : removals){
|
||||
//note that setting a new block automatically unlinks it
|
||||
if(toremove != null) toremove.setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
tile.link().getLinkedTiles(other -> other.setBlock(Blocks.air));
|
||||
}
|
||||
|
||||
public void setBlock(Tile tile, Block block, Team team){
|
||||
@@ -318,8 +299,7 @@ public class World implements ApplicationListener{
|
||||
if(!(worldx == tile.x && worldy == tile.y)){
|
||||
Tile toplace = world.tile(worldx, worldy);
|
||||
if(toplace != null){
|
||||
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
|
||||
toplace.setTeam(team);
|
||||
toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,15 +307,6 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
public int transform(int packed, int oldWidth, int oldHeight, int newWidth, int shiftX, int shiftY){
|
||||
int x = packed % oldWidth;
|
||||
int y = packed / oldWidth;
|
||||
if(!Structs.inBounds(x, y, oldWidth, oldHeight)) return -1;
|
||||
x += shiftX;
|
||||
y += shiftY;
|
||||
return y * newWidth + x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raycast, but with world coordinates.
|
||||
*/
|
||||
@@ -413,11 +384,6 @@ public class World implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads raw map tile data into a Tile[][] array, setting up multiblocks, cliffs and ores. */
|
||||
void loadTileData(Tile[][] tiles){
|
||||
prepareTiles(tiles);
|
||||
}
|
||||
|
||||
public void addDarkness(Tile[][] tiles){
|
||||
byte[][] dark = new byte[tiles.length][tiles[0].length];
|
||||
byte[][] writeBuffer = new byte[tiles.length][tiles[0].length];
|
||||
@@ -456,7 +422,7 @@ public class World implements ApplicationListener{
|
||||
for(int y = 0; y < tiles[0].length; y++){
|
||||
Tile tile = tiles[x][y];
|
||||
if(tile.block().solid && !tile.block().synthetic()){
|
||||
tiles[x][y].setRotation(dark[x][y]);
|
||||
tiles[x][y].rotation(dark[x][y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,25 +469,47 @@ public class World implements ApplicationListener{
|
||||
if(!(worldx == x && worldy == y)){
|
||||
Tile toplace = world.tile(worldx, worldy);
|
||||
if(toplace != null){
|
||||
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
|
||||
toplace.setTeam(team);
|
||||
toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update cliffs, occlusion data
|
||||
for(int x = 0; x < tiles.length; x++){
|
||||
for(int y = 0; y < tiles[0].length; y++){
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.updateOcclusion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Raycaster{
|
||||
boolean accept(int x, int y);
|
||||
}
|
||||
|
||||
class Context implements WorldContext{
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
return tiles[x][y];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height){
|
||||
createTiles(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
return (tiles[x][y] = new Tile(x, y, floorID, overlayID, wallID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerating(){
|
||||
return World.this.isGenerating();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
beginMapLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endMapLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class DrawOperation{
|
||||
private LongArray array = new LongArray();
|
||||
@@ -24,32 +25,46 @@ public class DrawOperation{
|
||||
public void undo(MapEditor editor){
|
||||
for(int i = array.size - 1; i >= 0; i--){
|
||||
long l = array.get(i);
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.from(l));
|
||||
array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), get(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l))));
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l));
|
||||
}
|
||||
}
|
||||
|
||||
public void redo(MapEditor editor){
|
||||
for(int i = 0; i < array.size; i++){
|
||||
long l = array.get(i);
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.to(l));
|
||||
array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), get(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l))));
|
||||
set(editor, editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l));
|
||||
}
|
||||
}
|
||||
|
||||
void set(MapEditor editor, Tile tile, byte type, byte to){
|
||||
short get(Tile tile, byte type){
|
||||
if(type == OpType.floor.ordinal()){
|
||||
return tile.floorID();
|
||||
}else if(type == OpType.block.ordinal()){
|
||||
return tile.blockID();
|
||||
}else if(type == OpType.rotation.ordinal()){
|
||||
return tile.rotation();
|
||||
}else if(type == OpType.team.ordinal()){
|
||||
return tile.getTeamID();
|
||||
}else if(type == OpType.overlay.ordinal()){
|
||||
return tile.overlayID();
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid type.");
|
||||
}
|
||||
|
||||
void set(MapEditor editor, Tile tile, byte type, short to){
|
||||
editor.load(() -> {
|
||||
if(type == OpType.floor.ordinal()){
|
||||
tile.setFloor((Floor)content.block(to));
|
||||
}else if(type == OpType.block.ordinal()){
|
||||
Block block = content.block(to);
|
||||
tile.setBlock(block);
|
||||
if(block.isMultiblock()){
|
||||
editor.updateLinks(block, tile.x, tile.y);
|
||||
}
|
||||
world.setBlock(tile, block, tile.getTeam(), tile.rotation());
|
||||
}else if(type == OpType.rotation.ordinal()){
|
||||
tile.setRotation(to);
|
||||
tile.rotation(to);
|
||||
}else if(type == OpType.team.ordinal()){
|
||||
tile.setTeam(Team.all[to]);
|
||||
}else if(type == OpType.ore.ordinal()){
|
||||
}else if(type == OpType.overlay.ordinal()){
|
||||
tile.setOverlayID(to);
|
||||
}
|
||||
});
|
||||
@@ -61,8 +76,7 @@ public class DrawOperation{
|
||||
short x;
|
||||
short y;
|
||||
byte type;
|
||||
byte from;
|
||||
byte to;
|
||||
short value;
|
||||
}
|
||||
|
||||
public enum OpType{
|
||||
@@ -70,6 +84,6 @@ public class DrawOperation{
|
||||
block,
|
||||
rotation,
|
||||
team,
|
||||
ore
|
||||
overlay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.anuke.mindustry.editor;
|
||||
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.editor.DrawOperation.OpType;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.gen.TileOp;
|
||||
@@ -9,21 +10,23 @@ import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.*;
|
||||
import io.anuke.mindustry.world.modules.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.state;
|
||||
import static io.anuke.mindustry.Vars.ui;
|
||||
|
||||
//TODO somehow remove or replace this class with a more flexible solution
|
||||
public class EditorTile extends Tile{
|
||||
|
||||
public EditorTile(int x, int y, byte floor, byte wall){
|
||||
super(x, y, floor, wall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Team getTeam(){
|
||||
return Team.all[getTeamID()];
|
||||
public EditorTile(int x, int y, int floor, int overlay, int wall){
|
||||
super(x, y, floor, overlay, wall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloor(Floor type){
|
||||
if(state.is(State.playing)){
|
||||
super.setFloor(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if(type instanceof OverlayFloor){
|
||||
//don't place on liquids
|
||||
if(!floor.isLiquid){
|
||||
@@ -32,63 +35,91 @@ public class EditorTile extends Tile{
|
||||
return;
|
||||
}
|
||||
|
||||
Block previous = floor();
|
||||
Block ore = overlay();
|
||||
if(previous == type && ore == Blocks.air) return;
|
||||
if(floor == type && overlayID() == 0) return;
|
||||
if(overlayID() != 0) op(OpType.overlay, overlayID());
|
||||
if(floor != type) op(OpType.floor, floor.id);
|
||||
super.setFloor(type);
|
||||
//ore may get nullified so make sure to save edits
|
||||
if(overlay() != ore){
|
||||
op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), ore.id, overlay().id));
|
||||
}
|
||||
if(previous != type){
|
||||
op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous.id, type.id));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(Block type){
|
||||
Block previous = block;
|
||||
byte pteam = getTeamID();
|
||||
if(previous == type) return;
|
||||
super.setBlock(type);
|
||||
if(pteam != getTeamID()){
|
||||
op(TileOp.get(x, y, (byte)OpType.team.ordinal(), pteam, getTeamID()));
|
||||
if(state.is(State.playing)){
|
||||
super.setBlock(type);
|
||||
return;
|
||||
}
|
||||
op(TileOp.get(x, y, (byte)OpType.block.ordinal(), previous.id, type.id));
|
||||
|
||||
if(block == type) return;
|
||||
op(OpType.block, block.id);
|
||||
if(rotation != 0) op(OpType.rotation, rotation);
|
||||
if(team != 0) op(OpType.team, team);
|
||||
super.setBlock(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(Block type, Team team, int rotation){
|
||||
if(state.is(State.playing)){
|
||||
super.setBlock(type, team, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
setBlock(type);
|
||||
setTeam(team);
|
||||
rotation(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTeam(Team team){
|
||||
byte previous = getTeamID();
|
||||
if(previous == team.ordinal()) return;
|
||||
if(state.is(State.playing)){
|
||||
super.setTeam(team);
|
||||
return;
|
||||
}
|
||||
|
||||
if(getTeamID() == team.ordinal()) return;
|
||||
op(OpType.team, getTeamID());
|
||||
super.setTeam(team);
|
||||
op(TileOp.get(x, y, (byte)OpType.team.ordinal(), previous, (byte)team.ordinal()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotation(byte rotation){
|
||||
byte previous = getRotation();
|
||||
if(previous == rotation) return;
|
||||
super.setRotation(rotation);
|
||||
op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation));
|
||||
public void rotation(int rotation){
|
||||
if(state.is(State.playing)){
|
||||
super.rotation(rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
if(rotation == rotation()) return;
|
||||
op(OpType.rotation, rotation());
|
||||
super.rotation(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverlayID(byte ore){
|
||||
byte previous = getOverlayID();
|
||||
if(previous == ore) return;
|
||||
super.setOverlayID(ore);
|
||||
op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore));
|
||||
public void setOverlayID(short overlay){
|
||||
if(state.is(State.playing)){
|
||||
super.setOverlayID(overlay);
|
||||
return;
|
||||
}
|
||||
|
||||
if(overlayID() == overlay) return;
|
||||
op(OpType.overlay, overlay);
|
||||
super.setOverlayID(overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preChanged(){
|
||||
if(state.is(State.playing)){
|
||||
super.preChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
super.setTeam(Team.none);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void changed(){
|
||||
if(state.is(State.playing)){
|
||||
super.changed();
|
||||
return;
|
||||
}
|
||||
|
||||
entity = null;
|
||||
|
||||
if(block == null){
|
||||
@@ -102,8 +133,7 @@ public class EditorTile extends Tile{
|
||||
Block block = block();
|
||||
|
||||
if(block.hasEntity()){
|
||||
entity = block.newEntity();
|
||||
entity.health = block.health;
|
||||
entity = block.newEntity().init(this, false);
|
||||
entity.cons = new ConsumeModule(entity);
|
||||
if(block.hasItems) entity.items = new ItemModule();
|
||||
if(block.hasLiquids) entity.liquids = new LiquidModule();
|
||||
@@ -111,7 +141,7 @@ public class EditorTile extends Tile{
|
||||
}
|
||||
}
|
||||
|
||||
private static void op(long op){
|
||||
ui.editor.editor.addTileOp(op);
|
||||
private void op(OpType type, short value){
|
||||
ui.editor.editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,7 @@ public enum EditorTool{
|
||||
public void touched(MapEditor editor, int x, int y){
|
||||
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
|
||||
|
||||
Tile tile = editor.tile(x, y);
|
||||
|
||||
byte link = tile.getLinkByte();
|
||||
|
||||
if(tile.isLinked()){
|
||||
x -= (Pack.leftByte(link) - 8);
|
||||
y -= (Pack.rightByte(link) - 8);
|
||||
|
||||
tile = editor.tile(x, y);
|
||||
}
|
||||
|
||||
//do not.
|
||||
if(tile.isLinked()){
|
||||
return;
|
||||
}
|
||||
Tile tile = editor.tile(x, y).link();
|
||||
|
||||
editor.drawBlock = tile.block() == Blocks.air ? tile.overlay() == Blocks.air ? tile.floor() : tile.overlay() : tile.block();
|
||||
}
|
||||
@@ -87,7 +73,7 @@ public enum EditorTool{
|
||||
Block draw = editor.drawBlock;
|
||||
dest = draw instanceof OverlayFloor ? tile.overlay() : isfloor ? floor : block;
|
||||
|
||||
if(dest == draw || block == Blocks.part || block.isMultiblock()){
|
||||
if(dest == draw || block instanceof BlockPart || block.isMultiblock()){
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,7 +102,7 @@ public enum EditorTool{
|
||||
}
|
||||
|
||||
if(draw.rotate){
|
||||
write.setRotation((byte)editor.rotation);
|
||||
write.rotation((byte)editor.rotation);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
package io.anuke.mindustry.editor;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Pixmap;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.arc.util.Structs;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.gen.TileOp;
|
||||
import io.anuke.mindustry.io.LegacyMapIO;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
|
||||
import java.io.IOException;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class MapEditor{
|
||||
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20};
|
||||
|
||||
private ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
private final Context context = new Context();
|
||||
private StringMap tags = new StringMap();
|
||||
private MapRenderer renderer = new MapRenderer(this);
|
||||
private Tile[][] tiles;
|
||||
|
||||
private OperationStack stack = new OperationStack();
|
||||
private DrawOperation currentOp;
|
||||
@@ -32,7 +33,7 @@ public class MapEditor{
|
||||
public Block drawBlock = Blocks.stone;
|
||||
public Team drawTeam = Team.blue;
|
||||
|
||||
public ObjectMap<String, String> getTags(){
|
||||
public StringMap getTags(){
|
||||
return tags;
|
||||
}
|
||||
|
||||
@@ -40,39 +41,39 @@ public class MapEditor{
|
||||
reset();
|
||||
|
||||
loading = true;
|
||||
tiles = createTiles(width, height);
|
||||
createTiles(width, height);
|
||||
renderer.resize(width(), height());
|
||||
loading = false;
|
||||
}
|
||||
|
||||
public void beginEdit(Map map) throws IOException{
|
||||
public void beginEdit(Map map){
|
||||
reset();
|
||||
|
||||
loading = true;
|
||||
tiles = createTiles(map.width, map.height);
|
||||
tags.putAll(map.tags);
|
||||
MapIO.readTiles(map, tiles);
|
||||
MapIO.loadMap(map, context);
|
||||
checkLinkedTiles();
|
||||
renderer.resize(width(), height());
|
||||
loading = false;
|
||||
}
|
||||
|
||||
public void beginEdit(Tile[][] tiles){
|
||||
public void beginEdit(Pixmap pixmap){
|
||||
reset();
|
||||
|
||||
this.tiles = tiles;
|
||||
checkLinkedTiles();
|
||||
createTiles(pixmap.getWidth(), pixmap.getHeight());
|
||||
load(() -> LegacyMapIO.readPixmap(pixmap, tiles()));
|
||||
renderer.resize(width(), height());
|
||||
}
|
||||
|
||||
//adds missing blockparts
|
||||
public void checkLinkedTiles(){
|
||||
Tile[][] tiles = world.getTiles();
|
||||
|
||||
//clear block parts first
|
||||
for(int x = 0; x < width(); x++){
|
||||
for(int y = 0; y < height(); y++){
|
||||
if(tiles[x][y].block() == Blocks.part){
|
||||
if(tiles[x][y].block() instanceof BlockPart){
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
tiles[x][y].setLinkByte((byte)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,22 +81,8 @@ public class MapEditor{
|
||||
//set up missing blockparts
|
||||
for(int x = 0; x < width(); x++){
|
||||
for(int y = 0; y < height(); y++){
|
||||
Block drawBlock = tiles[x][y].block();
|
||||
if(drawBlock.isMultiblock()){
|
||||
int offsetx = -(drawBlock.size - 1) / 2;
|
||||
int offsety = -(drawBlock.size - 1) / 2;
|
||||
for(int dx = 0; dx < drawBlock.size; dx++){
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
|
||||
if(Structs.inBounds(worldx, worldy, width(), height()) && !(dx + offsetx == 0 && dy + offsety == 0)){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
tile.setBlock(Blocks.part);
|
||||
tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(tiles[x][y].block().isMultiblock()){
|
||||
world.setBlock(tiles[x][y], tiles[x][y].block(), tiles[x][y].getTeam());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,63 +95,41 @@ public class MapEditor{
|
||||
}
|
||||
|
||||
/** Creates a 2-D array of EditorTiles with stone as the floor block. */
|
||||
public Tile[][] createTiles(int width, int height){
|
||||
tiles = new Tile[width][height];
|
||||
private void createTiles(int width, int height){
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0);
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
public Map createMap(FileHandle file){
|
||||
return new Map(file, width(), height(), new ObjectMap<>(tags), true);
|
||||
return new Map(file, width(), height(), new StringMap(tags), true);
|
||||
}
|
||||
|
||||
private void reset(){
|
||||
clearOp();
|
||||
brushSize = 1;
|
||||
drawBlock = Blocks.stone;
|
||||
tags = new ObjectMap<>();
|
||||
tags = new StringMap();
|
||||
}
|
||||
|
||||
public Tile[][] tiles(){
|
||||
return tiles;
|
||||
return world.getTiles();
|
||||
}
|
||||
|
||||
public Tile tile(int x, int y){
|
||||
return tiles[x][y];
|
||||
return world.rawTile(x, y);
|
||||
}
|
||||
|
||||
public int width(){
|
||||
return tiles.length;
|
||||
return world.width();
|
||||
}
|
||||
|
||||
public int height(){
|
||||
return tiles[0].length;
|
||||
}
|
||||
|
||||
public void updateLinks(Block block, int x, int y){
|
||||
int offsetx = -(block.size - 1) / 2;
|
||||
int offsety = -(block.size - 1) / 2;
|
||||
|
||||
for(int dx = 0; dx < block.size; dx++){
|
||||
for(int dy = 0; dy < block.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
|
||||
if(!(worldx == x && worldy == y)){
|
||||
tile.setBlock(Blocks.part);
|
||||
tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return world.height();
|
||||
}
|
||||
|
||||
public void draw(int x, int y, boolean paint){
|
||||
@@ -177,45 +142,36 @@ public class MapEditor{
|
||||
|
||||
public void draw(int x, int y, boolean paint, Block drawBlock, double chance){
|
||||
boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air;
|
||||
Tile[][] tiles = world.getTiles();
|
||||
|
||||
if(drawBlock.isMultiblock()){
|
||||
|
||||
x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1);
|
||||
y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1);
|
||||
|
||||
int offsetx = -(drawBlock.size - 1) / 2;
|
||||
int offsety = -(drawBlock.size - 1) / 2;
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
for(int dx = 0; dx < drawBlock.size; dx++){
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
for(int dx = 0; dx < drawBlock.size; dx++){
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
Tile tile = tiles[worldx][worldy];
|
||||
|
||||
if(i == 1){
|
||||
tile.setBlock(Blocks.part);
|
||||
tile.setLinkByte(Pack.byteByte((byte)(dx + offsetx + 8), (byte)(dy + offsety + 8)));
|
||||
}else{
|
||||
byte link = tile.getLinkByte();
|
||||
Block block = tile.block();
|
||||
Block block = tile.block();
|
||||
|
||||
if(link != 0){
|
||||
removeLinked(worldx - (Pack.leftByte(link) - 8), worldy - (Pack.rightByte(link) - 8));
|
||||
}else if(block.isMultiblock()){
|
||||
removeLinked(worldx, worldy);
|
||||
}
|
||||
}
|
||||
//bail out if there's anything blocking the way
|
||||
if(block.isMultiblock() || block instanceof BlockPart){
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.updatePoint(worldx, worldy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tile tile = tiles[x][y];
|
||||
tile.setBlock(drawBlock);
|
||||
tile.setTeam(drawTeam);
|
||||
world.setBlock(tiles[x][y], drawBlock, drawTeam);
|
||||
}else{
|
||||
for(int rx = -brushSize; rx <= brushSize; rx++){
|
||||
for(int ry = -brushSize; ry <= brushSize; ry++){
|
||||
@@ -228,14 +184,8 @@ public class MapEditor{
|
||||
|
||||
Tile tile = tiles[wx][wy];
|
||||
|
||||
if(!isfloor){
|
||||
byte link = tile.getLinkByte();
|
||||
|
||||
if(tile.block().isMultiblock()){
|
||||
removeLinked(wx, wy);
|
||||
}else if(link != 0 && tiles[wx][wy].block() == Blocks.part){
|
||||
removeLinked(wx - (Pack.leftByte(link) - 8), wy - (Pack.rightByte(link) - 8));
|
||||
}
|
||||
if(!isfloor && (tile.isLinked() || tile.block().isMultiblock())){
|
||||
world.removeBlock(tile.link());
|
||||
}
|
||||
|
||||
if(isfloor){
|
||||
@@ -246,7 +196,7 @@ public class MapEditor{
|
||||
tile.setTeam(drawTeam);
|
||||
}
|
||||
if(drawBlock.rotate){
|
||||
tile.setRotation((byte)rotation);
|
||||
tile.rotation((byte)rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,22 +205,6 @@ public class MapEditor{
|
||||
}
|
||||
}
|
||||
|
||||
private void removeLinked(int x, int y){
|
||||
Block block = tiles[x][y].block();
|
||||
|
||||
int offsetx = -(block.size - 1) / 2;
|
||||
int offsety = -(block.size - 1) / 2;
|
||||
for(int dx = 0; dx < block.size; dx++){
|
||||
for(int dy = 0; dy < block.size; dy++){
|
||||
int worldx = x + dx + offsetx, worldy = y + dy + offsety;
|
||||
if(Structs.inBounds(worldx, worldy, width(), height())){
|
||||
tiles[worldx][worldy].setTeam(Team.none);
|
||||
tiles[worldx][worldy].setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MapRenderer renderer(){
|
||||
return renderer;
|
||||
}
|
||||
@@ -278,11 +212,11 @@ public class MapEditor{
|
||||
public void resize(int width, int height){
|
||||
clearOp();
|
||||
|
||||
Tile[][] previous = tiles;
|
||||
Tile[][] previous = world.getTiles();
|
||||
int offsetX = -(width - width()) / 2, offsetY = -(height - height()) / 2;
|
||||
loading = true;
|
||||
|
||||
tiles = new Tile[width][height];
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
int px = offsetX + x, py = offsetY + y;
|
||||
@@ -291,7 +225,7 @@ public class MapEditor{
|
||||
tiles[x][y].x = (short)x;
|
||||
tiles[x][y].y = (short)y;
|
||||
}else{
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (byte)0);
|
||||
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,4 +272,36 @@ public class MapEditor{
|
||||
|
||||
renderer.updatePoint(TileOp.x(data), TileOp.y(data));
|
||||
}
|
||||
|
||||
class Context implements WorldContext{
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
return world.tile(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height){
|
||||
world.createTiles(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
return (tiles()[x][y] = new EditorTile(x, y, floorID, overlayID, wallID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGenerating(){
|
||||
return world.isGenerating();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
world.beginMapLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
world.endMapLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.anuke.mindustry.editor;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.function.Consumer;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
@@ -17,8 +18,10 @@ import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.scene.ui.layout.Unit;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.io.JsonIO;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
@@ -40,6 +43,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
private MapGenerateDialog generateDialog;
|
||||
private ScrollPane pane;
|
||||
private FloatingDialog menu;
|
||||
private Rules lastSavedRules;
|
||||
private boolean saved = false;
|
||||
private boolean shownWithMap = false;
|
||||
private Array<Block> blocksOut = new Array<>();
|
||||
@@ -91,7 +95,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
Platform.instance.showFileChooser("$editor.loadmap", "Map Files", file -> ui.loadAnd(() -> {
|
||||
try{
|
||||
//TODO what if it's an image? users should be warned for their stupidity
|
||||
editor.beginEdit(MapIO.readMap(file, true));
|
||||
editor.beginEdit(MapIO.createMap(file, true));
|
||||
}catch(Exception e){
|
||||
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
|
||||
Log.err(e);
|
||||
@@ -103,9 +107,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
ui.loadAnd(() -> {
|
||||
try{
|
||||
Pixmap pixmap = new Pixmap(file);
|
||||
Tile[][] tiles = editor.createTiles(pixmap.getWidth(), pixmap.getHeight());
|
||||
editor.load(() -> MapIO.readLegacyPixmap(pixmap, tiles));
|
||||
editor.beginEdit(tiles);
|
||||
editor.beginEdit(pixmap);
|
||||
pixmap.dispose();
|
||||
}catch(Exception e){
|
||||
ui.showError(Core.bundle.format("editor.errorload", Strings.parseException(e, false)));
|
||||
Log.err(e);
|
||||
@@ -122,7 +125,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
if(!editor.getTags().containsKey("name")){
|
||||
editor.getTags().put("name", result.nameWithoutExtension());
|
||||
}
|
||||
MapIO.writeMap(result, editor.createMap(result), editor.tiles());
|
||||
MapIO.writeMap(result, editor.createMap(result));
|
||||
}catch(Exception e){
|
||||
ui.showError(Core.bundle.format("editor.errorsave", Strings.parseException(e, false)));
|
||||
Log.err(e);
|
||||
@@ -133,10 +136,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.addImageTextButton("$editor.ingame", "icon-arrow", isize, this::playtest).padTop(-5).size(swidth * 2f + 10, 60f);
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.addImageTextButton("$quit", "icon-back", isize, () -> {
|
||||
tryExit();
|
||||
menu.hide();
|
||||
}).padTop(-5).size(swidth * 2f + 10, 60f);
|
||||
}).size(swidth * 2f + 10, 60f);
|
||||
|
||||
resizeDialog = new MapResizeDialog(editor, (x, y) -> {
|
||||
if(!(editor.width() == x && editor.height() == y)){
|
||||
@@ -181,11 +188,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
});
|
||||
|
||||
shown(() -> {
|
||||
//clear units, rules and other unnecessary stuff
|
||||
logic.reset();
|
||||
saved = true;
|
||||
if(!Core.settings.getBool("landscape")) Platform.instance.beginForceLandscape();
|
||||
editor.clearOp();
|
||||
Core.scene.setScrollFocus(view);
|
||||
if(!shownWithMap){
|
||||
state.rules = new Rules();
|
||||
editor.beginEdit(200, 200);
|
||||
}
|
||||
shownWithMap = false;
|
||||
@@ -205,8 +215,47 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
drawDefaultBackground(x, y);
|
||||
}
|
||||
|
||||
public void resumeEditing(){
|
||||
state.set(State.menu);
|
||||
shownWithMap = true;
|
||||
show();
|
||||
state.rules = (lastSavedRules == null ? new Rules() : lastSavedRules);
|
||||
lastSavedRules = null;
|
||||
editor.renderer().updateAll();
|
||||
}
|
||||
|
||||
private void playtest(){
|
||||
menu.hide();
|
||||
ui.loadAnd(() -> {
|
||||
lastSavedRules = state.rules;
|
||||
hide();
|
||||
//only reset the player; logic.reset() will clear entities, which we do not want
|
||||
player.reset();
|
||||
state.rules = Gamemode.editor.apply(new Rules());
|
||||
world.setMap(new Map(StringMap.of(
|
||||
"name", "Editor Playtesting",
|
||||
"width", editor.width(),
|
||||
"height", editor.height()
|
||||
)));
|
||||
world.endMapLoad();
|
||||
//add entities so they update. is this really needed?
|
||||
for(int x = 0; x < world.width(); x++){
|
||||
for(int y = 0; y < world.height(); y++){
|
||||
Tile tile = world.rawTile(x, y);
|
||||
if(tile.entity != null){
|
||||
tile.entity.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
player.setDead(false);
|
||||
logic.play();
|
||||
});
|
||||
}
|
||||
|
||||
private void save(){
|
||||
String name = editor.getTags().get("name", "").trim();
|
||||
editor.getTags().put("rules", JsonIO.write(state.rules));
|
||||
|
||||
if(name.isEmpty()){
|
||||
infoDialog.show();
|
||||
@@ -216,7 +265,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
if(map != null && !map.custom){
|
||||
ui.showError("$editor.save.overwrite");
|
||||
}else{
|
||||
world.maps.saveMap(editor.getTags(), editor.tiles());
|
||||
world.maps.saveMap(editor.getTags());
|
||||
ui.showInfoFade("$editor.saved");
|
||||
}
|
||||
}
|
||||
@@ -281,9 +330,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
public void beginEditMap(FileHandle file){
|
||||
ui.loadAnd(() -> {
|
||||
try{
|
||||
Map map = MapIO.readMap(file, true);
|
||||
shownWithMap = true;
|
||||
editor.beginEdit(map);
|
||||
editor.beginEdit(MapIO.createMap(file, true));
|
||||
show();
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
|
||||
@@ -224,7 +224,7 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
Tile tile = editor.tile(x, y);
|
||||
input.begin(editor, x, y, tile.floor(), tile.block(), tile.overlay());
|
||||
filter.apply(input);
|
||||
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.getRotation());
|
||||
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
Tile tile = editor.tile(x, y);
|
||||
DummyTile write = writeTiles[x][y];
|
||||
|
||||
tile.setRotation(write.rotation);
|
||||
tile.rotation(write.rotation);
|
||||
tile.setFloor((Floor)content.block(write.floor));
|
||||
tile.setBlock(content.block(write.block));
|
||||
tile.setTeam(Team.all[write.team]);
|
||||
@@ -322,7 +322,8 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
}
|
||||
|
||||
public static class DummyTile{
|
||||
public byte block, floor, ore, team, rotation;
|
||||
public byte team, rotation;
|
||||
public short block, floor, ore;
|
||||
|
||||
void set(Block floor, Block wall, Block ore, Team team, int rotation){
|
||||
this.floor = floor.id;
|
||||
@@ -341,7 +342,7 @@ public class MapGenerateDialog extends FloatingDialog{
|
||||
}
|
||||
|
||||
void set(Tile other){
|
||||
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.getRotation());
|
||||
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.rotation());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.scene.ui.TextArea;
|
||||
import io.anuke.arc.scene.ui.TextField;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.ui.dialogs.CustomRulesDialog;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
|
||||
public class MapInfoDialog extends FloatingDialog{
|
||||
private final MapEditor editor;
|
||||
private final WaveInfoDialog waveinfo;
|
||||
private final CustomRulesDialog ruleinfo = new CustomRulesDialog();
|
||||
|
||||
public MapInfoDialog(MapEditor editor){
|
||||
super("$editor.mapinfo");
|
||||
@@ -36,7 +40,6 @@ public class MapInfoDialog extends FloatingDialog{
|
||||
name.setMessageText("$unknown");
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.add("$editor.description").padRight(8).left();
|
||||
|
||||
TextArea description = cont.addArea(tags.get("description", ""), "textarea", text -> {
|
||||
@@ -44,7 +47,6 @@ public class MapInfoDialog extends FloatingDialog{
|
||||
}).size(400f, 140f).get();
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.add("$editor.author").padRight(8).left();
|
||||
|
||||
TextField author = cont.addField(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
|
||||
@@ -54,14 +56,13 @@ public class MapInfoDialog extends FloatingDialog{
|
||||
}).size(400, 55f).get();
|
||||
author.setMessageText("$unknown");
|
||||
|
||||
cont.row();
|
||||
cont.add("$editor.rules").padRight(8).left();
|
||||
cont.addButton("$edit", () -> ruleinfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules())).left().width(200f);;
|
||||
|
||||
cont.row();
|
||||
cont.add("$editor.waves").padRight(8).left();
|
||||
cont.table(t -> {
|
||||
t.add().growX();
|
||||
t.label(() -> tags.containsKey("waves") ? "" : Core.bundle.get("editor.default")).left();
|
||||
t.add().growX();
|
||||
t.addButton("$edit", waveinfo::show).growY().width(200f);
|
||||
}).size(400, 55f);
|
||||
cont.addButton("$edit", waveinfo::show).left().width(200f);
|
||||
|
||||
name.change();
|
||||
description.change();
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.graphics.IndexedRenderer;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
@@ -84,7 +85,6 @@ public class MapRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void updatePoint(int x, int y){
|
||||
//TODO spread out over multiple frames?
|
||||
updates.add(x + y * width);
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ public class MapRenderer implements Disposable{
|
||||
int idxWall = (wx % chunksize) + (wy % chunksize) * chunksize;
|
||||
int idxDecal = (wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize;
|
||||
|
||||
if(wall != Blocks.air && (wall.synthetic() || wall == Blocks.part)){
|
||||
if(wall != Blocks.air && (wall.synthetic() || wall instanceof BlockPart)){
|
||||
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
|
||||
|
||||
if(wall.rotate){
|
||||
mesh.draw(idxWall, region,
|
||||
wx * tilesize + wall.offset(), wy * tilesize + wall.offset(),
|
||||
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.getRotation() * 90 - 90);
|
||||
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.rotation() * 90 - 90);
|
||||
}else{
|
||||
mesh.draw(idxWall, region,
|
||||
wx * tilesize + wall.offset() + (tilesize - region.getWidth() * Draw.scl) / 2f,
|
||||
|
||||
@@ -85,9 +85,8 @@ public class Damage{
|
||||
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
|
||||
tr.trns(angle, length);
|
||||
world.raycastEachWorld(x, y, x + tr.x, y + tr.y, (cx, cy) -> {
|
||||
Tile tile = world.tile(cx, cy);
|
||||
if(tile != null) tile = tile.target();
|
||||
if(tile != null && tile.entity != null && tile.target().getTeamID() != team.ordinal() && tile.entity.collide(hitter)){
|
||||
Tile tile = world.ltile(cx, cy);
|
||||
if(tile != null && tile.entity != null && tile.getTeamID() != team.ordinal() && tile.entity.collide(hitter)){
|
||||
tile.entity.collision(hitter);
|
||||
hitter.getBulletType().hit(hitter, tile.worldx(), tile.worldy());
|
||||
}
|
||||
@@ -216,12 +215,10 @@ public class Damage{
|
||||
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
|
||||
|
||||
bits.set(bitOffset + x, bitOffset + y);
|
||||
Tile tile = world.tile(startx + x, starty + y);
|
||||
Tile tile = world.ltile(startx + x, starty + y);
|
||||
|
||||
if(scaledDamage <= 0 || tile == null) continue;
|
||||
|
||||
tile = tile.target();
|
||||
|
||||
//apply damage to entity if needed
|
||||
if(tile.entity != null && tile.getTeam() != team){
|
||||
int health = (int)tile.entity.health;
|
||||
|
||||
@@ -7,10 +7,11 @@ import io.anuke.arc.function.Consumer;
|
||||
import io.anuke.arc.function.Predicate;
|
||||
import io.anuke.arc.graphics.Camera;
|
||||
import io.anuke.arc.math.geom.Rectangle;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.entities.traits.DrawTrait;
|
||||
import io.anuke.mindustry.entities.traits.Entity;
|
||||
|
||||
import static io.anuke.mindustry.Vars.collisions;
|
||||
|
||||
public class Entities{
|
||||
public static final int maxLeafObjects = 4;
|
||||
private static final Array<EntityGroup<?>> groupArray = new Array<>();
|
||||
@@ -48,7 +49,7 @@ public class Entities{
|
||||
group.updateEvents();
|
||||
|
||||
if(group.useTree()){
|
||||
Vars.collisions.updatePhysics(group);
|
||||
collisions.updatePhysics(group);
|
||||
}
|
||||
|
||||
for(Entity e : group.all()){
|
||||
|
||||
@@ -225,9 +225,8 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool
|
||||
if(type.hitTiles && collidesTiles() && !supressCollision && initialized){
|
||||
world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> {
|
||||
|
||||
Tile tile = world.tile(x, y);
|
||||
Tile tile = world.ltile(x, y);
|
||||
if(tile == null) return false;
|
||||
tile = tile.target();
|
||||
|
||||
if(tile.entity != null && tile.entity.collide(this) && type.collides(this, tile) && !tile.entity.isDead() && (type.collidesTeam || tile.getTeam() != team)){
|
||||
if(tile.getTeam() != team){
|
||||
|
||||
@@ -72,6 +72,11 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float lifetime(){
|
||||
return lifetime;
|
||||
@@ -98,7 +103,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
|
||||
return;
|
||||
}
|
||||
|
||||
TileEntity entity = tile.target().entity;
|
||||
TileEntity entity = tile.link().entity;
|
||||
boolean damage = entity != null;
|
||||
|
||||
float flammability = baseFlammability + puddleFlammability;
|
||||
@@ -151,7 +156,7 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
this.loadedPosition = stream.readInt();
|
||||
this.lifetime = stream.readFloat();
|
||||
this.time = stream.readFloat();
|
||||
|
||||
@@ -143,6 +143,11 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
|
||||
return liquid.flammability * amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Rectangle rectangle){
|
||||
rectangle.setCenter(x, y).setSize(tilesize);
|
||||
@@ -201,7 +206,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
|
||||
}
|
||||
});
|
||||
|
||||
if(liquid.temperature > 0.7f && (tile.target().entity != null) && Mathf.chance(0.3 * Time.delta())){
|
||||
if(liquid.temperature > 0.7f && (tile.link().entity != null) && Mathf.chance(0.3 * Time.delta())){
|
||||
Fire.create(tile);
|
||||
}
|
||||
|
||||
@@ -245,7 +250,7 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
this.loadedPosition = stream.readInt();
|
||||
this.x = stream.readFloat();
|
||||
this.y = stream.readFloat();
|
||||
|
||||
@@ -40,10 +40,98 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
||||
float placeDistance = 220f;
|
||||
float mineDistance = 70f;
|
||||
|
||||
//due to iOS wierdness
|
||||
class BuildDataStatic{
|
||||
static Array<BuildRequest> removal = new Array<>();
|
||||
static Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()};
|
||||
/**
|
||||
* Update building mechanism for this unit.
|
||||
* This includes mining.
|
||||
*/
|
||||
default void updateBuilding(){
|
||||
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : placeDistance;
|
||||
Unit unit = (Unit)this;
|
||||
//remove already completed build requests
|
||||
removal.clear();
|
||||
for(BuildRequest req : getPlaceQueue()){
|
||||
removal.add(req);
|
||||
}
|
||||
|
||||
getPlaceQueue().clear();
|
||||
|
||||
for(BuildRequest request : removal){
|
||||
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
|
||||
(!request.breaking && (world.tile(request.x, request.y).rotation() == request.rotation || !request.block.rotate)
|
||||
&& world.tile(request.x, request.y).block() == request.block))){
|
||||
getPlaceQueue().addLast(request);
|
||||
}
|
||||
}
|
||||
|
||||
BuildRequest current = getCurrentRequest();
|
||||
|
||||
//update mining here
|
||||
if(current == null){
|
||||
if(getMineTile() != null){
|
||||
updateMining();
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
setMineTile(null);
|
||||
}
|
||||
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
|
||||
if(dst(tile) > finalPlaceDst){
|
||||
if(getPlaceQueue().size > 1){
|
||||
getPlaceQueue().removeFirst();
|
||||
getPlaceQueue().addLast(current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(tile.block() instanceof BuildBlock)){
|
||||
if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
|
||||
Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
|
||||
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
|
||||
Call.beginBreak(getTeam(), current.x, current.y);
|
||||
}else{
|
||||
getPlaceQueue().removeFirst();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TileEntity core = unit.getClosestCore();
|
||||
|
||||
//if there is no core to build with or no build entity, stop building!
|
||||
if((core == null && !state.rules.infiniteResources) || !(tile.entity instanceof BuildEntity)){
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise, update it.
|
||||
BuildEntity entity = tile.entity();
|
||||
|
||||
if(entity == null){
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.dst(tile) <= finalPlaceDst){
|
||||
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f);
|
||||
}
|
||||
|
||||
//progress is synced, thus not updated clientside
|
||||
if(!Net.client()){
|
||||
//deconstructing is 2x as fast
|
||||
if(current.breaking){
|
||||
entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}else{
|
||||
entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}
|
||||
|
||||
current.progress = entity.progress();
|
||||
}else{
|
||||
entity.progress = current.progress;
|
||||
}
|
||||
|
||||
if(!current.initialized){
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking)));
|
||||
current.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the queue for storing build requests. */
|
||||
@@ -148,97 +236,10 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
||||
return getPlaceQueue().size == 0 ? null : getPlaceQueue().first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update building mechanism for this unit.
|
||||
* This includes mining.
|
||||
*/
|
||||
default void updateBuilding(){
|
||||
Unit unit = (Unit)this;
|
||||
//remove already completed build requests
|
||||
removal.clear();
|
||||
for(BuildRequest req : getPlaceQueue()){
|
||||
removal.add(req);
|
||||
}
|
||||
|
||||
getPlaceQueue().clear();
|
||||
|
||||
for(BuildRequest request : removal){
|
||||
if(!((request.breaking && world.tile(request.x, request.y).block() == Blocks.air) ||
|
||||
(!request.breaking && (world.tile(request.x, request.y).getRotation() == request.rotation || !request.block.rotate)
|
||||
&& world.tile(request.x, request.y).block() == request.block))){
|
||||
getPlaceQueue().addLast(request);
|
||||
}
|
||||
}
|
||||
|
||||
BuildRequest current = getCurrentRequest();
|
||||
|
||||
//update mining here
|
||||
if(current == null){
|
||||
if(getMineTile() != null){
|
||||
updateMining();
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
setMineTile(null);
|
||||
}
|
||||
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
|
||||
if(dst(tile) > placeDistance){
|
||||
if(getPlaceQueue().size > 1){
|
||||
getPlaceQueue().removeFirst();
|
||||
getPlaceQueue().addLast(current);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(tile.block() instanceof BuildBlock)){
|
||||
if(canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
|
||||
Call.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
|
||||
}else if(canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
|
||||
Call.beginBreak(getTeam(), current.x, current.y);
|
||||
}else{
|
||||
getPlaceQueue().removeFirst();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TileEntity core = unit.getClosestCore();
|
||||
|
||||
//if there is no core to build with or no build entity, stop building!
|
||||
if(core == null || !(tile.entity instanceof BuildEntity)){
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise, update it.
|
||||
BuildEntity entity = tile.entity();
|
||||
|
||||
if(entity == null){
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.dst(tile) <= placeDistance){
|
||||
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f);
|
||||
}
|
||||
|
||||
//progress is synced, thus not updated clientside
|
||||
if(!Net.client()){
|
||||
//deconstructing is 2x as fast
|
||||
if(current.breaking){
|
||||
entity.deconstruct(unit, core, 2f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}else{
|
||||
entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
|
||||
}
|
||||
|
||||
current.progress = entity.progress();
|
||||
}else{
|
||||
entity.progress = current.progress;
|
||||
}
|
||||
|
||||
if(!current.initialized){
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), this, current.breaking)));
|
||||
current.initialized = true;
|
||||
}
|
||||
//due to iOS wierdness, this is apparently required
|
||||
class BuildDataStatic{
|
||||
static Array<BuildRequest> removal = new Array<>();
|
||||
static Vector2[] tmptr = new Vector2[]{new Vector2(), new Vector2(), new Vector2(), new Vector2()};
|
||||
}
|
||||
|
||||
/** Do not call directly. */
|
||||
@@ -291,7 +292,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
|
||||
|
||||
Tile tile = world.tile(request.x, request.y);
|
||||
|
||||
if(dst(tile) > placeDistance){
|
||||
if(dst(tile) > placeDistance && !state.isEditor()){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ package io.anuke.mindustry.entities.traits;
|
||||
* Marks an entity as serializable.
|
||||
*/
|
||||
public interface SaveTrait extends Entity, TypeTrait, Saveable{
|
||||
byte version();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,5 @@ import java.io.*;
|
||||
|
||||
public interface Saveable{
|
||||
void writeSave(DataOutput stream) throws IOException;
|
||||
|
||||
void readSave(DataInput stream) throws IOException;
|
||||
void readSave(DataInput stream, byte version) throws IOException;
|
||||
}
|
||||
|
||||
@@ -41,15 +41,6 @@ public interface SyncTrait extends Entity, TypeTrait{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Whether this entity is clipped and not synced when out of viewport. */
|
||||
default boolean isClipped(){
|
||||
return true;
|
||||
}
|
||||
|
||||
default float clipSize(){
|
||||
return (this instanceof DrawTrait ? ((DrawTrait)this).drawSize() : 8f);
|
||||
}
|
||||
|
||||
//Read and write sync data, usually position
|
||||
void write(DataOutput data) throws IOException;
|
||||
|
||||
|
||||
@@ -23,9 +23,7 @@ public interface TypeTrait{
|
||||
lastRegisteredID[0]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a syncable type by ID.
|
||||
*/
|
||||
/**Gets a syncable type by ID.*/
|
||||
static Supplier<? extends TypeTrait> getTypeByID(int id){
|
||||
if(id == -1){
|
||||
throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?");
|
||||
|
||||
@@ -306,11 +306,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
return type.hitsize * 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float clipSize(){
|
||||
return isBoss() ? 10000000000f : super.clipSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(){
|
||||
Call.onUnitDeath(this);
|
||||
@@ -338,6 +333,11 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
return unitGroups[team.ordinal()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSave(DataOutput stream) throws IOException{
|
||||
super.writeSave(stream);
|
||||
@@ -346,8 +346,8 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
super.readSave(stream);
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
super.readSave(stream, version);
|
||||
byte type = stream.readByte();
|
||||
this.spawner = stream.readInt();
|
||||
|
||||
@@ -365,7 +365,9 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
|
||||
@Override
|
||||
public void read(DataInput data) throws IOException{
|
||||
float lastx = x, lasty = y, lastrot = rotation;
|
||||
super.readSave(data);
|
||||
|
||||
super.readSave(data, version());
|
||||
|
||||
this.type = content.getByID(ContentType.unit, data.readByte());
|
||||
this.spawner = data.readInt();
|
||||
|
||||
|
||||
@@ -432,7 +432,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
if(getCurrentRequest() == request && request.progress > 0.001f) continue;
|
||||
|
||||
if(request.breaking){
|
||||
Block block = world.tile(request.x, request.y).target().block();
|
||||
Block block = world.ltile(request.x, request.y).block();
|
||||
|
||||
//draw removal request
|
||||
Lines.stroke(2f, Pal.removeBack);
|
||||
@@ -636,13 +636,17 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
}
|
||||
|
||||
protected void updateShooting(){
|
||||
if(isShooting() && mech.canShoot(this)){
|
||||
if(!state.isEditor() && isShooting() && mech.canShoot(this)){
|
||||
mech.weapon.update(this, pointerX, pointerY);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateFlying(){
|
||||
if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && ((TileEntity)target).isAdded() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range())){
|
||||
if(Units.invalidateTarget(target, this) && !(target instanceof TileEntity && ((TileEntity)target).damaged() && target.isValid() && target.getTeam() == team && mech.canHeal && dst(target) < getWeapon().bullet.range())){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(state.isEditor()){
|
||||
target = null;
|
||||
}
|
||||
|
||||
@@ -790,7 +794,11 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
|
||||
public void updateRespawning(){
|
||||
|
||||
if(spawner != null && spawner.isValid()){
|
||||
if(state.isEditor()){
|
||||
//instant respawn at center of map.
|
||||
set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
setDead(false);
|
||||
}else if(spawner != null && spawner.isValid()){
|
||||
spawner.updateSpawning(this);
|
||||
}else if(!netServer.isWaitingForPlayers()){
|
||||
if(!Net.client()){
|
||||
@@ -817,8 +825,8 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
//region read and write methods
|
||||
|
||||
@Override
|
||||
public boolean isClipped(){
|
||||
return false;
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -833,7 +841,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
boolean local = stream.readBoolean();
|
||||
|
||||
if(local){
|
||||
@@ -844,14 +852,14 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
lastSpawner = (SpawnerTrait)stile.entity;
|
||||
}
|
||||
Player player = headless ? this : Vars.player;
|
||||
player.readSaveSuper(stream);
|
||||
player.readSaveSuper(stream, version);
|
||||
player.mech = content.getByID(ContentType.mech, mechid);
|
||||
player.dead = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void readSaveSuper(DataInput stream) throws IOException{
|
||||
super.readSave(stream);
|
||||
private void readSaveSuper(DataInput stream, byte version) throws IOException{
|
||||
super.readSave(stream, version);
|
||||
|
||||
add();
|
||||
}
|
||||
@@ -859,7 +867,7 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
@Override
|
||||
public void write(DataOutput buffer) throws IOException{
|
||||
super.writeSave(buffer, !isLocal);
|
||||
TypeIO.writeStringData(buffer, name); //TODO writing strings is very inefficient
|
||||
TypeIO.writeStringData(buffer, name);
|
||||
buffer.writeByte(Pack.byteValue(isAdmin) | (Pack.byteValue(dead) << 1) | (Pack.byteValue(isBoosting) << 2) | (Pack.byteValue(isTyping) << 3));
|
||||
buffer.writeInt(Color.rgba8888(color));
|
||||
buffer.writeByte(mech.id);
|
||||
@@ -873,7 +881,9 @@ public class Player extends Unit implements BuilderTrait, ShooterTrait{
|
||||
@Override
|
||||
public void read(DataInput buffer) throws IOException{
|
||||
float lastx = x, lasty = y, lastrot = rotation, lastvx = velocity.x, lastvy = velocity.y;
|
||||
super.readSave(buffer);
|
||||
|
||||
super.readSave(buffer, version());
|
||||
|
||||
name = TypeIO.readStringData(buffer);
|
||||
byte bools = buffer.readByte();
|
||||
isAdmin = (bools & 1) != 0;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package io.anuke.mindustry.entities.type;
|
||||
|
||||
import io.anuke.annotations.Annotations.Loc;
|
||||
import io.anuke.annotations.Annotations.Remote;
|
||||
import io.anuke.annotations.Annotations.*;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectSet;
|
||||
import io.anuke.arc.math.geom.Point2;
|
||||
import io.anuke.arc.math.geom.Vector2;
|
||||
import io.anuke.arc.util.Interval;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.entities.EntityGroup;
|
||||
import io.anuke.mindustry.entities.bullet.Bullet;
|
||||
import io.anuke.mindustry.entities.impl.BaseEntity;
|
||||
@@ -115,16 +113,35 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
return dead || tile.entity != this;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeShort((short)health);
|
||||
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.rotation())); //team + rotation
|
||||
if(items != null) items.write(stream);
|
||||
if(power != null) power.write(stream);
|
||||
if(liquids != null) liquids.write(stream);
|
||||
if(cons != null) cons.write(stream);
|
||||
}
|
||||
|
||||
public void writeConfig(DataOutput stream) throws IOException{
|
||||
@CallSuper
|
||||
public void read(DataInput stream, byte revision) throws IOException{
|
||||
health = stream.readUnsignedShort();
|
||||
byte tr = stream.readByte();
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.rotation(rotation);
|
||||
|
||||
if(items != null) items.read(stream);
|
||||
if(power != null) power.read(stream);
|
||||
if(liquids != null) liquids.read(stream);
|
||||
if(cons != null) cons.read(stream);
|
||||
}
|
||||
|
||||
public void read(DataInput stream) throws IOException{
|
||||
}
|
||||
|
||||
public void readConfig(DataInput stream) throws IOException{
|
||||
/** Returns the version of this TileEntity IO code.*/
|
||||
public byte version(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean collide(Bullet other){
|
||||
@@ -168,14 +185,14 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
|
||||
Point2[] nearby = Edges.getEdges(block.size);
|
||||
for(Point2 point : nearby){
|
||||
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
|
||||
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
|
||||
//remove this tile from all nearby tile's proximities
|
||||
if(other != null){
|
||||
other = other.target();
|
||||
other.block().onProximityUpdate(other);
|
||||
}
|
||||
if(other != null && other.entity != null){
|
||||
other.entity.proximity.removeValue(tile, true);
|
||||
|
||||
if(other.entity != null){
|
||||
other.entity.proximity.removeValue(tile, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,10 +203,9 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
|
||||
Point2[] nearby = Edges.getEdges(block.size);
|
||||
for(Point2 point : nearby){
|
||||
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
|
||||
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
|
||||
|
||||
if(other == null) continue;
|
||||
other = other.target();
|
||||
if(other.entity == null || !(other.interactable(tile.getTeam()))) continue;
|
||||
|
||||
other.block().onProximityUpdate(other);
|
||||
@@ -280,6 +296,11 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(){
|
||||
return !isDead() && tile.entity == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityGroup targetGroup(){
|
||||
return tileGroup;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.anuke.mindustry.entities.type;
|
||||
|
||||
import io.anuke.annotations.Annotations.Nullable;
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
@@ -8,7 +9,8 @@ import io.anuke.arc.graphics.g2d.TextureRegion;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.Geometry;
|
||||
import io.anuke.arc.math.geom.Vector2;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.Tmp;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.content.Fx;
|
||||
import io.anuke.mindustry.entities.*;
|
||||
@@ -138,7 +140,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
byte team = stream.readByte();
|
||||
boolean dead = stream.readBoolean();
|
||||
float x = stream.readFloat();
|
||||
@@ -150,7 +152,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
|
||||
byte itemID = stream.readByte();
|
||||
short itemAmount = stream.readShort();
|
||||
|
||||
this.status.readSave(stream);
|
||||
this.status.readSave(stream, version);
|
||||
this.item.amount = itemAmount;
|
||||
this.item.item = content.item(itemID);
|
||||
this.dead = dead;
|
||||
@@ -221,7 +223,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
|
||||
velocity.add(moveVector.x / mass() * Time.delta(), moveVector.y / mass() * Time.delta());
|
||||
}
|
||||
|
||||
public TileEntity getClosestCore(){
|
||||
public @Nullable TileEntity getClosestCore(){
|
||||
TeamData data = state.teams.get(team);
|
||||
|
||||
Tile tile = Geometry.findClosest(x, y, data.cores);
|
||||
|
||||
@@ -58,7 +58,7 @@ public class Drone extends FlyingUnit implements BuilderTrait{
|
||||
if(isBreaking){
|
||||
getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y));
|
||||
}else{
|
||||
getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.getRotation(), entity.cblock));
|
||||
getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.rotation(), entity.cblock));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public class Statuses implements Saveable{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSave(DataInput stream) throws IOException{
|
||||
public void readSave(DataInput stream, byte version) throws IOException{
|
||||
for(StatusEntry effect : statuses){
|
||||
Pools.free(effect);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import io.anuke.mindustry.type.ContentType;
|
||||
|
||||
/** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */
|
||||
public abstract class Content{
|
||||
public final byte id;
|
||||
public final short id;
|
||||
|
||||
public Content(){
|
||||
this.id = (byte)Vars.content.getBy(getContentType()).size;
|
||||
this.id = (short)Vars.content.getBy(getContentType()).size;
|
||||
Vars.content.handleContent(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
package io.anuke.mindustry.game;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.function.Supplier;
|
||||
import io.anuke.arc.function.Consumer;
|
||||
|
||||
/** Defines preset rule sets.. */
|
||||
public enum Gamemode{
|
||||
survival(() -> new Rules(){{
|
||||
waveTimer = true;
|
||||
waves = true;
|
||||
unitDrops = true;
|
||||
spawns = DefaultWaves.get();
|
||||
}}),
|
||||
sandbox(() -> new Rules(){{
|
||||
infiniteResources = true;
|
||||
waves = true;
|
||||
waveTimer = false;
|
||||
respawnTime = 0f;
|
||||
}}),
|
||||
attack(() -> new Rules(){{
|
||||
enemyCheat = true;
|
||||
unitDrops = true;
|
||||
waves = false;
|
||||
attackMode = true;
|
||||
}}),
|
||||
pvp(() -> new Rules(){{
|
||||
pvp = true;
|
||||
enemyCoreBuildRadius = 600f;
|
||||
respawnTime = 60 * 10;
|
||||
buildCostMultiplier = 0.5f;
|
||||
buildSpeedMultiplier = 2f;
|
||||
playerDamageMultiplier = 0.45f;
|
||||
playerHealthMultiplier = 0.8f;
|
||||
unitBuildSpeedMultiplier = 3f;
|
||||
unitHealthMultiplier = 2f;
|
||||
attackMode = true;
|
||||
}});
|
||||
survival(rules -> {
|
||||
rules.waveTimer = true;
|
||||
rules.waves = true;
|
||||
rules.unitDrops = true;
|
||||
}),
|
||||
sandbox(rules -> {
|
||||
rules.infiniteResources = true;
|
||||
rules.waves = true;
|
||||
rules.waveTimer = false;
|
||||
rules.respawnTime = 0f;
|
||||
}),
|
||||
attack(rules -> {
|
||||
rules.enemyCheat = true;
|
||||
rules.unitDrops = true;
|
||||
rules.waves = false;
|
||||
rules.attackMode = true;
|
||||
}),
|
||||
pvp(rules -> {
|
||||
rules.pvp = true;
|
||||
rules.enemyCoreBuildRadius = 600f;
|
||||
rules.respawnTime = 60 * 10;
|
||||
rules.buildCostMultiplier = 0.5f;
|
||||
rules.buildSpeedMultiplier = 2f;
|
||||
rules.playerDamageMultiplier = 0.45f;
|
||||
rules.playerHealthMultiplier = 0.8f;
|
||||
rules.unitBuildSpeedMultiplier = 3f;
|
||||
rules.unitHealthMultiplier = 2f;
|
||||
rules.attackMode = true;
|
||||
}),
|
||||
editor(true, rules -> {
|
||||
rules.infiniteResources = true;
|
||||
rules.editor = true;
|
||||
rules.waves = false;
|
||||
rules.enemyCoreBuildRadius = 0f;
|
||||
rules.waveTimer = false;
|
||||
rules.respawnTime = 0f;
|
||||
});
|
||||
|
||||
private final Supplier<Rules> rules;
|
||||
private final Consumer<Rules> rules;
|
||||
public final boolean hidden;
|
||||
|
||||
Gamemode(Supplier<Rules> rules){
|
||||
this.rules = rules;
|
||||
Gamemode(Consumer<Rules> rules){
|
||||
this(false, rules);
|
||||
}
|
||||
|
||||
public Rules get(){
|
||||
return rules.get();
|
||||
Gamemode(boolean hidden, Consumer<Rules> rules){
|
||||
this.rules = rules;
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
/** Applies this preset to this ruleset. */
|
||||
public Rules apply(Rules in){
|
||||
rules.accept(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
public String description(){
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.anuke.mindustry.game;
|
||||
|
||||
import io.anuke.annotations.Annotations.Serialize;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.mindustry.io.JsonIO;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
|
||||
/**
|
||||
* Defines current rules on how the game should function.
|
||||
@@ -47,10 +49,10 @@ public class Rules{
|
||||
public float bossWaveMultiplier = 3f;
|
||||
/** How many times longer a launch wave takes. */
|
||||
public float launchWaveMultiplier = 2f;
|
||||
/** Zone ID, -1 for invalid zone. */
|
||||
public byte zone = -1;
|
||||
/** Zone for saves that have them.*/
|
||||
public Zone zone;
|
||||
/** Spawn layout. Should be assigned on save load based on map or zone. */
|
||||
public transient Array<SpawnGroup> spawns = DefaultWaves.get();
|
||||
public Array<SpawnGroup> spawns = DefaultWaves.get();
|
||||
/** Determines if there should be limited respawns. */
|
||||
public boolean limitedRespawns = false;
|
||||
/** How many times player can respawn during one wave. */
|
||||
@@ -59,4 +61,11 @@ public class Rules{
|
||||
public boolean waitForWaveToEnd = false;
|
||||
/** Determinates if gamemode is attack mode */
|
||||
public boolean attackMode = false;
|
||||
/** Whether this is the editor gamemode. */
|
||||
public boolean editor = false;
|
||||
|
||||
/** Copies this ruleset exactly. Not very efficient at all, do not use often. */
|
||||
public Rules copy(){
|
||||
return JsonIO.read(Rules.class, JsonIO.write(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,21 @@ import io.anuke.arc.Core;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.util.Strings;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.game.EventType.StateChangeEvent;
|
||||
import io.anuke.mindustry.io.SaveIO;
|
||||
import io.anuke.mindustry.io.SaveIO.SaveException;
|
||||
import io.anuke.mindustry.io.SaveMeta;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
import static io.anuke.mindustry.Vars.saveExtension;
|
||||
import static io.anuke.mindustry.Vars.state;
|
||||
|
||||
public class Saves{
|
||||
private int nextSlot;
|
||||
@@ -52,7 +51,7 @@ public class Saves{
|
||||
SaveSlot slot = new SaveSlot(index);
|
||||
saves.add(slot);
|
||||
saveMap.put(slot.index, slot);
|
||||
slot.meta = SaveIO.getData(index);
|
||||
slot.meta = SaveIO.getMeta(index);
|
||||
nextSlot = Math.max(index + 1, nextSlot);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +133,7 @@ public class Saves{
|
||||
slot.setName(file.nameWithoutExtension());
|
||||
saves.add(slot);
|
||||
saveMap.put(slot.index, slot);
|
||||
slot.meta = SaveIO.getData(slot.index);
|
||||
slot.meta = SaveIO.getMeta(slot.index);
|
||||
current = slot;
|
||||
saveSlots();
|
||||
return slot;
|
||||
@@ -172,7 +171,7 @@ public class Saves{
|
||||
public void load() throws SaveException{
|
||||
try{
|
||||
SaveIO.loadFromSlot(index);
|
||||
meta = SaveIO.getData(index);
|
||||
meta = SaveIO.getMeta(index);
|
||||
current = this;
|
||||
totalPlaytime = meta.timePlayed;
|
||||
}catch(Exception e){
|
||||
@@ -186,7 +185,7 @@ public class Saves{
|
||||
totalPlaytime = time;
|
||||
|
||||
SaveIO.saveToSlot(index);
|
||||
meta = SaveIO.getData(index);
|
||||
meta = SaveIO.getMeta(index);
|
||||
if(!state.is(State.menu)){
|
||||
current = this;
|
||||
}
|
||||
@@ -224,7 +223,7 @@ public class Saves{
|
||||
}
|
||||
|
||||
public Zone getZone(){
|
||||
return meta == null || meta.rules == null ? null : content.getByID(ContentType.zone, meta.rules.zone);
|
||||
return meta == null || meta.rules == null ? null : meta.rules.zone;
|
||||
}
|
||||
|
||||
public int getBuild(){
|
||||
|
||||
@@ -9,7 +9,7 @@ import io.anuke.mindustry.type.*;
|
||||
@Serialize
|
||||
public class Stats{
|
||||
/** Items delivered to global resoure counter. Zones only. */
|
||||
public ObjectIntMap<Item> itemsDelivered = new ObjectIntMap<>();
|
||||
public transient ObjectIntMap<Item> itemsDelivered = new ObjectIntMap<>();
|
||||
/** Enemy (red team) units destroyed. */
|
||||
public int enemyUnitsDestroyed;
|
||||
/** Total waves lasted. */
|
||||
|
||||
@@ -78,7 +78,7 @@ public class BlockRenderer implements Disposable{
|
||||
for(int y = 0; y < world.height(); y++){
|
||||
Tile tile = world.rawTile(x, y);
|
||||
int edgeBlend = 2;
|
||||
float rot = tile.getRotation();
|
||||
float rot = tile.rotation();
|
||||
boolean fillable = (tile.block().solid && tile.block().fillsTile && !tile.block().synthetic());
|
||||
int edgeDst = Math.min(x, Math.min(y, Math.min(Math.abs(x - (world.width() - 1)), Math.abs(y - (world.height() - 1)))));
|
||||
if(edgeDst <= edgeBlend){
|
||||
|
||||
@@ -62,7 +62,7 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
//loop through all layers, and add layer index if it exists
|
||||
for(int i = 0; i < layers; i++){
|
||||
if(chunk.caches[i] != -1){
|
||||
if(chunk.caches[i] != -1 && i != CacheLayer.walls.ordinal()){
|
||||
drawnLayerSet.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public class MinimapRenderer implements Disposable{
|
||||
}
|
||||
|
||||
private int colorFor(Tile tile){
|
||||
tile = tile.target();
|
||||
tile = tile.link();
|
||||
return MapIO.colorFor(tile.floor(), tile.block(), tile.overlay(), tile.getTeam());
|
||||
}
|
||||
|
||||
|
||||
@@ -97,11 +97,10 @@ public class OverlayRenderer{
|
||||
//draw selected block bars and info
|
||||
if(input.block == null && !Core.scene.hasMouse()){
|
||||
Vector2 vec = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());
|
||||
Tile tile = world.tileWorld(vec.x, vec.y);
|
||||
Tile tile = world.ltileWorld(vec.x, vec.y);
|
||||
|
||||
if(tile != null && tile.block() != Blocks.air && tile.target().getTeam() == player.getTeam()){
|
||||
Tile target = tile.target();
|
||||
target.block().drawSelect(target);
|
||||
if(tile != null && tile.block() != Blocks.air && tile.getTeam() == player.getTeam()){
|
||||
tile.block().drawSelect(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +112,7 @@ public class OverlayRenderer{
|
||||
Lines.circle(v.x, v.y, 6 + Mathf.absin(Time.time(), 5f, 1f));
|
||||
Draw.reset();
|
||||
|
||||
Tile tile = world.tileWorld(v.x, v.y);
|
||||
if(tile != null) tile = tile.target();
|
||||
Tile tile = world.ltileWorld(v.x, v.y);
|
||||
if(tile != null && tile.interactable(player.getTeam()) && tile.block().acceptStack(player.item().item, player.item().amount, tile, player) > 0){
|
||||
Draw.color(Pal.place);
|
||||
Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f + 1 + Mathf.absin(Time.time(), 5f, 1f));
|
||||
|
||||
@@ -5,7 +5,6 @@ import io.anuke.arc.graphics.g2d.*;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Tmp;
|
||||
|
||||
//TODO remove
|
||||
public class Shapes{
|
||||
|
||||
public static void laser(String line, String edge, float x, float y, float x2, float y2, float scale){
|
||||
|
||||
@@ -96,9 +96,8 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
for(int x = dresult.x; x <= dresult.x2; x++){
|
||||
for(int y = dresult.y; y <= dresult.y2; y++){
|
||||
Tile tile = world.tile(x, y);
|
||||
Tile tile = world.ltile(x, y);
|
||||
if(tile == null || !validBreak(tile.x, tile.y)) continue;
|
||||
tile = tile.target();
|
||||
|
||||
Draw.color(Pal.removeBack);
|
||||
Lines.square(tile.drawx(), tile.drawy() - 1, tile.block().size * tilesize / 2f - 1);
|
||||
@@ -175,7 +174,7 @@ public class DesktopInput extends InputHandler{
|
||||
Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
||||
|
||||
if(cursor != null){
|
||||
cursor = cursor.target();
|
||||
cursor = cursor.link();
|
||||
|
||||
cursorType = cursor.block().getCursor(cursor);
|
||||
|
||||
@@ -257,7 +256,7 @@ public class DesktopInput extends InputHandler{
|
||||
}
|
||||
|
||||
if(selected != null){
|
||||
tryDropItems(selected.target(), Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
tryDropItems(selected.link(), Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
}
|
||||
|
||||
mode = none;
|
||||
|
||||
@@ -154,7 +154,7 @@ public abstract class InputHandler implements InputProcessor{
|
||||
|
||||
/** Handles tile tap events that are not platform specific. */
|
||||
boolean tileTapped(Tile tile){
|
||||
tile = tile.target();
|
||||
tile = tile.link();
|
||||
|
||||
boolean consumed = false, showedInventory = false;
|
||||
|
||||
@@ -331,7 +331,7 @@ public abstract class InputHandler implements InputProcessor{
|
||||
}
|
||||
|
||||
public void breakBlock(int x, int y){
|
||||
Tile tile = world.tile(x, y).target();
|
||||
Tile tile = world.ltile(x, y);
|
||||
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
player.setMineTile(null);
|
||||
player.target = unit;
|
||||
}else{
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
if(tile != null) tile = tile.target();
|
||||
Tile tile = world.ltileWorld(x, y);
|
||||
|
||||
if(tile != null && tile.synthetic() && state.teams.areEnemies(player.getTeam(), tile.getTeam())){
|
||||
TileEntity entity = tile.entity;
|
||||
@@ -416,9 +415,8 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
for(int x = dresult.x; x <= dresult.x2; x++){
|
||||
for(int y = dresult.y; y <= dresult.y2; y++){
|
||||
Tile other = world.tile(x, y);
|
||||
Tile other = world.ltile(x, y);
|
||||
if(other == null || !validBreak(other.x, other.y)) continue;
|
||||
other = other.target();
|
||||
|
||||
Draw.color(Pal.removeBack);
|
||||
Lines.square(other.drawx(), other.drawy() - 1, other.block().size * tilesize / 2f - 1);
|
||||
@@ -514,12 +512,10 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
int wx = lineStartX + x * Mathf.sign(tileX - lineStartX);
|
||||
int wy = lineStartY + y * Mathf.sign(tileY - lineStartY);
|
||||
|
||||
Tile tar = world.tile(wx, wy);
|
||||
Tile tar = world.ltile(wx, wy);
|
||||
|
||||
if(tar == null) continue;
|
||||
|
||||
tar = tar.target();
|
||||
|
||||
if(!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)){
|
||||
PlaceRequest request = new PlaceRequest(tar.x, tar.y);
|
||||
request.scale = 1f;
|
||||
@@ -535,7 +531,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
if(tile == null) return false;
|
||||
|
||||
tryDropItems(tile.target(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
tryDropItems(tile.link(), Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -585,11 +581,11 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, block, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, block)){
|
||||
//add to selection queue if it's a valid place position
|
||||
selection.add(lastPlaced = new PlaceRequest(cursor.x, cursor.y, block, rotation));
|
||||
}else if(mode == breaking && validBreak(cursor.target().x, cursor.target().y) && !hasRequest(cursor.target())){
|
||||
}else if(mode == breaking && validBreak(cursor.link().x, cursor.link().y) && !hasRequest(cursor.link())){
|
||||
//add to selection queue if it's a valid BREAK position
|
||||
cursor = cursor.target();
|
||||
cursor = cursor.link();
|
||||
selection.add(new PlaceRequest(cursor.x, cursor.y));
|
||||
}else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.target())){
|
||||
}else if(!canTapPlayer(worldx, worldy) && !tileTapped(cursor.link())){
|
||||
tryBeginMine(cursor);
|
||||
}
|
||||
|
||||
|
||||
40
core/src/io/anuke/mindustry/io/JsonIO.java
Normal file
40
core/src/io/anuke/mindustry/io/JsonIO.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.util.serialization.Json;
|
||||
import io.anuke.arc.util.serialization.JsonValue;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
|
||||
public class JsonIO{
|
||||
private static Json json = new Json(){{
|
||||
setIgnoreUnknownFields(true);
|
||||
setElementType(Rules.class, "spawns", SpawnGroup.class);
|
||||
|
||||
setSerializer(Zone.class, new Serializer<Zone>(){
|
||||
@Override
|
||||
public void write(Json json, Zone object, Class knownType){
|
||||
json.writeValue(object.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Zone read(Json json, JsonValue jsonData, Class type){
|
||||
return Vars.content.getByName(ContentType.zone, jsonData.asString());
|
||||
}
|
||||
});
|
||||
}};
|
||||
|
||||
public static String write(Object object){
|
||||
return json.toJson(object);
|
||||
}
|
||||
|
||||
public static <T> T read(Class<T> type, String string){
|
||||
return json.fromJson(type, string);
|
||||
}
|
||||
|
||||
public static String print(String in){
|
||||
return json.prettyPrint(in);
|
||||
}
|
||||
}
|
||||
226
core/src/io/anuke/mindustry/io/LegacyMapIO.java
Normal file
226
core/src/io/anuke/mindustry/io/LegacyMapIO.java
Normal file
@@ -0,0 +1,226 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.Pixmap;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.arc.util.Structs;
|
||||
import io.anuke.arc.util.serialization.Json;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.io.MapIO.TileProvider;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
/** Map IO for the "old" .mmap format.
|
||||
* Differentiate between legacy maps and new maps by checking the extension (or the header).*/
|
||||
public class LegacyMapIO{
|
||||
private static final ObjectMap<String, String> fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad");
|
||||
private static final Json json = new Json();
|
||||
|
||||
/* Convert a map from the old format to the new format. */
|
||||
public static void convertMap(FileHandle in, FileHandle out) throws IOException{
|
||||
Map map = readMap(in, true);
|
||||
|
||||
String waves = map.tags.get("waves", "[]");
|
||||
Array<SpawnGroup> groups = new Array<>(json.fromJson(SpawnGroup[].class, waves));
|
||||
|
||||
Tile[][] tiles = world.createTiles(map.width, map.height);
|
||||
for(int x = 0; x < map.width; x++){
|
||||
for(int y = 0; y < map.height; y++){
|
||||
tiles[x][y] = new CachedTile();
|
||||
}
|
||||
}
|
||||
state.rules.spawns = groups;
|
||||
readTiles(map, tiles);
|
||||
MapIO.writeMap(out, map);
|
||||
}
|
||||
|
||||
public static Map readMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(1024))){
|
||||
StringMap tags = new StringMap();
|
||||
|
||||
//meta is uncompressed
|
||||
int version = stream.readInt();
|
||||
int build = stream.readInt();
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
return new Map(file, width, height, tags, custom, version, build);
|
||||
}
|
||||
}
|
||||
|
||||
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
|
||||
readTiles(map, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
public static void readTiles(Map map, TileProvider tiles) throws IOException{
|
||||
readTiles(map.file, map.width, map.height, tiles);
|
||||
}
|
||||
|
||||
private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{
|
||||
readTiles(file, width, height, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{
|
||||
try(BufferedInputStream input = file.read(bufferSize)){
|
||||
|
||||
//read map
|
||||
{
|
||||
DataInputStream stream = new DataInputStream(input);
|
||||
|
||||
stream.readInt(); //version
|
||||
stream.readInt(); //build
|
||||
stream.readInt(); //width + height
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
}
|
||||
|
||||
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
|
||||
|
||||
try{
|
||||
byte mapped = stream.readByte();
|
||||
IntMap<Block> idmap = new IntMap<>();
|
||||
IntMap<String> namemap = new IntMap<>();
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
byte type = stream.readByte();
|
||||
short total = stream.readShort();
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
if(type == 1){
|
||||
Block res = content.getByName(ContentType.block, fallback.get(name, name));
|
||||
idmap.put(j, res == null ? Blocks.air : res);
|
||||
namemap.put(j, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
int floorid = stream.readUnsignedByte();
|
||||
int oreid = stream.readUnsignedByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setFloor((Floor)idmap.get(floorid));
|
||||
tile.setOverlay(idmap.get(oreid));
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = tiles.get(newx, newy);
|
||||
newTile.setFloor((Floor)idmap.get(floorid));
|
||||
newTile.setOverlay(idmap.get(oreid));
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
int id = stream.readUnsignedByte();
|
||||
Block block = idmap.get(id);
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
//the spawn block is saved in the block tile layer in older maps, shift it to the overlay
|
||||
if(block != Blocks.spawn){
|
||||
tile.setBlock(block);
|
||||
}else{
|
||||
tile.setOverlay(block);
|
||||
}
|
||||
|
||||
if(namemap.get(id).equals("part")){
|
||||
stream.readByte(); //link
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
stream.readShort(); //read health (which is actually irrelevant)
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = tile.block().health;
|
||||
tile.rotation(rotation);
|
||||
|
||||
if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){
|
||||
stream.readByte(); //these blocks have an extra config byte, read it
|
||||
}
|
||||
}else{ //no entity/part, read consecutives
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles.get(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads a pixmap in the 3.5 pixmap format. */
|
||||
public static void readPixmap(Pixmap pixmap, Tile[][] tiles){
|
||||
for(int x = 0; x < pixmap.getWidth(); x++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y++){
|
||||
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
|
||||
LegacyBlock block = LegacyColorMapper.get(color);
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.setFloor(block.floor);
|
||||
tile.setBlock(block.wall);
|
||||
if(block.ore != null) tile.setOverlay(block.ore);
|
||||
|
||||
//place core
|
||||
if(color == Color.rgba8888(Color.GREEN)){
|
||||
for(int dx = 0; dx < 3; dx++){
|
||||
for(int dy = 0; dy < 3; dy++){
|
||||
int worldx = dx - 1 + x;
|
||||
int worldy = dy - 1 + y;
|
||||
|
||||
//multiblock parts
|
||||
if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){
|
||||
Tile write = tiles[worldx][worldy];
|
||||
write.setBlock(BlockPart.get(dx - 1, dy - 1));
|
||||
write.setTeam(Team.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//actual core parts
|
||||
tile.setBlock(Blocks.coreShard);
|
||||
tile.setTeam(Team.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,27 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.IntIntMap;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.ObjectMap.Entry;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.Pixmap;
|
||||
import io.anuke.arc.graphics.Pixmap.Format;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.game.Version;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.world.*;
|
||||
import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock;
|
||||
import io.anuke.mindustry.world.blocks.BlockPart;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static io.anuke.mindustry.Vars.bufferSize;
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
/** Reads and writes map files. */
|
||||
//TODO does this class even need to exist??? move to Maps?
|
||||
public class MapIO{
|
||||
public static final int version = 1;
|
||||
|
||||
private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
||||
private static ObjectMap<String, Block> missingBlocks;
|
||||
|
||||
private static void initBlocks(){
|
||||
if(missingBlocks != null) return;
|
||||
|
||||
//only for legacy maps
|
||||
missingBlocks = ObjectMap.of(
|
||||
"stained-stone", Blocks.shale,
|
||||
"stained-stone-red", Blocks.shale,
|
||||
"stained-stone-yellow", Blocks.shale,
|
||||
"stained-rocks", Blocks.shaleRocks,
|
||||
"stained-boulder", Blocks.shaleBoulder,
|
||||
"stained-rocks-red", Blocks.shaleRocks,
|
||||
"stained-rocks-yellow", Blocks.shaleRocks
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isImage(FileHandle file){
|
||||
try(InputStream stream = file.read(32)){
|
||||
@@ -62,42 +36,107 @@ public class MapIO{
|
||||
}
|
||||
}
|
||||
|
||||
public static Map createMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
SaveIO.readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
StringMap tags = new StringMap();
|
||||
ver.region("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in)));
|
||||
return new Map(file, tags.getInt("width"), tags.getInt("height"), tags, custom, version, Version.build);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeMap(FileHandle file, Map map) throws IOException{
|
||||
try{
|
||||
SaveIO.write(file, map.tags);
|
||||
}catch(Exception e){
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadMap(Map map){
|
||||
SaveIO.load(map.file);
|
||||
}
|
||||
|
||||
public static void loadMap(Map map, WorldContext cons){
|
||||
SaveIO.load(map.file, cons);
|
||||
}
|
||||
|
||||
public static Pixmap generatePreview(Map map) throws IOException{
|
||||
Time.mark();
|
||||
Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
int black = Color.rgba8888(Color.BLACK);
|
||||
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
|
||||
CachedTile tile = new CachedTile(){
|
||||
@Override
|
||||
public void setFloor(Floor type){
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(type, Blocks.air, Blocks.air, getTeam()));
|
||||
}
|
||||
//by default, it does not have an enemy core or any other cores
|
||||
map.tags.put("enemycore", "false");
|
||||
map.tags.put("othercore", "false");
|
||||
|
||||
@Override
|
||||
public void setOverlayID(byte b){
|
||||
if(b != 0)
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(floor(), Blocks.air, content.block(b), getTeam()));
|
||||
}
|
||||
try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
SaveIO.readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
ver.region("meta", stream, counter, ver::readStringMap);
|
||||
|
||||
@Override
|
||||
protected void changed(){
|
||||
super.changed();
|
||||
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
|
||||
if(c != black){
|
||||
walls.drawPixel(x, floors.getHeight() - 1 - y, c);
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade);
|
||||
Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
int black = Color.rgba8888(Color.BLACK);
|
||||
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
|
||||
CachedTile tile = new CachedTile(){
|
||||
@Override
|
||||
public void setBlock(Block type){
|
||||
super.setBlock(type);
|
||||
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
|
||||
if(c != black){
|
||||
walls.drawPixel(x, floors.getHeight() - 1 - y, c);
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y + 1, shade);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
readTiles(map, (x, y) -> {
|
||||
tile.x = (short)x;
|
||||
tile.y = (short)y;
|
||||
return tile;
|
||||
});
|
||||
floors.drawPixmap(walls, 0, 0);
|
||||
walls.dispose();
|
||||
return floors;
|
||||
|
||||
@Override
|
||||
public void setTeam(Team team){
|
||||
super.setTeam(team);
|
||||
if(block instanceof CoreBlock){
|
||||
if(team != defaultTeam){
|
||||
//map must have other team's cores
|
||||
map.tags.put("othercore", "true");
|
||||
}
|
||||
|
||||
if(team == waveTeam){
|
||||
//map must have default enemy team's core
|
||||
map.tags.put("enemycore", "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ver.region("content", stream, counter, ver::readContentHeader);
|
||||
ver.region("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){
|
||||
@Override public void resize(int width, int height){}
|
||||
@Override public boolean isGenerating(){return false;}
|
||||
@Override public void begin(){}
|
||||
@Override public void end(){}
|
||||
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
tile.x = (short)x;
|
||||
tile.y = (short)y;
|
||||
return tile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
if(overlayID != 0){
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.none));
|
||||
}else{
|
||||
floors.drawPixel(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.none));
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
}));
|
||||
|
||||
floors.drawPixmap(walls, 0, 0);
|
||||
walls.dispose();
|
||||
return floors;
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pixmap generatePreview(Tile[][] tiles){
|
||||
@@ -118,360 +157,6 @@ public class MapIO{
|
||||
return Color.rgba8888(wall.solid ? wall.color : ore == Blocks.air ? floor.color : ore.color);
|
||||
}
|
||||
|
||||
public static void writeMap(FileHandle file, Map map, Tile[][] tiles) throws IOException{
|
||||
OutputStream output = file.write(false, bufferSize);
|
||||
|
||||
{
|
||||
DataOutputStream stream = new DataOutputStream(output);
|
||||
stream.writeInt(version);
|
||||
stream.writeInt(Version.build);
|
||||
stream.writeShort(tiles.length);
|
||||
stream.writeShort(tiles[0].length);
|
||||
stream.writeByte((byte)map.tags.size);
|
||||
|
||||
for(Entry<String, String> entry : map.tags.entries()){
|
||||
stream.writeUTF(entry.key);
|
||||
stream.writeUTF(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){
|
||||
int width = map.width, height = map.height;
|
||||
|
||||
SaveIO.getSaveWriter().writeContentHeader(stream);
|
||||
|
||||
//floor first
|
||||
for(int i = 0; i < tiles.length * tiles[0].length; i++){
|
||||
Tile tile = tiles[i % width][i / width];
|
||||
stream.writeByte(tile.getFloorID());
|
||||
stream.writeByte(tile.getOverlayID());
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < width * height && consecutives < 255; j++){
|
||||
Tile nextTile = tiles[j % width][j / width];
|
||||
|
||||
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.getOverlayID() != tile.getOverlayID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//then blocks
|
||||
for(int i = 0; i < tiles.length * tiles[0].length; i++){
|
||||
Tile tile = tiles[i % width][i / width];
|
||||
stream.writeByte(tile.getBlockID());
|
||||
|
||||
if(tile.block() instanceof BlockPart){
|
||||
stream.writeByte(tile.getLinkByte());
|
||||
}else if(tile.entity != null){
|
||||
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
|
||||
stream.writeShort(/*(short)tile.entity.health*/tile.block().health); //health
|
||||
tile.entity.writeConfig(stream);
|
||||
}else{
|
||||
//write consecutive non-entity blocks
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < width * height && consecutives < 255; j++){
|
||||
Tile nextTile = tiles[j % width][j / width];
|
||||
|
||||
if(nextTile.block() != tile.block()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map readMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(1024))){
|
||||
ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
|
||||
//meta is uncompressed
|
||||
int version = stream.readInt();
|
||||
if(version == 0){
|
||||
return readLegacyMap(file, custom);
|
||||
}
|
||||
int build = stream.readInt();
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
return new Map(file, width, height, tags, custom, version, build);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads tiles from a map, version-agnostic. */
|
||||
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
|
||||
readTiles(map, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
/** Reads tiles from a map, version-agnostic. */
|
||||
public static void readTiles(Map map, TileProvider tiles) throws IOException{
|
||||
if(map.version == 0){
|
||||
readLegacyMmapTiles(map.file, tiles);
|
||||
}else if(map.version == version){
|
||||
readTiles(map.file, map.width, map.height, tiles);
|
||||
}else{
|
||||
throw new IOException("Unknown map version. What?");
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads tiles from a map in the new build-65 format. */
|
||||
private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{
|
||||
readTiles(file, width, height, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
/** Reads tiles from a map in the new build-65 format. */
|
||||
private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{
|
||||
try(BufferedInputStream input = file.read(bufferSize)){
|
||||
|
||||
//read map
|
||||
{
|
||||
DataInputStream stream = new DataInputStream(input);
|
||||
|
||||
stream.readInt(); //version
|
||||
stream.readInt(); //build
|
||||
stream.readInt(); //width + height
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
}
|
||||
|
||||
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
|
||||
|
||||
MappableContent[][] c = SaveIO.getSaveWriter().readContentHeader(stream);
|
||||
|
||||
try{
|
||||
content.setTemporaryMapper(c);
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
byte floorid = stream.readByte();
|
||||
byte oreid = stream.readByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setFloor((Floor)content.block(floorid));
|
||||
tile.setOverlay(content.block(oreid));
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = tiles.get(newx, newy);
|
||||
newTile.setFloor((Floor)content.block(floorid));
|
||||
newTile.setOverlay(content.block(oreid));
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
Block block = content.block(stream.readByte());
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setBlock(block);
|
||||
|
||||
if(block == Blocks.part){
|
||||
tile.setLinkByte(stream.readByte());
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
short health = stream.readShort();
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = /*health*/tile.block().health;
|
||||
tile.setRotation(rotation);
|
||||
|
||||
tile.entity.readConfig(stream);
|
||||
}else{ //no entity/part, read consecutives
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles.get(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region legacy IO
|
||||
|
||||
/** Reads a pixmap in the 3.5 pixmap format. */
|
||||
public static void readLegacyPixmap(Pixmap pixmap, Tile[][] tiles){
|
||||
for(int x = 0; x < pixmap.getWidth(); x++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y++){
|
||||
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
|
||||
LegacyBlock block = LegacyColorMapper.get(color);
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.setFloor(block.floor);
|
||||
tile.setBlock(block.wall);
|
||||
if(block.ore != null) tile.setOverlay(block.ore);
|
||||
|
||||
//place core
|
||||
if(color == Color.rgba8888(Color.GREEN)){
|
||||
for(int dx = 0; dx < 3; dx++){
|
||||
for(int dy = 0; dy < 3; dy++){
|
||||
int worldx = dx - 1 + x;
|
||||
int worldy = dy - 1 + y;
|
||||
|
||||
//multiblock parts
|
||||
if(Structs.inBounds(worldx, worldy, pixmap.getWidth(), pixmap.getHeight())){
|
||||
Tile write = tiles[worldx][worldy];
|
||||
write.setBlock(Blocks.part);
|
||||
write.setTeam(Team.blue);
|
||||
write.setLinkByte(Pack.byteByte((byte)(dx - 1 + 8), (byte)(dy - 1 + 8)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//actual core parts
|
||||
tile.setBlock(Blocks.coreShard);
|
||||
tile.setTeam(Team.blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads a pixmap in the old 4.0 .mmap format. */
|
||||
private static void readLegacyMmapTiles(FileHandle file, Tile[][] tiles) throws IOException{
|
||||
readLegacyMmapTiles(file, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
/** Reads a mmap in the old 4.0 .mmap format. */
|
||||
private static void readLegacyMmapTiles(FileHandle file, TileProvider tiles) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){
|
||||
stream.readInt(); //version
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
|
||||
initBlocks();
|
||||
|
||||
//block id -> real id map
|
||||
IntIntMap map = new IntIntMap();
|
||||
IntIntMap oreMap = new IntIntMap();
|
||||
|
||||
short blocks = stream.readShort();
|
||||
for(int i = 0; i < blocks; i++){
|
||||
short id = stream.readShort();
|
||||
String name = stream.readUTF();
|
||||
Block block = content.getByName(ContentType.block, name);
|
||||
if(block == null){
|
||||
//substitute for replacement in missingBlocks if possible
|
||||
if(missingBlocks.containsKey(name)){
|
||||
block = missingBlocks.get(name);
|
||||
}else if(name.startsWith("ore-")){ //an ore floor combination
|
||||
String[] split = name.split("-");
|
||||
String itemName = split[1], floorName = Strings.join("-", Arrays.copyOfRange(split, 2, split.length));
|
||||
Item item = content.getByName(ContentType.item, itemName);
|
||||
Block oreBlock = item == null ? null : content.getByName(ContentType.block, "ore-" + item.name);
|
||||
Block floor = missingBlocks.get(floorName, content.getByName(ContentType.block, floorName));
|
||||
if(oreBlock != null && floor != null){
|
||||
oreMap.put(id, oreBlock.id);
|
||||
block = floor;
|
||||
}else{
|
||||
block = Blocks.air;
|
||||
}
|
||||
}else{
|
||||
block = Blocks.air;
|
||||
}
|
||||
|
||||
}
|
||||
map.put(id, block.id);
|
||||
}
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
|
||||
for(int y = 0; y < height; y++){
|
||||
for(int x = 0; x < width; x++){
|
||||
Tile tile = tiles.get(x, y);
|
||||
byte floorb = stream.readByte();
|
||||
byte blockb = stream.readByte();
|
||||
byte link = stream.readByte();
|
||||
byte rotTeamb = stream.readByte();
|
||||
stream.readByte();//unused stuff
|
||||
|
||||
tile.setFloor((Floor)content.block(map.get(floorb, 0)));
|
||||
tile.setBlock(content.block(map.get(blockb, 0)));
|
||||
tile.setRotation(Pack.leftByte(rotTeamb));
|
||||
if(tile.block().synthetic()){
|
||||
tile.setTeam(Team.all[Mathf.clamp(Pack.rightByte(rotTeamb), 0, Team.all.length)]);
|
||||
}
|
||||
|
||||
if(tile.block() == Blocks.part){
|
||||
tile.setLinkByte(link);
|
||||
}
|
||||
|
||||
if(oreMap.containsKey(floorb)){
|
||||
tile.setOverlay(content.block(oreMap.get(floorb, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map readLegacyMap(FileHandle file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){
|
||||
ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
|
||||
int version = stream.readInt();
|
||||
if(version != 0) throw new IOException("Attempted to read non-legacy map in legacy method!");
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
short blocks = stream.readShort();
|
||||
for(int i = 0; i < blocks; i++){
|
||||
stream.readShort();
|
||||
stream.readUTF();
|
||||
}
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
|
||||
//note that build 64 is the default build of all maps <65; while this can be inaccurate it's better than nothing
|
||||
return new Map(file, width, height, tags, custom, 0, 64);
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
interface TileProvider{
|
||||
Tile get(int x, int y);
|
||||
}
|
||||
|
||||
113
core/src/io/anuke/mindustry/io/SaveFileReader.java
Normal file
113
core/src/io/anuke/mindustry/io/SaveFileReader.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.ObjectMap.Entry;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.arc.util.io.ReusableByteOutStream;
|
||||
import io.anuke.mindustry.world.WorldContext;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public abstract class SaveFileReader{
|
||||
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
|
||||
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall);
|
||||
protected final ObjectMap<String, String> fallback = ObjectMap.of();
|
||||
|
||||
protected void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
|
||||
counter.resetCount();
|
||||
int length;
|
||||
try{
|
||||
length = readChunk(stream, cons);
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error reading region \"" + name + "\".", e);
|
||||
}
|
||||
|
||||
if(length != counter.count() - 4){
|
||||
throw new IOException("Error reading region \"" + name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() - 4));
|
||||
}
|
||||
}
|
||||
|
||||
protected void region(String name, DataOutput stream, IORunner<DataOutput> cons) throws IOException{
|
||||
try{
|
||||
writeChunk(stream, cons);
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error writing region \"" + name + "\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChunk(DataOutput output, IORunner<DataOutput> runner) throws IOException{
|
||||
writeChunk(output, false, runner);
|
||||
}
|
||||
|
||||
/** Write a chunk of input to the stream. An integer of some length is written first, followed by the data. */
|
||||
public void writeChunk(DataOutput output, boolean isByte, IORunner<DataOutput> runner) throws IOException{
|
||||
ReusableByteOutStream dout = isByte ? byteOutputSmall : byteOutput;
|
||||
//reset output position
|
||||
dout.reset();
|
||||
//write the needed info
|
||||
runner.accept(isByte ? dataBytesSmall : dataBytes);
|
||||
int length = dout.size();
|
||||
//write length (either int or byte) followed by the output bytes
|
||||
if(!isByte){
|
||||
output.writeInt(length);
|
||||
}else{
|
||||
if(length > Short.MAX_VALUE){
|
||||
throw new IOException("Byte write length exceeded: " + length + " > " + Short.MAX_VALUE);
|
||||
}
|
||||
output.writeShort(length);
|
||||
}
|
||||
output.write(dout.getBytes(), 0, length);
|
||||
}
|
||||
|
||||
public int readChunk(DataInput input, IORunner<DataInput> runner) throws IOException{
|
||||
return readChunk(input, false, runner);
|
||||
}
|
||||
|
||||
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
|
||||
public int readChunk(DataInput input, boolean isByte, IORunner<DataInput> runner) throws IOException{
|
||||
int length = isByte ? input.readUnsignedShort() : input.readInt();
|
||||
runner.accept(input);
|
||||
return length;
|
||||
}
|
||||
|
||||
public void skipRegion(DataInput input) throws IOException{
|
||||
skipRegion(input, false);
|
||||
}
|
||||
|
||||
/** Skip a region completely. */
|
||||
public void skipRegion(DataInput input, boolean isByte) throws IOException{
|
||||
int length = readChunk(input, isByte, t -> {});
|
||||
int skipped = input.skipBytes(length);
|
||||
if(length != skipped){
|
||||
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeStringMap(DataOutput stream, ObjectMap<String, String> map) throws IOException{
|
||||
stream.writeShort(map.size);
|
||||
for(Entry<String, String> entry : map.entries()){
|
||||
stream.writeUTF(entry.key);
|
||||
stream.writeUTF(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringMap readStringMap(DataInput stream) throws IOException{
|
||||
StringMap map = new StringMap();
|
||||
short size = stream.readShort();
|
||||
for(int i = 0; i < size; i++){
|
||||
map.put(stream.readUTF(), stream.readUTF());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public abstract void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException;
|
||||
|
||||
public abstract void write(DataOutputStream stream) throws IOException;
|
||||
|
||||
protected interface IORunner<T>{
|
||||
void accept(T stream) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.entities.Entities;
|
||||
import io.anuke.mindustry.entities.EntityGroup;
|
||||
import io.anuke.mindustry.entities.traits.*;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.gen.Serialization;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public abstract class SaveFileVersion{
|
||||
public final int version;
|
||||
private final ObjectMap<String, String> fallback = ObjectMap.of(
|
||||
"alpha-dart-mech-pad", "dart-mech-pad"
|
||||
);
|
||||
|
||||
public SaveFileVersion(int version){
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public SaveMeta getData(DataInputStream stream) throws IOException{
|
||||
long time = stream.readLong();
|
||||
long playtime = stream.readLong();
|
||||
int build = stream.readInt();
|
||||
|
||||
Rules rules = Serialization.readRulesStreamJson(stream);
|
||||
String map = stream.readUTF();
|
||||
int wave = stream.readInt();
|
||||
return new SaveMeta(version, time, playtime, build, map, wave, rules);
|
||||
}
|
||||
|
||||
public void writeMap(DataOutputStream stream) throws IOException{
|
||||
//write world size
|
||||
stream.writeShort(world.width());
|
||||
stream.writeShort(world.height());
|
||||
|
||||
//floor first
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeByte(tile.getFloorID());
|
||||
stream.writeByte(tile.getOverlayID());
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getOverlayID() != tile.getOverlayID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//blocks
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeByte(tile.getBlockID());
|
||||
|
||||
if(tile.block() == Blocks.part){
|
||||
stream.writeByte(tile.getLinkByte());
|
||||
}else if(tile.entity != null){
|
||||
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation
|
||||
stream.writeShort((short)tile.entity.health); //health
|
||||
|
||||
if(tile.entity.items != null) tile.entity.items.write(stream);
|
||||
if(tile.entity.power != null) tile.entity.power.write(stream);
|
||||
if(tile.entity.liquids != null) tile.entity.liquids.write(stream);
|
||||
if(tile.entity.cons != null) tile.entity.cons.write(stream);
|
||||
|
||||
tile.entity.writeConfig(stream);
|
||||
tile.entity.write(stream);
|
||||
}else{
|
||||
//write consecutive non-entity blocks
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.block() != tile.block()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readMap(DataInputStream stream) throws IOException{
|
||||
short width = stream.readShort();
|
||||
short height = stream.readShort();
|
||||
|
||||
world.beginMapLoad();
|
||||
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
byte floorid = stream.readByte();
|
||||
byte oreid = stream.readByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
Block ore = content.block(oreid);
|
||||
|
||||
tiles[x][y] = new Tile(x, y, floorid, (byte)0);
|
||||
tiles[x][y].setOverlay(ore);
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = new Tile(newx, newy, floorid, (byte)0);
|
||||
newTile.setOverlay(ore);
|
||||
tiles[newx][newy] = newTile;
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
Block block = content.block(stream.readByte());
|
||||
Tile tile = tiles[x][y];
|
||||
tile.setBlock(block);
|
||||
|
||||
if(block == Blocks.part){
|
||||
tile.setLinkByte(stream.readByte());
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
short health = stream.readShort();
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = health;
|
||||
tile.setRotation(rotation);
|
||||
|
||||
if(tile.entity.items != null) tile.entity.items.read(stream);
|
||||
if(tile.entity.power != null) tile.entity.power.read(stream);
|
||||
if(tile.entity.liquids != null) tile.entity.liquids.read(stream);
|
||||
if(tile.entity.cons != null) tile.entity.cons.read(stream);
|
||||
|
||||
tile.entity.readConfig(stream);
|
||||
tile.entity.read(stream);
|
||||
}else{
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles[newx][newy].setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
content.setTemporaryMapper(null);
|
||||
world.endMapLoad();
|
||||
}
|
||||
|
||||
public void writeEntities(DataOutputStream stream) throws IOException{
|
||||
int groups = 0;
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
groups++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(groups);
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
stream.writeInt(group.size());
|
||||
for(Entity entity : group.all()){
|
||||
stream.writeByte(((SaveTrait)entity).getTypeID());
|
||||
((SaveTrait)entity).writeSave(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readEntities(DataInputStream stream) throws IOException{
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
byte typeid = stream.readByte();
|
||||
SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get();
|
||||
trait.readSave(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MappableContent[][] readContentHeader(DataInputStream stream) throws IOException{
|
||||
|
||||
byte mapped = stream.readByte();
|
||||
|
||||
MappableContent[][] map = new MappableContent[ContentType.values().length][0];
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
ContentType type = ContentType.values()[stream.readByte()];
|
||||
short total = stream.readShort();
|
||||
map[type.ordinal()] = new MappableContent[total];
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public void writeContentHeader(DataOutputStream stream) throws IOException{
|
||||
Array<Content>[] map = content.getContentMap();
|
||||
|
||||
int mappable = 0;
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
mappable++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(mappable);
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
stream.writeByte(arr.first().getContentType().ordinal());
|
||||
stream.writeShort(arr.size);
|
||||
for(Content c : arr){
|
||||
stream.writeUTF(((MappableContent)c).name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void read(DataInputStream stream) throws IOException;
|
||||
|
||||
public abstract void write(DataOutputStream stream) throws IOException;
|
||||
}
|
||||
@@ -2,39 +2,46 @@ package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.arc.util.io.FastDeflaterOutputStream;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.io.versions.Save1;
|
||||
import io.anuke.mindustry.world.WorldContext;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
//TODO load backup meta if possible
|
||||
public class SaveIO{
|
||||
public static final IntArray breakingVersions = IntArray.with(47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63);
|
||||
public static final IntMap<SaveFileVersion> versions = new IntMap<>();
|
||||
public static final Array<SaveFileVersion> versionArray = Array.with(new Save1());
|
||||
/** Format header. This is the string 'MSAV' in ASCII. */
|
||||
public static final byte[] header = {77, 83, 65, 86};
|
||||
public static final IntMap<SaveVersion> versions = new IntMap<>();
|
||||
public static final Array<SaveVersion> versionArray = Array.with(new Save1());
|
||||
|
||||
static{
|
||||
for(SaveFileVersion version : versionArray){
|
||||
for(SaveVersion version : versionArray){
|
||||
versions.put(version.version, version);
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveFileVersion getSaveWriter(){
|
||||
public static SaveVersion getSaveWriter(){
|
||||
return versionArray.peek();
|
||||
}
|
||||
|
||||
public static SaveVersion getSaveWriter(int version){
|
||||
return versions.get(version);
|
||||
}
|
||||
|
||||
public static void saveToSlot(int slot){
|
||||
FileHandle file = fileFor(slot);
|
||||
boolean exists = file.exists();
|
||||
if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension()));
|
||||
if(exists) file.moveTo(backupFileFor(file));
|
||||
try{
|
||||
write(fileFor(slot));
|
||||
}catch(Exception e){
|
||||
if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file);
|
||||
if(exists) backupFileFor(file).moveTo(file);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@@ -47,22 +54,30 @@ public class SaveIO{
|
||||
return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize)));
|
||||
}
|
||||
|
||||
public static DataInputStream getBackupSlotStream(int slot){
|
||||
return new DataInputStream(new InflaterInputStream(backupFileFor(fileFor(slot)).read(bufferSize)));
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(int slot){
|
||||
try{
|
||||
return isSaveValid(getSlotStream(slot));
|
||||
getMeta(slot);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(FileHandle file){
|
||||
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
|
||||
try{
|
||||
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
|
||||
}catch(Exception e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(DataInputStream stream){
|
||||
|
||||
try{
|
||||
getData(stream);
|
||||
getMeta(stream);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
@@ -70,15 +85,20 @@ public class SaveIO{
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveMeta getData(int slot){
|
||||
return getData(getSlotStream(slot));
|
||||
public static SaveMeta getMeta(int slot){
|
||||
try{
|
||||
return getMeta(getSlotStream(slot));
|
||||
}catch(Exception e){
|
||||
return getMeta(getBackupSlotStream(slot));
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveMeta getData(DataInputStream stream){
|
||||
public static SaveMeta getMeta(DataInputStream stream){
|
||||
|
||||
try{
|
||||
readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveMeta meta = versions.get(version).getData(stream);
|
||||
SaveMeta meta = versions.get(version).getMeta(stream);
|
||||
stream.close();
|
||||
return meta;
|
||||
}catch(IOException e){
|
||||
@@ -90,61 +110,79 @@ public class SaveIO{
|
||||
return saveDirectory.child(slot + "." + Vars.saveExtension);
|
||||
}
|
||||
|
||||
public static void write(FileHandle file){
|
||||
write(new DeflaterOutputStream(file.write(false, bufferSize)){
|
||||
byte[] tmp = {0};
|
||||
|
||||
public void write(int var1) throws IOException{
|
||||
tmp[0] = (byte)(var1 & 255);
|
||||
this.write(tmp, 0, 1);
|
||||
}
|
||||
});
|
||||
public static FileHandle backupFileFor(FileHandle file){
|
||||
return file.sibling(file.name() + "-backup." + file.extension());
|
||||
}
|
||||
|
||||
public static void write(OutputStream os){
|
||||
DataOutputStream stream;
|
||||
public static void write(FileHandle file, StringMap tags){
|
||||
write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags);
|
||||
}
|
||||
|
||||
try{
|
||||
stream = new DataOutputStream(os);
|
||||
getVersion().write(stream);
|
||||
stream.close();
|
||||
public static void write(FileHandle file){
|
||||
write(file, null);
|
||||
}
|
||||
|
||||
public static void write(OutputStream os, StringMap tags){
|
||||
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||
stream.write(header);
|
||||
stream.writeInt(getVersion().version);
|
||||
if(tags == null){
|
||||
getVersion().write(stream);
|
||||
}else{
|
||||
getVersion().write(stream, tags);
|
||||
}
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void load(FileHandle file) throws SaveException{
|
||||
load(file, world.context);
|
||||
}
|
||||
|
||||
public static void load(FileHandle file, WorldContext context) throws SaveException{
|
||||
try{
|
||||
//try and load; if any exception at all occurs
|
||||
load(new InflaterInputStream(file.read(bufferSize)));
|
||||
load(new InflaterInputStream(file.read(bufferSize)), context);
|
||||
}catch(SaveException e){
|
||||
e.printStackTrace();
|
||||
FileHandle backup = file.sibling(file.name() + "-backup." + file.extension());
|
||||
if(backup.exists()){
|
||||
load(new InflaterInputStream(backup.read(bufferSize)));
|
||||
load(new InflaterInputStream(backup.read(bufferSize)), context);
|
||||
}else{
|
||||
throw new SaveException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void load(InputStream is) throws SaveException{
|
||||
try(DataInputStream stream = new DataInputStream(is)){
|
||||
/** Loads from a deflated (!) input stream.*/
|
||||
public static void load(InputStream is, WorldContext context) throws SaveException{
|
||||
try(CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
logic.reset();
|
||||
readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveFileVersion ver = versions.get(version);
|
||||
SaveVersion ver = versions.get(version);
|
||||
|
||||
ver.read(stream);
|
||||
ver.read(stream, counter, context);
|
||||
}catch(Exception e){
|
||||
content.setTemporaryMapper(null);
|
||||
throw new SaveException(e);
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveFileVersion getVersion(){
|
||||
public static SaveVersion getVersion(){
|
||||
return versionArray.peek();
|
||||
}
|
||||
|
||||
public static void readHeader(DataInput input) throws IOException{
|
||||
byte[] bytes = new byte[header.length];
|
||||
input.readFully(bytes);
|
||||
if(!Arrays.equals(bytes, header)){
|
||||
throw new IOException("Incorrect header! Expecting: " + Arrays.toString(header) + "; Actual: " + Arrays.toString(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveException extends RuntimeException{
|
||||
public SaveException(Throwable throwable){
|
||||
super(throwable);
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.anuke.mindustry.maps.Map;
|
||||
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
//TODO consider removing and replacing with a raw StringMap
|
||||
public class SaveMeta{
|
||||
public int version;
|
||||
public int build;
|
||||
|
||||
279
core/src/io/anuke/mindustry/io/SaveVersion.java
Normal file
279
core/src/io/anuke/mindustry/io/SaveVersion.java
Normal file
@@ -0,0 +1,279 @@
|
||||
package io.anuke.mindustry.io;
|
||||
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.io.CounterInputStream;
|
||||
import io.anuke.mindustry.entities.Entities;
|
||||
import io.anuke.mindustry.entities.EntityGroup;
|
||||
import io.anuke.mindustry.entities.traits.*;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public abstract class SaveVersion extends SaveFileReader{
|
||||
public final int version;
|
||||
|
||||
public SaveVersion(int version){
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public SaveMeta getMeta(DataInput stream) throws IOException{
|
||||
stream.readInt(); //length of data, doesn't matter here
|
||||
StringMap map = readStringMap(stream);
|
||||
return new SaveMeta(map.getInt("version"), map.getLong("saved"), map.getLong("playtime"), map.getInt("build"), map.get("mapname"), map.getInt("wave"), JsonIO.read(Rules.class, map.get("rules", "{}")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(DataOutputStream stream) throws IOException{
|
||||
write(stream, new StringMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
|
||||
region("meta", stream, counter, this::readMeta);
|
||||
region("content", stream, counter, this::readContentHeader);
|
||||
region("map", stream, counter, in -> readMap(in, context));
|
||||
region("entities", stream, counter, this::readEntities);
|
||||
}
|
||||
|
||||
public final void write(DataOutputStream stream, StringMap extraTags) throws IOException{
|
||||
region("meta", stream, out -> writeMeta(out, extraTags));
|
||||
region("content", stream, this::writeContentHeader);
|
||||
region("map", stream, this::writeMap);
|
||||
region("entities", stream, this::writeEntities);
|
||||
}
|
||||
|
||||
public void writeMeta(DataOutput stream, StringMap tags) throws IOException{
|
||||
writeStringMap(stream, StringMap.of(
|
||||
"saved", Time.millis(),
|
||||
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
|
||||
"build", Version.build,
|
||||
"mapname", world.getMap() == null ? "unknown" : world.getMap().name(),
|
||||
"wave", state.wave,
|
||||
"wavetime", state.wavetime,
|
||||
"stats", JsonIO.write(state.stats),
|
||||
"rules", JsonIO.write(state.rules),
|
||||
"width", world.width(),
|
||||
"height", world.height()
|
||||
).merge(tags));
|
||||
}
|
||||
|
||||
public void readMeta(DataInput stream) throws IOException{
|
||||
StringMap map = readStringMap(stream);
|
||||
|
||||
state.wave = map.getInt("wave");
|
||||
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
||||
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
||||
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||
Map worldmap = world.maps.byName(map.get("mapname", "\\\\\\"));
|
||||
world.setMap(worldmap == null ? new Map(StringMap.of(
|
||||
"name", map.get("mapname", "Unknown"),
|
||||
"width", 1,
|
||||
"height", 1
|
||||
)) : worldmap);
|
||||
}
|
||||
|
||||
public void writeMap(DataOutput stream) throws IOException{
|
||||
//write world size
|
||||
stream.writeShort(world.width());
|
||||
stream.writeShort(world.height());
|
||||
|
||||
//floor + overlay
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeShort(tile.floorID());
|
||||
stream.writeShort(tile.overlayID());
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//blocks
|
||||
for(int i = 0; i < world.width() * world.height(); i++){
|
||||
Tile tile = world.tile(i % world.width(), i / world.width());
|
||||
stream.writeShort(tile.blockID());
|
||||
|
||||
if(tile.entity != null){
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(tile.entity.version());
|
||||
tile.entity.write(out);
|
||||
});
|
||||
}else{
|
||||
//write consecutive non-entity blocks
|
||||
int consecutives = 0;
|
||||
|
||||
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
|
||||
Tile nextTile = world.tile(j % world.width(), j / world.width());
|
||||
|
||||
if(nextTile.blockID() != tile.blockID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readMap(DataInput stream, WorldContext context) throws IOException{
|
||||
int width = stream.readUnsignedShort();
|
||||
int height = stream.readUnsignedShort();
|
||||
|
||||
boolean generating = context.isGenerating();
|
||||
|
||||
if(!generating) context.begin();
|
||||
|
||||
context.resize(width, height);
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
short floorid = stream.readShort();
|
||||
short oreid = stream.readShort();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
context.create(x, y, floorid, oreid, (short)0);
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
context.create(newx, newy, floorid, oreid, (short)0);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
Block block = content.block(stream.readShort());
|
||||
Tile tile = context.tile(x, y);
|
||||
tile.setBlock(block);
|
||||
|
||||
if(tile.entity != null){
|
||||
readChunk(stream, true, in -> {
|
||||
byte version = in.readByte();
|
||||
tile.entity.read(in, version);
|
||||
});
|
||||
}else{
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
context.tile(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
content.setTemporaryMapper(null);
|
||||
if(!generating) context.end();
|
||||
}
|
||||
|
||||
public void writeEntities(DataOutput stream) throws IOException{
|
||||
//write entity chunk
|
||||
int groups = 0;
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
groups++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(groups);
|
||||
|
||||
for(EntityGroup<?> group : Entities.getAllGroups()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
stream.writeInt(group.size());
|
||||
for(Entity entity : group.all()){
|
||||
SaveTrait save = (SaveTrait)entity;
|
||||
//each entity is a separate chunk.
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(save.getTypeID());
|
||||
out.writeByte(save.version());
|
||||
save.writeSave(out);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
//TODO throw exception on read fail
|
||||
readChunk(stream, true, in -> {
|
||||
byte typeid = in.readByte();
|
||||
byte version = in.readByte();
|
||||
SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get();
|
||||
trait.readSave(in, version);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readContentHeader(DataInput stream) throws IOException{
|
||||
|
||||
byte mapped = stream.readByte();
|
||||
|
||||
MappableContent[][] map = new MappableContent[ContentType.values().length][0];
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
ContentType type = ContentType.values()[stream.readByte()];
|
||||
short total = stream.readShort();
|
||||
map[type.ordinal()] = new MappableContent[total];
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
|
||||
content.setTemporaryMapper(map);
|
||||
}
|
||||
|
||||
public void writeContentHeader(DataOutput stream) throws IOException{
|
||||
Array<Content>[] map = content.getContentMap();
|
||||
|
||||
int mappable = 0;
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
mappable++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(mappable);
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
stream.writeByte(arr.first().getContentType().ordinal());
|
||||
stream.writeShort(arr.size);
|
||||
for(Content c : arr){
|
||||
stream.writeUTF(((MappableContent)c).name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,12 +109,12 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Block.class)
|
||||
public static void writeBlock(ByteBuffer buffer, Block block){
|
||||
buffer.put(block.id);
|
||||
buffer.putShort(block.id);
|
||||
}
|
||||
|
||||
@ReadClass(Block.class)
|
||||
public static Block readBlock(ByteBuffer buffer){
|
||||
return content.block(buffer.get());
|
||||
return content.block(buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(BuildRequest[].class)
|
||||
@@ -124,7 +124,7 @@ public class TypeIO{
|
||||
buffer.put(request.breaking ? (byte)1 : 0);
|
||||
buffer.putInt(Pos.get(request.x, request.y));
|
||||
if(!request.breaking){
|
||||
buffer.put(request.block.id);
|
||||
buffer.putShort(request.block.id);
|
||||
buffer.put((byte)request.rotation);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class TypeIO{
|
||||
if(type == 1){ //remove
|
||||
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position));
|
||||
}else{ //place
|
||||
byte block = buffer.get();
|
||||
short block = buffer.getShort();
|
||||
byte rotation = buffer.get();
|
||||
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block));
|
||||
}
|
||||
@@ -205,7 +205,7 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Mech.class)
|
||||
public static void writeMech(ByteBuffer buffer, Mech mech){
|
||||
buffer.put(mech.id);
|
||||
buffer.put((byte)mech.id);
|
||||
}
|
||||
|
||||
@ReadClass(Mech.class)
|
||||
@@ -215,33 +215,33 @@ public class TypeIO{
|
||||
|
||||
@WriteClass(Liquid.class)
|
||||
public static void writeLiquid(ByteBuffer buffer, Liquid liquid){
|
||||
buffer.put(liquid == null ? -1 : liquid.id);
|
||||
buffer.putShort(liquid == null ? -1 : liquid.id);
|
||||
}
|
||||
|
||||
@ReadClass(Liquid.class)
|
||||
public static Liquid readLiquid(ByteBuffer buffer){
|
||||
byte id = buffer.get();
|
||||
return id == -1 ? null : content.liquid(buffer.get());
|
||||
short id = buffer.getShort();
|
||||
return id == -1 ? null : content.liquid(buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(BulletType.class)
|
||||
public static void writeBulletType(ByteBuffer buffer, BulletType type){
|
||||
buffer.put(type.id);
|
||||
buffer.putShort(type.id);
|
||||
}
|
||||
|
||||
@ReadClass(BulletType.class)
|
||||
public static BulletType readBulletType(ByteBuffer buffer){
|
||||
return content.getByID(ContentType.bullet, buffer.get());
|
||||
return content.getByID(ContentType.bullet, buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(Item.class)
|
||||
public static void writeItem(ByteBuffer buffer, Item item){
|
||||
buffer.put(item == null ? -1 : item.id);
|
||||
buffer.putShort(item == null ? -1 : item.id);
|
||||
}
|
||||
|
||||
@ReadClass(Item.class)
|
||||
public static Item readItem(ByteBuffer buffer){
|
||||
byte id = buffer.get();
|
||||
short id = buffer.getShort();
|
||||
return id == -1 ? null : content.item(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,82 +1,10 @@
|
||||
package io.anuke.mindustry.io.versions;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.gen.Serialization;
|
||||
import io.anuke.mindustry.io.SaveFileVersion;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.Zone;
|
||||
import io.anuke.mindustry.io.SaveVersion;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class Save1 extends SaveFileVersion{
|
||||
public class Save1 extends SaveVersion{
|
||||
|
||||
public Save1(){
|
||||
super(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream stream) throws IOException{
|
||||
stream.readLong(); //time
|
||||
stream.readLong(); //total playtime
|
||||
stream.readInt(); //build
|
||||
|
||||
//general state
|
||||
state.rules = Serialization.readRulesStreamJson(stream);
|
||||
String mapname = stream.readUTF();
|
||||
Map map = world.maps.all().find(m -> m.name().equals(mapname));
|
||||
if(map == null) map = new Map(customMapDirectory.child(mapname), 1, 1, new ObjectMap<>(), true);
|
||||
world.setMap(map);
|
||||
state.rules.spawns = map.getWaves();
|
||||
if(content.getByID(ContentType.zone, state.rules.zone) != null){
|
||||
Rules rules = content.<Zone>getByID(ContentType.zone, state.rules.zone).rules.get();
|
||||
if(rules.spawns != DefaultWaves.get()){
|
||||
state.rules.spawns = rules.spawns;
|
||||
}
|
||||
}
|
||||
|
||||
int wave = stream.readInt();
|
||||
float wavetime = stream.readFloat();
|
||||
|
||||
state.wave = wave;
|
||||
state.wavetime = wavetime;
|
||||
state.stats = Serialization.readStats(stream);
|
||||
world.spawner.read(stream);
|
||||
|
||||
content.setTemporaryMapper(readContentHeader(stream));
|
||||
|
||||
readEntities(stream);
|
||||
readMap(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutputStream stream) throws IOException{
|
||||
//--META--
|
||||
stream.writeInt(version); //version id
|
||||
stream.writeLong(Time.millis()); //last saved
|
||||
stream.writeLong(headless ? 0 : control.saves.getTotalPlaytime()); //playtime
|
||||
stream.writeInt(Version.build); //build
|
||||
|
||||
//--GENERAL STATE--
|
||||
Serialization.writeRulesStreamJson(stream, state.rules);
|
||||
stream.writeUTF(world.getMap().name()); //map name
|
||||
|
||||
stream.writeInt(state.wave); //wave
|
||||
stream.writeFloat(state.wavetime); //wave countdown
|
||||
|
||||
Serialization.writeStats(stream, state.stats);
|
||||
world.spawner.write(stream);
|
||||
|
||||
writeContentHeader(stream);
|
||||
|
||||
//--ENTITIES--
|
||||
|
||||
writeEntities(stream);
|
||||
|
||||
writeMap(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
package io.anuke.mindustry.maps;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Texture;
|
||||
import io.anuke.arc.util.Log;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.game.DefaultWaves;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.io.JsonIO;
|
||||
|
||||
public class Map implements Comparable<Map>{
|
||||
/** Whether this is a custom map. */
|
||||
public final boolean custom;
|
||||
/** Metadata. Author description, display name, etc. */
|
||||
public final ObjectMap<String, String> tags;
|
||||
public final StringMap tags;
|
||||
/** Base file of this map. File can be named anything at all. */
|
||||
public final FileHandle file;
|
||||
/** Format version. */
|
||||
@@ -29,7 +24,7 @@ public class Map implements Comparable<Map>{
|
||||
/** Build that this map was created in. -1 = unknown or custom build. */
|
||||
public int build;
|
||||
|
||||
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom, int version, int build){
|
||||
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version, int build){
|
||||
this.custom = custom;
|
||||
this.tags = tags;
|
||||
this.file = file;
|
||||
@@ -39,26 +34,16 @@ public class Map implements Comparable<Map>{
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom, int version){
|
||||
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version){
|
||||
this(file, width, height, tags, custom, version, -1);
|
||||
}
|
||||
|
||||
public Map(FileHandle file, int width, int height, ObjectMap<String, String> tags, boolean custom){
|
||||
this(file, width, height, tags, custom, MapIO.version);
|
||||
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom){
|
||||
this(file, width, height, tags, custom, -1);
|
||||
}
|
||||
|
||||
public Array<SpawnGroup> getWaves(){
|
||||
if(tags.containsKey("waves")){
|
||||
try{
|
||||
return world.maps.readWaves(tags.get("waves"));
|
||||
}catch(Exception e){
|
||||
Log.err("Malformed waves: {0}", tags.get("waves"));
|
||||
e.printStackTrace();
|
||||
return DefaultWaves.get();
|
||||
}
|
||||
}else{
|
||||
return DefaultWaves.get();
|
||||
}
|
||||
public Map(StringMap tags){
|
||||
this(Vars.customMapDirectory.child(tags.get("name", "unknown")), 0, 0, tags, true);
|
||||
}
|
||||
|
||||
public int getHightScore(){
|
||||
@@ -70,6 +55,23 @@ public class Map implements Comparable<Map>{
|
||||
Vars.data.modified();
|
||||
}
|
||||
|
||||
/** This creates a new instance.*/
|
||||
public Rules rules(){
|
||||
return JsonIO.read(Rules.class, tags.get("rules", "{}"));
|
||||
}
|
||||
|
||||
/** Whether this map has a core of the enemy 'wave' team. Default: true.
|
||||
* Used for checking Attack mode validity.*/
|
||||
public boolean hasEnemyCore(){
|
||||
return tags.get("enemycore", "true").equals("true");
|
||||
}
|
||||
|
||||
/** Whether this map has a core of any team except the default player team. Default: true.
|
||||
* Used for checking PvP mode validity.*/
|
||||
public boolean hasOtherCores(){
|
||||
return tags.get("othercore", "true").equals("true");
|
||||
}
|
||||
|
||||
public String author(){
|
||||
return tag("author");
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package io.anuke.mindustry.maps;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.*;
|
||||
import io.anuke.arc.files.FileHandle;
|
||||
import io.anuke.arc.graphics.Texture;
|
||||
import io.anuke.arc.util.Disposable;
|
||||
import io.anuke.arc.util.Log;
|
||||
import io.anuke.arc.util.serialization.Json;
|
||||
import io.anuke.mindustry.game.SpawnGroup;
|
||||
import io.anuke.mindustry.io.MapIO;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.io.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
@@ -52,7 +50,7 @@ public class Maps implements Disposable{
|
||||
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension);
|
||||
|
||||
try{
|
||||
return MapIO.readMap(file, false);
|
||||
return MapIO.createMap(file, false);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -81,13 +79,12 @@ public class Maps implements Disposable{
|
||||
* Save a custom map to the directory. This updates all values and stored data necessary.
|
||||
* The tags are copied to prevent mutation later.
|
||||
*/
|
||||
public void saveMap(ObjectMap<String, String> baseTags, Tile[][] data){
|
||||
public void saveMap(ObjectMap<String, String> baseTags){
|
||||
|
||||
try{
|
||||
ObjectMap<String, String> tags = new ObjectMap<>(baseTags);
|
||||
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?");
|
||||
//FileHandle file = customMapDirectory.child(name + "." + mapExtension);
|
||||
FileHandle file;
|
||||
|
||||
//find map with the same exact display name
|
||||
@@ -106,11 +103,11 @@ public class Maps implements Disposable{
|
||||
}
|
||||
|
||||
//create map, write it, etc etc etc
|
||||
Map map = new Map(file, data.length, data[0].length, tags, true);
|
||||
MapIO.writeMap(file, map, data);
|
||||
Map map = new Map(file, world.width(), world.height(), tags, true);
|
||||
MapIO.writeMap(file, map);
|
||||
|
||||
if(!headless){
|
||||
map.texture = new Texture(MapIO.generatePreview(data));
|
||||
map.texture = new Texture(MapIO.generatePreview(world.getTiles()));
|
||||
}
|
||||
maps.add(map);
|
||||
maps.sort();
|
||||
@@ -160,6 +157,34 @@ public class Maps implements Disposable{
|
||||
return str == null ? null : str.equals("[]") ? new Array<>() : Array.with(json.fromJson(SpawnGroup[].class, str));
|
||||
}
|
||||
|
||||
public void loadLegacyMaps(){
|
||||
boolean convertedAny = false;
|
||||
for(FileHandle file : customMapDirectory.list()){
|
||||
if(file.extension().equalsIgnoreCase(oldMapExtension)){
|
||||
try{
|
||||
LegacyMapIO.convertMap(file, file.sibling(file.nameWithoutExtension() + "." + mapExtension));
|
||||
//delete old, converted file; it is no longer useful
|
||||
file.delete();
|
||||
convertedAny = true;
|
||||
Log.info("Converted file {0}", file);
|
||||
}catch(IOException e){
|
||||
//rename the file to a 'mmap_conversion_failed' extension to keep it there just in case
|
||||
//but don't delete it
|
||||
file.copyTo(file.sibling(file.name() + "_conversion_failed"));
|
||||
file.delete();
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//free up any potential memory that was used up during conversion
|
||||
if(convertedAny){
|
||||
world.createTiles(1, 1);
|
||||
//reload maps to load the converted ones
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
/** Find a new filename to put a map to. */
|
||||
private FileHandle findFile(){
|
||||
//find a map name that isn't used.
|
||||
@@ -171,7 +196,7 @@ public class Maps implements Disposable{
|
||||
}
|
||||
|
||||
private void loadMap(FileHandle file, boolean custom) throws IOException{
|
||||
Map map = MapIO.readMap(file, custom);
|
||||
Map map = MapIO.createMap(file, custom);
|
||||
|
||||
if(map.name() == null){
|
||||
throw new IOException("Map name cannot be empty! File: " + file);
|
||||
|
||||
@@ -156,8 +156,7 @@ public abstract class BasicGenerator extends RandomGenerator{
|
||||
block = tiles[x][y].block();
|
||||
ore = tiles[x][y].overlay();
|
||||
r.accept(x, y);
|
||||
tiles[x][y] = new Tile(x, y, floor.id, block.id);
|
||||
tiles[x][y].setOverlay(ore);
|
||||
tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +199,7 @@ public abstract class BasicGenerator extends RandomGenerator{
|
||||
Tile child = tiles[newx][newy];
|
||||
if(!closed.get(child.x, child.y)){
|
||||
closed.set(child.x, child.y);
|
||||
child.setRotation(child.relativeTo(next.x, next.y));
|
||||
child.rotation(child.relativeTo(next.x, next.y));
|
||||
costs.put(child.pos(), th.cost(child) + baseCost);
|
||||
queue.add(child);
|
||||
}
|
||||
@@ -215,7 +214,7 @@ public abstract class BasicGenerator extends RandomGenerator{
|
||||
Tile current = end;
|
||||
while(current != start){
|
||||
out.add(current);
|
||||
Point2 p = Geometry.d4(current.getRotation());
|
||||
Point2 p = Geometry.d4(current.rotation());
|
||||
current = tiles[current.x + p.x][current.y + p.y];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,10 @@ import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Loadout;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.Floor;
|
||||
import io.anuke.mindustry.world.blocks.StaticWall;
|
||||
import io.anuke.mindustry.world.blocks.*;
|
||||
import io.anuke.mindustry.world.blocks.storage.CoreBlock;
|
||||
import io.anuke.mindustry.world.blocks.storage.StorageBlock;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class MapGenerator extends Generator{
|
||||
@@ -70,115 +67,116 @@ public class MapGenerator extends Generator{
|
||||
|
||||
@Override
|
||||
public void generate(Tile[][] tiles){
|
||||
try{
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
tiles[x][y] = new Tile(x, y);
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
tiles[x][y] = new Tile(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO this will probably not get the desired effect
|
||||
MapIO.loadMap(map);
|
||||
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){
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MapIO.readTiles(map, tiles);
|
||||
Array<Point2> players = new Array<>();
|
||||
Array<Point2> enemies = new Array<>();
|
||||
Simplex simplex = new Simplex(Mathf.random(99999));
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
if(tiles[x][y].block() instanceof CoreBlock){
|
||||
players.add(new Point2(x, y));
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
final double scl = 10;
|
||||
Tile tile = tiles[x][y];
|
||||
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1);
|
||||
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1);
|
||||
|
||||
if(tiles[x][y].block() == Blocks.spawn && enemySpawns != -1){
|
||||
enemies.add(new Point2(x, y));
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
|
||||
if(tiles[x][y].block() == Blocks.part){
|
||||
tiles[x][y].setBlock(Blocks.air);
|
||||
}
|
||||
if(((tile.block() instanceof StaticWall
|
||||
&& tiles[newX][newY].block() instanceof StaticWall)
|
||||
|| (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic())
|
||||
|| (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall))){
|
||||
tile.setBlock(tiles[newX][newY].block());
|
||||
}
|
||||
}
|
||||
|
||||
Simplex simplex = new Simplex(Mathf.random(99999));
|
||||
|
||||
for(int x = 0; x < width; x++){
|
||||
for(int y = 0; y < height; y++){
|
||||
final double scl = 10;
|
||||
Tile tile = tiles[x][y];
|
||||
int newX = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x, y) * distortion + x), 0, width - 1);
|
||||
int newY = Mathf.clamp((int)(simplex.octaveNoise2D(1, 1, 1.0 / scl, x + 9999, y + 9999) * distortion + y), 0, height - 1);
|
||||
|
||||
if(((tile.block() instanceof StaticWall
|
||||
&& tiles[newX][newY].block() instanceof StaticWall)
|
||||
|| (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic())
|
||||
|| (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall)) && tiles[newX][newY].block() != Blocks.spawn && tile.block() != Blocks.spawn){
|
||||
tile.setBlock(tiles[newX][newY].block());
|
||||
}
|
||||
|
||||
if(distortFloor){
|
||||
tile.setFloor(tiles[newX][newY].floor());
|
||||
if(distortFloor){
|
||||
tile.setFloor(tiles[newX][newY].floor());
|
||||
if(tiles[newX][newY].overlay() != Blocks.spawn && tile.overlay() != Blocks.spawn){
|
||||
tile.setOverlay(tiles[newX][newY].overlay());
|
||||
}
|
||||
}
|
||||
|
||||
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 instanceof Floor && Mathf.chance(decor.chance)){
|
||||
tile.setFloor((Floor)decor.wall);
|
||||
}
|
||||
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() 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(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 instanceof Floor && 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);
|
||||
}
|
||||
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].setBlock(Blocks.spawn);
|
||||
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;
|
||||
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];
|
||||
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.");
|
||||
}
|
||||
|
||||
loadout.setup(core.x, core.y);
|
||||
|
||||
world.prepareTiles(tiles);
|
||||
world.setMap(map);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Point2 core = players.random();
|
||||
if(core == null){
|
||||
throw new IllegalArgumentException("All zone maps must have a core.");
|
||||
}
|
||||
|
||||
loadout.setup(core.x, core.y);
|
||||
|
||||
world.prepareTiles(tiles);
|
||||
world.setMap(map);
|
||||
}
|
||||
|
||||
public static class Decoration{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package io.anuke.mindustry.maps.generators;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.StringMap;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
import static io.anuke.mindustry.Vars.customMapDirectory;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public abstract class RandomGenerator extends Generator{
|
||||
@@ -26,14 +25,13 @@ public abstract class RandomGenerator extends Generator{
|
||||
block = Blocks.air;
|
||||
ore = Blocks.air;
|
||||
generate(x, y);
|
||||
tiles[x][y] = new Tile(x, y, floor.id, block.id);
|
||||
tiles[x][y].setOverlay(ore);
|
||||
tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id);
|
||||
}
|
||||
}
|
||||
|
||||
decorate(tiles);
|
||||
|
||||
world.setMap(new Map(customMapDirectory.child("generated"), 0, 0, new ObjectMap<>(), true));
|
||||
world.setMap(new Map(new StringMap()));
|
||||
}
|
||||
|
||||
public abstract void decorate(Tile[][] tiles);
|
||||
|
||||
@@ -41,7 +41,7 @@ public class DesertWastesGenerator extends BasicGenerator{
|
||||
overlay(tiles, Blocks.sand, Blocks.pebbles, 0.15f, 5, 0.8f, 30f, 0.62f);
|
||||
//scatter(tiles, Blocks.sandRocks, Blocks.creeptree, 1f);
|
||||
|
||||
tiles[endX][endY].setBlock(Blocks.spawn);
|
||||
tiles[endX][endY].setOverlay(Blocks.spawn);
|
||||
loadout.setup(spawnX, spawnY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class OvergrowthGenerator extends BasicGenerator{
|
||||
noise(tiles, Blocks.darksandTaintedWater, Blocks.duneRocks, 4, 0.7f, 120f, 0.64f);
|
||||
//scatter(tiles, Blocks.sporePine, Blocks.whiteTreeDead, 1f);
|
||||
|
||||
tiles[endX][endY].setBlock(Blocks.spawn);
|
||||
tiles[endX][endY].setOverlay(Blocks.spawn);
|
||||
loadout.setup(spawnX, spawnY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ public class Administration{
|
||||
|
||||
public Administration(){
|
||||
Core.settings.defaults(
|
||||
"strict", true
|
||||
"strict", true,
|
||||
"servername", "Server"
|
||||
);
|
||||
|
||||
load();
|
||||
|
||||
@@ -26,7 +26,6 @@ public class CrashSender{
|
||||
exception.printStackTrace();
|
||||
|
||||
//don't create crash logs for me (anuke) or custom builds, as it's expected
|
||||
//TODO maybe custom builds such as bleeding edge in certain cases
|
||||
if(System.getProperty("user.name").equals("anuke") || Version.build == -1) return;
|
||||
|
||||
//attempt to load version regardless
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package io.anuke.mindustry.net;
|
||||
|
||||
import io.anuke.arc.collection.ObjectMap;
|
||||
import io.anuke.arc.collection.ObjectMap.Entry;
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.entities.Entities;
|
||||
import io.anuke.mindustry.entities.type.Player;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.game.Teams.TeamData;
|
||||
import io.anuke.mindustry.game.Version;
|
||||
import io.anuke.mindustry.gen.Serialization;
|
||||
import io.anuke.mindustry.io.SaveIO;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
@@ -22,45 +20,16 @@ public class NetworkIO{
|
||||
public static void writeWorld(Player player, OutputStream os){
|
||||
|
||||
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||
//--GENERAL STATE--
|
||||
Serialization.writeRules(stream, state.rules);
|
||||
stream.writeUTF(world.getMap().name()); //map name
|
||||
SaveIO.getSaveWriter().writeStringMap(stream, world.getMap().tags);
|
||||
|
||||
//write tags
|
||||
ObjectMap<String, String> tags = world.getMap().tags;
|
||||
stream.writeByte(tags.size);
|
||||
for(Entry<String, String> entry : tags.entries()){
|
||||
stream.writeUTF(entry.key);
|
||||
stream.writeUTF(entry.value);
|
||||
}
|
||||
|
||||
stream.writeInt(state.wave); //wave
|
||||
stream.writeFloat(state.wavetime); //wave countdown
|
||||
stream.writeInt(state.wave);
|
||||
stream.writeFloat(state.wavetime);
|
||||
|
||||
stream.writeInt(player.id);
|
||||
player.write(stream);
|
||||
|
||||
world.spawner.write(stream);
|
||||
SaveIO.getSaveWriter().writeMap(stream);
|
||||
|
||||
stream.write(Team.all.length);
|
||||
|
||||
//write team data
|
||||
for(Team team : Team.all){
|
||||
TeamData data = state.teams.get(team);
|
||||
stream.writeByte(team.ordinal());
|
||||
|
||||
stream.writeByte(data.enemies.size());
|
||||
for(Team enemy : data.enemies){
|
||||
stream.writeByte(enemy.ordinal());
|
||||
}
|
||||
|
||||
stream.writeByte(data.cores.size);
|
||||
for(Tile tile : data.cores){
|
||||
stream.writeInt(tile.pos());
|
||||
}
|
||||
}
|
||||
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -70,25 +39,11 @@ public class NetworkIO{
|
||||
|
||||
try(DataInputStream stream = new DataInputStream(is)){
|
||||
Time.clear();
|
||||
|
||||
//general state
|
||||
state.rules = Serialization.readRules(stream);
|
||||
String map = stream.readUTF();
|
||||
world.setMap(new Map(SaveIO.getSaveWriter().readStringMap(stream)));
|
||||
|
||||
ObjectMap<String, String> tags = new ObjectMap<>();
|
||||
|
||||
byte tagSize = stream.readByte();
|
||||
for(int i = 0; i < tagSize; i++){
|
||||
String key = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(key, value);
|
||||
}
|
||||
|
||||
int wave = stream.readInt();
|
||||
float wavetime = stream.readFloat();
|
||||
|
||||
state.wave = wave;
|
||||
state.wavetime = wavetime;
|
||||
state.wave = stream.readInt();
|
||||
state.wavetime = stream.readFloat();
|
||||
|
||||
Entities.clear();
|
||||
int id = stream.readInt();
|
||||
@@ -97,82 +52,60 @@ public class NetworkIO{
|
||||
player.resetID(id);
|
||||
player.add();
|
||||
|
||||
//map
|
||||
world.spawner.read(stream);
|
||||
SaveIO.getSaveWriter().readMap(stream);
|
||||
world.setMap(new Map(customMapDirectory.child(map), 0, 0, new ObjectMap<>(), true));
|
||||
|
||||
state.teams = new Teams();
|
||||
|
||||
byte teams = stream.readByte();
|
||||
for(int i = 0; i < teams; i++){
|
||||
Team team = Team.all[stream.readByte()];
|
||||
|
||||
byte enemies = stream.readByte();
|
||||
Team[] enemyArr = new Team[enemies];
|
||||
for(int j = 0; j < enemies; j++){
|
||||
enemyArr[j] = Team.all[stream.readByte()];
|
||||
}
|
||||
|
||||
state.teams.add(team, enemyArr);
|
||||
|
||||
byte cores = stream.readByte();
|
||||
|
||||
for(int j = 0; j < cores; j++){
|
||||
state.teams.get(team).cores.add(world.tile(stream.readInt()));
|
||||
}
|
||||
}
|
||||
|
||||
SaveIO.getSaveWriter().readMap(stream, world.context);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteBuffer writeServerData(){
|
||||
int maxlen = 32;
|
||||
|
||||
String host = (headless ? "Server" : player.name);
|
||||
String name = (headless ? Core.settings.getString("servername") : player.name);
|
||||
String map = world.getMap() == null ? "None" : world.getMap().name();
|
||||
|
||||
host = host.substring(0, Math.min(host.length(), maxlen));
|
||||
map = map.substring(0, Math.min(map.length(), maxlen));
|
||||
ByteBuffer buffer = ByteBuffer.allocate(256);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(128);
|
||||
|
||||
buffer.put((byte)host.getBytes(charset).length);
|
||||
buffer.put(host.getBytes(charset));
|
||||
|
||||
buffer.put((byte)map.getBytes(charset).length);
|
||||
buffer.put(map.getBytes(charset));
|
||||
writeString(buffer, name, 100);
|
||||
writeString(buffer, map);
|
||||
|
||||
buffer.putInt(playerGroup.size());
|
||||
buffer.putInt(state.wave);
|
||||
buffer.putInt(Version.build);
|
||||
buffer.put((byte)Version.type.getBytes(charset).length);
|
||||
buffer.put(Version.type.getBytes(charset));
|
||||
writeString(buffer, Version.type);
|
||||
//TODO additional information:
|
||||
// - gamemode ID/name (just pick the closest one?)
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static Host readServerData(String hostAddress, ByteBuffer buffer){
|
||||
byte hlength = buffer.get();
|
||||
byte[] hb = new byte[hlength];
|
||||
buffer.get(hb);
|
||||
|
||||
byte mlength = buffer.get();
|
||||
byte[] mb = new byte[mlength];
|
||||
buffer.get(mb);
|
||||
|
||||
String host = new String(hb, charset);
|
||||
String map = new String(mb, charset);
|
||||
|
||||
String host = readString(buffer);
|
||||
String map = readString(buffer);
|
||||
int players = buffer.getInt();
|
||||
int wave = buffer.getInt();
|
||||
int version = buffer.getInt();
|
||||
byte tlength = buffer.get();
|
||||
byte[] tb = new byte[tlength];
|
||||
buffer.get(tb);
|
||||
String vertype = new String(tb, charset);
|
||||
String vertype = readString(buffer);
|
||||
|
||||
return new Host(host, hostAddress, map, wave, players, version, vertype);
|
||||
}
|
||||
|
||||
private static void writeString(ByteBuffer buffer, String string, int maxlen){
|
||||
byte[] bytes = string.getBytes(charset);
|
||||
//truncating this way may lead to wierd encoding errors at the ends of strings...
|
||||
if(bytes.length > maxlen){
|
||||
bytes = Arrays.copyOfRange(bytes, 0, maxlen);
|
||||
}
|
||||
|
||||
buffer.put((byte)bytes.length);
|
||||
buffer.put(bytes);
|
||||
}
|
||||
|
||||
private static void writeString(ByteBuffer buffer, String string){
|
||||
writeString(buffer, string, 32);
|
||||
}
|
||||
|
||||
private static String readString(ByteBuffer buffer){
|
||||
short length = (short)(buffer.get() & 0xff);
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
return new String(bytes, charset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class Loadout extends Content{
|
||||
int ry = Pos.y(entry.key);
|
||||
Tile tile = world.tile(x + rx, y + ry);
|
||||
world.setBlock(tile, entry.value.block, defaultTeam);
|
||||
tile.setRotation((byte)entry.value.rotation);
|
||||
tile.rotation((byte)entry.value.rotation);
|
||||
if(entry.value.ore != null){
|
||||
for(Tile t : tile.getLinkedTiles(outArray)){
|
||||
t.setOverlay(entry.value.ore);
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.anuke.mindustry.type;
|
||||
|
||||
import io.anuke.mindustry.game.Content;
|
||||
|
||||
//TODO implement-- should it even be content?
|
||||
//currently unimplemented, see trello for implementation plans
|
||||
public class WeatherEvent extends Content{
|
||||
public final String name;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ public class BorderImage extends Image{
|
||||
float scaleY = getScaleY();
|
||||
|
||||
Draw.color(Pal.accent);
|
||||
Draw.alpha(parentAlpha);
|
||||
Lines.stroke(Unit.dp.scl(thickness));
|
||||
Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY);
|
||||
Draw.reset();
|
||||
|
||||
@@ -7,9 +7,7 @@ import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.scene.ui.ScrollPane;
|
||||
import io.anuke.arc.scene.ui.layout.Cell;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.util.OS;
|
||||
import io.anuke.arc.util.Strings;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.graphics.Pal;
|
||||
import io.anuke.mindustry.io.Contributors;
|
||||
import io.anuke.mindustry.io.Contributors.Contributor;
|
||||
@@ -27,7 +25,10 @@ public class AboutDialog extends FloatingDialog{
|
||||
super("$about.button");
|
||||
|
||||
if(!ios){
|
||||
Contributors.getContributors(out -> contributors = out, Throwable::printStackTrace);
|
||||
shown(() -> Contributors.getContributors(out -> {
|
||||
contributors = out;
|
||||
Core.app.post(this::setup);
|
||||
}, Throwable::printStackTrace));
|
||||
}
|
||||
|
||||
shown(this::setup);
|
||||
|
||||
@@ -24,15 +24,17 @@ public class ChangelogDialog extends FloatingDialog{
|
||||
|
||||
cont.add("$changelog.loading");
|
||||
|
||||
if(!ios && !OS.isMac){
|
||||
Changelogs.getChangelog(result -> {
|
||||
versions = result;
|
||||
Core.app.post(this::setup);
|
||||
}, t -> {
|
||||
Log.err(t);
|
||||
Core.app.post(this::setup);
|
||||
});
|
||||
}
|
||||
shown(() -> {
|
||||
if(!ios && !OS.isMac){
|
||||
Changelogs.getChangelog(result -> {
|
||||
versions = result;
|
||||
Core.app.post(this::setup);
|
||||
}, t -> {
|
||||
Log.err(t);
|
||||
Core.app.post(this::setup);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setup(){
|
||||
|
||||
@@ -2,28 +2,18 @@ package io.anuke.mindustry.ui.dialogs;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.graphics.g2d.TextureRegion;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.scene.event.Touchable;
|
||||
import io.anuke.arc.scene.ui.ButtonGroup;
|
||||
import io.anuke.arc.scene.ui.ImageButton;
|
||||
import io.anuke.arc.scene.ui.ScrollPane;
|
||||
import io.anuke.arc.scene.ui.TextButton;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.util.Align;
|
||||
import io.anuke.arc.util.Scaling;
|
||||
import io.anuke.mindustry.game.Difficulty;
|
||||
import io.anuke.mindustry.game.Gamemode;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.ui.BorderImage;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class CustomGameDialog extends FloatingDialog{
|
||||
Difficulty difficulty = Difficulty.normal;
|
||||
CustomRulesDialog dialog = new CustomRulesDialog();
|
||||
Rules rules;
|
||||
Gamemode selectedGamemode;
|
||||
private MapPlayDialog dialog = new MapPlayDialog();
|
||||
|
||||
public CustomGameDialog(){
|
||||
super("$customgame");
|
||||
@@ -33,9 +23,11 @@ public class CustomGameDialog extends FloatingDialog{
|
||||
}
|
||||
|
||||
void setup(){
|
||||
selectedGamemode = Gamemode.survival;
|
||||
rules = selectedGamemode.get();
|
||||
|
||||
clearChildren();
|
||||
add(titleTable);
|
||||
row();
|
||||
stack(cont, buttons).grow();
|
||||
buttons.bottom();
|
||||
cont.clear();
|
||||
|
||||
Table maps = new Table();
|
||||
@@ -44,63 +36,9 @@ public class CustomGameDialog extends FloatingDialog{
|
||||
pane.setFadeScrollBars(false);
|
||||
|
||||
int maxwidth = (Core.graphics.isPortrait() ? 2 : 4);
|
||||
|
||||
Table selmode = new Table();
|
||||
ButtonGroup<TextButton> group = new ButtonGroup<>();
|
||||
selmode.add("$level.mode").colspan(4);
|
||||
selmode.row();
|
||||
int i = 0;
|
||||
|
||||
Table modes = new Table();
|
||||
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
modes.addButton(mode.toString(), "toggle", () -> {
|
||||
selectedGamemode = mode;
|
||||
rules = mode.get();
|
||||
dialog.selectedGamemode = null;
|
||||
dialog.rules = null;
|
||||
}).update(b -> b.setChecked(selectedGamemode == mode)).group(group).size(140f, 54f);
|
||||
if(i++ % 2 == 1) modes.row();
|
||||
}
|
||||
selmode.add(modes);
|
||||
selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f);
|
||||
|
||||
cont.add(selmode);
|
||||
cont.row();
|
||||
|
||||
Difficulty[] ds = Difficulty.values();
|
||||
|
||||
float s = 50f;
|
||||
|
||||
Table sdif = new Table();
|
||||
|
||||
sdif.add("$setting.difficulty.name").colspan(3);
|
||||
sdif.row();
|
||||
sdif.defaults().height(s + 4);
|
||||
sdif.addImageButton("icon-arrow-left", 10 * 3, () -> {
|
||||
difficulty = (ds[Mathf.mod(difficulty.ordinal() - 1, ds.length)]);
|
||||
state.wavetime = difficulty.waveTime;
|
||||
}).width(s);
|
||||
|
||||
sdif.addButton("", () -> {
|
||||
})
|
||||
.update(t -> {
|
||||
t.setText(difficulty.toString());
|
||||
t.touchable(Touchable.disabled);
|
||||
}).width(180f);
|
||||
|
||||
sdif.addImageButton("icon-arrow-right", 10 * 3, () -> {
|
||||
difficulty = (ds[Mathf.mod(difficulty.ordinal() + 1, ds.length)]);
|
||||
state.wavetime = difficulty.waveTime;
|
||||
}).width(s);
|
||||
sdif.addButton("$customize", () -> dialog.show(rules, selectedGamemode)).width(140).padLeft(10);
|
||||
|
||||
cont.add(sdif);
|
||||
cont.row();
|
||||
|
||||
float images = 146f;
|
||||
|
||||
i = 0;
|
||||
int i = 0;
|
||||
maps.defaults().width(170).fillY().top().pad(4f);
|
||||
for(Map map : world.maps.all()){
|
||||
|
||||
@@ -121,10 +59,7 @@ public class CustomGameDialog extends FloatingDialog{
|
||||
border.setScaling(Scaling.fit);
|
||||
image.replaceImage(border);
|
||||
|
||||
image.clicked(() -> {
|
||||
hide();
|
||||
control.playMap(map, (dialog.rules == null) ? rules : dialog.rules);
|
||||
});
|
||||
image.clicked(() -> dialog.show(map));
|
||||
|
||||
maps.add(image);
|
||||
|
||||
@@ -137,22 +72,4 @@ public class CustomGameDialog extends FloatingDialog{
|
||||
|
||||
cont.add(pane).uniformX();
|
||||
}
|
||||
|
||||
private void displayGameModeHelp(){
|
||||
FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title"));
|
||||
d.setFillParent(false);
|
||||
Table table = new Table();
|
||||
table.defaults().pad(1f);
|
||||
ScrollPane pane = new ScrollPane(table);
|
||||
pane.setFadeScrollBars(false);
|
||||
table.row();
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f);
|
||||
table.row();
|
||||
}
|
||||
|
||||
d.cont.add(pane);
|
||||
d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f);
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.util.Strings;
|
||||
import io.anuke.mindustry.core.Platform;
|
||||
import io.anuke.mindustry.game.Gamemode;
|
||||
import io.anuke.mindustry.game.Rules;
|
||||
import io.anuke.mindustry.graphics.Pal;
|
||||
|
||||
@@ -13,8 +12,8 @@ import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
public class CustomRulesDialog extends FloatingDialog{
|
||||
private Table main;
|
||||
public Rules rules;
|
||||
public Gamemode selectedGamemode;
|
||||
private Rules rules;
|
||||
private Supplier<Rules> resetter;
|
||||
|
||||
public CustomRulesDialog(){
|
||||
super("$mode.custom");
|
||||
@@ -24,9 +23,9 @@ public class CustomRulesDialog extends FloatingDialog{
|
||||
addCloseButton();
|
||||
}
|
||||
|
||||
public void show(Rules rules, Gamemode gamemode){
|
||||
public void show(Rules rules, Supplier<Rules> resetter){
|
||||
this.rules = rules;
|
||||
this.selectedGamemode = gamemode;
|
||||
this.resetter = resetter;
|
||||
show();
|
||||
}
|
||||
|
||||
@@ -35,12 +34,12 @@ public class CustomRulesDialog extends FloatingDialog{
|
||||
cont.pane(m -> main = m);
|
||||
main.margin(10f);
|
||||
main.addButton("$settings.reset", () -> {
|
||||
rules = selectedGamemode.get();
|
||||
rules = resetter.get();
|
||||
setup();
|
||||
}).size(300f, 50f);
|
||||
main.left().defaults().fillX().left().pad(5);
|
||||
main.row();
|
||||
|
||||
|
||||
title("$rules.title.waves");
|
||||
check("$rules.waves", b -> rules.waves = b, () -> rules.waves);
|
||||
check("$rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer, () -> rules.waves);
|
||||
@@ -49,8 +48,8 @@ public class CustomRulesDialog extends FloatingDialog{
|
||||
number("$rules.dropzoneradius", false, f -> rules.dropZoneRadius = f * tilesize, () -> rules.dropZoneRadius / tilesize, () -> rules.waves);
|
||||
|
||||
title("$rules.title.respawns");
|
||||
check("$rules.limitedRespawns", b -> rules.limitedRespawns= b, () -> rules.limitedRespawns);
|
||||
number("$rules.respawns", true, f -> rules.respawns = (int) f, () -> rules.respawns, () -> rules.limitedRespawns);
|
||||
check("$rules.limitedRespawns", b -> rules.limitedRespawns = b, () -> rules.limitedRespawns);
|
||||
number("$rules.respawns", true, f -> rules.respawns = (int)f, () -> rules.respawns, () -> rules.limitedRespawns);
|
||||
number("$rules.respawntime", f -> rules.respawnTime = f * 60f, () -> rules.respawnTime / 60f);
|
||||
|
||||
title("$rules.title.resourcesbuilding");
|
||||
@@ -63,7 +62,7 @@ public class CustomRulesDialog extends FloatingDialog{
|
||||
number("$rules.playerhealthmultiplier", f -> rules.playerHealthMultiplier = f, () -> rules.playerHealthMultiplier);
|
||||
|
||||
title("$rules.title.unit");
|
||||
check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, ()->true);
|
||||
check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, () -> true);
|
||||
number("$rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier);
|
||||
number("$rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier);
|
||||
number("$rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier);
|
||||
@@ -80,11 +79,11 @@ public class CustomRulesDialog extends FloatingDialog{
|
||||
main.table(t -> {
|
||||
t.left();
|
||||
t.add(text).left().padRight(5)
|
||||
.update(a->a.setColor(condition.get() ? Color.WHITE : Color.GRAY));
|
||||
.update(a -> a.setColor(condition.get() ? Color.WHITE : Color.GRAY));
|
||||
Platform.instance.addDialog(t.addField((integer ? (int)prov.get() : prov.get()) + "", s -> cons.accept(Strings.parseFloat(s)))
|
||||
.padRight(100f)
|
||||
.update(a -> a.setDisabled(!condition.get()))
|
||||
.valid(Strings::canParsePositiveFloat).width(120f) .left().get());
|
||||
.padRight(100f)
|
||||
.update(a -> a.setDisabled(!condition.get()))
|
||||
.valid(Strings::canParsePositiveFloat).width(120f).left().get());
|
||||
}).padTop(0);
|
||||
main.row();
|
||||
}
|
||||
|
||||
@@ -164,9 +164,9 @@ public class JoinDialog extends FloatingDialog{
|
||||
server.content.clear();
|
||||
|
||||
server.content.table(t -> {
|
||||
t.add(versionString).left();
|
||||
t.add("[lightgray]" + host.name).width(targetWidth() - 10f).left().get().setEllipsis(true);
|
||||
t.row();
|
||||
t.add("[lightgray]" + Core.bundle.format("server.hostname", host.name)).width(targetWidth() - 10f).left().get().setEllipsis(true);
|
||||
t.add(versionString).left();
|
||||
t.row();
|
||||
t.add("[lightgray]" + (host.players != 1 ? Core.bundle.format("players", host.players) :
|
||||
Core.bundle.format("players.single", host.players))).left();
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.anuke.arc.Core;
|
||||
import io.anuke.arc.scene.ui.*;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.util.Log;
|
||||
import io.anuke.arc.util.Strings;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -28,7 +29,7 @@ public class LanguageDialog extends FloatingDialog{
|
||||
ButtonGroup<TextButton> group = new ButtonGroup<>();
|
||||
|
||||
for(Locale loc : locales){
|
||||
TextButton button = new TextButton(loc.getDisplayName(loc), "toggle");
|
||||
TextButton button = new TextButton(Strings.capitalize(loc.getDisplayName(loc)), "toggle");
|
||||
button.clicked(() -> {
|
||||
if(getLocale().equals(loc)) return;
|
||||
Core.settings.put("locale", loc.toString());
|
||||
|
||||
@@ -185,12 +185,7 @@ public class LoadDialog extends FloatingDialog{
|
||||
button.clicked(() -> {
|
||||
if(!button.childrenPressed()){
|
||||
int build = slot.getBuild();
|
||||
if(SaveIO.breakingVersions.contains(build)){
|
||||
ui.showInfo("$save.old");
|
||||
slot.delete();
|
||||
}else{
|
||||
runLoadSave(slot);
|
||||
}
|
||||
runLoadSave(slot);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
118
core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java
Normal file
118
core/src/io/anuke/mindustry/ui/dialogs/MapPlayDialog.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package io.anuke.mindustry.ui.dialogs;
|
||||
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.scene.event.Touchable;
|
||||
import io.anuke.arc.scene.ui.ScrollPane;
|
||||
import io.anuke.arc.scene.ui.layout.Table;
|
||||
import io.anuke.arc.util.Scaling;
|
||||
import io.anuke.mindustry.game.*;
|
||||
import io.anuke.mindustry.maps.Map;
|
||||
import io.anuke.mindustry.ui.BorderImage;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class MapPlayDialog extends FloatingDialog{
|
||||
Difficulty difficulty = Difficulty.normal;
|
||||
CustomRulesDialog dialog = new CustomRulesDialog();
|
||||
Rules rules;
|
||||
Gamemode selectedGamemode;
|
||||
|
||||
public MapPlayDialog(){
|
||||
super("");
|
||||
setFillParent(false);
|
||||
}
|
||||
|
||||
public void show(Map map){
|
||||
title.setText(map.name());
|
||||
cont.clearChildren();
|
||||
rules = map.rules();
|
||||
|
||||
Table selmode = new Table();
|
||||
selmode.add("$level.mode").colspan(4);
|
||||
selmode.row();
|
||||
int i = 0;
|
||||
|
||||
Table modes = new Table();
|
||||
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
if(mode.hidden) continue;
|
||||
|
||||
if((mode == Gamemode.attack && !map.hasEnemyCore()) || (mode == Gamemode.pvp && !map.hasOtherCores())){
|
||||
continue;
|
||||
}
|
||||
|
||||
modes.addButton(mode.toString(), "toggle", () -> {
|
||||
selectedGamemode = selectedGamemode == mode ? null : mode;
|
||||
rules = mode.apply(map.rules());
|
||||
}).update(b -> b.setChecked(selectedGamemode == mode)).size(140f, 54f);
|
||||
if(i++ % 2 == 1) modes.row();
|
||||
}
|
||||
selmode.add(modes);
|
||||
selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f);
|
||||
|
||||
cont.add(selmode);
|
||||
cont.row();
|
||||
|
||||
Difficulty[] ds = Difficulty.values();
|
||||
|
||||
float s = 50f;
|
||||
|
||||
Table sdif = new Table();
|
||||
|
||||
sdif.add("$setting.difficulty.name").colspan(3);
|
||||
sdif.row();
|
||||
sdif.defaults().height(s + 4);
|
||||
sdif.addImageButton("icon-arrow-left", 10 * 3, () -> {
|
||||
difficulty = (ds[Mathf.mod(difficulty.ordinal() - 1, ds.length)]);
|
||||
state.wavetime = difficulty.waveTime;
|
||||
}).width(s);
|
||||
|
||||
sdif.addButton("", () -> {}).update(t -> {
|
||||
t.setText(difficulty.toString());
|
||||
t.touchable(Touchable.disabled);
|
||||
}).width(180f);
|
||||
|
||||
sdif.addImageButton("icon-arrow-right", 10 * 3, () -> {
|
||||
difficulty = (ds[Mathf.mod(difficulty.ordinal() + 1, ds.length)]);
|
||||
state.wavetime = difficulty.waveTime;
|
||||
}).width(s);
|
||||
sdif.addButton("$customize", () -> dialog.show(rules, () -> rules = (selectedGamemode == null ? map.rules() : selectedGamemode.apply(map.rules())))).width(140).padLeft(10);
|
||||
|
||||
cont.add(sdif);
|
||||
cont.row();
|
||||
cont.add(new BorderImage(map.texture, 3f)).size(250f).get().setScaling(Scaling.fit);
|
||||
|
||||
buttons.clearChildren();
|
||||
addCloseButton();
|
||||
|
||||
buttons.addImageTextButton("$play", "icon-play", 8*3, () -> {
|
||||
control.playMap(map, rules);
|
||||
hide();
|
||||
ui.custom.hide();
|
||||
}).size(210f, 64f);
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void displayGameModeHelp(){
|
||||
FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title"));
|
||||
d.setFillParent(false);
|
||||
Table table = new Table();
|
||||
table.defaults().pad(1f);
|
||||
ScrollPane pane = new ScrollPane(table);
|
||||
pane.setFadeScrollBars(false);
|
||||
table.row();
|
||||
for(Gamemode mode : Gamemode.values()){
|
||||
if(mode.hidden) continue;
|
||||
table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f);
|
||||
table.row();
|
||||
}
|
||||
|
||||
d.cont.add(pane);
|
||||
d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f);
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class MapsDialog extends FloatingDialog{
|
||||
buttons.addImageTextButton("$editor.importmap", "icon-add", 14 * 2, () -> {
|
||||
Platform.instance.showFileChooser("$editor.importmap", "Map File", file -> {
|
||||
try{
|
||||
Map map = MapIO.readMap(file, true);
|
||||
Map map = MapIO.createMap(file, true);
|
||||
String name = map.tags.get("name");
|
||||
if(name == null){
|
||||
ui.showError("$editor.errorname");
|
||||
|
||||
@@ -47,7 +47,7 @@ public class PausedDialog extends FloatingDialog{
|
||||
}
|
||||
cont.addButton("$settings", ui.settings::show);
|
||||
|
||||
if(!world.isZone()){
|
||||
if(!world.isZone() && !state.isEditor()){
|
||||
cont.row();
|
||||
cont.addButton("$savegame", save::show);
|
||||
cont.addButton("$loadgame", load::show).disabled(b -> Net.active());
|
||||
@@ -55,9 +55,10 @@ public class PausedDialog extends FloatingDialog{
|
||||
|
||||
cont.row();
|
||||
|
||||
cont.addButton("$hostserver", ui.host::show).disabled(b -> Net.active()).colspan(2).width(dw * 2 + 20f);
|
||||
|
||||
cont.row();
|
||||
if(!state.isEditor()){
|
||||
cont.addButton("$hostserver", ui.host::show).disabled(b -> Net.active()).colspan(2).width(dw * 2 + 20f);
|
||||
cont.row();
|
||||
}
|
||||
|
||||
cont.addButton("$quit", () -> {
|
||||
ui.showConfirm("$confirm", "$quit.confirm", () -> {
|
||||
@@ -74,7 +75,7 @@ public class PausedDialog extends FloatingDialog{
|
||||
cont.addRowImageTextButton("$back", "icon-play-2", isize, this::hide);
|
||||
cont.addRowImageTextButton("$settings", "icon-tools", isize, ui.settings::show);
|
||||
|
||||
if(!world.isZone()){
|
||||
if(!world.isZone() && !state.isEditor()){
|
||||
cont.addRowImageTextButton("$save", "icon-save", isize, save::show);
|
||||
|
||||
cont.row();
|
||||
@@ -84,7 +85,9 @@ public class PausedDialog extends FloatingDialog{
|
||||
cont.row();
|
||||
}
|
||||
|
||||
cont.addRowImageTextButton("$hostserver.mobile", "icon-host", isize, ui.host::show).disabled(b -> Net.active());
|
||||
if(!state.isEditor()){
|
||||
cont.addRowImageTextButton("$hostserver.mobile", "icon-host", isize, ui.host::show).disabled(b -> Net.active());
|
||||
}
|
||||
cont.addRowImageTextButton("$quit", "icon-quit", isize, () -> {
|
||||
ui.showConfirm("$confirm", "$quit.confirm", () -> {
|
||||
if(Net.client()) netClient.disconnectQuietly();
|
||||
@@ -96,8 +99,12 @@ public class PausedDialog extends FloatingDialog{
|
||||
}
|
||||
|
||||
public void runExitSave(){
|
||||
if(control.saves.getCurrent() == null ||
|
||||
!control.saves.getCurrent().isAutosave()){
|
||||
if(state.isEditor()){
|
||||
ui.editor.resumeEditing();
|
||||
return;
|
||||
}
|
||||
|
||||
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave()){
|
||||
state.set(State.menu);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class ZoneInfoDialog extends FloatingDialog{
|
||||
ui.deploy.hide();
|
||||
data.removeItems(zone.getLaunchCost());
|
||||
hide();
|
||||
world.playZone(zone);
|
||||
control.playZone(zone);
|
||||
}
|
||||
}).minWidth(150f).margin(13f).padTop(5).disabled(b -> zone.locked() ? !canUnlock(zone) : !data.hasItems(zone.getLaunchCost())).uniformY().get();
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ public class BlockInventoryFragment extends Fragment{
|
||||
}
|
||||
|
||||
public void showFor(Tile t){
|
||||
if(this.tile == t.target()){
|
||||
if(this.tile == t){
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
this.tile = t.target();
|
||||
this.tile = t;
|
||||
if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0)
|
||||
return;
|
||||
rebuild(true);
|
||||
|
||||
@@ -4,30 +4,39 @@ import io.anuke.arc.Core;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.graphics.Color;
|
||||
import io.anuke.arc.graphics.g2d.Draw;
|
||||
import io.anuke.arc.graphics.g2d.Lines;
|
||||
import io.anuke.arc.input.KeyCode;
|
||||
import io.anuke.arc.math.Interpolation;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.Vector2;
|
||||
import io.anuke.arc.scene.Element;
|
||||
import io.anuke.arc.scene.Group;
|
||||
import io.anuke.arc.scene.actions.Actions;
|
||||
import io.anuke.arc.scene.event.Touchable;
|
||||
import io.anuke.arc.scene.style.TextureRegionDrawable;
|
||||
import io.anuke.arc.scene.ui.*;
|
||||
import io.anuke.arc.scene.ui.layout.*;
|
||||
import io.anuke.arc.scene.utils.Elements;
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.mindustry.content.Fx;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.entities.Effects;
|
||||
import io.anuke.mindustry.entities.Units;
|
||||
import io.anuke.mindustry.entities.type.BaseUnit;
|
||||
import io.anuke.mindustry.game.EventType.StateChangeEvent;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.game.UnlockableContent;
|
||||
import io.anuke.mindustry.gen.Call;
|
||||
import io.anuke.mindustry.graphics.Pal;
|
||||
import io.anuke.mindustry.input.Binding;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.mindustry.net.Packets.AdminAction;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.type.UnitType;
|
||||
import io.anuke.mindustry.ui.*;
|
||||
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
|
||||
|
||||
import java.lang.StringBuilder;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class HudFragment extends Fragment{
|
||||
@@ -131,8 +140,13 @@ public class HudFragment extends Fragment{
|
||||
}
|
||||
});
|
||||
|
||||
cont.table(stuff -> {
|
||||
stuff.left();
|
||||
Table wavesMain, editorMain;
|
||||
|
||||
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(e -> wavesMain.isVisible() ? wavesMain.getPrefHeight() : editorMain.getPrefHeight());
|
||||
|
||||
{
|
||||
wavesMain.visible(() -> shown && !state.isEditor());
|
||||
wavesMain.left();
|
||||
Stack stack = new Stack();
|
||||
TextButton waves = new TextButton("", "wave");
|
||||
Table btable = new Table().margin(0);
|
||||
@@ -142,17 +156,103 @@ public class HudFragment extends Fragment{
|
||||
|
||||
addWaveTable(waves);
|
||||
addPlayButton(btable);
|
||||
stuff.add(stack).width(dsize * 4 + 3f);
|
||||
stuff.row();
|
||||
stuff.table("button", t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.WHITE))
|
||||
wavesMain.add(stack).width(dsize * 4 + 3f);
|
||||
wavesMain.row();
|
||||
wavesMain.table("button", t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.WHITE))
|
||||
.grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).get();
|
||||
stuff.row();
|
||||
}).visible(() -> shown);
|
||||
wavesMain.row();
|
||||
}
|
||||
|
||||
{
|
||||
editorMain.table("button-edge-4", t -> {
|
||||
//t.margin(0f);
|
||||
t.add("$editor.teams").growX().left();
|
||||
t.row();
|
||||
t.table(teams -> {
|
||||
teams.left();
|
||||
int i = 0;
|
||||
for(Team team : Team.all){
|
||||
ImageButton button = teams.addImageButton("white", "clear-toggle-partial", 40f, () -> player.setTeam(team))
|
||||
.size(50f).margin(6f).get();
|
||||
button.getImageCell().grow();
|
||||
button.getStyle().imageUpColor = team.color;
|
||||
button.update(() -> button.setChecked(player.getTeam() == team));
|
||||
|
||||
if(++i % 3 == 0){
|
||||
teams.row();
|
||||
}
|
||||
}
|
||||
}).left();
|
||||
|
||||
t.row();
|
||||
t.addImageTextButton("$editor.spawn", "icon-add", 8*3, () -> {
|
||||
FloatingDialog dialog = new FloatingDialog("$editor.spawn");
|
||||
int i = 0;
|
||||
for(UnitType type : content.<UnitType>getBy(ContentType.unit)){
|
||||
dialog.cont.addImageButton("white", 48, () -> {
|
||||
BaseUnit unit = type.create(player.getTeam());
|
||||
unit.set(player.x, player.y);
|
||||
unit.rotation = player.rotation;
|
||||
unit.add();
|
||||
//trigger the entity to become visible
|
||||
unitGroups[player.getTeam().ordinal()].updateEvents();
|
||||
collisions.updatePhysics( unitGroups[player.getTeam().ordinal()]);
|
||||
dialog.hide();
|
||||
}).get().getStyle().imageUp = new TextureRegionDrawable(type.iconRegion);
|
||||
if(++i % 4 == 0) dialog.cont.row();
|
||||
}
|
||||
dialog.addCloseButton();
|
||||
dialog.setFillParent(false);
|
||||
dialog.show();
|
||||
}).fillX();
|
||||
|
||||
float[] size = {0};
|
||||
float[] position = {0, 0};
|
||||
|
||||
t.row();
|
||||
t.addImageTextButton("$editor.removeunit", "icon-quit", "toggle", 8*3, () -> {
|
||||
|
||||
}).fillX().update(b -> {
|
||||
boolean[] found = {false};
|
||||
if(b.isChecked()){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
if(e == null){
|
||||
Vector2 world = Core.input.mouseWorld();
|
||||
Units.nearby(world.x, world.y, 1f, 1f, unit -> {
|
||||
if(!found[0] && unit instanceof BaseUnit){
|
||||
if(Core.input.keyTap(KeyCode.MOUSE_LEFT)){
|
||||
Effects.effect(Fx.spawn, unit);
|
||||
unit.remove();
|
||||
unitGroups[unit.getTeam().ordinal()].updateEvents();
|
||||
collisions.updatePhysics(unitGroups[unit.getTeam().ordinal()]);
|
||||
}
|
||||
found[0] = true;
|
||||
unit.hitbox(Tmp.r1);
|
||||
size[0] = Mathf.lerpDelta(size[0], Tmp.r1.width*2f + Mathf.absin(Time.time(), 10f, 5f), 0.1f);
|
||||
position[0] = unit.x;
|
||||
position[1] = unit.y;
|
||||
}
|
||||
});
|
||||
//TODO check for unit removal, remove unit if needed
|
||||
}
|
||||
}
|
||||
|
||||
Draw.color(Pal.accent, Color.WHITE, Mathf.absin(Time.time(), 8f, 1f));
|
||||
Lines.poly(position[0], position[1], 4, size[0]/2f);
|
||||
Draw.reset();
|
||||
|
||||
if(!found[0]){
|
||||
size[0] = Mathf.lerpDelta(size[0], 0f, 0.2f);
|
||||
}
|
||||
});
|
||||
}).width(dsize * 4 + 3f);
|
||||
editorMain.visible(() -> shown && state.isEditor());
|
||||
}
|
||||
|
||||
//fps display
|
||||
cont.table(info -> {
|
||||
info.top().left().margin(4).visible(() -> Core.settings.getBool("fps"));
|
||||
info.update(() -> info.setTranslation(state.rules.waves ? 0f : -Unit.dp.scl(dsize * 4 + 3), 0));
|
||||
info.update(() -> info.setTranslation(state.rules.waves || state.isEditor() ? 0f : -Unit.dp.scl(dsize * 4 + 3), 0));
|
||||
IntFormat fps = new IntFormat("fps");
|
||||
IntFormat ping = new IntFormat("ping");
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PlacementFragment extends Fragment{
|
||||
Table blockTable, toggler, topTable;
|
||||
boolean lastGround;
|
||||
|
||||
//TODO make this configurable
|
||||
//not configurable, no plans to make it configurable
|
||||
final KeyCode[] inputGrid = {
|
||||
KeyCode.NUM_1, KeyCode.NUM_2, KeyCode.NUM_3, KeyCode.NUM_4,
|
||||
KeyCode.Q, KeyCode.W, KeyCode.E, KeyCode.R,
|
||||
@@ -78,7 +78,7 @@ public class PlacementFragment extends Fragment{
|
||||
Tile tile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
|
||||
if(tile != null){
|
||||
tile = tile.target();
|
||||
tile = tile.link();
|
||||
Block tryRecipe = tile.block();
|
||||
if(tryRecipe.isVisible() && unlocked(tryRecipe)){
|
||||
input.block = tryRecipe;
|
||||
@@ -150,7 +150,7 @@ public class PlacementFragment extends Fragment{
|
||||
|
||||
button.update(() -> { //color unplacable things gray
|
||||
TileEntity core = player.getClosestCore();
|
||||
Color color = core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources) ? Color.WHITE : Color.GRAY;
|
||||
Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.buildRequirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.WHITE : Color.GRAY;
|
||||
button.forEach(elem -> elem.setColor(color));
|
||||
button.setChecked(input.block == block);
|
||||
});
|
||||
@@ -313,7 +313,7 @@ public class PlacementFragment extends Fragment{
|
||||
if(!Core.scene.hasMouse() && topTable.hit(v.x, v.y, false) == null){
|
||||
Tile tile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||
if(tile != null){
|
||||
hoverTile = tile.target();
|
||||
hoverTile = tile.link();
|
||||
}else{
|
||||
hoverTile = null;
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ public class Block extends BlockStorage{
|
||||
}
|
||||
|
||||
public void draw(Tile tile){
|
||||
Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.getRotation() * 90 : 0);
|
||||
Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.rotation() * 90 : 0);
|
||||
}
|
||||
|
||||
public void drawTeam(Tile tile){
|
||||
@@ -451,6 +451,10 @@ public class Block extends BlockStorage{
|
||||
}
|
||||
}
|
||||
|
||||
public Tile linked(Tile tile){
|
||||
return tile;
|
||||
}
|
||||
|
||||
public boolean isSolidFor(Tile tile){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public abstract class BlockStorage extends UnlockableContent{
|
||||
|
||||
public void tryDumpLiquid(Tile tile, Liquid liquid){
|
||||
Array<Tile> proximity = tile.entity.proximity();
|
||||
int dump = tile.getDump();
|
||||
int dump = tile.rotation();
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(tile, proximity.size);
|
||||
@@ -138,7 +138,7 @@ public abstract class BlockStorage extends UnlockableContent{
|
||||
public float tryMoveLiquid(Tile tile, Tile next, boolean leak, Liquid liquid){
|
||||
if(next == null) return 0;
|
||||
|
||||
next = next.target();
|
||||
next = next.link();
|
||||
|
||||
if(next.getTeam() == tile.getTeam() && next.block().hasLiquids && tile.entity.liquids.get(liquid) > 0f){
|
||||
|
||||
@@ -182,7 +182,7 @@ public abstract class BlockStorage extends UnlockableContent{
|
||||
*/
|
||||
public void offloadNear(Tile tile, Item item){
|
||||
Array<Tile> proximity = tile.entity.proximity();
|
||||
int dump = tile.getDump();
|
||||
int dump = tile.rotation();
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(tile, proximity.size);
|
||||
@@ -212,7 +212,7 @@ public abstract class BlockStorage extends UnlockableContent{
|
||||
return false;
|
||||
|
||||
Array<Tile> proximity = entity.proximity();
|
||||
int dump = tile.getDump();
|
||||
int dump = tile.rotation();
|
||||
|
||||
if(proximity.size == 0) return false;
|
||||
|
||||
@@ -249,7 +249,7 @@ public abstract class BlockStorage extends UnlockableContent{
|
||||
}
|
||||
|
||||
protected void incrementDump(Tile tile, int prox){
|
||||
tile.setDump((byte)((tile.getDump() + 1) % prox));
|
||||
tile.rotation((byte)((tile.rotation() + 1) % prox));
|
||||
}
|
||||
|
||||
/** Used for dumping items. */
|
||||
@@ -259,8 +259,9 @@ public abstract class BlockStorage extends UnlockableContent{
|
||||
|
||||
/** Try offloading an item to a nearby container in its facing direction. Returns true if success. */
|
||||
public boolean offloadDir(Tile tile, Item item){
|
||||
Tile other = tile.getNearby(tile.getRotation());
|
||||
if(other != null && other.target().getTeamID() == tile.getTeamID() && other.block().acceptItem(item, other, tile)){
|
||||
Tile other = tile.getNearby(tile.rotation());
|
||||
if(other != null) other = other.link();
|
||||
if(other != null && other.getTeam() == tile.getTeam() && other.block().acceptItem(item, other, tile)){
|
||||
other.block().handleItem(item, other, tile);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.entities.Units;
|
||||
import io.anuke.mindustry.game.EventType.BlockBuildBeginEvent;
|
||||
import io.anuke.mindustry.game.Team;
|
||||
import io.anuke.mindustry.type.ContentType;
|
||||
import io.anuke.mindustry.world.blocks.BuildBlock;
|
||||
import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
@@ -25,7 +25,7 @@ public class Build{
|
||||
return;
|
||||
}
|
||||
|
||||
Tile tile = world.tile(x, y);
|
||||
Tile tile = world.ltile(x, y);
|
||||
float prevPercent = 1f;
|
||||
|
||||
//just in case
|
||||
@@ -35,38 +35,15 @@ public class Build{
|
||||
prevPercent = tile.entity.healthf();
|
||||
}
|
||||
|
||||
tile = tile.target();
|
||||
|
||||
int rotation = tile.rotation();
|
||||
Block previous = tile.block();
|
||||
Block sub = BuildBlock.get(previous.size);
|
||||
|
||||
Block sub = content.getByName(ContentType.block, "build" + previous.size);
|
||||
|
||||
tile.setBlock(sub);
|
||||
world.setBlock(tile, sub, team, rotation);
|
||||
tile.<BuildEntity>entity().setDeconstruct(previous);
|
||||
tile.setTeam(team);
|
||||
tile.entity.health = tile.entity.maxHealth() * prevPercent;
|
||||
|
||||
if(previous.isMultiblock()){
|
||||
int offsetx = -(previous.size - 1) / 2;
|
||||
int offsety = -(previous.size - 1) / 2;
|
||||
|
||||
for(int dx = 0; dx < previous.size; dx++){
|
||||
for(int dy = 0; dy < previous.size; dy++){
|
||||
int worldx = dx + offsetx + tile.x;
|
||||
int worldy = dy + offsety + tile.y;
|
||||
if(!(worldx == tile.x && worldy == tile.y)){
|
||||
Tile toplace = world.tile(worldx, worldy);
|
||||
if(toplace != null){
|
||||
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
|
||||
toplace.setTeam(team);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tile ftile = tile;
|
||||
Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(ftile, team, true)));
|
||||
Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(tile, team, true)));
|
||||
}
|
||||
|
||||
/** Places a BuildBlock at this location. */
|
||||
@@ -82,31 +59,10 @@ public class Build{
|
||||
if(tile == null) return;
|
||||
|
||||
Block previous = tile.block();
|
||||
Block sub = BuildBlock.get(result.size);
|
||||
|
||||
Block sub = content.getByName(ContentType.block, "build" + result.size);
|
||||
|
||||
tile.setBlock(sub, rotation);
|
||||
world.setBlock(tile, sub, team, rotation);
|
||||
tile.<BuildEntity>entity().setConstruct(previous, result);
|
||||
tile.setTeam(team);
|
||||
|
||||
if(result.isMultiblock()){
|
||||
int offsetx = -(result.size - 1) / 2;
|
||||
int offsety = -(result.size - 1) / 2;
|
||||
|
||||
for(int dx = 0; dx < result.size; dx++){
|
||||
for(int dy = 0; dy < result.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
if(!(worldx == x && worldy == y)){
|
||||
Tile toplace = world.tile(worldx, worldy);
|
||||
if(toplace != null){
|
||||
toplace.setLinked((byte)(dx + offsetx), (byte)(dy + offsety));
|
||||
toplace.setTeam(team);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(tile, team, false)));
|
||||
}
|
||||
@@ -130,6 +86,7 @@ public class Build{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Tile tile = world.tile(x, y);
|
||||
|
||||
if(tile == null) return false;
|
||||
@@ -166,7 +123,7 @@ public class Build{
|
||||
&& (!tile.floor().isDeep() || type.floating)
|
||||
&& tile.floor().placeableOn
|
||||
&& ((type.canReplace(tile.block())
|
||||
&& !(type == tile.block() && rotation == tile.getRotation() && type.rotate)) || tile.block().alwaysReplace || tile.block() == Blocks.air)
|
||||
&& !(type == tile.block() && rotation == tile.rotation() && type.rotate)) || tile.block().alwaysReplace || tile.block() == Blocks.air)
|
||||
&& tile.block().isMultiblock() == type.isMultiblock() && type.canPlaceOn(tile);
|
||||
}
|
||||
}
|
||||
@@ -195,9 +152,7 @@ public class Build{
|
||||
|
||||
/** Returns whether the tile at this position is breakable by this team */
|
||||
public static boolean validBreak(Team team, int x, int y){
|
||||
Tile tile = world.tile(x, y);
|
||||
if(tile != null) tile = tile.target();
|
||||
|
||||
Tile tile = world.ltile(x, y);
|
||||
return tile != null && tile.block().canBreak(tile) && tile.breakable() && tile.interactable(team);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public class CachedTile extends Tile{
|
||||
if(!entities.containsKey(block.id)){
|
||||
TileEntity n = block.newEntity();
|
||||
n.cons = new ConsumeModule(entity);
|
||||
n.tile = this;
|
||||
if(block.hasItems) n.items = new ItemModule();
|
||||
if(block.hasLiquids) n.liquids = new LiquidModule();
|
||||
if(block.hasPower) n.power = new PowerModule();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package io.anuke.mindustry.world;
|
||||
|
||||
import io.anuke.arc.util.*;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static io.anuke.mindustry.Vars.content;
|
||||
|
||||
public class ItemBuffer{
|
||||
@@ -57,4 +60,23 @@ public class ItemBuffer{
|
||||
System.arraycopy(buffer, 1, buffer, 0, index - 1);
|
||||
index--;
|
||||
}
|
||||
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
stream.writeByte((byte)index);
|
||||
stream.writeByte((byte)buffer.length);
|
||||
for(long l : buffer){
|
||||
stream.writeLong(l);
|
||||
}
|
||||
}
|
||||
|
||||
public void read(DataInput stream) throws IOException{
|
||||
index = stream.readByte();
|
||||
byte length = stream.readByte();
|
||||
for(int i = 0; i < length; i++){
|
||||
long l = stream.readLong();
|
||||
if(i < buffer.length){
|
||||
buffer[i] = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class LegacyColorMapper implements ContentList{
|
||||
public void load(){
|
||||
defaultValue = new LegacyBlock(Blocks.stone, Blocks.air);
|
||||
|
||||
map("ff0000", Blocks.stone, Blocks.spawn);
|
||||
map("ff0000", Blocks.stone, Blocks.air, Blocks.spawn);
|
||||
map("00ff00", Blocks.stone);
|
||||
map("323232", Blocks.stone);
|
||||
map("646464", Blocks.stone, Blocks.rocks);
|
||||
@@ -60,9 +60,7 @@ public class LegacyColorMapper implements ContentList{
|
||||
public final Block ore;
|
||||
|
||||
public LegacyBlock(Block floor, Block wall){
|
||||
this.floor = (Floor)floor;
|
||||
this.wall = wall;
|
||||
this.ore = null;
|
||||
this(floor, wall, Blocks.air);
|
||||
}
|
||||
|
||||
public LegacyBlock(Block floor, Block wall, Block ore){
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.anuke.arc.collection.Array;
|
||||
import io.anuke.arc.function.Consumer;
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.math.geom.*;
|
||||
import io.anuke.arc.util.Pack;
|
||||
import io.anuke.mindustry.content.Blocks;
|
||||
import io.anuke.mindustry.entities.traits.TargetTrait;
|
||||
import io.anuke.mindustry.entities.type.TileEntity;
|
||||
@@ -24,12 +23,12 @@ public class Tile implements Position, TargetTrait{
|
||||
public short x, y;
|
||||
protected Block block;
|
||||
protected Floor floor;
|
||||
/** Rotation, 0-3. Also used to store offload location, in which case it can be any number. */
|
||||
private byte rotation;
|
||||
/** Rotation, 0-3. Also used to store offload location, in which case it can be any number.*/
|
||||
protected byte rotation;
|
||||
/** Team ordinal. */
|
||||
private byte team;
|
||||
protected byte team;
|
||||
/** Ore that is on top of this (floor) block. */
|
||||
private byte overlay = 0;
|
||||
protected short overlay = 0;
|
||||
|
||||
public Tile(int x, int y){
|
||||
this.x = (short)x;
|
||||
@@ -37,20 +36,15 @@ public class Tile implements Position, TargetTrait{
|
||||
block = floor = (Floor)Blocks.air;
|
||||
}
|
||||
|
||||
public Tile(int x, int y, byte floor, byte block){
|
||||
this(x, y);
|
||||
public Tile(int x, int y, int floor, int overlay, int wall){
|
||||
this.x = (short)x;
|
||||
this.y = (short)y;
|
||||
this.floor = (Floor)content.block(floor);
|
||||
this.block = content.block(block);
|
||||
changed();
|
||||
}
|
||||
this.block = content.block(wall);
|
||||
this.overlay = (short)overlay;
|
||||
|
||||
public Tile(int x, int y, byte floor, byte block, byte rotation, byte team){
|
||||
this(x, y);
|
||||
this.floor = (Floor)content.block(floor);
|
||||
this.block = content.block(block);
|
||||
this.rotation = rotation;
|
||||
//update entity and create it if needed
|
||||
changed();
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
/** Returns this tile's position as a {@link Pos}. */
|
||||
@@ -58,14 +52,6 @@ public class Tile implements Position, TargetTrait{
|
||||
return Pos.get(x, y);
|
||||
}
|
||||
|
||||
public byte getBlockID(){
|
||||
return block.id;
|
||||
}
|
||||
|
||||
public byte getFloorID(){
|
||||
return floor.id;
|
||||
}
|
||||
|
||||
/** Return relative rotation to a coordinate. Returns -1 if the coordinate is not near this tile. */
|
||||
public byte relativeTo(int cx, int cy){
|
||||
if(x == cx && y == cy - 1) return 1;
|
||||
@@ -139,7 +125,7 @@ public class Tile implements Position, TargetTrait{
|
||||
|
||||
@Override
|
||||
public Team getTeam(){
|
||||
return Team.all[target().team];
|
||||
return Team.all[link().team];
|
||||
}
|
||||
|
||||
public void setTeam(Team team){
|
||||
@@ -154,25 +140,12 @@ public class Tile implements Position, TargetTrait{
|
||||
preChanged();
|
||||
this.block = type;
|
||||
this.team = (byte)team.ordinal();
|
||||
this.rotation = 0;
|
||||
this.rotation = (byte)Mathf.mod(rotation, 4);
|
||||
changed();
|
||||
}
|
||||
|
||||
public void setBlock(Block type, int rotation){
|
||||
preChanged();
|
||||
this.block = type;
|
||||
this.rotation = 0;
|
||||
this.rotation = (byte)Mathf.mod(rotation, 4);
|
||||
changed();
|
||||
}
|
||||
|
||||
public void setBlock(Block type, Team team){
|
||||
preChanged();
|
||||
this.block = type;
|
||||
this.team = (byte)team.ordinal();
|
||||
this.rotation = 0;
|
||||
changed();
|
||||
setBlock(type, team, 0);
|
||||
}
|
||||
|
||||
public void setBlock(Block type){
|
||||
@@ -188,27 +161,27 @@ public class Tile implements Position, TargetTrait{
|
||||
this.overlay = 0;
|
||||
}
|
||||
|
||||
public byte getRotation(){
|
||||
public byte rotation(){
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public void setRotation(byte rotation){
|
||||
this.rotation = rotation;
|
||||
public void rotation(int rotation){
|
||||
this.rotation = (byte)rotation;
|
||||
}
|
||||
|
||||
public byte getDump(){
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public void setDump(byte dump){
|
||||
this.rotation = dump;
|
||||
}
|
||||
|
||||
public byte getOverlayID(){
|
||||
public short overlayID(){
|
||||
return overlay;
|
||||
}
|
||||
|
||||
public void setOverlayID(byte ore){
|
||||
public short blockID(){
|
||||
return block.id;
|
||||
}
|
||||
|
||||
public short floorID(){
|
||||
return floor.id;
|
||||
}
|
||||
|
||||
public void setOverlayID(short ore){
|
||||
this.overlay = ore;
|
||||
}
|
||||
|
||||
@@ -221,31 +194,24 @@ public class Tile implements Position, TargetTrait{
|
||||
}
|
||||
|
||||
public boolean passable(){
|
||||
Block block = block();
|
||||
Block floor = floor();
|
||||
return isLinked() || !((floor.solid && (block == Blocks.air || block.solidifes)) || (block.solid && (!block.destructible && !block.update)));
|
||||
}
|
||||
|
||||
/** Whether this block was placed by a player/unit. */
|
||||
public boolean synthetic(){
|
||||
Block block = block();
|
||||
return block.update || block.destructible;
|
||||
}
|
||||
|
||||
public boolean solid(){
|
||||
Block block = block();
|
||||
Block floor = floor();
|
||||
return block.solid || (floor.solid && (block == Blocks.air || block.solidifes)) || block.isSolidFor(this)
|
||||
|| (isLinked() && getLinked().block().isSolidFor(getLinked()));
|
||||
return block.solid || block.isSolidFor(this) || (isLinked() && link().solid());
|
||||
}
|
||||
|
||||
public boolean breakable(){
|
||||
Block block = block();
|
||||
if(!isLinked()){
|
||||
return (block.destructible || block.breakable || block.update);
|
||||
}else{
|
||||
return getLinked() != this && getLinked().getLinked() == null && getLinked().breakable();
|
||||
}
|
||||
return !isLinked() ? (block.destructible || block.breakable || block.update) : link().breakable();
|
||||
}
|
||||
|
||||
public Tile link(){
|
||||
return block.linked(this);
|
||||
}
|
||||
|
||||
public boolean isEnemyCheat(){
|
||||
@@ -253,21 +219,27 @@ public class Tile implements Position, TargetTrait{
|
||||
}
|
||||
|
||||
public boolean isLinked(){
|
||||
return block == Blocks.part;
|
||||
return block instanceof BlockPart;
|
||||
}
|
||||
|
||||
public byte getLinkByte(){
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public void setLinkByte(byte b){
|
||||
this.rotation = b;
|
||||
}
|
||||
|
||||
/** Sets this to a linked tile, which sets the block to a part. dx and dy can only be -8-7. */
|
||||
public void setLinked(byte dx, byte dy){
|
||||
setBlock(Blocks.part);
|
||||
rotation = Pack.byteByte((byte)(dx + 8), (byte)(dy + 8));
|
||||
/**
|
||||
* Returns the list of all tiles linked to this multiblock, or an empty array if it's not a multiblock.
|
||||
* This array contains all linked tiles, including this tile itself.
|
||||
*/
|
||||
public void getLinkedTiles(Consumer<Tile> cons){
|
||||
if(block.isMultiblock()){
|
||||
int size = block.size;
|
||||
int offsetx = -(size - 1) / 2;
|
||||
int offsety = -(size - 1) / 2;
|
||||
for(int dx = 0; dx < size; dx++){
|
||||
for(int dy = 0; dy < size; dy++){
|
||||
Tile other = world.tile(x + dx + offsetx, y + dy + offsety);
|
||||
if(other != null) cons.accept(other);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
cons.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,20 +247,8 @@ public class Tile implements Position, TargetTrait{
|
||||
* This array contains all linked tiles, including this tile itself.
|
||||
*/
|
||||
public Array<Tile> getLinkedTiles(Array<Tile> tmpArray){
|
||||
Block block = block();
|
||||
tmpArray.clear();
|
||||
if(block.isMultiblock()){
|
||||
int offsetx = -(block.size - 1) / 2;
|
||||
int offsety = -(block.size - 1) / 2;
|
||||
for(int dx = 0; dx < block.size; dx++){
|
||||
for(int dy = 0; dy < block.size; dy++){
|
||||
Tile other = world.tile(x + dx + offsetx, y + dy + offsety);
|
||||
if(other != null) tmpArray.add(other);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
tmpArray.add(this);
|
||||
}
|
||||
getLinkedTiles(tmpArray::add);
|
||||
return tmpArray;
|
||||
}
|
||||
|
||||
@@ -313,7 +273,7 @@ public class Tile implements Position, TargetTrait{
|
||||
return tmpArray;
|
||||
}
|
||||
|
||||
/** Returns the block the multiblock is linked to, or null if it is not linked to any block. */
|
||||
/** Returns the block the multiblock is linked to, or null if it is not linked to any block.
|
||||
public Tile getLinked(){
|
||||
if(!isLinked()){
|
||||
return null;
|
||||
@@ -322,28 +282,10 @@ public class Tile implements Position, TargetTrait{
|
||||
}
|
||||
}
|
||||
|
||||
public void allNearby(Consumer<Tile> cons){
|
||||
for(Point2 point : Edges.getEdges(block().size)){
|
||||
Tile tile = world.tile(x + point.x, y + point.y);
|
||||
if(tile != null){
|
||||
cons.accept(tile.target());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void allInside(Consumer<Tile> cons){
|
||||
for(Point2 point : Edges.getInsideEdges(block().size)){
|
||||
Tile tile = world.tile(x + point.x, y + point.y);
|
||||
if(tile != null){
|
||||
cons.accept(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Tile target(){
|
||||
Tile link = getLinked();
|
||||
return link == null ? this : link;
|
||||
}
|
||||
}*/
|
||||
|
||||
public Rectangle getHitbox(Rectangle rect){
|
||||
return rect.setSize(block().size * tilesize).setCenter(drawx(), drawy());
|
||||
@@ -398,8 +340,8 @@ public class Tile implements Position, TargetTrait{
|
||||
|
||||
//+26
|
||||
|
||||
if(target().synthetic()){
|
||||
cost += Mathf.clamp(target().block().health / 10f, 0, 20);
|
||||
if(link().synthetic()){
|
||||
cost += Mathf.clamp(link().block.health / 10f, 0, 20);
|
||||
}
|
||||
|
||||
//+46
|
||||
@@ -453,9 +395,8 @@ public class Tile implements Position, TargetTrait{
|
||||
}else if(!(block instanceof BlockPart) && !world.isGenerating()){
|
||||
//since the entity won't update proximity for us, update proximity for all nearby tiles manually
|
||||
for(Point2 p : Geometry.d4){
|
||||
Tile tile = world.tile(x + p.x, y + p.y);
|
||||
Tile tile = world.ltile(x + p.x, y + p.y);
|
||||
if(tile != null){
|
||||
tile = tile.target();
|
||||
tile.block().onProximityUpdate(tile);
|
||||
}
|
||||
}
|
||||
@@ -498,20 +439,6 @@ public class Tile implements Position, TargetTrait{
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
Block block = block();
|
||||
Block floor = floor();
|
||||
|
||||
return floor.name + ":" + block.name + ":" + content.block(overlay) + "[" + x + "," + y + "] " + "entity=" + (entity == null ? "null" : (entity.getClass())) +
|
||||
(isLinked() ? " link=[" + linkX(rotation) + ", " + linkY(rotation) + "]" : "");
|
||||
}
|
||||
|
||||
/**Returns the relative X from a link byte.*/
|
||||
public static int linkX(byte value){
|
||||
return -((byte)((value >> 4) & (byte)0x0F) - 8);
|
||||
}
|
||||
|
||||
/**Returns the relative Y from a link byte.*/
|
||||
public static int linkY(byte value){
|
||||
return -((byte)(value & 0x0F) - 8);
|
||||
return floor.name + ":" + block.name + ":" + content.block(overlay) + "[" + x + "," + y + "] " + "entity=" + (entity == null ? "null" : (entity.getClass()));
|
||||
}
|
||||
}
|
||||
22
core/src/io/anuke/mindustry/world/WorldContext.java
Normal file
22
core/src/io/anuke/mindustry/world/WorldContext.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package io.anuke.mindustry.world;
|
||||
|
||||
public interface WorldContext{
|
||||
|
||||
/** Return a tile in the tile array.*/
|
||||
Tile tile(int x, int y);
|
||||
|
||||
/** Create the tile array.*/
|
||||
void resize(int width, int height);
|
||||
|
||||
/** This should create a tile and put it into the tile array, then return it. */
|
||||
Tile create(int x, int y, int floorID, int overlayID, int wallID);
|
||||
|
||||
/** Returns whether the world is already generating.*/
|
||||
boolean isGenerating();
|
||||
|
||||
/** Begins generating.*/
|
||||
void begin();
|
||||
|
||||
/** End generating, prepares tiles.*/
|
||||
void end();
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package io.anuke.mindustry.world.blocks;
|
||||
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
@@ -11,18 +9,36 @@ import io.anuke.mindustry.world.Tile;
|
||||
* They are made to share all properties from the linked tile/block.
|
||||
*/
|
||||
public class BlockPart extends Block{
|
||||
public final static int maxSize = 9;
|
||||
private final static BlockPart[][] parts = new BlockPart[maxSize][maxSize];
|
||||
|
||||
public BlockPart(){
|
||||
super("part");
|
||||
private final int dx, dy;
|
||||
|
||||
public BlockPart(int dx, int dy){
|
||||
super("part_" + dx + "_" + dy);
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
solid = false;
|
||||
hasPower = hasItems = hasLiquids = true;
|
||||
parts[dx + maxSize/2][dy + maxSize/2] = this;
|
||||
}
|
||||
|
||||
public static BlockPart get(int dx, int dy){
|
||||
if(dx == -maxSize/2 && dy == -maxSize/2) throw new IllegalArgumentException("Why are you getting a [0,0] blockpart? Stop it.");
|
||||
return parts[dx + maxSize/2][dy + maxSize/2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTeam(Tile tile){
|
||||
|
||||
public Tile linked(Tile tile){
|
||||
return tile.getNearby(-dx, -dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTeam(Tile tile){}
|
||||
|
||||
@Override
|
||||
public void draw(Tile tile){}
|
||||
|
||||
@Override
|
||||
public boolean synthetic(){
|
||||
return true;
|
||||
@@ -34,42 +50,9 @@ public class BlockPart extends Block{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Tile tile){
|
||||
//do nothing
|
||||
public String toString(){
|
||||
return "BlockPart[" + dx + ", " + dy + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolidFor(Tile tile){
|
||||
return tile.getLinked() == null
|
||||
|| (tile.getLinked().block() instanceof BlockPart || tile.getLinked().solid()
|
||||
|| tile.getLinked().block().isSolidFor(tile.getLinked()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleItem(Item item, Tile tile, Tile source){
|
||||
tile.getLinked().block().handleItem(item, tile.getLinked(), source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptItem(Item item, Tile tile, Tile source){
|
||||
return tile.getLinked().block().acceptItem(item, tile.getLinked(), source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptLiquid(Tile tile, Tile source, Liquid liquid, float amount){
|
||||
Block block = linked(tile);
|
||||
return block.hasLiquids
|
||||
&& block.acceptLiquid(tile.getLinked(), source, liquid, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLiquid(Tile tile, Tile source, Liquid liquid, float amount){
|
||||
Block block = linked(tile);
|
||||
block.handleLiquid(tile.getLinked(), source, liquid, amount);
|
||||
}
|
||||
|
||||
private Block linked(Tile tile){
|
||||
return tile.getLinked().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.anuke.mindustry.world.blocks;
|
||||
|
||||
import io.anuke.annotations.Annotations.Loc;
|
||||
import io.anuke.annotations.Annotations.Remote;
|
||||
import io.anuke.annotations.Annotations.*;
|
||||
import io.anuke.arc.Core;
|
||||
import io.anuke.arc.Events;
|
||||
import io.anuke.arc.Graphics.Cursor;
|
||||
@@ -28,15 +27,25 @@ import java.io.*;
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class BuildBlock extends Block{
|
||||
public static final int maxSize = 9;
|
||||
private static final BuildBlock[] buildBlocks = new BuildBlock[maxSize];
|
||||
|
||||
public BuildBlock(String name){
|
||||
super(name);
|
||||
public BuildBlock(int size){
|
||||
super("build" + size);
|
||||
this.size = size;
|
||||
update = true;
|
||||
size = Integer.parseInt(name.charAt(name.length() - 1) + "");
|
||||
health = 20;
|
||||
layer = Layer.placement;
|
||||
consumesTap = true;
|
||||
solidifes = true;
|
||||
|
||||
buildBlocks[size - 1] = this;
|
||||
}
|
||||
|
||||
/** Returns a BuildBlock by size. */
|
||||
public static BuildBlock get(int size){
|
||||
if(size > maxSize) throw new IllegalArgumentException("No. Don't place BuildBlocks of size greater than " + maxSize);
|
||||
return buildBlocks[size - 1];
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
@@ -102,7 +111,7 @@ public class BuildBlock extends Block{
|
||||
//if the target is constructible, begin constructing
|
||||
if(entity.cblock != null){
|
||||
player.clearBuilding();
|
||||
player.addBuildRequest(new BuildRequest(tile.x, tile.y, tile.getRotation(), entity.cblock));
|
||||
player.addBuildRequest(new BuildRequest(tile.x, tile.y, tile.rotation(), entity.cblock));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +136,7 @@ public class BuildBlock extends Block{
|
||||
if(entity.previous == null) return;
|
||||
|
||||
if(Core.atlas.isFound(entity.previous.icon(Icon.full))){
|
||||
Draw.rect(entity.previous.icon(Icon.full), tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.getRotation() * 90 : 0);
|
||||
Draw.rect(entity.previous.icon(Icon.full), tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.rotation() * 90 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +155,7 @@ public class BuildBlock extends Block{
|
||||
Shaders.blockbuild.region = region;
|
||||
Shaders.blockbuild.progress = entity.progress;
|
||||
|
||||
Draw.rect(region, tile.drawx(), tile.drawy(), target.rotate ? tile.getRotation() * 90 : 0);
|
||||
Draw.rect(region, tile.drawx(), tile.drawy(), target.rotate ? tile.rotation() * 90 : 0);
|
||||
Draw.flush();
|
||||
}
|
||||
}
|
||||
@@ -175,13 +184,13 @@ public class BuildBlock extends Block{
|
||||
private float[] accumulator;
|
||||
private float[] totalAccumulator;
|
||||
|
||||
public void construct(Unit builder, TileEntity core, float amount){
|
||||
public void construct(Unit builder, @Nullable TileEntity core, float amount){
|
||||
if(cblock == null){
|
||||
kill();
|
||||
return;
|
||||
}
|
||||
|
||||
float maxProgress = checkRequired(core.items, amount, false);
|
||||
float maxProgress = core == null ? amount : checkRequired(core.items, amount, false);
|
||||
|
||||
for(int i = 0; i < cblock.buildRequirements.length; i++){
|
||||
int reqamount = Math.round(state.rules.buildCostMultiplier * cblock.buildRequirements[i].amount);
|
||||
@@ -189,7 +198,7 @@ public class BuildBlock extends Block{
|
||||
totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * maxProgress, reqamount);
|
||||
}
|
||||
|
||||
maxProgress = checkRequired(core.items, maxProgress, true);
|
||||
maxProgress = core == null ? maxProgress : checkRequired(core.items, maxProgress, true);
|
||||
|
||||
progress = Mathf.clamp(progress + maxProgress);
|
||||
|
||||
@@ -198,11 +207,11 @@ public class BuildBlock extends Block{
|
||||
}
|
||||
|
||||
if(progress >= 1f || state.rules.infiniteResources){
|
||||
Call.onConstructFinish(tile, cblock, builderID, tile.getRotation(), builder.getTeam());
|
||||
Call.onConstructFinish(tile, cblock, builderID, tile.rotation(), builder.getTeam());
|
||||
}
|
||||
}
|
||||
|
||||
public void deconstruct(Unit builder, TileEntity core, float amount){
|
||||
public void deconstruct(Unit builder, @Nullable TileEntity core, float amount){
|
||||
float deconstructMultiplier = 0.5f;
|
||||
|
||||
if(cblock != null){
|
||||
@@ -219,10 +228,13 @@ public class BuildBlock extends Block{
|
||||
int accumulated = (int)(accumulator[i]); //get amount
|
||||
|
||||
if(amount > 0 && accumulated > 0){ //if it's positive, add it to the core
|
||||
int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder);
|
||||
core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder);
|
||||
|
||||
accumulator[i] -= accepting;
|
||||
if(core != null){
|
||||
int accepting = core.tile.block().acceptStack(requirements[i].item, accumulated, core.tile, builder);
|
||||
core.tile.block().handleStack(requirements[i].item, accepting, core.tile, builder);
|
||||
accumulator[i] -= accepting;
|
||||
}else{
|
||||
accumulator[i] -= accumulated;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,6 +304,7 @@ public class BuildBlock extends Block{
|
||||
|
||||
@Override
|
||||
public void write(DataOutput stream) throws IOException{
|
||||
super.write(stream);
|
||||
stream.writeFloat(progress);
|
||||
stream.writeShort(previous == null ? -1 : previous.id);
|
||||
stream.writeShort(cblock == null ? -1 : cblock.id);
|
||||
@@ -308,7 +321,8 @@ public class BuildBlock extends Block{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInput stream) throws IOException{
|
||||
public void read(DataInput stream, byte revision) throws IOException{
|
||||
super.read(stream, revision);
|
||||
progress = stream.readFloat();
|
||||
short pid = stream.readShort();
|
||||
short rid = stream.readShort();
|
||||
|
||||
@@ -38,7 +38,7 @@ public class LiquidBlock extends Block{
|
||||
public void draw(Tile tile){
|
||||
LiquidModule mod = tile.entity.liquids;
|
||||
|
||||
int rotation = rotate ? tile.getRotation() * 90 : 0;
|
||||
int rotation = rotate ? tile.rotation() * 90 : 0;
|
||||
|
||||
Draw.rect(bottomRegion, tile.drawx(), tile.drawy(), rotation);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user