diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b953..1b33c55ba 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0aaefbcaf..4f5eb9dcc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6..23d15a936 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c7..5eed7ee84 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/tanks/Crusade.java b/src/main/java/tanks/Crusade.java index dbad61631..4c9994b6f 100644 --- a/src/main/java/tanks/Crusade.java +++ b/src/main/java/tanks/Crusade.java @@ -8,6 +8,7 @@ import tanks.network.ServerHandler; import tanks.network.event.*; import tanks.tank.*; +import tanks.tankson.*; import java.util.*; @@ -294,7 +295,17 @@ public void begin() public void loadLevel() { - Level l = new Level(this.levels.get(this.currentLevel).levelString, this.customTanks); + Level l; + try + { + l = (Level) Serializer.fromTanksON(this.levels.get(this.currentLevel).levelString); + l.init(customTanks); + l.levelString = this.levels.get(this.currentLevel).levelString; + } + catch (RuntimeException e) + { + l = new Level(this.levels.get(this.currentLevel).levelString, customTanks); + } Game.player.hotbar.enabledCoins = true; Game.player.hotbar.itemBar.showItems = true; diff --git a/src/main/java/tanks/Game.java b/src/main/java/tanks/Game.java index f4a6d9640..ebae8f360 100644 --- a/src/main/java/tanks/Game.java +++ b/src/main/java/tanks/Game.java @@ -25,6 +25,7 @@ import tanks.registry.*; import tanks.rendering.*; import tanks.tank.*; +import tanks.tankson.Serializer; import com.codedisaster.steamworks.SteamMatchmaking; @@ -1378,7 +1379,17 @@ public static boolean loadLevel(BaseFile f, ILevelPreviewScreen s) line.append(f.nextLine()).append("\n"); } - Level l = new Level(line.substring(0, line.length() - 1)); + Level l; + try + { + l = (Level) Serializer.fromTanksON(line.substring(0, line.length() - 1)); + l.init(); + l.levelString = line.substring(0, line.length() - 1); + } + catch (RuntimeException e) + { + l = new Level(line.substring(0, line.length() - 1)); + } l.loadLevel(s); f.stopReading(); diff --git a/src/main/java/tanks/Level.java b/src/main/java/tanks/Level.java index 51c26c62e..43c5fb74d 100644 --- a/src/main/java/tanks/Level.java +++ b/src/main/java/tanks/Level.java @@ -8,21 +8,26 @@ import tanks.item.Item; import tanks.network.ServerHandler; import tanks.network.event.*; -import tanks.obstacle.Obstacle; -import tanks.obstacle.ObstacleBeatBlock; +import tanks.obstacle.*; import tanks.registry.RegistryTank; import tanks.tank.*; +import tanks.tankson.*; import java.util.*; +@TanksONable("level") public class Level { public String levelString; + @Property(id = "tank_pos", name = "Tank Positions") + public ArrayList> tanksIR; public ArrayList tanks; ArrayList tanksToRemove; public Team[] tankTeams; public ArrayList obstacles; + @Property(id = "obstacles", name = "Obstacles") + public ArrayList> obstaclesIR; public boolean enableTeams = false; public static Color currentColor = new Color(235, 207, 166); @@ -34,25 +39,34 @@ public class Level public static Random random = new Random(); + @Property(id = "editable", name = "Editable") public boolean editable = true; public boolean remote = false; public boolean preview = false; + @Property(id = "timer", name = "Timer") public double timer = -1; public int startX; public int startY; + @Property(id = "size_x", name = "Size X") public int sizeX; + @Property(id = "size_y", name = "Size Y") public int sizeY; + @Property(id = "color", name = "Color") public Color color = new Color(235, 207, 166); + @Property(id = "color_var", name = "Color Variation") public Color colorVar = new Color(20, 20, 20); public int tilesRandomSeed = (int) (Math.random() * Integer.MAX_VALUE); + @Property(id = "light", name = "Light") public double light = 1.0; + @Property(id = "shadow", name = "Shadow") public double shadow = 0.5; + @Property(id = "teams", name = "Teams") public LinkedHashMap teamsMap = new LinkedHashMap<>(); public ArrayList availablePlayerSpawns = new ArrayList<>(); @@ -64,9 +78,13 @@ public class Level public ArrayList includedPlayers = new ArrayList<>(); + @Property(id = "coins", name = "Coins") public int startingCoins; + @Property(id = "Shop", name = "Shop") public ArrayList shop = new ArrayList<>(); + @Property(id = "items", name = "Starting Items") public ArrayList> startingItems = new ArrayList<>(); + @Property(id = "builds", name = "Player Builds") public ArrayList playerBuilds = new ArrayList<>(); // Saved on the client to keep track of what each item is @@ -74,6 +92,7 @@ public class Level public ArrayList clientShop = new ArrayList<>(); public ArrayList> clientStartingItems = new ArrayList<>(); + @Property(id = "custom_tanks", name = "Custom Tanks") public ArrayList customTanks; public LinkedHashMap itemNumbers = new LinkedHashMap<>(); @@ -86,6 +105,16 @@ public class Level public HashMap tankLookupTable = null; + public Level() + { + obstacles = new ArrayList(); + this.tanks = new ArrayList(); + this.customTanks = new ArrayList<>(); + + this.remote = false; + this.disableFriendlyFire = false; + } + public Level(String level) { this(level, false); @@ -110,6 +139,11 @@ public Level(String level, ArrayList customTanks) */ public Level(String level, ArrayList customTanks, boolean remote, boolean disableFriendlyFire) { + obstacles = new ArrayList<>(); + this.tanks = new ArrayList<>(); + this.obstaclesIR = new ArrayList<>(); + this.tanksIR = new ArrayList<>(); + this.disableFriendlyFire = disableFriendlyFire; this.remote = remote; @@ -127,8 +161,6 @@ public Level(String level, ArrayList customTanks, boolean remo //Look Ahead Split (keeping the delimiter with the associated block) String[] blocks = this.levelString.split("(?=(level|items|shop|coins|tanks|builds)\n)"); - obstacles = new ArrayList(); - this.tanks = new ArrayList(); for (String s: blocks) { @@ -277,6 +309,17 @@ else if (t.length >= 2) { String[] obs = obstaclesPo.split("-"); + ArrayList obsIR = new ArrayList<>(); + obsIR.add(obs[0].replace("...", ":")); //X Coordinate (changed to sliced notation) + obsIR.add(obs[1].replace("...", ":")); //Y Coordinate (changed to sliced notation) + if (obs.length >= 3) + obsIR.add(obs[2]); //Name + if (obs.length >= 4) + obsIR.add(obs[3]); //Metadata + obstaclesIR.add(obsIR); + + + String[] xPos = obs[0].split("\\.\\.\\."); double startX; @@ -342,6 +385,17 @@ else if (t.length >= 2) for (String s: tanks) { String[] tank = s.split("-"); + + ArrayList tankIR = new ArrayList<>(); + tankIR.add(tank[0]); //X Coordinate + tankIR.add(tank[1]); //Y Coordinate + tankIR.add(tank[2]); //Name + if (tank.length >= 4) + tankIR.add(tank[3]); //Angle + if (tank.length >= 5) + tankIR.add(tank[4]); //Team + tanksIR.add(tankIR); + double x = Game.tile_size * (0.5 + Double.parseDouble(tank[0])); double y = Game.tile_size * (0.5 + Double.parseDouble(tank[1])); String type = tank[2].toLowerCase(); @@ -435,6 +489,160 @@ else if (t.length >= 2) } } + public void init() + { + init(false); + } + + public void init(boolean remote) + { + init(new ArrayList<>(), remote, ScreenPartyHost.isServer && Game.disablePartyFriendlyFire); + } + + public void init(ArrayList customTanks) + { + init(customTanks, false, ScreenPartyHost.isServer && Game.disablePartyFriendlyFire); + } + + public void init(ArrayList customTanks, boolean remote, boolean disableFriendlyFire) + { + this.remote = remote; + this.disableFriendlyFire = disableFriendlyFire; + this.customTanks.addAll(customTanks); + + if (teamsMap.isEmpty()) + { + if (disableFriendlyFire) + { + teamsMap.put("ally", Game.playerTeamNoFF); + teamsMap.put("enemy", Game.enemyTeamNoFF); + } + else + { + teamsMap.put("ally", Game.playerTeam); + teamsMap.put("enemy", Game.enemyTeam); + } + } + + for (ArrayList obs: obstaclesIR) + { + String[] xs = obs.get(0).split(":"); + double startX = Double.parseDouble(xs[0]); + double endX = (xs.length > 1 ? Double.parseDouble(xs[1]) : startX) + 1; + String[] ys = obs.get(1).split(":"); + double startY = Double.parseDouble(ys[0]); + double endY = (ys.length > 1 ? Double.parseDouble(ys[1]) : startY) + 1; + + for (double x = startX; x < endX; x++) + { + for (double y = startY; y < endY; y++) + { + Obstacle o = Game.registryObstacle.getEntry(obs.get(2)).getObstacle(x, y); + + if (obs.size() >= 4) + o.setMetadata(obs.get(3)); + + if (o instanceof ObstacleBeatBlock) + { + this.synchronizeMusic = true; + this.beatBlocks |= (int) ((ObstacleBeatBlock) o).beatFrequency; + } + + obstacles.add(o); + } + } + } + + int currentCrusadeID = 0; + + LinkedHashMap customTanksMap = new LinkedHashMap<>(); + for (TankAIControlled t: this.customTanks) + customTanksMap.put(t.name, t); + + tanksToRemove = new ArrayList<>(); + + for (ArrayList tankIR: tanksIR) + { + String[] tank = new String[tankIR.size()]; + tankIR.toArray(tank); + double x = Game.tile_size * (0.5 + Double.parseDouble(tank[0])); + double y = Game.tile_size * (0.5 + Double.parseDouble(tank[1])); + String type = tank[2].toLowerCase(); + double angle = 0; + + StringBuilder metadata = new StringBuilder(); + for (int i = 3; i < tank.length; i++) + { + metadata.append(tank[i]); + if (i < tank.length - 1) + metadata.append("-"); + } + + if (tank.length >= 4) + angle = (Math.PI / 2 * Double.parseDouble(tank[3])); + + Team team = Game.enemyTeam; + + if (this.disableFriendlyFire) + team = Game.enemyTeamNoFF; + + if (enableTeams) + { + if (tank.length >= 5) + team = teamsMap.get(tank[4]); + else + team = null; + } + + Tank t; + if (type.equals("player")) + { + if (team == Game.enemyTeam) + team = Game.playerTeam; + + if (team == Game.enemyTeamNoFF) + team = Game.playerTeamNoFF; + + this.playerSpawnsX.add(x); + this.playerSpawnsY.add(y); + this.playerSpawnsAngle.add(angle); + this.playerSpawnsTeam.add(team); + + continue; + } + + if (customTanksMap.get(type) != null) + t = customTanksMap.get(type).instantiate(type, x, y, angle); + else + t = Game.registryTank.getEntry(type).getTank(x, y, angle); + + t.crusadeID = currentCrusadeID; + currentCrusadeID++; + + Level l = Game.currentLevel; + Game.currentLevel = this; + if (Crusade.crusadeMode && !Crusade.currentCrusade.respawnTanks && Crusade.currentCrusade.retry && !Crusade.currentCrusade.livingTankIDs.contains(t.crusadeID)) + tanksToRemove.add(t); + else + t.setMetadata(metadata.toString()); + Game.currentLevel = l; + + if (remote) + this.tanks.add(new TankRemote(t)); + else + { + this.tanks.add(t); + setSolidTank((int) Double.parseDouble(tank[0]), (int) Double.parseDouble(tank[1]), true); + } + } + + if (TankModels.tank != null && playerBuilds.isEmpty()) + { + TankPlayer.ShopTankBuild tp = new TankPlayer.ShopTankBuild(); + playerBuilds.add(tp); + } + } + protected static ArrayList getJsonObjects(String s) { int depth = 0; @@ -1083,4 +1291,160 @@ else if (!Character.isWhitespace(c)) return out.substring(1); } + + public String save() + { + this.obstaclesIR.clear(); + this.tanksIR.clear(); + ArrayList unmarked = (ArrayList) Game.obstacles.clone(); + String[][][] obstacles = new String[Game.registryObstacle.obstacleEntries.size()][this.sizeX][this.sizeY]; + + for (int h = 0; h < Game.registryObstacle.obstacleEntries.size(); h++) + { + for (int i = 0; i < Game.obstacles.size(); i++) + { + Obstacle o = Game.obstacles.get(i); + int x = (int) (o.posX / Game.tile_size); + int y = (int) (o.posY / Game.tile_size); + + if (x < obstacles[h].length && x >= 0 && y < obstacles[h][0].length && y >= 0 && o.name.equals(Game.registryObstacle.getEntry(h).name)) + { + obstacles[h][x][y] = o.getMetadata(); + + unmarked.remove(o); + } + } + + //compression + for (int i = 0; i < this.sizeX; i++) + { + for (int j = 0; j < this.sizeY; j++) + { + if (obstacles[h][i][j] != null) + { + String stack = obstacles[h][i][j]; + + int xLength = 0; + + while (true) + { + xLength += 1; + + if (i + xLength >= obstacles[h].length) + break; + else if (!Objects.equals(obstacles[h][i + xLength][j], stack)) + break; + } + + + int yLength = 0; + + while (true) + { + yLength += 1; + + if (j + yLength >= obstacles[h][0].length) + break; + else if (!Objects.equals(obstacles[h][i][j + yLength], stack)) + break; + } + + String name = ""; + String obsName = Game.registryObstacle.obstacleEntries.get(h).name; + + if (!obsName.equals("normal") || !stack.equals("1.0")) + name = obsName; + + if (xLength >= yLength) + { + ArrayList obs = new ArrayList<>(); + if (xLength == 1) + { + obs.add(Integer.toString(i)); + obs.add(Integer.toString(j)); + obs.add(name); + } + else + { + obs.add(Integer.toString(i) + ":" + Integer.toString(i + xLength - 1)); + obs.add(Integer.toString(j)); + obs.add(name); + } + + if (!stack.isEmpty()) + obs.add(stack); + + this.obstaclesIR.add(obs); + + for (int z = 0; z < xLength; z++) + { + obstacles[h][i + z][j] = null; + } + } + else + { + ArrayList obs = new ArrayList<>(); + obs.add(Integer.toString(i)); + obs.add(Integer.toString(j) + ":" + Integer.toString(j + yLength - 1)); + obs.add(name); + + if (!stack.isEmpty()) + obs.add(stack); + + this.obstaclesIR.add(obs); + + for (int z = 0; z < yLength; z++) + { + obstacles[h][i][j + z] = null; + } + } + } + } + } + } + + for (Obstacle obstacle: unmarked) + { + ArrayList obs = new ArrayList<>(); + obs.add(Integer.toString((int) (obstacle.posX / Game.tile_size))); + obs.add(Integer.toString((int) (obstacle.posY / Game.tile_size))); + obs.add(obstacle.name); + + if (obstacle instanceof ObstacleUnknown && ((ObstacleUnknown) obstacle).metadata != null) + obs.add(((ObstacleUnknown) obstacle).metadata); + else + { + String meta = obstacle.getMetadata(); + if (!meta.isEmpty()) + obs.add(meta); + } + + + this.obstaclesIR.add(obs); + } + + for (int i = 0; i < Game.movables.size(); i++) + { + if (Game.movables.get(i) instanceof Tank) + { + Tank t = (Tank) Game.movables.get(i); + int x = (int) (t.posX / Game.tile_size); + int y = (int) (t.posY / Game.tile_size); + int angle = (int) (t.angle * 2 / Math.PI); + + ArrayList tank = new ArrayList<>(); + tank.add(Integer.toString(x)); + tank.add(Integer.toString(y)); + tank.add(t.name); + tank.add(Integer.toString(angle)); + + if (t.team != null) + tank.add(t.team.name); + + this.tanksIR.add(tank); + } + } + + return Serializer.toTanksON(this); + } } diff --git a/src/main/java/tanks/Team.java b/src/main/java/tanks/Team.java index a5aaea128..16e91159f 100644 --- a/src/main/java/tanks/Team.java +++ b/src/main/java/tanks/Team.java @@ -1,8 +1,7 @@ package tanks; import basewindow.Color; -import tanks.tankson.Property; -import tanks.tankson.TanksONable; +import tanks.tankson.*; @TanksONable("team") public class Team diff --git a/src/main/java/tanks/gui/screen/DisplayCrusadeLevels.java b/src/main/java/tanks/gui/screen/DisplayCrusadeLevels.java index 3b9565c3d..b658b24ef 100644 --- a/src/main/java/tanks/gui/screen/DisplayCrusadeLevels.java +++ b/src/main/java/tanks/gui/screen/DisplayCrusadeLevels.java @@ -7,6 +7,7 @@ import tanks.rendering.TerrainRenderer; import tanks.tank.TankAIControlled; import tanks.tank.TankSpawnMarker; +import tanks.tankson.Serializer; import java.util.ArrayList; import java.util.HashMap; @@ -89,7 +90,16 @@ public void initialize(ScreenLevel l) Game.cleanUp(); - l.level = new Level(l.levelString, l.tanks); + try + { + l.level = (Level) Serializer.fromTanksON(l.levelString); + l.level.init(l.tanks); + l.level.levelString = l.levelString; + } + catch (RuntimeException e) + { + l.level = new Level(l.levelString, l.tanks); + } if (!l.isTransition) addTransitionLevels(l); diff --git a/src/main/java/tanks/gui/screen/ScreenCrusadeAddLevel.java b/src/main/java/tanks/gui/screen/ScreenCrusadeAddLevel.java index 81ce6a4a1..1f4348e99 100644 --- a/src/main/java/tanks/gui/screen/ScreenCrusadeAddLevel.java +++ b/src/main/java/tanks/gui/screen/ScreenCrusadeAddLevel.java @@ -4,6 +4,7 @@ import tanks.Game; import tanks.gui.Button; import tanks.gui.SavedFilesList; +import tanks.tankson.Serializer; public class ScreenCrusadeAddLevel extends ScreenPlaySavedLevels { @@ -34,7 +35,14 @@ public void initializeLevels() if (Game.loadLevel(file, s)) { s.level.buildOverrides.addAll(Game.currentLevel.playerBuilds); - s.level.levelString = Game.currentLevel.levelString; + try + { + s.level.levelString = Serializer.toTanksON(Serializer.fromTanksON(Game.currentLevel.levelString)); + } + catch (RuntimeException e) + { + s.level.levelString = Game.currentLevel.levelString; + } s.level.tanks.addAll(Game.currentLevel.customTanks); Game.screen = s; } diff --git a/src/main/java/tanks/gui/screen/ScreenCrusadeEditor.java b/src/main/java/tanks/gui/screen/ScreenCrusadeEditor.java index 4e2d82f57..052bd8693 100644 --- a/src/main/java/tanks/gui/screen/ScreenCrusadeEditor.java +++ b/src/main/java/tanks/gui/screen/ScreenCrusadeEditor.java @@ -749,7 +749,20 @@ public void save() for (int i = 0; i < this.crusade.levels.size(); i++) { String l = this.crusade.levels.get(i).levelString; - f.println(l.substring(l.indexOf('{'), l.indexOf('}') + 1) + " name=" + this.crusade.levels.get(i).levelName); + try + { + Map m = (Map) TanksON.parseObject(l); + m.remove("coins"); + m.remove("shop"); + m.remove("items"); + m.remove("builds"); + m.remove("custom_tanks"); + f.println(TanksON.toString(m) + " name=" + this.crusade.levels.get(i).levelName); + } + catch (RuntimeException e) + { + f.println(l.substring(l.indexOf('{'), l.lastIndexOf('}') + 1) + " name=" + this.crusade.levels.get(i).levelName); + } } f.println("build_overrides"); diff --git a/src/main/java/tanks/gui/screen/leveleditor/ScreenLevelEditor.java b/src/main/java/tanks/gui/screen/leveleditor/ScreenLevelEditor.java index 419d8344a..6ce1c85c2 100644 --- a/src/main/java/tanks/gui/screen/leveleditor/ScreenLevelEditor.java +++ b/src/main/java/tanks/gui/screen/leveleditor/ScreenLevelEditor.java @@ -1737,6 +1737,38 @@ public void save() } public void save(String levelName) + { + this.new_save(levelName); + } + + public void new_save(String levelName) + { + Game.currentLevelString = this.level.save(); + + BaseFile file = Game.game.fileManager.getFile(Game.homedir + Game.levelDir + "/" + levelName); + if (file.exists()) + { + if (!this.level.editable) + { + return; + } + } + + try + { + file.create(); + + file.startWriting(); + file.println(Serializer.toTanksON(this.level)); + file.stopWriting(); + } + catch (IOException e) + { + Game.exitToCrash(e); + } + } + + public void legacy_save(String levelName) { StringBuilder level = new StringBuilder("{"); @@ -2537,7 +2569,7 @@ public void play() this.replaceSpawns(); Game.currentLevel.reloadTiles(); - Game.currentLevel = new Level(Game.currentLevelString); + Game.currentLevel = this.level; Game.currentLevel.tilesRandomSeed = level.tilesRandomSeed; Game.currentLevel.timer = level.timer; diff --git a/src/main/java/tanks/tankson/Serializer.java b/src/main/java/tanks/tankson/Serializer.java index c747d9bb1..8a97303c1 100644 --- a/src/main/java/tanks/tankson/Serializer.java +++ b/src/main/java/tanks/tankson/Serializer.java @@ -1,15 +1,13 @@ package tanks.tankson; import basewindow.Color; -import tanks.Game; -import tanks.Team; +import tanks.*; import tanks.bullet.*; import tanks.item.Item; import tanks.tank.*; import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; +import java.lang.reflect.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -154,6 +152,29 @@ else if (o2 instanceof ArrayList) p.put(getid(f), f.get(o)); } } + else if (o2 instanceof Map) + { + if (!((Map) o2).isEmpty() && isTanksONable(((Map) o2).values().iterator().next())) + { + ArrayList o3keys = new ArrayList<>(); + ArrayList o3vals = new ArrayList<>(); + for (Map.Entry o3: ((Map) o2).entrySet()) + { + if (o3.getKey() instanceof Byte || o3.getKey() instanceof Character || o3.getKey() instanceof String || o3.getKey() instanceof Number || + o3.getKey() instanceof Boolean) + { + o3keys.add(o3.getKey()); + o3vals.add(toMap(o3.getValue())); + } + else + throw new RuntimeException("Key must be Primitive or String for Map Serialization. Type: " + o3.getKey().getClass()); + } + p.put(getid(f), Arrays.asList(o3keys, o3vals)); + + } + else + p.put(getid(f), f.get(o)); + } else if (o2 instanceof Enum) p.put(getid(f), ((Enum) o2).name()); else if (o2 instanceof Serializable) @@ -373,6 +394,9 @@ public static Object parseObject(Map m) case "team": o = new Team(); break; + case "level": + o = new Level(); + break; default: throw new RuntimeException("Bad object type: " + (String) m.get("obj_type")); } @@ -403,8 +427,16 @@ public static Object parseObject(Map m) else if (o2 instanceof ArrayList) { ParameterizedType pt = (ParameterizedType) f.getGenericType(); + Type t = pt.getActualTypeArguments()[0]; + Class elem; + if (t instanceof Class) + elem = (Class) t; + else if (t instanceof ParameterizedType) + elem = (Class) ((ParameterizedType) t).getRawType(); + else + elem = (Class) t; ArrayList arr = (ArrayList) m.get(getid(f)); - if (!arr.isEmpty() && (arr.get(0) instanceof Map)) + if (!arr.isEmpty() && isTanksONable(elem)) { ArrayList o3s = new ArrayList(); for (Map o3: ((ArrayList) m.get(getid(f)))) @@ -413,7 +445,7 @@ else if (o2 instanceof ArrayList) } f.set(o, o3s); } - else if (pt.getActualTypeArguments()[0] == Color.class) + else if (elem == Color.class) { ArrayList colors = new ArrayList<>(); ArrayList> els = (ArrayList>) m.get(getid(f)); @@ -428,6 +460,27 @@ else if (pt.getActualTypeArguments()[0] == Color.class) f.set(o, m.get(getid(f))); + } + else if (o2 instanceof Map) + { + ArrayList keys = (ArrayList) ((ArrayList) m.get(getid(f))).get(0); + ArrayList vals = (ArrayList) ((ArrayList) m.get(getid(f))).get(1); + ParameterizedType pt = (ParameterizedType) f.getGenericType(); + Map o3s; + if (o2 instanceof LinkedHashMap) + o3s = new LinkedHashMap<>(); + else if (o2 instanceof HashMap) + o3s = new HashMap<>(); + else + throw new RuntimeException("Unknown map type: " + o2.getClass()); + for (int i = 0; i < keys.size(); i++) + { + if (isTanksONable((Class) pt.getActualTypeArguments()[1])) + ((Map) o3s).put(keys.get(i), parseObject((Map) vals.get(i))); + else + ((Map) o3s).put(keys.get(i), vals.get(i)); + } + f.set(o, o3s); } else if (o2 instanceof Color) { @@ -466,7 +519,7 @@ else if (o2 instanceof Boolean) } catch (Exception e) { - System.out.println(getid(f)); + System.err.println(getid(f)); throw new RuntimeException(e); } } @@ -500,7 +553,7 @@ else if (o2 instanceof Boolean) } catch (NoSuchFieldException | NullPointerException | IllegalAccessException e) { - System.out.println("Unconvertable field found! " + k); + System.err.println("Unconvertable field found! " + k); e.printStackTrace(); } }