commit af4b79a14b1744322f62e28c9f710ca06b28d21b Author: ceikry Date: Fri Jul 16 14:35:49 2021 -0500 Added v1.0 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..ab5919e --- /dev/null +++ b/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.4.32' +} + +group 'org.example' +version '1.0-SNAPSHOT' + +repositories { + repositories { + mavenCentral() + } +} + +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" +} + +task buildJar(type: Jar) { + manifest { + attributes( + 'Main-Class': 'MainScreenKt' + ) + } + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + { + exclude "META-INF/*.SF" + exclude "META-INF/*.DSA" + exclude "META-INF/*.RSA" + } + with jar +} 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/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d59ae17 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'rs09-thanos-tool' + diff --git a/src/main/kotlin/DropTableEditor.kt b/src/main/kotlin/DropTableEditor.kt new file mode 100644 index 0000000..d761e86 --- /dev/null +++ b/src/main/kotlin/DropTableEditor.kt @@ -0,0 +1,363 @@ +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.* +import java.math.BigDecimal +import java.math.RoundingMode +import java.util.* +import javax.swing.* +import javax.swing.event.ChangeEvent +import javax.swing.table.DefaultTableModel + +class DropTableEditor(val table: NPCDropTable) : JFrame("Editing ${if(table.ids.isNotBlank()) TableData.npcNames[table.ids.split(",")[0].toInt()] ?: "Unknown Table" else "None"}") { + val IDsField = JTextField() + val descriptionField = JTextField() + val mainModel = object : DefaultTableModel(){} + val defaultModel = object : DefaultTableModel(){} + val charmModel = object : DefaultTableModel(){} + + val mainTable = object : JTable(mainModel){ + override fun editCellAt(row: Int, column: Int): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(id,row,0) + model.setValueAt(name,row,1) + table[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column) + } + + override fun editCellAt(row: Int, column: Int, e: EventObject?): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(id,row,0) + model.setValueAt(name,row,1) + table[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column, e) + } + override fun editingStopped(e: ChangeEvent?) { + val editor = cellEditor + val newValue = editor.cellEditorValue.toString() + when(editingColumn){ + 2 -> table[editingRow].minAmt = newValue + 3 -> table[editingRow].maxAmt = newValue + 4 -> { + table.totalWeight += newValue.toDouble() - table[editingRow].weight + table[editingRow].weight = newValue.toDouble() + updateMainPercentages() + } + } + super.editingStopped(e) + } + } + val mainScrollpane = JScrollPane(mainTable) + val mainSubtable = Subtable("Main",mainScrollpane,table,mainModel) + + val defaultTable = object : JTable(defaultModel){ + override fun editCellAt(row: Int, column: Int): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(id,row,0) + model.setValueAt(name,row,1) + table.alwaysTable[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column) + } + + override fun editCellAt(row: Int, column: Int, e: EventObject?): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(id,row,0) + model.setValueAt(name,row,1) + table.alwaysTable[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column, e) + } + override fun editingStopped(e: ChangeEvent?) { + val editor = cellEditor + val newValue = editor.cellEditorValue.toString() + when(editingColumn){ + 2 -> table.alwaysTable[editingRow].minAmt = newValue + 3 -> table.alwaysTable[editingRow].maxAmt = newValue + } + super.editingStopped(e) + } + } + val defaultScrollPane = JScrollPane(defaultTable) + val defaultSubtable = Subtable("Always",defaultScrollPane,table.alwaysTable,defaultModel) + + val charmTable = object : JTable(charmModel){ + override fun editCellAt(row: Int, column: Int): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(id,row,0) + model.setValueAt(name,row,1) + table.charmTable[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column) + } + + override fun editCellAt(row: Int, column: Int, e: EventObject?): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(id,row,0) + model.setValueAt(name,row,1) + table.charmTable[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column, e) + } + override fun editingStopped(e: ChangeEvent?) { + val editor = cellEditor + val newValue = editor.cellEditorValue.toString() + when(editingColumn){ + 2 -> table.charmTable[editingRow].minAmt = newValue + 3 -> table.charmTable[editingRow].maxAmt = newValue + 4 -> { + table.charmTable.totalWeight += newValue.toDouble() - table.charmTable[editingRow].weight + table.charmTable[editingRow].weight = newValue.toDouble() + updateCharmPercentages() + } + } + super.editingStopped(e) + } + } + val charmScrollPane = JScrollPane(charmTable) + val charmSubtable = Subtable("Charm",charmScrollPane,table.charmTable,charmModel) + + init { + layout = BorderLayout() + val topPanel = JPanel() + val IDsLabel = JLabel("IDs: ") + IDsField.preferredSize = Dimension(200,20) + IDsField.addKeyListener(object : KeyListener { + override fun keyTyped(e: KeyEvent?) {} + override fun keyPressed(e: KeyEvent?) {} + override fun keyReleased(e: KeyEvent?) { + table.ids = IDsField.text + } + }) + IDsField.text = table.ids + descriptionField.preferredSize = Dimension(400,20) + descriptionField.text = table.description + descriptionField.addKeyListener(object : KeyListener{ + override fun keyTyped(e: KeyEvent?) {} + override fun keyPressed(e: KeyEvent?) {} + override fun keyReleased(e: KeyEvent?) { + table.description = descriptionField.text + } + }) + topPanel.add(IDsLabel) + topPanel.add(IDsField) + + val mainButton = JButton("Edit Main") + mainButton.addActionListener { + mainSubtable.open() + } + topPanel.add(mainButton) + + val charmsButton = JButton("Edit Charms") + charmsButton.addActionListener { + charmSubtable.open() + } + topPanel.add(charmsButton) + + val defaultButton = JButton("Edit Default") + defaultButton.addActionListener { + defaultSubtable.open() + } + topPanel.add(defaultButton) + + add(topPanel,BorderLayout.NORTH) + + val descriptionPanel = JPanel() + val descriptionLabel = JLabel("Description: ") + descriptionPanel.add(descriptionLabel) + descriptionPanel.add(descriptionField) + + add(descriptionPanel,BorderLayout.SOUTH) + + mainModel.addColumn("ID") + mainModel.addColumn("Name") + mainModel.addColumn("Min Amt") + mainModel.addColumn("Max Amt") + mainModel.addColumn("Weight") + mainModel.addColumn("(%)") + + defaultModel.addColumn("ID") + defaultModel.addColumn("Name") + defaultModel.addColumn("Min Amt") + defaultModel.addColumn("Max Amt") + + charmModel.addColumn("ID") + charmModel.addColumn("Name") + charmModel.addColumn("Min Amt") + charmModel.addColumn("Max Amt") + charmModel.addColumn("Weight") + charmModel.addColumn("(%)") + + mainTable.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete") + mainTable.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + table.totalWeight -= table[mainTable.selectedRow].weight + table.removeAt(mainTable.selectedRow) + mainModel.removeRow(mainTable.selectedRow) + updateMainPercentages() + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${mainTable.selectedRow}") + } + } + }) + + defaultTable.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete") + defaultTable.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + table.alwaysTable.removeAt(defaultTable.selectedRow) + defaultModel.removeRow(defaultTable.selectedRow) + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${defaultTable.selectedRow}") + } + } + }) + + charmTable.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete") + charmTable.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + table.totalWeight -= table.charmTable[charmTable.selectedRow].weight + table.charmTable.removeAt(charmTable.selectedRow) + charmModel.removeRow(charmTable.selectedRow) + updateCharmPercentages() + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${charmTable.selectedRow}") + } + } + }) + + addWindowListener(object : WindowAdapter(){ + override fun windowClosing(e: WindowEvent?) { + FileLoader.dropTableList?.populated = false + FileLoader.dropTableList?.dispatchEvent(WindowEvent(FileLoader.dropTableList!!, WindowEvent.WINDOW_CLOSING)) + FileLoader.dropTableList?.open() + super.windowClosing(e) + } + }) + + pack() + + isVisible = false + } + + class Subtable(val label: String, val pane: JScrollPane, val table: WeightBasedTable, val model: DefaultTableModel) : JFrame("Editing $label Drops"){ + init { + layout = BorderLayout() + + val panel = JPanel() + panel.layout = BorderLayout() + val topPanel = JPanel() + + val mainLabel = JLabel("$label Drops") + topPanel.add(mainLabel) + val addButton = JButton("Add Drop") + addButton.addActionListener { + ItemMenu.caller = {id,name -> + when(label){ + "Always" -> { + model.addRow(arrayOf(id,name,1,1)) + table.add(WeightedItem(id,"1","1",1.0,true)) + } + else -> { + model.addRow(arrayOf(id,name,1,1,1.0,"")) + table.add(WeightedItem(id,"1","1",1.0,false)) + updatePercentages(model) + } + } + } + ItemMenu.open() + } + topPanel.add(addButton) + + panel.add(topPanel,BorderLayout.NORTH) + panel.add(pane,BorderLayout.SOUTH) + + add(panel) + pack() + minimumSize = Dimension(width + 40,height + 50) + isVisible = false + } + + fun open(){ + isVisible = true + } + fun updatePercentages(model: DefaultTableModel){ + for(i in 0 until model.rowCount){ + model.setValueAt(getPercentage(table[i].weight,table),i,5) + } + } + + fun getPercentage(weight: Double, table: WeightBasedTable): String{ + return "${round((weight / table.totalWeight) * 100.0,2)}%" + } + fun round(value: Double, places: Int): Double { + require(places >= 0) + var bd: BigDecimal = BigDecimal.valueOf(value) + bd = bd.setScale(places, RoundingMode.HALF_UP) + return bd.toDouble() + } + } + + fun open(){ + for(item in table.alwaysTable){ + defaultModel.addRow(arrayOf(item.id,TableData.getItemName(item.id),item.minAmt,item.maxAmt)) + } + for(item in table.charmTable){ + charmModel.addRow(arrayOf(item.id,TableData.getItemName(item.id),item.minAmt,item.maxAmt,item.weight,getPercentage(item.weight,table.charmTable))) + } + for(item in table){ + mainModel.addRow(arrayOf(item.id,TableData.getItemName(item.id),item.minAmt,item.maxAmt,item.weight,getPercentage(item.weight,table))) + } + isVisible = true + } + + fun getPercentage(weight: Double, table: WeightBasedTable): String{ + return "${round((weight / table.totalWeight) * 100.0,2)}%" + } + + fun updateCharmPercentages(){ + for(i in 0 until charmModel.rowCount){ + charmModel.setValueAt(getPercentage(table.charmTable[i].weight,table.charmTable),i,5) + } + } + + fun updateMainPercentages(){ + for(i in 0 until mainModel.rowCount){ + mainModel.setValueAt(getPercentage(table[i].weight,table),i,5) + } + } + + fun round(value: Double, places: Int): Double { + require(places >= 0) + var bd: BigDecimal = BigDecimal.valueOf(value) + bd = bd.setScale(places, RoundingMode.HALF_UP) + return bd.toDouble() + } +} \ No newline at end of file diff --git a/src/main/kotlin/EditorConstants.kt b/src/main/kotlin/EditorConstants.kt new file mode 100644 index 0000000..15272c6 --- /dev/null +++ b/src/main/kotlin/EditorConstants.kt @@ -0,0 +1,21 @@ +import com.github.weisj.darklaf.LafManager +import com.github.weisj.darklaf.theme.OneDarkTheme + +object EditorConstants { + var BUILD_NUMBER = "0.1.0" + + var DARK_MODE = true + + var CONFIG_PATH = "" + + var VALID_FILES = arrayOf("drop_tables.json","npc_configs.json","item_configs.json", "shops.json", "object_configs.json") + + var CREDITS = arrayOf( + "weisJ - darklaf look and feel themes http://github.com/weisj", + "ceikry - design and programming http://gitlab.com/ceikry" + ) + + fun updateTheme(){ + if(EditorConstants.DARK_MODE) LafManager.install(OneDarkTheme()) else LafManager.install() + } +} \ No newline at end of file diff --git a/src/main/kotlin/EditorData.kt b/src/main/kotlin/EditorData.kt new file mode 100644 index 0000000..d5f0c35 --- /dev/null +++ b/src/main/kotlin/EditorData.kt @@ -0,0 +1,19 @@ +import org.json.simple.JSONArray +import org.json.simple.parser.JSONParser +import java.io.File +import java.io.FileReader + +open class EditorData(val fileName: String) { + var reader: FileReader? = null// = FileReader(EditorConstants.CONFIG_PATH + File.separator + fileName) + val parser = JSONParser() + var data = JSONArray() + + fun configureParser(){ + reader = FileReader(EditorConstants.CONFIG_PATH + File.separator + fileName) + data = parser.parse(reader) as JSONArray + } + + open fun parse() {} + open fun save() {} + open fun show() {} +} \ No newline at end of file diff --git a/src/main/kotlin/Editors.kt b/src/main/kotlin/Editors.kt new file mode 100644 index 0000000..3209b90 --- /dev/null +++ b/src/main/kotlin/Editors.kt @@ -0,0 +1,269 @@ +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import org.json.simple.parser.JSONParser +import java.io.File +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.lang.Exception +import java.lang.StringBuilder +import javax.script.ScriptEngineManager + +enum class Editors(val data: EditorData) { + DROP_TABLES(object : EditorData("drop_tables.json"){ + var counter = 0 + override fun parse() { + Logger.logInfo("Parsing Drop Tables") + configureParser() + data.forEach { dropTableRaw -> + val dropTable = dropTableRaw as org.json.simple.JSONObject + val table = NPCDropTable() + table.ids = dropTable["ids"].toString() + parseTable(dropTable["main"] as JSONArray,table,false) + parseTable(dropTable["default"] as JSONArray,table,true) + parseTable(dropTable["charm"] as JSONArray,table,false) + table.description = (dropTable["description"] ?: "").toString() + TableData.tables.add(table) + counter++ + } + Logger.logInfo("$counter Drop Tables Parsed.") + } + + override fun save() { + val CHARMS = intArrayOf(12160,12163,12159,12158) + val dtables = JSONArray() + for(table in TableData.tables){ + val t = JSONObject() + t.put("ids",table.ids) + val main = saveTable(table) + val charms = saveTable(table.charmTable) + val always = saveTable(table.alwaysTable) + t.put("main",main) + t.put("charm",charms) + t.put("default",always) + t.put("description",table.description) + dtables.add(t) + } + + + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", dtables.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() + } + } + + override fun show() { + super.show() + } + }), + NPC_CONFIGS(object : EditorData("npc_configs.json"){ + override fun parse() { + Logger.logInfo("Parsing NPC configs") + configureParser() + data.forEach { npcDataRaw -> + val npcData = npcDataRaw as JSONObject + val id = npcData["id"].toString().toInt() + val name = npcData["name"].toString() + TableData.npcNames[id] = name + TableData.npcConfigKeys.addAll(npcData.keys.toHashSet() as HashSet) + TableData.npcConfigs.add(npcData) + } + } + + override fun save() { + val array = JSONArray() + TableData.npcConfigs.forEach { + array.add(it) + } + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", array.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() + } + } + }), + ITEM_CONFIGS(object : EditorData("item_configs.json"){ + override fun parse() { + configureParser() + data.forEach { itemDataRaw -> + val itemData = itemDataRaw as JSONObject + val id = itemData["id"].toString().toInt() + val name = itemData["name"].toString() + TableData.itemConfigKeys.addAll(itemData.keys.toHashSet() as HashSet) + TableData.itemNames[id] = name + TableData.itemConfigs.add(itemData) + } + } + + override fun save() { + val array = JSONArray() + TableData.itemConfigs.forEach { + array.add(it) + } + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", array.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() + } + } + }), + OBJECT_CONFIGS(object : EditorData("object_configs.json"){ + override fun parse() { + Logger.logInfo("Parsing Object configs") + configureParser() + data.forEach { ObjDataRaw -> + val objData = ObjDataRaw as JSONObject + TableData.objConfigKeys.addAll(objData.keys.toHashSet() as HashSet) + TableData.objConfigs.add(objData) + } + } + + override fun save() { + val array = JSONArray() + TableData.objConfigs.forEach { + array.add(it) + } + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", array.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() + } + } + }), + SHOPS(object : EditorData("shops.json"){ + override fun parse() { + Logger.logInfo("Parsing shop data...") + configureParser() + var counter = 0 + + data.forEach { shopDataRaw -> + val shopData = shopDataRaw as JSONObject + val id = shopData["id"].toString().toInt() + val title = shopData["title"].toString() + val general = shopData["general_store"].toString().toBoolean() + val stock = parseStock(shopData["stock"].toString()) + val npcs = if(shopData["npcs"].toString().isNotBlank()) shopData["npcs"].toString() else "" + val currency = shopData["currency"].toString().toInt() + val highAlch = shopData["high_alch"].toString() == "1" + TableData.shops[id] = TableData.Shop(id,title,stock,npcs,currency,general,highAlch) + counter++ + } + + Logger.logInfo("Loaded $counter shops.") + } + + override fun save() { + val shs = JSONArray() + + fun ArrayList.isLast(thing: T): Boolean{ + return indexOf(thing) == size - 1 + } + + for((_,shop) in TableData.shops){ + val sh = JSONObject() + sh.put("id",shop.id.toString()) + sh.put("title",shop.title) + val stockBuilder = StringBuilder() + for(item in shop.stock){ + stockBuilder.append("{${item.id},${if(item.infinite) "inf" else item.amount}}${if(shop.stock.isLast(item)) "" else "-"}") + } + sh.put("stock",stockBuilder.toString()) + sh.put("npcs",shop.npcs) + sh.put("currency",shop.currency.toString()) + sh.put("high_alch",if(shop.high_alch) "1" else "0") + sh.put("general_store",shop.general_store.toString()) + shs.add(sh) + } + + + val manager = ScriptEngineManager() + val scriptEngine = manager.getEngineByName("JavaScript") + scriptEngine.put("jsonString", shs.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() + } + } + }) +} + + +fun parseStock(stock: String): ArrayList{ + val items = ArrayList() + if(stock.isEmpty()){ + return items + } + stock.split('-').map { + val tokens = it.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray() + var amount = tokens[1].trim() + items.add(Item(tokens[0].trim().toInt(),amount,amount == "inf")) + } + return items +} +private fun parseTable(data: JSONArray, destTable: WeightBasedTable, isAlways: Boolean) { + for(it in data){ + val item = it as JSONObject + val id = item["id"].toString().toInt() + val minAmount = item["minAmount"].toString().toInt() + val maxAmount = item["maxAmount"].toString().toInt() + val weight = item["weight"].toString().toDouble() + val newItem = WeightedItem(id,minAmount.toString(),maxAmount.toString(),weight.toDouble(),isAlways) + destTable.add(newItem) + } +} +private fun saveTable(table: WeightBasedTable): JSONArray{ + val arr = JSONArray() + for(item in table){ + val it = JSONObject() + it.put("id",item.id.toString()) + it.put("weight",item.weight.toString()) + it.put("minAmount",item.minAmt.toString()) + it.put("maxAmount",item.maxAmt.toString()) + arr.add(it) + } + return arr +} diff --git a/src/main/kotlin/FileLoader.kt b/src/main/kotlin/FileLoader.kt new file mode 100644 index 0000000..65ad24c --- /dev/null +++ b/src/main/kotlin/FileLoader.kt @@ -0,0 +1,106 @@ +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import org.json.simple.parser.JSONParser +import java.io.FileReader +import java.lang.Exception +import javax.swing.* + +object FileLoader{ + val loadStatus = JLabel("File not loaded.") + val loadButton = JButton("Select drop_tables.json") + val chooser = JFileChooser() + var dropTableList: TableList? = null + + private fun parseTable(data: JSONArray, destTable: WeightBasedTable, isAlways: Boolean) { + for(it in data){ + val item = it as JSONObject + val id = item["id"].toString().toInt() + val minAmount = item["minAmount"].toString().toInt() + val maxAmount = item["maxAmount"].toString().toInt() + val weight = item["weight"].toString().toDouble() + val newItem = WeightedItem(id,minAmount.toString(),maxAmount.toString(),weight.toDouble(),isAlways) + destTable.add(newItem) + } + } + + fun load(){ + Logger.logInfo("Parsing Drop Tables...") + var parser = JSONParser() + var reader = FileReader(EditorConstants.CONFIG_PATH + "/drop_tables.json") + var data = parser.parse(reader) as JSONArray + var counter = 0 + + data.forEach { dropTableRaw -> + val dropTable = dropTableRaw as JSONObject + val table = NPCDropTable() + table.ids = dropTable["ids"].toString() + parseTable(dropTable["main"] as JSONArray,table,false) + parseTable(dropTable["default"] as JSONArray,table,true) + parseTable(dropTable["charm"] as JSONArray,table,false) + table.description = (dropTable["description"] ?: "").toString() + TableData.tables.add(table) + counter++ + } + Logger.logInfo("Parsed Drop Tables.") + Logger.logInfo("Loaded $counter drop tables.") + + Logger.logInfo("Parsing Item Configs...") + try { + data = parser.parse(FileReader(EditorConstants.CONFIG_PATH + "/item_configs.json")) as JSONArray + data.forEach { itemDataRaw -> + val itemData = itemDataRaw as JSONObject + val id = itemData["id"].toString().toInt() + val name = itemData["name"].toString() + TableData.itemNames[id] = name + } + Logger.logInfo("Parsed item configs.") + } catch (e: Exception){ + Logger.logErr("Failed to load item_configs.json. Item names will be unavailable when viewing and editing shops.") + } + Logger.logInfo("Parsing NPC configs...") + try { + data = parser.parse(FileReader(EditorConstants.CONFIG_PATH + "/npc_configs.json")) as JSONArray + data.forEach { npcDataRaw -> + val npcData = npcDataRaw as JSONObject + val id = npcData["id"].toString().toInt() + val name = npcData["name"].toString() + TableData.npcNames[id] = name + } + Logger.logInfo("Parsed NPC configs") + } catch (e: Exception){ + Logger.logErr("Failed to load npc_configs.json. Drop tables will lack proper naming. Good luck figuring out what you want to edit.") + } + + } +} + +class Item(var id: Int,var amount: String,var infinite: Boolean) +class WeightedItem(var id: Int, var minAmt: String, var maxAmt: String, var weight: Double, var isAlways: Boolean) + +private val CHARMS = intArrayOf(12160,12163,12159,12158) + +open class WeightBasedTable() : ArrayList(){ + var totalWeight: Double = 0.0 + override fun add(element: WeightedItem): Boolean { + totalWeight += element.weight + return super.add(element) + } +} + +class NPCDropTable() : WeightBasedTable(){ + val charmTable = WeightBasedTable() + val alwaysTable = WeightBasedTable() + var ids = "" + var description = "" + override fun add(element: WeightedItem): Boolean { + if(element.isAlways){ + alwaysTable.add(element) + return true + } + if(CHARMS.contains(element.id)){ + charmTable.add(element) + return true + } + return super.add(element) + } +} \ No newline at end of file diff --git a/src/main/kotlin/Globals.kt b/src/main/kotlin/Globals.kt new file mode 100644 index 0000000..0fe17ef --- /dev/null +++ b/src/main/kotlin/Globals.kt @@ -0,0 +1,57 @@ +import java.awt.BorderLayout +import java.awt.Dimension +import javax.swing.* + +val confirmation = object : JFrame("Notice"){ + init { + layout = BorderLayout() + minimumSize = Dimension(250,100) + defaultCloseOperation = HIDE_ON_CLOSE + val okButton = JButton("OK") + okButton.addActionListener { + isVisible = false + } + add(JLabel("Added a new row to the table."), BorderLayout.NORTH) + add(okButton, BorderLayout.SOUTH) + setLocationRelativeTo(null) + isVisible = false + pack() + } +} +val saveConfirmation = object : JFrame("Notice"){ + init { + minimumSize = Dimension(250,100) + defaultCloseOperation = HIDE_ON_CLOSE + layout = BorderLayout() + val okButton = JButton("OK") + okButton.addActionListener { + isVisible = false + } + add(JLabel("Data saved."), BorderLayout.NORTH) + add(okButton, BorderLayout.SOUTH) + isVisible = false + setLocationRelativeTo(null) + pack() + } +} + +val credits = object : JFrame("Credits"){ + init { + defaultCloseOperation = HIDE_ON_CLOSE + val creditPanel = JPanel() + creditPanel.layout = BoxLayout(creditPanel, BoxLayout.PAGE_AXIS) + layout = BorderLayout() + + for(credit in EditorConstants.CREDITS){ + creditPanel.add(JLabel(credit)) + } + + add(creditPanel) + + isVisible = false + + setLocationRelativeTo(null) + + pack() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ItemMenu.kt b/src/main/kotlin/ItemMenu.kt new file mode 100644 index 0000000..773e1fe --- /dev/null +++ b/src/main/kotlin/ItemMenu.kt @@ -0,0 +1,100 @@ +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 ItemMenu : JFrame("Item Selection Menu") { + var model = DefaultTableModel() + var searchField = JTextField() + var amountField = 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, 1) + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun open() { + if (!populated) { + SwingUtilities.invokeLater { + for ((id,_) in TableData.itemNames) { + model.addRow(arrayOf(id,TableData.getItemName(id))) + } + isVisible = true + populated = true + } + } else { + isVisible = true + } + } + + init { + layout = BorderLayout() + cellRenderer.toolTipText = "Double-Click to select." + val searchPanel = JPanel() + val searchLabel = JLabel("Search for Item:") + val amountLabel = JLabel("Amount: ") + amountField.preferredSize = Dimension(45, 20) + 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/Logger.kt b/src/main/kotlin/Logger.kt new file mode 100644 index 0000000..dc024e4 --- /dev/null +++ b/src/main/kotlin/Logger.kt @@ -0,0 +1,21 @@ +import com.github.ajalt.mordant.rendering.TextColors +import com.github.ajalt.mordant.terminal.Terminal +import java.text.SimpleDateFormat +import java.util.* + +object Logger { + val t = Terminal() + val formatter = SimpleDateFormat("HH:mm:ss") + + fun logInfo(message: String){ + t.println("${getTime()} ${TextColors.white("[INFO] $message")}") + } + + fun logErr(message: String){ + t.println("${getTime()} ${TextColors.red("[ ERR] $message")}") + } + + fun getTime(): String{ + return "[" + formatter.format(Date(System.currentTimeMillis())) +"]" + } +} \ No newline at end of file diff --git a/src/main/kotlin/MainScreen.kt b/src/main/kotlin/MainScreen.kt new file mode 100644 index 0000000..4ec31fc --- /dev/null +++ b/src/main/kotlin/MainScreen.kt @@ -0,0 +1,143 @@ +import com.github.weisj.darklaf.LafManager +import com.github.weisj.darklaf.theme.DarculaTheme +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.FlowLayout +import java.io.File +import java.util.* +import javax.swing.* +import javax.swing.table.DefaultTableModel + +object MainScreen : JFrame("RS09 Thanos Tool") { + val dirChooser = object : JFileChooser(){ + override fun updateUI() { + EditorConstants.updateTheme() + super.updateUI() + } + } + val selectDir = JButton("Select Config Folder") + val openDrops = JButton("Edit Drop Tables") + val openNPC = JButton("Edit NPC Configs") + val openItem = JButton("Edit Item Configs") + val openShops = JButton("Edit Shops") + val openObjects = JButton("Edit Object Configs") + var loadedModel = object : DefaultTableModel(){} + val loadedTable = object : JTable(loadedModel){ + override fun editCellAt(p0: Int, p1: Int): Boolean { + return false + } + + override fun editCellAt(p0: Int, p1: Int, p2: EventObject?): Boolean { + return false + } + } + val pane = JScrollPane(loadedTable) + + init { + isResizable = false + layout = BorderLayout() + dirChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + defaultCloseOperation = EXIT_ON_CLOSE + preferredSize = Dimension(550,300) + loadedModel.addColumn("Filename") + + iconImage = ImageIcon(javaClass.getResource("/thanos.png")).image + + + selectDir.addActionListener { + val response = dirChooser.showOpenDialog(null) + EditorConstants.updateTheme() + if(response == JFileChooser.APPROVE_OPTION){ + Logger.logInfo("Selected directory: ${dirChooser.selectedFile.absolutePath}") + EditorConstants.CONFIG_PATH = dirChooser.selectedFile.absolutePath + showLoaded() + } + } + + openDrops.addActionListener { + FileLoader.dropTableList = TableList() + FileLoader.dropTableList!!.open() + } + + openItem.addActionListener { + TableEditor(Editors.ITEM_CONFIGS).open() + } + + openNPC.addActionListener { + TableEditor(Editors.NPC_CONFIGS).open() + } + + openObjects.addActionListener { + TableEditor(Editors.OBJECT_CONFIGS).open() + } + + openShops.addActionListener { + ShopList().open() + } + + val showCredits = JButton("Show Credits") + showCredits.addActionListener { + credits.isVisible = true + } + + 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)){ + i.preferredSize = Dimension(175,40) + i.maximumSize = Dimension(175,40) + i.isEnabled = i == showCredits + editorPanel.add(i) + } + + add(editorPanel, BorderLayout.WEST) + + val buttonPane = JPanel() + buttonPane.layout = BorderLayout() + selectDir.preferredSize = Dimension(300,40) + selectDir.maximumSize = Dimension(300,40) + buttonPane.add(selectDir, BorderLayout.CENTER) + + selectionPanel.layout = BoxLayout(selectionPanel, BoxLayout.Y_AXIS) + selectionPanel.add(buttonPane) + + pane.preferredSize = Dimension(225,200) + selectionPanel.add(pane) + + add(JSeparator(SwingConstants.VERTICAL), BorderLayout.CENTER) + + setLocationRelativeTo(null) + add(selectionPanel, BorderLayout.EAST) + EditorConstants.updateTheme() + pack() + } +} + +fun showLoaded(){ + MainScreen.loadedModel = object : DefaultTableModel(){} + MainScreen.loadedModel.addColumn("Filename") + MainScreen.loadedTable.model = MainScreen.loadedModel + + val files = File(EditorConstants.CONFIG_PATH).listFiles() ?: return + + for(file in files){ + if(file.name in EditorConstants.VALID_FILES){ + MainScreen.loadedModel.addRow(arrayOf(file.name)) + when(file.nameWithoutExtension){ + "drop_tables" -> MainScreen.openDrops.isEnabled = true.also { Editors.DROP_TABLES.data.parse() } + "npc_configs" -> MainScreen.openNPC.isEnabled = true.also { Editors.NPC_CONFIGS.data.parse() } + "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()} + } + } + } + + MainScreen.pane.revalidate() + MainScreen.pane.repaint() +} + +fun main() { + MainScreen.isVisible = true +} \ No newline at end of file diff --git a/src/main/kotlin/ShopEdit.kt b/src/main/kotlin/ShopEdit.kt new file mode 100644 index 0000000..d0ab1bd --- /dev/null +++ b/src/main/kotlin/ShopEdit.kt @@ -0,0 +1,170 @@ +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.* +import java.util.* +import javax.swing.* +import javax.swing.event.ChangeEvent +import javax.swing.table.DefaultTableModel +import kotlin.collections.ArrayList + +class ShopEdit(val shopID: Int) : JFrame("Edit Shop") { + val shop = TableData.shops[shopID] + val idField = JTextField() + val titleField = JTextField() + val currencyField = JTextField() + val generalCheckbox = JCheckBox("General Store") + val highAlchCheckbox = JCheckBox("High Alch Prices") + var model = DefaultTableModel() + + + init { + layout = BorderLayout() + val topPanel = JPanel() + val idLabel = JLabel("ID: ") + idField.preferredSize = Dimension(40, 20) + idField.text = shopID.toString() + idField.addKeyListener(object : KeyListener{ + override fun keyTyped(e: KeyEvent?) {} + override fun keyPressed(e: KeyEvent?) {} + override fun keyReleased(e: KeyEvent?) { + shop?.id = idField.text.toInt() + } + }) + topPanel.add(idLabel) + topPanel.add(idField) + + val currencyLabel = JLabel("Currency: ") + currencyField.preferredSize = Dimension(50,20) + currencyField.text = shop?.currency.toString() + currencyField.addKeyListener(object : KeyListener{ + override fun keyTyped(e: KeyEvent?) {} + override fun keyPressed(e: KeyEvent?) {} + override fun keyReleased(e: KeyEvent?) { + shop?.currency = currencyField.text.toInt() + } + }) + topPanel.add(currencyLabel) + topPanel.add(currencyField) + + val titleLabel = JLabel("Title: ") + titleField.preferredSize = Dimension(300,20) + titleField.text = shop?.title + titleField.addKeyListener(object : KeyListener{ + override fun keyTyped(e: KeyEvent?) {} + override fun keyPressed(e: KeyEvent?) {} + override fun keyReleased(e: KeyEvent?) { + shop?.title = titleField.text + } + }) + topPanel.add(titleLabel) + topPanel.add(titleField) + + add(topPanel,BorderLayout.NORTH) + + val checkPanel = JPanel() + val npcLabel = JLabel("NPCs: ") + val npcField = JTextField() + npcField.addKeyListener(object : KeyListener{ + override fun keyTyped(e: KeyEvent?) { + shop?.npcs = npcField.text + e?.keyChar + } + override fun keyPressed(e: KeyEvent?) {} + override fun keyReleased(e: KeyEvent?) {} + }) + npcField.preferredSize = Dimension(50,20) + npcField.text = shop?.npcs + checkPanel.add(npcLabel) + checkPanel.add(npcField) + generalCheckbox.isSelected = shop?.general_store ?: false + generalCheckbox.addChangeListener { + shop!!.general_store = !shop.general_store + } + checkPanel.add(generalCheckbox) + highAlchCheckbox.isBorderPaintedFlat = shop?.high_alch ?: false + highAlchCheckbox.isSelected = shop?.high_alch ?: false + highAlchCheckbox.addChangeListener { + shop!!.high_alch = !shop.high_alch + } + checkPanel.add(highAlchCheckbox) + + val addButton = JButton("Add Item") + addButton.addActionListener { + ItemMenu.caller = {id,name -> + model.addRow(arrayOf(name,id,0,false)) + shop!!.stock.add(Item(id,"0",false)) + } + ItemMenu.open() + } + checkPanel.add(addButton) + add(checkPanel,BorderLayout.CENTER) + + val itemTable: JTable = object : JTable(model) { + override fun editCellAt(row: Int, column: Int): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(name,row,0) + model.setValueAt(id,row,1) + shop!!.stock[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column) + } + + override fun editCellAt(row: Int, column: Int, e: EventObject?): Boolean { + if(column == 0 || column == 1){ + ItemMenu.caller = {id,name -> + model.setValueAt(name,row,0) + model.setValueAt(id,row,1) + shop!!.stock[row].id = id + } + ItemMenu.open() + return false + } + return super.editCellAt(row, column, e) + } + override fun editingStopped(e: ChangeEvent?) { + val editor = cellEditor + val newValue = editor.cellEditorValue.toString() + when(editingColumn){ + 1 -> TableData.shops[shopID]!!.stock[editingRow].id = newValue.toInt() + 2 -> TableData.shops[shopID]!!.stock[editingRow].amount = newValue + 3 -> TableData.shops[shopID]!!.stock[editingRow].infinite = newValue.toBoolean() + } + super.editingStopped(e) + } + } + itemTable.putClientProperty("terminateEditOnFocusLost", true); + + itemTable.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete") + itemTable.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + shop!!.stock.removeAt(itemTable.selectedRow) + model.removeRow(itemTable.selectedRow) + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${itemTable.selectedRow}") + } + } + }) + + model.addColumn("Name") + model.addColumn("ID") + model.addColumn("Amount") + model.addColumn("Infinite?") + + val scrollPane = JScrollPane(itemTable) + add(scrollPane,BorderLayout.SOUTH) + + pack() + isVisible = false + } + + fun open(){ + for(item in shop?.stock ?: ArrayList()){ + model.addRow(arrayOf(TableData.itemNames[item.id] ?: "unknown",item.id,item.amount,item.infinite)) + } + isVisible = true + } +} \ No newline at end of file diff --git a/src/main/kotlin/ShopList.kt b/src/main/kotlin/ShopList.kt new file mode 100644 index 0000000..c47d726 --- /dev/null +++ b/src/main/kotlin/ShopList.kt @@ -0,0 +1,131 @@ +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.* +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 +import kotlin.collections.ArrayList + +class ShopList : JFrame("RS09 Shop Editor") { + val searchField = JTextField() + var model = DefaultTableModel() + var sorter = TableRowSorter(model) + var cellRenderer = DefaultTableCellRenderer() + var populated = false + + init { + isVisible = false + layout = BorderLayout() + + val searchPanel = JPanel() + val searchLabel = JLabel("Search for shop: ") + val addButton = JButton("Add Shop") + val saveButton = JButton("Save Shops") + addButton.addActionListener { + val highestID = TableData.shops.keys.toIntArray()[TableData.shops.size - 1] + TableData.shops.put(highestID + 1, TableData.Shop(highestID + 1,"", ArrayList(),"",995,false,false)) + ShopEdit(highestID + 1).open() + } + saveButton.addActionListener { + Editors.SHOPS.data.save() + saveConfirmation.isVisible = true + } + searchField.preferredSize = Dimension(100, 20) + searchPanel.add(searchLabel) + searchPanel.add(searchField) + searchPanel.add(addButton) + searchPanel.add(saveButton) + add(searchPanel, BorderLayout.NORTH) + setLocationRelativeTo(null) + + val itemTable: JTable = object : JTable(model) { + override fun editCellAt(i: Int, i1: Int, eventObject: EventObject): Boolean { + return false + } + } + + itemTable.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete") + itemTable.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + val table = e?.source as JTable + TableData.shops.remove(table.getValueAt(table.selectedRow,0)) + model.removeRow(table.convertRowIndexToModel(table.selectedRow)) + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${itemTable.selectedRow}") + } + } + }) + + 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 + ShopEdit(table.getValueAt(row,0) as Int).open() + } + } + + 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 + } + + private fun filter() { + var rf: RowFilter? = null + //If current expression doesn't parse, don't update. + rf = try { + RowFilter.regexFilter(searchField.text, 1) + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun open() { + if (!populated) { + Logger.logInfo("Loading shops data...") + for(i in 0 until model.rowCount){ + model.removeRow(0) + } + SwingUtilities.invokeLater { + for ((id,shop) in TableData.shops) { + model.addRow(arrayOf(id, shop.title)) + } + isVisible = true + populated = true + } + } else { + isVisible = true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/TableData.kt b/src/main/kotlin/TableData.kt new file mode 100644 index 0000000..d5d6194 --- /dev/null +++ b/src/main/kotlin/TableData.kt @@ -0,0 +1,33 @@ +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import java.io.File +import java.io.FileWriter +import java.io.IOException +import java.lang.StringBuilder +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) + val tables = ArrayList() + val itemNames = hashMapOf() + val npcNames = hashMapOf() + val npcConfigKeys = HashSet() + val npcConfigs = ArrayList() + val objConfigKeys = HashSet() + val objConfigs = ArrayList() + var itemConfigKeys = HashSet() + var itemConfigs = ArrayList() + val shops = hashMapOf() + + fun getItemName(id: Int): String{ + return when(id){ + 31 -> "RDT Slot" + 1 -> "Clue Scroll (easy)" + 5733 -> "Clue Scroll (med)" + 12070 -> "Clue Scroll (hard)" + 0 -> "Nothing" + else -> itemNames[id] ?: "Unknown" + } + } +} + diff --git a/src/main/kotlin/TableEditor.kt b/src/main/kotlin/TableEditor.kt new file mode 100644 index 0000000..60699be --- /dev/null +++ b/src/main/kotlin/TableEditor.kt @@ -0,0 +1,152 @@ +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.FlowLayout +import java.awt.event.ActionEvent +import java.awt.event.KeyEvent +import java.awt.event.KeyListener +import java.util.regex.PatternSyntaxException +import javax.swing.* +import javax.swing.event.TableModelEvent +import javax.swing.event.TableModelListener +import javax.swing.table.DefaultTableModel +import javax.swing.table.TableRowSorter + +/** + * Represents a generic JTable-based editor. Pretty much just a cleaner visualisation of JSON objects with editing functionality. + */ +class TableEditor(val editor: Editors) : JFrame("Editing ${editor.name.replace("_"," ").toLowerCase()}") { + val searchField = JTextField() + val model = DefaultTableModel() + var table = JTable(model) + val sorter = TableRowSorter(model) + + init { + layout = BorderLayout() + isVisible = false + table.autoResizeMode = JTable.AUTO_RESIZE_OFF + searchField.preferredSize = Dimension(200,20) + searchField.maximumSize = Dimension(200,20) + minimumSize = Dimension(1000, 650) + + searchField.addKeyListener(object : KeyListener{ + override fun keyTyped(p0: KeyEvent?) { filter() } + override fun keyPressed(p0: KeyEvent?) {} + override fun keyReleased(p0: KeyEvent?) {} + }) + + table.rowSorter = sorter + + + val addButton = JButton("Add Row") + val saveButton = JButton("Save Data") + + addButton.addActionListener { + getData().add(JSONObject()) + model.addRow(arrayOf()) + confirmation.isVisible = true + } + + saveButton.addActionListener { + editor.data.save() + saveConfirmation.isVisible = true + } + + val searchPanel = JPanel() + searchPanel.layout = FlowLayout() + searchPanel.add(JLabel("Search by Name ")) + searchPanel.add(searchField) + searchPanel.add(addButton) + searchPanel.add(saveButton) + + val pane = JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS) + add(pane, BorderLayout.CENTER) + add(searchPanel, BorderLayout.NORTH) + + val keys = when (editor) { + Editors.NPC_CONFIGS -> TableData.npcConfigKeys + Editors.ITEM_CONFIGS -> TableData.itemConfigKeys + Editors.OBJECT_CONFIGS -> TableData.objConfigKeys + else -> ArrayList() + } + + if(editor == Editors.OBJECT_CONFIGS){ + model.addColumn("ids") + model.addColumn("examine") + keys.remove("ids") + keys.remove("examine") + table.columnModel.getColumn(0).preferredWidth = 400 + table.columnModel.getColumn(1).preferredWidth = 600 + } else { + model.addColumn("id") + model.addColumn("name") + keys.remove("id") + keys.remove("name") + } + + keys.sortedBy { it.toString() }.forEach { model.addColumn(it) } + if(editor != Editors.OBJECT_CONFIGS) { + table.columnModel.getColumn(1).preferredWidth = 150 + } + + model.addTableModelListener(object : TableModelListener{ + override fun tableChanged(event: TableModelEvent?) { + event ?: return + if(event.type != TableModelEvent.UPDATE) return + val label = model.getColumnName(event.column) + getData()[event.firstRow][label] = model.getValueAt(event.firstRow, event.column) + Logger.logInfo("Changed ${getData()[event.firstRow]["name"]}'s $label to ${model.getValueAt(event.firstRow,event.column)}") + } + }) + + table.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + val table = e?.source as JTable + getData().removeAt(table.convertRowIndexToModel(table.selectedRow)) + model.removeRow(table.convertRowIndexToModel(table.selectedRow)) + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${table.selectedRow}") + } + } + }) + + EditorConstants.updateTheme() + pack() + } + + fun open(){ + val data = getData() + + for(datum in data){ + val vals = JSONArray() + for(i in 0 until model.columnCount){ + val label = model.getColumnName(i) + vals.add(datum[label] ?: "") + } + model.addRow(vals.toArray()) + } + isVisible = true + } + + private fun filter() { + var rf: RowFilter? = null + //If current expression doesn't parse, don't update. + rf = try { + RowFilter.regexFilter(searchField.text) + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun getData(): ArrayList { + return when(editor){ + Editors.ITEM_CONFIGS -> TableData.itemConfigs + Editors.NPC_CONFIGS -> TableData.npcConfigs + Editors.OBJECT_CONFIGS -> TableData.objConfigs + else -> ArrayList() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/TableList.kt b/src/main/kotlin/TableList.kt new file mode 100644 index 0000000..746ed3c --- /dev/null +++ b/src/main/kotlin/TableList.kt @@ -0,0 +1,126 @@ +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.* +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 + +class TableList : JFrame("RS09 Drop Table Editor") { + val searchField = JTextField() + var model = DefaultTableModel() + var sorter = TableRowSorter(model) + var cellRenderer = DefaultTableCellRenderer() + var populated = false + + init { + isVisible = false + layout = BorderLayout() + + val searchPanel = JPanel() + val searchLabel = JLabel("Search for table: ") + val addButton = JButton("Add Table") + val saveButton = JButton("Save Tables") + addButton.addActionListener { + TableData.tables.add(NPCDropTable()) + DropTableEditor(TableData.tables.last()).open() + } + saveButton.addActionListener { + Editors.DROP_TABLES.data.save() + saveConfirmation.isVisible = true + } + searchField.preferredSize = Dimension(100, 20) + searchPanel.add(searchLabel) + searchPanel.add(searchField) + searchPanel.add(addButton) + searchPanel.add(saveButton) + add(searchPanel, BorderLayout.NORTH) + setLocationRelativeTo(null) + + val itemTable: JTable = object : JTable(model) { + override fun editCellAt(i: Int, i1: Int, eventObject: EventObject): Boolean { + return false + } + } + + itemTable.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete") + itemTable.actionMap.put("delete",object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + try { + val table = e?.source as JTable + TableData.tables.removeAt(table.convertRowIndexToModel(table.selectedRow)) + model.removeRow(table.convertRowIndexToModel(table.selectedRow)) + } catch(e: Exception){ + Logger.logErr("Tried to remove nonexistent row ${itemTable.selectedRow}") + } + } + }) + + model.addColumn("Name") + + itemTable.rowSorter = sorter + itemTable.columnModel.getColumn(0).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 + DropTableEditor(TableData.tables[table.convertRowIndexToModel(row)]).open() + } + } + + 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 + } + + private fun filter() { + var rf: RowFilter? = null + //If current expression doesn't parse, don't update. + rf = try { + RowFilter.regexFilter(searchField.text, 0) + } catch (e: PatternSyntaxException) { + return + } + sorter.rowFilter = rf + } + + fun open() { + if (!populated) { + Logger.logInfo("Loading table data...") + for(i in 0 until model.rowCount){ + model.removeRow(0) + } + SwingUtilities.invokeLater { + for (table in TableData.tables) { + model.addRow(arrayOf(if(table.ids.isNotBlank()) TableData.npcNames[table.ids.split(",")[0].toInt()] ?: "Unknown Table" else "None")) + } + isVisible = true + populated = true + } + } else { + isVisible = true + } + } +} \ No newline at end of file diff --git a/src/main/resources/thanos.png b/src/main/resources/thanos.png new file mode 100644 index 0000000..bab94d6 Binary files /dev/null and b/src/main/resources/thanos.png differ