Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore14
-rw-r--r--assets/opensb/interface/opensb/bindings/bind.pngbin0 -> 418 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/bindings.config160
-rw-r--r--assets/opensb/interface/opensb/bindings/bindings.lua372
-rw-r--r--assets/opensb/interface/opensb/bindings/bindname.pngbin0 -> 249 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/body.pngbin0 -> 986 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/category.pngbin0 -> 181 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/categoryback.pngbin0 -> 318 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/footer.pngbin0 -> 260 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/garbage.pngbin0 -> 171 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/groupname.pngbin0 -> 475 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/header.pngbin0 -> 601 bytes
-rw-r--r--assets/opensb/interface/opensb/bindings/reset.pngbin0 -> 187 bytes
-rw-r--r--assets/opensb/interface/optionsmenu/controlsbutton.pngbin0 -> 213 bytes
-rw-r--r--assets/opensb/interface/optionsmenu/controlsbuttonhover.pngbin0 -> 213 bytes
-rw-r--r--assets/opensb/interface/optionsmenu/optionsmenu.config.patch25
-rw-r--r--source/client/StarClientApplication.cpp26
-rw-r--r--source/frontend/CMakeLists.txt4
-rw-r--r--source/frontend/StarBaseScriptPane.cpp183
-rw-r--r--source/frontend/StarBaseScriptPane.hpp45
-rw-r--r--source/frontend/StarBindingsMenu.cpp23
-rw-r--r--source/frontend/StarBindingsMenu.hpp24
-rw-r--r--source/frontend/StarMainInterface.cpp22
-rw-r--r--source/frontend/StarMainInterface.hpp2
-rw-r--r--source/frontend/StarOptionsMenu.cpp13
-rw-r--r--source/frontend/StarOptionsMenu.hpp4
-rw-r--r--source/frontend/StarScriptPane.cpp113
-rw-r--r--source/frontend/StarScriptPane.hpp21
-rw-r--r--source/game/CMakeLists.txt2
-rw-r--r--source/game/StarInput.cpp278
-rw-r--r--source/game/StarInput.hpp56
-rw-r--r--source/game/scripting/StarInputLuaBindings.cpp36
-rw-r--r--source/game/scripting/StarInputLuaBindings.hpp17
-rw-r--r--source/game/scripting/StarLuaComponents.hpp8
34 files changed, 1271 insertions, 177 deletions
diff --git a/.gitignore b/.gitignore
index 8cd7c60..dc72520 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,17 @@ install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
+
+
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix \ No newline at end of file
diff --git a/assets/opensb/interface/opensb/bindings/bind.png b/assets/opensb/interface/opensb/bindings/bind.png
new file mode 100644
index 0000000..64efa20
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/bind.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/bindings.config b/assets/opensb/interface/opensb/bindings/bindings.config
new file mode 100644
index 0000000..639fa28
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/bindings.config
@@ -0,0 +1,160 @@
+{
+ "scripts" : ["/interface/opensb/bindings/bindings.lua"],
+ "scriptDelta" : 0,
+ "scriptWidgetCallbacks" : [
+ "selectCategory",
+ "applyBind",
+ "eraseBind",
+ "resetBind",
+ "snare"
+ ],
+
+ "gui" : {
+ "panefeature" : {
+ "type" : "panefeature",
+ "positionLocked" : false
+ },
+ "background" : {
+ "type" : "background",
+ "fileHeader" : "/interface/opensb/bindings/header.png",
+ "fileBody" : "/interface/opensb/bindings/body.png",
+ "fileFooter" : "/interface/opensb/bindings/footer.png"
+ },
+ "banner" : {
+ "type" : "canvas",
+ "rect" : [146, 187, 398, 215]
+ },
+ "snare" : {
+ "type" : "textbox",
+ "position" : [2147483647, 2147483647],
+ "regex" : "(){0,0}",
+ "maxWidth" : 0,
+ "focus" : false,
+ "escapeKey": "snare",
+ "enterKey": "snare",
+ "callback": "snare"
+ },
+ "categories" : {
+ "type" : "scrollArea",
+ "rect" : [4, 16, 145, 214],
+ "children" : {
+ "list" : {
+ "type" : "list",
+ "schema" : {
+ "selectedBG" : "/interface/opensb/bindings/categoryback.png?multiply=0f0",
+ "unselectedBG" : "/interface/opensb/bindings/categoryback.png?multiply=222",
+ "spacing" : [0, 1],
+ "memberSize" : [130, 16],
+ "listTemplate" : {
+ "background" : {
+ "type" : "image",
+ "file" : "/interface/opensb/bindings/categoryback.png?multiply=222",
+ "position" : [0, 0],
+ "zlevel" : -1
+ },
+ "button" : {
+ "type" : "button",
+ "callback" : "selectCategory",
+ "caption" : "Unnamed",
+ "base" : "/interface/opensb/bindings/category.png?replace;fff=fff0;000=0007",
+ "hover" : "/interface/opensb/bindings/category.png?replace;fff=fff7;000=3337",
+ "press" : "/interface/opensb/bindings/category.png?replace;fff=000;000=7777",
+ "pressedOffset" : [0, 0],
+ "position" : [0, 0]
+ }
+ }
+ }
+ }
+ },
+ "buttons" : {
+ "horizontal" : {
+ "forward" : { "base" : "", "hover" : "", "pressed" : "" },
+ "backward" : { "base" : "", "hover": "", "pressed" : "" }
+ },
+ "vertical" : {
+ "forward" : {
+ "base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-forwardhover.png",
+ "pressed" : ""
+ },
+ "backward" : {
+ "base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-backwardhover.png",
+ "pressed" : ""
+ }
+ }
+ },
+ "thumbs" : {
+ "horizontal" : {
+ "base" : { "begin" : "", "end" : "", "inner" : "" },
+ "hover" : { "begin" : "", "end" : "", "inner" : "" },
+ "pressed" : { "begin" : "", "end" : "", "inner" : "" }
+ },
+ "vertical" : {
+ "base" : {
+ "begin" : "/interface/scrollarea/vthumb-begin.png",
+ "end" : "/interface/scrollarea/vthumb-end.png",
+ "inner" : "/interface/scrollarea/vthumb-inner.png"
+ },
+ "hover" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ },
+ "pressed" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ }
+ }
+ }
+ },
+ "binds" : {
+ "type" : "scrollArea",
+ "rect" : [147, 16, 398, 185],
+ "children" : {},
+ "buttons" : {
+ "horizontal" : {
+ "forward" : { "base" : "", "hover" : "", "pressed" : "" },
+ "backward" : { "base" : "", "hover": "", "pressed" : "" }
+ },
+ "vertical" : {
+ "forward" : {
+ "base" : "/interface/scrollarea/varrow-forward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-forwardhover.png",
+ "pressed" : ""
+ },
+ "backward" : {
+ "base" : "/interface/scrollarea/varrow-backward.png?setcolor=fff",
+ "hover" : "/interface/scrollarea/varrow-backwardhover.png",
+ "pressed" : ""
+ }
+ }
+ },
+ "thumbs" : {
+ "horizontal" : {
+ "base" : { "begin" : "", "end" : "", "inner" : "" },
+ "hover" : { "begin" : "", "end" : "", "inner" : "" },
+ "pressed" : { "begin" : "", "end" : "", "inner" : "" }
+ },
+ "vertical" : {
+ "base" : {
+ "begin" : "/interface/scrollarea/vthumb-begin.png",
+ "end" : "/interface/scrollarea/vthumb-end.png",
+ "inner" : "/interface/scrollarea/vthumb-inner.png"
+ },
+ "hover" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ },
+ "pressed" : {
+ "begin" : "/interface/scrollarea/vthumb-beginhover.png",
+ "end" : "/interface/scrollarea/vthumb-endhover.png",
+ "inner" : "/interface/scrollarea/vthumb-innerhover.png"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/assets/opensb/interface/opensb/bindings/bindings.lua b/assets/opensb/interface/opensb/bindings/bindings.lua
new file mode 100644
index 0000000..1287636
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/bindings.lua
@@ -0,0 +1,372 @@
+--constants
+local PATH = "/interface/opensb/bindings/"
+local CATEGORY_LIST_WIDGET = "categories.list"
+local BINDS_WIDGET = "binds"
+
+local fmt = string.format
+local log = function() end
+
+--SNARE
+
+local snared = false
+
+function snare(key) end
+
+local mods = {}
+for i, mod in ipairs{"LShift", "RShift", "LCtrl", "RCtrl", "LAlt", "RAlt", "LGui", "RGui", "AltGr", "Scroll"} do
+ local data = {name = mod, active = false}
+ mods[i], mods[mod] = data, data
+end
+
+
+local function getMods(key)
+ local bindMods = jarray()
+ for i, mod in ipairs(mods) do
+ if mod.active and mod.name ~= key then
+ bindMods[#bindMods + 1] = mod.name
+ end
+ end
+ if bindMods[1] then return bindMods end
+end
+
+local function finishBind(type, value)
+ widget.blur("snare")
+ snared = false
+ snareFinished{ type = type, value = value, mods = getMods(value) }
+ for i, mod in ipairs(mods) do
+ mod.active = false
+ end
+end
+
+local function scanInputEvents()
+ local events = input.events()
+ for i, event in pairs(events) do
+ local type, data = event.type, event.data
+ if type == "KeyDown" then
+ local key = data.key
+ local mod = mods[key]
+ if mod then mod.active = true end
+ elseif type == "KeyUp" then
+ return finishBind("key", data.key)
+ elseif type == "MouseButtonDown" then
+ return finishBind("mouse", data.mouseButton)
+ end
+ end
+end
+
+function update()
+ if snared then
+ scanInputEvents()
+ if snared and not widget.hasFocus("snare") then
+ snared = false
+ snareFinished()
+ for i, mod in ipairs(mods) do
+ mod.active = false
+ end
+ end
+ end
+end
+
+local function beginSnare()
+ snared = true
+ widget.focus("snare")
+end
+
+-- BINDING
+
+
+local function alphabeticalNameSortGreater(a, b) return a.name > b.name end
+local function alphabeticalNameSortLesser(a, b) return a.name < b.name end
+local sortedCategories = {}
+local categories = {}
+
+local widgetsToCategories = {}
+local allBinds = {}
+
+local function addCategoryToList(data)
+ local name = widget.addListItem(CATEGORY_LIST_WIDGET)
+ widget.setText(fmt("%s.%s.button", CATEGORY_LIST_WIDGET, name), data.name)
+ log("Added category ^cyan;%s^reset; to list", data.name)
+ return name
+end
+
+local function parseBinds()
+ for i, path in pairs(root.assetsByExtension("binds")) do
+ local data = root.assetJson(path)
+ for categoryId, data in pairs(data) do
+ if not data.name then data.name = categoryId end
+ data.categoryId = categoryId
+ categories[categoryId] = data
+ end
+ end
+
+ for categoryId, data in pairs(categories) do
+ sortedCategories[#sortedCategories + 1] = data
+ end
+ table.sort(sortedCategories, alphabeticalNameSortLesser)
+ for i = 1, #sortedCategories do
+ local data = sortedCategories[i]
+ data.index = i
+ local name = addCategoryToList(data)
+ data.widget = name
+ widgetsToCategories[name] = data
+ end
+ if sortedCategories[1] then
+ local first = sortedCategories[1].widget
+ widget.setListSelected(CATEGORY_LIST_WIDGET, first)
+ selectCategory(first)
+ end
+end
+
+function bindsToString(binds)
+ local t = {}
+ for i, bind in pairs(binds) do
+ local str = ""
+ if bind.mods then
+ for i, v in pairs(bind.mods) do
+ str = str .. v .. " + "
+ end
+ end
+ if bind.type == "key" then
+ str = str .. bind.value
+ elseif bind.type == "mouse" then
+ str = str .. bind.value
+ end
+ local _i = (i - 1) * 2
+ if _i ~= 0 then
+ t[_i] = ", "
+ end
+ t[_i + 1] = str
+ end
+ return table.concat(t)
+end
+
+function setButtonsEnabled(state)
+ for i, bind in pairs(allBinds) do
+ widget.setButtonEnabled(fmt("%s.apply_%s", BINDS_WIDGET, bind.bindId), state)
+ widget.setButtonEnabled(fmt("%s.erase_%s", BINDS_WIDGET, bind.bindId), state)
+ widget.setButtonEnabled(fmt("%s.reset_%s", BINDS_WIDGET, bind.bindId), state)
+ end
+ for widgetId in pairs(widgetsToCategories) do
+ widget.setButtonEnabled(fmt("%s.%s.button", CATEGORY_LIST_WIDGET, widgetId), state)
+ end
+ widget.setButtonEnabled(CATEGORY_LIST_WIDGET, state)
+end
+
+local activeBind
+local activeCategory
+
+function snareFinished(newBind)
+ setButtonsEnabled(true)
+ if not newBind or not activeBind or not activeCategory then return end
+ local currentBinds = input.getBinds(activeCategory, activeBind)
+ local replace = false
+ for i, v in pairs(currentBinds) do
+ if sb.printJson(v) == sb.printJson(newBind) then
+ --equal, replace all binds
+ currentBinds = {newBind}
+ replace = true
+ break
+ end
+ end
+ if replace then
+ currentBinds = {newBind}
+ log("Replaced %s with %s", activeBind, sb.printJson(newBind))
+ else
+ currentBinds[#currentBinds + 1] = newBind
+ log("Added %s to %s", sb.printJson(newBind), activeBind)
+ end
+ input.setBinds(activeCategory, activeBind, currentBinds)
+ widget.setText(fmt("%s.apply_%s", BINDS_WIDGET, activeBind), bindsToString(currentBinds))
+end
+
+function eraseBind(bind)
+ bind = bind:sub(7)
+ local binds = jarray()
+ binds[1] = nil
+ input.setBinds(activeCategory, bind, binds)
+ widget.setText(fmt("%s.apply_%s", BINDS_WIDGET, bind), "")
+end
+
+function resetBind(bind)
+ bind = bind:sub(7)
+ local defaultBinds = input.getDefaultBinds(activeCategory, bind)
+ if #defaultBinds == 0 then
+ defaultBinds = jarray()
+ defaultBinds[1] = nil
+ end
+ input.setBinds(activeCategory, bind, defaultBinds)
+ widget.setText(fmt("%s.apply_%s", BINDS_WIDGET, bind), bindsToString(defaultBinds))
+end
+
+function applyBind(bind)
+ bind = bind:sub(7)
+ log("Modifying bind %s", bind)
+ setButtonsEnabled(false)
+ activeBind = bind
+ beginSnare()
+end
+
+local function addBindGroup(data, i, added)
+ local y = (i - 1) * -14
+ local bg = {
+ type = "image",
+ file = PATH .. "groupname.png",
+ position = {-12, y}
+ }
+ local name = "group_" .. i
+ widget.addChild(BINDS_WIDGET, bg, name)
+ added[#added + 1] = name
+ local label = {
+ type = "label",
+ value = data.name,
+ wrapWidth = 120,
+ fontSize = 8,
+ hAnchor = "mid",
+ vAnchor = "mid",
+ position = {120, 6}
+ }
+ widget.addChild(fmt("%s.%s", BINDS_WIDGET, name), label, "text")
+end
+
+local function addBindSet(data, i, added)
+ local y = (i - 1) * -14
+ local bg = {
+ type = "image",
+ file = PATH .. "bindname.png",
+ position = {-12, y}
+ }
+ local name = "label_" .. i
+ widget.addChild(BINDS_WIDGET, bg, name)
+ added[#added + 1] = name
+ local label = {
+ type = "label",
+ value = data.name,
+ wrapWidth = 120,
+ fontSize = 8,
+ hAnchor = "mid",
+ vAnchor = "mid",
+ position = {62, 6}
+ }
+ widget.addChild(fmt("%s.%s", BINDS_WIDGET, name), label, "text")
+ local button = {
+ type = "button",
+ callback = "applyBind",
+ position = {112, y + 2},
+ pressedOffset = {0, 0},
+ base = PATH .. "bind.png",
+ hover = PATH .. "bind.png?fade=fff;0.025",
+ pressed = PATH .. "bind.png?fade=fff;0.05?multiply=0f0",
+ caption = bindsToString(input.getBinds(data.categoryId, data.bindId))
+ }
+ name = "apply_" .. data.bindId
+ added[#added + 1] = name
+ widget.addChild(BINDS_WIDGET, button, name)
+ local erase = {
+ type = "button",
+ callback = "eraseBind",
+ position = {209, y + 2},
+ pressedOffset = {0, -1},
+ base = PATH .. "garbage.png",
+ hover = PATH .. "garbage.png?multiply=faa",
+ pressed = PATH .. "garbage.png?multiply=f00",
+ }
+ name = "erase_" .. data.bindId
+ added[#added + 1] = name
+ widget.addChild(BINDS_WIDGET, erase, name)
+ local reset = {
+ type = "button",
+ callback = "resetBind",
+ position = {218, y + 2},
+ pressedOffset = {0, -1},
+ base = PATH .. "reset.png",
+ hover = PATH .. "reset.png?multiply=faa",
+ pressed = PATH .. "reset.png?multiply=f00",
+ }
+ name = "reset_" .. data.bindId
+ added[#added + 1] = name
+ widget.addChild(BINDS_WIDGET, reset, name)
+end
+
+function selectCategory()
+ local selected = widget.getListSelected(CATEGORY_LIST_WIDGET)
+ local category = widgetsToCategories[selected]
+ local dataFromPrev = widget.getData(BINDS_WIDGET)
+ if dataFromPrev then
+ for i, v in pairs(dataFromPrev) do
+ widget.removeChild(BINDS_WIDGET, v)
+ end
+ end
+ widgetsToBinds = {}
+
+ local groups = category.groups or {}
+ if not groups.unsorted then
+ groups.unsorted = {name = "Unsorted"}
+ end
+
+ local sortedGroups = {}
+ for groupId, data in pairs(groups) do
+ data.name = tostring(data.name or groupId)
+ data.sortedBinds = {}
+ sortedGroups[#sortedGroups + 1] = data
+ end
+
+ allBinds = {}
+ for bindId, data in pairs(category.binds) do
+ if not data.name then data.name = bindId end
+ data.bindId = bindId
+ data.categoryId = category.categoryId
+ local group = groups[data.group or "unsorted"] or groups.unsorted
+ group.sortedBinds[#group.sortedBinds + 1] = data
+ allBinds[#allBinds + 1] = data
+ end
+
+ activeCategory = category.categoryId
+ table.sort(sortedGroups, alphabeticalNameSortLesser)
+
+ for groupId, data in pairs(groups) do
+ table.sort(data.sortedBinds, alphabeticalNameSortLesser)
+ end
+
+ local bannerBinds = widget.bindCanvas("banner")
+ bannerBinds:clear()
+
+ local bannerName = tostring(category.bannerName or category.name or category.categoryId)
+ bannerBinds:drawText(bannerName, {position = {127, 13}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16)
+
+ local onlyUnsorted = not sortedGroups[2] and sortedGroups[1] == groups.unsorted
+
+ local added = {}
+ local index = 0
+ for iA = 1, #sortedGroups do
+ local group = sortedGroups[iA]
+ local bindsCount = #group.sortedBinds
+ if bindsCount > 0 then
+ if not onlyUnsorted then
+ index = index + 1
+ addBindGroup(group, index, added)
+ end
+ for iB = 1, bindsCount do
+ index = index + 1
+ addBindSet(group.sortedBinds[iB], index, added)
+ end
+ end
+ end
+
+ widget.setData(BINDS_WIDGET, added)
+end
+
+
+local function initCallbacks()
+ widget.registerMemberCallback(CATEGORY_LIST_WIDGET, "selectCategory", selectCategory)
+end
+
+function init()
+ --log = sb.logInfo
+
+ widget.clearListItems(CATEGORY_LIST_WIDGET)
+ initCallbacks()
+ parseBinds()
+
+ script.setUpdateDelta(1)
+end \ No newline at end of file
diff --git a/assets/opensb/interface/opensb/bindings/bindname.png b/assets/opensb/interface/opensb/bindings/bindname.png
new file mode 100644
index 0000000..c4ee548
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/bindname.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/body.png b/assets/opensb/interface/opensb/bindings/body.png
new file mode 100644
index 0000000..a63b6fd
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/body.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/category.png b/assets/opensb/interface/opensb/bindings/category.png
new file mode 100644
index 0000000..115f33c
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/category.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/categoryback.png b/assets/opensb/interface/opensb/bindings/categoryback.png
new file mode 100644
index 0000000..3bff20b
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/categoryback.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/footer.png b/assets/opensb/interface/opensb/bindings/footer.png
new file mode 100644
index 0000000..151f336
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/footer.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/garbage.png b/assets/opensb/interface/opensb/bindings/garbage.png
new file mode 100644
index 0000000..aa3e15f
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/garbage.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/groupname.png b/assets/opensb/interface/opensb/bindings/groupname.png
new file mode 100644
index 0000000..c447114
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/groupname.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/header.png b/assets/opensb/interface/opensb/bindings/header.png
new file mode 100644
index 0000000..fa6ec21
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/header.png
Binary files differ
diff --git a/assets/opensb/interface/opensb/bindings/reset.png b/assets/opensb/interface/opensb/bindings/reset.png
new file mode 100644
index 0000000..f516834
--- /dev/null
+++ b/assets/opensb/interface/opensb/bindings/reset.png
Binary files differ
diff --git a/assets/opensb/interface/optionsmenu/controlsbutton.png b/assets/opensb/interface/optionsmenu/controlsbutton.png
new file mode 100644
index 0000000..1a2a6ed
--- /dev/null
+++ b/assets/opensb/interface/optionsmenu/controlsbutton.png
Binary files differ
diff --git a/assets/opensb/interface/optionsmenu/controlsbuttonhover.png b/assets/opensb/interface/optionsmenu/controlsbuttonhover.png
new file mode 100644
index 0000000..ba57e02
--- /dev/null
+++ b/assets/opensb/interface/optionsmenu/controlsbuttonhover.png
Binary files differ
diff --git a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch
new file mode 100644
index 0000000..fe5def8
--- /dev/null
+++ b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch
@@ -0,0 +1,25 @@
+{
+ "paneLayout" : {
+ "showKeybindings" : {
+ "type" : "button",
+ "position" : [150, 95],
+ "caption" : "Game Binds",
+ "base" : "/interface/optionsmenu/controlsbutton.png",
+ "hover" : "/interface/optionsmenu/controlsbuttonhover.png"
+ },
+ "showModBindings" : {
+ "type" : "button",
+ "position" : [87, 95],
+ "caption" : "Mod Binds",
+ "base" : "/interface/optionsmenu/controlsbutton.png",
+ "hover" : "/interface/optionsmenu/controlsbuttonhover.png"
+ },
+ "showGraphics" : {
+ "type" : "button",
+ "position" : [24, 95],
+ "caption" : "Graphics",
+ "base" : "/interface/optionsmenu/controlsbutton.png",
+ "hover" : "/interface/optionsmenu/controlsbuttonhover.png"
+ }
+ }
+} \ No newline at end of file
diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp
index eedb399..33a4552 100644
--- a/source/client/StarClientApplication.cpp
+++ b/source/client/StarClientApplication.cpp
@@ -207,7 +207,7 @@ void ClientApplication::renderInit(RendererPtr renderer) {
Application::renderInit(renderer);
auto assets = m_root->assets();
- auto loadConfig = [&](String const& name) {
+ auto loadEffectConfig = [&](String const& name) {
String path = strf("/rendering/{}.config", name);
if (assets->assetExists(path)) {
StringMap<String> shaders;
@@ -230,8 +230,8 @@ void ClientApplication::renderInit(RendererPtr renderer) {
Logger::warn("No rendering config found for renderer with id '{}'", renderer->rendererId());
};
- loadConfig("world");
- loadConfig("default");
+ loadEffectConfig("world");
+ loadEffectConfig("default");
if (m_root->configuration()->get("limitTextureAtlasSize").optBool().value(false))
renderer->setSizeLimitEnabled(true);
@@ -301,17 +301,20 @@ void ClientApplication::processInput(InputEvent const& event) {
if (!m_errorScreen->accepted() && m_errorScreen->handleInputEvent(event))
return;
- if (m_state == MainAppState::Splash) {
- m_cinematicOverlay->handleInputEvent(event);
+ bool processed = false;
+ if (m_state == MainAppState::Splash) {
+ processed = m_cinematicOverlay->handleInputEvent(event);
} else if (m_state == MainAppState::Title) {
- if (!m_cinematicOverlay->handleInputEvent(event))
- m_titleScreen->handleInputEvent(event);
+ if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
+ processed = m_titleScreen->handleInputEvent(event);
} else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) {
- if (!m_cinematicOverlay->handleInputEvent(event))
- m_mainInterface->handleInputEvent(event);
+ if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
+ processed = m_mainInterface->handleInputEvent(event);
}
+
+ m_input->handleInput(event, processed);
}
void ClientApplication::update() {
@@ -348,6 +351,7 @@ void ClientApplication::update() {
m_guiContext->cleanup();
m_edgeKeyEvents.clear();
+ m_input->reset();
}
void ClientApplication::render() {
@@ -822,7 +826,9 @@ void ClientApplication::updateRunning() {
m_mainInterface->update();
m_mainMixer->update(m_cinematicOverlay->muteSfx(), m_cinematicOverlay->muteMusic());
- appController()->setAcceptingTextInput(m_mainInterface->textInputActive());
+ bool inputActive = m_mainInterface->textInputActive();
+ appController()->setAcceptingTextInput(inputActive);
+ m_input->setTextInputActive(inputActive);
for (auto const& interactAction : m_player->pullInteractActions())
m_mainInterface->handleInteractAction(interactAction);
diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt
index cd06ce7..50c4229 100644
--- a/source/frontend/CMakeLists.txt
+++ b/source/frontend/CMakeLists.txt
@@ -13,6 +13,8 @@ INCLUDE_DIRECTORIES (
SET (star_frontend_HEADERS
StarActionBar.hpp
StarAiInterface.hpp
+ StarBaseScriptPane.hpp
+ StarBindingsMenu.hpp
StarBookmarkInterface.hpp
StarChat.hpp
StarCharCreation.hpp
@@ -59,6 +61,8 @@ SET (star_frontend_HEADERS
SET (star_frontend_SOURCES
StarActionBar.cpp
StarAiInterface.cpp
+ StarBaseScriptPane.cpp
+ StarBindingsMenu.cpp
StarBookmarkInterface.cpp
StarChat.cpp
StarCharCreation.cpp
diff --git a/source/frontend/StarBaseScriptPane.cpp b/source/frontend/StarBaseScriptPane.cpp
new file mode 100644
index 0000000..a174622
--- /dev/null
+++ b/source/frontend/StarBaseScriptPane.cpp
@@ -0,0 +1,183 @@
+#include "StarBaseScriptPane.hpp"
+#include "StarRoot.hpp"
+#include "StarAssets.hpp"
+#include "StarGuiReader.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarConfigLuaBindings.hpp"
+#include "StarLuaGameConverters.hpp"
+#include "StarWidgetLuaBindings.hpp"
+#include "StarCanvasWidget.hpp"
+#include "StarItemTooltip.hpp"
+#include "StarItemGridWidget.hpp"
+#include "StarSimpleTooltip.hpp"
+#include "StarImageWidget.hpp"
+
+namespace Star {
+
+BaseScriptPane::BaseScriptPane(Json config) : Pane() {
+ auto& root = Root::singleton();
+ auto assets = root.assets();
+
+ if (config.type() == Json::Type::Object && config.contains("baseConfig")) {
+ auto baseConfig = assets->fetchJson(config.getString("baseConfig"));
+ m_config = jsonMerge(baseConfig, config);
+ } else {
+ m_config = assets->fetchJson(config);
+ }
+ m_reader.registerCallback("close", [this](Widget*) { dismiss(); });
+
+ for (auto const& callbackName : jsonToStringList(m_config.get("scriptWidgetCallbacks", JsonArray{}))) {
+ m_reader.registerCallback(callbackName, [this, callbackName](Widget* widget) {
+ m_script.invoke(callbackName, widget->name(), widget->data());
+ });
+ }
+
+ m_reader.construct(assets->fetchJson(m_config.get("gui")), this);
+
+ for (auto pair : m_config.getObject("canvasClickCallbacks", {}))
+ m_canvasClickCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
+ for (auto pair : m_config.getObject("canvasKeyCallbacks", {}))
+ m_canvasKeyCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
+
+ m_script.setScripts(jsonToStringList(m_config.get("scripts", JsonArray())));
+ m_script.setUpdateDelta(m_config.getUInt("scriptDelta", 1));
+
+ m_callbacksAdded = false;
+}
+
+void BaseScriptPane::show() {
+ Pane::show();
+}
+
+void BaseScriptPane::displayed() {
+ Pane::displayed();
+ if (!m_callbacksAdded) {
+ m_script.addCallbacks("pane", makePaneCallbacks());
+ m_script.addCallbacks("widget", LuaBindings::makeWidgetCallbacks(this, &m_reader));
+ m_script.addCallbacks("config", LuaBindings::makeConfigCallbacks( [this](String const& name, Json const& def) {
+ return m_config.query(name, def);
+ }));
+ m_callbacksAdded = true;
+ }
+ m_script.init();
+ m_script.invoke("displayed");
+}
+
+void BaseScriptPane::dismissed() {
+ Pane::dismissed();
+ m_script.invoke("dismissed");
+ m_script.uninit();
+}
+
+void BaseScriptPane::tick() {
+ Pane::tick();
+
+ for (auto p : m_canvasClickCallbacks) {
+ for (auto const& clickEvent : p.first->pullClickEvents())
+ m_script.invoke(p.second, jsonFromVec2I(clickEvent.position), (uint8_t)clickEvent.button, clickEvent.buttonDown);
+ }
+ for (auto p : m_canvasKeyCallbacks) {
+ for (auto const& keyEvent : p.first->pullKeyEvents())
+ m_script.invoke(p.second, (int)keyEvent.key, keyEvent.keyDown);
+ }
+
+ m_playingSounds.filter([](pair<String, AudioInstancePtr> const& p) {
+ return p.second->finished() == false;
+ });
+
+ m_script.update(m_script.updateDt());
+}
+
+bool BaseScriptPane::sendEvent(InputEvent const& event) {
+ // Intercept GuiClose before the canvas child so GuiClose always closes
+ // BaseScriptPanes without having to support it in the script.
+ if (context()->actions(event).contains(InterfaceAction::GuiClose)) {
+ dismiss();
+ return true;
+ }
+
+ return Pane::sendEvent(event);
+}
+
+PanePtr BaseScriptPane::createTooltip(Vec2I const& screenPosition) {
+ auto result = m_script.invoke<Json>("createTooltip", screenPosition);
+ if (result && !result.value().isNull()) {
+ if (result->type() == Json::Type::String) {
+ return SimpleTooltipBuilder::buildTooltip(result->toString());
+ } else {
+ PanePtr tooltip = make_shared<Pane>();
+ m_reader.construct(*result, tooltip.get());
+ return tooltip;
+ }
+ } else {
+ ItemPtr item;
+ if (auto child = getChildAt(screenPosition)) {
+ if (auto itemSlot = as<ItemSlotWidget>(child))
+ item = itemSlot->item();
+ if (auto itemGrid = as<ItemGridWidget>(child))
+ item = itemGrid->itemAt(screenPosition);
+ }
+ if (item)
+ return ItemTooltipBuilder::buildItemTooltip(item);
+ return {};
+ }
+}
+
+Maybe<String> BaseScriptPane::cursorOverride(Vec2I const& screenPosition) {
+ auto result = m_script.invoke<Maybe<String>>("cursorOverride", screenPosition);
+ if (result)
+ return *result;
+ else
+ return {};
+}
+
+LuaCallbacks BaseScriptPane::makePaneCallbacks() {
+ LuaCallbacks callbacks;
+
+ callbacks.registerCallback("dismiss", [this]() { dismiss(); });
+
+ callbacks.registerCallback("playSound",
+ [this](String const& audio, Maybe<int> loops, Maybe<float> volume) {
+ auto assets = Root::singleton().assets();
+ auto config = Root::singleton().configuration();
+ auto audioInstance = make_shared<AudioInstance>(*assets->audio(audio));
+ audioInstance->setVolume(volume.value(1.0));
+ audioInstance->setLoops(loops.value(0));
+ auto& guiContext = GuiContext::singleton();
+ guiContext.playAudio(audioInstance);
+ m_playingSounds.append({audio, move(audioInstance)});
+ });
+
+ callbacks.registerCallback("stopAllSounds", [this](String const& audio) {
+ m_playingSounds.filter([audio](pair<String, AudioInstancePtr> const& p) {
+ if (p.first == audio) {
+ p.second->stop();
+ return false;
+ }
+ return true;
+ });
+ });
+
+ callbacks.registerCallback("setTitle", [this](String const& title, String const& subTitle) {
+ setTitleString(title, subTitle);
+ });
+
+ callbacks.registerCallback("setTitleIcon", [this](String const& image) {
+ if (auto icon = as<ImageWidget>(titleIcon()))
+ icon->setImage(image);
+ });
+
+ callbacks.registerCallback("addWidget", [this](Json const& newWidgetConfig, Maybe<String> const& newWidgetName) {
+ String name = newWidgetName.value(strf("{}", Random::randu64()));
+ WidgetPtr newWidget = m_reader.makeSingle(name, newWidgetConfig);
+ this->addChild(name, newWidget);
+ });
+
+ callbacks.registerCallback("removeWidget", [this](String const& widgetName) {
+ this->removeChild(widgetName);
+ });
+
+ return callbacks;
+}
+
+}
diff --git a/source/frontend/StarBaseScriptPane.hpp b/source/frontend/StarBaseScriptPane.hpp
new file mode 100644
index 0000000..f88cd40
--- /dev/null
+++ b/source/frontend/StarBaseScriptPane.hpp
@@ -0,0 +1,45 @@
+#ifndef STAR_BASE_SCRIPT_PANE_HPP
+#define STAR_BASE_SCRIPT_PANE_HPP
+
+#include "StarPane.hpp"
+#include "StarLuaComponents.hpp"
+#include "StarGuiReader.hpp"
+
+namespace Star {
+
+STAR_CLASS(CanvasWidget);
+STAR_CLASS(BaseScriptPane);
+
+// A more 'raw' script pane that doesn't depend on a world being present.
+// Requires a derived class to provide a Lua root.
+class BaseScriptPane : public Pane {
+public:
+ BaseScriptPane(Json config);
+
+ virtual void show() override;
+ void displayed() override;
+ void dismissed() override;
+
+ void tick() override;
+
+ bool sendEvent(InputEvent const& event) override;
+
+ PanePtr createTooltip(Vec2I const& screenPosition) override;
+ Maybe<String> cursorOverride(Vec2I const& screenPosition) override;
+protected:
+ virtual LuaCallbacks makePaneCallbacks();
+ Json m_config;
+
+ GuiReader m_reader;
+
+ Map<CanvasWidgetPtr, String> m_canvasClickCallbacks;
+ Map<CanvasWidgetPtr, String> m_canvasKeyCallbacks;
+
+ bool m_callbacksAdded;
+ LuaUpdatableComponent<LuaBaseComponent> m_script;
+ List<pair<String, AudioInstancePtr>> m_playingSounds;
+};
+
+}
+
+#endif
diff --git a/source/frontend/StarBindingsMenu.cpp b/source/frontend/StarBindingsMenu.cpp
new file mode 100644
index 0000000..ec56e19
--- /dev/null
+++ b/source/frontend/StarBindingsMenu.cpp
@@ -0,0 +1,23 @@
+#include "StarBindingsMenu.hpp"
+#include "StarInputLuaBindings.hpp"
+
+namespace Star {
+
+BindingsMenu::BindingsMenu(Json const& config) : BaseScriptPane(config) {
+ m_script.setLuaRoot(make_shared<LuaRoot>());
+ m_script.addCallbacks("input", LuaBindings::makeInputCallbacks());
+}
+
+void BindingsMenu::show() {
+ BaseScriptPane::show();
+}
+
+void BindingsMenu::displayed() {
+ BaseScriptPane::displayed();
+}
+
+void BindingsMenu::dismissed() {
+ BaseScriptPane::dismissed();
+}
+
+} \ No newline at end of file
diff --git a/source/frontend/StarBindingsMenu.hpp b/source/frontend/StarBindingsMenu.hpp
new file mode 100644
index 0000000..53d08ed
--- /dev/null
+++ b/source/frontend/StarBindingsMenu.hpp
@@ -0,0 +1,24 @@
+#ifndef STAR_BINDINGS_MENU_HPP
+#define STAR_BINDINGS_MENU_HPP
+
+#include "StarBaseScriptPane.hpp"
+
+namespace Star {
+
+STAR_CLASS(BindingsMenu);
+
+class BindingsMenu : public BaseScriptPane {
+public:
+ BindingsMenu(Json const& config);
+
+ virtual void show() override;
+ void displayed() override;
+ void dismissed() override;
+
+private:
+
+};
+
+}
+
+#endif
diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp
index 2664d2c..86c7bb6 100644
--- a/source/frontend/StarMainInterface.cpp
+++ b/source/frontend/StarMainInterface.cpp
@@ -357,7 +357,7 @@ bool MainInterface::handleInputEvent(InputEvent const& event) {
} else if (auto mouseDown = event.ptr<MouseButtonDownEvent>()) {
if (mouseDown->mouseButton == MouseButton::Left || mouseDown->mouseButton == MouseButton::Right
|| mouseDown->mouseButton == MouseButton::Middle)
- overlayClick(mouseDown->mousePosition, mouseDown->mouseButton);
+ return overlayClick(mouseDown->mousePosition, mouseDown->mouseButton);
} else if (auto mouseUp = event.ptr<MouseButtonUpEvent>()) {
if (mouseUp->mouseButton == MouseButton::Left)
@@ -1428,7 +1428,7 @@ bool MainInterface::overButton(PolyI buttonPoly, Vec2I const& mousePos) const {
return buttonPoly.contains(mousePos);
}
-void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton) {
+bool MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton) {
PolyI mainBarPoly = m_config->mainBarPoly;
Vec2I barPos = mainBarPosition();
mainBarPoly.translate(barPos);
@@ -1436,17 +1436,17 @@ void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton)
if (overButton(m_config->mainBarInventoryButtonPoly, mousePos)) {
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Inventory);
- return;
+ return true;
}
if (overButton(m_config->mainBarCraftButtonPoly, mousePos)) {
togglePlainCraftingWindow();
- return;
+ return true;
}
if (overButton(m_config->mainBarCodexButtonPoly, mousePos)) {
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Codex);
- return;
+ return true;
}
if (overButton(m_config->mainBarDeployButtonPoly, mousePos)) {
@@ -1454,29 +1454,29 @@ void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton)
warpToOrbitedWorld(true);
else if (m_client->canBeamUp())
warpToOwnShip();
- return;
+ return true;
}
if (overButton(m_config->mainBarBeamButtonPoly, mousePos)) {
if (m_client->canBeamDown())
warpToOrbitedWorld();
- return;
+ return true;
}
if (overButton(m_config->mainBarQuestLogButtonPoly, mousePos)) {
m_paneManager.toggleRegisteredPane(MainInterfacePanes::QuestLog);
- return;
+ return true;
}
if (overButton(m_config->mainBarMmUpgradeButtonPoly, mousePos)) {
if (m_client->mainPlayer()->inventory()->essentialItem(EssentialItem::BeamAxe))
m_paneManager.toggleRegisteredPane(MainInterfacePanes::MmUpgrade);
- return;
+ return true;
}
if (overButton(m_config->mainBarCollectionsButtonPoly, mousePos)) {
m_paneManager.toggleRegisteredPane(MainInterfacePanes::Collections);
- return;
+ return true;
}
if (mouseButton == MouseButton::Left)
@@ -1485,6 +1485,8 @@ void MainInterface::overlayClick(Vec2I const& mousePos, MouseButton mouseButton)
m_client->mainPlayer()->beginAltFire();
if (mouseButton == MouseButton::Middle)
m_client->mainPlayer()->beginTrigger();
+
+ return false;
}
} \ No newline at end of file
diff --git a/source/frontend/StarMainInterface.hpp b/source/frontend/StarMainInterface.hpp
index aae2890..403b250 100644
--- a/source/frontend/StarMainInterface.hpp
+++ b/source/frontend/StarMainInterface.hpp
@@ -135,7 +135,7 @@ private:
bool overButton(PolyI buttonPoly, Vec2I const& mousePos) const;
- void overlayClick(Vec2I const& mousePos, MouseButton mouseButton);
+ bool overlayClick(Vec2I const& mousePos, MouseButton mouseButton);
GuiContext* m_guiContext;
MainInterfaceConfigConstPtr m_config;
diff --git a/source/frontend/StarOptionsMenu.cpp b/source/frontend/StarOptionsMenu.cpp
index 1805a06..cef2e7d 100644
--- a/source/frontend/StarOptionsMenu.cpp
+++ b/source/frontend/StarOptionsMenu.cpp
@@ -7,6 +7,7 @@
#include "StarLabelWidget.hpp"
#include "StarAssets.hpp"
#include "StarKeybindingsMenu.hpp"
+#include "StarBindingsMenu.hpp"
#include "StarGraphicsMenu.hpp"
namespace Star {
@@ -48,11 +49,16 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
reader.registerCallback("showKeybindings", [=](Widget*) {
displayControls();
});
+ reader.registerCallback("showModBindings", [=](Widget*) {
+ displayModBindings();
+ });
reader.registerCallback("showGraphics", [=](Widget*) {
displayGraphics();
});
- reader.construct(assets->json("/interface/optionsmenu/optionsmenu.config:paneLayout"), this);
+ Json config = assets->json("/interface/optionsmenu/optionsmenu.config");
+
+ reader.construct(config.get("paneLayout"), this);
m_sfxSlider = fetchChild<SliderBarWidget>("sfxSlider");
m_musicSlider = fetchChild<SliderBarWidget>("musicSlider");
@@ -68,6 +74,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt());
m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt());
+ m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config")));
m_keybindingsMenu = make_shared<KeybindingsMenu>();
m_graphicsMenu = make_shared<GraphicsMenu>();
@@ -162,6 +169,10 @@ void OptionsMenu::displayControls() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu);
}
+void OptionsMenu::displayModBindings() {
+ m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu);
+}
+
void OptionsMenu::displayGraphics() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_graphicsMenu);
}
diff --git a/source/frontend/StarOptionsMenu.hpp b/source/frontend/StarOptionsMenu.hpp
index 14158be..bd8c4ba 100644
--- a/source/frontend/StarOptionsMenu.hpp
+++ b/source/frontend/StarOptionsMenu.hpp
@@ -12,7 +12,7 @@ STAR_CLASS(ButtonWidget);
STAR_CLASS(LabelWidget);
STAR_CLASS(KeybindingsMenu);
STAR_CLASS(GraphicsMenu);
-
+STAR_CLASS(BindingsMenu);
STAR_CLASS(OptionsMenu);
class OptionsMenu : public Pane {
@@ -38,6 +38,7 @@ private:
void syncGuiToConf();
void displayControls();
+ void displayModBindings();
void displayGraphics();
SliderBarWidgetPtr m_sfxSlider;
@@ -58,6 +59,7 @@ private:
JsonObject m_origConfig;
JsonObject m_localChanges;
+ BindingsMenuPtr m_modBindingsMenu;
KeybindingsMenuPtr m_keybindingsMenu;
GraphicsMenuPtr m_graphicsMenu;
PaneManager* m_paneManager;
diff --git a/source/frontend/StarScriptPane.cpp b/source/frontend/StarScriptPane.cpp
index e2e8a33..38659f1 100644
--- a/source/frontend/StarScriptPane.cpp
+++ b/source/frontend/StarScriptPane.cpp
@@ -20,64 +20,34 @@
namespace Star {
-ScriptPane::ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId) {
+ScriptPane::ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId) : BaseScriptPane(config) {
auto& root = Root::singleton();
auto assets = root.assets();
m_client = move(client);
-
- if (config.type() == Json::Type::Object && config.contains("baseConfig")) {
- auto baseConfig = assets->fetchJson(config.getString("baseConfig"));
- m_config = jsonMerge(baseConfig, config);
- } else {
- m_config = assets->fetchJson(config);
- }
m_sourceEntityId = sourceEntityId;
- m_reader.registerCallback("close", [this](Widget*) { dismiss(); });
-
- for (auto const& callbackName : jsonToStringList(m_config.get("scriptWidgetCallbacks", JsonArray{}))) {
- m_reader.registerCallback(callbackName, [this, callbackName](Widget* widget) {
- m_script.invoke(callbackName, widget->name(), widget->data());
- });
- }
-
- m_reader.construct(assets->fetchJson(m_config.get("gui")), this);
-
- for (auto pair : m_config.getObject("canvasClickCallbacks", {}))
- m_canvasClickCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
- for (auto pair : m_config.getObject("canvasKeyCallbacks", {}))
- m_canvasKeyCallbacks.set(findChild<CanvasWidget>(pair.first), pair.second.toString());
-
- m_script.setScripts(jsonToStringList(m_config.get("scripts", JsonArray())));
- m_script.addCallbacks("pane", makePaneCallbacks());
- m_script.addCallbacks("widget", LuaBindings::makeWidgetCallbacks(this, &m_reader));
- m_script.addCallbacks("config", LuaBindings::makeConfigCallbacks( [this](String const& name, Json const& def) {
- return m_config.query(name, def);
- }));
m_script.addCallbacks("player", LuaBindings::makePlayerCallbacks(m_client->mainPlayer().get()));
m_script.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(m_client->mainPlayer()->statusController()));
m_script.addCallbacks("celestial", LuaBindings::makeCelestialCallbacks(m_client.get()));
- m_script.setUpdateDelta(m_config.getUInt("scriptDelta", 1));
}
void ScriptPane::displayed() {
- Pane::displayed();
auto world = m_client->worldClient();
- if (world && world->inWorld())
- m_script.init(world.get());
-
- m_script.invoke("displayed");
+ if (world && world->inWorld()) {
+ m_script.setLuaRoot(world->luaRoot());
+ m_script.addCallbacks("world", LuaBindings::makeWorldCallbacks(world.get()));
+ }
+ BaseScriptPane::displayed();
}
void ScriptPane::dismissed() {
- Pane::dismissed();
- m_script.invoke("dismissed");
- m_script.uninit();
+ BaseScriptPane::dismissed();
+ m_script.removeCallbacks("world");
}
void ScriptPane::tick() {
- Pane::tick();
+ BaseScriptPane::tick();
if (m_sourceEntityId != NullEntityId && !m_client->worldClient()->playerCanReachEntity(m_sourceEntityId))
dismiss();
@@ -98,17 +68,6 @@ void ScriptPane::tick() {
m_script.update(m_script.updateDt());
}
-bool ScriptPane::sendEvent(InputEvent const& event) {
- // Intercept GuiClose before the canvas child so GuiClose always closes
- // ScriptPanes without having to support it in the script.
- if (context()->actions(event).contains(InterfaceAction::GuiClose)) {
- dismiss();
- return true;
- }
-
- return Pane::sendEvent(event);
-}
-
PanePtr ScriptPane::createTooltip(Vec2I const& screenPosition) {
auto result = m_script.invoke<Json>("createTooltip", screenPosition);
if (result && !result.value().isNull()) {
@@ -133,61 +92,9 @@ PanePtr ScriptPane::createTooltip(Vec2I const& screenPosition) {
}
}
-Maybe<String> ScriptPane::cursorOverride(Vec2I const& screenPosition) {
- auto result = m_script.invoke<Maybe<String>>("cursorOverride", screenPosition);
- if (result)
- return *result;
- else
- return {};
-}
-
LuaCallbacks ScriptPane::makePaneCallbacks() {
- LuaCallbacks callbacks;
-
+ LuaCallbacks callbacks = BaseScriptPane::makePaneCallbacks();
callbacks.registerCallback("sourceEntity", [this]() { return m_sourceEntityId; });
- callbacks.registerCallback("dismiss", [this]() { dismiss(); });
-
- callbacks.registerCallback("playSound",
- [this](String const& audio, Maybe<int> loops, Maybe<float> volume) {
- auto assets = Root::singleton().assets();
- auto config = Root::singleton().configuration();
- auto audioInstance = make_shared<AudioInstance>(*assets->audio(audio));
- audioInstance->setVolume(volume.value(1.0));
- audioInstance->setLoops(loops.value(0));
- auto& guiContext = GuiContext::singleton();
- guiContext.playAudio(audioInstance);
- m_playingSounds.append({audio, move(audioInstance)});
- });
-
- callbacks.registerCallback("stopAllSounds", [this](String const& audio) {
- m_playingSounds.filter([audio](pair<String, AudioInstancePtr> const& p) {
- if (p.first == audio) {
- p.second->stop();
- return false;
- }
- return true;
- });
- });
-
- callbacks.registerCallback("setTitle", [this](String const& title, String const& subTitle) {
- setTitleString(title, subTitle);
- });
-
- callbacks.registerCallback("setTitleIcon", [this](String const& image) {
- if (auto icon = as<ImageWidget>(titleIcon()))
- icon->setImage(image);
- });
-
- callbacks.registerCallback("addWidget", [this](Json const& newWidgetConfig, Maybe<String> const& newWidgetName) {
- String name = newWidgetName.value(strf("{}", Random::randu64()));
- WidgetPtr newWidget = m_reader.makeSingle(name, newWidgetConfig);
- this->addChild(name, newWidget);
- });
-
- callbacks.registerCallback("removeWidget", [this](String const& widgetName) {
- this->removeChild(widgetName);
- });
-
return callbacks;
}
diff --git a/source/frontend/StarScriptPane.hpp b/source/frontend/StarScriptPane.hpp
index 1ba7ea0..d994b68 100644
--- a/source/frontend/StarScriptPane.hpp
+++ b/source/frontend/StarScriptPane.hpp
@@ -1,9 +1,7 @@
#ifndef STAR_SCRIPT_PANE_HPP
#define STAR_SCRIPT_PANE_HPP
-#include "StarPane.hpp"
-#include "StarLuaComponents.hpp"
-#include "StarGuiReader.hpp"
+#include "StarBaseScriptPane.hpp"
namespace Star {
@@ -11,7 +9,7 @@ STAR_CLASS(CanvasWidget);
STAR_CLASS(ScriptPane);
STAR_CLASS(UniverseClient);
-class ScriptPane : public Pane {
+class ScriptPane : public BaseScriptPane {
public:
ScriptPane(UniverseClientPtr client, Json config, EntityId sourceEntityId = NullEntityId);
@@ -20,28 +18,15 @@ public:
void tick() override;
- bool sendEvent(InputEvent const& event) override;
-
PanePtr createTooltip(Vec2I const& screenPosition) override;
- Maybe<String> cursorOverride(Vec2I const& screenPosition) override;
bool openWithInventory() const;
private:
- LuaCallbacks makePaneCallbacks();
+ LuaCallbacks makePaneCallbacks() override;
UniverseClientPtr m_client;
EntityId m_sourceEntityId;
- Json m_config;
-
- GuiReader m_reader;
-
- Map<CanvasWidgetPtr, String> m_canvasClickCallbacks;
- Map<CanvasWidgetPtr, String> m_canvasKeyCallbacks;
-
- LuaWorldComponent<LuaUpdatableComponent<LuaBaseComponent>> m_script;
-
- List<pair<String, AudioInstancePtr>> m_playingSounds;
};
}
diff --git a/source/game/CMakeLists.txt b/source/game/CMakeLists.txt
index 97fe8f2..120e9e8 100644
--- a/source/game/CMakeLists.txt
+++ b/source/game/CMakeLists.txt
@@ -234,6 +234,7 @@ SET (star_game_HEADERS
scripting/StarConfigLuaBindings.hpp
scripting/StarEntityLuaBindings.hpp
scripting/StarFireableItemLuaBindings.hpp
+ scripting/StarInputLuaBindings.hpp
scripting/StarItemLuaBindings.hpp
scripting/StarLuaActorMovementComponent.hpp
scripting/StarLuaAnimationComponent.hpp
@@ -472,6 +473,7 @@ SET (star_game_SOURCES
scripting/StarConfigLuaBindings.cpp
scripting/StarEntityLuaBindings.cpp
scripting/StarFireableItemLuaBindings.cpp
+ scripting/StarInputLuaBindings.cpp
scripting/StarItemLuaBindings.cpp
scripting/StarLuaComponents.cpp
scripting/StarLuaGameConverters.cpp
diff --git a/source/game/StarInput.cpp b/source/game/StarInput.cpp
index a8ac76f..7943ff5 100644
--- a/source/game/StarInput.cpp
+++ b/source/game/StarInput.cpp
@@ -5,6 +5,21 @@
namespace Star {
+const char* InputBindingConfigRoot = "modBindings";
+
+BiMap<Key, KeyMod> const KeysToMods{
+ {Key::LShift, KeyMod::LShift},
+ {Key::RShift, KeyMod::RShift},
+ {Key::LCtrl, KeyMod::LCtrl},
+ {Key::RCtrl, KeyMod::RCtrl},
+ {Key::LAlt, KeyMod::LAlt},
+ {Key::RAlt, KeyMod::RAlt},
+ {Key::LGui, KeyMod::LGui},
+ {Key::RGui, KeyMod::RGui},
+ {Key::AltGr, KeyMod::AltGr},
+ {Key::ScrollLock, KeyMod::Scroll}
+};
+
const KeyMod KeyModOptional = KeyMod::Num | KeyMod::Caps | KeyMod::Scroll;
inline bool compareKeyModLenient(KeyMod input, KeyMod test) {
@@ -33,7 +48,7 @@ Json keyModsToJson(KeyMod mod) {
if ((bool)(mod & KeyMod::AltGr )) array.emplace_back("AltGr" );
if ((bool)(mod & KeyMod::Scroll)) array.emplace_back("Scroll");
- return move(array);
+ return array.empty() ? Json() : move(array);
}
// Optional pointer argument to output calculated priority
@@ -115,6 +130,8 @@ Json Input::inputEventToJson(InputEvent const& input) {
Input::Bind Input::bindFromJson(Json const& json) {
Bind bind;
+ if (json.isNull())
+ return bind;
String type = json.getString("type");
Json value = json.get("value", {});
@@ -143,18 +160,22 @@ Input::Bind Input::bindFromJson(Json const& json) {
Json Input::bindToJson(Bind const& bind) {
if (auto keyBind = bind.ptr<KeyBind>()) {
- return JsonObject{
+ auto obj = JsonObject{
{"type", "key"},
- {"value", KeyNames.getRight(keyBind->key)},
- {"mods", keyModsToJson(keyBind->mods)}
- };
+ {"value", KeyNames.getRight(keyBind->key)}
+ }; // don't want empty mods to exist as null entry
+ if (auto mods = keyModsToJson(keyBind->mods))
+ obj.emplace("mods", move(mods));
+ return move(obj);
}
else if (auto mouseBind = bind.ptr<MouseBind>()) {
- return JsonObject{
+ auto obj = JsonObject{
{"type", "mouse"},
- {"value", MouseButtonNames.getRight(mouseBind->button)},
- {"mods", keyModsToJson(mouseBind->mods)}
+ {"value", MouseButtonNames.getRight(mouseBind->button)}
};
+ if (auto mods = keyModsToJson(mouseBind->mods))
+ obj.emplace("mods", move(mods));
+ return move(obj);
}
else if (auto controllerBind = bind.ptr<ControllerBind>()) {
return JsonObject{
@@ -168,7 +189,7 @@ Json Input::bindToJson(Bind const& bind) {
Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory const& parentCategory) {
category = &parentCategory;
- id = move(entryId);
+ id = entryId;
name = config.getString("name", id);
for (Json const& jBind : config.getArray("default", {})) {
@@ -179,13 +200,55 @@ Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory con
}
}
+void Input::BindEntry::updated() {
+ auto config = Root::singleton().configuration();
+
+ JsonArray array;
+ for (auto const& bind : customBinds)
+ array.emplace_back(bindToJson(bind));
+
+ if (!config->get(InputBindingConfigRoot).isType(Json::Type::Object))
+ config->set(InputBindingConfigRoot, JsonObject());
+
+ String path = strf("{}.{}", InputBindingConfigRoot, category->id);
+ if (!config->getPath(path).isType(Json::Type::Object)) {
+ config->setPath(path, JsonObject{
+ { id, move(array) }
+ });
+ }
+ else {
+ path = strf("{}.{}", path, id);
+ config->setPath(path, array);
+ }
+
+ Input::singleton().rebuildMappings();
+}
+
+Input::BindRef::BindRef(BindEntry& bindEntry, KeyBind& keyBind) {
+ entry = &bindEntry;
+ priority = keyBind.priority;
+ mods = keyBind.mods;
+}
+
+Input::BindRef::BindRef(BindEntry& bindEntry, MouseBind& mouseBind) {
+ entry = &bindEntry;
+ priority = mouseBind.priority;
+ mods = mouseBind.mods;
+}
+
+Input::BindRef::BindRef(BindEntry& bindEntry) {
+ entry = &bindEntry;
+ priority = 0;
+ mods = KeyMod::NoMod;
+}
+
Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig) {
- id = move(categoryId);
+ id = categoryId;
config = categoryConfig;
name = config.getString("name", id);
ConfigurationPtr userConfig = Root::singletonPtr()->configuration();
- auto userBindings = userConfig->get("modBindings");
+ auto userBindings = userConfig->get(InputBindingConfigRoot);
for (auto& pair : config.getObject("binds", {})) {
String const& bindId = pair.first;
@@ -193,7 +256,7 @@ Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig)
if (!bindConfig.isType(Json::Type::Object))
continue;
- BindEntry& entry = entries.insert(bindId, BindEntry(bindId, bindConfig, *this)).first->second;
+ BindEntry& entry = entries.try_emplace(bindId, bindId, bindConfig, *this).first->second;
if (userBindings.isType(Json::Type::Object)) {
for (auto& jBind : userBindings.queryArray(strf("{}.{}", id, bindId), {})) {
@@ -203,6 +266,9 @@ Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig)
{ Logger::error("Binds: Error loading user bind in {}.{}: {}", id, bindId, e.what()); }
}
}
+
+ if (entry.customBinds.empty())
+ entry.customBinds = entry.defaultBinds;
}
}
@@ -220,6 +286,34 @@ List<Input::BindEntry*> Input::filterBindEntries(List<Input::BindRef> const& bin
return result;
}
+Input::BindEntry* Input::bindEntryPtr(String const& categoryId, String const& bindId) {
+ if (auto category = m_bindCategories.ptr(categoryId)) {
+ if (auto entry = category->entries.ptr(bindId)) {
+ return entry;
+ }
+ }
+
+ return nullptr;
+}
+
+Input::BindEntry& Input::bindEntry(String const& categoryId, String const& bindId) {
+ if (auto ptr = bindEntryPtr(categoryId, bindId))
+ return *ptr;
+ else
+ throw InputException::format("Could not find bind entry {}.{}", categoryId, bindId);
+}
+
+Input::InputState* Input::bindStatePtr(String const& categoryId, String const& bindId) {
+ if (auto ptr = bindEntryPtr(categoryId, bindId))
+ return m_bindStates.ptr(ptr);
+ else
+ return nullptr;
+}
+
+Input::InputState* Input::inputStatePtr(InputVariant key) {
+ return m_inputStates.ptr(key);
+}
+
Input* Input::s_singleton;
Input* Input::singletonPtr() {
@@ -239,6 +333,8 @@ Input::Input() {
s_singleton = this;
+ m_pressedMods = KeyMod::NoMod;
+
reload();
m_rootReloadListener = make_shared<CallbackListener>([&]() {
@@ -256,10 +352,31 @@ List<std::pair<InputEvent, bool>> const& Input::inputEventsThisFrame() const {
return m_inputEvents;
}
+
+
void Input::reset() {
m_inputEvents.resize(0); // keeps reserved memory
- m_inputStates.clear();
- m_bindStates.clear();
+ {
+ auto it = m_inputStates.begin();
+ while (it != m_inputStates.end()) {
+ if (it->second.held) {
+ it->second.reset();
+ ++it;
+ }
+ else it = m_inputStates.erase(it);
+ }
+ }
+
+ {
+ auto it = m_bindStates.begin();
+ while (it != m_bindStates.end()) {
+ if (it->second.held) {
+ it->second.reset();
+ ++it;
+ }
+ else it = m_bindStates.erase(it);
+ }
+ }
}
void Input::update() {
@@ -268,17 +385,83 @@ void Input::update() {
bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
m_inputEvents.emplace_back(input, gameProcessed);
+ if (auto keyDown = input.ptr<KeyDownEvent>()) {
+ auto keyToMod = KeysToMods.rightPtr(keyDown->key);
+ if (keyToMod)
+ m_pressedMods |= *keyToMod;
+
+ if (!gameProcessed && !m_textInputActive) {
+ m_inputStates[keyDown->key].press();
+
+ if (auto binds = m_bindMappings.ptr(keyDown->key)) {
+ for (auto bind : filterBindEntries(*binds, keyDown->mods))
+ m_bindStates[bind].press();
+ }
+ }
+ } else if (auto keyUp = input.ptr<KeyUpEvent>()) {
+ auto keyToMod = KeysToMods.rightPtr(keyUp->key);
+ if (keyToMod)
+ m_pressedMods &= ~*keyToMod;
+
+ // We need to be able to release input even when gameProcessed is true, but only if it's already down.
+ if (auto state = m_inputStates.ptr(keyUp->key))
+ state->release();
+
+ if (auto binds = m_bindMappings.ptr(keyUp->key)) {
+ for (auto& bind : *binds) {
+ if (auto state = m_bindStates.ptr(bind.entry))
+ state->release();
+ }
+ }
+ } else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
+ if (!gameProcessed) {
+ m_inputStates[mouseDown->mouseButton].press();
+
+ if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) {
+ for (auto bind : filterBindEntries(*binds, m_pressedMods))
+ m_bindStates[bind].press();
+ }
+ }
+ } else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
+ if (auto state = m_inputStates.ptr(mouseUp->mouseButton))
+ state->release();
+ if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) {
+ for (auto& bind : *binds) {
+ if (auto state = m_bindStates.ptr(bind.entry))
+ state->release();
+ }
+ }
+ }
return false;
}
void Input::rebuildMappings() {
+ reset();
m_bindMappings.clear();
+
+ for (auto& category : m_bindCategories) {
+ for (auto& pair : category.second.entries) {
+ auto& entry = pair.second;
+ for (auto& bind : entry.customBinds) {
+ if (auto keyBind = bind.ptr<KeyBind>())
+ m_bindMappings[keyBind->key].emplace_back(entry, *keyBind);
+ if (auto mouseBind = bind.ptr<MouseBind>())
+ m_bindMappings[mouseBind->button].emplace_back(entry, *mouseBind);
+ if (auto controllerBind = bind.ptr<ControllerBind>())
+ m_bindMappings[controllerBind->button].emplace_back(entry);
+ }
+ }
+ }
+
+ for (auto& pair : m_bindMappings) {
+ pair.second.sort([](BindRef const& a, BindRef const& b)
+ { return a.priority > b.priority; });
+ }
}
-void Input::reload() {
- reset();
+void Input::reload() {;
m_bindCategories.clear();
auto assets = Root::singleton().assets();
@@ -290,7 +473,7 @@ void Input::reload() {
if (!categoryConfig.isType(Json::Type::Object))
continue;
- m_bindCategories.insert(categoryId, BindCategory(categoryId, categoryConfig));
+ m_bindCategories.try_emplace(categoryId, categoryId, categoryConfig);
}
}
@@ -303,4 +486,65 @@ void Input::reload() {
rebuildMappings();
}
+void Input::setTextInputActive(bool active) {
+ m_textInputActive = active;
+}
+
+Maybe<unsigned> Input::bindDown(String const& categoryId, String const& bindId) {
+ if (auto state = bindStatePtr(categoryId, bindId))
+ if (state->presses)
+ return state->presses;
+
+ return {};
+}
+
+bool Input::bindHeld(String const& categoryId, String const& bindId) {
+ if (auto state = bindStatePtr(categoryId, bindId))
+ return state->held;
+ else
+ return false;
+}
+
+Maybe<unsigned> Input::bindUp(String const& categoryId, String const& bindId) {
+ if (auto state = bindStatePtr(categoryId, bindId))
+ if (state->releases)
+ return state->releases;
+
+ return {};
+}
+
+void Input::resetBinds(String const& categoryId, String const& bindId) {
+ auto& entry = bindEntry(categoryId, bindId);
+
+ entry.customBinds = entry.defaultBinds;
+ entry.updated();
+}
+
+Json Input::getDefaultBinds(String const& categoryId, String const& bindId) {
+ JsonArray array;
+ for (Bind const& bind : bindEntry(categoryId, bindId).defaultBinds)
+ array.emplace_back(bindToJson(bind));
+
+ return move(array);
+}
+
+Json Input::getBinds(String const& categoryId, String const& bindId) {
+ JsonArray array;
+ for (Bind const& bind : bindEntry(categoryId, bindId).customBinds)
+ array.emplace_back(bindToJson(bind));
+
+ return move(array);
+}
+
+void Input::setBinds(String const& categoryId, String const& bindId, Json const& jBinds) {
+ auto& entry = bindEntry(categoryId, bindId);
+
+ List<Bind> binds;
+ for (Json const& jBind : jBinds.toArray())
+ binds.emplace_back(bindFromJson(jBind));
+
+ entry.customBinds = move(binds);
+ entry.updated();
+}
+
} \ No newline at end of file
diff --git a/source/game/StarInput.hpp b/source/game/StarInput.hpp
index 6dab146..1c55bc4 100644
--- a/source/game/StarInput.hpp
+++ b/source/game/StarInput.hpp
@@ -69,20 +69,17 @@ namespace Star {
List<Bind> customBinds;
BindEntry(String entryId, Json const& config, BindCategory const& parentCategory);
+ void updated();
};
struct BindRef {
KeyMod mods;
- uint8_t priority;
+ uint8_t priority = 0;
+ BindEntry* entry = nullptr; // Invalidated on reload, careful!
- // Invalidated on reload, careful!
- BindEntry* entry;
- };
-
- struct BindRefSorter {
- inline bool operator() (BindRef const& a, BindRef const& b) {
- return a.priority > b.priority;
- }
+ struct BindRef(BindEntry& bindEntry, KeyBind& keyBind);
+ struct BindRef(BindEntry& bindEntry, MouseBind& mouseBind);
+ struct BindRef(BindEntry& bindEntry);
};
struct BindCategory {
@@ -90,14 +87,17 @@ namespace Star {
String name;
Json config;
- StringMap<BindEntry> entries;
+ StableHashMap<String, BindEntry> entries;
BindCategory(String categoryId, Json const& categoryConfig);
};
struct InputState {
- size_t presses = 0;
- size_t releases = 0;
+ unsigned presses = 0;
+ unsigned releases = 0;
+ bool pressed = false;
+ bool held = false;
+ bool released = false;
// Calls the passed functions for each press and release.
template <typename PressFunction, typename ReleaseFunction>
@@ -108,9 +108,13 @@ namespace Star {
}
}
- constexpr bool held() {
- return presses < releases;
+ inline void reset() {
+ presses = releases = 0;
+ pressed = released = false;
}
+
+ inline void press() { pressed = ++presses; held = true; }
+ inline void release() { released = ++releases; held = false; }
};
// Get pointer to the singleton root instance, if it exists. Otherwise,
@@ -141,13 +145,30 @@ namespace Star {
// Loads input categories and their binds from Assets.
void reload();
+
+ void setTextInputActive(bool active);
+
+ Maybe<unsigned> bindDown(String const& categoryId, String const& bindId);
+ bool bindHeld(String const& categoryId, String const& bindId);
+ Maybe<unsigned> bindUp (String const& categoryId, String const& bindId);
+
+ void resetBinds(String const& categoryId, String const& bindId);
+ void setBinds(String const& categoryId, String const& bindId, Json const& binds);
+ Json getDefaultBinds(String const& categoryId, String const& bindId);
+ Json getBinds(String const& categoryId, String const& bindId);
private:
List<BindEntry*> filterBindEntries(List<BindRef> const& binds, KeyMod mods) const;
+ BindEntry* bindEntryPtr(String const& categoryId, String const& bindId);
+ BindEntry& bindEntry(String const& categoryId, String const& bindId);
+
+ InputState* bindStatePtr(String const& categoryId, String const& bindId);
+ InputState* inputStatePtr(InputVariant key);
+
static Input* s_singleton;
// Regenerated on reload.
- StringMap<BindCategory> m_bindCategories;
+ StableHashMap<String, BindCategory> m_bindCategories;
// Contains raw pointers to bind entries in categories, so also regenerated on reload.
HashMap<InputVariant, List<BindRef>> m_bindMappings;
@@ -160,7 +181,10 @@ namespace Star {
//Input states
HashMap<InputVariant, InputState> m_inputStates;
//Bind states
- HashMap<BindEntry*, InputState> m_bindStates;
+ HashMap<BindEntry const*, InputState> m_bindStates;
+
+ KeyMod m_pressedMods;
+ bool m_textInputActive;
};
}
diff --git a/source/game/scripting/StarInputLuaBindings.cpp b/source/game/scripting/StarInputLuaBindings.cpp
new file mode 100644
index 0000000..5195e31
--- /dev/null
+++ b/source/game/scripting/StarInputLuaBindings.cpp
@@ -0,0 +1,36 @@
+#include "StarInputLuaBindings.hpp"
+#include "StarLuaGameConverters.hpp"
+#include "StarInput.hpp"
+
+namespace Star {
+
+LuaCallbacks LuaBindings::makeInputCallbacks() {
+ LuaCallbacks callbacks;
+
+ auto input = Input::singletonPtr();
+
+ callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindDown", bind(mem_fn(&Input::bindDown), input, _1, _2));
+ callbacks.registerCallbackWithSignature<bool, String, String>("bindHeld", bind(mem_fn(&Input::bindHeld), input, _1, _2));
+ callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindUp", bind(mem_fn(&Input::bindUp), input, _1, _2));
+
+ callbacks.registerCallbackWithSignature<void, String, String>("resetBinds", bind(mem_fn(&Input::resetBinds), input, _1, _2));
+ callbacks.registerCallbackWithSignature<void, String, String, Json>("setBinds", bind(mem_fn(&Input::setBinds), input, _1, _2, _3));
+ callbacks.registerCallbackWithSignature<Json, String, String>("getDefaultBinds", bind(mem_fn(&Input::getDefaultBinds), input, _1, _2));
+ callbacks.registerCallbackWithSignature<Json, String, String>("getBinds", bind(mem_fn(&Input::getBinds), input, _1, _2));
+
+ callbacks.registerCallback("events", [input]() -> Json {
+ JsonArray result;
+
+ for (auto& pair : input->inputEventsThisFrame()) {
+ if (auto jEvent = Input::inputEventToJson(pair.first))
+ result.emplace_back(jEvent.set("processed", pair.second));
+ }
+
+ return move(result);
+ });
+
+ return callbacks;
+}
+
+
+}
diff --git a/source/game/scripting/StarInputLuaBindings.hpp b/source/game/scripting/StarInputLuaBindings.hpp
new file mode 100644
index 0000000..f0c1f4d
--- /dev/null
+++ b/source/game/scripting/StarInputLuaBindings.hpp
@@ -0,0 +1,17 @@
+#ifndef STAR_INPUT_LUA_BINDINGS_HPP
+#define STAR_INPUT_LUA_BINDINGS_HPP
+
+#include "StarGameTypes.hpp"
+#include "StarLua.hpp"
+
+namespace Star {
+
+STAR_CLASS(Input);
+
+namespace LuaBindings {
+ LuaCallbacks makeInputCallbacks();
+}
+
+}
+
+#endif
diff --git a/source/game/scripting/StarLuaComponents.hpp b/source/game/scripting/StarLuaComponents.hpp
index f370679..ac18106 100644
--- a/source/game/scripting/StarLuaComponents.hpp
+++ b/source/game/scripting/StarLuaComponents.hpp
@@ -6,6 +6,7 @@
#include "StarListener.hpp"
#include "StarWorld.hpp"
#include "StarWorldLuaBindings.hpp"
+#include "StarInputLuaBindings.hpp"
namespace Star {
@@ -282,6 +283,11 @@ void LuaWorldComponent<Base>::init(World* world) {
Base::setLuaRoot(world->luaRoot());
Base::addCallbacks("world", LuaBindings::makeWorldCallbacks(world));
+
+ if (world->isClient()) {
+ Base::addCallbacks("input", LuaBindings::makeInputCallbacks());
+ }
+
Base::init();
}
@@ -289,6 +295,8 @@ template <typename Base>
void LuaWorldComponent<Base>::uninit() {
Base::uninit();
Base::removeCallbacks("world");
+
+ Base::removeCallbacks("input");
}
template <typename Base>