From 4b673c9063450d8474e6742a75c76bb09afc731c Mon Sep 17 00:00:00 2001 From: ceikry Date: Sat, 11 Mar 2023 15:17:57 -0600 Subject: [PATCH] Implemented NPC spawn editing and lots of UI improvements --- .gitignore | 3 + build.gradle | 10 + src/main/kotlin/EditorConstants.kt | 2 +- src/main/kotlin/Editors.kt | 61 +++ src/main/kotlin/MainScreen.kt | 10 +- src/main/kotlin/NPCMenu.kt | 97 +++++ src/main/kotlin/Rs2MapEditor.kt | 405 +++++++++++++++++ src/main/kotlin/TableData.kt | 8 + src/main/kotlin/cacheops/cache/Cache.kt | 40 ++ .../kotlin/cacheops/cache/CacheDelegate.kt | 88 ++++ src/main/kotlin/cacheops/cache/Definition.kt | 5 + .../cacheops/cache/DefinitionDecoder.kt | 131 ++++++ .../cacheops/cache/DefinitionEncoder.kt | 7 + .../cache/buffer/read/BufferReader.kt | 223 ++++++++++ .../cacheops/cache/buffer/read/Reader.kt | 116 +++++ .../cache/buffer/write/BufferWriter.kt | 206 +++++++++ .../cacheops/cache/buffer/write/Writer.kt | 101 +++++ .../cacheops/cache/definition/ColorPalette.kt | 15 + .../kotlin/cacheops/cache/definition/Extra.kt | 16 + .../cache/definition/Parameterized.kt | 28 ++ .../cacheops/cache/definition/Recolourable.kt | 30 ++ .../definition/data/AnimationDefinition.kt | 124 ++++++ .../definition/data/AtmosphereDefinition.kt | 19 + .../data/BlendedTextureDefinition.kt | 9 + .../cache/definition/data/BodyDefinition.kt | 16 + .../definition/data/ClientScriptDefinition.kt | 71 +++ .../cache/definition/data/EnumDefinition.kt | 19 + .../definition/data/GraphicDefinition.kt | 82 ++++ .../cache/definition/data/IndexedSprite.kt | 36 ++ .../cache/definition/data/Instructions.kt | 10 + .../data/InterfaceComponentDefinition.kt | 409 ++++++++++++++++++ .../data/InterfaceComponentSetting.kt | 27 ++ .../definition/data/InterfaceDefinition.kt | 11 + .../cache/definition/data/ItemDefinition.kt | 249 +++++++++++ .../cache/definition/data/MapDefinition.kt | 24 + .../cache/definition/data/MapObject.kt | 43 ++ .../cacheops/cache/definition/data/MapTile.kt | 58 +++ .../cache/definition/data/NPCDefinition.kt | 250 +++++++++++ .../cache/definition/data/ObjectDefinition.kt | 295 +++++++++++++ .../definition/data/OverlayDefinition.kt | 26 ++ .../data/QuickChatOptionDefinition.kt | 36 ++ .../cache/definition/data/SpriteDefinition.kt | 8 + .../definition/data/TextureDefinition.kt | 25 ++ .../definition/data/UnderlayDefinition.kt | 23 + .../cache/definition/data/VarBitDefinition.kt | 9 + .../definition/data/WorldMapDefinition.kt | 18 + .../cache/definition/data/WorldMapIcon.kt | 6 + .../definition/data/WorldMapIconDefinition.kt | 25 ++ .../cache/definition/data/WorldMapSection.kt | 3 + .../definition/decoder/AtmosphereDecoder.kt | 7 + .../decoder/BlendedTextureDecoder.kt | 95 ++++ .../decoder/FloorOverlayConfiguration.kt | 32 ++ .../definition/decoder/FloorOverlayDecoder.kt | 157 +++++++ .../decoder/FloorUnderlayConfiguration.kt | 33 ++ .../decoder/FloorUnderlayDecoder.kt | 132 ++++++ .../definition/decoder/GroundItemDecoder.kt | 53 +++ .../cache/definition/decoder/ItemDecoder.kt | 251 +++++++++++ .../definition/decoder/MapTileDecoder.kt | 48 ++ .../cache/definition/decoder/MapTileParser.kt | 55 +++ .../cache/definition/decoder/NPCDecoder.kt | 194 +++++++++ .../definition/decoder/NPCSpawnDecoder.kt | 48 ++ .../cache/definition/decoder/ObjectDecoder.kt | 237 ++++++++++ .../definition/decoder/TileSceneryParser.kt | 59 +++ .../cache/definition/decoder/XteaKeys.kt | 33 ++ .../definition/encoder/GroundItemEncoder.kt | 48 ++ .../definition/encoder/MapTileEncoder.kt | 49 +++ .../definition/encoder/NPCSpawnEncoder.kt | 35 ++ .../kotlin/cacheops/cache/secure/Huffman.kt | 210 +++++++++ src/main/kotlin/cacheops/cache/secure/RSA.kt | 56 +++ src/main/kotlin/cacheops/cache/secure/Xtea.kt | 77 ++++ src/main/kotlin/const/Archives.kt | 10 + src/main/kotlin/const/Image.kt | 13 + src/main/kotlin/const/Indices.kt | 32 ++ src/main/kotlin/const/Misc.kt | 8 + src/main/kotlin/ext/JagexTypes.kt | 61 +++ src/main/kotlin/ext/Misc.kt | 12 + src/main/kotlin/ext/Vector3f.kt | 24 + src/main/kotlin/misc/CustomEventQueue.kt | 22 + src/main/kotlin/misc/GlobalKeybinds.kt | 65 +++ src/main/kotlin/misc/ImgButton.kt | 56 +++ src/main/kotlin/tools/ItemSearchTool.kt | 101 +++++ src/main/kotlin/tools/NPCSearchTool.kt | 100 +++++ src/main/kotlin/tools/Util.kt | 23 + src/main/kotlin/ui/InfoPane.kt | 23 + src/main/kotlin/ui/ItemSpawnPanel.kt | 128 ++++++ src/main/kotlin/ui/MapCell.kt | 396 +++++++++++++++++ src/main/kotlin/ui/NPCSpawnPanel.kt | 215 +++++++++ src/main/kotlin/ui/PlaneButton.kt | 16 + src/main/kotlin/ui/SelectableUnderlayCell.kt | 80 ++++ src/main/kotlin/ui/TileInformationPanel.kt | 16 + src/main/kotlin/ui/UnderlaySelectionPanel.kt | 15 + src/main/kotlin/worldmap/OverlayHandler.kt | 185 ++++++++ src/main/resources/add_dark.png | Bin 0 -> 356 bytes src/main/resources/add_hi.png | Bin 0 -> 337 bytes src/main/resources/dot_red.png | Bin 0 -> 114 bytes src/main/resources/dot_yellow.png | Bin 0 -> 115 bytes src/main/resources/trash_dark.png | Bin 0 -> 330 bytes src/main/resources/trash_hi.png | Bin 0 -> 323 bytes 98 files changed, 7040 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 src/main/kotlin/NPCMenu.kt create mode 100644 src/main/kotlin/Rs2MapEditor.kt create mode 100644 src/main/kotlin/cacheops/cache/Cache.kt create mode 100644 src/main/kotlin/cacheops/cache/CacheDelegate.kt create mode 100644 src/main/kotlin/cacheops/cache/Definition.kt create mode 100644 src/main/kotlin/cacheops/cache/DefinitionDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/DefinitionEncoder.kt create mode 100644 src/main/kotlin/cacheops/cache/buffer/read/BufferReader.kt create mode 100644 src/main/kotlin/cacheops/cache/buffer/read/Reader.kt create mode 100644 src/main/kotlin/cacheops/cache/buffer/write/BufferWriter.kt create mode 100644 src/main/kotlin/cacheops/cache/buffer/write/Writer.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/ColorPalette.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/Extra.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/Parameterized.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/Recolourable.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/AnimationDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/AtmosphereDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/BlendedTextureDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/BodyDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/ClientScriptDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/EnumDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/GraphicDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/IndexedSprite.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/Instructions.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentSetting.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/InterfaceDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/ItemDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/MapDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/MapObject.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/MapTile.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/NPCDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/ObjectDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/OverlayDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/QuickChatOptionDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/SpriteDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/TextureDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/UnderlayDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/VarBitDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/WorldMapDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/WorldMapIcon.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/WorldMapIconDefinition.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/data/WorldMapSection.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/AtmosphereDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/BlendedTextureDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayConfiguration.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayConfiguration.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/GroundItemDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/ItemDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/MapTileDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/MapTileParser.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/NPCDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/NPCSpawnDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/TileSceneryParser.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/decoder/XteaKeys.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/encoder/GroundItemEncoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/encoder/MapTileEncoder.kt create mode 100644 src/main/kotlin/cacheops/cache/definition/encoder/NPCSpawnEncoder.kt create mode 100644 src/main/kotlin/cacheops/cache/secure/Huffman.kt create mode 100644 src/main/kotlin/cacheops/cache/secure/RSA.kt create mode 100644 src/main/kotlin/cacheops/cache/secure/Xtea.kt create mode 100644 src/main/kotlin/const/Archives.kt create mode 100644 src/main/kotlin/const/Image.kt create mode 100644 src/main/kotlin/const/Indices.kt create mode 100644 src/main/kotlin/const/Misc.kt create mode 100644 src/main/kotlin/ext/JagexTypes.kt create mode 100644 src/main/kotlin/ext/Misc.kt create mode 100644 src/main/kotlin/ext/Vector3f.kt create mode 100644 src/main/kotlin/misc/CustomEventQueue.kt create mode 100644 src/main/kotlin/misc/GlobalKeybinds.kt create mode 100644 src/main/kotlin/misc/ImgButton.kt create mode 100644 src/main/kotlin/tools/ItemSearchTool.kt create mode 100644 src/main/kotlin/tools/NPCSearchTool.kt create mode 100644 src/main/kotlin/tools/Util.kt create mode 100644 src/main/kotlin/ui/InfoPane.kt create mode 100644 src/main/kotlin/ui/ItemSpawnPanel.kt create mode 100644 src/main/kotlin/ui/MapCell.kt create mode 100644 src/main/kotlin/ui/NPCSpawnPanel.kt create mode 100644 src/main/kotlin/ui/PlaneButton.kt create mode 100644 src/main/kotlin/ui/SelectableUnderlayCell.kt create mode 100644 src/main/kotlin/ui/TileInformationPanel.kt create mode 100644 src/main/kotlin/ui/UnderlaySelectionPanel.kt create mode 100644 src/main/kotlin/worldmap/OverlayHandler.kt create mode 100644 src/main/resources/add_dark.png create mode 100644 src/main/resources/add_hi.png create mode 100644 src/main/resources/dot_red.png create mode 100644 src/main/resources/dot_yellow.png create mode 100644 src/main/resources/trash_dark.png create mode 100644 src/main/resources/trash_hi.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09bdf3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +.idea/ +.gradle/ diff --git a/build.gradle b/build.gradle index 74a8a77..825a134 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '1.4.32' + id 'application' } group 'org.example' @@ -11,12 +12,21 @@ repositories { } } +application { + mainClass = 'MainScreenKt' +} + dependencies { implementation "org.jetbrains:markdown-jvm:0.2.0" implementation "com.github.ajalt.mordant:mordant:2.0.0-alpha2" implementation "com.github.weisj:darklaf-core:2.5.5" implementation "com.googlecode.json-simple:json-simple:1.1.1" implementation "org.jetbrains.kotlin:kotlin-stdlib" + // https://mvnrepository.com/artifact/com.displee/disio + implementation group: 'com.displee', name: 'disio', version: '2.2' + + // https://mvnrepository.com/artifact/com.displee/rs-cache-library + implementation group: 'com.displee', name: 'rs-cache-library', version: '6.8.1' } task buildJar(type: Jar) { diff --git a/src/main/kotlin/EditorConstants.kt b/src/main/kotlin/EditorConstants.kt index f6afb44..5db7189 100644 --- a/src/main/kotlin/EditorConstants.kt +++ b/src/main/kotlin/EditorConstants.kt @@ -8,7 +8,7 @@ object EditorConstants { var CONFIG_PATH = "" - var VALID_FILES = arrayOf("drop_tables.json","npc_configs.json","item_configs.json", "shops.json", "object_configs.json") + var VALID_FILES = arrayOf("drop_tables.json","npc_configs.json","item_configs.json", "shops.json", "object_configs.json", "npc_spawns.json", "ground_item_spawns.json") var CREDITS = arrayOf( "weisJ - darklaf look and feel themes http://github.com/weisj", diff --git a/src/main/kotlin/Editors.kt b/src/main/kotlin/Editors.kt index 081bbc8..1add965 100644 --- a/src/main/kotlin/Editors.kt +++ b/src/main/kotlin/Editors.kt @@ -1,6 +1,7 @@ import org.json.simple.JSONArray import org.json.simple.JSONObject import org.json.simple.parser.JSONParser +import tools.Util import java.io.File import java.io.FileReader import java.io.FileWriter @@ -254,6 +255,66 @@ enum class Editors(val data: EditorData) { e.printStackTrace() } } + }), + NPC_SPAWNS(object : EditorData("npc_spawns.json") { + override fun parse() { + Logger.logInfo("Parsing npc spawn data...") + val spawnMap = HashMap>() + configureParser() + data.forEach { spawnRaw -> + val spawn = spawnRaw as JSONObject + val id = spawn["npc_id"].toString().toInt() + val datas: Array = spawn["loc_data"].toString().split("-".toRegex()).toTypedArray() + var tokens: Array + for (d in datas) { + if(d.isEmpty()){ + continue + } + tokens = d.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray() + val isWalks = tokens[3].trim { it <= ' ' } == "1" + val spawnDirection = Integer.valueOf(tokens[4].trim { it <= ' ' }) + val spawnCoords = intArrayOf(Integer.valueOf(tokens[0].trim { it <= ' ' }), Integer.valueOf(tokens[1].trim { it <= ' ' }), Integer.valueOf(tokens[2].trim { it <= ' ' })) + val npc = TableData.NPCSpawn(id, TableData.Location(spawnCoords[0], spawnCoords[1], spawnCoords[2]), isWalks, spawnDirection) + val regionId = Util.getRegionId(npc.location) + if (!spawnMap.containsKey(regionId)) + spawnMap[regionId] = ArrayList() + spawnMap[regionId]!!.add(npc) + } + } + TableData.npcSpawns = spawnMap + } + + override fun save() { + Logger.logInfo("Saving NPC spawn info...") + val spawnMap = HashMap() + for ((_, spawns) in TableData.npcSpawns) { + for (spawn in spawns) { + if (!spawnMap.contains(spawn.id)) spawnMap[spawn.id] = StringBuilder() + spawnMap[spawn.id]!!.append("{${spawn.location.x},${spawn.location.y},${spawn.location.z},${if(spawn.canWalk) 1 else 0},${spawn.spawnDirection}}-") + } + } + val spawnsJsonArray = JSONArray() + for ((id, spawnString) in spawnMap.entries.sortedBy { it.key.toInt() }) { + val spawn = JSONObject() + spawn["npc_id"] = id.toString() + spawn["loc_data"] = spawnString.toString() + spawnsJsonArray.add(spawn) + } + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", spawnsJsonArray.toJSONString()) + scriptEngine.eval("result = JSON.stringify(JSON.parse(jsonString), null, 2)") + val prettyPrintedJson = scriptEngine["result"] as String + + try { + FileWriter(EditorConstants.CONFIG_PATH + File.separator + fileName).use { file -> + file.write(prettyPrintedJson) + file.flush() + } + } catch (e: IOException) { + e.printStackTrace() + } + } }) } diff --git a/src/main/kotlin/MainScreen.kt b/src/main/kotlin/MainScreen.kt index c6f360c..8230572 100644 --- a/src/main/kotlin/MainScreen.kt +++ b/src/main/kotlin/MainScreen.kt @@ -21,6 +21,7 @@ object MainScreen : JFrame("RS09 Thanos Tool ${EditorConstants.BUILD_NUMBER}") { val openItem = JButton("Edit Item Configs") val openShops = JButton("Edit Shops") val openObjects = JButton("Edit Object Configs") + val openNpcItemSpawns = JButton("Edit NPC/Item Spawns") var loadedModel = object : DefaultTableModel(){} val loadedTable = object : JTable(loadedModel){ override fun editCellAt(p0: Int, p1: Int): Boolean { @@ -79,6 +80,10 @@ object MainScreen : JFrame("RS09 Thanos Tool ${EditorConstants.BUILD_NUMBER}") { ShopList().open() } + openNpcItemSpawns.addActionListener { + Rs2MapEditor.main(arrayOf()) + } + val showCredits = JButton("Show Credits") showCredits.addActionListener { credits.isVisible = true @@ -87,8 +92,8 @@ object MainScreen : JFrame("RS09 Thanos Tool ${EditorConstants.BUILD_NUMBER}") { val editorPanel = JPanel() val selectionPanel = JPanel() - editorPanel.layout = BoxLayout(editorPanel, BoxLayout.Y_AXIS)//BoxLayout(editorPanel, BoxLayout.PAGE_AXIS) - for(i in arrayOf(openDrops, openNPC, openItem, openObjects, openShops, showCredits)){ + editorPanel.layout = BoxLayout(editorPanel, BoxLayout.Y_AXIS) + for(i in arrayOf(openDrops, openNPC, openItem, openObjects, openShops, openNpcItemSpawns, showCredits)){ i.preferredSize = Dimension(175,40) i.maximumSize = Dimension(175,40) i.isEnabled = i == showCredits @@ -134,6 +139,7 @@ fun showLoaded(){ "item_configs" -> MainScreen.openItem.isEnabled = true.also { Editors.ITEM_CONFIGS.data.parse() } "shops" -> MainScreen.openShops.isEnabled = true.also { Editors.SHOPS.data.parse() } "object_configs" -> MainScreen.openObjects.isEnabled = true.also { Editors.OBJECT_CONFIGS.data.parse()} + "npc_spawns" -> MainScreen.openNpcItemSpawns.isEnabled = true.also { Editors.NPC_SPAWNS.data.parse() } } } } diff --git a/src/main/kotlin/NPCMenu.kt b/src/main/kotlin/NPCMenu.kt new file mode 100644 index 0000000..ef7c983 --- /dev/null +++ b/src/main/kotlin/NPCMenu.kt @@ -0,0 +1,97 @@ +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.KeyEvent +import java.awt.event.KeyListener +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import java.util.* +import java.util.regex.PatternSyntaxException +import javax.swing.* +import javax.swing.table.DefaultTableCellRenderer +import javax.swing.table.DefaultTableModel +import javax.swing.table.TableRowSorter + +object NPCMenu : JFrame("NPC Selection Menu") { + var model = DefaultTableModel() + var searchField = JTextField() + var sorter = TableRowSorter(model) + var cellRenderer = DefaultTableCellRenderer() + var populated = false + var caller: ((Int,String) -> Unit)? = null + private fun filter() { + var rf: RowFilter? = null + //If current expression doesn't parse, don't update. + rf = try { + RowFilter.regexFilter("(?i)${searchField.text}") + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun open() { + if (!populated) { + SwingUtilities.invokeLater { + for ((id,name) in TableData.npcNames) { + model.addRow(arrayOf(id,name)) + } + isVisible = true + populated = true + } + } else { + isVisible = true + } + } + + init { + layout = BorderLayout() + cellRenderer.toolTipText = "Double-Click to select." + val searchPanel = JPanel() + val searchLabel = JLabel("Search for NPC:") + searchField.preferredSize = Dimension(100, 20) + searchPanel.add(searchLabel) + searchPanel.add(searchField) + add(searchPanel, BorderLayout.NORTH) + setLocationRelativeTo(null) + val npcTable: JTable = object : JTable(model) { + override fun editCellAt(i: Int, i1: Int, eventObject: EventObject): Boolean { + return false + } + } + model.addColumn("ID") + model.addColumn("Name") + npcTable.rowSorter = sorter + npcTable.columnModel.getColumn(0).maxWidth = 55 + npcTable.columnModel.getColumn(0).cellRenderer = cellRenderer + npcTable.columnModel.getColumn(1).cellRenderer = cellRenderer + npcTable.addMouseListener(object : MouseListener { + override fun mouseClicked(mouseEvent: MouseEvent) { + if (mouseEvent.clickCount == 2) { + val table = mouseEvent.source as JTable + val row = table.selectedRow + val selectedID = table.getValueAt(row,0).toString().toInt() + val selectedName = table.getValueAt(row,1).toString() + caller?.invoke(selectedID,selectedName) + isVisible = false + } + } + + override fun mousePressed(mouseEvent: MouseEvent) {} + override fun mouseReleased(mouseEvent: MouseEvent) {} + override fun mouseEntered(mouseEvent: MouseEvent) {} + override fun mouseExited(mouseEvent: MouseEvent) {} + }) + searchField.addKeyListener(object : KeyListener { + override fun keyTyped(keyEvent: KeyEvent) { + filter() + } + + override fun keyPressed(keyEvent: KeyEvent) {} + override fun keyReleased(keyEvent: KeyEvent) {} + }) + val scrollPane = JScrollPane(npcTable) + add(scrollPane, BorderLayout.SOUTH) + pack() + isVisible = false + } +} diff --git a/src/main/kotlin/Rs2MapEditor.kt b/src/main/kotlin/Rs2MapEditor.kt new file mode 100644 index 0000000..21f2c89 --- /dev/null +++ b/src/main/kotlin/Rs2MapEditor.kt @@ -0,0 +1,405 @@ + +import cacheops.cache.definition.data.OverlayDefinition +import cacheops.cache.definition.data.UnderlayDefinition +import cacheops.cache.definition.data.MapTile +import cacheops.cache.definition.decoder.* +import com.displee.cache.CacheLibrary +import const.Image.RED_DOT +import const.Image.YELLOW_DOT +import const.cachePath +import misc.CustomEventQueue +import tools.ItemSearchTool +import tools.NPCSearchTool +import tools.Util +import ui.* +import java.awt.* +import java.awt.event.* +import javax.swing.* +import javax.swing.border.* +import kotlin.system.exitProcess + +object Rs2MapEditor { + + val library = CacheLibrary.create(cachePath) + var region = 12850 + var npcs = TableData.npcSpawns[region] + var items = GroundItemSpawnParser.parseItemSpawns(region) + var sceneries = TileSceneryParser.parseRegion(region) + val npcRows = ArrayList() + val itemRows = ArrayList() + var npcsUpdated = false + var itemsUpdated = false + var tileDataUpdated = false + var plane = 0 + var visualizeHeight = false + var drawGrid = true + var previousHeight = -1 + lateinit var mapPanel: MapPane + lateinit var npcPanel: NPCSpawnPanel + lateinit var itemPanel: ItemSpawnPanel + lateinit var infoPane: InfoPane + + @JvmStatic + fun main(args: Array) { + FloorUnderlayConfiguration.init() + FloorOverlayConfiguration.init() + MapTileParser.init() + underlayMap = FloorUnderlayConfiguration.floorUnderlays + overlayMap = FloorOverlayConfiguration.floorOverlays + MainFrame() + } + + var cellX = 20 + var cellY = 20 + + // Formatted points + var selectedPointX = 0 + + var selectedPointY = 0 + get() = (64 - field) - 1 + + // Stores the underlay ID for each point + var colorPointMap: HashMap = hashMapOf() + + val componentPointMap = hashMapOf() + + // Stores the underlay ID with the Underlay Definition + lateinit var underlayMap: HashMap + lateinit var overlayMap: HashMap + + var selectedUnderlayId: Int? = null + + var statusLabel = JLabel() + var infoLabel = JLabel() + var underlayInfo = JLabel("Right click a tile to get more information") + var underlayLabel = JLabel() + lateinit var npcIdInput: JTextField + lateinit var npcCanWalkCheckbox: JCheckBox + lateinit var npcDirectionCheckboxes: Array + lateinit var directionalCheckboxPanel: JPanel + lateinit var itemIdInput: JTextField + lateinit var itemRespawnInput: JTextField + lateinit var itemAmountInput: JTextField + lateinit var infoScrollPane: ScrollPane2 + var state = EditorState.NONE + + class MainFrame : JFrame("2009scape Map Viewer v1.0") { + init { + val statusBar = JPanel(FlowLayout(FlowLayout.LEFT)) + statusBar.border = CompoundBorder( + LineBorder(Color.DARK_GRAY), + EmptyBorder(2, 3, 2, 3) + ) + statusLabel.foreground = Color.YELLOW + + directionalCheckboxPanel = JPanel(GridLayout(3,3)) + npcDirectionCheckboxes = Array(8) { JCheckBox().also { it.addItemListener(DirectionCheckboxOnClick) } } + var cbCounter = 0 + for (i in 0 until 9) { + if (i == 0) npcDirectionCheckboxes[i].setSelected(true) + if (i != 4) { + directionalCheckboxPanel.add(npcDirectionCheckboxes[cbCounter++]) + } else { + directionalCheckboxPanel.add(JLabel("↻", SwingConstants.CENTER)) + } + } + statusBar.add(statusLabel) + + bounds = Rectangle(Dimension(1567, 850)) + size.setSize(1567, 850) + maximumSize = Dimension(1567, 850) + defaultCloseOperation = HIDE_ON_CLOSE + jMenuBar = MenuBar() + mapPanel = MapPane() + npcIdInput = JTextField() + npcCanWalkCheckbox = JCheckBox() + itemIdInput = JTextField() + itemRespawnInput = JTextField() + itemAmountInput = JTextField() + npcPanel = NPCSpawnPanel() + itemPanel = ItemSpawnPanel() + infoPane = InfoPane() + infoScrollPane = ScrollPane2() + add(ScrollPane(), BorderLayout.CENTER) + add(infoScrollPane, BorderLayout.EAST) + add(statusBar, BorderLayout.SOUTH) + infoLabel.text = "Information" + statusLabel.text = "-" + setLocationRelativeTo(null) + isResizable = false + isVisible = true + + CustomEventQueue.install() + } + } + + class MapPane : JPanel() { + val gbc = GridBagConstraints() + val mapHeight = 64 + val mapWidth = 64 + init { + layout = GridBagLayout() + gbc.fill = GridBagConstraints.NONE + gbc.weightx = 1.0 + SwingUtilities.invokeLater { + repaintMap() + } + } + + fun repaintMap() { + colorPointMap.clear() + componentPointMap.clear() + this.removeAll() + for (row in 0 until mapHeight) { + for (column in 0 until mapWidth) { + gbc.gridx = column + gbc.gridy = row + var border: Border = MatteBorder(1, 1, if (row == mapHeight) 1 else 0, if (column == mapWidth) 1 else 0, Color(255,255,255,25)) + val flippedY = (64 - row) - 1 + val point = Point(column, flippedY) + colorPointMap[point] = MapTileParser.definition.getTile(column, flippedY, plane).underlayId - 1 + val mapCell = MapCell() + + val tileDef = MapTileParser.definition.getTile(column, flippedY, plane) + var overlayID = (MapTileParser.definition.getTile(column, flippedY, plane).overlayId - 1) + var underlayID = (MapTileParser.definition.getTile(column, flippedY, plane).underlayId - 1) + + + componentPointMap[point] = mapCell + add(mapCell, gbc) + + if(overlayID < 0 && underlayID < 0){ + mapCell.defaultBackground = Color(0,0,0,0) + mapCell.background = mapCell.defaultBackground + continue + } + if (underlayID < 0) { + underlayID = 0 + } + if (overlayID < 0) { + overlayID = 0 + } + + val fluDef = underlayMap[underlayID] + val floDef = overlayMap[overlayID] + + if (overlayID == 0) { + mapCell.background = fluDef!!.getRGB() + } else if (floDef!!.blendColor != -1) { + mapCell.background = Color(floDef.blendColor) + } else { + mapCell.background = floDef.getRGB() + } + + if(visualizeHeight){ + val height = tileDef.height + val otherTiles: List = Pair(column, flippedY).getSurroundingTiles() + + val borderSizes = intArrayOf(0,0,0,0) + var borderColor = Color(0,0,0,0) + + for(tileIndex in otherTiles.indices){ + val tile = otherTiles[tileIndex] + if(height > tile.height){ + val tileDiff = height - tile.height + val alpha = Math.min(tileDiff * 8, 255) + val color: Color + try { + color = Color(0,0,0, alpha) + } catch (e: Exception){ + println("Invalid alpha value: $alpha") + return + } + + val borderIndex = when(tileIndex){ + 0 -> 0 + 3 -> 1 + 2 -> 2 + 1 -> 3 + else -> -1 + } + + borderSizes[borderIndex] = 3 + if(color.alpha > borderColor.alpha) borderColor = color + } + } + + mapCell.border = MatteBorder(borderSizes[0], borderSizes[1], borderSizes[2], borderSizes[3], borderColor) + } else if(drawGrid) { + mapCell.border = border + } + + val sceneryHere = sceneries.filter { it.x == column && it.y == flippedY && it.plane == plane }.toList() + if(sceneryHere.isNotEmpty()){ + sceneryHere.forEach(mapCell::flagScenery) + } + + val thisAbsolute = Util.getAbsoluteCoordinates(region, column, flippedY, plane) + val npcsHere = npcs!!.filter { it.location == thisAbsolute }.toList() + if (npcsHere.isNotEmpty()) { + mapCell.layout = BorderLayout() + mapCell.add(JLabel(YELLOW_DOT)) + } + val itemSpawnsHere = items.filter { it.x == column && it.y == flippedY && it.plane == plane }.toList() + if (itemSpawnsHere.isNotEmpty()) { + mapCell.layout = BorderLayout() + mapCell.add(JLabel(RED_DOT)) + } + } + } + SwingUtilities.invokeLater { + this.repaint() + } + } + } + + class ScrollPane : JPanel() { + init { + // Scroll bar + val scrollFrame = JScrollPane(mapPanel) + scrollFrame.maximumSize = Dimension(1300, 750) + scrollFrame.preferredSize = Dimension(1300, 750) + scrollFrame.verticalScrollBar.unitIncrement = 20 + add(scrollFrame, BorderLayout.CENTER) + } + } + + class ScrollPane2 : JPanel() { + lateinit var scrollFrame: JScrollPane + init { + // Scroll bar + layout = BorderLayout() + scrollFrame = JScrollPane(infoPane) + scrollFrame.preferredSize = Dimension(255, 980) + scrollFrame.verticalScrollBar.unitIncrement = 20 + add(scrollFrame, BorderLayout.CENTER) + } + } + + fun makeTextPanel(text: String?): JComponent { + val panel = JPanel(false) + val filler = JLabel(text) + filler.horizontalAlignment = JLabel.CENTER + panel.layout = GridLayout(1, 1) + panel.add(filler) + return panel + } + + class MenuBar : JMenuBar() { + init { + add(GoMenuOption()) + add(MoveRegionButton("NORTH", 0, 1)) + add(MoveRegionButton("SOUTH", 0, -1)) + add(MoveRegionButton("EAST", 1, 0)) + add(MoveRegionButton("WEST", -1, 0)) + add(JSeparator(JSeparator.VERTICAL)) + add(JLabel("Plane: ")) + add(PlaneButton(0)) + add(PlaneButton(1)) + add(PlaneButton(2)) + add(PlaneButton(3)) + } + } + + class GoMenuOption : JMenu("Go..") { + init { + add(LoadRegionItem()) + add(LoadByCoordinatesItem()) + } + } + + class LoadRegionItem : JMenuItem("To Region") { + init { + addActionListener { + val region = JOptionPane.showInputDialog("Enter Desired Region ID") + val regionId = region.toIntOrNull() ?: JOptionPane.showInputDialog("Invalid Integer Entered").toIntOrNull() ?: return@addActionListener + + Rs2MapEditor.loadRegion(regionId) + } + } + } + + class LoadByCoordinatesItem : JMenuItem("To Coords") { + init { + addActionListener { + val coords = JOptionPane.showInputDialog("Enter Coords") + val components = coords.split(",").map { it.trim { it <= ' ' }.toInt() }.toIntArray() + val regionId = Util.getRegionId (TableData.Location(components[0], components[1], components[2])) + + Rs2MapEditor.loadRegion(regionId) + } + } + } + + class PrefMenuOption : JMenu("Preferences") { + init { + add(visualizeHeightItem()) + } + } + + + class visualizeHeightItem : JCheckBoxMenuItem("Visualize Heights", false) { + init { + addActionListener { + Rs2MapEditor.visualizeHeight = !Rs2MapEditor.visualizeHeight + SwingUtilities.invokeLater { + mapPanel.repaintMap() + } + } + } + } + + class MoveRegionButton(symbol: String, val diffX: Int, val diffY: Int) : JButton(symbol) { + init { + this.addMouseListener (object : MouseAdapter() { + override fun mouseClicked (e: MouseEvent) { + var currentRegion = Rs2MapEditor.region + var newRegion = Util.getRegion(currentRegion, diffX, diffY) + Rs2MapEditor.loadRegion(newRegion) + } + }) + } + } + + var DirectionCheckboxOnClick = object : ItemListener { + override fun itemStateChanged (e: ItemEvent) { + var source = e.getItemSelectable() + if (source !is JCheckBox) return + if (e.getStateChange() == ItemEvent.SELECTED) { + for (checkbox in npcDirectionCheckboxes) { if (checkbox != source) checkbox.setSelected(false) } + } else { + var anySelected = false + for (checkbox in npcDirectionCheckboxes) { if (checkbox.isSelected) anySelected = true } + if (!anySelected) source.setSelected(true) + } + } + } + + fun loadRegion (regionId: Int) { + Rs2MapEditor.region = regionId + Rs2MapEditor.plane = 0 + npcs = TableData.npcSpawns[regionId] ?: ArrayList().also { TableData.npcSpawns[regionId] = it } + items = GroundItemSpawnParser.parseItemSpawns(regionId) + sceneries = TileSceneryParser.parseRegion(regionId) + npcPanel.redrawRows() + itemPanel.redrawRows() + try { + MapTileParser.init() + mapPanel.repaintMap() + } catch (e: Exception){ + mapPanel.removeAll() + mapPanel.revalidate() + JOptionPane.showMessageDialog(null, "No Map Data for Region ID $regionId.") + } + } +} + +enum class EditorState{ + NONE, + SET_UNDERLAY, + SET_OVERLAY, + ADD_NPC, + ADD_GROUNDITEM, + DEL_NPC, + DEL_GROUNDITEM +} diff --git a/src/main/kotlin/TableData.kt b/src/main/kotlin/TableData.kt index 9e0b837..f1a1d4f 100644 --- a/src/main/kotlin/TableData.kt +++ b/src/main/kotlin/TableData.kt @@ -1,5 +1,6 @@ import org.json.simple.JSONArray import org.json.simple.JSONObject +import tools.Util import java.io.File import java.io.FileWriter import java.io.IOException @@ -8,11 +9,18 @@ import javax.script.ScriptEngineManager object TableData { class Shop(var id: Int,var title: String,var stock: ArrayList,var npcs: String,var currency: Int,var general_store: Boolean,var high_alch: Boolean,var forceShared: Boolean) + data class NPCSpawn(var id: Int, var location: Location, var canWalk: Boolean, var spawnDirection: Int) + data class ItemSpawn(var id: Int, var location: Location, var respawnTicks: Int, var amount: Int) + data class Location(val x: Int, val y: Int, val z: Int) { + val localCoords = Util.getRegionCoordinates(this) + } val tables = ArrayList() val itemNames = hashMapOf() val npcNames = hashMapOf() val npcConfigKeys = HashSet() val npcConfigs = ArrayList() + var npcSpawns = HashMap>() + val itemSpawns = HashMap>() val objConfigKeys = HashSet() val objConfigs = ArrayList() var itemConfigKeys = HashSet() diff --git a/src/main/kotlin/cacheops/cache/Cache.kt b/src/main/kotlin/cacheops/cache/Cache.kt new file mode 100644 index 0000000..a85ffc9 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/Cache.kt @@ -0,0 +1,40 @@ +package cacheops.cache + +import com.displee.cache.index.Index255 +import java.math.BigInteger + +interface Cache { + + var index255: Index255? + + fun getFile(index: Int, archive: Int, file: Int = 0, xtea: IntArray? = null): ByteArray? + + fun getFile(index: Int, name: String, xtea: IntArray? = null): ByteArray? + + fun getArchive(indexId: Int, archiveId: Int): ByteArray? + + fun generateOldVersionTable(): ByteArray + + fun generateNewVersionTable(exponent: BigInteger, modulus: BigInteger): ByteArray + + fun close() + + fun getIndexCrc(indexId: Int): Int + + fun archiveCount(indexId: Int, archiveId: Int): Int + + fun lastFileId(indexId: Int, archive: Int): Int + + fun lastArchiveId(indexId: Int): Int + + fun getArchiveId(index: Int, name: String): Int + + fun getArchiveId(index: Int, archive: Int): Int + + fun getArchives(index: Int): IntArray + + fun write(index: Int, archive: Int, file: Int, data: ByteArray, xteas: IntArray? = null) + + fun update(): Boolean + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/CacheDelegate.kt b/src/main/kotlin/cacheops/cache/CacheDelegate.kt new file mode 100644 index 0000000..f9fb069 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/CacheDelegate.kt @@ -0,0 +1,88 @@ +package cacheops.cache + +import com.displee.cache.CacheLibrary +import com.displee.cache.index.Index255 +import java.math.BigInteger + +class CacheDelegate(directory: String) : Cache { + + private val delegate = CacheLibrary(directory) + + init { + println("Cache read from $directory") + } + + override var index255: Index255? + get() = delegate.index255 + set(value) { + delegate.index255 = value + } + + + override fun getFile(index: Int, archive: Int, file: Int, xtea: IntArray?) = + delegate.data(index, archive, file, xtea) + + override fun getFile(index: Int, name: String, xtea: IntArray?) = delegate.data(index, name, xtea) + + override fun getArchive(indexId: Int, archiveId: Int): ByteArray? { + val index = if (indexId == 255) index255 else delegate.index(indexId) + if (index == null) { + println("Unable to find valid index for file request [indexId=$indexId, archiveId=$archiveId]}") + return null + } + val archiveSector = index.readArchiveSector(archiveId) + if (archiveSector == null) { + println("Unable to read archive sector $archiveId in index $indexId") + return null + } + return archiveSector.data + } + + override fun generateOldVersionTable(): ByteArray = delegate.generateOldUkeys() + + override fun generateNewVersionTable(exponent: BigInteger, modulus: BigInteger) = delegate.generateNewUkeys(exponent, modulus) + + override fun close() = delegate.close() + + override fun getIndexCrc(indexId: Int): Int { + return delegate.index(indexId).crc + } + + override fun archiveCount(indexId: Int, archiveId: Int): Int { + return delegate.index(indexId).archive(archiveId)?.fileIds()?.size ?: 0 + } + + override fun lastFileId(indexId: Int, archive: Int): Int { + return delegate.index(indexId).archive(archive)?.last()?.id ?: -1 + } + + override fun lastArchiveId(indexId: Int): Int { + return delegate.index(indexId).last()?.id ?: 0 + } + + override fun getArchiveId(index: Int, name: String): Int { + return delegate.index(index).archiveId(name) + } + + override fun getArchiveId(index: Int, hash: Int): Int { + delegate.index(index).archives().forEach { archive -> + if(archive.hashName == hash) { + return archive.id + } + } + return -1 + } + + override fun getArchives(index: Int): IntArray { + return delegate.index(index).archiveIds() + } + + override fun write(index: Int, archive: Int, file: Int, data: ByteArray, xteas: IntArray?) { + delegate.put(index, archive, file, data, xteas) + } + + override fun update(): Boolean { + delegate.update() + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/Definition.kt b/src/main/kotlin/cacheops/cache/Definition.kt new file mode 100644 index 0000000..b940156 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/Definition.kt @@ -0,0 +1,5 @@ +package cacheops.cache + +interface Definition { + var id: Int +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/DefinitionDecoder.kt b/src/main/kotlin/cacheops/cache/DefinitionDecoder.kt new file mode 100644 index 0000000..86613ab --- /dev/null +++ b/src/main/kotlin/cacheops/cache/DefinitionDecoder.kt @@ -0,0 +1,131 @@ +package cacheops.cache + + +import cacheops.cache.buffer.read.BufferReader +import cacheops.cache.buffer.read.Reader +import java.util.concurrent.ConcurrentHashMap + +abstract class DefinitionDecoder(protected val cache: Cache, internal val index: Int) { + + protected val dataCache = ConcurrentHashMap() + + open val last: Int + get() = cache.lastArchiveId(index) * 256 + (cache.archiveCount(index, cache.lastArchiveId(index))) + + val indices: IntRange + get() = 0..last + + fun getOrNull(id: Int): T? { + var value = dataCache[id] + if (value == null) { + value = readData(id) + if (value != null) { + dataCache[id] = value + } + } + return value + } + + open fun get(id: Int) = getOrNull(id) ?: create() + + protected abstract fun create(): T + + protected open fun getData(archive: Int, file: Int): ByteArray? { + return cache.getFile(index, archive, file) + } + + protected open fun readData(id: Int): T? { + val archive = getArchive(id) + val file = getFile(id) + val data = getData(archive, file) + if (data != null) { + val definition = create() + definition.id = id + readLoop(definition, BufferReader(data)) + definition.changeValues() + return definition + } + return null + } + + protected open fun readLoop(definition: T, buffer: Reader) { + while (true) { + val opcode = buffer.readUnsignedByte() + if (opcode == 0) { + break + } + definition.read(opcode, buffer) + } + } + + open fun getFile(id: Int) = id + + open fun getArchive(id: Int) = id + + protected abstract fun T.read(opcode: Int, buffer: Reader) + + protected open fun T.changeValues() { + } + + open fun clear() { + dataCache.clear() + } + + fun forEach(function: (T) -> Unit) { + for (i in indices) { + val def = getOrNull(i) ?: continue + function.invoke(def) + } + } + + companion object { + + fun byteToChar(b: Byte): Char { + var i = 0xff and b.toInt() + require(i != 0) { "Non cp1252 character 0x" + i.toString(16) + " provided" } + if (i in 128..159) { + var char = UNICODE_TABLE[i - 128].toInt() + if (char == 0) { + char = 63 + } + i = char + } + return i.toChar() + } + + private var UNICODE_TABLE = charArrayOf( + '\u20ac', + '\u0000', + '\u201a', + '\u0192', + '\u201e', + '\u2026', + '\u2020', + '\u2021', + '\u02c6', + '\u2030', + '\u0160', + '\u2039', + '\u0152', + '\u0000', + '\u017d', + '\u0000', + '\u0000', + '\u2018', + '\u2019', + '\u201c', + '\u201d', + '\u2022', + '\u2013', + '\u2014', + '\u02dc', + '\u2122', + '\u0161', + '\u203a', + '\u0153', + '\u0000', + '\u017e', + '\u0178' + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/DefinitionEncoder.kt b/src/main/kotlin/cacheops/cache/DefinitionEncoder.kt new file mode 100644 index 0000000..f715753 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/DefinitionEncoder.kt @@ -0,0 +1,7 @@ +package cacheops.cache + +import cacheops.cache.buffer.write.Writer + +interface DefinitionEncoder { + fun Writer.encode(definition: T) +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/buffer/read/BufferReader.kt b/src/main/kotlin/cacheops/cache/buffer/read/BufferReader.kt new file mode 100644 index 0000000..2e8a06b --- /dev/null +++ b/src/main/kotlin/cacheops/cache/buffer/read/BufferReader.kt @@ -0,0 +1,223 @@ +package cacheops.cache.buffer.read + +import java.nio.ByteBuffer + +class BufferReader( + val buffer: ByteBuffer +) : Reader { + + constructor(array: ByteArray) : this(buffer = ByteBuffer.wrap(array)) + + override val length: Int = buffer.remaining() + private var bitIndex = 0 + + override fun readByte(): Int { + return buffer.get().toInt() + } + + override fun readByteAdd(): Int { + return (readByte() - 128).toByte().toInt() + } + + override fun readByteInverse(): Int { + return -readByte() + } + + override fun readByteSubtract(): Int { + return (readByteInverse() + 128).toByte().toInt() + } + + override fun readUnsignedByte(): Int { + return readByte() and 0xff + } + + override fun readShort(): Int { + return (readByte() shl 8) or readUnsignedByte() + } + + override fun readShortAdd(): Int { + return (readByte() shl 8) or readUnsignedByteAdd() + } + + override fun readUnsignedShortAdd(): Int { + return (readByte() shl 8) or ((readByte() - 128) and 0xff) + } + + override fun readShortLittle(): Int { + return readUnsignedByte() or (readByte() shl 8) + } + + override fun readShortAddLittle(): Int { + return readUnsignedByteAdd() or (readByte() shl 8) + } + + override fun readUnsignedByteAdd(): Int { + return (readByte() - 128).toByte().toInt() + } + + override fun readUnsignedShort(): Int { + return (readUnsignedByte() shl 8) or readUnsignedByte() + } + + override fun readUnsignedShortLittle(): Int { + return readUnsignedByte() or (readUnsignedByte() shl 8) + } + + override fun readMedium(): Int { + return (readByte() shl 16) or (readByte() shl 8) or readUnsignedByte() + } + + override fun readUnsignedMedium(): Int { + return (readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte() + } + + override fun readInt(): Int { + return (readUnsignedByte() shl 24) or (readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte() + } + + override fun readIntInverseMiddle(): Int { + return (readByte() shl 16) or (readByte() shl 24) or readUnsignedByte() or (readByte() shl 8) + } + + override fun readIntLittle(): Int { + return readUnsignedByte() or (readByte() shl 8) or (readByte() shl 16) or (readByte() shl 24) + } + + override fun readUnsignedIntMiddle(): Int { + return (readUnsignedByte() shl 8) or readUnsignedByte() or (readUnsignedByte() shl 24) or (readUnsignedByte() shl 16) + } + + override fun readSmart(): Int { + val peek = readUnsignedByte() + return if (peek < 128) { + peek and 0xFF + } else { + (peek shl 8 or readUnsignedByte()) - 32768 + } + } + + override fun readBigSmart(): Int { + val peek = readByte() + return if (peek < 0) { + ((peek shl 24) or (readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte()) and 0x7fffffff + } else { + val value = (peek shl 8) or readUnsignedByte() + if (value == 32767) -1 else value + } + } + + override fun readLargeSmart(): Int { + var baseValue = 0 + var lastValue = readSmart() + while (lastValue == 32767) { + lastValue = readSmart() + baseValue += 32767 + } + return baseValue + lastValue + } + + override fun readLong(): Long { + val first = readInt().toLong() and 0xffffffffL + val second = readInt().toLong() and 0xffffffffL + return second + (first shl 32) + } + + override fun readString(): String { + val sb = StringBuilder() + var b: Int + while (buffer.hasRemaining()) { + b = readByte() + if (b == 0) { + break + } + sb.append(b.toChar()) + } + return sb.toString() + } + + override fun readBytes(value: ByteArray) { + buffer.get(value) + } + + override fun readBytes(array: ByteArray, offset: Int, length: Int) { + buffer.get(array, offset, length) + } + + override fun skip(amount: Int) { + buffer.position(buffer.position() + amount) + } + + override fun position(): Int { + return buffer.position() + } + + override fun position(index: Int) { + buffer.position(index) + } + + override fun array(): ByteArray { + return buffer.array() + } + + override fun readableBytes(): Int { + return buffer.remaining() + } + + override fun startBitAccess(): Reader { + bitIndex = buffer.position() * 8 + return this + } + + override fun finishBitAccess(): Reader { + buffer.position((bitIndex + 7) / 8) + return this + } + + @Suppress("NAME_SHADOWING") + override fun readBits(bitCount: Int): Int { + if (bitCount < 0 || bitCount > 32) { + throw IllegalArgumentException("Number of bits must be between 1 and 32 inclusive") + } + + var bitCount = bitCount + var bytePos = bitIndex shr 3 + var bitOffset = 8 - (bitIndex and 7) + var value = 0 + bitIndex += bitCount + + while (bitCount > bitOffset) { + value += buffer.get(bytePos++).toInt() and BIT_MASKS[bitOffset] shl bitCount - bitOffset + bitCount -= bitOffset + bitOffset = 8 + } + value += if (bitCount == bitOffset) { + buffer.get(bytePos).toInt() and BIT_MASKS[bitOffset] + } else { + buffer.get(bytePos).toInt() shr bitOffset - bitCount and BIT_MASKS[bitCount] + } + return value + } + + fun readIncrSmallSmart(): Int { + var var3: Int = readSmart() + var value = 0 + while (32767 == var3) { + var3 = readSmart() + value += 32767 + } + value += var3 + return value + } + + companion object { + /** + * Bit masks for [readBits] + */ + private val BIT_MASKS = IntArray(32) + + init { + for (i in BIT_MASKS.indices) + BIT_MASKS[i] = (1 shl i) - 1 + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/buffer/read/Reader.kt b/src/main/kotlin/cacheops/cache/buffer/read/Reader.kt new file mode 100644 index 0000000..270e376 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/buffer/read/Reader.kt @@ -0,0 +1,116 @@ +package cacheops.cache.buffer.read + +interface Reader { + + /** + * Starting length of the packet + */ + val length: Int + + fun readBoolean() = readByte() == 1 + + fun readBooleanAdd() = readByteAdd() == 1 + + fun readBooleanInverse() = readByteInverse() == 1 + + fun readBooleanSubtract() = readByteSubtract() == 1 + + fun readUnsignedBoolean() = readUnsignedByte() == 1 + + fun readByte(): Int + + fun readByteAdd(): Int + + fun readByteInverse(): Int + + fun readByteSubtract(): Int + + fun readUnsignedByte(): Int + + fun readUnsignedByteAdd(): Int + + fun readShort(): Int + + fun readShortAdd(): Int + + fun readShortLittle(): Int + + fun readShortAddLittle(): Int + + fun readUnsignedShort(): Int + + fun readUnsignedShortLittle(): Int + + fun readUnsignedShortAdd(): Int + + fun readMedium(): Int + + fun readUnsignedMedium(): Int + + fun readInt(): Int + + fun readIntInverseMiddle(): Int + + fun readIntLittle(): Int + + fun readUnsignedIntMiddle(): Int + + fun readSmart(): Int + + fun readBigSmart(): Int + + fun readLargeSmart(): Int + + fun readLong(): Long + + fun readString(): String + + /** + * Reads all bytes into [ByteArray] + * @param value The array to be written to. + */ + fun readBytes(value: ByteArray) + + /** + * Reads [length] number of bytes starting at [offset] to [array]. + * @param array The [ByteArray] to be written to + * @param offset Destination index + * @param length Number of bytes to read + */ + fun readBytes(array: ByteArray, offset: Int, length: Int = array.size) + + /** + * Skips the [amount] bytes. + * @param amount Number of bytes to skip + */ + fun skip(amount: Int) + + fun position(): Int + + fun array(): ByteArray + + fun position(index: Int) + + /** + * Returns the remaining number of readable bytes. + * @return [Int] + */ + fun readableBytes(): Int + + /** + * Enables individual decoded byte writing aka 'bit access' + */ + fun startBitAccess(): Reader + + /** + * Disables 'bit access' + */ + fun finishBitAccess(): Reader + + /** + * Writes a bit during 'bit access' + * @param bitCount number of bits to be written + * @param value bit value to be set + */ + fun readBits(bitCount: Int): Int +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/buffer/write/BufferWriter.kt b/src/main/kotlin/cacheops/cache/buffer/write/BufferWriter.kt new file mode 100644 index 0000000..24e29f5 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/buffer/write/BufferWriter.kt @@ -0,0 +1,206 @@ +package cacheops.cache.buffer.write + +import java.nio.ByteBuffer + +/** + * All functions relative to writing directly to a packet are done by this class + */ +class BufferWriter( + capacity: Int = 64, + private val buffer: ByteBuffer = ByteBuffer.allocate(capacity) +) : Writer { + + private var bitIndex = 0 + + override fun writeByte(value: Int) { + buffer.put(value.toByte()) + } + + override fun writeByteAdd(value: Int) { + writeByte(value + 128) + } + + override fun writeByteInverse(value: Int) { + writeByte(-value) + } + + override fun writeByteSubtract(value: Int) { + writeByte(-value + 128) + } + + override fun setByte(index: Int, value: Int) { + buffer.put(index, value.toByte()) + } + + override fun writeShort(value: Int) { + writeByte(value shr 8) + writeByte(value) + } + + override fun writeShortAdd(value: Int) { + writeByte(value shr 8) + writeByteAdd(value) + } + + override fun writeShortLittle(value: Int) { + writeByte(value) + writeByte(value shr 8) + } + + override fun writeShortAddLittle(value: Int) { + writeByteAdd(value) + writeByte(value shr 8) + } + + override fun writeMedium(value: Int) { + writeByte(value shr 16) + writeByte(value shr 8) + writeByte(value) + } + + override fun writeInt(value: Int) { + writeByte(value shr 24) + writeByte(value shr 16) + writeByte(value shr 8) + writeByte(value) + } + + override fun writeIntMiddle(value: Int) { + writeByte(value shr 8) + writeByte(value) + writeByte(value shr 24) + writeByte(value shr 16) + } + + override fun writeIntInverse(value: Int) { + writeByte(value shr 8) + writeByte(value shr 24) + writeByte(value shr 16) + writeByteInverse(value) + } + + override fun writeIntInverseMiddle(value: Int) { + writeByte(value shr 16) + writeByte(value shr 24) + writeByte(value) + writeByte(value shr 8) + } + + override fun writeIntLittle(value: Int) { + writeByte(value) + writeByte(value shr 8) + writeByte(value shr 16) + writeByte(value shr 24) + } + + override fun writeIntInverseLittle(value: Int) { + writeByteInverse(value) + writeByte(value shr 8) + writeByte(value shr 16) + writeByte(value shr 24) + } + + override fun writeLong(value: Long) { + writeByte((value shr 56).toInt()) + writeByte((value shr 48).toInt()) + writeByte((value shr 40).toInt()) + writeByte((value shr 32).toInt()) + writeByte((value shr 24).toInt()) + writeByte((value shr 16).toInt()) + writeByte((value shr 8).toInt()) + writeByte(value.toInt()) + } + + override fun writeBytes(value: ByteArray) { + buffer.put(value) + } + + override fun writeBytes(data: ByteArray, offset: Int, length: Int) { + buffer.put(data, offset, length) + } + + override fun startBitAccess() { + bitIndex = buffer.position() * 8 + } + + override fun finishBitAccess() { + buffer.position((bitIndex + 7) / 8) + } + + override fun writeBits(bitCount: Int, value: Boolean) { + writeBits(bitCount, if (value) 1 else 0) + } + + override fun writeBits(bitCount: Int, value: Int) { + var numBits = bitCount + + var byteIndex = bitIndex shr 3 + var bitOffset = 8 - (bitIndex and 7) + bitIndex += numBits + + var tmp: Int + var max: Int + while (numBits > bitOffset) { + tmp = buffer.get(byteIndex).toInt() + max = BIT_MASKS[bitOffset] + tmp = tmp and max.inv() or (value shr numBits - bitOffset and max) + buffer.put(byteIndex++, tmp.toByte()) + numBits -= bitOffset + bitOffset = 8 + } + + tmp = buffer.get(byteIndex).toInt() + max = BIT_MASKS[numBits] + if (numBits == bitOffset) { + tmp = tmp and max.inv() or (value and max) + } else { + tmp = tmp and (max shl bitOffset - numBits).inv() + tmp = tmp or (value and max shl bitOffset - numBits) + } + buffer.put(byteIndex, tmp.toByte()) + } + + override fun position(): Int { + return buffer.position() + } + + override fun position(index: Int) { + buffer.position(index) + } + + override fun toArray(): ByteArray { + val data = ByteArray(position()) + System.arraycopy(buffer.array(), 0, data, 0, data.size) + return data + } + + override fun array(): ByteArray { + return buffer.array() + } + + override fun clear() { + buffer.clear() + } + + override fun remaining(): Int { + return buffer.remaining() + } + + override fun writeIncrSmallSmart(value: Int) { + if (value < 128) { + writeByte(value) + } else { + writeShort(0x8000 or value) + } + } + + companion object { + val BIT_MASKS = IntArray(32) + + init { + for (i in BIT_MASKS.indices) { + BIT_MASKS[i] = (1 shl i) - 1 + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/buffer/write/Writer.kt b/src/main/kotlin/cacheops/cache/buffer/write/Writer.kt new file mode 100644 index 0000000..95bcbcf --- /dev/null +++ b/src/main/kotlin/cacheops/cache/buffer/write/Writer.kt @@ -0,0 +1,101 @@ +package cacheops.cache.buffer.write + +/** + * All functions relative to writing directly to a packet are done by this class + */ +interface Writer { + + fun setByte(index: Int, value: Int) + + fun writeByte(value: Int) + + fun writeByteAdd(value: Int) + + fun writeByteInverse(value: Int) + + fun writeByteSubtract(value: Int) + + fun writeByte(value: Boolean) { + writeByte(if (value) 1 else 0) + } + + fun writeShort(value: Int) + + fun writeShortAdd(value: Int) + + fun writeShortLittle(value: Int) + + fun writeShortAddLittle(value: Int) + + fun writeMedium(value: Int) + + fun writeInt(value: Int) + + fun writeIntMiddle(value: Int) + + fun writeIntInverse(value: Int) + + fun writeIntInverseMiddle(value: Int) + + fun writeIntLittle(value: Int) + + fun writeIntInverseLittle(value: Int) + + fun writeLong(value: Long) + + fun writeSmart(value: Int) { + if (value >= 128) { + writeShort(value + 32768) + } else { + writeByte(value) + } + } + + fun writeString(value: String?) { + if (value != null) { + writeBytes(value.toByteArray()) + } + writeByte(0) + } + + fun writePrefixedString(value: String) { + writeByte(0) + writeBytes(value.toByteArray()) + writeByte(0) + } + + fun writeBytes(value: ByteArray) + + fun writeBytes(data: ByteArray, offset: Int, length: Int) + + fun startBitAccess() + + fun finishBitAccess() + + fun writeBits(bitCount: Int, value: Boolean) { + writeBits(bitCount, if (value) 1 else 0) + } + + fun writeBits(bitCount: Int, value: Int) + + fun skip(position: Int) { + for (i in 0 until position) { + writeByte(0) + } + } + + fun position(): Int + + fun position(index: Int) + + fun toArray(): ByteArray + + fun array(): ByteArray + + fun clear() + + fun remaining(): Int + + fun writeIncrSmallSmart(value: Int) + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/ColorPalette.kt b/src/main/kotlin/cacheops/cache/definition/ColorPalette.kt new file mode 100644 index 0000000..c08b8b4 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/ColorPalette.kt @@ -0,0 +1,15 @@ +package cacheops.cache.definition + +import cacheops.cache.buffer.read.Reader + +interface ColorPalette { + var recolourPalette: ByteArray? + + fun readColourPalette(buffer: Reader) { + val length = buffer.readUnsignedByte() + recolourPalette = ByteArray(length) + repeat(length) { count -> + recolourPalette!![count] = buffer.readByte().toByte() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/Extra.kt b/src/main/kotlin/cacheops/cache/definition/Extra.kt new file mode 100644 index 0000000..241e065 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/Extra.kt @@ -0,0 +1,16 @@ +package cacheops.cache.definition + +@Suppress("UNCHECKED_CAST") +interface Extra { + + var extras: Map + + operator fun get(key: String): T = extras.getValue(key) as T + + fun has(key: String) = extras.containsKey(key) + + fun getOrNull(key: String): Any? = extras[key] + + operator fun get(key: String, defaultValue: T) = getOrNull(key) as? T ?: defaultValue + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/Parameterized.kt b/src/main/kotlin/cacheops/cache/definition/Parameterized.kt new file mode 100644 index 0000000..0e26613 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/Parameterized.kt @@ -0,0 +1,28 @@ +package cacheops.cache.definition + +import cacheops.cache.buffer.read.Reader + +@Suppress("UNCHECKED_CAST") +interface Parameterized { + + var params: HashMap? + + fun getParam(key: Long) = params?.get(key) as T + + fun getParamOrNull(key: Long) = params?.get(key) as? T + + fun getParam(key: Long, default: T) = params?.get(key) as? T ?: default + + fun readParameters(buffer: Reader) { + val length = buffer.readUnsignedByte() + if (length == 0) { + return + } + params = HashMap() + repeat(length) { + val string = buffer.readUnsignedBoolean() + val id = buffer.readUnsignedMedium().toLong() + params!![id] = if (string) buffer.readString() else buffer.readInt() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/Recolourable.kt b/src/main/kotlin/cacheops/cache/definition/Recolourable.kt new file mode 100644 index 0000000..5a746b2 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/Recolourable.kt @@ -0,0 +1,30 @@ +package cacheops.cache.definition + +import cacheops.cache.buffer.read.Reader + +interface Recolourable { + var originalColours: ShortArray? + var modifiedColours: ShortArray? + var originalTextureColours: ShortArray? + var modifiedTextureColours: ShortArray? + + fun readColours(buffer: Reader) { + val length = buffer.readUnsignedByte() + originalColours = ShortArray(length) + modifiedColours = ShortArray(length) + repeat(length) { count -> + originalColours!![count] = buffer.readUnsignedShort().toShort() + modifiedColours!![count] = buffer.readUnsignedShort().toShort() + } + } + + fun readTextures(buffer: Reader) { + val length = buffer.readUnsignedByte() + originalTextureColours = ShortArray(length) + modifiedTextureColours = ShortArray(length) + repeat(length) { count -> + originalTextureColours!![count] = buffer.readUnsignedShort().toShort() + modifiedTextureColours!![count] = buffer.readUnsignedShort().toShort() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/AnimationDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/AnimationDefinition.kt new file mode 100644 index 0000000..4c15cd2 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/AnimationDefinition.kt @@ -0,0 +1,124 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition +import cacheops.cache.definition.Extra + +data class AnimationDefinition( + override var id: Int = -1, + var durations: IntArray? = null, + var primaryFrames: IntArray? = null, + var loopOffset: Int = -1, + var interleaveOrder: BooleanArray? = null, + var priority: Int = 5, + var leftHandItem: Int = -1, + var rightHandItem: Int = -1, + var maxLoops: Int = 99, + var animatingPrecedence: Int = -1, + var walkingPrecedence: Int = -1, + var replayMode: Int = 2, + var secondaryFrames: IntArray? = null, + var anIntArrayArray700: Array? = null, + var aBoolean691: Boolean = false, + var tweened: Boolean = false, + var aBoolean699: Boolean = false, + var anIntArray701: IntArray? = null, + var anIntArray690: IntArray? = null, + var anIntArray692: IntArray? = null, + override var extras: Map = emptyMap() +) : Definition, Extra { + + val time: Long + get() = (durations?.sum() ?: 0) * 20L + + val clientTicks: Int + get() { + if (durations == null) { + return 0 + } + var total = 0 + for (i in 0 until durations!!.size - 3) { + total += durations!![i] + } + return total + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AnimationDefinition + + if (id != other.id) return false + if (durations != null) { + if (other.durations == null) return false + if (!durations!!.contentEquals(other.durations!!)) return false + } else if (other.durations != null) return false + if (primaryFrames != null) { + if (other.primaryFrames == null) return false + if (!primaryFrames!!.contentEquals(other.primaryFrames!!)) return false + } else if (other.primaryFrames != null) return false + if (loopOffset != other.loopOffset) return false + if (interleaveOrder != null) { + if (other.interleaveOrder == null) return false + if (!interleaveOrder!!.contentEquals(other.interleaveOrder!!)) return false + } else if (other.interleaveOrder != null) return false + if (priority != other.priority) return false + if (leftHandItem != other.leftHandItem) return false + if (rightHandItem != other.rightHandItem) return false + if (maxLoops != other.maxLoops) return false + if (animatingPrecedence != other.animatingPrecedence) return false + if (walkingPrecedence != other.walkingPrecedence) return false + if (replayMode != other.replayMode) return false + if (secondaryFrames != null) { + if (other.secondaryFrames == null) return false + if (!secondaryFrames!!.contentEquals(other.secondaryFrames!!)) return false + } else if (other.secondaryFrames != null) return false + if (anIntArrayArray700 != null) { + if (other.anIntArrayArray700 == null) return false + if (!anIntArrayArray700!!.contentDeepEquals(other.anIntArrayArray700!!)) return false + } else if (other.anIntArrayArray700 != null) return false + if (aBoolean691 != other.aBoolean691) return false + if (tweened != other.tweened) return false + if (aBoolean699 != other.aBoolean699) return false + if (anIntArray701 != null) { + if (other.anIntArray701 == null) return false + if (!anIntArray701!!.contentEquals(other.anIntArray701!!)) return false + } else if (other.anIntArray701 != null) return false + if (anIntArray690 != null) { + if (other.anIntArray690 == null) return false + if (!anIntArray690!!.contentEquals(other.anIntArray690!!)) return false + } else if (other.anIntArray690 != null) return false + if (anIntArray692 != null) { + if (other.anIntArray692 == null) return false + if (!anIntArray692!!.contentEquals(other.anIntArray692!!)) return false + } else if (other.anIntArray692 != null) return false + if (extras != other.extras) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + (durations?.contentHashCode() ?: 0) + result = 31 * result + (primaryFrames?.contentHashCode() ?: 0) + result = 31 * result + loopOffset + result = 31 * result + (interleaveOrder?.contentHashCode() ?: 0) + result = 31 * result + priority + result = 31 * result + leftHandItem + result = 31 * result + rightHandItem + result = 31 * result + maxLoops + result = 31 * result + animatingPrecedence + result = 31 * result + walkingPrecedence + result = 31 * result + replayMode + result = 31 * result + (secondaryFrames?.contentHashCode() ?: 0) + result = 31 * result + (anIntArrayArray700?.contentDeepHashCode() ?: 0) + result = 31 * result + aBoolean691.hashCode() + result = 31 * result + tweened.hashCode() + result = 31 * result + aBoolean699.hashCode() + result = 31 * result + (anIntArray701?.contentHashCode() ?: 0) + result = 31 * result + (anIntArray690?.contentHashCode() ?: 0) + result = 31 * result + (anIntArray692?.contentHashCode() ?: 0) + result = 31 * result + extras.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/AtmosphereDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/AtmosphereDefinition.kt new file mode 100644 index 0000000..bfef0c8 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/AtmosphereDefinition.kt @@ -0,0 +1,19 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition +import ext.Vector3f + +//TODO: Maybe finish and come back who knows. +data class AtmosphereDefinition( + override var id: Int = -1, + val DEFAULT_SKY_COLOR: Int = 0x17c439a8, + val DEFAULT_SUN_COLOR: Int = 0x36dac459, + val DEFAULT_SUN_X_POSITION: Float = 0.69921875F, + val DEFAULT_SUN_Y_POSITION: Float = 1.2F, + val DEFAULT_SUN_X_ANGLE: Int = -50, + val DEFAULT_SUN_Y_ANGLE: Int = -60, + val DEFAULT_SUN_Z_ANGLE: Int = -50, + val DEFAULT_SUN_ANGLE: Vector3f = Vector3f(DEFAULT_SUN_X_ANGLE.toFloat(), DEFAULT_SUN_Y_ANGLE.toFloat(), DEFAULT_SUN_Z_ANGLE.toFloat()), + val DEFAULT_SUN_SHININESS: Float = 1.1523438F, + +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/BlendedTextureDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/BlendedTextureDefinition.kt new file mode 100644 index 0000000..27ad4d2 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/BlendedTextureDefinition.kt @@ -0,0 +1,9 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition + +data class BlendedTextureDefinition( + override var id: Int = -1, + var renderOnMap: Boolean = false, + var blendedColor: Int = -1 +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/BodyDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/BodyDefinition.kt new file mode 100644 index 0000000..f6bf5d3 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/BodyDefinition.kt @@ -0,0 +1,16 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition + +/** + * Equipment Slots Definition + */ +@Suppress("ArrayInDataClass") +data class BodyDefinition( + override var id: Int = -1, + var disabledSlots: IntArray = IntArray(0), + var anInt4506: Int = -1, + var anInt4504: Int = -1, + var anIntArray4501: IntArray? = null, + var anIntArray4507: IntArray? = null +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/ClientScriptDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/ClientScriptDefinition.kt new file mode 100644 index 0000000..2c4fe41 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/ClientScriptDefinition.kt @@ -0,0 +1,71 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition + +data class ClientScriptDefinition( + override var id: Int = -1, + var intArgumentCount: Int = 0, + var stringVariableCount: Int = 0, + var longVariableCount: Int = 0, + var intVariableCount: Int = 0, + var stringArgumentCount: Int = 0, + var longArgumentCount: Int = 0, + var switchStatementIndices: Array>>? = null, + var name: String? = null, + var instructions: IntArray = intArrayOf(), + var stringOperands: Array? = null, + var longOperands: LongArray? = null, + var intOperands: IntArray? = null +) : Definition { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ClientScriptDefinition + + if (id != other.id) return false + if (intArgumentCount != other.intArgumentCount) return false + if (stringVariableCount != other.stringVariableCount) return false + if (longVariableCount != other.longVariableCount) return false + if (intVariableCount != other.intVariableCount) return false + if (stringArgumentCount != other.stringArgumentCount) return false + if (longArgumentCount != other.longArgumentCount) return false + if (switchStatementIndices != null) { + if (other.switchStatementIndices == null) return false + if (!switchStatementIndices!!.contentEquals(other.switchStatementIndices!!)) return false + } else if (other.switchStatementIndices != null) return false + if (name != other.name) return false + if (!instructions.contentEquals(other.instructions)) return false + if (stringOperands != null) { + if (other.stringOperands == null) return false + if (!stringOperands!!.contentEquals(other.stringOperands!!)) return false + } else if (other.stringOperands != null) return false + if (longOperands != null) { + if (other.longOperands == null) return false + if (!longOperands!!.contentEquals(other.longOperands!!)) return false + } else if (other.longOperands != null) return false + if (intOperands != null) { + if (other.intOperands == null) return false + if (!intOperands!!.contentEquals(other.intOperands!!)) return false + } else if (other.intOperands != null) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + intArgumentCount + result = 31 * result + stringVariableCount + result = 31 * result + longVariableCount + result = 31 * result + intVariableCount + result = 31 * result + stringArgumentCount + result = 31 * result + longArgumentCount + result = 31 * result + (switchStatementIndices?.contentHashCode() ?: 0) + result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + instructions.contentHashCode() + result = 31 * result + (stringOperands?.contentHashCode() ?: 0) + result = 31 * result + (longOperands?.contentHashCode() ?: 0) + result = 31 * result + (intOperands?.contentHashCode() ?: 0) + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/EnumDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/EnumDefinition.kt new file mode 100644 index 0000000..fafc6bd --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/EnumDefinition.kt @@ -0,0 +1,19 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition + +data class EnumDefinition( + override var id: Int = -1, + var keyType: Char = 0.toChar(), + var valueType: Char = 0.toChar(), + var defaultString: String = "null", + var defaultInt: Int = 0, + var length: Int = 0, + var map: HashMap? = null +) : Definition { + fun getKey(value: Any) = map?.filterValues { it == value }?.keys?.firstOrNull() ?: -1 + + fun getInt(id: Int) = map?.get(id) as? Int ?: defaultInt + + fun getString(id: Int) = map?.get(id) as? String ?: defaultString +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/GraphicDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/GraphicDefinition.kt new file mode 100644 index 0000000..15c93e3 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/GraphicDefinition.kt @@ -0,0 +1,82 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition +import cacheops.cache.definition.Extra +import cacheops.cache.definition.Recolourable + +data class GraphicDefinition( + override var id: Int = -1, + var modelId: Int = 0, + var animationId: Int = -1, + var sizeXY: Int = 128, + var sizeZ: Int = 128, + var rotation: Int = 0, + var ambience: Int = 0, + var contrast: Int = 0, + var aByte2381: Byte = 0, + var anInt2385: Int = -1, + var aBoolean2402: Boolean = false, + override var originalColours: ShortArray? = null, + override var modifiedColours: ShortArray? = null, + override var originalTextureColours: ShortArray? = null, + override var modifiedTextureColours: ShortArray? = null, + override var extras: Map = emptyMap() +) : Definition, Recolourable, Extra { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GraphicDefinition + + if (id != other.id) return false + if (modelId != other.modelId) return false + if (animationId != other.animationId) return false + if (sizeXY != other.sizeXY) return false + if (sizeZ != other.sizeZ) return false + if (rotation != other.rotation) return false + if (ambience != other.ambience) return false + if (contrast != other.contrast) return false + if (aByte2381 != other.aByte2381) return false + if (anInt2385 != other.anInt2385) return false + if (aBoolean2402 != other.aBoolean2402) return false + if (originalColours != null) { + if (other.originalColours == null) return false + if (!originalColours!!.contentEquals(other.originalColours!!)) return false + } else if (other.originalColours != null) return false + if (modifiedColours != null) { + if (other.modifiedColours == null) return false + if (!modifiedColours!!.contentEquals(other.modifiedColours!!)) return false + } else if (other.modifiedColours != null) return false + if (originalTextureColours != null) { + if (other.originalTextureColours == null) return false + if (!originalTextureColours!!.contentEquals(other.originalTextureColours!!)) return false + } else if (other.originalTextureColours != null) return false + if (modifiedTextureColours != null) { + if (other.modifiedTextureColours == null) return false + if (!modifiedTextureColours!!.contentEquals(other.modifiedTextureColours!!)) return false + } else if (other.modifiedTextureColours != null) return false + if (extras != other.extras) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + modelId + result = 31 * result + animationId + result = 31 * result + sizeXY + result = 31 * result + sizeZ + result = 31 * result + rotation + result = 31 * result + ambience + result = 31 * result + contrast + result = 31 * result + aByte2381 + result = 31 * result + anInt2385 + result = 31 * result + aBoolean2402.hashCode() + result = 31 * result + (originalColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedColours?.contentHashCode() ?: 0) + result = 31 * result + (originalTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedTextureColours?.contentHashCode() ?: 0) + result = 31 * result + extras.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/IndexedSprite.kt b/src/main/kotlin/cacheops/cache/definition/data/IndexedSprite.kt new file mode 100644 index 0000000..a861256 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/IndexedSprite.kt @@ -0,0 +1,36 @@ +package cacheops.cache.definition.data + +import java.awt.image.BufferedImage + +@Suppress("ArrayInDataClass") +data class IndexedSprite( + var offsetX: Int = 0, + var offsetY: Int = 0, + var width: Int = 0, + var height: Int = 0, + var deltaHeight: Int = 0, + var deltaWidth: Int = 0, + var alpha: ByteArray? = null +) { + lateinit var raster: ByteArray + lateinit var palette: IntArray + + fun toBufferedImage(): BufferedImage { + val bi = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + for (x in 0 until width) { + for (y in 0 until height) { + val i = x + y * width + if (alpha == null) { + val colour = palette[raster[i].toInt() and 255] + if (colour != 0) { + bi.setRGB(x, y, -16777216 or colour) + } + } else { + bi.setRGB(x, y, palette[raster[i].toInt() and 255] or (alpha!![i].toInt() shl 24)) + } + } + } + return bi + } + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/Instructions.kt b/src/main/kotlin/cacheops/cache/definition/data/Instructions.kt new file mode 100644 index 0000000..ab7f2bf --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/Instructions.kt @@ -0,0 +1,10 @@ +package cacheops.cache.definition.data + +object Instructions { + const val PUSH_INT = 0 + const val PUSH_STRING = 3 + const val MERGE_STRINGS = 37 + const val CALL_CS2 = 40 + const val SWITCH = 51 + const val GOTO = 6 +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentDefinition.kt new file mode 100644 index 0000000..f0d0858 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentDefinition.kt @@ -0,0 +1,409 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition +import cacheops.cache.definition.Extra + +data class InterfaceComponentDefinition( + override var id: Int = -1, + var type: Int = 0, + var unknown: String? = null, + var contentType: Int = 0, + var basePositionX: Int = 0, + var basePositionY: Int = 0, + var baseWidth: Int = 0, + var baseHeight: Int = 0, + var horizontalSizeMode: Byte = 0, + var verticalSizeMode: Byte = 0, + var horizontalPositionMode: Byte = 0, + var verticalPositionMode: Byte = 0, + var parent: Int = -1, + var hidden: Boolean = false, + var disableHover: Boolean = false, + var scrollWidth: Int = 0, + var scrollHeight: Int = 0, + var colour: Int = 0, + var filled: Boolean = false, + var alpha: Int = 0, + var fontId: Int = -1, + var monochrome: Boolean = true, + var text: String = "", + var lineHeight: Int = 0, + var horizontalTextAlign: Int = 0, + var verticalTextAlign: Int = 0, + var shaded: Boolean = false, + var lineCount: Int = 0, + var defaultImage: Int = -1, + var imageRotation: Int = 0, + var aBoolean4861: Boolean = false, + var imageRepeat: Boolean = false, + var rotation: Int = 0, + var backgroundColour: Int = 0, + var flipVertical: Boolean = false, + var flipHorizontal: Boolean = false, + var aBoolean4782: Boolean = true, + var defaultMediaType: Int = 1, + var defaultMediaId: Int = 0, + var animated: Boolean = false, + var centreType: Boolean = false, + var ignoreZBuffer: Boolean = false, + var viewportX: Int = 0, + var viewportY: Int = 0, + var viewportZ: Int = 0, + var spritePitch: Int = 0, + var spriteRoll: Int = 0, + var spriteYaw: Int = 0, + var spriteScale: Int = 100, + var animation: Int = -1, + var viewportWidth: Int = 0, + var viewportHeight: Int = 0, + var lineWidth: Int = 1, + var lineMirrored: Boolean = false, + var keyRepeat: ByteArray? = null, + var keyCodes: ByteArray? = null, + var keyModifiers: IntArray? = null, + var clickable: Boolean = false, + var name: String = "", + var options: Array? = null, + var mouseIcon: IntArray? = null, + var optionOverride: String? = null, + var anInt4708: Int = 0,// Drag type + var anInt4795: Int = 0,// Drag slider? + var anInt4860: Int = 0,// Friends list icons/buttons? + var useOption: String = "", + var anInt4698: Int = -1, + var anInt4839: Int = -1,// Unused + var anInt4761: Int = -1,// Unused + var setting: InterfaceComponentSetting = InterfaceComponentSetting(0, -1), + val params: HashMap? = null, + var anObjectArray4758: Array? = null, + var mouseEnterHandler: Array? = null, + var mouseExitHandler: Array? = null, + var anObjectArray4771: Array? = null, + var anObjectArray4768: Array? = null, + var stateChangeHandler: Array? = null, + var invUpdateHandler: Array? = null, + var refreshHandler: Array? = null, + var updateHandler: Array? = null, + var anObjectArray4770: Array? = null, + var anObjectArray4751: Array? = null, + var mouseMotionHandler: Array? = null, + var mousePressedHandler: Array? = null, + var mouseDraggedHandler: Array? = null, + var mouseReleasedHandler: Array? = null, + var mouseDragPassHandler: Array? = null, + var anObjectArray4852: Array? = null, + var anObjectArray4711: Array? = null, + var anObjectArray4753: Array? = null, + var anObjectArray4688: Array? = null, + var anObjectArray4775: Array? = null, + var clientVarp: IntArray? = null, + var containers: IntArray? = null, + var anIntArray4789: IntArray? = null, + var clientVarc: IntArray? = null, + var anIntArray4805: IntArray? = null, + var hasScript: Boolean = false, + override var extras: Map = emptyMap() +) : Definition, Extra { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as InterfaceComponentDefinition + + if (id != other.id) return false + if (type != other.type) return false + if (unknown != other.unknown) return false + if (contentType != other.contentType) return false + if (basePositionX != other.basePositionX) return false + if (basePositionY != other.basePositionY) return false + if (baseWidth != other.baseWidth) return false + if (baseHeight != other.baseHeight) return false + if (horizontalSizeMode != other.horizontalSizeMode) return false + if (verticalSizeMode != other.verticalSizeMode) return false + if (horizontalPositionMode != other.horizontalPositionMode) return false + if (verticalPositionMode != other.verticalPositionMode) return false + if (parent != other.parent) return false + if (hidden != other.hidden) return false + if (disableHover != other.disableHover) return false + if (scrollWidth != other.scrollWidth) return false + if (scrollHeight != other.scrollHeight) return false + if (colour != other.colour) return false + if (filled != other.filled) return false + if (alpha != other.alpha) return false + if (fontId != other.fontId) return false + if (monochrome != other.monochrome) return false + if (text != other.text) return false + if (lineHeight != other.lineHeight) return false + if (horizontalTextAlign != other.horizontalTextAlign) return false + if (verticalTextAlign != other.verticalTextAlign) return false + if (shaded != other.shaded) return false + if (lineCount != other.lineCount) return false + if (defaultImage != other.defaultImage) return false + if (imageRotation != other.imageRotation) return false + if (aBoolean4861 != other.aBoolean4861) return false + if (imageRepeat != other.imageRepeat) return false + if (rotation != other.rotation) return false + if (backgroundColour != other.backgroundColour) return false + if (flipVertical != other.flipVertical) return false + if (flipHorizontal != other.flipHorizontal) return false + if (aBoolean4782 != other.aBoolean4782) return false + if (defaultMediaType != other.defaultMediaType) return false + if (defaultMediaId != other.defaultMediaId) return false + if (animated != other.animated) return false + if (centreType != other.centreType) return false + if (ignoreZBuffer != other.ignoreZBuffer) return false + if (viewportX != other.viewportX) return false + if (viewportY != other.viewportY) return false + if (viewportZ != other.viewportZ) return false + if (spritePitch != other.spritePitch) return false + if (spriteRoll != other.spriteRoll) return false + if (spriteYaw != other.spriteYaw) return false + if (spriteScale != other.spriteScale) return false + if (animation != other.animation) return false + if (viewportWidth != other.viewportWidth) return false + if (viewportHeight != other.viewportHeight) return false + if (lineWidth != other.lineWidth) return false + if (lineMirrored != other.lineMirrored) return false + if (keyRepeat != null) { + if (other.keyRepeat == null) return false + if (!keyRepeat!!.contentEquals(other.keyRepeat!!)) return false + } else if (other.keyRepeat != null) return false + if (keyCodes != null) { + if (other.keyCodes == null) return false + if (!keyCodes!!.contentEquals(other.keyCodes!!)) return false + } else if (other.keyCodes != null) return false + if (keyModifiers != null) { + if (other.keyModifiers == null) return false + if (!keyModifiers!!.contentEquals(other.keyModifiers!!)) return false + } else if (other.keyModifiers != null) return false + if (clickable != other.clickable) return false + if (name != other.name) return false + if (options != null) { + if (other.options == null) return false + if (!options!!.contentEquals(other.options!!)) return false + } else if (other.options != null) return false + if (mouseIcon != null) { + if (other.mouseIcon == null) return false + if (!mouseIcon!!.contentEquals(other.mouseIcon!!)) return false + } else if (other.mouseIcon != null) return false + if (optionOverride != other.optionOverride) return false + if (anInt4708 != other.anInt4708) return false + if (anInt4795 != other.anInt4795) return false + if (anInt4860 != other.anInt4860) return false + if (useOption != other.useOption) return false + if (anInt4698 != other.anInt4698) return false + if (anInt4839 != other.anInt4839) return false + if (anInt4761 != other.anInt4761) return false + if (setting != other.setting) return false + if (params != other.params) return false + if (anObjectArray4758 != null) { + if (other.anObjectArray4758 == null) return false + if (!anObjectArray4758!!.contentEquals(other.anObjectArray4758!!)) return false + } else if (other.anObjectArray4758 != null) return false + if (mouseEnterHandler != null) { + if (other.mouseEnterHandler == null) return false + if (!mouseEnterHandler!!.contentEquals(other.mouseEnterHandler!!)) return false + } else if (other.mouseEnterHandler != null) return false + if (mouseExitHandler != null) { + if (other.mouseExitHandler == null) return false + if (!mouseExitHandler!!.contentEquals(other.mouseExitHandler!!)) return false + } else if (other.mouseExitHandler != null) return false + if (anObjectArray4771 != null) { + if (other.anObjectArray4771 == null) return false + if (!anObjectArray4771!!.contentEquals(other.anObjectArray4771!!)) return false + } else if (other.anObjectArray4771 != null) return false + if (anObjectArray4768 != null) { + if (other.anObjectArray4768 == null) return false + if (!anObjectArray4768!!.contentEquals(other.anObjectArray4768!!)) return false + } else if (other.anObjectArray4768 != null) return false + if (stateChangeHandler != null) { + if (other.stateChangeHandler == null) return false + if (!stateChangeHandler!!.contentEquals(other.stateChangeHandler!!)) return false + } else if (other.stateChangeHandler != null) return false + if (invUpdateHandler != null) { + if (other.invUpdateHandler == null) return false + if (!invUpdateHandler!!.contentEquals(other.invUpdateHandler!!)) return false + } else if (other.invUpdateHandler != null) return false + if (refreshHandler != null) { + if (other.refreshHandler == null) return false + if (!refreshHandler!!.contentEquals(other.refreshHandler!!)) return false + } else if (other.refreshHandler != null) return false + if (updateHandler != null) { + if (other.updateHandler == null) return false + if (!updateHandler!!.contentEquals(other.updateHandler!!)) return false + } else if (other.updateHandler != null) return false + if (anObjectArray4770 != null) { + if (other.anObjectArray4770 == null) return false + if (!anObjectArray4770!!.contentEquals(other.anObjectArray4770!!)) return false + } else if (other.anObjectArray4770 != null) return false + if (anObjectArray4751 != null) { + if (other.anObjectArray4751 == null) return false + if (!anObjectArray4751!!.contentEquals(other.anObjectArray4751!!)) return false + } else if (other.anObjectArray4751 != null) return false + if (mouseMotionHandler != null) { + if (other.mouseMotionHandler == null) return false + if (!mouseMotionHandler!!.contentEquals(other.mouseMotionHandler!!)) return false + } else if (other.mouseMotionHandler != null) return false + if (mousePressedHandler != null) { + if (other.mousePressedHandler == null) return false + if (!mousePressedHandler!!.contentEquals(other.mousePressedHandler!!)) return false + } else if (other.mousePressedHandler != null) return false + if (mouseDraggedHandler != null) { + if (other.mouseDraggedHandler == null) return false + if (!mouseDraggedHandler!!.contentEquals(other.mouseDraggedHandler!!)) return false + } else if (other.mouseDraggedHandler != null) return false + if (mouseReleasedHandler != null) { + if (other.mouseReleasedHandler == null) return false + if (!mouseReleasedHandler!!.contentEquals(other.mouseReleasedHandler!!)) return false + } else if (other.mouseReleasedHandler != null) return false + if (mouseDragPassHandler != null) { + if (other.mouseDragPassHandler == null) return false + if (!mouseDragPassHandler!!.contentEquals(other.mouseDragPassHandler!!)) return false + } else if (other.mouseDragPassHandler != null) return false + if (anObjectArray4852 != null) { + if (other.anObjectArray4852 == null) return false + if (!anObjectArray4852!!.contentEquals(other.anObjectArray4852!!)) return false + } else if (other.anObjectArray4852 != null) return false + if (anObjectArray4711 != null) { + if (other.anObjectArray4711 == null) return false + if (!anObjectArray4711!!.contentEquals(other.anObjectArray4711!!)) return false + } else if (other.anObjectArray4711 != null) return false + if (anObjectArray4753 != null) { + if (other.anObjectArray4753 == null) return false + if (!anObjectArray4753!!.contentEquals(other.anObjectArray4753!!)) return false + } else if (other.anObjectArray4753 != null) return false + if (anObjectArray4688 != null) { + if (other.anObjectArray4688 == null) return false + if (!anObjectArray4688!!.contentEquals(other.anObjectArray4688!!)) return false + } else if (other.anObjectArray4688 != null) return false + if (anObjectArray4775 != null) { + if (other.anObjectArray4775 == null) return false + if (!anObjectArray4775!!.contentEquals(other.anObjectArray4775!!)) return false + } else if (other.anObjectArray4775 != null) return false + if (clientVarp != null) { + if (other.clientVarp == null) return false + if (!clientVarp!!.contentEquals(other.clientVarp!!)) return false + } else if (other.clientVarp != null) return false + if (containers != null) { + if (other.containers == null) return false + if (!containers!!.contentEquals(other.containers!!)) return false + } else if (other.containers != null) return false + if (anIntArray4789 != null) { + if (other.anIntArray4789 == null) return false + if (!anIntArray4789!!.contentEquals(other.anIntArray4789!!)) return false + } else if (other.anIntArray4789 != null) return false + if (clientVarc != null) { + if (other.clientVarc == null) return false + if (!clientVarc!!.contentEquals(other.clientVarc!!)) return false + } else if (other.clientVarc != null) return false + if (anIntArray4805 != null) { + if (other.anIntArray4805 == null) return false + if (!anIntArray4805!!.contentEquals(other.anIntArray4805!!)) return false + } else if (other.anIntArray4805 != null) return false + if (hasScript != other.hasScript) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + type + result = 31 * result + (unknown?.hashCode() ?: 0) + result = 31 * result + contentType + result = 31 * result + basePositionX + result = 31 * result + basePositionY + result = 31 * result + baseWidth + result = 31 * result + baseHeight + result = 31 * result + horizontalSizeMode + result = 31 * result + verticalSizeMode + result = 31 * result + horizontalPositionMode + result = 31 * result + verticalPositionMode + result = 31 * result + parent + result = 31 * result + hidden.hashCode() + result = 31 * result + disableHover.hashCode() + result = 31 * result + scrollWidth + result = 31 * result + scrollHeight + result = 31 * result + colour + result = 31 * result + filled.hashCode() + result = 31 * result + alpha + result = 31 * result + fontId + result = 31 * result + monochrome.hashCode() + result = 31 * result + text.hashCode() + result = 31 * result + lineHeight + result = 31 * result + horizontalTextAlign + result = 31 * result + verticalTextAlign + result = 31 * result + shaded.hashCode() + result = 31 * result + lineCount + result = 31 * result + defaultImage + result = 31 * result + imageRotation + result = 31 * result + aBoolean4861.hashCode() + result = 31 * result + imageRepeat.hashCode() + result = 31 * result + rotation + result = 31 * result + backgroundColour + result = 31 * result + flipVertical.hashCode() + result = 31 * result + flipHorizontal.hashCode() + result = 31 * result + aBoolean4782.hashCode() + result = 31 * result + defaultMediaType + result = 31 * result + defaultMediaId + result = 31 * result + animated.hashCode() + result = 31 * result + centreType.hashCode() + result = 31 * result + ignoreZBuffer.hashCode() + result = 31 * result + viewportX + result = 31 * result + viewportY + result = 31 * result + viewportZ + result = 31 * result + spritePitch + result = 31 * result + spriteRoll + result = 31 * result + spriteYaw + result = 31 * result + spriteScale + result = 31 * result + animation + result = 31 * result + viewportWidth + result = 31 * result + viewportHeight + result = 31 * result + lineWidth + result = 31 * result + lineMirrored.hashCode() + result = 31 * result + (keyRepeat?.contentHashCode() ?: 0) + result = 31 * result + (keyCodes?.contentHashCode() ?: 0) + result = 31 * result + (keyModifiers?.contentHashCode() ?: 0) + result = 31 * result + clickable.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + (options?.contentHashCode() ?: 0) + result = 31 * result + (mouseIcon?.contentHashCode() ?: 0) + result = 31 * result + (optionOverride?.hashCode() ?: 0) + result = 31 * result + anInt4708 + result = 31 * result + anInt4795 + result = 31 * result + anInt4860 + result = 31 * result + useOption.hashCode() + result = 31 * result + anInt4698 + result = 31 * result + anInt4839 + result = 31 * result + anInt4761 + result = 31 * result + setting.hashCode() + result = 31 * result + (params?.hashCode() ?: 0) + result = 31 * result + (anObjectArray4758?.contentHashCode() ?: 0) + result = 31 * result + (mouseEnterHandler?.contentHashCode() ?: 0) + result = 31 * result + (mouseExitHandler?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4771?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4768?.contentHashCode() ?: 0) + result = 31 * result + (stateChangeHandler?.contentHashCode() ?: 0) + result = 31 * result + (invUpdateHandler?.contentHashCode() ?: 0) + result = 31 * result + (refreshHandler?.contentHashCode() ?: 0) + result = 31 * result + (updateHandler?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4770?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4751?.contentHashCode() ?: 0) + result = 31 * result + (mouseMotionHandler?.contentHashCode() ?: 0) + result = 31 * result + (mousePressedHandler?.contentHashCode() ?: 0) + result = 31 * result + (mouseDraggedHandler?.contentHashCode() ?: 0) + result = 31 * result + (mouseReleasedHandler?.contentHashCode() ?: 0) + result = 31 * result + (mouseDragPassHandler?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4852?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4711?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4753?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4688?.contentHashCode() ?: 0) + result = 31 * result + (anObjectArray4775?.contentHashCode() ?: 0) + result = 31 * result + (clientVarp?.contentHashCode() ?: 0) + result = 31 * result + (containers?.contentHashCode() ?: 0) + result = 31 * result + (anIntArray4789?.contentHashCode() ?: 0) + result = 31 * result + (clientVarc?.contentHashCode() ?: 0) + result = 31 * result + (anIntArray4805?.contentHashCode() ?: 0) + result = 31 * result + hasScript.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentSetting.kt b/src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentSetting.kt new file mode 100644 index 0000000..3d1e0c7 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/InterfaceComponentSetting.kt @@ -0,0 +1,27 @@ +package cacheops.cache.definition.data + +data class InterfaceComponentSetting(var setting: Int, var anInt7413: Int) { + fun method2743(): Int { + return setting and 0x3fda8 shr 11 + } + + fun method2744(): Boolean { + return 0x1 and setting shr 22 != 0 + } + + fun method2745(): Int { + return 0x1d36c1 and setting shr 18 + } + + fun method2746(): Boolean { + return 0x1 and setting != 0 + } + + fun method2747(): Boolean { + return 0x1 and setting shr 21 != 0 + } + + fun unlockedSlot(slot: Int): Boolean { + return 0x1 and setting shr slot + 1 != 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/InterfaceDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/InterfaceDefinition.kt new file mode 100644 index 0000000..3fd9673 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/InterfaceDefinition.kt @@ -0,0 +1,11 @@ +package cacheops.cache.definition.data + +import cacheops.cache.Definition +import cacheops.cache.definition.Extra + +@Suppress("ArrayInDataClass") +data class InterfaceDefinition( + override var id: Int = -1, + var components: Map? = null, + override var extras: Map = emptyMap() +) : Definition, Extra \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/ItemDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/ItemDefinition.kt new file mode 100644 index 0000000..0830bd1 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/ItemDefinition.kt @@ -0,0 +1,249 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition +import cacheops.cache.definition.ColorPalette +import cacheops.cache.definition.Extra +import cacheops.cache.definition.Parameterized +import cacheops.cache.definition.Recolourable + +data class ItemDefinition( + override var id: Int = -1, + var modelId: Int = 0, + var name: String = "null", + var spriteScale: Int = 2000, + var spritePitch: Int = 0, + var spriteCameraRoll: Int = 0, + var spriteTranslateX: Int = 0, + var spriteTranslateY: Int = 0, + var stackable: Int = 0, + var cost: Int = 1, + var members: Boolean = false, + var multiStackSize: Int = -1, + var primaryMaleModel: Int = -1, + var secondaryMaleModel: Int = -1, + var primaryFemaleModel: Int = -1, + var secondaryFemaleModel: Int = -1, + var floorOptions: Array = arrayOf(null, null, "Take", null, null, "Examine"), + var options: Array = arrayOf(null, null, null, null, "Drop"), + override var originalColours: ShortArray? = null, + override var modifiedColours: ShortArray? = null, + override var originalTextureColours: ShortArray? = null, + override var modifiedTextureColours: ShortArray? = null, + override var recolourPalette: ByteArray? = null, + var exchangeable: Boolean = false, + var tertiaryMaleModel: Int = -1, + var tertiaryFemaleModel: Int = -1, + var primaryMaleDialogueHead: Int = -1, + var primaryFemaleDialogueHead: Int = -1, + var secondaryMaleDialogueHead: Int = -1, + var secondaryFemaleDialogueHead: Int = -1, + var spriteCameraYaw: Int = 0, + var dummyItem: Int = 0, + var noteId: Int = -1, + var notedTemplateId: Int = -1, + var stackIds: IntArray? = null, + var stackAmounts: IntArray? = null, + var floorScaleX: Int = 128, + var floorScaleZ: Int = 128, + var floorScaleY: Int = 128, + var ambience: Int = 0, + var diffusion: Int = 0, + var team: Int = 0, + var lendId: Int = -1, + var lendTemplateId: Int = -1, + var maleWieldX: Int = 0, + var maleWieldZ: Int = 0, + var maleWieldY: Int = 0, + var femaleWieldX: Int = 0, + var femaleWieldZ: Int = 0, + var femaleWieldY: Int = 0, + var primaryCursorOpcode: Int = -1, + var primaryCursor: Int = -1, + var secondaryCursorOpcode: Int = -1, + var secondaryCursor: Int = -1, + var primaryInterfaceCursorOpcode: Int = -1, + var primaryInterfaceCursor: Int = -1, + var secondaryInterfaceCursorOpcode: Int = -1, + var secondaryInterfaceCursor: Int = -1, + var campaigns: IntArray? = null, + var pickSizeShift: Int = 0, + var singleNoteId: Int = -1, + var singleNoteTemplateId: Int = -1, + override var params: HashMap? = null, + override var extras: Map = emptyMap() +) : Definition, Recolourable, ColorPalette, Parameterized, Extra { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ItemDefinition + + if (id != other.id) return false + if (modelId != other.modelId) return false + if (name != other.name) return false + if (spriteScale != other.spriteScale) return false + if (spritePitch != other.spritePitch) return false + if (spriteCameraRoll != other.spriteCameraRoll) return false + if (spriteTranslateX != other.spriteTranslateX) return false + if (spriteTranslateY != other.spriteTranslateY) return false + if (stackable != other.stackable) return false + if (cost != other.cost) return false + if (members != other.members) return false + if (multiStackSize != other.multiStackSize) return false + if (primaryMaleModel != other.primaryMaleModel) return false + if (secondaryMaleModel != other.secondaryMaleModel) return false + if (primaryFemaleModel != other.primaryFemaleModel) return false + if (secondaryFemaleModel != other.secondaryFemaleModel) return false + if (!floorOptions.contentEquals(other.floorOptions)) return false + if (!options.contentEquals(other.options)) return false + if (originalColours != null) { + if (other.originalColours == null) return false + if (!originalColours!!.contentEquals(other.originalColours!!)) return false + } else if (other.originalColours != null) return false + if (modifiedColours != null) { + if (other.modifiedColours == null) return false + if (!modifiedColours!!.contentEquals(other.modifiedColours!!)) return false + } else if (other.modifiedColours != null) return false + if (originalTextureColours != null) { + if (other.originalTextureColours == null) return false + if (!originalTextureColours!!.contentEquals(other.originalTextureColours!!)) return false + } else if (other.originalTextureColours != null) return false + if (modifiedTextureColours != null) { + if (other.modifiedTextureColours == null) return false + if (!modifiedTextureColours!!.contentEquals(other.modifiedTextureColours!!)) return false + } else if (other.modifiedTextureColours != null) return false + if (recolourPalette != null) { + if (other.recolourPalette == null) return false + if (!recolourPalette!!.contentEquals(other.recolourPalette!!)) return false + } else if (other.recolourPalette != null) return false + if (exchangeable != other.exchangeable) return false + if (tertiaryMaleModel != other.tertiaryMaleModel) return false + if (tertiaryFemaleModel != other.tertiaryFemaleModel) return false + if (primaryMaleDialogueHead != other.primaryMaleDialogueHead) return false + if (primaryFemaleDialogueHead != other.primaryFemaleDialogueHead) return false + if (secondaryMaleDialogueHead != other.secondaryMaleDialogueHead) return false + if (secondaryFemaleDialogueHead != other.secondaryFemaleDialogueHead) return false + if (spriteCameraYaw != other.spriteCameraYaw) return false + if (dummyItem != other.dummyItem) return false + if (noteId != other.noteId) return false + if (notedTemplateId != other.notedTemplateId) return false + if (stackIds != null) { + if (other.stackIds == null) return false + if (!stackIds!!.contentEquals(other.stackIds!!)) return false + } else if (other.stackIds != null) return false + if (stackAmounts != null) { + if (other.stackAmounts == null) return false + if (!stackAmounts!!.contentEquals(other.stackAmounts!!)) return false + } else if (other.stackAmounts != null) return false + if (floorScaleX != other.floorScaleX) return false + if (floorScaleZ != other.floorScaleZ) return false + if (floorScaleY != other.floorScaleY) return false + if (ambience != other.ambience) return false + if (diffusion != other.diffusion) return false + if (team != other.team) return false + if (lendId != other.lendId) return false + if (lendTemplateId != other.lendTemplateId) return false + if (maleWieldX != other.maleWieldX) return false + if (maleWieldZ != other.maleWieldZ) return false + if (maleWieldY != other.maleWieldY) return false + if (femaleWieldX != other.femaleWieldX) return false + if (femaleWieldZ != other.femaleWieldZ) return false + if (femaleWieldY != other.femaleWieldY) return false + if (primaryCursorOpcode != other.primaryCursorOpcode) return false + if (primaryCursor != other.primaryCursor) return false + if (secondaryCursorOpcode != other.secondaryCursorOpcode) return false + if (secondaryCursor != other.secondaryCursor) return false + if (primaryInterfaceCursorOpcode != other.primaryInterfaceCursorOpcode) return false + if (primaryInterfaceCursor != other.primaryInterfaceCursor) return false + if (secondaryInterfaceCursorOpcode != other.secondaryInterfaceCursorOpcode) return false + if (secondaryInterfaceCursor != other.secondaryInterfaceCursor) return false + if (campaigns != null) { + if (other.campaigns == null) return false + if (!campaigns!!.contentEquals(other.campaigns!!)) return false + } else if (other.campaigns != null) return false + if (pickSizeShift != other.pickSizeShift) return false + if (singleNoteId != other.singleNoteId) return false + if (singleNoteTemplateId != other.singleNoteTemplateId) return false + if (params != other.params) return false + if (extras != other.extras) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + modelId + result = 31 * result + name.hashCode() + result = 31 * result + spriteScale + result = 31 * result + spritePitch + result = 31 * result + spriteCameraRoll + result = 31 * result + spriteTranslateX + result = 31 * result + spriteTranslateY + result = 31 * result + stackable + result = 31 * result + cost + result = 31 * result + members.hashCode() + result = 31 * result + multiStackSize + result = 31 * result + primaryMaleModel + result = 31 * result + secondaryMaleModel + result = 31 * result + primaryFemaleModel + result = 31 * result + secondaryFemaleModel + result = 31 * result + floorOptions.contentHashCode() + result = 31 * result + options.contentHashCode() + result = 31 * result + (originalColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedColours?.contentHashCode() ?: 0) + result = 31 * result + (originalTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (recolourPalette?.contentHashCode() ?: 0) + result = 31 * result + exchangeable.hashCode() + result = 31 * result + tertiaryMaleModel + result = 31 * result + tertiaryFemaleModel + result = 31 * result + primaryMaleDialogueHead + result = 31 * result + primaryFemaleDialogueHead + result = 31 * result + secondaryMaleDialogueHead + result = 31 * result + secondaryFemaleDialogueHead + result = 31 * result + spriteCameraYaw + result = 31 * result + dummyItem + result = 31 * result + noteId + result = 31 * result + notedTemplateId + result = 31 * result + (stackIds?.contentHashCode() ?: 0) + result = 31 * result + (stackAmounts?.contentHashCode() ?: 0) + result = 31 * result + floorScaleX + result = 31 * result + floorScaleZ + result = 31 * result + floorScaleY + result = 31 * result + ambience + result = 31 * result + diffusion + result = 31 * result + team + result = 31 * result + lendId + result = 31 * result + lendTemplateId + result = 31 * result + maleWieldX + result = 31 * result + maleWieldZ + result = 31 * result + maleWieldY + result = 31 * result + femaleWieldX + result = 31 * result + femaleWieldZ + result = 31 * result + femaleWieldY + result = 31 * result + primaryCursorOpcode + result = 31 * result + primaryCursor + result = 31 * result + secondaryCursorOpcode + result = 31 * result + secondaryCursor + result = 31 * result + primaryInterfaceCursorOpcode + result = 31 * result + primaryInterfaceCursor + result = 31 * result + secondaryInterfaceCursorOpcode + result = 31 * result + secondaryInterfaceCursor + result = 31 * result + (campaigns?.contentHashCode() ?: 0) + result = 31 * result + pickSizeShift + result = 31 * result + singleNoteId + result = 31 * result + singleNoteTemplateId + result = 31 * result + (params?.hashCode() ?: 0) + result = 31 * result + extras.hashCode() + return result + } + + val noted: Boolean + get() = notedTemplateId != -1 + + val lent: Boolean + get() = lendTemplateId != -1 + + val singleNote: Boolean + get() = singleNoteTemplateId != -1 + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/MapDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/MapDefinition.kt new file mode 100644 index 0000000..bfbdd7e --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/MapDefinition.kt @@ -0,0 +1,24 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition + +data class MapDefinition( + override var id: Int = -1, + val tiles: MutableMap = mutableMapOf(), + val objects: MutableList = mutableListOf() +) : Definition { + + fun getTile(localX: Int, localY: Int, plane: Int) = tiles[getHash(localX, localY, plane)] ?: MapTile.EMPTY + + fun setTile(localX: Int, localY: Int, plane: Int, tile: MapTile) { + tiles[getHash(localX, localY, plane)] = tile + } + + companion object { + fun getHash(localX: Int, localY: Int, plane: Int): Int { + return localY + (localX shl 6) + (plane shl 12) + } + fun getLocalX(tile: Int) = tile shr 6 and 0x3f + fun getLocalY(tile: Int) = tile and 0x3f + fun getPlane(tile: Int) = tile shr 12 and 0x3 + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/MapObject.kt b/src/main/kotlin/cacheops/cache/definition/data/MapObject.kt new file mode 100644 index 0000000..0747f5c --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/MapObject.kt @@ -0,0 +1,43 @@ +package cacheops.cache.definition.data + +inline class MapObject(val hash: Long) { + + constructor(id: Int, x: Int, y: Int, plane: Int, type: Int, rotation: Int) : this(getHash(id, x, y, plane, type, rotation)) + + val id: Int + get() = getId(hash) + val x: Int + get() = getX(hash) + val y: Int + get() = getY(hash) + val plane: Int + get() = getPlane(hash) + val type: Int + get() = getType(hash) + val rotation: Int + get() = getRotation(hash) + + companion object { + + fun getHash(id: Int, x: Int, y: Int, plane: Int, type: Int, rotation: Int): Long { + return getHash(id.toLong(), x.toLong(), y.toLong(), plane.toLong(), type.toLong(), rotation.toLong()) + } + + fun getHash(id: Long, x: Long, y: Long, plane: Long, type: Long, rotation: Long): Long { + return rotation + (type shl 2) + (plane shl 7) + (y shl 9) + (x shl 23) + (id shl 37) + } + + fun getId(hash: Long): Int = (hash shr 37 and 0x1ffff).toInt() + + fun getX(hash: Long): Int = (hash shr 23 and 0x3fff).toInt() + + fun getY(hash: Long): Int = (hash shr 9 and 0x3fff).toInt() + + fun getPlane(hash: Long): Int = (hash shr 7 and 0x3).toInt() + + fun getType(hash: Long): Int = (hash shr 2 and 0x1f).toInt() + + fun getRotation(hash: Long): Int = (hash and 0x3).toInt() + + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/MapTile.kt b/src/main/kotlin/cacheops/cache/definition/data/MapTile.kt new file mode 100644 index 0000000..07a1463 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/MapTile.kt @@ -0,0 +1,58 @@ +package cacheops.cache.definition.data + +inline class MapTile(val hash: Long) { + + constructor( + height: Int = 0, + opcode: Int = 0, + overlay: Int = 0, + path: Int = 0, + rotation: Int = 0, + settings: Int = 0, + underlay: Int = 0 + ) : this(getHash(height, opcode, overlay, path, rotation, settings, underlay)) + + val height: Int + get() = getHeight(hash) + + val attrOpcode: Int + get() = getOpcode(hash) + + val overlayId: Int + get() = getOverlay(hash) + + val overlayPath: Int + get() = getPath(hash) + + val overlayRotation: Int + get() = getRotation(hash) + + val settings: Int + get() = getSettings(hash) + + val underlayId: Int + get() = getUnderlay(hash) + + fun isTile(flag: Int) = settings and flag == flag + + companion object { + + val EMPTY = MapTile() + + fun getHash(height: Int, opcode: Int, overlay: Int, path: Int, rotation: Int, settings: Int, underlay: Int): Long { + return getHash(height.toLong(), opcode.toLong(), overlay.toLong(), path.toLong(), rotation.toLong(), settings.toLong(), underlay.toLong()) + } + + fun getHash(height: Long, opcode: Long, overlay: Long, path: Long, rotation: Long, settings: Long, underlay: Long): Long { + return height + (opcode shl 8) + (overlay shl 16) + (path shl 24) + (rotation shl 32) + (settings shl 40) + (underlay shl 48) + } + + fun getHeight(hash: Long): Int = (hash and 0xff).toInt() + fun getOpcode(hash: Long): Int = (hash shr 8 and 0xff).toInt() + fun getOverlay(hash: Long): Int = (hash shr 16 and 0xff).toInt() + fun getPath(hash: Long): Int = (hash shr 24 and 0xff).toInt() + fun getRotation(hash: Long): Int = (hash shr 32 and 0xff).toInt() + fun getSettings(hash: Long): Int = (hash shr 40 and 0xff).toInt() + fun getUnderlay(hash: Long): Int = (hash shr 48 and 0xff).toInt() + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/NPCDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/NPCDefinition.kt new file mode 100644 index 0000000..5544a39 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/NPCDefinition.kt @@ -0,0 +1,250 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition +import cacheops.cache.definition.Extra +import cacheops.cache.definition.Parameterized +import cacheops.cache.definition.Recolourable + +data class NPCDefinition( + override var id: Int = -1, + var modelIds: IntArray? = null, + var name: String = "null", + var size: Int = 1, + var options: Array = arrayOf(null, null, null, null, null, "Examine"), + override var originalColours: ShortArray? = null, + override var modifiedColours: ShortArray? = null, + override var originalTextureColours: ShortArray? = null, + override var modifiedTextureColours: ShortArray? = null, + var recolourPalette: ByteArray? = null, + var dialogueModels: IntArray? = null, + var drawMinimapDot: Boolean = true, + var combat: Int = -1, + var scaleXY: Int = 128, + var scaleZ: Int = 128, + var priorityRender: Boolean = false, + var lightModifier: Int = 0, + var shadowModifier: Int = 0, + var headIcon: Int = -1, + var rotation: Int = 32, + var varbit: Int = -1, + var varp: Int = -1, + var morphs: IntArray? = null, + var clickable: Boolean = true, + var slowWalk: Boolean = true, + var animateIdle: Boolean = true, + var primaryShadowColour: Short = 0, + var secondaryShadowColour: Short = 0, + var primaryShadowModifier: Byte = -96, + var secondaryShadowModifier: Byte = -16, + var walkMask: Byte = 0, + var translations: Array? = null, + var hitbarSprite: Int = -1, + var height: Int = -1, + var respawnDirection: Byte = 4, + var renderEmote: Int = -1, + var idleSound: Int = -1, + var crawlSound: Int = -1, + var walkSound: Int = -1, + var runSound: Int = -1, + var soundDistance: Int = 0, + var primaryCursorOp: Int = -1, + var primaryCursor: Int = -1, + var secondaryCursorOp: Int = -1, + var secondaryCursor: Int = -1, + var attackCursor: Int = -1, + var armyIcon: Int = -1, + var spriteId: Int = -1, + var ambientSoundVolume: Int = 255, + var visiblePriority: Boolean = false, + var mapFunction: Int = -1, + var invisiblePriority: Boolean = false, + var hue: Byte = 0, + var saturation: Byte = 0, + var lightness: Byte = 0, + var opacity: Byte = 0, + var mainOptionIndex: Byte = -1, + var campaigns: IntArray? = null, + var aBoolean2883: Boolean = false, + var anInt2803: Int = -1, + var anInt2844: Int = 256, + var anInt2852: Int = 256, + var anInt2831: Int = 0, + var anInt2862: Int = 0, + override var params: HashMap? = null, + override var extras: Map = emptyMap() +) : Definition, Recolourable, Parameterized, Extra { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as NPCDefinition + + if (id != other.id) return false + if (modelIds != null) { + if (other.modelIds == null) return false + if (!modelIds!!.contentEquals(other.modelIds!!)) return false + } else if (other.modelIds != null) return false + if (name != other.name) return false + if (size != other.size) return false + if (!options.contentEquals(other.options)) return false + if (originalColours != null) { + if (other.originalColours == null) return false + if (!originalColours!!.contentEquals(other.originalColours!!)) return false + } else if (other.originalColours != null) return false + if (modifiedColours != null) { + if (other.modifiedColours == null) return false + if (!modifiedColours!!.contentEquals(other.modifiedColours!!)) return false + } else if (other.modifiedColours != null) return false + if (originalTextureColours != null) { + if (other.originalTextureColours == null) return false + if (!originalTextureColours!!.contentEquals(other.originalTextureColours!!)) return false + } else if (other.originalTextureColours != null) return false + if (modifiedTextureColours != null) { + if (other.modifiedTextureColours == null) return false + if (!modifiedTextureColours!!.contentEquals(other.modifiedTextureColours!!)) return false + } else if (other.modifiedTextureColours != null) return false + if (recolourPalette != null) { + if (other.recolourPalette == null) return false + if (!recolourPalette!!.contentEquals(other.recolourPalette!!)) return false + } else if (other.recolourPalette != null) return false + if (dialogueModels != null) { + if (other.dialogueModels == null) return false + if (!dialogueModels!!.contentEquals(other.dialogueModels!!)) return false + } else if (other.dialogueModels != null) return false + if (drawMinimapDot != other.drawMinimapDot) return false + if (combat != other.combat) return false + if (scaleXY != other.scaleXY) return false + if (scaleZ != other.scaleZ) return false + if (priorityRender != other.priorityRender) return false + if (lightModifier != other.lightModifier) return false + if (shadowModifier != other.shadowModifier) return false + if (headIcon != other.headIcon) return false + if (rotation != other.rotation) return false + if (varbit != other.varbit) return false + if (varp != other.varp) return false + if (morphs != null) { + if (other.morphs == null) return false + if (!morphs!!.contentEquals(other.morphs!!)) return false + } else if (other.morphs != null) return false + if (clickable != other.clickable) return false + if (slowWalk != other.slowWalk) return false + if (animateIdle != other.animateIdle) return false + if (primaryShadowColour != other.primaryShadowColour) return false + if (secondaryShadowColour != other.secondaryShadowColour) return false + if (primaryShadowModifier != other.primaryShadowModifier) return false + if (secondaryShadowModifier != other.secondaryShadowModifier) return false + if (walkMask != other.walkMask) return false + if (translations != null) { + if (other.translations == null) return false + if (!translations!!.contentDeepEquals(other.translations!!)) return false + } else if (other.translations != null) return false + if (hitbarSprite != other.hitbarSprite) return false + if (height != other.height) return false + if (respawnDirection != other.respawnDirection) return false + if (renderEmote != other.renderEmote) return false + if (idleSound != other.idleSound) return false + if (crawlSound != other.crawlSound) return false + if (walkSound != other.walkSound) return false + if (runSound != other.runSound) return false + if (soundDistance != other.soundDistance) return false + if (primaryCursorOp != other.primaryCursorOp) return false + if (primaryCursor != other.primaryCursor) return false + if (secondaryCursorOp != other.secondaryCursorOp) return false + if (secondaryCursor != other.secondaryCursor) return false + if (attackCursor != other.attackCursor) return false + if (armyIcon != other.armyIcon) return false + if (spriteId != other.spriteId) return false + if (ambientSoundVolume != other.ambientSoundVolume) return false + if (visiblePriority != other.visiblePriority) return false + if (mapFunction != other.mapFunction) return false + if (invisiblePriority != other.invisiblePriority) return false + if (hue != other.hue) return false + if (saturation != other.saturation) return false + if (lightness != other.lightness) return false + if (opacity != other.opacity) return false + if (mainOptionIndex != other.mainOptionIndex) return false + if (campaigns != null) { + if (other.campaigns == null) return false + if (!campaigns!!.contentEquals(other.campaigns!!)) return false + } else if (other.campaigns != null) return false + if (aBoolean2883 != other.aBoolean2883) return false + if (anInt2803 != other.anInt2803) return false + if (anInt2844 != other.anInt2844) return false + if (anInt2852 != other.anInt2852) return false + if (anInt2831 != other.anInt2831) return false + if (anInt2862 != other.anInt2862) return false + if (params != other.params) return false + if (extras != other.extras) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + (modelIds?.contentHashCode() ?: 0) + result = 31 * result + name.hashCode() + result = 31 * result + size + result = 31 * result + options.contentHashCode() + result = 31 * result + (originalColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedColours?.contentHashCode() ?: 0) + result = 31 * result + (originalTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (recolourPalette?.contentHashCode() ?: 0) + result = 31 * result + (dialogueModels?.contentHashCode() ?: 0) + result = 31 * result + drawMinimapDot.hashCode() + result = 31 * result + combat + result = 31 * result + scaleXY + result = 31 * result + scaleZ + result = 31 * result + priorityRender.hashCode() + result = 31 * result + lightModifier + result = 31 * result + shadowModifier + result = 31 * result + headIcon + result = 31 * result + rotation + result = 31 * result + varbit + result = 31 * result + varp + result = 31 * result + (morphs?.contentHashCode() ?: 0) + result = 31 * result + clickable.hashCode() + result = 31 * result + slowWalk.hashCode() + result = 31 * result + animateIdle.hashCode() + result = 31 * result + primaryShadowColour + result = 31 * result + secondaryShadowColour + result = 31 * result + primaryShadowModifier + result = 31 * result + secondaryShadowModifier + result = 31 * result + walkMask + result = 31 * result + (translations?.contentDeepHashCode() ?: 0) + result = 31 * result + hitbarSprite + result = 31 * result + height + result = 31 * result + respawnDirection + result = 31 * result + renderEmote + result = 31 * result + idleSound + result = 31 * result + crawlSound + result = 31 * result + walkSound + result = 31 * result + runSound + result = 31 * result + soundDistance + result = 31 * result + primaryCursorOp + result = 31 * result + primaryCursor + result = 31 * result + secondaryCursorOp + result = 31 * result + secondaryCursor + result = 31 * result + attackCursor + result = 31 * result + armyIcon + result = 31 * result + spriteId + result = 31 * result + ambientSoundVolume + result = 31 * result + visiblePriority.hashCode() + result = 31 * result + mapFunction + result = 31 * result + invisiblePriority.hashCode() + result = 31 * result + hue + result = 31 * result + saturation + result = 31 * result + lightness + result = 31 * result + opacity + result = 31 * result + mainOptionIndex + result = 31 * result + (campaigns?.contentHashCode() ?: 0) + result = 31 * result + aBoolean2883.hashCode() + result = 31 * result + anInt2803 + result = 31 * result + anInt2844 + result = 31 * result + anInt2852 + result = 31 * result + anInt2831 + result = 31 * result + anInt2862 + result = 31 * result + (params?.hashCode() ?: 0) + result = 31 * result + extras.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/ObjectDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/ObjectDefinition.kt new file mode 100644 index 0000000..c237620 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/ObjectDefinition.kt @@ -0,0 +1,295 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition +import cacheops.cache.definition.ColorPalette +import cacheops.cache.definition.Extra +import cacheops.cache.definition.Parameterized +import cacheops.cache.definition.Recolourable + +data class ObjectDefinition( + override var id: Int = -1, + var modelIds: IntArray? = null, + var modelTypes: IntArray? = null, + var name: String = "null", + var sizeX: Int = 1, + var sizeY: Int = 1, + var blocksSky: Boolean = true, + var solid: Int = 2, + var interactive: Int = -1, + var contouredGround: Byte = 0, + var delayShading: Boolean = false, + var offsetMultiplier: Int = 64, + var brightness: Int = 0, + var options: Array = arrayOf(null, null, null, null, null, "Examine"), + var contrast: Int = 0, + override var originalColours: ShortArray? = null, + override var modifiedColours: ShortArray? = null, + override var originalTextureColours: ShortArray? = null, + override var modifiedTextureColours: ShortArray? = null, + override var recolourPalette: ByteArray? = null, + var mirrored: Boolean = false, + var castsShadow: Boolean = true, + var modelSizeX: Int = 128, + var modelSizeZ: Int = 128, + var modelSizeY: Int = 128, + var blockFlag: Int = 0, + var offsetX: Int = 0, + var offsetZ: Int = 0, + var offsetY: Int = 0, + var blocksLand: Boolean = false, + var ignoreOnRoute: Boolean = false, + var supportItems: Int = -1, + var varbitIndex: Int = -1, + var configId: Int = -1, + var configObjectIds: IntArray? = null, + var anInt3015: Int = -1, + var anInt3012: Int = 0, + var anInt2989: Int = 0, + var anInt2971: Int = 0, + var anIntArray3036: IntArray? = null, + var anInt3023: Int = -1, + var hideMinimap: Boolean = false, + var aBoolean2972: Boolean = true, + var animateImmediately: Boolean = true, + var aBoolean1502: Boolean = false, + var aBoolean1507: Boolean = false, + var isMembers: Boolean = false, + var adjustMapSceneRotation: Boolean = false, + var hasAnimation: Boolean = false, + var anInt2987: Int = -1, + var anInt3008: Int = -1, + var anInt3038: Int = -1, + var anInt3013: Int = -1, + var mapSceneRotation: Int = 0, + var mapscene: Int = -1, + var culling: Int = -1, + var anInt3024: Int = 255, + var invertMapScene: Boolean = false, + var animations: IntArray? = null, + var percents: IntArray? = null, + var mapDefinitionId: Int = -1, + var anIntArray2981: IntArray? = null, + var aByte2974: Byte = 0, + var aByte3045: Byte = 0, + var aByte3052: Byte = 0, + var aByte2960: Byte = 0, + var anInt2964: Int = 0, + var anInt2963: Int = 0, + var anInt3018: Int = 0, + var anInt2983: Int = 0, + var aBoolean2961: Boolean = false, + var aBoolean2993: Boolean = false, + var anInt3032: Int = 960, + var anInt2962: Int = 0, + var anInt3050: Int = 256, + var anInt3020: Int = 256, + var aBoolean2992: Boolean = false, + var anInt2975: Int = 0, + override var params: HashMap? = null, + override var extras: Map = emptyMap() +) : Definition, Recolourable, ColorPalette, Parameterized, Extra { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ObjectDefinition + + if (id != other.id) return false + if (modelIds != null) { + if (other.modelIds == null) return false + if (!modelIds!!.contentEquals(other.modelIds!!)) return false + } else if (other.modelIds != null) return false + if (modelTypes != null) { + if (other.modelTypes == null) return false + if (!modelTypes!!.contentEquals(other.modelTypes!!)) return false + } else if (other.modelTypes != null) return false + if (name != other.name) return false + if (sizeX != other.sizeX) return false + if (sizeY != other.sizeY) return false + if (blocksSky != other.blocksSky) return false + if (solid != other.solid) return false + if (interactive != other.interactive) return false + if (contouredGround != other.contouredGround) return false + if (delayShading != other.delayShading) return false + if (offsetMultiplier != other.offsetMultiplier) return false + if (brightness != other.brightness) return false + if (!options.contentEquals(other.options)) return false + if (contrast != other.contrast) return false + if (originalColours != null) { + if (other.originalColours == null) return false + if (!originalColours!!.contentEquals(other.originalColours!!)) return false + } else if (other.originalColours != null) return false + if (modifiedColours != null) { + if (other.modifiedColours == null) return false + if (!modifiedColours!!.contentEquals(other.modifiedColours!!)) return false + } else if (other.modifiedColours != null) return false + if (originalTextureColours != null) { + if (other.originalTextureColours == null) return false + if (!originalTextureColours!!.contentEquals(other.originalTextureColours!!)) return false + } else if (other.originalTextureColours != null) return false + if (modifiedTextureColours != null) { + if (other.modifiedTextureColours == null) return false + if (!modifiedTextureColours!!.contentEquals(other.modifiedTextureColours!!)) return false + } else if (other.modifiedTextureColours != null) return false + if (recolourPalette != null) { + if (other.recolourPalette == null) return false + if (!recolourPalette!!.contentEquals(other.recolourPalette!!)) return false + } else if (other.recolourPalette != null) return false + if (mirrored != other.mirrored) return false + if (castsShadow != other.castsShadow) return false + if (modelSizeX != other.modelSizeX) return false + if (modelSizeZ != other.modelSizeZ) return false + if (modelSizeY != other.modelSizeY) return false + if (blockFlag != other.blockFlag) return false + if (offsetX != other.offsetX) return false + if (offsetZ != other.offsetZ) return false + if (offsetY != other.offsetY) return false + if (blocksLand != other.blocksLand) return false + if (ignoreOnRoute != other.ignoreOnRoute) return false + if (supportItems != other.supportItems) return false + if (varbitIndex != other.varbitIndex) return false + if (configId != other.configId) return false + if (configObjectIds != null) { + if (other.configObjectIds == null) return false + if (!configObjectIds!!.contentEquals(other.configObjectIds!!)) return false + } else if (other.configObjectIds != null) return false + if (anInt3015 != other.anInt3015) return false + if (anInt3012 != other.anInt3012) return false + if (anInt2989 != other.anInt2989) return false + if (anInt2971 != other.anInt2971) return false + if (anIntArray3036 != null) { + if (other.anIntArray3036 == null) return false + if (!anIntArray3036!!.contentEquals(other.anIntArray3036!!)) return false + } else if (other.anIntArray3036 != null) return false + if (anInt3023 != other.anInt3023) return false + if (hideMinimap != other.hideMinimap) return false + if (aBoolean2972 != other.aBoolean2972) return false + if (animateImmediately != other.animateImmediately) return false + if (isMembers != other.isMembers) return false + if (adjustMapSceneRotation != other.adjustMapSceneRotation) return false + if (hasAnimation != other.hasAnimation) return false + if (anInt2987 != other.anInt2987) return false + if (anInt3008 != other.anInt3008) return false + if (anInt3038 != other.anInt3038) return false + if (anInt3013 != other.anInt3013) return false + if (mapSceneRotation != other.mapSceneRotation) return false + if (mapscene != other.mapscene) return false + if (culling != other.culling) return false + if (anInt3024 != other.anInt3024) return false + if (invertMapScene != other.invertMapScene) return false + if (animations != null) { + if (other.animations == null) return false + if (!animations!!.contentEquals(other.animations!!)) return false + } else if (other.animations != null) return false + if (percents != null) { + if (other.percents == null) return false + if (!percents!!.contentEquals(other.percents!!)) return false + } else if (other.percents != null) return false + if (mapDefinitionId != other.mapDefinitionId) return false + if (anIntArray2981 != null) { + if (other.anIntArray2981 == null) return false + if (!anIntArray2981!!.contentEquals(other.anIntArray2981!!)) return false + } else if (other.anIntArray2981 != null) return false + if (aByte2974 != other.aByte2974) return false + if (aByte3045 != other.aByte3045) return false + if (aByte3052 != other.aByte3052) return false + if (aByte2960 != other.aByte2960) return false + if (anInt2964 != other.anInt2964) return false + if (anInt2963 != other.anInt2963) return false + if (anInt3018 != other.anInt3018) return false + if (anInt2983 != other.anInt2983) return false + if (aBoolean2961 != other.aBoolean2961) return false + if (aBoolean2993 != other.aBoolean2993) return false + if (anInt3032 != other.anInt3032) return false + if (anInt2962 != other.anInt2962) return false + if (anInt3050 != other.anInt3050) return false + if (anInt3020 != other.anInt3020) return false + if (aBoolean2992 != other.aBoolean2992) return false + if (anInt2975 != other.anInt2975) return false + if (params != other.params) return false + if (extras != other.extras) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + (modelIds?.contentHashCode() ?: 0) + result = 31 * result + (modelTypes?.contentHashCode() ?: 0) + result = 31 * result + name.hashCode() + result = 31 * result + sizeX + result = 31 * result + sizeY + result = 31 * result + blocksSky.hashCode() + result = 31 * result + solid + result = 31 * result + interactive + result = 31 * result + contouredGround + result = 31 * result + delayShading.hashCode() + result = 31 * result + offsetMultiplier + result = 31 * result + brightness + result = 31 * result + options.contentHashCode() + result = 31 * result + contrast + result = 31 * result + (originalColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedColours?.contentHashCode() ?: 0) + result = 31 * result + (originalTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (modifiedTextureColours?.contentHashCode() ?: 0) + result = 31 * result + (recolourPalette?.contentHashCode() ?: 0) + result = 31 * result + mirrored.hashCode() + result = 31 * result + castsShadow.hashCode() + result = 31 * result + modelSizeX + result = 31 * result + modelSizeZ + result = 31 * result + modelSizeY + result = 31 * result + blockFlag + result = 31 * result + offsetX + result = 31 * result + offsetZ + result = 31 * result + offsetY + result = 31 * result + blocksLand.hashCode() + result = 31 * result + ignoreOnRoute.hashCode() + result = 31 * result + supportItems + result = 31 * result + varbitIndex + result = 31 * result + configId + result = 31 * result + (configObjectIds?.contentHashCode() ?: 0) + result = 31 * result + anInt3015 + result = 31 * result + anInt3012 + result = 31 * result + anInt2989 + result = 31 * result + anInt2971 + result = 31 * result + (anIntArray3036?.contentHashCode() ?: 0) + result = 31 * result + anInt3023 + result = 31 * result + hideMinimap.hashCode() + result = 31 * result + aBoolean2972.hashCode() + result = 31 * result + animateImmediately.hashCode() + result = 31 * result + isMembers.hashCode() + result = 31 * result + adjustMapSceneRotation.hashCode() + result = 31 * result + hasAnimation.hashCode() + result = 31 * result + anInt2987 + result = 31 * result + anInt3008 + result = 31 * result + anInt3038 + result = 31 * result + anInt3013 + result = 31 * result + mapSceneRotation + result = 31 * result + mapscene + result = 31 * result + culling + result = 31 * result + anInt3024 + result = 31 * result + invertMapScene.hashCode() + result = 31 * result + (animations?.contentHashCode() ?: 0) + result = 31 * result + (percents?.contentHashCode() ?: 0) + result = 31 * result + mapDefinitionId + result = 31 * result + (anIntArray2981?.contentHashCode() ?: 0) + result = 31 * result + aByte2974 + result = 31 * result + aByte3045 + result = 31 * result + aByte3052 + result = 31 * result + aByte2960 + result = 31 * result + anInt2964 + result = 31 * result + anInt2963 + result = 31 * result + anInt3018 + result = 31 * result + anInt2983 + result = 31 * result + aBoolean2961.hashCode() + result = 31 * result + aBoolean2993.hashCode() + result = 31 * result + anInt3032 + result = 31 * result + anInt2962 + result = 31 * result + anInt3050 + result = 31 * result + anInt3020 + result = 31 * result + aBoolean2992.hashCode() + result = 31 * result + anInt2975 + result = 31 * result + (params?.hashCode() ?: 0) + result = 31 * result + extras.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/OverlayDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/OverlayDefinition.kt new file mode 100644 index 0000000..ea63601 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/OverlayDefinition.kt @@ -0,0 +1,26 @@ +package cacheops.cache.definition.data + +import java.awt.Color + +data class OverlayDefinition( + var color: Int = 0, + var texture: Int = -1, + var hideUnderlay: Boolean = true, + var blendColor: Int = -1, + var scale: Int = 128, + var blockShadow: Boolean = true, + var aByte0: Int = 8, + var blendTexture: Boolean = false, + var waterColor: Int = 1190717, + var waterScale: Int = 16, + var anInt0: Int = -1, + var waterIntensity: Int = 16 +) { + override fun toString(): String { + return "" + } + + fun getRGB(): Color { + return Color(color) + } +} diff --git a/src/main/kotlin/cacheops/cache/definition/data/QuickChatOptionDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/QuickChatOptionDefinition.kt new file mode 100644 index 0000000..79f4082 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/QuickChatOptionDefinition.kt @@ -0,0 +1,36 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition + +@Suppress("ArrayInDataClass") +data class QuickChatOptionDefinition( + override var id: Int = -1, + var optionText: String? = null, + var quickReplyOptions: IntArray? = null, + var navigateChars: CharArray? = null, + var dynamicData: IntArray? = null, + var staticData: CharArray? = null +) : Definition { + fun getDynamicIndex(c: Char): Int { + if (dynamicData == null) { + return -1 + } + for (i in dynamicData!!.indices) { + if (staticData!![i] == c) { + return dynamicData!![i] + } + } + return -1 + } + + fun getOption(c: Char): Int { + if (quickReplyOptions == null) { + return -1 + } + for (i in quickReplyOptions!!.indices) { + if (navigateChars!![i] == c) { + return quickReplyOptions!![i] + } + } + return -1 + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/SpriteDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/SpriteDefinition.kt new file mode 100644 index 0000000..304b8a8 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/SpriteDefinition.kt @@ -0,0 +1,8 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition + +@Suppress("ArrayInDataClass") +data class SpriteDefinition( + override var id: Int = -1, + var sprites: Array? = null +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/TextureDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/TextureDefinition.kt new file mode 100644 index 0000000..e3aae37 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/TextureDefinition.kt @@ -0,0 +1,25 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition + +data class TextureDefinition( + override var id: Int = -1, + var useTextureColour: Boolean = false, + var aBoolean1204: Boolean = false, + var aBoolean1205: Boolean = false, + var aByte1217: Byte = 0, + var aByte1225: Byte = 0, + var type: Byte = 0, + var aByte1213: Byte = 0, + var colour: Int = 0, + var aByte1211: Byte = 0, + var aByte1203: Byte = 0, + var aBoolean1222: Boolean = false, + var aBoolean1216: Boolean = false, + var aByte1207: Byte = 0, + var aBoolean1212: Boolean = false, + var aBoolean1210: Boolean = false, + var aBoolean1215: Boolean = false, + var anInt1202: Int = 0, + var anInt1206: Int = 0, + var anInt1226: Int = 0 +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/UnderlayDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/UnderlayDefinition.kt new file mode 100644 index 0000000..3b77e0f --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/UnderlayDefinition.kt @@ -0,0 +1,23 @@ +package cacheops.cache.definition.data + +import java.awt.Color + +data class UnderlayDefinition( + var color: Int = 0, + var hue: Int = 0, + var saturation: Int = 0, + var lumiance: Int = 0, + var texture: Int = -1, + var scale: Int = 128, + var blockShadow: Boolean = true, + var blendHueMultiplier: Int = 0, + var blendHue: Int = 0 +) { + override fun toString(): String { + return "[RAW(HSL): $color, BlendH: $blendHue, H: $hue, S: $saturation, L: $lumiance, Texture: $texture, Scale: $scale, Shadow: $blockShadow]" + } + + fun getRGB(): Color { + return Color(color) + } +} diff --git a/src/main/kotlin/cacheops/cache/definition/data/VarBitDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/VarBitDefinition.kt new file mode 100644 index 0000000..05c8656 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/VarBitDefinition.kt @@ -0,0 +1,9 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition + +data class VarBitDefinition( + override var id: Int = -1, + var index: Int = 0, + var leastSignificantBit: Int = 0, + var mostSignificantBit: Int = 0 +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/WorldMapDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/WorldMapDefinition.kt new file mode 100644 index 0000000..4cef66b --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/WorldMapDefinition.kt @@ -0,0 +1,18 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition +import java.util.* + +data class WorldMapDefinition( + override var id: Int = -1, + var map: String = "", + var name: String = "", + var position: Int = -1, + var anInt9542: Int = -1, + var static: Boolean = false, + var anInt9547: Int = -1, + var sections: LinkedList? = null, + var minX: Int = 12800, + var minY: Int = 12800, + var maxX: Int = 0, + var maxY: Int = 0 +) : Definition \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/WorldMapIcon.kt b/src/main/kotlin/cacheops/cache/definition/data/WorldMapIcon.kt new file mode 100644 index 0000000..d89bdb7 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/WorldMapIcon.kt @@ -0,0 +1,6 @@ +package cacheops.cache.definition.data + +data class WorldMapIcon( + var id: Int = -1, + var position: Int = -1 +) \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/WorldMapIconDefinition.kt b/src/main/kotlin/cacheops/cache/definition/data/WorldMapIconDefinition.kt new file mode 100644 index 0000000..a886855 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/WorldMapIconDefinition.kt @@ -0,0 +1,25 @@ +package cacheops.cache.definition.data +import cacheops.cache.Definition + +data class WorldMapIconDefinition( + override var id: Int = -1, + var icons: Array = emptyArray() +) : Definition { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as WorldMapIconDefinition + + if (id != other.id) return false + if (!icons.contentEquals(other.icons)) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + icons.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/data/WorldMapSection.kt b/src/main/kotlin/cacheops/cache/definition/data/WorldMapSection.kt new file mode 100644 index 0000000..7f47d49 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/data/WorldMapSection.kt @@ -0,0 +1,3 @@ +package cacheops.cache.definition.data + +data class WorldMapSection(val plane: Int, val minX: Int, val minY: Int, val maxX: Int, val maxY: Int, var startX: Int, var startY: Int, var endX: Int, var endY: Int) \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/AtmosphereDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/AtmosphereDecoder.kt new file mode 100644 index 0000000..0ae15f4 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/AtmosphereDecoder.kt @@ -0,0 +1,7 @@ +package cacheops.cache.definition.decoder + +class AtmosphereDecoder { + + + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/BlendedTextureDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/BlendedTextureDecoder.kt new file mode 100644 index 0000000..2efec55 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/BlendedTextureDecoder.kt @@ -0,0 +1,95 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.Cache +import cacheops.cache.DefinitionDecoder +import cacheops.cache.buffer.read.BufferReader +import cacheops.cache.buffer.read.Reader +import cacheops.cache.definition.data.BlendedTextureDefinition +import const.Indices + +class BlendedTextureDecoder(cache: Cache) : DefinitionDecoder(cache, Indices.MATERIALS) { + + companion object { + val blendedTextureDef = HashMap() + } + + override fun create() = BlendedTextureDefinition() + + var metricsCount = 0 + + override val last: Int + get() = metricsCount + + init { + val data = getData(0, 0) + if (data != null) { + decode(BufferReader(data)) + } + } + + fun decode(buffer: Reader) { + metricsCount = buffer.readShort() + for (id in 0 until metricsCount) { + if (buffer.readUnsignedBoolean()) { + blendedTextureDef[id] = BlendedTextureDefinition(id = id) + } + } + + for (texture in blendedTextureDef.values) { // J + texture.renderOnMap = !buffer.readUnsignedBoolean() // 0 + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() // 1 + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() // 1 + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() // 1 + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() + } + for (texture in blendedTextureDef.values) { + texture.blendedColor = buffer.readUnsignedShort() + println(texture.blendedColor) + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() + } + for (texture in blendedTextureDef.values) { + buffer.readByte().toByte() + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() + } + for (texture in blendedTextureDef.values) { + buffer.readUnsignedBoolean() + } + } + + override fun clear() { + } + + override fun BlendedTextureDefinition.read(opcode: Int, buffer: Reader) { + throw IllegalStateException("Shouldn't be used.") + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayConfiguration.kt b/src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayConfiguration.kt new file mode 100644 index 0000000..e87e34a --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayConfiguration.kt @@ -0,0 +1,32 @@ +package cacheops.cache.definition.decoder + +import Rs2MapEditor +import cacheops.cache.definition.data.OverlayDefinition +import const.Archives +import const.Indices +import java.nio.ByteBuffer + +object FloorOverlayConfiguration { + + val cache = Rs2MapEditor.library.index(Indices.CONFIGURATION).archive(Archives.FLOOR_OVERLAYS) + + var floorOverlays = hashMapOf() + + @JvmStatic + fun main(args: Array) { + cache!!.files().forEach { + val buffer = ByteBuffer.wrap(it.data) + val definition = FloorOverlayDecoder().decode(buffer) + floorOverlays[it.id] = definition + } + } + + fun init() { + cache!!.files().forEach { + val buffer = ByteBuffer.wrap(it.data) + val definition = FloorOverlayDecoder().decode(buffer) + floorOverlays[it.id] = definition + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayDecoder.kt new file mode 100644 index 0000000..2e61fb1 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/FloorOverlayDecoder.kt @@ -0,0 +1,157 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.definition.data.OverlayDefinition +import ext.medium +import ext.unsignedByte +import ext.unsignedShort +import java.nio.ByteBuffer + +class FloorOverlayDecoder { + + private var color = 0 + private var texture = -1 + private var hideUnderlay = true + private var blendColor = -1 + private var anInt3081 = 0 + private var scale = 128 + private var blockShadow = true + private var aByte0 = 8 + private var blendTexture = false + private var waterColor = 1190717 + private var waterScale = 16 + private var anInt0 = -1 + private var waterIntensity = 16 + + fun decode(buffer: ByteBuffer): OverlayDefinition { + while (true) { + val opcode = buffer.unsignedByte() + if (opcode == 0) { + return OverlayDefinition(color, texture, hideUnderlay, blendColor, scale, blockShadow, aByte0, blendTexture, waterColor, waterScale, anInt0, waterIntensity) + } + decodeOverlayType(opcode, buffer) + } + } + + /** + * Overlay Type Decoder + * + * Takes the standard params for a decoder, [opcode], [rsByteBuffer] and a [ID]. + * + * Currently opcode [10] is unknown. + */ + private fun decodeOverlayType(opcode: Int, buffer: ByteBuffer) { + + when (opcode) { + 1 -> color = buffer.medium()//rgbToHsl(buffer.medium()) + 2 -> texture = buffer.unsignedByte() + 3 -> { + texture = buffer.unsignedShort() + if (texture == 65535) { + texture = -1 + } + } + 5 -> hideUnderlay = false + 7 -> blendColor = buffer.medium()//rgbToHsl(buffer.medium()) + 8 -> anInt3081 + 9 -> scale = buffer.unsignedShort() + 10 -> blockShadow = false //Possibly blockShadow? + 11 -> aByte0 = buffer.unsignedByte() + 12 -> blendTexture = true + 13 -> waterColor = buffer.medium() + 14 -> waterScale = buffer.unsignedByte() + 15 -> { + anInt0 = buffer.unsignedShort() + if (anInt0 == 65535) { + anInt0 = -1 + } + } + 16 -> waterIntensity = buffer.unsignedByte() + /* 14 + * Handles how deep into water the player is able to see, + * seems to (but not confirmed) work in jumps of 2, so: "0, 2, 4, 6" etc. + * It seems any number equals to or less than 0, removes any visual + * effect obscuring the depth view. The first increment in order, + * being 2, blocks almost 100% of the view of the underwater map (UM). + */ + else -> println("Unhandled opcode $opcode") + } + } + + private fun rgbToHsl(rgb: Int): Int { + if (rgb == 16711935) { + return -1 + + } else { + + val d = (((0xff80ae and rgb) shr 16).toDouble() / 256.0) + val d_8_ = (((0xff6a and rgb) shr 8).toDouble() / 256.0) + val d_9_ = ((rgb and 0xff).toDouble() / 256.0) + var d_11_ = d + if (d_11_ > d_8_) { + d_11_ = d_8_ + } + if (d_11_ > d_9_) { + d_11_ = d_9_ + } + var d_12_ = d + if (d_8_ > d_12_) { + d_12_ = d_8_ + } + if (d_12_ < d_9_) { + d_12_ = d_9_ + } + var h = 0.0 + var s = 0.0 + val l = (d_12_ + d_11_) / 2.0 + if (d_11_ != d_12_) { + if (l < 0.5) { + s = (-d_11_ + d_12_) / (d_12_ + d_11_) + } + if (l >= 0.5) { + s = (d_12_ - d_11_) / (-d_12_ + 2.0 - d_11_) + } + if (d_12_ == d) { + h = (d_8_ - d_9_) / (d_12_ - d_11_) + } else if (d_12_ == d_8_) { + h = (-d + d_9_) / (-d_11_ + d_12_) + 2.0 + } else if (d_12_ == d_9_) { + h = (d - d_8_) / (d_12_ - d_11_) + 4.0 + } + } + h /= 6.0 + + val hue = (h * 256.0).toInt() + var saturation = (s * 256.0).toInt() + var lumiance = (l * 256.0).toInt() + + if (saturation >= 0) { + if (saturation > 255) { + saturation = 255 + } + } else { + saturation = 0 + } + + if (lumiance >= 0) { + if (lumiance > 255) { + lumiance = 255 + } + } else { + lumiance = 0 + } + if (lumiance > 243) { + saturation = (saturation shr 4) + } else if (lumiance <= 217) { + if (lumiance > 192) { + saturation = (saturation shr 2) + } else if (lumiance > 179) { + saturation = (saturation shr 1) + } + } else { + saturation = (saturation shr 3) + } + return ((((hue and 0xff) shr 2) shl 10) + ((saturation shr 5) shl 7) + (lumiance shr 1)) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayConfiguration.kt b/src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayConfiguration.kt new file mode 100644 index 0000000..2ea8c9b --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayConfiguration.kt @@ -0,0 +1,33 @@ +package cacheops.cache.definition.decoder + +import Rs2MapEditor +import cacheops.cache.definition.data.UnderlayDefinition +import const.Archives +import const.Indices +import java.nio.ByteBuffer + +object FloorUnderlayConfiguration { + + val cache = Rs2MapEditor.library.index(Indices.CONFIGURATION).archive(Archives.FLOOR_UNDERLAYS) + + var floorUnderlays = hashMapOf() + + @JvmStatic + fun main(args: Array) { + cache!!.files().forEach { + val buffer = ByteBuffer.wrap(it.data) + val definition = FloorUnderlayDecoder().decode(buffer) + floorUnderlays[it.id] = definition + } + } + + fun init() { + cache!!.files().forEach { + val buffer = ByteBuffer.wrap(it.data) + val definition = FloorUnderlayDecoder().decode(buffer) + floorUnderlays[it.id] = definition + //println(definition.toString()) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayDecoder.kt new file mode 100644 index 0000000..50bc3f5 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/FloorUnderlayDecoder.kt @@ -0,0 +1,132 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.definition.data.UnderlayDefinition +import ext.medium +import ext.unsignedByte +import ext.unsignedShort +import java.nio.ByteBuffer + +class FloorUnderlayDecoder { + + private var color: Int = 0 + private var hsl16: Int = 0 + private var blendHueMultiplier: Int = 0 + private var hue: Int = 0 + private var saturation: Int = 0 + private var lumiance: Int = 0 + private var textureID = -1 + private var textureSize = 128 + private var blockShadow = true + private var blendHue: Int = 0 + + fun decode(buffer: ByteBuffer): UnderlayDefinition { + while (true) { + val opcode = buffer.unsignedByte() + if (opcode == 0) { + return UnderlayDefinition(color, hue, saturation, lumiance, textureID, textureSize, blockShadow, blendHueMultiplier, blendHue) + } + decodeUnderlayType(opcode, buffer) + } + } + + private fun decodeUnderlayType(opcode: Int, buffer: ByteBuffer) { + when (opcode) { + 1 -> { + color = buffer.medium() + intToColor(color) + } + 2 -> { + textureID = buffer.unsignedShort() + if (textureID == 65535) { + textureID = -1 + } + } + 3 -> { + textureSize = buffer.unsignedShort() + } + 4 -> { + blockShadow = false + } + } + } + + private fun intToColor(rgb: Int) { + val r: Double = (rgb shr 16 and 0xff) / 256.0 + val g: Double = (rgb shr 8 and 0xff) / 256.0 + val b: Double = (rgb and 0xff) / 256.0 + var min = r + if (g < min) { + min = g + } + if (b < min) { + min = b + } + var max = r + if (g > max) { + max = g + } + if (b > max) { + max = b + } + var h = 0.0 + var s = 0.0 + val l = (min + max) / 2.0 + if (min != max) { + if (l < 0.5) { + s = (max - min) / (max + min) + } + if (l >= 0.5) { + s = (max - min) / (2.0 - max - min) + } + if (r == max) { + h = (g - b) / (max - min) + } else if (g == max) { + h = 2.0 + (b - r) / (max - min) + } else if (b == max) { + h = 4.0 + (r - g) / (max - min) + } + } + h /= 6.0 + hue = (h * 256.0).toInt() + saturation = (s * 256.0).toInt() + lumiance = (l * 256.0).toInt() + if (saturation < 0) { + saturation = 0 + } else if (saturation > 255) { + saturation = 255 + } + if (lumiance < 0) { + lumiance = 0 + } else if (lumiance > 255) { + lumiance = 255 + } + blendHueMultiplier = if (l > 0.5) { + ((1.0 - l) * s * 512.0).toInt() + } else { + (l * s * 512.0).toInt() + } + if (blendHueMultiplier < 1) { + blendHueMultiplier = 1 + } + blendHue = (h * blendHueMultiplier).toInt() + hsl16 = hsl24to16(hue, saturation, lumiance) + } + + + private fun hsl24to16(h: Int, s: Int, l: Int): Int { + var s = s + if (l > 179) { + s /= 2 + } + if (l > 192) { + s /= 2 + } + if (l > 217) { + s /= 2 + } + if (l > 243) { + s /= 2 + } + return (h / (4 shl 10)) + (s / (32 shl 7)) + l / 2 + } +} diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/GroundItemDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/GroundItemDecoder.kt new file mode 100644 index 0000000..6e36764 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/GroundItemDecoder.kt @@ -0,0 +1,53 @@ +package cacheops.cache.definition.decoder + +import Rs2MapEditor +import cacheops.cache.definition.data.ItemDefinition +import const.cache +import ext.unsignedShort +import java.nio.ByteBuffer + +val itemDecoder = ItemDecoder(cache) + +class GroundItemDecoder { + + fun decode(data: ByteArray): ArrayList { + + val buffer = ByteBuffer.wrap(data) + val list = ArrayList() + + while (buffer.hasRemaining()) { + val coords = buffer.unsignedShort() + + val lz = coords shr 14 + val lx = (coords shr 7) and 0x3f + val ly = coords and 0x3f + + val itemID = buffer.unsignedShort() + + val respawnTime = buffer.unsignedShort() + + val amount = buffer.int + + list.add(Item(itemID, lx, ly, lz, respawnTime, amount)) + } + return list + } +} + +object GroundItemSpawnParser { + + fun parseItemSpawns(regionId: Int): ArrayList { + val x = (regionId shr 8) and 0xFF + val y = regionId and 0xFF + val groundItemData = Rs2MapEditor.library.data(5, "i${x}_${y}") + + if (groundItemData != null && groundItemData.isNotEmpty()) { + return GroundItemDecoder().decode(groundItemData) + } + return ArrayList() + } +} + +class Item(val id: Int, val x: Int, val y: Int, val plane: Int, val respawnTime: Int, val amount: Int) { + val definition: ItemDefinition = itemDecoder.forId(id) +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/ItemDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/ItemDecoder.kt new file mode 100644 index 0000000..232cc5c --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/ItemDecoder.kt @@ -0,0 +1,251 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.Cache +import cacheops.cache.DefinitionDecoder +import cacheops.cache.buffer.read.Reader +import cacheops.cache.definition.data.ItemDefinition +import const.Indices + +class ItemDecoder(cache: Cache) : DefinitionDecoder(cache, Indices.CONFIGURATION_ITEMS) { + + companion object { + val DEFINITIONS = HashMap() + } + + override fun create() = ItemDefinition() + + override fun getFile(id: Int) = id and 0xff + + override fun getArchive(id: Int) = id ushr 8 + + override fun readData(id: Int): ItemDefinition? { + return super.readData(id) + } + + fun forId(id : Int): ItemDefinition { + if(DEFINITIONS[id] != null) return DEFINITIONS[id]!! + val def = readData(id) + DEFINITIONS[id] = def ?: ItemDefinition(id, 0, "null") + return DEFINITIONS[id]!! + } + + override fun ItemDefinition.read(opcode: Int, buffer: Reader) { + when (opcode) { + 1 -> modelId = buffer.readShort() + 2 -> name = buffer.readString() + 4 -> spriteScale = buffer.readShort() + 5 -> spritePitch = buffer.readShort() + 6 -> spriteCameraRoll = buffer.readShort() + 7 -> { + spriteTranslateX = buffer.readShort() + if (spriteTranslateX > 32767) { + spriteTranslateX -= 65536 + } + } + 8 -> { + spriteTranslateY = buffer.readShort() + if (spriteTranslateY > 32767) { + spriteTranslateY -= 65536 + } + } + 11 -> stackable = 1 + 12 -> cost = buffer.readInt() + 16 -> members = true + 18 -> multiStackSize = buffer.readShort() + 23 -> primaryMaleModel = buffer.readUnsignedShort() + 24 -> secondaryMaleModel = buffer.readUnsignedShort() + 25 -> primaryFemaleModel = buffer.readUnsignedShort() + 26 -> secondaryFemaleModel = buffer.readUnsignedShort() + in 30..34 -> floorOptions[opcode - 30] = buffer.readString() + in 35..39 -> options[opcode - 35] = buffer.readString() + 40 -> readColours(buffer) + 41 -> readTextures(buffer) + 42 -> readColourPalette(buffer) + 65 -> exchangeable = true + 78 -> tertiaryMaleModel = buffer.readShort() + 79 -> tertiaryFemaleModel = buffer.readShort() + 90 -> primaryMaleDialogueHead = buffer.readShort() + 91 -> primaryFemaleDialogueHead = buffer.readShort() + 92 -> secondaryMaleDialogueHead = buffer.readShort() + 93 -> secondaryFemaleDialogueHead = buffer.readShort() + 95 -> spriteCameraYaw = buffer.readShort() + 96 -> dummyItem = buffer.readUnsignedByte() + 97 -> noteId = buffer.readShort() + 98 -> notedTemplateId = buffer.readShort() + in 100..109 -> { + if (stackIds == null) { + stackAmounts = IntArray(10) + stackIds = IntArray(10) + } + stackIds!![opcode - 100] = buffer.readShort() + stackAmounts!![opcode - 100] = buffer.readShort() + } + 110 -> floorScaleX = buffer.readShort() + 111 -> floorScaleZ = buffer.readShort() + 112 -> floorScaleY = buffer.readShort() + 113 -> ambience = buffer.readByte() + 114 -> diffusion = buffer.readByte() * 5 + 115 -> team = buffer.readUnsignedByte() + 121 -> lendId = buffer.readShort() + 122 -> lendTemplateId = buffer.readShort() + 125 -> { + maleWieldX = buffer.readByte() shl 2 + maleWieldZ = buffer.readByte() shl 2 + maleWieldY = buffer.readByte() shl 2 + } + 126 -> { + femaleWieldX = buffer.readByte() shl 2 + femaleWieldZ = buffer.readByte() shl 2 + femaleWieldY = buffer.readByte() shl 2 + } + 127 -> { + primaryCursorOpcode = buffer.readUnsignedByte() + primaryCursor = buffer.readShort() + } + 128 -> { + secondaryCursorOpcode = buffer.readUnsignedByte() + secondaryCursor = buffer.readShort() + } + 129 -> { + primaryInterfaceCursorOpcode = buffer.readUnsignedByte() + primaryInterfaceCursor = buffer.readShort() + } + 130 -> { + secondaryInterfaceCursorOpcode = buffer.readUnsignedByte() + secondaryInterfaceCursor = buffer.readShort() + } + 132 -> { + val length = buffer.readUnsignedByte() + campaigns = IntArray(length) + repeat(length) { count -> + campaigns!![count] = buffer.readShort() + } + } + 134 -> pickSizeShift = buffer.readUnsignedByte() + 139 -> singleNoteId = buffer.readShort() + 140 -> singleNoteTemplateId = buffer.readShort() + 249 -> readParameters(buffer) + } + } + + override fun ItemDefinition.changeValues() { + if (notedTemplateId != -1) { + toNote(getOrNull(notedTemplateId), getOrNull(noteId)) + } + if (lendTemplateId != -1) { + toLend(getOrNull(lendId), getOrNull(lendTemplateId)) + } + if (singleNoteTemplateId != -1) { + toSingleNote(getOrNull(singleNoteTemplateId), getOrNull(singleNoteId)) + } + } + + fun ItemDefinition.toLend(item: ItemDefinition?, template: ItemDefinition?) { + if (item == null || template == null) { + return + } + modifiedColours = item.modifiedColours + primaryMaleDialogueHead = item.primaryMaleDialogueHead + secondaryMaleDialogueHead = item.secondaryMaleDialogueHead + tertiaryMaleModel = item.tertiaryMaleModel + team = item.team + params = item.params + members = item.members + modifiedTextureColours = item.modifiedTextureColours + maleWieldZ = item.maleWieldZ + secondaryFemaleModel = item.secondaryFemaleModel + spriteCameraYaw = template.spriteCameraYaw + floorOptions = item.floorOptions + secondaryFemaleDialogueHead = item.secondaryFemaleDialogueHead + recolourPalette = item.recolourPalette + femaleWieldZ = item.femaleWieldZ + spritePitch = template.spritePitch + primaryFemaleModel = item.primaryFemaleModel + modelId = template.modelId + options = arrayOfNulls(5) + spriteCameraRoll = template.spriteCameraRoll + spriteTranslateY = template.spriteTranslateY + originalTextureColours = item.originalTextureColours + femaleWieldX = item.femaleWieldX + secondaryMaleModel = item.secondaryMaleModel + cost = 0 + maleWieldY = item.maleWieldY + originalColours = item.originalColours + spriteTranslateX = template.spriteTranslateX + femaleWieldY = item.femaleWieldY + primaryFemaleDialogueHead = item.primaryFemaleDialogueHead + spriteScale = template.spriteScale + name = item.name + tertiaryFemaleModel = item.tertiaryFemaleModel + primaryMaleModel = item.primaryMaleModel + maleWieldX = item.maleWieldX + System.arraycopy(item.options, 0, options, 0, 4) + options[4] = "Discard" + } + + fun ItemDefinition.toNote(template: ItemDefinition?, item: ItemDefinition?) { + if (item == null || template == null) { + return + } + spriteTranslateY = template.spriteTranslateY + originalColours = template.originalColours + cost = item.cost + name = item.name + modifiedTextureColours = template.modifiedTextureColours + spriteCameraRoll = template.spriteCameraRoll + spriteCameraYaw = template.spriteCameraYaw + originalTextureColours = template.originalTextureColours + modelId = template.modelId + spriteScale = template.spriteScale + recolourPalette = template.recolourPalette + stackable = 1 + spritePitch = template.spritePitch + spriteTranslateX = template.spriteTranslateX + members = item.members + modifiedColours = template.modifiedColours + } + + fun ItemDefinition.toSingleNote(template: ItemDefinition?, item: ItemDefinition?) { + if (item == null || template == null) { + return + } + cost = 0 + tertiaryMaleModel = item.tertiaryMaleModel + stackable = item.stackable + members = item.members + recolourPalette = item.recolourPalette + spriteTranslateY = template.spriteTranslateY + team = item.team + secondaryMaleModel = item.secondaryMaleModel + options = arrayOfNulls(5) + floorOptions = item.floorOptions + maleWieldZ = item.maleWieldZ + primaryMaleDialogueHead = item.primaryMaleDialogueHead + femaleWieldZ = item.femaleWieldZ + name = item.name + spriteScale = template.spriteScale + originalColours = item.originalColours + secondaryFemaleDialogueHead = item.secondaryFemaleDialogueHead + params = item.params + primaryFemaleModel = item.primaryFemaleModel + spritePitch = template.spritePitch + spriteCameraRoll = template.spriteCameraRoll + femaleWieldX = item.femaleWieldX + secondaryMaleDialogueHead = item.secondaryMaleDialogueHead + tertiaryFemaleModel = item.tertiaryFemaleModel + modifiedTextureColours = item.modifiedTextureColours + maleWieldX = item.maleWieldX + primaryFemaleDialogueHead = item.primaryFemaleDialogueHead + modelId = template.modelId + modifiedColours = item.modifiedColours + secondaryFemaleModel = item.secondaryFemaleModel + spriteTranslateX = template.spriteTranslateX + spriteCameraYaw = template.spriteCameraYaw + primaryMaleModel = item.primaryMaleModel + femaleWieldY = item.femaleWieldY + maleWieldY = item.maleWieldY + originalTextureColours = item.originalTextureColours + System.arraycopy(item.options, 0, options, 0, 4) + options[4] = "Discard" + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/MapTileDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/MapTileDecoder.kt new file mode 100644 index 0000000..cf34221 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/MapTileDecoder.kt @@ -0,0 +1,48 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.definition.data.MapDefinition +import cacheops.cache.definition.data.MapTile +import ext.unsignedByte +import java.nio.ByteBuffer + +class MapTileDecoder { + + fun readLoop(definition: MapDefinition, buffer: ByteBuffer): ByteArray { + + for (plane in 0 until 4) { + for (localX in 0 until 64) { + for (localY in 0 until 64) { + var height = 0 + var attrOpcode = 0 + var overlayPath = 0 + var overlayRotation = 0 + var overlayId = 0 + var settings = 0 + var underlayId = 0 + loop@ while (true) { + val config = buffer.unsignedByte() + if (config == 0) { + break@loop + } else if (config == 1) { + height = buffer.unsignedByte() + break@loop + } else if (config <= 49) { + attrOpcode = config + overlayId = buffer.unsignedByte() + overlayPath = (config - 2) / 4 + overlayRotation = 3 and (config - 2) + } else if (config <= 81) { + settings = config - 49 + } else { + underlayId = (config - 81) and 0xff + } + } + if (height != 0 || attrOpcode != 0 || overlayPath != 0 || overlayRotation != 0 || overlayId != 0 || settings != 0 || underlayId != 0) { + definition.setTile(localX, localY, plane, MapTile(height, attrOpcode, if(overlayId == 42) 0 else overlayId, overlayPath, overlayRotation, settings, underlayId)) + } + } + } + } + return buffer.array().copyOfRange(buffer.position(), buffer.capacity()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/MapTileParser.kt b/src/main/kotlin/cacheops/cache/definition/decoder/MapTileParser.kt new file mode 100644 index 0000000..512a38c --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/MapTileParser.kt @@ -0,0 +1,55 @@ +package cacheops.cache.definition.decoder + +import Rs2MapEditor +import cacheops.cache.definition.data.MapDefinition +import java.nio.ByteBuffer + +object MapTileParser { + + val definition = MapDefinition() + + lateinit var atmosphereDataTail: ByteArray + + @JvmStatic + fun main(args: Array) { + + val region = 12850 + val baseX = ((region shr 8) shl 6) + val baseY = ((region and 0xFF) shl 6) + val x = (region shr 8) and 0xFF + val y = region and 0xFF + val mapData = Rs2MapEditor.library.data(5,"m${x}_${y}") + + MapTileDecoder().readLoop(definition, ByteBuffer.wrap(mapData)) + + for (lclX in 0 until 64) { + for (lclY in 0 until 64) { + println(definition.getTile(lclX,lclY,0).underlayId) + } + } + + } + + fun init() { + definition.objects.clear() + definition.tiles.clear() + val region = Rs2MapEditor.region + val baseX = ((region shr 8) shl 6) + val baseY = ((region and 0xFF) shl 6) + val x = (region shr 8) and 0xFF + val y = region and 0xFF + val mapData = Rs2MapEditor.library.data(5,"m${x}_${y}") + println(mapData!!.size) + atmosphereDataTail = MapTileDecoder().readLoop(definition, ByteBuffer.wrap(mapData)) + } + + fun coordinateX(x: Int): Int { + val baseX = ((Rs2MapEditor.region shr 8) shl 6) + return x + baseX + } + + fun coordinateY(y: Int): Int { + val baseY = ((Rs2MapEditor.region and 0xFF) shl 6) + return y + baseY + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/NPCDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/NPCDecoder.kt new file mode 100644 index 0000000..466ccb7 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/NPCDecoder.kt @@ -0,0 +1,194 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.Cache +import cacheops.cache.DefinitionDecoder +import cacheops.cache.buffer.read.Reader +import cacheops.cache.definition.data.NPCDefinition +import const.Indices + +class NPCDecoder(cache: Cache, val member: Boolean) : DefinitionDecoder(cache, Indices.CONFIGURATION_NPCS) { + + companion object { + val DEFINITIONS = HashMap() + } + + override fun create() = NPCDefinition() + + override fun getFile(id: Int) = id and 0x7f + + override fun getArchive(id: Int) = id ushr 7 + + override fun readData(id: Int): NPCDefinition? { + return super.readData(id) + } + + fun forId(id : Int): NPCDefinition { + if(DEFINITIONS[id] != null) return DEFINITIONS[id]!! + val def = readData(id) + DEFINITIONS[id] = def ?: NPCDefinition(id,null,"null") + return DEFINITIONS[id]!! + } + + override fun NPCDefinition.read(opcode: Int, buffer: Reader) { + when (opcode) { + 1 -> { + val length = buffer.readUnsignedByte() + modelIds = IntArray(length) + repeat(length) { count -> + modelIds!![count] = buffer.readShort() + if (modelIds!![count] == 65535) { + modelIds!![count] = -1 + } + } + } + 2 -> name = buffer.readString() + 12 -> size = buffer.readUnsignedByte() + in 30..34 -> options[-30 + opcode] = buffer.readString() + 40 -> readColours(buffer) + 41 -> readTextures(buffer) + 42 -> { + val length = buffer.readUnsignedByte() + recolourPalette = ByteArray(length) + repeat(length) { count -> + recolourPalette!![count] = buffer.readByte().toByte() + } + } + 60 -> { + val length = buffer.readUnsignedByte() + dialogueModels = IntArray(length) + repeat(length) { count -> + dialogueModels!![count] = buffer.readShort() + } + } + 93 -> drawMinimapDot = false + 95 -> combat = buffer.readShort() + 97 -> scaleXY = buffer.readShort() + 98 -> scaleZ = buffer.readShort() + 99 -> priorityRender = true + 100 -> lightModifier = buffer.readByte() + 101 -> shadowModifier = 5 * buffer.readByte() + 102 -> headIcon = buffer.readShort() + 103 -> rotation = buffer.readShort() + 106, 118 -> { + varbit = buffer.readShort() + if (varbit == 65535) { + varbit = -1 + } + varp = buffer.readShort() + if (varp == 65535) { + varp = -1 + } + var last = -1 + if (opcode == 118) { + last = buffer.readShort() + if (last == 65535) { + last = -1 + } + } + val count = buffer.readUnsignedByte() + morphs = IntArray(count + 2) + for (index in 0..count) { + morphs!![index] = buffer.readShort() + if (morphs!![index] == 65535) { + morphs!![index] = -1 + } + } + morphs!![count + 1] = last + } + 107 -> clickable = false + 109 -> slowWalk = false + 111 -> animateIdle = false + 113 -> { + primaryShadowColour = buffer.readShort().toShort() + secondaryShadowColour = buffer.readShort().toShort() + } + 114 -> { + primaryShadowModifier = buffer.readByte().toByte() + secondaryShadowModifier = buffer.readByte().toByte() + } + 119 -> walkMask = buffer.readByte().toByte() + 121 -> { + translations = arrayOfNulls(modelIds!!.size) + val length = buffer.readUnsignedByte() + repeat(length) { + val index = buffer.readUnsignedByte() + translations!![index] = intArrayOf( + buffer.readByte(), + buffer.readByte(), + buffer.readByte() + ) + } + } + 122 -> hitbarSprite = buffer.readShort() + 123 -> height = buffer.readShort() + 125 -> respawnDirection = buffer.readByte().toByte() + 127 -> renderEmote = buffer.readShort() + 128 -> buffer.readUnsignedByte() + 134 -> { + idleSound = buffer.readShort() + if (idleSound == 65535) { + idleSound = -1 + } + crawlSound = buffer.readShort() + if (crawlSound == 65535) { + crawlSound = -1 + } + walkSound = buffer.readShort() + if (walkSound == 65535) { + walkSound = -1 + } + runSound = buffer.readShort() + if (runSound == 65535) { + runSound = -1 + } + soundDistance = buffer.readUnsignedByte() + } + 135 -> { + primaryCursorOp = buffer.readUnsignedByte() + primaryCursor = buffer.readShort() + } + 136 -> { + secondaryCursorOp = buffer.readUnsignedByte() + secondaryCursor = buffer.readShort() + } + 137 -> attackCursor = buffer.readShort() + 138 -> armyIcon = buffer.readShort() + 139 -> spriteId = buffer.readShort() + 140 -> ambientSoundVolume = buffer.readUnsignedByte() + 141 -> visiblePriority = true + 142 -> mapFunction = buffer.readShort() + 143 -> invisiblePriority = true + in 150..154 -> { + options[opcode - 150] = buffer.readString() + if (!member) { + options[opcode - 150] = null + } + } + 155 -> { + hue = buffer.readByte().toByte() + saturation = buffer.readByte().toByte() + lightness = buffer.readByte().toByte() + opacity = buffer.readByte().toByte() + } + 158 -> mainOptionIndex = 1.toByte() + 159 -> mainOptionIndex = 0.toByte() + 160 -> { + val length = buffer.readUnsignedByte() + campaigns = IntArray(length) + repeat(length) { count -> + campaigns!![count] = buffer.readShort() + } + } + 162 -> aBoolean2883 = true + 163 -> anInt2803 = buffer.readUnsignedByte() + 164 -> { + anInt2844 = buffer.readShort() + anInt2852 = buffer.readShort() + } + 165 -> anInt2831 = buffer.readUnsignedByte() + 168 -> anInt2862 = buffer.readUnsignedByte() + 249 -> readParameters(buffer) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/NPCSpawnDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/NPCSpawnDecoder.kt new file mode 100644 index 0000000..bd482ee --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/NPCSpawnDecoder.kt @@ -0,0 +1,48 @@ +package cacheops.cache.definition.decoder + +import Rs2MapEditor +import cacheops.cache.definition.data.NPCDefinition +import const.cache +import ext.unsignedShort +import java.nio.ByteBuffer + +val decoder = NPCDecoder(cache, true) + +class NPCSpawnDecoder { + + fun decode(data: ByteArray): ArrayList { + + val buffer = ByteBuffer.wrap(data) + val list = ArrayList() + + while (buffer.hasRemaining()) { + val coords = buffer.unsignedShort() + + val lz = coords shr 14 + val lx = (coords shr 7) and 0x3f + val ly = coords and 0x3f + + val npcID = buffer.unsignedShort() + list.add(NPC(npcID, lx, ly, lz)) + } + return list + } +} + +object NPCSpawnParser { + + fun parseNPCSpawns(regionId: Int): ArrayList { + val x = (regionId shr 8) and 0xFF + val y = regionId and 0xFF + val npcSpawnData = Rs2MapEditor.library.data(5, "n${x}_${y}") + + if (npcSpawnData != null && npcSpawnData.isNotEmpty()) { + return NPCSpawnDecoder().decode(npcSpawnData) + } + return ArrayList() + } +} + +class NPC(val id: Int, val x: Int, val y: Int, val plane: Int) { + val definition: NPCDefinition = decoder.forId(id) +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt new file mode 100644 index 0000000..b24536d --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt @@ -0,0 +1,237 @@ +package cacheops.cache.definition.decoder + +import cacheops.cache.Cache +import cacheops.cache.DefinitionDecoder +import cacheops.cache.buffer.read.Reader +import cacheops.cache.definition.data.ObjectDefinition +import const.Indices + +open class ObjectDecoder(cache: Cache, val member: Boolean, val configReplace: Boolean) : DefinitionDecoder(cache, Indices.CONFIGURATION_OBJECT) { + + val DEFINITIONS = HashMap() + + fun forId(id: Int): ObjectDefinition{ + if(DEFINITIONS[id] != null) return DEFINITIONS[id]!! + val def = readData(id) + DEFINITIONS[id] = def ?: ObjectDefinition(id,null,null,"null") + return DEFINITIONS[id]!! + } + + override fun create() = ObjectDefinition() + + override fun getFile(id: Int) = id and 0xff + + override fun getArchive(id: Int) = id ushr 8 + + override fun readData(id: Int): ObjectDefinition? { + val def = super.readData(id) + val replacement = def?.getReplacementId() ?: return def + return super.readData(replacement) + } + + private fun ObjectDefinition.getReplacementId(): Int? { + if (!configReplace) { + return null + } + val configIndex = 0 + val configs = configObjectIds ?: return null + val config = if (configIndex < 0 || (configIndex >= configs.size - 1 || configs[configIndex] == -1)) { + configs[configs.size - 1] + } else { + configs.getOrNull(configIndex) + } + return if (config != -1) config else null + } + + override fun ObjectDefinition.read(opcode: Int, buffer: Reader) { + when (opcode) { + 1 -> { + val count = buffer.readUnsignedByte() + if (count > 0) { + if (modelIds == null) { + modelIds = IntArray(count) + modelTypes = IntArray(count) + var i = 0 + while (count > i) { + modelIds!![i] = buffer.readUnsignedShort() + modelTypes!![i] = buffer.readUnsignedByte() + i++ + } + } else { + buffer.position(buffer.position() + count * 3) + } + } + } + 2 -> name = buffer.readString() + 5 -> { + val i_30_ = buffer.readUnsignedByte() + if (i_30_ > 0) { + if (modelIds == null) { + modelIds = IntArray(i_30_) + modelTypes = null + var i_31_ = 0 + while (i_30_ > i_31_) { + modelIds!![i_31_] = buffer.readUnsignedShort() + i_31_++ + } + } else { + buffer.position(buffer.position() + i_30_ * 2) + } + } + } + 14 -> sizeX = buffer.readUnsignedByte() + 15 -> sizeY = buffer.readUnsignedByte() + 17 -> { + blocksSky = false + solid = 0 + } + 18 -> blocksSky = false + 19 -> interactive = buffer.readUnsignedByte() + 21 -> contouredGround = 1 + 22 -> delayShading = true + 23 -> culling = 1 + 24 -> { + val length = buffer.readUnsignedShort() + if (length != 65535) { + animations = intArrayOf(length) + } + } + 27 -> solid = 1 + 28 -> offsetMultiplier = buffer.readUnsignedByte() + 29 -> brightness = buffer.readByte() + in 30..34 -> options[opcode - 30] = buffer.readString() + 39 -> contrast = buffer.readByte() * 5 + 40 -> readColours(buffer) + 41 -> readTextures(buffer) + 42 -> readColourPalette(buffer) + 62 -> mirrored = true + 64 -> castsShadow = false + 65 -> modelSizeX = buffer.readUnsignedShort() + 66 -> modelSizeZ = buffer.readUnsignedShort() + 67 -> modelSizeY = buffer.readUnsignedShort() + 69 -> blockFlag = buffer.readUnsignedByte() + 70 -> offsetX = buffer.readUnsignedShort() shl 2 + 71 -> offsetZ = buffer.readUnsignedShort() shl 2 + 72 -> offsetY = buffer.readUnsignedShort() shl 2 + 73 -> blocksLand = true + 74 -> ignoreOnRoute = true + 75 -> supportItems = buffer.readUnsignedByte() + 77, 92 -> { + varbitIndex = buffer.readUnsignedShort() + if (varbitIndex == 65535) { + varbitIndex = -1 + } + configId = buffer.readUnsignedShort() + if (configId == 65535) { + configId = -1 + } + var last = -1 + if (opcode == 92) { + last = buffer.readUnsignedShort() + if (last == 65535) { + last = -1 + } + } + val length = buffer.readUnsignedByte() + configObjectIds = IntArray(length + 2) + for (count in 0..length) { + configObjectIds!![count] = buffer.readUnsignedShort() + if (configObjectIds!![count] == 65535) { + configObjectIds!![count] = -1 + } + } + configObjectIds!![length + 1] = last + } + 78 -> { + anInt3015 = buffer.readUnsignedShort() + anInt3012 = buffer.readUnsignedByte() + } + 79 -> { + anInt2989 = buffer.readUnsignedShort() + anInt2971 = buffer.readUnsignedShort() + anInt3012 = buffer.readUnsignedByte() + val length = buffer.readUnsignedByte() + anIntArray3036 = IntArray(length) + repeat(length) { count -> + anIntArray3036!![count] = buffer.readUnsignedShort() + } + } + 81 -> { + contouredGround = 2.toByte() + anInt3023 = buffer.readUnsignedByte() * 256 + } + 82 -> hideMinimap = true + 88 -> aBoolean2972 = false + 89 -> animateImmediately = false + 90 -> aBoolean1502 = true + 91 -> isMembers = true + 93 -> { + contouredGround = 3 + anInt3023 = buffer.readUnsignedShort() + } + 94 -> contouredGround = 4 + 95 -> { + contouredGround = 5 + } + 96 -> aBoolean1507 = true + 97 -> adjustMapSceneRotation = true + 98 -> hasAnimation = true + 99 -> { + anInt2987 = buffer.readUnsignedByte() + anInt3008 = buffer.readUnsignedShort() + } + 100 -> { + anInt3038 = buffer.readUnsignedByte() + anInt3013 = buffer.readUnsignedShort() + } + 101 -> mapSceneRotation = buffer.readUnsignedByte() + 102 -> mapscene = buffer.readUnsignedShort() + 103 -> culling = 0 + 104 -> anInt3024 = buffer.readUnsignedByte() + 105 -> invertMapScene = true + 106 -> { + val length = buffer.readUnsignedByte() + var total = 0 + animations = IntArray(length) + percents = IntArray(length) + repeat(length) { count -> + animations!![count] = buffer.readUnsignedShort() + if (animations!![count] == 65535) { + animations!![count] = -1 + } + percents!![count] = buffer.readUnsignedByte() + total += percents!![count] + } + repeat(length) { count -> + percents!![count] = 65535 * percents!![count] / total + } + } + 107 -> mapDefinitionId = buffer.readUnsignedShort() + in 150..154 -> { + options[-150 + opcode] = buffer.readString() + if (!member) { + options[-150 + opcode] = null + } + } + 160 -> { + val length = buffer.readUnsignedByte() + anIntArray2981 = IntArray(length) + repeat(length) { count -> + anIntArray2981!![count] = buffer.readUnsignedShort() + } + } + 249 -> readParameters(buffer) + } + } + + companion object { + private fun skip(buffer: Reader) { + val length = buffer.readUnsignedByte() + repeat(length) { + buffer.skip(1) + val amount = buffer.readUnsignedByte() + buffer.skip(amount * 2) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/TileSceneryParser.kt b/src/main/kotlin/cacheops/cache/definition/decoder/TileSceneryParser.kt new file mode 100644 index 0000000..0aee4f5 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/TileSceneryParser.kt @@ -0,0 +1,59 @@ +package cacheops.cache.definition.decoder + +import Rs2MapEditor +import cacheops.cache.definition.data.ObjectDefinition +import const.cache +import ext.getBigSmart +import ext.getSmart +import java.nio.ByteBuffer + +val objectDecoder = ObjectDecoder(cache, true, false) + +object TileSceneryParser { + fun parseRegion(id: Int): ArrayList{ + if(XteaKeys.XTEAS.isEmpty()){ + XteaKeys.load() + } + + val regionX = (id shr 8) and 0xFF; + val regionY = id and 0xFF; + val data = Rs2MapEditor.library.data(5, "l${regionX}_${regionY}",XteaKeys.get(id)) + + return if(data != null) decode(ByteBuffer.wrap(data)) + else ArrayList() + } + + private fun decode(data: ByteBuffer): ArrayList{ + val list = ArrayList() + var objectId = -1 + while (true) { + var offset: Int = data.getBigSmart() + if (offset == 0) { + break + } + objectId += offset + var location = 0 + while (true) { + offset = data.getSmart() + if (offset == 0) { + break + } + location += offset - 1 + val y = location and 0x3f + val x = (location shr 6) and 0x3f + val configuration: Int = data.get().toInt() and 0xFF + val rotation = configuration and 0x3 + val type = configuration shr 2 + val z = location shr 12 + + list.add(Scenery(objectId,x,y,z,rotation,type)) + } + } + return list + } +} + +class Scenery(val id: Int, val x: Int, val y: Int, val plane: Int, val rotation: Int, val type: Int){ + val definition = objectDecoder.forId(id) +} + diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/XteaKeys.kt b/src/main/kotlin/cacheops/cache/definition/decoder/XteaKeys.kt new file mode 100644 index 0000000..a528b8d --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/decoder/XteaKeys.kt @@ -0,0 +1,33 @@ +package cacheops.cache.definition.decoder + +import const.xteaJson +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import org.json.simple.parser.JSONParser +import java.io.FileReader + +object XteaKeys { + val XTEAS = HashMap() + val DEFAULT_REGION_KEYS = intArrayOf(0,0,0,0) + + fun get(regionId: Int): IntArray{ + return XTEAS[regionId] ?: DEFAULT_REGION_KEYS + } + + val parser = JSONParser() + var reader: FileReader? = null + + fun load() { + var count = 0 + reader = FileReader(xteaJson) + val obj = parser.parse(reader) as JSONObject + val configlist = obj["xteas"] as JSONArray + for(config in configlist){ + val e = config as JSONObject + val id = e["regionId"].toString().toInt() + val keys = e["keys"].toString().split(",").map(String::toInt) + XTEAS[id] = intArrayOf(keys[0],keys[1],keys[2],keys[3]) + count++ + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/encoder/GroundItemEncoder.kt b/src/main/kotlin/cacheops/cache/definition/encoder/GroundItemEncoder.kt new file mode 100644 index 0000000..03bafe1 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/encoder/GroundItemEncoder.kt @@ -0,0 +1,48 @@ +package cacheops.cache.definition.encoder + +import Rs2MapEditor +import cacheops.cache.definition.decoder.Item +import const.Indices +import java.nio.ByteBuffer +import javax.swing.JOptionPane + +object GroundItemEncoder { + + fun write(item: ArrayList) { + + // Setup buffer | Determine capacity (ArrayList size * (short2 + short2 + short2 + int4)) + val buffer = ByteBuffer.allocate(item.size * 10) + + // Put data to buffer + item.forEach { + val formattedCoord = (it.plane shl 14) or (it.x shl 7) or it.y + buffer.putShort(formattedCoord.toShort()) + buffer.putShort(it.id.toShort()) + buffer.putShort(it.respawnTime.toShort()) + buffer.putInt(it.amount) + } + + // Convert buffer to byte array + val dataArray = buffer.array() + + // Get region information + val region = Rs2MapEditor.region + val x = (region shr 8) and 0xFF + val y = region and 0xFF + + // Put region data + if (dataArray != null) { + Rs2MapEditor.library.put(Indices.LANDSCAPES, "i${x}_${y}", dataArray, intArrayOf(0,0,0,0)) + Rs2MapEditor.library.update() + JOptionPane.showMessageDialog(Rs2MapEditor.mapPanel, "Items written to cache [✓]") + Rs2MapEditor.itemsUpdated = false + } else { + System.err.println("Panic! Something went VERY wrong!") + } + } +} + +fun main() { + val itemList = arrayListOf(Item(946, 24, 2,0, 6, 1)) + GroundItemEncoder.write(itemList) +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/encoder/MapTileEncoder.kt b/src/main/kotlin/cacheops/cache/definition/encoder/MapTileEncoder.kt new file mode 100644 index 0000000..6316064 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/encoder/MapTileEncoder.kt @@ -0,0 +1,49 @@ +package cacheops.cache.definition.encoder + +import Rs2MapEditor +import cacheops.cache.definition.data.MapDefinition +import cacheops.cache.definition.decoder.MapTileParser +import const.Indices +import java.nio.ByteBuffer + +object MapTileEncoder { + + fun write(definition: MapDefinition) { + val buffer = ByteBuffer.allocate(80000) + for (plane in 0 until 4) { + for (localX in 0 until 64) { + for (localY in 0 until 64) { + val tile = definition.getTile(localX, localY, plane) + if (tile.underlayId != 0) { + buffer.put(((tile.underlayId + 81) and 0xFF).toByte()) + } + if (tile.settings != 0) { + buffer.put(((tile.settings + 49) and 0xFF).toByte()) + } + if (tile.attrOpcode != 0) { + buffer.put((tile.attrOpcode and 0xFF).toByte()) + buffer.put((tile.overlayId and 0xFF).toByte()) + } + if (tile.height == 0) { + buffer.put((0).toByte()) + } else { + buffer.put((1).toByte()) + buffer.put((tile.height and 0xFF).toByte()) + } + } + } + } + val data = buffer.array().copyOf(buffer.position()) + val combinedData = data.plus(MapTileParser.atmosphereDataTail) + + val x = (Rs2MapEditor.region shr 8) and 0xFF + val y = Rs2MapEditor.region and 0xFF + + if (combinedData != null) { + Rs2MapEditor.library.put(Indices.LANDSCAPES, "m${x}_${y}", combinedData) + Rs2MapEditor.library.update() + } else { + System.err.println("Something went VERY wrong!") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/definition/encoder/NPCSpawnEncoder.kt b/src/main/kotlin/cacheops/cache/definition/encoder/NPCSpawnEncoder.kt new file mode 100644 index 0000000..f3a0ee2 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/definition/encoder/NPCSpawnEncoder.kt @@ -0,0 +1,35 @@ +package cacheops.cache.definition.encoder + +import Rs2MapEditor +import cacheops.cache.definition.decoder.NPC +import const.Indices +import java.nio.ByteBuffer +import javax.swing.JOptionPane + +object NPCSpawnEncoder { + + fun write(npc: ArrayList) { + val buffer = ByteBuffer.allocate(npc.size * 4) + + npc.forEach { + val formattedCoord = (it.plane shl 14) or (it.x shl 7) or it.y + buffer.putShort(formattedCoord.toShort()) + buffer.putShort(it.id.toShort()) + } + + val dataArray = buffer.array() + + val region = Rs2MapEditor.region + val x = (region shr 8) and 0xFF + val y = region and 0xFF + + if (dataArray != null) { + Rs2MapEditor.library.put(Indices.LANDSCAPES, "n${x}_${y}", dataArray, intArrayOf(0,0,0,0)) + Rs2MapEditor.library.update() + JOptionPane.showMessageDialog(Rs2MapEditor.mapPanel, "NPCs written to cache [✓]") + Rs2MapEditor.npcsUpdated = false + } else { + System.err.println("Panic! Something went VERY wrong!") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/secure/Huffman.kt b/src/main/kotlin/cacheops/cache/secure/Huffman.kt new file mode 100644 index 0000000..0e59060 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/secure/Huffman.kt @@ -0,0 +1,210 @@ +package cacheops.cache.secure + +import cacheops.cache.Cache +import cacheops.cache.buffer.read.Reader +import cacheops.cache.buffer.write.BufferWriter + +class Huffman(cache: Cache) { + + private var masks: IntArray? = null + private val frequencies: ByteArray + private var decryptKeys: IntArray + private val decryptedKeys: IntArray + private var decryptionValues = listOf(0, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1) + + /** + * Load huffman tree from cache for compression + */ + init { + val huffman = cache.getFile(10, 1)!! + frequencies = huffman + masks = IntArray(huffman.size) + decryptKeys = IntArray(8) + val freq = IntArray(33) + var key = 0 + //For each non-zero frequency + for ((index, size) in huffman.map { it.toInt() }.filter { it != 0 }.toIntArray().withIndex()) { + //Calculate maximum frequency + val maximumFreq = 1 shl 32 - size + //zero or the previous minimum + val currentFreq = freq[size] + //Store the min + masks!![index] = currentFreq + //Set the frequency to the + freq[size] = if (currentFreq and maximumFreq == 0) {//If the min and max are equal ish? + //Starting from the bottom find the smallest frequency indices + for (idx in size - 1 downTo 1) { + val leftFreq = freq[idx] + if (leftFreq != currentFreq) { + break + } + val rightFreq = 1 shl 32 - idx + if (rightFreq and leftFreq != 0) { + //Move up the tree? + freq[idx] = freq[idx - 1] + break + } + //Merge the two smallest trees + freq[idx] = leftFreq + rightFreq + } + //Sum of their frequencies + maximumFreq + currentFreq + } else { + //Move up the tree? + freq[size - 1] + } + for (idx in size + 1..32) { + if (currentFreq == freq[idx]) { + freq[idx] = freq[size] + } + } + + var decryptIndex = 0 + val value: Long = Int.MAX_VALUE + 1L + for(count in 0 until size) { + if (currentFreq and value.ushr(count).toInt() == 0) { + decryptIndex++ + } else { + if (decryptKeys[decryptIndex] == 0) { + decryptKeys[decryptIndex] = key + } + decryptIndex = decryptKeys[decryptIndex] + } + if (decryptKeys.size <= decryptIndex) { + val keys = IntArray(decryptKeys.size * 2) + System.arraycopy(decryptKeys, 0, keys, 0, decryptKeys.size) + decryptKeys = keys + } + } + decryptKeys[decryptIndex] = index xor -0x1 + if (key <= decryptIndex) { + key = 1 + decryptIndex + } + } + + decryptedKeys = decryptKeys.map { it xor -0x1 }.toIntArray() + } + + /** + * Decompresses string of length [characters] using Huffman coding + * @param packet The packet containing the compressed data + * @param characters The number of string characters to decompress + */ + fun decompress(packet: Reader, characters: Int): String { + val textBuffer = ByteArray(packet.readableBytes()) + packet.readBytes(textBuffer) + return decompress(textBuffer, characters) ?: "" + } + + fun decompress(message: ByteArray, length: Int): String? { + return try { + if(masks == null) { + return null + } + var charsDecoded = 0 + var keyIndex = 0 + val sb = StringBuilder() + chars@ for (character in message) { + for (value in decryptionValues) { + if (if (value == 0) character >= 0 else character.toInt() and value == 0) { + keyIndex++ + } else { + keyIndex = decryptKeys[keyIndex] + } + + val char = decryptedKeys[keyIndex] + if (char >= 0) { + sb.append(char.toChar()) + if (length <= ++charsDecoded) { + break@chars + } + keyIndex = 0 + } + } + } + sb.toString() + } catch (e: Throwable) { + e.printStackTrace() + null + } + } + + /** + * Formats, compresses and writes [message] to [builder] using Huffman coding + * @param message The message to encode + * @param builder The packet to write the compressed data too + */ + @ExperimentalStdlibApi + fun compress(message: String, builder: BufferWriter) { + try { + //Format the message + val messageData = formatMessage(message) + //Write message length + builder.writeSmart(messageData.size) + //Write the compressed message + compress(messageData, builder) + } catch (exception: Throwable) { + exception.printStackTrace() + } + } + + /** + * Compresses [message] using Huffman coding and writes to [builder] + * @param message The message to compress, split by symbol into a byte array + * @param builder The packet to write the compressed data too + */ + private fun compress(message: ByteArray, builder: BufferWriter) { + try { + if(masks == null) { + return + } + var key = 0 + val startPosition = builder.position() + var position = startPosition shl 3 + for (char in message) { + val character = char.toInt() and 0xff + val min = masks!![character] + val size = frequencies[character] + + var offset = position shr 3 + var bitOffset = position and 0x7 + key = key and (-bitOffset shr 31) + position += size + val byteSize = (bitOffset + size - 1 shr 3) + offset + bitOffset += 24 + key += min.ushr(bitOffset) + builder.setByte(offset, key) + + while (offset < byteSize) { + bitOffset -= 8 + key = min.ushr(bitOffset) + builder.setByte(++offset, key) + } + } + + //Set the packet position to the correct place + builder.position(7 + position shr 3) + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * Replaces unknown symbols with question marks + * @param message The text to format + * @return message split by character + */ + @ExperimentalStdlibApi + private fun formatMessage(message: String): ByteArray { + val array = ByteArray(message.length) + for ((index, c) in message.withIndex()) { + val char = c.code + array[index] = if (char <= 0 || (char in 128..159) || char > 255) { + 63.toByte() + } else { + char.toByte() + } + } + return array + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/secure/RSA.kt b/src/main/kotlin/cacheops/cache/secure/RSA.kt new file mode 100644 index 0000000..b699976 --- /dev/null +++ b/src/main/kotlin/cacheops/cache/secure/RSA.kt @@ -0,0 +1,56 @@ +package cacheops.cache.secure + +import java.math.BigInteger +import java.nio.ByteBuffer +import java.security.SecureRandom + +object RSA { + + /** + * Encrypt/decrypts bytes with key and modulus + */ + fun crypt(data: ByteArray, modulus: BigInteger, key: BigInteger): ByteArray { + return BigInteger(data).modPow(key, modulus).toByteArray() + } + + /** + * Encrypt/decrypts [ByteBuffer] with key and modulus + */ + fun crypt(data: ByteBuffer, modulus: BigInteger, key: BigInteger): ByteBuffer { + return ByteBuffer.wrap(BigInteger(data.array()).modPow(key, modulus).toByteArray()) + } + + @JvmStatic + fun main(args: Array) { + generateRsa() + } + + /** + * Generates rsa values + */ + private fun generateRsa() { + val bits = 1024 + val random = SecureRandom() + + var p: BigInteger + var q: BigInteger + var phi: BigInteger + var modulus: BigInteger + var publicKey: BigInteger + var privateKey: BigInteger + + do { + p = BigInteger.probablePrime(bits / 2, random) + q = BigInteger.probablePrime(bits / 2, random) + phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)) + + modulus = p.multiply(q) + publicKey = BigInteger("65537") + privateKey = publicKey.modInverse(phi) + } while (modulus.bitLength() != bits || privateKey.bitLength() != bits || phi.gcd(publicKey) != BigInteger.ONE) + + println("modulus: ${modulus.toString(16)}") + println("public key: ${publicKey.toString(16)}") + println("private key: ${privateKey.toString(16)}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/cacheops/cache/secure/Xtea.kt b/src/main/kotlin/cacheops/cache/secure/Xtea.kt new file mode 100644 index 0000000..d21d1fb --- /dev/null +++ b/src/main/kotlin/cacheops/cache/secure/Xtea.kt @@ -0,0 +1,77 @@ +package cacheops.cache.secure + +import java.nio.ByteBuffer + +object Xtea { + + /** + * The golden ratio. + */ + private const val GOLDEN_RATIO = -0x61c88647 + + /** + * The number of rounds. + */ + private const val ROUNDS = 32 + + /** + * Deciphers the specified [ByteBuffer] with the given key. + * @param buffer The buffer. + * @param key The key. + * @throws IllegalArgumentException if the key is not exactly 4 elements + * long. + */ + fun decipher(buffer: ByteArray, key: IntArray, start: Int = 0) { + if (key.size != 4) { + throw IllegalArgumentException() + } + + val numQuads = buffer.size / 8 + for (i in 0 until numQuads) { + var sum = GOLDEN_RATIO * ROUNDS + var v0 = getInt(buffer, start + i * 8) + var v1 = getInt(buffer, start + i * 8 + 4) + for (j in 0 until ROUNDS) { + v1 -= (v0 shl 4 xor v0.ushr(5)) + v0 xor sum + key[sum.ushr(11) and 3] + sum -= GOLDEN_RATIO + v0 -= (v1 shl 4 xor v1.ushr(5)) + v1 xor sum + key[sum and 3] + } + putInt(buffer, start + i * 8, v0) + putInt(buffer, start + i * 8 + 4, v1) + } + } + + /** + * Enciphers the specified [ByteArray] with the given key. + * @param buffer The buffer. + * @param key The key. + * @throws IllegalArgumentException if the key is not exactly 4 elements + * long. + */ + fun encipher(buffer: ByteArray, start: Int, end: Int, key: IntArray) { + if (key.size != 4) + throw IllegalArgumentException() + + val numQuads = (end - start) / 8 + for (i in 0 until numQuads) { + var sum = 0 + var v0 = getInt(buffer, start + i * 8) + var v1 = getInt(buffer, start + i * 8 + 4) + for (j in 0 until ROUNDS) { + v0 += (v1 shl 4 xor v1.ushr(5)) + v1 xor sum + key[sum and 3] + sum += GOLDEN_RATIO + v1 += (v0 shl 4 xor v0.ushr(5)) + v0 xor sum + key[sum.ushr(11) and 3] + } + putInt(buffer, start + i * 8, v0) + putInt(buffer, start + i * 8 + 4, v1) + } + } + + private fun getInt(buffer: ByteArray, index: Int) = (buffer[index].toInt() and 0xff shl 24) or (buffer[index + 1].toInt() and 0xff shl 16) or (buffer[index + 2].toInt() and 0xff shl 8) or (buffer[index + 3].toInt() and 0xff) + private fun putInt(buffer: ByteArray, index: Int, value: Int) { + buffer[index] = (value shr 24).toByte() + buffer[index + 1] = (value shr 16).toByte() + buffer[index + 2] = (value shr 8).toByte() + buffer[index + 3] = value.toByte() + } +} \ No newline at end of file diff --git a/src/main/kotlin/const/Archives.kt b/src/main/kotlin/const/Archives.kt new file mode 100644 index 0000000..8008544 --- /dev/null +++ b/src/main/kotlin/const/Archives.kt @@ -0,0 +1,10 @@ +package const + +object Archives { + + /** + * Archives that are located in the CONFIGURATION index (2) + */ + const val FLOOR_UNDERLAYS = 1 + const val FLOOR_OVERLAYS = 4 +} \ No newline at end of file diff --git a/src/main/kotlin/const/Image.kt b/src/main/kotlin/const/Image.kt new file mode 100644 index 0000000..4bcbe7d --- /dev/null +++ b/src/main/kotlin/const/Image.kt @@ -0,0 +1,13 @@ +package const + +import javax.imageio.ImageIO +import javax.swing.ImageIcon + +object Image { + val RED_DOT = ImageIcon(ImageIO.read(javaClass.getResource("/dot_red.png"))) + val YELLOW_DOT = ImageIcon(ImageIO.read(javaClass.getResource("/dot_yellow.png"))) + val DELETE_HI = ImageIcon(ImageIO.read(javaClass.getResource("/trash_hi.png"))) + val DELTE_LO = ImageIcon(ImageIO.read(javaClass.getResource("/trash_dark.png"))) + val ADD_HI = ImageIcon(ImageIO.read(javaClass.getResource("/add_hi.png"))) + val ADD_LO = ImageIcon(ImageIO.read(javaClass.getResource("/add_dark.png"))) +} \ No newline at end of file diff --git a/src/main/kotlin/const/Indices.kt b/src/main/kotlin/const/Indices.kt new file mode 100644 index 0000000..d7e1341 --- /dev/null +++ b/src/main/kotlin/const/Indices.kt @@ -0,0 +1,32 @@ +package const + +object Indices { + const val SKELETONS = 0 + const val SKINS = 1 + const val CONFIGURATION = 2 + const val INTERFACES = 3 + const val SYNTH_SOUNDS = 4 + const val LANDSCAPES = 5 + const val MUSIC = 6 + const val MODELS = 7 + const val SPRITES = 8 + const val TEXTURES = 9 + const val HUFFMAN_ENCODING = 10 + const val MIDI_JINGLES = 11 + const val CLIENT_SCRIPTS = 12 + const val FONT_METRICS = 13 + const val VORBIS = 14 + const val MIDI_INSTRUMENTS = 15 + const val CONFIGURATION_OBJECT = 16 + const val CONFIGURATION_ENUMS = 17 + const val CONFIGURATION_NPCS = 18 + const val CONFIGURATION_ITEMS = 19 + const val CONFIGURATION_SEQUENCES = 20 + const val CONFIGURATION_SPOTANIM = 21 + const val CONFIGURATION_VARBIT = 22 + const val WORLD_MAP = 23 + const val QUICKCHAT_MESSAGES = 24 + const val QUICKCHAT_MENUS = 25 + const val MATERIALS = 26 + const val CONFIGURATION_PARTICLES = 27 +} \ No newline at end of file diff --git a/src/main/kotlin/const/Misc.kt b/src/main/kotlin/const/Misc.kt new file mode 100644 index 0000000..35285ca --- /dev/null +++ b/src/main/kotlin/const/Misc.kt @@ -0,0 +1,8 @@ +package const + +import cacheops.cache.Cache +import cacheops.cache.CacheDelegate + +val cachePath = "/home/ceikry/IdeaProjects/2009scape/Server/data/cache" +val xteaJson = "/home/ceikry/IdeaProjects/2009scape/Server/data/configs/xteas.json" +val cache: Cache = CacheDelegate(cachePath) diff --git a/src/main/kotlin/ext/JagexTypes.kt b/src/main/kotlin/ext/JagexTypes.kt new file mode 100644 index 0000000..452d1e9 --- /dev/null +++ b/src/main/kotlin/ext/JagexTypes.kt @@ -0,0 +1,61 @@ +package ext + +import java.nio.ByteBuffer + +fun ByteBuffer.medium(): Int { + return (((get().toInt() shl 16) and 0xff0000) + ((get().toInt() shl 8) and 0xff00) + (get().toInt() and 0xff)) +// return ((get().toInt() and 0xff) + (get().toInt() shl 8 and 0xff00) + ) +} + +fun ByteBuffer.putJagexString(string: String) { + put(0) + put(string.toByteArray()) + put(0) +} + +fun ByteBuffer.putJagexStringQuickChat(string: String) { + put(1) + put(string.toByteArray()) + put(0) +} + +fun ByteBuffer.getJagexString(): String { + val stringBuilder = StringBuilder() + var b: Byte + while (get().also { b = it }.toInt() != 0) { + stringBuilder.append(b.toInt().toChar()) + } + return stringBuilder.toString() +} + +fun ByteBuffer.readNullCircumfixedString(): String { + if (unsignedByte() != 0) + throw IllegalArgumentException("byte != 0 infront of null-circumfixed string") + return getJagexString() +} + +fun ByteBuffer.unsignedByte(): Int { + return get().toInt() and 0xFF +} + +fun ByteBuffer.unsignedShort(): Int { + return short.toInt() and 0xFFFF +} + +fun ByteBuffer.getBigSmart(): Int { + var value = 0 + var current: Int = getSmart() + while (current == 32767) { + current = getSmart() + value += 32767 + } + value += current + return value +} + +fun ByteBuffer.getSmart(): Int { + val peek: Int = get().toInt() and 0xFF + return if (peek <= Byte.MAX_VALUE) { + peek + } else (peek shl 8 or (get().toInt() and 0xFF)) - 32768 +} \ No newline at end of file diff --git a/src/main/kotlin/ext/Misc.kt b/src/main/kotlin/ext/Misc.kt new file mode 100644 index 0000000..2ad5ae1 --- /dev/null +++ b/src/main/kotlin/ext/Misc.kt @@ -0,0 +1,12 @@ +import cacheops.cache.definition.data.MapTile +import cacheops.cache.definition.decoder.MapTileParser + +fun Pair.getSurroundingTiles() : List { + val list = ArrayList() + list.add(MapTileParser.definition.getTile(first, second + 1, Rs2MapEditor.plane)) + list.add(MapTileParser.definition.getTile(first + 1, second, Rs2MapEditor.plane)) + list.add(MapTileParser.definition.getTile(first, second - 1, Rs2MapEditor.plane)) + list.add(MapTileParser.definition.getTile(first - 1, second, Rs2MapEditor.plane)) + + return list +} \ No newline at end of file diff --git a/src/main/kotlin/ext/Vector3f.kt b/src/main/kotlin/ext/Vector3f.kt new file mode 100644 index 0000000..2f92a40 --- /dev/null +++ b/src/main/kotlin/ext/Vector3f.kt @@ -0,0 +1,24 @@ +package ext + +class Vector3f(var x: Float, var y: Float, var z: Float) { + + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + result = 31 * result + z.hashCode() + return result + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Vector3f + + if (x != other.x) return false + if (y != other.y) return false + if (z != other.z) return false + + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/misc/CustomEventQueue.kt b/src/main/kotlin/misc/CustomEventQueue.kt new file mode 100644 index 0000000..249e210 --- /dev/null +++ b/src/main/kotlin/misc/CustomEventQueue.kt @@ -0,0 +1,22 @@ +package misc + +import java.awt.AWTEvent +import java.awt.Event +import java.awt.EventQueue +import java.awt.Toolkit +import java.awt.event.KeyEvent + +class CustomEventQueue : EventQueue() { + override fun dispatchEvent(event: AWTEvent?) { + if(event is KeyEvent){ + GlobalKeybinds.handle(event) + } + super.dispatchEvent(event) + } + + companion object { + fun install() { + Toolkit.getDefaultToolkit().systemEventQueue.push(CustomEventQueue()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/misc/GlobalKeybinds.kt b/src/main/kotlin/misc/GlobalKeybinds.kt new file mode 100644 index 0000000..f9cc678 --- /dev/null +++ b/src/main/kotlin/misc/GlobalKeybinds.kt @@ -0,0 +1,65 @@ +package misc + +import cacheops.cache.definition.encoder.GroundItemEncoder +import cacheops.cache.definition.encoder.NPCSpawnEncoder +import java.awt.Event +import java.awt.event.KeyEvent +import tools.* + +var ctrlPressed = false + +object GlobalKeybinds{ + fun handle(event: KeyEvent){ + if(event.id == Event.KEY_PRESS){ + if(event.keyCode == KeyEvent.VK_CONTROL) { + ctrlPressed = true + } + return + } + if(event.id == Event.KEY_RELEASE){ + if(event.keyCode == KeyEvent.VK_CONTROL) { + ctrlPressed = false + return + } + } + + if(ctrlPressed){ //Handle keybinds that rely on control being pressed + when(event.keyCode){ + KeyEvent.VK_S -> { //CTRL+S + if(Rs2MapEditor.npcsUpdated){ + //NPCSpawnEncoder.write(Rs2MapEditor.npcs) + Editors.NPC_SPAWNS.data.save() + } + if(Rs2MapEditor.itemsUpdated){ + GroundItemEncoder.write(Rs2MapEditor.items) + } + } + + KeyEvent.VK_UP -> { + var newRegion = Util.getRegion (Rs2MapEditor.region, 0, 1) + Rs2MapEditor.loadRegion(newRegion) + } + + KeyEvent.VK_DOWN -> { + var newRegion = Util.getRegion (Rs2MapEditor.region, 0, -1) + Rs2MapEditor.loadRegion(newRegion) + } + + KeyEvent.VK_LEFT -> { + var newRegion = Util.getRegion (Rs2MapEditor.region, -1, 0) + Rs2MapEditor.loadRegion(newRegion) + } + + KeyEvent.VK_RIGHT -> { + var newRegion = Util.getRegion (Rs2MapEditor.region, 1, 0) + Rs2MapEditor.loadRegion(newRegion) + } + + } + } + + else when(event.keyCode){ + KeyEvent.VK_ESCAPE -> Rs2MapEditor.state = EditorState.NONE + } + } +} diff --git a/src/main/kotlin/misc/ImgButton.kt b/src/main/kotlin/misc/ImgButton.kt new file mode 100644 index 0000000..c7a5487 --- /dev/null +++ b/src/main/kotlin/misc/ImgButton.kt @@ -0,0 +1,56 @@ +package misc + +import java.awt.Image +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import javax.swing.ImageIcon +import javax.swing.JLabel + +open class ImgButton(enabledImg: ImageIcon, disabledImage: ImageIcon = enabledImg, val autoHandleMouse: Boolean = true) : JLabel() { + + private var hoverMethod: (MouseEvent) -> Unit = {} + private var mouseLeaveMethod: (MouseEvent) -> Unit = {} + private var onClickMethod: (MouseEvent) -> Unit = {} + + init { + isEnabled = false + icon = enabledImg + disabledIcon = disabledImage + addMouseListener(object : MouseListener { + override fun mouseClicked(p0: MouseEvent) { + onClickMethod.invoke(p0) + } + + override fun mousePressed(p0: MouseEvent?) {} + + override fun mouseReleased(p0: MouseEvent?) {} + + override fun mouseEntered(p0: MouseEvent) { + if(autoHandleMouse) isEnabled = true + hoverMethod.invoke(p0) + } + + override fun mouseExited(p0: MouseEvent) { + if(autoHandleMouse) isEnabled = false + mouseLeaveMethod.invoke(p0) + } + }) + } + + fun onClick(handler: (event: MouseEvent) -> Unit){ + onClickMethod = handler + } + + fun onMouseEnter(handler: (event: MouseEvent) -> Unit){ + hoverMethod = handler + } + + fun onMouseExit(handler: (event: MouseEvent) -> Unit){ + mouseLeaveMethod = handler + } + + fun scale(width: Int, height: Int){ + icon = ImageIcon((icon as ImageIcon).image.getScaledInstance(width,height, Image.SCALE_SMOOTH)) + disabledIcon = ImageIcon((disabledIcon as ImageIcon).image.getScaledInstance(width, height, Image.SCALE_SMOOTH)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/tools/ItemSearchTool.kt b/src/main/kotlin/tools/ItemSearchTool.kt new file mode 100644 index 0000000..f9cd65c --- /dev/null +++ b/src/main/kotlin/tools/ItemSearchTool.kt @@ -0,0 +1,101 @@ +package tools + +import cacheops.cache.definition.decoder.decoder +import cacheops.cache.definition.decoder.itemDecoder +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.KeyEvent +import java.awt.event.KeyListener +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import java.util.* +import java.util.regex.PatternSyntaxException +import javax.swing.* +import javax.swing.table.DefaultTableCellRenderer +import javax.swing.table.DefaultTableModel +import javax.swing.table.TableRowSorter + +object ItemSearchTool : JFrame("Item Search") { + var model = DefaultTableModel() + var searchField = JTextField() + var sorter = TableRowSorter(model) + var cellRenderer = DefaultTableCellRenderer() + var populated = false + var caller: ((Int,String) -> Unit)? = null + private fun filter() { + var rf: RowFilter? = null + //If current expression doesn't parse, don't update. + rf = try { + RowFilter.regexFilter(searchField.text.capitalize(), 1) + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun open() { + if (!populated) { + SwingUtilities.invokeLater { + for (i in 0..15431) { + model.addRow(arrayOf(i, itemDecoder.forId(i).name)) + } + isVisible = true + populated = true + } + } else { + isVisible = true + } + } + + init { + layout = BorderLayout() + cellRenderer.toolTipText = "Double-Click to select." + val searchPanel = JPanel() + val searchLabel = JLabel("Search for NPC:") + searchField.preferredSize = Dimension(100, 20) + searchPanel.add(searchLabel) + searchPanel.add(searchField) + add(searchPanel, BorderLayout.NORTH) + setLocationRelativeTo(null) + val itemTable: JTable = object : JTable(model) { + override fun editCellAt(i: Int, i1: Int, eventObject: EventObject): Boolean { + return false + } + } + model.addColumn("ID") + model.addColumn("Name") + itemTable.rowSorter = sorter + itemTable.columnModel.getColumn(0).maxWidth = 55 + itemTable.columnModel.getColumn(0).cellRenderer = cellRenderer + itemTable.columnModel.getColumn(1).cellRenderer = cellRenderer + itemTable.addMouseListener(object : MouseListener { + override fun mouseClicked(mouseEvent: MouseEvent) { + if (mouseEvent.clickCount == 2) { + val table = mouseEvent.source as JTable + val row = table.selectedRow + val selectedID = table.getValueAt(row,0).toString().toInt() + val selectedName = table.getValueAt(row,1).toString() + caller?.invoke(selectedID,selectedName) + isVisible = false + } + } + + override fun mousePressed(mouseEvent: MouseEvent) {} + override fun mouseReleased(mouseEvent: MouseEvent) {} + override fun mouseEntered(mouseEvent: MouseEvent) {} + override fun mouseExited(mouseEvent: MouseEvent) {} + }) + searchField.addKeyListener(object : KeyListener { + override fun keyTyped(keyEvent: KeyEvent) { + filter() + } + + override fun keyPressed(keyEvent: KeyEvent) {} + override fun keyReleased(keyEvent: KeyEvent) {} + }) + val scrollPane = JScrollPane(itemTable) + add(scrollPane, BorderLayout.SOUTH) + pack() + isVisible = false + } +} \ No newline at end of file diff --git a/src/main/kotlin/tools/NPCSearchTool.kt b/src/main/kotlin/tools/NPCSearchTool.kt new file mode 100644 index 0000000..8747561 --- /dev/null +++ b/src/main/kotlin/tools/NPCSearchTool.kt @@ -0,0 +1,100 @@ +package tools + +import cacheops.cache.definition.decoder.decoder +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.KeyEvent +import java.awt.event.KeyListener +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import java.util.* +import java.util.regex.PatternSyntaxException +import javax.swing.* +import javax.swing.table.DefaultTableCellRenderer +import javax.swing.table.DefaultTableModel +import javax.swing.table.TableRowSorter + +object NPCSearchTool : JFrame("NPC Search") { + var model = DefaultTableModel() + var searchField = JTextField() + var sorter = TableRowSorter(model) + var cellRenderer = DefaultTableCellRenderer() + var populated = false + var caller: ((Int,String) -> Unit)? = null + private fun filter() { + var rf: RowFilter? = null + //If current expression doesn't parse, don't update. + rf = try { + RowFilter.regexFilter(searchField.text.capitalize(), 1) + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun open() { + if (!populated) { + SwingUtilities.invokeLater { + for (i in 0..9422) { + model.addRow(arrayOf(i, decoder.forId(i).name)) + } + isVisible = true + populated = true + } + } else { + isVisible = true + } + } + + init { + layout = BorderLayout() + cellRenderer.toolTipText = "Double-Click to select." + val searchPanel = JPanel() + val searchLabel = JLabel("Search for NPC:") + searchField.preferredSize = Dimension(100, 20) + searchPanel.add(searchLabel) + searchPanel.add(searchField) + add(searchPanel, BorderLayout.NORTH) + setLocationRelativeTo(null) + val npcTable: JTable = object : JTable(model) { + override fun editCellAt(i: Int, i1: Int, eventObject: EventObject): Boolean { + return false + } + } + model.addColumn("ID") + model.addColumn("Name") + npcTable.rowSorter = sorter + npcTable.columnModel.getColumn(0).maxWidth = 55 + npcTable.columnModel.getColumn(0).cellRenderer = cellRenderer + npcTable.columnModel.getColumn(1).cellRenderer = cellRenderer + npcTable.addMouseListener(object : MouseListener { + override fun mouseClicked(mouseEvent: MouseEvent) { + if (mouseEvent.clickCount == 2) { + val table = mouseEvent.source as JTable + val row = table.selectedRow + val selectedID = table.getValueAt(row,0).toString().toInt() + val selectedName = table.getValueAt(row,1).toString() + caller?.invoke(selectedID,selectedName) + isVisible = false + } + } + + override fun mousePressed(mouseEvent: MouseEvent) {} + override fun mouseReleased(mouseEvent: MouseEvent) {} + override fun mouseEntered(mouseEvent: MouseEvent) {} + override fun mouseExited(mouseEvent: MouseEvent) {} + }) + searchField.addKeyListener(object : KeyListener { + override fun keyTyped(keyEvent: KeyEvent) { + filter() + } + + override fun keyPressed(keyEvent: KeyEvent) {} + override fun keyReleased(keyEvent: KeyEvent) {} + }) + val scrollPane = JScrollPane(npcTable) + add(scrollPane, BorderLayout.SOUTH) + pack() + isVisible = false + } +} \ No newline at end of file diff --git a/src/main/kotlin/tools/Util.kt b/src/main/kotlin/tools/Util.kt new file mode 100644 index 0000000..588d275 --- /dev/null +++ b/src/main/kotlin/tools/Util.kt @@ -0,0 +1,23 @@ +package tools + +object Util { + fun getRegionId(location: TableData.Location) : Int { + return ((location.x shr 6) shl 8) or (location.y shr 6) + } + + fun getRegionCoordinates(location: TableData.Location) : IntArray { + return intArrayOf(location.x and 63, location.y and 63, location.z) + } + + fun getAbsoluteCoordinates(regionId: Int, regionX: Int, regionY: Int, plane: Int) : TableData.Location { + val x = regionId shr 8 + val y = regionId and 0xFF + return TableData.Location((x shl 6) or regionX, (y shl 6) or regionY, plane) + } + + fun getRegion (currentRegionId: Int, changeX: Int, changeY: Int) : Int { + var x = ((currentRegionId shr 8) + changeX) shl 8 + var y = ((currentRegionId and 0xFF) + changeY) + return (x or y) + } +} diff --git a/src/main/kotlin/ui/InfoPane.kt b/src/main/kotlin/ui/InfoPane.kt new file mode 100644 index 0000000..a8ec759 --- /dev/null +++ b/src/main/kotlin/ui/InfoPane.kt @@ -0,0 +1,23 @@ +package ui + +import java.awt.Color +import java.awt.Dimension +import java.awt.event.KeyEvent +import javax.swing.JComponent +import javax.swing.JTabbedPane +import javax.swing.border.CompoundBorder +import javax.swing.border.EmptyBorder +import javax.swing.border.LineBorder + +class InfoPane : JTabbedPane() { + init { + val informationComponent: JComponent = TileInformationPanel() + + preferredSize = Dimension(230, 4000) + border = CompoundBorder(LineBorder(Color.DARK_GRAY), EmptyBorder(0, 0, 0, 0)) + + addTab("Information", informationComponent) + add("NPC Spawn", Rs2MapEditor.npcPanel) + add("Item Spawn", Rs2MapEditor.itemPanel) + } +} diff --git a/src/main/kotlin/ui/ItemSpawnPanel.kt b/src/main/kotlin/ui/ItemSpawnPanel.kt new file mode 100644 index 0000000..2f8755a --- /dev/null +++ b/src/main/kotlin/ui/ItemSpawnPanel.kt @@ -0,0 +1,128 @@ +package ui + +import cacheops.cache.definition.decoder.Item +import const.Image +import misc.ImgButton +import java.awt.BorderLayout +import java.awt.Color +import java.awt.Dimension +import java.awt.FlowLayout +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.BoxLayout +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.border.MatteBorder + +class ItemSpawnPanel : JPanel() { + val rowBorder = MatteBorder(1, 1, 1, 1, Color.WHITE) + init { + layout = BoxLayout(this, BoxLayout.PAGE_AXIS) + + add(ItemSpawnerPanel()) + + Rs2MapEditor.items.forEach { + val row = ItemRow(it, this) + row.border = rowBorder + Rs2MapEditor.itemRows.add(row) + add(row) + } + } + + fun redrawRows() { + Rs2MapEditor.itemRows.forEach { + remove(it) + } + Rs2MapEditor.itemRows.clear() + Rs2MapEditor.items.filter { it.plane == Rs2MapEditor.plane }.forEach { + val row = ItemRow(it, this) + row.border = rowBorder + Rs2MapEditor.itemRows.add(row) + add(row) + } + repaint() + } + + class ItemSpawnerPanel : JPanel() { + init { + val topPanel = JPanel(FlowLayout()) + val bottomPanel = JPanel(FlowLayout()) + val addButton = ImgButton(Image.ADD_HI, Image.ADD_LO) + addButton.onClick { + Rs2MapEditor.state = EditorState.ADD_GROUNDITEM + } + Rs2MapEditor.itemIdInput.minimumSize = Dimension(200, 25) + Rs2MapEditor.itemIdInput.maximumSize = Dimension(200, 25) + Rs2MapEditor.itemIdInput.preferredSize = Dimension(200, 25) + + Rs2MapEditor.itemRespawnInput.minimumSize = Dimension(50, 25) + Rs2MapEditor.itemRespawnInput.maximumSize = Dimension(50, 25) + Rs2MapEditor.itemRespawnInput.preferredSize = Dimension(50, 25) + Rs2MapEditor.itemRespawnInput.text = "1" + + Rs2MapEditor.itemAmountInput.minimumSize = Dimension(75, 25) + Rs2MapEditor.itemAmountInput.maximumSize = Dimension(75, 25) + Rs2MapEditor.itemAmountInput.preferredSize = Dimension(75, 25) + Rs2MapEditor.itemAmountInput.text = "1" + + val timeLabel = JLabel("\uD83D\uDD64") + timeLabel.toolTipText = "Respawn Time (ticks)" + + minimumSize = Dimension(300, 68) + preferredSize = Dimension(300, 68) + maximumSize = Dimension(300, 68) + + layout = BorderLayout() + topPanel.add(Rs2MapEditor.itemIdInput) + topPanel.add(addButton) + bottomPanel.add(timeLabel) + bottomPanel.add(Rs2MapEditor.itemRespawnInput) + bottomPanel.add(JLabel("AMT:")) + bottomPanel.add(Rs2MapEditor.itemAmountInput) + add(topPanel,BorderLayout.NORTH) + add(bottomPanel,BorderLayout.SOUTH) + border = MatteBorder(1, 1, 1, 1, Color.GREEN) + } + } + + class ItemRow(val item: Item, val parent: JPanel) : JPanel(){ + init { + layout = FlowLayout() + add(JLabel("${item.definition.name} [${item.amount}] -- \uD83D\uDD64 ${item.respawnTime}")) + val deleteButton = ImgButton(Image.DELETE_HI, Image.DELTE_LO) + add(deleteButton) + minimumSize = Dimension(300, 40) + preferredSize = Dimension(300, 40) + maximumSize = Dimension(300, 40) + deleteButton.onClick { + Rs2MapEditor.items.remove(item) + parent.remove(this) + parent.repaint() + Rs2MapEditor.itemRows.remove(this) + if(Rs2MapEditor.items.filter { it.x == item.x && it.y == item.y }.isEmpty()){ + Rs2MapEditor.componentPointMap.filter { it.key.x == item.x && it.key.y == item.y }.forEach { (_,cell) -> + cell.components.forEach { c -> + if(c is JLabel && c.icon == Image.RED_DOT) cell.remove(c) + } + cell.repaint() + Rs2MapEditor.itemsUpdated = true + } + } + } + + addMouseListener(object : MouseAdapter(){ + val border = MatteBorder(1, 1, 1, 1, Color.YELLOW) + val defaultBorder = MatteBorder(1, 1, 1, 1, Color.GRAY) + override fun mouseEntered(e: MouseEvent?) { + super.mouseEntered(e) + Rs2MapEditor.componentPointMap.filter { it.key.x == item.x && it.key.y == item.y }.forEach { it.value.border = border } + } + + override fun mouseExited(e: MouseEvent?) { + super.mouseExited(e) + Rs2MapEditor.componentPointMap.filter { it.key.x == item.x && it.key.y == item.y }.forEach{ it.value.border = defaultBorder } + } + }) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ui/MapCell.kt b/src/main/kotlin/ui/MapCell.kt new file mode 100644 index 0000000..bfcce8d --- /dev/null +++ b/src/main/kotlin/ui/MapCell.kt @@ -0,0 +1,396 @@ +package ui + +import EditorData +import EditorState +import Rs2MapEditor +import TableData +import cacheops.cache.definition.data.MapTile +import cacheops.cache.definition.decoder.Item +import cacheops.cache.definition.decoder.MapTileParser +import cacheops.cache.definition.decoder.NPC +import cacheops.cache.definition.decoder.Scenery +import const.Image +import tools.Util +import java.awt.* +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.SwingUtilities + +class MapCell : JPanel() { + var defaultBackground: Color = background + val scenery = ArrayList() + override fun getPreferredSize(): Dimension { + return Dimension(Rs2MapEditor.cellX, Rs2MapEditor.cellY) + } + init { + + //add(underlayLabel) + addMouseListener(object : MouseAdapter() { + override fun mouseEntered(e: MouseEvent) { + Rs2MapEditor.selectedPointX = e.component.bounds.location.x / Rs2MapEditor.cellX + Rs2MapEditor.selectedPointY = e.component.bounds.location.y / Rs2MapEditor.cellY + Rs2MapEditor.statusLabel.foreground = Color.YELLOW + Rs2MapEditor.statusLabel.text = "Region: ${Rs2MapEditor.region} | " + + "Local Coordinates: [${Rs2MapEditor.selectedPointX}, ${Rs2MapEditor.selectedPointY}] | " + + "Global Coordinates: [${MapTileParser.coordinateX(Rs2MapEditor.selectedPointX)}, ${ + MapTileParser.coordinateX( + Rs2MapEditor.selectedPointY + ) + }] | " + + "Viewing Underlay: ${ + MapTileParser.definition.getTile( + Rs2MapEditor.selectedPointX, + Rs2MapEditor.selectedPointY, 0).underlayId} | " + + "Selected Underlay: ${if (Rs2MapEditor.selectedUnderlayId == null) "No underlay selected!" else "${Rs2MapEditor.selectedUnderlayId}"}" + defaultBackground = background + if(Rs2MapEditor.state == EditorState.SET_UNDERLAY || Rs2MapEditor.state == EditorState.NONE) { + background = Color.BLUE + } else if (Rs2MapEditor.state == EditorState.ADD_NPC){ + background = Color.YELLOW + } else if (Rs2MapEditor.state == EditorState.ADD_GROUNDITEM){ + background = Color.RED + } + } + + override fun mouseExited(e: MouseEvent) { + background = defaultBackground + } + + override fun mouseClicked(e: MouseEvent) { + if (SwingUtilities.isLeftMouseButton(e)) { + if (Rs2MapEditor.selectedUnderlayId != null && Rs2MapEditor.state == EditorState.SET_UNDERLAY) { + val originalTile = MapTileParser.definition.getTile( + Rs2MapEditor.selectedPointX, + Rs2MapEditor.selectedPointY, 0) + val underlayID = Rs2MapEditor.selectedUnderlayId + val point = Point(Rs2MapEditor.selectedPointX, Rs2MapEditor.selectedPointY) + val oldValue = Rs2MapEditor.colorPointMap[point] + Rs2MapEditor.colorPointMap[point] = underlayID!! + println("UPDATED UNDERLAY VALUES @ [${point.x}, ${point.y}] [$oldValue >> ${Rs2MapEditor.colorPointMap[point]}]") + defaultBackground = Rs2MapEditor.underlayMap[Rs2MapEditor.selectedUnderlayId]?.getRGB()!! + background = Rs2MapEditor.underlayMap[Rs2MapEditor.selectedUnderlayId]?.getRGB() + + // Update map tile cacheops.definitions with new tile information + MapTileParser.definition.setTile(point.x, point.y, Rs2MapEditor.plane, + MapTile( + originalTile.height, + originalTile.attrOpcode, + originalTile.overlayId, + originalTile.overlayPath, + originalTile.overlayRotation, + originalTile.settings, + underlayID + ) + ) + } + else if (Rs2MapEditor.npcIdInput.text.isNotEmpty() && Rs2MapEditor.state == EditorState.ADD_NPC){ + val id = Rs2MapEditor.npcIdInput.text.toIntOrNull() + if(id == null){ + Rs2MapEditor.state = EditorState.NONE + return + } + + val canWalk = Rs2MapEditor.npcCanWalkCheckbox.isSelected() + var spawnDirection = 0 + for ((index, checkbox) in Rs2MapEditor.npcDirectionCheckboxes.withIndex()) { + if (checkbox.isSelected()) { spawnDirection = index; break } + } + val absolute = Util.getAbsoluteCoordinates(Rs2MapEditor.region, Rs2MapEditor.selectedPointX, Rs2MapEditor.selectedPointY, Rs2MapEditor.plane) + val npcSpawn = TableData.NPCSpawn(id, absolute, canWalk, spawnDirection) + Rs2MapEditor.npcs!!.add(npcSpawn) + Rs2MapEditor.componentPointMap.filter { it.key.x == Rs2MapEditor.selectedPointX && it.key.y == Rs2MapEditor.selectedPointY }.forEach { (_, cell) -> + var hasLabel = false + cell.components.forEach { if(it is JLabel && it.icon == Image.YELLOW_DOT) hasLabel = true } + cell.layout = FlowLayout() + if(!hasLabel) cell.add(JLabel(Image.YELLOW_DOT)) + cell.repaint() + } + Rs2MapEditor.npcPanel.redrawRows() + Rs2MapEditor.npcsUpdated = true + } + else if (Rs2MapEditor.itemIdInput.text.isNotEmpty() && Rs2MapEditor.state == EditorState.ADD_GROUNDITEM){ + val id = Rs2MapEditor.itemIdInput.text.toIntOrNull() + val amount = Rs2MapEditor.itemAmountInput.text.toIntOrNull() ?: 1 + val respawnTime = Rs2MapEditor.itemRespawnInput.text.toIntOrNull() ?: 1 + if(id == null){ + Rs2MapEditor.state = EditorState.NONE + return + } + + val item = Item( + id, + Rs2MapEditor.selectedPointX, + Rs2MapEditor.selectedPointY, + Rs2MapEditor.plane, + respawnTime, + amount + ) + Rs2MapEditor.items.add(item) + Rs2MapEditor.componentPointMap.filter { it.key.x == Rs2MapEditor.selectedPointX && it.key.y == Rs2MapEditor.selectedPointY }.forEach { (_, cell) -> + var hasLabel = false + cell.components.forEach { if(it is JLabel && it.icon == Image.RED_DOT) hasLabel = true } + cell.layout = FlowLayout() + if(!hasLabel) cell.add(JLabel(Image.RED_DOT)) + cell.repaint() + } + Rs2MapEditor.itemPanel.redrawRows() + Rs2MapEditor.itemsUpdated = true + } + } else if (SwingUtilities.isRightMouseButton(e)) { + val point = Point(Rs2MapEditor.selectedPointX, Rs2MapEditor.selectedPointY) + val def = MapTileParser.definition.getTile(point.x, point.y, Rs2MapEditor.plane) + val floDef = Rs2MapEditor.overlayMap[def.overlayId]!! + val cell = Rs2MapEditor.componentPointMap[point]!! + val string = StringBuilder() + .append("

Tile [${point.x}, ${point.y}] Information


") + .append("

") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("
UnderlayIDHeightSettings
${def.underlayId}${def.height}${def.settings}
") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("
OverlayIDColorHide underlay
${def.overlayId}${floDef.color}${floDef.hideUnderlay}
") + .append("
") + val thisAbsolute = Util.getAbsoluteCoordinates(Rs2MapEditor.region, Rs2MapEditor.selectedPointX, Rs2MapEditor.selectedPointY, Rs2MapEditor.plane) + val itemsHere = Rs2MapEditor.items.filter { it.x == Rs2MapEditor.selectedPointX && it.y == Rs2MapEditor.selectedPointY && it.plane == Rs2MapEditor.plane }.toList() + val npcsHere = Rs2MapEditor.npcs!!.filter { it.location == thisAbsolute }.toList() + + if(npcsHere.isNotEmpty()) { + string.append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + for (npc in npcsHere) { + string.append("") + string.append("") + string.append("") + string.append("") + } + string.append("
NPC IDName
${npc.id}${TableData.npcNames[npc.id]}

") + } + + if(itemsHere.isNotEmpty()) { + string.append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + for (item in itemsHere) { + string.append("") + string.append("") + string.append("") + string.append("") + } + string.append("
Item IDInfo
${item.id}${item.definition.name}[${item.amount}] \uD83D\uDD64${item.respawnTime}

") + } + + if(scenery.isNotEmpty()) { + string.append("

Scenery Info

") + for(sc in scenery) { + if (sc.id != -1) { + string.append("${sc.definition.name} [ID: ${sc.id}]
") + string.append("Type: ${sc.type}
") + string.append("Rotation: ${sc.rotation}
") + string.append("---------------------
") + } + } + } + + Rs2MapEditor.underlayInfo.text = String.format("%s", string.toString()) + Rs2MapEditor.infoPane.selectedIndex = 0 + } + } + }) + } + + fun flagScenery(scenery: Scenery){ + this.scenery.add(scenery) + } + + override fun paintComponent(g: Graphics) { + if(this.defaultBackground == Color(0,0,0,0)) return + super.paintComponent(g) + for(sc in scenery) { + var pointX1 = 0 + var pointY1 = 0 + var pointX2 = 0 + var pointY2 = 0 + val color = g.color + val rotationOdd = sc.rotation % 2 != 0 + when (sc.type) { + 10 -> g.drawOval(5, 5, this.width - 10, this.height - 10) + 4 -> { + val offSetX = + if (!rotationOdd) (sc.rotation * 0.375 * width).toInt() else (0.35 * width).toInt() + val offSetY = + if (!rotationOdd) (0.65 * height).toInt() else (sc.rotation * 0.375 * height).toInt() + g.drawString("=", offSetX, offSetY) + } + 11 -> { + g.color = Color(255, 255, 255, 75) + g.drawString("⛝", (0.25 * width).toInt(), (0.75 * height).toInt()) + g.color = color + } + 22 -> { + g.color = Color(255, 255, 255, 55) + g.drawString("⛆", (0.15 * width).toInt(), (0.65 * height).toInt()) + g.color = color + } + 0, 5 -> { + if (sc.id == 85) return + when (sc.rotation) { + 0 -> { + pointX1 = 1; pointY1 = this.height - 1; pointX2 = 1; pointY2 = 1 + } + 1 -> { + pointX1 = 1; pointY1 = 1; pointX2 = this.width - 1; pointY2 = 1 + } + 2 -> { + pointX1 = this.width - 1; pointY1 = 1; pointX2 = this.width - 1; pointY2 = this.height - 1 + } + 3 -> { + pointX1 = 1; pointY1 = this.height - 1; pointX2 = this.width - 1; pointY2 = this.height - 1 + } + } + if (sc.definition.interactive == 1 && sc.type == 0) { + g.color = Color.RED + } + g.drawLine(pointX1, pointY1, pointX2, pointY2) + g.color = color + } + + 2 -> { + val nPoints = 4 + var xArray: IntArray = intArrayOf(0, 0, 0, 0) + var yArray: IntArray = intArrayOf(0, 0, 0, 0) + when (sc.rotation) { + 0 -> { + xArray = intArrayOf(1, 1, 1, this.width - 1); yArray = intArrayOf(1, this.height - 1, 1, 1) + } + 1 -> { + xArray = intArrayOf(1, this.width - 1, this.width - 1, this.width - 1); yArray = intArrayOf(1, 1, 1, this.height - 1) + } + 2 -> { + xArray = intArrayOf(1, this.width - 1, this.width - 1, this.width - 1); yArray = intArrayOf(this.height - 1, this.height - 1, 1, this.height - 1) + } + 3 -> { + xArray = intArrayOf(1,1,1,this.width - 1); yArray = intArrayOf(1,this.height - 1, this.height - 1, this.height - 1) + } + } + if (sc.definition.interactive == 1) { + g.color = Color.RED + } + g.drawPolyline(xArray, yArray, nPoints) + g.color = color + } + + 9, 7, 6, 8 -> { + if (sc.id == 85 || sc.id == 83) return + when (sc.rotation) { + 0, 2 -> { + pointX1 = 0; pointY1 = height; pointX2 = width; pointY2 = 0 + } + 1, 3 -> { + pointX1 = 0; pointY1 = 0; pointX2 = width; pointY2 = height + } + } + + val overlayNorth = getOverlayId(sc.x + 1, sc.y) + val overlaySouth = getOverlayId(sc.x - 1, sc.y) + val northWest = getOverlayId(sc.x - 1, sc.y + 1) + val north = getOverlayId(sc.x, sc.y + 1) + val northEast = getOverlayId(sc.x + 1, sc.y + 1) + val east = getOverlayId(sc.x + 1, sc.y) + val southWest = getOverlayId(sc.x - 1, sc.y - 1) + val south = getOverlayId(sc.x, sc.y - 1) + val southEast = getOverlayId(sc.x + 1, sc.y - 1) + val west = getOverlayId(sc.x - 1, sc.y) + + var backFillVertice = Pair(0, 0) + when (sc.rotation) { + 0, 2 -> { + val sumA = getOverlaySum(north, northWest, west) + val sumB = getOverlaySum(south, southEast, east) + backFillVertice = if (sumA > sumB) Pair(width, height) + else if (sumA == sumB) Pair(-1, -1) + else Pair(0, 0) + } + 1, 3 -> { + val sumA = getOverlaySum(north, northEast, east) + val sumB = getOverlaySum(south, southWest, west) + backFillVertice = if (sumA > sumB) Pair(0, height) + else if (sumA == sumB) Pair(-1, -1) + else Pair(width, 0) + } + } + + val fluDef = getUnderlayColor(sc.x, sc.y) + g.color = fluDef + if (overlaySouth != overlayNorth && backFillVertice.first != -1) + g.fillPolygon( + intArrayOf(pointX1, pointX2, backFillVertice.first), + intArrayOf(pointY1, pointY2, backFillVertice.second), + 3 + ) + g.color = color + g.drawLine(pointX1, pointY1, pointX2, pointY2) + } + } + } + } + + fun getOverlaySum(vararg overlays: Int): Int{ + var sum = 0 + for(overlay in overlays){ + sum += overlay.coerceAtMost(1) + } + return sum + } + + fun getUnderlayColor(x: Int, y: Int): Color { + var underlayID = (MapTileParser.definition.getTile(x, y, Rs2MapEditor.plane).underlayId - 1) + if (underlayID < 0) { + underlayID = 0 + } + return Rs2MapEditor.underlayMap[underlayID]?.getRGB() ?: Color.BLACK + } + + fun getOverlayId(x: Int, y: Int): Int { + return MapTileParser.definition.getTile(x, y, Rs2MapEditor.plane).overlayId + } +} diff --git a/src/main/kotlin/ui/NPCSpawnPanel.kt b/src/main/kotlin/ui/NPCSpawnPanel.kt new file mode 100644 index 0000000..d1b916a --- /dev/null +++ b/src/main/kotlin/ui/NPCSpawnPanel.kt @@ -0,0 +1,215 @@ +package ui + +import EditorState +import Rs2MapEditor +import TableData +import cacheops.cache.definition.decoder.NPC +import const.Image +import misc.ImgButton +import tools.Util +import java.awt.Color +import java.awt.Dimension +import java.awt.FlowLayout +import java.awt.BorderLayout +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.BoxLayout +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JTextField +import javax.swing.SwingUtilities +import javax.swing.Box +import javax.swing.SwingConstants +import javax.swing.border.MatteBorder +import javax.swing.event.DocumentListener +import javax.swing.event.DocumentEvent + +class NPCSpawnPanel : JPanel() { + val rowBorder = MatteBorder(1, 1, 1, 1, Color.WHITE) + val removedRows = ArrayList() + + init { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + + add(NPCSpawnerPanel()) + + val filterPanel = JPanel(FlowLayout()) + val filterLabel = JLabel("Filter ") + val filterBox = JTextField("Enter search term...") + filterBox.document.addDocumentListener(FilterBoxListener(filterBox, ::filterRows)) + + filterPanel.add(filterLabel) + filterPanel.add(filterBox) + filterBox.minimumSize = Dimension(150, 30) + filterBox.preferredSize = Dimension(150, 30) + filterBox.maximumSize = Dimension(150, 60) + filterPanel.minimumSize = Dimension(300, 60) + filterPanel.maximumSize = Dimension(300, 60) + + add(filterPanel) + + + Rs2MapEditor.npcs!!.forEach { + val row = NPCRow(it, this) + row.border = rowBorder + Rs2MapEditor.npcRows.add(row) + add(row) + } + } + + fun filterRows (term: String) { + SwingUtilities.invokeLater { + var termAsInt = term.toIntOrNull() + Rs2MapEditor.npcRows.addAll(removedRows) + var rows = Rs2MapEditor.npcRows.toTypedArray() + removedRows.clear() + + if (term.isNotEmpty() && termAsInt != null) { + for (row in rows) { + if (row.npc.id != termAsInt) { + removedRows.add(row) + Rs2MapEditor.npcRows.remove(row) + } + } + } + else if (term.isNotEmpty()) { + for (row in rows) { + var name = TableData.npcNames[row.npc.id]?.toLowerCase() ?: "null" + if (!name.contains(term, ignoreCase = true) && !term.contains(name, ignoreCase = true) && term.toLowerCase() != name) { + removedRows.add(row) + Rs2MapEditor.npcRows.remove(row) + } + } + } + + for (row in rows) { + remove(row) + revalidate() + } + for (row in Rs2MapEditor.npcRows) { + add(row) + revalidate() + } + Rs2MapEditor.infoPane.revalidate() + + repaint(5L) + Rs2MapEditor.infoScrollPane.scrollFrame.viewport.repaint(5L) + } + } + + fun redrawRows() { + Rs2MapEditor.npcRows.forEach { + remove(it) + } + Rs2MapEditor.npcRows.clear() + Rs2MapEditor.npcs!!.filter { it.location.z == Rs2MapEditor.plane }.forEach { + val row = NPCRow(it, this) + row.border = rowBorder + Rs2MapEditor.npcRows.add(row) + add(row) + } + removedRows.clear() + repaint() + } + + class NPCSpawnerPanel : JPanel() { + init { + val addButton = ImgButton(Image.ADD_HI, Image.ADD_LO) + addButton.onClick { + Rs2MapEditor.state = EditorState.ADD_NPC + } + Rs2MapEditor.npcIdInput.minimumSize = Dimension(200, 25) + Rs2MapEditor.npcIdInput.maximumSize = Dimension(200, 25) + Rs2MapEditor.npcIdInput.preferredSize = Dimension(200, 25) + + Rs2MapEditor.npcIdInput.addMouseListener (object : MouseAdapter() { + override fun mouseClicked (e: MouseEvent) { + NPCMenu.caller = {id, name -> + Rs2MapEditor.npcIdInput.text = id.toString() + } + NPCMenu.open() + } + }) + + minimumSize = Dimension(300, 120) + preferredSize = Dimension(300, 120) + maximumSize = Dimension(300, 120) + + layout = FlowLayout() + add(Rs2MapEditor.npcIdInput) + add(addButton) + add(Rs2MapEditor.directionalCheckboxPanel) + add(JLabel("Can Walk?")) + add(Rs2MapEditor.npcCanWalkCheckbox) + border = MatteBorder(1, 1, 1, 1, Color.GREEN) + } + } + + class FilterBoxListener (val textField: JTextField, val filterRows: (String) -> Unit) : DocumentListener { + override fun changedUpdate (e: DocumentEvent) { + filterRows(textField.text) + } + + override fun insertUpdate (e: DocumentEvent) { + filterRows(textField.text) + } + + override fun removeUpdate (e: DocumentEvent) { + filterRows(textField.text) + } + } + + class NPCRow(val npc: TableData.NPCSpawn, val parent: JPanel) : JPanel(){ + init { + layout = BorderLayout() + val topPanel = JPanel(FlowLayout()) + topPanel.add(JLabel("${TableData.npcNames[npc.id]}[${npc.id}]")) + + val deleteButton = ImgButton(Image.DELETE_HI, Image.DELTE_LO) + topPanel.add(deleteButton) + + val bottomPanel = JPanel(FlowLayout()) + bottomPanel.add(JLabel("Direction [${spawnArrows[npc.spawnDirection]}] Walks? ${npc.canWalk}")) + add(topPanel, BorderLayout.NORTH) + add(JLabel("{${npc.location.x}, ${npc.location.y}, ${npc.location.z}}", SwingConstants.CENTER), BorderLayout.CENTER) + add(bottomPanel, BorderLayout.SOUTH) + + minimumSize = Dimension(300, 65) + preferredSize = Dimension(300, 65) + maximumSize = Dimension(300, 65) + deleteButton.onClick { + Rs2MapEditor.npcs!!.remove(npc) + parent.remove(this) + parent.repaint() + Rs2MapEditor.npcRows.remove(this) + if(Rs2MapEditor.npcs!!.filter { it.location.x == npc.location.x && it.location.y == npc.location.y }.isEmpty()){ + Rs2MapEditor.componentPointMap.filter { it.key.x == npc.location.localCoords[0] && it.key.y == npc.location.localCoords[1] }.forEach { (_,cell) -> + cell.components.forEach { c -> + if(c is JLabel) cell.remove(c) + } + cell.repaint() + Rs2MapEditor.npcsUpdated = true + } + } + } + + addMouseListener(object : MouseAdapter(){ + val border = MatteBorder(1, 1, 1, 1, Color.YELLOW) + val defaultBorder = MatteBorder(1, 1, 1, 1, Color.GRAY) + override fun mouseEntered(e: MouseEvent?) { + super.mouseEntered(e) + Rs2MapEditor.componentPointMap.filter { it.key.x == npc.location.localCoords[0] && it.key.y == npc.location.localCoords[1] }.forEach { it.value.border = border } + } + + override fun mouseExited(e: MouseEvent?) { + super.mouseExited(e) + Rs2MapEditor.componentPointMap.filter { it.key.x == npc.location.localCoords[0] && it.key.y == npc.location.localCoords[1] }.forEach{ it.value.border = defaultBorder } + } + }) + } + + companion object { + val spawnArrows = arrayOf("⇖","⇑","⇗","⇐","⇒","⇙","⇓","⇘") + } + } +} diff --git a/src/main/kotlin/ui/PlaneButton.kt b/src/main/kotlin/ui/PlaneButton.kt new file mode 100644 index 0000000..0a9f6b3 --- /dev/null +++ b/src/main/kotlin/ui/PlaneButton.kt @@ -0,0 +1,16 @@ +package ui + +import javax.swing.JButton + +class PlaneButton(val plane: Int) : JButton(plane.toString()){ + init { + addActionListener { + if(Rs2MapEditor.plane != this.plane){ + Rs2MapEditor.plane = this.plane + Rs2MapEditor.npcPanel.redrawRows() + Rs2MapEditor.itemPanel.redrawRows() + Rs2MapEditor.mapPanel.repaintMap() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ui/SelectableUnderlayCell.kt b/src/main/kotlin/ui/SelectableUnderlayCell.kt new file mode 100644 index 0000000..eac551b --- /dev/null +++ b/src/main/kotlin/ui/SelectableUnderlayCell.kt @@ -0,0 +1,80 @@ +package ui + +import cacheops.cache.definition.decoder.MapTileParser +import java.awt.Color +import java.awt.Dimension +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.BorderFactory +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.SwingUtilities +import javax.swing.border.Border +import javax.swing.border.MatteBorder + +class SelectableUnderlayCell(color: Color, size: Int, val id: Any) : JPanel() { + init { + when (id) { + is Int -> { + background = color + } + else -> { + background = color + border = BorderFactory.createDashedBorder(Color.GREEN) + } + } + val text = JLabel("$id") + text.foreground = Color((color.rgb).inv()).brighter() + + minimumSize = Dimension(size, size) + maximumSize = Dimension(size, size) + preferredSize = Dimension(size, size) + add(text) + + addMouseListener(object : MouseAdapter() { + val border: Border = MatteBorder(1, 1, 1, 1, Color.GRAY) + val highlight: Border = MatteBorder(1, 1, 1, 1, Color.YELLOW) + val selectionBorder = MatteBorder(1, 1, 1, 1, Color.WHITE) + + override fun mouseEntered(e: MouseEvent?) { + this@SelectableUnderlayCell.border = selectionBorder + Rs2MapEditor.colorPointMap.filter { it.value == id }.forEach{ Rs2MapEditor.componentPointMap[it.key]!!.border = highlight} + } + + override fun mouseExited(e: MouseEvent?) { + this@SelectableUnderlayCell.border = null + Rs2MapEditor.colorPointMap.filter { it.value == id }.forEach{ Rs2MapEditor.componentPointMap[it.key]!!.border = border} + } + + override fun mouseClicked(e: MouseEvent) { + if (SwingUtilities.isLeftMouseButton(e)) { + when (id) { + is Int -> { + if(Rs2MapEditor.selectedUnderlayId == id){ + Rs2MapEditor.state = EditorState.NONE + } else { + Rs2MapEditor.state = EditorState.SET_UNDERLAY + Rs2MapEditor.selectedUnderlayId = id + } + Rs2MapEditor.statusLabel.text = "Region: ${Rs2MapEditor.region} | " + + "Local Coordinates: [${Rs2MapEditor.selectedPointX}, ${Rs2MapEditor.selectedPointY}, ${Rs2MapEditor.plane}] | " + + "Global Coordinates: [${MapTileParser.coordinateX(Rs2MapEditor.selectedPointX)}, ${ + MapTileParser.coordinateX( + Rs2MapEditor.selectedPointY + ) + }, ${Rs2MapEditor.plane}] | " + + "Viewing Underlay: ${ + MapTileParser.definition.getTile( + Rs2MapEditor.selectedPointX, + Rs2MapEditor.selectedPointY, 0).underlayId - 1} | " + + "Selected Underlay: ${if (Rs2MapEditor.selectedUnderlayId == null) "No underlay selected!" else "${Rs2MapEditor.selectedUnderlayId}"}" + } + else -> { + println("User trying to add new underlay") + } + } + } + } + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ui/TileInformationPanel.kt b/src/main/kotlin/ui/TileInformationPanel.kt new file mode 100644 index 0000000..a16c970 --- /dev/null +++ b/src/main/kotlin/ui/TileInformationPanel.kt @@ -0,0 +1,16 @@ +package ui + +import java.awt.BorderLayout +import java.awt.Color +import javax.swing.JPanel +import javax.swing.SwingConstants + +class TileInformationPanel : JPanel() { + init { + Rs2MapEditor.underlayInfo.foreground = Color.white + Rs2MapEditor.underlayInfo.verticalAlignment = SwingConstants.TOP + Rs2MapEditor.underlayInfo.horizontalAlignment = SwingConstants.CENTER + layout = BorderLayout() + add(Rs2MapEditor.underlayInfo) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ui/UnderlaySelectionPanel.kt b/src/main/kotlin/ui/UnderlaySelectionPanel.kt new file mode 100644 index 0000000..4f1b859 --- /dev/null +++ b/src/main/kotlin/ui/UnderlaySelectionPanel.kt @@ -0,0 +1,15 @@ +package ui + +import java.awt.Color +import javax.swing.JPanel + +class UnderlaySelectionPanel : JPanel() { + init { + Rs2MapEditor.underlayMap.forEach { + val underlay = SelectableUnderlayCell(it.value.getRGB(), 30, it.key) + add(underlay) + } + val addAnUnderlay = SelectableUnderlayCell(Color.darkGray, 30, "+") + add(addAnUnderlay) + } +} \ No newline at end of file diff --git a/src/main/kotlin/worldmap/OverlayHandler.kt b/src/main/kotlin/worldmap/OverlayHandler.kt new file mode 100644 index 0000000..900c353 --- /dev/null +++ b/src/main/kotlin/worldmap/OverlayHandler.kt @@ -0,0 +1,185 @@ +package worldmap + +import cacheops.cache.definition.data.OverlayDefinition +import cacheops.cache.definition.decoder.BlendedTextureDecoder +import cacheops.cache.definition.decoder.FloorOverlayConfiguration +import kotlin.math.pow + +object OverlayHandler { + + var blendedOverlayColors: IntArray = IntArray(FloorOverlayConfiguration.floorOverlays.size + 1) + var p: IntArray = IntArray(65536) + + var f_l = (Math.random() * 11.0).toInt() - 5 + var n = (Math.random() * 17.0).toInt() - 8 + + fun init() { + f(0) + f_l += -2 + (5.0 * Math.random()).toInt() + if (f_l < -8) { + f_l = -8 + } + n += -2 + (Math.random() * 5.0).toInt() + if (f_l > 8) { + f_l = 8 + } + if (n < -16) { + n = -16 + } + if (n > 16) { + n = 16 + } + setOverlayColorArray(((f_l shr 2) shl 10), n shr 1) + } + + fun setOverlayColorArray(randomColorOffset1: Int, randomColorOffset2: Int) { + for (overlayId in 0 until FloorOverlayConfiguration.floorOverlays.size) { + blendedOverlayColors[overlayId + 1] = getOverlayColor(overlayId,randomColorOffset1,randomColorOffset2) + } + } + + fun getOverlayColor(id: Int, colorOffset1: Int, colorOffset2: Int): Int { + val floDefinition: OverlayDefinition = FloorOverlayConfiguration.floorOverlays[id] ?: return 0 + var floorOverlayTexture = floDefinition.texture + if (floorOverlayTexture >= 0 && BlendedTextureDecoder.blendedTextureDef[floorOverlayTexture]!!.renderOnMap) { + floorOverlayTexture = -1 + } + val negativeRGBColor: Int + if (floDefinition.blendColor >= 0) { + val floorOverlayBlendColor = floDefinition.blendColor + var offsetBlendedValue = ((floorOverlayBlendColor and 0x7F) + colorOffset2) + if (offsetBlendedValue < 0) { + offsetBlendedValue = 0 + } else if (offsetBlendedValue > 127) { + offsetBlendedValue = 127 + } + val combinedBlendedValue = ((floorOverlayBlendColor + (colorOffset1 and 0xfc00) + ((floorOverlayBlendColor and 0x380))) + offsetBlendedValue) + negativeRGBColor = (0xffffff.inv() or p[colorFunc2(colorFunc1(96, 2, combinedBlendedValue),-2045205981).toInt() and 0xffff]) + } else if (floorOverlayTexture >= 0) { + negativeRGBColor = (0xffffff.inv() or p[colorFunc2(colorFunc1(96, 2, BlendedTextureDecoder.blendedTextureDef[floorOverlayTexture]!!.blendedColor),-2045205981).toInt() and 0xffff]) + } else if (floDefinition.color == -1) { + negativeRGBColor = 0 + } else { + val floorOverlayColor = floDefinition.color + var offsetColorValue = ((floorOverlayColor and 0x7f) + colorOffset2) + if (offsetColorValue < 0) { + offsetColorValue = 0 + } else if (offsetColorValue > 127) { + offsetColorValue = 127 + } + val combinedColorValue = (((colorOffset1 and 0xfc00) + floorOverlayColor) + ((floorOverlayColor and 0x380) + offsetColorValue)) + negativeRGBColor = (0xffffff.inv() or p[colorFunc2(colorFunc1(96, 2, combinedColorValue),-2045205981).toInt() and 0xffff]) + } + return negativeRGBColor + } + + fun colorFunc1(i: Int, i_1_: Int, i_2_: Int): Int { + var i = i + if (i_1_ != 2) { + return 64 + } + if (i_2_ == -2) { + return 12345678 + } + if (i_2_ == -1) { + if (i >= 2) { + if (i > 126) { + i = 126 + } + } else { + i = 2 + } + return i + } + i = i * (i_2_ and 0x7f) shr 7 + if (i < 2) { + i = 2 + } else if (i > 126) { + i = 126 + } + return (0xff80 and i_2_) + i + } + + fun colorFunc2(i: Int, i_1_: Int): Short { + return try { + val i_2_ = ((0xfddc and i) shr 10) + if (i_1_ != -2045205981) { + return 123.toShort() + } + var i_3_ = 0x384 and i shr 3 + val i_4_ = 0x7f and i + i_3_ = if (i_4_ <= 64) i_4_ * i_3_ shr 7 else (127 - i_4_) * i_3_ shr 7 + val i_5_ = i_3_ + i_4_ + var i_6_: Int + do { + if (i_5_ == 0) { + i_6_ = i_3_ shl 1 + if (!true) { + break + } + } + i_6_ = (i_3_ shl 8) / i_5_ + } while (false) + (i_5_ or (i_2_ shl 10 or i_6_ shr 4 shl 7)).toShort() + } catch (runtimeexception: RuntimeException) { + throw error("fuk") + } + } + + fun f(i: Int) { + val d = -0.015 + 0.03 * Math.random() + 0.7 + var i_7_ = i + for (i_8_ in 0..511) { + val f = (0.0078125f + (i_8_ shr 3).toFloat() / 64.0f) * 360.0f + val f_9_ = (i_8_ and 0x7).toFloat() / 8.0f + 0.0625f + for (i_10_ in 0..127) { + val f_11_ = i_10_.toFloat() / 128.0f + var f_12_ = 0.0f + var f_13_ = 0.0f + var f_14_ = 0.0f + val f_15_ = f / 60.0f + val i_16_ = f_15_.toInt() + val i_17_ = i_16_ % 6 + val f_18_ = f_15_ - i_16_.toFloat() + val f_19_ = f_11_ * (1.0f - f_9_) + val f_20_ = f_11_ * (-(f_18_ * f_9_) + 1.0f) + val f_21_ = f_11_ * (-(f_9_ * (-f_18_ + 1.0f)) + 1.0f) + if (i_17_ == 0) { + f_13_ = f_21_ + f_14_ = f_19_ + f_12_ = f_11_ + } else if (i_17_ == 1) { + f_14_ = f_19_ + f_13_ = f_11_ + f_12_ = f_20_ + } else if (i_17_ == 2) { + f_14_ = f_21_ + f_13_ = f_11_ + f_12_ = f_19_ + } else if (i_17_ == 3) { + f_13_ = f_20_ + f_12_ = f_19_ + f_14_ = f_11_ + } else if (i_17_ == 4) { + f_13_ = f_19_ + f_12_ = f_21_ + f_14_ = f_11_ + } else if (i_17_ == 5) { + f_12_ = f_11_ + f_13_ = f_19_ + f_14_ = f_20_ + } + f_12_ = f_12_.toDouble().pow(d).toFloat() + f_13_ = f_13_.toDouble().pow(d).toFloat() + f_14_ = f_14_.toDouble().pow(d).toFloat() + val i_22_ = (f_12_ * 256.0f).toInt() + val i_23_ = (f_13_ * 254.0f).toInt() + val i_24_ = (f_14_ * 256.0f).toInt() + val i_25_ = i_24_ + ((i_23_ shl 8) - 16777216 + (i_22_ shl 16)) + p[i_7_++] = i_25_ + } + } + } + + +} \ No newline at end of file diff --git a/src/main/resources/add_dark.png b/src/main/resources/add_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f0222f9ac88894984a07c37043a1f99722bb0a5f GIT binary patch literal 356 zcmV-q0h|7bP)d!;lrB8`QfY9gLCH z=xfM<4NHy;LKuYZ8W0941YvZ8VB#PQ3&ZdLcZgvyn>1xbuzCmCAY_c;Dr^R|u8M!n0<(!S5Tu|aC-Z+{Rx%jpBqw6v z?$*Zt-7WRF4FgHw(u~cpzK%vr!(fJFB}b!RBQ<3d3=_v`7>I*z2+W{0$9lmS*&vu< zllwXm2H|oIA%l=z1~v@lnv9qbtlmL32;G|~t^yIskwGX1Ej`o<#~?%2pO}CmhHTK( zNj-!OT6mxy1EU*+;v$eB$S`bPgBd`S*Fb>{GYCDfkqttZ&xnioziiQC$G!gE7<+ki`V^8!Yg#VUR(14M8tP ju%&jG0c4d+#1?D-q=|NrsB zpY(=e5s`oY6O(`c7dw&4aPok~a|b3Ko}T~tOp?_HWZXK;IG7JHGB_v8G);7HVFen( N;OXk;vd$@?2>_27B-8)^ literal 0 HcmV?d00001 diff --git a/src/main/resources/dot_yellow.png b/src/main/resources/dot_yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8ef5f2084ba1c9a7c324295581e2c276f30b0a GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^EI`c4!3HFg*81KCQdXWWjv*Ddk`ox3>i@I+Z#H1= z`TU&!^8fYmlK<}SJ^f)m;}OP(+w&#=&9^+v5OkDdT4RBP;w=Rp4t55s>1svmeEeSl PO=0kK^>bP0l+XkKx1}bz literal 0 HcmV?d00001 diff --git a/src/main/resources/trash_dark.png b/src/main/resources/trash_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..38876c7c4d1e4473ba59acbc79d4fc79ac8912ed GIT binary patch literal 330 zcmV-Q0k!^#P)PfglA&YRdnOwILY9M#qssUjKuATyPr( zl0eq1r=|dBgXEC;p*~*!tBSKR4Fd^iD@%hhx(h&Z6E{ILf%q^rAXhcjl%g7j%^+A< zzzl)WAgr$;2UZgw8H6wh<|U9JAm@WHOd~Fg91Nf^0~-c&ji#&!+*P=u9vNe}3YS43 zn?Z`P(Z~iRBO9cyAW0pAC~*-yO~C?AMM{VygJ2Pak~m=!FjpZPgpOebfz-jW3`#lz zDFV3)-7suX5Au?Mnk=S4$gV;+3?0Md>@4&M=Qof-kb&5+mVy}E5~7L`n1RR`Bt~|* cL~OwZ0Eo(n3Q+{@z5oCK07*qoM6N<$f=~!~DgXcg literal 0 HcmV?d00001 diff --git a/src/main/resources/trash_hi.png b/src/main/resources/trash_hi.png new file mode 100644 index 0000000000000000000000000000000000000000..800b5211209dd45065a6dc53b3624f106b2c599b GIT binary patch literal 323 zcmV-J0lfZ+P)!n0<(!S5Tu|aC-Z+{Rx%jpBqw6v z?$*Zt-7WRF4FgHw(rl!rjA2+`Mo7~rlFbJD#U>b2@WWzu%0~-c&O-4)zhHLO)3|E1Ou|_*AQa~0H%x|#3$A&=$;WY%k7{QkJU0RZ=^ V{)-@BEF=H`002ovPDHLkV1kRVeOmwk literal 0 HcmV?d00001