aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHimbeer <himbeer@disroot.org>2024-12-05 22:12:18 +0100
committerHimbeer <himbeer@disroot.org>2024-12-05 22:14:35 +0100
commitfed47e8f13cf051c2f09b08112c513deb08b47a1 (patch)
tree2f7fcb3b756695f502cf8df50bfa409a6b51f0b3
parent3bff2563fae6af73013e964e7e08109cea6fef4f (diff)
Implement (inventory) formspec plugin API
Node-based formspecs are not supported because there is no node API yet. Proxy-level formspec functionality is invisible to upstream servers. The API can be used to display a formspec that is submitted to the upstream server. It is also able to intercept the submission of formspecs displayed by the upstream server. Closes #131.
-rw-r--r--client_conn.go2
-rw-r--r--plugin_formspec.go82
-rw-r--r--process.go4
3 files changed, 88 insertions, 0 deletions
diff --git a/client_conn.go b/client_conn.go
index 1a16d94..2f5ba04 100644
--- a/client_conn.go
+++ b/client_conn.go
@@ -66,6 +66,8 @@ type ClientConn struct {
modChsMu sync.RWMutex
cltInfo *mt.ToSrvCltInfo
+
+ FormspecPrepend string
}
// Name returns the player name of the ClientConn.
diff --git a/plugin_formspec.go b/plugin_formspec.go
new file mode 100644
index 0000000..7803108
--- /dev/null
+++ b/plugin_formspec.go
@@ -0,0 +1,82 @@
+package proxy
+
+import (
+ "strings"
+ "sync"
+
+ "github.com/HimbeerserverDE/mt"
+)
+
+var (
+ onPlayerReceiveFields map[string][]func(*ClientConn, []mt.Field)
+ onPlayerReceiveFieldsMu sync.Mutex
+ onPlayerReceiveFieldsOnce sync.Once
+)
+
+// ShowFormspec opens a formspec on the client.
+// The form name should follow the standard minetest naming convention,
+// i.e. "pluginname:formname".
+// If formname is empty, it reshows the inventory formspec
+// without updating it for future opens.
+// If formspec is empty, the formspec is closed.
+// If formname is empty at the same time, any formspec is closed
+// regardless of its name.
+func (cc *ClientConn) ShowFormspec(formname string, formspec string) {
+ cc.SendCmd(&mt.ToCltShowFormspec{
+ Formspec: cc.FormspecPrepend + formspec,
+ Formname: formname,
+ })
+}
+
+// FormspecEscape escapes characters that cannot be used in formspecs.
+func FormspecEscape(formspec string) string {
+ formspec = strings.ReplaceAll(formspec, "\\", "\\\\")
+ formspec = strings.ReplaceAll(formspec, "[", "\\[")
+ formspec = strings.ReplaceAll(formspec, "]", "\\]")
+ formspec = strings.ReplaceAll(formspec, ";", "\\;")
+ formspec = strings.ReplaceAll(formspec, ",", "\\,")
+ formspec = strings.ReplaceAll(formspec, "$", "\\$")
+ return formspec
+}
+
+// RegisterOnPlayerReceiveFields registers a callback that is called
+// when a client submits a specific formspec.
+// Events triggering this callback include a button being pressed,
+// Enter being pressed while a text field is focused,
+// a checkbox being toggled, an item being selected in a dropdown list,
+// selecting a new tab, changing the selection in a textlist or table,
+// selecting an entry in a textlist or table, a scrollbar being moved
+// or the form actively being closed by the player.
+func RegisterOnPlayerReceiveFields(formname string, handler func(*ClientConn, []mt.Field)) {
+ initOnPlayerReceiveFields()
+
+ onPlayerReceiveFieldsMu.Lock()
+ defer onPlayerReceiveFieldsMu.Unlock()
+
+ onPlayerReceiveFields[formname] = append(onPlayerReceiveFields[formname], handler)
+}
+
+func handleOnPlayerReceiveFields(cc *ClientConn, cmd *mt.ToSrvInvFields) bool {
+ onPlayerReceiveFieldsMu.Lock()
+ defer onPlayerReceiveFieldsMu.Unlock()
+
+ handlers, ok := onPlayerReceiveFields[cmd.Formname]
+ if !ok || len(handlers) == 0 {
+ return false
+ }
+
+ for _, handler := range handlers {
+ handler(cc, cmd.Fields)
+ }
+
+ return true
+}
+
+func initOnPlayerReceiveFields() {
+ onPlayerReceiveFieldsOnce.Do(func() {
+ onPlayerReceiveFieldsMu.Lock()
+ defer onPlayerReceiveFieldsMu.Unlock()
+
+ onPlayerReceiveFields = make(map[string][]func(*ClientConn, []mt.Field))
+ })
+}
diff --git a/process.go b/process.go
index 862f29d..a68238c 100644
--- a/process.go
+++ b/process.go
@@ -480,6 +480,10 @@ func (cc *ClientConn) process(pkt mt.Pkt) {
case *mt.ToSrvCltInfo:
// Store for any future hops (need to send it to the new server).
cc.cltInfo = cmd
+ case *mt.ToSrvInvFields:
+ if handleOnPlayerReceiveFields(cc, cmd) {
+ return
+ }
}
forward(pkt)