commit 9a908784f2bff024b33f6df42650fbcd1cea3fea Author: woahscam Date: Wed Oct 27 15:15:11 2021 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5609ad8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +./idea +./build +.class diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..abfa832 --- /dev/null +++ b/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.5.31' + id 'java' +} + +group 'org.praesecho' +version '0.3' + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +repositories { + mavenCentral() + mavenCentral() +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" + + // Flatlaf + implementation 'com.formdev:flatlaf:1.6.1' + + // 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' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..744e882 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f5b4789 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Praesecho-Map-Underlay-Editor' + diff --git a/src/main/kotlin/Rs2MapEditor.kt b/src/main/kotlin/Rs2MapEditor.kt new file mode 100644 index 0000000..e899b68 --- /dev/null +++ b/src/main/kotlin/Rs2MapEditor.kt @@ -0,0 +1,400 @@ +import com.displee.cache.CacheLibrary +import com.formdev.flatlaf.FlatDarculaLaf +import decoders.FloorUnderlayConfiguration +import decoders.MapTileParser +import definitions.UnderlayDefinition +import java.awt.* +import java.awt.event.KeyEvent +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.* +import javax.swing.border.* +import kotlin.system.exitProcess + +object Rs2MapEditor { + + val library = CacheLibrary.create("K:\\RSPSDev\\RS-2009Toolset\\data\\cache578_preservation") + + @JvmStatic + fun main(args: Array) { + FlatDarculaLaf.setup() + FloorUnderlayConfiguration.init() + MapTileParser.init() + underlayMap = FloorUnderlayConfiguration.floorUnderlays + 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: MutableMap = mutableMapOf() + + // Stores the underlay ID with the Underlay Definition + lateinit var underlayMap: MutableMap + + var selectedUnderlayId: Int? = null + + var statusLabel = JLabel() + var infoLabel = JLabel() + var underlayInfo = JLabel("Right click a tile to get more information") + var underlayLabel = JLabel() + + class MainFrame : JFrame("Praesecho Map Editor v0.3") { + init { + val statusBar = JPanel(FlowLayout(FlowLayout.LEFT)) + statusBar.border = CompoundBorder( + LineBorder(Color.DARK_GRAY), + EmptyBorder(2, 3, 2, 3) + ) + statusLabel.foreground = Color.YELLOW + statusBar.add(statusLabel) + + bounds = Rectangle(Dimension(1567, 1100)) + size.setSize(1567, 1100) + maximumSize = Dimension(1567, 1100) + defaultCloseOperation = EXIT_ON_CLOSE + jMenuBar = MenuBar() + add(SettingsPane(), BorderLayout.PAGE_START) + add(ScrollPane(), BorderLayout.CENTER) + add(ScrollPane2(), BorderLayout.EAST) + add(statusBar, BorderLayout.SOUTH) + infoLabel.text = "Information" + statusLabel.text = "-" + //frame.pack() + setLocationRelativeTo(null) + isResizable = false + isVisible = true + } + } + + class GridPane : JPanel() { + 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) { + 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 flippedY = (64 - row) - 1 + colorPointMap[Point(column, flippedY)] = MapTileParser.definition.getTile(column, flippedY, 0).underlayId + val cellPane = CellPane() + cellPane.background = underlayMap[MapTileParser.definition.getTile(column, flippedY, 0).underlayId]!!.getRGB() + cellPane.border = border + add(cellPane, gbc) + } + } + } + } + + class CellPane : JPanel() { + var defaultBackground: Color = background + override fun getPreferredSize(): Dimension { + return Dimension(cellX, cellY) + } + init { + //add(underlayLabel) + addMouseListener(object : MouseAdapter() { + override fun mouseEntered(e: MouseEvent) { + selectedPointX = e.component.bounds.location.x / cellX + selectedPointY = e.component.bounds.location.y / cellY + statusLabel.foreground = Color.YELLOW + statusLabel.text = "Region: ${MapTileParser.region} | " + + "Local Coordinates: [$selectedPointX, $selectedPointY] | " + + "Global Coordinates: [${MapTileParser.coordinateX(selectedPointX)}, ${MapTileParser.coordinateX(selectedPointY)}] | " + + "Viewing Underlay: ${MapTileParser.definition.getTile(selectedPointX, selectedPointY, 0).underlayId} | " + + "Selected Underlay: ${if (selectedUnderlayId == null) "No underlay selected!" else "$selectedUnderlayId"}" + defaultBackground = background + background = Color.BLUE + } + + override fun mouseExited(e: MouseEvent) { + background = defaultBackground + } + + override fun mouseClicked(e: MouseEvent) { + if (SwingUtilities.isLeftMouseButton(e)) { + if (selectedUnderlayId != null) { + val underlayID = selectedUnderlayId + val point = Point(selectedPointX, selectedPointY) + val oldValue = colorPointMap[point] + colorPointMap[point] = underlayID!! + println("UPDATED UNDERLAY VALUES @ [${point.x}, ${point.y}] [$oldValue >> ${colorPointMap[point]}]") + defaultBackground = underlayMap[selectedUnderlayId]?.getRGB()!! + background = underlayMap[selectedUnderlayId]?.getRGB() + } + } else if (SwingUtilities.isRightMouseButton(e)) { + val point = Point(selectedPointX, selectedPointY) + val def = MapTileParser.definition.getTile(point.x, point.y, 0) + underlayInfo.text = + String.format("%s", + "

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


