├── src └── main │ ├── resources │ ├── icon.png │ ├── defaulttile.png │ └── objects │ │ ├── bed.png │ │ ├── mine.png │ │ ├── oven.png │ │ ├── sink.png │ │ ├── vent.png │ │ ├── chair.png │ │ ├── shower.png │ │ ├── toilet.png │ │ ├── cabinet.png │ │ ├── cable_tv.png │ │ ├── freezer.png │ │ ├── generator.png │ │ ├── guard_bed.png │ │ ├── ladder_up.png │ │ ├── payphone.png │ │ ├── ladder_down.png │ │ ├── medical_bed.png │ │ ├── sun_lounger.png │ │ ├── vent_slats.png │ │ ├── cutlery_table.png │ │ ├── dining_table.png │ │ ├── door_gardening.png │ │ ├── door_general.png │ │ ├── door_janitor.png │ │ ├── door_kitchen.png │ │ ├── door_laundry.png │ │ ├── door_librarian.png │ │ ├── door_mailroom.png │ │ ├── door_metalshop.png │ │ ├── door_red_key.png │ │ ├── door_woodshop.png │ │ ├── job_book_chest.png │ │ ├── job_raw_metal.png │ │ ├── job_raw_wood.png │ │ ├── job_selection.png │ │ ├── personal_desk.png │ │ ├── prison_sniper.png │ │ ├── prisoner_stash.png │ │ ├── serving_table.png │ │ ├── solitary_bed.png │ │ ├── door_deliveries.png │ │ ├── door_orange_key.png │ │ ├── door_tailorshop.png │ │ ├── door_vent_orange.png │ │ ├── job_fabric_chest.png │ │ ├── job_metal_tools.png │ │ ├── medical_supplies.png │ │ ├── security_camera.png │ │ ├── training_chinup.png │ │ ├── training_jogging.png │ │ ├── training_redtile.png │ │ ├── training_weight.png │ │ ├── washing_machine.png │ │ ├── contraband_detector.png │ │ ├── forced_prisoner_bed.png │ │ ├── job_clean_laundry.png │ │ ├── job_dirty_laundry.png │ │ ├── job_gardening_tools.png │ │ ├── job_mailroom_file.png │ │ ├── job_prepared_metal.png │ │ ├── job_prepared_wood.png │ │ ├── training_bookshelf.png │ │ ├── training_internet.png │ │ ├── training_pressups.png │ │ ├── training_punchbag.png │ │ ├── training_skipping.png │ │ ├── training_speedbag.png │ │ ├── training_treadmill.png │ │ ├── door_general_outside.png │ │ ├── door_jungle_entrance.png │ │ ├── door_prison_entrance.png │ │ ├── forced_prisoner_desk.png │ │ ├── job_cleaning_supplies.png │ │ ├── job_clothing_storage.png │ │ ├── job_deliveries_truck.png │ │ ├── visitation_guest_seat.png │ │ ├── job_deliveries_blue_box.png │ │ ├── job_deliveries_red_box.png │ │ ├── jungle_checkpoint_guard.png │ │ └── visitation_player_seat.png │ └── java │ └── net │ └── jselby │ └── escapists │ └── editor │ ├── utils │ ├── OSUtils.java │ ├── StringUtils.java │ ├── IOUtils.java │ ├── logging │ │ ├── LoggingDebugPrintStream.java │ │ └── Rollbar.java │ ├── SteamFinder.java │ └── BlowfishCompatEncryption.java │ ├── tools │ └── ItemEditor.java │ ├── ActionMode.java │ ├── layers │ ├── impl │ │ ├── UndergroundLayer.java │ │ ├── RoofLayer.java │ │ ├── WorldLayer.java │ │ ├── VentsLayer.java │ │ └── ZoneLayer.java │ └── Layer.java │ ├── objects │ ├── impl │ │ ├── VentMarker.java │ │ └── Light.java │ ├── ObjectRegistry.java │ ├── WorldObject.java │ └── Objects.java │ ├── mapping │ ├── store │ │ ├── PropertiesSection.java │ │ └── PropertiesFile.java │ ├── MapRenderer.java │ └── Map.java │ ├── filters │ └── PreFilters.java │ ├── elements │ ├── ZonesDialog.java │ ├── MapSelectionGUI.java │ ├── MapRendererComponent.java │ ├── PropertiesDialog.java │ └── RenderView.java │ └── EscapistsEditor.java ├── LICENSE ├── README.md ├── .gitignore └── pom.xml /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/defaulttile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/defaulttile.png -------------------------------------------------------------------------------- /src/main/resources/objects/bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/bed.png -------------------------------------------------------------------------------- /src/main/resources/objects/mine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/mine.png -------------------------------------------------------------------------------- /src/main/resources/objects/oven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/oven.png -------------------------------------------------------------------------------- /src/main/resources/objects/sink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/sink.png -------------------------------------------------------------------------------- /src/main/resources/objects/vent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/vent.png -------------------------------------------------------------------------------- /src/main/resources/objects/chair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/chair.png -------------------------------------------------------------------------------- /src/main/resources/objects/shower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/shower.png -------------------------------------------------------------------------------- /src/main/resources/objects/toilet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/toilet.png -------------------------------------------------------------------------------- /src/main/resources/objects/cabinet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/cabinet.png -------------------------------------------------------------------------------- /src/main/resources/objects/cable_tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/cable_tv.png -------------------------------------------------------------------------------- /src/main/resources/objects/freezer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/freezer.png -------------------------------------------------------------------------------- /src/main/resources/objects/generator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/generator.png -------------------------------------------------------------------------------- /src/main/resources/objects/guard_bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/guard_bed.png -------------------------------------------------------------------------------- /src/main/resources/objects/ladder_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/ladder_up.png -------------------------------------------------------------------------------- /src/main/resources/objects/payphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/payphone.png -------------------------------------------------------------------------------- /src/main/resources/objects/ladder_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/ladder_down.png -------------------------------------------------------------------------------- /src/main/resources/objects/medical_bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/medical_bed.png -------------------------------------------------------------------------------- /src/main/resources/objects/sun_lounger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/sun_lounger.png -------------------------------------------------------------------------------- /src/main/resources/objects/vent_slats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/vent_slats.png -------------------------------------------------------------------------------- /src/main/resources/objects/cutlery_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/cutlery_table.png -------------------------------------------------------------------------------- /src/main/resources/objects/dining_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/dining_table.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_gardening.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_gardening.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_general.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_janitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_janitor.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_kitchen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_kitchen.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_laundry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_laundry.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_librarian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_librarian.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_mailroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_mailroom.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_metalshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_metalshop.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_red_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_red_key.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_woodshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_woodshop.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_book_chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_book_chest.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_raw_metal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_raw_metal.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_raw_wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_raw_wood.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_selection.png -------------------------------------------------------------------------------- /src/main/resources/objects/personal_desk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/personal_desk.png -------------------------------------------------------------------------------- /src/main/resources/objects/prison_sniper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/prison_sniper.png -------------------------------------------------------------------------------- /src/main/resources/objects/prisoner_stash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/prisoner_stash.png -------------------------------------------------------------------------------- /src/main/resources/objects/serving_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/serving_table.png -------------------------------------------------------------------------------- /src/main/resources/objects/solitary_bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/solitary_bed.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_deliveries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_deliveries.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_orange_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_orange_key.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_tailorshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_tailorshop.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_vent_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_vent_orange.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_fabric_chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_fabric_chest.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_metal_tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_metal_tools.png -------------------------------------------------------------------------------- /src/main/resources/objects/medical_supplies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/medical_supplies.png -------------------------------------------------------------------------------- /src/main/resources/objects/security_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/security_camera.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_chinup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_chinup.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_jogging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_jogging.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_redtile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_redtile.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_weight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_weight.png -------------------------------------------------------------------------------- /src/main/resources/objects/washing_machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/washing_machine.png -------------------------------------------------------------------------------- /src/main/resources/objects/contraband_detector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/contraband_detector.png -------------------------------------------------------------------------------- /src/main/resources/objects/forced_prisoner_bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/forced_prisoner_bed.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_clean_laundry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_clean_laundry.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_dirty_laundry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_dirty_laundry.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_gardening_tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_gardening_tools.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_mailroom_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_mailroom_file.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_prepared_metal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_prepared_metal.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_prepared_wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_prepared_wood.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_bookshelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_bookshelf.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_internet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_internet.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_pressups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_pressups.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_punchbag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_punchbag.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_skipping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_skipping.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_speedbag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_speedbag.png -------------------------------------------------------------------------------- /src/main/resources/objects/training_treadmill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/training_treadmill.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_general_outside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_general_outside.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_jungle_entrance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_jungle_entrance.png -------------------------------------------------------------------------------- /src/main/resources/objects/door_prison_entrance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/door_prison_entrance.png -------------------------------------------------------------------------------- /src/main/resources/objects/forced_prisoner_desk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/forced_prisoner_desk.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_cleaning_supplies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_cleaning_supplies.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_clothing_storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_clothing_storage.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_deliveries_truck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_deliveries_truck.png -------------------------------------------------------------------------------- /src/main/resources/objects/visitation_guest_seat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/visitation_guest_seat.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_deliveries_blue_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_deliveries_blue_box.png -------------------------------------------------------------------------------- /src/main/resources/objects/job_deliveries_red_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/job_deliveries_red_box.png -------------------------------------------------------------------------------- /src/main/resources/objects/jungle_checkpoint_guard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/jungle_checkpoint_guard.png -------------------------------------------------------------------------------- /src/main/resources/objects/visitation_player_seat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/EscapistsEditor/master/src/main/resources/objects/visitation_player_seat.png -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/OSUtils.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Various operating system related tools. 7 | * 8 | * @author j_selby 9 | */ 10 | public class OSUtils { 11 | public static File getDataStore() { 12 | String os = System.getProperty("os.name").toLowerCase(); 13 | String workingDirectory = System.getProperty("user.home"); 14 | 15 | if (os.contains("win")) { 16 | //it is simply the location of the "AppData" folder 17 | workingDirectory = System.getenv("AppData"); 18 | } else if (os.contains("mac")) { 19 | workingDirectory += "/Library/Application Support"; 20 | } 21 | 22 | return new File(workingDirectory); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/tools/ItemEditor.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.tools; 2 | 3 | import net.jselby.escapists.editor.utils.IOUtils; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.math.BigInteger; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | /** 12 | * Allows for items to be edited. 13 | * 14 | * @author j_selby 15 | */ 16 | public class ItemEditor { 17 | public static void main(String[] args) throws NoSuchAlgorithmException, IOException { 18 | MessageDigest instance = MessageDigest.getInstance("MD5"); 19 | instance.update(IOUtils.toByteArray(new File("items_eng.dat"))); 20 | String md5 = new BigInteger(1, instance.digest()).toString(16); 21 | System.out.println(md5); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils; 2 | 3 | /** 4 | * A very small set of utilities. 5 | * 6 | * @author j_selby 7 | */ 8 | public class StringUtils { 9 | public static boolean isNumber(String output) { 10 | try { 11 | Integer.parseInt(output); 12 | return true; 13 | } catch (Exception e) { 14 | return false; 15 | } 16 | } 17 | 18 | public static String capitalize(String string) { 19 | if (string == null || string.trim().length() == 0) { 20 | return string; 21 | } 22 | 23 | String[] tokens = string.split("\\s"); 24 | string = ""; 25 | 26 | for (int i = 0; i < tokens.length; i++) { 27 | char capLetter = Character.toUpperCase(tokens[i].charAt(0)); 28 | string += (i == 0 ? "" : " ") + capLetter + tokens[i].substring(1, tokens[i].length()); 29 | } 30 | 31 | return string; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/ActionMode.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor; 2 | 3 | /** 4 | * Defines modes used by the editor, in interaction with the screen contents/render. 5 | * 6 | * @author j_selby 7 | */ 8 | public enum ActionMode { 9 | /** 10 | * Toolbar option for creating objects. 11 | */ 12 | CREATE_OBJECT("Create Object"), 13 | /** 14 | * Toolbar option for deleting objects. 15 | */ 16 | DELETE_OBJECT("Delete Object"), 17 | /** 18 | * Toolbar option for setting tiles within a map. 19 | */ 20 | SET_TILE("Set Tile"), 21 | /** 22 | * Toolbar option for editing map zone. 23 | */ 24 | ZONE_EDIT("Edit Zones"); 25 | 26 | private String title; 27 | 28 | ActionMode(String title) { 29 | this.title = title; 30 | } 31 | 32 | /** 33 | * Gets the user-friendly version of the toolbar option. 34 | * @return A user-friendly toolbar prompt 35 | */ 36 | public String getTitle() { 37 | return title; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 James Lonie 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/layers/impl/UndergroundLayer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.layers.impl; 2 | 3 | import net.jselby.escapists.editor.layers.Layer; 4 | import net.jselby.escapists.editor.mapping.Map; 5 | import net.jselby.escapists.editor.mapping.MapRenderer; 6 | 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | 10 | /** 11 | * Renders under the world. 12 | */ 13 | public class UndergroundLayer extends Layer { 14 | @Override 15 | public void render(Map map, MapRenderer renderer, BufferedImage image, Graphics2D g, 16 | java.util.Map tileCache, 17 | BufferedImage tiles, BufferedImage ground) { 18 | 19 | g.drawImage(renderer.layerCache.get("World"), null, 0, 0); 20 | 21 | g.setColor(new Color(0f, 0f, 0f, 0.8f)); 22 | g.fillRect(0, 0, image.getWidth(), image.getHeight()); 23 | 24 | genericRender(map, image, g, tileCache, tiles, 0); 25 | } 26 | 27 | public String getLayerName() { 28 | return "Underground"; 29 | } 30 | 31 | @Override 32 | public boolean isTransparent() { 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/layers/impl/RoofLayer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.layers.impl; 2 | 3 | import net.jselby.escapists.editor.layers.Layer; 4 | import net.jselby.escapists.editor.mapping.Map; 5 | import net.jselby.escapists.editor.mapping.MapRenderer; 6 | 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | 10 | /** 11 | * Renders the main world. 12 | */ 13 | public class RoofLayer extends Layer { 14 | @Override 15 | public void render(Map map, MapRenderer renderer, BufferedImage image, Graphics2D g, 16 | java.util.Map tileCache, 17 | BufferedImage tiles, BufferedImage ground) { 18 | 19 | // Draw the background floor 20 | g.drawImage(renderer.layerCache.get("World"), null, 0, 0); 21 | 22 | g.setColor(new Color(1f, 1f, 1f, 0.8f)); 23 | g.fillRect(0, 0, image.getWidth(), image.getHeight()); 24 | 25 | genericRender(map, image, g, tileCache, tiles, 3); 26 | } 27 | 28 | public String getLayerName() { 29 | return "Roof"; 30 | } 31 | 32 | @Override 33 | public boolean isTransparent() { 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/layers/impl/WorldLayer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.layers.impl; 2 | 3 | import net.jselby.escapists.editor.layers.Layer; 4 | import net.jselby.escapists.editor.mapping.Map; 5 | import net.jselby.escapists.editor.mapping.MapRenderer; 6 | 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | 10 | /** 11 | * Renders the main world. 12 | */ 13 | public class WorldLayer extends Layer { 14 | @Override 15 | public void render(Map map, MapRenderer renderer, BufferedImage image, Graphics2D g, 16 | java.util.Map tileCache, 17 | BufferedImage tiles, BufferedImage ground) { 18 | 19 | // Draw the ground texture 20 | for (int x = 0; x < image.getWidth(); x += ground.getWidth()) { 21 | for (int y = 0; y < image.getHeight(); y+= ground.getHeight()) { 22 | g.drawImage(ground, null, x, y); 23 | } 24 | } 25 | 26 | genericRender(map, image, g, tileCache, tiles, 1); 27 | } 28 | 29 | public String getLayerName() { 30 | return "World"; 31 | } 32 | 33 | @Override 34 | public boolean isTransparent() { 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/layers/impl/VentsLayer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.layers.impl; 2 | 3 | import net.jselby.escapists.editor.layers.Layer; 4 | import net.jselby.escapists.editor.mapping.Map; 5 | import net.jselby.escapists.editor.mapping.MapRenderer; 6 | 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | 10 | /** 11 | * Renders vents. 12 | */ 13 | public class VentsLayer extends Layer { 14 | @Override 15 | public void render(Map map, MapRenderer renderer, BufferedImage image, Graphics2D g, 16 | java.util.Map tileCache, 17 | BufferedImage tiles, BufferedImage ground) { 18 | // Black out everything 19 | g.setColor(Color.WHITE); 20 | g.fillRect(0, 0, image.getWidth(), image.getHeight()); 21 | 22 | g.drawImage(renderer.layerCache.get("World"), null, 0, 0); 23 | 24 | g.setColor(new Color(1f, 1f, 1f, 0.8f)); 25 | g.fillRect(0, 0, image.getWidth(), image.getHeight()); 26 | 27 | genericRender(map, image, g, tileCache, tiles, 2); 28 | } 29 | 30 | public String getLayerName() { 31 | return "Vents"; 32 | } 33 | 34 | @Override 35 | public boolean isTransparent() { 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Unofficial Escapists Editor 2 | =========================== 3 | 4 | **Description:** 5 | 6 | While I was working on the [rendered images](http://www.reddit.com/r/theescapists/comments/2w2dzd/full_resolution_renders_of_ingame_renders_wip/) of the various vanilla maps included with the game, I learned quite a bit about the file format, and therefore decided to develop a editing tool for it. 7 | 8 | The tool is fairly basic in nature currently, but [several](http://steamcommunity.com//app/298630/discussions/0/617328415062159839/) maps have been released using the tool successfully. The interface itself should be fairly easy to use, and documentation for it is coming soon. 9 | 10 | If you do make something cool with this, please feel free to share the tool around! 11 | 12 |   13 | 14 | **Running:** 15 | 16 | Double clicking the executable will give you a GUI interface, simple as that. 17 | 18 | If you want more advanced tools, invoking it as 19 | 20 | java -jar EscapistsEditor-1.0.exe --help 21 | 22 | (yes, that is a .exe) will give you command-line access to some utilities. 23 | 24 |   25 | 26 | **Playing Maps:** 27 | 28 | Feel free to tweak your map in the editor and, when completed, save it to a easy-to-access location. These can be pasted over the maps located at Data\Maps in the Escapists install directory for Stream (etc: C:\Program Files (x86)\Steam\steamapps\common\The Escapists\Data\Maps), but make **BACKUPS** first! 29 | 30 | The editor does require Java 1.8. -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/objects/impl/VentMarker.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.objects.impl; 2 | 3 | import net.jselby.escapists.editor.objects.WorldObject; 4 | 5 | import java.awt.*; 6 | import java.awt.image.BufferedImage; 7 | 8 | /** 9 | * Marks where vents are. 10 | * 11 | * @author j_selby 12 | */ 13 | public class VentMarker extends WorldObject { 14 | private int argument; 15 | 16 | /** 17 | * Creates a new VentMarker. 18 | * 19 | * @param x The X position of this object 20 | * @param y The Y position of this object 21 | */ 22 | public VentMarker(int x, int y, int argument) { 23 | super(x, y); 24 | this.argument = argument; 25 | } 26 | 27 | @Override 28 | public int getID() { 29 | return 50; 30 | } 31 | 32 | @Override 33 | public int getIDArgument() { 34 | return argument; 35 | } 36 | 37 | @Override 38 | public void draw(Graphics2D g, Graphics2D gLighting) { 39 | BufferedImage img = asWorldDictionary() != null ? asWorldDictionary().getTexture() : null; 40 | if (img != null) { 41 | int relativeDrawX = (int) (asWorldDictionary().getDrawX() * 16); 42 | int relativeDrawY = (int) (asWorldDictionary().getDrawY() * 16); 43 | g.drawImage(img, 44 | null, 45 | getX() * 16 + relativeDrawX, 46 | getY() * 16 + relativeDrawY); 47 | } 48 | 49 | int x1 = getX() * 16; 50 | int y1 = getY() * 16; 51 | int x2 = x1 + 16; 52 | int y2 = y1 + 16; 53 | 54 | g.setColor(Color.orange); 55 | g.drawLine(x1, y1, x2, y2); 56 | g.drawLine(x1, y2, x2, y1); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/objects/impl/Light.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.objects.impl; 2 | 3 | import net.jselby.escapists.editor.objects.WorldObject; 4 | 5 | import java.awt.*; 6 | import java.awt.geom.Point2D; 7 | 8 | /** 9 | * A light, rendered using a gradient onto the screen. 10 | * 11 | * @author j_selby 12 | */ 13 | public class Light extends WorldObject { 14 | private int argument; 15 | 16 | /** 17 | * Creates a new Light. 18 | * 19 | * @param x The X position of this object 20 | * @param y The Y position of this object 21 | */ 22 | public Light(int x, int y, int argument) { 23 | super(x, y); 24 | this.argument = argument; 25 | } 26 | 27 | @Override 28 | public int getID() { 29 | return 49; 30 | } 31 | 32 | @Override 33 | public int getIDArgument() { 34 | return argument; 35 | } 36 | 37 | @Override 38 | public void draw(Graphics2D g, Graphics2D gLighting) { 39 | int radius = 3; 40 | // The random addition (16, 0) is due to how The Escapists renders them 41 | int centerX = getX() * 16 + 16; 42 | int centerY = getY() * 16; 43 | 44 | Point2D center = new Point2D.Float(centerX, centerY); 45 | float[] dist = {0.0f, 0.2f, 0.9f}; 46 | Color[] colors = {new Color(1f, 1f, 1f, 0.4f), new Color(1f, 1f, 1f, 0.3f), new Color(1f, 1f, 1f, 0f)}; 47 | RadialGradientPaint p = 48 | new RadialGradientPaint(center, (radius - 1) * 16, dist, 49 | colors, MultipleGradientPaint.CycleMethod.NO_CYCLE); 50 | 51 | int width = ((radius + 1) * 16) / 2; 52 | gLighting.setPaint(p); 53 | gLighting.fillRect(centerX - width, centerY - width, width * 2, width * 2); 54 | gLighting.setPaint(null); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/IOUtils.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils; 2 | 3 | import java.io.*; 4 | import java.math.BigInteger; 5 | import java.net.URL; 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | 9 | /** 10 | * Basic IOUtils replacement. 11 | */ 12 | public class IOUtils { 13 | private final static int BUFFER_SIZE = 8192; 14 | 15 | public static byte[] toByteArray(URL url) throws IOException { 16 | InputStream in = url.openStream(); 17 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 18 | 19 | byte[] buf = new byte[BUFFER_SIZE]; 20 | int val; 21 | while((val = in.read(buf)) != -1) { 22 | out.write(buf, 0, val); 23 | } 24 | 25 | in.close(); 26 | 27 | return out.toByteArray(); 28 | } 29 | 30 | public static byte[] toByteArray(File file) throws IOException { 31 | InputStream in = new FileInputStream(file); 32 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 33 | 34 | byte[] buf = new byte[BUFFER_SIZE]; 35 | int val; 36 | while((val = in.read(buf)) != -1) { 37 | out.write(buf, 0, val); 38 | } 39 | 40 | in.close(); 41 | 42 | return out.toByteArray(); 43 | } 44 | 45 | public static String hash(File file) throws IOException, NoSuchAlgorithmException { 46 | byte[] s = toByteArray(file); 47 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 48 | md5.update(s, 0, s.length); 49 | return new BigInteger(1, md5.digest()).toString(16); 50 | } 51 | 52 | public static String toString(URL url) throws IOException { 53 | return new String(toByteArray(url)); 54 | } 55 | 56 | public static void write(File f, String s) throws IOException { 57 | write(f, s.getBytes()); 58 | } 59 | 60 | public static void write(File f, byte[] s) throws IOException { 61 | try (FileOutputStream out = new FileOutputStream(f)) { 62 | out.write(s); 63 | out.flush(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/mapping/store/PropertiesSection.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.mapping.store; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * A PropertiesSection is a section of a file, containing related pieces of information. 8 | * 9 | * @author j_selby 10 | */ 11 | public class PropertiesSection { 12 | private final String name; 13 | 14 | private final Map contents; 15 | 16 | public PropertiesSection(String name) { 17 | this.name = name; 18 | 19 | contents = new LinkedHashMap<>(); 20 | } 21 | 22 | /** 23 | * Stores a key-value pair within this section. 24 | * 25 | * @param key The key to store with 26 | * @param value The value to insert 27 | */ 28 | public void put(String key, Object value) { 29 | contents.put(key, value); 30 | } 31 | 32 | /** 33 | * Fetches the value for a RELATIVE key for this section. 34 | * 35 | * @param key The RELATIVE key to use. 36 | */ 37 | public Object get(String key) { 38 | return contents.get(key); 39 | } 40 | 41 | /** 42 | * Returns the name of this section 43 | * @return A name 44 | */ 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | /** 50 | * Returns an entire set of values for this section. 51 | * @return A entryset 52 | */ 53 | public Iterable> entrySet() { 54 | return contents.entrySet(); 55 | } 56 | 57 | /** 58 | * Returns the amount of values within this section. 59 | * @return A count of key-value pairs in this section. 60 | */ 61 | public int size() { 62 | return contents.size(); 63 | } 64 | 65 | /** 66 | * Removes all entries from this section. 67 | */ 68 | public void clear() { 69 | contents.clear(); 70 | } 71 | 72 | /** 73 | * Returns a String representation of this section, matching style. 74 | * @return A string representation. 75 | */ 76 | @Override 77 | public String toString() { 78 | String rep = "[" + getName() + "]\n"; 79 | for (Map.Entry val : contents.entrySet()) { 80 | rep += val.getKey() + "=" + val.getValue() + "\n"; 81 | } 82 | 83 | return rep; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Escapists output 3 | EscapistsEditor-*.exe 4 | escapistseditor.log 5 | renders/ 6 | 7 | launch4j.log 8 | 9 | ### Maven template 10 | target/ 11 | pom.xml.tag 12 | pom.xml.releaseBackup 13 | pom.xml.versionsBackup 14 | pom.xml.next 15 | release.properties 16 | dependency-reduced-pom.xml 17 | 18 | 19 | ### NetBeans template 20 | nbproject/private/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | nbactions.xml 25 | nb-configuration.xml 26 | .nb-gradle/ 27 | 28 | 29 | ### JetBrains template 30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 31 | 32 | *.iml 33 | 34 | ## Directory-based project format: 35 | .idea/ 36 | # if you remove the above rule, at least ignore the following: 37 | 38 | # User-specific stuff: 39 | # .idea/workspace.xml 40 | # .idea/tasks.xml 41 | # .idea/dictionaries 42 | 43 | # Sensitive or high-churn files: 44 | # .idea/dataSources.ids 45 | # .idea/dataSources.xml 46 | # .idea/sqlDataSources.xml 47 | # .idea/dynamic.xml 48 | # .idea/uiDesigner.xml 49 | 50 | # Gradle: 51 | # .idea/gradle.xml 52 | # .idea/libraries 53 | 54 | # Mongo Explorer plugin: 55 | # .idea/mongoSettings.xml 56 | 57 | ## File-based project format: 58 | *.ipr 59 | *.iws 60 | 61 | ## Plugin-specific files: 62 | 63 | # IntelliJ 64 | out/ 65 | 66 | # mpeltonen/sbt-idea plugin 67 | .idea_modules/ 68 | 69 | # JIRA plugin 70 | atlassian-ide-plugin.xml 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | 77 | 78 | ### Eclipse template 79 | *.pydevproject 80 | .metadata 81 | .gradle 82 | bin/ 83 | tmp/ 84 | *.tmp 85 | *.bak 86 | *.swp 87 | *~.nib 88 | local.properties 89 | .settings/ 90 | .loadpath 91 | 92 | # Eclipse Core 93 | .project 94 | 95 | # External tool builders 96 | .externalToolBuilders/ 97 | 98 | # Locally stored "Eclipse launch configurations" 99 | *.launch 100 | 101 | # CDT-specific 102 | .cproject 103 | 104 | # JDT-specific (Eclipse Java Development Tools) 105 | .classpath 106 | 107 | # PDT-specific 108 | .buildpath 109 | 110 | # sbteclipse plugin 111 | .target 112 | 113 | # TeXlipse plugin 114 | .texlipse 115 | 116 | 117 | ### Java template 118 | *.class 119 | 120 | # Mobile Tools for Java (J2ME) 121 | .mtj.tmp/ 122 | 123 | # Package Files # 124 | *.war 125 | *.ear 126 | 127 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 128 | hs_err_pid* 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/objects/ObjectRegistry.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.objects; 2 | 3 | import net.jselby.escapists.editor.objects.impl.Light; 4 | import net.jselby.escapists.editor.objects.impl.VentMarker; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * The ObjectRegistry discovers and creates WorldObjects, according to their implementations. 12 | * 13 | * @author j_selby 14 | */ 15 | public class ObjectRegistry { 16 | 17 | private static final Class[] CUSTOM_CLASSES = new Class[] { 18 | Light.class, 19 | VentMarker.class 20 | }; 21 | 22 | public final ConcurrentHashMap> objects = new ConcurrentHashMap<>(); 23 | 24 | /** 25 | * Creates a new ObjectRegistry 26 | * @param packageName The package to discover WorldObjects under 27 | */ 28 | public ObjectRegistry(String packageName) { 29 | for (Class type : CUSTOM_CLASSES) { 30 | // Create a instance to work out what ID this has 31 | try { 32 | // X:Y 33 | Constructor constructor 34 | = type.getConstructor(new Class[]{Integer.TYPE, Integer.TYPE, Integer.TYPE}); 35 | WorldObject instance = constructor.newInstance(-1, -1, 0); 36 | objects.put(instance.getID(), type); 37 | } catch (InstantiationException | 38 | InvocationTargetException | 39 | NoSuchMethodException | 40 | IllegalAccessException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | 46 | public WorldObject instanceWithUnknown(int idToAdd, int x, int y, int level) { 47 | Class gameObjectClass = objects.get(idToAdd); 48 | if (gameObjectClass == null) { 49 | return new WorldObject.Unknown(x, y, idToAdd, level); 50 | } 51 | 52 | try { 53 | // X:Y;iD 54 | Constructor constructor 55 | = gameObjectClass.getConstructor(new Class[]{Integer.TYPE, Integer.TYPE, Integer.TYPE}); 56 | return constructor.newInstance(x, y, level); 57 | } catch (InstantiationException | 58 | InvocationTargetException | 59 | NoSuchMethodException | 60 | IllegalAccessException e) { 61 | e.printStackTrace(); 62 | } 63 | 64 | return null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.jselby.escapists 8 | EscapistsEditor 9 | 1.6.0-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 3.2 21 | 22 | 1.8 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-shade-plugin 30 | 2.3 31 | 32 | 33 | package 34 | 35 | shade 36 | 37 | 38 | true 39 | 40 | 41 | 42 | net.jselby.escapists.editor.EscapistsEditor 43 | 44 | 45 | 46 | 47 | 48 | *:* 49 | 50 | META-INF/*.SF 51 | META-INF/*.DSA 52 | META-INF/*.RSA 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | com.googlecode.json-simple 66 | json-simple 67 | 1.1.1 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/filters/PreFilters.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.filters; 2 | 3 | import net.jselby.escapists.editor.mapping.Map; 4 | import net.jselby.escapists.editor.mapping.store.PropertiesSection; 5 | import net.jselby.escapists.editor.objects.WorldObject; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Filters the map at load time. 12 | * 13 | * @author j_selby 14 | */ 15 | public class PreFilters { 16 | public static void run(Map map) { 17 | // We need to remove all duplicate objects 18 | List currentObjects = new ArrayList<>(); 19 | for (WorldObject obj : 20 | map.getObjects().toArray(new WorldObject[map.getObjects().size()])) { 21 | String str = obj.getID() + ":" + obj.getX() + ":" + obj.getY(); 22 | if (currentObjects.contains(str)) { 23 | map.getObjects().remove(obj); 24 | } else { 25 | currentObjects.add(str); 26 | } 27 | } 28 | 29 | /*if (map.getWidth() != Map.DEFAULT_WIDTH || map.getHeight() != Map.DEFAULT_HEIGHT) { 30 | // Assuming array sizing is broken. 31 | System.out.println(" > Map decode warning: Bad width/height of tiles. Repairing array..."); 32 | 33 | String[] sections = new String[] { 34 | "World", "Underground", "Vents", "Roof" 35 | }; 36 | for (String section : sections) { 37 | int[][] newArray = new int[Map.DEFAULT_HEIGHT][Map.DEFAULT_WIDTH + 1]; 38 | int[][] oldArray = map.getTiles(section); 39 | for (int y = 0; y < oldArray.length; y++) { 40 | for (int x = 0; x < oldArray[y].length; x++) { 41 | if (newArray.length > y && newArray[y].length > x) { 42 | newArray[y][x] = oldArray[y][x]; 43 | } 44 | } 45 | } 46 | map.setTiles(section, newArray); 47 | } 48 | }*/ 49 | 50 | // Escapists Update: Custom map tags 51 | /* 52 | [Perim] 53 | Top=16 54 | Bottom=16 55 | Left=16 56 | Right=16 57 | [Info] 58 | Rdy=1 59 | Custom=2 60 | */ 61 | // Search for "Perim" section 62 | if (map.getMap("Perim") == null) { 63 | // Outdated version. 64 | System.out.println(" > Old-style Escapists map - upgrading..."); 65 | PropertiesSection section = new PropertiesSection("Perim"); 66 | section.put("Top", 16); 67 | section.put("Bottom", 1696); 68 | section.put("Left", 16); 69 | section.put("Right", 1696); 70 | map.setMap("Perim", section); 71 | map.set("Info.Custom", 2); 72 | map.set("Info.Rdy", 1); 73 | map.set("Info.Warden", "Bob"); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/layers/Layer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.layers; 2 | 3 | import net.jselby.escapists.editor.mapping.Map; 4 | import net.jselby.escapists.editor.mapping.MapRenderer; 5 | import net.jselby.escapists.editor.objects.WorldObject; 6 | 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | 10 | /** 11 | * A layer is a renderable layer for the MapRenderer which lays out everything in a proper fashion. 12 | */ 13 | public abstract class Layer { 14 | public abstract void render(Map map, MapRenderer renderer, 15 | 16 | BufferedImage image, Graphics2D g, 17 | 18 | java.util.Map tileCache, 19 | BufferedImage tiles, 20 | BufferedImage ground); 21 | 22 | protected void genericRender(Map map, 23 | 24 | BufferedImage image, Graphics2D g, 25 | 26 | java.util.Map tileCache, 27 | BufferedImage tiles, 28 | 29 | int layer) { 30 | BufferedImage lighting = 31 | new BufferedImage(image.getWidth(), image.getHeight(), 32 | BufferedImage.TYPE_INT_ARGB); 33 | Graphics2D gLighting = lighting.createGraphics(); 34 | 35 | // Add some minimal darkness to the world 36 | gLighting.setColor(new Color(0f, 0f, 0f, 0.1f)); 37 | gLighting.fillRect(0, 0, lighting.getWidth(), lighting.getHeight()); 38 | 39 | drawTiles(map, g, tileCache, tiles, getLayerName()); 40 | 41 | // Draw objects 42 | for (WorldObject object : map.getObjects()) { 43 | if (object.getIDArgument() == layer) { 44 | object.draw(g, gLighting); 45 | } 46 | } 47 | 48 | // Merge lighting 49 | g.drawImage(lighting, null, 0, 0); 50 | 51 | // Done! 52 | // No need to return, our parent already has everything. 53 | } 54 | 55 | protected void drawTiles(Map map, Graphics2D g, 56 | java.util.Map tileCache, BufferedImage tiles, 57 | String layer) { 58 | for (int x = 0; x < map.getWidth(); x++) { 59 | for (int y = 0; y < map.getHeight(); y++) { 60 | int tile = map.getTile(x, y, layer); 61 | if (tile == 0) { 62 | continue; 63 | } 64 | tile--; 65 | 66 | // Get tile from main tiles 67 | int tileX = (int) Math.floor(((double) tile) / (tiles.getHeight() / 16)); 68 | int tileY = tile - (tileX * (tiles.getHeight() / 16)); 69 | int absTileX = tileX * 16; 70 | int absTileY = tileY * 16; 71 | 72 | if (!tileCache.containsKey(tile)) { 73 | tileCache.put(tile, tiles.getSubimage(absTileX, absTileY, 16, 16)); 74 | } 75 | BufferedImage tileRender = tileCache.get(tile); 76 | 77 | g.drawImage(tileRender, null, x * 16, y * 16); 78 | } 79 | } 80 | } 81 | 82 | public abstract String getLayerName(); 83 | 84 | public abstract boolean isTransparent(); 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/mapping/MapRenderer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.mapping; 2 | 3 | import net.jselby.escapists.editor.layers.Layer; 4 | import net.jselby.escapists.editor.layers.impl.*; 5 | 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | 11 | /** 12 | * Renders maps into a BufferedImage. 13 | * 14 | * @author j_selby 15 | */ 16 | public class MapRenderer { 17 | 18 | // These are public for ease-of-use 19 | public boolean showZones = false; 20 | public boolean zoneEditing = false; 21 | 22 | public String view = "World"; 23 | 24 | // Caches individual assets 25 | private java.util.Map tileCache = new HashMap<>(); 26 | 27 | // Caches entire layers 28 | public java.util.Map layerCache = new HashMap<>(); 29 | 30 | private java.util.List layers = new ArrayList<>(); 31 | 32 | public MapRenderer() { 33 | // Build layers 34 | layers.add(new UndergroundLayer()); 35 | layers.add(new WorldLayer()); 36 | layers.add(new VentsLayer()); 37 | layers.add(new RoofLayer()); 38 | layers.add(new ZoneLayer()); 39 | } 40 | 41 | public BufferedImage render(Map map, String view) { 42 | int renderView = 1; 43 | if (view.equalsIgnoreCase("Underground")) { 44 | renderView = 0; 45 | } else if (view.equalsIgnoreCase("Vents")) { 46 | renderView = 2; 47 | } else if (view.equalsIgnoreCase("Roof")) { 48 | renderView = 3; 49 | } 50 | 51 | BufferedImage renderPlatform = 52 | new BufferedImage((map.getHeight() - 1) * 16, (map.getWidth() - 3) * 16, 53 | BufferedImage.TYPE_INT_ARGB); 54 | 55 | Graphics2D g = renderPlatform.createGraphics(); 56 | 57 | // Render this layer 58 | g.drawImage(renderLayer(map, renderView), null, 0, 0); 59 | 60 | // Render zones, if required 61 | if (showZones) { 62 | g.drawImage(renderLayer(map, 4), null, 0, 0); 63 | } 64 | 65 | return renderPlatform; 66 | } 67 | 68 | private BufferedImage renderLayer(Map map, int layer) { 69 | // Compute view 70 | 71 | // Get that index 72 | Layer mainLayer = layers.get(layer); 73 | 74 | // Get tiles, and render it 75 | BufferedImage renderPlatform = 76 | new BufferedImage((map.getHeight() - 1) * 16, (map.getWidth() - 3) * 16, 77 | mainLayer.isTransparent() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); 78 | 79 | Graphics2D g = renderPlatform.createGraphics(); 80 | 81 | // Get tiles 82 | BufferedImage tiles = map.getTilesImage(); 83 | BufferedImage ground = map.getBackgroundImage(); 84 | 85 | // Make sure dependencies are sorted 86 | if (mainLayer.getLayerName().equalsIgnoreCase("Roof") 87 | && !layerCache.containsKey("World")) { 88 | // Render world first 89 | renderLayer(map, 1); 90 | } 91 | 92 | // Render that layer 93 | mainLayer.render(map, this, renderPlatform, g, tileCache, tiles, ground); 94 | 95 | // Add it to the main index 96 | layerCache.put(mainLayer.getLayerName(), renderPlatform); 97 | 98 | return renderPlatform; 99 | } 100 | 101 | public ZoneLayer getZoning() { 102 | return (ZoneLayer) layers.get(4); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/logging/LoggingDebugPrintStream.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils.logging; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | import java.io.PrintStream; 6 | 7 | /** 8 | * Redirects a stream to a output stream and the original printstream. 9 | * 10 | * @author j_selby 11 | */ 12 | public class LoggingDebugPrintStream extends PrintStream { 13 | private PrintStream file; 14 | private PrintStream original; 15 | 16 | public LoggingDebugPrintStream(OutputStream file, PrintStream original) { 17 | super(file, true); 18 | this.file = new PrintStream(file); 19 | this.original = original; 20 | } 21 | 22 | @Override 23 | public void write(int b) { 24 | file.write(b); 25 | original.write(b); 26 | if (b == '\n') { 27 | flush(); 28 | } 29 | } 30 | 31 | @Override 32 | public void flush() { 33 | file.flush(); 34 | original.flush(); 35 | 36 | } 37 | 38 | @Override 39 | public void print(boolean b) { 40 | file.print(b); 41 | original.print(b); 42 | } 43 | 44 | @Override 45 | public void print(char c) { 46 | file.print(c); 47 | original.print(c); 48 | } 49 | 50 | @Override 51 | public void print(double d) { 52 | file.print(d); 53 | original.print(d); 54 | } 55 | 56 | @Override 57 | public void print(float f) { 58 | file.print(f); 59 | original.print(f); 60 | } 61 | 62 | @Override 63 | public void print(int i) { 64 | file.print(i); 65 | original.print(i); 66 | } 67 | 68 | @Override 69 | public void print(long l) { 70 | file.print(l); 71 | original.print(l); 72 | } 73 | 74 | @Override 75 | public void print(Object obj) { 76 | file.print(obj); 77 | original.print(obj); 78 | } 79 | 80 | @Override 81 | public void print(char[] s) { 82 | file.print(s); 83 | original.print(s); 84 | } 85 | 86 | @Override 87 | public void print(String s) { 88 | file.print(s); 89 | original.print(s); 90 | } 91 | 92 | @Override 93 | public void println() { 94 | file.println(); 95 | original.println(); 96 | } 97 | 98 | @Override 99 | public void println(boolean x) { 100 | file.println(x); 101 | original.println(x); 102 | } 103 | 104 | @Override 105 | public void println(char x) { 106 | file.println(x); 107 | original.println(x); 108 | } 109 | 110 | @Override 111 | public void println(int x) { 112 | file.println(x); 113 | original.println(x); 114 | } 115 | 116 | @Override 117 | public void println(double x) { 118 | file.println(x); 119 | original.println(x); 120 | } 121 | 122 | @Override 123 | public void println(char[] x) { 124 | file.println(x); 125 | original.println(x); 126 | } 127 | 128 | @Override 129 | public void println(String x) { 130 | file.println(x); 131 | original.println(x); 132 | } 133 | 134 | @Override 135 | public void println(long x) { 136 | file.println(x); 137 | original.println(x); 138 | } 139 | 140 | @Override 141 | public void println(float x) { 142 | file.println(x); 143 | original.println(x); 144 | } 145 | 146 | @Override 147 | public void println(Object x) { 148 | file.println(x); 149 | original.println(x); 150 | } 151 | 152 | @Override 153 | public void write(byte[] b) throws IOException { 154 | file.write(b); 155 | original.write(b); 156 | } 157 | 158 | @Override 159 | public void write(byte[] buf, int off, int len) { 160 | file.write(buf, off, len); 161 | original.write(buf, off, len); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/SteamFinder.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils; 2 | 3 | import net.jselby.escapists.editor.EscapistsEditor; 4 | 5 | import javax.swing.*; 6 | import javax.swing.filechooser.FileFilter; 7 | import java.io.File; 8 | 9 | /** 10 | * The SteamFinder discovers possible Steam paths, if required. 11 | * 12 | * @author j_selby 13 | */ 14 | public class SteamFinder { 15 | /** 16 | * Possible Steam locations. Prefixed by a drive. 17 | */ 18 | private static final String[] POSSIBLE_LOCS = new String[] { 19 | "Program Files (x86)" + File.separator + "Steam" + File.separator, 20 | "Program Files" + File.separator + "Steam" + File.separator, 21 | "Steam" + File.separator, 22 | "SteamLibrary" + File.separator, 23 | "Games" + File.separator, 24 | "Games" + File.separator + "Steam" + File.separator 25 | }; 26 | 27 | /** 28 | * Discovers a steam installation. 29 | * 30 | * @return A steam installation, or null if one was not found. 31 | * @param editor The editor to use for generating prompts 32 | */ 33 | public static File getSteamPath(EscapistsEditor editor) { 34 | // TODO: Support platforms other then Windows. 35 | // Discover drive letters 36 | File[] drives = File.listRoots(); 37 | 38 | // Check locations 39 | for (File drive : drives) { 40 | for (String possibleLoc : POSSIBLE_LOCS) { 41 | File steamDir = new File(drive, possibleLoc); 42 | 43 | if (steamDir.exists()) { 44 | // Make sure that a Steam.dll exists here. 45 | File steamFile = new File(steamDir, "Steam.dll"); 46 | if (steamFile.exists()) { 47 | // Check that the Escapists is here 48 | if (new File(steamDir, 49 | "SteamApps" + File.separator + "common" + File.separator + "The Escapists").exists()) { 50 | return new File(steamDir, 51 | "SteamApps" + File.separator + "common" + File.separator + "The Escapists"); 52 | } 53 | if (new File(steamDir, 54 | "common" + File.separator + "The Escapists").exists()) { 55 | return new File(steamDir, 56 | "common" + File.separator + "The Escapists"); 57 | } 58 | if (new File(steamDir, 59 | "The Escapists").exists()) { 60 | return new File(steamDir, 61 | "The Escapists"); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | // Ask user to find directory, if we are in a GUI. 69 | editor.dialog("No Stream installation detected. Please locate The Escapists in the following prompt."); 70 | JFileChooser chooser = new JFileChooser(); 71 | chooser.setFileFilter(new FileFilter() { 72 | @Override 73 | public boolean accept(File f) { 74 | return f.isDirectory(); 75 | } 76 | 77 | @Override 78 | public String getDescription() { 79 | return "Directory"; 80 | } 81 | }); 82 | chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 83 | int result = chooser.showOpenDialog(null); 84 | if (JFileChooser.APPROVE_OPTION == result) { 85 | // Check for Escapists here 86 | File resultFile = chooser.getSelectedFile(); 87 | File escapistsTest = new File(resultFile, "TheEscapists.exe"); 88 | if (escapistsTest.exists()) { 89 | return resultFile; 90 | } else { 91 | EscapistsEditor.fatalError(new Exception("Invalid directory (No TheEscapists.exe)")); 92 | } 93 | } 94 | 95 | return null; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/mapping/store/PropertiesFile.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.mapping.store; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * A PropertiesFile stores the raw backing format used by the Escapists, a key-value store. 8 | * 9 | * @author j_selby 10 | */ 11 | public class PropertiesFile { 12 | private final Map sections; 13 | 14 | public PropertiesFile(String contents) { 15 | sections = new LinkedHashMap<>(); 16 | 17 | String[] lines = contents.split("\n"); 18 | 19 | ReadState state = ReadState.UNDEFINED; 20 | PropertiesSection section = null; 21 | 22 | for (int i = 0; i < lines.length; i++) { 23 | String line = lines[i]; 24 | String trimmedLine = line.trim(); 25 | int humanLineNum = i + 1; 26 | 27 | if (trimmedLine.length() == 0) { 28 | // Blank line, skip. 29 | continue; 30 | } 31 | 32 | if (trimmedLine.startsWith("[")) { 33 | // Section title 34 | 35 | // Check for closing bracket 36 | if (trimmedLine.endsWith("]")) { 37 | // OK: Setup new section 38 | String name = trimmedLine.substring(1, trimmedLine.length() - 1); 39 | 40 | section = new PropertiesSection(name); 41 | sections.put(name, section); 42 | 43 | state = ReadState.KEYS; 44 | } else { 45 | System.out.println("Parse error: Section definition not closed @ ln " + humanLineNum); 46 | } 47 | 48 | continue; 49 | } 50 | 51 | if (state == ReadState.UNDEFINED) { 52 | // Bad section 53 | System.out.println("Parse error: Section not defined @ ln " + humanLineNum); 54 | continue; 55 | } 56 | 57 | // OK! Lets see if we have a key-value pair here 58 | if (line.contains("=")) { 59 | // We do! Split and store! 60 | int index = trimmedLine.indexOf("="); 61 | 62 | String key = trimmedLine.substring(0, index); 63 | String value = trimmedLine.substring(index + 1); 64 | 65 | section.put(key, value); 66 | } else { 67 | // Completely unknown line 68 | System.out.println("Parse error: Unknown syntax @ ln " + humanLineNum); 69 | } 70 | } 71 | 72 | // Done! 73 | } 74 | 75 | /** 76 | * Returns a section from this file. 77 | * 78 | * @param name A name of a section 79 | * @return A PropertiesSection, or null 80 | */ 81 | public PropertiesSection getSection(String name) { 82 | return sections.get(name); 83 | } 84 | 85 | /** 86 | * Returns the entire set of sections from this file. 87 | * @return A entire entryset 88 | */ 89 | public Iterable> entrySet() { 90 | return sections.entrySet(); 91 | } 92 | 93 | /** 94 | * Checks for a existing section. 95 | * 96 | * @param name A name of a section 97 | * @return If it exists 98 | */ 99 | public boolean containsSection(String name) { 100 | return sections.containsKey(name); 101 | } 102 | 103 | /** 104 | * Returns a string representation of this file. 105 | * @return A correctly formatted list. 106 | */ 107 | @Override 108 | public String toString() { 109 | String build = ""; 110 | for (PropertiesSection section : sections.values()) { 111 | build += section.toString(); 112 | } 113 | return build; 114 | } 115 | 116 | public void setSection(String key, PropertiesSection value) { 117 | sections.put(key, value); 118 | } 119 | 120 | private enum ReadState { 121 | UNDEFINED, 122 | KEYS 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/elements/ZonesDialog.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.elements; 2 | 3 | import net.jselby.escapists.editor.mapping.Map; 4 | 5 | import javax.swing.*; 6 | import javax.swing.event.DocumentEvent; 7 | import javax.swing.event.DocumentListener; 8 | import java.awt.*; 9 | 10 | /** 11 | * A simple dialog for showing properties. 12 | */ 13 | public class ZonesDialog extends JDialog { 14 | public ZonesDialog(RenderView parent, final MapRendererComponent renderer, final Map mapToEdit) { 15 | super(parent, "Zone Editor", false); 16 | 17 | JPanel scrollableContent = new JPanel(); 18 | scrollableContent.setLayout(new GridLayout(0, 2, 0, 10)); 19 | scrollableContent.add(Box.createVerticalStrut(10)); 20 | scrollableContent.add(Box.createVerticalGlue()); 21 | scrollableContent.add(Box.createVerticalGlue()); 22 | scrollableContent.add(new JLabel("Syntax: \"x1_y1_x2_y2\" or .")); 23 | scrollableContent.add(Box.createVerticalGlue()); 24 | scrollableContent.add(new JLabel("Coordinates is *16 from tiles!")); 25 | 26 | // Build fields 27 | for (java.util.Map.Entry fieldSet : mapToEdit.getMap("Zones").entrySet()) { 28 | final String name = fieldSet.getKey(); 29 | final String startingContent = ((String) fieldSet.getValue()).trim(); 30 | 31 | JLabel label = new JLabel(name); 32 | 33 | final JTextField contentField = new JTextField(startingContent); 34 | label.setLabelFor(contentField); 35 | label.setBorder(BorderFactory.createEmptyBorder(0, 50, 0, 0)); 36 | 37 | contentField.getDocument().addDocumentListener(new DocumentListener() { 38 | @Override 39 | public void insertUpdate(DocumentEvent e) { 40 | documentEvent(e); 41 | } 42 | 43 | @Override 44 | public void removeUpdate(DocumentEvent e) { 45 | documentEvent(e); 46 | } 47 | 48 | @Override 49 | public void changedUpdate(DocumentEvent e) { 50 | documentEvent(e); 51 | } 52 | 53 | public void documentEvent(DocumentEvent evt) { 54 | mapToEdit.set("Zones." + name, ""); 55 | int amount = contentField.getText().trim().split("_").length; 56 | if (contentField.getText().trim().length() != 0 && amount != 4) { 57 | contentField.setBackground(Color.red); 58 | return; 59 | } 60 | 61 | if (contentField.getText().trim().length() != 0) { 62 | for (String str : contentField.getText().split("_")) { 63 | try { 64 | int number = Integer.parseInt(str); 65 | if (number < 0 || number > (96 * 16)) { 66 | System.out.println("Invalid number: out of range"); 67 | contentField.setBackground(Color.red); 68 | return; 69 | } 70 | } catch (Exception err) { 71 | System.out.println("Invalid number: not a number"); 72 | contentField.setBackground(Color.red); 73 | return; 74 | } 75 | } 76 | } 77 | 78 | contentField.setBackground(Color.white); 79 | if (amount == 4) { 80 | mapToEdit.set("Zones." + name, contentField.getText().trim()); 81 | } else { 82 | mapToEdit.set("Zones." + name, ""); 83 | } 84 | renderer.refresh(); 85 | } 86 | }); 87 | 88 | // Sizing 89 | contentField.setMinimumSize(new Dimension(150, 50)); 90 | scrollableContent.add(label, BorderLayout.WEST); 91 | scrollableContent.add(contentField, BorderLayout.CENTER); 92 | } 93 | 94 | scrollableContent.add(Box.createVerticalStrut(10)); 95 | scrollableContent.add(Box.createVerticalGlue()); 96 | 97 | JScrollPane scroller = new JScrollPane(scrollableContent); 98 | scroller.getVerticalScrollBar().setUnitIncrement(16); 99 | 100 | add(scroller); 101 | 102 | setSize(new Dimension(640, 480)); 103 | setResizable(false); 104 | setLocationRelativeTo(null); 105 | setVisible(true); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/objects/WorldObject.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.objects; 2 | 3 | import net.jselby.escapists.editor.EscapistsEditor; 4 | import net.jselby.escapists.editor.utils.StringUtils; 5 | 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | 9 | /** 10 | * A world object is a editable entity in the world, which can be interacted with, open, close, or configure 11 | * game logic. 12 | * 13 | * @author j_selby 14 | */ 15 | public abstract class WorldObject { 16 | private int x; 17 | private int y; 18 | 19 | /** 20 | * Creates a new WorldObject. 21 | * @param x The X position of this object 22 | * @param y The Y position of this object 23 | */ 24 | public WorldObject(int x, int y) { 25 | this.x = x; 26 | this.y = y; 27 | } 28 | 29 | /** 30 | * Returns the human-readable name of this object. 31 | * @return A constant String name 32 | */ 33 | public String getName() { 34 | String providedName = null; 35 | for (Objects object : Objects.values()) { 36 | if (object.getID() == getID()) { 37 | providedName = StringUtils.capitalize(object.name().toLowerCase().replace("_", " ")); 38 | } 39 | } 40 | if (providedName == null) { 41 | providedName = StringUtils.capitalize(getClass().getSimpleName().toLowerCase().replace("_", " ")) 42 | + " (ID: " + getID() + ")"; 43 | } 44 | return getX() + ":" + getY() + " = " + providedName; 45 | } 46 | 47 | /** 48 | * Returns the ID of this object. 49 | * @return A constant ID, conforming to the IDs used by The Escapists. 50 | */ 51 | public abstract int getID(); 52 | 53 | /** 54 | * Returns the additional argument of this object. 55 | * @return A constant argument, conforming to the arguments used by The Escapists. 56 | */ 57 | public abstract int getIDArgument(); 58 | 59 | @Override 60 | public String toString() { 61 | return "ID: " + getID(); 62 | } 63 | 64 | /** 65 | * Draw this object. Must be relative to coords. 66 | * @param g Graphics place to draw onto 67 | * @param gLighting Graphics to draw lighting onto 68 | */ 69 | public abstract void draw(Graphics2D g, Graphics2D gLighting); 70 | 71 | public int getX() { 72 | return x; 73 | } 74 | 75 | public int getY() { 76 | return y; 77 | } 78 | 79 | public Objects asWorldDictionary() { 80 | for (Objects object : Objects.values()) { 81 | if (object.getID() == getID()) { 82 | return object; 83 | } 84 | } 85 | return null; 86 | } 87 | 88 | public static class Unknown extends WorldObject { 89 | 90 | private int id; 91 | private int argument; 92 | 93 | /** 94 | * Creates a new Unknown object representation. 95 | * 96 | * @param x The X position of this object 97 | * @param y The Y position of this object 98 | */ 99 | public Unknown(int x, int y, int id, int argument) { 100 | super(x, y); 101 | this.id = id; 102 | this.argument = argument; 103 | } 104 | 105 | @Override 106 | public int getIDArgument() { 107 | return this.argument; 108 | } 109 | 110 | @Override 111 | public int getID() { 112 | return id; 113 | } 114 | 115 | @Override 116 | public void draw(Graphics2D g, Graphics2D gLighting) { 117 | // Try to grab the texture for this asset 118 | BufferedImage img = asWorldDictionary() != null ? asWorldDictionary().getTexture() : null; 119 | if (img != null) { 120 | int relativeDrawX = (int) (asWorldDictionary().getDrawX() * 16); 121 | int relativeDrawY = (int) (asWorldDictionary().getDrawY() * 16); 122 | g.drawImage(img, 123 | null, 124 | getX() * 16 + relativeDrawX, 125 | getY() * 16 + relativeDrawY); 126 | } else { 127 | if (asWorldDictionary() != null 128 | && asWorldDictionary().name().toLowerCase().startsWith("ai") 129 | && !EscapistsEditor.showGUI) { 130 | return; 131 | } 132 | g.setColor(new Color(1f, 0f, 0f, 1f)); 133 | g.drawRect(getX() * 16, getY() * 16, 16, 16); 134 | if (EscapistsEditor.DRAW_TEXT) { 135 | g.drawString(getID() + "", getX() * 16 + 2, getY() * 16 + 12); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/objects/Objects.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.objects; 2 | 3 | import javax.imageio.ImageIO; 4 | import java.awt.image.BufferedImage; 5 | 6 | public enum Objects { 7 | // General items 8 | BED(1), 9 | CHAIR(2), 10 | TOILET(3), 11 | OVEN(4, 0, -1), 12 | WASHING_MACHINE(5, 0, -1), 13 | PERSONAL_DESK(9), 14 | FREEZER(10), 15 | SERVING_TABLE(11), 16 | MEDICAL_BED(12), 17 | DINING_TABLE(13), 18 | CUTLERY_TABLE(15), 19 | SHOWER(21), 20 | CONTRABAND_DETECTOR(32), 21 | SECURITY_CAMERA(33), 22 | SINK(34), 23 | MINE(40), 24 | MEDICAL_SUPPLIES(48), 25 | LIGHT(49), 26 | VENT(50), 27 | VENT_SLATS(51), 28 | LADDER_UP(52), 29 | LADDER_DOWN(53), 30 | FORCED_PRISONER_BED(54), 31 | FORCED_PRISONER_DESK(55), 32 | GUARD_BED(56), 33 | PRISON_SNIPER(57), 34 | SOLITARY_BED(59), 35 | ROOF_SPOTLIGHTS(60), 36 | PRISONER_STASH(61), 37 | JUNGLE_CHECKPOINT_GUARD(62, 0, -1), 38 | GENERATOR(65), 39 | CABINET(68, 0, -1), 40 | VISITATION_GUEST_SEAT(70), 41 | VISITATION_PLAYER_SEAT(71), 42 | PAYPHONE(74), 43 | CABLE_TV(87), 44 | SUN_LOUNGER(88), 45 | SIDEWAYS_PRISONER_BED(110), 46 | FORCED_SIDEWAYS_PRISONER_BED(111), 47 | 48 | // Job items 49 | JOB_DIRTY_LAUNDRY(35), 50 | JOB_CLEAN_LAUNDRY(36), 51 | JOB_RAW_WOOD(41), 52 | JOB_PREPARED_WOOD(42), 53 | JOB_RAW_METAL(43), 54 | JOB_PREPARED_METAL(44), 55 | JOB_METAL_TOOLS(45, 0, -1), 56 | JOB_SELECTION(46), 57 | JOB_CLEANING_SUPPLIES(47), 58 | JOB_DELIVERIES_TRUCK(78, -1.5, -1), 59 | JOB_FABRIC_CHEST(79), 60 | JOB_CLOTHING_STORAGE(80), 61 | JOB_BOOK_CHEST(81), 62 | JOB_MAILROOM_FILE(82), 63 | JOB_GARDENING_TOOLS(89), 64 | JOB_DELIVERIES_RED_BOX(92), 65 | JOB_DELIVERIES_BLUE_BOX(93), 66 | 67 | // Doors 68 | DOOR_GENERAL(24), 69 | DOOR_GENERAL_OUTSIDE(25), 70 | DOOR_ORANGE_KEY(26), 71 | DOOR_KITCHEN(27), 72 | DOOR_LAUNDRY(28), 73 | DOOR_JANITOR(29), 74 | DOOR_RED_KEY(30), 75 | DOOR_METALSHOP(31), 76 | DOOR_LIBRARIAN(37), 77 | DOOR_WOODSHOP(38), 78 | DOOR_PRISON_ENTRANCE(75), 79 | DOOR_MAILROOM(83), 80 | DOOR_GARDENING(84), 81 | DOOR_TAILORSHOP(85), 82 | DOOR_DELIVERIES(86), 83 | DOOR_JUNGLE_ENTRANCE(95), 84 | DOOR_VENT_ORANGE(103), 85 | 86 | // Ziplines 87 | ZIPLINE_START_UP(76), 88 | ZIPLINE_END(77), 89 | ZIPLINE_START_DOWN(99), 90 | ZIPLINE_START_RIGHT(100), 91 | ZIPLINE_START_LEFT(101), 92 | 93 | // Training 94 | TRAINING_BOOKSHELF(6, 0, -1), 95 | TRAINING_TREADMILL(7), 96 | TRAINING_WEIGHT(8), 97 | TRAINING_INTERNET(69), 98 | TRAINING_JOGGING(90), 99 | TRAINING_PRESSUPS(91), 100 | TRAINING_SKIPPING(96), 101 | TRAINING_PUNCHBAG(97, 0, -1), 102 | TRAINING_SPEEDBAG(98, 0, -1), 103 | TRAINING_CHINUP(102), 104 | 105 | // Jeep 106 | AI_JEEP_1(63), 107 | AI_JEEP_2(64), 108 | AI_JEEP_3(66), 109 | AI_JEEP_4(67), 110 | 111 | // AI Waypoints 112 | AI_WP_GUARD_SHOWERS(14), 113 | AI_WP_PRISONER_ROLLCALL(16), 114 | AI_WP_GUARD_ROLLCALL(17), 115 | AI_WP_PRISONER_GENERAL(18), 116 | AI_WP_GUARD_GENERAL(19), 117 | AI_WP_GUARD_MEALS(22), 118 | AI_WP_PRISONER_MEALS(23), 119 | AI_WP_GUARD_EXERCISE(39), 120 | AI_NPC_SPAWN(72), 121 | AI_WP_EMPLOYMENT_OFFICER(73), 122 | AI_WP_DOCTOR_WORK(94), 123 | ; 124 | 125 | private final int id; 126 | private double drawXRelative; 127 | private double drawYRelative; 128 | 129 | private BufferedImage texture; 130 | private boolean textureLoaded; 131 | 132 | /** 133 | * Basic constructor for all objects in a ENUM 134 | * @param id The ID of the object 135 | */ 136 | Objects(int id) { 137 | this.id = id; 138 | } 139 | 140 | /** 141 | * Basic constructor for all objects in a ENUM 142 | * @param id The ID of the object 143 | */ 144 | Objects(int id, double drawXRelative, double drawYRelative) { 145 | this.id = id; 146 | this.drawXRelative = drawXRelative; 147 | this.drawYRelative = drawYRelative; 148 | } 149 | 150 | public int getID() { 151 | return id; 152 | } 153 | 154 | public double getDrawX() { 155 | return drawXRelative; 156 | } 157 | 158 | public double getDrawY() { 159 | return drawYRelative; 160 | } 161 | 162 | public BufferedImage getTexture() { 163 | if (textureLoaded) { 164 | return texture; 165 | } 166 | 167 | textureLoaded = true; 168 | 169 | // Load it, if possible 170 | try { 171 | texture = ImageIO.read(getClass() 172 | .getResource("/objects/" + name().toLowerCase() + ".png")); 173 | } catch (IllegalArgumentException ignored) { 174 | } catch (Exception e) { 175 | e.printStackTrace(); 176 | } 177 | 178 | return texture; 179 | } 180 | 181 | public WorldObject asWorldObject(int x, int y) { 182 | return new WorldObject.Unknown(x, y, id, 1); 183 | } 184 | } -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/logging/Rollbar.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils.logging; 2 | 3 | import net.jselby.escapists.editor.EscapistsEditor; 4 | import net.jselby.escapists.editor.utils.IOUtils; 5 | import net.jselby.escapists.editor.utils.OSUtils; 6 | import org.json.simple.JSONArray; 7 | import org.json.simple.JSONObject; 8 | 9 | import javax.net.ssl.HttpsURLConnection; 10 | import javax.swing.*; 11 | import java.io.*; 12 | import java.net.URL; 13 | import java.util.prefs.BackingStoreException; 14 | 15 | /** 16 | * Rollbar is a online logging system. 17 | * 18 | * @author j_selby 19 | */ 20 | public class Rollbar { 21 | private static final String ACCESS_TOKEN = "ab6de73cd144479da279f262d23540b2"; 22 | private static final String ENDPOINT = "https://api.rollbar.com/api/1/item/"; 23 | 24 | private static boolean enabled = false; 25 | 26 | public static void init() throws BackingStoreException { 27 | File f = new File(OSUtils.getDataStore(), "unofficial-editor.cfg"); 28 | 29 | // Make sure user has accepted 30 | if (!f.exists()) { 31 | try { 32 | IOUtils.write(f, JOptionPane.showConfirmDialog(null, 33 | "To help with development of the editor, \n" + 34 | "would it be fine to enable anonymous error submission?", 35 | "Escapists Map Editor", JOptionPane.YES_NO_OPTION) == 0 ? "yes" : "no"); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | try { 42 | enabled = IOUtils.toString(f.toURI().toURL()).trim().toLowerCase().equalsIgnoreCase("yes"); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | public static Thread fatal(Exception e) { 49 | // Encode to JSON 50 | JSONObject object = new JSONObject(); 51 | object.put("access_token", ACCESS_TOKEN); 52 | 53 | JSONObject trace = new JSONObject(); 54 | JSONArray frames = new JSONArray(); 55 | StackTraceElement[] elements = e.getStackTrace(); 56 | for (int x = elements.length - 1; x >= 0; x--) { 57 | StackTraceElement element = elements[x]; 58 | JSONObject frame = new JSONObject(); 59 | frame.put("filename", element.getFileName()); 60 | frame.put("lineno", element.getLineNumber()); 61 | frame.put("method", element.getMethodName()); 62 | frame.put("class_name", element.getClassName()); 63 | frames.add(frame); 64 | } 65 | trace.put("frames", frames); 66 | JSONObject exception = new JSONObject(); 67 | exception.put("class", e.getClass().getName()); 68 | exception.put("message", e.getMessage()); 69 | trace.put("exception", exception); 70 | 71 | JSONObject body = new JSONObject(); 72 | body.put("trace", trace); 73 | 74 | JSONObject data = new JSONObject(); 75 | data.put("environment", "production"); 76 | data.put("body", body); 77 | data.put("code_version", EscapistsEditor.VERSION); 78 | data.put("language", "java"); 79 | data.put("framework", "java"); 80 | data.put("platform", "client"); 81 | 82 | JSONObject client = new JSONObject(); 83 | client.put("java_version", System.getProperty("java.version")); 84 | client.put("os_version", System.getProperty("os.name")); 85 | data.put("client", client); 86 | 87 | object.put("data", data); 88 | 89 | String json = object.toJSONString(); 90 | 91 | Thread thread = new Thread(() -> { 92 | try { 93 | submit(json); 94 | } catch (IOException ignored) {} 95 | }); 96 | thread.start(); 97 | return thread; 98 | } 99 | 100 | private static void submit(String json) throws IOException { 101 | if (!enabled) { 102 | return; 103 | } 104 | 105 | URL obj = new URL(ENDPOINT); 106 | HttpsURLConnection connection = (HttpsURLConnection) obj.openConnection(); 107 | 108 | //add reuqest header 109 | connection.setRequestMethod("POST"); 110 | 111 | // Send post request 112 | connection.setDoOutput(true); 113 | DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); 114 | wr.writeBytes(json); 115 | wr.flush(); 116 | wr.close(); 117 | 118 | int responseCode = connection.getResponseCode(); 119 | if (responseCode != 200) { 120 | System.out.println("Failed to upload errorlog: " + responseCode); 121 | BufferedReader in = new BufferedReader( 122 | new InputStreamReader(connection.getErrorStream())); 123 | String inputLine; 124 | StringBuffer response = new StringBuffer(); 125 | 126 | while ((inputLine = in.readLine()) != null) { 127 | response.append(inputLine); 128 | } 129 | in.close(); 130 | 131 | //print result 132 | System.out.println(response.toString()); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/layers/impl/ZoneLayer.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.layers.impl; 2 | 3 | import net.jselby.escapists.editor.layers.Layer; 4 | import net.jselby.escapists.editor.mapping.Map; 5 | import net.jselby.escapists.editor.mapping.MapRenderer; 6 | 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | import java.util.HashMap; 10 | import java.util.Random; 11 | 12 | /** 13 | * Renders zones ontop of the world. 14 | */ 15 | public class ZoneLayer extends Layer { 16 | 17 | private static final int margin = 10; 18 | 19 | private java.util.Map zoneColorMappings = new HashMap<>(); 20 | private java.util.Map.Entry zoneClicked; 21 | 22 | private int origX; 23 | private int origY; 24 | 25 | @Override 26 | public void render(Map map, MapRenderer renderer, BufferedImage image, Graphics2D g, 27 | java.util.Map tileCache, 28 | BufferedImage tiles, BufferedImage ground) { 29 | for (java.util.Map.Entry values : map.getMap("Zones").entrySet()) { 30 | String value = ((String) values.getValue()).trim(); 31 | if (!value.contains("_")) { 32 | continue; 33 | } 34 | String[] args = value.split("_"); 35 | int x1 = Integer.parseInt(args[0]); 36 | int y1 = Integer.parseInt(args[1]); 37 | int x2 = Integer.parseInt(args[2]); 38 | int y2 = Integer.parseInt(args[3]); 39 | 40 | Random r = new Random(); 41 | if (!zoneColorMappings.containsKey(values.getKey())) { 42 | zoneColorMappings.put(values.getKey(), 43 | new Color(r.nextFloat(), r.nextFloat(), r.nextFloat(), 0.3f)); 44 | } 45 | Color color = zoneColorMappings.get(values.getKey()); 46 | zoneColorMappings.put(values.getKey(), color); 47 | g.setColor(color); 48 | g.fillRect(x1, y1, x2 - x1, y2 - y1); 49 | 50 | g.setColor(Color.RED); 51 | g.drawRect(x1, y1, x2 - x1, y2 - y1); 52 | 53 | // Draw little dragging spots 54 | if (renderer.zoneEditing) { 55 | g.setColor(Color.WHITE); 56 | g.fillRect(x1, y1, margin, margin); // Top left 57 | g.fillRect(x2 - margin, y1, margin, margin); // Top right 58 | g.fillRect(x2 - margin, y2 - margin, margin, margin); // Bottom right 59 | g.fillRect(x1, y2 - margin, margin, margin); // Bottom left 60 | g.setColor(Color.BLACK); 61 | g.drawRect(x1, y1, margin, margin); // Top left 62 | g.drawRect(x2 - margin, y1, margin, margin); // Top right 63 | g.drawRect(x2 - margin, y2 - margin, margin, margin); // Bottom right 64 | g.drawRect(x1, y2 - margin, margin, margin); // Bottom left 65 | } 66 | 67 | g.setColor(Color.WHITE); 68 | g.drawString(values.getKey(), x1 + 12, y1 + 12); 69 | } 70 | } 71 | 72 | @Override 73 | public String getLayerName() { 74 | return "Zones"; 75 | } 76 | 77 | @Override 78 | public boolean isTransparent() { 79 | return true; 80 | } 81 | 82 | public void mouseDragged(Map map, int x, int y) { 83 | // Update the zone accordingly 84 | if (zoneClicked != null) { 85 | // Move the point around 86 | 87 | // Build arguments 88 | String[] args = zoneClicked.getValue().toString().trim().split("_"); 89 | int zoneX1 = Integer.parseInt(args[0]); 90 | int zoneY1 = Integer.parseInt(args[1]); 91 | int zoneX2 = Integer.parseInt(args[2]); 92 | int zoneY2 = Integer.parseInt(args[3]); 93 | 94 | // Update the actual values 95 | int diffX = x - origX; 96 | int diffY = y - origY; 97 | 98 | int margin = 12; 99 | // Check if it is in a corner 100 | if (origX < zoneX1 + margin 101 | && origY < zoneY1 + margin) { // Top left corner 102 | zoneX1 += diffX; 103 | zoneY1 += diffY; 104 | } else if (origX > zoneX2 - margin 105 | && origY < zoneY1 + margin) { // Top right corner 106 | zoneX2 += diffX; 107 | zoneY1 += diffY; 108 | } else if (origX > zoneX2 - margin 109 | && origY > zoneY2 - margin) { // Bottom right corner 110 | zoneX2 += diffX; 111 | zoneY2 += diffY; 112 | } else if (origX < zoneX1 + margin 113 | && origY > zoneY2 - margin) { // Bottom left corner 114 | zoneX1 += diffX; 115 | zoneY2 += diffY; 116 | } else { 117 | zoneX1 += diffX; 118 | zoneX2 += diffX; 119 | zoneY1 += diffY; 120 | zoneY2 += diffY; 121 | } 122 | 123 | origX = x; 124 | origY = y; 125 | 126 | // Update it 127 | String builtArg = zoneX1 + "_" + zoneY1 + "_" + zoneX2 + "_" + zoneY2; 128 | zoneClicked.setValue(builtArg); 129 | } 130 | 131 | } 132 | 133 | public void mouseDown(Map map, int x, int y) { 134 | 135 | origX = x; 136 | origY = y; 137 | 138 | int minZoneSize = Integer.MAX_VALUE; 139 | 140 | // Get the zone this refers to 141 | for (java.util.Map.Entry values : map.getMap("Zones").entrySet()) { 142 | String[] args = values.getValue().toString().trim().split("_"); 143 | if (args.length != 4) { 144 | continue; 145 | } 146 | 147 | int zoneX1 = Integer.parseInt(args[0]); 148 | int zoneY1 = Integer.parseInt(args[1]); 149 | int zoneX2 = Integer.parseInt(args[2]); 150 | int zoneY2 = Integer.parseInt(args[3]); 151 | 152 | int width = zoneX2 - zoneX1; 153 | int height = zoneY2 - zoneY1; 154 | int size = width * height; 155 | 156 | if (minZoneSize > size && x > zoneX1 && x < zoneX2 157 | && y > zoneY1 && y < zoneY2) { 158 | minZoneSize = size; 159 | zoneClicked = values; 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/utils/BlowfishCompatEncryption.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.utils; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.spec.SecretKeySpec; 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | /** 9 | * Decrypts Escapists map and resource files, using their default key. 10 | *
11 | *
12 | * Solution idea from - http://stackoverflow.com/questions/11422497/whats-the-difference-between-blowfish-and-blowfish-compat 13 | *
14 | * Removing the null bytes at the end of the decrypted content is fine since we are dealing with strings 15 | * http://stackoverflow.com/questions/5672012/mcrypt-encrypt-adding-s-bunch-of-00-to-end-of-string 16 | * 17 | * @author Zirow (code) - Major kudos! 18 | **/ 19 | public final class BlowfishCompatEncryption { 20 | private BlowfishCompatEncryption() {} 21 | 22 | /** 23 | * The default encryption key used by the Escapists. 24 | * 25 | * This is a magic value! 26 | */ 27 | private static final String ENCRYPTION_KEY = "mothking"; 28 | 29 | /** 30 | * Decrypts a Blowfish encrypted file using the built-in key. 31 | * 32 | * @param t The file to read and decrypt 33 | * @return The byte contents of this file decrypted 34 | **/ 35 | public static byte[] decrypt(File t) throws IOException { 36 | // get the data 37 | byte[] data = IOUtils.toByteArray(t); 38 | 39 | return decryptBytes(data, ENCRYPTION_KEY.getBytes()); 40 | } 41 | 42 | /** 43 | * Decrypts a Blowfish encrypted array using the built-in key. 44 | * 45 | * @param t The file to read and decrypt 46 | * @return The byte contents of this file decrypted 47 | **/ 48 | public static byte[] decrypt(byte[] t) throws IOException { 49 | return decryptBytes(t, ENCRYPTION_KEY.getBytes()); 50 | } 51 | 52 | /** 53 | * Decrypts the specified byte array encrypted via Blowfish using 54 | * the specified key. 55 | * 56 | * @param data The data to decrypt 57 | * @param key The key to use for decryption 58 | * @return The decrypted bytes 59 | **/ 60 | private static byte[] decryptBytes(byte[] data, byte[] key) throws IOException { 61 | try { 62 | SecretKeySpec skeySpec = new SecretKeySpec(key, "Blowfish"); 63 | Cipher cipher; 64 | 65 | // get the cipher 66 | String cipherInstName = "Blowfish/ECB/NoPadding"; 67 | cipher = Cipher.getInstance(cipherInstName); 68 | cipher.init(Cipher.DECRYPT_MODE, skeySpec); 69 | 70 | // reverse 71 | inplaceReverse(data); 72 | 73 | // decrypt 74 | byte[] decrypted = cipher.doFinal(data); 75 | inplaceReverse(decrypted); 76 | 77 | // Remove all null bytes at the end until a character is found 78 | ///* 79 | int end = decrypted.length - 1; 80 | 81 | while (decrypted[end] == 0) 82 | end--; 83 | 84 | byte[] result = new byte[end + 1]; 85 | 86 | System.arraycopy(decrypted, 0, result, 0, end + 1); 87 | 88 | decrypted = result; 89 | //*/ 90 | return decrypted; 91 | } catch (Exception e) { 92 | // Generic Exception handler to make sure things don't break upstream. 93 | throw new IOException(e); 94 | } 95 | } 96 | 97 | /** 98 | * Encrypts a Blowfish encrypted file using the built-in key. 99 | * 100 | * @param t The file to read and encrypt 101 | * @return The byte contents of this file encrypted 102 | **/ 103 | public static byte[] encrypt(File t) throws IOException { 104 | // get the data 105 | byte[] data = IOUtils.toByteArray(t); 106 | 107 | return encryptBytes(data, ENCRYPTION_KEY.getBytes()); 108 | } 109 | 110 | /** 111 | * Encrypts a Blowfish encrypted array using the built-in key. 112 | * 113 | * @param t The file to read and encrypt 114 | * @return The byte contents of this file encrypted 115 | **/ 116 | public static byte[] encrypt(byte[] t) throws IOException { 117 | return encryptBytes(t, ENCRYPTION_KEY.getBytes()); 118 | } 119 | 120 | 121 | /** 122 | * Encrypted the specified byte array via Blowfish using 123 | * the specified key. 124 | * 125 | * @param data The data to encrypt 126 | * @param key The key to use for encryption 127 | * @return The encrypted bytes 128 | **/ 129 | private static byte[] encryptBytes(byte[] data, byte[] key) throws IOException { 130 | try { 131 | ///* 132 | if (data.length % 8 > 0) { 133 | // we need to add padding nulls 134 | int left = 8 - (data.length % 8); 135 | 136 | byte[] newData = new byte[data.length + left]; 137 | 138 | System.arraycopy(data, 0, newData, 0, data.length); 139 | // no need to put 0's, it's byte's default value 140 | data = newData; 141 | } 142 | //*/ 143 | 144 | SecretKeySpec skeySpec = new SecretKeySpec(key, "Blowfish"); 145 | Cipher cipher; 146 | 147 | // get the cipher 148 | // shouldn't i need to do some kind of padding here? 149 | String cipherInstName = "Blowfish/ECB/NoPadding"; 150 | cipher = Cipher.getInstance(cipherInstName); 151 | cipher.init(Cipher.ENCRYPT_MODE, skeySpec); 152 | 153 | inplaceReverse(data); 154 | // decrypt 155 | byte[] encrypted = cipher.doFinal(data); 156 | inplaceReverse(encrypted); 157 | //inplaceReverse(data); 158 | return encrypted; 159 | 160 | } catch (Exception e) { 161 | // Generic Exception handler to make sure things don't break upstream. 162 | throw new IOException(e); 163 | } 164 | } 165 | 166 | /** 167 | * Swaps around bytes in the specified array to remain compatible with 168 | * Blowfish-Compat encryption. 169 | * 170 | * @param data The data to reverse. 171 | */ 172 | private static void inplaceReverse(byte[] data) { 173 | byte a0, a1, a2, a3; 174 | for (int i = 0; i < data.length; i += 4) { 175 | a0 = data[i]; 176 | a1 = data[i + 1]; 177 | a2 = data[i + 2]; 178 | a3 = data[i + 3]; 179 | 180 | data[i] = a3; 181 | data[i + 1] = a2; 182 | data[i + 2] = a1; 183 | data[i + 3] = a0; 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/elements/MapSelectionGUI.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.elements; 2 | 3 | import net.jselby.escapists.editor.EscapistsEditor; 4 | 5 | import javax.imageio.ImageIO; 6 | import javax.swing.*; 7 | import javax.swing.filechooser.FileFilter; 8 | import java.awt.*; 9 | import java.awt.event.ActionEvent; 10 | import java.awt.event.ActionListener; 11 | import java.awt.event.WindowAdapter; 12 | import java.awt.event.WindowEvent; 13 | import java.io.File; 14 | import java.io.IOException; 15 | 16 | /** 17 | * A simple map selection GUI 18 | */ 19 | public class MapSelectionGUI extends JFrame { 20 | private RenderView oldView; 21 | 22 | public MapSelectionGUI(final EscapistsEditor editor) throws IOException { 23 | String[] maps = editor.getMaps(); 24 | setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS)); 25 | 26 | add(Box.createVerticalStrut(20)); 27 | final JLabel descriptor = new JLabel("Select a map:"); 28 | descriptor.setAlignmentX(CENTER_ALIGNMENT); 29 | add(descriptor); 30 | 31 | add(Box.createVerticalStrut(20)); 32 | 33 | final JComboBox mapsSelector = new JComboBox(maps); 34 | mapsSelector.setAlignmentX(CENTER_ALIGNMENT); 35 | mapsSelector.setMaximumSize(new Dimension(150, 30)); 36 | add(mapsSelector); 37 | 38 | add(Box.createVerticalStrut(20)); 39 | 40 | // Allow for custom maps 41 | final JButton custom = new JButton("Other..."); 42 | custom.setAlignmentX(CENTER_ALIGNMENT); 43 | custom.setMaximumSize(new Dimension(150, 30)); 44 | add(custom); 45 | 46 | final JButton go = new JButton("Go!"); 47 | go.setAlignmentX(CENTER_ALIGNMENT); 48 | go.setMaximumSize(new Dimension(150, 30)); 49 | add(go); 50 | 51 | // Button actions 52 | custom.addActionListener(new ActionListener() { 53 | @Override 54 | public void actionPerformed(ActionEvent e) { 55 | 56 | final JFileChooser fc = new JFileChooser() { 57 | @Override 58 | public void approveSelection() { 59 | if (getSelectedFile().isFile()) { 60 | super.approveSelection(); 61 | } 62 | } 63 | };; 64 | fc.addChoosableFileFilter(new FileFilter() { 65 | @Override 66 | public boolean accept(File f) { 67 | return f.getName().toLowerCase().endsWith(".map") || f.isDirectory() 68 | || f.getName().toLowerCase().endsWith(".pmap") 69 | || f.getName().toLowerCase().endsWith(".cmap"); 70 | } 71 | 72 | @Override 73 | public String getDescription() { 74 | return ".map, .pmap or .cmap"; 75 | } 76 | }); 77 | FileFilter filter = new FileFilter() { 78 | @Override 79 | public boolean accept(File f) { 80 | return f.getName().toLowerCase().endsWith(".proj") || f.isDirectory(); 81 | } 82 | 83 | @Override 84 | public String getDescription() { 85 | return ".proj Project"; 86 | } 87 | }; 88 | fc.addChoosableFileFilter(filter); 89 | fc.setFileFilter(filter); 90 | int result = fc.showDialog(MapSelectionGUI.this, "Load"); 91 | if (result == JFileChooser.APPROVE_OPTION) { 92 | mapsSelector.setEditable(false); 93 | descriptor.setText("Loading..."); 94 | custom.setEnabled(false); 95 | go.setEnabled(false); 96 | 97 | SwingUtilities.invokeLater(new Runnable() { 98 | @Override 99 | public void run() { 100 | try { 101 | editor.edit(fc.getSelectedFile().getAbsolutePath()); 102 | } catch (Exception e1) { 103 | JOptionPane. 104 | showConfirmDialog(MapSelectionGUI.this, 105 | "Error: " + e1.getLocalizedMessage(), 106 | "Escapists Map Editor", 107 | JOptionPane.OK_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE); 108 | e1.printStackTrace(); 109 | System.exit(1); 110 | } 111 | 112 | dispose(); 113 | } 114 | }); 115 | } 116 | } 117 | }); 118 | 119 | go.addActionListener(new ActionListener() { 120 | @Override 121 | public void actionPerformed(ActionEvent e) { 122 | mapsSelector.setEditable(false); 123 | descriptor.setText("Loading..."); 124 | custom.setEnabled(false); 125 | go.setEnabled(false); 126 | 127 | SwingUtilities.invokeLater(new Runnable() { 128 | @Override 129 | public void run() { 130 | try { 131 | editor.edit((String) mapsSelector.getSelectedItem()); 132 | } catch (IOException e1) { 133 | e1.printStackTrace(); 134 | } 135 | 136 | dispose(); 137 | } 138 | }); 139 | } 140 | }); 141 | 142 | setSize(200, 200); 143 | setResizable(false); 144 | 145 | addWindowListener(new WindowAdapter() { 146 | @Override 147 | public void windowClosing(WindowEvent e) { 148 | if (oldView != null) { 149 | oldView.setEnabled(true); 150 | } 151 | } 152 | }); 153 | 154 | setLocationRelativeTo(null); 155 | setTitle("Load..."); 156 | setIconImage(ImageIO.read(getClass().getResource("/icon.png"))); 157 | try { 158 | UIManager.setLookAndFeel( 159 | UIManager.getSystemLookAndFeelClassName()); 160 | } catch (ClassNotFoundException | 161 | InstantiationException | 162 | IllegalAccessException | 163 | UnsupportedLookAndFeelException e) { 164 | e.printStackTrace(); 165 | } 166 | setVisible(true); 167 | } 168 | 169 | public void setOldView(RenderView oldView) { 170 | this.oldView = oldView; 171 | } 172 | 173 | public RenderView getOldView() { 174 | return oldView; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/elements/MapRendererComponent.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.elements; 2 | 3 | import javafx.application.Platform; 4 | import javafx.beans.value.ChangeListener; 5 | import javafx.beans.value.ObservableValue; 6 | import javafx.concurrent.Worker; 7 | import javafx.embed.swing.JFXPanel; 8 | import javafx.scene.Scene; 9 | import javafx.scene.web.WebEngine; 10 | import javafx.scene.web.WebView; 11 | import net.jselby.escapists.editor.mapping.Map; 12 | import net.jselby.escapists.editor.mapping.MapRenderer; 13 | import org.w3c.dom.Document; 14 | import org.w3c.dom.Node; 15 | import org.w3c.dom.NodeList; 16 | import org.w3c.dom.events.Event; 17 | import org.w3c.dom.events.EventTarget; 18 | import org.w3c.dom.html.HTMLAnchorElement; 19 | 20 | import javax.swing.*; 21 | import java.awt.*; 22 | import java.awt.event.*; 23 | import java.awt.image.BufferedImage; 24 | import java.net.URL; 25 | 26 | /** 27 | * A custom component which renders maps in the map editor. 28 | * 29 | * @author j_selby 30 | */ 31 | public class MapRendererComponent extends JPanel { 32 | private final JFXPanel panel; 33 | private float origWidth; 34 | private float origHeight; 35 | 36 | private Map mapToEdit; 37 | 38 | private BufferedImage render; 39 | 40 | private float zoomFactor = 1.0f; 41 | private String view = "World"; 42 | private MapRenderer renderer; 43 | 44 | public MapRendererComponent(Map map, MouseListener clickListener, MouseMotionListener motionListener) { 45 | this.mapToEdit = map; 46 | 47 | addMouseListener(clickListener); 48 | addMouseMotionListener(motionListener); 49 | 50 | // Build the renderer 51 | renderer = new MapRenderer(); 52 | 53 | addMouseListener(new MouseAdapter() { 54 | @Override 55 | public void mousePressed(MouseEvent e) { 56 | if (renderer.showZones && renderer.zoneEditing) { 57 | renderer.getZoning().mouseDown(mapToEdit, (int) ((float) e.getX() / (float) zoomFactor), 58 | (int) ((float) e.getY() / (float) zoomFactor)); 59 | refresh(); 60 | } 61 | } 62 | }); 63 | 64 | addMouseMotionListener(new MouseAdapter() { 65 | @Override 66 | public void mouseDragged(MouseEvent e) { 67 | if (renderer.showZones && renderer.zoneEditing) { 68 | renderer.getZoning().mouseDragged(mapToEdit, (int) ((float) e.getX() / (float) zoomFactor), 69 | (int) ((float) e.getY() / (float) zoomFactor)); 70 | refresh(); 71 | } 72 | } 73 | }); 74 | 75 | panel = new JFXPanel(); 76 | panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); 77 | 78 | setLayout(new BorderLayout()); 79 | setMap(map); 80 | 81 | addComponentListener(new ComponentAdapter() { 82 | @Override 83 | public void componentResized(ComponentEvent e) { 84 | double oldWidth = panel.getSize().getWidth(); 85 | double oldHeight = panel.getSize().getWidth(); 86 | double newWidth = getSize().getWidth(); 87 | double newHeight = getSize().getWidth(); 88 | if (oldWidth != newWidth || newHeight != oldHeight) { 89 | panel.setSize(getSize()); 90 | } 91 | } 92 | }); 93 | 94 | Platform.runLater(new Runnable() { 95 | @Override 96 | public void run() { 97 | initFX(panel); 98 | } 99 | }); 100 | } 101 | 102 | @Override 103 | protected void paintComponent(Graphics g) { 104 | Graphics2D graphics2D = (Graphics2D) g; 105 | 106 | if (render != null) { 107 | graphics2D.scale(zoomFactor, zoomFactor); 108 | g.drawImage(render, 0, 0, null); 109 | } 110 | 111 | Dimension size = new Dimension((int) (origWidth * zoomFactor),(int) (origHeight * zoomFactor)); 112 | setSize(size); 113 | setPreferredSize(size); 114 | setMaximumSize(size); 115 | setMinimumSize(size); 116 | 117 | // Re-Layout the panel 118 | validate(); 119 | } 120 | 121 | public void refresh() { 122 | // Render a snapshot 123 | if (mapToEdit != null) { 124 | render = renderer.render(mapToEdit, view); 125 | } 126 | 127 | setIgnoreRepaint(false); 128 | repaint(); 129 | } 130 | 131 | public void setZoomFactor(float newZoom) { 132 | this.zoomFactor = newZoom; 133 | refresh(); 134 | } 135 | 136 | public float getZoomFactor() { 137 | return zoomFactor; 138 | } 139 | 140 | public void setShowZones(boolean showZones) { 141 | renderer.showZones = showZones; 142 | } 143 | 144 | public void setView(String view) { 145 | this.view = view; 146 | } 147 | 148 | public String getView() { 149 | return view; 150 | } 151 | 152 | public void setEditZones(boolean editZones) { 153 | renderer.zoneEditing = editZones; 154 | } 155 | 156 | public void setMap(Map map) { 157 | 158 | boolean zoneEditing = renderer.zoneEditing; 159 | boolean showZones = renderer.showZones; 160 | renderer = new MapRenderer(); 161 | renderer.zoneEditing = zoneEditing; 162 | renderer.showZones = showZones; 163 | this.mapToEdit = map; 164 | 165 | if (mapToEdit != null) { 166 | removeAll(); 167 | 168 | Dimension size = new Dimension((map.getHeight() - 1) * 16, (map.getWidth() - 3) * 16); 169 | setSize(size); 170 | setPreferredSize(size); 171 | setMaximumSize(size); 172 | setMinimumSize(size); 173 | 174 | // These are inverted, don't worry. 175 | origWidth = (map.getHeight() - 1) * 16; 176 | origHeight = (map.getWidth() - 3) * 16; 177 | } else { 178 | origHeight = 500; 179 | origWidth = 734; 180 | 181 | add(panel, BorderLayout.NORTH); 182 | } 183 | 184 | refresh(); 185 | } 186 | 187 | private static void initFX(final JFXPanel fxPanel) { 188 | final WebView webView = new WebView(); 189 | fxPanel.setScene(new Scene(webView)); 190 | 191 | // Obtain the webEngine to navigate 192 | final WebEngine webEngine = webView.getEngine(); 193 | webEngine.loadContent("Escapists Map Editor\n" + 194 | "Written by jselby\nhttp://redd.it/2wacp2\n\n" + 195 | "You don't have a map loaded currently - \nGo to File in the top left, and press a button there!\n" + 196 | "Loading..."); 197 | webEngine.load("http://escapists.jselby.net/welcome/"); 198 | 199 | webView.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener() { 200 | @Override 201 | public void changed(ObservableValue observable, 202 | Worker.State oldValue, Worker.State newValue) { 203 | if (newValue == Worker.State.SUCCEEDED) { 204 | final Document document = webEngine.getDocument(); 205 | NodeList nodeList = document.getElementsByTagName("a"); 206 | for (int i = 0; i < nodeList.getLength(); i++) { 207 | Node node = nodeList.item(i); 208 | EventTarget eventTarget = (EventTarget) node; 209 | eventTarget.addEventListener("click", new org.w3c.dom.events.EventListener() { 210 | @Override 211 | public void handleEvent(Event evt) { 212 | EventTarget target = evt.getCurrentTarget(); 213 | HTMLAnchorElement anchorElement = (HTMLAnchorElement) target; 214 | String href = anchorElement.getHref(); 215 | Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; 216 | if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { 217 | try { 218 | desktop.browse(new URL(href).toURI()); 219 | } catch (Exception e) { 220 | e.printStackTrace(); 221 | } 222 | } 223 | evt.preventDefault(); 224 | } 225 | }, false); 226 | } 227 | } 228 | } 229 | }); 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/elements/PropertiesDialog.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.elements; 2 | 3 | import net.jselby.escapists.editor.mapping.Map; 4 | import net.jselby.escapists.editor.utils.StringUtils; 5 | 6 | import javax.swing.*; 7 | import javax.swing.event.DocumentEvent; 8 | import javax.swing.event.DocumentListener; 9 | import javax.swing.text.AbstractDocument; 10 | import javax.swing.text.AttributeSet; 11 | import javax.swing.text.BadLocationException; 12 | import javax.swing.text.DocumentFilter; 13 | import java.awt.*; 14 | import java.awt.event.ActionEvent; 15 | import java.awt.event.ActionListener; 16 | import java.util.ArrayList; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | /** 21 | * A simple dialog for showing properties. 22 | */ 23 | public class PropertiesDialog extends JDialog { 24 | private final MapRendererComponent renderer; 25 | private final Map mapToEdit; 26 | 27 | public PropertiesDialog(RenderView parent, MapRendererComponent renderer, final Map mapToEdit) { 28 | super(parent, "Map Properties", true); 29 | 30 | this.renderer = renderer; 31 | this.mapToEdit = mapToEdit; 32 | 33 | // TODO: Add dropdown menus for some of these options 34 | // Build fields 35 | ArrayList fieldsToEdit = new ArrayList<>(); 36 | fieldsToEdit.add(stringField(mapToEdit, "Prison Name", "Info.MapName")); 37 | fieldsToEdit.add(numberField(mapToEdit, "Inmates (1 - around 20)", "Info.Inmates")); 38 | fieldsToEdit.add(numberField(mapToEdit, "Guards (1- around 20)", "Info.Guards")); 39 | fieldsToEdit.add(numberField(mapToEdit, "NPC Level (1-3 known)", "Info.NPClvl")); 40 | fieldsToEdit.add(stringField(mapToEdit, "Music", "Info.Music")); 41 | fieldsToEdit.add(stringField(mapToEdit, "Fight Frequency", "Info.FightFreq")); 42 | fieldsToEdit.add(stringField(mapToEdit, "Tileset", "Info.Tileset")); 43 | fieldsToEdit.add(stringField(mapToEdit, "Warden name", "Info.Warden")); 44 | fieldsToEdit.add(textBoxField(mapToEdit, "Intro text", "Info.Intro")); 45 | 46 | // Jobs 47 | fieldsToEdit.add(stringField(mapToEdit, "Starting Job (first letter CAPITAL)", "Jobs.StartingJob")); 48 | fieldsToEdit.add(booleanField(mapToEdit, "Metalshop Job Enabled", "Jobs.Metalshop")); 49 | fieldsToEdit.add(booleanField(mapToEdit, "Mailman Job Enabled", "Jobs.Mailman")); 50 | fieldsToEdit.add(booleanField(mapToEdit, "Kitchen Job Enabled", "Jobs.Kitchen")); 51 | fieldsToEdit.add(booleanField(mapToEdit, "Gardening Job Enabled", "Jobs.Gardening")); 52 | fieldsToEdit.add(booleanField(mapToEdit, "Janitor Job Enabled", "Jobs.Janitor")); 53 | fieldsToEdit.add(booleanField(mapToEdit, "Woodshop Job Enabled", "Jobs.Woodshop")); 54 | fieldsToEdit.add(booleanField(mapToEdit, "Library Job Enabled", "Jobs.Library")); 55 | fieldsToEdit.add(booleanField(mapToEdit, "Tailor Job Enabled", "Jobs.Tailor")); 56 | fieldsToEdit.add(booleanField(mapToEdit, "Deliveries Job Enabled", "Jobs.Deliveries")); 57 | fieldsToEdit.add(booleanField(mapToEdit, "Laundry Job Enabled", "Jobs.Laundry")); 58 | 59 | // Experimental 60 | fieldsToEdit.add(new Component[]{Box.createVerticalStrut(50), Box.createHorizontalStrut(1)}); 61 | JLabel experimental = new JLabel("The following are unsupported "); 62 | experimental.setHorizontalAlignment(SwingConstants.RIGHT); 63 | JLabel experimental2 = new JLabel("and likely won't work yet."); 64 | experimental.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); 65 | fieldsToEdit.add(new Component[]{experimental, experimental2}); 66 | 67 | fieldsToEdit.add(stringField(mapToEdit, "Map Type", "Info.MapType")); 68 | fieldsToEdit.add(stringField(mapToEdit, "Routine Set", "Info.RoutineSet")); 69 | fieldsToEdit.add(textBoxField(mapToEdit, "Hint (Part 1)", "Info.Hint1")); 70 | fieldsToEdit.add(textBoxField(mapToEdit, "Hint (Part 2)", "Info.Hint2")); 71 | fieldsToEdit.add(textBoxField(mapToEdit, "Hint (Part 3)", "Info.Hint3")); 72 | 73 | // Build GUI 74 | JPanel scrollableContent = new JPanel(); 75 | scrollableContent.setLayout(new GridBagLayout()); 76 | GridBagConstraints c = new GridBagConstraints(); 77 | scrollableContent.add(Box.createVerticalStrut(10), c); 78 | 79 | // Build fields 80 | int y = 1; 81 | c.ipady = 10; 82 | for (Component[] components : fieldsToEdit) { 83 | if (components.length == 0) { 84 | continue; 85 | } 86 | 87 | c.gridy = y; 88 | c.gridx = 0; 89 | c.weightx = 0.3; 90 | c.fill = GridBagConstraints.NONE; 91 | c.fill = GridBagConstraints.HORIZONTAL; 92 | 93 | if (components[0] instanceof JComponent) { 94 | ((JComponent)components[0]).setAlignmentX(LEFT_ALIGNMENT); 95 | } 96 | if (components[1] instanceof JComponent) { 97 | ((JComponent)components[1]).setAlignmentX(LEFT_ALIGNMENT); 98 | } 99 | 100 | scrollableContent.add(components[0], c); 101 | c.gridx = 1; 102 | c.fill = GridBagConstraints.HORIZONTAL; 103 | c.weightx = 0.7; 104 | scrollableContent.add(components[1], c); 105 | y++; 106 | } 107 | c.fill = GridBagConstraints.NONE; 108 | c.ipady = 0; 109 | c.weightx = 0; 110 | 111 | c.gridy = y; 112 | 113 | scrollableContent.add(Box.createVerticalStrut(10), c); 114 | 115 | JScrollPane scrollableContainer = new JScrollPane(scrollableContent); 116 | scrollableContainer.getVerticalScrollBar().setUnitIncrement(16); 117 | 118 | add(scrollableContainer); 119 | 120 | setSize(new Dimension(640, 480)); 121 | setResizable(false); 122 | setLocationRelativeTo(null); 123 | setVisible(true); 124 | } 125 | 126 | private static JComponent[] numberField(final Map mapToEdit, String name, final String valueSrc) { 127 | String startingContent = (String) mapToEdit.get(valueSrc); 128 | startingContent = startingContent.trim(); 129 | 130 | JLabel label = new JLabel(name); 131 | 132 | final JTextField contentField = new JTextField(startingContent); 133 | label.setLabelFor(contentField); 134 | label.setHorizontalTextPosition(SwingConstants.LEFT); 135 | label.setBorder(BorderFactory.createEmptyBorder(10, 50, 0, 0)); 136 | 137 | contentField.getDocument().addDocumentListener(new DocumentListener() { 138 | @Override 139 | public void insertUpdate(DocumentEvent e) { 140 | documentEvent(e); 141 | } 142 | 143 | @Override 144 | public void removeUpdate(DocumentEvent e) { 145 | documentEvent(e); 146 | } 147 | 148 | @Override 149 | public void changedUpdate(DocumentEvent e) { 150 | documentEvent(e); 151 | } 152 | 153 | public void documentEvent(DocumentEvent evt) { 154 | mapToEdit.set(valueSrc, contentField.getText()); 155 | } 156 | }); 157 | 158 | // Force numbers only 159 | ((AbstractDocument) contentField.getDocument()).setDocumentFilter(new DocumentFilter() { 160 | Pattern regEx = Pattern.compile("\\d+"); 161 | 162 | @Override 163 | public void replace(FilterBypass fb, int offset, int length, String text, 164 | AttributeSet attrs) throws BadLocationException { 165 | Matcher matcher = regEx.matcher(text); 166 | if (!matcher.matches()) { 167 | return; 168 | } 169 | super.replace(fb, offset, length, text, attrs); 170 | } 171 | }); 172 | 173 | // Sizing 174 | contentField.setMinimumSize(new Dimension(150, 20)); 175 | contentField.setMaximumSize(new Dimension(150, 20)); 176 | contentField.setPreferredSize(new Dimension(150, 20)); 177 | contentField.setSize(new Dimension(150, 20)); 178 | 179 | return new JComponent[]{label, contentField}; 180 | } 181 | 182 | private static JComponent[] booleanField(final Map mapToEdit, String name, final String valueSrc) { 183 | String val = (mapToEdit.get(valueSrc) + "").trim(); 184 | int startingContent = StringUtils.isNumber(val) ? Integer.parseInt(val) : 0; 185 | 186 | JLabel label = new JLabel(name); 187 | 188 | final JCheckBox box = new JCheckBox(); 189 | label.setLabelFor(box); 190 | label.setHorizontalTextPosition(SwingConstants.LEFT); 191 | label.setBorder(BorderFactory.createEmptyBorder(10, 50, 0, 0)); 192 | box.setSelected(startingContent == 1); 193 | 194 | box.addActionListener(new ActionListener() { 195 | @Override 196 | public void actionPerformed(ActionEvent e) { 197 | mapToEdit.set(valueSrc, box.isSelected() ? 1 : 0); 198 | } 199 | }); 200 | 201 | return new JComponent[]{label, box}; 202 | } 203 | 204 | private static JComponent[] textBoxField(final Map mapToEdit, String name, final String valueSrc) { 205 | String startingContent = (String) mapToEdit.get(valueSrc); 206 | if (startingContent == null) { 207 | startingContent = ""; 208 | } 209 | startingContent = startingContent.trim(); 210 | 211 | JLabel label = new JLabel(name); 212 | 213 | final JTextArea contentField = new JTextArea(startingContent); 214 | contentField.setLineWrap(true); 215 | contentField.setWrapStyleWord(true); 216 | label.setLabelFor(contentField); 217 | label.setHorizontalTextPosition(SwingConstants.LEFT); 218 | label.setBorder(BorderFactory.createEmptyBorder(10, 50, 0, 0)); 219 | 220 | contentField.getDocument().addDocumentListener(new DocumentListener() { 221 | @Override 222 | public void insertUpdate(DocumentEvent e) { 223 | documentEvent(e); 224 | } 225 | 226 | @Override 227 | public void removeUpdate(DocumentEvent e) { 228 | documentEvent(e); 229 | } 230 | 231 | @Override 232 | public void changedUpdate(DocumentEvent e) { 233 | documentEvent(e); 234 | } 235 | 236 | public void documentEvent(DocumentEvent evt) { 237 | mapToEdit.set(valueSrc, contentField.getText()); 238 | } 239 | }); 240 | 241 | // Sizing 242 | 243 | JScrollPane scrollPane = new JScrollPane(contentField); 244 | scrollPane.setMinimumSize(new Dimension(150, 50)); 245 | scrollPane.setMaximumSize(new Dimension(150, 50)); 246 | scrollPane.setPreferredSize(new Dimension(150, 50)); 247 | scrollPane.setSize(new Dimension(150, 50)); 248 | 249 | JPanel northOnlyPanel = new JPanel(); 250 | northOnlyPanel.setLayout(new BorderLayout()); 251 | northOnlyPanel.add(scrollPane, BorderLayout.NORTH); 252 | return new JComponent[]{label, northOnlyPanel}; 253 | } 254 | 255 | private static JComponent[] stringField(final Map mapToEdit, String name, final String valueSrc) { 256 | if (name == null || valueSrc == null) { 257 | return new JComponent[0]; 258 | } 259 | 260 | String startingContent = (String) mapToEdit.get(valueSrc); 261 | if (startingContent == null) { 262 | startingContent = ""; 263 | } 264 | startingContent = startingContent.trim(); 265 | 266 | JLabel label = new JLabel(name); 267 | 268 | final JTextField contentField = new JTextField(startingContent); 269 | label.setLabelFor(contentField); 270 | label.setHorizontalTextPosition(SwingConstants.LEFT); 271 | label.setBorder(BorderFactory.createEmptyBorder(10, 50, 0, 0)); 272 | 273 | contentField.getDocument().addDocumentListener(new DocumentListener() { 274 | @Override 275 | public void insertUpdate(DocumentEvent e) { 276 | documentEvent(e); 277 | } 278 | 279 | @Override 280 | public void removeUpdate(DocumentEvent e) { 281 | documentEvent(e); 282 | } 283 | 284 | @Override 285 | public void changedUpdate(DocumentEvent e) { 286 | documentEvent(e); 287 | } 288 | 289 | public void documentEvent(DocumentEvent evt) { 290 | mapToEdit.set(valueSrc, contentField.getText()); 291 | } 292 | }); 293 | 294 | // Sizing 295 | contentField.setMinimumSize(new Dimension(150, 20)); 296 | contentField.setMaximumSize(new Dimension(150, 20)); 297 | contentField.setPreferredSize(new Dimension(150, 20)); 298 | contentField.setSize(new Dimension(150, 20)); 299 | 300 | return new JComponent[]{label, contentField}; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/EscapistsEditor.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor; 2 | 3 | import net.jselby.escapists.editor.elements.RenderView; 4 | import net.jselby.escapists.editor.mapping.Map; 5 | import net.jselby.escapists.editor.mapping.MapRenderer; 6 | import net.jselby.escapists.editor.objects.ObjectRegistry; 7 | import net.jselby.escapists.editor.utils.BlowfishCompatEncryption; 8 | import net.jselby.escapists.editor.utils.IOUtils; 9 | import net.jselby.escapists.editor.utils.logging.LoggingDebugPrintStream; 10 | import net.jselby.escapists.editor.utils.SteamFinder; 11 | import net.jselby.escapists.editor.utils.logging.Rollbar; 12 | 13 | import javax.imageio.ImageIO; 14 | import javax.swing.*; 15 | import java.awt.image.BufferedImage; 16 | import java.io.*; 17 | import java.net.URL; 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * The main entry point for the EscapistsEditor. 22 | * 23 | * @author j_selbys 24 | */ 25 | public class EscapistsEditor { 26 | public static final String VERSION = "1.6.0"; 27 | public static final boolean DEBUG = true; 28 | 29 | // For embedded environments 30 | public static final boolean DRAW_TEXT = true; 31 | 32 | // -- Arguments 33 | private String decryptFile; 34 | 35 | private String encryptFile; 36 | 37 | private String encryptAndInstallFile; 38 | 39 | private String renderMap; 40 | 41 | private String editMap; 42 | 43 | private boolean showHelp; 44 | 45 | private boolean renderAll; 46 | 47 | private String escapistsPathUser; 48 | 49 | 50 | // -- Internal vars 51 | /** 52 | * The root directory of Escapists 53 | */ 54 | public File escapistsPath; 55 | 56 | /** 57 | * Create a new default registry for objects 58 | */ 59 | public ObjectRegistry registry; 60 | public static boolean showGUI; 61 | private RenderView view; 62 | 63 | public static String updateMessage; 64 | 65 | private void start() { 66 | System.out.println("========================="); 67 | System.out.println("Operating system: " + System.getProperty("os.name")); 68 | System.out.println("Java version: " + System.getProperty("java.version")); 69 | 70 | registry = new ObjectRegistry("net.jselby.escapists.editor.objects"); 71 | 72 | // Discover Escapists directory 73 | if (escapistsPathUser == null) { 74 | File steamPath = SteamFinder.getSteamPath(this); 75 | 76 | if (steamPath == null) { 77 | fatalError("Failed to discover Steam installation with Escapists."); 78 | } 79 | 80 | // Check that Escapists is installed 81 | escapistsPath = steamPath; 82 | } else { 83 | escapistsPath = new File(escapistsPathUser); 84 | } 85 | if (!escapistsPath.exists()) { 86 | fatalError("Escapists is not installed @ " + escapistsPath.getPath()); 87 | } 88 | 89 | // I don't support piracy, in terms of obtaining support for such. 90 | // This hashes the steam_api.dll, checking for bad stuff there. 91 | File file = new File(escapistsPath, "steam_api.dll"); 92 | if (file.exists()) { 93 | try { 94 | String hash = IOUtils.hash(file); 95 | System.out.println("Hash: " + hash); 96 | } catch (Exception ignored) {} 97 | } else { 98 | System.out.println("Warning: No Steam API in Escapists dir!"); 99 | } 100 | 101 | System.out.println("========================="); 102 | 103 | // Parse arguments 104 | System.out.println("Discovered Escapists @ " + escapistsPath.getPath()); 105 | 106 | // Check for update 107 | Thread updateThread = new Thread(new Runnable() { 108 | @Override 109 | public void run() { 110 | if (!showGUI) { 111 | return; 112 | } 113 | try { 114 | String newVersion = IOUtils.toString(new URL("http://escapists.jselby.net/version.txt")).trim(); 115 | String message = ""; 116 | if (newVersion.contains("\n")) { 117 | message = newVersion.split("\n")[1]; 118 | newVersion = newVersion.split("\n")[0].trim(); 119 | } 120 | if (!newVersion.equalsIgnoreCase(VERSION) && newVersion.length() != 0) { 121 | updateMessage = newVersion + "\n" + message; 122 | 123 | dialog("New version found (" + newVersion + "). " + 124 | "Download it at http://escapists.jselby.net\n" + message); 125 | } 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | }); 131 | updateThread.start(); 132 | 133 | } 134 | 135 | private static void fatalError(String s) { 136 | System.err.println(s); 137 | if (showGUI) { 138 | JOptionPane.showMessageDialog(null, s); 139 | } 140 | System.exit(1); 141 | } 142 | 143 | public void dialog(String s) { 144 | System.out.println(" - Dialog: "); 145 | for (String split : s.split("\n")) { 146 | System.out.println(" > " + split); 147 | } 148 | 149 | if (showGUI) { 150 | JOptionPane.showMessageDialog(null, s); 151 | } 152 | } 153 | 154 | public String[] getMaps() { 155 | File mapsDir = new File(escapistsPath, "Data" + File.separator + "Maps"); 156 | ArrayList maps = new ArrayList<>(); 157 | for (File file : mapsDir.listFiles()) { 158 | if (file.isFile()) { 159 | maps.add(file.getName()); 160 | } 161 | } 162 | System.out.println("Listing " + maps.size() + " maps."); 163 | return maps.toArray(new String[maps.size()]); 164 | } 165 | 166 | public void dump(String name) throws IOException { 167 | File mapPath = new File(name); 168 | String fileExtension = mapPath.getName().contains(".") ? ("." + mapPath.getName().split("\\.")[1]) : ""; 169 | 170 | if (!mapPath.exists()) { 171 | mapPath = new File(escapistsPath, "Data" + File.separator + "Maps" + File.separator + name); 172 | if (!mapPath.exists()) { 173 | dialog("Map \"" + name.trim() + "\" not found."); 174 | return; 175 | } 176 | } 177 | 178 | // Decrypt it 179 | byte[] content = BlowfishCompatEncryption.decrypt(mapPath); 180 | File decryptedMap = new File(name.split("\\.")[0] + ".decrypted" + fileExtension); 181 | System.out.println("Decrypting \"" + name + " to \"" + decryptedMap.getPath() + "\"..."); 182 | IOUtils.write(decryptedMap, content); 183 | } 184 | 185 | public void render(String name) throws IOException { 186 | String rawName = new File(name).getName().split("\\.")[0]; 187 | File mapPath = new File(name); 188 | if (!mapPath.exists()) { 189 | mapPath = new File(escapistsPath, "Data" + File.separator + "Maps" + File.separator + name); 190 | if (!mapPath.exists()) { 191 | dialog("Map \"" + name.trim() + "\" not found."); 192 | return; 193 | } 194 | } 195 | 196 | // Decrypt it 197 | String content = new String(BlowfishCompatEncryption.decrypt(mapPath)); 198 | 199 | // Decode it 200 | Map map = new Map(this, registry, mapPath.getPath(), content); 201 | 202 | BufferedImage image = new MapRenderer().render(map, "World"); 203 | 204 | File parent = new File("renders"); 205 | if (!parent.exists()) { 206 | if (!parent.mkdir()) { 207 | fatalError("Failed to create output directory: \"" + parent.getPath() + "\"."); 208 | } 209 | } 210 | File outputfile = new File(parent, rawName.toLowerCase() + ".png"); 211 | ImageIO.write(image, "png", outputfile); 212 | 213 | System.out.println("Successfully rendered \"" + outputfile.getPath() + "\"."); 214 | } 215 | 216 | private void encrypt(String name, boolean install) throws IOException { 217 | File mapPath = new File(name); 218 | String fileExtension = mapPath.getName().contains(".") ? ("." + mapPath.getName().split("\\.")[1]) : ""; 219 | if (!mapPath.exists()) { 220 | mapPath = new File(escapistsPath, "Data" + File.separator + "Maps" + File.separator + name); 221 | if (!mapPath.exists()) { 222 | dialog("Map \"" + name.trim() + "\" not found."); 223 | return; 224 | } 225 | } 226 | 227 | // Decrypt it 228 | byte[] content = BlowfishCompatEncryption.encrypt(mapPath); 229 | File decryptedMap; 230 | if (install) { 231 | decryptedMap = new File(escapistsPath, "Data" + File.separator + "Maps" 232 | + File.separator + name.split("\\.")[0] + ".map"); 233 | } else { 234 | decryptedMap = new File(name.split("\\.")[0] + ".encrypted" + fileExtension); 235 | } 236 | System.out.println("Encrypting \"" + name + " to \"" + decryptedMap.getPath() + "\"..."); 237 | IOUtils.write(decryptedMap, content); 238 | } 239 | 240 | public void edit(byte[] decryptedBytes) throws IOException { 241 | String contents = new String(decryptedBytes); 242 | 243 | Map map = new Map(this, registry, "", contents); 244 | 245 | if (map.getTilesImage() == null && showGUI) { 246 | JOptionPane.showMessageDialog(null, "Failed to load resources."); 247 | System.exit(1); 248 | } 249 | 250 | if (view != null) { 251 | view.setEnabled(true); 252 | view.setMap(map); 253 | } else { 254 | view = new RenderView(this, map); 255 | } 256 | } 257 | 258 | public void edit(String name) throws IOException { 259 | File mapPath = new File(name); 260 | 261 | if (!mapPath.exists()) { 262 | mapPath = new File(escapistsPath, "Data" + File.separator + "Maps" + File.separator + name); 263 | if (!mapPath.exists()) { 264 | dialog("Map \"" + name.trim() + "\" not found."); 265 | return; 266 | } 267 | } 268 | 269 | String contents; 270 | try { 271 | contents = new String(BlowfishCompatEncryption.decrypt(mapPath)); 272 | } catch (Exception e) { 273 | contents = new String(IOUtils.toByteArray(mapPath)); 274 | } 275 | 276 | Map map = new Map(this, registry, mapPath.getPath(), contents); 277 | 278 | if (map.getTilesImage() == null && showGUI) { 279 | JOptionPane.showMessageDialog(null, "Failed to load resources."); 280 | System.exit(1); 281 | } 282 | if (view != null) { 283 | view.setEnabled(true); 284 | view.setMap(map); 285 | } else { 286 | view = new RenderView(this, map); 287 | } 288 | } 289 | 290 | public static void main(String[] args) { 291 | try { 292 | Rollbar.init(); 293 | 294 | // Redirect SysOut 295 | try { 296 | OutputStream fileOut = new FileOutputStream(new File("escapistseditor.log")); 297 | System.setOut(new LoggingDebugPrintStream(fileOut, System.out)); 298 | System.setErr(new LoggingDebugPrintStream(fileOut, System.err)); 299 | } catch (Exception e) { 300 | System.err.println("Failed to start logging."); 301 | e.printStackTrace(); 302 | } 303 | 304 | System.out.println("The Escapists Editor v" + VERSION); 305 | System.out.println("By jselby"); 306 | 307 | // Parse 308 | EscapistsEditor editor = new EscapistsEditor(); 309 | 310 | if (args.length == 0) { 311 | showGUI = true; 312 | } else if (args.length > 0) { 313 | // Check for a command 314 | int pos = 0; 315 | while(pos < args.length) { 316 | if (args[pos].startsWith("-")) { 317 | // Sure is. Get its name 318 | String name = args[pos].substring(1).toLowerCase(); 319 | while (name.startsWith("-")) { 320 | name = name.substring(1); 321 | } 322 | 323 | if (name.startsWith("help") && args.length == 1) { 324 | editor.showHelp = true; 325 | } else if (name.startsWith("render-all") && args.length == 1) { 326 | editor.renderAll = true; 327 | } else if (args.length > 1) { 328 | String val = args[pos + 1]; 329 | 330 | if (args[pos + 1].startsWith("\"")) { 331 | // Multi-space arg 332 | val = val.substring(1); 333 | for (int i = pos + 2; i < args.length; i++) { 334 | pos = i; 335 | String str = args[i]; 336 | val += " " + str; 337 | if (str.endsWith("\"")) { 338 | val = val.substring(0, val.length() - 1); 339 | break; 340 | } 341 | } 342 | } else { 343 | pos++; 344 | } 345 | 346 | // Parse 347 | if (name.equalsIgnoreCase("decrypt")) { 348 | editor.decryptFile = val; 349 | } else if (name.equalsIgnoreCase("encrypt")) { 350 | editor.encryptFile = val; 351 | } else if (name.equalsIgnoreCase("encrypt-and-install")) { 352 | editor.encryptAndInstallFile = val; 353 | } else if (name.equalsIgnoreCase("render")) { 354 | editor.renderMap = val; 355 | } else if (name.equalsIgnoreCase("edit")) { 356 | editor.editMap = val; 357 | } else if (name.equalsIgnoreCase("escapists-path")) { 358 | editor.escapistsPathUser = val; 359 | } 360 | } else { 361 | editor.showHelp = true; 362 | } 363 | } else { 364 | System.out.println(args[pos]); 365 | editor.showHelp = true; 366 | } 367 | pos++; 368 | } 369 | } 370 | 371 | if (editor.showHelp) { 372 | System.out.println("Usage:"); 373 | System.out.println(" --decrypt\tDecrypts the passed file."); 374 | System.out.println(" --encrypt\tEncrypts the passed file."); 375 | System.out.println(" --encrypt-and-install\tEncrypts and installs the passed file."); 376 | System.out.println(" --render\tRenders the passed file."); 377 | System.out.println(" --edit\tEdits the passed unencrypted map."); 378 | System.out.println(" --render-all\tRenders all maps."); 379 | System.out.println(" --escapists-path\tForces a path for The Escapists install directory."); 380 | return; 381 | } 382 | 383 | editor.start(); 384 | 385 | // Check what we need to do 386 | 387 | if (editor.decryptFile == null && 388 | editor.encryptFile == null && 389 | editor.renderMap == null && 390 | editor.encryptAndInstallFile == null && 391 | editor.editMap == null && 392 | !editor.renderAll) { 393 | // Select a map through a GUI first 394 | showGUI = true; 395 | editor.view = new RenderView(editor, null); 396 | } 397 | 398 | if (editor.decryptFile != null) { 399 | editor.dump(editor.decryptFile); 400 | } 401 | if (editor.encryptFile != null) { 402 | editor.encrypt(editor.encryptFile, false); 403 | } 404 | if (editor.encryptAndInstallFile != null) { 405 | editor.encrypt(editor.encryptAndInstallFile, true); 406 | } 407 | if (editor.renderMap != null) { 408 | editor.render(editor.renderMap); 409 | } 410 | if (editor.renderAll) { 411 | for (String map : editor.getMaps()) { 412 | editor.render(map); 413 | } 414 | } 415 | if (editor.editMap != null) { 416 | editor.edit(editor.editMap); 417 | } 418 | } catch (Exception e) { 419 | fatalError(e, Rollbar.fatal(e)); 420 | } 421 | } 422 | 423 | public static void fatalError(Exception e, Thread fatal) { 424 | String s = "Error: "; 425 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 426 | PrintStream stream = new PrintStream(out); 427 | e.printStackTrace(stream); 428 | s += out.toString(); 429 | System.err.println(s); 430 | if (showGUI) { 431 | JOptionPane.showMessageDialog(null, s); 432 | } 433 | try { 434 | fatal.join(); 435 | } catch (InterruptedException e1) { 436 | e1.printStackTrace(); 437 | } 438 | System.exit(1); 439 | } 440 | 441 | public static void fatalError(Exception e) { 442 | String string = "Error: "; 443 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 444 | PrintStream stream = new PrintStream(out); 445 | e.printStackTrace(stream); 446 | string += out.toString(); 447 | fatalError(string); 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/mapping/Map.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.mapping; 2 | 3 | import net.jselby.escapists.editor.EscapistsEditor; 4 | import net.jselby.escapists.editor.filters.PreFilters; 5 | import net.jselby.escapists.editor.mapping.store.PropertiesFile; 6 | import net.jselby.escapists.editor.mapping.store.PropertiesSection; 7 | import net.jselby.escapists.editor.objects.ObjectRegistry; 8 | import net.jselby.escapists.editor.objects.Objects; 9 | import net.jselby.escapists.editor.objects.WorldObject; 10 | import net.jselby.escapists.editor.utils.BlowfishCompatEncryption; 11 | 12 | import javax.imageio.ImageIO; 13 | import java.awt.image.BufferedImage; 14 | import java.io.ByteArrayInputStream; 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.lang.reflect.Constructor; 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * A map contains elements of a map. 25 | * 26 | * @author j_selby 27 | */ 28 | public class Map { 29 | public static final int DEFAULT_WIDTH = 96; 30 | public static final int DEFAULT_HEIGHT = 93; 31 | 32 | private int width; 33 | private int height; 34 | 35 | private PropertiesFile sections; 36 | 37 | private int[][] tiles; 38 | private int[][] vents; 39 | private int[][] roof; 40 | private int[][] underground; 41 | 42 | private List objects = new ArrayList<>(); 43 | 44 | private final BufferedImage tilesImage; 45 | private final BufferedImage backgroundImage; 46 | 47 | /** 48 | * Decodes a map. 49 | * 50 | * @param editor The editor to pull resources from 51 | * @param registry The registry to spawn objects with 52 | * @param filename The filename of this map 53 | * @param content The content of this map, decrypted. 54 | * @throws IOException 55 | */ 56 | public Map(EscapistsEditor editor, ObjectRegistry registry, 57 | String filename, String content) throws IOException { 58 | System.out.println(" - Decoding map \"" + filename + "\"..."); 59 | this.sections = new PropertiesFile(content); 60 | 61 | // Get the raw filename 62 | String rawName = new File(filename).getName().split("\\.")[0]; 63 | 64 | // Decode tiles, if it exists 65 | if (sections.containsSection("Tiles")) { 66 | // Decode! 67 | PropertiesSection section = sections.getSection("Tiles"); 68 | tiles = compileIDSection(section); 69 | 70 | } else { 71 | tiles = new int[0][0]; 72 | System.out.println(" > Map decode warning: No tiles found."); 73 | } 74 | 75 | if (sections.containsSection("Vents")) { 76 | // Decode! 77 | PropertiesSection section = sections.getSection("Vents"); 78 | vents = compileIDSection(section); 79 | } else { 80 | vents = new int[0][0]; 81 | System.out.println(" > Map decode warning: No vents found."); 82 | } 83 | 84 | if (sections.containsSection("Roof")) { 85 | // Decode! 86 | PropertiesSection section = sections.getSection("Roof"); 87 | roof = compileIDSection(section); 88 | } else { 89 | roof = new int[0][0]; 90 | System.out.println(" > Map decode warning: No roof found."); 91 | } 92 | 93 | if (sections.containsSection("Underground")) { 94 | // Decode! 95 | PropertiesSection section = sections.getSection("Underground"); 96 | underground = compileIDSection(section); 97 | } else { 98 | underground = new int[0][0]; 99 | System.out.println(" > Map decode warning: No underground found."); 100 | } 101 | 102 | 103 | // Decode Object entities 104 | if (sections.containsSection("Objects")) { 105 | for (java.util.Map.Entry object : 106 | sections.getSection("Objects").entrySet()) { 107 | // Find this object 108 | String[] args = ((String)object.getValue()).trim().split("x"); 109 | 110 | int x = Integer.parseInt(args[0]); 111 | int y = Integer.parseInt(args[1]); 112 | int id = Integer.parseInt(args[2]); 113 | int level = Integer.parseInt(args[3]); 114 | 115 | if (id == 0) { 116 | continue; 117 | } 118 | 119 | // From the Registry 120 | Class classOfObject = registry.objects.get(id); 121 | if (classOfObject != null) { 122 | try { 123 | Constructor constructor 124 | = classOfObject.getConstructor(Integer.TYPE, Integer.TYPE, Integer.TYPE); 125 | WorldObject instance = constructor.newInstance(x, y, level); 126 | objects.add(instance); 127 | } catch (InvocationTargetException 128 | | NoSuchMethodException 129 | | IllegalAccessException 130 | | InstantiationException e) { 131 | System.err.println(" > Map decode warning: Failed to create entity: " + id + " @ " 132 | + x + ":" + y + " (" + (x * 16) + ":" + (y * 16) + ")"); 133 | e.printStackTrace(); 134 | } 135 | } else { 136 | objects.add(new WorldObject.Unknown(x, y, id, level)); 137 | if (EscapistsEditor.DEBUG) { 138 | System.out.println(" > Map decode warning: Unknown entity: " + id + " @ " 139 | + x + ":" + y + " (" + (x * 16) + ":" + (y * 16) + ")"); 140 | } 141 | } 142 | } 143 | } else { 144 | System.out.println(" > Map decode warning: No objects/entities found."); 145 | } 146 | 147 | // Decode tiles for this 148 | String texName = (String) get("Info.Tileset"); 149 | File tilesFile = new File(editor.escapistsPath, "Data" + 150 | File.separator + "images" + File.separator + "tiles_" + texName + ".gif"); 151 | if (!tilesFile.exists()) { 152 | System.out.println(" > No tiles found for \"" + texName + "\". Using \"perks\"."); 153 | tilesFile = new File(editor.escapistsPath, "Data" + 154 | File.separator + "images" + File.separator + "tiles_perks.gif"); 155 | } 156 | 157 | File background = new File(editor.escapistsPath, "Data" + 158 | File.separator + "images" + File.separator + "ground_" + texName + ".gif"); 159 | if (!background.exists()) { 160 | System.out.println(" > No background found for \"" + texName + "\". Using \"perks\"."); 161 | background = new File(editor.escapistsPath, "Data" + 162 | File.separator + "images" + File.separator + "ground_perks.gif"); 163 | } 164 | 165 | // Decrypt the tiles themselves 166 | tilesImage = ImageIO.read(new ByteArrayInputStream(BlowfishCompatEncryption.decrypt(tilesFile))); 167 | backgroundImage = ImageIO.read(background); 168 | 169 | // Pre filter me 170 | PreFilters.run(this); 171 | if (tiles.length > 0) { 172 | width = tiles[0].length - 1; 173 | height = tiles.length; 174 | } 175 | 176 | System.out.println(" > Done."); 177 | } 178 | 179 | private int[][] compileIDSection(PropertiesSection section) { 180 | // Get first row, so we can work out width 181 | String firstRow = (String) section.get("0"); 182 | width = firstRow.split("_").length - 1; 183 | height = section.size(); 184 | 185 | // This is stored in a y-x format, so that it can be more easily converted to its original format. 186 | // This is flipped by most get methods, so this should be abstract. 187 | int[][] tiles = new int[height][width]; 188 | 189 | for (java.util.Map.Entry tile : section.entrySet()) { 190 | int heightPos = Integer.parseInt(tile.getKey()); 191 | String row = (String) tile.getValue(); 192 | String[] values = row.split("_"); 193 | int[] rowDecompiled = new int[values.length]; 194 | 195 | int widthPos = -1; 196 | for (String value : values) { 197 | widthPos++; 198 | if (value.trim().length() < 1) { 199 | continue; 200 | } 201 | 202 | //System.out.println(widthPos); 203 | rowDecompiled[widthPos] = Integer.parseInt(value); 204 | } 205 | 206 | tiles[heightPos] = rowDecompiled; 207 | } 208 | 209 | return tiles; 210 | } 211 | 212 | public String getName() { 213 | return (String) get("Info.MapName"); 214 | } 215 | 216 | public Object get(String key) { 217 | // Get first . 218 | String section = key.split("\\.")[0]; 219 | key = key.substring(key.indexOf(section) + section.length() + 1); 220 | 221 | PropertiesSection discoveredSection = sections.getSection(section); 222 | if (discoveredSection != null) { 223 | return discoveredSection.get(key); 224 | } else { 225 | return null; 226 | } 227 | } 228 | 229 | public void setMap(String key, PropertiesSection value) { 230 | sections.setSection(key, value); 231 | } 232 | 233 | public PropertiesSection getMap(String key) { 234 | return sections.getSection(key); 235 | } 236 | 237 | public int getHeight() { 238 | return height; 239 | } 240 | 241 | public int getWidth() { 242 | return width; 243 | } 244 | 245 | public int getTile(int x, int y) { 246 | return tiles[y][x]; 247 | } 248 | 249 | public int getTile(int x, int y, String view) { 250 | if (x >= width || y >= height) { 251 | throw new IllegalArgumentException("Fetching bad location @ " + x + ":" + y); 252 | } 253 | if (view.equalsIgnoreCase("World")) { 254 | return tiles[y][x]; 255 | } else if (view.equalsIgnoreCase("Underground")) { 256 | return underground[y][x]; 257 | } else if (view.equalsIgnoreCase("Vents")) { 258 | return vents[y][x]; 259 | } else if (view.equalsIgnoreCase("Roof")) { 260 | return roof[y][x]; 261 | } else { 262 | return 0; 263 | } 264 | } 265 | 266 | public List getObjects() { 267 | return objects; 268 | } 269 | 270 | public BufferedImage getTilesImage() { 271 | return tilesImage; 272 | } 273 | 274 | public BufferedImage getBackgroundImage() { 275 | return backgroundImage; 276 | } 277 | 278 | public void setTile(int x, int y, int value) { 279 | tiles[y][x] = value; 280 | } 281 | 282 | public void setTile(int x, int y, int selectedIndex, String view) { 283 | if (view.equalsIgnoreCase("World")) { 284 | tiles[y][x] = selectedIndex; 285 | } else if (view.equalsIgnoreCase("Underground")) { 286 | underground[y][x] = selectedIndex; 287 | } else if (view.equalsIgnoreCase("Vents")) { 288 | vents[y][x] = selectedIndex; 289 | } else if (view.equalsIgnoreCase("Roof")) { 290 | roof[y][x] = selectedIndex; 291 | } 292 | } 293 | 294 | public int count(Objects object) { 295 | return count(object.getID()); 296 | } 297 | 298 | public int count(int id) { 299 | int count = 0; 300 | for (WorldObject object : objects) { 301 | if (object.getID() == id) { 302 | count++; 303 | } 304 | } 305 | return count; 306 | } 307 | 308 | public void save(File selectedFile) throws IOException { 309 | 310 | if (!selectedFile.getName().toLowerCase().endsWith(".proj")) { 311 | // Do checks 312 | // Correct points for guards 313 | if (count(Objects.AI_WP_GUARD_ROLLCALL) != 3) { 314 | throw new IOException("Compile Error: Invalid amount of rollcall guard waypoints - \n" + 315 | "You need 3."); 316 | } 317 | if (count(Objects.AI_WP_GUARD_MEALS) != 3) { 318 | throw new IOException("Compile Error: Invalid amount of meal guard waypoints - \n" + 319 | "You need 3."); 320 | } 321 | if (count(Objects.AI_WP_GUARD_EXERCISE) != 3) { 322 | throw new IOException("Compile Error: Invalid amount of exercise guard waypoints - \n" + 323 | "You need 3."); 324 | } 325 | if (count(Objects.AI_WP_GUARD_SHOWERS) != 3) { 326 | throw new IOException("Compile Error: Invalid amount of shower guard waypoints - \n" + 327 | "You need 3."); 328 | } 329 | if (count(Objects.GUARD_BED) < 2) { 330 | throw new IOException("Compile Error: Invalid amount of guard beds - \n" + 331 | "You need more than 1."); 332 | } 333 | if (count(Objects.AI_WP_GUARD_GENERAL) < 5) { 334 | throw new IOException("Compile Error: Invalid amount of general guard waypoints - \nYou need more than 4."); 335 | } 336 | 337 | // Workout equipment 338 | int count = 0; 339 | for (Objects object : Objects.values()) { 340 | if (object.name().toLowerCase().startsWith("training")) { 341 | count += count(object); 342 | } 343 | } 344 | int required = Integer.parseInt(get("Info.Inmates").toString()); 345 | if (count < required) { 346 | throw new IOException("Compile Error: Invalid amount of training objects - \nYou need more than " + (count - 1) + "."); 347 | } 348 | 349 | // Player stuff 350 | if ((count(Objects.FORCED_PRISONER_BED) + count(Objects.FORCED_SIDEWAYS_PRISONER_BED)) != 1) { 351 | throw new IOException("Compile Error: You need a single forced prisoner bed for the player!"); 352 | } 353 | if (count(Objects.FORCED_PRISONER_DESK) != 1) { 354 | throw new IOException("Compile Error: You need a single forced prisoner desk for the player!"); 355 | } 356 | if (count(Objects.SOLITARY_BED) == 0) { 357 | throw new IOException("Compile Error: You need at least 1 solitary bed!"); 358 | } 359 | if (count(Objects.MEDICAL_BED) == 0) { 360 | throw new IOException("Compile Error: You need at least 1 medical bed!"); 361 | } 362 | 363 | // Other desk stuff 364 | if ((count(Objects.BED) + count(Objects.SIDEWAYS_PRISONER_BED)) < (required - 1)) { 365 | throw new IOException("Compile Error: You need " + (required - 1) + " general beds for non-player prisoners!"); 366 | } 367 | 368 | // Lights 369 | if (count(Objects.LIGHT) == 0) { 370 | throw new IOException("Compile Error: You need some Light objects!"); 371 | } 372 | 373 | // Food trays 374 | if (count(Objects.SERVING_TABLE) < 3) { 375 | throw new IOException("Compile Error: You need at least 3 serving tables!"); 376 | } 377 | if (count(Objects.AI_WP_PRISONER_MEALS) == 0) { 378 | throw new IOException("Compile Error: You need a prisoner meals waypoint!"); 379 | } 380 | if (count(Objects.CHAIR) < (required - 1)) { 381 | throw new IOException("Compile Error: You need at least " + (required - 1) + " chairs in the canteen!"); 382 | } 383 | 384 | // Warden name 385 | if (get("Info.Warden") == null || get("Info.Warden").toString().length() == 0) { 386 | throw new IOException("Compile Error: You need a warden name!"); 387 | } 388 | 389 | // Other NPC prisoners 390 | if (count(Objects.AI_WP_PRISONER_ROLLCALL) != (required - 1)) { 391 | throw new IOException("Compile Error: You need " + (required - 1) + " rollcall waypoints for prisoners!"); 392 | } 393 | if (count(Objects.PERSONAL_DESK) < (required - 1)) { 394 | throw new IOException("Compile Error: You need " + (required - 1) + " standard personal desks for non-player prisoners!"); 395 | } 396 | if (count(Objects.AI_WP_PRISONER_GENERAL) < 5) { 397 | throw new IOException("Compile Error: You need at least 5 general waypoints for prisoners!"); 398 | } 399 | if (count(Objects.AI_NPC_SPAWN) != 1) { 400 | throw new IOException("Compile Error: You need a single AI Npc Spawn point!"); 401 | } 402 | if (count(Objects.AI_WP_DOCTOR_WORK) != 1) { 403 | throw new IOException("Compile Error: You need a single AI Doctor Work waypoint!"); 404 | } 405 | if (count(Objects.AI_WP_EMPLOYMENT_OFFICER) != 1) { 406 | throw new IOException("Compile Error: You need a single AI Employment Officer waypoint!"); 407 | } 408 | 409 | // TODO: Zones 410 | /** 411 | 25) ZONES: Solitary missing 412 | 26) ZONES: Player cell missing 413 | 27) ZONES: Rollcall missing 414 | 28) ZONES: Canteen missing 415 | 29) ZONES: Showers missing 416 | 30) ZONES: Gym missing 417 | 31) ZONES: Cells1 missing 418 | */ 419 | } 420 | 421 | // Count tiles, more then 1 422 | boolean foundTile = false; 423 | for (int[] row : tiles) { 424 | for (int tile : row) { 425 | if (tile > 0) { 426 | foundTile = true; 427 | break; 428 | } 429 | } 430 | } 431 | if (!foundTile) { 432 | throw new IOException("Compile Error: You need at least 1 tile in the world!"); 433 | } 434 | 435 | System.out.println("Saving..."); 436 | 437 | // Serialize tiles 438 | sections.getSection("Tiles").clear(); 439 | for (int y = 0; y < tiles.length; y++) { 440 | String arrayBuild = ""; 441 | for (int x = 0; x < tiles[y].length; x++) { 442 | arrayBuild += tiles[y][x] + "_"; 443 | } 444 | sections.getSection("Tiles").put(y + "", arrayBuild); 445 | } 446 | 447 | sections.getSection("Underground").clear(); 448 | for (int y = 0; y < underground.length; y++) { 449 | String arrayBuild = ""; 450 | for (int x = 0; x < underground[y].length; x++) { 451 | arrayBuild += underground[y][x] + "_"; 452 | } 453 | sections.getSection("Underground").put(y + "", arrayBuild); 454 | } 455 | 456 | sections.getSection("Vents").clear(); 457 | for (int y = 0; y < vents.length; y++) { 458 | String arrayBuild = ""; 459 | for (int x = 0; x < vents[y].length; x++) { 460 | arrayBuild += vents[y][x] + "_"; 461 | } 462 | sections.getSection("Vents").put(y + "", arrayBuild); 463 | } 464 | 465 | sections.getSection("Roof").clear(); 466 | for (int y = 0; y < roof.length; y++) { 467 | String arrayBuild = ""; 468 | for (int x = 0; x < roof[y].length; x++) { 469 | arrayBuild += roof[y][x] + "_"; 470 | } 471 | sections.getSection("Roof").put(y + "", arrayBuild); 472 | } 473 | 474 | // Serialize Objects 475 | int objCount = 1; 476 | sections.getSection("Objects").clear(); 477 | for (WorldObject worldObject : objects) { 478 | int x = worldObject.getX(); 479 | int y = worldObject.getY(); 480 | int id = worldObject.getID(); 481 | int argument = worldObject.getIDArgument(); 482 | 483 | if (id != 0) { 484 | sections.getSection("Objects").put(objCount + "", x + "x" + y + "x" + id + "x" + argument); 485 | objCount++; 486 | } 487 | } 488 | 489 | // If this is a project, set it up 490 | if (selectedFile.getName().toLowerCase().endsWith(".proj")) { 491 | set("Info.Custom", -1); 492 | } else { 493 | set("Info.Custom", 2); 494 | } 495 | 496 | // Build sections 497 | String allSections = sections.toString(); 498 | 499 | // Save it 500 | byte[] encryptedBytes = allSections.getBytes();//BlowfishCompatEncryption.encrypt(allSections.getBytes()); 501 | 502 | System.out.println("Writing to " + selectedFile.getPath()); 503 | try (FileOutputStream out = new FileOutputStream(selectedFile)) { 504 | out.write(encryptedBytes); 505 | out.flush(); 506 | } 507 | } 508 | 509 | public WorldObject getObjectAt(int x, int y, String view) { 510 | int level = 1; 511 | if (view.equalsIgnoreCase("Underground")) { 512 | level = 0; 513 | } else if (view.equalsIgnoreCase("Vents")) { 514 | level = 2; 515 | } else if (view.equalsIgnoreCase("Roof")) { 516 | level = 3; 517 | } 518 | 519 | for (WorldObject object : objects) { 520 | if (object.getX() == x && object.getY() == y && object.getIDArgument() == level) { 521 | return object; 522 | } 523 | } 524 | return null; 525 | } 526 | 527 | public void set(String key, Object value) { 528 | // Get first . 529 | String section = key.split("\\.")[0]; 530 | key = key.substring(key.indexOf(section) + section.length() + 1); 531 | 532 | PropertiesSection discoveredSection = sections.getSection(section); 533 | if (discoveredSection != null) { 534 | discoveredSection.put(key, value); 535 | } 536 | } 537 | 538 | public boolean isObjectAt(int x, int y, int id, int layer) { 539 | for (WorldObject obj : objects) { 540 | if (obj.getID() == id && obj.getX() == x 541 | && obj.getY() == y && obj.getIDArgument() == layer) { 542 | return true; 543 | } 544 | } 545 | return false; 546 | } 547 | 548 | public int[][] getTiles(String section) { 549 | if (section.equalsIgnoreCase("World")) { 550 | return tiles; 551 | } else if (section.equalsIgnoreCase("Underground")) { 552 | return underground; 553 | } else if (section.equalsIgnoreCase("Vents")) { 554 | return vents; 555 | } else if (section.equalsIgnoreCase("Roof")) { 556 | return roof; 557 | } else { 558 | return null; 559 | } 560 | } 561 | 562 | public void setTiles(String section, int[][] tiles) { 563 | if (section.equalsIgnoreCase("World")) { 564 | this.tiles = tiles; 565 | } else if (section.equalsIgnoreCase("Underground")) { 566 | this.underground = tiles; 567 | } else if (section.equalsIgnoreCase("Vents")) { 568 | this.vents = tiles; 569 | } else if (section.equalsIgnoreCase("Roof")) { 570 | this.roof = tiles; 571 | } 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /src/main/java/net/jselby/escapists/editor/elements/RenderView.java: -------------------------------------------------------------------------------- 1 | package net.jselby.escapists.editor.elements; 2 | 3 | import net.jselby.escapists.editor.EscapistsEditor; 4 | import net.jselby.escapists.editor.ActionMode; 5 | import net.jselby.escapists.editor.mapping.Map; 6 | import net.jselby.escapists.editor.objects.Objects; 7 | import net.jselby.escapists.editor.objects.WorldObject; 8 | import net.jselby.escapists.editor.utils.IOUtils; 9 | import net.jselby.escapists.editor.utils.StringUtils; 10 | import net.jselby.escapists.editor.utils.logging.Rollbar; 11 | 12 | import javax.imageio.ImageIO; 13 | import javax.swing.*; 14 | import javax.swing.event.ChangeEvent; 15 | import javax.swing.event.ChangeListener; 16 | import javax.swing.event.MenuEvent; 17 | import javax.swing.event.MenuListener; 18 | import javax.swing.filechooser.FileFilter; 19 | import java.awt.*; 20 | import java.awt.event.*; 21 | import java.awt.image.BufferedImage; 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.ArrayList; 25 | 26 | /** 27 | * The render view shows the render in a window, and has toolbar options addiitonally. 28 | * 29 | * @author j_selby 30 | */ 31 | public class RenderView extends JFrame { 32 | private final MapRendererComponent renderer; 33 | 34 | private final JPanel sidebar; 35 | private JScrollPane tileSelect; 36 | private JLabel selectedTile; 37 | 38 | private final JPanel toolOptions; 39 | 40 | // Tools 41 | private final JComboBox id; 42 | 43 | private EscapistsEditor editor; 44 | private Map mapToEdit; 45 | 46 | private int x; 47 | private int y; 48 | 49 | private ActionMode mode = ActionMode.CREATE_OBJECT; 50 | private int bigBrush; 51 | 52 | private boolean showZone; 53 | private String currentZone = "World"; 54 | private JPanel iconPanel; 55 | 56 | public RenderView(final EscapistsEditor editor, Map renderMap) throws IOException { 57 | this.editor = editor; 58 | this.mapToEdit = renderMap; 59 | 60 | // Configure the defaults 61 | // ID box 62 | ArrayList objectIds = new ArrayList<>(); 63 | for (Objects objectId : Objects.values()) { 64 | objectIds.add(objectId.getID() + ": " 65 | + StringUtils.capitalize(objectId.name().toLowerCase().replace("_", " "))); 66 | } 67 | objectIds.add("Other..."); 68 | id = new JComboBox(objectIds.toArray()); 69 | id.setFocusable(false); 70 | id.setFont(new Font(Font.DIALOG, Font.PLAIN, 10)); 71 | id.setSelectedIndex(0); 72 | id.setMaximumSize(new Dimension(150, 30)); 73 | 74 | // Build tiles 75 | iconPanel = new JPanel(); 76 | tileSelect = new JScrollPane(iconPanel); 77 | tileSelect.getVerticalScrollBar().setUnitIncrement(16); 78 | updateTiling(); 79 | 80 | // Configure the view 81 | setLayout(new BorderLayout()); 82 | 83 | // Add a menu bar 84 | final JMenuBar menuBar = new JMenuBar(); 85 | JMenu fileMenu = new JMenu("File"); 86 | fileMenu.add(new AbstractAction("New") { 87 | @Override 88 | public void actionPerformed(ActionEvent e) { 89 | try { 90 | editor.edit(IOUtils.toByteArray( 91 | getClass().getResource("/blank.decrypted.map"))); 92 | } catch (IOException e1) { 93 | EscapistsEditor.fatalError(e1); 94 | } 95 | } 96 | }); 97 | fileMenu.add(new AbstractAction("Load...") { 98 | @Override 99 | public void actionPerformed(ActionEvent e) { 100 | // Save map 101 | try { 102 | RenderView.this.setEnabled(false); 103 | new MapSelectionGUI(editor).setOldView( 104 | RenderView.this); 105 | } catch (IOException e1) { 106 | e1.printStackTrace(); 107 | } 108 | } 109 | }); 110 | fileMenu.add(new AbstractAction("Save...") { 111 | @Override 112 | public void actionPerformed(ActionEvent e) { 113 | // Save map 114 | // Get our target 115 | JFileChooser fc = new JFileChooser() { 116 | @Override 117 | public void approveSelection() { 118 | if (getSelectedFile().isFile() || !getSelectedFile().exists()) { 119 | super.approveSelection(); 120 | } 121 | } 122 | }; 123 | File file = new File(System.getProperty("user.home")); 124 | File documents = new File(file, "Documents" + File.separator 125 | + "The Escapists" + File.separator + "Custom Maps"); 126 | if (documents.exists()) { 127 | fc.setCurrentDirectory(documents); 128 | } else { 129 | fc.setCurrentDirectory(new File(editor.escapistsPath, 130 | "Data" + File.separator + "Maps")); 131 | } 132 | FileFilter filter = new FileFilter() { 133 | @Override 134 | public boolean accept(File f) { 135 | return f.getName().toLowerCase().endsWith(".proj") || f.isDirectory(); 136 | } 137 | 138 | @Override 139 | public String getDescription() { 140 | return ".proj Project"; 141 | } 142 | }; 143 | fc.addChoosableFileFilter(new FileFilter() { 144 | @Override 145 | public boolean accept(File f) { 146 | return f.getName().toLowerCase().endsWith(".cmap") || f.isDirectory(); 147 | } 148 | 149 | @Override 150 | public String getDescription() { 151 | return ".cmap Maps"; 152 | } 153 | }); 154 | fc.addChoosableFileFilter(filter); 155 | fc.setFileFilter(filter); 156 | int dialog = fc.showSaveDialog(RenderView.this); 157 | if (JFileChooser.APPROVE_OPTION == dialog) { 158 | try { 159 | File file1 = fc.getSelectedFile(); 160 | String extension = fc.getFileFilter() != null ? fc.getFileFilter().getDescription().split(" ")[0] 161 | : ".pmap"; 162 | if (!file1.getName().toLowerCase().endsWith(extension)) { 163 | file1 = new File(file1.getParent(), file1.getName() + extension); 164 | } 165 | mapToEdit.save(file1); 166 | } catch (Exception error) { 167 | error.printStackTrace(); 168 | editor.dialog(error.getMessage()); 169 | } 170 | } 171 | } 172 | }); 173 | fileMenu.addSeparator(); 174 | fileMenu.add(new AbstractAction("Exit") { 175 | @Override 176 | public void actionPerformed(ActionEvent e) { 177 | System.exit(0); 178 | } 179 | }); 180 | 181 | menuBar.add(fileMenu); 182 | final JMenu propertiesMenu = new JMenu("Properties"); 183 | 184 | propertiesMenu.addMenuListener(new MenuListener() { 185 | @Override 186 | public void menuSelected(MenuEvent e) { 187 | SwingUtilities.invokeLater(new Runnable() { 188 | @Override 189 | public void run() { 190 | if (RenderView.this.mapToEdit != null) { 191 | try { 192 | new PropertiesDialog(RenderView.this, renderer, RenderView.this.mapToEdit); 193 | } catch (Exception err) { 194 | err.printStackTrace(); 195 | Rollbar.fatal(err); 196 | } 197 | } else { 198 | editor.dialog("You must have a map loaded!"); 199 | } 200 | } 201 | }); 202 | 203 | } 204 | 205 | public void menuDeselected(MenuEvent e) { 206 | } 207 | 208 | public void menuCanceled(MenuEvent e) { 209 | } 210 | }); 211 | menuBar.add(propertiesMenu); 212 | 213 | JMenu aboutMenu = new JMenu("About"); 214 | aboutMenu.addMenuListener(new MenuListener() { 215 | @Override 216 | public void menuSelected(MenuEvent e) { 217 | SwingUtilities.invokeLater(new Runnable() { 218 | @Override 219 | public void run() { 220 | editor.dialog("Editor by jselby (http://jselby.net)\nGame by Mouldy Toof Studios & Team17"); 221 | } 222 | }); 223 | } 224 | 225 | public void menuDeselected(MenuEvent e) { 226 | } 227 | 228 | public void menuCanceled(MenuEvent e) { 229 | } 230 | }); 231 | menuBar.add(aboutMenu); 232 | menuBar.setAlignmentX(LEFT_ALIGNMENT); 233 | menuBar.setOpaque(false); 234 | menuBar.setPreferredSize(new Dimension(700, 20)); 235 | add(menuBar, BorderLayout.BEFORE_FIRST_LINE); 236 | 237 | // Add a small panel at the bottom for position etc 238 | final JPanel mousePosition = new JPanel() { 239 | @Override 240 | protected void paintComponent(Graphics g) { 241 | super.paintComponent(g); 242 | 243 | // Get a object, if applicable 244 | 245 | WorldObject object = mapToEdit != null ? mapToEdit.getObjectAt(x, y, currentZone) : null; 246 | String objectName = null; 247 | if (object != null) { 248 | if (object.asWorldDictionary() == null) { 249 | objectName = object.getClass().getName(); 250 | } else { 251 | objectName = StringUtils.capitalize(object.asWorldDictionary().name().toLowerCase().replace("_", " ")); 252 | } 253 | } 254 | String objectDef = (object != null ? (", Object " + object.toString() + " (" + objectName + ")") : ""); 255 | 256 | boolean validPosition = x >= 0 && y >= 0 && ((mapToEdit != null && 257 | x < mapToEdit.getHeight() && y < mapToEdit.getWidth()) || mapToEdit == null); 258 | String worldElements = "X: " + x + ", Y: " + y 259 | + objectDef 260 | + ((mapToEdit != null && validPosition) ? (", Tile: " + mapToEdit.getTile(x, y, currentZone)) : ""); 261 | 262 | // Render a top bar 263 | int width = getWidth(); 264 | int height = 20; 265 | g.setColor(new Color(0f, 0f, 0f, 1f)); 266 | g.fillRect(0, 0, width, height); 267 | g.setColor(Color.white); 268 | g.drawString(worldElements, 10, 12); 269 | } 270 | }; 271 | mousePosition.setIgnoreRepaint(false); 272 | 273 | renderer = new MapRendererComponent(mapToEdit, new MouseAdapter() { 274 | @Override 275 | public void mousePressed(MouseEvent e) { 276 | // Get position 277 | 278 | x = e.getX() / 16; 279 | y = e.getY() / 16; 280 | 281 | // Convert x & y with scaling 282 | x = (int) (((float) x) / ((float) renderer.getZoomFactor())); 283 | y = (int) (((float) y) / ((float) renderer.getZoomFactor())); 284 | 285 | toolHandler(x, y); 286 | } 287 | 288 | @Override 289 | public void mouseReleased(MouseEvent e) { 290 | x = -1; 291 | y = -1; 292 | } 293 | }, new MouseMotionAdapter() { 294 | @Override 295 | public void mouseDragged(MouseEvent e) { 296 | // Get position 297 | int newX = e.getX() / 16; 298 | int newY = e.getY() / 16; 299 | if (newX != x || newY != y) { 300 | x = newX; 301 | y = newY; 302 | } else { 303 | return; 304 | } 305 | 306 | // Convert x & y with scaling 307 | x = (int) (((float) x) / ((float) renderer.getZoomFactor())); 308 | y = (int) (((float) y) / ((float) renderer.getZoomFactor())); 309 | 310 | toolHandler(x, y); 311 | } 312 | 313 | @Override 314 | public void mouseMoved(MouseEvent e) { 315 | int newX = e.getX() / 16; 316 | int newY = e.getY() / 16; 317 | if (newX != x || newY != y) { 318 | x = newX; 319 | y = newY; 320 | } else { 321 | return; 322 | } 323 | 324 | // Convert x & y with scaling 325 | x = (int) (((float) x) / ((float) renderer.getZoomFactor())); 326 | y = (int) (((float) y) / ((float) renderer.getZoomFactor())); 327 | 328 | mousePosition.repaint(); 329 | } 330 | }); 331 | 332 | // Add scroll bars to the screen 333 | final JScrollPane scrollArea = new JScrollPane(renderer); 334 | scrollArea.setOpaque(false); 335 | scrollArea.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); 336 | scrollArea.setWheelScrollingEnabled(true); 337 | scrollArea.getHorizontalScrollBar().setUnitIncrement(16); 338 | scrollArea.getVerticalScrollBar().setUnitIncrement(16); 339 | scrollArea.setPreferredSize(new Dimension(700, 580)); 340 | 341 | // Create a wrapper for the area 342 | final JPanel scrollAreaWrapper = new JPanel(); 343 | scrollAreaWrapper.setIgnoreRepaint(false); 344 | scrollAreaWrapper.setLayout(new BorderLayout()); 345 | scrollAreaWrapper.setPreferredSize(new Dimension(700, 580)); 346 | 347 | // Put it together 348 | mousePosition.setPreferredSize(new Dimension(700, 20)); 349 | scrollAreaWrapper.add(scrollArea, BorderLayout.CENTER); 350 | scrollAreaWrapper.add(mousePosition, BorderLayout.NORTH); 351 | add(scrollAreaWrapper); 352 | 353 | // Implement controls for this scrollArea 354 | final int scrollIncrements = 16; 355 | getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, KeyEvent.VK_UNDEFINED), "moveRight"); 356 | getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, KeyEvent.VK_UNDEFINED), "moveLeft"); 357 | getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.VK_UNDEFINED), "moveUp"); 358 | getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.VK_UNDEFINED), "moveDown"); 359 | getRootPane().getActionMap().put("moveRight", new AbstractAction() { 360 | @Override 361 | public void actionPerformed(ActionEvent e) { 362 | Point position = scrollArea.getViewport().getViewPosition(); 363 | position.translate(scrollIncrements, 0); 364 | if (position.getX() >= 0 && position.getY() >= 0 365 | && (position.getX() + scrollArea.getViewport().getWidth()) 366 | < scrollArea.getHorizontalScrollBar().getMaximum() 367 | && (position.getY() + scrollArea.getViewport().getHeight()) 368 | < scrollArea.getVerticalScrollBar().getMaximum()) { 369 | scrollArea.getViewport().setViewPosition(position); 370 | } 371 | } 372 | }); 373 | getRootPane().getActionMap().put("moveLeft", new AbstractAction() { 374 | @Override 375 | public void actionPerformed(ActionEvent e) { 376 | Point position = scrollArea.getViewport().getViewPosition(); 377 | position.translate(-scrollIncrements, 0); 378 | if (position.getX() >= 0 && position.getY() >= 0 379 | && (position.getX() + scrollArea.getViewport().getWidth()) 380 | < scrollArea.getHorizontalScrollBar().getMaximum() 381 | && (position.getY() + scrollArea.getViewport().getHeight()) 382 | < scrollArea.getVerticalScrollBar().getMaximum()) { 383 | scrollArea.getViewport().setViewPosition(position); 384 | } 385 | } 386 | }); 387 | getRootPane().getActionMap().put("moveUp", new AbstractAction() { 388 | @Override 389 | public void actionPerformed(ActionEvent e) { 390 | Point position = scrollArea.getViewport().getViewPosition(); 391 | position.translate(0, -scrollIncrements); 392 | if (position.getX() >= 0 && position.getY() >= 0 393 | && (position.getX() + scrollArea.getViewport().getWidth()) 394 | < scrollArea.getHorizontalScrollBar().getMaximum() 395 | && (position.getY() + scrollArea.getViewport().getHeight()) 396 | < scrollArea.getVerticalScrollBar().getMaximum()) { 397 | scrollArea.getViewport().setViewPosition(position); 398 | } 399 | } 400 | }); 401 | getRootPane().getActionMap().put("moveDown", new AbstractAction() { 402 | @Override 403 | public void actionPerformed(ActionEvent e) { 404 | Point position = scrollArea.getViewport().getViewPosition(); 405 | position.translate(0, scrollIncrements); 406 | if (position.getX() >= 0 && position.getY() >= 0 407 | && (position.getX() + scrollArea.getViewport().getWidth()) 408 | < scrollArea.getHorizontalScrollBar().getMaximum() 409 | && (position.getY() + scrollArea.getViewport().getHeight()) 410 | < scrollArea.getVerticalScrollBar().getMaximum()) { 411 | scrollArea.getViewport().setViewPosition(position); 412 | } 413 | } 414 | }); 415 | 416 | // Toolbar 417 | sidebar = new JPanel(); 418 | sidebar.setLayout(new BoxLayout(sidebar, BoxLayout.Y_AXIS)); 419 | Dimension sidebarSize = new Dimension(200, 600); 420 | sidebar.setPreferredSize(sidebarSize); 421 | sidebar.setMaximumSize(sidebarSize); 422 | sidebar.setMinimumSize(sidebarSize); 423 | sidebar.setAlignmentY(TOP_ALIGNMENT); 424 | add(sidebar, BorderLayout.WEST); 425 | 426 | sidebar.add(Box.createVerticalStrut(10)); 427 | 428 | JLabel displayLabel = new JLabel("Display"); 429 | displayLabel.setAlignmentX(CENTER_ALIGNMENT); 430 | displayLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 14)); 431 | sidebar.add(displayLabel); 432 | 433 | sidebar.add(Box.createVerticalStrut(10)); 434 | 435 | // View 436 | final JComboBox views = new JComboBox(new String[]{ 437 | "Underground", "World", "Vents", "Roof" 438 | }); 439 | views.setFocusable(false); 440 | views.setSelectedIndex(1); 441 | views.setMaximumSize(new Dimension(150, 30)); 442 | views.setAlignmentX(CENTER_ALIGNMENT); 443 | views.addActionListener(new ActionListener() { 444 | @Override 445 | public void actionPerformed(ActionEvent e) { 446 | currentZone = (String) views.getSelectedItem(); 447 | renderer.setView((String) views.getSelectedItem()); 448 | renderer.refresh(); 449 | } 450 | }); 451 | JLabel viewLabel = new JLabel("Layer"); 452 | viewLabel.setAlignmentX(CENTER_ALIGNMENT); 453 | sidebar.add(viewLabel); 454 | sidebar.add(Box.createVerticalStrut(10)); 455 | sidebar.add(views); 456 | 457 | // Zoom 458 | sidebar.add(Box.createVerticalStrut(10)); 459 | JLabel zoomLabel = new JLabel("Zoom"); 460 | zoomLabel.setAlignmentX(CENTER_ALIGNMENT); 461 | sidebar.add(zoomLabel); 462 | final JSlider zoom = new JSlider(JSlider.HORIZONTAL, 463 | 1, 5, 3); 464 | zoom.setFocusable(false); 465 | zoom.setMajorTickSpacing(1); 466 | zoom.addChangeListener(new ChangeListener() { 467 | @Override 468 | public void stateChanged(ChangeEvent e) { 469 | float value = zoom.getValue(); 470 | if (value < 3) { 471 | if (value == 1) { 472 | value = 0.5f; 473 | } else if (value == 2) { 474 | value = 0.75f; 475 | } 476 | } else { 477 | value -= 2; 478 | if (value == 2) { 479 | value = 1.5f; 480 | } else if (value == 3) { 481 | value = 2f; 482 | } 483 | } 484 | renderer.setZoomFactor(value); 485 | } 486 | }); 487 | zoom.setAlignmentX(CENTER_ALIGNMENT); 488 | sidebar.add(zoom); 489 | 490 | // Checkbox for rendering 491 | final JCheckBox showZones = new JCheckBox(); 492 | showZones.setFocusable(false); 493 | showZones.setText("Show Zones"); 494 | showZones.addActionListener(new ActionListener() { 495 | @Override 496 | public void actionPerformed(ActionEvent e) { 497 | showZone = showZones.isSelected(); 498 | 499 | if (mode != ActionMode.ZONE_EDIT) { 500 | renderer.setShowZones(showZone); 501 | renderer.refresh(); 502 | } 503 | } 504 | }); 505 | showZones.setAlignmentX(CENTER_ALIGNMENT); 506 | sidebar.add(showZones); 507 | sidebar.add(Box.createVerticalStrut(30)); 508 | 509 | JComponent seperator = (JComponent) Box.createRigidArea(new Dimension(150, 10)); 510 | seperator.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.BLACK)); 511 | sidebar.add(seperator); 512 | 513 | JLabel toolLabel = new JLabel("Tools"); 514 | toolLabel.setAlignmentX(CENTER_ALIGNMENT); 515 | toolLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 14)); 516 | sidebar.add(toolLabel); 517 | sidebar.add(Box.createVerticalStrut(10)); 518 | 519 | // Tool chooser 520 | String[] rawModes = new String[ActionMode.values().length]; 521 | ActionMode[] values = ActionMode.values(); 522 | for (int i = 0; i < values.length; i++) { 523 | rawModes[i] = values[i].getTitle(); 524 | } 525 | final JComboBox petList = new JComboBox(rawModes); 526 | petList.setFocusable(false); 527 | petList.setSelectedIndex(2); 528 | petList.setMaximumSize(new Dimension(150, 30)); 529 | petList.addActionListener(new ActionListener() { 530 | @Override 531 | public void actionPerformed(ActionEvent e) { 532 | try { 533 | setMode(ActionMode.values()[petList.getSelectedIndex()]); 534 | } catch (Exception err) { 535 | EscapistsEditor.fatalError(err); 536 | } 537 | } 538 | }); 539 | sidebar.add(petList); 540 | 541 | // Add the options 542 | sidebar.add(Box.createVerticalStrut(20)); 543 | 544 | toolOptions = new JPanel(); 545 | toolOptions.setAlignmentX(CENTER_ALIGNMENT); 546 | toolOptions.setLayout(new BoxLayout(toolOptions, BoxLayout.Y_AXIS)); 547 | sidebar.add(toolOptions); 548 | 549 | 550 | setMode(ActionMode.values()[petList.getSelectedIndex()]); 551 | 552 | setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 553 | 554 | setSize(800 + 150, 600); 555 | setLocationRelativeTo(null); 556 | 557 | setTitle("Escapists Map Editor v" + EscapistsEditor.VERSION + " - " + 558 | (mapToEdit == null ? "No map loaded" : mapToEdit.getName())); 559 | setIconImage(ImageIO.read(getClass().getResource("/icon.png"))); 560 | try { 561 | UIManager.setLookAndFeel( 562 | UIManager.getSystemLookAndFeelClassName()); 563 | } catch (ClassNotFoundException | 564 | InstantiationException | 565 | IllegalAccessException | 566 | UnsupportedLookAndFeelException e) { 567 | EscapistsEditor.fatalError(e); 568 | } 569 | 570 | for (java.util.Map.Entry entry : javax.swing.UIManager.getDefaults().entrySet()) { 571 | Object key = entry.getKey(); 572 | Object value = javax.swing.UIManager.get(key); 573 | if (value != null && value instanceof javax.swing.plaf.FontUIResource) { 574 | javax.swing.plaf.FontUIResource f = new javax.swing.plaf.FontUIResource(Font.DIALOG, Font.PLAIN, 12); 575 | javax.swing.UIManager.put(key, f); 576 | } 577 | } 578 | 579 | SwingUtilities.invokeLater(new Runnable() { 580 | @Override 581 | public void run() { 582 | SwingUtilities.updateComponentTreeUI(RenderView.this); 583 | setVisible(true); 584 | } 585 | }); 586 | } 587 | 588 | private void updateTiling() throws IOException { 589 | ArrayList icons = new ArrayList<>(); 590 | icons.add(new ImageIcon(ImageIO.read(getClass().getResource("/defaulttile.png")))); 591 | iconPanel.removeAll(); 592 | iconPanel.setLayout(new GridLayout(0, 4)); 593 | tileSelect.setPreferredSize(new Dimension(150, 250)); 594 | if (mapToEdit != null) { 595 | BufferedImage tiles = mapToEdit.getTilesImage(); 596 | int tileCount = (tiles.getWidth() / 16) * (tiles.getHeight() / 16); 597 | int realSize = (150) / 4 - (5 * 2); 598 | for (int i = -1; i < tileCount; i++) { 599 | int tileX = (int) Math.floor(((double) i) / (tiles.getHeight() / 16)); 600 | int tileY = i - (tileX * (tiles.getHeight() / 16)); 601 | 602 | int absTileX = tileX * 16; 603 | int absTileY = tileY * 16; 604 | 605 | BufferedImage newImage; 606 | if (i > -1) { 607 | BufferedImage tileRender = tiles.getSubimage(absTileX, absTileY, 16, 16); 608 | // Resize 609 | newImage = new BufferedImage(realSize, realSize, BufferedImage.TYPE_INT_RGB); 610 | Graphics g = newImage.createGraphics(); 611 | g.drawImage(tileRender, 0, 0, realSize, realSize, null); 612 | g.dispose(); 613 | } else { 614 | newImage = ImageIO.read(getClass().getResource("/defaulttile.png")); 615 | } 616 | 617 | ImageIcon icon = new ImageIcon(newImage, "1"); 618 | final JLabel representation = new JLabel(icon); 619 | representation.setName("Tile " + (i + 1)); 620 | representation.setMaximumSize(new Dimension(realSize, realSize)); 621 | representation.setMinimumSize(new Dimension(realSize, realSize)); 622 | representation.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 623 | representation.addMouseListener(new MouseAdapter() { 624 | @Override 625 | public void mousePressed(MouseEvent e) { 626 | if (selectedTile != null) { 627 | selectedTile.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 628 | } 629 | selectedTile = representation; 630 | representation.setBorder(BorderFactory.createMatteBorder(5, 5, 5, 5, Color.BLUE)); 631 | } 632 | }); 633 | if (i == -1) { 634 | selectedTile = representation; 635 | representation.setBorder(BorderFactory.createMatteBorder(5, 5, 5, 5, Color.BLUE)); 636 | } 637 | iconPanel.add(representation); 638 | } 639 | } 640 | } 641 | 642 | private void toolHandler(int x, int y) { 643 | if (mapToEdit == null) { 644 | return; 645 | } 646 | 647 | // Get object at that position 648 | WorldObject clickedObject = mapToEdit.getObjectAt(x, y, currentZone); 649 | 650 | switch (mode) { 651 | case CREATE_OBJECT: 652 | // Get ID 653 | int level = 1; 654 | if (currentZone.equalsIgnoreCase("Underground")) { 655 | level = 0; 656 | } else if (currentZone.equalsIgnoreCase("Vents")) { 657 | level = 2; 658 | } else if (currentZone.equalsIgnoreCase("Roof")) { 659 | level = 3; 660 | } 661 | int idToAdd; 662 | if (id.getSelectedItem().toString().equalsIgnoreCase("Other...")) { 663 | String output = JOptionPane.showInputDialog(RenderView.this, "Enter an ID (1-150): "); 664 | if (output == null) { 665 | break; 666 | } 667 | output = output.trim(); 668 | if (StringUtils.isNumber(output)) { 669 | int num = Integer.parseInt(output); 670 | if (num > 0 && num < 150) { 671 | if (!mapToEdit.isObjectAt(x, y, num, level)) { 672 | mapToEdit.getObjects().add(editor.registry.instanceWithUnknown(num, x, y, level)); 673 | renderer.refresh(); 674 | } 675 | } else { 676 | editor.dialog("Invalid ID entered!"); 677 | } 678 | } else { 679 | editor.dialog("Invalid ID entered!"); 680 | } 681 | this.x = -1; 682 | this.y = -1; 683 | } else { 684 | idToAdd = Integer.parseInt(id.getSelectedItem().toString().split(":")[0].trim()); 685 | if (idToAdd > 0 && idToAdd < 150) { 686 | // Check for existing object 687 | if (!mapToEdit.isObjectAt(x, y, idToAdd, level)) { 688 | mapToEdit.getObjects().add(editor.registry.instanceWithUnknown(idToAdd, x, y, level)); 689 | renderer.refresh(); 690 | } 691 | } else { 692 | editor.dialog("Invalid ID for object!"); 693 | } 694 | } 695 | break; 696 | case DELETE_OBJECT: 697 | mapToEdit.getObjects().remove(clickedObject); 698 | break; 699 | case SET_TILE: 700 | // Get tile 701 | if (selectedTile != null) { 702 | int id = Integer.parseInt(selectedTile.getName().split(" ")[1].trim()); 703 | for (int relativeX = -bigBrush; relativeX <= bigBrush; relativeX++) { 704 | for (int relativeY = -bigBrush; relativeY <= bigBrush; relativeY++) { 705 | int myX = x + relativeX; 706 | int myY = y + relativeY; 707 | if (myX >= 0 && myY >= 0 && myX < mapToEdit.getWidth() && myY < mapToEdit.getHeight()) { 708 | try { 709 | mapToEdit.setTile(myX, myY, id, currentZone); 710 | } catch (Exception e) { 711 | System.out.println("Error @ setting " + myX + ":" + myY); 712 | } 713 | } 714 | } 715 | } 716 | } 717 | break; 718 | case ZONE_EDIT: 719 | // Handled upstream 720 | return; 721 | } 722 | 723 | // Redraw the object 724 | renderer.refresh(); 725 | } 726 | 727 | public void setMode(ActionMode mode) { 728 | System.out.println(" - Selecting tool: " + mode.name()); 729 | toolOptions.removeAll(); 730 | 731 | renderer.setShowZones(showZone); 732 | renderer.setEditZones(false); 733 | switch (mode) { 734 | case CREATE_OBJECT: 735 | toolOptions.add(new JLabel("ID:")); 736 | toolOptions.add(id); 737 | break; 738 | case DELETE_OBJECT: 739 | break; 740 | case SET_TILE: 741 | 742 | JLabel zoomLabel = new JLabel("Brush size"); 743 | zoomLabel.setAlignmentX(CENTER_ALIGNMENT); 744 | toolOptions.add(zoomLabel); 745 | final JSlider zoom = new JSlider(JSlider.HORIZONTAL, 746 | 0, 5, 0); 747 | zoom.setFocusable(false); 748 | zoom.setMajorTickSpacing(1); 749 | zoom.addChangeListener(new ChangeListener() { 750 | @Override 751 | public void stateChanged(ChangeEvent e) { 752 | bigBrush = zoom.getValue(); 753 | } 754 | }); 755 | zoom.setAlignmentX(CENTER_ALIGNMENT); 756 | toolOptions.add(zoom); 757 | toolOptions.add(tileSelect); 758 | break; 759 | case ZONE_EDIT: 760 | renderer.setEditZones(true); 761 | renderer.setShowZones(true); 762 | JButton manualEdit = new JButton("Manual..."); 763 | manualEdit.addActionListener(new ActionListener() { 764 | @Override 765 | public void actionPerformed(ActionEvent e) { 766 | if (mapToEdit != null) { 767 | new ZonesDialog(RenderView.this, renderer, mapToEdit); 768 | } 769 | } 770 | }); 771 | manualEdit.setAlignmentX(CENTER_ALIGNMENT); 772 | toolOptions.add(manualEdit); 773 | break; 774 | } 775 | 776 | renderer.refresh(); 777 | 778 | toolOptions.revalidate(); 779 | toolOptions.repaint(); 780 | 781 | this.mode = mode; 782 | } 783 | 784 | public void setMap(Map newMap) { 785 | mapToEdit = newMap; 786 | setTitle("Escapists Map Editor v" + EscapistsEditor.VERSION + " - " + 787 | (mapToEdit == null ? "No map loaded" : mapToEdit.getName())); 788 | try { 789 | updateTiling(); 790 | } catch (IOException e) { 791 | e.printStackTrace(); 792 | } 793 | validate(); 794 | repaint(); 795 | renderer.setMap(mapToEdit); 796 | } 797 | } 798 | --------------------------------------------------------------------------------