diff --git a/build.gradle b/build.gradle index abfa832..72253eb 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,8 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + + implementation files("libs/json-simple-1.1.1.jar") } test { diff --git a/libs/json-simple-1.1.1.jar b/libs/json-simple-1.1.1.jar new file mode 100644 index 0000000..dfd5856 Binary files /dev/null and b/libs/json-simple-1.1.1.jar differ diff --git a/src/main/kotlin/Rs2MapEditor.kt b/src/main/kotlin/Rs2MapEditor.kt index 63541a5..20d7f74 100644 --- a/src/main/kotlin/Rs2MapEditor.kt +++ b/src/main/kotlin/Rs2MapEditor.kt @@ -8,6 +8,7 @@ import com.displee.cache.CacheLibrary import com.formdev.flatlaf.FlatDarculaLaf import const.Image.RED_DOT import const.Image.YELLOW_DOT +import const.cachePath import misc.CustomEventQueue import misc.ItemSpawnPanel import tools.ItemSearchTool @@ -22,10 +23,11 @@ import kotlin.system.exitProcess object Rs2MapEditor { - val library = CacheLibrary.create("/home/ceikry/IdeaProjects/rs09-remake/Server/data/cache/") + val library = CacheLibrary.create(cachePath) var region = 12850 var npcs = NPCSpawnParser.parseNPCSpawns(region) var items = GroundItemSpawnParser.parseItemSpawns(region) + var sceneries = TileSceneryParser.parseRegion(region) val npcRows = ArrayList() val itemRows = ArrayList() var npcsUpdated = false @@ -134,14 +136,7 @@ object Rs2MapEditor { for (column in 0 until mapWidth) { gbc.gridx = column gbc.gridy = row - val border: Border = - MatteBorder( - 1, - 1, - if (row == mapHeight) 1 else 0, - if (column == mapWidth) 1 else 0, - Color.GRAY - ) + val border: Border = MatteBorder(1, 1, if (row == mapHeight) 1 else 0, if (column == mapWidth) 1 else 0, Color.GRAY) val flippedY = (64 - row) - 1 val point = Point(column, flippedY) colorPointMap[point] = MapTileParser.definition.getTile(column, flippedY, plane).underlayId @@ -157,6 +152,12 @@ object Rs2MapEditor { } mapCell.border = border + + val sceneryHere = sceneries.filter { it.x == column && it.y == flippedY && it.plane == plane }.toList() + if(sceneryHere.isNotEmpty()){ + mapCell.flagScenery(sceneryHere.first()) + } + val npcsHere = npcs.filter { it.x == column && it.y == flippedY && it.plane == plane }.toList() if (npcsHere.isNotEmpty()) { mapCell.layout = BorderLayout() @@ -179,6 +180,7 @@ object Rs2MapEditor { class MapCell : JPanel() { var defaultBackground: Color = background + var scenery: Scenery = Scenery(-1,-1,-1,-1,-1,-1) override fun getPreferredSize(): Dimension { return Dimension(cellX, cellY) } @@ -268,6 +270,7 @@ object Rs2MapEditor { val point = Point(selectedPointX, selectedPointY) val def = MapTileParser.definition.getTile(point.x, point.y, 0) val floDef = overlayMap[def.overlayId]!! + val cell = componentPointMap[point]!! val string = StringBuilder() .append("

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


") .append("

") @@ -304,29 +307,82 @@ object Rs2MapEditor { .append("") .append("") .append("
") - .append("") - .append("") - .append("") - .append("") - .append("") - .append("") - .append("") - .append("") - val npcsHere = npcs.filter { println(it.definition.name + " ${it.x}:${it.y}:${it.plane}"); it.x == selectedPointX && it.y == selectedPointY && it.plane == 0 }.toList() - for(npc in npcsHere){ - string.append("") - string.append("") - string.append("") - string.append("") + val itemsHere = items.filter { it.x == selectedPointX && it.y == selectedPointY && it.plane == plane }.toList() + val npcsHere = npcs.filter { it.x == selectedPointX && it.y == selectedPointY && it.plane == plane }.toList() + + if(npcsHere.isNotEmpty()) { + string.append("
NPC IDName
${npc.id}${npc.definition.name}(${npc.definition.combat})
") + .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}${npc.definition.name}(${npc.definition.combat})

") } - println("${selectedPointX} : ${selectedPointY}") - string.append("
") + + 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(cell.scenery.id != -1){ + string.append("

Scenery Info

") + string.append("${cell.scenery.definition.name} [ID: ${cell.scenery.id}]
") + string.append("Type: ${cell.scenery.type}
") + string.append("Rotation: ${cell.scenery.rotation}
") + string.append("") + } + underlayInfo.text = String.format("%s", string.toString()) infoPane.selectedIndex = 0 } } }) } + + fun flagScenery(scenery: Scenery){ + this.scenery = scenery + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + when(scenery.type){ + 10 -> g.drawOval(5,5,this.width - 10, this.height - 10) + 11 -> { + val color = g.color + g.color = Color(255,255,255,75) + g.drawString("⛝",(0.25 * width).toInt(),(0.75 * height).toInt()) + g.color = color + } + 22 -> { + val color = g.color + g.color = Color(255,255,255,55) + g.drawString("⛆",(0.15 * width).toInt(),(0.65 * height).toInt()) + g.color = color + } + } + } } class ScrollPane : JPanel() { diff --git a/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt b/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt index 41f49ae..7fceb6b 100644 --- a/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt +++ b/src/main/kotlin/cacheops/cache/definition/decoder/ObjectDecoder.kt @@ -3,11 +3,21 @@ 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 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 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/const/Misc.kt b/src/main/kotlin/const/Misc.kt index 376b131..2691346 100644 --- a/src/main/kotlin/const/Misc.kt +++ b/src/main/kotlin/const/Misc.kt @@ -3,4 +3,6 @@ package const import cacheops.cache.Cache import cacheops.cache.CacheDelegate -val cache: Cache = CacheDelegate("/home/ceikry/IdeaProjects/rs09-remake/Server/data/cache/") +val cachePath = "/home/ceikry/IdeaProjects/rs09-remake/Server/data/cache" +val xteaJson = "/home/ceikry/IdeaProjects/rs09-remake/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 index 9fd676f..452d1e9 100644 --- a/src/main/kotlin/ext/JagexTypes.kt +++ b/src/main/kotlin/ext/JagexTypes.kt @@ -40,4 +40,22 @@ fun ByteBuffer.unsignedByte(): Int { 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