diff options
author | Himbeer <himbeer@disroot.org> | 2024-12-05 22:12:18 +0100 |
---|---|---|
committer | Himbeer <himbeer@disroot.org> | 2024-12-05 22:14:35 +0100 |
commit | fed47e8f13cf051c2f09b08112c513deb08b47a1 (patch) | |
tree | 2f7fcb3b756695f502cf8df50bfa409a6b51f0b3 | |
parent | 3bff2563fae6af73013e964e7e08109cea6fef4f (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.go | 2 | ||||
-rw-r--r-- | plugin_formspec.go | 82 | ||||
-rw-r--r-- | process.go | 4 |
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)) + }) +} @@ -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) |