NPC map stuff

This commit is contained in:
ceikry 2021-10-28 20:00:23 -05:00
parent 9c67d6d9da
commit 1a14bd7dbc
12 changed files with 349 additions and 58 deletions

View file

@ -0,0 +1,54 @@
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(enabledURL: String, disabledURL: String = enabledURL, val autoHandleMouse: Boolean = true) : JLabel() {
private var hoverMethod: (MouseEvent) -> Unit = {}
private var mouseLeaveMethod: (MouseEvent) -> Unit = {}
private var onClickMethod: (MouseEvent) -> Unit = {}
init {
isEnabled = false
icon = ImageIcon(javaClass.getResource(enabledURL))
disabledIcon = ImageIcon(javaClass.getResource(disabledURL))
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))
}
}

View file

@ -3,21 +3,29 @@ import cacheops.cache.definition.data.MapTile
import cacheops.cache.definition.data.UnderlayDefinition
import cacheops.cache.definition.decoder.FloorUnderlayConfiguration
import cacheops.cache.definition.decoder.MapTileParser
import cacheops.cache.definition.decoder.NPC
import cacheops.cache.definition.decoder.NPCSpawnParser
import cacheops.cache.definition.encoder.MapTileWriter
import com.displee.cache.CacheLibrary
import com.formdev.flatlaf.FlatDarculaLaf
import const.Image.YELLOW_DOT
import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.lang.StringBuilder
import javax.swing.*
import javax.swing.border.*
import kotlin.system.exitProcess
object Rs2MapEditor {
val library = CacheLibrary.create("K:\\RSPSDev\\2-009Scape-578\\Server\\data\\cache")
var region = 12850 // Lumbridge
val library = CacheLibrary.create("/home/ceikry/IdeaProjects/rs09-remake/Server/data/cache/")
var region = 7474 // Lumbridge
var npcs = NPCSpawnParser.parseNPCSpawns(region)
val npcRows = ArrayList<NPCSpawnPanel.NPCRow>()
lateinit var mapPanel: MapPane
lateinit var npcPanel: NPCSpawnPanel
@JvmStatic
fun main(args: Array<String>) {
@ -40,7 +48,7 @@ object Rs2MapEditor {
// Stores the underlay ID for each point
var colorPointMap: HashMap<Point, Int> = hashMapOf()
val componentPointMap = hashMapOf<Point, CellPane>()
val componentPointMap = hashMapOf<Point, MapCell>()
// Stores the underlay ID with the Underlay Definition
lateinit var underlayMap: HashMap<Int, UnderlayDefinition>
@ -51,6 +59,8 @@ object Rs2MapEditor {
var infoLabel = JLabel()
var underlayInfo = JLabel("Right click a tile to get more information")
var underlayLabel = JLabel()
val npcIdInput = JTextField()
var state = EditorState.NONE
class MainFrame : JFrame("Praesecho Map Editor v0.3") {
init {
@ -67,6 +77,8 @@ object Rs2MapEditor {
maximumSize = Dimension(1567, 1100)
defaultCloseOperation = EXIT_ON_CLOSE
jMenuBar = MenuBar()
mapPanel = MapPane()
npcPanel = NPCSpawnPanel()
add(SettingsPane(), BorderLayout.PAGE_START)
add(ScrollPane(), BorderLayout.CENTER)
add(ScrollPane2(), BorderLayout.EAST)
@ -80,38 +92,82 @@ object Rs2MapEditor {
}
}
class GridPane : JPanel() {
class MapPane : JPanel() {
val gbc = GridBagConstraints()
val mapHeight = 64
val mapWidth = 64
init {
layout = GridBagLayout()
val gbc = GridBagConstraints()
val height = 64
val width = 64
gbc.fill = GridBagConstraints.NONE
gbc.weightx = 1.0
for (row in 0 until height) {
for (column in 0 until width) {
SwingUtilities.invokeLater {
for (row in 0 until mapHeight) {
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 flippedY = (64 - row) - 1
val point = Point(column, flippedY)
colorPointMap[point] = MapTileParser.definition.getTile(column, flippedY, 0).underlayId
val mapCell = MapCell()
mapCell.background =
underlayMap[MapTileParser.definition.getTile(column, flippedY, 0).underlayId]!!.getRGB()
mapCell.border = border
val npcsHere = npcs.filter { it.x == column && it.y == flippedY && it.plane == 0 }.toList()
if (npcsHere.isNotEmpty()) {
mapCell.layout = BorderLayout()
mapCell.add(JLabel(YELLOW_DOT))
}
componentPointMap[point] = mapCell
add(mapCell, gbc)
}
}
}
}
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
val border: Border = MatteBorder(1, 1, if (row == height) 1 else 0, if (column == width) 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, 0).underlayId
val cellPane = CellPane()
cellPane.background = underlayMap[MapTileParser.definition.getTile(column, flippedY, 0).underlayId]!!.getRGB()
cellPane.border = border
componentPointMap[point] = cellPane
add(cellPane, gbc)
val mapCell = MapCell()
mapCell.background =
underlayMap[MapTileParser.definition.getTile(column, flippedY, 0).underlayId]!!.getRGB()
mapCell.border = border
val npcsHere = npcs.filter { it.x == column && it.y == flippedY && it.plane == 0 }.toList()
if (npcsHere.isNotEmpty()) {
mapCell.layout = BorderLayout()
mapCell.add(JLabel(YELLOW_DOT))
}
componentPointMap[point] = mapCell
add(mapCell, gbc)
}
}
}
}
class CellPane : JPanel() {
class MapCell : JPanel() {
var defaultBackground: Color = background
override fun getPreferredSize(): Dimension {
return Dimension(cellX, cellY)
}
init {
//add(underlayLabel)
addMouseListener(object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
@ -124,7 +180,11 @@ object Rs2MapEditor {
"Viewing Underlay: ${MapTileParser.definition.getTile(selectedPointX, selectedPointY, 0).underlayId} | " +
"Selected Underlay: ${if (selectedUnderlayId == null) "<font color='red'>No underlay selected!</font>" else "$selectedUnderlayId"}</html>"
defaultBackground = background
background = Color.BLUE
if(state == EditorState.SET_UNDERLAY || state == EditorState.NONE) {
background = Color.BLUE
} else if (state == EditorState.ADD_NPC || state == EditorState.ADD_GROUNDITEM){
background = Color.YELLOW
}
}
override fun mouseExited(e: MouseEvent) {
@ -133,7 +193,7 @@ object Rs2MapEditor {
override fun mouseClicked(e: MouseEvent) {
if (SwingUtilities.isLeftMouseButton(e)) {
if (selectedUnderlayId != null) {
if (selectedUnderlayId != null && state == EditorState.SET_UNDERLAY) {
val originalTile = MapTileParser.definition.getTile(selectedPointX, selectedPointY, 0)
val underlayID = selectedUnderlayId
val point = Point(selectedPointX, selectedPointY)
@ -146,29 +206,65 @@ object Rs2MapEditor {
// Update map tile cacheops.definitions with new tile information
MapTileParser.definition.setTile(point.x, point.y, 0, MapTile(originalTile.height, originalTile.attrOpcode, originalTile.overlayId, originalTile.overlayPath, originalTile.overlayRotation, originalTile.settings, underlayID))
}
else if (npcIdInput.text.isNotEmpty() && state == EditorState.ADD_NPC){
val id = npcIdInput.text.toIntOrNull()
if(id == null){
state = EditorState.NONE
return
}
val npc = NPC(id, selectedPointX, selectedPointY, 0)
npcs.add(npc)
componentPointMap.filter { it.key.x == selectedPointX && it.key.y == selectedPointY }.forEach {(_, cell) ->
var hasLabel = false
cell.components.forEach { if(it is JLabel) hasLabel = true }
cell.layout = BorderLayout()
if(!hasLabel) cell.add(JLabel(YELLOW_DOT))
cell.repaint()
}
npcPanel.redrawRows()
}
} else if (SwingUtilities.isRightMouseButton(e)) {
val point = Point(selectedPointX, selectedPointY)
val def = MapTileParser.definition.getTile(point.x, point.y, 0)
underlayInfo.text =
String.format("<html><body style=\"text-align: center;\">%s</body></html>",
"<h2><b>Tile [${point.x}, ${point.y}] Information</b></h2><br>" +
"<hr width=\"250px\"><br>" +
"<table style=\"border:1px solid gray;margin-left:auto;margin-right:auto;>" +
"<thead>" +
"<tr style=\"text-align:center\">" +
"<th>UnderlayID</th>" +
"<th>Height</th>" +
"<th>Settings</th>" +
"</tr>" +
"</thead>" +
"<tbody>" +
"<tr style=\"text-align:center\">" +
"<td>${def.underlayId}</td>" +
"<td>${def.height}</td>" +
"<td>${def.settings}</td>" +
"</tr>" +
"</tbody>" +
"</table>")
val string = StringBuilder()
.append("<h2><b>Tile [${point.x}, ${point.y}] Information</b></h2><br>")
.append("<hr width=\"250px\"><br>")
.append("<table style=\"border:1px solid gray;margin-left:auto;margin-right:auto;>")
.append("<thead>")
.append("<tr style=\"text-align:center\">")
.append("<th>UnderlayID</th>")
.append("<th>Height</th>")
.append("<th>Settings</th>")
.append("</tr>")
.append("</thead>")
.append("<tbody>")
.append("<tr style=\"text-align:center\">")
.append("<td>${def.underlayId}</td>")
.append("<td>${def.height}</td>")
.append("<td>${def.settings}</td>")
.append("</tr>")
.append("</tbody>")
.append("</table>")
.append("<br/>")
.append("<table style=\"border:1px solid gray;margin-left:auto;margin-right:auto;>")
.append("<thead>")
.append("<tr style=\"text-align:center\">")
.append("<th>NPC ID</th>")
.append("<th>Name</th>")
.append("</tr>")
.append("</thead>")
.append("<tbody>")
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("<tr style=\"text-align:center\">")
string.append("<td>${npc.id}</td>")
string.append("<td>${npc.definition.name}(${npc.definition.combat})</td>")
string.append("</tr>")
}
println("${selectedPointX} : ${selectedPointY}")
string.append("</tbody></table><br/>")
underlayInfo.text = String.format("<html><body style=\"text-align: center;\">%s</body></html>", string.toString())
}
}
})
@ -178,7 +274,7 @@ object Rs2MapEditor {
class ScrollPane : JPanel() {
init {
// Scroll bar
val scrollFrame = JScrollPane(GridPane())
val scrollFrame = JScrollPane(mapPanel)
scrollFrame.maximumSize = Dimension(1300, 980)
scrollFrame.preferredSize = Dimension(1300, 980)
add(scrollFrame, BorderLayout.CENTER)
@ -201,17 +297,16 @@ object Rs2MapEditor {
val underlayComponent: JComponent = FLUGridPanel()
val panel3: JComponent = makeTextPanel("Howdy Ho! I'm an overlay panel, or soon will be that is!")
preferredSize = Dimension(230, 980)
preferredSize = Dimension(230, 4000)
border = CompoundBorder(LineBorder(Color.DARK_GRAY), EmptyBorder(0, 0, 0, 0))
addTab("Information", informationComponent)
setMnemonicAt(0, KeyEvent.VK_1)
add("Underlays", underlayComponent)
setMnemonicAt(1, KeyEvent.VK_2)
addTab("Overlays", panel3)
setMnemonicAt(2, KeyEvent.VK_3)
add("NPC Spawn", npcPanel)
}
}
@ -253,6 +348,96 @@ object Rs2MapEditor {
}
}
class NPCSpawnPanel : JPanel() {
val rowBorder = MatteBorder(1,1,1,1,Color.WHITE)
init {
layout = BoxLayout(this, BoxLayout.PAGE_AXIS)
add(NPCSpawnerPanel())
npcs.forEach {
val row = NPCRow(it, this)
row.border = rowBorder
npcRows.add(row)
add(row)
}
}
fun redrawRows() {
npcRows.forEach {
remove(it)
}
npcRows.clear()
npcs.forEach {
val row = NPCRow(it, this)
row.border = rowBorder
npcRows.add(row)
add(row)
}
repaint()
}
class NPCSpawnerPanel : JPanel() {
init {
val addButton = ImgButton("add_hi.png","add_dark.png")
addButton.onClick {
state = EditorState.ADD_NPC
}
npcIdInput.minimumSize = Dimension(200,25)
npcIdInput.maximumSize = Dimension(200,25)
npcIdInput.preferredSize = Dimension(200,25)
minimumSize = Dimension(300, 60)
preferredSize = Dimension(300, 60)
maximumSize = Dimension(300, 60)
layout = FlowLayout()
add(npcIdInput)
add(addButton)
border = MatteBorder(1,1,1,1, Color.GREEN)
}
}
class NPCRow(val npc: NPC, val parent: JPanel) : JPanel(){
init {
layout = FlowLayout()
add(JLabel("${npc.definition.name} (LVL ${npc.definition.combat}) [${npc.x} | ${npc.y} | ${npc.plane}]"))
val deleteButton = ImgButton("trash_hi.png","trash_dark.png")
add(deleteButton)
minimumSize = Dimension(300, 40)
preferredSize = Dimension(300, 40)
maximumSize = Dimension(300, 40)
deleteButton.onClick {
npcs.remove(npc)
parent.remove(this)
parent.repaint()
npcRows.remove(this)
if(npcs.filter { it.x == npc.x && it.y == npc.y }.isEmpty()){
componentPointMap.filter { it.key.x == npc.x && it.key.y == npc.y }.forEach { (_,cell) ->
cell.components.forEach { c ->
if(c is JLabel) cell.remove(c)
}
cell.repaint()
}
}
}
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)
componentPointMap.filter { it.key.x == npc.x && it.key.y == npc.y }.forEach { it.value.border = border }
}
override fun mouseExited(e: MouseEvent?) {
super.mouseExited(e)
componentPointMap.filter { it.key.x == npc.x && it.key.y == npc.y }.forEach{ it.value.border = defaultBorder }
}
})
}
}
}
class UnderlaySquarePanel(color: Color, size: Int, val id: Any) : JPanel() {
init {
when (id) {
@ -291,7 +476,12 @@ object Rs2MapEditor {
if (SwingUtilities.isLeftMouseButton(e)) {
when (id) {
is Int -> {
selectedUnderlayId = id
if(selectedUnderlayId == id){
state = EditorState.NONE
} else {
state = EditorState.SET_UNDERLAY
selectedUnderlayId = id
}
statusLabel.text = "<html>Region: $region | " +
"Local Coordinates: [$selectedPointX, $selectedPointY] | " +
"Global Coordinates: [${MapTileParser.coordinateX(selectedPointX)}, ${MapTileParser.coordinateX(selectedPointY)}] | " +
@ -417,3 +607,13 @@ object Rs2MapEditor {
}
}
}
enum class EditorState{
NONE,
SET_UNDERLAY,
SET_OVERLAY,
ADD_NPC,
ADD_GROUNDITEM,
DEL_NPC,
DEL_GROUNDITEM
}

View file

@ -8,12 +8,27 @@ import const.Indices
class NPCDecoder(cache: Cache, val member: Boolean) : DefinitionDecoder<NPCDefinition>(cache, Indices.CONFIGURATION_NPCS) {
companion object {
val DEFINITIONS = HashMap<Int, NPCDefinition>()
}
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!!
return def
}
override fun NPCDefinition.read(opcode: Int, buffer: Reader) {
when (opcode) {
1 -> {

View file

@ -1,42 +1,49 @@
package cacheops.cache.definition.decoder
import Rs2MapEditor
import cacheops.cache.definition.data.NPCDefinition
import const.Image
import const.cache
import ext.unsignedShort
import java.nio.ByteBuffer
val decoder = NPCDecoder(cache, true)
class NPCSpawnDecoder {
fun decode(data: ByteArray) {
fun decode(data: ByteArray): ArrayList<NPC> {
val buffer = ByteBuffer.wrap(data)
val list = ArrayList<NPC>()
while (buffer.hasRemaining()) {
val coords = buffer.unsignedShort()
val lz = coords shr 14
val ly = (coords shr 7) and 0x3f
val lx = coords and 0x3f
val lx = (coords shr 7) and 0x3f
val ly = coords and 0x3f
val npcID = buffer.unsignedShort()
println("Npc: $npcID | Spawn: [$lx, $ly, $lz]")
list.add(NPC(npcID, lx, ly, lz))
}
return list
}
}
object NPCSpawnParser {
fun parseNPCSpawns() {
// Meh 18000 is good enough for now and covers all of our regions
for (regionIDs in 0..18000) {
val x = (regionIDs shr 8) and 0xFF
val y = regionIDs and 0xFF
val npcSpawnData = Rs2MapEditor.library.data(5, "n${x}_${y}")
fun parseNPCSpawns(regionId: Int): ArrayList<NPC> {
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()) {
println("NPC DATA FOR $regionIDs")
NPCSpawnDecoder().decode(npcSpawnData)
println("\n")
}
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)
}

View file

@ -0,0 +1,9 @@
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")))
}

View file

@ -0,0 +1,6 @@
package const
import cacheops.cache.Cache
import cacheops.cache.CacheDelegate
val cache: Cache = CacheDelegate("/home/ceikry/IdeaProjects/rs09-remake/Server/data/cache/")

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B