" + + "

" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
UnderlayIDHeightSettings
${def.underlayId}${def.height}${def.settings}
") + } + } + }) + } + } + + class ScrollPane : JPanel() { + init { + // Scroll bar + val scrollFrame = JScrollPane(GridPane()) + scrollFrame.maximumSize = Dimension(1300, 980) + scrollFrame.preferredSize = Dimension(1300, 980) + add(scrollFrame, BorderLayout.CENTER) + } + } + + class ScrollPane2 : JPanel() { + init { + // Scroll bar + layout = BorderLayout() + val scrollFrame = JScrollPane(InfoPane()) + scrollFrame.preferredSize = Dimension(255, 980) + add(scrollFrame, BorderLayout.CENTER) + } + } + + class InfoPane : JTabbedPane() { + init { + val informationComponent: JComponent = TileInformationPanel() + 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) + 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) + } + } + + class SettingsPane : JPanel(FlowLayout(FlowLayout.RIGHT)) { + init { + layout = FlowLayout(FlowLayout.LEFT) + add(CommitButton()) + add(RevertButton()) + } + } + + 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 TileInformationPanel : JPanel() { + init { + underlayInfo.foreground = Color.white + underlayInfo.verticalAlignment = SwingConstants.TOP + underlayInfo.horizontalAlignment = SwingConstants.CENTER + layout = BorderLayout() + add(underlayInfo) + } + } + + class FLUGridPanel : JPanel() { + init { + underlayMap.forEach { + val underlay = UnderlaySquarePanel(it.value.getRGB(),30, it.key) + add(underlay) + } + val addAnUnderlay = UnderlaySquarePanel(Color.darkGray,30, "+") + add(addAnUnderlay) + } + } + + class UnderlaySquarePanel(color: Color, size: Int, id: Any) : JPanel() { + init { + when (id) { + is Int -> { + background = color + } + else -> { + background = color + border = BorderFactory.createDashedBorder(Color.GREEN) + } + } + val text = JLabel("$id") + val defaultBackground = color + + + minimumSize = Dimension(size, size) + maximumSize = Dimension(size, size) + preferredSize = Dimension(size, size) + add(text) + + addMouseListener(object : MouseAdapter() { + override fun mouseEntered(e: MouseEvent) { + background = Color.BLUE + } + + override fun mouseExited(e: MouseEvent) { + background = defaultBackground + } + + override fun mouseClicked(e: MouseEvent) { + if (SwingUtilities.isLeftMouseButton(e)) { + when (selectedUnderlayId) { + is Int -> { + selectedUnderlayId = id as Int + statusLabel.text = "Region: ${MapTileParser.region} | " + + "Local Coordinates: [$selectedPointX, $selectedPointY] | " + + "Global Coordinates: [${MapTileParser.coordinateX(selectedPointX)}, ${MapTileParser.coordinateX(selectedPointY)}] | " + + "Viewing Underlay: ${MapTileParser.definition.getTile(selectedPointX, selectedPointY, 0).underlayId} | " + + "Selected Underlay: ${if (selectedUnderlayId == null) "No underlay selected!" else "$selectedUnderlayId"}" + } + else -> { + println("User trying to add new underlay") + } + } + } + } + }) + } + } + + class CommitButton : JButton() { + init { + val label = JLabel("Commit") + add(label) + addActionListener { + println("User attempting to commit") + } + } + } + + class RevertButton : JButton() { + init { + val label = JLabel("Revert") + add(label) + addActionListener { + println("User attempting to revert") + } + } + } + + class MenuBar : JMenuBar() { + init { + add(FileMenuOption()) + add(RegionMenuOption()) + add(PrefMenuOption()) + } + } + + class FileMenuOption : JMenu("File") { + init { + add(LoadItem()) + add(QuitItem()) + } + } + + class LoadItem : JMenuItem("Load Cache") { + init { + addActionListener { + println("This feature has not been enabled yet") + } + } + } + + class QuitItem : JMenuItem("Quit") { + init { + addActionListener { + exitProcess(0) + } + } + } + + class RegionMenuOption : JMenu("Region") { + init { + add(LoadRegionItem()) + } + } + + class LoadRegionItem : JMenuItem("Load") { + init { + addActionListener { + println("This feature has not been enabled yet") + } + } + } + + class PrefMenuOption : JMenu("Preferences") { + init { + add(DisplayUnderlayItem()) + add(DisplayOverlayItem()) + add(DisplaySceneryItem()) + addSeparator() + add(DisplayHeightItem()) + } + } + + class DisplayUnderlayItem : JCheckBoxMenuItem("Display Underlays", true) { + init { + addActionListener { + println("This feature has not been enabled yet") + } + } + } + + class DisplayOverlayItem : JCheckBoxMenuItem("Display Overlays", false) { + init { + addActionListener { + println("This feature has not been enabled yet") + } + } + } + + class DisplaySceneryItem : JCheckBoxMenuItem("Display Scenery", false) { + init { + addActionListener { + println("This feature has not been enabled yet") + } + } + } + + class DisplayHeightItem : JCheckBoxMenuItem("Display Underlay IDs", false) { + init { + addActionListener { + underlayLabel.isVisible = !underlayLabel.isVisible + } + } + } +} 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/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/decoders/FloorUnderlayConfiguration.kt b/src/main/kotlin/decoders/FloorUnderlayConfiguration.kt new file mode 100644 index 0000000..81e6d75 --- /dev/null +++ b/src/main/kotlin/decoders/FloorUnderlayConfiguration.kt @@ -0,0 +1,33 @@ +package decoders + +import Rs2MapEditor +import const.Archives +import const.Indices +import definitions.UnderlayDefinition +import java.nio.ByteBuffer + +object FloorUnderlayConfiguration { + + val cache = Rs2MapEditor.library.index(Indices.CONFIGURATION).archive(Archives.FLOOR_UNDERLAYS) + + var floorUnderlays = mutableMapOf() + + @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/decoders/FloorUnderlayDecoder.kt b/src/main/kotlin/decoders/FloorUnderlayDecoder.kt new file mode 100644 index 0000000..0ca0051 --- /dev/null +++ b/src/main/kotlin/decoders/FloorUnderlayDecoder.kt @@ -0,0 +1,132 @@ +package decoders + +import definitions.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/decoders/MapTileDecoder.kt b/src/main/kotlin/decoders/MapTileDecoder.kt new file mode 100644 index 0000000..858d79a --- /dev/null +++ b/src/main/kotlin/decoders/MapTileDecoder.kt @@ -0,0 +1,55 @@ +package decoders + +import definitions.MapDefinition +import ext.unsignedByte +import world.MapTile +import java.nio.ByteBuffer + +class MapTileDecoder { + + fun readLoop(definition: MapDefinition, buffer: ByteBuffer) { + 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, + overlayId, + overlayPath, + overlayRotation, + settings, + underlayId) + ) + } + } + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/decoders/MapTileParser.kt b/src/main/kotlin/decoders/MapTileParser.kt new file mode 100644 index 0000000..b10d81e --- /dev/null +++ b/src/main/kotlin/decoders/MapTileParser.kt @@ -0,0 +1,53 @@ +package decoders + +import Rs2MapEditor +import definitions.MapDefinition +import java.nio.ByteBuffer + +object MapTileParser { + + val definition = MapDefinition() + + val region = 12850 // Lumbridge + + @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() { + val region = 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}") + + MapTileDecoder().readLoop(definition, ByteBuffer.wrap(mapData)) + } + + fun coordinateX(x: Int): Int { + val baseX = ((region shr 8) shl 6) + return x + baseX + } + + fun coordinateY(y: Int): Int { + val baseY = ((region and 0xFF) shl 6) + return y + baseY + } +} \ No newline at end of file diff --git a/src/main/kotlin/definitions/MapDefinition.kt b/src/main/kotlin/definitions/MapDefinition.kt new file mode 100644 index 0000000..8d158bd --- /dev/null +++ b/src/main/kotlin/definitions/MapDefinition.kt @@ -0,0 +1,26 @@ +package definitions + +import world.MapObject +import world.MapTile + +data class MapDefinition( + var id: Int = -1, + val tiles: MutableMap = mutableMapOf(), + val objects: MutableList = mutableListOf() +) { + + 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 + } +} diff --git a/src/main/kotlin/definitions/UnderlayDefinition.kt b/src/main/kotlin/definitions/UnderlayDefinition.kt new file mode 100644 index 0000000..19e3a10 --- /dev/null +++ b/src/main/kotlin/definitions/UnderlayDefinition.kt @@ -0,0 +1,23 @@ +package definitions + +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/ext/JagexTypes.kt b/src/main/kotlin/ext/JagexTypes.kt new file mode 100644 index 0000000..72cb882 --- /dev/null +++ b/src/main/kotlin/ext/JagexTypes.kt @@ -0,0 +1,43 @@ +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 +} \ No newline at end of file diff --git a/src/main/kotlin/world/MapObject.kt b/src/main/kotlin/world/MapObject.kt new file mode 100644 index 0000000..668d495 --- /dev/null +++ b/src/main/kotlin/world/MapObject.kt @@ -0,0 +1,44 @@ +package world + +@JvmInline +value 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/world/MapTile.kt b/src/main/kotlin/world/MapTile.kt new file mode 100644 index 0000000..95ff60e --- /dev/null +++ b/src/main/kotlin/world/MapTile.kt @@ -0,0 +1,59 @@ +package world + +@JvmInline +value 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