From dc66afd919404ab4182e49e9a7b0c9a2fa8ba347 Mon Sep 17 00:00:00 2001 From: Argonui Date: Tue, 22 Nov 2022 12:57:00 -0600 Subject: [PATCH 1/6] Make XML unbundle and change filenames --- bundler/{bundler.go => luabundler.go} | 0 .../{bundler_test.go => luabundler_test.go} | 0 bundler/xmlbundler.go | 62 ++++++++++++++++ bundler/xmlbundler_test.go | 72 +++++++++++++++++++ 4 files changed, 134 insertions(+) rename bundler/{bundler.go => luabundler.go} (100%) rename bundler/{bundler_test.go => luabundler_test.go} (100%) create mode 100644 bundler/xmlbundler.go create mode 100644 bundler/xmlbundler_test.go diff --git a/bundler/bundler.go b/bundler/luabundler.go similarity index 100% rename from bundler/bundler.go rename to bundler/luabundler.go diff --git a/bundler/bundler_test.go b/bundler/luabundler_test.go similarity index 100% rename from bundler/bundler_test.go rename to bundler/luabundler_test.go diff --git a/bundler/xmlbundler.go b/bundler/xmlbundler.go new file mode 100644 index 0000000..4d0f0b4 --- /dev/null +++ b/bundler/xmlbundler.go @@ -0,0 +1,62 @@ +package bundler + +import ( + "fmt" + "regexp" + "strings" +) + +// BundleXML converts 's into full xml +func BundleXML() error { + return nil +} + +// UnbundleAllXML converts a bundled xml file to mapping of filenames to +// contents +func UnbundleAllXML(rawxml string) (map[string]string, error) { + type inc struct { + name string + start int + } + store := map[string]string{} + inctag := regexp.MustCompile(`(?m)^(.*?)`) + stack := []inc{} + xmlarray := strings.Split(rawxml, "\n") + + for ln := 0; ln < len(xmlarray); ln++ { + val := xmlarray[ln] + if !inctag.Match([]byte(val)) { + continue + } + submatches := inctag.FindSubmatch([]byte(val)) + key := string(submatches[2]) + if len(stack) > 0 && stack[0].name == key { + // found end of include, process it + indent := string(submatches[1]) + indentedvals := xmlarray[stack[0].start+1 : ln] + store[stack[0].name] = unindentAndJoin(indentedvals, indent) + + insertLine := fmt.Sprintf("%s", indent, stack[0].name) + // remove the include from xmlarray + tmp := append(xmlarray[:stack[0].start], insertLine) + xmlarray = append(tmp, xmlarray[ln+1:]...) + ln = stack[0].start + stack = stack[1:] + } else { + stack = append([]inc{inc{name: key, start: ln}}, stack...) + } + } + if len(stack) != 0 { + return nil, fmt.Errorf("Bundled xml left after finished reading file: %v", stack) + } + store[Rootname] = unindentAndJoin(xmlarray, "") + return store, nil +} + +func unindentAndJoin(raw []string, indent string) string { + ret := []string{} + for _, v := range raw { + ret = append(ret, strings.Replace(v, indent, "", 1)) + } + return strings.Join(ret, "\n") +} diff --git a/bundler/xmlbundler_test.go b/bundler/xmlbundler_test.go new file mode 100644 index 0000000..a24d145 --- /dev/null +++ b/bundler/xmlbundler_test.go @@ -0,0 +1,72 @@ +package bundler + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestUnbundleXML(t *testing.T) { + input := ` + + + + + + + + + + +` + + want := map[string]string{ + "__root": ` +`, + "Main": ` + + `, + "ui/Shop": ` + +`, + "ui/CameraControl": ` +`, + } + + got, err := UnbundleAllXML(input) + if err != nil { + t.Fatalf("UnbundleAllXML(): %v", err) + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } +} From 07fe4e3383b95e97ae2062fd1014a07e935fbc09 Mon Sep 17 00:00:00 2001 From: Argonui Date: Tue, 22 Nov 2022 13:08:18 -0600 Subject: [PATCH 2/6] Rename Lua* operations to Text* Nothing in LuaOps or the Lua* interfaces was lua specific. this will more clearly let it be reused on xml, luascriptstate, and any other text files without confusion --- bundler/luabundler.go | 2 +- file/{luaops.go => textops.go} | 32 +++++++++++------------- file/{luaops_test.go => textops_test.go} | 8 +++--- handler/luahandler.go | 6 ++--- main.go | 4 +-- mod/generate.go | 2 +- mod/reverse.go | 4 +-- objects/objects.go | 10 ++++---- 8 files changed, 32 insertions(+), 36 deletions(-) rename file/{luaops.go => textops.go} (67%) rename file/{luaops_test.go => textops_test.go} (96%) diff --git a/bundler/luabundler.go b/bundler/luabundler.go index 1d13422..61d396c 100644 --- a/bundler/luabundler.go +++ b/bundler/luabundler.go @@ -145,7 +145,7 @@ func Unbundle(rawlua string) (string, error) { } // Bundle grabs all dependencies and creates a single luascript -func Bundle(rawlua string, l file.LuaReader) (string, error) { +func Bundle(rawlua string, l file.TextReader) (string, error) { if IsBundled(rawlua) { return rawlua, nil } diff --git a/file/luaops.go b/file/textops.go similarity index 67% rename from file/luaops.go rename to file/textops.go index 1437247..16779df 100644 --- a/file/luaops.go +++ b/file/textops.go @@ -7,36 +7,32 @@ import ( "path" ) -const ( - expectedSuffix = ".ttslua" -) - -// LuaOps allows for arbitrary reads and writes of luascript -type LuaOps struct { +// TextOps allows for arbitrary reads and writes of text files +type TextOps struct { basepaths []string writeBasepath string readFileToBytes func(string) ([]byte, error) writeBytesToFile func(string, []byte) error } -// LuaReader serves to describe all ways to read luascripts -type LuaReader interface { +// TextReader serves to describe all ways to read luascripts +type TextReader interface { EncodeFromFile(string) (string, error) } -// LuaWriter serves to describe all ways to write luascripts -type LuaWriter interface { +// TextWriter serves to describe all ways to write luascripts +type TextWriter interface { EncodeToFile(script, file string) error } -// NewLuaOps initializes our object on a directory -func NewLuaOps(base string) *LuaOps { - return NewLuaOpsMulti([]string{base}, base) +// NewTextOps initializes our object on a directory +func NewTextOps(base string) *TextOps { + return NewTextOpsMulti([]string{base}, base) } -// NewLuaOpsMulti allows for luascript to be read from multiple directories -func NewLuaOpsMulti(readDirs []string, writeDir string) *LuaOps { - return &LuaOps{ +// NewTextOpsMulti allows for luascript to be read from multiple directories +func NewTextOpsMulti(readDirs []string, writeDir string) *TextOps { + return &TextOps{ basepaths: readDirs, writeBasepath: writeDir, readFileToBytes: func(s string) ([]byte, error) { @@ -69,7 +65,7 @@ func NewLuaOpsMulti(readDirs []string, writeDir string) *LuaOps { } // EncodeFromFile pulls a file from configs and encodes it as a string. -func (l *LuaOps) EncodeFromFile(filename string) (string, error) { +func (l *TextOps) EncodeFromFile(filename string) (string, error) { for _, base := range l.basepaths { p := path.Join(base, filename) b, err := l.readFileToBytes(p) @@ -83,7 +79,7 @@ func (l *LuaOps) EncodeFromFile(filename string) (string, error) { } // EncodeToFile takes a single string and decodes escape characters; writes it. -func (l *LuaOps) EncodeToFile(script, file string) error { +func (l *TextOps) EncodeToFile(script, file string) error { p := path.Join(l.writeBasepath, file) return l.writeBytesToFile(p, []byte(script)) } diff --git a/file/luaops_test.go b/file/textops_test.go similarity index 96% rename from file/luaops_test.go rename to file/textops_test.go index a1df6cc..f6e8de4 100644 --- a/file/luaops_test.go +++ b/file/textops_test.go @@ -29,7 +29,7 @@ func TestRead(t *testing.T) { ff := &fakeFiles{ fs: map[string][]byte{"foo/bar": []byte("bar = 2")}, } - l := &LuaOps{ + l := &TextOps{ basepaths: []string{"foo"}, readFileToBytes: ff.read, } @@ -52,7 +52,7 @@ func TestReadMulti(t *testing.T) { "foo2/bar": []byte("bar = 2"), }, } - l := &LuaOps{ + l := &TextOps{ basepaths: []string{"foo", "foo2"}, readFileToBytes: ff.read, } @@ -75,7 +75,7 @@ func TestReadErr(t *testing.T) { "foo2/bar": []byte("bar = 2"), }, } - l := &LuaOps{ + l := &TextOps{ basepaths: []string{"foo", "foo2"}, readFileToBytes: ff.read, } @@ -90,7 +90,7 @@ func TestWrite(t *testing.T) { ff := &fakeFiles{ fs: map[string][]byte{}, } - l := LuaOps{ + l := TextOps{ writeBasepath: "foo", writeBytesToFile: ff.write, } diff --git a/handler/luahandler.go b/handler/luahandler.go index dcd3524..d0e8362 100644 --- a/handler/luahandler.go +++ b/handler/luahandler.go @@ -10,9 +10,9 @@ import ( // and if it it long enough to be written to separate file at all has become // burdensome. abstract into struct type LuaHandler struct { - ObjWriter file.LuaWriter - SrcWriter file.LuaWriter - Reader file.LuaReader + ObjWriter file.TextWriter + SrcWriter file.TextWriter + Reader file.TextReader } // HandleAction describes at the end of handling, diff --git a/main.go b/main.go index ebc8f26..e44fae8 100644 --- a/main.go +++ b/main.go @@ -29,11 +29,11 @@ const ( func main() { flag.Parse() - lua := file.NewLuaOpsMulti( + lua := file.NewTextOpsMulti( []string{path.Join(*moddir, textSubdir), path.Join(*moddir, objectsSubdir)}, path.Join(*moddir, objectsSubdir), ) - luaSrc := file.NewLuaOps(path.Join(*moddir, textSubdir)) + luaSrc := file.NewTextOps(path.Join(*moddir, textSubdir)) ms := file.NewJSONOps(path.Join(*moddir, modsettingsDir)) objs := file.NewJSONOps(path.Join(*moddir, objectsSubdir)) objdir := file.NewDirOps(path.Join(*moddir, objectsSubdir)) diff --git a/mod/generate.go b/mod/generate.go index b7d59fb..a560812 100644 --- a/mod/generate.go +++ b/mod/generate.go @@ -32,7 +32,7 @@ type Mod struct { RootRead file.JSONReader RootWrite file.JSONWriter - Lua file.LuaReader + Lua file.TextReader Modsettings file.JSONReader Objs file.JSONReader Objdirs file.DirExplorer diff --git a/mod/reverse.go b/mod/reverse.go index a1631d0..96b1d7e 100644 --- a/mod/reverse.go +++ b/mod/reverse.go @@ -13,8 +13,8 @@ import ( // Reverser holds interfaces and configs for the reversing process type Reverser struct { ModSettingsWriter file.JSONWriter - LuaWriter file.LuaWriter - LuaSrcWriter file.LuaWriter + LuaWriter file.TextWriter + LuaSrcWriter file.TextWriter ObjWriter file.JSONWriter ObjDirCreeator file.DirCreator RootWrite file.JSONWriter diff --git a/objects/objects.go b/objects/objects.go index 2131b5d..7251230 100644 --- a/objects/objects.go +++ b/objects/objects.go @@ -107,7 +107,7 @@ func (o *objConfig) parseFromJSON(data map[string]interface{}) error { return nil } -func (o *objConfig) print(l file.LuaReader) (J, error) { +func (o *objConfig) print(l file.TextReader) (J, error) { var out J out = o.data @@ -263,7 +263,7 @@ type db struct { dir file.DirExplorer } -func (d *db) print(l file.LuaReader, order []string) (ObjArray, error) { +func (d *db) print(l file.TextReader, order []string) (ObjArray, error) { var oa ObjArray if len(order) != len(d.root) { return nil, fmt.Errorf("expected order (%v) and db.root (%v) to have same length", len(order), len(d.root)) @@ -291,7 +291,7 @@ func (d *db) print(l file.LuaReader, order []string) (ObjArray, error) { // --bar.json (guid=888) // --888/ // --baz.json (guid=999) << this is a child of bar.json -func ParseAllObjectStates(l file.LuaReader, j file.JSONReader, dir file.DirExplorer, order []string) ([]map[string]interface{}, error) { +func ParseAllObjectStates(l file.TextReader, j file.JSONReader, dir file.DirExplorer, order []string) ([]map[string]interface{}, error) { d := db{ j: j, dir: dir, @@ -329,8 +329,8 @@ func (d *db) parseFromFolder(relpath string, parent *objConfig) error { // Printer holds the info of which writer is which, because paratemter lists are rough type Printer struct { - Lua file.LuaWriter - LuaSrc file.LuaWriter + Lua file.TextWriter + LuaSrc file.TextWriter J file.JSONWriter Dir file.DirCreator } From 90a77cb2ca13a9124b4aef2e648fa4e20209bc1d Mon Sep 17 00:00:00 2001 From: Argonui Date: Tue, 22 Nov 2022 13:20:02 -0600 Subject: [PATCH 3/6] Bugfix add '/' to Include xml --- bundler/xmlbundler.go | 8 +++++--- bundler/xmlbundler_test.go | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bundler/xmlbundler.go b/bundler/xmlbundler.go index 4d0f0b4..021e07d 100644 --- a/bundler/xmlbundler.go +++ b/bundler/xmlbundler.go @@ -1,14 +1,16 @@ package bundler import ( + "ModCreator/file" "fmt" "regexp" "strings" ) // BundleXML converts 's into full xml -func BundleXML() error { - return nil +func BundleXML(rawxml string, xr file.TextReader) (string, error) { + + return "", nil } // UnbundleAllXML converts a bundled xml file to mapping of filenames to @@ -36,7 +38,7 @@ func UnbundleAllXML(rawxml string) (map[string]string, error) { indentedvals := xmlarray[stack[0].start+1 : ln] store[stack[0].name] = unindentAndJoin(indentedvals, indent) - insertLine := fmt.Sprintf("%s", indent, stack[0].name) + insertLine := fmt.Sprintf("%s", indent, stack[0].name) // remove the include from xmlarray tmp := append(xmlarray[:stack[0].start], insertLine) xmlarray = append(tmp, xmlarray[ln+1:]...) diff --git a/bundler/xmlbundler_test.go b/bundler/xmlbundler_test.go index a24d145..1a614f7 100644 --- a/bundler/xmlbundler_test.go +++ b/bundler/xmlbundler_test.go @@ -36,15 +36,15 @@ func TestUnbundleXML(t *testing.T) { ` want := map[string]string{ - "__root": ` + "__root": ` `, "Main": ` + + + + + + + +` + + input := map[string]string{ + "__root": ` +`, + "Main.xml": ` + + `, + "ui/Shop.xml": ` + +`, + "ui/CameraControl.xml": ` +`, + } + ff := tests.NewFF() + ff.Fs = input + + got, err := BundleXML(input[Rootname], ff) + if err != nil { + t.Fatalf("UnbundleAllXML(): %v", err) + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } +} From 287e2b1fcd58dd2871559b914f2633935cb5aa87 Mon Sep 17 00:00:00 2001 From: Argonui Date: Tue, 22 Nov 2022 14:54:36 -0600 Subject: [PATCH 5/6] Abstract LuaHandler to support xml XML bundline is equally complicated and identical from a code perspective. abstract the bundline functions and use the same handler. --- handler/{luahandler.go => bundlehandler.go} | 68 +++++++++++++++------ mod/generate.go | 7 ++- mod/reverse.go | 8 +-- objects/objects.go | 13 ++-- 4 files changed, 63 insertions(+), 33 deletions(-) rename handler/{luahandler.go => bundlehandler.go} (54%) diff --git a/handler/luahandler.go b/handler/bundlehandler.go similarity index 54% rename from handler/luahandler.go rename to handler/bundlehandler.go index d0e8362..e23291a 100644 --- a/handler/luahandler.go +++ b/handler/bundlehandler.go @@ -4,15 +4,42 @@ import ( "ModCreator/bundler" "ModCreator/file" "fmt" + "strings" ) -// LuaHandler is needed because handling if a script should be written to src or to objects folder, +// Handler is needed because handling if a script should be written to src or to objects folder, // and if it it long enough to be written to separate file at all has become -// burdensome. abstract into struct -type LuaHandler struct { - ObjWriter file.TextWriter - SrcWriter file.TextWriter - Reader file.TextReader +// burdensome. abstract into struct. Also for XML bundling +type Handler struct { + DefaultWriter file.TextWriter + SrcWriter file.TextWriter + Reader file.TextReader + + key, keypath, extension string + bundle func(string, file.TextReader) (string, error) + unbundle func(string) (map[string]string, error) +} + +// NewLuaHandler fills in relevant info for lua bundling +func NewLuaHandler() *Handler { + return &Handler{ + key: "LuaScript", + keypath: "LuaScript_path", + extension: ".ttslua", + bundle: bundler.Bundle, + unbundle: bundler.UnbundleAll, + } +} + +// NewXMLHandler fills in relevant info for lua bundling +func NewXMLHandler() *Handler { + return &Handler{ + key: "XmlUI", + keypath: "XmlUI_path", + extension: ".xml", + bundle: bundler.BundleXML, + unbundle: bundler.UnbundleAllXML, + } } // HandleAction describes at the end of handling, @@ -25,27 +52,27 @@ type HandleAction struct { // WhileReadingFromFile consolidates expected behavior of both objects and root // json while reading lua from file. -func (lh *LuaHandler) WhileReadingFromFile(rawj map[string]interface{}) (HandleAction, error) { +func (h *Handler) WhileReadingFromFile(rawj map[string]interface{}) (HandleAction, error) { rawscript := "" - if spraw, ok := rawj["LuaScript_path"]; ok { + if spraw, ok := rawj[h.keypath]; ok { sp, ok := spraw.(string) if !ok { - return HandleAction{}, fmt.Errorf("Expected LuaScript_path to be type string, was %T", spraw) + return HandleAction{}, fmt.Errorf("Expected %s to be type string, was %T", h.keypath, spraw) } - encoded, err := lh.Reader.EncodeFromFile(sp) + encoded, err := h.Reader.EncodeFromFile(sp) if err != nil { return HandleAction{}, fmt.Errorf("l.EncodeFromFile(%s) : %v", sp, err) } rawscript = encoded } - if sraw, ok := rawj["LuaScript"]; ok { + if sraw, ok := rawj[h.key]; ok { s, ok := sraw.(string) if !ok { - return HandleAction{}, fmt.Errorf("Expected LuaScript to be type string, was %T", sraw) + return HandleAction{}, fmt.Errorf("Expected %s to be type string, was %T", h.key, sraw) } rawscript = s } - bundled, err := bundler.Bundle(rawscript, lh.Reader) + bundled, err := h.bundle(rawscript, h.Reader) if err != nil { return HandleAction{}, fmt.Errorf("Bundle(%s): %v", rawscript, err) } @@ -56,7 +83,7 @@ func (lh *LuaHandler) WhileReadingFromFile(rawj map[string]interface{}) (HandleA } return HandleAction{ - Key: "LuaScript", + Key: h.key, Value: bundled, Noop: false, }, nil @@ -64,7 +91,7 @@ func (lh *LuaHandler) WhileReadingFromFile(rawj map[string]interface{}) (HandleA // WhileWritingToFile consolidates the logic and flow of conditionally writing // lua to a file, and which file to write to -func (lh *LuaHandler) WhileWritingToFile(rawj map[string]interface{}, possiblefname string) (HandleAction, error) { +func (h *Handler) WhileWritingToFile(rawj map[string]interface{}, possiblefname string) (HandleAction, error) { rawscript, ok := rawj["LuaScript"] if !ok { return HandleAction{Noop: true}, nil @@ -83,7 +110,7 @@ func (lh *LuaHandler) WhileWritingToFile(rawj map[string]interface{}, possiblefn rootscript, _ := allScripts[bundler.Rootname] returnAction := HandleAction{Noop: false} if len(rootscript) > 80 { - err = lh.ObjWriter.EncodeToFile(rootscript, possiblefname) + err = h.DefaultWriter.EncodeToFile(rootscript, possiblefname) if err != nil { return HandleAction{}, fmt.Errorf("EncodeToFile(, %s): %v", possiblefname, err) } @@ -96,11 +123,14 @@ func (lh *LuaHandler) WhileWritingToFile(rawj map[string]interface{}, possiblefn delete(allScripts, bundler.Rootname) for k, script := range allScripts { - fname := fmt.Sprintf("%s.ttslua", k) - if lh.SrcWriter == nil { + fname := k + if strings.HasSuffix(k, h.extension) { + fname = fmt.Sprintf("%s%s", k, h.extension) + } + if h.SrcWriter == nil { break } - err := lh.SrcWriter.EncodeToFile(script, fname) + err := h.SrcWriter.EncodeToFile(script, fname) if err != nil { return HandleAction{}, fmt.Errorf("SrcWriter.EncodeToFile(<>, %s): %v", fname, err) } diff --git a/mod/generate.go b/mod/generate.go index a560812..598aedf 100644 --- a/mod/generate.go +++ b/mod/generate.go @@ -72,9 +72,10 @@ func (m *Mod) generate(raw types.J) error { for _, objarraybased := range ExpectedObjArr { tryPut(&m.Data, objarraybased+ext, objarraybased, objArray) } - lh := &handler.LuaHandler{ - Reader: m.Lua, - } + + lh := handler.NewLuaHandler() + lh.Reader = m.Lua + act, err := lh.WhileReadingFromFile(m.Data) if err != nil { return fmt.Errorf("WhileReadingFromFile(): %v", err) diff --git a/mod/reverse.go b/mod/reverse.go index 96b1d7e..d906b18 100644 --- a/mod/reverse.go +++ b/mod/reverse.go @@ -55,10 +55,10 @@ func (r *Reverser) Write(raw map[string]interface{}) error { delete(raw, strKey) } - lh := handler.LuaHandler{ - ObjWriter: r.LuaWriter, - SrcWriter: r.LuaSrcWriter, - } + lh := handler.NewLuaHandler() + lh.DefaultWriter = r.LuaWriter + lh.SrcWriter = r.LuaSrcWriter + act, err := lh.WhileWritingToFile(raw, "LuaScript.ttslua") if err != nil { return fmt.Errorf("WhileWritingToFile(<>, root luascript): %v", err) diff --git a/objects/objects.go b/objects/objects.go index 7251230..3cd32d0 100644 --- a/objects/objects.go +++ b/objects/objects.go @@ -112,9 +112,8 @@ func (o *objConfig) print(l file.TextReader) (J, error) { var out J out = o.data - lh := &handler.LuaHandler{ - Reader: l, - } + lh := handler.NewLuaHandler() + lh.Reader = l act, err := lh.WhileReadingFromFile(o.data) if err != nil { return nil, fmt.Errorf("WhileReadingFromFile(): %v", err) @@ -158,10 +157,10 @@ func (o *objConfig) printToFile(filepath string, p *Printer) error { var out J out = o.data - lh := handler.LuaHandler{ - ObjWriter: p.Lua, - SrcWriter: p.LuaSrc, - } + lh := handler.NewLuaHandler() + lh.DefaultWriter = p.Lua + lh.SrcWriter = p.LuaSrc + maybeNeededFname := path.Join(filepath, o.getAGoodFileName()+".ttslua") act, err := lh.WhileWritingToFile(o.data, maybeNeededFname) if err != nil { From 2dd822e99b02e00f11837dee57e334c72534fe6e Mon Sep 17 00:00:00 2001 From: Argonui Date: Tue, 22 Nov 2022 17:47:13 -0600 Subject: [PATCH 6/6] Allow bundling & unbundling of XML this will be done into a new `xml/` directory --- bundler/xmlbundler.go | 4 + bundler/xmlbundler_test.go | 2 +- handler/bundlehandler.go | 14 +- main.go | 17 +- mod/generate.go | 16 +- mod/reverse.go | 19 +- objects/functional_test.go | 2 +- objects/objects.go | 39 +- objects/objects_test.go | 4 +- tests/e2e_test.go | 46 +- tests/testdata/e2e/GHE_Dev_no_objects.json | 2 +- .../e2e/GHE_Dev_no_objects_no_lua.json | 1344 +++++++++++++++++ 12 files changed, 1470 insertions(+), 39 deletions(-) create mode 100644 tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json diff --git a/bundler/xmlbundler.go b/bundler/xmlbundler.go index 54a3987..d10feac 100644 --- a/bundler/xmlbundler.go +++ b/bundler/xmlbundler.go @@ -49,6 +49,10 @@ func indentString(s string, indent string) string { lines := strings.Split(s, "\n") final := []string{} for _, v := range lines { + if v == "" { + final = append(final, "") + continue + } final = append(final, fmt.Sprintf("%s%s", indent, v)) } diff --git a/bundler/xmlbundler_test.go b/bundler/xmlbundler_test.go index 05e8956..0f7ed83 100644 --- a/bundler/xmlbundler_test.go +++ b/bundler/xmlbundler_test.go @@ -83,7 +83,7 @@ func TestBundleXML(t *testing.T) { - + , %s): %v", possiblefname, err) } - returnAction.Key = "LuaScript_path" + returnAction.Key = h.keypath returnAction.Value = possiblefname } else { - returnAction.Key = "LuaScript" + returnAction.Key = h.key returnAction.Value = rootscript } delete(allScripts, bundler.Rootname) for k, script := range allScripts { fname := k - if strings.HasSuffix(k, h.extension) { + if !strings.HasSuffix(k, h.extension) { fname = fmt.Sprintf("%s%s", k, h.extension) } if h.SrcWriter == nil { diff --git a/main.go b/main.go index e44fae8..2672b37 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,8 @@ var ( ) const ( - textSubdir = "src" + luasrcSubdir = "src" + xmlsrcSubdir = "xml" modsettingsDir = "modsettings" objectsSubdir = "objects" ) @@ -30,10 +31,15 @@ func main() { flag.Parse() lua := file.NewTextOpsMulti( - []string{path.Join(*moddir, textSubdir), path.Join(*moddir, objectsSubdir)}, + []string{path.Join(*moddir, luasrcSubdir), path.Join(*moddir, objectsSubdir)}, path.Join(*moddir, objectsSubdir), ) - luaSrc := file.NewTextOps(path.Join(*moddir, textSubdir)) + xml := file.NewTextOpsMulti( + []string{path.Join(*moddir, xmlsrcSubdir), path.Join(*moddir, objectsSubdir)}, + path.Join(*moddir, objectsSubdir), + ) + xmlSrc := file.NewTextOps(path.Join(*moddir, xmlsrcSubdir)) + luaSrc := file.NewTextOps(path.Join(*moddir, luasrcSubdir)) ms := file.NewJSONOps(path.Join(*moddir, modsettingsDir)) objs := file.NewJSONOps(path.Join(*moddir, objectsSubdir)) objdir := file.NewDirOps(path.Join(*moddir, objectsSubdir)) @@ -47,12 +53,14 @@ func main() { r := mod.Reverser{ ModSettingsWriter: ms, LuaWriter: lua, + XMLWriter: xml, ObjWriter: objs, ObjDirCreeator: objdir, RootWrite: rootops, } if *writeToSrc { r.LuaSrcWriter = luaSrc + r.XMLSrcWriter = xmlSrc } err = r.Write(raw) if err != nil { @@ -69,6 +77,7 @@ func main() { m := &mod.Mod{ Lua: lua, + XML: xml, Modsettings: ms, Objs: objs, Objdirs: objdir, @@ -88,7 +97,7 @@ func main() { // prepForReverse creates the expected subdirectories in config path func prepForReverse(cPath, modfile string) (types.J, error) { - subDirs := []string{textSubdir, modsettingsDir, objectsSubdir} + subDirs := []string{luasrcSubdir, modsettingsDir, objectsSubdir, xmlsrcSubdir} for _, s := range subDirs { p := path.Join(cPath, s) diff --git a/mod/generate.go b/mod/generate.go index 598aedf..047a35c 100644 --- a/mod/generate.go +++ b/mod/generate.go @@ -33,6 +33,7 @@ type Mod struct { RootRead file.JSONReader RootWrite file.JSONWriter Lua file.TextReader + XML file.TextReader Modsettings file.JSONReader Objs file.JSONReader Objdirs file.DirExplorer @@ -86,6 +87,19 @@ func (m *Mod) generate(raw types.J) error { m.Data[act.Key] = act.Value } + xh := handler.NewXMLHandler() + xh.Reader = m.XML + + act, err = xh.WhileReadingFromFile(m.Data) + if err != nil { + return fmt.Errorf("WhileReadingFromFile(): %v", err) + } + if !act.Noop { + delete(m.Data, "XmlUI") + delete(m.Data, "XmlUI_path") + m.Data[act.Key] = act.Value + } + objOrder := []string{} files, _, _ := m.Objdirs.ListFilesAndFolders("") hasObjects := len(files) > 0 @@ -95,7 +109,7 @@ func (m *Mod) generate(raw types.J) error { return fmt.Errorf("Has Objects, but can't discern their order: %v", err) } - allObjs, err := objects.ParseAllObjectStates(m.Lua, m.Objs, m.Objdirs, objOrder) + allObjs, err := objects.ParseAllObjectStates(m.Lua, m.XML, m.Objs, m.Objdirs, objOrder) if err != nil { return fmt.Errorf("objects.ParseAllObjectStates(%s) : %v", "", err) } diff --git a/mod/reverse.go b/mod/reverse.go index d906b18..755bd0f 100644 --- a/mod/reverse.go +++ b/mod/reverse.go @@ -15,6 +15,8 @@ type Reverser struct { ModSettingsWriter file.JSONWriter LuaWriter file.TextWriter LuaSrcWriter file.TextWriter + XMLWriter file.TextWriter + XMLSrcWriter file.TextWriter ObjWriter file.JSONWriter ObjDirCreeator file.DirCreator RootWrite file.JSONWriter @@ -34,7 +36,7 @@ func (r *Reverser) Write(raw map[string]interface{}) error { if !ok { return fmt.Errorf("expected string value in key %s, got %v", strKey, rawVal) } - if strKey == "LuaScript" { + if strKey == "LuaScript" || strKey == "XmlUI" { // let the LuaHAndler handle the complicated case continue } @@ -68,6 +70,19 @@ func (r *Reverser) Write(raw map[string]interface{}) error { raw[act.Key] = act.Value } + xh := handler.NewXMLHandler() + xh.DefaultWriter = r.XMLWriter + xh.SrcWriter = r.XMLSrcWriter + + act, err = xh.WhileWritingToFile(raw, "Root.xml") + if err != nil { + return fmt.Errorf("WhileWritingToFile(<>, root luascript): %v", err) + } + if !act.Noop { + delete(raw, "XmlUI") + raw[act.Key] = act.Value + } + for _, objKey := range ExpectedObj { rawVal, ok := raw[objKey] if ok { @@ -129,6 +144,8 @@ func (r *Reverser) Write(raw map[string]interface{}) error { printer := &objects.Printer{ Lua: r.LuaWriter, LuaSrc: r.LuaSrcWriter, + XML: r.XMLWriter, + XMLSrc: r.XMLSrcWriter, J: r.ObjWriter, Dir: r.ObjDirCreeator, } diff --git a/objects/functional_test.go b/objects/functional_test.go index 4654389..5edd676 100644 --- a/objects/functional_test.go +++ b/objects/functional_test.go @@ -87,7 +87,7 @@ func TestFileToJson(t *testing.T) { if err != nil { t.Fatalf("Expected no error parsing from %s: got %v", "foo/cool.123456.json", err) } - got, err := o.print(ff) + got, err := o.print(ff, ff) if err != nil { t.Errorf("Expected no error printing %s: got %v", o.guid, err) } diff --git a/objects/objects.go b/objects/objects.go index 3cd32d0..cbe5fcb 100644 --- a/objects/objects.go +++ b/objects/objects.go @@ -107,7 +107,7 @@ func (o *objConfig) parseFromJSON(data map[string]interface{}) error { return nil } -func (o *objConfig) print(l file.TextReader) (J, error) { +func (o *objConfig) print(l, x file.TextReader) (J, error) { var out J out = o.data @@ -123,6 +123,17 @@ func (o *objConfig) print(l file.TextReader) (J, error) { delete(out, "LuaScript_path") out[act.Key] = act.Value } + xh := handler.NewXMLHandler() + xh.Reader = x + act, err = xh.WhileReadingFromFile(o.data) + if err != nil { + return nil, fmt.Errorf("WhileReadingFromFile(): %v", err) + } + if !act.Noop { + delete(out, "XmlUI") + delete(out, "XmlUI_path") + out[act.Key] = act.Value + } if o.gmnotesPath != "" { encoded, err := l.EncodeFromFile(o.gmnotesPath) @@ -141,7 +152,7 @@ func (o *objConfig) print(l file.TextReader) (J, error) { subs := []J{} for _, sub := range o.subObj { - printed, err := sub.print(l) + printed, err := sub.print(l, x) if err != nil { return nil, err } @@ -172,6 +183,20 @@ func (o *objConfig) printToFile(filepath string, p *Printer) error { out[act.Key] = act.Value } + xh := handler.NewXMLHandler() + xh.DefaultWriter = p.XML + xh.SrcWriter = p.XMLSrc + maybeNeededFname = path.Join(filepath, o.getAGoodFileName()+".xml") + act, err = lh.WhileWritingToFile(o.data, maybeNeededFname) + if err != nil { + return fmt.Errorf("WhileWritingToFile(<>, %s): %v", maybeNeededFname, err) + } + if !act.Noop { + delete(out, "LuaScript") + delete(out, "LuaScript_path") + out[act.Key] = act.Value + } + if rawscript, ok := o.data["LuaScriptState"]; ok { if script, ok := rawscript.(string); ok { if len(script) > 80 { @@ -262,7 +287,7 @@ type db struct { dir file.DirExplorer } -func (d *db) print(l file.TextReader, order []string) (ObjArray, error) { +func (d *db) print(l file.TextReader, x file.TextReader, order []string) (ObjArray, error) { var oa ObjArray if len(order) != len(d.root) { return nil, fmt.Errorf("expected order (%v) and db.root (%v) to have same length", len(order), len(d.root)) @@ -271,7 +296,7 @@ func (d *db) print(l file.TextReader, order []string) (ObjArray, error) { if _, ok := d.root[nextGUID]; !ok { return nil, fmt.Errorf("order expected %s, not found in db", nextGUID) } - printed, err := d.root[nextGUID].print(l) + printed, err := d.root[nextGUID].print(l, x) if err != nil { return ObjArray{}, fmt.Errorf("obj (%s) did not print : %v", nextGUID, err) } @@ -290,7 +315,7 @@ func (d *db) print(l file.TextReader, order []string) (ObjArray, error) { // --bar.json (guid=888) // --888/ // --baz.json (guid=999) << this is a child of bar.json -func ParseAllObjectStates(l file.TextReader, j file.JSONReader, dir file.DirExplorer, order []string) ([]map[string]interface{}, error) { +func ParseAllObjectStates(l file.TextReader, x file.TextReader, j file.JSONReader, dir file.DirExplorer, order []string) ([]map[string]interface{}, error) { d := db{ j: j, dir: dir, @@ -301,7 +326,7 @@ func ParseAllObjectStates(l file.TextReader, j file.JSONReader, dir file.DirExpl if err != nil { return []map[string]interface{}{}, fmt.Errorf("parseFolder(%s): %v", "", err) } - return d.print(l, order) + return d.print(l, x, order) } func (d *db) parseFromFolder(relpath string, parent *objConfig) error { @@ -330,6 +355,8 @@ func (d *db) parseFromFolder(relpath string, parent *objConfig) error { type Printer struct { Lua file.TextWriter LuaSrc file.TextWriter + XML file.TextWriter + XMLSrc file.TextWriter J file.JSONWriter Dir file.DirCreator } diff --git a/objects/objects_test.go b/objects/objects_test.go index 163dac1..d413b68 100644 --- a/objects/objects_test.go +++ b/objects/objects_test.go @@ -103,7 +103,7 @@ func TestObjPrinting(t *testing.T) { "core/AgendaDeck.ttslua": "var a = 42;", }, } - got, err := tc.o.print(l) + got, err := tc.o.print(l, l) if err != nil { t.Errorf("printing %v, got %v", tc.o, err) } @@ -477,7 +477,7 @@ func TestDBPrint(t *testing.T) { db := db{ root: tc.root, } - got, err := db.print(ff, tc.orderInput) + got, err := db.print(ff, ff, tc.orderInput) if err != nil { t.Fatalf("got unexpected err %v", err) } diff --git a/tests/e2e_test.go b/tests/e2e_test.go index e0f05db..7657f0b 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -52,6 +52,8 @@ func TestAllReverseThenBuild(t *testing.T) { r := mod.Reverser{ ModSettingsWriter: modsettings, LuaWriter: objsAndLua, + XMLWriter: objsAndLua, + XMLSrcWriter: objsAndLua, LuaSrcWriter: objsAndLua, ObjWriter: objsAndLua, ObjDirCreeator: objsAndLua, @@ -64,11 +66,15 @@ func TestAllReverseThenBuild(t *testing.T) { objsAndLua.DebugFileNames(t.Logf) finalOutput.DebugFileNames(t.Logf) - reversedConfig, _ := finalOutput.ReadObj("config.json") + reversedConfig, err := finalOutput.ReadObj("config.json") + if err != nil { + t.Fatalf("Couldn't find root config: %v", err) + } t.Logf("%v\n", reversedConfig) m := &mod.Mod{ Lua: objsAndLua, + XML: objsAndLua, Modsettings: modsettings, Objs: objsAndLua, Objdirs: objsAndLua, @@ -97,21 +103,31 @@ func TestAllReverseThenBuild(t *testing.T) { return false } - // bundler.AnalyzeBundle(want["LuaScript"].(string), t.Logf) - // bundler.AnalyzeBundle(got["LuaScript"].(string), t.Logf) - wantBundles, err := bundler.UnbundleAll(want["LuaScript"].(string)) - if err != nil { - t.Fatalf("unbundle want : %v", err) - } - gotBundles, err := bundler.UnbundleAll(got["LuaScript"].(string)) - if err != nil { - t.Fatalf("unbundle got : %v", err) - } - if diff := cmp.Diff(mapOfKeys(wantBundles), mapOfKeys(gotBundles)); diff != "" { - t.Errorf("want != got:\n%v\n", diff) + wls, wok := want["LuaScript"] + gls, gok := got["LuaScript"] + if wok && gok { + wlss, ok := wls.(string) + if !ok { + t.Fatalf("non string found in luascript, found %T", wls) + } + glss, ok := gls.(string) + if !ok { + t.Fatalf("non string found in luascript, found %T", gls) + } + wantBundles, err := bundler.UnbundleAll(wlss) + if err != nil { + t.Fatalf("unbundle want : %v", err) + } + gotBundles, err := bundler.UnbundleAll(glss) + if err != nil { + t.Fatalf("unbundle got : %v", err) + } + if diff := cmp.Diff(mapOfKeys(wantBundles), mapOfKeys(gotBundles)); diff != "" { + t.Errorf("want != got:\n%v\n", diff) + } + delete(want, "LuaScript") + delete(got, "LuaScript") } - delete(want, "LuaScript") - delete(got, "LuaScript") if diff := cmp.Diff(want, got, cmpopts.IgnoreMapEntries(ignoreUnpredictable)); diff != "" { t.Errorf("want != got:\n%v\n", diff) diff --git a/tests/testdata/e2e/GHE_Dev_no_objects.json b/tests/testdata/e2e/GHE_Dev_no_objects.json index a279fe1..6f7f827 100644 --- a/tests/testdata/e2e/GHE_Dev_no_objects.json +++ b/tests/testdata/e2e/GHE_Dev_no_objects.json @@ -618,7 +618,7 @@ ], "LuaScript": "-- Bundled by luabundle {\"version\":\"1.6.0\"}\nlocal __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)\n\tlocal loadingPlaceholder = {[{}] = true}\n\n\tlocal register\n\tlocal modules = {}\n\n\tlocal require\n\tlocal loaded = {}\n\n\tregister = function(name, body)\n\t\tif not modules[name] then\n\t\t\tmodules[name] = body\n\t\tend\n\tend\n\n\trequire = function(name)\n\t\tlocal loadedModule = loaded[name]\n\n\t\tif loadedModule then\n\t\t\tif loadedModule == loadingPlaceholder then\n\t\t\t\treturn nil\n\t\t\tend\n\t\telse\n\t\t\tif not modules[name] then\n\t\t\t\tif not superRequire then\n\t\t\t\t\tlocal identifier = type(name) == 'string' and '\\\"' .. name .. '\\\"' or tostring(name)\n\t\t\t\t\terror('Tried to require ' .. identifier .. ', but no such module has been registered')\n\t\t\t\telse\n\t\t\t\t\treturn superRequire(name)\n\t\t\t\tend\n\t\t\tend\n\n\t\t\tloaded[name] = loadingPlaceholder\n\t\t\tloadedModule = modules[name](require, loaded, register, modules)\n\t\t\tloaded[name] = loadedModule\n\t\tend\n\n\t\treturn loadedModule\n\tend\n\n\treturn require, loaded, register, modules\nend)(require)\n__bundle_register(\"__root\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"Main\")\r\n\nend)\n__bundle_register(\"Main\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--- Configs\nrequire(\"registry.Config\")\nrequire(\"factory.Config\")\n\n--- make sure options load pretty early\nrequire(\"Options\")\nrequire(\"ContentHandler\")\n\n--- Load the Game data\nrequire(\"Game.Gloomhaven.Config\")\nrequire(\"Game.ForgottenCircles.Config\")\nrequire(\"Game.Frosthaven.Config\")\n\n--- Handlers for different mechanics\nlocal GameController = require(\"GameController\")\nrequire(\"AutoSetup\")\nrequire(\"Scripts\")\nrequire(\"campaign-manager.CampaignManager\")\nrequire(\"Scenario\")\n\n--- UI elements\nrequire(\"ui.Config\")\nrequire(\"HotKeys\")\nrequire(\"UpdateChecker\")\nlocal InitiativeTracker = require(\"InitiativeTracker\")\n\n--- Tools\nrequire(\"utils.SectionTool\")\nrequire(\"CommandLine\")\n\n--- required for the code that still happens to be here\nlocal SaveManager = require(\"lib.SaveManager\")\nlocal Object = require(\"lib.Object\")\nlocal Event = require(\"Event\")\nlocal Game = require(\"Game\")\nlocal OptionsApi = require(\"api.OptionsApi\")\nlocal ScenarioApi = require(\"api.ScenarioApi\")\nlocal DialCounter = require(\"Component.internal.DialCounter\")\nlocal R = require(\"api.Resource\")\nlocal Party = require(\"Party\")\nlocal AttackModifierMechanic = require(\"mechanic.attackmodifier.AttackModifierMechanic\")\nlocal ExtensionHandler = require(\"ExtensionHandler\")\n\n--- Global script\nsetup = {\n advancedAbilities = true,\n advancedModifiers = true,\n hidePMTooltip = true,\n buttons = true\n}\nlocal tableToken = \"7ef9dd\"\n\nmamd = { - 2.05, 1.88, - 16.05}\nmamdp = {2.05, 1.88, - 16.05}\n\ntoCheck = {}\ntoCheck[\"Player Curse (Null)\"] = {pos = {51.60, 4.00, 23.65}}\ntoCheck[\"Bless (x2)\"] = {pos = {51.60, 4.00, 19.99}}\ntoCheck[\"Monster Curse (Null)\"] = {pos = {51.60, 4.00, -23.66}}\n\nlocal function megaFreeze()\n ---@type GUID[]\n local megaFreezeIT = {\n '75ab50', 'f3ffb7', 'b53fb2', 'a75fcd', '7ef9dd', 'faad27', '577a97',\n '15adf5', '0625c4', '0625c4', '4895cd', '29c2ce', '457c84', '6a8ca6', 'bb85b9', 'd23231', '74238d',\n '20d00f', '4efdc4', '359306', '0a3abe', 'c912f5', '21be0e', '0b51bb', 'a945e4', 'fe14ef',\n '8d2074', '3ea749', '3458a9', '987957', 'bf8414', 'f01091', 'b26c16',\n '6c775e', '10a47a', 'a41a54', '3e4319', '4a2ae5', 'f82d4f', '11c127', 'a42d87', 'd30150'}\n\n for _, guid in pairs(megaFreezeIT) do\n local obj = --[[---@not nil]] getObjectFromGUID(guid)\n if obj ~= nil then\n obj.interactable = false\n obj.tooltip = false\n else\n print(\"Megafreeze did not find this GUID: \", guid)\n end\n end\nend\n\nlocal function onLoad()\n megaFreeze()\n print(\"Version: \" .. table.concat(R.Version, \".\"))\nend\n\nSaveManager.registerOnLoad(onLoad)\n\n\nlootingDistance = {}\nlootingDistance[\"0\"] = {0.5, 2, 0.5}\nlootingDistance[\"1\"] = {2, 2, 2}\nlootingDistance[\"2\"] = {3.5, 2, 3.5}\n\n\nfunction looting(className)\n local classesInPlay = {}\n for color, _ in pairs(colorToPlayer) do\n if color ~= \"Black\" then\n local class = getClassFromColor(color)\n if class then\n if className:find(class) then\n classesInPlay[class] = color\n end\n end\n end\n end\n\n for _, obj in pairs(getAllObjects()) do\n if classesInPlay[obj.getName()] then\n local findCoin = Physics.cast({\n origin = obj.getPosition(),\n direction = {0, 1, 0},\n type = 3,\n size = lootingDistance[\"0\"],\n max_distance = 0,\n debug = false\n })\n for _, found in pairs(findCoin) do\n if found.hit_object.getName():find(\"Coin\") or found.hit_object.getName():find(\"Treasure Chest\") then\n found.hit_object.setPositionSmooth(colorToPlayer[classesInPlay[obj.getName()]].coin)\n found.hit_object.unlock()\n end\n end\n end\n end\nend\n\n\nfunction reveal()\n GameController.startRound()\nend\n\nfunction AddInitiative(params)\n InitiativeTracker.add(params)\n InitiativeTracker.render()\nend\n\nfunction endRound(player, value, id, final)\n GameController.endRound(final)\nend\n\nfunction endRoundSilently()\n endRound(nil,nil,nil,true)\nend\n\n---@param player integer\nfunction checkForBlessCurse(player)\n local playerInfo = Party.getCharacterInfo(player)\n AttackModifierMechanic.endOfRoundCheck(playerInfo)\nend\n\nfunction checkForAdditionalNeg1(deck)\n\tlocal Neg1DeckHitlist = Physics.cast({\n\t\torigin = {51.60, 2.6, 16.38},\n\t\tdirection = {0, -1., 0},\n\t\ttype = 3,\n\t\tsize = {0.05, 1.75, 0.05},\n\t\tmax_distance = 0,\n\t\tdebug = false\n\t})\n\n\tlocal neg1Bag\n\tfor _,obj in pairs(Neg1DeckHitlist) do\n\t if obj.hit_object.tag == \"Bag\" then neg1Bag = obj.hit_object end\n\tend\n\n\tfor _, u in pairs(deck.getObjects()) do\n\t\tif u.name == \"Attack Modifier (-1)*\" then\n\t\t\tneg1Bag.putObject(deck.takeObject({\n\t\t\tguid = u.guid,\n rotation = { 0, 180, 0},\n smooth = false\n }))\n\t\t\tcheckForAdditionalNeg1(deck)\n\t\t\treturn\n\t end\n\tend\n\nend\n\nfunction moveRoundTracker()\n local tracker = {\n number = 0.6\n }\n\n local roundTracker = nil\n local startPos = {x = -3.30, y = 1.92, z = -18.86}\n for i = 0, 11 do\n local hitlist = Physics.cast({\n origin = {startPos.x + 0.6 * i, startPos.y, startPos.z},\n direction = {0, 1, 0},\n type = 3,\n size = {1, 1, 1},\n max_distance = 0,\n debug = false\n })\n\n for u, v in pairs(hitlist) do\n if v.hit_object.getName() == \"Round Tracker\" then roundTracker = v.hit_object end\n end\n end\n if roundTracker then\n local oldPos = roundTracker.getPosition()\n if oldPos.x < - 3 and tracker.number == -0.6 then\n roundTracker.setPosition({3.30, 1.92, -18.90})\n return\n end\n if not (oldPos.x > 3 and tracker.number == 0.6) then\n roundTracker.setPosition({oldPos.x + tracker.number, oldPos.y, oldPos.z})\n return\n end\n\n roundTracker.setPosition({-3.30, 1.92, -18.86})\n end\n\nend\n\n\nlocal function characterFromColor(color)\n for i, playerInfo in ipairs(Game.PlayerInfo) do\n if playerInfo.Color == color then\n return Party.getCharacterInfo(i)\n end\n end\nend\n\n---@param player tts__Player\n---@param id string\n---@return gloom_CharacterInfo\nlocal function getCharacterForButtonClick(player, id)\n if not id then\n return characterFromColor(player.color)\n end\n return characterFromColor(id)\nend\n\nfunction drawClicked(player, _, id)\n AttackModifierMechanic.drawCard(getCharacterForButtonClick(player, id))\nend\n\nfunction drawClickedEnemy()\n AttackModifierMechanic.drawCard(\"Monster\")\nend\n\nfunction addCurse(player, _, id)\n AttackModifierMechanic.addCard(getCharacterForButtonClick(player, id), \"Curse\")\nend\n\nfunction addCurseEnemy()\n AttackModifierMechanic.addCard(\"Monster\", \"Curse\")\nend\n\nfunction addBless(player, _, id)\n AttackModifierMechanic.addCard(getCharacterForButtonClick(player, id), \"Bless\")\nend\n\nfunction addBlessEnemy()\n AttackModifierMechanic.addCard(\"Monster\",\"Bless\")\nend\n\nfunction sortHand(player, value, id)\n local handObjects\n if id == nil then\n handObjects = player.getHandObjects(1)\n else\n handObjects = Player[id].getHandObjects(1)\n end\n\n local cards = {}\n local handPos = {}\n for i, j in pairs(handObjects) do\n local numbers = j.getName():match(\"%((%d+)%)$\")\n if not numbers then\n numbers = \"0\"\n end\n if numbers and tonumber(numbers) ~= nil then\n table.insert(cards, {j, tonumber(numbers)})\n table.insert(handPos, j.getPosition())\n end\n end\n table.sort(cards, function(a, b) return a[2] < b[2] end)\n for i, j in ipairs(cards) do\n j[1].setPosition(handPos[i])\n end\nend\n\nfunction longRest(player, value, id)\n local color\n if id == nil then\n color = player.color\n else\n color = id\n end\n local class = getClassFromColor(color)\n InitiativeTracker.LongRest({class = class, color = color})\nend\n\nfunction getClassFromColor(color)\n local zone = colorToPlayer[color].zone\n if zone ~= nil then\n for i, j in pairs(getObjectFromGUID(zone).getObjects()) do\n if j.getName() == \"Character Mat\" then\n return j.getDescription()\n end\n end\n end\nend\n\nfunction showBI(player, value, id)\n if shown.soloMode == false then\n showForPlayer({panel = \"PlayerInterface\", color = player.color})\n else\n showForPlayer({panel = \"SoloInterface\", color = player.color})\n end\nend\n\nfunction showMI(player, value, id)\n showForPlayer({panel = \"MonsterInterface\", color = player.color})\nend\n\nfunction showButtons(player, value, id)\n if shown.buttons == false then\n Global.UI.show(\"buttons\")\n shown.buttons = true\n Global.UI.setAttribute(\"showButtons\", \"text\", \"\u25b2\")\n else\n Global.UI.hide(\"buttons\")\n shown.buttons = false\n Global.UI.setAttribute(\"showButtons\", \"text\", \"\u25bc\")\n end\nend\n\nfunction showIni(player, value, id)\n showForPlayer({panel = \"InitiativeRows\", color = player.color})\nend\n\nfunction showSolo()\n local settings = {nil,\n {height = \"430\", imageOffset = \"0 180\", tableOffset = \"0 -190\", panelOffset=\"0 150\"},\n {height = \"565\", imageOffset = \"0 250\", tableOffset = \"0 -260\", panelOffset=\"0 90\"},\n {height = \"690\", imageOffset = \"0 317\", tableOffset = \"0 -323\", panelOffset=\"0 30\"},\n }\n local playerCount = 0\n for color, i in pairs(colorToPlayer) do\n if color ~= \"Black\" then\n local playerMat = false\n for _, j in pairs(getObjectFromGUID(i.zone).getObjects()) do\n if j.getName():find(\"Player Mat\") then\n playerMat = true\n playerCount = playerCount + 1\n end\n end\n if playerMat then\n Global.UI.setAttribute(i.pre, \"active\", \"true\")\n else\n Global.UI.setAttribute(i.pre, \"active\", \"false\")\n end\n end\n end\n\n if playerCount < 2 then\n broadcastToAll(\"In solo mode you need at least 2 characters\", {1, 0, 0})\n return\n end\n\n Global.UI.setAttribute(\"SoloInterface\", \"height\", settings[playerCount].height)\n Global.UI.setAttribute(\"SoloInterface\", \"offsetXY\", settings[playerCount].panelOffset)\n Global.UI.setAttribute(\"BIImage\", \"offsetXY\", settings[playerCount].imageOffset)\n Global.UI.setAttribute(\"tableSolo\", \"offsetXY\", settings[playerCount].tableOffset)\n\n if shown.soloMode == false then\n Global.UI.show(\"SoloInterface\")\n Global.UI.hide(\"PlayerInterface\")\n shown.soloMode = true\n else\n Global.UI.hide(\"SoloInterface\")\n Global.UI.show(\"PlayerInterface\")\n shown.soloMode = false\n end\nend\n\nfunction spawnerClicked(player, value, id)\n if id == \"ScenarioMS\" then\n self.UI.setAttribute(\"ScenarioSpawner\", \"active\", \"true\")\n self.UI.setAttribute(\"MonsterSpawner\", \"active\", \"false\")\n elseif id == \"SingleMS\" then\n self.UI.setAttribute(\"MonsterSpawner\", \"active\", \"true\")\n self.UI.setAttribute(\"ScenarioSpawner\", \"active\", \"false\")\n elseif id == \"showSpawner\" or id == \"CloseSpawner\" then\n showForPlayer({panel = \"Spawner\", color = player.color})\n elseif id == \"win\" or id == \"lost\" then\n local classesInPlay = {}\n for color, _ in pairs(colorToPlayer) do\n if color ~= \"Black\" then\n if getClassFromColor(color) then\n classesInPlay[getClassFromColor(color)] = color\n end\n end\n end\n\n for _, obj in pairs(getAllObjects()) do\n if classesInPlay[obj.getName()] then\n if obj.tag == \"Figurine\" then\n obj.setPositionSmooth(colorToPlayer[classesInPlay[obj.getName()]].coin)\n obj.call(\"setHpMax\")\n obj.call(\"clearConditions\")\n end\n end\n if obj.getName() == \"Bear Companion\" and colorToPlayer[classesInPlay[\"Beast Tyrant\"]] ~= nil then\n local bpos = colorToPlayer[classesInPlay[\"Beast Tyrant\"]].coin\n obj.setPositionSmooth({bpos[1] + 3, bpos[2], bpos[3]})\n obj.call(\"setHpMax\")\n obj.call(\"clearConditions\")\n end\n end\n\n local scenarioBonus = ScenarioApi.getScenarioLevelSettings()\n local playArea\n for _, obj in ipairs(getObjects()) do\n if obj.getName() == \"Play Area\" then playArea = obj end\n end\n for color, t in pairs(colorToPlayer) do\n if color ~= \"Black\" then\n local class = getClassFromColor(color)\n if class then\n local coins = 0\n local charSheet = nil\n local xpToken = nil\n local getting = {xp = 0, gold = 0}\n for _, obj in pairs(getObjectFromGUID(t.zone).getObjects()) do\n if obj.getName() == \"Coin 1\" then\n if obj.getQuantity() > 0 then\n coins = coins + obj.getQuantity()\n else\n coins = coins + 1\n end\n obj.destruct()\n elseif obj.getName() == \"Coin 5\" then\n if obj.getQuantity() > 0 then\n coins = coins + obj.getQuantity() * 5\n else\n coins = coins + 5\n end\n obj.destruct()\n elseif obj.getName() == \"Character Sheet\" then\n charSheet = obj\n elseif obj.hasTag(R.Tag.Class.XpDial) then\n xpToken = obj\n end\n end\n local goldOld = charSheet.UI.getAttribute(\"gold\", \"text\")\n local xpOld = charSheet.UI.getAttribute(\"xp\", \"text\")\n\n if coins > 0 then\n getting.gold = coins * scenarioBonus.gold\n charSheet.call(\"addEx\", {name = \"gold\", amount = getting.gold})\n end\n if xpToken then\n getting.xp = DialCounter.getValue(xpToken)\n DialCounter.setValue(xpToken, 0)\n end\n if id == \"win\" then\n getting.xp = getting.xp + scenarioBonus.experience\n end\n charSheet.call(\"addEx\", {name = \"xp\", amount = getting.xp})\n Wait.time(\n function()\n broadcastToAll(class .. \" got \" .. getting.gold .. \" Gold (From \" .. goldOld .. \" To \" .. goldOld + getting.gold .. \") and \"\n .. getting.xp .. \" XP. (From \" .. xpOld .. \" To \" .. xpOld + getting.xp .. \")\", color)\n end\n , 3 )\n for k, v in pairs(t) do\n if k ~= \"color\" and k ~= \"zone\" and k ~= \"pre\" and k ~= \"coin\" and k ~= \"items\" and k ~= \"equip\" then\n local hitlist = Physics.cast({\n origin = v,\n direction = {0, 1, 0},\n type = 3,\n size = {1, 1, 1},\n max_distance = 0,\n debug = false\n })\n for u, v in pairs(hitlist) do\n if k == \"FC\" or k == \"SC\" or k == \"D\" or k == \"L\" or k:find(\"AS\") then\n if v.hit_object.tag == \"Card\" or v.hit_object.tag == \"Deck\" then v.hit_object.deal(20, color) end\n end\n end\n end\n if k == \"equip\" then\n local hitlist = Physics.cast({\n origin = v,\n direction = {0, 1, 0},\n type = 3,\n size = {18, 1, 1},\n max_distance = 0,\n debug = false\n })\n for u, v in pairs(hitlist) do\n if v.hit_object.tag == \"Card\" or v.hit_object.tag == \"Deck\" then\n v.hit_object.setRotation({0, 180, 0})\n end\n end\n end\n if k == \"items\" then\n local hitlist = Physics.cast({\n origin = v,\n direction = {0, 1, 0},\n type = 3,\n size = {6.2, 1, 10},\n max_distance = 0,\n debug = false\n })\n for u, v in pairs(hitlist) do\n if v.hit_object.tag == \"Card\" or v.hit_object.tag == \"Deck\" then\n v.hit_object.setRotation({0, 180, 0})\n end\n end\n end\n end\n end\n end\n\n for _, character in pairs(Party.getActiveCharacters()) do\n AttackModifierMechanic.resetDeck(character)\n end\n Wait.time(function () AttackModifierMechanic.resetDeck(\"Monster\") end, 4)\n end\n\n Wait.time(\n function()\n if id == \"win\" then\n broadcastToAll(\"Check your Battlegoals and read the Scenario Conclusion!\", \"Orange\")\n for u, v in pairs(getAllObjects()) do\n if v.getName() == \"Round Tracker\" then\n v.setPosition({-3.30, 1.92, -18.86})\n end\n end\n else\n broadcastToAll(\"Discard your Battlegoals and try again!\", \"Orange\")\n for u, v in pairs(getAllObjects()) do\n if v.getName() == \"Round Tracker\" then\n v.setPosition({-3.30, 1.92, -18.86})\n end\n end\n end\n end\n , 3.5)\n endRoundSilently()\n end\nend\n\nlocal function packupActiveScenario()\n local scenarioBag = R.Object.ScenarioBag()\n for _, scenario in ipairs(getObjectsWithTag(R.Tag.Scenario.Active)) do\n scenario.removeTag(R.Tag.Scenario.Active)\n scenarioBag.putObject(scenario)\n end\nend\n\n---@param scenarioId string\nlocal function unpackActiveScenario(scenarioId)\n local scenarioBag = R.Object.ScenarioBag()\n for _, scenario in ipairs(scenarioBag.getObjects()) do\n if scenario.name == tostring(scenarioId) then\n local taken = --[[---@not nil]] scenarioBag.takeObject({\n index = scenario.index,\n })\n taken.addTag(R.Tag.Scenario.Active)\n taken.lock()\n break\n end\n end\nend\n\nfunction scenarioWon()\n spawnerClicked(nil,nil,\"win\")\n packupActiveScenario()\nend\n\nfunction scenarioLost()\n spawnerClicked(nil,nil,\"lost\")\n packupActiveScenario()\nend\n\nfunction resetLevel(params)\n params[1].UI.setAttribute(\"scenarioLevel\", \"text\", params[2])\nend\n\n---@param params gloom_Setup_Scenario\nfunction createMap(params)\n ---@type boolean\n local hiddenSetup = params.hiddenSetup\n if hiddenSetup == nil then\n hiddenSetup = OptionsApi.useHiddenSetup()\n end\n packupActiveScenario()\n unpackActiveScenario(params.scenario)\n\n local scenario = ScenarioApi.getScenario(params.scenario)\n ExtensionHandler.call(\"onScenarioLoad\", scenario)\n\n setupScenario({\n NumScenario = params.scenario,\n HiddenRooms = hiddenSetup,\n })\n\n AttackModifierMechanic.resetDeck(\"Monster\")\n\n local numBattleGoals = tonumber(OptionsApi.dealBattleGoals()) or 0\n if numBattleGoals > 0 then\n local battleGoals = --[[---@type tts__Bag]] R.Object.BattleGoalsBag()\n if battleGoals.getQuantity() < 24 then\n broadcastToAll(\"There are less then 24 Battle Goals in the Deck. Some appear to have gone missing!.\", \"Red\")\n end\n\n Wait.time(function() battleGoals.shuffle() end, 0.3)\n Wait.time(\n function()\n for _, character in pairs(Party.getActiveCharacters()) do\n battleGoals.deal(numBattleGoals, character.color)\n end\n end,\n 3)\n end\nend\n\n---@shape __showForPlayer_Params\n---@field panel string\n---@field color tts__PlayerColor\n\n---@param params __showForPlayer_Params\nfunction showForPlayer(params)\n local panel = params.panel\n local color = params.color\n local opened = self.UI.getAttribute(panel, \"visibility\")\n if opened == nil then opened = \"\" end\n\n if opened:find(color) then\n opened = opened:gsub(\"|\" .. color, \"\")\n opened = opened:gsub(color .. \"|\", \"\")\n opened = opened:gsub(color, \"\")\n self.UI.setAttribute(panel, \"visibility\", opened)\n if opened == \"\" then\n self.UI.setAttribute(panel, \"active\", \"false\")\n shown[panel] = false\n end\n else\n if shown[panel] ~= true then\n self.UI.setAttribute(panel, \"active\", \"true\")\n self.UI.setAttribute(panel, \"visibility\", color)\n shown[panel] = true\n else\n self.UI.setAttribute(panel, \"visibility\", opened .. \"|\" .. color)\n end\n end\nend\n\nfunction updateSliderValue(player, value, id)\n if id == \"SSSlider\" then\n self.UI.setAttribute(\"SSScenario\", \"text\", value)\n elseif id == \"MSSlider\" then\n if tonumber(value) == -1 then\n value = \"Auto\"\n end\n self.UI.setAttribute(\"MSSlevel\", \"text\", value)\n else\n self.UI.setAttribute(id, \"value\", value)\n end\nend\n\nfunction updateDropdown(player, value, id)\n self.UI.setAttribute(id, \"selected\", value)\nend\n\nfunction toggleChange(player, value, id)\n if id:find(\"HiddenRooms\") then\n Global.UI.setAttribute(id .. \"Image\", \"image\", \"Radio Button Selected\")\n Global.UI.setAttribute(\"HiddenRooms\" .. value .. \"Image\", \"image\", \"Radio Button\")\n else\n local isOn = Global.UI.getAttribute(id, \"isOn\")\n if isOn == \"true\" then\n self.setVar(id, false)\n Global.UI.setAttribute(id, \"isOn\", \"false\")\n Global.UI.hide(id .. \"Image\")\n else\n self.setVar(id, true)\n Global.UI.setAttribute(id, \"isOn\", \"true\")\n Global.UI.show(id .. \"Image\")\n end\n end\nend\n\nfunction cameraControls(player, value, id)\n if id == \"showCamControls\" then\n showForPlayer({panel = \"CameraControl\", color = player.color})\n end\nend\n\n---@param player tts__Player\n---@param button tts__UIElement_Id\nfunction cameraPosition(player, button)\n if button == \"One\" then\n player.lookAt(Game.PlayerInfo[1].Camera)\n elseif button == \"Two\" then\n player.lookAt(Game.PlayerInfo[2].Camera)\n elseif button == \"Three\" then\n player.lookAt(Game.PlayerInfo[3].Camera)\n elseif button == \"Four\" then\n player.lookAt(Game.PlayerInfo[4].Camera)\n elseif button == \"showScenarioBoard\" then\n player.lookAt({\n position = { x = 0, y = 0, z = 4.5 },\n pitch = 65,\n yaw = 0,\n distance = 40,\n })\n elseif button == \"showGameBoard\" then\n player.lookAt({\n position = { x = 0, y = 0, z = -20 },\n pitch = 90,\n yaw = 0,\n distance = 25,\n })\n end\nend\n\n---@param url URL\nlocal function onBackgroundChanged(url)\n local token = --[[---@not nil]] getObjectFromGUID(tableToken)\n token.setCustomObject({ image = url })\n token.reload()\n Wait.frames(function() Wait.condition(function()\n local obj = --[[---@not nil]] getObjectFromGUID(tableToken)\n obj.tooltip = false\n obj.lock()\n obj.interactable = false\n end, function() return not token.loading_custom and not token.spawning end)\n end, 1)\nend\n\nEvent.registerForBackgroundChanged(onBackgroundChanged)\n\n\nshown = {\n battleInterface = false,\n enemyInterface = false,\n iniList = true,\n soloMode = false,\n spawner = false,\n CameraControls = false\n}\n\nisElite = false\nthreeD = false\n\nboards = {\n map = \"9cc037\"\n}\n\ncolorToPlayer = {\n Red = {zone = \"49a4e0\",\n color = {0.856, 0.1, 0.094},\n pre = \"P1\",\n AMDiscard = { - 40.72, 2, - 33.96},\n AMDeck = { - 40.72, 2, - 36.62},\n FC = { - 16.60, 1.82, - 23.70},\n SC = { - 13.40, 1.82, - 23.70},\n coin = { - 36.38, 6, - 42.54},\n AS1 = { - 36.85, 1.85, - 34.79},\n AS2 = { - 33.69, 1.85, - 34.79},\n AS3 = { - 30.53, 1.85, - 34.79},\n AS4 = { - 27.37, 1.85, - 34.79},\n D = { - 40.43, 1.85, - 40.80},\n L = { - 27.37, 1.85, - 40.80},\n equip = { -38.63, 1.76, -45.51},\n items = { -46, 1.76, -38.14}\n },\n White = {zone = \"dac936\",\n color = {1, 1, 1},\n pre = \"P2\",\n AMDiscard = { - 16.73, 2, - 33.96},\n AMDeck = { - 16.72, 2, - 36.62},\n FC = { - 9.32, 1.82, - 23.70},\n SC = { - 6.15, 1.82, - 23.70},\n coin = { - 12.25, 6, - 42.54},\n AS1 = { - 12.85, 1.85, - 34.79},\n AS2 = { - 9.69, 1.85, - 34.79},\n AS3 = { - 6.53, 1.85, - 34.79},\n AS4 = { - 3.37, 1.85, - 34.80},\n D = { - 16.43, 1.85, - 40.80},\n L = { - 3.37, 1.85, - 40.81},\n equip = { -14.63, 1.76, -45.51},\n items = { -22, 1.76, -38.14}\n },\n Blue = {zone = \"62cd94\",\n color = {0.118, 0.53, 1},\n pre = \"P3\",\n AMDiscard = {7.28, 2, - 33.96},\n AMDeck = {7.28, 2, - 36.62},\n FC = {6.14, 1.82, - 23.70},\n SC = {9.33, 1.82, - 23.70},\n coin = {11.68, 6, - 42.54},\n AS1 = {11.16, 1.85, - 34.79},\n AS2 = {20.63, 1.85, - 34.80},\n AS3 = {17.48, 1.85, - 34.79},\n AS4 = {14.32, 1.85, - 34.79},\n D = {7.57, 1.85, - 40.80},\n L = {20.63, 1.85, - 40.80},\n equip = {8.67, 1.76, -45.51},\n items = {1.02, 1.76, -38.14}\n },\n Green = {zone = \"963318\",\n color = {0.192, 0.701, 0.168},\n pre = \"P4\",\n AMDiscard = {31.28, 2, - 33.96},\n AMDeck = {31.28, 2, - 36.62},\n FC = {13.41, 1.82, - 23.70},\n SC = {16.58, 1.82, - 23.70},\n coin = {35.63, 6, - 42.54},\n AS1 = {35.16, 1.85, - 34.79},\n AS2 = {38.32, 1.85, - 34.79},\n AS3 = {41.48, 1.85, - 34.79},\n AS4 = {44.63, 1.85, - 34.80},\n D = {31.57, 1.85, - 40.80},\n L = {44.63, 1.85, - 40.81},\n equip = {33.67, 1.76, -45.51},\n items = {26.02, 1.76, -38.14}\n },\n Black = {\n AMDiscard = {2.07, 2, - 16.05},\n AMDeck = { - 2.05, 2, - 16.05}\n }\n}\n\nend)\n__bundle_register(\"ExtensionHandler\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal ExtensionPoints = require(\"extension.ExtensionPoints\")\r\nrequire(\"extension.Config\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal ExtensionHandler = {}\r\n\r\n---@class __ExtensionHandler_this\r\nlocal this = {}\r\n\r\n---@type table>\r\nlocal eventToExtension = {}\r\n\r\n---@param event gloom_Extension_Event\r\nfunction ExtensionHandler.call(event, ...)\r\n return this.execute(event, table.pack(...))\r\nend\r\n\r\nfunction this.execute(event, parameters)\r\n local handlers = eventToExtension[event] or {}\r\n\r\n local extensionPoint = --[[---@not nil]] ExtensionPoints.get(event)\r\n if not extensionPoint then\r\n Logger.error(\"Tried to call extension point '%s', but it doesn't exist.\", event)\r\n return nil\r\n end\r\n\r\n local finalResult = nil\r\n for handlerGuid, _ in pairs(handlers) do\r\n local handlerObject = --[[---@not nil]] getObjectFromGUID(handlerGuid)\r\n if not handlerObject then\r\n Logger.debug(\"Unregistering extension %s as it doesn't exist anymore\", handlerGuid)\r\n this.unregisterExtension(handlerGuid)\r\n else\r\n local result = handlerObject.call(\"__callExtension\", { event = event, parameters = parameters })\r\n if result ~= R.Error.Extension then\r\n finalResult = extensionPoint.accumulateResult(result, finalResult)\r\n parameters = extensionPoint.updateParameters(parameters, result)\r\n end\r\n end\r\n end\r\n\r\n return finalResult\r\nend\r\n\r\n---@param handler GUID\r\n---@param event gloom_Extension_Event\r\nfunction this.registerExtension(handler, event)\r\n local eventHandlers = TableUtil.getOrElse(eventToExtension, event, --[[---@type set]] {})\r\n eventHandlers[handler] = true\r\nend\r\n\r\n---@overload fun(handler: GUID)\r\n---@param handler GUID\r\n---@param event gloom_Extension_Event\r\nfunction this.unregisterExtension(handler, event)\r\n if event then\r\n eventToExtension[event][handler] = false\r\n else\r\n for _, handlers in pairs(eventToExtension) do\r\n handlers[handler] = nil\r\n end\r\n end\r\nend\r\n\r\n---@shape __registerEvent_Params : gloom_Api_Call\r\n---@field event gloom_Extension_Event\r\n\r\n---@param params __registerEvent_Params\r\nfunction __registerEvent(params)\r\n local handler = params.__caller\r\n local event = params.event\r\n\r\n if not handler then\r\n Logger.error(\"No extension handler provided while registering event %s. I don't know who called me.\", event)\r\n return\r\n end\r\n\r\n if not ExtensionPoints.get(event) then\r\n Logger.error(\"Can not register extension %s for event %s. No such event exists\", handler, event)\r\n return\r\n end\r\n\r\n this.registerExtension(handler, event)\r\nend\r\n\r\nfunction __fireEvent(params)\r\n local event = params.event\r\n local parameters = params.parameters\r\n return ExtensionHandler.call(event, parameters)\r\nend\r\n\r\nreturn ExtensionHandler\r\n\nend)\n__bundle_register(\"api.Resource\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Resource = {}\n\n---@type seb_Version\nResource.Version = { 1, 3, 0, \"beta\", 20 }\n\nResource.Remove = \"__REMOVE__\"\n---@type gloom_Spawn_Element\nResource.EmptyElement = { type = 0, name = Resource.Remove }\n\nResource.LockType = {\n None = 0,\n Hard = 1,\n Soft = 2,\n}\n\nResource.ElementType = {\n Enemy = 0,\n Corridor = 1,\n DifficultTerrain = 2,\n HazardousTerrain = 3,\n Obstacle = 5,\n Trap = 7,\n Treasure = 8,\n Coin = 9,\n Door = 10,\n Start = 11,\n MapTile = 12,\n ScenarioAid = 13,\n ScenarioSection = 14,\n ObjectiveToken = 15,\n Figure = 16,\n Summon = 17,\n}\n\n---@param guid GUID\n---@return fun(): tts__Object\nlocal function byGuid(guid)\n return function()\n return --[[---@not nil]] getObjectFromGUID(guid)\n end\nend\n\n---@param tag string\n---@return fun(): tts__Object\nlocal function byTag(tag)\n return function()\n return --[[---@not nil]] getObjectsWithTag(tag)[1]\n end\nend\n\n---@param guid GUID\n---@param name string\n---@return fun(name: string): tts__ObjectState\nlocal function childByName(guid)\n return function(name)\n local parent = --[[---@type tts__Container]] getObjectFromGUID(guid)\n for _, child in ipairs(parent.getData().ContainedObjects) do\n if child.Nickname == name then\n return child\n end\n end\n end\nend\n\n---@alias typed fun(): R\n\nResource.Object = {\n HiddenSection = byGuid(\"d30150\"),\n BlessBag = byGuid(\"f96829\"),\n PlayerCurseBag = byGuid(\"1802d6\"),\n MonsterCurseBag = byGuid(\"670e89\"),\n BattleGoalsBag = byGuid(\"2d7a80\"),\n Backup = childByName(\"29c2ce\"),\n ScenarioBag = --[[---@type typed]] byTag(\"UnlockedScenarios\"),\n}\n\nResource.Error = {\n Extension = \"__ERROR_IN_EXTENSION__\",\n}\n\nResource.Tag = {\n --- Tags for class specific components.\n Class = {\n Envelope = \"Class Envelope\",\n Sheet = \"Character Sheet\",\n Figure = \"Character\",\n Summon = \"Summon\",\n Ability = \"Ability Card\",\n HpDial = \"HP Dial\",\n XpDial = \"XP Dial\",\n },\n --- Tags for enemy specific components.\n Monster = {\n Envelope = \"Enemy Envelope\",\n Figure = \"Enemy\",\n Mat = \"MonsterMat\",\n StatSheet = \"MonsterStatSheet\",\n Bag = \"MonsterFigureBag\",\n Abilities = \"Monster Ability Deck\",\n Ability = \"Monster Ability Card\",\n },\n --- Tags for Overlays.\n Overlay = {\n Corridor = \"Corridor\",\n DifficultTerrain = \"Difficult Terrain\",\n Door = \"Door\",\n HazardousTerrain = \"Hazardous Terrain\",\n Map = \"Map\",\n Obstacle = \"Obstacle\",\n Trap = \"Trap\",\n TreasureChest = \"Treasure Chest\",\n Figure = \"Figure\",\n },\n --- Tags for traits a component can have.\n Trait = {\n --- Supports adding actions.\n HasAction = \"Has Action\",\n --- Supports adding aid tokens.\n HasAidTokens = \"Has Aid Tokens\",\n HasAttackEffects = \"Has Attack Effects\",\n --- Supports adding conditions.\n HasConditions = \"Has Conditions\",\n --- Supports adding health bar.\n HasHealth = \"Has Health\",\n --- Supports adding immunities.\n HasImmunities = \"Has Immunities\",\n HasStats = \"Has Stats\",\n HasInitiative = \"Has Initiative\",\n HasTheme = \"Has Theme\",\n CanReload = \"Can Reload\",\n CanSpawn = \"Can Spawn\",\n },\n Scenario = {\n Definition = \"Scenario\",\n ExtraContent = \"Scenario Extra Content\",\n Active = \"Active Scenario\",\n },\n Event = {\n City = \"City Event\",\n Road = \"Road Event\",\n },\n Item = {\n Item = \"Item\",\n Head = \"ItemHead\",\n Chest = \"ItemChest\",\n OneHand = \"ItemOneHanded\",\n TwoHand = \"ItemTwoHanded\",\n Boots = \"ItemBoots\",\n Consumable = \"ItemConsumable\",\n Design = \"Item Design\",\n Reward = \"Item Reward\",\n Solo = \"Item Solo Reward\",\n },\n AMD = {\n RemoveAfterDiscard = \"Remove After Discard\",\n },\n Component = {\n Mock = \"GHE API Mock\",\n BagOfLockedScenarios = \"LockedScenarios\",\n BagOfLockedCharacters = \"LockedCharacters\",\n BagOfMonsters = \"MonsterBag\",\n BagOfMonsterAbilities = \"Bag of Monster Abilities\",\n BagOfExtraContent = \"Bag of Extra Content\",\n BagOfSummons = \"Bag of Summons\",\n BagOfUnlockedScenarios = \"UnlockedScenarios\",\n BagOfPersonalQuests = \"PersonalQuestBag\",\n LockedContent = \"Locked Content\",\n Treasure = \"Treasure\",\n ShopItems = \"Shop Items\",\n RewardItems = \"Reward Items\",\n ItemDesigns = \"Item Designs\",\n SoloRewardItems = \"Solo Reward Items\",\n RoadEvents = \"Available Road Events\",\n CityEvents = \"Available City Events\",\n RiftEvents = \"Available Rift Events\",\n PersonalQuests = \"Personal Quests\",\n Book = \"Book\",\n },\n Book = {\n Guide = \"Guide Book\",\n Scenarios = \"Scenario Book\",\n Sections = \"Section Book\",\n },\n Game = {\n Gloomhaven = \"Gloomhaven\",\n ForgottenCircles = \"Forgotten Circles\",\n },\n -- TODO better group those below\n Character = \"Character\",\n Enemy = \"Enemy\",\n Summon = \"Summon\",\n Tile = {\n Start = \"Tile - Start Area\"\n },\n SoftLock = \"Soft-Lock\",\n EnemyStatSheet = \"MonsterStatSheet\",\n CustomContent = \"Gloomhaven Custom Content\",\n PartySheet = \"Party Sheet\",\n ScenarioLevelChart = \"Scenario Level Chart\",\n PersonalQuest = \"PersonalQuest\",\n ConditionStack = \"ConditionStack\"\n}\n\nreturn Resource\n\nend)\n__bundle_register(\"extension.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"extension.CampaignExtensionPoint\")\nrequire(\"extension.CharacterExtensionPoint\")\nrequire(\"extension.ScenarioExtensionPoint\")\n\nend)\n__bundle_register(\"extension.ScenarioExtensionPoint\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ExtensionPoints = require(\"extension.ExtensionPoints\")\nlocal BaseExtensionPoint = require(\"extension.BaseExtensionPoint\")\n\nlocal ScenarioExtensionPoint = {}\n\n---@alias Extension_Scenario 'onScenarioLoad'\n---@class gloom_OnScenarioLoadExtensionPoint : gloom_ExtensionPoint\n\nlocal OnScenarioLoadExtension = --[[---@type gloom_OnScenarioLoadExtensionPoint]] BaseExtensionPoint()\n\nExtensionPoints.register(\"onScenarioLoad\", OnScenarioLoadExtension)\n\nreturn ScenarioExtensionPoint\n\nend)\n__bundle_register(\"extension.BaseExtensionPoint\", function(require, _LOADED, __bundle_register, __bundle_modules)\n---@class gloom_BaseExtensionPoint_static : gloom_BaseExtensionPoint\n---@overload fun(): gloom_BaseExtensionPoint\nlocal BaseExtensionPoint = {}\n\n---@return gloom_BaseExtensionPoint\nlocal function new()\n ---@class gloom_BaseExtensionPoint : gloom_ExtensionPoint\n local self = {}\n\n function self.updateParameters(parameters, result)\n return parameters\n end\n\n function self.accumulateResult(result, currentResult)\n return result\n end\n\n return self\nend\n\nsetmetatable(BaseExtensionPoint, {\n ---@return gloom_BaseExtensionPoint\n __call = function(_)\n return new()\n end\n})\n\nreturn BaseExtensionPoint\n\nend)\n__bundle_register(\"extension.ExtensionPoints\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ExtensionPoints = {}\n\n---@type table\nlocal extensionPoints = {}\n\n---@param name string\n---@param extensionPoint gloom_ExtensionPoint\nfunction ExtensionPoints.register(name, extensionPoint)\n extensionPoints[name] = extensionPoint\nend\n\n---@param name string\n---@return nil | gloom_ExtensionPoint\nfunction ExtensionPoints.get(name)\n return extensionPoints[name]\nend\n\nreturn ExtensionPoints\n\nend)\n__bundle_register(\"extension.CharacterExtensionPoint\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ExtensionPoints = require(\"extension.ExtensionPoints\")\nlocal BaseExtensionPoint = require(\"extension.BaseExtensionPoint\")\n\nlocal CharacterExtensionPoint = {}\n\n---@alias Extension_Character 'onCharacterSave' | 'onCharacterLoad' | 'onCharacterRemove'\n---@class gloom_OnCharacterSaveExtensionPoint : gloom_ExtensionPoint\n---@class gloom_OnCharacterLoadExtensionPoint : gloom_ExtensionPoint\n---@class gloom_OnCharacterRemoveExtensionPoint : gloom_ExtensionPoint\n\nlocal OnCharacterSaveExtension = --[[---@type gloom_OnCharacterSaveExtensionPoint]] BaseExtensionPoint()\nlocal OnCharacterLoadExtension = --[[---@type gloom_OnCharacterLoadExtensionPoint]] BaseExtensionPoint()\nlocal OnCharacterRemoveExtension = --[[---@type gloom_OnCharacterRemoveExtensionPoint]] BaseExtensionPoint()\n\nfunction OnCharacterSaveExtension.updateParameters(parameters, result)\n if result then\n return { result, parameters[2] }\n end\n return parameters\nend\n\nfunction OnCharacterSaveExtension.accumulateResult(result, currentResult)\n return result or currentResult\nend\n\nExtensionPoints.register(\"onCharacterSave\", OnCharacterSaveExtension)\nExtensionPoints.register(\"onCharacterLoad\", OnCharacterLoadExtension)\nExtensionPoints.register(\"onCharacterRemove\", OnCharacterRemoveExtension)\n\nreturn CharacterExtensionPoint\n\nend)\n__bundle_register(\"extension.CampaignExtensionPoint\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ExtensionPoints = require(\"extension.ExtensionPoints\")\nlocal BaseExtensionPoint = require(\"extension.BaseExtensionPoint\")\n\nlocal CampaignExtensionPoint = {}\n\n---@alias Extension_Campaign 'onCampaignSave' | 'onCampaignLoad'\n---@class gloom_OnCampaignSaveExtensionPoint : gloom_ExtensionPoint\n---@class gloom_OnCampaignLoadExtensionPoint : gloom_ExtensionPoint\n\nlocal OnCampaignSaveExtension = --[[---@type gloom_OnCampaignSaveExtensionPoint]] BaseExtensionPoint()\nlocal OnCampaignLoadExtension = --[[---@type gloom_OnCampaignLoadExtensionPoint]] BaseExtensionPoint()\n\nfunction OnCampaignSaveExtension.updateParameters(parameters, result)\n if result then\n return { result }\n end\n return parameters\nend\n\nfunction OnCampaignSaveExtension.accumulateResult(result, currentResult)\n return result or currentResult\nend\n\nExtensionPoints.register(\"onCampaignSave\", OnCampaignSaveExtension)\nExtensionPoints.register(\"onCampaignLoad\", OnCampaignLoadExtension)\n\nreturn CampaignExtensionPoint\n\nend)\n__bundle_register(\"lib.TableUtil\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\n\r\nreturn TableUtil\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.TableUtil\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal GeTableUtils = require(\"ge_tts.TableUtils\")\r\nlocal StringUtil = require(\"sebaestschjin-tts.StringUtil\")\r\n\r\nlocal TableUtil = {}\r\n\r\n---@return number\r\nfunction TableUtil.length(tab)\r\n local len = 0\r\n for _, _ in TableUtil.pairs(tab) do\r\n len = len + 1\r\n end\r\n return len\r\nend\r\n\r\n--- Variant of pairs that also works for nil values.\r\n---@generic K, V\r\n---@param tab nil | table\r\n---@return fun(tab: table):K, V\r\nfunction TableUtil.pairs(tab)\r\n if tab then\r\n return pairs(--[[---@not nil]]tab)\r\n end\r\n return pairs(--[[---@type table]]{})\r\nend\r\n\r\n--- Variant of ipairs that also works for nil values.\r\n---@generic V\r\n---@param tab nil | V[]\r\n---@return fun(tab: V[]): number, V\r\nfunction TableUtil.ipairs(tab)\r\n if tab then\r\n return ipairs(--[[---@not nil]]tab)\r\n end\r\n return ipairs(--[[---@type V[] ]]{})\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@return K[]\r\nfunction TableUtil.keys(tab)\r\n local keys = {}\r\n for key, _ in TableUtil.pairs(tab) do\r\n table.insert(keys, key)\r\n end\r\n return keys\r\nend\r\n\r\n---@generic K, V\r\n---@param tab nil | table\r\n---@return V[]\r\nfunction TableUtil.values(tab)\r\n local values = {}\r\n\r\n for _, value in TableUtil.pairs(tab) do\r\n table.insert(values, value)\r\n end\r\n\r\n return values\r\nend\r\n\r\n---@generic K, V\r\n---@param table table\r\n---@param key K\r\n---@param default V\r\n---@return V\r\nfunction TableUtil.getOrElse(table, key, default)\r\n local value = table[key]\r\n if not value then\r\n value = default\r\n table[key] = value\r\n end\r\n\r\n return table[key]\r\nend\r\n\r\n--- Checks whether a table is empty. A nil value is also considered an empty table.\r\n---@param tab nil | table\r\n---@return boolean\r\nfunction TableUtil.isEmpty(tab)\r\n return not tab or not next(--[[---@not nil]]tab)\r\nend\r\n\r\n--- Checks whether a table is not empty. A nil value is always considered an empty table.\r\n---@param tab nil | table\r\n---@return boolean\r\nfunction TableUtil.isNotEmpty(tab)\r\n return not TableUtil.isEmpty(tab)\r\nend\r\n\r\n---@param a table\r\n---@param b table\r\n---@return boolean\r\nfunction TableUtil.areEqual(a, b)\r\n if TableUtil.length(a) ~= TableUtil.length(b) then\r\n return false\r\n end\r\n\r\n for key, value in pairs(a) do\r\n if type(value) == \"table\" then\r\n if not TableUtil.areEqual(value, b[key]) then\r\n return false\r\n end\r\n else\r\n if b[key] == nil or value ~= b[key] then\r\n return false\r\n end\r\n end\r\n end\r\n\r\n return true\r\nend\r\n\r\n--- Returns `true`, if any of the elements of the table satisfies the condition `func`.\r\n---@overload fun(tab: V[], func: fun(value: V): boolean): boolean\r\n---@overload fun(tab: table, func: fun(value: V, key: K): boolean): boolean\r\nfunction TableUtil.any(tab, func)\r\n for key, value in pairs(tab) do\r\n if func(value, key) then\r\n return true\r\n end\r\n end\r\n\r\n return false\r\nend\r\n\r\n--- Returns `true`, if all of the elements of the table satisfies the condition `func`.\r\n---@overload fun(tab: V[], func: fun(value: V): boolean): boolean\r\n---@overload fun(tab: table, func: fun(value: V, key: K): boolean): boolean\r\nfunction TableUtil.all(tab, func)\r\n for key, value in pairs(tab) do\r\n if not func(value, key) then\r\n return false\r\n end\r\n end\r\n\r\n return true\r\nend\r\n\r\n--- Returns `true`, if none of the elements of the table satisfies the condition `func`.\r\n---@overload fun(tab: V[], func: fun(value: V): boolean): boolean\r\n---@overload fun(tab: table, func: fun(value: V, key: K): boolean): boolean\r\nfunction TableUtil.none(tab, func)\r\n return not TableUtil.any(tab, func)\r\nend\r\n\r\n---@overload fun(tab: V[], value: V): boolean\r\n---@overload fun(tab: V[], value: V, comparator: fun(a: V, b: V): boolean): boolean\r\n---@overload fun(tab: table, value: V): boolean\r\n---@overload fun(tab: table, value: V, comparator: fun(a: V, b: V): boolean): boolean\r\nfunction TableUtil.contains(tab, value, comparator)\r\n if not tab then\r\n return false\r\n end\r\n\r\n comparator = comparator or function(a, b) return a == b end\r\n for _, v in pairs(tab) do\r\n if comparator(v, value) then\r\n return true\r\n end\r\n end\r\n return false\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@param key K\r\n---@return boolean\r\nfunction TableUtil.containsKey(tab, key)\r\n return tab[key] ~= nil\r\nend\r\n\r\n---@generic V\r\n---@param tab V[]\r\nfunction TableUtil.shuffle(tab)\r\n for i = #tab, 2, -1 do\r\n local j = math.random(1, i)\r\n tab[i], tab[j] = tab[j], tab[i]\r\n end\r\nend\r\n\r\n---@generic V\r\n---@param tab V[]\r\n---@return V\r\nfunction TableUtil.getRandom(tab)\r\n return tab[math.random(TableUtil.length(tab))]\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table | V[]\r\n---@param value V\r\n---@return nil | K\r\nfunction TableUtil.find(tab, value)\r\n return GeTableUtils.find(tab, value)\r\nend\r\n\r\n---@overload fun(tab: V[], func: fun(value: V, key: number): MappedV): MappedV[]\r\n---@generic K, V, MappedV\r\n---@param tab table\r\n---@param func fun(value: V, key: K): MappedV\r\n---@return table\r\nfunction TableUtil.map(tab, func)\r\n return GeTableUtils.map(tab, func)\r\nend\r\n\r\n---@overload fun(tab: table, func: fun(memo: R, value: V, key: K): R): nil | R\r\n---@generic K, V, R\r\n---@param tab table\r\n---@param initial R\r\n---@param func fun(memo: R, value: V, key: K): R\r\n---@return R\r\nfunction TableUtil.reduce(tab, initial, func)\r\n return GeTableUtils.reduce(tab, initial, func)\r\nend\r\n\r\n---@overload fun(finish: integer): integer[]\r\n---@param start integer\r\n---@param finish integer\r\n---@return integer[]\r\nfunction TableUtil.range(start, finish)\r\n if not finish then\r\n finish = start\r\n start = 1\r\n end\r\n\r\n local range = {}\r\n for i = start, finish do\r\n table.insert(range, i)\r\n end\r\n\r\n return range\r\nend\r\n\r\n---@overload fun(arr: std__Packed, start: number): std__Packed\r\n---@overload fun(arr: V[], start: number): V[]\r\n---@generic V\r\n---@param arr V[]\r\n---@param start number\r\n---@param finish number\r\n---@return V[]\r\nfunction TableUtil.slice(arr, start, finish)\r\n return GeTableUtils.range(arr, start, finish)\r\nend\r\n\r\n---@generic V\r\n---@param arr V[]\r\n---@param shift integer\r\n---@return V[]\r\nfunction TableUtil.shift(arr, shift)\r\n local shifted = --[[---@type V[] ]] {}\r\n local length = #arr\r\n\r\n for i = 1, length do\r\n local shiftedPosition = (i + shift) % length\r\n if shiftedPosition == 0 then\r\n shiftedPosition = length\r\n end\r\n shifted[i] = arr[shiftedPosition]\r\n end\r\n\r\n return shifted\r\nend\r\n\r\n---@overload fun(arr: V[], func: fun(value: V, index: number): boolean): V[]\r\n---@generic K, V\r\n---@param tab table\r\n---@param func fun(value: V, key: K): any\r\n---@return table\r\nfunction TableUtil.filter(tab, func)\r\n return GeTableUtils.select(tab, func)\r\nend\r\n\r\n---@overload fun(...: T): T\r\n---@vararg table\r\n---@return table\r\nfunction TableUtil.merge(...)\r\n return GeTableUtils.merge(...)\r\nend\r\n\r\n---@generic V\r\n---@param first V[]\r\n---@param second V[]\r\n---@return V[]\r\nfunction TableUtil.append(first, second)\r\n local result = {}\r\n\r\n for _, v in ipairs(first) do\r\n table.insert(result, v)\r\n end\r\n\r\n for _, v in ipairs(second) do\r\n table.insert(result, v)\r\n end\r\n\r\n return result\r\nend\r\n\r\n---@overload fun(tab: T): T\r\n---@generic T\r\n---@param tab T\r\n---@param recursive boolean @Default false\r\n---@return T\r\nfunction TableUtil.copy(tab, recursive)\r\n return GeTableUtils.copy(tab, recursive)\r\nend\r\n\r\n---@overload fun(tab: T): T\r\n---@generic T\r\n---@param tab T\r\n---@return T\r\nfunction TableUtil.deepCopy(tab)\r\n return GeTableUtils.copy(tab, true)\r\nend\r\n\r\n---@generic T\r\n---@param tab T\r\n---@return nil | T\r\nfunction TableUtil.emptyToNil(tab)\r\n if TableUtil.isEmpty(tab) then\r\n return nil\r\n end\r\n return tab\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@param key K\r\n---@return V\r\nfunction TableUtil.removeKey(tab, key)\r\n local element = tab[key]\r\n tab[key] = nil\r\n return element\r\nend\r\n\r\n---@generic V\r\n---@param tab V[]\r\n---@param value V\r\n---@return nil | V\r\nfunction TableUtil.removeValue(tab, value)\r\n local index = -1\r\n for i, v in ipairs(tab) do\r\n if v == value then\r\n index = i\r\n break\r\n end\r\n end\r\n if index > 0 then\r\n return table.remove(tab, index)\r\n end\r\n return nil\r\nend\r\n\r\n---@generic T\r\n---@param tab T[]\r\n---@param attributeName string\r\n---@return T[]\r\nfunction TableUtil.sortByAttribute(tab, attributeName)\r\n return table.sort(tab, function(l, r)\r\n return l[attributeName] < r[attributeName]\r\n end)\r\nend\r\n\r\n---@generic K\r\n---@param list K[]\r\n---@return set\r\nfunction TableUtil.listToSet(list)\r\n ---@type set\r\n local set = {}\r\n for _, value in ipairs(list) do\r\n set[value] = true\r\n end\r\n\r\n return set\r\nend\r\n\r\n---@generic K\r\n---@param set table\r\n---@return K[]\r\nfunction TableUtil.setToList(set)\r\n ---@type K[]\r\n local list = {}\r\n for entry, value in pairs(set) do\r\n if value then\r\n table.insert(list, entry)\r\n end\r\n end\r\n\r\n return list\r\nend\r\n\r\nlocal TYPE_STRINGIFIERS = {\r\n ['nil'] = function(_) return 'nil' end,\r\n boolean = function(v) return tostring(v) end,\r\n number = function(v) return tostring(v) end,\r\n string = function(v) return \"'\" .. v .. \"'\" end,\r\n userdata = function(_) return 'userdata' end,\r\n ['function'] = function(_) return 'function' end,\r\n thread = function(_) return 'thread' end,\r\n table = function(v) return tostring(v) end,\r\n}\r\n\r\n--- Taken from ge_tts.TableUtils with changed signature.\r\n---@overload fun(tab: table): string\r\n---@overload fun(tab: table, exclude: string[]): string\r\n---@param tab table\r\n---@param exclude string[]\r\n---@param depth number\r\n---@return string\r\nfunction TableUtil.dump(tab, exclude, depth)\r\n exclude = exclude or {}\r\n depth = depth or 1\r\n\r\n local isVector = TableUtil.length(tab) == 3 and tab.x ~= nil and tab.y ~= nil and tab.z ~= nil\r\n\r\n local indentation = string.rep(' ', depth)\r\n local str = '{'\r\n\r\n ---@type table\r\n local ordered_keys = {}\r\n\r\n for i, v in ipairs(--[[---@type any[] ]] tab) do\r\n ordered_keys[i] = true\r\n str = str .. '\\n' .. indentation .. '[' .. i .. '] = '\r\n\r\n if type(v) == 'table' then\r\n str = str .. TableUtil.dump(v, exclude, depth + 1) .. ','\r\n else\r\n str = str .. TYPE_STRINGIFIERS[type(v)](v) .. ','\r\n end\r\n end\r\n\r\n for k, v in pairs(tab) do\r\n if not ordered_keys[--[[---@type number]] k] and not TableUtil.containsKey(exclude, k) then\r\n local keyEntry\r\n if type(k) == \"string\" and StringUtil.isIdentifier(k) then\r\n keyEntry = k\r\n else\r\n keyEntry = '[' .. TYPE_STRINGIFIERS[type(k)](k) .. ']'\r\n end\r\n\r\n if isVector then\r\n str = str .. ' '\r\n else\r\n str = str .. '\\n' .. indentation\r\n end\r\n str = str .. keyEntry .. ' = '\r\n\r\n if type(v) == 'table' then\r\n str = str .. TableUtil.dump(v, exclude, depth + 1) .. ','\r\n else\r\n str = str .. TYPE_STRINGIFIERS[type(v)](v) .. ','\r\n end\r\n end\r\n end\r\n\r\n if isVector then\r\n str = str .. ' }'\r\n else\r\n str = str .. '\\n' .. string.rep(' ', depth - 1) .. '}'\r\n end\r\n\r\n return str\r\nend\r\n\r\nreturn TableUtil\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.StringUtil\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Base64 = require(\"ge_tts.Base64\")\r\n\r\nlocal StringUtil = {}\r\n\r\n---@type set\r\nlocal LuaKeywords = {\r\n [\"and\"] = true,\r\n [\"break\"] = true,\r\n [\"do\"] = true,\r\n [\"else\"] = true,\r\n [\"elseif\"] = true,\r\n [\"end\"] = true,\r\n [\"false\"] = true,\r\n [\"for\"] = true,\r\n [\"function\"] = true,\r\n [\"if\"] = true,\r\n [\"in\"] = true,\r\n [\"local\"] = true,\r\n [\"nil\"] = true,\r\n [\"not\"] = true,\r\n [\"or\"] = true,\r\n [\"repeat\"] = true,\r\n [\"return\"] = true,\r\n [\"then\"] = true,\r\n [\"true\"] = true,\r\n [\"until\"] = true,\r\n [\"while\"] = true,\r\n}\r\n\r\n---@param value nil | string\r\n---@return boolean\r\nfunction StringUtil.isEmpty(value)\r\n return value == nil or value == \"\"\r\nend\r\n\r\n---@param value nil | string\r\n---@return boolean\r\nfunction StringUtil.isNotEmpty(value)\r\n return not StringUtil.isEmpty(value)\r\nend\r\n\r\n---@overload fun(input: string, pattern: string): string\r\n---@param input string\r\n---@param pattern string\r\n---@param replacement string\r\n---@return string\r\nfunction StringUtil.replace(input, pattern, replacement)\r\n local r, _ = input:gsub(pattern, replacement or \"\")\r\n return r\r\nend\r\n\r\n---@overload fun(text: string, separator: string): string[]\r\n---@param text nil | string\r\n---@param separators string[]\r\n---@return string[]\r\nfunction StringUtil.split(text, separators)\r\n if not text then\r\n return {}\r\n end\r\n\r\n if type(separators) == \"string\" then\r\n separators = { --[[---@type string]] separators }\r\n end\r\n\r\n local parts = {}\r\n local separatorExpression = \"[^\" .. table.concat(separators, \"\") .. \"]+\"\r\n for part in string.gmatch(text, separatorExpression) do\r\n table.insert(parts, part)\r\n end\r\n return parts\r\nend\r\n\r\n--- Replaces whitespace at the start and end of the string.\r\n---@param text string\r\n---@return string\r\nfunction StringUtil.strip(text)\r\n return StringUtil.replace(StringUtil.replace(text, \"^%s+\"), \"%s+$\")\r\nend\r\n\r\n---@param text string\r\n---@return string\r\nfunction StringUtil.capitalize(text)\r\n local first = text:sub(1, 1):upper()\r\n return first .. text:sub(2)\r\nend\r\n\r\n---@param text string\r\n---@return string\r\nfunction StringUtil.capitalizeWords(text)\r\n local words = StringUtil.split(text, \" \")\r\n for i=1, #words do\r\n words[i] = StringUtil.capitalize(words[i])\r\n end\r\n return table.concat(words, \" \")\r\nend\r\n\r\n---@param text string\r\n---@return string\r\nfunction StringUtil.escapePattern(text)\r\n local escaped, _ = text:gsub(\"([-+()%[%]])\", \"%%%1\")\r\n return escaped\r\nend\r\n\r\n---@param text string\r\n---@return number[]\r\nfunction StringUtil.bytes(text)\r\n local bytes = --[[---@type number[] ]]{}\r\n\r\n for i = 1, #text do\r\n table.insert(bytes, text:byte(i))\r\n end\r\n\r\n return bytes\r\nend\r\n\r\nfunction StringUtil.chars(bytes)\r\n local text = \"\"\r\n\r\n for _, byte in pairs(bytes) do\r\n text = text .. string.char(byte)\r\n end\r\n\r\n return text\r\nend\r\n\r\n---@param text any\r\n---@return boolean\r\nfunction StringUtil.isGuid(text)\r\n return type(text) == \"string\"\r\n and text:find(\"^[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]$\") ~= nil\r\nend\r\n\r\n---@param text string\r\n---@return boolean\r\nfunction StringUtil.isBase64(text)\r\n return text:find(\"^[a-zA-Z0-9+/]+=?=?$\") ~= nil\r\nend\r\n\r\n---@param text string\r\n---@return boolean\r\nfunction StringUtil.isKeyword(text)\r\n return LuaKeywords[text]\r\nend\r\n\r\n---@param text string\r\n---@return boolean\r\nfunction StringUtil.isIdentifier(text)\r\n return not StringUtil.isKeyword(text) and text:find(\"^[_a-zA-Z][_a-zA-Z0-9]*$\") ~= nil\r\nend\r\n\r\n---@param value string\r\n---@return string\r\nfunction StringUtil.encodeBase64(value)\r\n return Base64.encode(StringUtil.bytes(value))\r\nend\r\n\r\n---@param value string\r\n---@return string\r\nfunction StringUtil.decodeBase64(value)\r\n return StringUtil.chars(Base64.decode(value))\r\nend\r\n\r\n---@param value string\r\n---@param others string[]\r\n---@param maxDistance number\r\n---@return nil | string\r\nfunction StringUtil.findNearest(value, others, maxDistance)\r\n local minDistance, otherValue\r\n\r\n for _, other in pairs(others) do\r\n local distance = StringUtil.distance(value, other)\r\n if not minDistance or minDistance > distance then\r\n minDistance = distance\r\n otherValue = other\r\n end\r\n end\r\n\r\n if minDistance and minDistance > maxDistance then\r\n return nil\r\n end\r\n return otherValue\r\nend\r\n\r\n---@param first string\r\n---@param second string\r\n---@return number\r\nfunction StringUtil.distance(first, second)\r\n local firstLength, secondLength = #first, #second\r\n\r\n if firstLength == 0 then\r\n return secondLength\r\n end\r\n if secondLength == 0 then\r\n return firstLength\r\n end\r\n if first == second then\r\n return 0\r\n end\r\n\r\n local firstBytes = StringUtil.bytes(first)\r\n local secondBytes = StringUtil.bytes(second)\r\n\r\n local matrix = --[[---@type number[][] ]]{}\r\n for i = 0, firstLength do\r\n matrix[i] = { [0] = i }\r\n end\r\n for j = 0, secondLength do\r\n matrix[0][j] = j\r\n end\r\n\r\n for i = 1, firstLength do\r\n for j = 1, secondLength do\r\n if firstBytes[i] == secondBytes[j] then\r\n matrix[i][j] = matrix[i - 1][j - 1]\r\n else\r\n matrix[i][j] = math.min(matrix[i - 1][j] + 1,\r\n matrix[i][j - 1] + 1,\r\n matrix[i - 1][j - 1] + 1)\r\n end\r\n end\r\n end\r\n\r\n return matrix[firstLength][secondLength]\r\nend\r\n\r\nreturn StringUtil\r\n\nend)\n__bundle_register(\"ge_tts.Base64\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\n-- Base64 implementation originally based on https://github.com/iskolbin/lbase64 (public domain),\r\n-- but modified for simplicity, TTS and to work with number[] buffers, rather than strings.\r\n\r\nlocal TableUtils = require(\"ge_tts.TableUtils\")\r\n\r\n---@class ge_tts__Base64\r\nlocal Base64 = {}\r\n\r\nlocal extract = bit32.extract\r\n\r\nlocal PAD_KEY = 64\r\n\r\n---@overload fun(char62: string, char63: string): table\r\n---@overload fun(char62: string): table\r\n---@overload fun(): table\r\n---@param char62 string\r\n---@param char63 string\r\n---@param charPad string\r\n---@return table\r\nfunction Base64.encodingMap(char62, char63, charPad)\r\n ---@type table\r\n local encodingTable = {}\r\n\r\n for b64code, char in pairs({\r\n [0] = 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',\r\n 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',\r\n 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',\r\n 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',\r\n '3', '4', '5', '6', '7', '8', '9', char62 or '+', char63 or '/', charPad or '='\r\n }) do\r\n encodingTable[b64code] = char:byte()\r\n end\r\n\r\n return encodingTable\r\nend\r\n\r\n---@overload fun(char62: string, char63: string): table\r\n---@overload fun(char62: string): table\r\n---@overload fun(): table\r\n---@param char62 string\r\n---@param char63 string\r\n---@param charPad string\r\n---@return table\r\nfunction Base64.decodingMap(char62, char63, charPad)\r\n return TableUtils.invert(Base64.encodingMap(char62, char63, charPad))\r\nend\r\n\r\nlocal DEFAULT_ENCODING_MAP = Base64.encodingMap()\r\nlocal DEFAULT_DECODING_MAP = Base64.decodingMap()\r\n\r\n---@overload fun(buffer: number[], pad: boolean): string\r\n---@overload fun(buffer: number[]): string\r\n---@param buffer number[]\r\n---@param pad boolean\r\n---@param map table\r\n---@return string\r\nfunction Base64.encode(buffer, pad, map)\r\n pad = pad == nil or pad\r\n map = map or DEFAULT_ENCODING_MAP\r\n\r\n ---@type string[]\r\n local components = {}\r\n local index = 1\r\n local length = #buffer\r\n local lastComponentSize = length % 3\r\n\r\n for offset = 1, length - lastComponentSize, 3 do\r\n local a, b, c = --[[---@not nil, nil, nil]] table.unpack(buffer, offset, offset + 2)\r\n local v = a * 0x10000 + b * 0x100 + c\r\n\r\n components[index] = string.char(map[extract(v, 18, 6)], map[extract(v, 12, 6)], map[extract(v, 6, 6)], map[extract(v, 0, 6)])\r\n index = index + 1\r\n end\r\n\r\n if lastComponentSize == 2 then\r\n local a, b = --[[---@not nil, nil]] table.unpack(buffer, length - 1, length)\r\n local v = a * 0x10000 + b * 0x100\r\n\r\n components[index] = string.char(map[extract(v, 18, 6)], map[extract(v, 12, 6)], map[extract(v, 6, 6)]) .. (pad and string.char(map[PAD_KEY]) or '')\r\n elseif lastComponentSize == 1 then\r\n local v = buffer[length] * 0x10000\r\n\r\n components[index] = string.char(map[extract(v, 18, 6)], map[extract(v, 12, 6)]) .. (pad and string.char(map[PAD_KEY], map[PAD_KEY]) or '')\r\n end\r\n\r\n return table.concat(components)\r\nend\r\n\r\n---@overload fun(b64: string): number[]\r\n---@param b64 string\r\n---@param map table\r\n---@return number[]\r\nfunction Base64.decode(b64, map)\r\n map = map or DEFAULT_DECODING_MAP\r\n\r\n ---@type number[]\r\n local buffer = {}\r\n local offset = 1\r\n\r\n local length = #b64\r\n\r\n if map[--[[---@not nil]] b64:sub(-2, -2):byte()] == PAD_KEY then\r\n length = length - 2\r\n elseif map[--[[---@not nil]] b64:sub(-1, -1):byte()] == PAD_KEY then\r\n length = length - 1\r\n end\r\n\r\n local lastBlockSize = length % 4\r\n local fullBlockEnd = length - lastBlockSize\r\n\r\n for i = 1, fullBlockEnd, 4 do\r\n local a, b, c, d = --[[---@not nil, nil, nil, nil]] b64:byte(i, i + 3)\r\n\r\n local v = map[a] * 0x40000 + map[b] * 0x1000 + map[c] * 0x40 + map[d]\r\n\r\n buffer[offset] = extract(v, 16, 8)\r\n buffer[offset + 1] = extract(v, 8, 8)\r\n buffer[offset + 2] = extract(v, 0, 8)\r\n\r\n offset = offset + 3\r\n end\r\n\r\n\r\n if lastBlockSize == 3 then\r\n local a, b, c = --[[---@not nil, nil, nil]] b64:byte(fullBlockEnd + 1, fullBlockEnd + 3)\r\n local v = map[a] * 0x40000 + map[b] * 0x1000 + map[c] * 0x40\r\n\r\n buffer[offset] = extract(v, 16, 8)\r\n buffer[offset + 1] = extract(v, 8, 8)\r\n elseif lastBlockSize == 2 then\r\n local a, b = --[[---@not nil, nil]] b64:byte(fullBlockEnd + 1, fullBlockEnd + 2)\r\n local v = map[a] * 0x40000 + map[b] * 0x1000\r\n\r\n buffer[offset] = extract(v, 16, 8)\r\n end\r\n\r\n return buffer\r\nend\r\n\r\nreturn Base64\r\n\nend)\n__bundle_register(\"ge_tts.TableUtils\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- Omitted to prevent cyclic require:\r\n-- require('ge.tts/License')\r\n\r\n-- This module operates on tables that contain only positive consecutive integer keys starting at 1 (i.e. a plain array), as well as tables which contain no\r\n-- array component. Behavior is undefined for tables that contain a key for [1] as well as non-consecutive integer or non-integer keys.\r\n\r\n---@generic T\r\n---@param length number\r\n---@return fun(arr: std__Packed, i: number): nil | (number, T)\r\nlocal function fixedLengthIterator(length)\r\n ---@type fun(arr: std__Packed, i: number): nil | (number, T)\r\n return function(arr, i)\r\n i = i + 1\r\n if i <= length then\r\n return i, arr[i]\r\n end\r\n end\r\nend\r\n\r\n---@overload fun>(arr: A): (fun(arr: A, i: number): number, V), V[], 0\r\n---@overload fun(arr: V[]): (fun(arr: V[], i: number): number, V), V[], 0\r\n---@generic K, V\r\n---@param tab table\r\n---@return (fun(tab: table, k: K): nil | (K, V)), table, K\r\nlocal function iterate(tab)\r\n local fixedLength = (--[[---@type std__Packed]] tab).n\r\n\r\n if type(fixedLength) == 'number' and fixedLength >= 0 then\r\n return --[[---@type fun(tab: table, k: K): nil | (K, V)]] fixedLengthIterator(fixedLength), tab, --[[---@type K]] 0\r\n elseif tab[--[[---@type K]] 1] ~= nil then\r\n return --[[---@type (fun(tab: table, k: K): nil | (K, V)), table, K]] ipairs(--[[---@type V[] ]] tab)\r\n else\r\n return pairs(tab)\r\n end\r\nend\r\n\r\n---@class ge_tts__TableUtils\r\nlocal TableUtils = {}\r\n\r\n--- Returns true if TableUtils will interpret the table as an array i.e. if tab[1] ~= nil or\r\n--- type(tab.n) == 'number'.\r\n---\r\n--- If tab is an array, and it's passed to a TableUtils function that iterates over tab calling a callback, the\r\n--- iteration over keys is guaranteed to take place in sequential order (\u00e0 la ipairs).\r\n---\r\n--- In the case of type(tab.n) == 'number', tab.n will be treated as the length of the array and TableUtils will\r\n--- continue iterating over \"holes\" (nil values) up to this length.\r\n---@overload fun(tab: V[]): true\r\n---@overload fun(tab: std__Packed): true\r\n---@overload fun(tab: table): false\r\n---@overload fun(tab: table): false\r\n---@overload fun(tab: table): false\r\n---@overload fun(tab: table): false\r\n---@param tab table\r\n---@return boolean\r\nfunction TableUtils.isArray(tab)\r\n return tab[1] ~= nil or type((--[[---@type std__Packed]] tab).n) == 'number'\r\nend\r\n\r\n--- Returns the length of arr and a boolean indicating whether arr is a std__Packed.\r\n---@generic V\r\n---@param arr V[] | std__Packed\r\n---@return number, boolean\r\nfunction TableUtils.arrayLength(arr)\r\n local fixedLength = (--[[---@type std__Packed]] arr).n\r\n local isFixed = type(fixedLength) == 'number'\r\n return isFixed and fixedLength or #arr, isFixed\r\nend\r\n\r\n---@overload fun(tab: V[], func: fun(value: V, key: number): MappedV): MappedV[]\r\n---@generic K, V, MappedV\r\n---@param tab table\r\n---@param func fun(value: V, key: K): MappedV\r\n---@return table\r\nfunction TableUtils.map(tab, func)\r\n ---@type table\r\n local mapped = {}\r\n\r\n for k, v in iterate(tab) do\r\n mapped[k] = func(v, k)\r\n end\r\n\r\n return mapped\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@return table\r\nfunction TableUtils.invert(tab)\r\n ---@type table\r\n local inverted = {}\r\n\r\n for k, v in pairs(tab) do\r\n inverted[v] = k\r\n end\r\n\r\n return inverted\r\nend\r\n\r\n---@generic K, V, RemappedK\r\n---@param tab table\r\n---@param func fun(value: V, key: K): RemappedK\r\n---@return table\r\nfunction TableUtils.remap(tab, func)\r\n ---@type table\r\n local remapped = {}\r\n\r\n for k, v in iterate(tab) do\r\n remapped[func(v, k)] = v\r\n end\r\n\r\n return remapped\r\nend\r\n\r\n---@overload fun(arr: V[], func: fun(value: V, index: number): boolean): V[]\r\n---@generic K, V\r\n---@param tab table\r\n---@param func fun(value: V, key: K): boolean\r\n---@return table\r\nfunction TableUtils.select(tab, func)\r\n ---@type table\r\n local selected = {}\r\n\r\n if TableUtils.isArray(tab) then\r\n local i = 0\r\n\r\n for k, v in iterate(tab) do\r\n if func(v, k) then\r\n i = i + 1\r\n (--[[---@type V[] ]] selected)[i] = v\r\n end\r\n end\r\n else\r\n for k, v in pairs(tab) do\r\n if func(v, k) then\r\n selected[k] = v\r\n end\r\n end\r\n end\r\n\r\n return selected\r\nend\r\n\r\n---@overload fun(arr: V[], func: fun(value: V, index: number): boolean): V[]\r\n---@generic K, V\r\n---@param tab table\r\n---@param func fun(value: V, key: K): boolean\r\n---@return table\r\nfunction TableUtils.reject(tab, func)\r\n return TableUtils.select(tab, function(v, k) return not func(v, k) end)\r\nend\r\n\r\n---@overload fun(tab: table, func: fun(memo: R, value: V, key: K): R): nil | R\r\n---@generic K, V, R\r\n---@param tab table\r\n---@param initial R\r\n---@param func fun(memo: R, value: V, key: K): R\r\n---@return R\r\nfunction TableUtils.reduce(tab, initial, func)\r\n local iterator, _, initialK = iterate(tab)\r\n\r\n ---@type R\r\n local memo\r\n\r\n ---@type fun(memo: R, value: V, key: K): R\r\n local reducer\r\n\r\n if func then\r\n memo = initial\r\n reducer = func\r\n else\r\n local control, value = iterator(tab, initialK)\r\n\r\n if control == nil then\r\n -- Overload may return nil\r\n return --[[---@type any]] nil\r\n end\r\n\r\n initialK = --[[---@not nil]] control\r\n memo = --[[---@type R]] value\r\n reducer = --[[---@type fun(memo: R, value: V, key: K): R]] initial\r\n end\r\n\r\n if not func then\r\n initialK = --[[---@type K]] memo\r\n end\r\n\r\n for k, v in iterator, tab, initialK do\r\n memo = reducer(memo, v, k)\r\n end\r\n\r\n return memo\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@param value any\r\n---@return nil | K\r\nfunction TableUtils.find(tab, value)\r\n for k, v in iterate(tab) do\r\n if v == value then\r\n return k\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@param func fun(value: V, key: K): boolean\r\n---@return (nil, nil) | (V, K)\r\nfunction TableUtils.detect(tab, func)\r\n for k, v in iterate(tab) do\r\n if func(v, k) then\r\n return v, k\r\n end\r\n end\r\n\r\n return nil, nil\r\nend\r\n\r\n---@overload fun(tab: T): T\r\n---@generic T\r\n---@param tab T\r\n---@param recursive boolean\r\n---@return T\r\nfunction TableUtils.copy(tab, recursive)\r\n ---@type table\r\n local copied = {}\r\n\r\n for k, v in pairs(--[[---@type table]] tab) do\r\n copied[k] = (recursive and type(v) == 'table' and\r\n TableUtils.copy(--[[---@type table]] v, true)\r\n ) or v\r\n end\r\n\r\n return --[[---@type T]] copied\r\nend\r\n\r\n---@overload fun(arr: V[], ...: V[]): void\r\n---@overload fun(arr: std__Packed, ...: V[] | std__Packed): void\r\n---@generic K, V\r\n---@param tab table\r\n---@vararg table\r\n---@return void\r\nfunction TableUtils.inject(tab, ...)\r\n local otherTables = { ... }\r\n\r\n if TableUtils.isArray(tab) then\r\n local arr = --[[---@type V[] | std__Packed]] tab\r\n local i, isFixed = TableUtils.arrayLength(arr)\r\n\r\n for _, t in ipairs(otherTables) do\r\n for _, v in iterate(--[[---@type V[] ]] t) do\r\n i = i + 1\r\n arr[i] = v\r\n end\r\n end\r\n\r\n if isFixed then\r\n (--[[---@type std__Packed]] tab).n = i\r\n end\r\n else\r\n for _, t in ipairs(otherTables) do\r\n for k, v in pairs(t) do\r\n tab[k] = v\r\n end\r\n end\r\n end\r\nend\r\n\r\n---@overload fun(...: T): T\r\n---@vararg table\r\n---@return table\r\nfunction TableUtils.merge(...)\r\n local merged = {}\r\n TableUtils.inject(merged, ...)\r\n return merged\r\nend\r\n\r\n---@overload fun(arrays: std__Packed[]): std__Packed, number\r\n---@generic V\r\n---@param arrays V[][]\r\n---@return V[], number\r\nfunction TableUtils.flatten(arrays)\r\n ---@type V[]\r\n local flattened = {}\r\n local i = 0\r\n\r\n for _, array in ipairs(arrays) do\r\n for _, v in iterate(array) do\r\n i = i + 1\r\n flattened[i] = v\r\n end\r\n end\r\n\r\n\r\n if i > 0 and type((--[[---@type std__Packed]] arrays[1]).n) == 'number' then\r\n (--[[---@type std__Packed]] flattened).n = i\r\n end\r\n\r\n return flattened, i\r\nend\r\n\r\n---@generic K, V\r\n---@param tab table\r\n---@return K[]\r\nfunction TableUtils.keys(tab)\r\n ---@type K[]\r\n local keys = {}\r\n\r\n for k, _ in pairs(tab) do\r\n table.insert(keys, k)\r\n end\r\n\r\n return keys\r\nend\r\n\r\n---@overload fun(arr: std__Packed): std__Packed\r\n---@generic K, V\r\n---@param tab table\r\n---@return V[], number\r\nfunction TableUtils.values(tab)\r\n ---@type V[]\r\n local values = {}\r\n local i = 0\r\n\r\n for _, v in iterate(tab) do\r\n i = i + 1\r\n values[i] = v\r\n end\r\n\r\n if type((--[[---@type std__Packed]] tab).n) == 'number' then\r\n (--[[---@type std__Packed]] values).n = i\r\n end\r\n\r\n return values, i\r\nend\r\n\r\n---@param tab table\r\n---@return number\r\nfunction TableUtils.count(tab)\r\n local count = 0\r\n\r\n for _, _ in pairs(tab) do\r\n count = count + 1\r\n end\r\n\r\n return count\r\nend\r\n\r\n---@overload fun(arr: std__Packed): std__Packed\r\n---@generic V\r\n---@param arr V[]\r\n---@return V[]\r\nfunction TableUtils.reverse(arr)\r\n ---@type V[]\r\n local reversed = {}\r\n\r\n local length, isFixed = TableUtils.arrayLength(arr)\r\n local j = 1\r\n\r\n for i = length, 1, -1 do\r\n reversed[j] = arr[i]\r\n j = j + 1\r\n end\r\n\r\n if isFixed then\r\n (--[[---@type std__Packed]] reversed).n = length\r\n end\r\n\r\n return reversed\r\nend\r\n\r\n---@overload fun(arr: std__Packed, start: number): std__Packed\r\n---@overload fun(arr: V[], start: number): V[]\r\n---@generic V\r\n---@param arr V[]\r\n---@param start number\r\n---@param finish number\r\n---@return V[]\r\nfunction TableUtils.range(arr, start, finish)\r\n ---@type V[]\r\n local range = {}\r\n\r\n for i in fixedLengthIterator(finish or TableUtils.arrayLength(arr)), arr, start - 1 do\r\n range[i - start + 1] = arr[i]\r\n end\r\n\r\n if type((--[[---@type std__Packed]] arr).n) == 'number' then\r\n (--[[---@type std__Packed]] range).n = finish - start + 1\r\n end\r\n\r\n return range\r\nend\r\n\r\n---@overload fun(arr: std__Packed): std__Packed\r\n---@generic V\r\n---@param arr V[]\r\n---@return V[], number\r\nfunction TableUtils.unique(arr)\r\n ---@type V[]\r\n local unique = {}\r\n local i = 0\r\n\r\n for _, value in ipairs(arr) do\r\n if not TableUtils.find(unique, value) then\r\n i = i + 1\r\n unique[i] = value\r\n end\r\n end\r\n\r\n return unique, i\r\nend\r\n\r\nlocal TYPE_STRINGIFIERS = {\r\n ['nil'] = function(_) return 'nil' end,\r\n boolean = function(v) return tostring(v) end,\r\n number = function(v) return tostring(v) end,\r\n string = function(v) return \"'\" .. v .. \"'\" end,\r\n userdata = function(_) return 'userdata' end,\r\n ['function'] = function(_) return 'function' end,\r\n thread = function(_) return 'thread' end,\r\n table = function(v) return tostring(v) end,\r\n}\r\n\r\n---@overload fun(tab: table): string\r\n---@overload fun(tab: table, recursive: boolean): string\r\n---@param tab table\r\n---@param recursive boolean\r\n---@param depth number\r\n---@return string\r\nfunction TableUtils.dump(tab, recursive, depth)\r\n depth = depth or 1\r\n\r\n local indentation = string.rep(' ', depth)\r\n local str = '{'\r\n\r\n ---@type table\r\n local ordered_keys = {}\r\n\r\n for i, v in ipairs(--[[---@type any[] ]] tab) do\r\n ordered_keys[i] = true\r\n str = str .. '\\n' .. indentation .. '[' .. i .. '] = '\r\n\r\n if recursive and type(v) == 'table' then\r\n str = str .. TableUtils.dump(v, true, depth + 1) .. ','\r\n else\r\n local a = TYPE_STRINGIFIERS['nil']\r\n str = str .. TYPE_STRINGIFIERS[type(v)](v) .. ','\r\n end\r\n end\r\n\r\n for k, v in pairs(tab) do\r\n if not ordered_keys[--[[---@type number]] k] then\r\n str = str .. '\\n' .. indentation .. '[' .. TYPE_STRINGIFIERS[type(k)](k) .. '] = '\r\n\r\n if recursive and type(v) == 'table' then\r\n str = str .. TableUtils.dump(v, true, depth + 1) .. ','\r\n else\r\n str = str .. TYPE_STRINGIFIERS[type(v)](v) .. ','\r\n end\r\n end\r\n end\r\n\r\n str = str .. '\\n' .. string.rep(' ', depth - 1) .. '}'\r\n\r\n return str\r\nend\r\n\r\nreturn TableUtils\r\n\nend)\n__bundle_register(\"ge_tts.License\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtils = require(\"ge_tts.TableUtils\")\r\n\r\n-- This license applies to ge_tts. Do *not* assume it extends to the mod!\r\n---@type table\r\nlocal licenses = {\r\n ge_tts = [[Copyright (c) 2019 Benjamin Dobell, Glass Echidna\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.\r\n]],\r\n}\r\n\r\nlocal License = {}\r\n\r\n---@param library string\r\n---@param license string\r\n---@return boolean\r\nfunction License.add(library, license)\r\n if licenses[library] then\r\n return false\r\n end\r\n\r\n licenses[library] = license\r\n return true\r\nend\r\n\r\n---@param library string\r\n---@return nil | string\r\nfunction License.get(library)\r\n return licenses[library]\r\nend\r\n\r\n---@return string[]\r\nfunction License.getLibraries()\r\n return TableUtils.keys(licenses)\r\nend\r\n\r\nreturn License\r\n\nend)\n__bundle_register(\"lib.Logger\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"sebaestschjin-tts.Logger\")\r\n\r\nreturn Logger\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Logger\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtils = require(\"ge_tts.TableUtils\")\r\nlocal GeLogger = require(\"ge_tts.Logger\")\r\n\r\n---@class seb_Logger : ge_tts__Logger\r\n\r\n---@class seb_Logger_static\r\n---@overload fun(): seb_Logger\r\nlocal Logger = {}\r\n\r\nLogger.ERROR = GeLogger.ERROR\r\nLogger.WARNING = GeLogger.WARNING\r\nLogger.INFO = GeLogger.INFO\r\nLogger.DEBUG = GeLogger.DEBUG\r\nLogger.VERBOSE = GeLogger.VERBOSE\r\n\r\n---@type table\r\nlocal levelPrefixes = {\r\n [GeLogger.ERROR] = 'ERROR: ',\r\n [GeLogger.WARNING] = 'WARNING: ',\r\n [GeLogger.INFO] = 'INFO: ',\r\n [GeLogger.DEBUG] = 'DEBUG: ',\r\n [GeLogger.VERBOSE] = 'VERBOSE: ',\r\n}\r\n\r\n---@type table\r\nlocal levelColors = {\r\n [GeLogger.ERROR] = 'Red',\r\n [GeLogger.WARNING] = 'Yellow',\r\n [GeLogger.INFO] = 'Blue',\r\n}\r\n\r\n--- Logger instances registered on other objects.\r\n---@type GUID[]\r\nlocal objectLoggers = {}\r\n\r\nsetmetatable(Logger, TableUtils.merge(getmetatable(GeLogger), {\r\n __call = function()\r\n local self = GeLogger()\r\n\r\n ---@param message string\r\n ---@param level ge_tts__Logger_LogLevel\r\n function self.log(message, level)\r\n printToAll(levelPrefixes[level] .. message, levelColors[level])\r\n end\r\n\r\n return self\r\n end,\r\n __index = GeLogger,\r\n}))\r\n\r\nlocal logger = Logger()\r\n\r\nlocal function buildMessage(...)\r\n local args = table.pack(...)\r\n for i = 1, args.n do\r\n args[i] = logString(args[i])\r\n end\r\n\r\n local success, result = pcall(function()\r\n return string.format(table.unpack(args))\r\n end)\r\n\r\n if success then\r\n return result\r\n end\r\n\r\n return table.unpack(args)\r\nend\r\n\r\n---@param functionName string\r\nlocal function notifyObjectLoggers(functionName, parameter)\r\n if self == Global then\r\n for i = #objectLoggers, 1, -1 do\r\n local objectLogger = objectLoggers[i]\r\n local obj = getObjectFromGUID(objectLogger)\r\n if obj then\r\n (--[[---@not nil]] obj).call(functionName, parameter)\r\n else\r\n table.remove(objectLoggers, i)\r\n end\r\n end\r\n end\r\nend\r\n\r\n---@param level ge_tts__Logger_LogLevel\r\nfunction Logger.setLevel(level)\r\n logger.setFilterLevel(level)\r\n notifyObjectLoggers(\"__set_logger_level\", level)\r\nend\r\n\r\nfunction Logger.error(...)\r\n if logger.getFilterLevel() >= GeLogger.ERROR then\r\n logger.log(buildMessage(...), GeLogger.ERROR)\r\n end\r\nend\r\n\r\nfunction Logger.warn(...)\r\n if logger.getFilterLevel() >= GeLogger.WARNING then\r\n logger.log(buildMessage(...), GeLogger.WARNING)\r\n end\r\nend\r\n\r\nfunction Logger.info(...)\r\n if logger.getFilterLevel() >= GeLogger.INFO then\r\n logger.log(buildMessage(...), GeLogger.INFO)\r\n end\r\nend\r\n\r\nfunction Logger.debug(...)\r\n if logger.getFilterLevel() >= GeLogger.DEBUG then\r\n logger.log(buildMessage(...), GeLogger.DEBUG)\r\n end\r\nend\r\n\r\nfunction Logger.verbose(...)\r\n if logger.getFilterLevel() >= GeLogger.VERBOSE then\r\n logger.log(buildMessage(...), GeLogger.VERBOSE)\r\n end\r\nend\r\n\r\nfunction Logger.assert(value, ...)\r\n if not value then\r\n logger.assert(value, buildMessage(...))\r\n end\r\nend\r\n\r\nif self == Global then\r\n ---@param guid GUID\r\n _G.__register_object_logger = function(guid)\r\n table.insert(objectLoggers, guid)\r\n end\r\n _G.__logger_exists = true\r\nelse\r\n if Global.getVar(\"__logger_exists\") then\r\n Global.call(\"__register_object_logger\", self.getGUID())\r\n end\r\n _G.__set_logger_level = function(level)\r\n Logger.setLevel(level)\r\n end\r\nend\r\n\r\nreturn Logger\r\n\nend)\n__bundle_register(\"ge_tts.Logger\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\n---@class ge_tts__Logger\r\n\r\n---@class ge_tts__static_Logger\r\n---@overload fun(): ge_tts__Logger\r\nlocal Logger = {}\r\n\r\nLogger.ERROR = 1\r\nLogger.WARNING = 2\r\nLogger.INFO = 3\r\nLogger.DEBUG = 4\r\nLogger.VERBOSE = 5\r\n\r\n---@alias ge_tts__Logger_LogLevel 1 | 2 | 3 | 4 | 5\r\n\r\n---@type table\r\nlocal levelPrefixes = {\r\n [Logger.ERROR] = 'ERROR: ',\r\n [Logger.WARNING] = 'WARNING: ',\r\n [Logger.INFO] = '',\r\n [Logger.DEBUG] = '',\r\n [Logger.VERBOSE] = '',\r\n}\r\n\r\n---@type ge_tts__Logger_LogLevel\r\nlocal defaultLogLevel = Logger.DEBUG\r\n\r\nsetmetatable(Logger, {\r\n __call = function()\r\n local self = --[[---@type ge_tts__Logger]] {}\r\n\r\n ---@type ge_tts__Logger_LogLevel\r\n local filterLevel = Logger.INFO\r\n\r\n ---@return ge_tts__Logger_LogLevel\r\n function self.getFilterLevel()\r\n return filterLevel\r\n end\r\n\r\n ---@param level ge_tts__Logger_LogLevel | `Logger.ERROR` | `Logger.WARNING` | `Logger.INFO` | `Logger.DEBUG` | `Logger.VERBOSE`\r\n function self.setFilterLevel(level)\r\n filterLevel = level\r\n end\r\n\r\n ---@overload fun(message: string): void\r\n ---@param message string\r\n ---@param level ge_tts__Logger_LogLevel | `Logger.ERROR` | `Logger.WARNING` | `Logger.INFO` | `Logger.DEBUG` | `Logger.VERBOSE`\r\n function self.log(message, level)\r\n level = level or defaultLogLevel\r\n if level <= filterLevel then\r\n print(levelPrefixes[level] .. message)\r\n end\r\n end\r\n\r\n ---\r\n ---If value is false, logs message at level Logger.ERROR and then calls Lua's in-built error(message).\r\n ---\r\n ---@param value any\r\n ---@param message string\r\n function self.assert(value, message)\r\n if not value then\r\n self.log(message, Logger.ERROR)\r\n error(message, 2)\r\n end\r\n end\r\n\r\n return self\r\n end\r\n})\r\n\r\nlocal defaultLogger = Logger()\r\n\r\n---@param logger ge_tts__Logger\r\nfunction Logger.setDefaultLogger(logger)\r\n defaultLogger = logger\r\nend\r\n\r\nfunction Logger.getDefaultLogger()\r\n return defaultLogger\r\nend\r\n\r\n---\r\n---When calling log() without specifying a log level, messages will log at the provided log level.\r\n---\r\n---@param level ge_tts__Logger_LogLevel | `Logger.ERROR` | `Logger.WARNING` | `Logger.INFO` | `Logger.DEBUG` | `Logger.VERBOSE`\r\nfunction Logger.setDefaultLogLevel(level)\r\n defaultLogLevel = level\r\nend\r\n\r\n---\r\n---Returns the default log level.\r\n---\r\n---@return ge_tts__Logger_LogLevel\r\nfunction Logger.getDefaultLogLevel()\r\n return defaultLogLevel\r\nend\r\n\r\n---\r\n---Logs a message at the specified log level. If level is omitted, the default log level will be used.\r\n---\r\n---@overload fun(message: string): void\r\n---@param message string\r\n---@param level ge_tts__Logger_LogLevel | `Logger.ERROR` | `Logger.WARNING` | `Logger.INFO` | `Logger.DEBUG` | `Logger.VERBOSE`\r\nfunction Logger.log(message, level)\r\n level = level or defaultLogLevel\r\n defaultLogger.log(message, level)\r\nend\r\n\r\n---\r\n---If value is false, logs message at level Logger.ERROR using the default logger, and then calls Lua's error(message).\r\n---\r\n---@param value any\r\n---@param message string\r\nfunction Logger.assert(value, message)\r\n if not value then\r\n defaultLogger.log(message, Logger.ERROR)\r\n error(message, 2)\r\n end\r\nend\r\n\r\nreturn Logger\r\n\nend)\n__bundle_register(\"mechanic.attackmodifier.AttackModifierMechanic\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\n\nlocal AttackModifierDeck = require(\"mechanic.attackmodifier.AttackModifierDeck\")\nlocal R = require(\"api.Resource\")\n\nlocal AttackModifierMechanic = {}\n\n---@class __AttackModifierMechanic_this\nlocal this = {}\n\n---@alias AttackModifierDeck_Source gloom_CharacterInfo | 'Monster'\n\n---@overload fun()\n---@param source AttackModifierDeck_Source\nfunction AttackModifierMechanic.drawCard(source)\n local deck = this.getDeck(source)\n if deck.deck.isEmpty() then\n deck.shuffle()\n end\n\n local flip = false\n if this.isFaceDown(deck.deck) then\n flip = true\n end\n\n local drawn = --[[---@type tts__Card]] deck.deck.takeObject({\n position = deck.discard.getPosition():add(Vector(0, 0.5, 0)),\n flip = flip,\n })\n\n if not drawn then\n Logger.error(\"Could not draw a card from the attack modifier deck!\")\n return\n end\n\n if this.isMonsterDeck(source) then\n broadcastToAll(\"The Monsters Drew: \" .. drawn.getName())\n else\n broadcastToAll(source.class .. \" Drew: \" .. drawn.getName())\n end\nend\n\n---@param source AttackModifierDeck_Source\nfunction AttackModifierMechanic.shuffleDeck(source)\n local deck = this.getDeck(source)\n deck.shuffle()\nend\n\n---@alias AttackModifierMechanic_CardToAdd 'Bless' | 'Curse'\n\n---@param source AttackModifierDeck_Source\n---@param card AttackModifierMechanic_CardToAdd\nfunction AttackModifierMechanic.addCard(source, card)\n local targetDeck = this.getDeck(source)\n\n ---@type tts__Object\n local fromDeck\n if card == \"Bless\" then\n fromDeck = R.Object.BlessBag()\n elseif card == \"Curse\" then\n if this.isMonsterDeck(source) then\n fromDeck = R.Object.MonsterCurseBag()\n else\n fromDeck = R.Object.PlayerCurseBag()\n end\n end\n\n if not fromDeck then\n Logger.error(\"The %s deck can not be found.\", card)\n return\n end\n\n if fromDeck.getQuantity() <= 0 then\n Logger.error(\"No more %s cards left to distribute!\", card)\n return\n end\n\n local taken = --[[---@type tts__Card]] fromDeck.takeObject({ smooth = false })\n targetDeck.deck.putObject(taken)\n targetDeck.deck.shuffle()\n\n if this.isMonsterDeck(source) then\n broadcastToAll(card .. \" added to Monster Attack Modifier Deck\")\n else\n broadcastToAll(card .. \" added to \" .. source.class .. \" Attack Modifier Deck\")\n end\nend\n\n---@param source AttackModifierDeck_Source\nfunction AttackModifierMechanic.endOfRoundCheck(source)\n local deck = this.getDeck(source)\n if deck.needsShuffling() then\n deck.shuffle()\n else\n deck.cleanupDiscard()\n end\nend\n\n---@param source AttackModifierDeck_Source\nfunction AttackModifierMechanic.resetDeck(source)\n local deck = this.getDeck(source)\n deck.reset()\nend\n\n---@param source AttackModifierDeck_Source\n---@return gloom_AttackModifierDeck\nfunction this.getDeck(source)\n if this.isMonsterDeck(source) then\n -- TODO make this local to the play area instead of global\n local position = Vector(-2.04, 1.78, -16.05)\n return AttackModifierDeck(position, AttackModifierDeck.Direction.Right)\n end\n\n local playerSource = --[[---@type gloom_CharacterInfo]] source\n local playerMat = (--[[---@not nil]] playerSource.objects).playerMat\n local position = playerMat.positionToWorld({ 0.62, 1, -0.4 })\n return AttackModifierDeck(position, AttackModifierDeck.Direction.Top)\nend\n\n---@param source AttackModifierDeck_Source\nfunction this.isMonsterDeck(source)\n return source == \"Monster\"\nend\n\n---@param deck seb_WrappedDeck\nfunction this.isFaceDown(deck)\n local currentRotation = deck.getRotation().z\n return math.abs(currentRotation) > 0.01\nend\n\nreturn AttackModifierMechanic\n\nend)\n__bundle_register(\"mechanic.attackmodifier.AttackModifierDeck\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\r\nlocal WrappedDeck = require(\"lib.WrappedDeck\")\r\n\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@class gloom_AttackModifierDeck_Static\r\n---@overload fun(position: tts__VectorShape, direction: gloom_AttackModifierDeck_Direction): gloom_AttackModifierDeck\r\nlocal AttackModifierDeck = {}\r\n\r\n---@class gloom_AttackModifierDeck\r\n\r\n---@alias gloom_AttackModifierDeck_Direction 1 | 2 | 3\r\n\r\n--- Determines where the discard deck for this AMD is placed relative to the deck\r\nAttackModifierDeck.Direction = {\r\n --- The discard is above the deck (like for players)\r\n Top = 1,\r\n Left = 2,\r\n --- The discard is right of the deck (like for monsters)\r\n Right = 3,\r\n}\r\n\r\n---@class __AttackModifierDeck_this\r\nlocal this = {}\r\n\r\n---@shape gloom_AttackModifierDeck_Decks\r\n---@field deck seb_WrappedDeck\r\n---@field discard seb_WrappedDeck\r\n\r\n\r\n---@param position tts__VectorShape\r\n---@param direction gloom_AttackModifierDeck_Direction\r\n---@return gloom_AttackModifierDeck\r\nlocal function new(position, direction)\r\n local self = --[[---@type gloom_AttackModifierDeck]] {}\r\n\r\n local basePosition = position\r\n self.deck = WrappedDeck(Vector(position))\r\n self.discard = this.createDiscard(basePosition, direction)\r\n self.removed = WrappedDeck(this.getRemovedPosition(position, direction))\r\n self.direction = direction\r\n\r\n --- Cleans the discard pile from cards that should be removed after discarding.\r\n --- This returns all cards that should be removed after discarding (e.g. Curses/Blesses).\r\n function self.cleanupDiscard()\r\n this.cleanup(self.discard, self.removed)\r\n end\r\n\r\n --- Resets the deck to its original form.\r\n --- This returns all cards that should be removed after discarding (e.g. Curses/Blesses).\r\n --- It also puts the discard on the draw deck and shuffles the whole deck.\r\n function self.reset()\r\n this.cleanup(self.deck, self.removed)\r\n self.shuffle()\r\n end\r\n\r\n --- Returns `true` if the discard deck should be added to the draw deck and the whole deck should be reshuffled.\r\n ---@return boolean\r\n function self.needsShuffling()\r\n for _, obj in ipairs(self.discard.getObjects()) do\r\n if Object.name(obj):find(\"Shuffle\") then\r\n return true\r\n end\r\n end\r\n return false\r\n end\r\n\r\n function self.shuffle()\r\n self.cleanupDiscard()\r\n self.deck.putObject(self.discard)\r\n self.deck.setRotation({ 0, 180, 180 })\r\n self.deck.shuffle()\r\n self.discard = this.createDiscard(basePosition, direction)\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(AttackModifierDeck, {\r\n __call = function(_, position, direction)\r\n return new(position, direction)\r\n end\r\n})\r\n\r\n---@param position tts__VectorShape\r\n---@param direction gloom_AttackModifierDeck_Direction\r\n---@return tts__Vector\r\nfunction this.getDiscardPosition(position, direction)\r\n if direction == AttackModifierDeck.Direction.Left then\r\n return Vector(position):add(Vector(-4, 0, 0))\r\n elseif direction == AttackModifierDeck.Direction.Right then\r\n return Vector(position):add(Vector(4, 0, 0))\r\n elseif direction == AttackModifierDeck.Direction.Top then\r\n return Vector(position):add(Vector(0, 0, 3))\r\n end\r\n\r\n return Vector(position)\r\nend\r\n\r\n---@param position tts__VectorShape\r\n---@param direction gloom_AttackModifierDeck_Direction\r\n---@return tts__Vector\r\nfunction this.getRemovedPosition(position, direction)\r\n if direction == AttackModifierDeck.Direction.Top then\r\n return Vector(position):add(Vector(0, 0, 6))\r\n end\r\n\r\n return Vector(position):add(Vector(0, 0, 5))\r\nend\r\n\r\nfunction this.createDiscard(basePosition, direction)\r\n return WrappedDeck(this.getDiscardPosition(basePosition, direction))\r\nend\r\n\r\n---@param deck seb_WrappedDeck\r\n---@param removed seb_WrappedDeck\r\nfunction this.cleanup(deck, removed)\r\n for _, obj in ipairs(deck.getObjects()) do\r\n if Object.hasTag(obj, R.Tag.AMD.RemoveAfterDiscard) then\r\n local target = this.findTarget(obj)\r\n local taken = deck.takeObject({\r\n guid = Object.guid(obj),\r\n })\r\n if target then\r\n (--[[---@not nil]] target).putObject(taken)\r\n else\r\n removed.putObject(--[[---@type tts__Card]] taken)\r\n end\r\n end\r\n end\r\nend\r\n\r\n---@param obj tts__ObjectState\r\n---@return nil | tts__Object\r\nfunction this.findTarget(obj)\r\n if Object.name(obj):find(\"Bless (x2)\") then\r\n return R.Object.BlessBag()\r\n elseif Object.name(obj):find(\"Player Curse\") then\r\n return R.Object.PlayerCurseBag()\r\n elseif Object.name(obj):find(\"Monster Curse\") then\r\n return R.Object.MonsterCurseBag()\r\n end\r\n\r\n return nil\r\nend\r\n\r\nreturn AttackModifierDeck\r\n\nend)\n__bundle_register(\"lib.WrappedDeck\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal WrappedDeck = require(\"sebaestschjin-tts.WrappedDeck\")\r\n\r\nreturn WrappedDeck\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.WrappedDeck\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"sebaestschjin-tts.Logger\")\r\nlocal Object = require(\"sebaestschjin-tts.Object\")\r\n\r\n---@class seb_WrappedDeck\r\n\r\n---@alias seb_WrappedDeck_Target tts__Deck | tts__Card | tts__Vector\r\n\r\n---@class seb_WrappedDeck_static : seb_WrappedDeck\r\n---@overload fun(deck: seb_WrappedDeck_Target): seb_WrappedDeck\r\nlocal WrappedDeck = {}\r\n\r\nWrappedDeck.ClassName = \"WrappedDeck\"\r\n\r\n---@param value any\r\n---@return boolean\r\nfunction WrappedDeck.isInstance(value)\r\n return type(value) == \"table\" and value.__class == WrappedDeck.ClassName\r\nend\r\n\r\n---@param obj seb_WrappedDeck_Target\r\nlocal function new(obj)\r\n local self = --[[---@type seb_WrappedDeck]] {}\r\n\r\n self.__class = WrappedDeck.ClassName\r\n\r\n ---@type nil | tts__Deck | tts__Card\r\n local wrappedObject\r\n ---@type boolean\r\n local isDeck = false\r\n ---@type boolean\r\n local isCard = false\r\n ---@type nil | tts__Vector\r\n local lastPosition\r\n ---@type nil | tts__Vector\r\n local lastRotation\r\n ---@type nil | string\r\n local lastName\r\n\r\n ---@return tts__Deck\r\n local function asDeck()\r\n return --[[---@type tts__Deck]] wrappedObject\r\n end\r\n\r\n ---@return tts__Card\r\n local function asCard()\r\n return --[[---@type tts__Card]] wrappedObject\r\n end\r\n\r\n ---@param card tts__Card\r\n local function makeCard(card)\r\n wrappedObject = card\r\n isDeck = false\r\n isCard = true\r\n end\r\n\r\n ---@param deck tts__Deck\r\n local function makeDeck(deck)\r\n if deck.remainder then\r\n -- we have a deck, but it going to be a single card soon\r\n makeCard(--[[---@type tts__Card]] deck.remainder)\r\n else\r\n wrappedObject = deck\r\n isDeck = true\r\n isCard = false\r\n if lastName then\r\n deck.setName(--[[---@not nil]] lastName)\r\n end\r\n end\r\n end\r\n\r\n ---@param position tts__Vector\r\n ---@param rotation tts__VectorShape\r\n local function makeEmpty(position, rotation)\r\n wrappedObject = nil\r\n isDeck = false\r\n isCard = false\r\n lastPosition = Vector(position)\r\n lastRotation = Vector(rotation)\r\n end\r\n\r\n ---@param card tts__Card\r\n ---@param parameters tts__Object_GuidTakeObjectParameters | tts__Object_IndexTakeObjectParameters\r\n local function takeObjectSingle(card, parameters)\r\n local parametersGuid = (--[[---@type tts__Object_GuidTakeObjectParameters]] parameters).guid\r\n if parametersGuid and card.getGUID() ~= parametersGuid then\r\n error(\"Deck doesn't contain guid \" .. parametersGuid)\r\n end\r\n\r\n if parameters.position then\r\n if parameters.smooth then\r\n card.setPositionSmooth(--[[---@type tts__VectorShape]] parameters.position)\r\n else\r\n card.setPosition(--[[---@type tts__VectorShape]] parameters.position)\r\n end\r\n end\r\n\r\n if parameters.rotation then\r\n card.setRotation(--[[---@type tts__VectorShape]] parameters.rotation)\r\n elseif parameters.flip then\r\n local newRotation = card.getRotation():add(Vector(0, 0, 180))\r\n card.setRotation(newRotation)\r\n end\r\n\r\n if parameters.callback_function then\r\n parameters.callback_function(card)\r\n end\r\n end\r\n\r\n local function initialize()\r\n if (--[[---@type tts__Object]] obj).type == Object.Type.Deck then\r\n makeDeck(--[[---@type tts__Deck]] obj)\r\n elseif (--[[---@type tts__Object]] obj).type == Object.Type.Card then\r\n makeCard(--[[---@type tts__Card]] obj)\r\n else\r\n local hit = Physics.cast({\r\n origin = obj + Vector(0, 3, 0),\r\n direction = { 0, -1, 0 },\r\n type = 1,\r\n })\r\n\r\n if hit then\r\n for _, hitInfo in pairs(hit) do\r\n local hitObject = hitInfo.hit_object\r\n if hitObject.type == Object.Type.Deck then\r\n makeDeck(--[[---@type tts__Deck]] hitObject)\r\n return\r\n elseif hitObject.type == Object.Type.Card then\r\n makeCard(--[[---@type tts__Card]] hitObject)\r\n return\r\n end\r\n end\r\n end\r\n makeEmpty(--[[---@type tts__Vector]] obj, { 0, 180, 0 })\r\n end\r\n end\r\n\r\n ---@type tts__ObjectType\r\n self.tag = Object.Type.Deck\r\n self.type = Object.Type.Deck\r\n\r\n ---@return nil | tts__Card | tts__Deck\r\n function self.unwrap()\r\n return wrappedObject\r\n end\r\n\r\n function self.isEmpty()\r\n return not isDeck and not isCard\r\n end\r\n\r\n ---@return string\r\n function self.getName()\r\n if isDeck then\r\n return asDeck().getName()\r\n end\r\n return lastName or \"\"\r\n end\r\n\r\n ---@param name string\r\n function self.setName(name)\r\n if isDeck then\r\n asDeck().setName(name)\r\n else\r\n lastName = name\r\n end\r\n end\r\n\r\n ---@return tts__ObjectState[]\r\n function self.getObjects()\r\n if isDeck then\r\n return asDeck().getData().ContainedObjects\r\n elseif isCard then\r\n return { asCard().getData() }\r\n else\r\n return --[[---@type tts__ObjectState[] ]] {}\r\n end\r\n end\r\n\r\n ---@param parameters tts__Object_GuidTakeObjectParameters | tts__Object_IndexTakeObjectParameters\r\n ---@return nil | tts__Object\r\n function self.takeObject(parameters)\r\n if isDeck then\r\n local deckName = asDeck().getName()\r\n local result = asDeck().takeObject(parameters)\r\n if (--[[---@type tts__Deck]] wrappedObject).remainder then\r\n makeCard(--[[---@type tts__Card]] asDeck().remainder)\r\n lastName = deckName\r\n end\r\n return result\r\n elseif isCard then\r\n local position = asCard().getPosition()\r\n local rotation = asCard().getRotation()\r\n local cardObject = asCard()\r\n takeObjectSingle(asCard(), parameters)\r\n makeEmpty(position, rotation)\r\n return cardObject\r\n end\r\n end\r\n\r\n ---@param cardOrDeck tts__Card | tts__Deck | seb_WrappedDeck\r\n ---@return tts__Deck\r\n function self.putObject(cardOrDeck)\r\n if WrappedDeck.isInstance(cardOrDeck) then\r\n local unwrapped = (--[[---@type seb_WrappedDeck]] cardOrDeck).unwrap()\r\n if unwrapped then\r\n return self.putObject(--[[---@not nil]] unwrapped)\r\n else\r\n return self.unwrap()\r\n end\r\n end\r\n\r\n if isDeck then\r\n return asDeck().putObject(cardOrDeck)\r\n elseif isCard then\r\n --[[---@type tts__Deck]]\r\n local formedDeck\r\n if Object.isCard(cardOrDeck) then\r\n formedDeck = asCard().putObject(--[[---@type tts__Card]] cardOrDeck)\r\n else\r\n local currentPosition = asCard().getPosition()\r\n local currentRotation = asCard().getRotation()\r\n formedDeck = (--[[---@type tts__Deck]] cardOrDeck).putObject(asCard())\r\n formedDeck.setPosition(currentPosition)\r\n formedDeck.setRotation(currentRotation)\r\n end\r\n\r\n if not formedDeck then\r\n Logger.error(\"Could not form a new deck from %s and %s\", wrappedObject, cardOrDeck)\r\n end\r\n\r\n makeDeck(formedDeck)\r\n return formedDeck\r\n else\r\n cardOrDeck.setPosition(--[[---@not nil]] lastPosition)\r\n cardOrDeck.setRotation(--[[---@not nil]] lastRotation)\r\n if Object.isCard(cardOrDeck) then\r\n makeCard(--[[---@type tts__Card]] cardOrDeck)\r\n else\r\n makeDeck(--[[---@type tts__Deck]] cardOrDeck)\r\n end\r\n end\r\n end\r\n\r\n ---@return tts__Vector\r\n function self.getPosition()\r\n if isDeck or isCard then\r\n local position = (--[[---@not nil]] wrappedObject).getPosition()\r\n return position\r\n end\r\n return --[[---@not nil]] lastPosition\r\n end\r\n\r\n ---@return tts__Vector\r\n function self.getRotation()\r\n if isDeck or isCard then\r\n local rotation = (--[[---@not nil]] wrappedObject).getRotation()\r\n return rotation\r\n end\r\n return --[[---@not nil]] lastRotation\r\n end\r\n\r\n ---@param rotation tts__VectorShape\r\n function self.setRotation(rotation)\r\n if isDeck or isCard then\r\n (--[[---@not nil]] wrappedObject).setRotation(rotation)\r\n else\r\n lastRotation = Vector(rotation)\r\n end\r\n end\r\n\r\n ---@return tts__DeckState\r\n function self.getData()\r\n if isDeck then\r\n return --[[---@type tts__DeckState]] asDeck().getData()\r\n elseif isCard then\r\n local cardData = --[[---@type tts__DeckState]] asCard().getData()\r\n cardData.ContainedObjects = { cardData }\r\n return cardData\r\n else\r\n return --[[---@type tts__DeckState]] {\r\n ContainedObjects = {}\r\n }\r\n end\r\n end\r\n\r\n function self.shuffle()\r\n if isDeck then\r\n asDeck().shuffle()\r\n end\r\n end\r\n\r\n initialize()\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(WrappedDeck, {\r\n __call = function(_, obj)\r\n return new(obj)\r\n end\r\n})\r\n\r\nreturn WrappedDeck\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Object\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = {}\r\n\r\nObject.Name = {\r\n AssetBundle = \"Custom_Assetbundle\",\r\n AssetBundleBag = \"Custom_Assetbundle_Bag\",\r\n AssetBundleInfiniteBag = \"Custom_Assetbundle_Infinite_Bag\",\r\n BackgammonBoard = \"backgammon_board\",\r\n BackgammonPieceBrown = \"backgammon_piece_brown\",\r\n BackgammonPieceWhite = \"backgammon_piece_white\",\r\n Bag = \"Bag\",\r\n BlockRectangle = \"BlockRectangle\",\r\n BlockSquare = \"BlockSquare\",\r\n BlockTriangle = \"BlockTriangle\",\r\n Board = \"Custom_Board\",\r\n Card = \"Card\",\r\n CardCustom = \"CardCustom\",\r\n CheckerBlack = \"Checker_black\",\r\n CheckerBoard = \"Checker_Board\",\r\n CheckerRed = \"Checker_red\",\r\n CheckerWhite = \"Checker_white\",\r\n ChessBishop = \"Chess_Bishop\",\r\n ChessBoard = \"Chess_Board\",\r\n ChessKing = \"Chess_King\",\r\n ChessKnight = \"Chess_Knight\",\r\n ChessPawn = \"Chess_Pawn\",\r\n ChessQueen = \"Chess_Queen\",\r\n ChessRook = \"Chess_Rook\",\r\n ChiP1000 = \"Chip_1000\",\r\n ChineseCheckersBoard = \"Chinese_Checkers_Board\",\r\n ChineseCheckersPiece = \"Chinese_Checkers_Piece\",\r\n Chip10 = \"Chip_10\",\r\n Chip100 = \"Chip_100\",\r\n Chip50 = \"Chip_50\",\r\n Chip500 = \"Chip_500\",\r\n Deck = \"Deck\",\r\n DeckCardBotHead = \"Deck_CardBot_Head\",\r\n DeckCardBotMain = \"Deck_CardBot_Main\",\r\n DeckCustom = \"DeckCustom\",\r\n Die10 = \"Die_10\",\r\n Die12 = \"Die_12\",\r\n Die20 = \"Die_20\",\r\n Die4 = \"Die_4\",\r\n Die6 = \"Die_6\",\r\n Die6Rounded = \"Die_6_Rounded\",\r\n Die8 = \"Die_8\",\r\n DieCustom = \"Custom_Dice\",\r\n DiePiecepack = \"Die_Piecepack\",\r\n DigitalClock = \"Digital_Clock\",\r\n Domino = \"Domino\",\r\n FigurineCardBot = \"Figurine_Card_Bot\",\r\n FigurineCustom = \"Figurine_Custom\",\r\n FigurineKimiKat = \"Figurine_Kimi_Kat\",\r\n FigurineKnil = \"Figurine_Knil\",\r\n FigurineMara = \"Figurine_Mara\",\r\n FigurineSirLoin = \"Figurine_Sir_Loin\",\r\n FigurineZeke = \"Figurine_Zeke\",\r\n FigurineZomblor = \"Figurine_Zomblor\",\r\n GoBoard = \"Go_Board\",\r\n GoGameBowlBlack = \"go_game_bowl_black\",\r\n GoGameBowlWhite = \"go_game_bowl_white\",\r\n GoGamePieceBlack = \"go_game_piece_black\",\r\n GoGamePieceWhite = \"go_game_piece_white\",\r\n InfiniteBag = \"Infinite_Bag\",\r\n LayoutZone = \"LayoutZone\",\r\n MahjongTile = \"Mahjong_Tile\",\r\n MetalBall = \"Ball\",\r\n Model = \"Custom_Model\",\r\n ModelBag = \"Custom_Model_Bag\",\r\n ModelInfinite = \"Custom_Model_Infinite_Bag\",\r\n Pachisiboard = \"Pachisi_board\",\r\n PlayerPawn = \"PlayerPawn\",\r\n Quarter = \"Quarter\",\r\n RPGBear = \"rpg_BEAR\",\r\n RPGChimera = \"rpg_CHIMERA\",\r\n RPGCyclop = \"rpg_CYCLOP\",\r\n RPGDragonide = \"rpg_DRAGONIDE\",\r\n RPGEvilWatcher = \"rpg_EVIL_WATCHER\",\r\n RPGGhoul = \"rpg_GHOUL\",\r\n RPGGiantViper = \"rpg_GIANT_VIPER\",\r\n RPGGoblin = \"rpg_GOBLIN\",\r\n RPGGolem = \"rpg_GOLEM\",\r\n RPGGriffon = \"rpg_GRIFFON\",\r\n RPGHydra = \"rpg_HYDRA\",\r\n RPGKobold = \"rpg_KOBOLD\",\r\n RPGLizardWarrior = \"rpg_LIZARD_WARRIOR\",\r\n RPGManticora = \"rpg_MANTICORA\",\r\n RPGMummy = \"rpg_MUMMY\",\r\n RPGOgre = \"rpg_OGRE\",\r\n RPGOrc = \"rpg_ORC\",\r\n RPGRat = \"rpg_RAT\",\r\n RPGSkeletonKnight = \"rpg_SKELETON_KNIGHT\",\r\n RPGTreeEnt = \"rpg_TREE_ENT\",\r\n RPGTroll = \"rpg_TROLL\",\r\n RPGVampire = \"rpg_VAMPIRE\",\r\n RPGWerewolf = \"rpg_WEREWOLF\",\r\n RPGWolf = \"rpg_WOLF\",\r\n RPGWyvern = \"rpg_WYVERN\",\r\n ReversiBoard = \"reversi_board\",\r\n ReversiChip = \"reversi_chip\",\r\n ScriptingTrigger = \"ScriptingTrigger\",\r\n Tablet = \"Tablet\",\r\n Tile = \"Custom_Tile\",\r\n TilesetBarrel = \"Tileset_Barrel\",\r\n TilesetChair = \"Tileset_Chair\",\r\n TilesetChest = \"Tileset_Chest\",\r\n TilesetCorner = \"Tileset_Corner\",\r\n TilesetFloor = \"Tileset_Floor\",\r\n TilesetRock = \"Tileset_Rock\",\r\n TilesetTable = \"Tileset_Table\",\r\n TilesetTree = \"Tileset_Tree\",\r\n TilesetWall = \"Tileset_Wall\",\r\n Token = \"Custom_Token\",\r\n}\r\n\r\nObject.Type = {\r\n BackgammonPiece = \"Backgammon Piece\",\r\n Bag = \"Bag\",\r\n Block = \"Block\",\r\n Board = \"Board\",\r\n Calculator = \"Calculator\",\r\n Card = \"Card\",\r\n Checker = \"Checker\",\r\n Chess = \"Chess\",\r\n Chip = \"Chip\",\r\n Clock = \"Clock\",\r\n Coin = \"Coin\",\r\n Counter = \"Counter\",\r\n Deck = \"Deck\",\r\n Die = \"Dice\",\r\n Domino = \"Domino\",\r\n Figurine = \"Figurine\",\r\n Fog = \"Fog\",\r\n FogOfWar = \"FogOfWar\",\r\n Generic = \"Generic\",\r\n GoPiece = \"GoPiece\",\r\n Hand = \"Hand\",\r\n Infinite = \"Infinite\",\r\n InventoryBackground = \"InventoryBackground\",\r\n InventoryBotBackground = \"InventoryBotBG\",\r\n InventoryItemBlank = \"InventoryItemBlank\",\r\n InventoryTopBackground = \"InventoryTopBG\",\r\n Jigsaw = \"Jigsaw\",\r\n JigsawBox = \"Jigsaw Box\",\r\n MP3 = \"Mp3\",\r\n Notecard = \"Notecard\",\r\n Pointer = \"Pointer\",\r\n RPGFigurine = \"rpgFigurine\",\r\n Randomize = \"Randomize\",\r\n Scripting = \"Scripting\",\r\n Stack = \"Stack\",\r\n Superfight = \"Superfight\",\r\n Surface = \"Surface\",\r\n Tablet = \"Tablet\",\r\n Text = \"3D Text\",\r\n Tile = \"Tile\",\r\n Tileset = \"Tileset\",\r\n VRUI = \"VR UI\",\r\n}\r\n\r\nObject.ModelType = {\r\n Generic = 0,\r\n Figurine = 1,\r\n Dice = 2,\r\n Coin = 3,\r\n Board = 4,\r\n Chip = 5,\r\n Bag = 6,\r\n Infinite = 7,\r\n}\r\n\r\nObject.MaterialType = {\r\n Plastic = 0,\r\n Wood = 1,\r\n Metal = 2,\r\n Cardboard = 3,\r\n Glass = 4,\r\n}\r\n\r\n--- Types for custom tiles.\r\n---@type table\r\nObject.TileType = {\r\n Box = 0,\r\n Hex = 1,\r\n Circle = 2,\r\n Rounded = 3,\r\n}\r\n\r\nObject.LayoutZone = {\r\n ---@type table\r\n Direction = {\r\n RightDown = 0,\r\n DownRight = 1,\r\n LeftDown = 2,\r\n DownLeft = 3,\r\n RightUp = 4,\r\n UpRight = 5,\r\n LeftUp = 6,\r\n UpLeft = 7,\r\n },\r\n ---@type table\r\n Facing = {\r\n DoNotChange = 0,\r\n FaceUp = 1,\r\n FaceDown = 2,\r\n GroupIsTipped = 3,\r\n },\r\n ---@type table\r\n GroupDirection = {\r\n Eastward = 0,\r\n Westward = 1,\r\n Northward = 2,\r\n Southward = 3,\r\n },\r\n ---@type table\r\n GroupSort = {\r\n None = 0,\r\n AddedTime = 1,\r\n Value = 2,\r\n Name = 3,\r\n Description = 4,\r\n GmNotes = 5,\r\n Memo = 6,\r\n }\r\n}\r\n\r\n---@type table\r\nObject.TypeForName = {\r\n [Object.Name.AssetBundleBag] = Object.Type.Bag,\r\n [Object.Name.AssetBundleInfiniteBag] = Object.Type.Infinite,\r\n [Object.Name.Bag] = Object.Type.Bag,\r\n [Object.Name.Board] = Object.Type.Board,\r\n [Object.Name.Card] = Object.Type.Card,\r\n [Object.Name.CardCustom] = Object.Type.Card,\r\n [Object.Name.Deck] = Object.Type.Deck,\r\n [Object.Name.DeckCustom] = Object.Type.Deck,\r\n [Object.Name.Die4] = Object.Type.Die,\r\n [Object.Name.Die6] = Object.Type.Die,\r\n [Object.Name.Die6Rounded] = Object.Type.Die,\r\n [Object.Name.Die8] = Object.Type.Die,\r\n [Object.Name.Die10] = Object.Type.Die,\r\n [Object.Name.Die12] = Object.Type.Die,\r\n [Object.Name.Die20] = Object.Type.Die,\r\n [Object.Name.DieCustom] = Object.Type.Die,\r\n [Object.Name.FigurineCardBot] = Object.Type.Figurine,\r\n [Object.Name.FigurineCustom] = Object.Type.Figurine,\r\n [Object.Name.FigurineKimiKat] = Object.Type.Figurine,\r\n [Object.Name.FigurineKnil] = Object.Type.Figurine,\r\n [Object.Name.FigurineMara] = Object.Type.Figurine,\r\n [Object.Name.FigurineSirLoin] = Object.Type.Figurine,\r\n [Object.Name.FigurineZeke] = Object.Type.Figurine,\r\n [Object.Name.FigurineZomblor] = Object.Type.Figurine,\r\n [Object.Name.ModelBag] = Object.Type.Bag,\r\n [Object.Name.ModelInfinite] = Object.Type.Infinite,\r\n [Object.Name.InfiniteBag] = Object.Type.Infinite,\r\n [Object.Name.ScriptingTrigger] = Object.Type.Scripting,\r\n [Object.Name.Tile] = Object.Type.Tile,\r\n}\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return boolean\r\nfunction Object.isObject(object)\r\n return type(object) == \"userdata\"\r\nend\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return boolean\r\nfunction Object.isSimple(object)\r\n return type(object) == \"table\" and (--[[---@type tts__IndexedSimpleObjectState]] object).name ~= nil\r\nend\r\n\r\n---@param object seb_Object\r\n---@return boolean\r\nfunction Object.isCard(object)\r\n return Object.type(object) == Object.Type.Card\r\nend\r\n\r\n---@param object seb_Object\r\n---@return boolean\r\nfunction Object.isDeck(object)\r\n return Object.type(object) == Object.Type.Deck\r\nend\r\n\r\n---@param object seb_Object\r\n---@return boolean\r\nfunction Object.isBag(object)\r\n return Object.type(object) == Object.Type.Bag\r\nend\r\n\r\n---@param object seb_Object\r\n---@return boolean\r\nfunction Object.isInfiniteBag(object)\r\n return Object.type(object) == Object.Type.Infinite\r\nend\r\n\r\n---@param object seb_Object\r\n---@return boolean\r\nfunction Object.isFigurine(object)\r\n return Object.type(object) == Object.Type.Figurine\r\nend\r\n\r\n---@param object seb_Object\r\n---@return boolean\r\nfunction Object.isContainer(object)\r\n return Object.isDeck(object) or Object.isBag(object) or Object.isInfiniteBag(object)\r\nend\r\n\r\n---@param object tts__Object\r\nfunction Object.isLoaded(object)\r\n return not object.spawning and not object.loading_custom\r\nend\r\n\r\n---@param object seb_Object\r\n---@return tts__ObjectType\r\nfunction Object.type(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).type\r\n end\r\n if Object.isSimple(object) then\r\n return Object.Type.Card\r\n end\r\n return Object.TypeForName[(--[[---@type tts__ObjectState]] object).Name]\r\nend\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return GUID\r\nfunction Object.guid(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getGUID()\r\n end\r\n if Object.isSimple(object) then\r\n return (--[[---@type tts__IndexedSimpleObjectState]] object).guid\r\n end\r\n return --[[---@not nil]] (--[[---@type tts__ObjectState]] object).GUID\r\nend\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return string\r\nfunction Object.name(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getName()\r\n end\r\n if Object.isSimple(object) then\r\n return (--[[---@type tts__IndexedSimpleObjectState]] object).name\r\n end\r\n return --[[---@not nil]] (--[[---@type tts__ObjectState]] object).Nickname\r\nend\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return string\r\nfunction Object.description(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getDescription()\r\n end\r\n if Object.isSimple(object) then\r\n return (--[[---@type tts__IndexedSimpleObjectState]] object).description or \"\"\r\n end\r\n return (--[[---@type tts__ObjectState]] object).Description or \"\"\r\nend\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return string\r\nfunction Object.gmNotes(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getGMNotes() or \"\"\r\n end\r\n if Object.isSimple(object) then\r\n return (--[[---@type tts__IndexedSimpleObjectState]] object).gm_notes or \"\"\r\n end\r\n return (--[[---@type tts__ObjectState]] object).GMNotes or \"\"\r\nend\r\n\r\n---@param object seb_Object_Identifiable\r\n---@return string\r\nfunction Object.memo(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getMemo() or \"\"\r\n end\r\n if Object.isSimple(object) then\r\n return (--[[---@type tts__IndexedSimpleObjectState]] object).memo or \"\"\r\n end\r\n return (--[[---@type tts__ObjectState]] object).Memo or \"\"\r\nend\r\n\r\n---@param object seb_Object\r\n---@return tts__ObjectState\r\nfunction Object.data(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getData()\r\n end\r\n return --[[---@type tts__ObjectState]] object\r\nend\r\n\r\n---@param object seb_Object\r\n---@return nil | number\r\nfunction Object.cardIndex(object)\r\n local cardId = Object.data(object).CardID\r\n if not cardId then\r\n return nil\r\n end\r\n\r\n return tonumber(tostring(cardId):sub(-2, -1))\r\nend\r\n\r\n---@param object seb_Object\r\n---@return tts__Vector\r\nfunction Object.position(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getPosition()\r\n end\r\n return Object.transformToPosition(--[[---@not nil]] (--[[---@type tts__ObjectState]] object).Transform)\r\nend\r\n\r\n---@param object seb_Object_Container\r\n---@return tts__Object[] | tts__ObjectState[]\r\nfunction Object.objects(object)\r\n if Object.isObject(object) then\r\n if Object.type(object) == Object.Type.Scripting then\r\n return (--[[---@type tts__ScriptingTrigger]] object).getObjects()\r\n else\r\n return (--[[---@type tts__Container]] object).getData().ContainedObjects or {}\r\n end\r\n end\r\n return (--[[---@type tts__ContainerState]] object).ContainedObjects or {}\r\nend\r\n\r\n---@param object seb_Object\r\n---@return tts__Object_Decal[]\r\nfunction Object.decals(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getDecals() or {}\r\n end\r\n\r\n local decals = --[[---@type tts__Object_Decal[] ]]{}\r\n local attachedDecals = --[[---@type tts__ObjectState_Decal[] ]] (--[[---@type tts__ObjectState]] object).AttachedDecals or {}\r\n for _, decal in ipairs(attachedDecals) do\r\n table.insert(decals, {\r\n name = decal.CustomDecal.Name,\r\n url = decal.CustomDecal.ImageURL,\r\n position = Object.transformToPosition(decal.Transform),\r\n rotation = Object.transformToPosition(decal.Transform),\r\n scale = Object.transformToPosition(decal.Transform),\r\n })\r\n end\r\n return decals\r\nend\r\n\r\n---@param object seb_Object\r\n---@return tts__Object_Tag[]\r\nfunction Object.tags(object)\r\n if Object.isObject(object) then\r\n return (--[[---@type tts__Object]] object).getTags()\r\n end\r\n return (--[[---@type tts__ObjectState]] object).Tags or {}\r\nend\r\n\r\n---@param object seb_Object\r\n---@param tag tts__Object_Tag\r\n---@return boolean\r\nfunction Object.hasTag(object, tag)\r\n for _, objectTag in ipairs(Object.tags(object)) do\r\n if objectTag == tag then\r\n return true\r\n end\r\n end\r\n return false\r\nend\r\n\r\n---@param transform tts__ObjectState_Transform\r\n---@return tts__Vector\r\nfunction Object.transformToPosition(transform)\r\n return Vector(--[[---@not nil]] transform.posX, --[[---@not nil]] transform.posY, --[[---@not nil]] transform.posZ)\r\nend\r\n\r\n---@param transform tts__ObjectState_Transform\r\n---@return tts__Vector\r\nfunction Object.transformToRotation(transform)\r\n return Vector(--[[---@not nil]] transform.rotX, --[[---@not nil]] transform.rotY, --[[---@not nil]] transform.rotZ)\r\nend\r\n\r\n---@param transform tts__ObjectState_Transform\r\n---@return tts__Vector\r\nfunction Object.transformToScale(transform)\r\n return Vector(--[[---@not nil]] transform.scaleX, --[[---@not nil]] transform.scaleY, --[[---@not nil]] transform.scaleZ)\r\nend\r\n\r\n---@overload fun(state: tts__ObjectState): void\r\n---@param state tts__ObjectState\r\n---@param callback tts__ObjectCallbackFunction\r\nfunction Object.respawn(state, callback)\r\n if state.GUID then\r\n local obj = getObjectFromGUID(--[[---@not nil]] state.GUID)\r\n if obj then\r\n (--[[---@not nil]] obj).destruct()\r\n end\r\n end\r\n spawnObjectData({\r\n data = state,\r\n callback_function = callback\r\n })\r\nend\r\n\r\nreturn Object\r\n\nend)\n__bundle_register(\"lib.Object\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"sebaestschjin-tts.Object\")\r\n\r\nreturn Object\r\n\nend)\n__bundle_register(\"Party\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\nlocal Search = require(\"lib.Search\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal R = require(\"api.Resource\")\nlocal Game = require(\"Game\")\n\nlocal Party = {}\n\n---@class __Party_this\nlocal this = {}\nlocal CampaignApi = --[[---@type CampaignApi]] ApiProvider(\"campaign\")\nlocal CharacterApi = --[[---@type CharacterApi]] ApiProvider(\"character\")\n\n---@param player integer\n---@param search seb_Search_Full\n---@return nil | tts__Object\nlocal function getPlayerObject(player, search)\n local zone = this.getPlayerZone(player)\n return Search.inZone(zone, search)\nend\n\n---@param player integer\n---@return nil | string\nlocal function getCharacterClass(player)\n local characterMat = this.getCharacterMat(player)\n if characterMat then\n return Object.description(--[[---@not nil]] characterMat)\n end\nend\n\n--- Returns a list of all active characters.\n---@return integer\nfunction Party.getActiveCharacterCount()\n return TableUtil.length(Party.getActiveCharacters())\nend\n\n--- Returns a list of all active characters.\n---@return table\nfunction Party.getActiveCharacters()\n local activeCharacters = {}\n for playerNumber = 1, Game.PlayerCount do\n local character = Party.getCharacterInfo(playerNumber)\n if character.isPresent then\n activeCharacters[playerNumber] = character\n end\n end\n\n return activeCharacters\nend\n\n---@param playerNumber integer\n---@return gloom_CharacterInfo\nfunction Party.getCharacterInfo(playerNumber)\n local characterClass = getCharacterClass(playerNumber)\n local isPresent = characterClass ~= nil\n\n ---@type gloom_CharacterInfo\n local result = {\n playerNumber = playerNumber,\n color = Game.PlayerInfo[playerNumber].Color,\n isPresent = isPresent,\n }\n if not isPresent then\n return result\n end\n\n local activeResult = --[[---@type gloom_ActiveCharacterInfo]] result\n activeResult.class = --[[---@not nil]] characterClass\n activeResult.objects = this.getCharacterObjects(playerNumber, --[[---@not nil]] characterClass)\n activeResult.level = this.getPlayerLevel(playerNumber)\n activeResult.name = this.getCharacterName(playerNumber)\n\n return activeResult\nend\n\n---@param className string\n---@return gloom_CharacterInfo\nfunction Party.getCharacterInfoByClass(className)\n for _, characterInfo in pairs(Party.getActiveCharacters()) do\n if characterInfo.class == className then\n return characterInfo\n end\n end\nend\n\nfunction Party.getSpawnableElements(player)\n return this.getSpawnableElements(player)\nend\n\nCampaignApi.hasPartyAchievement = function(name)\n local partySheet = getObjectsWithTag(R.Tag.PartySheet)[1]\n local achievements = partySheet.UI.getAttribute(\"achievements\", \"Text\")\n return achievements:find(name) ~= nil\nend\n\nCharacterApi.getPlayerObjects = function(player)\n return this.getPlayerObjects(player)\nend\n\nCharacterApi.placePlayerObject = function(player, object, placement)\n this.placePlayerObject(player, object, placement)\nend\n\n---@param player integer\n---@return tts__Object[]\nfunction this.getPlayerObjects(player)\n return this.getPlayerZone(player).getObjects()\nend\n\n---@param player integer\n---@param className string\n---@return gloom_CharacterInfo_Objects\nfunction this.getCharacterObjects(player, className)\n ---@type gloom_CharacterInfo_Objects\n local objects = {}\n\n for _, figure in ipairs(getObjectsWithTag(R.Tag.Class.Figure)) do\n if figure.getName():find(className) then\n objects.figure = figure\n break\n end\n end\n\n local function findByTag(tag)\n local playerZone = this.getPlayerZone(player)\n for _, object in ipairs(getObjectsWithTag(tag)) do\n if TableUtil.contains(object.getZones(), playerZone) then\n return object\n end\n end\n end\n\n objects.xpDial = function()\n return findByTag(R.Tag.Class.XpDial)\n end\n objects.hpDial = function()\n return findByTag(R.Tag.Class.HpDial)\n end\n objects.characterMat = function()\n return --[[---@not nil]] this.getCharacterMat(player)\n end\n objects.characterSheet = function()\n return --[[---@not nil]] getPlayerObject(player, { name = \"Character Sheet\" })\n end\n objects.playerMat = this.getPlayerMat(player)\n\n objects.spawnableElements = function()\n return this.getSpawnableElements(player)\n end\n\n return objects\nend\n\n---@return integer\nfunction this.getPlayerLevel(player)\n local sheet = this.getCharacterSheet(player)\n if not sheet then\n return 0\n end\n\n if sheet.hasTag(R.Tag.Class.Sheet) then\n return --[[---@type integer]] sheet.call(\"getLevel\")\n end\n\n local function oldStyle()\n local maxClickedLevel = 0\n local buttons = sheet.getTable(\"buttons\")\n for name, button in pairs(buttons) do\n local level = name:match(\"level(%d)\")\n if level and button.label ~= \"\" then\n level = tonumber(level)\n if level > maxClickedLevel then\n maxClickedLevel = level\n end\n end\n end\n return maxClickedLevel\n end\n\n return oldStyle()\nend\n\n---@return string\nfunction this.getCharacterName(player)\n local sheet = this.getCharacterSheet(player)\n if not sheet then\n return \"\"\n end\n\n if sheet.hasTag(R.Tag.Class.Sheet) then\n return --[[---@type string]] sheet.call(\"getCharacterName\")\n end\n\n return sheet.UI.getAttribute(\"Name\", \"text\")\nend\n\n---@param player integer\n---@return tts__ScriptingTrigger\nfunction this.getPlayerZone(player)\n local zoneGuid = Game.PlayerInfo[player].Zone\n return --[[---@type tts__ScriptingTrigger]] getObjectFromGUID(zoneGuid)\nend\n\n---@param player integer\n---@return nil | tts__Object\nfunction this.getCharacterMat(player)\n return getPlayerObject(player, { name = \"Character Mat\" })\nend\n\n---@param player integer\nfunction this.getPlayerMat(player)\n return getPlayerObject(player, { name = \"Player Mat\" })\nend\n\n---@param player integer\nfunction this.getCharacterSheet(player)\n return --[[---@not nil]] getPlayerObject(player, { name = \"Character Sheet\" })\nend\n\n---@param player integer\n---@param index 1 | 2\nfunction this.getAbilityCard(player, index)\n local playerInfo = Game.getPlayerInfo(player)\n\n local hits = Physics.cast({\n origin = playerInfo.Abilities[index],\n direction = { 0, 1, 0 },\n type = 3,\n size = { 1, 1, 1 },\n max_distance = 0,\n debug = true\n })\n\n for _, obj in pairs(hits) do\n if obj.hit_object.type == \"Card\" then\n return obj.hit_object\n end\n end\n\n return nil\nend\n\n---@param player integer\n---@return gloom_Spawn_Definition[]\nfunction this.getSpawnableElements(player)\n local spawns = {}\n local playerZone = this.getPlayerZone(player)\n ---@type tts__Object[]\n local objects = playerZone.getObjects()\n\n local function mayAddAbilityCard(index)\n local abilityCard = this.getAbilityCard(player, index)\n if abilityCard then\n table.insert(objects, --[[---@not nil]] abilityCard)\n end\n end\n\n mayAddAbilityCard(1)\n mayAddAbilityCard(2)\n\n for _, obj in ipairs(objects) do\n if obj.hasTag(R.Tag.Trait.CanSpawn) and obj.type ~= Object.Type.Deck then\n local objectSpawns = obj.call(\"getSpawnableElements\")\n for _, objectSpawn in ipairs(objectSpawns or {}) do\n objectSpawn = TableUtil.deepCopy(objectSpawn)\n objectSpawn.source = obj.getName()\n objectSpawn.sourceObject = obj\n table.insert(spawns, objectSpawn)\n end\n end\n end\n\n return spawns\nend\n\n---@param object tts__Object | tts__ObjectState\n---@param placement CharacterApi_Placement\nfunction this.placePlayerObject(player, object, placement)\n local position = this.getTargetPosition(player, placement.position, placement.relativeTo)\n local rotation = placement.rotation or { 0, 180, 0 }\n\n if Object.isObject(object) then\n local asObject = --[[---@type tts__Object]] object\n asObject.setPosition(position)\n asObject.setRotation(rotation)\n else\n spawnObjectData({\n data = --[[---@type tts__ObjectState]] object,\n position = position,\n rotation = rotation,\n })\n end\nend\n\n---@param player integer\n---@param position nil | tts__VectorShape\n---@param relativeTo nil | gloom_Class_ExtraTarget\nfunction this.getTargetPosition(player, position, relativeTo)\n local targetObject = this.getTargetObject(player, relativeTo)\n position = position or { 0, 0, 0 }\n return targetObject.positionToWorld(position)\nend\n\n---@param player integer\n---@param relativeTo nil | gloom_Class_ExtraTarget\nfunction this.getTargetObject(player, relativeTo)\n if relativeTo == \"CharacterMat\" then\n return this.getCharacterMat(player)\n elseif relativeTo == \"PlayerMat\" then\n return this.getPlayerMat(player)\n elseif relativeTo == \"CharacterSheet\" then\n return this.getCharacterSheet(player)\n end\n\n return this.getPlayerZone(player)\nend\n\nreturn Party\n\nend)\n__bundle_register(\"Game\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Game = {}\r\n\r\n---@type gloom_PlayerInformation[]\r\nGame.PlayerInfo = {\r\n [1] = {\r\n Color = \"Red\",\r\n Zone = \"49a4e0\",\r\n Loot = { -36.38, 6, -42.54 },\r\n Camera = {\r\n position = { x = -36, y = 0, z = -40 },\r\n pitch = 90,\r\n yaw = 0,\r\n distance = 25,\r\n },\r\n Abilities = {\r\n [1] = { -16.60, 1.82, -23.70 },\r\n [2] = { -13.40, 1.82, -23.70 },\r\n },\r\n },\r\n [2] = {\r\n Color = \"White\",\r\n Zone = \"dac936\",\r\n Loot = { -12.25, 6, -42.54 },\r\n Camera = {\r\n position = { x = -12, y = 0, z = -40 },\r\n pitch = 90,\r\n yaw = 0,\r\n distance = 25,\r\n },\r\n Abilities = {\r\n [1] = { -9.32, 1.82, -23.70 },\r\n [2] = { -6.15, 1.82, -23.70 },\r\n }\r\n },\r\n [3] = {\r\n Color = \"Blue\",\r\n Zone = \"62cd94\",\r\n Loot = { 11.68, 6, -42.54 },\r\n Camera = {\r\n position = { x = 12, y = 0, z = -40 },\r\n pitch = 90,\r\n yaw = 0,\r\n distance = 25,\r\n },\r\n Abilities = {\r\n [1] = { 6.14, 1.82, -23.70 },\r\n [2] = { 9.33, 1.82, -23.70 },\r\n }\r\n },\r\n [4] = {\r\n Color = \"Green\",\r\n Zone = \"963318\",\r\n Loot = { 35.63, 6, -42.54 },\r\n Camera = {\r\n position = { x = 36, y = 0, z = -40 },\r\n pitch = 90,\r\n yaw = 0,\r\n distance = 25,\r\n },\r\n Abilities = {\r\n [1] = { 13.41, 1.82, -23.70 },\r\n [2] = { 16.58, 1.82, -23.70 },\r\n },\r\n },\r\n}\r\nGame.PlayerCount = #Game.PlayerInfo\r\n\r\n---@param player number | tts__PlayerColor\r\n---@return gloom_PlayerInformation\r\nfunction Game.getPlayerInfo(player)\r\n if type(player) == \"number\" then\r\n return Game.PlayerInfo[--[[---@type number]] player]\r\n end\r\n\r\n for _, playerInfo in ipairs(Game.PlayerInfo) do\r\n if playerInfo.Color == player then\r\n return playerInfo\r\n end\r\n end\r\nend\r\n\r\n--- A list of possible player colors. This includes the Black player color.\r\n---@type tts__PlayerColor[]\r\nGame.PossiblePlayerColors = {}\r\nfor _, player in ipairs(Game.PlayerInfo) do\r\n table.insert(Game.PossiblePlayerColors, player.Color)\r\nend\r\ntable.insert(Game.PossiblePlayerColors, \"Black\")\r\n\r\nreturn Game\r\n\nend)\n__bundle_register(\"api.ApiProvider\", function(require, _LOADED, __bundle_register, __bundle_modules)\n---@class ApiProvider\n\n---@class ApiProvider_static\n---@overload fun(name: string): ApiProvider\nlocal ApiProvider = {}\n\n---@param name string\nlocal function new(name)\n local provider = {}\n setmetatable(provider, {\n __newindex = function(_, key, value)\n local apiFunctionName = \"api_\" .. name .. \"_\" .. key\n _G[apiFunctionName] = function(params)\n return value(table.unpack(params))\n end\n end\n })\n return provider\nend\n\nsetmetatable(ApiProvider, {\n ---@param name string\n __call = function(_, name)\n return new(name)\n end\n})\n\nreturn ApiProvider\n\nend)\n__bundle_register(\"lib.Search\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Search = require(\"sebaestschjin-tts.Search\")\r\n\r\nreturn Search\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Search\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"sebaestschjin-tts.Object\")\r\nlocal StringUtil = require(\"sebaestschjin-tts.StringUtil\")\r\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\n\r\nlocal Search = {}\r\n\r\n---@shape seb_Search\r\n---@field guid nil | GUID\r\n---@field name nil | string\r\n---@field description nil | string\r\n---@field memo nil | string\r\n---@field isPattern nil | boolean\r\n\r\n---@shape seb_Search_Full : seb_Search\r\n---@field cardId nil | number\r\n---@field type nil | tts__ObjectType\r\n\r\n---@shape seb_Search_Minimal : seb_Search\r\n---@field cardId nil\r\n---@field type nil\r\n\r\n\r\n---@param searchField nil | string | number\r\n---@param searchValue nil | string | number\r\n---@param isPattern nil | boolean\r\n---@return boolean\r\nlocal function matches(searchField, searchValue, isPattern)\r\n local plain = not isPattern\r\n return searchField == nil or string.find(tostring(searchValue), tostring(searchField), 1, plain) ~= nil\r\nend\r\n\r\n--- Finds the first object in all objects that matches the given search.\r\n---@param search seb_Search_Full\r\n---@return nil | tts__Object\r\nfunction Search.inAllObjects(search)\r\n for _, contained in ipairs(getObjects()) do\r\n if matches(search.guid, contained.getGUID(), search.isPattern)\r\n and matches(search.name, contained.getName(), search.isPattern)\r\n and matches(search.description, contained.getDescription(), search.isPattern)\r\n and matches(search.memo, contained.getMemo(), search.isPattern)\r\n and matches(search.cardId, contained.getData().CardID, search.isPattern)\r\n and matches(search.type, contained.type, search.isPattern)\r\n then\r\n return contained\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n--- Finds the first object in the given container that matches the given search.\r\n---@param object tts__Container\r\n---@param search seb_Search_Minimal\r\n---@return nil | tts__IndexedSimpleObjectState\r\nfunction Search.inContainer(object, search)\r\n for _, contained in ipairs(object.getObjects()) do\r\n if matches(search.guid, contained.guid, search.isPattern)\r\n and matches(search.name, contained.name, search.isPattern)\r\n and matches(search.description, contained.description, search.isPattern)\r\n and matches(search.memo, contained.memo, search.isPattern)\r\n then\r\n return contained\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n--- Finds the first object in the given container that matches the given search.\r\n---@param container tts__ContainerState\r\n---@param search seb_Search_Minimal\r\n---@return nil | tts__ObjectState\r\nfunction Search.inContainerData(container, search)\r\n for _, contained in ipairs(container.ContainedObjects or {}) do\r\n if matches(search.guid, contained.GUID, search.isPattern)\r\n and matches(search.name, contained.Nickname, search.isPattern)\r\n and matches(search.description, contained.Description, search.isPattern)\r\n and matches(search.memo, contained.Memo, search.isPattern)\r\n then\r\n return contained\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n--- Finds the first object in the given scripting zone that matches the given search.\r\n---@param zone tts__ScriptingTrigger\r\n---@param search seb_Search_Full\r\n---@return nil | tts__Object\r\nfunction Search.inZone(zone, search)\r\n for _, contained in ipairs(zone.getObjects()) do\r\n if matches(search.guid, contained.getGUID(), search.isPattern)\r\n and matches(search.name, contained.getName(), search.isPattern)\r\n and matches(search.description, contained.getDescription(), search.isPattern)\r\n and matches(search.memo, contained.getMemo(), search.isPattern)\r\n and matches(search.cardId, contained.getData().CardID, search.isPattern)\r\n and matches(search.type, contained.type, search.isPattern)\r\n then\r\n return contained\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n--- Finds the first object in the given container that matches the given search. Unlike Search.inContainer this search\r\n--- uses the internal representation of the container objects and thus can use more fields for searching. The returned\r\n--- object state also contains a lot more information\r\n--- Note: The returned index starts with 0! This was done to have the same behaviour as the index attribute in\r\n--- tts__IndexedSimpleObjectState.\r\n---@param object tts__Container | seb_WrappedDeck\r\n---@param search seb_Search_Full\r\n---@return nil | (tts__ObjectState, number)\r\nfunction Search.inContainedObjects(object, search)\r\n for i, contained in TableUtil.ipairs(object.getData().ContainedObjects) do\r\n if matches(search.guid, contained.GUID, search.isPattern)\r\n and matches(search.name, contained.Nickname, search.isPattern)\r\n and matches(search.description, contained.Description, search.isPattern)\r\n and matches(search.memo, contained.Memo, search.isPattern)\r\n and matches(search.cardId, contained.CardID, search.isPattern)\r\n and matches(search.type, Object.type(contained), search.isPattern)\r\n then\r\n return contained, i - 1\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n--- Finds the nearest object in the given container. The nearest object is determined by the Levenshtein distance on the\r\n--- given name. If maxDistance is given only objects with a distance lower than this value are considered.\r\n---@overload fun(container: tts__Container | seb_WrappedDeck, name: string): nil | (tts__ObjectState, number)\r\n---@param container tts__Container | seb_WrappedDeck\r\n---@param name string\r\n---@param maxDistance number\r\n---@return nil | (tts__ObjectState, number)\r\nfunction Search.nearestInContainedObjects(container, name, maxDistance)\r\n local nearestDistance, nearestObject\r\n\r\n for _, object in TableUtil.ipairs(container.getData().ContainedObjects) do\r\n local distance = StringUtil.distance(Object.name(object), name)\r\n if (not maxDistance or distance <= maxDistance)\r\n and (not nearestDistance or distance < nearestDistance) then\r\n nearestDistance = distance\r\n nearestObject = object\r\n end\r\n end\r\n\r\n return nearestObject, nearestDistance\r\nend\r\n\r\nreturn Search\r\n\nend)\n__bundle_register(\"Component.internal.DialCounter\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal SaveManager = require(\"lib.SaveManager\")\r\n\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal DialCounter = {}\r\n\r\n---@class __DialCounter_this\r\nlocal this = {}\r\n\r\n---@type string[]\r\nlocal DIAL_TAGS = { R.Tag.Class.HpDial, R.Tag.Class.XpDial }\r\n\r\n---@param dial tts__Object\r\n---@return integer\r\nfunction DialCounter.getValue(dial)\r\n return tonumber(dial.getDescription())\r\nend\r\n\r\n---@param dial tts__Object\r\n---@param value integer\r\nfunction DialCounter.setValue(dial, value)\r\n dial.setDescription(tostring(value))\r\n dial.UI.setAttribute(\"trackerValue\", \"text\", value)\r\nend\r\n\r\n---@param object tts__Object\r\n---@return boolean\r\nfunction this.isRelevant(object)\r\n for _, dialTag in ipairs(DIAL_TAGS) do\r\n if object.hasTag(dialTag) then\r\n return true\r\n end\r\n end\r\n return false\r\nend\r\n\r\n---@param object tts__Object\r\n---@param number integer\r\nfunction this.onObjectNumberTyped(object, _, number)\r\n if this.isRelevant(object) then\r\n DialCounter.setValue(object, number)\r\n end\r\nend\r\n\r\n---@param object tts__Object\r\nfunction this.onObjectSpawn(object)\r\n if this.isRelevant(object) then\r\n object.max_typed_number = 99\r\n Wait.condition(function()\r\n DialCounter.setValue(object, DialCounter.getValue(object))\r\n end, function() return object.UI.getXml() ~= \"\" end)\r\n end\r\nend\r\n\r\nfunction this.onLoad()\r\n for _, dialTag in ipairs(DIAL_TAGS) do\r\n for _, dial in ipairs(getObjectsWithTag(dialTag)) do\r\n this.onObjectSpawn(dial)\r\n end\r\n end\r\nend\r\n\r\nEventManager.addHandler(\"onObjectNumberTyped\", this.onObjectNumberTyped)\r\nEventManager.addHandler(\"onObjectSpawn\", this.onObjectSpawn)\r\nSaveManager.registerOnLoad(this.onLoad)\r\n\r\nreturn DialCounter\r\n\nend)\n__bundle_register(\"lib.SaveManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal SaveManager = require(\"ge_tts.SaveManager\")\r\n\r\nreturn SaveManager\r\n\nend)\n__bundle_register(\"ge_tts.SaveManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\nlocal Logger = require(\"ge_tts.Logger\")\r\nlocal TableUtils = require(\"ge_tts.TableUtils\")\r\n\r\nlocal SAVE_STATE_IDENTIFIER = \"__ge_tts_save__\"\r\n\r\n---@class ge_tts__SaveManager\r\nlocal SaveManager = {}\r\n\r\n---@shape __ge_tts__SaveManager_Callbacks\r\n---@field onLoads (fun(savedState: string): void)[]\r\n---@field onSave nil | (fun(): nil | string)\r\n\r\nlocal ORIGINAL_PSEUDO_MODULE_NAME = '__originalSavedState'\r\n\r\n---@type table\r\nlocal callbacks = {}\r\n\r\nlocal originalOnSave = --[[---@type nil | fun(): string]] _G.onSave\r\nlocal originalOnLoad = --[[---@type nil | fun(savedState: string): void]] _G.onLoad\r\n\r\n---@param moduleName string\r\n---@return __ge_tts__SaveManager_Callbacks\r\nlocal function getModuleCallbacks(moduleName)\r\n local moduleCallbacks = callbacks[moduleName]\r\n\r\n if not moduleCallbacks then\r\n moduleCallbacks = {onLoads = {}}\r\n callbacks[moduleName] = moduleCallbacks\r\n end\r\n\r\n return moduleCallbacks\r\nend\r\n\r\n---@param moduleName string\r\n---@param savedState string\r\nlocal function executeOnLoads(moduleName, savedState)\r\n if moduleName == ORIGINAL_PSEUDO_MODULE_NAME and originalOnLoad then\r\n (--[[---@not nil]] originalOnLoad)(savedState)\r\n else\r\n local onLoads = TableUtils.copy(getModuleCallbacks(moduleName).onLoads) -- Copying because callbacks may modify onLoads whilst we iterate.\r\n\r\n for _, onLoad in ipairs(onLoads) do\r\n onLoad(savedState)\r\n end\r\n end\r\nend\r\n\r\n---\r\n---Registers onSave for the specified moduleName. moduleName must be unique.\r\n---\r\n---Any onLoad registered for the same moduleName will be called with the savedState returned from onSave. This allows\r\n---several Lua modules/files to independently maintain their own savedState.\r\n---\r\n---@param moduleName string\r\n---@param onSave fun(): nil | string\r\nfunction SaveManager.registerOnSave(moduleName, onSave)\r\n Logger.assert(type(moduleName) == 'string' and moduleName ~= '', 'moduleName must be specified')\r\n\r\n local moduleCallbacks = getModuleCallbacks(moduleName)\r\n\r\n Logger.assert(moduleCallbacks.onSave == nil, 'onSave is already registered for module: ' .. moduleName)\r\n\r\n moduleCallbacks.onSave = onSave\r\nend\r\n\r\n\r\n---\r\n---Registers onLoad for the specified moduleName. You may have multiple onLoad registered for the same moduleName.\r\n---\r\n---The provided onLoad function will only be called with data pertaining to the provided moduleName. This allows Lua\r\n---modules to independently maintain their own savedState.\r\n---\r\n---If the moduleName argument is omitted, the provided onLoad will be called with an empty string. This is useful if you\r\n---simply want your onLoad callback called when Tabletop Simulator finished loading, but you don't need any saved state.\r\n---\r\n---@overload fun(onLoad: (fun(savedState: string): void)): boolean\r\n---@overload fun(moduleName: string, onLoad: (fun(savedState: string): void)): boolean\r\n---@param moduleNameOrOnLoad string | fun(savedState: string): void\r\n---@param nilOrOnLoad nil | fun(savedState: string): void\r\nfunction SaveManager.registerOnLoad(moduleNameOrOnLoad, nilOrOnLoad)\r\n if type(moduleNameOrOnLoad) == 'function' then\r\n SaveManager.registerOnLoad('', --[[---@type fun(savedState: string): void]] moduleNameOrOnLoad)\r\n return\r\n end\r\n\r\n Logger.assert(type(moduleNameOrOnLoad) == 'string', 'moduleName must be a string')\r\n\r\n local moduleName = --[[---@type string]] moduleNameOrOnLoad\r\n local moduleCallbacks = getModuleCallbacks(moduleName)\r\n local onLoad = --[[---@type fun(savedState: string): void]] nilOrOnLoad\r\n\r\n table.insert(moduleCallbacks.onLoads, onLoad)\r\nend\r\n\r\n---\r\n---Remove the existing onSave callback for moduleName.\r\n---\r\n---Returns true if there was an existing onSave callback and it was removed, or false if there was already no onSave for moduleName.\r\n---\r\n---@param moduleName string\r\n---@return boolean\r\nfunction SaveManager.removeOnSave(moduleName)\r\n local moduleCallbacks = callbacks[moduleName]\r\n\r\n if moduleCallbacks and moduleCallbacks.onSave then\r\n moduleCallbacks.onSave = nil\r\n return true\r\n end\r\n\r\n return false\r\nend\r\n\r\n---@overload fun(onLoad: (fun(savedState: string): void)): boolean\r\n---@overload fun(moduleName: string, onLoad: (fun(savedState: string): void)): boolean\r\n---@param moduleNameOrOnLoad string | fun(savedState: string): void\r\n---@param nilOrOnLoad nil | fun(savedState: string): void\r\n---@return boolean\r\nfunction SaveManager.removeOnLoad(moduleNameOrOnLoad, nilOrOnLoad)\r\n if type(moduleNameOrOnLoad) == 'function' then\r\n return SaveManager.removeOnLoad('', --[[---@type fun(savedState: string): void]] moduleNameOrOnLoad)\r\n end\r\n\r\n Logger.assert(type(moduleNameOrOnLoad) == 'string', 'SaveManager moduleName must be a string')\r\n\r\n local moduleName = --[[---@type string]] moduleNameOrOnLoad\r\n local moduleCallbacks = callbacks[moduleName]\r\n local onLoad = nilOrOnLoad\r\n\r\n if moduleCallbacks then\r\n for i, existingOnLoad in ipairs(moduleCallbacks.onLoads) do\r\n if existingOnLoad == onLoad then\r\n table.remove(moduleCallbacks.onLoads, i)\r\n return true\r\n end\r\n end\r\n end\r\n\r\n return false\r\nend\r\n\r\n---@return string\r\nfunction onSave()\r\n local savedState = SAVE_STATE_IDENTIFIER\r\n\r\n for moduleName, moduleCallbacks in pairs(callbacks) do\r\n if moduleCallbacks.onSave then\r\n local moduleSavedState = (--[[---@not nil]] moduleCallbacks.onSave)()\r\n\r\n if moduleSavedState ~= nil then\r\n Logger.assert(type(moduleSavedState) == 'string', moduleName .. \"'s onSave returned a \" .. type(moduleSavedState) .. ', a string is required.')\r\n\r\n savedState = savedState .. moduleName:len() .. ' ' .. moduleName .. ' ' .. (--[[---@not nil]] moduleSavedState):len() .. ' ' .. moduleSavedState\r\n end\r\n end\r\n end\r\n\r\n if originalOnSave then\r\n local originalSavedStated = (--[[---@not nil]] originalOnSave)()\r\n savedState = savedState .. ORIGINAL_PSEUDO_MODULE_NAME:len() .. ' ' .. ORIGINAL_PSEUDO_MODULE_NAME .. ' ' .. originalSavedStated:len() .. ' ' .. originalSavedStated\r\n end\r\n\r\n return savedState\r\nend\r\n\r\nlocal GE_MODULE_PREFIX = 'ge_tts.'\r\n\r\n---@param savedState string\r\nfunction onLoad(savedState)\r\n savedState = savedState or ''\r\n\r\n Logger.assert(savedState == '' or savedState:sub(1, SAVE_STATE_IDENTIFIER:len()) == SAVE_STATE_IDENTIFIER, \"When working with ge_tts, you must use ge_tts.SaveManager instead of writing directly to script_state.\")\r\n\r\n local savedStateLength = savedState:len()\r\n local moduleNameOffset = SAVE_STATE_IDENTIFIER:len() + 1\r\n local i = moduleNameOffset\r\n\r\n ---@type table\r\n local moduleStateRanges = {}\r\n\r\n repeat\r\n if savedState:sub(i, i) == ' ' then\r\n local moduleNameLength = tonumber(savedState:sub(moduleNameOffset, i - 1))\r\n local moduleName = savedState:sub(i + 1, i + moduleNameLength)\r\n local moduleSizeOffset = i + moduleNameLength + 2\r\n\r\n for j = moduleSizeOffset, savedStateLength do\r\n if savedState:sub(j, j) == ' ' then\r\n local moduleStateLength = tonumber(savedState:sub(moduleSizeOffset, j - 1))\r\n local moduleSavedStateEnd = j + moduleStateLength\r\n\r\n moduleStateRanges[moduleName] = {\r\n rangeStart = j + 1,\r\n rangeEnd = moduleSavedStateEnd\r\n }\r\n\r\n moduleNameOffset = moduleSavedStateEnd + 1\r\n i = moduleSavedStateEnd + 1\r\n break\r\n end\r\n end\r\n else\r\n i = i + 1\r\n end\r\n until i > savedStateLength\r\n\r\n -- ge_tts listeners execute first\r\n for moduleName, _ in pairs(callbacks) do\r\n if moduleName:sub(1, GE_MODULE_PREFIX:len()) == GE_MODULE_PREFIX then\r\n local stateRange = moduleStateRanges[moduleName]\r\n\r\n if stateRange then\r\n local moduleSavedState = savedState:sub(stateRange.rangeStart, stateRange.rangeEnd)\r\n executeOnLoads(moduleName, moduleSavedState)\r\n else\r\n executeOnLoads(moduleName, '')\r\n end\r\n end\r\n end\r\n\r\n for moduleName, _ in pairs(callbacks) do\r\n if moduleName:sub(1, GE_MODULE_PREFIX:len()) ~= GE_MODULE_PREFIX then\r\n local stateRange = moduleStateRanges[moduleName]\r\n\r\n if stateRange then\r\n local moduleSavedState = savedState:sub(stateRange.rangeStart, stateRange.rangeEnd)\r\n executeOnLoads(moduleName, moduleSavedState)\r\n else\r\n executeOnLoads(moduleName, '')\r\n end\r\n end\r\n end\r\nend\r\n\r\nreturn SaveManager\r\n\nend)\n__bundle_register(\"lib.EventManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"ge_tts.EventManager\")\r\n\r\nreturn EventManager\r\n\nend)\n__bundle_register(\"ge_tts.EventManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\nlocal TableUtils = require(\"ge_tts.TableUtils\")\r\nlocal Logger = require(\"ge_tts.Logger\")\r\n\r\n---@type table\r\nlocal EVENT_DEFAULT_RETURN_VALUES = {\r\n filterObjectEnterContainer = true,\r\n}\r\n\r\n---@type table\r\nlocal eventHandlers = {}\r\n\r\nlocal globalHandlers = --[[---@type {[string]: nil | function}]] _G\r\n\r\n---@param event string\r\nlocal function listen(event)\r\n local previousGlobalHandler = globalHandlers[event]\r\n\r\n ;(--[[---@type table]] _G)[event] = function(...)\r\n local handlers = TableUtils.copy(eventHandlers[event]) -- Copied in case we add/remove handlers during a handler callback\r\n\r\n ---@type std__Packed\r\n local finalResult = --[[---@type std__Packed]] {n = 0}\r\n\r\n for _, handler in ipairs(handlers) do\r\n local result = table.pack(handler(...))\r\n\r\n if result.n > 0 then\r\n finalResult = result\r\n end\r\n end\r\n\r\n if finalResult.n > 0 then\r\n return table.unpack(finalResult, 1, finalResult.n)\r\n else\r\n local defaultValue = EVENT_DEFAULT_RETURN_VALUES[event]\r\n\r\n if defaultValue ~= nil then\r\n return defaultValue\r\n end\r\n end\r\n end\r\n\r\n ---@type function[]\r\n local handlers = {}\r\n\r\n eventHandlers[event] = handlers\r\n\r\n Logger.log('EventManager now listening for ' .. event, Logger.VERBOSE)\r\n\r\n if previousGlobalHandler then\r\n table.insert(handlers, --[[---@not nil]] previousGlobalHandler)\r\n Logger.log('Pre-existing global ' .. event .. ' handler preserved as the first handler', Logger.VERBOSE)\r\n end\r\n\r\n return handlers\r\nend\r\n\r\nlocal SAVE_MANAGER_EVENTS = {'onSave', 'onLoad'}\r\n\r\n---@class ge_tts__EventManager\r\nlocal EventManager = {}\r\n\r\n---@param event string @Event name\r\n---@param handler function @Function that will be called when the event fires. Parameters vary depending on the event.\r\nfunction EventManager.addHandler(event, handler)\r\n assert(not TableUtils.find(SAVE_MANAGER_EVENTS, event), 'EventManager cannot handle ' .. event .. '. Please use SaveManager instead.')\r\n\r\n local handlers = eventHandlers[event] or listen(event)\r\n\r\n if not TableUtils.find(handlers, handler) then\r\n table.insert(handlers, handler)\r\n end\r\nend\r\n\r\n---@param event string @Event name\r\n---@param handler function @A previously registered handler that you wish to remove.\r\nfunction EventManager.removeHandler(event, handler)\r\n assert(not TableUtils.find(SAVE_MANAGER_EVENTS, event), 'EventManager cannot handle ' .. event .. '. Please use SaveManager instead.')\r\n\r\n local handlers = eventHandlers[event]\r\n local handlerIndex = handlers and TableUtils.find(handlers, handler)\r\n\r\n if handlerIndex then\r\n table.remove(handlers, --[[---@not nil]] handlerIndex)\r\n end\r\nend\r\n\r\n---@param event string @Event name\r\n---@vararg any\r\nfunction EventManager.triggerEvent(event, ...)\r\n local handler = globalHandlers[event]\r\n\r\n if handler then\r\n (--[[---@not nil]] handler)(...)\r\n end\r\nend\r\n\r\nreturn EventManager\r\n\nend)\n__bundle_register(\"api.ScenarioApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal ScenarioApi = --[[---@type ScenarioApi]] ApiConsumer(\"scenario\")\n .withApi(\"registerScenario\")\n .withApi(\"registerRandomPool\")\n .withApi(\"getCampaigns\")\n .withApi(\"loadCampaign\")\n .withApi(\"unlockScenario\")\n .withApi(\"calculateFormula\")\n .withApi(\"getScenarioLevel\")\n .withApi(\"getScenarioLevelSettings\")\n .withApi(\"getScenario\")\n .withApi(\"getActiveScenario\")\n .withApi(\"getCharacterCount\")\n .withApi(\"getRandomPool\")\n .withApi(\"getRandomPools\")\n .withApi(\"revealRooms\")\n .withApi(\"revealTreasure\")\n\nScenarioApi.GridType = {\n Horizontal = 1,\n Vertical = 2,\n}\n\nScenarioApi.MonsterLevel = {\n Normal = 1,\n Elite = 2,\n}\n\nScenarioApi.OverlayType = {\n Corridor = 1,\n DifficultTerrain = 2,\n HazardousTerrain = 3,\n Obstacle = 5,\n Trap = 7,\n Treasure = 8,\n Door = 10,\n}\n\nScenarioApi.RandomType = {\n Tokens = 1,\n Objects = 2,\n Card = 3,\n}\n\nreturn ScenarioApi\n\nend)\n__bundle_register(\"api.ApiConsumer\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Api = require(\"api.ApiUtil\").forObject(Global)\n\n---@class ApiConsumer\n\n---@class ApiConsumer_static\n---@overload fun(name: string): ApiConsumer\nlocal ApiConsumer = {}\n\n---@param name string\nlocal function new(name)\n local consumer = --[[---@type ApiConsumer]] {}\n\n ---@param base string\n ---@param name string\n ---@return ApiConsumer\n function consumer.withApi(apiName)\n consumer[apiName] = function(...)\n return Api.call(\"api_\" .. name .. \"_\" .. apiName, table.pack(...))\n end\n\n return consumer\n end\n\n return consumer\nend\n\nsetmetatable(ApiConsumer, {\n ---@param name string\n __call = function(_, name)\n return new(name)\n end\n})\n\nreturn ApiConsumer\n\nend)\n__bundle_register(\"api.ApiUtil\", function(require, _LOADED, __bundle_register, __bundle_modules)\n---@class gloom_Api_Util_Static\r\n---@overload fun(object: GUID | tts__Global | tts__Object_Tag, type: gloom_Api_FindType): gloom_Api_Util\r\nlocal ApiUtil = {}\r\n\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@alias gloom_Api_FindType 'object' | 'guid' | 'tag'\r\n\r\nlocal FindType = {\r\n Object = \"object\",\r\n Guid = \"guid\",\r\n Tag = \"tag\",\r\n}\r\n\r\n---@class gloom_Api_Util\r\n\r\n---@return gloom_Api_Util\r\nlocal function new(object, findType)\r\n local this = --[[---@type gloom_Api_Util]] {}\r\n\r\n ---@type tts__Object\r\n local onObject\r\n\r\n ---@return boolean\r\n local isInGloomhavenMod = Info.name:find(\"^Gloomhaven %- TTS Enhanced\") ~= nil\r\n\r\n ---@return tts__Object\r\n function this.getObject()\r\n if onObject ~= nil then\r\n return onObject\r\n end\r\n\r\n if findType == FindType.Object then\r\n onObject = --[[---@not string]] object\r\n elseif findType == FindType.Guid then\r\n onObject = --[[---@not nil]] getObjectFromGUID(--[[---@type GUID]] object)\r\n elseif findType == FindType.Tag then\r\n local objects = getObjectsWithTag(--[[---@type tts__Object_Tag]] object)\r\n if not objects or not objects[1] then\r\n ApiUtil.error(\"Not object with tag \" .. tostring(object) .. \" found!\")\r\n elseif objects[2] then\r\n ApiUtil.error(\"Multiple objects with tag \" .. tostring(object) .. \" found!\")\r\n else\r\n onObject = objects[1]\r\n end\r\n else\r\n ApiUtil.error(\"Unknown API type: \" .. tostring(findType))\r\n end\r\n\r\n return onObject\r\n end\r\n\r\n ---@param functionName string\r\n ---@param parameters any\r\n local function callInObject(functionName, parameters)\r\n parameters = parameters or {}\r\n if type(parameters) ~= \"table\" then\r\n ApiUtil.error(\"Wrong parameter type given for calling API \" .. functionName)\r\n end\r\n parameters[\"__caller\"] = self.getGUID()\r\n parameters[\"__version\"] = R.Version\r\n\r\n return this.getObject().call(functionName, parameters)\r\n end\r\n\r\n ---@overload fun(functionName: string): any\r\n ---@param functionName string\r\n ---@param parameters any\r\n ---@return any\r\n function this.call(functionName, parameters)\r\n if isInGloomhavenMod then\r\n if this.getObject() == self then\r\n if _G[functionName] then\r\n return _G[functionName](parameters)\r\n end\r\n error(\"The function \" .. functionName .. \" doesn't exist! This will lead to more errors\")\r\n return nil\r\n else\r\n return callInObject(functionName, parameters)\r\n end\r\n else\r\n local hasApiMock = getObjectsWithTag(R.Tag.Component.Mock)[1] ~= nil\r\n if hasApiMock then\r\n ApiUtil.debug(\"Calling API mock \" .. functionName)\r\n findType = FindType.Tag\r\n object = R.Tag.Component.Mock\r\n\r\n local success, value = pcall(function()\r\n return callInObject(functionName, parameters)\r\n end)\r\n\r\n if success then\r\n return value\r\n else\r\n ApiUtil.warning(\"Tried to reach API mock for \" .. functionName .. \" but it returned error \" .. value)\r\n return nil\r\n end\r\n else\r\n ApiUtil.debug(\"Would call API \" .. functionName)\r\n return nil\r\n end\r\n end\r\n end\r\n\r\n return this\r\nend\r\n\r\nsetmetatable(ApiUtil, {\r\n ---@param object GUID | tts__Global | tts__Object_Tag\r\n ---@param findType gloom_Api_FindType\r\n __call = function(_, object, findType)\r\n return new(object, findType)\r\n end\r\n})\r\n\r\n---@param message string\r\nfunction ApiUtil.debug(message)\r\n log(message)\r\nend\r\n\r\n---@param message string\r\nfunction ApiUtil.warning(message)\r\n printToAll(message, \"Yellow\")\r\nend\r\n\r\n---@param message string\r\nfunction ApiUtil.error(message)\r\n printToAll(message, \"Red\")\r\nend\r\n\r\n---@param object GUID | tts__Global\r\n---@return gloom_Api_Util\r\nfunction ApiUtil.forObject(object)\r\n if type(object) == \"string\" then\r\n return ApiUtil(object, FindType.Guid)\r\n end\r\n return ApiUtil(object, FindType.Object)\r\nend\r\n\r\n---@param tag tts__Object_Tag\r\n---@return gloom_Api_Util\r\nfunction ApiUtil.forInstance(tag)\r\n return ApiUtil(tag, FindType.Tag)\r\nend\r\n\r\nreturn ApiUtil\r\n\nend)\n__bundle_register(\"api.OptionsApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\n--- Provides access to the configurable options.\nlocal OptionsApi = --[[---@type OptionsApi]] ApiConsumer(\"options\")\n .withApi(\"registerOptionGroup\")\n .withApi(\"registerOption\")\n .withApi(\"getOptions\")\n .withApi(\"getValues\")\n .withApi(\"setValues\")\n .withApi(\"getOption\")\n .withApi(\"getValue\")\n .withApi(\"setValue\")\n\n--- Defines the type of the option\nOptionsApi.OptionType = {\n Toggle = \"toggle\",\n Image = \"image\",\n Text = \"text\",\n}\n\n--- Possible values for the scenario page option.\n---@type table\nOptionsApi.ScenarioPage = {\n Spawn = \"spawn\", Change = \"change\", None = \"none\",\n}\n\n--- Possible values for the hp changes option.\n---@type table\nOptionsApi.HpChanges = {\n All = \"all\", Character = \"chars\", Monster = \"monster\", None = \"none\",\n}\n\n--- Possible values for the 3D models option.\nOptionsApi.Use3DModels = {\n ClosedDoors = \"door\",\n OpenDoors = \"openDoor\",\n Tree = \"tree\",\n Other = \"other\",\n ParticleEffects = \"particle\",\n}\n\n--- Possible values for the enhancement system to use\nOptionsApi.EnhancementSystem = {\n Classic = \"classic\",\n NonPermanent = \"nonPermanent\",\n}\n\n---@return gloom_Options_Setup_ScenarioPage\nfunction OptionsApi.scenarioBookSettings()\n return --[[---@type gloom_Options_Setup_ScenarioPage]] OptionsApi.getValue(\"setup.scenarioPage\")\nend\n\n---@return boolean\nfunction OptionsApi.useHiddenSetup()\n return --[[---@type boolean]] OptionsApi.getValue(\"setup.useHiddenSetup\")\nend\n\n---@param type gloom_Options_Setup_3DModels\n---@return boolean\nfunction OptionsApi.use3DModels(type)\n local values = --[[---@type set]] OptionsApi.getValue(\"setup.use3D\")\n return values[type]\nend\n\n---@return boolean\nfunction OptionsApi.placeMonstersUpfront()\n return --[[---@type boolean]] OptionsApi.getValue(\"setup.loadMonstersUpfront\")\nend\n\n---@return string\nfunction OptionsApi.dealBattleGoals()\n return --[[---@type string]] OptionsApi.getValue(\"scenario.dealBattleGoals\")\nend\n\n---@return boolean\nfunction OptionsApi.hideAbilityCards()\n return --[[---@type boolean]] OptionsApi.getValue(\"scenario.hideAbilityCards\")\nend\n\n---@return boolean\nfunction OptionsApi.useOpenInformation()\n return --[[---@type boolean]] OptionsApi.getValue(\"variants.openInformation\")\nend\n\n---@return gloom_Options_EnhancementSystem\nfunction OptionsApi.enhancementSystem()\n return --[[---@type gloom_Options_EnhancementSystem]] OptionsApi.getValue(\"variants.enhancementSystem\")\nend\n\nreturn OptionsApi\n\nend)\n__bundle_register(\"Event\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal Logger = require(\"lib.Logger\")\r\n\r\nlocal Event = {}\r\n\r\n---@alias gloom_EventType string\r\n\r\nEvent.EventType = {\r\n BackgroundChanged = \"gloom_onBackgroundChanged\",\r\n CreateGlobalUi = \"gloom_onCreateGlobalUi\",\r\n UpdateAssets = \"gloom_onUpdateAssets\",\r\n ThemeChanged = \"gloom_themeChanged\",\r\n EnemySpawned = \"gloom_onEnemySpawned\",\r\n Scenario = {\r\n Start = \"gloom_onScenarioStart\",\r\n }\r\n}\r\n\r\n---@param event string\r\n---@param handler fun\r\nfunction Event.registerHandler(event, handler)\r\n Logger.debug(\"Registering handler for event %s\", event)\r\n EventManager.addHandler(event, handler)\r\nend\r\n\r\n---@param event string\r\nfunction Event.triggerEvent(event, ...)\r\n Logger.debug(\"Triggering event %s\", event)\r\n EventManager.triggerEvent(event, ...)\r\nend\r\n\r\n---@param handler fun(url: URL): void\r\nfunction Event.registerForBackgroundChanged(handler)\r\n Event.registerHandler(Event.EventType.BackgroundChanged, handler)\r\nend\r\n\r\n---@param handler fun(ui: seb_XmlUi): void\r\nfunction Event.registerForCreateGlobalUi(handler)\r\n Event.registerHandler(Event.EventType.CreateGlobalUi, handler)\r\nend\r\n---@param ui seb_XmlUi\r\nfunction Event.triggerCreateGlobalUi(ui)\r\n Event.triggerEvent(Event.EventType.CreateGlobalUi, ui);\r\nend\r\n\r\n---@param handler fun(ui: seb_XmlUi): void\r\nfunction Event.registerForUpdateAssets(handler)\r\n Event.registerHandler(Event.EventType.UpdateAssets, handler)\r\nend\r\n---@param ui seb_XmlUi\r\nfunction Event.triggerUpdateAssets(ui)\r\n Event.triggerEvent(Event.EventType.UpdateAssets, ui);\r\nend\r\n\r\n---@param handler fun(enemy: tts__Object, summoner: GUID): void\r\nfunction Event.registerForEnemySpawned(handler)\r\n Event.registerHandler(Event.EventType.EnemySpawned, handler)\r\nend\r\n---@param enemy tts__Object\r\n---@param summoner nil | GUID\r\nfunction Event.triggerEnemySpawned(enemy, summoner)\r\n Event.triggerEvent(Event.EventType.EnemySpawned, enemy, summoner)\r\nend\r\n\r\nreturn Event\r\n\nend)\n__bundle_register(\"CommandLine\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ChatCommands = require(\"lib.ChatCommands\")\nlocal Logger = require(\"lib.Logger\")\nlocal Math = require(\"lib.Math\")\n\n--- Defines commands for the chat window that help to debug the mod.\nlocal CommandLine = {}\n\n---@type set\nlocal trueValues = { [\"true\"] = true, [\"yes\"] = true, [\"on\"] = true }\n---@type set\nlocal falseValues = { [\"false\"] = true, [\"no\"] = true, [\"off\"] = true }\n\n---@param name string\n---@param command lib_ChatCommands_Callback\nfunction CommandLine.addCommand(name, command)\n ChatCommands.addCommand(name, command)\nend\n\n---@param value string\n---@param setter fun(value: boolean)\nfunction CommandLine.setBoolean(value, setter)\n local parsed = CommandLine.parseBoolean(value)\n if parsed ~= nil then\n setter(--[[---@not nil]] parsed)\n return\n end\n\n Logger.warn(\"'%s' is not a valid boolean value\", value)\nend\n\n---@param value string\n---@param setter fun(value: integer)\nfunction CommandLine.setInteger(value, setter)\n local parsed = tonumber(value)\n if parsed then\n setter(--[[---@not nil]] parsed)\n return\n end\n\n Logger.warn(\"'%s' is not a valid number\", value)\nend\n\n---@param value string\n---@param options string[]\n---@param setter fun(value: string)\nfunction CommandLine.setOption(value, options, setter)\n for _, option in ipairs(options) do\n if option:lower() == value:lower() then\n setter(option)\n return\n end\n end\n\n Logger.warn(\"'%s' is not a valid option. Possible values are [%s]\", value, options)\nend\n\n---@param value string\n---@return nil | boolean\nfunction CommandLine.parseBoolean(value)\n local lowerValue = value:lower()\n if trueValues[lowerValue] then\n return true\n end\n\n if falseValues[lowerValue] then\n return false\n end\nend\n\n--- Changes to log level to the given value.\n---@param value string\nlocal function changeLogLevel(value)\n local newLevel = Logger[value:upper()]\n\n if newLevel == nil then\n print(\"Unknown logging level \" .. value)\n else\n Logger.setLevel(newLevel)\n print(\"Logging level changed to \" .. value:upper())\n end\nend\n\n---@param player tts__Player\nlocal function printPosition(_, player)\n local object = --[[---@not nil]] player.getHoverObject()\n if not object then\n return\n end\n local position = object.getPosition()\n local rotation = object.getRotation()\n\n local function toString(vec)\n return \"{ \" .. Math.round(vec.x, 2) .. \", \" .. Math.round(vec.y, 2) .. \", \" .. Math.round(vec.z, 2) .. \" }\"\n end\n\n local positionString = \"position = \" .. toString(position) .. \",\\n\"\n local rotationString = \"rotation = \" .. toString(rotation) .. \",\\n\"\n print(positionString, rotationString)\nend\n\nlocal function spawnScenario(scenario)\n if not scenario then\n return\n end\n\n if tonumber(scenario) then\n scenario = tonumber(scenario)\n end\n\n local tabletop = --[[---@not nil]] getObjectFromGUID(\"7ef9dd\")\n tabletop.call(\"cleanArea\")\n tabletop.call(\"cleanMonster\")\n Wait.time(function() createMap({ scenario = scenario }) end, 1)\nend\n\nlocal function runTests()\n local testObject = --[[---@not nil]] getObjectFromGUID(\"abc999\")\n testObject.call(\"runTests\")\nend\n\nChatCommands.setPattern(\"^>([a-zA-Z0-9_-]+)%s*(.*)\")\n\nCommandLine.addCommand(\"log-level\", changeLogLevel)\nCommandLine.addCommand(\"show-pos\", printPosition)\nCommandLine.addCommand(\"spawn-scenario\", spawnScenario)\nCommandLine.addCommand(\"run-tests\", runTests)\n\nreturn CommandLine\n\nend)\n__bundle_register(\"lib.Math\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Math = require(\"sebaestschjin-tts.Math\")\n\nreturn Math\n\nend)\n__bundle_register(\"sebaestschjin-tts.Math\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Math = {}\n\n--- Same as Math.roundUp()\n---@overload fun(value: number): number\n---@param value number\n---@param decimalPlaces number @Defaults to 0.\n---@return number\nfunction Math.round(value, decimalPlaces)\n return Math.roundUp(value, decimalPlaces)\nend\n\n---@overload fun(value: number): number\n---@param value number\n---@param decimalPlaces number @Defaults to 0.\n---@return number\nfunction Math.roundUp(value, decimalPlaces)\n if decimalPlaces and decimalPlaces > 0 then\n local multiple = 10 ^ decimalPlaces\n return math.floor(value * multiple + 0.5) / multiple\n end\n\n return math.floor(value + 0.5)\nend\n\n---@overload fun(value: number): number\n---@param value number\n---@param decimalPlaces number @Defaults to 0.\n---@return number\nfunction Math.roundDown(value, decimalPlaces)\n if decimalPlaces and decimalPlaces > 0 then\n local multiple = 10 ^ decimalPlaces\n return math.ceil(value * multiple + 0.5) / multiple\n end\n\n return math.ceil(value - 0.5)\nend\n\n---@param value number\n---@param min number\n---@param max number\nfunction Math.clamp(value, min, max)\n if value < min then\n return min\n end\n\n if value > max then\n return max\n end\n\n return value\nend\n\nreturn Math\n\nend)\n__bundle_register(\"lib.ChatCommands\", function(require, _LOADED, __bundle_register, __bundle_modules)\n---@alias lib_ChatCommands_Callback ge_tts__ChatCommand_Callback\r\n\r\nlocal ChatCommands = require(\"ge_tts.ChatCommands\")\r\n\r\nreturn ChatCommands\r\n\nend)\n__bundle_register(\"ge_tts.ChatCommands\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\nlocal EventManager = require(\"ge_tts.EventManager\")\r\n\r\n---@alias ge_tts__ChatCommand_Callback fun(value: string, player: tts__Player): void\r\n\r\n---@type table\r\nlocal commandCallbacks = {}\r\n\r\nlocal chatCommandPattern = '^~(.+)~%s*(.*)'\r\n\r\n---@class tts__ChatCommands\r\nlocal ChatCommand = {}\r\n\r\n--- Replaces the current command pattern (default '^~(.+)~%s*(.*)').\r\n---\r\n--- The pattern must have two (and only two) capture groups, the first being the command name, and\r\n--- the second being the value, which once captured will be passed to command callbacks.\r\n---@param pattern string\r\nfunction ChatCommand.setPattern(pattern)\r\n chatCommandPattern = pattern\r\nend\r\n\r\n---@param command string\r\n---@param callback ge_tts__ChatCommand_Callback\r\nfunction ChatCommand.addCommand(command, callback)\r\n commandCallbacks[command] = callback\r\nend\r\n\r\n---@param command string\r\nfunction ChatCommand.removeCommand(command)\r\n commandCallbacks[command] = nil\r\nend\r\n\r\n---@param message string\r\n---@param player tts__Player\r\nlocal function onChat(message, player)\r\n local _, _, command, value = message:find(chatCommandPattern)\r\n\r\n if not command then\r\n return true\r\n end\r\n\r\n local callback = commandCallbacks[--[[---@type string]] command]\r\n\r\n if callback then\r\n callback(--[[---@type string]] value, player)\r\n else\r\n broadcastToColor('Unknown command: ' .. command, player.color, 'Red')\r\n end\r\n\r\n return false\r\nend\r\n\r\nEventManager.addHandler('onChat', onChat)\r\n\r\nreturn ChatCommand\r\n\nend)\n__bundle_register(\"utils.SectionTool\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal SaveManager = require(\"lib.SaveManager\")\r\nlocal Logger = require(\"lib.Logger\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\nlocal Math = require(\"lib.Math\")\r\n\r\nlocal CommandLine = require(\"CommandLine\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@class gloom_SectionTool\r\nlocal SectionTool = {}\r\n\r\n---@class __gloom_SectionTool_this\r\nlocal this = {}\r\n\r\n---@shape gloom_SectionTool_Settings\r\n---@field mode gloom_SectionTool_Mode\r\n---@field prefix gloom_SectionTool_Prefix\r\n---@field resetCounter boolean\r\n---@field pageOffset integer\r\n\r\n---@alias gloom_SectionTool_Mode 'alpha' | 'numeric'\r\n---@alias gloom_SectionTool_Prefix 'none' | 'page'\r\n\r\n---@type gloom_SectionTool_Settings\r\nlocal settings = {\r\n mode = \"alpha\",\r\n prefix = \"page\",\r\n resetCounter = true,\r\n pageOffset = 1,\r\n}\r\n\r\nlocal MODES = { \"alpha\", \"numeric\" }\r\nlocal PREFIX_MODES = { \"none\", \"page\" }\r\n\r\n--- The currently edited section book.\r\n---@type tts__Book\r\nlocal bookObject\r\n---@type gloom_SectionBook\r\nlocal bookPages = {}\r\n---@type integer\r\nlocal currentSection = 1\r\n---@type nil | tts__Vector\r\nlocal currentTopLeft\r\n---@type nil | string\r\nlocal lastSection\r\n\r\n--- Starts editing the given Section Book.\r\n---@param object tts__Book\r\nfunction SectionTool.startEdit(object)\r\n if not object or not object.Book or not object.hasTag(R.Tag.Book.Sections) then\r\n Logger.error(\"Invalid object. Select a valid PDF Section Book to start editing.\")\r\n return\r\n end\r\n\r\n local current = object.memo\r\n if current and current ~= \"\" then\r\n bookPages = --[[---@type gloom_SectionBook]] JSON.decode(--[[---@not nil]] current)\r\n else\r\n bookPages = {}\r\n end\r\n bookObject = object\r\nend\r\n\r\n---@param point tts__Vector\r\nfunction SectionTool.addPoint(point)\r\n point.x = Math.round(point.x, 2)\r\n point.y = Math.round(point.y, 2)\r\n point.z = Math.round(point.z, 2)\r\n\r\n if currentTopLeft ~= nil then\r\n this.addRegion(--[[---@not nil]] currentTopLeft, point)\r\n currentTopLeft = nil\r\n currentSection = currentSection + 1\r\n this.updateBook()\r\n else\r\n currentTopLeft = point\r\n end\r\nend\r\n\r\nfunction SectionTool.undoLastAction()\r\n if currentTopLeft ~= nil then\r\n currentTopLeft = nil\r\n elseif lastSection then\r\n currentSection = currentSection - 1\r\n for _, page in pairs(bookPages) do\r\n if page.sections[--[[---@not nil]] lastSection] then\r\n page.sections[--[[---@not nil]] lastSection] = nil\r\n lastSection = nil\r\n this.updateBook()\r\n end\r\n end\r\n end\r\nend\r\n\r\n---@param point tts__Vector\r\nfunction SectionTool.removeSectionAt(point)\r\n ---@param value number\r\n ---@param a number\r\n ---@param b number\r\n ---@return boolean\r\n local function isBetween(value, a, b)\r\n return value >= a and value <= b or value <= a and value >= b\r\n end\r\n\r\n ---@param region gloom_SectionBook_Region\r\n ---@return boolean\r\n local function isInRegion(region)\r\n return isBetween(point.x, region[1].x, region[2].x)\r\n and isBetween(point.z, region[1].z, region[2].z)\r\n end\r\n\r\n for _, page in pairs(bookPages) do\r\n for i, section in pairs(page.sections) do\r\n if isInRegion(section.region) then\r\n page.sections[i] = nil\r\n this.updateBook()\r\n return\r\n end\r\n end\r\n end\r\nend\r\n\r\nfunction SectionTool.saveBook()\r\n if not bookObject then\r\n return\r\n end\r\n\r\n bookObject.memo = JSON.encode(bookPages)\r\nend\r\n\r\n---@param mode string\r\nfunction SectionTool.setMode(mode)\r\n if TableUtil.contains(MODES, mode) then\r\n settings.mode = --[[---@type gloom_SectionTool_Mode]] mode\r\n else\r\n Logger.error(\"Unknown mode setting %s\", mode)\r\n end\r\nend\r\n\r\n---@param reset boolean\r\nfunction SectionTool.setResetOnPageChange(reset)\r\n settings.resetCounter = reset\r\nend\r\n\r\n---@param prefix string\r\nfunction SectionTool.setPrefix(prefix)\r\n if TableUtil.contains(PREFIX_MODES, prefix) then\r\n settings.prefix = --[[---@type gloom_SectionTool_Prefix]] prefix\r\n else\r\n Logger.warn(\"Unknown prefix setting %s\", prefix)\r\n end\r\nend\r\n\r\n---@param offset integer\r\nfunction SectionTool.setPageOffset(offset)\r\n settings.pageOffset = offset\r\nend\r\n\r\nfunction this.updateBook()\r\n if bookObject then\r\n bookObject.call(\"setPages\", bookPages)\r\n end\r\nend\r\n\r\n---@param topLeft tts__Vector\r\n---@param bottomRight tts__Vector\r\nfunction this.addRegion(topLeft, bottomRight)\r\n ---@param pageNumber integer\r\n ---@return gloom_SectionBook_Page\r\n local function getPage(pageNumber)\r\n if not bookPages[pageNumber] then\r\n bookPages[pageNumber] = { page = pageNumber, sections = {} }\r\n end\r\n\r\n return bookPages[pageNumber]\r\n end\r\n\r\n ---@param pageNumber integer\r\n ---@return string\r\n local function getSectionName(pageNumber)\r\n local name = \"\"\r\n if settings.prefix == \"page\" then\r\n if settings.pageOffset then\r\n name = name .. (pageNumber + settings.pageOffset)\r\n else\r\n name = name .. pageNumber\r\n end\r\n end\r\n\r\n if settings.mode == \"alpha\" then\r\n name = name .. string.char(currentSection + 64)\r\n elseif settings.mode == \"numeric\" then\r\n name = name .. currentSection\r\n end\r\n\r\n return name\r\n end\r\n\r\n local pageNumber = bookObject.Book.getPage()\r\n local page = getPage(pageNumber)\r\n local sectionName = getSectionName(pageNumber)\r\n ---@type gloom_SectionBook_Section\r\n local section = {\r\n id = sectionName,\r\n name = sectionName,\r\n region = { topLeft, bottomRight }\r\n }\r\n\r\n lastSection = section.id\r\n page.sections[section.id] = section\r\nend\r\n\r\n---@param object tts__Object\r\nfunction this.pageChanged(object)\r\n if object == bookObject then\r\n if settings.resetCounter then\r\n currentSection = 1\r\n end\r\n end\r\nend\r\n\r\nfunction this.load()\r\n addHotkey(\"Section Tool: Add Point\", function(player, obj, pointer)\r\n if not obj or obj ~= bookObject then\r\n Logger.warn(\"Wrong object selected. Start edit mode for this object first.\")\r\n return\r\n end\r\n\r\n local localPosition = bookObject.positionToLocal(--[[---@not nil]] pointer)\r\n SectionTool.addPoint(localPosition)\r\n\r\n Player[player].pingTable(--[[---@not nil]] pointer)\r\n end)\r\n\r\n addHotkey(\"Section Tool: Undo Last Action\", function()\r\n SectionTool.undoLastAction()\r\n end)\r\n\r\n addHotkey(\"Section Tool: Remove Section at Cursor\", function(_, obj, pointer)\r\n if not obj or obj ~= bookObject then\r\n Logger.warn(\"Wrong object selected. Start edit mode for this object first.\")\r\n return\r\n end\r\n\r\n local localPosition = bookObject.positionToLocal(--[[---@not nil]] pointer)\r\n SectionTool.removeSectionAt(localPosition)\r\n end)\r\nend\r\n\r\nCommandLine.addCommand(\"section-tool-start\", function(_, player)\r\n local obj = player.getHoverObject()\r\n SectionTool.startEdit(--[[---@type tts__Book]] obj)\r\nend)\r\n\r\nCommandLine.addCommand(\"section-tool-save\", function()\r\n SectionTool.saveBook()\r\nend)\r\n\r\nCommandLine.addCommand(\"section-tool-set-mode\", function(value)\r\n CommandLine.setOption(value, MODES, SectionTool.setMode)\r\nend)\r\n\r\nCommandLine.addCommand(\"section-tool-set-reset\", function(value)\r\n CommandLine.setBoolean(value, SectionTool.setResetOnPageChange)\r\nend)\r\n\r\nCommandLine.addCommand(\"section-tool-set-prefix\", function(value)\r\n CommandLine.setOption(value, PREFIX_MODES, SectionTool.setPrefix)\r\nend)\r\n\r\nCommandLine.addCommand(\"section-tool-set-offset\", function(value)\r\n CommandLine.setInteger(value, SectionTool.setPageOffset)\r\nend)\r\n\r\nCommandLine.addCommand(\"section-tool-config\", function()\r\n local out = [[\r\nMode : ]] .. settings.mode .. [[\r\nPrefix : ]] .. settings.prefix .. [[\r\nReset at page: ]] .. settings.resetCounter .. [[\r\nPage Offset : ]] .. settings.pageOffset .. [[\r\n]]\r\n print(out)\r\nend)\r\n\r\nEventManager.addHandler(\"onObjectPageChange\", this.pageChanged)\r\n\r\nSaveManager.registerOnLoad(\"Component.SectionTool\", this.load)\r\n\r\nreturn SectionTool\r\n\nend)\n__bundle_register(\"InitiativeTracker\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\n\r\nlocal UiApi = require(\"api.UiApi\")\r\n\r\nlocal InitiativeTracker = {}\r\n\r\n---@class __InitiativeTricker_this\r\nlocal this = {}\r\n\r\nlocal initiativeList = {}\r\n\r\nlocal MaxRows = 30\r\n\r\nfunction InitiativeTracker.add(params)\r\n if not this.validateEntry(params) then\r\n Logger.warn(\"Invalid entry for Initiative Tracker. It will be ignored\")\r\n return\r\n end\r\n table.insert(initiativeList, params)\r\nend\r\n\r\nfunction InitiativeTracker.render()\r\n table.sort(initiativeList,\r\n function(a, b)\r\n if a.first == b.first then\r\n return a.second < b.second\r\n else\r\n return a.first < b.first\r\n end\r\n end)\r\n\r\n for k = 1, MaxRows do\r\n local value = initiativeList[k]\r\n wipeRow(k)\r\n if value ~= nil then\r\n UI.setAttribute(\"Initiative_\" .. k, \"active\", \"true\")\r\n UI.setAttribute(\"InitiativeButton_\" .. k, \"text\", tostring(value.first) .. \" - \" .. value.name)\r\n UI.setAttribute(\"InitiativeButton_\" .. k, \"value\", value.name)\r\n if (value.marked) then\r\n UI.setAttribute(\"InitiativeButton_\" .. k, \"color\", \"rgba(0.6, 0, 0, 0.95)\")\r\n end\r\n UI.setAttribute(\"InitiativeButton_\" .. k, \"textColor\", \"#FFFFFF\")\r\n\r\n if value.multi then\r\n UI.setAttribute(\"HideInitiative_\" .. k, \"text\", \"X\")\r\n UI.setAttribute(\"HideInitiative_\" .. k, \"active\", \"true\")\r\n UI.setAttribute(\"HideInitiative_\" .. k, \"textColor\", \"#FFFFFF\")\r\n end\r\n if value.type == \"Monster\" then\r\n UI.setAttribute(\"MonsterAbilityCard_\" .. k, \"text\", \">\")\r\n UI.setAttribute(\"MonsterAbilityCard_\" .. k, \"active\", \"true\")\r\n UI.setAttribute(\"MonsterAbilityCard_\" .. k, \"textColor\", \"#FFFFFF\")\r\n end\r\n end\r\n end\r\n\r\n Global.UI.setAttribute(\"iniList\", \"height\", 10 + #initiativeList * 35 + (#initiativeList - 1) * 5)\r\nend\r\n\r\nfunction InitiativeTracker.reset()\r\n initiativeList = {}\r\n for k = 1, MaxRows do\r\n wipeRow(k)\r\n end\r\n Global.UI.setAttribute(\"iniList\", \"height\", 10)\r\n Wait.frames(function()\r\n this.hideMonsterAbility()\r\n end, 10)\r\nend\r\n\r\nfunction wipeRow(index)\r\n UI.setAttribute(\"Initiative_\" .. index, \"active\", \"false\")\r\n UI.setAttribute(\"InitiativeButton_\" .. index, \"value\", \"\")\r\n UI.setAttribute(\"InitiativeButton_\" .. index, \"text\", \"\")\r\n UI.setAttribute(\"InitiativeButton_\" .. index, \"color\", \"rgba(0, 0, 0, 0.95)\")\r\n UI.setAttribute(\"HideInitiative_\" .. index, \"active\", \"false\")\r\n UI.setAttribute(\"MonsterAbilityCard_\" .. index, \"active\", \"false\")\r\nend\r\n\r\nlocal notRestingMessages = {\r\n \" has decided to stay up and party\",\r\n \" got woken up by their own snoring\",\r\n \" realised they had more important things to do than long rest\",\r\n}\r\n\r\nfunction InitiativeTracker.LongRest(params)\r\n for i, j in ipairs(initiativeList) do\r\n if j.class == params.class and j.longRest then\r\n table.remove(initiativeList, i)\r\n broadcastToAll(params.class .. notRestingMessages[math.random(#notRestingMessages)], params.color)\r\n InitiativeTracker.render()\r\n return\r\n end\r\n end\r\n InitiativeTracker.add({\r\n class = params.class,\r\n type = \"Character\",\r\n name = params.class .. \" - Long Rest\",\r\n longRest = true,\r\n first = 99,\r\n second = 100,\r\n })\r\n broadcastToAll(params.class .. \" is taking a long rest\", params.color)\r\n InitiativeTracker.render()\r\nend\r\n\r\nfunction getMonsterAC(player, value, id)\r\n local row = tonumber(id:sub(20))\r\n this.hideMonsterAbility()\r\n\r\n if UI.getAttribute(id, \"color\") == \"rgba(0, 0, 0, 0.95)\" then\r\n for k, _ in ipairs(initiativeList) do\r\n UI.setAttribute(\"MonsterAbilityCard_\" .. k, \"color\", \"rgba(0, 0, 0, 0.95)\")\r\n UI.setAttribute(\"MonsterAbilityCard_\" .. k, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\n end\r\n UI.setAttribute(id, \"color\", \"rgba(0.6, 0, 0, 0.95)\")\r\n UI.setValue(\"MACText\", this.getAbilityText(row))\r\n UI.setAttribute(\"MonsterAbilityCard\", \"active\", true)\r\n else\r\n UI.setAttribute(id, \"color\", \"rgba(0, 0, 0, 0.95)\")\r\n end\r\n UI.setAttribute(id, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\nend\r\n\r\nfunction InitiativeTracker.hasInitiativeForClass(class)\r\n for _, v in pairs(initiativeList) do\r\n if v.class == class then\r\n return true\r\n end\r\n end\r\n return false\r\nend\r\n\r\nfunction MarkInitiative(player, value, id)\r\n local row = tonumber(id:sub(18))\r\n local init = initiativeList[row]\r\n if not init.marked then\r\n UI.setAttribute(id, \"color\", \"rgba(0.6, 0, 0, 0.95)\")\r\n if not init.hidden and init.type == \"Character\" then\r\n Global.call(\"looting\", init.name)\r\n end\r\n init.marked = true\r\n else\r\n UI.setAttribute(id, \"color\", \"rgba(0, 0, 0, 0.95)\")\r\n init.marked = false\r\n end\r\n UI.setAttribute(id, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\nend\r\n\r\nfunction UnmarkInitiative()\r\n -- This is triggered when you draw for a monster, if its already marked, then unmark it\r\n -- TODO\r\n -- UI.setAttribute(id, \"color\", \"rgba(0, 0, 0, 0.95)\")\r\n -- init.marked = false\r\nend\r\n\r\nfunction HideInitiative(player, value, id)\r\n local row = tonumber(id:sub(16))\r\n local init = initiativeList[row]\r\n if init.hidden then\r\n init.hidden = false\r\n UI.setAttribute(\"InitiativeButton_\" .. row, \"text\", tostring(init.first) .. \" - \" .. init.name)\r\n UI.setAttribute(\"InitiativeButton_\" .. row, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\n UI.setAttribute(\"HideInitiative_\" .. row, \"text\", \"X\")\r\n UI.setAttribute(\"HideInitiative_\" .. row, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\n else\r\n init.hidden = true\r\n UI.setAttribute(\"InitiativeButton_\" .. row, \"text\", \"\")\r\n UI.setAttribute(\"InitiativeButton_\" .. row, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\n UI.setAttribute(\"HideInitiative_\" .. row, \"text\", \"+\")\r\n UI.setAttribute(\"HideInitiative_\" .. row, \"textColor\", \"rgba(1, 1, 1, 1)\")\r\n end\r\nend\r\n\r\nfunction this.hideMonsterAbility()\r\n UI.setAttribute(\"MonsterAbilityCard\", \"active\", false)\r\n UI.setAttribute(\"monster-ability-area-1\", \"active\", false)\r\n UI.setAttribute(\"monster-ability-area-2\", \"active\", false)\r\n UI.setAttribute(\"MACText\", \"text\", \"\")\r\nend\r\n\r\n---@return boolean\r\nfunction this.validateEntry(entry)\r\n if not entry.type then\r\n Logger.warn(\"No type for initiative entry given.\")\r\n return false\r\n end\r\n\r\n if not entry.name then\r\n Logger.warn(\"No name for initiative entry given.\")\r\n return false\r\n end\r\n\r\n if not entry.first or not entry.second then\r\n Logger.warn(\"No initiative value given.\")\r\n return false\r\n end\r\n\r\n return true\r\nend\r\n\r\n---@param row integer\r\n---@return string\r\nfunction this.getAbilityText(row)\r\n ---@type string\r\n local baseText = initiativeList[row].ability\r\n if not baseText then\r\n return \"\"\r\n end\r\n\r\n baseText = baseText:gsub(\";\", \"\\n\")\r\n return UiApi.renderText(baseText, { { Global, \"monster-ability-area-1\" }, { Global, \"monster-ability-area-2\" } })\r\nend\r\n\r\nreturn InitiativeTracker\r\n\nend)\n__bundle_register(\"api.UiApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal UiApi = --[[---@type UiApi]] ApiConsumer(\"ui\")\n .withApi(\"renderText\")\n .withApi(\"renderAreaTo\")\n\n---@param glyph string\nfunction UiApi.solidText(glyph)\n return glyph\nend\n\n---@param glyph string\n---@param color string\n---@return string\nfunction UiApi.coloredText(glyph, color)\n return '' .. glyph .. ''\nend\n\n---@param glyph string\n---@param color string\n---@return string\nfunction UiApi.conditionText(glyph, color)\n return UiApi.coloredText(\"\\u{E09F}\", color) -- background\n .. UiApi.coloredText(glyph, \"#FFF\") -- actual condition\nend\n\n---@param glyph string\n---@param color string\n---@return string\nfunction UiApi.elementText(glyph, color)\n return UiApi.coloredText(\"\\u{E068}\", color) -- background\n .. UiApi.coloredText(glyph, \"#FFF\") -- actual element\nend\n\n---@param glyph string\n---@param color string\n---@return string\nfunction UiApi.consumeElementText(glyph, color)\n return UiApi.elementText(glyph, color)\n .. UiApi.coloredText(\"\\u{E067}\", \"#E16868\") .. UiApi.coloredText(\"\\u{E077}\", \"#FFF\")\nend\n\n---@param glyph string\n---@param color string\n---@return string\nfunction UiApi.areaText(glyph, color)\n return UiApi.coloredText(\"\\u{E0E8}\", color) -- background\n .. UiApi.coloredText(\"\\u{E0E7}\", \"black\") -- thin border\n .. UiApi.coloredText(glyph, \"white\") -- actual area\nend\n\nreturn UiApi\n\nend)\n__bundle_register(\"UpdateChecker\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal R = require(\"api.Resource\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal SaveManager = require(\"lib.SaveManager\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Version = require(\"lib.Version\")\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal GloomUi = require(\"ui.GloomUi\")\n\nlocal UpdateChecker = {}\n\n---@class __UpdateChecker_this\nlocal this = {}\n\n---@shape ChangeNotes\n---@field version string\n---@field release string\n---@field guide nil | URL\n---@field nickname nil | string\n---@field breaking nil | string[]\n---@field feature nil | string[]\n---@field fix nil | string[]\n---@field known nil | string[]\n\nlocal Ui = {\n Window = {\n Id = \"UpdateWindow\",\n Height = 475, Width = 800,\n },\n Header = {\n Height = 80,\n },\n Content = {\n Id = \"UpdateContent\",\n Width = 650,\n Height = 440,\n },\n Notification = {\n Id = \"UpdateNotification\",\n Asset = \"NewUpdateNotification\",\n },\n}\n\nlocal categories = {\n breaking = \"Breaking Changes\",\n feature = \"New Features\",\n fix = \"Bug Fixes\",\n known = \"Known Issues\"\n}\n\n---@type ChangeNotes[]\nlocal changes = {}\n\n---@param callback fun(changes: ChangeNotes[])\nlocal function getChangeNotes(callback)\n local host = \"https://raw.githubusercontent.com/gloomhaven-tts-enhanced/public-scripts/main\"\n --local host = \"http://localhost:3002\"\n local url = host .. \"/changes.json\"\n local headers = --[[---@type table]] {\n [\"User-Agent\"] = \"Gloomhaven Enhanced TTS\",\n }\n\n WebRequest.custom(url, \"GET\", true, nil, headers, function(response)\n if response.response_code == 200 then\n local responseContent = json.parse(response.text)\n callback(--[[---@type ChangeNotes[] ]] responseContent)\n else\n Logger.error(\"Error getting change notes\\n%s (%d)\", response.text, response.response_code)\n end\n end)\nend\n\n---@param content seb_XmlUi_Element\n---@param changeNotes ChangeNotes\n---@return integer\nlocal function parseChangeNotes(content, changeNotes)\n local versionName = changeNotes.version\n if changeNotes.nickname then\n versionName = versionName .. \" (\" .. changeNotes.nickname .. \")\"\n end\n local title = \"Version \" .. versionName .. \" - Release: \" .. changeNotes.release\n content.addText({\n text = title,\n color = GloomUi.Color.Yellow,\n fontStyle = XmlUi.FontStyle.Bold, fontSize = 30,\n alignment = XmlUi.Alignment.MiddleCenter,\n })\n\n local categoryCount = 0\n local entryCount = 0\n\n for category, categoryName in pairs(categories) do\n if changeNotes[category] then\n content.addText({\n text = categoryName,\n color = GloomUi.Color.Yellow,\n alignment = XmlUi.Alignment.UpperLeft,\n fontStyle = XmlUi.FontStyle.Bold, fontSize = 20,\n })\n categoryCount = categoryCount + 1\n\n for _, element in ipairs(changeNotes[category] or {}) do\n local entry = element\n entryCount = entryCount + 1\n\n ---@param line string\n local function replaceMarkup(line)\n line = StringUtil.replace(line, \"%*%*([^*]+)%*%*\", \"%1\")\n\n return line\n end\n\n entry = replaceMarkup(entry)\n entry = \"\\t\u2022 \" .. entry\n\n content.addText({\n text = entry,\n color = GloomUi.Color.Yellow,\n alignment = XmlUi.Alignment.UpperLeft,\n })\n end\n end\n end\n\n return (categoryCount + entryCount) * 40\nend\n\n---@param ui seb_XmlUi\n---@return seb_XmlUi_Element\nlocal function createWindow(ui)\n local window = ui.addPanel({\n id = Ui.Window.Id,\n width = Ui.Window.Width, height = Ui.Window.Height,\n rectAlignment = XmlUi.Alignment.MiddleCenter,\n color = GloomUi.Color.Black,\n allowDragging = true, returnToOriginalPositionWhenReleased = false,\n active = false,\n })\n\n local windowMenu = window.addPanel({\n width = Ui.Window.Width,\n --height = Ui.Header.Height,\n color = GloomUi.Color.Grey,\n })\n windowMenu.addText({\n text = \"Gloomhaven Enhanced - Updates\",\n color = GloomUi.Color.Yellow,\n height = Ui.Header.Height,\n offsetXY = { 0, 55 },\n alignment = XmlUi.Alignment.LowerCenter, rectAlignment = XmlUi.Alignment.UpperCenter,\n })\n windowMenu.addImage({\n image = \"element.close\",\n onClick = \"onCloseUpdateWindowClicked\",\n height = 30, width = 30,\n rectAlignment = XmlUi.Alignment.UpperRight,\n offsetXY = { -3, 0 },\n })\n\n local content = window .addVerticalScrollView({\n width = Ui.Window.Width, height = Ui.Content.Height, rectAlignment = XmlUi.Alignment.LowerCenter,\n color = GloomUi.Color.Black,\n scrollbarBackgroundColor = GloomUi.Color.Grey,\n scrollbarColors = { GloomUi.Color.Yellow, GloomUi.Color.Yellow, GloomUi.Color.Yellow, GloomUi.Color.Yellow },\n scrollSensitivity = 30\n }) .addVerticalLayout({\n id = Ui.Content.Id,\n width = Ui.Content.Width, height = 0,\n padding = { 8, 4, 4, 4 }, spacing = 4,\n childAlignment = XmlUi.Alignment.UpperLeft, rectAlignment = XmlUi.Alignment.UpperLeft,\n })\n\n return content\nend\n\n---@param ui seb_XmlUi\nlocal function createNotification(ui)\n local notification = ui.addPanel({\n id = Ui.Notification.Id,\n width = 200,\n height = 100,\n rectAlignment = XmlUi.Alignment.LowerRight,\n onClick = \"onOpenUpdateWindowClicked\",\n })\n\n notification.addImage({\n image = Ui.Notification.Asset,\n })\n\n local invisible = { 0, 0, 0, 0 }\n notification.addButton({\n width = 25, height = 25,\n colors = { invisible, invisible, invisible, invisible },\n rectAlignment = XmlUi.Alignment.UpperRight,\n offsetXY = { -3, -3 },\n onClick = \"onCloseUpdateNotificationClicked\",\n })\nend\n\nlocal function createUi()\n local ui = XmlUi(Global)\n\n local content = ui.findElement(Ui.Content.Id)\n if not content then\n createNotification(ui)\n content = createWindow(ui)\n else\n (--[[---@not nil]] content).clearElements()\n end\n\n local totalSize = 0\n for _, changeNotes in ipairs(changes) do\n local size = parseChangeNotes(--[[---@not nil]] content, changeNotes)\n totalSize = totalSize + size\n end\n (--[[---@not nil]] content).setHeight(totalSize)\n\n ui.update()\nend\n\n--- Returns the change notes for the current mod version\n---@param changeNotes ChangeNotes[]\nlocal function getCurrentVersion(changeNotes)\n local version = Version(R.Version)\n for _, notes in ipairs(changeNotes) do\n local changeVersion = Version(notes.version)\n if changeVersion == version then\n return notes\n end\n end\nend\n\n--- Updates all mod guide books if a newer version is available.\n---@param changeNotes ChangeNotes The change notes for the current mod version.\nlocal function updateGuideBook(changeNotes)\n if changeNotes.guide then\n for _, obj in ipairs(getObjectsWithTag(R.Tag.Book.Guide)) do\n local data = obj.getData()\n if data.CustomPDF.PDFUrl ~= changeNotes.guide then\n Logger.info(\"Updated Mod Guide to a newer version.\")\n data.CustomPDF.PDFUrl = changeNotes.guide\n obj.destruct()\n spawnObjectData({ data = data })\n end\n end\n end\nend\n\nlocal function determineVersions()\n getChangeNotes(function(changeNotes)\n local index = this.getLatestRelease(changeNotes)\n if index then\n Logger.info(\"New mod version found: %s\", changeNotes[index].version)\n\n changes = {}\n local version = Version(R.Version)\n for i = index, #changeNotes do\n local change = changeNotes[i]\n local changeVersion = Version(change.version)\n if changeVersion == version or changeVersion.isBefore(version) then\n break\n end\n\n -- only add beta changes, when in beta\n if not changeVersion.isBeta or version.isBeta then\n table.insert(changes, change)\n end\n end\n\n createUi()\n end\n\n local currentVersion = getCurrentVersion(changeNotes)\n if currentVersion then\n updateGuideBook(currentVersion)\n end\n end)\nend\n\nlocal function onLoad()\n Wait.time(function()\n determineVersions()\n end, 5)\nend\n\n---@param changeNotes ChangeNotes[]\n---@return nil | integer\nfunction this.getLatestRelease(changeNotes)\n local version = Version(R.Version)\n\n for i, change in ipairs(changeNotes) do\n if StringUtil.isNotEmpty(change.release) then\n local changeVersion = Version(change.version)\n if changeVersion.isAfter(version) then\n if not changeVersion.isBeta or version.isBeta then\n return i\n end\n end\n end\n end\n\n return nil\nend\n\nfunction onCloseUpdateNotificationClicked()\n Global.UI.hide(Ui.Notification.Id)\nend\n\nfunction onOpenUpdateWindowClicked()\n Global.UI.show(Ui.Window.Id)\nend\n\nfunction onCloseUpdateWindowClicked()\n Global.UI.hide(Ui.Window.Id)\nend\n\nSaveManager.registerOnLoad(onLoad)\n\nreturn UpdateChecker\n\nend)\n__bundle_register(\"ui.GloomUi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal GloomUiSetting = require(\"ui.GloomUiSetting\")\nlocal GloomUiFactory = require(\"ui.GloomUiFactory\")\n\nlocal GloomUi = {}\n\nrequire(\"ui.element.Dropdown\")\nrequire(\"ui.element.ImageChooser\")\nrequire(\"ui.element.MultiDropdown\")\nrequire(\"ui.element.Toggle\")\n\nGloomUi.Color = GloomUiSetting.Color\nGloomUi.Factory = GloomUiFactory\nGloomUi.Font = GloomUiSetting.Font\n\nreturn GloomUi\n\nend)\n__bundle_register(\"ui.element.Toggle\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal StringUtil = require(\"lib.StringUtil\")\r\nlocal XmlUi = require(\"lib.XmlUi\")\r\n\r\nlocal GloomUiFactory = require(\"ui.GloomUiFactory\")\r\n\r\nlocal Toggle = {}\r\n\r\n---@type gloom_UiToggle_Settings\r\nlocal settings = {\r\n elements = {},\r\n}\r\n\r\n---@param params gloom_UiToggle_Parameter\r\n---@return gloom_UiCreate_Result\r\nfunction Toggle.create(params)\r\n local togglePanel = XmlUi.Factory.createPanel({})\r\n\r\n ---@param fromState string\r\n ---@param toState string\r\n ---@param active boolean\r\n local function addToggleElement(fromState, toState, active)\r\n local element = XmlUi.Factory.createImage({\r\n id = params.id .. \"-\" .. fromState,\r\n image = \"element.checkbox.\" .. fromState,\r\n height = params.size, width = params.size,\r\n active = active,\r\n onClick = \"onToggleChanged(\" .. toState .. \")\",\r\n })\r\n GloomUiFactory.addTooltip(element, params.tooltip)\r\n togglePanel.addChild(element)\r\n end\r\n\r\n settings.elements[params.id] = {\r\n handler = params.handler,\r\n }\r\n\r\n addToggleElement(\"true\", \"false\", params.active)\r\n addToggleElement(\"false\", \"true\", not params.active)\r\n\r\n return {\r\n ui = togglePanel,\r\n assets = {},\r\n }\r\nend\r\n\r\n---@param player tts__Player\r\n---@param value string\r\n---@param id tts__UIElement_Id\r\nfunction onToggleChanged(player, value, id)\r\n local convertedValue\r\n if value:lower() == \"true\" then\r\n convertedValue = true\r\n elseif value:lower() == \"false\" then\r\n convertedValue = false\r\n else\r\n convertedValue = false\r\n Logger.debug(\"Unknown toggle value %s\", value)\r\n end\r\n\r\n local convertedId = StringUtil.replace(StringUtil.replace(id, \"-true\"), \"-false\")\r\n GloomUiFactory.setToggle(convertedId, convertedValue)\r\n\r\n local handlerFunction = settings.elements[convertedId].handler\r\n if not handlerFunction then\r\n Logger.warn(\"No handler function defined for toggle %s\", convertedId)\r\n end\r\n\r\n handlerFunction(player, convertedValue, convertedId)\r\nend\r\n\r\nGloomUiFactory.register(\"Toggle\", Toggle.create)\r\n\r\nreturn Toggle\r\n\nend)\n__bundle_register(\"ui.GloomUiFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal GloomUiSetting = require(\"ui.GloomUiSetting\")\r\n\r\nlocal GloomUiFactory = {}\r\n\r\nlocal ElementFactory = {}\r\n\r\n---@shape gloom_UiCreate_Result\r\n---@field ui seb_XmlUi_Element\r\n---@field assets tts__UIAsset[]\r\n\r\n---@param name string\r\n---@param constructor fun(params: any): gloom_UiCreate_Result\r\nfunction GloomUiFactory.register(name, constructor)\r\n ElementFactory[name] = constructor\r\nend\r\n\r\n---@param element seb_XmlUi_Element\r\n---@param tooltip nil | string\r\nfunction GloomUiFactory.addTooltip(element, tooltip)\r\n if tooltip then\r\n element.setTooltip(--[[---@not nil]] tooltip)\r\n element.setTooltipBackgroundColor(GloomUiSetting.Color.Black)\r\n element.setTooltipBorderColor(GloomUiSetting.Color.Yellow)\r\n element.setTooltipTextColor(GloomUiSetting.Color.Yellow)\r\n end\r\nend\r\n\r\n---@param id string\r\n---@param state boolean\r\nfunction GloomUiFactory.setToggle(id, state)\r\n self.UI.setAttribute(id .. \"-true\", \"active\", state)\r\n self.UI.setAttribute(id .. \"-false\", \"active\", not state)\r\nend\r\n\r\n---@param id string\r\n---@param state integer\r\nfunction GloomUiFactory.setDropdown(id, state)\r\n self.UI.setAttribute(id, \"value\", state)\r\nend\r\n\r\n---@param params gloom_UiDropdown_Parameter\r\n---@return gloom_UI_CreateDropdown\r\nfunction GloomUiFactory.createDropdown(params)\r\n return ElementFactory[\"Dropdown\"](params)\r\nend\r\n\r\n---@param params gloom_UiImageChooser_Parameter\r\n---@return gloom_UiCreate_Result\r\nfunction GloomUiFactory.createImageChooser(params)\r\n return ElementFactory[\"ImageChooser\"](params)\r\nend\r\n\r\n---@param params gloom_UiMultiDropdown_Parameter\r\n---@return gloom_UiCreate_Result\r\nfunction GloomUiFactory.createMultiDropdown(params)\r\n return ElementFactory[\"MultiDropdown\"](params)\r\nend\r\n\r\n---@param params gloom_UiToggle_Parameter\r\n---@return gloom_UiCreate_Result\r\nfunction GloomUiFactory.createToggle(params)\r\n return ElementFactory[\"Toggle\"](params)\r\nend\r\n\r\nreturn GloomUiFactory\r\n\nend)\n__bundle_register(\"ui.GloomUiSetting\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal GloomUiSetting = {}\n\nGloomUiSetting.Color = {\n Black = { r = 23, g = 23, b = 23 }, -- #171717\n Grey = { r = 46, g = 46, b = 46 }, -- #2E2E2E\n Yellow = { r = 158, g = 132, b = 106 }, --#9E846A\n Red = { r = 255, g = 0, b = 0 },\n Invisible = { r = 0, g = 0, b = 0, a = 0 },\n}\n\nGloomUiSetting.Font = {\n Pirata = \"Fonts/PirataOne\",\n Nyala = \"Fonts/Nyala\",\n}\n\nreturn GloomUiSetting\n\nend)\n__bundle_register(\"lib.XmlUi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal XmlUi = require(\"sebaestschjin-tts.XmlUi\")\r\n\r\nreturn XmlUi\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.XmlUi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\nlocal XmlUiContainer = require(\"sebaestschjin-tts.xmlui.XmlUiContainer\")\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\n\nrequire(\"sebaestschjin-tts.xmlui.XmlUiAxisLayout\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiButton\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiCell\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiDefaults\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiDropdown\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiGridLayout\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiImage\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiInputField\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiOption\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiPanel\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiProgressBar\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiRow\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiScrollView\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiSlider\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiTableLayout\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiText\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiToggle\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiToggleButton\")\nrequire(\"sebaestschjin-tts.xmlui.XmlUiToggleGroup\")\n\n---@class seb_XmlUi : seb_XmlUi_Container\n\n---@class seb_XmlUi_Static\n---@overload fun(object: tts__Object): seb_XmlUi\nlocal XmlUi = {}\n\nXmlUi.Factory = XmlUiFactory\n\n--- Values available for alignment attributes.\nXmlUi.Alignment = {\n UpperLeft = \"UpperLeft\",\n UpperCenter = \"UpperCenter\",\n UpperRight = \"UpperRight\",\n MiddleLeft = \"MiddleLeft\",\n MiddleCenter = \"MiddleCenter\",\n MiddleRight = \"MiddleRight\",\n LowerLeft = \"LowerLeft\",\n LowerCenter = \"LowerCenter\",\n LowerRight = \"LowerRight\",\n}\n\n--- Values available for animation attributes.\nXmlUi.Animation = {\n Show = {\n None = \"None\",\n Grow = \"Grow\",\n FadeIn = \"FadeIn\",\n SlideInLeft = \"SlideIn_Left\",\n SlideInRight = \"SlideIn_Right\",\n SlideInTop = \"SlideIn_Top\",\n SlideInBottom = \"SlideIn_Bottom\",\n },\n Hide = {\n None = \"None\",\n Shrink = \"Shrink\",\n FadeOut = \"FadeOut\",\n SlideOut_Left = \"SlideOut_Left\",\n SlideOutRight = \"SlideOutRight\",\n SlideOutTop = \"SlideOut_Top\",\n SlideOutBottom = \"SlideOutBottom\",\n },\n}\n\n--- Values available for fontStyle attributes.\nXmlUi.FontStyle = {\n Bold = \"Bold\",\n BoldAndItalic = \"BoldAndItalic\",\n Italic = \"Italic\",\n Normal = \"Normal\",\n}\n\nXmlUi.GridLayout = {\n FixedColumnCount = \"FixedColumnCount\"\n}\n\nXmlUi.MouseEvent = {\n LeftClick = \"-1\",\n RightClick = \"-2\",\n MiddleClick = \"-3\",\n SingleTouch = \"1\",\n DoubleTouch = \"2\",\n TripleTouch = \"3\",\n}\n\n---@param object tts__Object\nlocal function new(object)\n local self = --[[---@type seb_XmlUi]] XmlUiContainer()\n local boundObject = object\n local children = self._wrapChildren(boundObject.UI.getXmlTable())\n\n ---@type tts__UIAsset[]\n local assets = boundObject.UI.getCustomAssets()\n local assetsChanged = false\n\n ---@return tts__UIElement[]\n local function createXmlTable()\n return TableUtil.map(children, function(child) return child.getXmlElement() end)\n end\n\n ---@param childElements seb_XmlUi_Element[]\n ---@param elementId string\n local function findElementById(childElements, elementId)\n for _, element in pairs(childElements) do\n if element.getId() == elementId then\n return element\n end\n local inChild = findElementById(element.getChildren(), elementId)\n if inChild then\n return inChild\n end\n end\n end\n\n ---@param elementId tts__UIElement_Id\n ---@return nil | seb_XmlUi_Element\n function self.findElement(elementId)\n return findElementById(children, elementId)\n end\n\n ---@param elementId string\n ---@param attribute string\n ---@param value string | number | boolean\n function self.setAttribute(elementId, attribute, value)\n boundObject.UI.setAttribute(elementId, attribute, value)\n end\n\n ---@param element seb_XmlUi_Element\n function self.addChild(element)\n element.bindUi(self) -- TODO propagate to children\n\n for i, child in ipairs(children) do\n if element.getZIndex() < child.getZIndex() then\n table.insert(children, i, element)\n return\n end\n end\n\n table.insert(children, element)\n end\n\n ---@param elementId tts__UIElement_Id\n function self.show(elementId)\n boundObject.UI.show(elementId)\n end\n\n ---@param elementId tts__UIElement_Id\n function self.hide(elementId)\n boundObject.UI.hide(elementId)\n end\n\n function self.update()\n local xmlTable = createXmlTable()\n if TableUtil.isNotEmpty(xmlTable) then\n boundObject.UI.setXmlTable(xmlTable)\n end\n end\n\n ---@return boolean\n function self.updateUiAssets()\n if assetsChanged then\n boundObject.UI.setCustomAssets(assets)\n assetsChanged = false\n return true\n end\n\n return false\n end\n\n ---@param assetList tts__UIAsset[]\n function self.updateAssets(assetList)\n for _, asset in ipairs(assetList) do\n self.updateAsset(asset.name, asset.url)\n end\n end\n\n ---@param assetName string\n ---@param assetUrl URL\n function self.updateAsset(assetName, assetUrl)\n for _, asset in ipairs(assets) do\n if asset.name == assetName then\n if asset.url ~= assetUrl then\n asset.url = assetUrl\n assetsChanged = true\n end\n return\n end\n end\n\n table.insert(assets, { name = assetName, url = assetUrl, })\n assetsChanged = true\n end\n\n ---@param assetName string\n ---@return boolean\n function self.removeAsset(assetName)\n for i, asset in ipairs(assets) do\n if asset.name == assetName then\n table.remove(assets, i)\n return true\n end\n end\n\n return false\n end\n\n ---@return tts__UIAsset[]\n function self.getAssets()\n return assets\n end\n\n ---@param assetName string\n ---@return boolean\n function self.hasAsset(assetName)\n for _, asset in ipairs(assets) do\n if asset.name == assetName then\n return true\n end\n end\n return false\n end\n\n return self\nend\n\nsetmetatable(XmlUi, TableUtil.merge(getmetatable(XmlUiContainer), {\n ---@param object tts__Object\n __call = function(_, object)\n return new(object)\n end\n}))\n\n---@param object tts__Object\n---@param assetName string\n---@return boolean\nfunction XmlUi.hasAsset(object, assetName)\n local assets = object.UI.getCustomAssets()\n\n for _, asset in ipairs(assets) do\n if asset.name == assetName then\n return true\n end\n end\n\n return false\nend\n\nreturn XmlUi\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiToggleGroup\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_ToggleGroup : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_ToggleGroup_Static\r\n---@overload fun(element: tts__UIToggleGroupElement): seb_XmlUi_ToggleGroup\r\nlocal XmlUiToggleGroup = {}\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiToggleGroup, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIToggleGroupElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_ToggleGroup]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"ToggleGroup\", XmlUiToggleGroup, Attributes)\r\n\r\nreturn XmlUiToggleGroup\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiElement\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"sebaestschjin-tts.Logger\")\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\nlocal XmlUiContainer = require(\"sebaestschjin-tts.xmlui.XmlUiContainer\")\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\n\n---@class seb_XmlUi_Element : seb_XmlUi_Container\n\n---@class seb_XmlUi_Element_Static\n---@overload fun(element: tts__UIElement): seb_XmlUi_Element\nlocal XmlUiElement = {}\n\n---@shape seb_XmlUi_Attributes\n---@field id nil | string @A unique string used to identify the element from Lua scripting.\n---@field active nil | boolean @Specifies whether or not this element and its children are visible and contribute to layout. Modifying this via script will not trigger animations.\n---@field rectAlignment nil | tts__UIElement_Alignment @The element's anchor and pivot point, relative to its parent element.\n---@field width nil | number @ \tThe width of this element in pixels or as a percentage of the width of its parent.\n---@field height nil | number @The height of this element in pixels or as a percentage of the height of its parent.\n---@field offsetXY nil | seb_Vector2 @An offset to the position of this element, e.g. a value of -32 10 will cause this element to be 10 pixels up and 32 pixels to the left of where it would otherwise be.\n---@field alignment nil | tts__UIElement_Alignment @Typographic alignment of the text within its bounding box.\n---@field visibility nil | seb_XmlUi_VisibilityTarget | seb_XmlUi_VisibilityTarget[] @A list of visibility targets. An element is always treated as inactive to players not specified here.\n---@field showAnimation nil | tts__UIElement_ShowAnimation @Animation to play when show() is called for the element.\n---@field showAnimationDelay nil | number @Time in seconds to wait before playing this element's show animation. Useful for staggering the animations of multiple elements.\n---@field hideAnimation nil | tts__UIElement_HideAnimation @Animation to play when hide() is called for the element.\n---@field hideAnimationDelay nil | number @Time in seconds to wait before playing this element's hide animation. Useful for staggering the animations of multiple elements.\n---@field animationDuration nil | number @Time in seconds that show/hide animations take to play.\n---@field tooltip nil | string\n---@field tooltipBackgroundColor nil | seb_XmlUi_Color\n---@field tooltipBackgroundImage nil | tts__UIAssetName\n---@field tooltipBorderColor nil | seb_XmlUi_Color\n---@field tooltipBorderImage nil | tts__UIAssetName\n---@field tooltipOffset nil | integer\n---@field tooltipPosition nil | tts__UITooltipPosition\n---@field tooltipTextColor nil | seb_XmlUi_Color\n---@field onClick nil | seb_XmlUi_EventHandler\n---@field onMouseDown nil | seb_XmlUi_EventHandler\n---@field onMouseUp nil | seb_XmlUi_EventHandler\n---@field onMouseEnter nil | seb_XmlUi_EventHandler\n---@field onMouseExit nil | seb_XmlUi_EventHandler\n---@field class nil | string | string[] @A list of classes. An element will inherit attributes from any of its classes defined in Defaults.\n---@field color nil | seb_XmlUi_Color @Color of the text. Elements that also take an image color use textColor for this.\n---@field font nil | tts__UIAssetName\n---@field fontStyle nil | tts__UIElement_FontStyle @Typographic emphasis on the text.\n---@field fontSize nil | number @Height of the text in pixels.\n---@field shadow nil | tts__ColorShape @Defines the shadow color of this element.\n---@field shadowDistance nil | seb_Vector2 @Defines the distance of the shadow for this element.\n---@field outline nil | tts__ColorShape @Defines the outline color of this element.\n---@field outlineSize nil | seb_Vector2 @Defines the size of this elements outline.\n---@field resizeTextForBestFit nil | boolean @If set then fontSize is ignored and the text will be sized to be as large as possible while still fitting within its bounding box.\n---@field resizeTextMinSize nil | number @When resizeTextForBestFit is set, text will not be sized smaller than this.\n---@field resizeTextMaxSize nil | number @When resizeTextForBestFit is set, text will not be sized larger than this.\n---@field horizontalOverflow nil | tts__UITextElement_HorizontalOverflow @ Defines what happens when text extends beyond the left or right edges of its bounding box.\n---@field verticalOverflow nil | tts__UITextElement_VerticalOverflow @Defines what happens when text extends beyond the top or bottom edges of its bounding box.\n---@field allowDragging nil | boolean @Allows the element to be dragged around.\n---@field restrictDraggingToParentBounds nil | boolean @If set, prevents the element from being dragged outside the bounding box of its parent.\n---@field returnToOriginalPositionWhenReleased nil | boolean @If this is set to true, then the element will return to its original position when it is released.\n---@field ignoreLayout nil | boolean @If this element ignores its parent's layout group behavior and treats it as a regular Panel. (This means it would obey regular position/size attributes.)\n---@field minWidth nil | number @Elements will not be sized thinner than this.\n---@field minHeight nil | number @Elements will not be sized shorter than this.\n---@field preferredWidth nil | number @If there is space after minWidths are sized, then element widths are sized according to this.\n---@field preferredHeight nil | number @If there is space after minHeights are sized, then element heights are sized according to this.\n---@field flexibleWidth nil | number @If there is additional space after preferredWidths are sized, defines how much the element expands to fill the available horizontal space, relative to other elements.\n---@field flexibleHeight nil | number @If there is additional space after preferredHeightss are sized, defines how much the element expands to fill the available vertical space, relative to other elements.\n---@field zIndex nil | integer\n\n---@type table\nlocal Attributes = {\n -- General\n id = XmlUiFactory.AttributeType.string,\n class = XmlUiFactory.AttributeType.string,\n active = XmlUiFactory.AttributeType.boolean,\n visibility = XmlUiFactory.AttributeType.players,\n -- Text\n text = XmlUiFactory.AttributeType.string,\n alignment = XmlUiFactory.AttributeType.string,\n color = XmlUiFactory.AttributeType.color,\n font = XmlUiFactory.AttributeType.string,\n fontStyle = XmlUiFactory.AttributeType.string,\n fontSize = XmlUiFactory.AttributeType.integer,\n resizeTextForBestFit = XmlUiFactory.AttributeType.boolean,\n resizeTextMinSize = XmlUiFactory.AttributeType.integer,\n resizeTextMaxSize = XmlUiFactory.AttributeType.integer,\n horizontalOverflow = XmlUiFactory.AttributeType.string,\n verticalOverflow = XmlUiFactory.AttributeType.string,\n -- Appearance\n shadow = XmlUiFactory.AttributeType.color,\n shadowDistance = XmlUiFactory.AttributeType.vector2,\n outline = XmlUiFactory.AttributeType.color,\n outlineSize = XmlUiFactory.AttributeType.vector2,\n -- Layout\n ignoreLayout = XmlUiFactory.AttributeType.boolean,\n minWidth = XmlUiFactory.AttributeType.integer,\n minHeight = XmlUiFactory.AttributeType.integer,\n preferredWidth = XmlUiFactory.AttributeType.integer,\n preferredHeight = XmlUiFactory.AttributeType.integer,\n flexibleWidth = XmlUiFactory.AttributeType.integer,\n flexibleHeight = XmlUiFactory.AttributeType.integer,\n -- Position/Size\n rectAlignment = XmlUiFactory.AttributeType.string,\n width = XmlUiFactory.AttributeType.integer,\n height = XmlUiFactory.AttributeType.integer,\n offsetXY = XmlUiFactory.AttributeType.vector2,\n -- Dragging\n allowDragging = XmlUiFactory.AttributeType.boolean,\n restrictDraggingToParentBounds = XmlUiFactory.AttributeType.boolean,\n returnToOriginalPositionWhenReleased = XmlUiFactory.AttributeType.boolean,\n -- Animation\n showAnimation = XmlUiFactory.AttributeType.string,\n hideAnimation = XmlUiFactory.AttributeType.string,\n showAnimationDelay = XmlUiFactory.AttributeType.float,\n hideAnimationDelay = XmlUiFactory.AttributeType.float,\n animationDuration = XmlUiFactory.AttributeType.float,\n -- Tooltip\n tooltip = XmlUiFactory.AttributeType.string,\n tooltipBackgroundColor = XmlUiFactory.AttributeType.color,\n tooltipBackgroundImage = XmlUiFactory.AttributeType.string,\n tooltipBorderColor = XmlUiFactory.AttributeType.color,\n tooltipBorderImage = XmlUiFactory.AttributeType.string,\n tooltipOffset = XmlUiFactory.AttributeType.integer,\n tooltipPosition = XmlUiFactory.AttributeType.string,\n tooltipTextColor = XmlUiFactory.AttributeType.color,\n -- Event\n onClick = XmlUiFactory.AttributeType.handler,\n onMouseEnter = XmlUiFactory.AttributeType.handler,\n onMouseExit = XmlUiFactory.AttributeType.handler,\n onMouseDown = XmlUiFactory.AttributeType.handler,\n onMouseUp = XmlUiFactory.AttributeType.handler,\n -- Custom\n zIndex = XmlUiFactory.AttributeType.integer,\n}\n\nsetmetatable(XmlUiElement, TableUtil.merge(getmetatable(XmlUiContainer), {\n ---@param element tts__UIElement\n __call = function(_, element)\n local self = --[[---@type seb_XmlUi_Element]] XmlUiContainer()\n local boundElement = element\n ---@type nil | seb_XmlUi\n local boundUi\n\n local children = self._wrapChildren(--[[---@type tts__UIElement[] ]] element.children)\n\n ---@param name string\n ---@return nil | string | number | boolean\n local function getAttribute(name)\n if boundElement.attributes then\n return (--[[---@type table]] boundElement.attributes)[name]\n end\n return nil\n end\n\n ---@param handler fun(ui: seb_XmlUi, id: tts__UIElement_Id): void\n local function onBoundId(handler)\n local id = self.getId()\n if boundUi and id then\n handler(--[[---@not nil]] boundUi, --[[---@not nil]] id)\n else\n Logger.debug(\"Not bound\")\n end\n end\n\n ---@param name string\n ---@param value tts__UIAttributeValue\n function self.setAttribute(name, value)\n if not boundElement.attributes then\n (--[[---@type table]] boundElement).attributes = {}\n end\n (--[[---@type table]] boundElement.attributes)[name] = value\n\n onBoundId(function(ui, id) ui.setAttribute(id, name, value) end)\n end\n\n ---@param name string\n ---@return number | string | boolean\n function self.getAttribute(name)\n if boundElement.attributes then\n local attributes = --[[---@type table]] boundElement.attributes\n return attributes[name]\n end\n end\n\n ---@return nil | string\n function self.getId()\n return --[[---@type nil | string]] getAttribute(\"id\")\n end\n\n ---@param ui seb_XmlUi\n function self.bindUi(ui)\n boundUi = ui\n end\n\n ---@param uiElement seb_XmlUi_Element\n function self.addChild(uiElement)\n table.insert(children, uiElement)\n end\n\n ---@return seb_XmlUi_Element[]\n function self.getChildren()\n return children\n end\n\n ---@param child number\n ---@return seb_XmlUi_Element\n function self.getChild(child)\n return children[child]\n end\n\n function self.clearElements()\n children = {}\n end\n\n ---@return tts__UIElement\n function self.getXmlElement()\n -- the type cast is obviously bogus, but I didn't find another clear way to get rid of the wrong type error\n local unwrappedElement = --[[---@type tts__UILayoutElement]] boundElement\n unwrappedElement.children = TableUtil.map(children, function(c) return c.getXmlElement() end)\n return unwrappedElement\n end\n\n function self.show()\n onBoundId(function(ui, id) ui.show(id) end)\n end\n\n function self.hide()\n onBoundId(function(ui, id) ui.hide(id) end)\n end\n\n ---@return integer\n function self.getZIndex()\n local attribute = self.getAttribute(\"zIndex\")\n if not attribute then\n return 0\n end\n return --[[---@not nil]] tonumber(--[[---@type string]] attribute)\n end\n\n ---@param value number\n function self.setWidth(value)\n self.setAttribute(\"width\", value)\n end\n\n ---@param value number\n function self.setHeight(value)\n self.setAttribute(\"height\", value)\n end\n\n ---@param value string\n function self.setTooltip(value)\n self.setAttribute(\"tooltip\", value)\n end\n\n ---@param value seb_XmlUi_Color\n function self.setTooltipBackgroundColor(value)\n self.setAttribute(\"tooltipBackgroundColor\", XmlUiFactory.Converter.toColor(value))\n end\n\n ---@param value seb_XmlUi_Color\n function self.setTooltipBorderColor(value)\n self.setAttribute(\"tooltipBorderColor\", XmlUiFactory.Converter.toColor(value))\n end\n\n ---@param value seb_XmlUi_Color\n function self.setTooltipTextColor(value)\n self.setAttribute(\"tooltipTextColor\", XmlUiFactory.Converter.toColor(value))\n end\n\n ---@return tts__UIElement_Tag\n function self.getType()\n return boundElement.tag\n end\n\n return self\n end\n}))\n\nXmlUiFactory.register(nil, XmlUiElement, Attributes)\n\nreturn XmlUiElement\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"sebaestschjin-tts.Logger\")\r\nlocal Math = require(\"sebaestschjin-tts.Math\")\r\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\n\r\nlocal XmlUiFactory = {}\r\n\r\n---@alias seb_XmlUi_FactoryMethod fun(element: tts__UIElement): seb_XmlUi_Element\r\n\r\n---@shape seb_XmlUi_Factory\r\n---@field Attributes table\r\n---@field Method seb_XmlUi_FactoryMethod\r\n\r\n---@type table\r\nlocal ElementFactory = {}\r\nlocal DefaultFactoryName = \"__default__\"\r\n\r\n---@alias seb_XmlUi_AttributeType 'boolean' | 'string' | 'integer' | 'float' | 'floats' | 'handler' | 'color' | 'colorBlock' | 'padding' | 'players' | 'vector2' | 'vector4'\r\n\r\nXmlUiFactory.AttributeType = {\r\n boolean = \"boolean\",\r\n string = \"string\",\r\n integer = \"integer\",\r\n float = \"float\",\r\n floats = \"floats\",\r\n handler = \"handler\",\r\n color = \"color\",\r\n colorBlock = \"colorBlock\",\r\n padding = \"padding\",\r\n players = \"players\",\r\n vector2 = \"vector2\",\r\n vector4 = \"vector4\",\r\n}\r\n\r\n---@overload fun(value: tts__UIAttributeValue, separator: string): string\r\n---@param value tts__UIAttributeValue\r\n---@param separator string\r\n---@param multiple number\r\n---@return string\r\nlocal function toConcatenatedString(value, separator, multiple)\r\n multiple = multiple or 1\r\n\r\n local values = --[[---@type tts__UIAttributeValue]] {}\r\n if type(value) ~= \"table\" then\r\n for _ = 1, multiple do\r\n table.insert(values, value)\r\n end\r\n else\r\n values = value\r\n end\r\n\r\n return table.concat(--[[---@type string[] ]] values, separator)\r\nend\r\n\r\n---@return nil | string\r\nlocal function toList(value)\r\n return toConcatenatedString(value, \" \")\r\nend\r\n\r\n---@param value seb_XmlUi_Color\r\n---@return string\r\nlocal function toColor(value)\r\n if type(value) == \"string\" then\r\n return --[[---@type string]] value\r\n end\r\n\r\n ---@type tts__NumColorShape\r\n local numColor\r\n if (--[[---@type tts__CharColorShape]] value).r ~= nil then\r\n local charColor = --[[---@type tts__CharColorShape]] value\r\n numColor = { charColor.r, charColor.g, charColor.b, charColor.a }\r\n else\r\n numColor = --[[---@type tts__NumColorShape]] value\r\n end\r\n\r\n for i, v in ipairs(numColor) do\r\n if v > 1 then\r\n numColor[i] = Math.round(v / 255, 2)\r\n end\r\n end\r\n\r\n if numColor[4] ~= nil then\r\n return string.format(\"rgba(%s,%s,%s,%s)\", numColor[1], numColor[2], numColor[3], numColor[4])\r\n else\r\n return string.format(\"rgb(%s,%s,%s)\", numColor[1], numColor[2], numColor[3])\r\n end\r\nend\r\n\r\n---@param value seb_XmlUi_ColorBlock\r\n---@return string\r\nlocal function toColorBlock(value)\r\n return table.concat(TableUtil.map(value, toColor), \"|\")\r\nend\r\n\r\n---@param value seb_XmlUi_Padding\r\n---@return string\r\nlocal function toPadding(value)\r\n if type(value) == \"number\" then\r\n return toList({ value, value, value, value })\r\n end\r\n\r\n if value.l ~= nil then\r\n local charPadding = --[[---@type seb_XmlUi_Padding_Char]] value\r\n return toList({ charPadding.l, charPadding.r, charPadding.t, charPadding.b })\r\n end\r\n\r\n if value.h ~= nil then\r\n local charPadding = --[[---@type seb_XmlUi_Padding_AxisChar]] value\r\n return toList({ charPadding.h, charPadding.h, charPadding.v, charPadding.v })\r\n end\r\n\r\n return toList(value)\r\nend\r\n\r\n---@param value nil | seb_XmlUi_EventHandler\r\n---@return string\r\nlocal function toHandlerFunction(value)\r\n if type(value) == \"table\" then\r\n local asTable = --[[---@type seb_XmlUi_ObjectEventHandler]] value\r\n return asTable[1].getGUID() .. \"/\" .. asTable[2]\r\n end\r\n return --[[---@type string]] value\r\nend\r\n\r\n---@return string\r\nlocal function toPlayerColors(value)\r\n return toConcatenatedString(value, \"|\")\r\nend\r\n\r\n---@param value tts__UIAttributeValue\r\n---@return tts__UIAttributeValue\r\nlocal function identity(value)\r\n return value\r\nend\r\n\r\nXmlUiFactory.Converter = {\r\n toColor = toColor,\r\n}\r\n\r\n---@type table\r\nlocal AttributeTypeMapper = {\r\n [XmlUiFactory.AttributeType.string] = identity,\r\n [XmlUiFactory.AttributeType.integer] = identity,\r\n [XmlUiFactory.AttributeType.float] = identity,\r\n [XmlUiFactory.AttributeType.floats] = toList,\r\n [XmlUiFactory.AttributeType.boolean] = identity,\r\n [XmlUiFactory.AttributeType.handler] = toHandlerFunction,\r\n [XmlUiFactory.AttributeType.color] = toColor,\r\n [XmlUiFactory.AttributeType.colorBlock] = toColorBlock,\r\n [XmlUiFactory.AttributeType.padding] = toPadding,\r\n [XmlUiFactory.AttributeType.players] = toPlayerColors,\r\n [XmlUiFactory.AttributeType.vector2] = toList,\r\n [XmlUiFactory.AttributeType.vector4] = toList,\r\n}\r\n\r\n---@param tag string\r\n---@param constructor seb_XmlUi_FactoryMethod\r\n---@param attributes table\r\nfunction XmlUiFactory.register(tag, constructor, attributes)\r\n local factory = {\r\n Method = constructor,\r\n Attributes = attributes,\r\n }\r\n if tag then\r\n ElementFactory[tag] = factory\r\n else\r\n ElementFactory[DefaultFactoryName] = factory\r\n end\r\nend\r\n\r\n---@param element table\r\n---@param attributes seb_XmlUi_Attributes\r\n---@param name string\r\n---@param mapper fun(value: any): any\r\nlocal function copyAttribute(element, attributes, name, mapper)\r\n local value = attributes[name]\r\n if value ~= nil then\r\n if mapper then\r\n value = mapper(value)\r\n end\r\n (--[[---@not nil]] element.attributes)[name] = value\r\n end\r\nend\r\n\r\n---@param element table\r\n---@param attributes seb_XmlUi_Attributes\r\n---@param availableAttributes table\r\nlocal function copyAttributes(element, attributes, availableAttributes)\r\n for attribute, attributeType in pairs(availableAttributes) do\r\n copyAttribute(element, attributes, attribute, AttributeTypeMapper[attributeType])\r\n end\r\nend\r\n\r\n---@param element tts__UIElement\r\n---@return seb_XmlUi_Element\r\nfunction XmlUiFactory.wrapElement(element)\r\n local factory = ElementFactory[element.tag]\r\n if factory then\r\n return factory.Method(element)\r\n end\r\n\r\n Logger.verbose(\"No factory found for element of type %s. Using default one.\", element.tag)\r\n return ElementFactory[DefaultFactoryName].Method(element)\r\n --uiElement.bindUi(self) -- TODO !!!\r\nend\r\n\r\n---@param tag tts__UIElement_Tag\r\n---@param attributes nil | seb_XmlUi_Attributes\r\n---@return seb_XmlUi_Element\r\nfunction XmlUiFactory.createElement(tag, attributes)\r\n local theAttributes = attributes or {}\r\n ---@type tts__UIElement\r\n local ttsElement = {\r\n tag = tag,\r\n attributes = --[[---@type table]] {},\r\n children = {}\r\n }\r\n copyAttributes(ttsElement, theAttributes, ElementFactory[DefaultFactoryName].Attributes)\r\n copyAttributes(ttsElement, theAttributes, ElementFactory[tag].Attributes)\r\n\r\n if theAttributes.value then\r\n ttsElement.value = theAttributes.value\r\n end\r\n\r\n for name, value in pairs(theAttributes) do\r\n if name ~= \"value\" and value ~= nil and ttsElement.attributes[name] == nil then\r\n Logger.warn(\"Unmapped attribute '%s'!\", name)\r\n end\r\n end\r\n\r\n return ElementFactory[tag].Method(ttsElement)\r\nend\r\n\r\n---@param attributes seb_XmlUi_ButtonAttributes\r\n---@return seb_XmlUi_Button\r\nfunction XmlUiFactory.createButton(attributes)\r\n return --[[---@type seb_XmlUi_Button]] XmlUiFactory.createElement(\"Button\", attributes)\r\nend\r\n\r\n---@overload fun(): seb_XmlUi_Cell\r\n---@param attributes seb_XmlUi_CellAttributes\r\n---@return seb_XmlUi_Cell\r\nfunction XmlUiFactory.createCell(attributes)\r\n return --[[---@type seb_XmlUi_Cell]] XmlUiFactory.createElement(\"Cell\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_DropdownAttributes\r\n---@return seb_XmlUi_Dropdown\r\nfunction XmlUiFactory.createDropdown(attributes)\r\n return --[[---@type seb_XmlUi_Dropdown]] XmlUiFactory.createElement(\"Dropdown\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_GridLayoutAttributes\r\n---@return seb_XmlUi_GridLayout\r\nfunction XmlUiFactory.createGridLayout(attributes)\r\n return --[[---@type seb_XmlUi_GridLayout]] XmlUiFactory.createElement(\"GridLayout\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_AxisLayoutAttributes\r\n---@return seb_XmlUi_AxisLayout\r\nfunction XmlUiFactory.createHorizontalLayout(attributes)\r\n return --[[---@type seb_XmlUi_AxisLayout]] XmlUiFactory.createElement(\"HorizontalLayout\", attributes)\r\nend\r\n\r\n---@overload fun(): seb_XmlUi_ScrollView\r\n---@param attributes seb_XmlUi_ScrollViewAttributes\r\n---@return seb_XmlUi_ScrollView\r\nfunction XmlUiFactory.createHorizontalScrollView(attributes)\r\n return --[[---@type seb_XmlUi_ScrollView]] XmlUiFactory.createElement(\"HorizontalScrollView\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_ImageAttributes\r\n---@return seb_XmlUi_Image\r\nfunction XmlUiFactory.createImage(attributes)\r\n return --[[---@type seb_XmlUi_Image]] XmlUiFactory.createElement(\"Image\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_OptionAttributes\r\n---@return seb_XmlUi_Option\r\nfunction XmlUiFactory.createOption(attributes)\r\n return --[[---@type seb_XmlUi_Option]] XmlUiFactory.createElement(\"Option\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_PanelAttributes\r\n---@return seb_XmlUi_Panel\r\nfunction XmlUiFactory.createPanel(attributes)\r\n return --[[---@type seb_XmlUi_Panel]] XmlUiFactory.createElement(\"Panel\", attributes)\r\nend\r\n\r\n---@overload fun(): seb_XmlUi_Row\r\n---@param attributes seb_XmlUi_RowAttributes\r\n---@return seb_XmlUi_Row\r\nfunction XmlUiFactory.createRow(attributes)\r\n return --[[---@type seb_XmlUi_Row]] XmlUiFactory.createElement(\"Row\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_TableLayoutAttributes\r\n---@return seb_XmlUi_TableLayout\r\nfunction XmlUiFactory.createTableLayout(attributes)\r\n return --[[---@type seb_XmlUi_TableLayout]] XmlUiFactory.createElement(\"TableLayout\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_TextAttributes\r\n---@return seb_XmlUi_Text\r\nfunction XmlUiFactory.createText(attributes)\r\n return --[[---@type seb_XmlUi_Text]] XmlUiFactory.createElement(\"Text\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_ToggleAttributes\r\n---@return seb_XmlUi_Toggle\r\nfunction XmlUiFactory.createToggle(attributes)\r\n return --[[---@type seb_XmlUi_Toggle]] XmlUiFactory.createElement(\"Toggle\", attributes)\r\nend\r\n\r\n---@param attributes seb_XmlUi_AxisLayoutAttributes\r\n---@return seb_XmlUi_AxisLayout\r\nfunction XmlUiFactory.createVerticalLayout(attributes)\r\n return --[[---@type seb_XmlUi_AxisLayout]] XmlUiFactory.createElement(\"VerticalLayout\", attributes)\r\nend\r\n\r\n---@overload fun(): seb_XmlUi_ScrollView\r\n---@param attributes seb_XmlUi_ScrollViewAttributes\r\n---@return seb_XmlUi_ScrollView\r\nfunction XmlUiFactory.createVerticalScrollView(attributes)\r\n return --[[---@type seb_XmlUi_ScrollView]] XmlUiFactory.createElement(\"VerticalScrollView\", attributes)\r\nend\r\n\r\nreturn XmlUiFactory\r\n\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiContainer\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"sebaestschjin-tts.Logger\")\r\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\n\r\n---@class seb_XmlUi_Container\r\n\r\n---@class seb_XmlUi_Container_Static\r\n---@overload fun(): seb_XmlUi_Container\r\nlocal XmlUiContainer = {}\r\n\r\nsetmetatable(XmlUiContainer, {\r\n __call = function(_)\r\n local self = --[[---@type seb_XmlUi_Container]] {}\r\n\r\n ---@param unwrappedElements nil | tts__UIElement[]\r\n ---@return seb_XmlUi_Element[]\r\n function self._wrapChildren(unwrappedElements)\r\n local elements = --[[---@type seb_XmlUi_Element[] ]] {}\r\n for _, element in TableUtil.ipairs(--[[---@type tts__UIElement[] ]] unwrappedElements) do\r\n local uiElement = XmlUiFactory.wrapElement(element)\r\n table.insert(elements, uiElement)\r\n end\r\n return elements\r\n end\r\n\r\n ---@param _ seb_XmlUi_Element\r\n function self.addChild(_)\r\n Logger.error(\"Not implemented exception!\")\r\n end\r\n\r\n ---@param _ number\r\n ---@return seb_XmlUi_Element\r\n function self.getChild(_)\r\n Logger.error(\"Not implemented exception!\")\r\n return --[[---@type seb_XmlUi_Element]] nil\r\n end\r\n\r\n function self.clearElements()\r\n Logger.error(\"Not implemented exception!\")\r\n end\r\n\r\n ---@generic E: seb_XmlUi_Element\r\n ---@param element E\r\n ---@return E\r\n local function addToChildren(element)\r\n self.addChild(element)\r\n return element\r\n end\r\n\r\n ---@overload fun(): seb_XmlUi_Text\r\n ---@param attributes seb_XmlUi_TextAttributes\r\n ---@return seb_XmlUi_Text\r\n function self.addText(attributes)\r\n return addToChildren(XmlUiFactory.createText(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_ButtonAttributes\r\n ---@return seb_XmlUi_Button\r\n function self.addButton(attributes)\r\n return addToChildren(XmlUiFactory.createButton(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_ImageAttributes\r\n ---@return seb_XmlUi_Image\r\n function self.addImage(attributes)\r\n return addToChildren(XmlUiFactory.createImage(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_ToggleAttributes\r\n ---@return seb_XmlUi_Toggle\r\n function self.addToggle(attributes)\r\n return addToChildren(XmlUiFactory.createToggle(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_DropdownAttributes\r\n ---@return seb_XmlUi_Dropdown\r\n function self.addDropdown(attributes)\r\n return addToChildren(XmlUiFactory.createDropdown(attributes))\r\n end\r\n\r\n ---@overload fun(): seb_XmlUi_Panel\r\n ---@param attributes seb_XmlUi_PanelAttributes\r\n ---@return seb_XmlUi_Panel\r\n function self.addPanel(attributes)\r\n return addToChildren(XmlUiFactory.createPanel(attributes))\r\n end\r\n\r\n ---@overload fun(): seb_XmlUi_AxisLayout\r\n ---@param attributes seb_XmlUi_AxisLayoutAttributes\r\n ---@return seb_XmlUi_AxisLayout\r\n function self.addVerticalLayout(attributes)\r\n return addToChildren(XmlUiFactory.createVerticalLayout(attributes))\r\n end\r\n\r\n ---@overload fun(): seb_XmlUi_AxisLayout\r\n ---@param attributes seb_XmlUi_AxisLayoutAttributes\r\n ---@return seb_XmlUi_AxisLayout\r\n function self.addHorizontalLayout(attributes)\r\n return addToChildren(XmlUiFactory.createHorizontalLayout(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_TableLayoutAttributes\r\n ---@return seb_XmlUi_TableLayout\r\n function self.addTableLayout(attributes)\r\n return addToChildren(XmlUiFactory.createTableLayout(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_GridLayoutAttributes\r\n ---@return seb_XmlUi_GridLayout\r\n function self.addGridLayout(attributes)\r\n return addToChildren(XmlUiFactory.createGridLayout(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_ScrollViewAttributes\r\n ---@return seb_XmlUi_ScrollView\r\n function self.addHorizontalScrollView(attributes)\r\n return addToChildren(XmlUiFactory.createHorizontalScrollView(attributes))\r\n end\r\n\r\n ---@param attributes seb_XmlUi_ScrollViewAttributes\r\n ---@return seb_XmlUi_ScrollView\r\n function self.addVerticalScrollView(attributes)\r\n return addToChildren(XmlUiFactory.createVerticalScrollView(attributes))\r\n end\r\n\r\n return self\r\n end\r\n})\r\n\r\nreturn XmlUiContainer\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiToggleButton\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_ToggleButton : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_ToggleButton_Static\r\n---@overload fun(element: tts__UIToggleButtonElement): seb_XmlUi_ToggleButton\r\nlocal XmlUiToggleButton = {}\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiToggleButton, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIToggleButtonElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_ToggleButton]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"ToggleButton\", XmlUiToggleButton, Attributes)\r\n\r\nreturn XmlUiToggleButton\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiToggle\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\n\r\n---@class seb_XmlUi_Toggle : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Toggle_Static\r\n---@overload fun(element: tts__UIToggleElement): seb_XmlUi_Toggle\r\nlocal XmlUiToggle = {}\r\n\r\n---@shape seb_XmlUi_ToggleAttributes : seb_XmlUi_Attributes\r\n---@field onValueChanged nil | seb_XmlUi_EventHandler\r\n---@field isOn nil | boolean\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal Attributes = {\r\n isOn = XmlUiFactory.AttributeType.boolean,\r\n onValueChanged = XmlUiFactory.AttributeType.handler,\r\n}\r\n\r\nsetmetatable(XmlUiToggle, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIToggleElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Toggle]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Toggle\", XmlUiToggle, Attributes)\r\n\r\nreturn XmlUiToggle\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiText\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Text : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Text_Static\r\n---@overload fun(element: tts__UITextElement): seb_XmlUi_Text\r\nlocal XmlUiText = {}\r\n\r\n---@shape seb_XmlUi_TextAttributes : seb_XmlUi_Attributes\r\n---@field text nil | string\r\n---@field value nil | string\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiText, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UITextElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Text]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Text\", XmlUiText, Attributes)\r\n\r\nreturn XmlUiText\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiTableLayout\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_TableLayout : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_TableLayout_Static\r\n---@overload fun(element: tts__UITableLayoutElement): seb_XmlUi_TableLayout\r\nlocal XmlUiTableLayout = {}\r\n\r\n---@shape seb_XmlUi_TableLayoutAttributes : seb_XmlUi_Attributes\r\n---@field columnWidths nil | number[]\r\n---@field padding nil | seb_XmlUi_Padding\r\n---@field rowBackgroundColor nil | seb_XmlUi_Color | 'clear'\r\n---@field rowBackgroundImage nil | tts__UIAssetName\r\n---@field cellBackgroundColor nil | seb_XmlUi_Color | 'clear'\r\n---@field cellBackgroundImage nil | tts__UIAssetName\r\n---@field cellPadding nil | seb_XmlUi_Padding\r\n---@field autoCalculateHeight nil | boolean\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal Attributes = {\r\n autoCalculateHeight = XmlUiFactory.AttributeType.boolean,\r\n cellBackgroundColor = XmlUiFactory.AttributeType.color,\r\n cellBackgroundImage = XmlUiFactory.AttributeType.string,\r\n cellPadding = XmlUiFactory.AttributeType.padding,\r\n columnWidths = XmlUiFactory.AttributeType.floats,\r\n padding = XmlUiFactory.AttributeType.padding,\r\n rowBackgroundColor = XmlUiFactory.AttributeType.color,\r\n rowBackgroundImage = XmlUiFactory.AttributeType.string,\r\n}\r\n\r\nsetmetatable(XmlUiTableLayout, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UITableLayoutElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_TableLayout]] XmlUiElement(element)\r\n\r\n ---@overload fun(): seb_XmlUi_Row\r\n ---@param attributes seb_XmlUi_RowAttributes\r\n ---@return seb_XmlUi_Row\r\n function self.addRow(attributes)\r\n local row = XmlUiFactory.createRow(attributes)\r\n self.addChild(row)\r\n return row\r\n end\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"TableLayout\", XmlUiTableLayout, Attributes)\r\n\r\nreturn XmlUiTableLayout\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiSlider\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Slider : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Slider_Static\r\n---@overload fun(element: tts__UISliderElement): seb_XmlUi_Slider\r\nlocal XmlUiSlider = {}\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiSlider, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UISliderElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Slider]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Slider\", XmlUiSlider, Attributes)\r\n\r\nreturn XmlUiSlider\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiScrollView\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\n\n---@class seb_XmlUi_ScrollView : seb_XmlUi_Element\n\n---@class seb_XmlUi_ScrollView_Static\n---@overload fun(element: tts__UIScrollViewElement): seb_XmlUi_ScrollView\nlocal XmlUiScrollView = {}\n\n---@shape seb_XmlUi_ScrollViewAttributes : seb_XmlUi_Attributes\n---@field scrollbarBackgroundColor nil | seb_XmlUi_Color\n---@field scrollbarColors nil | seb_XmlUi_ColorBlock\n---@field scrollSensitivity nil | number\n---@field [any] nil @All other fields are invalid\n\nlocal Attributes = {\n scrollbarBackgroundColor = XmlUiFactory.AttributeType.color,\n scrollbarColors = XmlUiFactory.AttributeType.colorBlock,\n scrollSensitivity = XmlUiFactory.AttributeType.float,\n}\n\nsetmetatable(XmlUiScrollView, TableUtil.merge(getmetatable(XmlUiElement), {\n ---@param element tts__UIScrollViewElement\n __call = function(_, element)\n local self = --[[---@type seb_XmlUi_ScrollView]] XmlUiElement(element)\n\n return self\n end\n}))\n\nXmlUiFactory.register(\"HorizontalScrollView\", XmlUiScrollView, Attributes)\nXmlUiFactory.register(\"VerticalScrollView\", XmlUiScrollView, Attributes)\n\nreturn XmlUiScrollView\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiRow\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Row : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Row_Static\r\n---@overload fun(element: tts__UIRowElement): seb_XmlUi_Row\r\nlocal XmlUiRow = {}\r\n\r\n---@shape seb_XmlUi_RowAttributes : seb_XmlUi_Attributes\r\n---@field dontUseTableRowBackground nil | boolean\r\n---@field image nil | tts__UIAssetName\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal RowAttributes = {\r\n dontUseTableRowBackground = XmlUiFactory.AttributeType.boolean,\r\n image = XmlUiFactory.AttributeType.string,\r\n}\r\n\r\nsetmetatable(XmlUiRow, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIRowElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Row]] XmlUiElement(element)\r\n\r\n local super_addChild = self.addChild\r\n\r\n ---@overload fun(): seb_XmlUi_Cell\r\n ---@param attributes seb_XmlUi_CellAttributes\r\n ---@return seb_XmlUi_Cell\r\n function self.addCell(attributes)\r\n local cell = XmlUiFactory.createCell(attributes)\r\n self.addChild(cell)\r\n return cell\r\n end\r\n\r\n ---@param uiElement seb_XmlUi_Element\r\n function self.addChild(uiElement)\r\n if uiElement.getType() ~= \"Cell\" then\r\n local cell = XmlUiFactory.createCell()\r\n cell.addChild(uiElement)\r\n super_addChild(cell)\r\n else\r\n super_addChild(uiElement)\r\n end\r\n end\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Row\", XmlUiRow, RowAttributes)\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiProgressBar\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_ProgressBar : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_ProgressBar_Static\r\n---@overload fun(element: tts__UIProgressBarElement): seb_XmlUi_ProgressBar\r\nlocal XmlUiProgressBar = {}\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiProgressBar, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIProgressBarElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_ProgressBar]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"ProgressBar\", XmlUiProgressBar, Attributes)\r\n\r\nreturn XmlUiProgressBar\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiPanel\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Panel : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Panel_Static\r\n---@overload fun(element: tts__UIPanelElement): seb_XmlUi_Panel\r\nlocal XmlUiPanel = {}\r\n\r\n---@shape seb_XmlUi_PanelAttributes : seb_XmlUi_Attributes\r\n---@field padding nil | seb_XmlUi_Padding\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal Attributes = {\r\n padding = XmlUiFactory.AttributeType.padding,\r\n}\r\n\r\nsetmetatable(XmlUiPanel, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIPanelElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Panel]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Panel\", XmlUiPanel, Attributes)\r\n\r\nreturn XmlUiPanel\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiOption\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Option : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Option_Static\r\n---@overload fun(element: tts__UIOptionElement): seb_XmlUi_Option\r\nlocal XmlUiOption = {}\r\n\r\n---@shape seb_XmlUi_OptionAttributes : seb_XmlUi_Attributes\r\n---@field value number | string\r\n---@field selected nil | boolean\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal OptionAttributes = {\r\n selected = XmlUiFactory.AttributeType.boolean,\r\n}\r\n\r\nsetmetatable(XmlUiOption, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIOptionElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Option]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Option\", XmlUiOption, OptionAttributes)\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiInputField\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_InputField : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_InputField_Static\r\n---@overload fun(element: tts__UIInputFieldElement): seb_XmlUi_InputField\r\nlocal XmlUiInputField = {}\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiInputField, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIInputFieldElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_InputField]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"InputField\", XmlUiInputField, Attributes)\r\n\r\nreturn XmlUiInputField\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiImage\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Image : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Image_Static\r\n---@overload fun(element: tts__UIImageElement): seb_XmlUi_Image\r\nlocal XmlUiImage = {}\r\n\r\n---@shape seb_XmlUi_ImageAttributes : seb_XmlUi_Attributes\r\n---@field image URL\r\n---@field preserveAspect nil | boolean\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal Attributes = {\r\n image = XmlUiFactory.AttributeType.string,\r\n preserveAspect = XmlUiFactory.AttributeType.boolean,\r\n}\r\n\r\nsetmetatable(XmlUiImage, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIImageElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Image]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Image\", XmlUiImage, Attributes)\r\n\r\nreturn XmlUiImage\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiGridLayout\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_GridLayout : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_GridLayout_Static\r\n---@overload fun(element: tts__UIGridLayoutElement): seb_XmlUi_GridLayout\r\nlocal XmlUiGridLayout = {}\r\n\r\n---@shape seb_XmlUi_GridLayoutAttributes : seb_XmlUi_Attributes\r\n---@field padding nil | seb_XmlUi_Padding @Default {0, 0, 0, 0}\r\n---@field spacing nil | seb_XmlUi_Size @Default {0, 0}\r\n---@field cellSize nil | seb_XmlUi_Size @Default {100, 100}\r\n---@field startCorner nil | tts__UIElement_Alignment_Corner @Default \"UpperLeft\"\r\n---@field startAxis nil | tts__UIElement_Alignment_Axis @Default \"Horizontal\"\r\n---@field childAlignment nil | tts__UIElement_Alignment @Default \"UpperLeft\"\r\n---@field constraint nil | tts__UIGridLayoutElement_Constraint @Default \"Flexible\"\r\n---@field constraintCount nil | number @Default 2\r\n---@field [any] nil @All other fields are invalid\r\n\r\n\r\nlocal Attributes = {\r\n cellSize = XmlUiFactory.AttributeType.vector2,\r\n constraint = XmlUiFactory.AttributeType.string,\r\n constraintCount = XmlUiFactory.AttributeType.integer,\r\n spacing = XmlUiFactory.AttributeType.vector2,\r\n startAxis = XmlUiFactory.AttributeType.string,\r\n startCorner = XmlUiFactory.AttributeType.string,\r\n}\r\n\r\nsetmetatable(XmlUiGridLayout, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIGridLayoutElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_GridLayout]] XmlUiElement(element)\r\n\r\n ---@param value tts__UIGridLayoutElement_Constraint\r\n function self.setConstraint(value)\r\n self.setAttribute(\"constraint\", value)\r\n end\r\n\r\n ---@param value number\r\n function self.setConstraintCount(value)\r\n self.setAttribute(\"constraintCount\", value)\r\n end\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"GridLayout\", XmlUiGridLayout, Attributes)\r\n\r\nreturn XmlUiGridLayout\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiDropdown\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\n\n---@class seb_XmlUi_Dropdown : seb_XmlUi_Element\n\n---@class seb_XmlUi_DropDown_Static\n---@overload fun(element: tts__UIDropdownElement): seb_XmlUi_Dropdown\nlocal XmlUiDropdown = {}\n\n---@shape seb_XmlUi_DropdownAttributes : seb_XmlUi_Attributes\n---@field arrowColor nil | seb_XmlUi_Color\n---@field arrowImage nil | tts__UIAssetName\n---@field checkColor nil | seb_XmlUi_Color\n---@field checkImage nil | tts__UIAssetName\n---@field dropdownBackgroundColor nil | seb_XmlUi_Color\n---@field dropdownBackgroundImage nil | tts__UIAssetName\n---@field image nil | tts__UIAssetName @The image used as the background for a closed dropdown.\n---@field itemBackgroundColors nil | seb_XmlUi_ColorBlock\n---@field itemHeight nil | number\n---@field itemTextColor nil | seb_XmlUi_Color\n---@field onValueChanged nil | seb_XmlUi_EventHandler\n---@field scrollbarColors nil | seb_XmlUi_ColorBlock\n---@field scrollbarImage nil | tts__UIAssetName\n---@field scrollSensitivity nil | number\n---@field textColor nil | seb_XmlUi_Color\n---@field [any] nil @All other fields are invalid\n\nlocal Attributes = {\n arrowColor = XmlUiFactory.AttributeType.color,\n arrowImage = XmlUiFactory.AttributeType.string,\n checkColor = XmlUiFactory.AttributeType.color,\n checkImage = XmlUiFactory.AttributeType.string,\n dropdownBackgroundColor = XmlUiFactory.AttributeType.color,\n dropdownBackgroundImage = XmlUiFactory.AttributeType.string,\n image = XmlUiFactory.AttributeType.string,\n itemBackgroundColors = XmlUiFactory.AttributeType.colorBlock,\n itemHeight = XmlUiFactory.AttributeType.integer,\n itemTextColor = XmlUiFactory.AttributeType.color,\n onValueChanged = XmlUiFactory.AttributeType.handler,\n scrollbarColors = XmlUiFactory.AttributeType.colorBlock,\n scrollbarImage = XmlUiFactory.AttributeType.string,\n scrollSensitivity = XmlUiFactory.AttributeType.float,\n textColor = XmlUiFactory.AttributeType.color,\n}\n\nsetmetatable(XmlUiDropdown, TableUtil.merge(getmetatable(XmlUiElement), {\n ---@param element tts__UIDropdownElement\n __call = function(_, element)\n local self = --[[---@type seb_XmlUi_Dropdown]] XmlUiElement(element)\n\n ---@param attributes seb_XmlUi_OptionAttributes\n function self.addOption(attributes)\n local option = XmlUiFactory.createOption(attributes)\n self.addChild(option)\n return option\n end\n\n return self\n end\n}))\n\nXmlUiFactory.register(\"Dropdown\", XmlUiDropdown, Attributes)\n\nreturn XmlUiDropdown\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiDefaults\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Defaults : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Defaults_Static\r\n---@overload fun(element: tts__UIDefaultsElement): seb_XmlUi_Defaults\r\nlocal XmlUiDefaults = {}\r\n\r\nlocal Attributes = {}\r\n\r\nsetmetatable(XmlUiDefaults, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIDefaultsElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Defaults]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Defaults\", XmlUiDefaults, Attributes)\r\n\r\nreturn XmlUiDefaults\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiCell\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Cell : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Cell_Static\r\n---@overload fun(element: tts__UICellElement): seb_XmlUi_Cell\r\nlocal XmlUiCell = {}\r\n\r\n---@shape seb_XmlUi_CellAttributes : seb_XmlUi_Attributes\r\n---@field columnSpan nil | integer @Default 1\r\n---@field dontUseTableCellBackground nil | boolean @Default false\r\n---@field image nil | string\r\n---@field overrideGlobalCellPadding nil | boolean @Default false\r\n---@field padding nil | seb_XmlUi_Padding\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal CellAttributes = {\r\n columnSpan = XmlUiFactory.AttributeType.integer,\r\n dontUseTableCellBackground = XmlUiFactory.AttributeType.boolean,\r\n image = XmlUiFactory.AttributeType.string,\r\n overrideGlobalCellPadding = XmlUiFactory.AttributeType.boolean,\r\n padding = XmlUiFactory.AttributeType.padding,\r\n}\r\n\r\nsetmetatable(XmlUiCell, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UICellElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Cell]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Cell\", XmlUiCell, CellAttributes)\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiButton\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_Button : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_Button_Static\r\n---@overload fun(element: tts__UIButtonElement): seb_XmlUi_Button\r\nlocal XmlUiButton = {}\r\n\r\nlocal Attributes = {\r\n colors = XmlUiFactory.AttributeType.colorBlock,\r\n textColor = XmlUiFactory.AttributeType.color,\r\n}\r\n\r\n---@shape seb_XmlUi_ButtonAttributes : seb_XmlUi_Attributes\r\n---@field text nil | string\r\n---@field value nil | string\r\n---@field textColor nil | seb_XmlUi_Color\r\n---@field colors nil | seb_XmlUi_ColorBlock\r\n---@field [any] nil @All other fields are invalid\r\n\r\nsetmetatable(XmlUiButton, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIButtonElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_Button]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"Button\", XmlUiButton, Attributes)\r\n\r\nreturn XmlUiButton\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.xmlui.XmlUiAxisLayout\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\nlocal XmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\r\nlocal XmlUiElement = require(\"sebaestschjin-tts.xmlui.XmlUiElement\")\r\n\r\n---@class seb_XmlUi_AxisLayout : seb_XmlUi_Element\r\n\r\n---@class seb_XmlUi_AxisLayout_Static\r\n---@overload fun(element: tts__UIHorizontalLayoutElement | tts__UIVerticalLayoutElement): seb_XmlUi_AxisLayout\r\nlocal XmlUiAxisLayout = {}\r\n\r\n---@shape seb_XmlUi_AxisLayoutAttributes : seb_XmlUi_Attributes\r\n---@field childAlignment nil | tts__UIElement_Alignment\r\n---@field childForceExpandWidth nil | boolean\r\n---@field childForceExpandHeight nil | boolean\r\n---@field padding nil | seb_XmlUi_Padding\r\n---@field spacing nil | integer\r\n---@field [any] nil @All other fields are invalid\r\n\r\nlocal Attributes = {\r\n childAlignment = XmlUiFactory.AttributeType.string,\r\n childForceExpandWidth = XmlUiFactory.AttributeType.boolean,\r\n childForceExpandHeight = XmlUiFactory.AttributeType.boolean,\r\n padding = XmlUiFactory.AttributeType.padding,\r\n spacing = XmlUiFactory.AttributeType.integer,\r\n}\r\n\r\nsetmetatable(XmlUiAxisLayout, TableUtil.merge(getmetatable(XmlUiElement), {\r\n ---@param element tts__UIHorizontalLayoutElement | tts__UIVerticalLayoutElement\r\n __call = function(_, element)\r\n local self = --[[---@type seb_XmlUi_AxisLayout]] XmlUiElement(element)\r\n\r\n return self\r\n end\r\n}))\r\n\r\nXmlUiFactory.register(\"HorizontalLayout\", XmlUiAxisLayout, Attributes)\r\nXmlUiFactory.register(\"VerticalLayout\", XmlUiAxisLayout, Attributes)\r\n\r\nreturn XmlUiAxisLayout\r\n\nend)\n__bundle_register(\"lib.StringUtil\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal StringUtil = require(\"sebaestschjin-tts.StringUtil\")\r\n\r\nreturn StringUtil\r\n\nend)\n__bundle_register(\"ui.element.MultiDropdown\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal GloomUiFactory = require(\"ui.GloomUiFactory\")\nlocal GloomUiSetting = require(\"ui.GloomUiSetting\")\nlocal Event = require(\"Event\")\n\nlocal MultiDropdown = {}\n\n---@param elementId string\nlocal function numberedId(elementId)\n ---@param index integer\n return function(index) return elementId .. \"_\" .. index end\nend\n\nlocal Ui = {\n Button = \"element.dropdown\",\n Panel = \"element.multidropdown.panel\",\n Row = numberedId(\"element.multidropdown.panel.row\"),\n Toggle = numberedId(\"element.multidropdown.panel.toggle\"),\n Text = numberedId(\"element.multidropdown.panel.text\"),\n Close = \"element.multidropdown.close\",\n}\nlocal MaxRows = 10\n\n---@type gloom_UiMultiDropdown_Settings\nlocal settings = {\n current = nil,\n elements = {},\n}\n\n---@param params gloom_UiMultiDropdown_Parameter\n---@return gloom_UiCreate_Result\nfunction MultiDropdown.create(params)\n local choicesDropdown = XmlUi.Factory.createImage({\n id = params.id,\n preferredWidth = params.width,\n preserveAspect = false,\n image = Ui.Button,\n onClick = \"onMultiDropdownOpened\",\n })\n choicesDropdown.addText({\n text = \"Select...\",\n class = \"gloom\",\n })\n GloomUiFactory.addTooltip(choicesDropdown, params.tooltip)\n\n settings.elements[params.id] = {\n choices = params.choices,\n valueProvider = params.values,\n handler = params.handler,\n }\n\n return {\n ui = choicesDropdown,\n assets = {},\n }\nend\n\n---@param ui seb_XmlUi\nlocal function createMultiDropdownPanel(ui)\n local window = ui.addPanel({\n id = Ui.Panel,\n active = false,\n visibility = {},\n width = 300,\n rectAlignment = XmlUi.Alignment.MiddleCenter,\n zIndex = 100,\n })\n\n local content = window.addTableLayout({\n cellBackgroundColor = \"clear\",\n rowBackgroundColor = GloomUiSetting.Color.Black,\n columnWidths = { 50, 250 },\n cellPadding = { h = 8, v = 0 },\n })\n\n for i = 1, MaxRows do\n local row = content.addRow({\n id = Ui.Row(i),\n active = false,\n })\n GloomUiFactory.addTooltip(row, \"\")\n\n row.addChild(GloomUiFactory.createToggle({\n id = Ui.Toggle(i),\n handler = onMultiDropdownSelected,\n active = false,\n size = 20,\n }) .ui)\n row.addText({\n id = Ui.Text(i),\n class = \"gloom\",\n text = \"\",\n onClick = \"onMultiDropdownTextSelected\",\n })\n end\n\n local close = content.addRow().addCell({ columnSpan = 2 })\n local closeButton = close.addImage({\n id = Ui.Close,\n image = Ui.Button,\n preserveAspect = false,\n onClick = \"onMultiDropdownClosed\",\n })\n closeButton.addText({\n text = \"Confirm\",\n class = \"gloom\",\n })\nend\n\n---@param player tts__Player\n---@param id tts__UIElement_Id\nfunction onMultiDropdownOpened(player, _, id)\n if settings.current ~= nil then\n showForPlayer({ panel = Ui.Panel, color = player.color })\n return\n end\n\n local choices = settings.elements[id].choices\n local values = TableUtil.copy(settings.elements[id].valueProvider())\n settings.current = { id = id, choices = choices, values = values, }\n\n local existingRow = 1\n for _, choice in ipairs(choices) do\n self.UI.setAttribute(Ui.Row(existingRow), \"active\", true)\n self.UI.setAttribute(Ui.Text(existingRow), \"text\", choice.name)\n self.UI.setAttribute(Ui.Row(existingRow), \"tooltip\", choice.description or \"\")\n\n local toggled = values[choice.value]\n GloomUiFactory.setToggle(Ui.Toggle(existingRow), toggled)\n existingRow = existingRow + 1\n end\n self.UI.setAttribute(Ui.Panel, \"height\", (existingRow + 1) * 30)\n\n for otherRow = existingRow, MaxRows do\n self.UI.setAttribute(Ui.Row(otherRow), \"active\", false)\n end\n\n showForPlayer({ panel = Ui.Panel, color = player.color })\nend\n\n---@param value boolean\n---@param id tts__UIElement_Id\nfunction onMultiDropdownSelected(_, value, id)\n local _, _, rowS = id:find(\".*_(.*)\")\n local row = --[[---@not nil]] tonumber(--[[---@type string]] rowS)\n\n local current = --[[---@not nil]] settings.current\n local choice = current.choices[row]\n current.values[choice.value] = value\nend\n\n---@param row integer\n---@return boolean\nlocal function isMultiChoiceSelected(row)\n local current = --[[---@not nil]] settings.current\n local value = current.choices[row].value\n return current.values[value]\nend\n\n---@param player tts__Player\n---@param id tts__UIElement_Id\nfunction onMultiDropdownTextSelected(player, _, id)\n local _, _, rowS = id:find(\".*_(.*)\")\n local row = --[[---@not nil]] tonumber(--[[---@type string]] rowS)\n\n local newValue = not isMultiChoiceSelected(row)\n GloomUiFactory.setToggle(Ui.Toggle(row), newValue)\n onMultiDropdownSelected(player, newValue, Ui.Toggle(row))\nend\n\n---@param player tts__Player\nfunction onMultiDropdownClosed(player)\n showForPlayer({ panel = Ui.Panel, color = player.color })\n\n local current = --[[---@not nil]] settings.current\n settings.elements[current.id].handler(player, current.values, current.id)\n settings.current = nil\nend\n\n---@param ui seb_XmlUi\nlocal function createUi(ui)\n createMultiDropdownPanel(ui)\nend\n\nGloomUiFactory.register(\"MultiDropdown\", MultiDropdown.create)\nEvent.registerForCreateGlobalUi(createUi)\n\nreturn MultiDropdown\n\nend)\n__bundle_register(\"ui.element.ImageChooser\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal Event = require(\"Event\")\nlocal GloomUiFactory = require(\"ui.GloomUiFactory\")\nlocal GloomUiSetting = require(\"ui.GloomUiSetting\")\n\nlocal ImageChooser = {}\n\n---@param elementId string\nlocal function numberedId(elementId)\n ---@param index integer\n return function(index) return elementId .. \"_\" .. index end\nend\n\nlocal Ui = {\n Button = \"element.dropdown\",\n Panel = \"element.imagechooser.panel\",\n Image = numberedId(\"element.imagechooser.image\"),\n Text = numberedId(\"element.imagechooser.text\"),\n Asset = function(id, name)\n return \"element.imagechooser.asset_\" .. id .. \"_\" .. name\n end,\n Size = 200,\n}\n\nlocal ColumnCount = 4\nlocal MaxElements = ColumnCount * 4\n\n---@type gloom_UiImageChooser_Settings\nlocal settings = {\n current = nil,\n elements = {},\n}\n\n---@param params gloom_UiImageChooser_Parameter\n---@return gloom_UiCreate_Result\nfunction ImageChooser.create(params)\n local imageChooserOpen = XmlUi.Factory.createImage({\n id = params.id,\n preferredWidth = params.width,\n preserveAspect = false,\n image = Ui.Button,\n onClick = \"onImageChooserOpened\",\n })\n imageChooserOpen.addText({\n text = \"Select...\",\n class = \"gloom\",\n })\n GloomUiFactory.addTooltip(imageChooserOpen, params.tooltip)\n\n settings.elements[params.id] = {\n choices = params.choices,\n valueProvider = params.selected,\n handler = params.handler,\n }\n\n local assets = {}\n for _, choice in ipairs(params.choices) do\n table.insert(assets, {\n name = Ui.Asset(params.id, choice.name),\n url = choice.thumbnail,\n })\n end\n\n return {\n ui = imageChooserOpen,\n assets = assets,\n }\nend\n\n---@param ui seb_XmlUi\nlocal function createImageChooserPanel(ui)\n local window = ui.addPanel({\n id = Ui.Panel,\n active = false,\n width = ColumnCount * Ui.Size,\n rectAlignment = XmlUi.Alignment.MiddleCenter,\n color = GloomUiSetting.Color.Black,\n zIndex = 100,\n })\n\n local content = window.addGridLayout({\n constraint = XmlUi.GridLayout.FixedColumnCount,\n constraintCount = ColumnCount,\n cellSize = { Ui.Size, Ui.Size },\n })\n\n for i = 1, MaxElements do\n local imagePanel = content.addPanel({\n })\n\n imagePanel.addImage({\n id = Ui.Image(i),\n image = \"\",\n preserveAspect = true,\n onClick = \"onImageChooserSelected\",\n })\n imagePanel.addText({\n id = Ui.Text(i),\n class = \"gloom\",\n alignment = XmlUi.Alignment.LowerCenter,\n })\n end\nend\n\n---@param player tts__Player\n---@param id tts__UIElement_Id\nfunction onImageChooserOpened(player, _, id)\n if settings.current ~= nil then\n showForPlayer({ panel = Ui.Panel, color = player.color })\n return\n end\n\n local choices = settings.elements[id].choices\n local value = settings.elements[id].valueProvider()\n settings.current = {\n id = id,\n choices = choices,\n value = value,\n }\n\n local existingElements = 1\n for _, choice in ipairs(choices) do\n self.UI.setAttribute(Ui.Image(existingElements), \"active\", true)\n self.UI.setAttribute(Ui.Image(existingElements), \"image\", Ui.Asset(id, choice.name))\n self.UI.setAttribute(Ui.Text(existingElements), \"active\", true)\n self.UI.setAttribute(Ui.Text(existingElements), \"text\", choice.name)\n\n ---@type seb_XmlUi_Color\n local color = GloomUiSetting.Color.Yellow\n if choice.image == value then\n color = GloomUiSetting.Color.Red\n end\n self.UI.setAttribute(Ui.Text(existingElements), \"color\", XmlUi.Factory.Converter.toColor(color))\n\n existingElements = existingElements + 1\n end\n local rowCount = math.ceil((existingElements - 1) / ColumnCount)\n self.UI.setAttribute(Ui.Panel, \"height\", rowCount * Ui.Size)\n\n for otherElement = existingElements, MaxElements do\n self.UI.setAttribute(Ui.Image(otherElement), \"active\", false)\n self.UI.setAttribute(Ui.Text(otherElement), \"active\", false)\n end\n\n showForPlayer({ panel = Ui.Panel, color = player.color })\nend\n\n---@param id tts__UIElement_Id\nfunction onImageChooserSelected(player, _, id)\n local _, _, rowS = id:find(\".*_(.*)\")\n local row = --[[---@not nil]] tonumber(--[[---@type string]] rowS)\n\n local current = --[[---@not nil]] settings.current\n current.value = current.choices[row].image\n\n onImageChooserClosed(player, current.id)\nend\n\n---@param player tts__Player\n---@param imageChooserId tts__UIElement_Id\nfunction onImageChooserClosed(player, imageChooserId)\n showForPlayer({ panel = Ui.Panel, color = player.color })\n\n local value = (--[[---@not nil]] settings.current).value\n settings.elements[imageChooserId].handler(player, value, imageChooserId)\n settings.current = nil\nend\n\n---@param ui seb_XmlUi\nlocal function createUi(ui)\n createImageChooserPanel(ui)\nend\n\nGloomUiFactory.register(\"ImageChooser\", ImageChooser.create)\nEvent.registerForCreateGlobalUi(createUi)\n\nreturn ImageChooser\n\nend)\n__bundle_register(\"ui.element.Dropdown\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal GloomUiFactory = require(\"ui.GloomUiFactory\")\nlocal GloomUiSetting = require(\"ui.GloomUiSetting\")\n\nlocal Dropdown = {}\n\nlocal Ui = {\n Box = \"element.dropdown\",\n Check = \"element.dropdown.check\",\n}\n\n---@shape gloom_UI_CreateDropdown : gloom_UiCreate_Result\n---@field dropdown gloom_UI_Dropdown\n\n---@type gloom_UiDropdown_Settings\nlocal settings = {\n elements = {},\n}\n\n---@class gloom_UI_Dropdown\n---@field id string\n\n---@param id string\n---@return gloom_UI_Dropdown\nlocal function new(id)\n local dropdown = --[[---@type gloom_UI_Dropdown]] {}\n\n dropdown.id = id\n\n ---@param options string[]\n ---@param selected string\n function dropdown.setOptions(options, selected)\n local ui = XmlUi(self)\n local uiElement = --[[---@type seb_XmlUi_Dropdown]] ui.findElement(id)\n\n uiElement.clearElements()\n for _, option in ipairs(options) do\n uiElement.addOption({ value = option, selected = option == selected })\n end\n\n ui.update()\n end\n\n return dropdown\nend\n\n---@param params gloom_UiDropdown_Parameter\n---@return gloom_UI_CreateDropdown\nfunction Dropdown.create(params)\n local choicesDropdown = XmlUi.Factory.createDropdown({\n id = params.id,\n width = params.width,\n rectAlignment = params.rectAlignment,\n offsetXY = params.offsetXY,\n onValueChanged = \"onDropdownChanged\",\n image = Ui.Box,\n itemBackgroundColors = { GloomUiSetting.Color.Black, GloomUiSetting.Color.Grey, GloomUiSetting.Color.Grey, GloomUiSetting.Color.Grey },\n dropdownBackgroundColor = GloomUiSetting.Color.Black,\n font = \"Fonts/Nyala\",\n fontSize = 15,\n textColor = GloomUiSetting.Color.Yellow,\n arrowColor = GloomUiSetting.Color.Yellow,\n checkColor = GloomUiSetting.Color.Yellow,\n scrollbarColors = { GloomUiSetting.Color.Yellow, GloomUiSetting.Color.Yellow, GloomUiSetting.Color.Yellow, GloomUiSetting.Color.Yellow },\n checkImage = Ui.Check,\n scrollSensitivity = 30,\n })\n GloomUiFactory.addTooltip(choicesDropdown, params.tooltip)\n\n settings.elements[params.id] = {\n choices = params.choices,\n handler = params.handler,\n }\n\n for _, choice in ipairs(params.choices) do\n choicesDropdown.addOption({\n value = choice.name,\n selected = choice.value == params.selected,\n })\n end\n\n return {\n ui = choicesDropdown,\n assets = {},\n dropdown = new(params.id),\n }\nend\n\n---@param player tts__Player\n---@param value string\n---@param id tts__UIElement_Id\nfunction onDropdownChanged(player, value, id)\n local handler = settings.elements[id].handler\n if not handler then\n Logger.warn(\"No handler function defined for dropdown %s\", id)\n return\n end\n\n local choices = settings.elements[id].choices\n if not choices then\n Logger.warn(\"No choices defined for dropdown %s\", id)\n return\n end\n\n local index = 0\n local selectedValue\n\n for _, choice in ipairs(choices) do\n if choice.name == value then\n self.UI.setAttribute(id, \"value\", index)\n selectedValue = choice.value\n break\n end\n index = index + 1\n end\n\n handler(player, { name = value, value = selectedValue }, id)\nend\n\nGloomUiFactory.register(\"Dropdown\", Dropdown.create)\n\nreturn Dropdown\n\nend)\n__bundle_register(\"lib.Version\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal StringUtil = require(\"lib.StringUtil\")\n\n---@class gloom_Version_static\n---@overload fun(version: string | seb_Version): gloom_Version\nlocal Version = {}\n\n---@class gloom_Version\n---@field major integer\n---@field minor integer\n---@field fix integer\n---@field isBeta boolean\n---@field rc nil | integer\n\nlocal versionMeta = {\n __eq = function(a, b)\n return a.__eq(b)\n end,\n\n __tostring = function(a)\n return a.__tostring()\n end,\n}\n\n---@param version string | seb_Version\n---@return gloom_Version\nlocal function new(version)\n local self = --[[---@type gloom_Version]] {}\n\n setmetatable(self, versionMeta)\n\n local tableVersion\n if type(version) == \"string\" then\n tableVersion = StringUtil.split(--[[---@type string]] version, \".\")\n else\n tableVersion = --[[---@type seb_Version]] version\n end\n\n self.major = tonumber(tableVersion[1])\n self.minor = tonumber(tableVersion[2])\n self.fix = tonumber(tableVersion[3])\n\n if tableVersion[4] then\n self.isBeta = true\n self.rc = tonumber(tableVersion[5])\n else\n self.isBeta = false\n end\n\n ---@param other gloom_Version\n ---@return boolean\n function self.isBefore(other)\n return self ~= other and not self.isAfter(other)\n end\n\n ---@param other gloom_Version\n ---@return boolean\n function self.isAfter(other)\n if self.major > other.major then\n return true\n end\n if self.major < other.major then\n return false\n end\n\n -- now same major\n if self.minor > other.minor then\n return true\n end\n if self.minor < other.minor then\n return false\n end\n\n -- now same major.minor\n if self.fix > other.fix then\n return true\n end\n if self.fix < other.fix then\n return false\n end\n\n -- now same major.minor.fix\n if self.isBeta then\n if not other.isBeta then\n return false\n end\n\n return self.rc > other.rc\n end\n\n -- self is not a beta, but the other one with the same major.minor.fix\n return other.isBeta\n end\n\n ---@param other gloom_Version\n ---@return boolean\n function self.__eq(other)\n return self.major == other.major\n and self.minor == other.minor\n and self.fix == other.fix\n and self.isBeta == other.isBeta\n and self.rc == other.rc\n end\n\n function self.__tostring()\n local base = self.major .. \".\" .. self.minor .. \".\" .. self.fix\n if self.isBeta then\n base = base .. \".beta.\" .. self.rc\n end\n return base\n end\n\n return self\nend\n\nsetmetatable(Version, {\n ---@param version seb_Version\n __call = function(_, version)\n return new(version)\n end\n})\n\nreturn Version\n\nend)\n__bundle_register(\"HotKeys\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal SaveManager = require(\"lib.SaveManager\")\r\n\r\nlocal R = require(\"api.Resource\")\r\nlocal Figure = require(\"Figure\")\r\nlocal Game = require(\"Game\")\r\nlocal SoloPlay = require(\"SoloPlay\")\r\n\r\nlocal Loot = require(\"Hotkeys.Loot\")\r\nlocal DefeatMonster = require(\"Hotkeys.DefeatMonster\")\r\nlocal MonsterAttackModifier = require(\"Hotkeys.MonsterAttackModifier\")\r\nlocal PlayerHotkeys = require(\"Hotkeys.PlayerHotkeys\")\r\n\r\n--- Handles the inclusion of Hotkeys to the game. The actual implementation of the functionality should be placed in\r\n--- other relevant modules.\r\nlocal HotKeys = {}\r\n\r\n---@alias gloom_Hotkey_ObjectHandler fun(object: tts__Object, player: tts__PlayerColor): void\r\n---@alias gloom_Hotkey_GlobalHandler fun(player: tts__PlayerColor): void\r\n\r\n---@param label string\r\n---@param handler gloom_Hotkey_ObjectHandler\r\n---@param tags string | string[]\r\nlocal function addObjectHotkey(label, handler, tags)\r\n addHotkey(label, function(player, obj)\r\n if obj ~= nil then\r\n if type(tags) == \"string\" then\r\n tags = { --[[---@type string]] tags }\r\n end\r\n for _, tag in ipairs(--[[---@type string[] ]] tags) do\r\n if obj.hasTag(tag) then\r\n handler(obj, player)\r\n break\r\n end\r\n end\r\n end\r\n end)\r\nend\r\n\r\n---@param label string\r\n---@param handler gloom_Hotkey_GlobalHandler\r\nlocal function addGlobalHotkey(label, handler)\r\n addHotkey(label, function(player, obj)\r\n if obj == nil or obj.locked then\r\n handler(player)\r\n end\r\n end)\r\nend\r\n\r\n---@param fun fun(obj: tts__Object): void\r\n---@return gloom_Hotkey_ObjectHandler\r\nlocal function playerLess(fun)\r\n return function(obj, _)\r\n fun(obj)\r\n end\r\nend\r\n\r\nlocal function onLoad()\r\n ---@type string[]\r\n addObjectHotkey(\"Add Health\", playerLess(Figure.addHealth), R.Tag.Trait.HasHealth)\r\n addObjectHotkey(\"Remove Health\", playerLess(Figure.removeHealth), R.Tag.Trait.HasHealth)\r\n addHotkey(\"Show Context Menu\", Figure.showContextMenu)\r\n\r\n Loot.register()\r\n DefeatMonster.register()\r\n MonsterAttackModifier.register()\r\n PlayerHotkeys.register()\r\n\r\n addGlobalHotkey(\"Switch to next character\", SoloPlay.switchToNextCharacter)\r\n addGlobalHotkey(\"Switch to solo player\", function(player) SoloPlay.switchToPlayer(player, 0) end)\r\n for i = 1, Game.PlayerCount do\r\n addGlobalHotkey(\"Switch to player \" .. i, function(player)\r\n SoloPlay.switchToPlayer(player, i)\r\n end)\r\n end\r\nend\r\n\r\nSaveManager.registerOnLoad(onLoad)\r\n\r\nreturn HotKeys\r\n\nend)\n__bundle_register(\"Hotkeys.PlayerHotkeys\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Game = require(\"Game\")\r\nlocal Options = require(\"api.OptionsApi\")\r\n\r\nlocal PlayerHotkeys = {}\r\n\r\n---@param card tts__Object\r\n---@param index 1 | 2\r\nlocal function playCard(card, index)\r\n local function getOwningPlayer()\r\n for _, playerInfo in ipairs(Game.PlayerInfo) do\r\n for _, handObject in ipairs(Player[playerInfo.Color].getHandObjects()) do\r\n if handObject == card then\r\n return playerInfo\r\n end\r\n end\r\n end\r\n end\r\n\r\n local owningPlayer = getOwningPlayer()\r\n if not owningPlayer then\r\n return\r\n end\r\n\r\n if Options.hideAbilityCards() then\r\n card.flip()\r\n end\r\n\r\n local targetPosition = owningPlayer.Abilities[index]\r\n local hits = Physics.cast({\r\n origin = targetPosition,\r\n direction = { 0, 1, 0 },\r\n type = 3,\r\n size = { 0.5, 2, 0.5 },\r\n max_distance = 0,\r\n debug = false\r\n })\r\n\r\n for _, hit in pairs(hits) do\r\n if hit.hit_object.type == \"Card\" or hit.hit_object.type == \"Deck\" then\r\n hit.hit_object.deal(20, owningPlayer.Color)\r\n end\r\n end\r\n\r\n Wait.frames(function() card.setPosition(targetPosition) end, 10)\r\nend\r\n\r\n---@param card nil | tts__Object\r\nlocal function playFirstCard(_, card)\r\n if card then\r\n playCard(--[[---@not nil]] card, 1)\r\n end\r\nend\r\n\r\n---@param card nil | tts__Object\r\nlocal function playSecondCard(_, card)\r\n if card then\r\n playCard(--[[---@not nil]] card, 2)\r\n end\r\nend\r\n\r\nfunction PlayerHotkeys.register()\r\n addHotkey(\"Play 1st Card\", playFirstCard)\r\n addHotkey(\"Play 2nd Card\", playSecondCard)\r\nend\r\n\r\nreturn PlayerHotkeys\r\n\nend)\n__bundle_register(\"Hotkeys.MonsterAttackModifier\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal AttackModifierMechanic = require(\"mechanic.attackmodifier.AttackModifierMechanic\")\r\n\r\nlocal MonsterAttackModifier = {}\r\n\r\nlocal function draw()\r\n AttackModifierMechanic.drawCard(\"Monster\")\r\nend\r\n\r\nfunction MonsterAttackModifier.register()\r\n addHotkey(\"Draw Monster Attack Modifier\", draw)\r\nend\r\n\r\nreturn MonsterAttackModifier\r\n\nend)\n__bundle_register(\"Hotkeys.DefeatMonster\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal R = require(\"api.Resource\")\n\nlocal DefeatMonster = {}\n\n---@param object tts__Object\nlocal function defeat(_, object)\n if object ~= nil and (object.hasTag(R.Tag.Enemy) or object.hasTag(R.Tag.Summon)) then\n object.call(\"defeatClick\")\n end\nend\n\nfunction DefeatMonster.register()\n addHotkey(\"Defeat Monster\", defeat)\nend\n\nreturn DefeatMonster\n\nend)\n__bundle_register(\"Hotkeys.Loot\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Game = require(\"Game\")\r\n\r\nlocal Loot = {}\r\n\r\n---@param object tts__Object\r\nlocal function canLoot(object)\r\n return object.getName():find(\"Coin\") or object.getName():find(\"Treasure Chest\")\r\nend\r\n\r\n---@param player tts__PlayerColor\r\n---@param object tts__Object\r\nlocal function loot(player, object)\r\n -- TODO add looting for Black player at some point\r\n if object ~= nil and player ~= \"Black\" and canLoot(object) then\r\n object.setPositionSmooth(Game.getPlayerInfo(player).Loot)\r\n object.unlock()\r\n end\r\nend\r\n\r\nfunction Loot.register()\r\n addHotkey(\"Loot\", loot)\r\nend\r\n\r\nreturn Loot\r\n\nend)\n__bundle_register(\"SoloPlay\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Game = require(\"Game\")\r\nlocal Party = require(\"Party\")\r\n\r\nlocal SoloPlay = {}\r\n\r\n---@param player tts__PlayerColor @The player that wants to switch\r\n---@param playerNumber integer @The player number to switch to. If 0 it switches to Black.\r\nfunction SoloPlay.switchToPlayer(player, playerNumber)\r\n ---@type tts__PlayerColor\r\n local targetColor\r\n ---@type nil | tts__Player_CameraSetting\r\n local targetCamera\r\n\r\n if playerNumber == 0 then\r\n targetColor = \"Black\"\r\n else\r\n targetColor = Game.PlayerInfo[playerNumber].Color\r\n targetCamera = Game.PlayerInfo[playerNumber].Camera\r\n end\r\n\r\n Player[player].changeColor(targetColor)\r\n if targetCamera then\r\n Wait.time(function() Player[targetColor].lookAt(--[[---@not nil]] targetCamera) end, 0.1)\r\n end\r\nend\r\n\r\n---@param player tts__PlayerColor\r\nfunction SoloPlay.switchToNextCharacter(player)\r\n local activeCharacters = Party.getActiveCharacters()\r\n\r\n if TableUtil.isEmpty(activeCharacters) then\r\n return\r\n end\r\n\r\n ---@return integer\r\n local function getCurrentPlayerNumber()\r\n for playerNumber, playerInfo in ipairs(Game.PlayerInfo) do\r\n if playerInfo.Color == player then\r\n return playerNumber\r\n end\r\n end\r\n\r\n return 0\r\n end\r\n\r\n local players = TableUtil.shift(TableUtil.range(Game.PlayerCount), getCurrentPlayerNumber())\r\n for _, playerNumber in ipairs(players) do\r\n local playerColor = Game.PlayerInfo[playerNumber].Color\r\n if activeCharacters[playerNumber] and not Player[playerColor].seated then\r\n SoloPlay.switchToPlayer(player, playerNumber)\r\n return\r\n end\r\n end\r\n\r\n printToColor(\"No free character spot available!\", player, \"Yellow\")\r\nend\r\n\r\nreturn SoloPlay\r\n\nend)\n__bundle_register(\"Figure\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Party = require(\"Party\")\r\n\r\nlocal ClassApi = require(\"api.ClassApi\")\r\nlocal Conditions = require(\"api.ConditionApi\")\r\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal ContextMenu = require(\"ui.ContextMenu\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@class gloom_Figure_Static\r\nlocal Figure = {}\r\n\r\n---@class gloom_Figure\r\n---@overload fun(figure: tts__Object): gloom_Figure_Character\r\nlocal BaseFigure = {}\r\n\r\nsetmetatable(BaseFigure, {\r\n ---@param figure tts__Object\r\n __call = function(_, figure)\r\n local self = --[[---@type gloom_Figure]] {}\r\n\r\n local ttsObject = figure\r\n\r\n ---@return string\r\n function self.getName()\r\n return ttsObject.getName()\r\n end\r\n\r\n ---@return nil | tts__UIAssetName\r\n function self.getTracker()\r\n return nil\r\n end\r\n\r\n ---@overload fun(): void\r\n ---@param health integer\r\n function self.addHealth(health)\r\n health = health or 1\r\n for _ = 1, health do\r\n ttsObject.call(\"add\")\r\n end\r\n end\r\n\r\n ---@overload fun(): void\r\n ---@param health integer\r\n function self.removeHealth(health)\r\n health = health or 1\r\n for _ = 1, health do\r\n ttsObject.call(\"sub\")\r\n end\r\n end\r\n\r\n ---@return gloom_Figure_Health\r\n function self.getHealth()\r\n return --[[---@type gloom_Figure_Health]] ttsObject.call(\"getHp\")\r\n end\r\n\r\n ---@param health gloom_Figure_Health\r\n function self.setHealth(health)\r\n ttsObject.call(\"setHp\", health)\r\n end\r\n\r\n ---@param conditionName string\r\n function self.addCondition(conditionName)\r\n ttsObject.call(\"addCondition\", conditionName)\r\n end\r\n\r\n ---@param conditionName string\r\n ---@return boolean\r\n function self.isImmune(conditionName)\r\n return ttsObject.hasTag(R.Tag.Trait.HasImmunities) and ttsObject.call(\"isImmuneTo\", conditionName)\r\n end\r\n\r\n ---@return gloom_Spawn_Definition[]\r\n function self.getSpawnableElements()\r\n return {}\r\n end\r\n\r\n ---@return tts__Object\r\n function self.getObject()\r\n return ttsObject\r\n end\r\n\r\n return self\r\n end\r\n})\r\n\r\n---@class gloom_Figure_Character : gloom_Figure\r\n---@overload fun(figure: tts__Object): gloom_Figure_Character\r\nlocal CharacterFigure = {}\r\n\r\nsetmetatable(CharacterFigure, TableUtil.merge(getmetatable(BaseFigure), {\r\n ---@param figure tts__Object\r\n ---@return gloom_Figure_Character\r\n __call = function(_, figure)\r\n local self = --[[---@type gloom_Figure_Character]] BaseFigure(figure)\r\n\r\n function self.getTracker()\r\n return Conditions.getClassTracker(self.getName())\r\n end\r\n\r\n function self.getSpawnableElements()\r\n local className = self.getName()\r\n ---@type gloom_Spawn_Definition[]\r\n local spawns = {}\r\n local classSpawns = ClassApi.getSpawnableElements(className)\r\n for _, spawn in ipairs(classSpawns) do\r\n table.insert(spawns, spawn)\r\n end\r\n\r\n local playerInfo = Party.getCharacterInfoByClass(className)\r\n if playerInfo then\r\n local playerSpawns = playerInfo.objects.spawnableElements()\r\n for _, spawn in ipairs(playerSpawns) do\r\n table.insert(spawns, spawn)\r\n end\r\n end\r\n\r\n return spawns\r\n end\r\n\r\n return self\r\n end\r\n}))\r\n\r\n---@class gloom_Figure_Enemy : gloom_Figure\r\n---@overload fun(figure: tts__Object): gloom_Figure_Enemy\r\nlocal EnemyFigure = {}\r\n\r\nsetmetatable(EnemyFigure, TableUtil.merge(getmetatable(BaseFigure), {\r\n ---@param figure tts__Object\r\n ---@return gloom_Figure_Enemy\r\n __call = function(_, figure)\r\n local self = --[[---@type gloom_Figure_Enemy]] BaseFigure(figure)\r\n\r\n function self.getName()\r\n return --[[---@not nil]] self.getObject().memo\r\n end\r\n\r\n function self.getSpawnableElements()\r\n return EnemyApi.getSpawnableElements(self.getName())\r\n end\r\n\r\n return self\r\n end\r\n}))\r\n\r\n---@param figure tts__Object\r\n---@return gloom_Figure\r\nfunction Figure.create(figure)\r\n if figure.hasTag(R.Tag.Enemy) then\r\n return EnemyFigure(figure)\r\n end\r\n if figure.hasTag(R.Tag.Character) then\r\n return CharacterFigure(figure)\r\n end\r\n return BaseFigure(figure)\r\nend\r\n\r\n---@param figure tts__Object\r\n---@param health integer\r\nfunction Figure.changeHealth(figure, health)\r\n if health > 0 then\r\n Figure.addHealth(figure, health)\r\n else\r\n Figure.removeHealth(figure, math.abs(health))\r\n end\r\nend\r\n\r\n---@overload fun(figure: tts__Object): void\r\n---@param figure tts__Object\r\n---@param health integer\r\nfunction Figure.addHealth(figure, health)\r\n Figure.create(figure).addHealth(health)\r\nend\r\n\r\n---@overload fun(figure: tts__Object): void\r\n---@param figure tts__Object\r\n---@param health integer\r\nfunction Figure.removeHealth(figure, health)\r\n Figure.create(figure).removeHealth(health)\r\nend\r\n\r\n---@param figure tts__Object\r\n---@param conditionName string\r\nfunction Figure.addCondition(figure, conditionName)\r\n Figure.create(figure).addCondition(conditionName)\r\nend\r\n\r\n---@param figure tts__Object\r\n---@param player tts__PlayerColor\r\nfunction Figure.showContextMenu(player, figure)\r\n local figureTags = { R.Tag.Enemy, R.Tag.Character, R.Tag.Summon }\r\n\r\n if not figure or TableUtil.none(figure.getTags(), function(t) return TableUtil.contains(figureTags, t) end) then\r\n ContextMenu.hide(player)\r\n return\r\n end\r\n\r\n if figure.hasTag(R.Tag.Trait.HasConditions) then\r\n ContextMenu.show(Figure.create(figure), player)\r\n else\r\n Logger.warn(\"The selected figure doesn't currently support the context menu. \" ..\r\n \"It has to be updated to use version 1.3+.\")\r\n end\r\nend\r\n\r\nreturn Figure\r\n\nend)\n__bundle_register(\"ui.ContextMenu\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal Search = require(\"lib.Search\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal Conditions = require(\"api.ConditionApi\")\nlocal ComponentApi = require(\"api.ComponentApi\")\nlocal EnemyApi = require(\"api.EnemyApi\")\nlocal Event = require(\"Event\")\nlocal Game = require(\"Game\")\nlocal GloomUi = require(\"ui.GloomUi\")\nlocal Party = require(\"Party\")\nlocal R = require(\"Resources\")\n\nlocal ContextMenu = {}\n\nlocal Ui = {\n Menu = {\n ---@param player tts__PlayerColor\n Id = function(player) return \"FigureContextMenu_\" .. player end,\n },\n Spawn = {\n ---@param player tts__PlayerColor\n ---@param value integer\n Id = function(player, value) return \"Spawn_\" .. value .. \"_\" .. player end,\n TextId = function(player, value) return \"Spawn_Text_\" .. value .. \"_\" .. player end,\n },\n Condition = {\n ---@param player tts__PlayerColor\n ---@param conditionNumber integer\n Id = function(player, conditionNumber) return \"Condition_\" .. conditionNumber .. \"_\" .. player end,\n },\n Tracker = {\n ---@param player tts__PlayerColor\n ---@param playerNumber integer\n Id = function(player, playerNumber) return \"Tracker_\" .. playerNumber .. \"_\" .. player end,\n },\n Tooltip = {\n ---@param player tts__PlayerColor\n Id = function(player) return \"FigureContextMenuTooltip_\" .. player end,\n Text = function(player) return \"FigureContextMenuTooltipText_\" .. player end,\n },\n Columns = 5,\n Spacing = 5,\n Size = 150,\n}\n\nlocal MaxConditions = 25\nlocal MaxSpawns = 15\n\n---@shape gloom_ContextMenu_OpenInformation\n---@field figure gloom_Figure\n---@field spawns gloom_Spawn_Definition[]\n---@field trackers gloom_ContextMenu_OpenInformation_Tracker[]\n---@field conditions string[]\n\n---@shape gloom_ContextMenu_OpenInformation_Tracker\n---@field class string\n---@field name tts__UIAssetName\n\n---@type table\nlocal openedForFigure = {}\n\n---@param ui seb_XmlUi\n---@param player tts__PlayerColor\nlocal function createForPlayer(ui, player)\n local content = ui.addPanel({\n id = Ui.Menu.Id(player),\n offsetXY = { -75, -300 }, -- TODO make the placement better\n active = false,\n showAnimation = XmlUi.Animation.Show.FadeIn,\n hideAnimation = XmlUi.Animation.Hide.FadeOut,\n })\n\n local tooltipPanel = ui.addPanel({\n id = Ui.Tooltip.Id(player),\n offsetXY = { -260, 200 },\n active = false,\n width = 150, height = 70,\n })\n local tooltipContent = tooltipPanel.addPanel({\n visibility = player,\n color = GloomUi.Color.Black,\n outline = GloomUi.Color.Yellow,\n })\n tooltipContent.addText({\n id = Ui.Tooltip.Text(player),\n color = GloomUi.Color.Yellow,\n alignment = XmlUi.Alignment.MiddleCenter,\n })\n\n local contentLayout = content.addVerticalLayout({\n visibility = player,\n childForceExpandHeight = false,\n width = Ui.Size,\n spacing = Ui.Spacing,\n })\n\n local spawnMenu = contentLayout.addGridLayout({\n constraint = XmlUi.GridLayout.FixedColumnCount,\n constraintCount = Ui.Columns,\n spacing = { Ui.Spacing, Ui.Spacing },\n })\n for spawnNumber = 1, MaxSpawns do\n local image = spawnMenu.addImage({\n id = Ui.Spawn.Id(player, spawnNumber),\n image = \"\",\n onClick = \"onAddSpawnClicked\",\n onMouseEnter = \"onMouseEnterContextMenuElement\",\n onMouseExit = \"onMouseExitContextMenuElement\",\n })\n image.addText({\n id = Ui.Spawn.TextId(player, spawnNumber),\n active = false,\n color = GloomUi.Color.Yellow,\n alignment = XmlUi.Alignment.MiddleCenter,\n })\n end\n\n local trackerMenu = contentLayout.addGridLayout({\n constraint = XmlUi.GridLayout.FixedColumnCount,\n constraintCount = Ui.Columns,\n spacing = { Ui.Spacing, Ui.Spacing },\n })\n for playerNumber = 1, Game.PlayerCount do\n trackerMenu.addImage({\n id = Ui.Tracker.Id(player, playerNumber),\n image = \"\",\n onClick = \"onAddTrackerClicked\",\n onMouseEnter = \"onMouseEnterContextMenuElement\",\n onMouseExit = \"onMouseExitContextMenuElement\",\n })\n end\n\n local conditionMenu = contentLayout.addGridLayout({\n constraint = XmlUi.GridLayout.FixedColumnCount,\n constraintCount = Ui.Columns,\n spacing = { Ui.Spacing, Ui.Spacing },\n })\n for condition = 1, MaxConditions do\n conditionMenu.addImage({\n id = Ui.Condition.Id(player, condition),\n image = \"\",\n onClick = \"onAddConditionClicked\",\n onMouseEnter = \"onMouseEnterContextMenuElement\",\n onMouseExit = \"onMouseExitContextMenuElement\",\n })\n end\n\n local closeButton = contentLayout.addImage({\n image = \"element.dropdown\",\n preserveAspect = false,\n preferredHeight = 50,\n offsetXY = { 20, 0 },\n preferredWidth = Ui.Size,\n onClick = \"onCloseContextMenuClicked\",\n rectAlignment = XmlUi.Alignment.UpperRight,\n })\n closeButton.addText({\n text = \"Close\",\n alignment = XmlUi.Alignment.MiddleCenter,\n color = GloomUi.Color.Yellow,\n })\nend\n\n---@param ui seb_XmlUi\nlocal function createUi(ui)\n for _, playerColor in ipairs(Game.PossiblePlayerColors) do\n createForPlayer(ui, playerColor)\n end\nend\n\n---@param spawn gloom_Spawn_Definition\n---@return string\nlocal function getSpawnTooltip(spawn)\n if spawn.element.type == R.ElementType.Enemy then\n return \"Summon \" .. spawn.element.name .. \"\\nRight-Click to summon elite version\"\n end\n\n local tooltip = \"\"\n if spawn.source then\n tooltip = \"\" .. spawn.source .. \"\\n\"\n end\n\n tooltip = tooltip .. \"Create \" .. spawn.element.name\n if spawn.element.type == R.ElementType.Trap then\n local trapInfo = --[[---@type gloom_Spawn_Element_Trap]] spawn.element\n if trapInfo.damage then\n tooltip = tooltip .. \"\\n\" .. (--[[---@not nil]] trapInfo.damage) .. \" Damage\"\n end\n if trapInfo.conditions then\n tooltip = tooltip .. \"\\n\" .. table.concat(--[[---@not nil]] trapInfo.conditions, \", \")\n end\n end\n\n return tooltip\nend\n\n---@param player tts__PlayerColor\n---@param openInformation gloom_ContextMenu_OpenInformation\nlocal function updateUi(player, openInformation)\n for spawnNumber = 1, MaxSpawns do\n local spawn = openInformation.spawns[spawnNumber]\n local spawnId = Ui.Spawn.Id(player, spawnNumber)\n local spawnTextId = Ui.Spawn.TextId(player, spawnNumber)\n\n UI.setAttribute(spawnId, \"active\", spawn ~= nil)\n\n if spawn then\n local imageName\n if spawn.element.type == R.ElementType.Enemy then\n local enemy = EnemyApi.getEnemies()[spawn.element.name]\n if enemy and enemy.image then\n imageName = (--[[---@not nil]] enemy.image).name\n end\n elseif spawn.element.type == R.ElementType.Summon then\n local summon = ComponentApi.getSummons()[spawn.element.name]\n if summon then\n imageName = summon.image.name\n end\n else\n local overlay = ComponentApi.getOverlays()[spawn.element.name]\n if overlay then\n imageName = overlay.image.name\n end\n end\n\n if imageName and XmlUi.hasAsset(Global, imageName) then\n UI.setAttribute(spawnId, \"image\", imageName)\n UI.setAttribute(spawnTextId, \"active\", false)\n else\n UI.setAttribute(spawnId, \"image\", \"spawn.element.default\")\n UI.setAttribute(spawnTextId, \"active\", true)\n UI.setAttribute(spawnTextId, \"text\", spawn.element.name)\n end\n end\n end\n\n for playerNumber = 1, Game.PlayerCount do\n local tracker = openInformation.trackers[playerNumber]\n local trackerId = Ui.Tracker.Id(player, playerNumber)\n UI.setAttribute(trackerId, \"active\", tracker ~= nil)\n if tracker then\n UI.setAttribute(trackerId, \"image\", tracker.name)\n end\n end\n\n for conditionNumber = 1, MaxConditions do\n local condition = openInformation.conditions[conditionNumber]\n local conditionId = Ui.Condition.Id(player, conditionNumber)\n UI.setAttribute(conditionId, \"active\", condition ~= nil)\n if condition then\n UI.setAttribute(conditionId, \"image\", condition)\n end\n end\nend\n\nlocal function showUi(player)\n UI.show(Ui.Menu.Id(player))\nend\n\n---@param player tts__PlayerColor\nlocal function hideUi(player)\n UI.hide(Ui.Menu.Id(player))\n openedForFigure[player] = nil\nend\n\n---@param figure gloom_Figure\n---@return gloom_Spawn_Definition[]\nlocal function createSpawnInformation(figure)\n ---@param spawn gloom_Spawn_Definition\n ---@return boolean\n local function isSpawnAvailable(spawn)\n if spawn.sourceObject then\n return true\n end\n\n if spawn.source then\n return Search.inAllObjects({ name = --[[---@not nil]] spawn.source, isPattern = true }) ~= nil\n end\n\n return true\n end\n\n local spawns = TableUtil.filter(figure.getSpawnableElements(), isSpawnAvailable)\n\n if TableUtil.length(spawns) > MaxSpawns then\n Logger.warn(\"Currently only %s spawns can be shown in the menu, but %s are available. Extra spawns will be ignored.\",\n MaxSpawns, TableUtil.length(spawns))\n end\n\n return spawns\nend\n\n---@return gloom_ContextMenu_OpenInformation_Tracker[]\nlocal function createTrackerInformation()\n local trackers = {}\n for _, character in pairs(Party.getActiveCharacters()) do\n local classTracker = Conditions.getClassTracker(--[[---@not nil]] character.class)\n if classTracker then\n table.insert(trackers, --[[---@not nil]] { class = character.class, name = classTracker })\n else\n Logger.warn(\"The class %s has no registered tracker image\", character.class)\n end\n end\n\n return trackers\nend\n\n---@param figure gloom_Figure\n---@return string[]\nlocal function createConditionInformation(figure)\n local conditions = {}\n for conditionName, _ in pairs(Conditions.getConditions()) do\n if not figure.isImmune(conditionName) then\n table.insert(conditions, conditionName)\n end\n end\n table.sort(conditions)\n\n if TableUtil.length(conditions) > MaxConditions then\n Logger.warn(\"Currently only %s conditions can be shown in the menu, but %s are registered. Extra conditions will be ignored.\",\n MaxConditions, TableUtil.length(conditions))\n end\n\n return conditions\nend\n\n---@param figure gloom_Figure\n---@return gloom_ContextMenu_OpenInformation\nlocal function createInformation(figure)\n return {\n figure = figure,\n spawns = createSpawnInformation(figure),\n trackers = createTrackerInformation(),\n conditions = createConditionInformation(figure),\n }\nend\n\n---@param figure gloom_Figure\n---@param player tts__PlayerColor\nfunction ContextMenu.show(figure, player)\n if openedForFigure[player] then\n local currentFigure = openedForFigure[player]\n ContextMenu.hide(player)\n if currentFigure.figure.getObject().getGUID() == figure.getObject().getGUID() then\n return\n end\n end\n\n local currentOpen = createInformation(figure)\n openedForFigure[player] = currentOpen\n updateUi(player, currentOpen)\n\n showUi(player)\nend\n\n---@param player tts__PlayerColor\nfunction ContextMenu.hide(player)\n hideUi(player)\nend\n\n---@param id string\n---@return integer\nlocal function getElementIndex(id)\n local _, _, index = id:find(\".*_(.*)_.*\")\n return --[[---@not nil]] tonumber(--[[---@type string]] index)\nend\n\n---@param player tts__Player\n---@param id string\nfunction onAddSpawnClicked(player, mouseButton, id)\n local spawnNumber = getElementIndex(id)\n local openInformation = openedForFigure[player.color]\n local figure = openInformation.figure\n local spawn = openInformation.spawns[spawnNumber]\n\n ---@type tts__VectorShape\n local rotation = { 0, 0, 0 }\n if Grid.type == 3 and spawn.element.type ~= R.ElementType.Enemy then\n rotation = { 0, 30, 0 }\n end\n if spawn.element.type == R.ElementType.Summon then\n rotation = { 0, 180, 0 }\n end\n\n ---@type gloom_Spawn_Execution\n local spawnInfo = {\n element = TableUtil.deepCopy(spawn.element),\n action = spawn.action,\n placement = {\n position = figure.getObject().getPosition():add(Vector(0, 3, 0)),\n rotation = rotation,\n lock = R.LockType.Soft,\n summoner = figure.getObject().getGUID(),\n },\n source = spawn.source,\n sourceObject = spawn.sourceObject,\n }\n if spawnInfo.element.type == R.ElementType.Enemy and mouseButton == XmlUi.MouseEvent.RightClick then\n (--[[---@type gloom_Spawn_Element_Enemy]] spawnInfo.element).difficulty = \"elite\"\n end\n\n local tracker = figure.getTracker()\n if tracker and spawnInfo.element.type ~= R.ElementType.Corridor then\n if not spawnInfo.element.conditions then\n spawnInfo.element.conditions = {}\n end\n table.insert(--[[---@not nil]] spawnInfo.element.conditions, --[[---@not nil]] tracker)\n end\n\n ComponentApi.spawnElement(spawnInfo)\n\n hideUi(player.color)\nend\n\n---@param player tts__Player\n---@param value string\n---@param id string\nfunction onAddTrackerClicked(player, value, id)\n local trackerNumber = getElementIndex(id)\n local openInformation = openedForFigure[player.color]\n local tracker = openInformation.trackers[trackerNumber]\n local figure = openInformation.figure\n figure.addCondition(tracker.name)\n\n if value ~= XmlUi.MouseEvent.RightClick then\n openedForFigure[player.color] = nil\n hideUi(player.color)\n end\nend\n\n---@param player tts__Player\n---@param value string\n---@param id string\nfunction onAddConditionClicked(player, value, id)\n local conditionNumber = getElementIndex(id)\n local openInformation = openedForFigure[player.color]\n local condition = openInformation.conditions[conditionNumber]\n local figure = openInformation.figure\n figure.addCondition(condition)\n\n if value ~= XmlUi.MouseEvent.RightClick then\n openedForFigure[player.color] = nil\n hideUi(player.color)\n end\nend\n\n---@param player tts__Player\n---@param id tts__UIElement_Id\nfunction onMouseEnterContextMenuElement(player, _, id)\n local _, _, group, indexS = id:find(\"(.+)_(.+)_.+\")\n local index = --[[---@not nil]] tonumber(--[[---@type number]] indexS)\n local openInformation = openedForFigure[player.color]\n if openInformation ~= nil then\n local tooltip\n if group == \"Spawn\" then\n tooltip = getSpawnTooltip(openInformation.spawns[index])\n elseif group == \"Condition\" then\n tooltip = \"Apply \" .. openInformation.conditions[index]\n elseif group == \"Tracker\" then\n tooltip = \"Track class \" .. openInformation.trackers[index].class\n else\n Logger.warn(\"Unknown tooltip for \" .. group)\n return\n end\n\n self.UI.setAttribute(Ui.Tooltip.Text(player.color), \"text\", tooltip)\n self.UI.show(Ui.Tooltip.Id(player.color))\n end\nend\n\n---@param player tts__Player\nfunction onMouseExitContextMenuElement(player)\n self.UI.hide(Ui.Tooltip.Id(player.color))\nend\n\n---@param player tts__Player\nfunction onCloseContextMenuClicked(player)\n hideUi(player.color)\nend\n\nEvent.registerForCreateGlobalUi(createUi)\n\nreturn ContextMenu\n\nend)\n__bundle_register(\"Resources\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal ResourceApi = require(\"api.Resource\")\r\n\r\nlocal Resources = {}\r\n\r\nResources.Version = ResourceApi.Version\r\nResources.ElementType = ResourceApi.ElementType\r\nResources.Tag = ResourceApi.Tag\r\nResources.Remove = ResourceApi.Remove\r\nResources.EmptyElement = ResourceApi.EmptyElement\r\nResources.LockType = ResourceApi.LockType\r\n\r\nResources.Guid = {\r\n Gamebox = \"346ed5\",\r\n Treasures = \"fe14ef\",\r\n Backup = \"29c2ce\",\r\n}\r\n\r\n--- Returns the single object with a given tag.\r\n---@param tag string\r\n---@return tts__Object\r\nfunction Resources.getInstance(tag)\r\n local objects = getObjectsWithTag(tag)\r\n if objects[2] ~= nil then\r\n Logger.warn(\"More than one instance of '%s' found. This might lead to undefined behaviour.\", tag)\r\n end\r\n if objects[1] == nil then\r\n Logger.error(\"No instance of '%s' found. This will lead to undefined errors!\", tag)\r\n end\r\n\r\n return objects[1]\r\nend\r\n\r\n--- Returns all instances with the given tag.\r\n---@param tag string\r\n---@return tts__Object[]\r\nfunction Resources.getInstances(tag)\r\n return getObjectsWithTag(tag)\r\nend\r\n\r\nreturn Resources\r\n\nend)\n__bundle_register(\"api.EnemyApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal EnemyApi = ApiConsumer(\"enemy\")\n .withApi(\"getSpawnableElements\")\n .withApi(\"registerEnemyAbilityDeck\")\n .withApi(\"registerEnemy\")\n .withApi(\"registerBossEnemy\")\n .withApi(\"getEnemies\")\n .withApi(\"getEnemy\")\n\nreturn EnemyApi\n\nend)\n__bundle_register(\"api.ComponentApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal ComponentApi = --[[---@type ComponentApi]] ApiConsumer(\"component\")\n .withApi(\"spawnElement\")\n .withApi(\"hasElement\")\n .withApi(\"registerOverlay\")\n .withApi(\"getOverlays\")\n .withApi(\"registerSummon\")\n .withApi(\"getSummons\")\n .withApi(\"requestAssetUpdate\")\n\n\nreturn ComponentApi\n\nend)\n__bundle_register(\"api.ConditionApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal ConditionApi = --[[---@type ConditionApi]] ApiConsumer(\"condition\")\n .withApi(\"registerCondition\")\n .withApi(\"registerTracker\")\n .withApi(\"registerEffect\")\n .withApi(\"getConditions\")\n .withApi(\"getCondition\")\n .withApi(\"getClassTrackers\")\n .withApi(\"getClassTracker\")\n .withApi(\"getEffects\")\n .withApi(\"getEffect\")\n .withApi(\"getImmunities\")\n\nreturn ConditionApi\n\nend)\n__bundle_register(\"api.ClassApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal ClassApi = --[[---@type ClassApi]] ApiConsumer(\"class\")\n .withApi(\"registerClass\")\n .withApi(\"getClasses\")\n .withApi(\"getClass\")\n .withApi(\"getAbility\")\n .withApi(\"getAbilityForUnknownClass\")\n .withApi(\"getSpawnableElements\")\n\n--- Default HP progressions. Defines the maximum HP per level.\nClassApi.HpProgression = {\n Low = { 6, 7, 8, 9, 10, 11, 12, 13, 14 },\n Medium = { 8, 9, 11, 12, 14, 15, 17, 18, 20 },\n High = { 10, 12, 14, 16, 18, 20, 22, 24, 26 }\n}\n\nClassApi.AbilityType = {\n Move = \"move\",\n Attack = \"attack\",\n Range = \"range\",\n Shield = \"shield\",\n Push = \"push\",\n Pull = \"pull\",\n Pierce = \"pierce\",\n Retaliate = \"retaliate\",\n Heal = \"heal\",\n Target = \"target\",\n Teleport = \"teleport\",\n SummonMove = \"summMove\",\n SummonAttack = \"summAttack\",\n SummonRange = \"summRange\",\n SummonHp = \"summHp\",\n EnemyCondition = \"enemyCondition\",\n AllyCondition = \"allyCondition\",\n Hex = \"hex\",\n}\n\nClassApi.EnhancementShape = {\n Square = \"square\",\n Circle = \"circle\",\n Diamond = \"diamond\",\n DiamondPlus = \"diamondPlus\",\n Hex = \"hex\",\n}\n\nClassApi.PerkType = {\n IgnoreItem = \"I\",\n IgnoreScenario = \"S\",\n}\n\nreturn ClassApi\n\nend)\n__bundle_register(\"ui.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ui.OptionsUi\")\r\nrequire(\"ui.GameSetupUi\")\r\nrequire(\"ui.TextHandler\")\r\nrequire(\"ui.Shop\")\r\nrequire(\"ui.StatEditor\")\r\n\r\nreturn {}\r\n\nend)\n__bundle_register(\"ui.StatEditor\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\nlocal ____exports = {}\nlocal ____ui = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.index\")\nlocal XmlUi = ____ui.XmlUi\nlocal ____Event = require(\"Event\")\nlocal registerHandler = ____Event.registerHandler\nlocal ____sebaestschjin_2Dtts_2Exmlui_2EXmlUiFactory = require(\"sebaestschjin-tts.xmlui.XmlUiFactory\")\nlocal wrapElement = ____sebaestschjin_2Dtts_2Exmlui_2EXmlUiFactory.wrapElement\nlocal ____EditRow = require(\"ui.component.EditRow\")\nlocal EditRow = ____EditRow.EditRow\n____exports.StatEditor = function()\n return XmlUi:createElement(\n \"panel\",\n {width = 100, height = 300, color = \"#FF0000\", active=false},\n XmlUi:createElement(EditRow, {offset = 0}),\n XmlUi:createElement(EditRow, {offset = 100})\n )\nend\nregisterHandler(\n \"gloom_onCreateGlobalUi\",\n function(ui)\n local element = wrapElement(____exports.StatEditor())\n ui.addChild(element)\n end\n)\n____exports.default = {}\nreturn ____exports\n\nend)\n__bundle_register(\"ui.component.EditRow\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\r\nlocal ____exports = {}\r\nlocal ____ui = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.index\")\r\nlocal XmlUi = ____ui.XmlUi\r\n____exports.EditRow = function(____bindingPattern0)\r\n local offset\r\n offset = ____bindingPattern0.offset\r\n return XmlUi:createElement(\r\n \"text\",\r\n {\r\n text = \"Hello World!\",\r\n fontSize = 30,\r\n offsetXY = \"0 \" .. tostring(offset)\r\n }\r\n )\r\nend\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"lua_modules.@typed-tabletop-simulator.ui.dist.index\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\r\nlocal ____exports = {}\r\ndo\r\n local ____export = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.button\")\r\n for ____exportKey, ____exportValue in pairs(____export) do\r\n if ____exportKey ~= \"default\" then\r\n ____exports[____exportKey] = ____exportValue\r\n end\r\n end\r\nend\r\ndo\r\n local ____export = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.panel\")\r\n for ____exportKey, ____exportValue in pairs(____export) do\r\n if ____exportKey ~= \"default\" then\r\n ____exports[____exportKey] = ____exportValue\r\n end\r\n end\r\nend\r\ndo\r\n local ____xmlUi = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.xmlUi\")\r\n local XmlUi = ____xmlUi.XmlUi\r\n ____exports.XmlUi = XmlUi\r\nend\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"lua_modules.@typed-tabletop-simulator.ui.dist.xmlUi\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\r\nlocal ____exports = {}\r\nlocal function createFragment(props, ...)\r\n local children = {...}\r\n return children\r\nend\r\nlocal function createElement(self, tag, props, ...)\r\n local children = {...}\r\n if type(tag) == \"function\" then\r\n return tag(props or ({}), ...)\r\n end\r\n local theChildren = children\r\n local value = nil\r\n if type(children[1]) == \"string\" then\r\n value = children[1]\r\n theChildren = nil\r\n elseif children[1] == nil then\r\n value = nil\r\n theChildren = nil\r\n end\r\n return {tag = tag, attributes = props or ({}), children = theChildren, value = value}\r\nend\r\n____exports.XmlUi = {Fragment = createFragment, createElement = createElement}\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.panel\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\r\nlocal ____exports = {}\r\nlocal resolveProps\r\nlocal ____xmlUi = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.xmlUi\")\r\nlocal XmlUi = ____xmlUi.XmlUi\r\nlocal ____base = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.base\")\r\nlocal resolveBaseProps = ____base.resolveBaseProps\r\n____exports.Panel = function(props, ...)\r\n return XmlUi:createElement(\r\n \"panel\",\r\n resolveProps(props),\r\n ...\r\n )\r\nend\r\nresolveProps = function(props)\r\n local resolvedProps = resolveBaseProps(props)\r\n return resolvedProps\r\nend\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.base\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ____lualib = require(\"lualib_bundle\")\r\nlocal Map = ____lualib.Map\r\nlocal __TS__New = ____lualib.__TS__New\r\nlocal Set = ____lualib.Set\r\nlocal __TS__ArrayJoin = ____lualib.__TS__ArrayJoin\r\nlocal __TS__Iterator = ____lualib.__TS__Iterator\r\nlocal __TS__TypeOf = ____lualib.__TS__TypeOf\r\nlocal __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes\r\nlocal __TS__ObjectEntries = ____lualib.__TS__ObjectEntries\r\nlocal ____exports = {}\r\nlocal ____uiHandler = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.uiHandler\")\r\nlocal getHandlerName = ____uiHandler.getHandlerName\r\nlocal mappings = __TS__New(Map)\r\nlocal handlerFields = __TS__New(Set, {\"onClick\", \"onMouseDown\"})\r\nlocal listFields = __TS__New(Set, {\"offsetXY\"})\r\nlocal colorFields = __TS__New(Set, {\"color\"})\r\nlocal function listToString(entries, separator)\r\n return __TS__ArrayJoin(entries, separator)\r\nend\r\nlocal function spaceList(entries)\r\n return listToString(entries, \" \")\r\nend\r\nlocal function colorToString(color)\r\n if color.r then\r\n local rgb = color\r\n return (((((\"rgb(\" .. tostring(rgb.r)) .. \",\") .. tostring(rgb.g)) .. \",\") .. tostring(rgb.b)) .. \")\"\r\n end\r\n return \":sad_emoji:\"\r\nend\r\nfor ____, field in __TS__Iterator(handlerFields) do\r\n mappings:set(field, getHandlerName)\r\nend\r\nfor ____, field in __TS__Iterator(listFields) do\r\n mappings:set(field, spaceList)\r\nend\r\nfor ____, field in __TS__Iterator(colorFields) do\r\n mappings:set(field, colorToString)\r\nend\r\n____exports.resolveBaseProps = function(props)\r\n local resolvedProps = {}\r\n for ____, ____value in ipairs(__TS__ObjectEntries(props)) do\r\n local field = ____value[1]\r\n local value = ____value[2]\r\n local typedField = field\r\n if mappings:has(typedField) then\r\n resolvedProps[typedField] = mappings:get(typedField)(value)\r\n else\r\n local valueType = __TS__TypeOf(value)\r\n if not __TS__ArrayIncludes({\"string\", \"number\", \"boolean\"}, valueType) then\r\n printToAll(((\"Unmapped type for field \" .. field) .. \": \") .. valueType, \"Red\")\r\n end\r\n resolvedProps[typedField] = value\r\n end\r\n end\r\n return resolvedProps\r\nend\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.uiHandler\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ____lualib = require(\"lualib_bundle\")\r\nlocal Map = ____lualib.Map\r\nlocal __TS__New = ____lualib.__TS__New\r\nlocal ____exports = {}\r\nlocal handlerFunctions = __TS__New(Map)\r\n--- Returns the name of a global UI handler function for the given local handler function.\r\n-- \r\n-- @param func The function to call\r\n____exports.getHandlerName = function(func)\r\n local handlerName = handlerFunctions:get(func)\r\n if not handlerName then\r\n handlerName = \"__uiHandler_\" .. tostring(handlerFunctions.size)\r\n _G[handlerName] = func\r\n handlerFunctions:set(func, handlerName)\r\n end\r\n return handlerName\r\nend\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"lualib_bundle\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal function __TS__ArrayIsArray(value)\r\n return type(value) == \"table\" and (value[1] ~= nil or next(value) == nil)\r\nend\r\n\r\nlocal function __TS__ArrayConcat(self, ...)\r\n local items = {...}\r\n local result = {}\r\n local len = 0\r\n for i = 1, #self do\r\n len = len + 1\r\n result[len] = self[i]\r\n end\r\n for i = 1, #items do\r\n local item = items[i]\r\n if __TS__ArrayIsArray(item) then\r\n for j = 1, #item do\r\n len = len + 1\r\n result[len] = item[j]\r\n end\r\n else\r\n len = len + 1\r\n result[len] = item\r\n end\r\n end\r\n return result\r\nend\r\n\r\nlocal __TS__Symbol, Symbol\r\ndo\r\n local symbolMetatable = {__tostring = function(self)\r\n return (\"Symbol(\" .. (self.description or \"\")) .. \")\"\r\n end}\r\n function __TS__Symbol(description)\r\n return setmetatable({description = description}, symbolMetatable)\r\n end\r\n Symbol = {\r\n iterator = __TS__Symbol(\"Symbol.iterator\"),\r\n hasInstance = __TS__Symbol(\"Symbol.hasInstance\"),\r\n species = __TS__Symbol(\"Symbol.species\"),\r\n toStringTag = __TS__Symbol(\"Symbol.toStringTag\")\r\n }\r\nend\r\n\r\nlocal function __TS__ArrayEntries(array)\r\n local key = 0\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = array[key + 1] == nil, value = {key, array[key + 1]}}\r\n key = key + 1\r\n return result\r\n end\r\n }\r\nend\r\n\r\nlocal function __TS__ArrayEvery(self, callbackfn, thisArg)\r\n for i = 1, #self do\r\n if not callbackfn(thisArg, self[i], i - 1, self) then\r\n return false\r\n end\r\n end\r\n return true\r\nend\r\n\r\nlocal function __TS__ArrayFilter(self, callbackfn, thisArg)\r\n local result = {}\r\n local len = 0\r\n for i = 1, #self do\r\n if callbackfn(thisArg, self[i], i - 1, self) then\r\n len = len + 1\r\n result[len] = self[i]\r\n end\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ArrayForEach(self, callbackFn, thisArg)\r\n for i = 1, #self do\r\n callbackFn(thisArg, self[i], i - 1, self)\r\n end\r\nend\r\n\r\nlocal function __TS__ArrayFind(self, predicate, thisArg)\r\n for i = 1, #self do\r\n local elem = self[i]\r\n if predicate(thisArg, elem, i - 1, self) then\r\n return elem\r\n end\r\n end\r\n return nil\r\nend\r\n\r\nlocal function __TS__ArrayFindIndex(self, callbackFn, thisArg)\r\n for i = 1, #self do\r\n if callbackFn(thisArg, self[i], i - 1, self) then\r\n return i - 1\r\n end\r\n end\r\n return -1\r\nend\r\n\r\nlocal __TS__Iterator\r\ndo\r\n local function iteratorGeneratorStep(self)\r\n local co = self.____coroutine\r\n local status, value = coroutine.resume(co)\r\n if not status then\r\n error(value, 0)\r\n end\r\n if coroutine.status(co) == \"dead\" then\r\n return\r\n end\r\n return true, value\r\n end\r\n local function iteratorIteratorStep(self)\r\n local result = self:next()\r\n if result.done then\r\n return\r\n end\r\n return true, result.value\r\n end\r\n local function iteratorStringStep(self, index)\r\n index = index + 1\r\n if index > #self then\r\n return\r\n end\r\n return index, string.sub(self, index, index)\r\n end\r\n function __TS__Iterator(iterable)\r\n if type(iterable) == \"string\" then\r\n return iteratorStringStep, iterable, 0\r\n elseif iterable.____coroutine ~= nil then\r\n return iteratorGeneratorStep, iterable\r\n elseif iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n return iteratorIteratorStep, iterator\r\n else\r\n return ipairs(iterable)\r\n end\r\n end\r\nend\r\n\r\nlocal __TS__ArrayFrom\r\ndo\r\n local function arrayLikeStep(self, index)\r\n index = index + 1\r\n if index > self.length then\r\n return\r\n end\r\n return index, self[index]\r\n end\r\n local function arrayLikeIterator(arr)\r\n if type(arr.length) == \"number\" then\r\n return arrayLikeStep, arr, 0\r\n end\r\n return __TS__Iterator(arr)\r\n end\r\n function __TS__ArrayFrom(arrayLike, mapFn, thisArg)\r\n local result = {}\r\n if mapFn == nil then\r\n for ____, v in arrayLikeIterator(arrayLike) do\r\n result[#result + 1] = v\r\n end\r\n else\r\n for i, v in arrayLikeIterator(arrayLike) do\r\n result[#result + 1] = mapFn(thisArg, v, i - 1)\r\n end\r\n end\r\n return result\r\n end\r\nend\r\n\r\nlocal function __TS__ArrayIncludes(self, searchElement, fromIndex)\r\n if fromIndex == nil then\r\n fromIndex = 0\r\n end\r\n local len = #self\r\n local k = fromIndex\r\n if fromIndex < 0 then\r\n k = len + fromIndex\r\n end\r\n if k < 0 then\r\n k = 0\r\n end\r\n for i = k + 1, len do\r\n if self[i] == searchElement then\r\n return true\r\n end\r\n end\r\n return false\r\nend\r\n\r\nlocal function __TS__ArrayIndexOf(self, searchElement, fromIndex)\r\n if fromIndex == nil then\r\n fromIndex = 0\r\n end\r\n local len = #self\r\n if len == 0 then\r\n return -1\r\n end\r\n if fromIndex >= len then\r\n return -1\r\n end\r\n if fromIndex < 0 then\r\n fromIndex = len + fromIndex\r\n if fromIndex < 0 then\r\n fromIndex = 0\r\n end\r\n end\r\n for i = fromIndex + 1, len do\r\n if self[i] == searchElement then\r\n return i - 1\r\n end\r\n end\r\n return -1\r\nend\r\n\r\nlocal function __TS__ArrayJoin(self, separator)\r\n if separator == nil then\r\n separator = \",\"\r\n end\r\n local parts = {}\r\n for i = 1, #self do\r\n parts[i] = tostring(self[i])\r\n end\r\n return table.concat(parts, separator)\r\nend\r\n\r\nlocal function __TS__ArrayMap(self, callbackfn, thisArg)\r\n local result = {}\r\n for i = 1, #self do\r\n result[i] = callbackfn(thisArg, self[i], i - 1, self)\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ArrayPush(self, ...)\r\n local items = {...}\r\n local len = #self\r\n for i = 1, #items do\r\n len = len + 1\r\n self[len] = items[i]\r\n end\r\n return len\r\nend\r\n\r\nlocal function __TS__ArrayPushArray(self, items)\r\n local len = #self\r\n for i = 1, #items do\r\n len = len + 1\r\n self[len] = items[i]\r\n end\r\n return len\r\nend\r\n\r\nlocal function __TS__CountVarargs(...)\r\n return select(\"#\", ...)\r\nend\r\n\r\nlocal function __TS__ArrayReduce(self, callbackFn, ...)\r\n local len = #self\r\n local k = 0\r\n local accumulator = nil\r\n if __TS__CountVarargs(...) ~= 0 then\r\n accumulator = ...\r\n elseif len > 0 then\r\n accumulator = self[1]\r\n k = 1\r\n else\r\n error(\"Reduce of empty array with no initial value\", 0)\r\n end\r\n for i = k + 1, len do\r\n accumulator = callbackFn(\r\n nil,\r\n accumulator,\r\n self[i],\r\n i - 1,\r\n self\r\n )\r\n end\r\n return accumulator\r\nend\r\n\r\nlocal function __TS__ArrayReduceRight(self, callbackFn, ...)\r\n local len = #self\r\n local k = len - 1\r\n local accumulator = nil\r\n if __TS__CountVarargs(...) ~= 0 then\r\n accumulator = ...\r\n elseif len > 0 then\r\n accumulator = self[k + 1]\r\n k = k - 1\r\n else\r\n error(\"Reduce of empty array with no initial value\", 0)\r\n end\r\n for i = k + 1, 1, -1 do\r\n accumulator = callbackFn(\r\n nil,\r\n accumulator,\r\n self[i],\r\n i - 1,\r\n self\r\n )\r\n end\r\n return accumulator\r\nend\r\n\r\nlocal function __TS__ArrayReverse(self)\r\n local i = 1\r\n local j = #self\r\n while i < j do\r\n local temp = self[j]\r\n self[j] = self[i]\r\n self[i] = temp\r\n i = i + 1\r\n j = j - 1\r\n end\r\n return self\r\nend\r\n\r\nlocal function __TS__ArrayUnshift(self, ...)\r\n local items = {...}\r\n local numItemsToInsert = #items\r\n if numItemsToInsert == 0 then\r\n return #self\r\n end\r\n for i = #self, 1, -1 do\r\n self[i + numItemsToInsert] = self[i]\r\n end\r\n for i = 1, numItemsToInsert do\r\n self[i] = items[i]\r\n end\r\n return #self\r\nend\r\n\r\nlocal function __TS__ArraySort(self, compareFn)\r\n if compareFn ~= nil then\r\n table.sort(\r\n self,\r\n function(a, b) return compareFn(nil, a, b) < 0 end\r\n )\r\n else\r\n table.sort(self)\r\n end\r\n return self\r\nend\r\n\r\nlocal function __TS__ArraySlice(self, first, last)\r\n local len = #self\r\n local ____first_0 = first\r\n if ____first_0 == nil then\r\n ____first_0 = 0\r\n end\r\n first = ____first_0\r\n if first < 0 then\r\n first = len + first\r\n if first < 0 then\r\n first = 0\r\n end\r\n else\r\n if first > len then\r\n first = len\r\n end\r\n end\r\n local ____last_1 = last\r\n if ____last_1 == nil then\r\n ____last_1 = len\r\n end\r\n last = ____last_1\r\n if last < 0 then\r\n last = len + last\r\n if last < 0 then\r\n last = 0\r\n end\r\n else\r\n if last > len then\r\n last = len\r\n end\r\n end\r\n local out = {}\r\n first = first + 1\r\n last = last + 1\r\n local n = 1\r\n while first < last do\r\n out[n] = self[first]\r\n first = first + 1\r\n n = n + 1\r\n end\r\n return out\r\nend\r\n\r\nlocal function __TS__ArraySome(self, callbackfn, thisArg)\r\n for i = 1, #self do\r\n if callbackfn(thisArg, self[i], i - 1, self) then\r\n return true\r\n end\r\n end\r\n return false\r\nend\r\n\r\nlocal function __TS__ArraySplice(self, ...)\r\n local args = {...}\r\n local len = #self\r\n local actualArgumentCount = __TS__CountVarargs(...)\r\n local start = args[1]\r\n local deleteCount = args[2]\r\n if start < 0 then\r\n start = len + start\r\n if start < 0 then\r\n start = 0\r\n end\r\n elseif start > len then\r\n start = len\r\n end\r\n local itemCount = actualArgumentCount - 2\r\n if itemCount < 0 then\r\n itemCount = 0\r\n end\r\n local actualDeleteCount\r\n if actualArgumentCount == 0 then\r\n actualDeleteCount = 0\r\n elseif actualArgumentCount == 1 then\r\n actualDeleteCount = len - start\r\n else\r\n local ____deleteCount_0 = deleteCount\r\n if ____deleteCount_0 == nil then\r\n ____deleteCount_0 = 0\r\n end\r\n actualDeleteCount = ____deleteCount_0\r\n if actualDeleteCount < 0 then\r\n actualDeleteCount = 0\r\n end\r\n if actualDeleteCount > len - start then\r\n actualDeleteCount = len - start\r\n end\r\n end\r\n local out = {}\r\n for k = 1, actualDeleteCount do\r\n local from = start + k\r\n if self[from] ~= nil then\r\n out[k] = self[from]\r\n end\r\n end\r\n if itemCount < actualDeleteCount then\r\n for k = start + 1, len - actualDeleteCount do\r\n local from = k + actualDeleteCount\r\n local to = k + itemCount\r\n if self[from] then\r\n self[to] = self[from]\r\n else\r\n self[to] = nil\r\n end\r\n end\r\n for k = len - actualDeleteCount + itemCount + 1, len do\r\n self[k] = nil\r\n end\r\n elseif itemCount > actualDeleteCount then\r\n for k = len - actualDeleteCount, start + 1, -1 do\r\n local from = k + actualDeleteCount\r\n local to = k + itemCount\r\n if self[from] then\r\n self[to] = self[from]\r\n else\r\n self[to] = nil\r\n end\r\n end\r\n end\r\n local j = start + 1\r\n for i = 3, actualArgumentCount do\r\n self[j] = args[i]\r\n j = j + 1\r\n end\r\n for k = #self, len - actualDeleteCount + itemCount + 1, -1 do\r\n self[k] = nil\r\n end\r\n return out\r\nend\r\n\r\nlocal function __TS__ArrayToObject(self)\r\n local object = {}\r\n for i = 1, #self do\r\n object[i - 1] = self[i]\r\n end\r\n return object\r\nend\r\n\r\nlocal function __TS__ArrayFlat(self, depth)\r\n if depth == nil then\r\n depth = 1\r\n end\r\n local result = {}\r\n local len = 0\r\n for i = 1, #self do\r\n local value = self[i]\r\n if depth > 0 and __TS__ArrayIsArray(value) then\r\n local toAdd\r\n if depth == 1 then\r\n toAdd = value\r\n else\r\n toAdd = __TS__ArrayFlat(value, depth - 1)\r\n end\r\n for j = 1, #toAdd do\r\n local val = toAdd[j]\r\n len = len + 1\r\n result[len] = val\r\n end\r\n else\r\n len = len + 1\r\n result[len] = value\r\n end\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ArrayFlatMap(self, callback, thisArg)\r\n local result = {}\r\n local len = 0\r\n for i = 1, #self do\r\n local value = callback(thisArg, self[i], i - 1, self)\r\n if __TS__ArrayIsArray(value) then\r\n for j = 1, #value do\r\n len = len + 1\r\n result[len] = value[j]\r\n end\r\n else\r\n len = len + 1\r\n result[len] = value\r\n end\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ArraySetLength(self, length)\r\n if length < 0 or length ~= length or length == math.huge or math.floor(length) ~= length then\r\n error(\r\n \"invalid array length: \" .. tostring(length),\r\n 0\r\n )\r\n end\r\n for i = length + 1, #self do\r\n self[i] = nil\r\n end\r\n return length\r\nend\r\n\r\nlocal function __TS__InstanceOf(obj, classTbl)\r\n if type(classTbl) ~= \"table\" then\r\n error(\"Right-hand side of 'instanceof' is not an object\", 0)\r\n end\r\n if classTbl[Symbol.hasInstance] ~= nil then\r\n return not not classTbl[Symbol.hasInstance](classTbl, obj)\r\n end\r\n if type(obj) == \"table\" then\r\n local luaClass = obj.constructor\r\n while luaClass ~= nil do\r\n if luaClass == classTbl then\r\n return true\r\n end\r\n luaClass = luaClass.____super\r\n end\r\n end\r\n return false\r\nend\r\n\r\nlocal function __TS__New(target, ...)\r\n local instance = setmetatable({}, target.prototype)\r\n instance:____constructor(...)\r\n return instance\r\nend\r\n\r\nlocal function __TS__Class(self)\r\n local c = {prototype = {}}\r\n c.prototype.__index = c.prototype\r\n c.prototype.constructor = c\r\n return c\r\nend\r\n\r\nlocal __TS__Unpack = table.unpack or unpack\r\n\r\nlocal function __TS__FunctionBind(fn, ...)\r\n local boundArgs = {...}\r\n return function(____, ...)\r\n local args = {...}\r\n __TS__ArrayUnshift(\r\n args,\r\n __TS__Unpack(boundArgs)\r\n )\r\n return fn(__TS__Unpack(args))\r\n end\r\nend\r\n\r\nlocal __TS__Promise\r\ndo\r\n local function promiseDeferred(self)\r\n local resolve\r\n local reject\r\n local promise = __TS__New(\r\n __TS__Promise,\r\n function(____, res, rej)\r\n resolve = res\r\n reject = rej\r\n end\r\n )\r\n return {promise = promise, resolve = resolve, reject = reject}\r\n end\r\n local function isPromiseLike(self, thing)\r\n return __TS__InstanceOf(thing, __TS__Promise)\r\n end\r\n __TS__Promise = __TS__Class()\r\n __TS__Promise.name = \"__TS__Promise\"\r\n function __TS__Promise.prototype.____constructor(self, executor)\r\n self.state = 0\r\n self.fulfilledCallbacks = {}\r\n self.rejectedCallbacks = {}\r\n self.finallyCallbacks = {}\r\n do\r\n local function ____catch(e)\r\n self:reject(e)\r\n end\r\n local ____try, ____hasReturned = pcall(function()\r\n executor(\r\n nil,\r\n __TS__FunctionBind(self.resolve, self),\r\n __TS__FunctionBind(self.reject, self)\r\n )\r\n end)\r\n if not ____try then\r\n ____catch(____hasReturned)\r\n end\r\n end\r\n end\r\n function __TS__Promise.resolve(data)\r\n local promise = __TS__New(\r\n __TS__Promise,\r\n function()\r\n end\r\n )\r\n promise.state = 1\r\n promise.value = data\r\n return promise\r\n end\r\n function __TS__Promise.reject(reason)\r\n local promise = __TS__New(\r\n __TS__Promise,\r\n function()\r\n end\r\n )\r\n promise.state = 2\r\n promise.rejectionReason = reason\r\n return promise\r\n end\r\n __TS__Promise.prototype[\"then\"] = function(self, onFulfilled, onRejected)\r\n local ____promiseDeferred_result_0 = promiseDeferred(nil)\r\n local promise = ____promiseDeferred_result_0.promise\r\n local resolve = ____promiseDeferred_result_0.resolve\r\n local reject = ____promiseDeferred_result_0.reject\r\n local isFulfilled = self.state == 1\r\n local isRejected = self.state == 2\r\n if onFulfilled then\r\n local internalCallback = self:createPromiseResolvingCallback(onFulfilled, resolve, reject)\r\n local ____self_fulfilledCallbacks_1 = self.fulfilledCallbacks\r\n ____self_fulfilledCallbacks_1[#____self_fulfilledCallbacks_1 + 1] = internalCallback\r\n if isFulfilled then\r\n internalCallback(nil, self.value)\r\n end\r\n else\r\n local ____self_fulfilledCallbacks_2 = self.fulfilledCallbacks\r\n ____self_fulfilledCallbacks_2[#____self_fulfilledCallbacks_2 + 1] = function(____, v) return resolve(nil, v) end\r\n end\r\n if onRejected then\r\n local internalCallback = self:createPromiseResolvingCallback(onRejected, resolve, reject)\r\n local ____self_rejectedCallbacks_3 = self.rejectedCallbacks\r\n ____self_rejectedCallbacks_3[#____self_rejectedCallbacks_3 + 1] = internalCallback\r\n if isRejected then\r\n internalCallback(nil, self.rejectionReason)\r\n end\r\n else\r\n local ____self_rejectedCallbacks_4 = self.rejectedCallbacks\r\n ____self_rejectedCallbacks_4[#____self_rejectedCallbacks_4 + 1] = function(____, err) return reject(nil, err) end\r\n end\r\n if isFulfilled then\r\n resolve(nil, self.value)\r\n end\r\n if isRejected then\r\n reject(nil, self.rejectionReason)\r\n end\r\n return promise\r\n end\r\n function __TS__Promise.prototype.catch(self, onRejected)\r\n return self[\"then\"](self, nil, onRejected)\r\n end\r\n function __TS__Promise.prototype.finally(self, onFinally)\r\n if onFinally then\r\n local ____self_finallyCallbacks_5 = self.finallyCallbacks\r\n ____self_finallyCallbacks_5[#____self_finallyCallbacks_5 + 1] = onFinally\r\n if self.state ~= 0 then\r\n onFinally(nil)\r\n end\r\n end\r\n return self\r\n end\r\n function __TS__Promise.prototype.resolve(self, data)\r\n if __TS__InstanceOf(data, __TS__Promise) then\r\n data[\"then\"](\r\n data,\r\n function(____, v) return self:resolve(v) end,\r\n function(____, err) return self:reject(err) end\r\n )\r\n return\r\n end\r\n if self.state == 0 then\r\n self.state = 1\r\n self.value = data\r\n for ____, callback in ipairs(self.fulfilledCallbacks) do\r\n callback(nil, data)\r\n end\r\n for ____, callback in ipairs(self.finallyCallbacks) do\r\n callback(nil)\r\n end\r\n end\r\n end\r\n function __TS__Promise.prototype.reject(self, reason)\r\n if self.state == 0 then\r\n self.state = 2\r\n self.rejectionReason = reason\r\n for ____, callback in ipairs(self.rejectedCallbacks) do\r\n callback(nil, reason)\r\n end\r\n for ____, callback in ipairs(self.finallyCallbacks) do\r\n callback(nil)\r\n end\r\n end\r\n end\r\n function __TS__Promise.prototype.createPromiseResolvingCallback(self, f, resolve, reject)\r\n return function(____, value)\r\n do\r\n local function ____catch(e)\r\n reject(nil, e)\r\n end\r\n local ____try, ____hasReturned = pcall(function()\r\n self:handleCallbackData(\r\n f(nil, value),\r\n resolve,\r\n reject\r\n )\r\n end)\r\n if not ____try then\r\n ____catch(____hasReturned)\r\n end\r\n end\r\n end\r\n end\r\n function __TS__Promise.prototype.handleCallbackData(self, data, resolve, reject)\r\n if isPromiseLike(nil, data) then\r\n local nextpromise = data\r\n if nextpromise.state == 1 then\r\n resolve(nil, nextpromise.value)\r\n elseif nextpromise.state == 2 then\r\n reject(nil, nextpromise.rejectionReason)\r\n else\r\n data[\"then\"](data, resolve, reject)\r\n end\r\n else\r\n resolve(nil, data)\r\n end\r\n end\r\nend\r\n\r\nlocal function __TS__AsyncAwaiter(generator)\r\n return __TS__New(\r\n __TS__Promise,\r\n function(____, resolve, reject)\r\n local adopt, fulfilled, step, resolved, asyncCoroutine\r\n function adopt(self, value)\r\n local ____temp_0\r\n if __TS__InstanceOf(value, __TS__Promise) then\r\n ____temp_0 = value\r\n else\r\n ____temp_0 = __TS__Promise.resolve(value)\r\n end\r\n return ____temp_0\r\n end\r\n function fulfilled(self, value)\r\n local success, resultOrError = coroutine.resume(asyncCoroutine, value)\r\n if success then\r\n step(nil, resultOrError)\r\n else\r\n reject(nil, resultOrError)\r\n end\r\n end\r\n function step(self, result)\r\n if resolved then\r\n return\r\n end\r\n if coroutine.status(asyncCoroutine) == \"dead\" then\r\n resolve(nil, result)\r\n else\r\n local ____self_1 = adopt(nil, result)\r\n ____self_1[\"then\"](____self_1, fulfilled, reject)\r\n end\r\n end\r\n resolved = false\r\n asyncCoroutine = coroutine.create(generator)\r\n local success, resultOrError = coroutine.resume(\r\n asyncCoroutine,\r\n function(____, v)\r\n resolved = true\r\n local ____self_2 = adopt(nil, v)\r\n ____self_2[\"then\"](____self_2, resolve, reject)\r\n end\r\n )\r\n if success then\r\n step(nil, resultOrError)\r\n else\r\n reject(nil, resultOrError)\r\n end\r\n end\r\n )\r\nend\r\nlocal function __TS__Await(thing)\r\n return coroutine.yield(thing)\r\nend\r\n\r\nlocal function __TS__ClassExtends(target, base)\r\n target.____super = base\r\n local staticMetatable = setmetatable({__index = base}, base)\r\n setmetatable(target, staticMetatable)\r\n local baseMetatable = getmetatable(base)\r\n if baseMetatable then\r\n if type(baseMetatable.__index) == \"function\" then\r\n staticMetatable.__index = baseMetatable.__index\r\n end\r\n if type(baseMetatable.__newindex) == \"function\" then\r\n staticMetatable.__newindex = baseMetatable.__newindex\r\n end\r\n end\r\n setmetatable(target.prototype, base.prototype)\r\n if type(base.prototype.__index) == \"function\" then\r\n target.prototype.__index = base.prototype.__index\r\n end\r\n if type(base.prototype.__newindex) == \"function\" then\r\n target.prototype.__newindex = base.prototype.__newindex\r\n end\r\n if type(base.prototype.__tostring) == \"function\" then\r\n target.prototype.__tostring = base.prototype.__tostring\r\n end\r\nend\r\n\r\nlocal function __TS__CloneDescriptor(____bindingPattern0)\r\n local value\r\n local writable\r\n local set\r\n local get\r\n local configurable\r\n local enumerable\r\n enumerable = ____bindingPattern0.enumerable\r\n configurable = ____bindingPattern0.configurable\r\n get = ____bindingPattern0.get\r\n set = ____bindingPattern0.set\r\n writable = ____bindingPattern0.writable\r\n value = ____bindingPattern0.value\r\n local descriptor = {enumerable = enumerable == true, configurable = configurable == true}\r\n local hasGetterOrSetter = get ~= nil or set ~= nil\r\n local hasValueOrWritableAttribute = writable ~= nil or value ~= nil\r\n if hasGetterOrSetter and hasValueOrWritableAttribute then\r\n error(\"Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.\", 0)\r\n end\r\n if get or set then\r\n descriptor.get = get\r\n descriptor.set = set\r\n else\r\n descriptor.value = value\r\n descriptor.writable = writable == true\r\n end\r\n return descriptor\r\nend\r\n\r\nlocal function __TS__ObjectAssign(target, ...)\r\n local sources = {...}\r\n for i = 1, #sources do\r\n local source = sources[i]\r\n for key in pairs(source) do\r\n target[key] = source[key]\r\n end\r\n end\r\n return target\r\nend\r\n\r\nlocal function __TS__ObjectGetOwnPropertyDescriptor(object, key)\r\n local metatable = getmetatable(object)\r\n if not metatable then\r\n return\r\n end\r\n if not rawget(metatable, \"_descriptors\") then\r\n return\r\n end\r\n return rawget(metatable, \"_descriptors\")[key]\r\nend\r\n\r\nlocal __TS__SetDescriptor\r\ndo\r\n local function descriptorIndex(self, key)\r\n local value = rawget(self, key)\r\n if value ~= nil then\r\n return value\r\n end\r\n local metatable = getmetatable(self)\r\n while metatable do\r\n local rawResult = rawget(metatable, key)\r\n if rawResult ~= nil then\r\n return rawResult\r\n end\r\n local descriptors = rawget(metatable, \"_descriptors\")\r\n if descriptors then\r\n local descriptor = descriptors[key]\r\n if descriptor then\r\n if descriptor.get then\r\n return descriptor.get(self)\r\n end\r\n return descriptor.value\r\n end\r\n end\r\n metatable = getmetatable(metatable)\r\n end\r\n end\r\n local function descriptorNewIndex(self, key, value)\r\n local metatable = getmetatable(self)\r\n while metatable do\r\n local descriptors = rawget(metatable, \"_descriptors\")\r\n if descriptors then\r\n local descriptor = descriptors[key]\r\n if descriptor then\r\n if descriptor.set then\r\n descriptor.set(self, value)\r\n else\r\n if descriptor.writable == false then\r\n error(\r\n (((\"Cannot assign to read only property '\" .. key) .. \"' of object '\") .. tostring(self)) .. \"'\",\r\n 0\r\n )\r\n end\r\n descriptor.value = value\r\n end\r\n return\r\n end\r\n end\r\n metatable = getmetatable(metatable)\r\n end\r\n rawset(self, key, value)\r\n end\r\n function __TS__SetDescriptor(target, key, desc, isPrototype)\r\n if isPrototype == nil then\r\n isPrototype = false\r\n end\r\n local ____isPrototype_0\r\n if isPrototype then\r\n ____isPrototype_0 = target\r\n else\r\n ____isPrototype_0 = getmetatable(target)\r\n end\r\n local metatable = ____isPrototype_0\r\n if not metatable then\r\n metatable = {}\r\n setmetatable(target, metatable)\r\n end\r\n local value = rawget(target, key)\r\n if value ~= nil then\r\n rawset(target, key, nil)\r\n end\r\n if not rawget(metatable, \"_descriptors\") then\r\n metatable._descriptors = {}\r\n end\r\n local descriptor = __TS__CloneDescriptor(desc)\r\n metatable._descriptors[key] = descriptor\r\n metatable.__index = descriptorIndex\r\n metatable.__newindex = descriptorNewIndex\r\n end\r\nend\r\n\r\nlocal function __TS__Decorate(decorators, target, key, desc)\r\n local result = target\r\n do\r\n local i = #decorators\r\n while i >= 0 do\r\n local decorator = decorators[i + 1]\r\n if decorator then\r\n local oldResult = result\r\n if key == nil then\r\n result = decorator(nil, result)\r\n elseif desc == true then\r\n local value = rawget(target, key)\r\n local descriptor = __TS__ObjectGetOwnPropertyDescriptor(target, key) or ({configurable = true, writable = true, value = value})\r\n local desc = decorator(nil, target, key, descriptor) or descriptor\r\n local isSimpleValue = desc.configurable == true and desc.writable == true and not desc.get and not desc.set\r\n if isSimpleValue then\r\n rawset(target, key, desc.value)\r\n else\r\n __TS__SetDescriptor(\r\n target,\r\n key,\r\n __TS__ObjectAssign({}, descriptor, desc)\r\n )\r\n end\r\n elseif desc == false then\r\n result = decorator(nil, target, key, desc)\r\n else\r\n result = decorator(nil, target, key)\r\n end\r\n result = result or oldResult\r\n end\r\n i = i - 1\r\n end\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__DecorateParam(paramIndex, decorator)\r\n return function(____, target, key) return decorator(nil, target, key, paramIndex) end\r\nend\r\n\r\nlocal function __TS__StringIncludes(self, searchString, position)\r\n if not position then\r\n position = 1\r\n else\r\n position = position + 1\r\n end\r\n local index = string.find(self, searchString, position, true)\r\n return index ~= nil\r\nend\r\n\r\nlocal Error, RangeError, ReferenceError, SyntaxError, TypeError, URIError\r\ndo\r\n local function getErrorStack(self, constructor)\r\n local level = 1\r\n while true do\r\n local info = debug.getinfo(level, \"f\")\r\n level = level + 1\r\n if not info then\r\n level = 1\r\n break\r\n elseif info.func == constructor then\r\n break\r\n end\r\n end\r\n if __TS__StringIncludes(_VERSION, \"Lua 5.0\") then\r\n return debug.traceback((\"[Level \" .. tostring(level)) .. \"]\")\r\n else\r\n return debug.traceback(nil, level)\r\n end\r\n end\r\n local function wrapErrorToString(self, getDescription)\r\n return function(self)\r\n local description = getDescription(self)\r\n local caller = debug.getinfo(3, \"f\")\r\n local isClassicLua = __TS__StringIncludes(_VERSION, \"Lua 5.0\") or _VERSION == \"Lua 5.1\"\r\n if isClassicLua or caller and caller.func ~= error then\r\n return description\r\n else\r\n return (tostring(description) .. \"\\n\") .. self.stack\r\n end\r\n end\r\n end\r\n local function initErrorClass(self, Type, name)\r\n Type.name = name\r\n return setmetatable(\r\n Type,\r\n {__call = function(____, _self, message) return __TS__New(Type, message) end}\r\n )\r\n end\r\n local ____initErrorClass_2 = initErrorClass\r\n local ____class_0 = __TS__Class()\r\n ____class_0.name = \"\"\r\n function ____class_0.prototype.____constructor(self, message)\r\n if message == nil then\r\n message = \"\"\r\n end\r\n self.message = message\r\n self.name = \"Error\"\r\n self.stack = getErrorStack(nil, self.constructor.new)\r\n local metatable = getmetatable(self)\r\n if not metatable.__errorToStringPatched then\r\n metatable.__errorToStringPatched = true\r\n metatable.__tostring = wrapErrorToString(nil, metatable.__tostring)\r\n end\r\n end\r\n function ____class_0.prototype.__tostring(self)\r\n local ____temp_1\r\n if self.message ~= \"\" then\r\n ____temp_1 = (self.name .. \": \") .. self.message\r\n else\r\n ____temp_1 = self.name\r\n end\r\n return ____temp_1\r\n end\r\n Error = ____initErrorClass_2(nil, ____class_0, \"Error\")\r\n local function createErrorClass(self, name)\r\n local ____initErrorClass_4 = initErrorClass\r\n local ____class_3 = __TS__Class()\r\n ____class_3.name = ____class_3.name\r\n __TS__ClassExtends(____class_3, Error)\r\n function ____class_3.prototype.____constructor(self, ...)\r\n ____class_3.____super.prototype.____constructor(self, ...)\r\n self.name = name\r\n end\r\n return ____initErrorClass_4(nil, ____class_3, name)\r\n end\r\n RangeError = createErrorClass(nil, \"RangeError\")\r\n ReferenceError = createErrorClass(nil, \"ReferenceError\")\r\n SyntaxError = createErrorClass(nil, \"SyntaxError\")\r\n TypeError = createErrorClass(nil, \"TypeError\")\r\n URIError = createErrorClass(nil, \"URIError\")\r\nend\r\n\r\nlocal function __TS__ObjectGetOwnPropertyDescriptors(object)\r\n local metatable = getmetatable(object)\r\n if not metatable then\r\n return {}\r\n end\r\n return rawget(metatable, \"_descriptors\") or ({})\r\nend\r\n\r\nlocal function __TS__Delete(target, key)\r\n local descriptors = __TS__ObjectGetOwnPropertyDescriptors(target)\r\n local descriptor = descriptors[key]\r\n if descriptor then\r\n if not descriptor.configurable then\r\n error(\r\n __TS__New(\r\n TypeError,\r\n (((\"Cannot delete property \" .. tostring(key)) .. \" of \") .. tostring(target)) .. \".\"\r\n ),\r\n 0\r\n )\r\n end\r\n descriptors[key] = nil\r\n return true\r\n end\r\n target[key] = nil\r\n return true\r\nend\r\n\r\nlocal function __TS__StringAccess(self, index)\r\n if index >= 0 and index < #self then\r\n return string.sub(self, index + 1, index + 1)\r\n end\r\nend\r\n\r\nlocal function __TS__DelegatedYield(iterable)\r\n if type(iterable) == \"string\" then\r\n for index = 0, #iterable - 1 do\r\n coroutine.yield(__TS__StringAccess(iterable, index))\r\n end\r\n elseif iterable.____coroutine ~= nil then\r\n local co = iterable.____coroutine\r\n while true do\r\n local status, value = coroutine.resume(co)\r\n if not status then\r\n error(value, 0)\r\n end\r\n if coroutine.status(co) == \"dead\" then\r\n return value\r\n else\r\n coroutine.yield(value)\r\n end\r\n end\r\n elseif iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n while true do\r\n local result = iterator:next()\r\n if result.done then\r\n return result.value\r\n else\r\n coroutine.yield(result.value)\r\n end\r\n end\r\n else\r\n for ____, value in ipairs(iterable) do\r\n coroutine.yield(value)\r\n end\r\n end\r\nend\r\n\r\nlocal __TS__Generator\r\ndo\r\n local function generatorIterator(self)\r\n return self\r\n end\r\n local function generatorNext(self, ...)\r\n local co = self.____coroutine\r\n if coroutine.status(co) == \"dead\" then\r\n return {done = true}\r\n end\r\n local status, value = coroutine.resume(co, ...)\r\n if not status then\r\n error(value, 0)\r\n end\r\n return {\r\n value = value,\r\n done = coroutine.status(co) == \"dead\"\r\n }\r\n end\r\n function __TS__Generator(fn)\r\n return function(...)\r\n local args = {...}\r\n local argsLength = __TS__CountVarargs(...)\r\n return {\r\n ____coroutine = coroutine.create(function() return fn(__TS__Unpack(args, 1, argsLength)) end),\r\n [Symbol.iterator] = generatorIterator,\r\n next = generatorNext\r\n }\r\n end\r\n end\r\nend\r\n\r\nlocal function __TS__InstanceOfObject(value)\r\n local valueType = type(value)\r\n return valueType == \"table\" or valueType == \"function\"\r\nend\r\n\r\nlocal function __TS__LuaIteratorSpread(self, state, firstKey)\r\n local results = {}\r\n local key, value = self(state, firstKey)\r\n while key do\r\n results[#results + 1] = {key, value}\r\n key, value = self(state, key)\r\n end\r\n return __TS__Unpack(results)\r\nend\r\n\r\nlocal Map\r\ndo\r\n Map = __TS__Class()\r\n Map.name = \"Map\"\r\n function Map.prototype.____constructor(self, entries)\r\n self[Symbol.toStringTag] = \"Map\"\r\n self.items = {}\r\n self.size = 0\r\n self.nextKey = {}\r\n self.previousKey = {}\r\n if entries == nil then\r\n return\r\n end\r\n local iterable = entries\r\n if iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n while true do\r\n local result = iterator:next()\r\n if result.done then\r\n break\r\n end\r\n local value = result.value\r\n self:set(value[1], value[2])\r\n end\r\n else\r\n local array = entries\r\n for ____, kvp in ipairs(array) do\r\n self:set(kvp[1], kvp[2])\r\n end\r\n end\r\n end\r\n function Map.prototype.clear(self)\r\n self.items = {}\r\n self.nextKey = {}\r\n self.previousKey = {}\r\n self.firstKey = nil\r\n self.lastKey = nil\r\n self.size = 0\r\n end\r\n function Map.prototype.delete(self, key)\r\n local contains = self:has(key)\r\n if contains then\r\n self.size = self.size - 1\r\n local next = self.nextKey[key]\r\n local previous = self.previousKey[key]\r\n if next and previous then\r\n self.nextKey[previous] = next\r\n self.previousKey[next] = previous\r\n elseif next then\r\n self.firstKey = next\r\n self.previousKey[next] = nil\r\n elseif previous then\r\n self.lastKey = previous\r\n self.nextKey[previous] = nil\r\n else\r\n self.firstKey = nil\r\n self.lastKey = nil\r\n end\r\n self.nextKey[key] = nil\r\n self.previousKey[key] = nil\r\n end\r\n self.items[key] = nil\r\n return contains\r\n end\r\n function Map.prototype.forEach(self, callback)\r\n for ____, key in __TS__Iterator(self:keys()) do\r\n callback(nil, self.items[key], key, self)\r\n end\r\n end\r\n function Map.prototype.get(self, key)\r\n return self.items[key]\r\n end\r\n function Map.prototype.has(self, key)\r\n return self.nextKey[key] ~= nil or self.lastKey == key\r\n end\r\n function Map.prototype.set(self, key, value)\r\n local isNewValue = not self:has(key)\r\n if isNewValue then\r\n self.size = self.size + 1\r\n end\r\n self.items[key] = value\r\n if self.firstKey == nil then\r\n self.firstKey = key\r\n self.lastKey = key\r\n elseif isNewValue then\r\n self.nextKey[self.lastKey] = key\r\n self.previousKey[key] = self.lastKey\r\n self.lastKey = key\r\n end\r\n return self\r\n end\r\n Map.prototype[Symbol.iterator] = function(self)\r\n return self:entries()\r\n end\r\n function Map.prototype.entries(self)\r\n local items = self.items\r\n local nextKey = self.nextKey\r\n local key = self.firstKey\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = not key, value = {key, items[key]}}\r\n key = nextKey[key]\r\n return result\r\n end\r\n }\r\n end\r\n function Map.prototype.keys(self)\r\n local nextKey = self.nextKey\r\n local key = self.firstKey\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = not key, value = key}\r\n key = nextKey[key]\r\n return result\r\n end\r\n }\r\n end\r\n function Map.prototype.values(self)\r\n local items = self.items\r\n local nextKey = self.nextKey\r\n local key = self.firstKey\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = not key, value = items[key]}\r\n key = nextKey[key]\r\n return result\r\n end\r\n }\r\n end\r\n Map[Symbol.species] = Map\r\nend\r\n\r\nlocal __TS__Match = string.match\r\n\r\nlocal __TS__MathAtan2 = math.atan2 or math.atan\r\n\r\nlocal __TS__MathModf = math.modf\r\n\r\nlocal function __TS__MathSign(val)\r\n if val > 0 then\r\n return 1\r\n elseif val < 0 then\r\n return -1\r\n end\r\n return 0\r\nend\r\n\r\nlocal function __TS__Modulo50(a, b)\r\n return a - math.floor(a / b) * b\r\nend\r\n\r\nlocal function __TS__Number(value)\r\n local valueType = type(value)\r\n if valueType == \"number\" then\r\n return value\r\n elseif valueType == \"string\" then\r\n local numberValue = tonumber(value)\r\n if numberValue then\r\n return numberValue\r\n end\r\n if value == \"Infinity\" then\r\n return math.huge\r\n end\r\n if value == \"-Infinity\" then\r\n return -math.huge\r\n end\r\n local stringWithoutSpaces = string.gsub(value, \"%s\", \"\")\r\n if stringWithoutSpaces == \"\" then\r\n return 0\r\n end\r\n return 0 / 0\r\n elseif valueType == \"boolean\" then\r\n return value and 1 or 0\r\n else\r\n return 0 / 0\r\n end\r\nend\r\n\r\nlocal function __TS__NumberIsFinite(value)\r\n return type(value) == \"number\" and value == value and value ~= math.huge and value ~= -math.huge\r\nend\r\n\r\nlocal function __TS__NumberIsNaN(value)\r\n return value ~= value\r\nend\r\n\r\nlocal __TS__NumberToString\r\ndo\r\n local radixChars = \"0123456789abcdefghijklmnopqrstuvwxyz\"\r\n function __TS__NumberToString(self, radix)\r\n if radix == nil or radix == 10 or self == math.huge or self == -math.huge or self ~= self then\r\n return tostring(self)\r\n end\r\n radix = math.floor(radix)\r\n if radix < 2 or radix > 36 then\r\n error(\"toString() radix argument must be between 2 and 36\", 0)\r\n end\r\n local integer, fraction = __TS__MathModf(math.abs(self))\r\n local result = \"\"\r\n if radix == 8 then\r\n result = string.format(\"%o\", integer)\r\n elseif radix == 16 then\r\n result = string.format(\"%x\", integer)\r\n else\r\n repeat\r\n do\r\n result = __TS__StringAccess(radixChars, integer % radix) .. result\r\n integer = math.floor(integer / radix)\r\n end\r\n until not (integer ~= 0)\r\n end\r\n if fraction ~= 0 then\r\n result = result .. \".\"\r\n local delta = 1e-16\r\n repeat\r\n do\r\n fraction = fraction * radix\r\n delta = delta * radix\r\n local digit = math.floor(fraction)\r\n result = result .. __TS__StringAccess(radixChars, digit)\r\n fraction = fraction - digit\r\n end\r\n until not (fraction >= delta)\r\n end\r\n if self < 0 then\r\n result = \"-\" .. result\r\n end\r\n return result\r\n end\r\nend\r\n\r\nlocal function __TS__ObjectDefineProperty(target, key, desc)\r\n local ____temp_0\r\n if type(key) == \"number\" then\r\n ____temp_0 = key + 1\r\n else\r\n ____temp_0 = key\r\n end\r\n local luaKey = ____temp_0\r\n local value = rawget(target, luaKey)\r\n local hasGetterOrSetter = desc.get ~= nil or desc.set ~= nil\r\n local descriptor\r\n if hasGetterOrSetter then\r\n if value ~= nil then\r\n error(\r\n \"Cannot redefine property: \" .. tostring(key),\r\n 0\r\n )\r\n end\r\n descriptor = desc\r\n else\r\n local valueExists = value ~= nil\r\n local ____desc_set_5 = desc.set\r\n local ____desc_get_6 = desc.get\r\n local ____temp_1\r\n if desc.configurable ~= nil then\r\n ____temp_1 = desc.configurable\r\n else\r\n ____temp_1 = valueExists\r\n end\r\n local ____temp_2\r\n if desc.enumerable ~= nil then\r\n ____temp_2 = desc.enumerable\r\n else\r\n ____temp_2 = valueExists\r\n end\r\n local ____temp_3\r\n if desc.writable ~= nil then\r\n ____temp_3 = desc.writable\r\n else\r\n ____temp_3 = valueExists\r\n end\r\n local ____temp_4\r\n if desc.value ~= nil then\r\n ____temp_4 = desc.value\r\n else\r\n ____temp_4 = value\r\n end\r\n descriptor = {\r\n set = ____desc_set_5,\r\n get = ____desc_get_6,\r\n configurable = ____temp_1,\r\n enumerable = ____temp_2,\r\n writable = ____temp_3,\r\n value = ____temp_4\r\n }\r\n end\r\n __TS__SetDescriptor(target, luaKey, descriptor)\r\n return target\r\nend\r\n\r\nlocal function __TS__ObjectEntries(obj)\r\n local result = {}\r\n local len = 0\r\n for key in pairs(obj) do\r\n len = len + 1\r\n result[len] = {key, obj[key]}\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ObjectFromEntries(entries)\r\n local obj = {}\r\n local iterable = entries\r\n if iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n while true do\r\n local result = iterator:next()\r\n if result.done then\r\n break\r\n end\r\n local value = result.value\r\n obj[value[1]] = value[2]\r\n end\r\n else\r\n for ____, entry in ipairs(entries) do\r\n obj[entry[1]] = entry[2]\r\n end\r\n end\r\n return obj\r\nend\r\n\r\nlocal function __TS__ObjectKeys(obj)\r\n local result = {}\r\n local len = 0\r\n for key in pairs(obj) do\r\n len = len + 1\r\n result[len] = key\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ObjectRest(target, usedProperties)\r\n local result = {}\r\n for property in pairs(target) do\r\n if not usedProperties[property] then\r\n result[property] = target[property]\r\n end\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ObjectValues(obj)\r\n local result = {}\r\n local len = 0\r\n for key in pairs(obj) do\r\n len = len + 1\r\n result[len] = obj[key]\r\n end\r\n return result\r\nend\r\n\r\nlocal function __TS__ParseFloat(numberString)\r\n local infinityMatch = __TS__Match(numberString, \"^%s*(-?Infinity)\")\r\n if infinityMatch then\r\n local ____temp_0\r\n if __TS__StringAccess(infinityMatch, 0) == \"-\" then\r\n ____temp_0 = -math.huge\r\n else\r\n ____temp_0 = math.huge\r\n end\r\n return ____temp_0\r\n end\r\n local number = tonumber(__TS__Match(numberString, \"^%s*(-?%d+%.?%d*)\"))\r\n local ____number_1 = number\r\n if ____number_1 == nil then\r\n ____number_1 = 0 / 0\r\n end\r\n return ____number_1\r\nend\r\n\r\nlocal function __TS__StringSubstr(self, from, length)\r\n if from ~= from then\r\n from = 0\r\n end\r\n if length ~= nil then\r\n if length ~= length or length <= 0 then\r\n return \"\"\r\n end\r\n length = length + from\r\n end\r\n if from >= 0 then\r\n from = from + 1\r\n end\r\n return string.sub(self, from, length)\r\nend\r\n\r\nlocal function __TS__StringSubstring(self, start, ____end)\r\n if ____end ~= ____end then\r\n ____end = 0\r\n end\r\n if ____end ~= nil and start > ____end then\r\n start, ____end = ____end, start\r\n end\r\n if start >= 0 then\r\n start = start + 1\r\n else\r\n start = 1\r\n end\r\n if ____end ~= nil and ____end < 0 then\r\n ____end = 0\r\n end\r\n return string.sub(self, start, ____end)\r\nend\r\n\r\nlocal __TS__ParseInt\r\ndo\r\n local parseIntBasePattern = \"0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTvVwWxXyYzZ\"\r\n function __TS__ParseInt(numberString, base)\r\n if base == nil then\r\n base = 10\r\n local hexMatch = __TS__Match(numberString, \"^%s*-?0[xX]\")\r\n if hexMatch then\r\n base = 16\r\n local ____TS__Match_result__0_0\r\n if __TS__Match(hexMatch, \"-\") then\r\n ____TS__Match_result__0_0 = \"-\" .. __TS__StringSubstr(numberString, #hexMatch)\r\n else\r\n ____TS__Match_result__0_0 = __TS__StringSubstr(numberString, #hexMatch)\r\n end\r\n numberString = ____TS__Match_result__0_0\r\n end\r\n end\r\n if base < 2 or base > 36 then\r\n return 0 / 0\r\n end\r\n local ____temp_1\r\n if base <= 10 then\r\n ____temp_1 = __TS__StringSubstring(parseIntBasePattern, 0, base)\r\n else\r\n ____temp_1 = __TS__StringSubstr(parseIntBasePattern, 0, 10 + 2 * (base - 10))\r\n end\r\n local allowedDigits = ____temp_1\r\n local pattern = (\"^%s*(-?[\" .. allowedDigits) .. \"]*)\"\r\n local number = tonumber(\r\n __TS__Match(numberString, pattern),\r\n base\r\n )\r\n if number == nil then\r\n return 0 / 0\r\n end\r\n if number >= 0 then\r\n return math.floor(number)\r\n else\r\n return math.ceil(number)\r\n end\r\n end\r\nend\r\n\r\nlocal function __TS__PromiseAll(iterable)\r\n local results = {}\r\n local toResolve = {}\r\n local numToResolve = 0\r\n local i = 0\r\n for ____, item in __TS__Iterator(iterable) do\r\n if __TS__InstanceOf(item, __TS__Promise) then\r\n if item.state == 1 then\r\n results[i + 1] = item.value\r\n elseif item.state == 2 then\r\n return __TS__Promise.reject(item.rejectionReason)\r\n else\r\n numToResolve = numToResolve + 1\r\n toResolve[i] = item\r\n end\r\n else\r\n results[i + 1] = item\r\n end\r\n i = i + 1\r\n end\r\n if numToResolve == 0 then\r\n return __TS__Promise.resolve(results)\r\n end\r\n return __TS__New(\r\n __TS__Promise,\r\n function(____, resolve, reject)\r\n for index, promise in pairs(toResolve) do\r\n promise[\"then\"](\r\n promise,\r\n function(____, data)\r\n results[index + 1] = data\r\n numToResolve = numToResolve - 1\r\n if numToResolve == 0 then\r\n resolve(nil, results)\r\n end\r\n end,\r\n function(____, reason)\r\n reject(nil, reason)\r\n end\r\n )\r\n end\r\n end\r\n )\r\nend\r\n\r\nlocal function __TS__PromiseAllSettled(iterable)\r\n local results = {}\r\n local toResolve = {}\r\n local numToResolve = 0\r\n local i = 0\r\n for ____, item in __TS__Iterator(iterable) do\r\n if __TS__InstanceOf(item, __TS__Promise) then\r\n if item.state == 1 then\r\n results[i + 1] = {status = \"fulfilled\", value = item.value}\r\n elseif item.state == 2 then\r\n results[i + 1] = {status = \"rejected\", reason = item.rejectionReason}\r\n else\r\n numToResolve = numToResolve + 1\r\n toResolve[i] = item\r\n end\r\n else\r\n results[i + 1] = {status = \"fulfilled\", value = item}\r\n end\r\n i = i + 1\r\n end\r\n if numToResolve == 0 then\r\n return __TS__Promise.resolve(results)\r\n end\r\n return __TS__New(\r\n __TS__Promise,\r\n function(____, resolve)\r\n for index, promise in pairs(toResolve) do\r\n promise[\"then\"](\r\n promise,\r\n function(____, data)\r\n results[index + 1] = {status = \"fulfilled\", value = data}\r\n numToResolve = numToResolve - 1\r\n if numToResolve == 0 then\r\n resolve(nil, results)\r\n end\r\n end,\r\n function(____, reason)\r\n results[index + 1] = {status = \"rejected\", reason = reason}\r\n numToResolve = numToResolve - 1\r\n if numToResolve == 0 then\r\n resolve(nil, results)\r\n end\r\n end\r\n )\r\n end\r\n end\r\n )\r\nend\r\n\r\nlocal function __TS__PromiseAny(iterable)\r\n local rejections = {}\r\n local pending = {}\r\n for ____, item in __TS__Iterator(iterable) do\r\n if __TS__InstanceOf(item, __TS__Promise) then\r\n if item.state == 1 then\r\n return __TS__Promise.resolve(item.value)\r\n elseif item.state == 2 then\r\n rejections[#rejections + 1] = item.rejectionReason\r\n else\r\n pending[#pending + 1] = item\r\n end\r\n else\r\n return __TS__Promise.resolve(item)\r\n end\r\n end\r\n if #pending == 0 then\r\n return __TS__Promise.reject(\"No promises to resolve with .any()\")\r\n end\r\n local numResolved = 0\r\n return __TS__New(\r\n __TS__Promise,\r\n function(____, resolve, reject)\r\n for ____, promise in ipairs(pending) do\r\n promise[\"then\"](\r\n promise,\r\n function(____, data)\r\n resolve(nil, data)\r\n end,\r\n function(____, reason)\r\n rejections[#rejections + 1] = reason\r\n numResolved = numResolved + 1\r\n if numResolved == #pending then\r\n reject(nil, {name = \"AggregateError\", message = \"All Promises rejected\", errors = rejections})\r\n end\r\n end\r\n )\r\n end\r\n end\r\n )\r\nend\r\n\r\nlocal function __TS__PromiseRace(iterable)\r\n local pending = {}\r\n for ____, item in __TS__Iterator(iterable) do\r\n if __TS__InstanceOf(item, __TS__Promise) then\r\n if item.state == 1 then\r\n return __TS__Promise.resolve(item.value)\r\n elseif item.state == 2 then\r\n return __TS__Promise.reject(item.rejectionReason)\r\n else\r\n pending[#pending + 1] = item\r\n end\r\n else\r\n return __TS__Promise.resolve(item)\r\n end\r\n end\r\n return __TS__New(\r\n __TS__Promise,\r\n function(____, resolve, reject)\r\n for ____, promise in ipairs(pending) do\r\n promise[\"then\"](\r\n promise,\r\n function(____, value) return resolve(nil, value) end,\r\n function(____, reason) return reject(nil, reason) end\r\n )\r\n end\r\n end\r\n )\r\nend\r\n\r\nlocal Set\r\ndo\r\n Set = __TS__Class()\r\n Set.name = \"Set\"\r\n function Set.prototype.____constructor(self, values)\r\n self[Symbol.toStringTag] = \"Set\"\r\n self.size = 0\r\n self.nextKey = {}\r\n self.previousKey = {}\r\n if values == nil then\r\n return\r\n end\r\n local iterable = values\r\n if iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n while true do\r\n local result = iterator:next()\r\n if result.done then\r\n break\r\n end\r\n self:add(result.value)\r\n end\r\n else\r\n local array = values\r\n for ____, value in ipairs(array) do\r\n self:add(value)\r\n end\r\n end\r\n end\r\n function Set.prototype.add(self, value)\r\n local isNewValue = not self:has(value)\r\n if isNewValue then\r\n self.size = self.size + 1\r\n end\r\n if self.firstKey == nil then\r\n self.firstKey = value\r\n self.lastKey = value\r\n elseif isNewValue then\r\n self.nextKey[self.lastKey] = value\r\n self.previousKey[value] = self.lastKey\r\n self.lastKey = value\r\n end\r\n return self\r\n end\r\n function Set.prototype.clear(self)\r\n self.nextKey = {}\r\n self.previousKey = {}\r\n self.firstKey = nil\r\n self.lastKey = nil\r\n self.size = 0\r\n end\r\n function Set.prototype.delete(self, value)\r\n local contains = self:has(value)\r\n if contains then\r\n self.size = self.size - 1\r\n local next = self.nextKey[value]\r\n local previous = self.previousKey[value]\r\n if next and previous then\r\n self.nextKey[previous] = next\r\n self.previousKey[next] = previous\r\n elseif next then\r\n self.firstKey = next\r\n self.previousKey[next] = nil\r\n elseif previous then\r\n self.lastKey = previous\r\n self.nextKey[previous] = nil\r\n else\r\n self.firstKey = nil\r\n self.lastKey = nil\r\n end\r\n self.nextKey[value] = nil\r\n self.previousKey[value] = nil\r\n end\r\n return contains\r\n end\r\n function Set.prototype.forEach(self, callback)\r\n for ____, key in __TS__Iterator(self:keys()) do\r\n callback(nil, key, key, self)\r\n end\r\n end\r\n function Set.prototype.has(self, value)\r\n return self.nextKey[value] ~= nil or self.lastKey == value\r\n end\r\n Set.prototype[Symbol.iterator] = function(self)\r\n return self:values()\r\n end\r\n function Set.prototype.entries(self)\r\n local nextKey = self.nextKey\r\n local key = self.firstKey\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = not key, value = {key, key}}\r\n key = nextKey[key]\r\n return result\r\n end\r\n }\r\n end\r\n function Set.prototype.keys(self)\r\n local nextKey = self.nextKey\r\n local key = self.firstKey\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = not key, value = key}\r\n key = nextKey[key]\r\n return result\r\n end\r\n }\r\n end\r\n function Set.prototype.values(self)\r\n local nextKey = self.nextKey\r\n local key = self.firstKey\r\n return {\r\n [Symbol.iterator] = function(self)\r\n return self\r\n end,\r\n next = function(self)\r\n local result = {done = not key, value = key}\r\n key = nextKey[key]\r\n return result\r\n end\r\n }\r\n end\r\n Set[Symbol.species] = Set\r\nend\r\n\r\nlocal function __TS__SparseArrayNew(...)\r\n local sparseArray = {...}\r\n sparseArray.sparseLength = __TS__CountVarargs(...)\r\n return sparseArray\r\nend\r\n\r\nlocal function __TS__SparseArrayPush(sparseArray, ...)\r\n local args = {...}\r\n local argsLen = __TS__CountVarargs(...)\r\n local listLen = sparseArray.sparseLength\r\n for i = 1, argsLen do\r\n sparseArray[listLen + i] = args[i]\r\n end\r\n sparseArray.sparseLength = listLen + argsLen\r\nend\r\n\r\nlocal function __TS__SparseArraySpread(sparseArray)\r\n local ____unpack_0 = unpack\r\n if ____unpack_0 == nil then\r\n ____unpack_0 = table.unpack\r\n end\r\n local _unpack = ____unpack_0\r\n return _unpack(sparseArray, 1, sparseArray.sparseLength)\r\nend\r\n\r\nlocal WeakMap\r\ndo\r\n WeakMap = __TS__Class()\r\n WeakMap.name = \"WeakMap\"\r\n function WeakMap.prototype.____constructor(self, entries)\r\n self[Symbol.toStringTag] = \"WeakMap\"\r\n self.items = {}\r\n setmetatable(self.items, {__mode = \"k\"})\r\n if entries == nil then\r\n return\r\n end\r\n local iterable = entries\r\n if iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n while true do\r\n local result = iterator:next()\r\n if result.done then\r\n break\r\n end\r\n local value = result.value\r\n self.items[value[1]] = value[2]\r\n end\r\n else\r\n for ____, kvp in ipairs(entries) do\r\n self.items[kvp[1]] = kvp[2]\r\n end\r\n end\r\n end\r\n function WeakMap.prototype.delete(self, key)\r\n local contains = self:has(key)\r\n self.items[key] = nil\r\n return contains\r\n end\r\n function WeakMap.prototype.get(self, key)\r\n return self.items[key]\r\n end\r\n function WeakMap.prototype.has(self, key)\r\n return self.items[key] ~= nil\r\n end\r\n function WeakMap.prototype.set(self, key, value)\r\n self.items[key] = value\r\n return self\r\n end\r\n WeakMap[Symbol.species] = WeakMap\r\nend\r\n\r\nlocal WeakSet\r\ndo\r\n WeakSet = __TS__Class()\r\n WeakSet.name = \"WeakSet\"\r\n function WeakSet.prototype.____constructor(self, values)\r\n self[Symbol.toStringTag] = \"WeakSet\"\r\n self.items = {}\r\n setmetatable(self.items, {__mode = \"k\"})\r\n if values == nil then\r\n return\r\n end\r\n local iterable = values\r\n if iterable[Symbol.iterator] then\r\n local iterator = iterable[Symbol.iterator](iterable)\r\n while true do\r\n local result = iterator:next()\r\n if result.done then\r\n break\r\n end\r\n self.items[result.value] = true\r\n end\r\n else\r\n for ____, value in ipairs(values) do\r\n self.items[value] = true\r\n end\r\n end\r\n end\r\n function WeakSet.prototype.add(self, value)\r\n self.items[value] = true\r\n return self\r\n end\r\n function WeakSet.prototype.delete(self, value)\r\n local contains = self:has(value)\r\n self.items[value] = nil\r\n return contains\r\n end\r\n function WeakSet.prototype.has(self, value)\r\n return self.items[value] == true\r\n end\r\n WeakSet[Symbol.species] = WeakSet\r\nend\r\n\r\nlocal function __TS__SourceMapTraceBack(fileName, sourceMap)\r\n _G.__TS__sourcemap = _G.__TS__sourcemap or ({})\r\n _G.__TS__sourcemap[fileName] = sourceMap\r\n if _G.__TS__originalTraceback == nil then\r\n local originalTraceback = debug.traceback\r\n _G.__TS__originalTraceback = originalTraceback\r\n debug.traceback = function(thread, message, level)\r\n local trace\r\n if thread == nil and message == nil and level == nil then\r\n trace = originalTraceback()\r\n elseif __TS__StringIncludes(_VERSION, \"Lua 5.0\") then\r\n trace = originalTraceback(((\"[Level \" .. tostring(level)) .. \"] \") .. message)\r\n else\r\n trace = originalTraceback(thread, message, level)\r\n end\r\n if type(trace) ~= \"string\" then\r\n return trace\r\n end\r\n local function replacer(____, file, srcFile, line)\r\n local fileSourceMap = _G.__TS__sourcemap[file]\r\n if fileSourceMap and fileSourceMap[line] then\r\n local data = fileSourceMap[line]\r\n if type(data) == \"number\" then\r\n return (srcFile .. \":\") .. tostring(data)\r\n end\r\n return (tostring(data.file) .. \":\") .. tostring(data.line)\r\n end\r\n return (file .. \":\") .. line\r\n end\r\n local result = string.gsub(\r\n trace,\r\n \"(%S+)%.lua:(%d+)\",\r\n function(file, line) return replacer(nil, file .. \".lua\", file .. \".ts\", line) end\r\n )\r\n local function stringReplacer(____, file, line)\r\n local fileSourceMap = _G.__TS__sourcemap[file]\r\n if fileSourceMap and fileSourceMap[line] then\r\n local chunkName = __TS__Match(file, \"%[string \\\"([^\\\"]+)\\\"%]\")\r\n local sourceName = string.gsub(chunkName, \".lua$\", \".ts\")\r\n local data = fileSourceMap[line]\r\n if type(data) == \"number\" then\r\n return (sourceName .. \":\") .. tostring(data)\r\n end\r\n return (tostring(data.file) .. \":\") .. tostring(data.line)\r\n end\r\n return (file .. \":\") .. line\r\n end\r\n result = string.gsub(\r\n result,\r\n \"(%[string \\\"[^\\\"]+\\\"%]):(%d+)\",\r\n function(file, line) return stringReplacer(nil, file, line) end\r\n )\r\n return result\r\n end\r\n end\r\nend\r\n\r\nlocal function __TS__Spread(iterable)\r\n local arr = {}\r\n if type(iterable) == \"string\" then\r\n for i = 0, #iterable - 1 do\r\n arr[i + 1] = __TS__StringAccess(iterable, i)\r\n end\r\n else\r\n local len = 0\r\n for ____, item in __TS__Iterator(iterable) do\r\n len = len + 1\r\n arr[len] = item\r\n end\r\n end\r\n return __TS__Unpack(arr)\r\nend\r\n\r\nlocal function __TS__StringCharAt(self, pos)\r\n if pos ~= pos then\r\n pos = 0\r\n end\r\n if pos < 0 then\r\n return \"\"\r\n end\r\n return string.sub(self, pos + 1, pos + 1)\r\nend\r\n\r\nlocal function __TS__StringCharCodeAt(self, index)\r\n if index ~= index then\r\n index = 0\r\n end\r\n if index < 0 then\r\n return 0 / 0\r\n end\r\n local ____string_byte_result_0 = string.byte(self, index + 1)\r\n if ____string_byte_result_0 == nil then\r\n ____string_byte_result_0 = 0 / 0\r\n end\r\n return ____string_byte_result_0\r\nend\r\n\r\nlocal function __TS__StringEndsWith(self, searchString, endPosition)\r\n if endPosition == nil or endPosition > #self then\r\n endPosition = #self\r\n end\r\n return string.sub(self, endPosition - #searchString + 1, endPosition) == searchString\r\nend\r\n\r\nlocal function __TS__StringPadEnd(self, maxLength, fillString)\r\n if fillString == nil then\r\n fillString = \" \"\r\n end\r\n if maxLength ~= maxLength then\r\n maxLength = 0\r\n end\r\n if maxLength == -math.huge or maxLength == math.huge then\r\n error(\"Invalid string length\", 0)\r\n end\r\n if #self >= maxLength or #fillString == 0 then\r\n return self\r\n end\r\n maxLength = maxLength - #self\r\n if maxLength > #fillString then\r\n fillString = fillString .. string.rep(\r\n fillString,\r\n math.floor(maxLength / #fillString)\r\n )\r\n end\r\n return self .. string.sub(\r\n fillString,\r\n 1,\r\n math.floor(maxLength)\r\n )\r\nend\r\n\r\nlocal function __TS__StringPadStart(self, maxLength, fillString)\r\n if fillString == nil then\r\n fillString = \" \"\r\n end\r\n if maxLength ~= maxLength then\r\n maxLength = 0\r\n end\r\n if maxLength == -math.huge or maxLength == math.huge then\r\n error(\"Invalid string length\", 0)\r\n end\r\n if #self >= maxLength or #fillString == 0 then\r\n return self\r\n end\r\n maxLength = maxLength - #self\r\n if maxLength > #fillString then\r\n fillString = fillString .. string.rep(\r\n fillString,\r\n math.floor(maxLength / #fillString)\r\n )\r\n end\r\n return string.sub(\r\n fillString,\r\n 1,\r\n math.floor(maxLength)\r\n ) .. self\r\nend\r\n\r\nlocal __TS__StringReplace\r\ndo\r\n local sub = string.sub\r\n function __TS__StringReplace(source, searchValue, replaceValue)\r\n local startPos, endPos = string.find(source, searchValue, nil, true)\r\n if not startPos then\r\n return source\r\n end\r\n local before = sub(source, 1, startPos - 1)\r\n local ____temp_0\r\n if type(replaceValue) == \"string\" then\r\n ____temp_0 = replaceValue\r\n else\r\n ____temp_0 = replaceValue(nil, searchValue, startPos - 1, source)\r\n end\r\n local replacement = ____temp_0\r\n local after = sub(source, endPos + 1)\r\n return (before .. replacement) .. after\r\n end\r\nend\r\n\r\nlocal __TS__StringSplit\r\ndo\r\n local sub = string.sub\r\n local find = string.find\r\n function __TS__StringSplit(source, separator, limit)\r\n if limit == nil then\r\n limit = 4294967295\r\n end\r\n if limit == 0 then\r\n return {}\r\n end\r\n local result = {}\r\n local resultIndex = 1\r\n if separator == nil or separator == \"\" then\r\n for i = 1, #source do\r\n result[resultIndex] = sub(source, i, i)\r\n resultIndex = resultIndex + 1\r\n end\r\n else\r\n local currentPos = 1\r\n while resultIndex <= limit do\r\n local startPos, endPos = find(source, separator, currentPos, true)\r\n if not startPos then\r\n break\r\n end\r\n result[resultIndex] = sub(source, currentPos, startPos - 1)\r\n resultIndex = resultIndex + 1\r\n currentPos = endPos + 1\r\n end\r\n if resultIndex <= limit then\r\n result[resultIndex] = sub(source, currentPos)\r\n end\r\n end\r\n return result\r\n end\r\nend\r\n\r\nlocal __TS__StringReplaceAll\r\ndo\r\n local sub = string.sub\r\n local find = string.find\r\n function __TS__StringReplaceAll(source, searchValue, replaceValue)\r\n if type(replaceValue) == \"string\" then\r\n local concat = table.concat(\r\n __TS__StringSplit(source, searchValue),\r\n replaceValue\r\n )\r\n if #searchValue == 0 then\r\n return (replaceValue .. concat) .. replaceValue\r\n end\r\n return concat\r\n end\r\n local parts = {}\r\n local partsIndex = 1\r\n if #searchValue == 0 then\r\n parts[1] = replaceValue(nil, \"\", 0, source)\r\n partsIndex = 2\r\n for i = 1, #source do\r\n parts[partsIndex] = sub(source, i, i)\r\n parts[partsIndex + 1] = replaceValue(nil, \"\", i, source)\r\n partsIndex = partsIndex + 2\r\n end\r\n else\r\n local currentPos = 1\r\n while true do\r\n local startPos, endPos = find(source, searchValue, currentPos, true)\r\n if not startPos then\r\n break\r\n end\r\n parts[partsIndex] = sub(source, currentPos, startPos - 1)\r\n parts[partsIndex + 1] = replaceValue(nil, searchValue, startPos - 1, source)\r\n partsIndex = partsIndex + 2\r\n currentPos = endPos + 1\r\n end\r\n parts[partsIndex] = sub(source, currentPos)\r\n end\r\n return table.concat(parts)\r\n end\r\nend\r\n\r\nlocal function __TS__StringSlice(self, start, ____end)\r\n if start == nil or start ~= start then\r\n start = 0\r\n end\r\n if ____end ~= ____end then\r\n ____end = 0\r\n end\r\n if start >= 0 then\r\n start = start + 1\r\n end\r\n if ____end ~= nil and ____end < 0 then\r\n ____end = ____end - 1\r\n end\r\n return string.sub(self, start, ____end)\r\nend\r\n\r\nlocal function __TS__StringStartsWith(self, searchString, position)\r\n if position == nil or position < 0 then\r\n position = 0\r\n end\r\n return string.sub(self, position + 1, #searchString + position) == searchString\r\nend\r\n\r\nlocal function __TS__StringTrim(self)\r\n local result = string.gsub(self, \"^[%s\u00a0\ufeff]*(.-)[%s\u00a0\ufeff]*$\", \"%1\")\r\n return result\r\nend\r\n\r\nlocal function __TS__StringTrimEnd(self)\r\n local result = string.gsub(self, \"[%s\u00a0\ufeff]*$\", \"\")\r\n return result\r\nend\r\n\r\nlocal function __TS__StringTrimStart(self)\r\n local result = string.gsub(self, \"^[%s\u00a0\ufeff]*\", \"\")\r\n return result\r\nend\r\n\r\nlocal __TS__SymbolRegistryFor, __TS__SymbolRegistryKeyFor\r\ndo\r\n local symbolRegistry = {}\r\n function __TS__SymbolRegistryFor(key)\r\n if not symbolRegistry[key] then\r\n symbolRegistry[key] = __TS__Symbol(key)\r\n end\r\n return symbolRegistry[key]\r\n end\r\n function __TS__SymbolRegistryKeyFor(sym)\r\n for key in pairs(symbolRegistry) do\r\n if symbolRegistry[key] == sym then\r\n return key\r\n end\r\n end\r\n end\r\nend\r\n\r\nlocal function __TS__TypeOf(value)\r\n local luaType = type(value)\r\n if luaType == \"table\" then\r\n return \"object\"\r\n elseif luaType == \"nil\" then\r\n return \"undefined\"\r\n else\r\n return luaType\r\n end\r\nend\r\n\r\nreturn {\r\n __TS__ArrayConcat = __TS__ArrayConcat,\r\n __TS__ArrayEntries = __TS__ArrayEntries,\r\n __TS__ArrayEvery = __TS__ArrayEvery,\r\n __TS__ArrayFilter = __TS__ArrayFilter,\r\n __TS__ArrayForEach = __TS__ArrayForEach,\r\n __TS__ArrayFind = __TS__ArrayFind,\r\n __TS__ArrayFindIndex = __TS__ArrayFindIndex,\r\n __TS__ArrayFrom = __TS__ArrayFrom,\r\n __TS__ArrayIncludes = __TS__ArrayIncludes,\r\n __TS__ArrayIndexOf = __TS__ArrayIndexOf,\r\n __TS__ArrayIsArray = __TS__ArrayIsArray,\r\n __TS__ArrayJoin = __TS__ArrayJoin,\r\n __TS__ArrayMap = __TS__ArrayMap,\r\n __TS__ArrayPush = __TS__ArrayPush,\r\n __TS__ArrayPushArray = __TS__ArrayPushArray,\r\n __TS__ArrayReduce = __TS__ArrayReduce,\r\n __TS__ArrayReduceRight = __TS__ArrayReduceRight,\r\n __TS__ArrayReverse = __TS__ArrayReverse,\r\n __TS__ArrayUnshift = __TS__ArrayUnshift,\r\n __TS__ArraySort = __TS__ArraySort,\r\n __TS__ArraySlice = __TS__ArraySlice,\r\n __TS__ArraySome = __TS__ArraySome,\r\n __TS__ArraySplice = __TS__ArraySplice,\r\n __TS__ArrayToObject = __TS__ArrayToObject,\r\n __TS__ArrayFlat = __TS__ArrayFlat,\r\n __TS__ArrayFlatMap = __TS__ArrayFlatMap,\r\n __TS__ArraySetLength = __TS__ArraySetLength,\r\n __TS__AsyncAwaiter = __TS__AsyncAwaiter,\r\n __TS__Await = __TS__Await,\r\n __TS__Class = __TS__Class,\r\n __TS__ClassExtends = __TS__ClassExtends,\r\n __TS__CloneDescriptor = __TS__CloneDescriptor,\r\n __TS__CountVarargs = __TS__CountVarargs,\r\n __TS__Decorate = __TS__Decorate,\r\n __TS__DecorateParam = __TS__DecorateParam,\r\n __TS__Delete = __TS__Delete,\r\n __TS__DelegatedYield = __TS__DelegatedYield,\r\n Error = Error,\r\n RangeError = RangeError,\r\n ReferenceError = ReferenceError,\r\n SyntaxError = SyntaxError,\r\n TypeError = TypeError,\r\n URIError = URIError,\r\n __TS__FunctionBind = __TS__FunctionBind,\r\n __TS__Generator = __TS__Generator,\r\n __TS__InstanceOf = __TS__InstanceOf,\r\n __TS__InstanceOfObject = __TS__InstanceOfObject,\r\n __TS__Iterator = __TS__Iterator,\r\n __TS__LuaIteratorSpread = __TS__LuaIteratorSpread,\r\n Map = Map,\r\n __TS__Match = __TS__Match,\r\n __TS__MathAtan2 = __TS__MathAtan2,\r\n __TS__MathModf = __TS__MathModf,\r\n __TS__MathSign = __TS__MathSign,\r\n __TS__Modulo50 = __TS__Modulo50,\r\n __TS__New = __TS__New,\r\n __TS__Number = __TS__Number,\r\n __TS__NumberIsFinite = __TS__NumberIsFinite,\r\n __TS__NumberIsNaN = __TS__NumberIsNaN,\r\n __TS__NumberToString = __TS__NumberToString,\r\n __TS__ObjectAssign = __TS__ObjectAssign,\r\n __TS__ObjectDefineProperty = __TS__ObjectDefineProperty,\r\n __TS__ObjectEntries = __TS__ObjectEntries,\r\n __TS__ObjectFromEntries = __TS__ObjectFromEntries,\r\n __TS__ObjectGetOwnPropertyDescriptor = __TS__ObjectGetOwnPropertyDescriptor,\r\n __TS__ObjectGetOwnPropertyDescriptors = __TS__ObjectGetOwnPropertyDescriptors,\r\n __TS__ObjectKeys = __TS__ObjectKeys,\r\n __TS__ObjectRest = __TS__ObjectRest,\r\n __TS__ObjectValues = __TS__ObjectValues,\r\n __TS__ParseFloat = __TS__ParseFloat,\r\n __TS__ParseInt = __TS__ParseInt,\r\n __TS__Promise = __TS__Promise,\r\n __TS__PromiseAll = __TS__PromiseAll,\r\n __TS__PromiseAllSettled = __TS__PromiseAllSettled,\r\n __TS__PromiseAny = __TS__PromiseAny,\r\n __TS__PromiseRace = __TS__PromiseRace,\r\n Set = Set,\r\n __TS__SetDescriptor = __TS__SetDescriptor,\r\n __TS__SparseArrayNew = __TS__SparseArrayNew,\r\n __TS__SparseArrayPush = __TS__SparseArrayPush,\r\n __TS__SparseArraySpread = __TS__SparseArraySpread,\r\n WeakMap = WeakMap,\r\n WeakSet = WeakSet,\r\n __TS__SourceMapTraceBack = __TS__SourceMapTraceBack,\r\n __TS__Spread = __TS__Spread,\r\n __TS__StringAccess = __TS__StringAccess,\r\n __TS__StringCharAt = __TS__StringCharAt,\r\n __TS__StringCharCodeAt = __TS__StringCharCodeAt,\r\n __TS__StringEndsWith = __TS__StringEndsWith,\r\n __TS__StringIncludes = __TS__StringIncludes,\r\n __TS__StringPadEnd = __TS__StringPadEnd,\r\n __TS__StringPadStart = __TS__StringPadStart,\r\n __TS__StringReplace = __TS__StringReplace,\r\n __TS__StringReplaceAll = __TS__StringReplaceAll,\r\n __TS__StringSlice = __TS__StringSlice,\r\n __TS__StringSplit = __TS__StringSplit,\r\n __TS__StringStartsWith = __TS__StringStartsWith,\r\n __TS__StringSubstr = __TS__StringSubstr,\r\n __TS__StringSubstring = __TS__StringSubstring,\r\n __TS__StringTrim = __TS__StringTrim,\r\n __TS__StringTrimEnd = __TS__StringTrimEnd,\r\n __TS__StringTrimStart = __TS__StringTrimStart,\r\n __TS__Symbol = __TS__Symbol,\r\n Symbol = Symbol,\r\n __TS__SymbolRegistryFor = __TS__SymbolRegistryFor,\r\n __TS__SymbolRegistryKeyFor = __TS__SymbolRegistryKeyFor,\r\n __TS__TypeOf = __TS__TypeOf,\r\n __TS__Unpack = __TS__Unpack\r\n}\r\n\nend)\n__bundle_register(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.button\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]\r\nlocal ____exports = {}\r\nlocal resolveProps\r\nlocal ____xmlUi = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.xmlUi\")\r\nlocal XmlUi = ____xmlUi.XmlUi\r\nlocal ____base = require(\"lua_modules.@typed-tabletop-simulator.ui.dist.component.base\")\r\nlocal resolveBaseProps = ____base.resolveBaseProps\r\n____exports.Button = function(props)\r\n return XmlUi:createElement(\r\n \"button\",\r\n resolveProps(props)\r\n )\r\nend\r\nresolveProps = function(props)\r\n local resolvedProps = resolveBaseProps(props)\r\n return resolvedProps\r\nend\r\nreturn ____exports\r\n\nend)\n__bundle_register(\"ui.Shop\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Ui = require(\"lib.Ui\")\n\nlocal R = require(\"api.Resource\")\n\nlocal filter = TableUtil.filter\nlocal map = TableUtil.map\n\nlocal Shop = {}\n\n---@class __Shop_this\nlocal this = {}\n\n---@alias gloom_Shop_ItemGroups table\n\n---@alias gloom_Shop_GroupBy 'Type' | 'None'\n---@alias gloom_Shop_SortBy 'Name' | 'Number' | 'Cost'\n\nlocal groupBy = \"Type\"\nlocal sortBy = \"Name\"\n\n---@type table\nlocal GROUP_BY_NAMES = { [\"Type\"] = \"Type\", [\"None\"] = \"None\" }\n---@type table\nlocal SORT_BY_NAMES = { [\"Name\"] = \"Name\", [\"Item number\"] = \"Number\", [\"Cost\"] = \"Cost\" }\n-- Dummy to insert a gap in the shop rows, isn't an actual TTS Object, but is typed as such\nlocal GAP = --[[---@type tts__Object]] \"~GAP~\"\n\nlocal horizontalSpacing = 2.63\nlocal verticalSpacing = 4.52\nlocal maxPerRow = 30\nlocal minPerRow = 15\nlocal zonePos = { 2.67, 4.20, 60.98 }\nlocal zoneScale = { 84.14, 5.1, 55.30 }\nlocal startingPos = { -38.09, 1.75, 35 }\nlocal groupOrder = { \"feet\", \"body\", \"head\", \"hand1\", \"hand2\", \"bag\", \"all\" }\nlocal groupTags = {\n feet = \"ItemBoots\",\n hand1 = \"ItemOneHanded\",\n hand2 = \"ItemTwoHanded\",\n body = \"ItemChest\",\n head = \"ItemHead\",\n bag = \"ItemConsumable\"\n}\n\n---@type table>\nlocal filters = {}\n\n--- Return a list of all items that are currently laid out on the table.\n---@return tts__Object[]\nfunction Shop.getItems()\n return this.getItemsInShop()\nend\n\nfunction this.sortItems()\n local itemGroups = this.getShopCards()\n this.placeAllItemGroups(itemGroups)\nend\n\nfunction this.getItemsInShop()\n local hits = Physics.cast({\n type = 3,\n origin = zonePos,\n size = zoneScale,\n direction = { 0, 1, 0 },\n })\n\n return filter(map(hits, this.hitObject), this.isItemCard)\nend\n\n---@param hitInfo tts__Physics_CastResult\n---@return tts__Object\nfunction this.hitObject(hitInfo)\n return hitInfo.hit_object\nend\n\n---@param object tts__Object\n---@return boolean\nfunction this.isItemCard(object)\n return object.hasTag(R.Tag.Item.Item)\nend\n\nfunction this.getShopCards()\n local allObjects = this.getItemsInShop()\n local allCards = {}\n for _, currentObj in pairs(allObjects) do\n if currentObj.type == \"Card\" then\n table.insert(allCards, currentObj)\n elseif currentObj.type == \"Deck\" then\n while (currentObj.remainder == nil) do\n table.insert(allCards, currentObj.takeObject())\n end\n table.insert(allCards, currentObj.remainder)\n end\n end\n\n Logger.debug(\"Found items\\n%s\", allCards)\n\n return this.groupItems(allCards)\nend\n\n---@param items tts__Object[]\n---@return table\nfunction this.groupItems(items)\n ---@type table\n local cardGroups = {}\n\n local groups = { all = \"all\" }\n if groupBy == \"Type\" then\n groups = groupTags\n end\n\n for name, _ in pairs(groups) do\n cardGroups[name] = {}\n end\n\n for _, card in pairs(items) do\n for name, tag in pairs(groups) do\n if groupBy == \"Type\" then\n if card.hasTag(tag) then\n table.insert(cardGroups[name], card)\n end\n else\n table.insert(cardGroups[name], card)\n end\n end\n end\n\n Logger.debug(\"Grouped items by %s\\n%s\", groupBy, cardGroups)\n\n return cardGroups\nend\n\n---@param items seb_Object[]\n---@return seb_Object[]\nfunction this.sortGroup(items)\n local sorter = this.sortByName\n if sortBy == \"Number\" then\n sorter = this.sortByNumber\n elseif sortBy == \"Cost\" then\n sorter = this.sortByCost\n end\n\n table.sort(items, sorter)\n\n return items\nend\n\n---@param a seb_Object\n---@param b seb_Object\nfunction this.sortByNumber(a, b)\n local valueA = this.getItemNumber(a)\n local valueB = this.getItemNumber(b)\n\n if type(valueA) ~= type(valueB) then\n if type(valueA) == \"number\" then\n return true\n end\n if type(valueB) == \"number\" then\n return false\n end\n end\n\n return valueA < valueB\nend\n\n---@param a seb_Object\n---@param b seb_Object\nfunction this.sortByName(a, b)\n return Object.name(a) < Object.name(b)\nend\n\n---@param a seb_Object\n---@param b seb_Object\nfunction this.sortByCost(a, b)\n local aCost = tonumber(Object.gmNotes(a))\n local bCost = tonumber(Object.gmNotes(b))\n\n if aCost == bCost then\n return this.sortByName(a, b)\n end\n\n if aCost and not bCost then\n return true\n end\n if not aCost and bCost then\n return false\n end\n\n return aCost < bCost\nend\n\n---@param item seb_Object\nfunction this.getItemNumber(item)\n local description, _ = Object.description(item):gsub(\"blue\", \"\"):gsub(\"red\", \"\")\n local number = tonumber(description)\n if number then\n return number\n end\n return description\nend\n\n---@param itemGroups gloom_Shop_ItemGroups\nfunction this.placeAllItemGroups(itemGroups)\n ---@type gloom_Shop_ItemGroups\n local sortedGroups = {}\n for name, cards in pairs(itemGroups) do\n sortedGroups[name] = this.sortGroup(cards)\n end\n\n Logger.debug(\"Sorted item groups by %s\\n%s\", sortBy, sortedGroups)\n\n local nextPos = startingPos\n ---@type tts__Object[]\n local savedGroup = {}\n for _, group in ipairs(groupOrder) do\n local currentGroup = sortedGroups[group]\n if currentGroup then\n if #savedGroup > 0 then\n if #savedGroup + #currentGroup <= minPerRow then\n table.insert(savedGroup, GAP)\n savedGroup = TableUtil.append(savedGroup, currentGroup)\n else\n nextPos = this.placeItemGroup(savedGroup, nextPos)\n if #currentGroup < minPerRow then\n savedGroup = currentGroup\n else\n nextPos = this.placeItemGroup(currentGroup, nextPos)\n savedGroup = {}\n end\n end\n elseif #currentGroup < minPerRow then\n savedGroup = currentGroup\n else\n nextPos = this.placeItemGroup(currentGroup, nextPos)\n end\n end\n end\n if #savedGroup then\n nextPos = this.placeItemGroup(savedGroup, nextPos)\n end\nend\n\n---@param groupCards tts__Object[]\nfunction this.placeItemGroup(groupCards, firstCardPos)\n local col = 0\n local row = 0\n for _, card in ipairs(groupCards) do\n if col >= maxPerRow then\n row = row + 1\n col = 0\n end\n if card ~= GAP then\n card.setPositionSmooth({\n firstCardPos[1] + col * horizontalSpacing,\n firstCardPos[2],\n firstCardPos[3] + row * verticalSpacing\n })\n card.setRotationSmooth({ 0, 180, 0 })\n end\n col = col + 1\n end\n local nextPos = {\n firstCardPos[1],\n firstCardPos[2],\n firstCardPos[3] + (row + 1) * verticalSpacing\n }\n return nextPos\nend\n\n---@param object tts__Object\n---@return tts__PlayerColor[]\nfunction this.getFilteredPlayerColors(object)\n local filtered = {}\n\n for playerColor, objects in pairs(filters) do\n if objects[object.getGUID()] then\n table.insert(filtered, playerColor)\n end\n end\n\n return filtered\nend\n\nfunction this.updateFilter()\n for _, item in ipairs(Shop.getItems()) do\n local filteredPlayerColors = this.getFilteredPlayerColors(item)\n item.setInvisibleTo(filteredPlayerColors)\n end\nend\n\n---@param player tts__Player\n---@param input string\nfunction this.filterItems(player, input)\n local filtered = filters[player.color]\n for _, item in ipairs(Shop.getItems()) do\n if not item.getName():lower():find(input:lower()) then\n filtered[item.getGUID()] = true\n end\n end\nend\n\n---@param player tts__Player\nfunction onShowItemShop(player)\n Ui.showForPlayer(\"shop.window\", player)\nend\n\n---@param player tts__Player\nfunction onCloseShopWindowClicked(player)\n Ui.showForPlayer(\"shop.window\", player)\nend\n\n---@param value string\nfunction onGroupByChanged(_, value)\n groupBy = GROUP_BY_NAMES[value]\nend\n\n---@param value string\nfunction onSortByChanged(_, value)\n sortBy = SORT_BY_NAMES[value]\nend\n\nfunction onSortClicked()\n this.sortItems()\nend\n\n---@param player tts__Player\n---@param value string\nfunction onFilterEntered(player, value)\n filters[player.color] = {}\n this.filterItems(player, value)\n this.updateFilter()\nend\n\n---@param player tts__Player\nfunction onClearFilterClicked(player)\n filters[player.color] = {}\n Ui.setText(\"shop.filter.input\", \"\")\n this.updateFilter()\nend\n\nreturn Shop\n\nend)\n__bundle_register(\"lib.Ui\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Ui = {}\n\nUi.MouseEvent = {\n LeftClick = \"-1\",\n RightClick = \"-2\",\n MiddleClick = \"-3\",\n SingleTouch = \"1\",\n DoubleTouch = \"2\",\n TripleTouch = \"3\",\n}\n\n---@param id tts__UIElement_Id\n---@param player tts__Player\nfunction Ui.showForPlayer(id, player)\n showForPlayer({ panel = id, color = player.color })\nend\n\nfunction Ui.setAttribute(id, attribute, value)\n self.UI.setAttribute(id, attribute, value)\nend\n\n---@param active boolean\nfunction Ui.setActive(id, active)\n Ui.setAttribute(id, \"active\", active)\nend\n\n---@param text string | number\nfunction Ui.setText(id, text)\n Ui.setAttribute(id, \"text\", text)\nend\n\n---@param image string\nfunction Ui.setImage(id, image)\n Ui.setAttribute(id, \"image\", image)\nend\n\n---@param xml tts__UIElement[]\n---@param id string\nfunction Ui.findElement(xml, id)\n for _, element in ipairs(xml) do\n if element.attributes.id == id then\n return element\n end\n end\n\n for _, element in ipairs(xml) do\n if element.children then\n local found = Ui.findElement(element.children, id)\n if found then\n return found\n end\n end\n end\nend\n\nfunction Ui.isLoaded()\n return self.UI.getXml() ~= \"\"\nend\n\n---@param id string\n---@param pattern string\n---@return integer, integer...\nfunction Ui.getIndexes(id, pattern)\n local parts = table.pack(id:match(pattern))\n\n for i, part in ipairs(parts) do\n parts[i] = tonumber(part)\n end\n\n return table.unpack(parts)\nend\n\nreturn Ui\n\nend)\n__bundle_register(\"ui.TextHandler\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal StringUtil = require(\"lib.StringUtil\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal UiApi = require(\"api.UiApi\")\nlocal ConditionApi = require(\"api.ConditionApi\")\n\nlocal TextHandler = {}\n\nlocal Api = --[[---@type UiApi]] ApiProvider(\"ui\")\nlocal this = {}\n\n---@shape __TextHandler_Mapping\n---@field name string\n---@field text string\n\n---@type table\nlocal mappings = {}\n\nlocal MAX_UI_ELEMENTS = 6\n\n---@overload fun(text: string, areaTargets: string[]): string\n---@param text string\n---@param areaTargets gloom_Ui_AreaTarget[]\n---@param nameOnly boolean\n---@return string\nfunction TextHandler.renderText(text, areaTargets, nameOnly)\n local areaIndex = 1\n\n for group, element in text:gmatch(\"{(.-)%.(.-)}\") do\n local fullName = group .. \".\" .. element\n local name = StringUtil.capitalizeWords(element)\n local replacement\n\n if nameOnly then\n replacement = name\n else\n if mappings[fullName] then\n replacement = mappings[element].text\n elseif group == \"c\" then\n local condition = ConditionApi.getCondition(name)\n if condition then\n replacement = (--[[---@not nil]] condition).renderedMarkup\n end\n elseif group == \"e\" then\n local effect = ConditionApi.getEffect(name)\n if effect then\n replacement = (--[[---@not nil]] effect).renderedMarkup\n end\n end\n end\n\n if group == \"area\" then\n replacement = \"Area\"\n if areaIndex > 1 then\n replacement = replacement .. \" \" .. tostring(areaIndex)\n end\n local target = areaTargets and areaTargets[areaIndex]\n if target then\n UiApi.renderAreaTo(\"{area.\" .. element .. \"}\", target[1], target[2])\n end\n areaIndex = areaIndex + 1\n end\n\n if not replacement then\n replacement = name\n end\n\n text = text:gsub(\"{\" .. fullName .. \"}\", replacement)\n end\n\n return text\nend\n\n---@param text string\n---@return string[]\nfunction TextHandler.renderArea(text)\n local rows = --[[---@type string]] text:match(\"{area%.(.+)}\")\n if not rows then\n return {}\n end\n\n local renderedRows = {}\n for row in rows:gmatch(\"_?([^_]+)\") do\n local rendered = this.renderRow(row)\n table.insert(renderedRows, rendered)\n end\n\n return renderedRows\nend\n\n---@param text string\n---@param owner tts__Object\n---@param elementId tts__UIElement_Id\nfunction TextHandler.renderAreaTo(text, owner, elementId)\n local renderedRows = TextHandler.renderArea(text)\n local ui = owner.UI\n\n if renderedRows[1] then\n ui.setAttribute(elementId, \"active\", true)\n else\n ui.setAttribute(elementId, \"active\", false)\n end\n\n for i = 1, MAX_UI_ELEMENTS do\n local row = renderedRows[i]\n local rowElement = elementId .. \"-\" .. i\n if row then\n ui.setAttribute(rowElement, \"active\", true)\n ui.setValue(rowElement, row)\n else\n ui.setAttribute(rowElement, \"active\", false)\n end\n end\nend\n\nApi.renderText = function(text, areaTargets, nameOnly)\n return TextHandler.renderText(text, areaTargets, nameOnly)\nend\n\nApi.renderAreaTo = function(text, owner, elementId)\n TextHandler.renderAreaTo(text, owner, elementId)\nend\n\n---@param row string\n---@return string\nfunction this.renderRow(row)\n local rendered = \"\"\n for i = 1, #row do\n local char = row:sub(i, i)\n local mapping = mappings[\"area.\" .. char]\n if not mapping then\n rendered = rendered .. \"\"\n else\n rendered = rendered .. mapping.text\n end\n end\n\n return rendered\nend\n\n---@param name string\n---@param entry __TextHandler_Mapping\nfunction this.addMapping(name, entry)\n mappings[name] = entry\nend\n\n-- Elements\nthis.addMapping(\"e.use\", {\n name = \"Use\",\n text = UiApi.coloredText(\"\\u{E077}\", \"#E16868\")\n})\n\n-- Area\nthis.addMapping(\"area.a\", {\n name = \"Ally\",\n text = UiApi.areaText(\"\\u{E0E1}\", \"blue\"),\n})\nthis.addMapping(\"area.e\", {\n name = \"Empty\",\n text = UiApi.coloredText(\"\\u{E0E3}\", \"#00000000\"),\n})\nthis.addMapping(\"area.s\", {\n name = \"Self\",\n text = UiApi.areaText(\"\\u{E0E3}\", \"grey\"),\n})\nthis.addMapping(\"area.t\", {\n name = \"Target\",\n text = UiApi.areaText(\"\\u{E0E0}\", \"red\"),\n})\n\nreturn TextHandler\n\nend)\n__bundle_register(\"ui.GameSetupUi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Ui = require(\"lib.Ui\")\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal Event = require(\"Event\")\nlocal ScenarioApi = require(\"api.ScenarioApi\")\nlocal GloomUi = require(\"ui.GloomUi\")\n\n--- The Game Setup window\nlocal GameSetup = {}\n\n---@class __GameSetup_this\nlocal this = {\n maxElements = 150,\n ---@type table\n campaigns = {},\n ---@type gloom_Scenario_Identifier[]\n shownScenarios = {}\n}\n\n---@type gloom_UI_Dropdown\nlocal campaignDropdown\n\n---@param ui seb_XmlUi\nfunction this.createUi(ui)\n local scenarioWindow = --[[---@not nil]] ui.findElement(\"ScenarioSpawner\")\n\n local scenarioList = --[[---@not nil]] ui.findElement(\"scenarioList\")\n for i = 1, this.maxElements do\n local button = scenarioList.addButton({\n id = \"scenarioEntry_\" .. i,\n onClick = \"spawnScenario\",\n font = GloomUi.Font.Nyala,\n fontSize = 22,\n color = \"rgba(0, 0, 0, 0.95)\",\n textColor = \"rgb(1, 1, 1)\",\n active = false,\n })\n button.addImage({\n id = \"scenarioEntryImage_\" .. i,\n image = \"\",\n rectAlignment = XmlUi.Alignment.MiddleLeft,\n offsetXY = { 5, 0 },\n width = 30, height = 30,\n preserveAspect = true,\n active = false,\n })\n end\n\n local campaignsDropdown = GloomUi.Factory.createDropdown({\n id = \"scenarioCampaigns\",\n width = 300,\n offsetXY = { 0, 130 },\n choices = {},\n selected = \"\",\n handler = function(_, campaign) this.showScenarios(campaign.name) end,\n })\n campaignsDropdown.ui.setAttribute(\"active\", false)\n\n scenarioWindow.addChild(campaignsDropdown.ui)\n campaignDropdown = campaignsDropdown.dropdown\nend\n\n---@param campaignName string\nfunction this.showScenarios(campaignName)\n local scenarios = this.campaigns[campaignName]\n\n table.sort(scenarios, function(a, b)\n if type(a.id) == \"number\" and type(b.id) ~= \"number\" then\n return true\n end\n if type(b.id) == \"number\" and type(a.id) ~= \"number\" then\n return false\n end\n return a.id < b.id\n end)\n\n for i = 1, #scenarios do\n local scenario = scenarios[i]\n Ui.setText(\"scenarioEntry_\" .. i, scenario.id .. \" - \" .. scenario.name)\n Ui.setActive(\"scenarioEntry_\" .. i, true)\n Global.UI.setAttribute(\"scenarioEntry_\" .. i, \"textColor\", \"rgb(1, 1, 1)\")\n if scenario.icon then\n Ui.setActive(\"scenarioEntryImage_\" .. i, true)\n Ui.setImage(\"scenarioEntryImage_\" .. i, --[[---@not nil]] scenario.icon)\n else\n Ui.setActive(\"scenarioEntryImage_\" .. i, false)\n end\n end\n\n for i = #scenarios + 1, this.maxElements do\n Global.UI.setAttribute(\"scenarioEntry_\" .. i, \"active\", false)\n end\n\n Global.UI.setAttribute(\"scenarioList\", \"height\", #scenarios * 50)\n\n this.shownScenarios = scenarios\nend\n\nfunction syncScenarios()\n this.campaigns = TableUtil.deepCopy(ScenarioApi.getCampaigns())\n\n local campaignNames = TableUtil.keys(this.campaigns)\n campaignDropdown.setOptions(campaignNames, \"Gloomhaven\")\n self.UI.setAttribute(campaignDropdown.id, \"active\", true)\n\n Wait.frames(function() this.showScenarios(\"Gloomhaven\") end, 10)\nend\n\nfunction syncMonsters()\n local monsterTable = getObjectsWithTag(\"MonsterBag\")[1].getObjects()\n table.sort(monsterTable, function(a, b)\n return Object.name(a) < Object.name(b)\n end)\n\n local ui = XmlUi(Global)\n\n local monsterList = --[[---@not nil]] ui.findElement(\"monsterList\")\n monsterList.clearElements()\n\n for _, v in pairs(monsterTable) do\n monsterList.addButton({\n class = \"ini\",\n font = GloomUi.Font.Nyala,\n fontSize = 22,\n textColor = \"#FFFFFF\",\n onClick = \"spawnMonsterFromSetup\",\n id = \"SpawnMonster_\" .. Object.name(v),\n text = Object.name(v),\n })\n end\n\n ui.update()\n\n local count = TableUtil.length(monsterTable)\n Wait.frames(function() Global.UI.setAttribute(\"monsterList\", \"height\", count * 50) end, 10)\nend\n\n---@param player tts__Player\n---@param id string\nfunction spawnScenario(player, _, id)\n local found = id:match(\"_(.+)$\")\n if found then\n local hiddenSetup = (self.UI.getAttribute(\"HiddenRooms1Image\", \"image\") == \"Radio Button Selected\") and true or false\n local index = --[[---@not nil]] tonumber(--[[---@not nil]] found)\n local scenario = this.shownScenarios[index]\n createMap({\n scenario = scenario.id,\n hiddenSetup = hiddenSetup,\n })\n showForPlayer({ panel = \"Spawner\", color = player.color })\n endRoundSilently()\n end\nend\n\nfunction spawnMonsterFromSetup(player, value, id)\n local found = id:find(\"_.+$\")\n if not found then\n return\n end\n local monsterName = id:sub(found + 1)\n local requestedLevel = self.UI.getAttribute(\"MSSLevel\", \"text\")\n local params = {\n monsterName = monsterName,\n monsterType = \"Standee\",\n monsterDifficulty = isElite and \"elite\" or \"normal\",\n level = tonumber(requestedLevel)\n }\n spawnMonster(params)\nend\n\nEvent.registerForCreateGlobalUi(this.createUi)\n\nreturn GameSetup\n\nend)\n__bundle_register(\"ui.OptionsUi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal Event = require(\"Event\")\nlocal GloomUi = require(\"ui.GloomUi\")\nlocal Options = require(\"api.OptionsApi\")\n\nlocal OptionsUi = {}\n\nlocal Ui = {\n Id = \"optionsMenu\",\n Size = {\n Check = 20,\n Left = 300,\n Right = 200,\n },\n Header = {\n Height = 40,\n Asset = \"option.row\",\n },\n Group = {\n Height = 40,\n Asset = \"option.header\",\n },\n Item = {\n Height = 35,\n Asset = \"option.row\",\n },\n}\n\n---@param value boolean\n---@param optionName tts__UIElement_Id\nlocal function onToggleOptionChanged(_, value, optionName)\n Options.setValue(optionName, value)\nend\n\n---@param option gloom_Option_Toggle\n---@param optionName string\n---@param container seb_XmlUi_Element\nlocal function createToggle(option, optionName, container)\n local toggle = GloomUi.Factory.createToggle({\n id = optionName,\n handler = onToggleOptionChanged,\n active = --[[---@type boolean]] option.value,\n size = Ui.Size.Check,\n tooltip = option.description,\n })\n\n container.addChild(toggle.ui)\nend\n\n---@param choice gloom_UiDropdown_NamedChoice\n---@param optionName tts__UIElement_Id\nlocal function onDropdownOptionChanged(_, choice, optionName)\n Options.setValue(optionName, choice.value)\nend\n\n---@param option gloom_Option_Text\n---@param optionName string\n---@param container seb_XmlUi_Element\nlocal function createDropdown(option, optionName, container)\n local dropdown = GloomUi.Factory.createDropdown({\n id = optionName,\n handler = onDropdownOptionChanged,\n width = Ui.Size.Right,\n choices = (--[[---@not nil]] option.choices).values,\n selected = --[[---@type string]] option.value,\n tooltip = option.description,\n })\n\n container.addChild(dropdown.ui)\nend\n\n---@param choice gloom_UiMultiDropdown_SelectedChoices\n---@param optionName tts__UIElement_Id\nlocal function onMultiDropdownOptionChanged(_, choice, optionName)\n Options.setValue(optionName, choice)\nend\n\n---@param option gloom_Option_Text\n---@param optionName string\n---@param container seb_XmlUi_Element\nlocal function createMultiDropdown(option, optionName, container)\n local dropdown = GloomUi.Factory.createMultiDropdown({\n id = optionName,\n handler = onMultiDropdownOptionChanged,\n width = Ui.Size.Right,\n choices = (--[[---@not nil]] option.choices).values,\n values = function()\n return --[[---@type set ]] option.value\n end,\n selected = --[[---@type set]] option.value,\n tooltip = option.description,\n })\n\n container.addChild(dropdown.ui)\nend\n\n---@param choice URL\n---@param optionName tts__UIElement_Id\nlocal function onImageChooserChanged(_, choice, optionName)\n Options.setValue(optionName, choice)\nend\n\n---@param option gloom_Option_Image\n---@param optionName string\n---@param container seb_XmlUi_Element\n---@param ui seb_XmlUi\nlocal function createImageChooser(option, optionName, container, ui)\n local imageChooser = GloomUi.Factory.createImageChooser({\n id = optionName,\n handler = onImageChooserChanged,\n width = Ui.Size.Right,\n choices = option.choices,\n selected = function()\n return option.value\n end,\n tooltip = option.description,\n })\n\n container.addChild(imageChooser.ui)\n ui.updateAssets(imageChooser.assets)\nend\n\n---@param container seb_XmlUi_TableLayout\nlocal function createHeader(container)\n local headerRow = container.addRow({\n image = Ui.Header.Asset,\n dontUseTableRowBackground = true,\n })\n\n headerRow.addCell({ columnSpan = 2 }).addImage({\n image = \"element.close\",\n onClick = \"onCloseOptionsClicked\",\n offsetXY = { -3, 0 },\n width = Ui.Header.Height, height = Ui.Header.Height,\n rectAlignment = XmlUi.Alignment.UpperRight,\n })\nend\n\n---@param ui seb_XmlUi\nlocal function createUi(ui)\n local options = Options.getOptions()\n\n local window = ui.addPanel({\n id = Ui.Id,\n active = false,\n visibility = {},\n width = Ui.Size.Left + Ui.Size.Right,\n rectAlignment = XmlUi.Alignment.MiddleCenter,\n allowDragging = true,\n returnToOriginalPositionWhenReleased = false,\n })\n\n local content = window.addTableLayout({\n cellBackgroundColor = \"clear\",\n rowBackgroundColor = \"clear\",\n columnWidths = { Ui.Size.Left, Ui.Size.Right },\n cellPadding = { h = 8, v = 3 },\n })\n\n createHeader(content)\n\n local totalGroups = 0\n local totalOptions = 0\n\n for groupName, group in pairs(options) do\n totalGroups = totalGroups + 1\n local groupRow = content.addRow({\n image = Ui.Group.Asset,\n dontUseTableRowBackground = true,\n })\n\n groupRow.addText({\n text = group.displayName or groupName,\n class = \"gloom\",\n fontSize = 24,\n alignment = XmlUi.Alignment.MiddleLeft,\n })\n\n for name, option in pairs(group.options) do\n totalOptions = totalOptions + 1\n local fullName = groupName .. \".\" .. name\n\n local optionRow = content.addRow({\n dontUseTableRowBackground = true,\n image = Ui.Item.Asset,\n })\n\n optionRow.addText({\n text = option.displayName or name,\n class = \"gloom\",\n fontSize = 20,\n alignment = XmlUi.Alignment.MiddleLeft,\n })\n\n if option.type == Options.OptionType.Toggle then\n createToggle(--[[---@type gloom_Option_Toggle]] option, fullName, optionRow)\n elseif option.type == Options.OptionType.Text then\n local textOption = --[[---@type gloom_Option_Text]] option\n if textOption.choices.multiple then\n createMultiDropdown(textOption, fullName, optionRow)\n else\n createDropdown(textOption, fullName, optionRow)\n end\n elseif option.type == Options.OptionType.Image then\n createImageChooser(--[[---@type gloom_Option_Image]] option, fullName, optionRow, ui)\n end\n end\n end\n\n local contentHeight = Ui.Header.Height + totalGroups * Ui.Group.Height + totalOptions * Ui.Item.Height\n window.setHeight(contentHeight)\nend\n\nlocal function updateUi()\n local options = Options.getOptions()\n\n for groupName, group in pairs(options) do\n for name, option in pairs(group.options) do\n local fullName = groupName .. \".\" .. name\n if option.type == Options.OptionType.Toggle then\n GloomUi.Factory.setToggle(fullName, --[[---@type boolean]] option.value)\n elseif option.type == Options.OptionType.Text then\n local textOption = --[[---@type gloom_Option_Text]] option\n if not textOption.choices.multiple then\n local index = 0\n for _, choice in ipairs(textOption.choices.values) do\n if choice.value == option.value then\n GloomUi.Factory.setDropdown(fullName, index)\n end\n index = index + 1\n end\n end\n end\n end\n end\nend\n\n---@param player tts__Player\nfunction onShowOptionsClicked(player)\n updateUi()\n showForPlayer({ panel = Ui.Id, color = player.color })\nend\n\n---@param player tts__Player\nfunction onCloseOptionsClicked(player)\n showForPlayer({ panel = Ui.Id, color = player.color })\nend\n\nEvent.registerForCreateGlobalUi(createUi)\n\nreturn OptionsUi\n\nend)\n__bundle_register(\"Scenario\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Formula = require(\"Formula\")\nlocal Party = require(\"Party\")\nlocal R = require(\"Resources\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\n\nlocal Scenario = {}\n\nlocal Api = --[[---@type ScenarioApi]] ApiProvider(\"scenario\")\nlocal this = {}\n\n---@type table\nlocal randomPools = {}\n\n---@class gloom_Scenario_Context\n\n---@class gloom_Scenario_ContextStatic\n---@overload fun(object: tts__Object, extraContext: table): gloom_Scenario_Context\nlocal ScenarioContext = {}\n\nsetmetatable(ScenarioContext, {\n ---@param object nil | tts__Object\n ---@param extraContext table\n __call = function(_, object, extraContext)\n local this = --[[---@type gloom_Scenario_Context]] {}\n\n ---@param variable string\n ---@return gloom_Formula_Result\n function this.resolve(variable)\n if variable == \"C\" then\n return math.max(1, Party.getActiveCharacterCount())\n elseif variable == \"L\" then\n return Scenario.getScenarioLevel()\n elseif variable == \"self\" and object then\n return object.getGUID()\n end\n\n if extraContext and extraContext[variable] then\n return extraContext[variable]\n end\n\n return nil\n end\n\n return this\n end\n})\n\n---@return integer\nfunction Scenario.getScenarioLevel()\n local scenarioLevelChart = R.getInstance(R.Tag.ScenarioLevelChart)\n return --[[---@type integer]] scenarioLevelChart.call(\"getScenarioLevel\")\nend\n\n---@return Scenario_Conversion\nfunction Scenario.getScenarioLevelSettings()\n local scenarioLevelChart = R.getInstance(R.Tag.ScenarioLevelChart)\n return --[[---@type Scenario_Conversion]] scenarioLevelChart.call(\"getConversion\")\nend\n\n---@overload fun(formula: gloom_Formula, object: tts__Object)\n---@overload fun(formula: gloom_Formula)\n---@param formula gloom_Formula\n---@param object tts__Object\n---@param context table\n---@return number\nfunction Scenario.calculate(formula, object, context)\n return Formula.calculate(formula, ScenarioContext(object, context))\nend\n\nApi.registerRandomPool = function(randomPool)\n randomPools[randomPool.name] = randomPool\nend\n\nApi.getRandomPools = function()\n ---@type table[]>\n local pools = {}\n\n for name, _ in pairs(randomPools) do\n pools[name] = this.getRandomPool(name)\n end\n\n return pools\nend\n\nApi.getRandomPool = function(name)\n return this.getRandomPool(name)\nend\n\nApi.calculateFormula = function(formula, context)\n return Scenario.calculate(formula, nil, context)\nend\n\nApi.getScenarioLevel = function()\n return Scenario.getScenarioLevel()\nend\n\nApi.getScenarioLevelSettings = function()\n return Scenario.getScenarioLevelSettings()\nend\n\n---@param name string\nfunction this.getRandomPool(name)\n local pool = randomPools[name]\n if not pool then\n return nil\n end\n\n ---@type gloom_Spawn_Execution[]\n local elements = {}\n for _, element in ipairs(pool.objects) do\n table.insert(elements, {\n element = element,\n placement = {},\n })\n end\n\n return elements\nend\n\nrequire(\"Game.Gloomhaven.Random.Monster\")\n\nreturn Scenario\n\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"Game.Gloomhaven.Random.Monster.Archaic\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Arid\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Corrupted\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Crushing\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Cutthroat\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Defiled\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Drowned\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Etheral\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Foggy\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Fortified\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Frigid\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Hopeless\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Horrific\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Infected\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Mangy\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Putrid\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Rotting\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Scaled\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Tribal\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Unstable\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Venomous\")\r\nrequire(\"Game.Gloomhaven.Random.Monster.Wild\")\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Wild\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Cave Bear\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [3] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Hound\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Hound\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Bear Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Immobilize\" }\r\n },\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Bear Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Immobilize\" }\r\n },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Cave Bear\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Wild\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Venomous\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Harrower Infester\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Harrower Infester\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n conditions = { \"Poison\", \"Stun\", } },\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n conditions = { \"Poison\", \"Stun\", } },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Venomous\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Unstable\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Savvas Lavaflow\",\r\n },\r\n [2] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Earth Demon\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Earth Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [6] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n },\r\n [8] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Hot Coals 1\",\r\n },\r\n [9] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Hot Coals 1\",\r\n },\r\n [10] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Hot Coals 1\",\r\n },\r\n [11] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Hot Coals 1\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Unstable\",\r\n objects = spawns,\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Tribal\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Inox Guard\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Inox Guard\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Inox Shaman\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = R.EmptyElement,\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Inox Archer\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n } },\r\n [6] = R.EmptyElement,\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Inox Shaman\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n } },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Wound\", \"Disarm\" } },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Inox Archer\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n } },\r\n [10] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Wound\", \"Disarm\" } },\r\n [11] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Tribal\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Scaled\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Wound\", \"Poison\", } },\r\n [2] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Wound\", \"Poison\", } },\r\n [3] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Wound\", \"Poison\", } },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Rending Drake\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Rending Drake\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Spitting Drake\",\r\n },\r\n [9] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Spitting Drake\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [12] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Scaled\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Rotting\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Spirit\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Spirit\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n conditions = { \"Poison\", \"Stun\", } },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Spirit\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n conditions = { \"Poison\", \"Stun\", } },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n conditions = { \"Poison\", \"Stun\", } },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Rotting\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Putrid\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n },\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Muddle\" }\r\n },\r\n [10] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Muddle\" }\r\n },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Muddle\" }\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Putrid\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Mangy\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Shaman\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Cave Bear\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [11] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Mangy\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Infected\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [9] = R.EmptyElement,\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Infected\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Horrific\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Lurker\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Deep Terror\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Muddle\" }\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Muddle\" }\r\n },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Muddle\" }\r\n },\r\n [12] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Deep Terror\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Horrific\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Hopeless\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = R.EmptyElement,\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [9] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [10] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [12] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Black Imp\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Hopeless\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Frigid\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = R.EmptyElement,\r\n [2] = R.EmptyElement,\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Savvas Icestorm\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Frost Demon\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [7] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Disarm\" }\r\n },\r\n [8] = R.EmptyElement,\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Disarm\" }\r\n },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Disarm\" }\r\n },\r\n [12] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Frigid\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Fortified\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Guard\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Guard\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [4] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Archer\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Archer\",\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Archer\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Bear Trap\",\r\n damage = \"Auto\",\r\n },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ancient Artillery\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.Trap,\r\n name = \"Bear Trap\",\r\n damage = \"Auto\",\r\n },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Bear Trap\",\r\n damage = \"Auto\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ancient Artillery\",\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Frotified\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Foggy\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Frost Demon\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = R.EmptyElement,\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Frost Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Frost Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Hot Coals 1\",\r\n },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [11] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Hot Coals 1\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Foggy\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Etheral\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Aesther Ashblade\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [3] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Aesther Scout\",\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Aesther Ashblade\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Aesther Scout\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [7] = R.EmptyElement,\r\n [8] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [9] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Aesther Scout\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = R.Remove,\r\n }\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Etheral\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Drowned\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Lurker\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Lurker\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Lurker\",\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Spirit\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Spirit\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Spirit\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Stun\", } },\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Stun\", } },\r\n [10] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Stun\", } },\r\n [11] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Water 1\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Drowned\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Defiled\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Sun Demon\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Sun Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n },\r\n [8] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Poison\", \"Immobilize\" } },\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Poison\", \"Immobilize\" } },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Sun Demon\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n } },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Poison Gas\",\r\n damage = \"Auto\",\r\n conditions = { \"Poison\", \"Immobilize\" } },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Defiled\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Cutthroat\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Bandit Guard\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Coin,\r\n name = \"Coin 1\",\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Bandit Archer\",\r\n characterCount = {\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Bandit Archer\",\r\n characterCount = { }\r\n },\r\n [5] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Poison\", }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Hound\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Bandit Guard\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Hound\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [9] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Poison\", }\r\n },\r\n [10] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Bandit Archer\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [11] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Poison\", }\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\"\r\n },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Cutthroat\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Crushing\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Earth Demon\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n } },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [5] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Earth Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n },\r\n [10] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [11] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Crushing\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Corrupted\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Cultist\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [3] = R.Remove,\r\n }\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [5] = R.EmptyElement,\r\n [6] = R.EmptyElement,\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Cultist\",\r\n characterCount = {\r\n }\r\n },\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Cultist\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [11] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Corrupted\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Arid\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Savage\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [2] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Wound\", } },\r\n [3] = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = \"Auto\",\r\n conditions = { \"Wound\", } },\r\n [4] = R.EmptyElement,\r\n [5] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Savage\",\r\n characterCount = {\r\n }\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Savage\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Tracker\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [8] = R.EmptyElement,\r\n [9] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Tracker\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [10] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [11] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Arid\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Random.Monster.Archaic\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ScenarioApi = require(\"api.ScenarioApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal spawns = {\r\n [1] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [2] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [3] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Stone Golem\",\r\n characterCount = {\r\n [2] = { difficulty = \"elite\" },\r\n [3] = { difficulty = \"elite\" },\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [4] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Stone Golem\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [5] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [6] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ancient Artillery\",\r\n characterCount = {\r\n [2] = R.Remove,\r\n }\r\n },\r\n [7] = {\r\n type = R.ElementType.DifficultTerrain,\r\n name = \"Rubble\",\r\n },\r\n [8] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ancient Artillery\",\r\n characterCount = {\r\n }\r\n },\r\n [9] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [10] = {\r\n type = R.ElementType.HazardousTerrain,\r\n name = \"Thorns\",\r\n },\r\n [11] = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ancient Artillery\",\r\n characterCount = {\r\n [4] = { difficulty = \"elite\" },\r\n }\r\n },\r\n [12] = {\r\n type = R.ElementType.Treasure,\r\n name = \"Treasure Chest Vertical\" },\r\n}\r\n\r\nScenarioApi.registerRandomPool({\r\n name = \"Archaic\",\r\n objects = spawns,\r\n})\nend)\n__bundle_register(\"Formula\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal Math = require(\"lib.Math\")\r\nlocal StringUtil = require(\"lib.StringUtil\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Formula = {}\r\n\r\n---@alias gloom_Formula string | number | boolean | gloom_Formula[]\r\n---@alias gloom_Formula_Result nil | number | string | boolean\r\n---@alias gloom_Formula_Function fun(args: gloom_Formula[], context: gloom_Formula_Context): gloom_Formula_Result\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number[]\r\nlocal function resolve(args, context)\r\n return TableUtil.map(args, function(a) return Formula.calculate(a, context) end)\r\nend\r\n\r\n---@overload fun(args: number[], reducer: fun(a: number, b: number): number): number\r\n---@param args number[]\r\n---@param reducer fun(a: number, b: number): number\r\n---@param initial number\r\n---@return number\r\nlocal function reduce(args, reducer, initial)\r\n if initial then\r\n return TableUtil.reduce(args, initial, reducer)\r\n end\r\n\r\n local value = args[1]\r\n for i = 2, #args do\r\n value = reducer(value, args[i])\r\n end\r\n return value\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nlocal function add(args, context)\r\n return reduce(resolve(args, context), function(a, b) return a + b end, 0)\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nlocal function subtract(args, context)\r\n return reduce(resolve(args, context), function(a, b) return a - b end)\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nlocal function multiply(args, context)\r\n return reduce(resolve(args, context), function(a, b) return a * b end, 1)\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nlocal function divide(args, context)\r\n return reduce(resolve(args, context), function(a, b) return a / b end)\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nlocal function roundUp(args, context)\r\n return Math.roundUp(resolve(args, context)[1])\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nlocal function roundDown(args, context)\r\n return Math.roundDown(resolve(args, context)[1])\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return gloom_Formula_Result\r\nlocal function apply(args, context)\r\n local functionName = --[[---@type string]] Formula.calculate(args[1], context)\r\n\r\n ---@type nil | table\r\n local parameters\r\n ---@type nil | string\r\n local functionOwner\r\n if type(args[2]) == \"string\" then\r\n functionOwner = --[[---@type string]] args[2]\r\n parameters = --[[---@type table]] args[3]\r\n else\r\n parameters = --[[---@type table]] args[2]\r\n end\r\n\r\n ---@type table\r\n local resolvedParameters = {}\r\n for argName, argValue in TableUtil.pairs(parameters) do\r\n resolvedParameters[argName] = Formula.calculate(argValue, context)\r\n end\r\n\r\n if functionOwner then\r\n local ownerObject = getObjectFromGUID(--[[---@not nil]] functionOwner)\r\n if not ownerObject then\r\n Logger.error(\"Tried to call function %s on object %s, but didn't find the object!\",\r\n functionName, functionOwner)\r\n return nil\r\n end\r\n return (--[[---@not nil]] ownerObject).call(functionName, resolvedParameters)\r\n else\r\n local functionToCall = _G[functionName]\r\n if not functionToCall or type(functionToCall) ~= \"function\" then\r\n Logger.error(\"Tried to call the Global function %s, but it doesn't exist or isn't a callable function!\",\r\n functionName)\r\n return nil\r\n end\r\n return functionToCall(resolvedParameters)\r\n end\r\nend\r\n\r\n---@param args gloom_Formula[]\r\n---@param context gloom_Formula_Context\r\n---@return gloom_Formula_Result\r\nlocal function condition(args, context)\r\n local conditionResult = Formula.calculate(args[1], context)\r\n\r\n if conditionResult then\r\n return Formula.calculate(args[2], context)\r\n else\r\n return Formula.calculate(args[3], context)\r\n end\r\nend\r\n\r\n---@type table\r\nlocal Operator = {\r\n [\"+\"] = add,\r\n [\"-\"] = subtract,\r\n [\"*\"] = multiply,\r\n [\"/\"] = divide,\r\n [\"roundUp\"] = roundUp,\r\n [\"roundDown\"] = roundDown,\r\n [\"apply\"] = apply,\r\n [\"if\"] = condition,\r\n}\r\n\r\n--- !!! This is very basic at the moment. Only supports one operator with two arguments and only operator +, -, *, /.\r\n--- Considers x to be the operator *.\r\n---@param formula number | string\r\n---@return gloom_Formula\r\nfunction Formula.parse(formula)\r\n if type(formula) == \"number\" then\r\n return formula\r\n end\r\n\r\n local formulaS = --[[---@not number]] formula\r\n formulaS = formulaS:gsub(\"x\", \"*\")\r\n\r\n local operators = { \"+\", \"*\", \"-\", \"/\" }\r\n\r\n for _, operator in ipairs(operators) do\r\n if formulaS:find(operator) then\r\n local parts = StringUtil.split(formulaS, operator)\r\n\r\n ---@param arg string\r\n ---@return string | number\r\n local function parseArg(arg)\r\n if tonumber(arg) then\r\n return --[[---@not nil]] tonumber(arg)\r\n end\r\n return arg\r\n end\r\n\r\n local argOne = parseArg(parts[1])\r\n local argTwo = parseArg(parts[2])\r\n\r\n return { operator, argOne, argTwo }\r\n end\r\n end\r\nend\r\n\r\n---@param formula gloom_Formula\r\n---@param context gloom_Formula_Context\r\n---@return number\r\nfunction Formula.calculate(formula, context)\r\n local result\r\n if type(formula) == \"number\" then\r\n result = formula\r\n elseif type(formula) == \"string\" then\r\n local stringValue = --[[---@type string]] formula\r\n if stringValue:sub(1, 1) == \"$\" then\r\n result = stringValue:sub(2)\r\n else\r\n local resolved = context.resolve(--[[---@type string]] formula)\r\n if not resolved then\r\n Logger.error(\"Couldn't resolve variable %s! This will lead to errors.\", formula)\r\n end\r\n result = resolved\r\n end\r\n elseif type(formula) == \"table\" then\r\n local parameters = --[[---@type gloom_Formula[] ]] formula\r\n local operator = --[[---@type string]] parameters[1]\r\n local args = TableUtil.copy(parameters)\r\n table.remove(args, 1)\r\n result = Operator[operator](args, context)\r\n end\r\n\r\n Logger.debug(\"%s\\nevaluated to %s\", formula, result)\r\n return result\r\nend\r\n\r\nreturn Formula\r\n\nend)\n__bundle_register(\"campaign-manager.CampaignManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Json = require(\"lib.Json\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ExtensionHandler = require(\"ExtensionHandler\")\n\nlocal Savefile = require(\"campaign-manager.Savefile\")\nlocal Cleanup = require(\"campaign-manager.Cleanup\")\nlocal Achievement = require(\"campaign-manager.Achievement\")\nlocal Character = require(\"campaign-manager.Character\")\nlocal Enhancement = require(\"campaign-manager.Enhancement\")\nlocal Event = require(\"campaign-manager.Event\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal ManagerOptions = require(\"campaign-manager.ManagerOptions\")\nlocal Party = require(\"campaign-manager.Party\")\nlocal Player = require(\"campaign-manager.Player\")\nlocal Preparation = require(\"campaign-manager.Preparation\")\nlocal Prosperity = require(\"campaign-manager.Prosperity\")\nlocal Retirement = require(\"campaign-manager.Retirement\")\nlocal Sanctuary = require(\"campaign-manager.Sanctuary\")\nlocal Scenario = require(\"campaign-manager.Scenario\")\nlocal Shop = require(\"campaign-manager.Shop\")\nlocal Unlocked = require(\"campaign-manager.Unlocked\")\n\nlocal CampaignManager = {}\n\nlocal Api = --[[---@type ScenarioApi]] ApiProvider(\"scenario\")\n\n---@overload fun()\n---@param campaign nil | gh_Savefile_any\nfunction CampaignManager.loadAll(campaign)\n local content = Savefile.load(campaign)\n if content then\n CampaignManager.setupEventHandlers(--[[---@type gh_Savefile]] content)\n EventManager.triggerEvent(EventType.Loaded.Start)\n end\nend\n\n---@param savefile gh_Savefile\nfunction CampaignManager.setupEventHandlers(savefile)\n Preparation.setup()\n Cleanup.setup(savefile)\n Achievement.setup(savefile)\n Enhancement.setup(savefile)\n Event.setup(savefile)\n ManagerOptions.setup(savefile)\n Party.setup(savefile)\n Player.setup(savefile)\n Prosperity.setup(savefile)\n Retirement.setup(savefile)\n Sanctuary.setup(savefile)\n Scenario.setup(savefile)\n Shop.setup()\n Unlocked.setup(savefile)\nend\n\n---@type string[]\nlocal requiredForSave = {}\n\nfunction CampaignManager.saveAll()\n requiredForSave = { EventType.Saved.Achievements,\n EventType.Saved.Items,\n EventType.Saved.Options, }\n\n for _, required in ipairs(requiredForSave) do\n EventManager.addHandler(required, function(savefile)\n CampaignManager.tryFinishSave(required, savefile)\n end)\n end\n\n CampaignManager.createSaveFile()\nend\n\n---@param event string\n---@param savefile gh_Savefile\nfunction CampaignManager.tryFinishSave(event, savefile)\n TableUtil.removeValue(requiredForSave, event)\n\n if TableUtil.isEmpty(requiredForSave) then\n CampaignManager.finishSave(savefile)\n end\nend\n\nfunction CampaignManager.finishSave(savefile)\n local updated = ExtensionHandler.call(\"onCampaignSave\", savefile)\n if updated then\n savefile = updated\n end\n\n Savefile.save(savefile)\n local scenarioTree = Scenario.saveScenarioTree(savefile)\n Savefile.saveScenarioTree(scenarioTree)\nend\n\nfunction CampaignManager.createSaveFile()\n local savefile = Savefile.create()\n CampaignManager.doCreateSaveFile(savefile)\nend\n\n---@param savefile gh_Savefile\nfunction CampaignManager.doCreateSaveFile(savefile)\n Achievement.saveAll(savefile)\n Enhancement.saveAll(savefile)\n Event.saveAll(savefile)\n Party.saveAll(savefile)\n Player.saveAll(savefile)\n Prosperity.saveAll(savefile)\n Scenario.saveAll(savefile)\n Unlocked.saveAll(savefile)\nend\n\nfunction CampaignManager.upgradeSavefile()\n local savefile = Savefile.load()\n if savefile then\n Savefile.save(--[[---@not nil]] savefile)\n end\nend\n\nApi.loadCampaign = function(campaign)\n CampaignManager.loadAll(campaign)\nend\n\n---@shape gh_Func_packCharacter\n---@field player number\n---@field position tts__Vector\n\n--- Called from the replaced \"Remove Character\" button\n---@param parameters gh_Func_packCharacter\n---@return gh_Save_Character\nfunction packCharacter(parameters)\n return Character.packCharacterBox(parameters.player, parameters.position)\nend\n\n---@shape gh_Func_unpackCharacter\n---@field guid GUID the GUID of the bag containing the character\n---@field character string the character data as a string (of type gh_Save_Character)\n\n--- Called from the \"Unpack\" button on packed characters\n---@param parameters gh_Func_unpackCharacter\nfunction unpackCharacter(parameters)\n Character.unpackInactiveCharacterBox(parameters.guid, Json.decode(parameters.character))\nend\n\n---@shape __unpackCharacterBox_Params\n---@field player integer\n---@field character gh_Save_Character\n\n---@param params __unpackCharacterBox_Params\nfunction __unpackCharacterBox(params)\n local character = params.character\n if params.characterData then\n character = params.characterData.character\n character.enhancements = params.characterData.enhancements\n end\n\n Savefile.setDefaultValues(\"character\", character)\n Character.unpackCharacterBox(params.player, character)\nend\n\nfunction saveCampaign()\n CampaignManager.saveAll()\nend\n\nfunction onImportCampaignClicked()\n CampaignManager.loadAll()\nend\n\nfunction onExportCampaignClicked()\n CampaignManager.saveAll()\nend\n\nreturn CampaignManager\n\nend)\n__bundle_register(\"campaign-manager.Unlocked\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Chain = require(\"lib.Chain\")\nlocal Object = require(\"lib.Object\")\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal Search = require(\"lib.Search\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal Game = require(\"campaign-manager.Game\")\nlocal ManagerOptions = require(\"campaign-manager.ManagerOptions\")\nlocal Sanctuary = require(\"campaign-manager.Sanctuary\")\nlocal Shop = require(\"campaign-manager.Shop\")\n\nlocal R = require(\"Resources\")\n\nlocal Unlocked = {}\n\nlocal shopLoaded = false\nlocal expansionLoaded = false\n\n---@param savefile gh_Savefile\nfunction Unlocked.setup(savefile)\n shopLoaded = false\n expansionLoaded = false\n\n Unlocked.loadOpeningConditions(savefile.unlocked.specialConditions)\n EventManager.addHandler(EventType.Placed.Treasure, function() Unlocked.loadTreasures(savefile.unlocked.treasures) end)\n\n EventManager.addHandler(EventType.Loaded.Start, function()\n if TableUtil.isNotEmpty(savefile.unlocked.specialConditions) then\n Component.placeOpeningConditions()\n end\n end)\n\n EventManager.addHandler(EventType.Loaded.Shop, function()\n shopLoaded = true\n Unlocked.loadItems(savefile)\n end)\n EventManager.addHandler(EventType.Placed.LockedCharacters, function()\n expansionLoaded = true\n Unlocked.loadItems(savefile)\n end)\n EventManager.addHandler(EventType.Loaded.Class.Start, function(className) Unlocked.mayLoadClasses(savefile, className) end)\nend\n\n---@param savefile gh_Savefile\n---@param className string\nfunction Unlocked.mayLoadClasses(savefile, className)\n local index = --[[---@not nil]] TableUtil.find(savefile.unlocked.classes, className)\n if index then\n Unlocked.loadClass(index, className)\n else\n EventManager.triggerEvent(EventType.Loaded.Class.Unlocked, className)\n end\nend\n\n---@param number number\n---@param className string\nfunction Unlocked.loadClass(number, className)\n Logger.info('Unlocking class %s', className)\n\n local lastBox = Component.lastCharacterBox()\n local beforeLastBox = Component.beforeLastCharacterBox()\n local delta = (lastBox.getPosition() - beforeLastBox.getPosition()) * number\n\n ---@param boxGuid GUID\n local function placeLockedClassBox(boxGuid)\n if getObjectFromGUID(boxGuid) then\n EventManager.triggerEvent(EventType.Loaded.Class.Unlocked, className)\n else\n Component.lockedClasses().takeObject({\n position = lastBox.getPosition() + delta,\n rotation = lastBox.getRotation(),\n smooth = false,\n guid = boxGuid,\n callback_function = function(obj)\n obj.setLock(true)\n Wait.condition(function() EventManager.triggerEvent(EventType.Loaded.Class.Unlocked, className) end, function() return Object.isLoaded(obj) end)\n end\n })\n end\n end\n\n local class = Game.findClass(className)\n if not class then\n local box = Search.inContainer(Component.lockedClasses(), { memo = className })\n if box then\n placeLockedClassBox((--[[---@not nil]] box).guid)\n elseif className == \"Bladeswarm\" then\n local secretBox = getObjectFromGUID(\"568093\")\n secretBox.addTag(R.Tag.CustomContent)\n secretBox.reload()\n Wait.condition(function()\n EventManager.triggerEvent(EventType.Loaded.Class.Unlocked, className)\n end, function() return Game.findClass(className) ~= nil end)\n else\n Logger.warn(\"Tried to unlock class '%s', but can't find its box inside the locked characters box.\", className)\n end\n elseif class.isSpecialClass then\n if class.specialSource then\n local source = --[[---@not nil]] class.specialSource\n if source.scriptObjectGuid then\n local scriptObj = getObjectFromGUID((--[[---@not nil]] source).scriptObjectGuid)\n if scriptObj then\n (--[[---@not nil]] scriptObj).call(source.scriptFunction)\n else\n Logger.error(\"Could not find the special source to load class '%s'\", className)\n end\n end\n end\n local box = --[[---@not nil]] getObjectFromGUID(class.boxGuid)\n if box then\n box.setPositionSmooth(lastBox.getPosition() + delta, false, true)\n box.setRotationSmooth(lastBox.getRotation(), false, true)\n box.setLock(true)\n EventManager.triggerEvent(EventType.Loaded.Class.Unlocked, className)\n end\n elseif not class.isStartingClass and not class.isFrosthavenClass then\n placeLockedClassBox(class.boxGuid)\n else\n Logger.debug(\"Tried to load class '%s' but seems like we didn't need to\", className)\n EventManager.triggerEvent(EventType.Loaded.Class.Unlocked, className)\n end\nend\n\n---@param savefile gh_Savefile\nfunction Unlocked.loadItems(savefile)\n if not expansionLoaded or not shopLoaded then\n return\n end\n\n Logger.info(\"Loading unlocked items\")\n\n local chain = Chain()\n local shopDeck = Component.shopDeck()\n\n ---@type table\n local extraLoaded = {}\n\n ---@param c seb_Chain\n ---@param item string\n ---@param itemCard tts__Object\n local function unpackMultiItem(c, item, itemCard)\n if Object.isDeck(itemCard) then\n local itemDeck = --[[---@type tts__Deck]] itemCard\n for index, _ in pairs(itemDeck.getObjects()) do\n if not extraLoaded[item] then\n extraLoaded[item] = 0\n else\n extraLoaded[item] = extraLoaded[item] + 1\n end\n\n shopDeck.putObject(--[[---@not nil]] itemDeck.takeObject({\n index = index,\n smooth = false\n }))\n end\n else\n shopDeck.putObject(itemCard)\n end\n\n c.proceed()\n end\n\n ---@param c seb_Chain\n ---@param item string\n local function unpackItem(c, item)\n if extraLoaded[item] ~= nil then\n extraLoaded[item] = extraLoaded[item] - 1\n c.proceed()\n else\n Shop.takeRewardItem(item,\n function(itemCard) unpackMultiItem(c, item, itemCard) end,\n function() c.proceed() end)\n end\n end\n\n for _, item in pairs(savefile.unlocked.items) do\n chain.add(function(c) unpackItem(c, item) end)\n end\n\n chain.add(function()\n EventManager.triggerEvent(EventType.Loaded.Items)\n end)\n\n chain.proceed()\nend\n\n---@param openingConditions gh_Save_Unlocked_Conditions\nfunction Unlocked.loadOpeningConditions(openingConditions)\n Logger.info(\"Loading opening conditions\")\n\n local sheet = --[[---@not nil]] Component.openingConditions()\n if sheet then\n Unlocked.setOpeningCondition(sheet, openingConditions.ancientTechnology, \"Ancient\")\n Unlocked.setOpeningCondition(sheet, openingConditions.drakeAided, \"Drake\")\n Unlocked.setOpeningCondition(sheet, openingConditions.lowReputation, \"RepN10\")\n Unlocked.setOpeningCondition(sheet, openingConditions.lowestReputation, \"RepN20\")\n Unlocked.setOpeningCondition(sheet, openingConditions.highReputation, \"Rep10\")\n Unlocked.setOpeningCondition(sheet, openingConditions.highestReputation, \"Rep20\")\n\n if openingConditions.retired then\n sheet.call(\"clickedToggle\", \"Retire\")\n end\n\n if openingConditions.donations then\n for i = 1, openingConditions.donations do\n Unlocked.setOpeningCondition(sheet, true, \"Donation\" .. i)\n end\n if openingConditions.donations >= 10 then\n Unlocked.setOpeningCondition(sheet, true, \"DonationFull\")\n end\n end\n end\n\n local ct = --[[---@not nil]] Component.campaignTracker()\n if ct then\n Unlocked.setOpeningCondition(ct, openingConditions.ancientTechnology, \"Cond1\")\n Unlocked.setOpeningCondition(ct, openingConditions.drakeAided, \"Cond2\")\n Unlocked.setOpeningCondition(ct, openingConditions.lowReputation, \"Cond6\")\n Unlocked.setOpeningCondition(ct, openingConditions.lowestReputation, \"Cond7\")\n Unlocked.setOpeningCondition(ct, openingConditions.highReputation, \"Cond4\")\n Unlocked.setOpeningCondition(ct, openingConditions.highestReputation, \"Cond5\")\n\n if openingConditions.retired then\n ct.call(\"clickedToggle\", \"Cond8\")\n end\n\n if openingConditions.donations then\n for i = 1, openingConditions.donations do\n Unlocked.setOpeningCondition(ct, true, \"Gold\" .. i)\n end\n if openingConditions.donations >= 10 then\n Unlocked.setOpeningCondition(ct, true, \"Cond3\")\n end\n end\n end\n\n if openingConditions.retired then\n Component.placeTownRecords()\n end\nend\n\n---@param sheet tts__Object\n---@param done nil | boolean\n---@param conditionName string\nfunction Unlocked.setOpeningCondition(sheet, done, conditionName)\n if done then\n sheet.call(\"clickedToggle\", conditionName)\n end\nend\n\n---@param treasures number[]\nfunction Unlocked.loadTreasures(treasures)\n local treasureDeck = --[[---@not nil]] Component.treasureDeck()\n\n for _, treasure in ipairs(treasures) do\n local treasureCard = Search.inContainer(treasureDeck, { name = tostring(treasure) })\n if not treasureCard then\n Logger.error(\"Treasure '%s' does not exist. Treasure won't be loaded.\", treasure)\n else\n treasureDeck.takeObject({\n index = (--[[---@not nil]] treasureCard).index,\n position = Component.safePosition(),\n callback_function = function(obj)\n Component.discardTreasure(--[[---@type tts__Card]] obj, ManagerOptions.keepDiscardedItems) end,\n })\n end\n end\n\n EventManager.triggerEvent(EventType.Loaded.Treasure)\nend\n\n---@param savefile gh_Savefile\nfunction Unlocked.saveAll(savefile)\n Unlocked.saveClasses(savefile.unlocked.classes)\n savefile.unlocked.sanctuary = Sanctuary.save()\n Unlocked.saveOpeningConditions(savefile.unlocked.specialConditions)\n Unlocked.saveTreasures(savefile.unlocked.treasures)\n Shop.save(savefile)\nend\n\n---@param classes string[]\nfunction Unlocked.saveClasses(classes)\n for className, info in pairs(Game.classes()) do\n local box = Component.classBox(className)\n if box ~= nil\n and not info.isStartingClass\n and not info.isFrosthavenClass\n and Component.isAboveTable(--[[---@not nil]] box)\n then\n table.insert(classes, className)\n end\n end\n table.sort(classes)\nend\n\n---@param openingConditions gh_Save_Unlocked_Conditions\nfunction Unlocked.saveOpeningConditions(openingConditions)\n local openingConditionsSheet = Component.openingConditions()\n if openingConditionsSheet then\n local buttons = (--[[---@not nil]] openingConditionsSheet).getTable(\"buttons\")\n openingConditions.ancientTechnology = buttons[\"Ancient\"].label ~= \"\"\n openingConditions.drakeAided = buttons[\"Drake\"].label ~= \"\"\n openingConditions.lowReputation = buttons[\"RepN10\"].label ~= \"\"\n openingConditions.lowestReputation = buttons[\"RepN20\"].label ~= \"\"\n openingConditions.highReputation = buttons[\"Rep10\"].label ~= \"\"\n openingConditions.highestReputation = buttons[\"Rep20\"].label ~= \"\"\n openingConditions.retired = buttons[\"Retire\"].label ~= \"\"\n local donations = 0\n for i = 1, 10 do\n if buttons[\"Donation\" .. i].label ~= \"\" then\n donations = donations + 1\n end\n end\n openingConditions.donations = donations\n else\n local ct = Component.campaignTracker()\n if ct then\n local buttons = (--[[---@not nil]] ct).getTable(\"buttons\")\n openingConditions.ancientTechnology = buttons[\"Cond1\"].label ~= \"\"\n openingConditions.drakeAided = buttons[\"Cond2\"].label ~= \"\"\n openingConditions.lowReputation = buttons[\"Cond6\"].label ~= \"\"\n openingConditions.lowestReputation = buttons[\"Cond7\"].label ~= \"\"\n openingConditions.highReputation = buttons[\"Cond4\"].label ~= \"\"\n openingConditions.highestReputation = buttons[\"Cond5\"].label ~= \"\"\n openingConditions.retired = buttons[\"Cond8\"].label ~= \"\"\n local donations = 0\n for i = 1, 10 do\n -- if buttons[\"Donation\" .. i].label ~= \"\" then\n if buttons[\"Gold\" .. i].label ~= \"\" then\n donations = donations + 1\n end\n end\n openingConditions.donations = donations\n end\n end\nend\n\n---@param treasures number[]\nfunction Unlocked.saveTreasures(treasures)\n local treasureDeck, _ = Component.treasureDeckData()\n\n if not treasureDeck then\n Logger.warn(\"Could not find the treasure deck in the gamebox. Can not save unlocked treasures.\")\n return\n end\n\n local allTreasures = --[[---@type table]] {}\n for i = 1, 96 do\n allTreasures[i] = true\n end\n\n local treasureCards = (--[[---@type tts__ContainerState]] treasureDeck).ContainedObjects\n for _, treasureCard in pairs(treasureCards) do\n local cardName = --[[---@not nil]] treasureCard.Nickname\n local treasureNumber = --[[---@not nil]] tonumber(cardName:sub(-2))\n allTreasures[treasureNumber] = false\n end\n\n for treasure, unlocked in pairs(allTreasures) do\n if unlocked then\n table.insert(treasures, treasure)\n end\n end\nend\n\nreturn Unlocked\n\nend)\n__bundle_register(\"campaign-manager.Shop\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Chain = require(\"lib.Chain\")\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal Search = require(\"lib.Search\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal Prosperity = require(\"campaign-manager.Prosperity\")\nlocal Shop = require(\"ui.Shop\")\n\nlocal ManagerShop = {}\n\nfunction ManagerShop.setup()\n EventManager.addHandler(EventType.Loaded.Prosperity, function() ManagerShop.load() end)\nend\n\nfunction ManagerShop.load()\n Logger.info(\"Loading shop from prosperity\")\n\n local shopDeck = Component.shopDeck()\n shopDeck.setRotation({ 0, 90, 0 })\n\n local chain = Chain()\n\n for i = 1, Prosperity.getLevel() do\n local itemDeck = Component.itemDeck(i)\n\n for _ = 1, itemDeck.getQuantity() do\n chain.add(function(c)\n itemDeck.takeObject({\n position = Component.safePosition(),\n smooth = false,\n callback_function = function(card)\n shopDeck.putObject(--[[---@type tts__Card]] card)\n c.proceed()\n end\n })\n end)\n end\n end\n chain.add(function() EventManager.triggerEvent(EventType.Loaded.Shop) end)\n\n chain.proceed()\nend\n\n---@param savefile gh_Savefile\nfunction ManagerShop.save(savefile)\n local items = savefile.unlocked.items\n\n local shopDeck = Component.shopDeck()\n if shopDeck.getQuantity() ~= 0 then\n ManagerShop.saveFromObjects(shopDeck.getData().ContainedObjects, items)\n end\n\n for player = 1, Component.playerCount() do\n ManagerShop.saveFromObjects(Component.playerObjects(player), items)\n end\n\n ManagerShop.saveFromObjects(Shop.getItems(), savefile.unlocked.items)\n\n table.sort(items)\n EventManager.triggerEvent(EventType.Saved.Items, savefile)\nend\n\n---@param objects seb_Object_Array\n---@param items string[]\nfunction ManagerShop.saveFromObjects(objects, items)\n for _, item in pairs(--[[---@type tts__Object[] ]] objects) do\n if Component.isRewardItemCard(item, Prosperity.getLevel()) then\n table.insert(items, Object.name(item))\n elseif Object.isContainer(item) then\n ManagerShop.saveFromObjects(Object.objects(--[[---@type seb_Object_Container]] item), items)\n end\n end\nend\n\n---@shape __gh_Shop_TakeItem_Parameter\n---@field callback nil | tts__ObjectCallbackFunction\n---@field toPosition nil | tts__VectorShape\n---@field toRotation nil | tts__VectorShape\n---@field skipShop nil | boolean\n---@field [any] nil\n\n---@param name string\n---@param parameters __gh_Shop_TakeItem_Parameter\n---@return boolean\nfunction ManagerShop.takeItem(name, parameters)\n ---@overload fun(deck): boolean\n ---@param deck tts__Bag | seb_WrappedDeck\n ---@return boolean\n local function takeFromItemDeck(deck, rotation)\n if deck.type == Object.Type.Deck and not (--[[---@type seb_WrappedDeck]] deck).isEmpty()\n or deck.type == Object.Type.Bag and (--[[---@type tts__Bag]] deck).getQuantity() > 0\n then\n local item, index = Search.inContainedObjects(deck, { name = name })\n if item then\n deck.takeObject({\n index = index,\n smooth = false,\n position = parameters.toPosition or Component.safePosition(),\n rotation = parameters.toRotation or rotation,\n callback_function = parameters.callback,\n })\n return true\n end\n end\n end\n\n if parameters.skipShop == nil or not parameters.skipShop then\n if takeFromItemDeck(Component.shopDeck()) then\n return true\n end\n end\n\n for i = 1, Component.TotalItemDecks do\n if takeFromItemDeck(Component.itemDeck(i)) then\n return true\n end\n end\n\n return false\nend\n\n---@param name string\n---@param callback tts__ObjectCallbackFunction\n---@return boolean\nfunction ManagerShop.takeRewardItem(name, callback, failCallback)\n local result = ManagerShop.takeItem(name, { callback = callback, skipShop = true })\n if not result then\n Logger.warn(\"No reward item %s found\", name)\n failCallback()\n end\n return true\nend\n\nreturn ManagerShop\n\nend)\n__bundle_register(\"campaign-manager.Prosperity\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal Logger = require(\"lib.Logger\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Prosperity = {}\r\n\r\n---@param savefile gh_Savefile\r\nfunction Prosperity.setup(savefile)\r\n EventManager.addHandler(EventType.Loaded.Decks, function() Prosperity.load(savefile.global.prosperity) end)\r\nend\r\n\r\n---@param value number\r\nfunction Prosperity.load(value)\r\n Logger.info(\"Loading prosperity\")\r\n local map = Component.map()\r\n for i = 1, value do\r\n map.call(\"clickedPros\", i)\r\n end\r\n EventManager.triggerEvent(EventType.Loaded.Prosperity)\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Prosperity.saveAll(savefile)\r\n local checkmarks = Component.map().getTable(\"Pros\")\r\n local maxValue = -1\r\n for i, checked in pairs(checkmarks) do\r\n if checked and tonumber(i) > maxValue then\r\n maxValue = --[[---@not nil]] tonumber(i)\r\n end\r\n end\r\n\r\n savefile.global.prosperity = maxValue + 1\r\nend\r\n\r\n---@return number\r\nfunction Prosperity.getLevel()\r\n return Component.map().call(\"getProsLevel\", {})\r\nend\r\n\r\nreturn Prosperity\r\n\nend)\n__bundle_register(\"campaign-manager.EventType\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventType = {\r\n Character = {\r\n Figurine = \"character_figurine_placed\",\r\n HpTrack = \"character_hp_placed\",\r\n Mat = \"character_mat_placed\",\r\n AttackModifiers = \"attack_modifiers_placed\",\r\n AttackModifiersPlayer = \"attack_modifiers_player_placed\",\r\n },\r\n Placed = {\r\n LockedCharacters = \"locked_classes_placed\",\r\n OpeningConditions = \"opening_conditions_placed\",\r\n Treasure = \"treasure_deck_placed\",\r\n RetirementSheet = \"retirement_sheet_placed\",\r\n SanctuarySticker = \"sanctuary_sticker_placed\",\r\n },\r\n Loaded = {\r\n Start = \"start_loading\",\r\n Options = \"options_loaded\",\r\n Shop = \"shop_loaded\",\r\n Items = \"items_loaded\",\r\n Class = {\r\n Start = \"class_ready\",\r\n Unlocked = \"class_unlocked\",\r\n Enhanced = \"class_enhanced\",\r\n },\r\n Prosperity = \"prosperity_loaded\",\r\n Treasure = \"treasure_loaded\",\r\n Decks = \"decks_loaded\",\r\n },\r\n Saved = {\r\n Achievements = \"achievements_saved\",\r\n Events = \"events_saved\",\r\n Items = \"items_saved\",\r\n Party = \"party_saved\",\r\n Options = \"options_saved\",\r\n }\r\n}\r\n\r\nreturn EventType\r\n\nend)\n__bundle_register(\"campaign-manager.Component\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal ObjectState = require(\"lib.ObjectData\")\nlocal Search = require(\"lib.Search\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Utils = require(\"lib.Utils\")\nlocal WrappedDeck = require(\"lib.WrappedDeck\")\n\nlocal R = require(\"Resources\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal Game = require(\"campaign-manager.Game\")\n\n---\n--- Handles everything about the different game components required to load and save a Gloomhaven-Campaign. Has methods\n--- to locate and place those components.\n---\nlocal Component = {}\n\n---@class __Component_this\nlocal this = {}\n\n---@type table\nComponent.Tag = {\n InactiveCharacter = \"Inactive Character\",\n PersonalQuest = \"PersonalQuest\",\n Item = \"Item\",\n}\n\n--- A list of GUIDs of relevant components.\nlocal Guid = {\n Gamebox = \"346ed5\",\n GuideBox = \"17e420\",\n ForgottenCirclesBox = \"5e28e8\",\n LockedClasses = \"1e6549\",\n ScenarioBook = \"f3404a\",\n RuleBook = \"c9d3f5\",\n OpeningConditions = \"30ae8e\",\n CampaignTracker = \"c6f357\",\n RetirementSheet = \"a30c94\",\n TownRecords = \"a6c771\",\n TreasureDeck = \"284016\",\n PartySheet = \"6d3de2\",\n LastBox = \"fdef02\", -- tinkerer\n BeforeLastBox = \"0507cf\", -- Spellweaver\n Map = \"9cc037\",\n AchievementBag = \"3ea749\", -- Bag containing the Achievement board\n SanctuarySticker = \"800847\",\n DeckMat = \"a75fcd\",\n FCDeckMat = \"324a12\",\n CityEventsDeckInitial = \"759349\",\n CityEventsDeck = \"f13efd\",\n RoadEventsDeckInitial = \"83de73\",\n RoadEventsDeck = \"f08b69\",\n CityMat = \"f3ffb7\",\n EventsMat = \"b53fb2\",\n PersonalQuestDeck = \"73a423\",\n RandomScenarioDeck = \"58a29f\",\n}\n\n--- A list of components with multiple GUIDs (e.g. where newer versions have different GUIDs) or where multiple objects exist.\nlocal Guids = {\n Zones = --[[---@type GUID[] ]] { \"49a4e0\", \"dac936\", \"62cd94\", \"963318\" },\n Zones5Players = --[[---@type GUID[] ]] { \"49a4e0\", \"dac936\", \"62cd94\", \"963318\", \"aa25be\" },\n AddPlayers = --[[---@type GUID[] ]] { \"3f0cda\", \"4d61da\", \"d0f661\", \"f98ff8\" },\n AchievementBoard = --[[---@type GUID[] ]] { \"43d5b8\", \"546b57\" },\n}\n\n--- A list of names to identify components by.\nlocal Names = {\n CharacterMat = \"Character Mat\",\n AbilityDeck = \"Advanced Abilities\",\n StartingAbilityDeck = \"Starting Abilities\",\n AttackModifierDeck = \"Attack Modifiers\",\n AchievementBoard = \"Achievements\",\n InactiveCharacter = \"Repacked\",\n PlayerMat = \"Player Mat\",\n CharacterSheet = \"Character Sheet\",\n AttackModifierCard = \"Attack Modifier\",\n Summons = \"Summons\",\n Trackers = \"Trackers\",\n}\n\nlocal TableHigh = 0.65\n\n--- A list of positions where components should be placed.\nlocal Positions = {\n -- relative to player zone\n QuestCard = --[[---@type tts__NumVectorShape]] { 1, 2, -19 },\n ItemUnequipped = --[[---@type tts__NumVectorShape]] { -7, 2, -7 },\n Hand = --[[---@type tts__NumVectorShape]] { 0, 3, 5 },\n SecondHand = --[[---@type tts__NumVectorShape]] { 0, 3, -2 },\n AbilityDeck = --[[---@type tts__NumVectorShape]] { -3.2, -2.26, -15.17 },\n CharacterSheet = --[[---@type tts__NumVectorShape]] { 6.31, 0, -17 },\n Figurine = --[[---@type tts__NumVectorShape]] { 0, 0, 23.23 },\n AttackModifierDeck = --[[---@type tts__NumVectorShape]] { -7, 0, -14.27 },\n AttackModifierDiscard = --[[---@type tts__NumVectorShape]] { -7, 0, -17.27 },\n CharacterBox = --[[---@type tts__NumVectorShape]] { 0.62, 0, -15.77 },\n PlayerMat = { 0, -2.57, 17.23 },\n CharacterMat = { 2.18, -3, 16.95 },\n ExtraRules = --[[---@type tts__NumVectorShape]] { 6.31, 0, -28 },\n ExtraTokens = --[[---@type tts__NumVectorShape]] { 6.31, 0, -11 },\n CustomConditions = { 8.31, 0, -11 },\n CustomDeck = --[[---@type tts__NumVectorShape]] { -7, 0, -20.27 },\n -- absolute\n AchievementBoard = --[[---@type tts__NumVectorShape]] { 87.26, 2, 26.12 },\n OpeningConditions = --[[---@type tts__NumVectorShape]] { 65.19, 1.70, -29.55 },\n RetirementSheet = --[[---@type tts__NumVectorShape]] { -77.96, 1.70, -17.90 },\n ScenarioBook = --[[---@type tts__NumVectorShape]] { 63.88, 2, 37.89 },\n RuleBook = --[[---@type tts__NumVectorShape]] { -64.31, 2, -41.68 },\n TownRecords = --[[---@type tts__NumVectorShape]] { -86.63, 2, -21.22 },\n TreasureDeck = --[[---@type tts__NumVectorShape]] { -75, 2, -5.3 },\n ScenarioDiscard = --[[---@type tts__NumVectorShape]] { 56.0, 2, -1.52 },\n CompletedQuests = --[[---@type tts__NumVectorShape]] { 56.0, 2, -12.5 },\n LootedTreasures = --[[---@type tts__NumVectorShape]]{ 0, 2, 17 },\n InactiveCharacter = --[[---@type tts__NumVectorShape]] { -58.63, 1.7, -3.03 },\n}\n\nlocal Scales = {\n InactiveCharacterZone = { 8.14, 10, 8.63 },\n CharacterBox = { 0.48, 0.48, 0.48 },\n}\n\n--- A list of snap points on the player mat, e.g. for item slots.\n---@type table\nlocal PlayerMatItemPositions = {\n Head = 15,\n Armor = 16,\n HandLeft = 17,\n HandRight = 18,\n Boots = 19,\n Bag1 = 12,\n Bag2 = 13,\n Bag3 = 14,\n Bag4 = 22,\n Bag5 = 23,\n Bag6 = 24,\n Bag7 = 25,\n Active1 = 6,\n Active2 = 7,\n Active3 = 8,\n Active4 = 9,\n}\n\n--- A list of snap points on the deck mat containing different decks.\n---@type table\nlocal DeckMatSnapPoints = {\n MinusOneDeck = 3,\n RoadEventsInitial = 4,\n RoadEvents = 5,\n CityEventsInitial = 6,\n CityEvents = 7,\n RandomScenarios = 8,\n Quests = 11,\n BattleGoals = 12,\n}\n\n--- A list of snap points on the city deck mat containing different decks.\nlocal CityMatSnapPoints = {\n Shop = 1,\n}\n\n--- Player color associated with each player\n---@type tts__PlayerColor[]\nComponent.PlayerColors = { \"Red\", \"White\", \"Blue\", \"Green\", \"Purple\" }\n\n--- Information about an event deck.\n---@shape gh_EventDeckInfo\n---@field name string\n---@field initialDeckAt nil | integer\n---@field deckAt integer | string\n---@field atEventMat integer\n---@field removedDeckAt tts__VectorShape\n---@field counts table\n\n---@shape gh_EventDeckInfo_Count\n---@field start integer\n---@field total integer\n\n---@type table\nComponent.EventDecks = {\n [\"city\"] = {\n name = \"City Events\",\n initialDeckAt = DeckMatSnapPoints.CityEventsInitial,\n deckAt = R.Tag.Component.CityEvents,\n atEventMat = 2,\n removedDeckAt = { 56, 2, 3.68 },\n counts = {\n base = {\n start = 1,\n total = 81,\n },\n fc = {\n start = 82,\n total = 9,\n }\n },\n },\n [\"road\"] = {\n name = \"Road Events\",\n initialDeckAt = DeckMatSnapPoints.RoadEventsInitial,\n deckAt = R.Tag.Component.RoadEvents,\n atEventMat = 1,\n removedDeckAt = { 56, 2, 10.93 },\n counts = {\n base = {\n start = 1,\n total = 69,\n },\n fc = {\n start = 82,\n total = 2,\n }\n },\n },\n [\"rift\"] = {\n name = \"Rift Events\",\n deckAt = R.Tag.Component.RiftEvents,\n atEventMat = 3,\n removedDeckAt = { 56, 2, 25.43 },\n counts = {\n fc = {\n start = 1,\n total = 20,\n }\n },\n }\n}\n\n---@shape gh_PlayerComponent\n---@field name nil | string\n---@field class nil | boolean\n---@field snapPoint nil | number\n---@field position nil | tts__VectorShape\n---@field rotation nil | tts__VectorShape\n---@field locked nil | boolean\n---@field playerBag nil | GUID[]\n---@field bag nil | GUID\n---@field event nil | string\n\n---@type table\nComponent.PlayerComponent = {\n AttackModifierDeck = {\n name = Names.AttackModifierDeck,\n position = Positions.AttackModifierDeck,\n rotation = { 0, 180, 0 },\n event = EventType.Character.AttackModifiers,\n },\n}\n\n---@type table\nComponent.ExtraPlayerComponent = {\n AttackModifierPlayerDeck = {\n snapPoint = 4,\n rotation = { 0, 180, 180 },\n playerBag = Guids.ModifierDecks,\n event = EventType.Character.AttackModifiersPlayer,\n },\n}\n\nComponent.ObjectColor = {\n Red = --[[---@type tts__ColorShape]] { 0.82, 0.25, 0.18, 1 },\n Yellow = --[[---@type tts__ColorShape]] { 0.75, 0.67, 0.56, 1 },\n Black = --[[---@type tts__ColorShape]] { 0.18, 0.047, 0.047, 1 },\n White = --[[---@type tts__ColorShape]] { 0.812, 0.70, 0.60, 1 },\n Invisible = --[[---@type tts__ColorShape]] { 0, 0, 0, 0 },\n}\n\nComponent.TotalItemDecks = 14\n\n---@overload fun(guidName: string, search: seb_Search_Full): tts__Object\n---@param guidName string\n---@param search seb_Search_Full\n---@param isError boolean\n---@return tts__Object\nlocal function findComponent(guidName, search, isError)\n if isError == nil then\n isError = true\n end\n local guid = Guid[guidName]\n\n local byId = getObjectFromGUID(guid)\n if byId ~= nil then\n return --[[---@not nil]] byId\n end\n\n local bySearch = Search.inAllObjects(search)\n if bySearch ~= nil then\n Guid[guidName] = (--[[---@not nil]] bySearch).getGUID()\n return --[[---@not nil]] bySearch\n else\n if isError then\n Logger.error(\"Fatal error! Can not find component %s\", guidName)\n end\n end\nend\n\n---@return GUID[]\nlocal function addPlayerButtonsGuids()\n return Guids.AddPlayers\nend\n\n--------------------------------------------------------------------------------\n--- Positioning ----------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@param player number\n---@param position tts__VectorShape\n---@return tts__Vector\nlocal function relativeToZone(player, position)\n return Component.playerZone(player).getPosition() + Vector(position)\nend\n\n---@param player integer\n---@param position tts__VectorShape\n---@return tts__Vector\nfunction Component.playerPosition(player, position)\n return relativeToZone(player, position)\nend\n\nlocal MaxColumn = 40\nlocal colCounter = 0\nlocal rowCounter = 0\n\n---@overload fun(): tts__VectorShape\n---@param elevation nil | number\n---@return tts__Vector\nfunction Component.safePosition(elevation)\n colCounter = colCounter + 1\n local offsetX = colCounter * 3\n local offsetZ = rowCounter * 3\n\n if colCounter > MaxColumn then\n colCounter = 0\n rowCounter = rowCounter + 1\n end\n\n return Vector(-23 + offsetX, TableHigh + (elevation or 0), -85 - offsetZ)\nend\n\n---@param player number\n---@param target number\n---@return tts__Vector\nfunction Component.handPosition(player, target)\n if target == 1 then\n return relativeToZone(player, Positions.Hand)\n end\n if target == 2 then\n return relativeToZone(player, Positions.SecondHand)\n end\nend\n\n---@param player number\n---@return tts__Vector\nfunction Component.questPosition(player)\n return relativeToZone(player, Positions.QuestCard)\nend\n\n---@param player number\n---@return tts__Vector\nfunction Component.characterSheetPosition(player)\n return relativeToZone(player, Positions.CharacterSheet)\nend\n\nfunction Component.playerMatPosition(player)\n return relativeToZone(player, Positions.PlayerMat)\nend\n\n---@param object tts__Object\n---@return boolean\nfunction Component.isAboveTable(object)\n return object.getPosition().y > 0\nend\n\n--------------------------------------------------------------------------------\n--- Gamebox-Content ------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@param container tts__Object\n---@param guid GUID\n---@param position tts__VectorShape\n---@param event nil | string\nlocal function takeFromContainer(container, guid, position, event)\n local object = getObjectFromGUID(guid)\n if object == nil then\n container.takeObject({\n guid = guid,\n smooth = false,\n position = position,\n rotation = { 0, 180, 0 },\n callback_function = function()\n if event then\n Wait.frames(function() EventManager.triggerEvent(--[[---@not nil]] event) end, 1)\n end\n end,\n })\n elseif event then\n EventManager.triggerEvent(--[[---@not nil]] event)\n end\nend\n\n---@overload fun(guid: GUID, position: tts__VectorShape)\n---@param guid GUID\n---@param position tts__VectorShape\n---@param event string\nlocal function takeFromGamebox(guid, position, event)\n takeFromContainer(Component.gamebox(), guid, position, event)\nend\n\n---@overload fun(guid: GUID, position: tts__VectorShape)\n---@param guid GUID\n---@param position tts__VectorShape\n---@param event nil | string\nlocal function takeFromGuideBox(guid, position, event)\n takeFromContainer(Component.guideBox(), guid, position, event)\nend\n\n---@return tts__Bag\nfunction Component.gamebox()\n return --[[---@type tts__Bag]] findComponent(\"Gamebox\",\n { type = Object.Type.Bag, name = \"Unlockable Content\" })\nend\n\n---@return tts__Bag\nfunction Component.guideBox()\n return --[[---@type tts__Bag]] getObjectFromGUID(Guid.GuideBox)\nend\n\n---@return tts__Bag\nfunction Component.forgottenCirclesBox()\n return --[[---@type tts__Bag]] findComponent(\"ForgottenCirclesBox\",\n { type = Object.Type.Bag, name = \"Gloomhaven: Forgotten Circles\" })\nend\n\n---@return tts__Bag\nfunction Component.lockedClasses()\n return --[[---@type tts__Bag]] getObjectFromGUID(Guid.LockedClasses)\nend\n\n---@return nil | tts__Deck\nfunction Component.treasureDeck()\n return --[[---@type nil | tts__Deck]] getObjectFromGUID(Guid.TreasureDeck)\nend\n\n---@return nil | (tts__ObjectState, number)\nfunction Component.treasureDeckData()\n return Search.inContainedObjects(Component.gamebox(), { guid = Guid.TreasureDeck })\nend\n\n---@return nil | tts__Object\nfunction Component.retirementSheet()\n return getObjectFromGUID(Guid.RetirementSheet)\nend\n\n---@return nil | tts__Object\nfunction Component.openingConditions()\n return getObjectFromGUID(Guid.OpeningConditions)\nend\n\nfunction Component.campaignTracker()\n return getObjectFromGUID(Guid.CampaignTracker)\nend\n\nfunction Component.placeScenarioBook()\n takeFromGuideBox(Guid.ScenarioBook, Positions.ScenarioBook)\nend\n\nfunction Component.placeRuleBook()\n takeFromGamebox(Guid.RuleBook, Positions.RuleBook)\nend\n\nfunction Component.placeTreasureDeck()\n takeFromGamebox(Guid.TreasureDeck, Positions.TreasureDeck, EventType.Placed.Treasure)\nend\n\nfunction Component.placeLockedCharacter()\n takeFromGamebox(Guid.LockedClasses, Component.safePosition(), EventType.Placed.LockedCharacters)\nend\n\nfunction Component.placeOpeningConditions()\n takeFromGamebox(Guid.OpeningConditions, Positions.OpeningConditions, EventType.Placed.OpeningConditions)\nend\n\nfunction Component.placeRetirementSheet()\n takeFromGamebox(Guid.RetirementSheet, Positions.RetirementSheet, EventType.Placed.RetirementSheet)\nend\n\nfunction Component.placeTownRecords()\n takeFromGamebox(Guid.TownRecords, Positions.TownRecords)\nend\n\nfunction Component.placeSanctuarySticker()\n Component.map().call('unseal')\n Utils.waitForObject(Guid.SanctuarySticker, function()\n EventManager.triggerEvent(EventType.Placed.SanctuarySticker)\n end)\nend\n\n---@param inactiveCharacter gh_Save_Character\n---@param callback tts__ObjectCallbackFunction\n---@param position integer | tts__NumVectorShape @Either a numbered spot or a absolute position.\nfunction Component.placeInactiveCharacter(inactiveCharacter, callback, position)\n local script = [[\nfunction onCollisionEnter(info)\n if info.collision_object.getName():find(\"]] .. Names.PlayerMat .. [[\") then\n Global.call(\"unpackCharacter\", { guid = self.getGUID(), character = self.script_state })\n end\nend]]\n\n local classBox = --[[---@not nil]] Component.classBox(inactiveCharacter.class)\n local characterBoxData = --[[---@type tts__ModelCustomState]] Component.characterBox(classBox)\n local characterBox = ObjectState.model({\n type = Object.ModelType.Bag,\n name = inactiveCharacter.name,\n description = \"Inactive Character Box - Drop on player mat to load - Do NOT alter content\",\n mesh = characterBoxData.CustomMesh.MeshURL,\n diffuse = characterBoxData.CustomMesh.DiffuseURL,\n tint = characterBoxData.ColorDiffuse,\n state = inactiveCharacter,\n script = script,\n tags = { Component.Tag.InactiveCharacter },\n }, {\n position = Component.safePosition(),\n rotation = { 0, 270, 0 },\n scale = Scales.CharacterBox,\n })\n\n ---@type tts__NumVectorShape\n local targetPosition\n if type(position) == \"number\" then\n targetPosition = TableUtil.copy(Positions.InactiveCharacter)\n targetPosition[1] = targetPosition[1] - ((position - 1) * 3)\n else\n targetPosition = --[[---@not number]] position\n end\n\n spawnObjectData({\n data = characterBox,\n position = targetPosition,\n callback_function = callback,\n })\nend\n\n--------------------------------------------------------------------------------\n--- General --------------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@return tts__Bag\nfunction Component.lastCharacterBox()\n return --[[---@type tts__Bag]] getObjectFromGUID(Guid.LastBox)\nend\n\n---@return tts__Bag\nfunction Component.beforeLastCharacterBox()\n return --[[---@type tts__Bag]] getObjectFromGUID(Guid.BeforeLastBox)\nend\n\n---@return tts__Object\nfunction Component.map()\n return --[[---@not nil]] getObjectFromGUID(Guid.Map)\nend\n\n---@return tts__Bag\nfunction Component.achievementsBag()\n return --[[---@type tts__Bag]] getObjectFromGUID(Guid.AchievementBag)\nend\n\n---@return GUID\nfunction Component.achievementBoardGuid()\n for _, achievementBoard in ipairs(Guids.AchievementBoard) do\n local board = getObjectFromGUID(achievementBoard)\n if board then\n return achievementBoard\n end\n end\n\n local achievementBag = Component.achievementsBag()\n local achievementBoard = --[[---@not nil]] Search.inContainer(achievementBag, { name = Names.AchievementBoard })\n return achievementBoard.guid\nend\n\n---@return tts__VectorShape\nfunction Component.achievementBoardPosition()\n return { 87.26, 2, 26.12 }\nend\n\n---@return nil | tts__Object\nfunction Component.sanctuarySticker()\n return getObjectFromGUID(Guid.SanctuarySticker)\nend\n\n---@param deckMat tts__Object\n---@param snapPoint integer\n---@return tts__Bag\nlocal function getCardBagFromMat(deckMat, snapPoint)\n local deckPosition = Utils.getSnapPosition(deckMat, snapPoint)\n local hit = Physics.cast({\n origin = deckPosition + Vector(0, 3, 0),\n direction = { 0, -1, 0 },\n type = 1,\n })\n if hit then\n for _, hitInfo in pairs(hit) do\n local hitObject = hitInfo.hit_object\n if hitObject.type == Object.Type.Bag then\n return (--[[---@type tts__Bag]] hitObject)\n end\n end\n end\nend\n\n--------------------------------------------------------------------------------\n--- City-Mat -------------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@return tts__Object\nfunction Component.cityMat()\n return --[[---@not nil]] getObjectFromGUID(Guid.CityMat)\nend\n\n---@param snapPoint integer\n---@return tts__Bag\nlocal function fromCityMat(snapPoint)\n return getCardBagFromMat(Component.cityMat(), snapPoint)\nend\n\n---@return tts__Bag\nfunction Component.shopDeck()\n return fromCityMat(CityMatSnapPoints.Shop)\nend\n\n---@param index number\n---@return tts__Bag\nfunction Component.itemDeck(index)\n return getCardBagFromMat(Component.cityMat(), index + 1)\nend\n\n--------------------------------------------------------------------------------\n--- Deck-Mat -------------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@return tts__Object\nfunction Component.deckMat()\n return --[[---@not nil]] getObjectFromGUID(Guid.DeckMat)\nend\n\n---@param snapPoint integer\n---@return tts__Bag\nlocal function fromDeckMat(snapPoint)\n return getCardBagFromMat(Component.deckMat(), snapPoint)\nend\n\n---@return tts__Bag\nfunction Component.questDeck()\n return --[[---@not seb_WrappedDeck]] fromDeckMat(DeckMatSnapPoints.Quests)\nend\n\n---@return tts__Bag\nfunction Component.randomScenarioDeck()\n return --[[---@not seb_WrappedDeck]] fromDeckMat(DeckMatSnapPoints.RandomScenarios)\nend\n\n---@param eventDeckInfo gh_EventDeckInfo\n---@return nil | tts__Bag\nfunction Component.initialEventDeck(eventDeckInfo)\n if eventDeckInfo.initialDeckAt then\n return --[[---@not seb_WrappedDeck]] fromDeckMat(--[[---@not nil]] eventDeckInfo.initialDeckAt)\n end\n return nil\nend\n\n---@param eventDeckInfo gh_EventDeckInfo\n---@return tts__Bag\nfunction Component.eventDeck(eventDeckInfo)\n if type(eventDeckInfo.deckAt) == \"number\" then\n return --[[---@not seb_WrappedDeck]] fromDeckMat(--[[---@type number]] eventDeckInfo.deckAt)\n end\n return --[[---@type tts__Bag]] R.getInstance(--[[---@type string]] eventDeckInfo.deckAt)\nend\n\n---@return tts__Bag\nfunction Component.minusOneDeck()\n return --[[---@not seb_WrappedDeck]] fromDeckMat(DeckMatSnapPoints.MinusOneDeck)\nend\n\n---@return tts__Bag\nfunction Component.battleGoalsDeck()\n return --[[---@not seb_WrappedDeck]] fromDeckMat(DeckMatSnapPoints.BattleGoals)\nend\n\n--------------------------------------------------------------------------------\n--- Event Mat ------------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@return tts__Object\nfunction Component.eventMat()\n return --[[---@not nil]] getObjectFromGUID(Guid.EventsMat)\nend\n\n---@param info gh_EventDeckInfo\n---@return seb_WrappedDeck\nfunction Component.eventMatDeck(info)\n local position = --[[---@not nil]] Utils.getSnapPosition(Component.eventMat(), info.atEventMat)\n return WrappedDeck(position)\nend\n\n--------------------------------------------------------------------------------\n--- Character specific ---------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@param className string\n---@return nil | tts__Bag\nfunction Component.classBox(className)\n return --[[---@type nil | tts__Bag]] getObjectFromGUID(Game.class(className).boxGuid)\nend\n\n--- Returns the contained character box within a class box for the given class.\n---@param classBox tts__Bag | tts__ContainerState\n---@return tts__ContainerState\nfunction Component.characterBox(classBox)\n if Object.isObject(classBox) then\n return --[[---@type tts__ContainerState]] (--[[---@type tts__Bag]] classBox).getData().ContainedObjects[1]\n end\n return --[[---@type tts__ContainerState]] (--[[---@type tts__ContainerState]] classBox).ContainedObjects[1]\nend\n\n---@param object seb_Object_Identifiable\n---@return boolean\nfunction Component.isPlayerMat(object)\n return Object.name(object):find(Names.PlayerMat) ~= nil\nend\n\n---@param object tts__ObjectState\n---@return boolean\nfunction Component.isCharacterSheet(object)\n return Object.name(object) == Names.CharacterSheet\nend\n\n---@param object seb_Object_Identifiable\nfunction Component.isAbilityDeck(object)\n if not (Object.isSimple(object) or Object.isDeck(--[[---@type seb_Object]] object)) then\n return false\n end\n local name = Object.name(object)\n return name:find(Names.AbilityDeck) ~= nil or name:find(Names.StartingAbilityDeck) ~= nil\nend\n\n---@param player number\n---@return seb_WrappedDeck\nfunction Component.abilityDeck(player)\n local abilityDeck = --[[---@type tts__Deck]] Search.inZone(Component.playerZone(player), { name = Names.AbilityDeck })\n return WrappedDeck(abilityDeck)\nend\n\n---@param player number\n---@return tts__Vector\nfunction Component.abilityDeckPosition(player)\n return relativeToZone(player, Positions.AbilityDeck)\nend\n\n---@param object seb_Object\n---@param className string\n---@return nil | string\nfunction Component.getAbilityNameForClass(object, className)\n local name = Object.name(object)\n\n if not Object.isCard(object)\n or not Object.description(object):find(className)\n or name:find(Names.AttackModifierCard)\n or not name:find(\"%d%d\")\n then\n return nil\n end\n\n local cardName = StringUtil.replace(name, \"%s?%([%d%/]+%)\")\n local nearest = Game.ability(className, cardName)\n if nearest then\n return (--[[---@not nil]] nearest).name\n end\nend\n\n---@param object seb_Object\n---@param className string\n---@return nil | string\nfunction Component.getAbilityNameForUnknownClass(object, className)\n local name = Object.name(object)\n\n if not Object.isCard(object)\n or not Object.description(object):find(className)\n or name:find(Names.AttackModifierCard)\n then\n return nil\n end\n\n return StringUtil.replace(name, \"%s?%([%d%/]+%)\")\nend\n\n---@param className string\n---@param abilityName string\nfunction Component.isStartingAbility(className, abilityName)\n local ability = --[[---@not nil]] Game.ability(className, abilityName)\n return ability.level == 1 or ability.level == \"X\"\nend\n\n---@return tts__Bag[]\nfunction Component.inactiveCharacters()\n return --[[---@type tts__Bag[] ]] getObjectsWithTag(Component.Tag.InactiveCharacter)\nend\n\n---@return tts__Bag[]\nfunction Component.packedCharacters()\n return --[[---@type tts__Bag[] ]] TableUtil.filter(getObjects(), function(obj)\n return Object.isBag(obj) and obj.getDescription() == Names.InactiveCharacter\n end)\nend\n\n---@param player number\n---@param positionName gh_Save_Character_Item_Position\n---@return tts__Vector\nfunction Component.itemPosition(player, positionName)\n if positionName then\n local snapPoint = PlayerMatItemPositions[positionName]\n if snapPoint then\n return --[[---@not nil]] Utils.getSnapPosition(Component.playerMat(player), snapPoint)\n end\n end\n return relativeToZone(player, Positions.ItemUnequipped)\nend\n\n---@param playerMat tts__Object\n---@param object tts__Object | tts__ObjectState\n---@return gh_Save_Character_Item_Position\nfunction Component.itemPositionName(playerMat, object)\n for name, snap in pairs(PlayerMatItemPositions) do\n local snapPosition = Utils.getSnapPosition(playerMat, snap)\n if snapPosition then\n (--[[---@not nil]] snapPosition).y = 0\n local objectPosition = Object.position(object)\n objectPosition.y = 0\n\n if (--[[---@not nil]] snapPosition):equals(objectPosition) then\n return name\n end\n end\n end\n\n return \"Unequipped\"\nend\n\n--------------------------------------------------------------------------------\n--- Player specific ------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@return number\nfunction Component.playerCount()\n return 4\nend\n\n---@return GUID[]\nfunction Component.playerZones()\n return Guids.Zones\nend\n\n---@param player number\n---@return tts__ScriptingTrigger\nfunction Component.playerZone(player)\n return --[[---@type tts__ScriptingTrigger]] getObjectFromGUID(Component.playerZones()[player])\nend\n\n--- Returns all objects of the given player. Only objects which center is within the planar bounds of the player zone\n--- are returned. This avoids duplicated objects that overlap multiple zones.\n---@param player number\n---@return tts__Object[]\nfunction Component.playerObjects(player)\n local zone = Component.playerZone(player)\n return TableUtil.filter(zone.getObjects(),\n function(object) return Utils.isCenterInZone(zone, object) end)\nend\n\n---@param player number\n---@param object tts__Object\n---@return boolean\nfunction Component.isPlayerObject(player, object)\n local zone = Component.playerZone(player)\n return Utils.isCenterInZone(zone, object)\nend\n\n---@param player number\n---@return tts__Object[]\nfunction Component.playerHand(player)\n return Player[Component.PlayerColors[player]].getHandObjects()\nend\n\n---@param playerMat tts__ObjectState\nfunction Component.adjustPlayerMatSnapPoints(playerMat)\n ---@param snaps tts__ObjectState_SnapPoint[]\n ---@param zPosition number\n local function addSnapPoint(snaps, zPosition)\n table.insert(snaps, {\n Position = { x = 1.52, y = 0.1, z = zPosition },\n Tags = {},\n })\n end\n\n local snaps = --[[---@not nil]] playerMat.AttachedSnapPoints\n addSnapPoint(snaps, -0.72)\n addSnapPoint(snaps, -0.24)\n addSnapPoint(snaps, 0.24)\n addSnapPoint(snaps, 0.72)\nend\n\n---@param player number\n---@param callback fun(): void\nfunction Component.loadPlayer(player, callback)\n local playerZoneGuid = Component.playerZones()[player]\n Component.addPlayerButton(player).call(\"onButtonClicked\")\n Utils.waitForObjectInZone(playerZoneGuid, { name = Names.PlayerMat }, function()\n callback()\n end)\nend\n\n---@param player number\n---@param characterBoxData tts__BagState\nfunction Component.placeCharacterBox(player, characterBoxData)\n spawnObjectData({\n data = characterBoxData,\n position = relativeToZone(player, Positions.CharacterBox),\n rotation = { 0, 270, 0 },\n })\nend\n\n---@param player number\n---@param character gh_Save_Character\n---@param object tts__ObjectState\n---@param component gh_PlayerComponent\nfunction Component.placePlayerComponent(player, character, object, component)\n local position\n if component.snapPoint then\n position = Utils.getSnapPosition(Component.playerMat(player), --[[---@not nil]] component.snapPoint)\n else\n position = relativeToZone(player, --[[---@not nil]] component.position)\n end\n\n spawnObjectData({\n data = object,\n position = position:add({ x = 0, y = 1, z = 0 }),\n rotation = component.rotation,\n callback_function = function(obj)\n if component.locked then\n obj.setLock(true)\n end\n if component.event then\n Wait.time(function()\n EventManager.triggerEvent(--[[---@not nil]] component.event, obj, player, character)\n end, 1)\n end\n end,\n })\nend\n\n---@param player number\n---@param character gh_Save_Character\n---@param contained tts__ObjectState\n---@param component gloom_Class_Extra\nfunction Component.placeExtraPlayerComponent(player, character, contained, component)\n if component.type == \"Figurine\" then\n Component.placePlayerComponent(player, character, contained, {\n position = Vector(--[[---@not nil]] Component.PlayerComponent.Figurine.position) + Vector(2, 0, 0),\n event = EventType.Character.Figurine,\n })\n elseif component.type == \"Card\" then\n Component.placePlayerComponent(player, character, contained, {\n position = Positions.Hand,\n })\n else\n ---@type table\n local positionByType = {\n Rules = Positions.ExtraRules,\n Token = Positions.ExtraTokens,\n Condition = Positions.CustomConditions,\n Deck = Positions.CustomDeck,\n }\n local position = positionByType[component.type]\n Component.placePlayerComponent(player, character, contained, {\n position = position,\n rotation = { 0, 180, 0 }\n })\n end\nend\n\n---@param player number\n---@param character gh_Save_Character\n---@param component gh_PlayerComponent\nfunction Component.placeAdditionalPlayerComponents(player, character, component)\n ---@type nil | tts__Bag\n local fromBag\n if component.playerBag then\n local bagGuid = (--[[---@not nil]] component.playerBag)[player]\n fromBag = --[[---@type tts__Bag]] getObjectFromGUID(bagGuid)\n elseif component.bag then\n fromBag = --[[---@type tts__Bag]] getObjectFromGUID(--[[---@not nil]] component.bag)\n end\n\n if fromBag then\n local object = (--[[---@not nil]] fromBag).getData().ContainedObjects[1]\n Component.placePlayerComponent(player, character, object, component)\n end\nend\n\n---@param index number\n---@return tts__Object\nfunction Component.addPlayerButton(index)\n return --[[---@not nil]] getObjectFromGUID(addPlayerButtonsGuids()[index])\nend\n\n---@return tts__Object\nfunction Component.playerMat(player)\n return --[[---@not nil]] Search.inZone(Component.playerZone(player), { name = Names.PlayerMat, isPattern = true })\nend\n\n---@param player number\n---@return nil | tts__Object\nfunction Component.characterMat(player)\n return Search.inZone(Component.playerZone(player), { name = Names.CharacterMat })\nend\n\n---@param player number\n---@return tts__Object\nfunction Component.characterSheet(player)\n return --[[---@not nil]] Search.inZone(Component.playerZone(player), { name = Names.CharacterSheet })\nend\n\n---@param player number\n---@return tts__Deck\nfunction Component.attackModifiers(player)\n local search = { name = Names.AttackModifierDeck, description = \"Player\", isPattern = true }\n return --[[---@type tts__Deck]] Search.inZone(Component.playerZone(player), search)\nend\n\n---@param player number\n---@return seb_WrappedDeck\nfunction Component.additionalAttackModifiers(player)\n for _, obj in ipairs(Component.playerZone(player).getObjects()) do\n if obj.type == Object.Type.Deck\n and obj.getName() == Names.AttackModifierDeck\n and obj.getDescription() ~= \"Player \" .. player\n then\n return WrappedDeck(--[[---@type tts__Deck]] obj)\n end\n end\nend\n\n---@param name string\n---@return string\nfunction Component.modifierNamePattern(name)\n return \"^\" .. Names.AttackModifierCard .. \" \" .. StringUtil.escapePattern(name) .. \"$\"\nend\n\n---@param player number\n---@return nil | string\nfunction Component.playerClass(player)\n local characterMat = Component.characterMat(player)\n if characterMat then\n return (--[[---@not nil]] characterMat).getDescription()\n end\n return nil\nend\n\n--------------------------------------------------------------------------------\n--- Specific cards -------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n--- Returns true if the given object is an item card.\n---@param object seb_Object\nfunction Component.isItemCard(object)\n return Object.isCard(object) and not Component.isPersonalQuestCard(object) and (\n Component.isItemCardDescription(Object.description(object)) or\n TableUtil.contains(Object.tags(object), Component.Tag.Item)\n )\nend\n\n--- Returns true if the given item card object is an item from the rewards or item design deck.\n---@param item seb_Object\n---@param prosperityLevel nil | number\nfunction Component.isRewardItemCard(item, prosperityLevel)\n if not Component.isItemCard(item) then\n return false\n end\n\n prosperityLevel = prosperityLevel or 1\n local description = Object.description(item)\n description = StringUtil.replace(description, \"red\")\n description = StringUtil.replace(description, \"blue\")\n\n local itemNumber = tonumber(description)\n if itemNumber then\n return itemNumber > Game.Items.Prosperity[--[[---@not nil]] prosperityLevel]\n end\n\n return true\nend\n\n--- Returns true if the given object description is a possible item card's description.\nfunction Component.isItemCardDescription(description)\n return description:find(\"^%d+$\")\n or description:find(\"^%d+ red$\")\n or description:find(\"^%d+ blue$\")\n\nend\n\n---@param object seb_Object\nfunction Component.remainInCharacterBox(object)\n return Object.hasTag(object, \"Keep Stored\")\nend\n\n---@param object seb_Object\nfunction Component.isPersonalQuestCard(object)\n return Object.isCard(object) and (TableUtil.contains(Object.tags(object), Component.Tag.PersonalQuest) or Game.Quests[Object.name(object)] ~= nil)\nend\n\n---@param object seb_Object_Identifiable\n---@return nil | gh_Game_Quest_Info\nfunction Component.getQuestInfo(object)\n local questName = Object.name(object)\n return Game.quest(questName)\nend\n\n--------------------------------------------------------------------------------\n--- Discard --------------------------------------------------------------------\n--------------------------------------------------------------------------------\n\n---@param card tts__Card\n---@param player number\n---@param keepDiscarded boolean\nfunction Component.discardAttackModifier(card, player, keepDiscarded)\n if keepDiscarded then\n local position = relativeToZone(player, Positions.AttackModifierDiscard)\n local discarded = WrappedDeck(position)\n discarded.setName(\"Discarded Attack Modifiers\")\n discarded.putObject(card)\n else\n card.destruct()\n end\nend\n\n---@param card tts__Card\n---@param keepDiscarded boolean\nfunction Component.discardScenario(card, keepDiscarded)\n if keepDiscarded then\n local discarded = WrappedDeck(Vector(Positions.ScenarioDiscard))\n discarded.setName(\"Discarded Random Dungeons\")\n discarded.putObject(card)\n else\n card.destruct()\n end\nend\n\n---@param card tts__Card\n---@param keepDiscarded boolean\nfunction Component.discardTreasure(card, keepDiscarded)\n if keepDiscarded then\n local discarded = WrappedDeck(Vector(Positions.LootedTreasures))\n discarded.setName(\"Looted Treasures\")\n discarded.putObject(card)\n else\n card.destruct()\n end\nend\n\n---@param card tts__Card\n---@param keepDiscarded boolean\nfunction Component.discardQuest(card, keepDiscarded)\n if keepDiscarded then\n local discarded = WrappedDeck(Vector(Positions.CompletedQuests))\n discarded.setName(\"Completed Quests\")\n discarded.putObject(card)\n else\n card.destruct()\n end\nend\n\nreturn Component\n\nend)\n__bundle_register(\"campaign-manager.Game\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\n\r\nlocal ClassApi = require(\"api.ClassApi\")\r\n\r\n--- Contains information about different parts of the Gloomhaven mod and Gloomhaven components (e.g.\r\n--- all classes and their perks).\r\nlocal Game = {}\r\n\r\n---@return gloom_Classes\r\nfunction Game.classes()\r\n return ClassApi.getClasses()\r\nend\r\n\r\n---@param className string\r\n---@return nil | gloom_Class\r\nfunction Game.class(className)\r\n local class = Game.findClass(className)\r\n if not class then\r\n Logger.warn(\"Tried to get class information for class '%s', but it doesn't exist!\", className)\r\n end\r\n return class\r\nend\r\n\r\n--- Same as Game.class, but doesn't issue a warning if no class is found.\r\n---@param className string\r\n---@return nil | gloom_Class\r\nfunction Game.findClass(className)\r\n return ClassApi.getClass(className)\r\nend\r\n\r\n---@param className string\r\n---@param abilityName string\r\n---@return nil | gloom_Ability\r\nfunction Game.ability(className, abilityName)\r\n return ClassApi.getAbility(className, abilityName)\r\nend\r\n\r\n---@param ability gloom_Ability\r\n---@return boolean\r\nfunction Game.isStartingAbility(ability)\r\n return ability.level == 1 or ability.level == \"X\"\r\nend\r\n\r\n---@param className string\r\n---@return string[]\r\nfunction Game.startingAbilities(className)\r\n local abilities = --[[---@type string[] ]] {}\r\n for abilityName, ability in pairs(Game.class(className).abilities) do\r\n if Game.isStartingAbility(ability) then\r\n table.insert(abilities, abilityName)\r\n end\r\n end\r\n return abilities\r\nend\r\n\r\n---@param quest gh_Save_Quest\r\n---@return gh_Game_Quest_Info\r\nfunction Game.quest(quest)\r\n local questByName = Game.Quests[--[[---@type string]] quest]\r\n if questByName then\r\n return {\r\n name = --[[---@type string]] quest,\r\n number = questByName.number,\r\n }\r\n end\r\n\r\n for questName, questInfo in pairs(Game.Quests) do\r\n if questInfo.number == quest then\r\n return {\r\n name = questName,\r\n number = questInfo.number,\r\n }\r\n end\r\n end\r\n return {\r\n name = quest,\r\n number = nil,\r\n }\r\nend\r\n\r\n--- XP Requirements per level\r\n---@type number[]\r\nGame.XpRequirements = { 0, 45, 95, 150, 210, 275, 345, 420, 500 }\r\n\r\nGame.Items = {\r\n ---Max item number per prosperity level\r\n ---@type number[]\r\n Prosperity = { 14, 21, 28, 35, 42, 49, 56, 63, 70 },\r\n ---@type table\r\n NegativeEffects = {\r\n [\"Hide Armor\"] = 2,\r\n [\"Heavy Greaves\"] = 1,\r\n [\"Chain Mail\"] = 3,\r\n [\"Heavy Basinet\"] = 2,\r\n [\"Splintmail\"] = 4,\r\n [\"Steel Sabatons\"] = 2,\r\n [\"Platemail\"] = 5,\r\n [\"Swordedge Armor\"] = 4,\r\n [\"Chain Hood\"] = 1,\r\n },\r\n ---@type table\r\n WithPerks = {\r\n [\"Second Skin\"] = {\r\n remove = { \"(-1)\", \"(-1)\" }\r\n }\r\n },\r\n}\r\n\r\n---@type table\r\nGame.RandomScenarios = {\r\n [63] = 0,\r\n [64] = 1,\r\n [65] = 2,\r\n [66] = 3,\r\n [67] = 4,\r\n [68] = 5,\r\n [69] = 6,\r\n [70] = 7,\r\n [71] = 8,\r\n}\r\n\r\n---@type table\r\nGame.Achievements = {\r\n [\"Ancient Technology\"] = {\r\n maxCount = 5\r\n },\r\n [\"Artifact: Cleansed\"] = {\r\n maxCount = 1\r\n },\r\n [\"Artifact: Lost\"] = {\r\n maxCount = 1\r\n },\r\n [\"Artifact: Recovered\"] = {\r\n maxCount = 1\r\n },\r\n [\"City Rule: Demonic\"] = {\r\n maxCount = 1\r\n },\r\n [\"City Rule: Economic\"] = {\r\n maxCount = 1\r\n },\r\n [\"City Rule: Militaristic\"] = {\r\n maxCount = 1\r\n },\r\n [\"End of Corruption\"] = {\r\n maxCount = 3\r\n },\r\n [\"End of Gloom\"] = {\r\n maxCount = 1\r\n },\r\n [\"End of the Invasion\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Annihilation of the Order\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Dead Invade\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Rift Neutralized\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Drake Aided\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Drake Slain\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Edge of Darkness\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Merchant Flees\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Power of Enhancement\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Voice Freed\"] = {\r\n maxCount = 1\r\n },\r\n [\"The Voice Silenced\"] = {\r\n maxCount = 1\r\n },\r\n [\"Water-Breathing\"] = {\r\n maxCount = 1\r\n },\r\n [\"Through the Portal\"] = {\r\n maxCount = 1\r\n },\r\n [\"Severed Ties\"] = {\r\n maxCount = 1\r\n },\r\n [\"Knowledge is Power\"] = {\r\n maxCount = 4\r\n },\r\n [\"Peril Adverted\"] = {\r\n maxCount = 4\r\n },\r\n [\"Pieces of an Artifact\"] = {\r\n maxCount = 3\r\n },\r\n [\"Mechanical Splendor\"] = {\r\n maxCount = 1\r\n },\r\n}\r\n\r\n---@type table\r\nGame.Quests = {\r\n [\"A Helping Hand\"] = {\r\n number = 532\r\n },\r\n [\"A Study of Anatomy\"] = {\r\n number = 514\r\n },\r\n [\"Aberrant Slayer\"] = {\r\n number = 523\r\n },\r\n [\"Augmented Abilities\"] = {\r\n number = 530\r\n },\r\n [\"Battle Legend\"] = {\r\n number = 519\r\n },\r\n [\"Elemental Samples\"] = {\r\n number = 531\r\n },\r\n [\"Eternal Wanderer\"] = {\r\n number = 518\r\n },\r\n [\"Fearless Stand\"] = {\r\n number = 524\r\n },\r\n [\"Finding the Cure\"] = {\r\n number = 513\r\n },\r\n [\"Goliath Toppler\"] = {\r\n number = 528\r\n },\r\n [\"Greed is Good\"] = {\r\n number = 512\r\n },\r\n [\"Implement of Light\"] = {\r\n number = 520\r\n },\r\n [\"Law Bringer\"] = {\r\n number = 515\r\n },\r\n [\"Merchant Class\"] = {\r\n number = 511\r\n },\r\n [\"Piety in All Things\"] = {\r\n number = 525\r\n },\r\n [\"Pounds of Flesh\"] = {\r\n number = 516\r\n },\r\n [\"Seeker of Xorn\"] = {\r\n number = 510\r\n },\r\n [\"Take Back the Trees\"] = {\r\n number = 521\r\n },\r\n [\"The Fall of Man\"] = {\r\n number = 529\r\n },\r\n [\"The Perfect Poison\"] = {\r\n number = 533\r\n },\r\n [\"The Thin Places\"] = {\r\n number = 522\r\n },\r\n [\"Trophy Hunt\"] = {\r\n number = 517\r\n },\r\n [\"Vengeance\"] = {\r\n number = 526\r\n },\r\n [\"Zealot of the Blood God\"] = {\r\n number = 527\r\n },\r\n}\r\n\r\n--- List of treasure numbers per scenario.\r\n---@type table\r\nGame.Scenario = {\r\n [1] = { 7 },\r\n [2] = { 67 },\r\n [3] = { 65 },\r\n [4] = { 38, 46 },\r\n [5] = { 4, 28 },\r\n [6] = { 50 },\r\n [7] = { },\r\n [8] = { 51 },\r\n [9] = { },\r\n [10] = { 11 },\r\n [11] = { 5 },\r\n [12] = { 34 },\r\n [13] = { 10 },\r\n [14] = { 26 },\r\n [15] = {},\r\n [16] = { 1 },\r\n [17] = { 71 },\r\n [18] = { 63 },\r\n [19] = { 17 },\r\n [20] = { 60 },\r\n [21] = { 15 },\r\n [22] = { 21 },\r\n [23] = { 39, 72 },\r\n [24] = { 70 },\r\n [25] = { 58 },\r\n [26] = { 66 },\r\n [27] = {},\r\n [28] = { 32 },\r\n [29] = { 41 },\r\n [30] = {},\r\n [31] = { 69 },\r\n [32] = {},\r\n [33] = {},\r\n [34] = { 23 },\r\n [35] = { 61 },\r\n [36] = { 2 },\r\n [37] = { 49 },\r\n [38] = { 29 },\r\n [39] = { 73 },\r\n [40] = { 47 },\r\n [41] = { 24 },\r\n [42] = { 30, 55 },\r\n [43] = { 35 },\r\n [44] = {},\r\n [45] = { 74 },\r\n [46] = { 48 },\r\n [47] = { 18, 57 },\r\n [48] = { 64 },\r\n [49] = { 44 },\r\n [50] = {},\r\n [51] = { 56 },\r\n [52] = {},\r\n [53] = { 31 },\r\n [54] = { 25 },\r\n [55] = {},\r\n [56] = { 45 },\r\n [57] = { 3, 22 },\r\n [58] = {},\r\n [59] = {},\r\n [60] = {},\r\n [61] = {},\r\n [62] = { 59 },\r\n [63] = { 12 },\r\n [64] = { 9 },\r\n [65] = {},\r\n [66] = { 16, 36 },\r\n [67] = { 14 },\r\n [68] = { 33 },\r\n [69] = {},\r\n [70] = { 6 },\r\n [71] = {},\r\n [72] = {},\r\n [73] = {},\r\n [74] = { 20 },\r\n [75] = { 53 },\r\n [76] = { 75 },\r\n [77] = {},\r\n [78] = {},\r\n [79] = { 52 },\r\n [80] = {},\r\n [81] = { 68 },\r\n [82] = { 62 },\r\n [83] = {},\r\n [84] = { 42 },\r\n [85] = {},\r\n [86] = {},\r\n [87] = { 40 },\r\n [88] = { 8, 37 },\r\n [89] = { 13, 27, 43 },\r\n [90] = { 19 },\r\n [91] = {},\r\n [92] = {},\r\n [93] = { 54 },\r\n [94] = {},\r\n [95] = {},\r\n [96] = { 91 },\r\n [97] = { 96 },\r\n [98] = { 79 },\r\n [99] = { 95 },\r\n [100] = { 76, 85 },\r\n [101] = { 93 },\r\n [102] = { 77, 86 },\r\n [103] = { 81 },\r\n [104] = { 87 },\r\n [105] = { 83, 88 },\r\n [106] = {},\r\n [107] = { 78, 90 },\r\n [108] = {},\r\n [109] = { 80, 94 },\r\n [110] = { 84 },\r\n [111] = { 82 },\r\n [112] = {},\r\n [113] = {},\r\n [114] = {},\r\n [115] = { 96 },\r\n\r\n [301] = {},\r\n}\r\n\r\nreturn Game\r\n\nend)\n__bundle_register(\"lib.Utils\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Utils = require(\"sebaestschjin-tts.Utils\")\r\n\r\nreturn Utils\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Utils\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Search = require(\"sebaestschjin-tts.Search\")\r\n\r\nlocal Utils = {}\r\n\r\n---@overload fun(guid: GUID, callback: (fun(): void), timeout: number): void\r\n---@overload fun(guid: GUID, callback: (fun(): void)): void\r\n---@param guid GUID\r\n---@param callback fun(): void\r\n---@param timeout number\r\n---@param timeoutCallback fun(): void\r\nfunction Utils.waitForObject(guid, callback, timeout, timeoutCallback)\r\n local waiter = function()\r\n return getObjectFromGUID(guid) ~= nil\r\n and (--[[---@not nil]] getObjectFromGUID(guid)).spawning == false\r\n end\r\n Wait.condition(callback, waiter, timeout, timeoutCallback)\r\nend\r\n\r\n---@overload fun(zone: GUID, search: seb_Search_Full, callback: fun(): void): void\r\n---@param zone GUID\r\n---@param search seb_Search_Full\r\n---@param callback fun(): void\r\n---@param timeout number\r\n---@param timeoutCallback fun(): void\r\nfunction Utils.waitForObjectInZone(zone, search, callback, timeout, timeoutCallback)\r\n local waiter = function()\r\n local obj = Search.inZone(--[[---@type tts__ScriptingTrigger]] getObjectFromGUID(zone), search)\r\n return obj ~= nil and not (--[[---@type tts__Object]] obj).spawning\r\n end\r\n Wait.condition(callback, waiter, timeout, timeoutCallback)\r\nend\r\n\r\n---@param object tts__Object\r\n---@param snapPoint number\r\n---@return nil | tts__Vector\r\nfunction Utils.getSnapPosition(object, snapPoint)\r\n local snapPoints = object.getSnapPoints()\r\n if not snapPoints[snapPoint] then\r\n return nil\r\n end\r\n\r\n return object.positionToWorld(snapPoints[snapPoint].position)\r\nend\r\n\r\n---@param zone tts__ScriptingTrigger\r\n---@param object tts__Object\r\n---@return boolean\r\nfunction Utils.isCenterInZone(zone, object)\r\n local width = zone.getScale().x\r\n local height = zone.getScale().z\r\n local zoneCenter = zone.getBounds().center\r\n local left = zoneCenter.x - width / 2\r\n local right = zoneCenter.x + width / 2\r\n local top = zoneCenter.z + height / 2\r\n local bottom = zoneCenter.z - height / 2\r\n local objectCenter = object.getBounds().center\r\n\r\n return objectCenter.x > left and objectCenter.x < right and objectCenter.z > bottom and objectCenter.z < top\r\nend\r\n\r\nreturn Utils\r\n\nend)\n__bundle_register(\"lib.ObjectData\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ObjectData = require(\"sebaestschjin-tts.ObjectState\")\r\n\r\nreturn ObjectData\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.ObjectState\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Json = require(\"ge_tts.Json\")\r\nlocal Object = require(\"sebaestschjin-tts.Object\")\r\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\n\r\nlocal ObjectState = {}\r\n\r\n---@type number\r\nlocal currentDeckId = 0\r\n\r\n---@class __ObjectState_this\r\nlocal this = {}\r\n\r\n---@return number\r\nlocal function nextDeckId()\r\n currentDeckId = currentDeckId + 1\r\n return currentDeckId\r\nend\r\n\r\n---@param value nil | boolean\r\n---@param default boolean\r\n---@return boolean\r\nlocal function bool(value, default)\r\n if value == nil then\r\n return default\r\n end\r\n return --[[---@not nil]] value\r\nend\r\n\r\n---@param objectType string\r\n---@param object seb_CustomObject\r\n---@param transform nil | seb_Transform\r\n---@return tts__ObjectState\r\nfunction ObjectState.object(objectType, object, transform)\r\n ---@type string | nil\r\n local scriptState\r\n if object.state ~= nil then\r\n if type(object.state) == \"string\" then\r\n scriptState = --[[---@type string]] object.state\r\n else\r\n scriptState = Json.encode(--[[---@type table]] object.state)\r\n end\r\n end\r\n\r\n return {\r\n Name = objectType,\r\n Nickname = object.name,\r\n Description = object.description,\r\n Locked = object.locked,\r\n Grid = object.snapToGrid,\r\n Transform = ObjectState.transform(transform),\r\n ColorDiffuse = ObjectState.color(object.tint),\r\n LuaScript = object.script,\r\n LuaScriptState = scriptState,\r\n Tags = object.tags,\r\n }\r\nend\r\n\r\n---@overload fun(bag: seb_CustomObject_Bag): tts__BagState\r\n---@param bag seb_CustomObject_Bag\r\n---@param transform seb_Transform\r\n---@return tts__BagState\r\nfunction ObjectState.bag(bag, transform)\r\n local bagState = --[[---@type tts__BagState]] ObjectState.object(Object.Name.Bag, bag, transform)\r\n\r\n bagState.ContainedObjects = bag.objects or {}\r\n\r\n return bagState\r\nend\r\n\r\n---@overload fun(deck: seb_CustomObject_DeckCustom): tts__DeckCustomState\r\n---@param deck seb_CustomObject_DeckCustom\r\n---@return tts__DeckCustomState\r\nfunction ObjectState.deckCustom(deck, transform)\r\n local deckState = --[[---@type tts__DeckCustomState]] ObjectState.object(Object.Name.Deck, deck, transform)\r\n\r\n deckState.DeckIDs = {}\r\n deckState.ContainedObjects = {}\r\n deckState.CustomDeck = {}\r\n\r\n if deck.deck then\r\n local deckId = nextDeckId()\r\n deckState.CustomDeck = {\r\n [deckId] = {\r\n FaceURL = (--[[---@not nil]] deck.deck).image,\r\n BackURL = (--[[---@not nil]] deck.deck).imageBack,\r\n NumWidth = (--[[---@not nil]] deck.deck).width,\r\n NumHeight = (--[[---@not nil]] deck.deck).height,\r\n BackIsHidden = (--[[---@not nil]] deck.deck).backIsHidden,\r\n UniqueBack = false,\r\n }\r\n }\r\n\r\n for i = 1, (--[[---@not nil]] deck.deck).number do\r\n local cardId = --[[---@not nil]] tonumber(string.format(\"%d%02d\", deckId, i - 1))\r\n table.insert(deckState.ContainedObjects, {\r\n Name = Object.Name.Card,\r\n CardID = cardId,\r\n CustomDeck = deckState.CustomDeck,\r\n Transform = ObjectState.transform(transform),\r\n })\r\n table.insert(deckState.DeckIDs, cardId)\r\n end\r\n end\r\n\r\n if deck.cards then\r\n for _, card in ipairs(--[[---@not nil]] deck.cards) do\r\n table.insert(deckState.ContainedObjects, card)\r\n table.insert(deckState.DeckIDs, card.CardID)\r\n for id, customDeck in pairs(card.CustomDeck) do\r\n deckState.CustomDeck[id] = customDeck\r\n end\r\n end\r\n end\r\n\r\n return deckState\r\nend\r\n\r\n---@overload fun(card: seb_CustomObject_CardCustom): tts__CardCustomState\r\n---@param card seb_CustomObject_CardCustom\r\n---@param transform seb_Transform\r\n---@return tts__CardCustomState\r\nfunction ObjectState.cardCustom(card, transform)\r\n local cardState = --[[---@type tts__CardCustomState]] ObjectState.object(Object.Name.Card, card, transform)\r\n\r\n local deckId = nextDeckId()\r\n cardState.CardID = deckId * 100\r\n cardState.CustomDeck = {\r\n [deckId] = {\r\n FaceURL = card.image,\r\n BackURL = card.imageBack or card.image,\r\n NumWidth = 1,\r\n NumHeight = 1,\r\n BackIsHidden = true,\r\n UniqueBack = false,\r\n }\r\n }\r\n\r\n return cardState\r\nend\r\n\r\n---@overload fun(model: seb_CustomObject_Model): tts__ModelCustomState\r\n---@param model seb_CustomObject_Model\r\n---@param transform seb_Transform\r\n---@return tts__ModelCustomState\r\nfunction ObjectState.model(model, transform)\r\n local name = Object.Name.Model\r\n if model.type == Object.ModelType.Bag or model.type == Object.ModelType.Infinite then\r\n name = Object.Name.ModelBag\r\n end\r\n\r\n local modelState = --[[---@type tts__ModelCustomState]] ObjectState.object(name, model, transform)\r\n\r\n modelState.CustomMesh = {\r\n MeshURL = model.mesh,\r\n DiffuseURL = model.diffuse,\r\n ColliderURL = model.collider,\r\n Convex = true,\r\n MaterialIndex = model.material,\r\n TypeIndex = model.type,\r\n CastShadow = true,\r\n }\r\n\r\n return modelState\r\nend\r\n\r\n---@overload fun(token: seb_CustomObject_Token): tts__TokenState\r\n---@param token seb_CustomObject_Token\r\n---@param transform seb_Transform\r\n---@return tts__TokenState\r\nfunction ObjectState.token(token, transform)\r\n local tokenState = --[[---@type tts__TokenState]] ObjectState.object(Object.Name.Token, token, transform)\r\n\r\n tokenState.CustomImage = {\r\n ImageURL = token.image,\r\n CustomToken = {\r\n Stackable = token.stackable,\r\n MergeDistancePixels = token.mergeDistance or 15,\r\n Thickness = token.thickness or 0.2\r\n }\r\n }\r\n\r\n return tokenState\r\nend\r\n\r\n---@overload fun(token: seb_CustomObject_Tile): tts__TileState\r\n---@param tile seb_CustomObject_Tile\r\n---@param transform seb_Transform\r\n---@return tts__TileState\r\nfunction ObjectState.tile(tile, transform)\r\n local tokenState = --[[---@type tts__TileState]] ObjectState.object(Object.Name.Tile, tile, transform)\r\n\r\n tokenState.CustomImage = {\r\n ImageURL = tile.image,\r\n ImageSecondaryURL = tile.imageBottom,\r\n CustomTile = {\r\n Type = tile.type or Object.TileType.Box,\r\n Stackable = tile.stackable,\r\n Thickness = tile.thickness or 0.5,\r\n Stretch = bool(tile.stretch, true),\r\n }\r\n }\r\n\r\n return tokenState\r\nend\r\n\r\n---@overload fun(zone: seb_CustomObject_LayoutZone): tts__LayoutZoneState\r\n---@param zone seb_CustomObject_LayoutZone\r\n---@param transform seb_Transform\r\n---@return tts__LayoutZoneState\r\nfunction ObjectState.layoutZone(zone, transform)\r\n local layoutZoneState = --[[---@type tts__LayoutZoneState]] ObjectState.object(Object.Name.LayoutZone, zone, transform)\r\n\r\n layoutZoneState.LayoutZone = {\r\n Options = {\r\n TriggerForFaceUp = bool(zone.includeFaceUp, true),\r\n TriggerForFaceDown = bool(zone.includeFaceDown, true),\r\n TriggerForNonCards = bool(zone.includeNonCards, false),\r\n SplitAddedDecks = bool(zone.splitDecks, true),\r\n CombineIntoDecks = bool(zone.combineIntoDecks, false),\r\n CardsPerDeck = zone.combineCardsPerDeck or 0,\r\n Direction = zone.direction,\r\n NewObjectFacing = zone.facing or 1,\r\n HorizontalGroupPadding = zone.paddingHorizontal or 1,\r\n VerticalGroupPadding = zone.paddingVertical or 1,\r\n StickyCards = bool(zone.stickyCards, false),\r\n InstantRefill = bool(zone.instantRefill, false),\r\n Randomize = bool(zone.randomize, false),\r\n ManualOnly = bool(zone.manualOnly, false),\r\n MeldDirection = zone.groupDirection or 0,\r\n MeldSort = zone.groupSort or 3,\r\n MeldReverseSort = bool(zone.groupSortReverse, false),\r\n MeldSortExisting = bool(zone.groupSortExisting, false),\r\n HorizonalSpread = zone.spreadHorizontal or 0.6,\r\n VerticalSpread = zone.spreadVertical or 0,\r\n MaxObjectsPerGroup = zone.maxObjectsPerGroup or 13,\r\n AlternateDirection = bool(zone.alternateDirection, false),\r\n MaxObjectsPerNewGroup = zone.maxObjectsPerNewGroup or 0,\r\n AllowSwapping = bool(zone.allowSwapping, false),\r\n }\r\n }\r\n\r\n return layoutZoneState\r\nend\r\n\r\n--- Taken from ge_tts.ObjectUtils, but I don't want to always have the onObjectSpawn event registered.\r\n---@param transform nil | seb_Transform\r\n---@return tts__ObjectState_Transform\r\nfunction ObjectState.transform(transform)\r\n ---@type tts__ObjectState_Transform\r\n local state = {}\r\n\r\n if not transform then\r\n transform = {}\r\n end\r\n\r\n ---@type nil | tts__VectorShape\r\n local position = (--[[---@not nil]] transform).position\r\n if not position then\r\n position = { 0, 3, 0 }\r\n end\r\n state.posX = (--[[---@type tts__CharVectorShape]] position).x or (--[[---@type tts__NumVectorShape]] position)[1]\r\n state.posY = (--[[---@type tts__CharVectorShape]] position).y or (--[[---@type tts__NumVectorShape]] position)[2]\r\n state.posZ = (--[[---@type tts__CharVectorShape]] position).z or (--[[---@type tts__NumVectorShape]] position)[3]\r\n\r\n ---@type nil | tts__VectorShape\r\n local rotation = (--[[---@not nil]] transform).rotation\r\n if not rotation then\r\n rotation = { 0, 180, 0 }\r\n end\r\n state.rotX = (--[[---@type tts__CharVectorShape]] rotation).x or (--[[---@type tts__NumVectorShape]] rotation)[1]\r\n state.rotY = (--[[---@type tts__CharVectorShape]] rotation).y or (--[[---@type tts__NumVectorShape]] rotation)[2]\r\n state.rotZ = (--[[---@type tts__CharVectorShape]] rotation).z or (--[[---@type tts__NumVectorShape]] rotation)[3]\r\n\r\n ---@type nil | tts__VectorShape\r\n local scale = (--[[---@not nil]] transform).scale\r\n if not scale then\r\n scale = { 1, 1, 1 }\r\n end\r\n state.scaleX = (--[[---@type tts__CharVectorShape]] scale).x or (--[[---@type tts__NumVectorShape]] scale)[1]\r\n state.scaleY = (--[[---@type tts__CharVectorShape]] scale).y or (--[[---@type tts__NumVectorShape]] scale)[2]\r\n state.scaleZ = (--[[---@type tts__CharVectorShape]] scale).z or (--[[---@type tts__NumVectorShape]] scale)[3]\r\n\r\n return state\r\nend\r\n\r\n---@param color nil | tts__ColorShape\r\n---@return tts__CharColorShape\r\nfunction ObjectState.color(color)\r\n ---@type tts__CharColorShape\r\n local colorShape = { r = 1, g = 1, b = 1, a = 1 }\r\n\r\n if not color then\r\n return colorShape\r\n end\r\n\r\n colorShape.r = (--[[---@type tts__CharColorShape]] color).r or (--[[---@type tts__NumColorShape]] color)[1]\r\n colorShape.g = (--[[---@type tts__CharColorShape]] color).g or (--[[---@type tts__NumColorShape]] color)[2]\r\n colorShape.b = (--[[---@type tts__CharColorShape]] color).b or (--[[---@type tts__NumColorShape]] color)[3]\r\n\r\n if (--[[---@type tts__CharColorShape]] color).a ~= nil then\r\n colorShape.a = (--[[---@type tts__CharColorShape]] color).a\r\n elseif #colorShape == 4 then\r\n colorShape.a = (--[[---@type tts__NumColorShape]] color)[4]\r\n end\r\n\r\n return colorShape\r\nend\r\n\r\n---@param object tts__ObjectState\r\n---@param decal tts__Object_DecalParameters\r\nfunction ObjectState.addDecal(object, decal)\r\n local attached = object.AttachedDecals\r\n if not attached then\r\n attached = --[[---@type tts__ObjectState_Decal[] ]]{}\r\n object.AttachedDecals = attached\r\n end\r\n\r\n ---@type tts__ObjectState_Decal\r\n local decalData = {\r\n Transform = ObjectState.transform({\r\n position = decal.position,\r\n rotation = decal.rotation,\r\n scale = decal.scale,\r\n }),\r\n CustomDecal = {\r\n Name = decal.name,\r\n ImageURL = decal.url,\r\n }\r\n }\r\n table.insert(--[[---@not nil]] attached, decalData)\r\nend\r\n\r\n---@param object tts__ObjectState\r\n---@param tag tts__Object_Tag\r\nfunction ObjectState.addTag(object, tag)\r\n if not object.Tags then\r\n object.Tags = { tag }\r\n elseif not TableUtil.contains(object.Tags, tag) then\r\n table.insert(--[[---@not nil]] object.Tags, tag)\r\n end\r\nend\r\n\r\n---@param object tts__ObjectState\r\n---@param useGravity boolean\r\nfunction ObjectState.setUseGravity(object, useGravity)\r\n if not object.Rigidbody then\r\n object.Rigidbody = {}\r\n end\r\n object.Rigidbody.UseGravity = useGravity\r\nend\r\n\r\n---@param object tts__ObjectState\r\nfunction ObjectState.getStateId(object)\r\n if not object.States then\r\n return -1\r\n end\r\n\r\n for i = 1, TableUtil.length(object.States) + 1 do\r\n if (--[[---@not nil]] object.States)[i] == nil then\r\n return i\r\n end\r\n end\r\nend\r\n\r\n---@param object tts__ObjectState\r\n---@param state integer\r\n---@return tts__ObjectState\r\nfunction ObjectState.setState(object, state)\r\n if not object.States or not (--[[---@not nil]] object.States)[state] then\r\n return object\r\n end\r\n\r\n local currentObject = TableUtil.copy(object, true)\r\n local allStates = --[[---@not nil]] currentObject.States\r\n\r\n local newObject = allStates[state]\r\n newObject.States = {}\r\n\r\n for i = 1, TableUtil.length(allStates) + 1 do\r\n if allStates[i] == nil then\r\n (--[[---@not nil]] newObject.States)[i] = currentObject\r\n elseif i ~= state then\r\n (--[[---@not nil]] newObject.States)[i] = allStates[i]\r\n end\r\n end\r\n currentObject.States = nil\r\n\r\n return newObject\r\nend\r\n\r\n---@shape seb_ObjectState_Search\r\n---@field index nil | integer\r\n---@field guid nil | GUID\r\n\r\n---@overload fun(deck: tts__DeckCustomState, search: seb_ObjectState_Search): (nil | tts__CardCustomState)\r\n---@param container tts__BagState\r\n---@param search seb_ObjectState_Search\r\n---@return nil | tts__ObjectState\r\nfunction ObjectState.takeObject(container, search)\r\n ---@param contained tts__ObjectState\r\n local function isObject(contained)\r\n return search.guid and contained.GUID == search.guid\r\n end\r\n\r\n --- For some reason the DeckId for the CustomDeck data of a custom card inside a deck doesn't always match the one from its CardId.\r\n --- So this function replaces the DeckId for the card based on the data from the deck itself and removes the existing CustomDeck data.\r\n ---@param deck tts__DeckCustomState\r\n ---@param card tts__CardCustomState\r\n ---@param index integer\r\n local function fixDeckId(deck, card, index)\r\n local currentDeckId, customDeck = next(card.CustomDeck)\r\n local actualDeckId = this.deckIdFromCardId(deck.DeckIDs[index])\r\n if actualDeckId ~= currentDeckId then\r\n card.CustomDeck[actualDeckId] = customDeck\r\n card.CustomDeck[currentDeckId] = nil\r\n end\r\n return actualDeckId\r\n end\r\n\r\n ---@param deck tts__DeckCustomState\r\n ---@param card tts__CardCustomState\r\n ---@param index integer\r\n local function handleDeckData(deck, card, index)\r\n local cardDeckId = fixDeckId(deck, card, index)\r\n\r\n table.remove(deck.DeckIDs, index)\r\n\r\n -- if no other card for this deck id exist, remove the information for it\r\n for _, existing in ipairs(deck.DeckIDs) do\r\n local existingDeckId = this.deckIdFromCardId(existing)\r\n if existingDeckId == cardDeckId then\r\n return\r\n end\r\n end\r\n deck.CustomDeck[cardDeckId] = nil\r\n end\r\n\r\n for i, contained in ipairs(container.ContainedObjects or {}) do\r\n if search.index and i == search.index or isObject(contained) then\r\n table.remove(container.ContainedObjects, i)\r\n if Object.isDeck(container) then\r\n handleDeckData(--[[---@type tts__DeckCustomState]] container, --[[---@type tts__CardCustomState]] contained, i)\r\n end\r\n\r\n return contained\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n---@overload fun(deck: tts__DeckCustomState, card: tts__CardCustomState)\r\n---@param container tts__BagState\r\n---@param object tts__ObjectState\r\nfunction ObjectState.putObject(container, object)\r\n if not container.ContainedObjects then\r\n container.ContainedObjects = {}\r\n end\r\n\r\n table.insert(container.ContainedObjects, object)\r\n\r\n if Object.isDeck(container) then\r\n local deck = --[[---@type tts__DeckCustomState]] container\r\n local card = --[[---@type tts__CardCustomState]] object\r\n local cardId = card.CardID\r\n\r\n local deckId, customDeck = next(card.CustomDeck)\r\n deck.CustomDeck[deckId] = customDeck\r\n table.insert(deck.DeckIDs, cardId)\r\n end\r\nend\r\n\r\n---@param cardId integer\r\n---@return integer\r\nfunction this.deckIdFromCardId(cardId)\r\n return tonumber(tostring(cardId):sub(1,-3))\r\nend\r\n\r\nreturn ObjectState\r\n\nend)\n__bundle_register(\"ge_tts.Json\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\nlocal License = require(\"ge_tts.License\")\r\n\r\n-- JSON encoding of Color presently fails due to a bug in Color. Fortunately, we can patch Color to fix it.\r\nrequire(\"ge_tts.GlobalPatches\")\r\n\r\nlocal Coroutine = require(\"ge_tts.Coroutine\")\r\nlocal TableUtils = require(\"ge_tts.TableUtils\")\r\n\r\nlocal LunaJsonDecoder = require(\"ge_tts.lunajson.decoder\")\r\nlocal LunaJsonEncoder = require(\"ge_tts.lunajson.encoder\")\r\nlocal LunaJsonSax = require(\"ge_tts.lunajson.sax\")\r\n\r\n-- This license applies to lunajson. Do *not* assume it extends to the mod!\r\nlocal lunajsonLicense = [[The MIT License (MIT)\r\n\r\nCopyright (c) 2015-2017 Shunsuke Shimizu (grafi)\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in\r\nall copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r\nTHE SOFTWARE.]]\r\n\r\nLicense.add(\"lunajson\", lunajsonLicense)\r\n\r\n---@class ge_tts__JsonNull\r\nlocal NULL = setmetatable({}, {\r\n __index = {},\r\n __newindex = function() error(\"Attempt to modify JSON.null()\") end,\r\n __metatable = false\r\n})\r\n\r\n---@class ge_tts__Json\r\nlocal Json = {}\r\n\r\n---@return ge_tts__JsonNull\r\nfunction Json.null()\r\n return NULL\r\nend\r\n\r\n---@alias ge_tts__JsonObject table\r\n---@alias ge_tts__JsonArray ge_tts__JsonValue[]\r\n---@alias ge_tts__JsonContainer ge_tts__JsonObject | ge_tts__JsonArray\r\n---@alias ge_tts__JsonValue ge_tts__JsonContainer | number | string | boolean | nil | ge_tts__JsonNull\r\n\r\n---@alias __ge_tts__JsonNodeTypeObject 0\r\n---@alias __ge_tts__JsonNodeTypeArray 1\r\n---@alias __ge_tts__JsonNodeTypeKey 2\r\n\r\n---@type __ge_tts__JsonNodeTypeObject\r\nlocal NODE_OBJECT = 0\r\n\r\n---@type __ge_tts__JsonNodeTypeArray\r\nlocal NODE_ARRAY = 1\r\n\r\n---@type __ge_tts__JsonNodeTypeKey\r\nlocal NODE_KEY = 2\r\n\r\n---@alias __ge_tts__JsonNodeType __ge_tts__JsonNodeTypeObject | __ge_tts__JsonNodeTypeArray | __ge_tts__JsonNodeTypeKey\r\n\r\n---@alias __ge_tts__JsonObjectNode {[1]: __ge_tts__JsonNodeTypeObject, [2]: ge_tts__JsonObject}\r\n---@alias __ge_tts__JsonArrayNode {[1]: __ge_tts__JsonNodeTypeArray, [2]: ge_tts__JsonArray, [3]: number}\r\n---@alias __ge_tts__JsonKeyNode {[1]: __ge_tts__JsonNodeTypeKey, [2]: string }\r\n\r\n\r\n---@alias __ge_tts__JsonNode __ge_tts__JsonObjectNode | __ge_tts__JsonArrayNode | __ge_tts__JsonKeyNode\r\n\r\n---@shape ge_tts__Json_DecodeOptions\r\n---@field encodeArrayLength nil | boolean @Default false. When true, array lengths are written to an `n` field on the array (i.e. std__Packed). Thus an empty array can be discerned from an empty table.\r\n---@field nullIdentification nil | boolean @Default true. When true, null values in an array/object are represented by JSON.null() rather than being omitted.\r\n\r\n---@shape ge_tts__Json_DecodeAsyncOptions : ge_tts__Json_DecodeOptions\r\n---@field onCompletion fun(data: any): void\r\n---@field onError fun(message: string): void\r\n---@field charactersPerChunk nil | number @Default 2048 (2 KiB)\r\n---@field framesBetweenChunks nil | number @Default 1\r\n\r\n---@type ge_tts__Json_DecodeOptions\r\nlocal defaultDecodeOptions = {\r\n encodeArrayLength = false,\r\n nullIdentification = true,\r\n}\r\n\r\n--- Sets the default decoding options used by decode and decodeAsync when called with options omitted.\r\n---@param decodeOptions ge_tts__Json_DecodeOptions\r\nfunction Json.setDefaultDecodeOptions(decodeOptions)\r\n defaultDecodeOptions = decodeOptions\r\nend\r\n\r\n--- Parses JSON in a pseudo-async fashion using co-operative multi-tasking (i.e. coroutines).\r\n---\r\n--- The parser will only do a limited amount of work each frame before handing off processing back to TTS, thus we\r\n--- don't freeze the game when parsing large JSON.\r\n---\r\n--- Return value is a function that can be called to cancel decoding if it is yet to complete.\r\n---@param json string\r\n---@param options ge_tts__Json_DecodeAsyncOptions\r\n---@return fun(): void\r\nfunction Json.decodeAsync(json, options)\r\n local cancelled = false\r\n\r\n options = TableUtils.merge(--[[---@type ge_tts__Json_DecodeAsyncOptions]] defaultDecodeOptions, options)\r\n\r\n Coroutine.start(function()\r\n ---@type __ge_tts__JsonNode[]\r\n local stack = {}\r\n\r\n ---@type nil | __ge_tts__JsonNode\r\n local currentNode\r\n\r\n ---@type ge_tts__JsonValue\r\n local result\r\n\r\n ---@param value ge_tts__JsonValue\r\n local function addValue(value)\r\n if currentNode then\r\n local nodeType = (--[[---@not nil]] currentNode)[1]\r\n\r\n if value == nil and options.nullIdentification then\r\n value = Json.null()\r\n end\r\n\r\n if nodeType == NODE_KEY then\r\n local key = (--[[---@type __ge_tts__JsonKeyNode]] currentNode)[2]\r\n\r\n local parentNode = --[[---@type __ge_tts__JsonObjectNode]] table.remove(stack)\r\n local parentObject = parentNode[2]\r\n parentObject[key] = value\r\n\r\n currentNode = parentNode\r\n elseif nodeType == NODE_ARRAY then\r\n local arrayNode = --[[---@type __ge_tts__JsonArrayNode]] currentNode\r\n\r\n local array = arrayNode[2]\r\n arrayNode[3] = arrayNode[3] + 1 -- Update length\r\n array[arrayNode[3]] = value\r\n end\r\n else\r\n result = value\r\n end\r\n end\r\n\r\n ---@type lunajson__SaxHandler\r\n local handler = {\r\n startobject = function()\r\n if currentNode then\r\n table.insert(stack, --[[---@not nil]] currentNode)\r\n end\r\n\r\n currentNode = {NODE_OBJECT , {}}\r\n end,\r\n ---@param key string\r\n key = function(key)\r\n table.insert(stack, --[[---@not nil]] currentNode)\r\n currentNode = {NODE_KEY, key}\r\n end,\r\n endobject = function()\r\n local objectNode = (--[[---@type __ge_tts__JsonObjectNode]] currentNode)\r\n currentNode = table.remove(stack)\r\n addValue(objectNode[2])\r\n end,\r\n startarray = function()\r\n if currentNode then\r\n table.insert(stack, --[[---@not nil]] currentNode)\r\n end\r\n\r\n currentNode = {NODE_ARRAY , {}, 0}\r\n end,\r\n endarray = function()\r\n local objectNode = (--[[---@type __ge_tts__JsonArrayNode]] currentNode)\r\n local array = objectNode[2]\r\n currentNode = table.remove(stack)\r\n\r\n if options.encodeArrayLength then\r\n (--[[---@type std__Packed]] array).n = objectNode[3]\r\n end\r\n\r\n addValue(array)\r\n end,\r\n string = addValue,\r\n number = addValue,\r\n boolean = addValue,\r\n null = function()\r\n addValue(nil)\r\n end,\r\n }\r\n\r\n ---@type number\r\n local charactersPerChunk = 0\r\n\r\n if options.charactersPerChunk then\r\n charactersPerChunk = --[[---@not nil]] options.charactersPerChunk\r\n end\r\n\r\n if charactersPerChunk <= 0 then\r\n charactersPerChunk = 2048\r\n end\r\n\r\n ---@type number\r\n local framesBetweenChunks\r\n\r\n if options.framesBetweenChunks and framesBetweenChunks > 0 then\r\n framesBetweenChunks = --[[---@not nil]] options.framesBetweenChunks\r\n else\r\n framesBetweenChunks = 1\r\n end\r\n\r\n local offset = 1\r\n local length = #json\r\n\r\n local function feed()\r\n local characterCount = math.min(length - offset + 1, charactersPerChunk)\r\n\r\n if characterCount == 0 or cancelled then\r\n return nil\r\n end\r\n\r\n Coroutine.yieldFrames(framesBetweenChunks, function(message)\r\n if not cancelled then\r\n options.onError(message)\r\n end\r\n end)\r\n\r\n local nextOffset = offset + characterCount\r\n local substring = json:sub(offset, nextOffset - 1)\r\n offset = nextOffset\r\n return substring\r\n end\r\n\r\n local parser = --[[---@type {run: fun(): void}]] LunaJsonSax.newparser(feed, handler)\r\n parser.run()\r\n\r\n if not cancelled then\r\n options.onCompletion(result)\r\n end\r\n end)\r\n\r\n return function()\r\n cancelled = true\r\n end\r\nend\r\n\r\nlocal decode = LunaJsonDecoder()\r\n\r\n---@overload fun(json: string): any\r\n---@param json string\r\n---@param options nil | ge_tts__Json_DecodeOptions\r\n---@return any\r\nfunction Json.decode(json, options)\r\n local decodeOptions = TableUtils.merge(defaultDecodeOptions, --[[---@not nil]] options or {})\r\n local nullValue = decodeOptions.nullIdentification and Json.null() or nil\r\n return (decode(json, 0, nullValue, decodeOptions.encodeArrayLength or false))\r\nend\r\n\r\nlocal encode = LunaJsonEncoder()\r\n\r\n---@param value any\r\n---@return string\r\nfunction Json.encode(value)\r\n return encode(value, Json.null())\r\nend\r\n\r\n--- Fills gaps (up to the specified length) in sparseArray with Json.null(), then returns it.\r\n---@generic T\r\n---@generic N : number\r\n---@param sparseArray table\r\n---@param length number\r\n---@return (T | ge_tts__JsonNull)[]\r\nfunction Json.nullFillSparseArray(sparseArray, length)\r\n for i = 1, length do\r\n if type((--[[---@type T[] ]] sparseArray)[i]) == 'nil' then\r\n (--[[---@type (T | ge_tts__JsonNull)[] ]] sparseArray)[i] = Json.null()\r\n end\r\n end\r\n\r\n return --[[---@type (T | ge_tts__JsonNull)[] ]] sparseArray\r\nend\r\n\r\nreturn Json\r\n\nend)\n__bundle_register(\"ge_tts.lunajson.sax\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal setmetatable, tonumber, tostring =\r\n setmetatable, tonumber, tostring\r\nlocal floor, inf =\r\n math.floor, math.huge\r\nlocal byte, char, find, gsub, match, sub =\r\n string.byte, string.char, string.find, string.gsub, string.match, string.sub\r\n\r\nlocal function _parse_error(pos, errmsg)\r\n\terror(\"parse error at \" .. pos .. \": \" .. errmsg, 2)\r\nend\r\n\r\nlocal f_str_ctrl_pat = '[^\\32-\\255]'\r\nlocal type, unpack = type, table.unpack\r\n\r\nlocal _ENV = nil\r\n\r\n---@class lunajson__SaxParser\r\n---@field run fun(): void\r\n---@field tryc fun(): nil | number\r\n---@field read fun(n: number): string\r\n---@field tellpos fun(): number\r\n\r\n---@shape lunajson__SaxHandler\r\n---@field startobject nil | fun(): void\r\n---@field key nil | fun(key: string): void\r\n---@field endobject nil | fun(): void\r\n---@field startarray nil | fun(): void\r\n---@field endarray nil | fun(): void\r\n---@field string nil | fun(value: string): void\r\n---@field number nil | fun(value: number): void\r\n---@field boolean nil | fun(value: boolean): void\r\n---@field null nil | fun(value: nil): void\r\n\r\nlocal function nop() end\r\n\r\n---@overload fun(src: string, saxtbl: lunajson__SaxHandler): lunajson__SaxParser\r\n---@param src fun(): nil | string\r\n---@param saxtbl lunajson__SaxHandler\r\n---@return lunajson__SaxParser\r\nlocal function newparser(src, saxtbl)\r\n\t---@type string, (fun(): void), number\r\n\tlocal json, jsonnxt, rec_depth\r\n\tlocal jsonlen, pos, acc = 0, 1, 0\r\n\r\n\t-- `f` is the temporary for dispatcher[c] and\r\n\t-- the dummy for the first return value of `find`\r\n\t---@type {[number]: fun(): void}, fun(): void\r\n\tlocal dispatcher, f\r\n\r\n\t-- initialize\r\n\tif type(src) == 'string' then\r\n\t\tjson = --[[---@type string]] src\r\n\t\tjsonlen = #json\r\n\t\tjsonnxt = function()\r\n\t\t\tjson = ''\r\n\t\t\tjsonlen = 0\r\n\t\t\tjsonnxt = nop\r\n\t\tend\r\n\telse\r\n\t\tjsonnxt = function()\r\n\t\t\tacc = acc + jsonlen\r\n\t\t\tpos = 1\r\n\r\n\t\t\trepeat\r\n\t\t\t\t-- Don't like this cast, it's wrong. Ideally we'd have a local\r\n\t\t\t\t-- var that's nillable, but lunajson is heavily optimized, so we\r\n\t\t\t\t-- make do.\r\n\t\t\t\tjson = --[[---@not nil]] src()\r\n\r\n\t\t\t\tif not json then\r\n\t\t\t\t\tjson = ''\r\n\t\t\t\t\tjsonlen = 0\r\n\t\t\t\t\tjsonnxt = nop\r\n\t\t\t\t\treturn\r\n\t\t\t\tend\r\n\r\n\t\t\t\tjsonlen = #json\r\n\t\t\tuntil jsonlen > 0\r\n\t\tend\r\n\t\tjsonnxt()\r\n\tend\r\n\r\n\tlocal sax_startobject = saxtbl.startobject or nop\r\n\tlocal sax_key = saxtbl.key or nop\r\n\tlocal sax_endobject = saxtbl.endobject or nop\r\n\tlocal sax_startarray = saxtbl.startarray or nop\r\n\tlocal sax_endarray = saxtbl.endarray or nop\r\n\tlocal sax_string = saxtbl.string or nop\r\n\tlocal sax_number = saxtbl.number or nop\r\n\tlocal sax_boolean = saxtbl.boolean or nop\r\n\tlocal sax_null = saxtbl.null or nop\r\n\r\n\t--[[\r\n\t\tHelper\r\n\t--]]\r\n\tlocal function tryc()\r\n\t\tlocal c = byte(json, pos)\r\n\t\tif not c then\r\n\t\t\tjsonnxt()\r\n\t\t\tc = byte(json, pos)\r\n\t\tend\r\n\t\treturn c\r\n\tend\r\n\r\n\tlocal function parse_error(errmsg)\r\n\t\treturn _parse_error(acc + pos, errmsg)\r\n\tend\r\n\r\n\tlocal function tellc()\r\n\t\treturn tryc() or parse_error(\"unexpected termination\")\r\n\tend\r\n\r\n\tlocal function spaces() -- skip spaces and prepare the next char\r\n\t\twhile true do\r\n\t\t\tpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*()', pos)\r\n\t\t\tif pos <= jsonlen then\r\n\t\t\t\treturn\r\n\t\t\tend\r\n\t\t\tif jsonlen == 0 then\r\n\t\t\t\tparse_error(\"unexpected termination\")\r\n\t\t\tend\r\n\t\t\tjsonnxt()\r\n\t\tend\r\n\tend\r\n\r\n\t--[[\r\n\t\tInvalid\r\n\t--]]\r\n\tlocal function f_err()\r\n\t\tparse_error('invalid value')\r\n\tend\r\n\r\n\t--[[\r\n\t\tConstants\r\n\t--]]\r\n\t-- fallback slow constants parser\r\n\t---@overload fun(target: string, targetlen: number, ret: nil, sax_f: fun(value: nil): void): void\r\n\t---@overload fun(target: string, targetlen: number, ret: true, sax_f: fun(value: true): void): void\r\n\t---@overload fun(target: string, targetlen: number, ret: false, sax_f: fun(value: false): void): void\r\n\t---@param target string\r\n\t---@param targetlen number\r\n\t---@param ret nil | boolean\r\n\t---@param sax_f fun(value: nil | boolean): void\r\n\tlocal function generic_constant(target, targetlen, ret, sax_f)\r\n\t\tfor i = 1, targetlen do\r\n\t\t\tlocal c = tellc()\r\n\t\t\tif byte(target, i) ~= c then\r\n\t\t\t\tparse_error(\"invalid char\")\r\n\t\t\tend\r\n\t\t\tpos = pos+1\r\n\t\tend\r\n\t\tsax_f(ret)\r\n\tend\r\n\r\n\t-- null\r\n\tlocal function f_nul()\r\n\t\tif sub(json, pos, pos+2) == 'ull' then\r\n\t\t\tpos = pos+3\r\n\t\t\tsax_null(nil)\r\n\t\t\treturn\r\n\t\tend\r\n\t\tgeneric_constant('ull', 3, nil, sax_null)\r\n\tend\r\n\r\n\t-- false\r\n\tlocal function f_fls()\r\n\t\tif sub(json, pos, pos+3) == 'alse' then\r\n\t\t\tpos = pos+4\r\n\t\t\tsax_boolean(false)\r\n\t\t\treturn\r\n\t\tend\r\n\t\tgeneric_constant('alse', 4, false, sax_boolean)\r\n\tend\r\n\r\n\t-- true\r\n\tlocal function f_tru()\r\n\t\tif sub(json, pos, pos+2) == 'rue' then\r\n\t\t\tpos = pos+3\r\n\t\t\tsax_boolean(true)\r\n\t\t\treturn\r\n\t\tend\r\n\t\tgeneric_constant('rue', 3, true, sax_boolean)\r\n\tend\r\n\r\n\t--[[\r\n\t\tNumbers\r\n\t\tConceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp)\r\n\t\tis captured as a number and its conformance to the JSON spec is checked.\r\n\t--]]\r\n\t-- deal with non-standard locales\r\n\tlocal radixmark = --[[---@type string]] match(tostring(0.5), '[^0-9]')\r\n\tlocal fixedtonumber = tonumber\r\n\tif radixmark ~= '.' then\r\n\t\tif find(radixmark, '%W') then\r\n\t\t\tradixmark = '%' .. radixmark\r\n\t\tend\r\n\t\tfixedtonumber = function(s)\r\n\t\t\treturn tonumber((gsub(s, '.', radixmark)))\r\n\t\tend\r\n\tend\r\n\r\n\tlocal function number_error()\r\n\t\treturn parse_error('invalid number')\r\n\tend\r\n\r\n\t-- fallback slow parser\r\n\tlocal function generic_number(mns)\r\n\t\t---@type (nil | number)[]\r\n\t\tlocal buf = {}\r\n\t\tlocal i = 1\r\n\t\tlocal is_int = true\r\n\r\n\t\tlocal c = byte(json, pos)\r\n\t\tpos = pos+1\r\n\r\n\t\tlocal function nxt()\r\n\t\t\tbuf[i] = c\r\n\t\t\ti = i+1\r\n\t\t\tc = tryc()\r\n\t\t\tpos = pos+1\r\n\t\tend\r\n\r\n\t\tif c == 0x30 then\r\n\t\t\tnxt()\r\n\t\t\tif c and 0x30 <= c and c < 0x3A then\r\n\t\t\t\tnumber_error()\r\n\t\t\tend\r\n\t\telse\r\n\t\t\trepeat nxt() until not (c and 0x30 <= c and c < 0x3A)\r\n\t\tend\r\n\t\tif c == 0x2E then\r\n\t\t\tis_int = false\r\n\t\t\tnxt()\r\n\t\t\tif not (c and 0x30 <= c and c < 0x3A) then\r\n\t\t\t\tnumber_error()\r\n\t\t\tend\r\n\t\t\trepeat nxt() until not (c and 0x30 <= c and c < 0x3A)\r\n\t\tend\r\n\t\tif c == 0x45 or c == 0x65 then\r\n\t\t\tis_int = false\r\n\t\t\tnxt()\r\n\t\t\tif c == 0x2B or c == 0x2D then\r\n\t\t\t\tnxt()\r\n\t\t\tend\r\n\t\t\tif not (c and 0x30 <= c and c < 0x3A) then\r\n\t\t\t\tnumber_error()\r\n\t\t\tend\r\n\t\t\trepeat nxt() until not (c and 0x30 <= c and c < 0x3A)\r\n\t\tend\r\n\t\tif c and (0x41 <= c and c <= 0x5B or\r\n\t\t 0x61 <= c and c <= 0x7B or\r\n\t\t c == 0x2B or c == 0x2D or c == 0x2E) then\r\n\t\t\tnumber_error()\r\n\t\tend\r\n\t\tpos = pos-1\r\n\r\n\t\tlocal num = fixedtonumber(char(unpack(buf)))\r\n\t\tif mns then\r\n\t\t\tnum = -num\r\n\t\tend\r\n\t\tsax_number(--[[---@not nil]] num)\r\n\tend\r\n\r\n\t-- `0(\\.[0-9]*)?([eE][+-]?[0-9]*)?`\r\n\tlocal function f_zro(mns)\r\n\t\t---@type string, number | string\r\n\t\tlocal num, c = --[[---@type string, string]] match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0\r\n\r\n\t\tif num == '' then\r\n\t\t\tif pos > jsonlen then\r\n\t\t\t\tpos = pos - 1\r\n\t\t\t\tgeneric_number(mns)\r\n\t\t\t\treturn\r\n\t\t\tend\r\n\t\t\tif c == '' then\r\n\t\t\t\tif mns then\r\n\t\t\t\t\tsax_number(-0.0)\r\n\t\t\t\t\treturn\r\n\t\t\t\tend\r\n\t\t\t\tsax_number(0)\r\n\t\t\t\treturn\r\n\t\t\tend\r\n\r\n\t\t\tif c == 'e' or c == 'E' then\r\n\t\t\t\tnum, c = --[[---@type string, string]] match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)\r\n\t\t\t\tif c == '' then\r\n\t\t\t\t\tpos = pos + #num\r\n\t\t\t\t\tif pos > jsonlen then\r\n\t\t\t\t\t\tpos = pos - #num - 1\r\n\t\t\t\t\t\tgeneric_number(mns)\r\n\t\t\t\t\t\treturn\r\n\t\t\t\t\tend\r\n\t\t\t\t\tif mns then\r\n\t\t\t\t\t\tsax_number(-0.0)\r\n\t\t\t\t\t\treturn\r\n\t\t\t\t\tend\r\n\t\t\t\t\tsax_number(0.0)\r\n\t\t\t\t\treturn\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\tpos = pos-1\r\n\t\t\tgeneric_number(mns)\r\n\t\t\treturn\r\n\t\tend\r\n\r\n\t\tif byte(num) ~= 0x2E or byte(num, -1) == 0x2E then\r\n\t\t\tpos = pos-1\r\n\t\t\tgeneric_number(mns)\r\n\t\t\treturn\r\n\t\tend\r\n\r\n\t\tif c ~= '' then\r\n\t\t\tif c == 'e' or c == 'E' then\r\n\t\t\t\tnum, c = --[[---@type string, string]] match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)\r\n\t\t\tend\r\n\t\t\tif c ~= '' then\r\n\t\t\t\tpos = pos-1\r\n\t\t\t\tgeneric_number(mns)\r\n\t\t\t\treturn\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\tpos = pos + #num\r\n\t\tif pos > jsonlen then\r\n\t\t\tpos = pos - #num - 1\r\n\t\t\tgeneric_number(mns)\r\n\t\t\treturn\r\n\t\tend\r\n\t\tc = --[[---@not nil]] fixedtonumber(num)\r\n\r\n\t\tif mns then\r\n\t\t\tc = -c\r\n\t\tend\r\n\t\tsax_number(--[[---@type number]] c)\r\n\t\treturn\r\n\tend\r\n\r\n\t-- `[1-9][0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]*)?`\r\n\tlocal function f_num(mns)\r\n\t\tpos = pos-1\r\n\r\n\t\t---@type string, string | number\r\n\t\tlocal num, c = --[[---@type string, string]] match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos)\r\n\t\tif byte(num, -1) == 0x2E then -- error if ended with period\r\n\t\t\tgeneric_number(mns)\r\n\t\t\treturn\r\n\t\tend\r\n\r\n\t\tif c ~= '' then\r\n\t\t\tif c ~= 'e' and c ~= 'E' then\r\n\t\t\t\tgeneric_number(mns)\r\n\t\t\t\treturn\r\n\t\t\tend\r\n\t\t\tnum, c = --[[---@type string, string]] match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)\r\n\t\t\tif not num or c ~= '' then\r\n\t\t\t\tgeneric_number(mns)\r\n\t\t\t\treturn\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\tpos = pos + #num\r\n\t\tif pos > jsonlen then\r\n\t\t\tpos = pos - #num\r\n\t\t\tgeneric_number(mns)\r\n\t\t\treturn\r\n\t\tend\r\n\t\tc = --[[---@not nil]] fixedtonumber(num)\r\n\r\n\t\tif mns then\r\n\t\t\tc = -c\r\n\t\tend\r\n\t\tsax_number(--[[---@type number]] c)\r\n\tend\r\n\r\n\t-- skip minus sign\r\n\tlocal function f_mns()\r\n\t\tlocal c = (byte(json, pos)) or tellc()\r\n\t\tif c then\r\n\t\t\tpos = pos+1\r\n\t\t\tif c > 0x30 then\r\n\t\t\t\tif c < 0x3A then\r\n\t\t\t\t\tf_num(true)\r\n\t\t\t\tend\r\n\t\t\telse\r\n\t\t\t\tif c > 0x2F then\r\n\t\t\t\t\tf_zro(true)\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\tend\r\n\t\tparse_error(\"invalid number\")\r\n\tend\r\n\r\n\t--[[\r\n\t\tStrings\r\n\t--]]\r\n\tlocal f_str_hextbl = --[[---@type {[number]: number, __index: fun(): number}]] {\r\n\t\t0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,\r\n\t\t0x8, 0x9, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf,\r\n\t\tinf, inf, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, inf, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, inf, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,\r\n\t\t__index = function()\r\n\t\t\treturn inf\r\n\t\tend\r\n\t}\r\n\tsetmetatable(f_str_hextbl, f_str_hextbl)\r\n\r\n\tlocal f_str_escapetbl = {\r\n\t\t['\"'] = '\"',\r\n\t\t['\\\\'] = '\\\\',\r\n\t\t['/'] = '/',\r\n\t\t['b'] = '\\b',\r\n\t\t['f'] = '\\f',\r\n\t\t['n'] = '\\n',\r\n\t\t['r'] = '\\r',\r\n\t\t['t'] = '\\t',\r\n\t\t__index = function()\r\n\t\t\tparse_error(\"invalid escape sequence\")\r\n\t\tend\r\n\t}\r\n\tsetmetatable(f_str_escapetbl, f_str_escapetbl)\r\n\r\n\tlocal function surrogate_first_error()\r\n\t\treturn parse_error(\"1st surrogate pair byte not continued by 2nd\")\r\n\tend\r\n\r\n\tlocal f_str_surrogate_prev = 0\r\n\r\n\t---@overload fun(ch: string, ucode: string): string\r\n\t---@param ch '\"' | '\\\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' | 'u'\r\n\t---@param ucode number\r\n\t---@return string\r\n\tlocal function f_str_subst(ch, ucode)\r\n\t\tif ch == 'u' then\r\n\t\t\tlocal c1, c2, c3, c4, rest = --[[---@not nil, nil, nil, nil]] byte(--[[---@type string]] ucode, 1, 5)\r\n\t\t\tucode = f_str_hextbl[c1-47] * 0x1000 +\r\n\t\t\t f_str_hextbl[c2-47] * 0x100 +\r\n\t\t\t f_str_hextbl[c3-47] * 0x10 +\r\n\t\t\t f_str_hextbl[c4-47]\r\n\t\t\tif ucode ~= inf then\r\n\t\t\t\tif ucode < 0x80 then -- 1byte\r\n\t\t\t\t\tif rest then\r\n\t\t\t\t\t\treturn char(ucode, rest)\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn char(ucode)\r\n\t\t\t\telseif ucode < 0x800 then -- 2bytes\r\n\t\t\t\t\tc1 = floor(ucode / 0x40)\r\n\t\t\t\t\tc2 = ucode - c1 * 0x40\r\n\t\t\t\t\tc1 = c1 + 0xC0\r\n\t\t\t\t\tc2 = c2 + 0x80\r\n\t\t\t\t\tif rest then\r\n\t\t\t\t\t\treturn char(c1, c2, rest)\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn char(c1, c2)\r\n\t\t\t\telseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes\r\n\t\t\t\t\tc1 = floor(ucode / 0x1000)\r\n\t\t\t\t\tucode = ucode - c1 * 0x1000\r\n\t\t\t\t\tc2 = floor(ucode / 0x40)\r\n\t\t\t\t\tc3 = ucode - c2 * 0x40\r\n\t\t\t\t\tc1 = c1 + 0xE0\r\n\t\t\t\t\tc2 = c2 + 0x80\r\n\t\t\t\t\tc3 = c3 + 0x80\r\n\t\t\t\t\tif rest then\r\n\t\t\t\t\t\treturn char(c1, c2, c3, rest)\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn char(c1, c2, c3)\r\n\t\t\t\telseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st\r\n\t\t\t\t\tif f_str_surrogate_prev == 0 then\r\n\t\t\t\t\t\tf_str_surrogate_prev = ucode\r\n\t\t\t\t\t\tif not rest then\r\n\t\t\t\t\t\t\treturn ''\r\n\t\t\t\t\t\tend\r\n\t\t\t\t\t\tsurrogate_first_error()\r\n\t\t\t\t\tend\r\n\t\t\t\t\tf_str_surrogate_prev = 0\r\n\t\t\t\t\tsurrogate_first_error()\r\n\t\t\t\telse -- surrogate pair 2nd\r\n\t\t\t\t\tif f_str_surrogate_prev ~= 0 then\r\n\t\t\t\t\t\tucode = 0x10000 +\r\n\t\t\t\t\t\t (f_str_surrogate_prev - 0xD800) * 0x400 +\r\n\t\t\t\t\t\t (ucode - 0xDC00)\r\n\t\t\t\t\t\tf_str_surrogate_prev = 0\r\n\t\t\t\t\t\tc1 = floor(ucode / 0x40000)\r\n\t\t\t\t\t\tucode = ucode - c1 * 0x40000\r\n\t\t\t\t\t\tc2 = floor(ucode / 0x1000)\r\n\t\t\t\t\t\tucode = ucode - c2 * 0x1000\r\n\t\t\t\t\t\tc3 = floor(ucode / 0x40)\r\n\t\t\t\t\t\tc4 = ucode - c3 * 0x40\r\n\t\t\t\t\t\tc1 = c1 + 0xF0\r\n\t\t\t\t\t\tc2 = c2 + 0x80\r\n\t\t\t\t\t\tc3 = c3 + 0x80\r\n\t\t\t\t\t\tc4 = c4 + 0x80\r\n\t\t\t\t\t\tif rest then\r\n\t\t\t\t\t\t\treturn char(c1, c2, c3, c4, rest)\r\n\t\t\t\t\t\tend\r\n\t\t\t\t\t\treturn char(c1, c2, c3, c4)\r\n\t\t\t\t\tend\r\n\t\t\t\t\tparse_error(\"2nd surrogate pair byte appeared without 1st\")\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\tparse_error(\"invalid unicode codepoint literal\")\r\n\t\tend\r\n\t\tif f_str_surrogate_prev ~= 0 then\r\n\t\t\tf_str_surrogate_prev = 0\r\n\t\t\tsurrogate_first_error()\r\n\t\tend\r\n\t\treturn f_str_escapetbl[--[[---@not 'u']] ch] .. ucode\r\n\tend\r\n\r\n\tlocal function f_str(iskey)\r\n\t\tlocal pos2 = pos\r\n\t\t---@type number\r\n\t\tlocal newpos\r\n\t\tlocal str = ''\r\n\t\t---@type nil | true\r\n\t\tlocal bs\r\n\t\twhile true do\r\n\t\t\twhile true do -- search '\\' or '\"'\r\n\t\t\t\tnewpos = --[[---@not nil]] find(json, '[\\\\\"]', pos2)\r\n\t\t\t\tif newpos then\r\n\t\t\t\t\tbreak\r\n\t\t\t\tend\r\n\t\t\t\tstr = str .. sub(json, pos, jsonlen)\r\n\t\t\t\tif pos2 == jsonlen+2 then\r\n\t\t\t\t\tpos2 = 2\r\n\t\t\t\telse\r\n\t\t\t\t\tpos2 = 1\r\n\t\t\t\tend\r\n\t\t\t\tjsonnxt()\r\n\t\t\t\tif jsonlen == 0 then\r\n\t\t\t\t\tparse_error(\"unterminated string\")\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\tif byte(json, newpos) == 0x22 then -- break if '\"'\r\n\t\t\t\tbreak\r\n\t\t\tend\r\n\t\t\tpos2 = newpos+2 -- skip '\\'\r\n\t\t\tbs = true -- mark the existence of a backslash\r\n\t\tend\r\n\t\tstr = str .. sub(json, pos, newpos-1)\r\n\t\tpos = newpos+1\r\n\r\n\t\tif find(str, f_str_ctrl_pat) then\r\n\t\t\tparse_error(\"unescaped control string\")\r\n\t\tend\r\n\t\tif bs then -- a backslash exists\r\n\t\t\t-- We need to grab 4 characters after the escape char,\r\n\t\t\t-- for encoding unicode codepoint to UTF-8.\r\n\t\t\t-- As we need to ensure that every first surrogate pair byte is\r\n\t\t\t-- immediately followed by second one, we grab upto 5 characters and\r\n\t\t\t-- check the last for this purpose.\r\n\t\t\tstr = gsub(str, '\\\\(.)([^\\\\]?[^\\\\]?[^\\\\]?[^\\\\]?[^\\\\]?)', f_str_subst)\r\n\t\t\tif f_str_surrogate_prev ~= 0 then\r\n\t\t\t\tf_str_surrogate_prev = 0\r\n\t\t\t\tparse_error(\"1st surrogate pair byte not continued by 2nd\")\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\tif iskey then\r\n\t\t\tsax_key(str)\r\n\t\telse\r\n\t\t\tsax_string(str)\r\n\t\tend\r\n\tend\r\n\r\n\t--[[\r\n\t\tArrays, Objects\r\n\t--]]\r\n\t-- arrays\r\n\tlocal function f_ary()\r\n\t\trec_depth = rec_depth + 1\r\n\t\tif rec_depth > 1000 then\r\n\t\t\tparse_error('too deeply nested json (> 1000)')\r\n\t\tend\r\n\t\tsax_startarray()\r\n\r\n\t\tspaces()\r\n\t\tif byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty\r\n\t\t\tpos = pos+1\r\n\t\telse\r\n\t\t\t---@type number\r\n\t\t\tlocal newpos\r\n\t\t\twhile true do\r\n\t\t\t\tf = dispatcher[--[[---@not nil]] byte(json, pos)] -- parse value\r\n\t\t\t\tpos = pos+1\r\n\t\t\t\tf()\r\n\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*,[ \\n\\r\\t]*()', pos) -- check comma\r\n\t\t\t\tif newpos then\r\n\t\t\t\t\tpos = newpos\r\n\t\t\t\telse\r\n\t\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*%]()', pos) -- check closing bracket\r\n\t\t\t\t\tif newpos then\r\n\t\t\t\t\t\tpos = newpos\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tend\r\n\t\t\t\t\tspaces() -- since the current chunk can be ended, skip spaces toward following chunks\r\n\t\t\t\t\tlocal c = byte(json, pos)\r\n\t\t\t\t\tpos = pos+1\r\n\t\t\t\t\tif c == 0x2C then -- check comma again\r\n\t\t\t\t\t\tspaces()\r\n\t\t\t\t\telseif c == 0x5D then -- check closing bracket again\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\tparse_error(\"no closing bracket of an array\")\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\t\tif pos > jsonlen then\r\n\t\t\t\t\tspaces()\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\trec_depth = rec_depth - 1\r\n\t\tsax_endarray()\r\n\tend\r\n\r\n\t-- objects\r\n\tlocal function f_obj()\r\n\t\trec_depth = rec_depth + 1\r\n\t\tif rec_depth > 1000 then\r\n\t\t\tparse_error('too deeply nested json (> 1000)')\r\n\t\tend\r\n\t\tsax_startobject()\r\n\r\n\t\tspaces()\r\n\t\tif byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty\r\n\t\t\tpos = pos+1\r\n\t\telse\r\n\t\t\t---@type number\r\n\t\t\tlocal newpos\r\n\t\t\twhile true do\r\n\t\t\t\tif byte(json, pos) ~= 0x22 then\r\n\t\t\t\t\tparse_error(\"not key\")\r\n\t\t\t\tend\r\n\t\t\t\tpos = pos+1\r\n\t\t\t\tf_str(true) -- parse key\r\n\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*:[ \\n\\r\\t]*()', pos) -- check colon\r\n\t\t\t\tif newpos then\r\n\t\t\t\t\tpos = newpos\r\n\t\t\t\telse\r\n\t\t\t\t\tspaces() -- read spaces through chunks\r\n\t\t\t\t\tif byte(json, pos) ~= 0x3A then -- check colon again\r\n\t\t\t\t\t\tparse_error(\"no colon after a key\")\r\n\t\t\t\t\tend\r\n\t\t\t\t\tpos = pos+1\r\n\t\t\t\t\tspaces()\r\n\t\t\t\tend\r\n\t\t\t\tif pos > jsonlen then\r\n\t\t\t\t\tspaces()\r\n\t\t\t\tend\r\n\t\t\t\tf = dispatcher[--[[---@not nil]] byte(json, pos)]\r\n\t\t\t\tpos = pos+1\r\n\t\t\t\tf() -- parse value\r\n\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*,[ \\n\\r\\t]*()', pos) -- check comma\r\n\t\t\t\tif newpos then\r\n\t\t\t\t\tpos = newpos\r\n\t\t\t\telse\r\n\t\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*}()', pos) -- check closing bracket\r\n\t\t\t\t\tif newpos then\r\n\t\t\t\t\t\tpos = newpos\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tend\r\n\t\t\t\t\tspaces() -- read spaces through chunks\r\n\t\t\t\t\tlocal c = byte(json, pos)\r\n\t\t\t\t\tpos = pos+1\r\n\t\t\t\t\tif c == 0x2C then -- check comma again\r\n\t\t\t\t\t\tspaces()\r\n\t\t\t\t\telseif c == 0x7D then -- check closing bracket again\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\tparse_error(\"no closing bracket of an object\")\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\t\tif pos > jsonlen then\r\n\t\t\t\t\tspaces()\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\trec_depth = rec_depth - 1\r\n\t\tsax_endobject()\r\n\tend\r\n\r\n\t--[[\r\n\t\tThe jump table to dispatch a parser for a value,\r\n\t\tindexed by the code of the value's first char.\r\n\t\tKey should be non-nil.\r\n\t--]]\r\n\tdispatcher = --[[---@type {[number]: fun(): void}]] { [0] =\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,\r\n\t\tf_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num,\r\n\t\tf_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,\r\n\t}\r\n\r\n\t--[[\r\n\t\tpublic funcitons\r\n\t--]]\r\n\tlocal function run()\r\n\t\trec_depth = 0\r\n\t\tspaces()\r\n\t\tf = dispatcher[--[[---@not nil]] byte(json, pos)]\r\n\t\tpos = pos+1\r\n\t\tf()\r\n\tend\r\n\r\n\t---@param n number\r\n\t---@return string\r\n\tlocal function read(n)\r\n\t\tif n < 0 then\r\n\t\t\terror(\"the argument must be non-negative\")\r\n\t\tend\r\n\t\tlocal pos2 = (pos-1) + n\r\n\t\tlocal str = sub(json, pos, pos2)\r\n\t\twhile pos2 > jsonlen and jsonlen ~= 0 do\r\n\t\t\tjsonnxt()\r\n\t\t\tpos2 = pos2 - (jsonlen - (pos-1))\r\n\t\t\tstr = str .. sub(json, pos, pos2)\r\n\t\tend\r\n\t\tif jsonlen ~= 0 then\r\n\t\t\tpos = pos2+1\r\n\t\tend\r\n\t\treturn str\r\n\tend\r\n\r\n\tlocal function tellpos()\r\n\t\treturn acc + pos\r\n\tend\r\n\r\n\treturn --[[---@type lunajson__SaxParser]] {\r\n\t\trun = run,\r\n\t\ttryc = tryc,\r\n\t\tread = read,\r\n\t\ttellpos = tellpos,\r\n\t}\r\nend\r\n\r\nreturn {\r\n\tnewparser = newparser\r\n}\r\n\nend)\n__bundle_register(\"ge_tts.lunajson.encoder\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal error = error\r\nlocal byte, find, format, gsub, match = string.byte, string.find, string.format, string.gsub, string.match\r\nlocal concat = table.concat\r\nlocal tostring = tostring\r\nlocal rawget, pairs, type = rawget, pairs, type\r\nlocal setmetatable = setmetatable\r\nlocal huge, tiny = 1/0, -1/0\r\n\r\nlocal f_string_esc_pat = '[^ -!#-[%]^-\\255]'\r\nlocal _ENV = nil\r\n\r\n\r\nlocal function newencoder()\r\n\t---@type any, any\r\n\tlocal v, nullv\r\n\r\n\t---@type number, string[], table\r\n\tlocal i, builder, visited\r\n\r\n\t---@param v any\r\n\tlocal function f_tostring(v)\r\n\t\tbuilder[i] = tostring(v)\r\n\t\ti = i+1\r\n\tend\r\n\r\n\tlocal radixmark = --[[---@type nil | string]] match(tostring(0.5), '[^0-9]')\r\n\tlocal delimmark = --[[---@type string]] match(tostring(12345.12345), '[^0-9' .. radixmark .. ']')\r\n\tif radixmark == '.' then\r\n\t\tradixmark = nil\r\n\tend\r\n\r\n\t---@type nil | true\r\n\tlocal radixordelim\r\n\tif radixmark or delimmark then\r\n\t\tradixordelim = true\r\n\t\tif radixmark and find(--[[---@not nil]] radixmark, '%W') then\r\n\t\t\tradixmark = '%' .. radixmark\r\n\t\tend\r\n\t\tif delimmark and find(delimmark, '%W') then\r\n\t\t\tdelimmark = '%' .. delimmark\r\n\t\tend\r\n\tend\r\n\r\n\tlocal f_number = function(n)\r\n\t\tif tiny < n and n < huge then\r\n\t\t\tlocal s = format(\"%.17g\", n)\r\n\t\t\tif radixordelim then\r\n\t\t\t\tif delimmark then\r\n\t\t\t\t\ts = gsub(s, delimmark, '')\r\n\t\t\t\tend\r\n\t\t\t\tif radixmark then\r\n\t\t\t\t\ts = gsub(s, --[[---@not nil]] radixmark, '.')\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\tbuilder[i] = s\r\n\t\t\ti = i+1\r\n\t\t\treturn\r\n\t\tend\r\n\t\terror('invalid number')\r\n\tend\r\n\r\n\t---@type fun(v: any): void\r\n\tlocal doencode\r\n\r\n\tlocal f_string_subst = {\r\n\t\t['\"'] = '\\\\\"',\r\n\t\t['\\\\'] = '\\\\\\\\',\r\n\t\t['\\b'] = '\\\\b',\r\n\t\t['\\f'] = '\\\\f',\r\n\t\t['\\n'] = '\\\\n',\r\n\t\t['\\r'] = '\\\\r',\r\n\t\t['\\t'] = '\\\\t',\r\n\t\t__index = function(_, c)\r\n\t\t\treturn format('\\\\u00%02X', byte(c))\r\n\t\tend\r\n\t}\r\n\tsetmetatable(f_string_subst, f_string_subst)\r\n\r\n\t---@param s string\r\n\tlocal function f_string(s)\r\n\t\tbuilder[i] = '\"'\r\n\t\tif find(s, f_string_esc_pat) then\r\n\t\t\ts = gsub(s, f_string_esc_pat, f_string_subst)\r\n\t\tend\r\n\t\tbuilder[i+1] = s\r\n\t\tbuilder[i+2] = '\"'\r\n\t\ti = i+3\r\n\tend\r\n\r\n\t---@param o table\r\n\tlocal function f_table(o)\r\n\t\tif visited[o] then\r\n\t\t\terror(\"loop detected\")\r\n\t\tend\r\n\t\tvisited[o] = true\r\n\r\n\t\tlocal tmp = o.n\r\n\t\tif type(tmp) == 'number' then -- arraylen available\r\n\t\t\tbuilder[i] = '['\r\n\t\t\ti = i+1\r\n\t\t\tfor j = 1, tmp do\r\n\t\t\t\tdoencode(o[j])\r\n\t\t\t\tbuilder[i] = ','\r\n\t\t\t\ti = i+1\r\n\t\t\tend\r\n\t\t\tif tmp > 0 then\r\n\t\t\t\ti = i-1\r\n\t\t\tend\r\n\t\t\tbuilder[i] = ']'\r\n\r\n\t\telse\r\n\t\t\ttmp = rawget(o, 1)\r\n\t\t\tif tmp ~= nil then -- detected as array\r\n\t\t\t\tbuilder[i] = '['\r\n\t\t\t\ti = i+1\r\n\t\t\t\tlocal j = 2\r\n\t\t\t\trepeat\r\n\t\t\t\t\tdoencode(tmp)\r\n\t\t\t\t\ttmp = o[j]\r\n\t\t\t\t\tif tmp == nil then\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tend\r\n\t\t\t\t\tj = j+1\r\n\t\t\t\t\tbuilder[i] = ','\r\n\t\t\t\t\ti = i+1\r\n\t\t\t\tuntil false\r\n\t\t\t\tbuilder[i] = ']'\r\n\r\n\t\t\telse -- detected as object\r\n\t\t\t\tbuilder[i] = '{'\r\n\t\t\t\ti = i+1\r\n\t\t\t\tlocal tmp = i\r\n\t\t\t\tfor k, v in pairs(o) do\r\n\t\t\t\t\tif type(k) ~= 'string' then\r\n\t\t\t\t\t\terror('non-string key: ' .. tostring(k) .. ' (' .. type(k) .. ')')\r\n\t\t\t\t\tend\r\n\t\t\t\t\tf_string(k)\r\n\t\t\t\t\tbuilder[i] = ':'\r\n\t\t\t\t\ti = i+1\r\n\t\t\t\t\tdoencode(v)\r\n\t\t\t\t\tbuilder[i] = ','\r\n\t\t\t\t\ti = i+1\r\n\t\t\t\tend\r\n\t\t\t\tif i > tmp then\r\n\t\t\t\t\ti = i-1\r\n\t\t\t\tend\r\n\t\t\t\tbuilder[i] = '}'\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\ti = i+1\r\n\t\tvisited[o] = nil\r\n\tend\r\n\r\n\tlocal dispatcher = {\r\n\t\tboolean = f_tostring,\r\n\t\tnumber = f_number,\r\n\t\tstring = f_string,\r\n\t\ttable = f_table,\r\n\t\t__index = function()\r\n\t\t\terror(\"invalid type value\")\r\n\t\tend\r\n\t}\r\n\tsetmetatable(dispatcher, dispatcher)\r\n\r\n\tfunction doencode(v)\r\n\t\tif v == nullv then\r\n\t\t\tbuilder[i] = 'null'\r\n\t\t\ti = i+1\r\n\t\t\treturn\r\n\t\tend\r\n\t\treturn dispatcher[--[[---@not 'nil' | 'function' | 'thread' | 'userdata']] type(v)](v)\r\n\tend\r\n\r\n\tlocal function encode(v_, nullv_)\r\n\t\tv, nullv = v_, nullv_\r\n\t\ti, builder, visited = 1, {}, {}\r\n\r\n\t\tdoencode(v)\r\n\t\treturn concat(builder)\r\n\tend\r\n\r\n\treturn encode\r\nend\r\n\r\nreturn newencoder\r\n\nend)\n__bundle_register(\"ge_tts.lunajson.decoder\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal setmetatable, tonumber, tostring =\r\n setmetatable, tonumber, tostring\r\nlocal floor, inf =\r\n math.floor, math.huge\r\nlocal byte, char, find, gsub, match, sub =\r\n string.byte, string.char, string.find, string.gsub, string.match, string.sub\r\n\r\nlocal function _decode_error(pos, errmsg)\r\n\terror(\"parse error at \" .. pos .. \": \" .. errmsg, 2)\r\nend\r\n\r\nlocal f_str_ctrl_pat = '[^\\32-\\255]'\r\nlocal _ENV = nil\r\n\r\n\r\nlocal function newdecoder()\r\n\t---@type string, number, any, boolean, number\r\n\tlocal json, pos, nullv, arraylen, rec_depth\r\n\r\n\t-- `f` is the temporary for dispatcher[c] and\r\n\t-- the dummy for the first return value of `find`\r\n\t---@type {[number]: fun(): void}, fun(): any\r\n\tlocal dispatcher, f\r\n\r\n\t--[[\r\n\t\tHelper\r\n\t--]]\r\n\tlocal function decode_error(errmsg)\r\n\t\treturn _decode_error(pos, errmsg)\r\n\tend\r\n\r\n\t--[[\r\n\t\tInvalid\r\n\t--]]\r\n\tlocal function f_err()\r\n\t\tdecode_error('invalid value')\r\n\tend\r\n\r\n\t--[[\r\n\t\tConstants\r\n\t--]]\r\n\t-- null\r\n\tlocal function f_nul()\r\n\t\tif sub(json, pos, pos+2) == 'ull' then\r\n\t\t\tpos = pos+3\r\n\t\t\treturn nullv\r\n\t\tend\r\n\t\tdecode_error('invalid value')\r\n\tend\r\n\r\n\t-- false\r\n\tlocal function f_fls()\r\n\t\tif sub(json, pos, pos+3) == 'alse' then\r\n\t\t\tpos = pos+4\r\n\t\t\treturn false\r\n\t\tend\r\n\t\tdecode_error('invalid value')\r\n\tend\r\n\r\n\t-- true\r\n\tlocal function f_tru()\r\n\t\tif sub(json, pos, pos+2) == 'rue' then\r\n\t\t\tpos = pos+3\r\n\t\t\treturn true\r\n\t\tend\r\n\t\tdecode_error('invalid value')\r\n\tend\r\n\r\n\t--[[\r\n\t\tNumbers\r\n\t\tConceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp)\r\n\t\tis captured as a number and its conformance to the JSON spec is checked.\r\n\t--]]\r\n\t-- deal with non-standard locales\r\n\tlocal radixmark = --[[---@type string]] match(tostring(0.5), '[^0-9]')\r\n\tlocal fixedtonumber = tonumber\r\n\tif radixmark ~= '.' then\r\n\t\tif find(radixmark, '%W') then\r\n\t\t\tradixmark = '%' .. radixmark\r\n\t\tend\r\n\t\tfixedtonumber = function(s)\r\n\t\t\treturn tonumber((gsub(s, '.', radixmark)))\r\n\t\tend\r\n\tend\r\n\r\n\tlocal function number_error()\r\n\t\treturn decode_error('invalid number')\r\n\tend\r\n\r\n\t-- `0(\\.[0-9]*)?([eE][+-]?[0-9]*)?`\r\n\tlocal function f_zro(mns)\r\n\t\t---@type string, string | number\r\n\t\tlocal num, c = --[[---@type string, string]] match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0\r\n\r\n\t\tif num == '' then\r\n\t\t\tif c == '' then\r\n\t\t\t\tif mns then\r\n\t\t\t\t\treturn -0.0\r\n\t\t\t\tend\r\n\t\t\t\treturn 0\r\n\t\t\tend\r\n\r\n\t\t\tif c == 'e' or c == 'E' then\r\n\t\t\t\tnum, c = --[[---@type string, string]] match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)\r\n\t\t\t\tif c == '' then\r\n\t\t\t\t\tpos = pos + #num\r\n\t\t\t\t\tif mns then\r\n\t\t\t\t\t\treturn -0.0\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn 0.0\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\tnumber_error()\r\n\t\tend\r\n\r\n\t\tif byte(num) ~= 0x2E or byte(num, -1) == 0x2E then\r\n\t\t\tnumber_error()\r\n\t\tend\r\n\r\n\t\tif c ~= '' then\r\n\t\t\tif c == 'e' or c == 'E' then\r\n\t\t\t\tnum, c = --[[---@type string, string]] match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)\r\n\t\t\tend\r\n\t\t\tif c ~= '' then\r\n\t\t\t\tnumber_error()\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\tpos = pos + #num\r\n\t\tc = --[[---@not nil]] fixedtonumber(num)\r\n\r\n\t\tif mns then\r\n\t\t\tc = -c\r\n\t\tend\r\n\t\treturn c\r\n\tend\r\n\r\n\t-- `[1-9][0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]*)?`\r\n\tlocal function f_num(mns)\r\n\t\tpos = pos-1\r\n\r\n\t\t---@type string, string | number\r\n\t\tlocal num, c = --[[---@type string, string]] match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos)\r\n\t\tif byte(num, -1) == 0x2E then -- error if ended with period\r\n\t\t\tnumber_error()\r\n\t\tend\r\n\r\n\t\tif c ~= '' then\r\n\t\t\tif c ~= 'e' and c ~= 'E' then\r\n\t\t\t\tnumber_error()\r\n\t\t\tend\r\n\t\t\tnum, c = --[[---@type string, string]] match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)\r\n\t\t\tif not num or c ~= '' then\r\n\t\t\t\tnumber_error()\r\n\t\t\tend\r\n\t\tend\r\n\r\n\t\tpos = pos + #num\r\n\t\tc = --[[---@not nil]] fixedtonumber(num)\r\n\r\n\t\tif mns then\r\n\t\t\tc = -c\r\n\t\tend\r\n\t\treturn c\r\n\tend\r\n\r\n\t-- skip minus sign\r\n\tlocal function f_mns()\r\n\t\tlocal c = byte(json, pos)\r\n\t\tif c then\r\n\t\t\tpos = pos+1\r\n\t\t\tif c > 0x30 then\r\n\t\t\t\tif c < 0x3A then\r\n\t\t\t\t\treturn f_num(true)\r\n\t\t\t\tend\r\n\t\t\telse\r\n\t\t\t\tif c > 0x2F then\r\n\t\t\t\t\treturn f_zro(true)\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\tend\r\n\t\tdecode_error('invalid number')\r\n\tend\r\n\r\n\t--[[\r\n\t\tStrings\r\n\t--]]\r\n\tlocal f_str_hextbl = --[[---@type {[number]: number, __index: fun(): number}]] {\r\n\t\t0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,\r\n\t\t0x8, 0x9, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf,\r\n\t\tinf, inf, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, inf, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, inf, inf, inf, inf, inf, inf, inf,\r\n\t\tinf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,\r\n\t\t__index = function()\r\n\t\t\treturn inf\r\n\t\tend\r\n\t}\r\n\tsetmetatable(f_str_hextbl, f_str_hextbl)\r\n\r\n\tlocal f_str_escapetbl = {\r\n\t\t['\"'] = '\"',\r\n\t\t['\\\\'] = '\\\\',\r\n\t\t['/'] = '/',\r\n\t\t['b'] = '\\b',\r\n\t\t['f'] = '\\f',\r\n\t\t['n'] = '\\n',\r\n\t\t['r'] = '\\r',\r\n\t\t['t'] = '\\t',\r\n\t\t__index = function()\r\n\t\t\tdecode_error(\"invalid escape sequence\")\r\n\t\tend\r\n\t}\r\n\tsetmetatable(f_str_escapetbl, f_str_escapetbl)\r\n\r\n\tlocal function surrogate_first_error()\r\n\t\treturn decode_error(\"1st surrogate pair byte not continued by 2nd\")\r\n\tend\r\n\r\n\tlocal f_str_surrogate_prev = 0\r\n\r\n\t---@overload fun(ch: string, ucode: string): string\r\n\t---@param ch '\"' | '\\\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' | 'u'\r\n\t---@param ucode number\r\n\t---@return string\r\n\tlocal function f_str_subst(ch, ucode)\r\n\t\tif ch == 'u' then\r\n\t\t\tlocal c1, c2, c3, c4, rest = --[[---@not nil, nil, nil, nil]] byte(--[[---@type string]] ucode, 1, 5)\r\n\t\t\tucode = f_str_hextbl[c1-47] * 0x1000 +\r\n\t\t\t f_str_hextbl[c2-47] * 0x100 +\r\n\t\t\t f_str_hextbl[c3-47] * 0x10 +\r\n\t\t\t f_str_hextbl[c4-47]\r\n\t\t\tif ucode ~= inf then\r\n\t\t\t\tif ucode < 0x80 then -- 1byte\r\n\t\t\t\t\tif rest then\r\n\t\t\t\t\t\treturn char(ucode, rest)\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn char(ucode)\r\n\t\t\t\telseif ucode < 0x800 then -- 2bytes\r\n\t\t\t\t\tc1 = floor(ucode / 0x40)\r\n\t\t\t\t\tc2 = ucode - c1 * 0x40\r\n\t\t\t\t\tc1 = c1 + 0xC0\r\n\t\t\t\t\tc2 = c2 + 0x80\r\n\t\t\t\t\tif rest then\r\n\t\t\t\t\t\treturn char(c1, c2, rest)\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn char(c1, c2)\r\n\t\t\t\telseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes\r\n\t\t\t\t\tc1 = floor(ucode / 0x1000)\r\n\t\t\t\t\tucode = ucode - c1 * 0x1000\r\n\t\t\t\t\tc2 = floor(ucode / 0x40)\r\n\t\t\t\t\tc3 = ucode - c2 * 0x40\r\n\t\t\t\t\tc1 = c1 + 0xE0\r\n\t\t\t\t\tc2 = c2 + 0x80\r\n\t\t\t\t\tc3 = c3 + 0x80\r\n\t\t\t\t\tif rest then\r\n\t\t\t\t\t\treturn char(c1, c2, c3, rest)\r\n\t\t\t\t\tend\r\n\t\t\t\t\treturn char(c1, c2, c3)\r\n\t\t\t\telseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st\r\n\t\t\t\t\tif f_str_surrogate_prev == 0 then\r\n\t\t\t\t\t\tf_str_surrogate_prev = ucode\r\n\t\t\t\t\t\tif not rest then\r\n\t\t\t\t\t\t\treturn ''\r\n\t\t\t\t\t\tend\r\n\t\t\t\t\t\tsurrogate_first_error()\r\n\t\t\t\t\tend\r\n\t\t\t\t\tf_str_surrogate_prev = 0\r\n\t\t\t\t\tsurrogate_first_error()\r\n\t\t\t\telse -- surrogate pair 2nd\r\n\t\t\t\t\tif f_str_surrogate_prev ~= 0 then\r\n\t\t\t\t\t\tucode = 0x10000 +\r\n\t\t\t\t\t\t (f_str_surrogate_prev - 0xD800) * 0x400 +\r\n\t\t\t\t\t\t (ucode - 0xDC00)\r\n\t\t\t\t\t\tf_str_surrogate_prev = 0\r\n\t\t\t\t\t\tc1 = floor(ucode / 0x40000)\r\n\t\t\t\t\t\tucode = ucode - c1 * 0x40000\r\n\t\t\t\t\t\tc2 = floor(ucode / 0x1000)\r\n\t\t\t\t\t\tucode = ucode - c2 * 0x1000\r\n\t\t\t\t\t\tc3 = floor(ucode / 0x40)\r\n\t\t\t\t\t\tc4 = ucode - c3 * 0x40\r\n\t\t\t\t\t\tc1 = c1 + 0xF0\r\n\t\t\t\t\t\tc2 = c2 + 0x80\r\n\t\t\t\t\t\tc3 = c3 + 0x80\r\n\t\t\t\t\t\tc4 = c4 + 0x80\r\n\t\t\t\t\t\tif rest then\r\n\t\t\t\t\t\t\treturn char(c1, c2, c3, c4, rest)\r\n\t\t\t\t\t\tend\r\n\t\t\t\t\t\treturn char(c1, c2, c3, c4)\r\n\t\t\t\t\tend\r\n\t\t\t\t\tdecode_error(\"2nd surrogate pair byte appeared without 1st\")\r\n\t\t\t\tend\r\n\t\t\tend\r\n\t\t\tdecode_error(\"invalid unicode codepoint literal\")\r\n\t\tend\r\n\t\tif f_str_surrogate_prev ~= 0 then\r\n\t\t\tf_str_surrogate_prev = 0\r\n\t\t\tsurrogate_first_error()\r\n\t\tend\r\n\t\treturn f_str_escapetbl[--[[---@not 'u']] ch] .. ucode\r\n\tend\r\n\r\n\t-- caching interpreted keys for speed\r\n\tlocal f_str_keycache = --[[---@type {[string]: string}]] setmetatable({}, {__mode=\"v\"})\r\n\r\n\tlocal function f_str(iskey)\r\n\t\tlocal newpos = pos\r\n\r\n\t\t---@type number, number, number\r\n\t\tlocal tmppos, c1, c2\r\n\t\trepeat\r\n\t\t\tnewpos = --[[---@type number]] find(json, '\"', newpos, true) -- search '\"'\r\n\t\t\tif not newpos then\r\n\t\t\t\tdecode_error(\"unterminated string\")\r\n\t\t\tend\r\n\t\t\ttmppos = newpos-1\r\n\t\t\tnewpos = newpos+1\r\n\t\t\tc1, c2 = --[[---@not nil, nil]] byte(json, tmppos-1, tmppos)\r\n\t\t\tif c2 == 0x5C and c1 == 0x5C then -- skip preceding '\\\\'s\r\n\t\t\t\trepeat\r\n\t\t\t\t\ttmppos = tmppos-2\r\n\t\t\t\t\tc1, c2 = --[[---@not nil, nil]] byte(json, tmppos-1, tmppos)\r\n\t\t\t\tuntil c2 ~= 0x5C or c1 ~= 0x5C\r\n\t\t\t\ttmppos = newpos-2\r\n\t\t\tend\r\n\t\tuntil c2 ~= 0x5C -- leave if '\"' is not preceded by '\\'\r\n\r\n\t\tlocal str = sub(json, pos, tmppos)\r\n\t\tpos = newpos\r\n\r\n\t\tif iskey then -- check key cache\r\n\t\t\ttmppos = --[[---@type any]] f_str_keycache[str] -- reuse tmppos for cache key/val\r\n\t\t\tif tmppos then\r\n\t\t\t\treturn --[[---@type string]] tmppos\r\n\t\t\tend\r\n\t\t\ttmppos = --[[---@type any]] str\r\n\t\tend\r\n\r\n\t\tif find(str, f_str_ctrl_pat) then\r\n\t\t\tdecode_error(\"unescaped control string\")\r\n\t\tend\r\n\t\tif find(str, '\\\\', 1, true) then -- check whether a backslash exists\r\n\t\t\t-- We need to grab 4 characters after the escape char,\r\n\t\t\t-- for encoding unicode codepoint to UTF-8.\r\n\t\t\t-- As we need to ensure that every first surrogate pair byte is\r\n\t\t\t-- immediately followed by second one, we grab upto 5 characters and\r\n\t\t\t-- check the last for this purpose.\r\n\t\t\tstr = gsub(str, '\\\\(.)([^\\\\]?[^\\\\]?[^\\\\]?[^\\\\]?[^\\\\]?)', f_str_subst)\r\n\t\t\tif f_str_surrogate_prev ~= 0 then\r\n\t\t\t\tf_str_surrogate_prev = 0\r\n\t\t\t\tdecode_error(\"1st surrogate pair byte not continued by 2nd\")\r\n\t\t\tend\r\n\t\tend\r\n\t\tif iskey then -- commit key cache\r\n\t\t\tf_str_keycache[--[[---@type string]] tmppos] = str\r\n\t\tend\r\n\t\treturn str\r\n\tend\r\n\r\n\t--[[\r\n\t\tArrays, Objects\r\n\t--]]\r\n\t-- array\r\n\tlocal function f_ary()\r\n\t\trec_depth = rec_depth + 1\r\n\t\tif rec_depth > 1000 then\r\n\t\t\tdecode_error('too deeply nested json (> 1000)')\r\n\t\tend\r\n\t\tlocal ary = {}\r\n\r\n\t\tpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*()', pos)\r\n\r\n\t\tlocal i = 0\r\n\t\tif byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty\r\n\t\t\tpos = pos+1\r\n\t\telse\r\n\t\t\tlocal newpos = pos\r\n\t\t\trepeat\r\n\t\t\t\ti = i+1\r\n\t\t\t\tf = dispatcher[--[[---@not nil]] byte(json,newpos)] -- parse value\r\n\t\t\t\tpos = newpos+1\r\n\t\t\t\tary[i] = f()\r\n\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*,[ \\n\\r\\t]*()', pos) -- check comma\r\n\t\t\tuntil not newpos\r\n\r\n\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*%]()', pos) -- check closing bracket\r\n\t\t\tif not newpos then\r\n\t\t\t\tdecode_error(\"no closing bracket of an array\")\r\n\t\t\tend\r\n\t\t\tpos = newpos\r\n\t\tend\r\n\r\n\t\tif arraylen then -- commit the length of the array if `arraylen` is set\r\n\t\t\tary.n = i\r\n\t\tend\r\n\t\trec_depth = rec_depth - 1\r\n\t\treturn ary\r\n\tend\r\n\r\n\t-- objects\r\n\tlocal function f_obj()\r\n\t\trec_depth = rec_depth + 1\r\n\t\tif rec_depth > 1000 then\r\n\t\t\tdecode_error('too deeply nested json (> 1000)')\r\n\t\tend\r\n\r\n\t\tlocal obj = --[[---@type {[string]: any}]] {}\r\n\r\n\t\tpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*()', pos)\r\n\t\tif byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty\r\n\t\t\tpos = pos+1\r\n\t\telse\r\n\t\t\tlocal newpos = pos\r\n\r\n\t\t\trepeat\r\n\t\t\t\tif byte(json, newpos) ~= 0x22 then -- check '\"'\r\n\t\t\t\t\tdecode_error(\"not key\")\r\n\t\t\t\tend\r\n\t\t\t\tpos = newpos+1\r\n\t\t\t\tlocal key = f_str(true) -- parse key\r\n\r\n\t\t\t\t-- optimized for compact json\r\n\t\t\t\t-- c1, c2 == ':', or\r\n\t\t\t\t-- c1, c2, c3 == ':', ' ', \r\n\t\t\t\tf = f_err\r\n\t\t\t\tlocal c1, c2, c3 = --[[---@not nil, nil, nil]] byte(json, pos, pos+3)\r\n\t\t\t\tif c1 == 0x3A then\r\n\t\t\t\t\tif c2 ~= 0x20 then\r\n\t\t\t\t\t\tf = dispatcher[c2]\r\n\t\t\t\t\t\tnewpos = pos+2\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\tf = dispatcher[c3]\r\n\t\t\t\t\t\tnewpos = pos+3\r\n\t\t\t\t\tend\r\n\t\t\t\tend\r\n\t\t\t\tif f == f_err then -- read a colon and arbitrary number of spaces\r\n\t\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*:[ \\n\\r\\t]*()', pos)\r\n\t\t\t\t\tif not newpos then\r\n\t\t\t\t\t\tdecode_error(\"no colon after a key\")\r\n\t\t\t\t\tend\r\n\t\t\t\t\tf = dispatcher[--[[---@not nil]] byte(json, newpos)]\r\n\t\t\t\t\tnewpos = newpos+1\r\n\t\t\t\tend\r\n\t\t\t\tpos = newpos\r\n\t\t\t\tobj[key] = f() -- parse value\r\n\t\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*,[ \\n\\r\\t]*()', pos)\r\n\t\t\tuntil not newpos\r\n\r\n\t\t\tnewpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*}()', pos)\r\n\t\t\tif not newpos then\r\n\t\t\t\tdecode_error(\"no closing bracket of an object\")\r\n\t\t\tend\r\n\t\t\tpos = newpos\r\n\t\tend\r\n\r\n\t\trec_depth = rec_depth - 1\r\n\t\treturn obj\r\n\tend\r\n\r\n\t--[[\r\n\t\tThe jump table to dispatch a parser for a value,\r\n\t\tindexed by the code of the value's first char.\r\n\t\tNil key means the end of json.\r\n\t--]]\r\n\tdispatcher = --[[---@type {[number]: fun(): void}]] { [0] =\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,\r\n\t\tf_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num,\r\n\t\tf_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,\r\n\t\tf_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err,\r\n\t\tf_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,\r\n\t\t__index = function()\r\n\t\t\tdecode_error(\"unexpected termination\")\r\n\t\tend\r\n\t}\r\n\tsetmetatable(dispatcher, dispatcher)\r\n\r\n\t--[[\r\n\t\trun decoder\r\n\t--]]\r\n\t---@param json_ string\r\n\t---@param pos_ number\r\n\t---@param nullv_ any\r\n\t---@param arraylen_ boolean\r\n\t---@return any | (any, number)\r\n\tlocal function decode(json_, pos_, nullv_, arraylen_)\r\n\t\tjson, pos, nullv, arraylen = json_, pos_, nullv_, arraylen_\r\n\t\trec_depth = 0\r\n\r\n\t\tpos = --[[---@type number]] match(json, '^[ \\n\\r\\t]*()', pos)\r\n\r\n\t\tf = dispatcher[--[[---@not nil]] byte(json, pos)]\r\n\t\tpos = pos+1\r\n\t\tlocal v = f()\r\n\r\n\t\tif pos_ then\r\n\t\t\treturn v, pos\r\n\t\telse\r\n\t\t\tf, pos = --[[---@type any, number]] find(json, '^[ \\n\\r\\t]*', pos)\r\n\t\t\tif pos ~= #json then\r\n\t\t\t\tdecode_error('json ended')\r\n\t\t\tend\r\n\t\t\treturn v\r\n\t\tend\r\n\tend\r\n\r\n\treturn decode\r\nend\r\n\r\nreturn newdecoder\r\n\nend)\n__bundle_register(\"ge_tts.Coroutine\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\n---@class ge_tts__Coroutine\r\nlocal Coroutine = {}\r\n\r\n---@param co thread\r\n---@param onError nil | fun(message: string): void\r\nlocal function resumeWithErrorHandling(co, onError)\r\n local result, message = coroutine.resume(co)\r\n\r\n if not result then\r\n if onError then\r\n (--[[---@not nil]] onError)(message)\r\n else\r\n error(message)\r\n end\r\n end\r\nend\r\n\r\n--- Yields from the current coroutine. Resumes once a condition is met or an optional timeout is reached.\r\n---@overload fun(condition: fun(): boolean): true\r\n---@overload fun(condition: (fun(): boolean), timeout: number): boolean\r\n---@param condition fun(): boolean @Return true when the current coroutine should be resumed.\r\n---@param timeout nil | number @Timeout in seconds (optional).\r\n---@param onError nil | fun(message: string): void @A handler for any errors raised by the current coroutine after it has been resumed.\r\n---@return boolean @True if the condition was met, or false if the (optional) timeout was reached.\r\nfunction Coroutine.yieldCondition(condition, timeout, onError)\r\n local co = coroutine.running()\r\n\r\n ---@type nil | boolean\r\n local conditionMet\r\n\r\n local resume = function()\r\n conditionMet = true\r\n resumeWithErrorHandling(co, onError)\r\n end\r\n\r\n if timeout then\r\n Wait.condition(resume, condition, --[[---@not nil]] timeout, function()\r\n conditionMet = false\r\n resumeWithErrorHandling(co, onError)\r\n end)\r\n else\r\n Wait.condition(resume, condition)\r\n end\r\n\r\n coroutine.yield()\r\n\r\n if conditionMet == nil then\r\n error(\"Coroutine.yieldCondition(): attempt to resume before Wait was completed!\")\r\n end\r\n\r\n return --[[---@not nil]] conditionMet\r\nend\r\n\r\n--- Yields from the current coroutine, which will later be resumed after the specified number of frames have passed.\r\n---@overload fun(frames: number): void\r\n---@param frames number\r\n---@param onError nil | fun(message: string): void @A handler for any errors raised by the current coroutine after it has been resumed.\r\nfunction Coroutine.yieldFrames(frames, onError)\r\n local co = coroutine.running()\r\n\r\n ---@type boolean\r\n local done\r\n\r\n Wait.frames(function()\r\n done = true\r\n resumeWithErrorHandling(co, onError)\r\n end, frames)\r\n\r\n coroutine.yield()\r\n\r\n if not done then\r\n error(\"Coroutine.yieldFrames(): attempt to resume before Wait was completed!\")\r\n end\r\nend\r\n\r\n--- Yields from the current coroutine, which will later be resumed after the specified number of seconds have passed.\r\n---@overload fun(seconds: number): void\r\n---@param seconds number\r\n---@param onError nil | fun(message: string): void @A handler for any errors raised by the current coroutine after it has been resumed.\r\nfunction Coroutine.yieldSeconds(seconds, onError)\r\n local co = coroutine.running()\r\n\r\n ---@type boolean\r\n local done\r\n\r\n Wait.time(function()\r\n done = true\r\n resumeWithErrorHandling(co, onError)\r\n end, seconds)\r\n\r\n coroutine.yield()\r\n\r\n if not done then\r\n error(\"Coroutine.yieldSeconds(): attempt to resume before Wait was completed!\")\r\n end\r\nend\r\n\r\n--- Creates a co-routine from the specified function and immediately starts it, passing any any provided arguments.\r\n---@param func fun\r\n---@vararg any\r\n---@return boolean, any...\r\nfunction Coroutine.start(func, ...)\r\n return coroutine.resume(coroutine.create(func), ...)\r\nend\r\n\r\nreturn Coroutine\r\n\nend)\n__bundle_register(\"ge_tts.GlobalPatches\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"ge_tts.License\")\r\n\r\n-- From time to time there are bugs in TTS' APIs that we're able to fix/patch in a non-intrusive fashion.\r\n\r\n-- Lua Color indexing fix, see: https://github.com/Berserk-Games/Tabletop-Simulator-Lua-Classes/pull/1\r\n\r\n---@type {__index: fun(c: any, k: any): any}\r\nlocal colorMetatable = getmetatable(Color)\r\nlocal originalColorIndex = colorMetatable.__index\r\n\r\ncolorMetatable.__index = function(c, k)\r\n if type(k) ~= 'string' then\r\n return nil\r\n end\r\n\r\n return originalColorIndex(c, k)\r\nend\r\n\nend)\n__bundle_register(\"lib.Chain\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Chain = require(\"sebaestschjin-tts.Chain\")\r\n\r\nreturn Chain\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Chain\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"sebaestschjin-tts.TableUtil\")\r\n\r\n---@class seb_Chain\r\n\r\n---@alias seb_ChainCallback (fun(chain: seb_Chain): void) | (fun(chain: seb_Chain): boolean)\r\n\r\n---@class seb_Chain_static : seb_Chain\r\n---@overload fun(): seb_Chain\r\n---@overload fun(name: string): seb_Chain\r\nlocal Chain = {}\r\n\r\nsetmetatable(Chain, {\r\n __call = function(_, name)\r\n local self = --[[---@type seb_Chain]] {}\r\n\r\n local chainName = name or \"\"\r\n local callbacks = --[[---@type seb_ChainCallback[] ]]{}\r\n\r\n ---@param callback seb_ChainCallback\r\n function self.add(callback)\r\n table.insert(callbacks, callback)\r\n end\r\n\r\n ---@param callback seb_ChainCallback\r\n function self.addNext(callback)\r\n table.insert(callbacks, 1, callback)\r\n end\r\n\r\n function self.proceed()\r\n if TableUtil.isNotEmpty(callbacks) then\r\n local nextCallback = table.remove(callbacks, 1)\r\n local res = nextCallback(self)\r\n if res then\r\n self.proceed()\r\n end\r\n end\r\n end\r\n\r\n function self.dump()\r\n print(chainName)\r\n print(logString(callbacks))\r\n end\r\n\r\n return self\r\n end\r\n})\r\n\r\nreturn Chain\r\n\nend)\n__bundle_register(\"campaign-manager.Sanctuary\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Sanctuary = {}\r\n\r\n---@param savefile gh_Savefile\r\nfunction Sanctuary.setup(savefile)\r\n local value = savefile.unlocked.sanctuary\r\n EventManager.addHandler(EventType.Loaded.Start, function() Sanctuary.load(value) end)\r\n EventManager.addHandler(EventType.Placed.SanctuarySticker, function() Sanctuary.setLevel(--[[---@not nil]] value) end)\r\nend\r\n\r\n---@param value nil | number\r\nfunction Sanctuary.load(value)\r\n if value and value >= 0 then\r\n Component.placeSanctuarySticker()\r\n end\r\nend\r\n\r\n---@param value number\r\nfunction Sanctuary.setLevel(value)\r\n local sanctuarySticker = --[[---@not nil ]] Component.sanctuarySticker()\r\n for i = 0, value - 1 do\r\n sanctuarySticker.call(\"clickedPros\", i)\r\n end\r\nend\r\n\r\n---@return nil | number\r\nfunction Sanctuary.save()\r\n local sanctuarySticker = Component.sanctuarySticker()\r\n if not sanctuarySticker then\r\n return nil\r\n end\r\n\r\n local checkmarks = (--[[---@not nil]] sanctuarySticker).getTable(\"Pros\")\r\n local maxValue = -1\r\n for i, checked in pairs(checkmarks) do\r\n if checked and tonumber(i) > maxValue then\r\n maxValue = --[[---@not nil]] tonumber(i)\r\n end\r\n end\r\n\r\n return maxValue + 1\r\nend\r\n\r\nreturn Sanctuary\r\n\nend)\n__bundle_register(\"campaign-manager.ManagerOptions\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal Task = require(\"campaign-manager.Task\")\nlocal EventType = require(\"campaign-manager.EventType\")\n\nlocal Options = require(\"api.OptionsApi\")\n\nlocal ManagerOptions = {}\n\n---@type boolean\nManagerOptions.keepDiscardedItems = nil\n---@type boolean\nManagerOptions.loadExtendedBattleGoals = false\n---@type string\nManagerOptions.difficulty = \"Normal\"\n\n---@param options gloom_Option_Values\nlocal function updateOptions(options)\n -- remove options that where hidden first (but still saved), but don't actually exist\n options[\"message.activeAbilities\"] = nil\n options[\"version.revisedStaminaPotions\"] = nil\n -- remove deleted options\n options[\"message.hpChanges\"] = nil\n\n -- transform boolean flag to new number format\n if options[\"scenario.dealBattleGoals\"] == true then\n options[\"scenario.dealBattleGoals\"] = \"2\"\n elseif options[\"scenario.dealBattleGoals\"] == false then\n options[\"scenario.dealBattleGoals\"] = \"0\"\n end\n\n -- transform true/false flag to newer multi-toggle version\n if options[\"setup.use3D\"] == true then\n ---@type set\n local optionSet = {}\n for _, threeD in pairs(Options.Use3DModels) do\n optionSet[threeD] = true\n end\n options[\"setup.use3D\"] = optionSet\n elseif options[\"setup.use3D\"] == false then\n options[\"setup.use3D\"] = {}\n end\n\n -- rename difficulty group\n if options[\"difficulty.openInformation\"] ~= nil then\n options[\"variants.openInformation\"] = options[\"difficulty.openInformation\"]\n end\n\n -- add the default for the new particle setting\n if options[\"setup.use3D\"] then\n if options[\"setup.use3D\"][\"particle\"] == nil then\n options[\"setup.use3D\"][\"particle\"] = true\n end\n end\nend\n\n---@param savefile gh_Savefile\nfunction ManagerOptions.setup(savefile)\n EventManager.addHandler(EventType.Loaded.Start, function() ManagerOptions.loadAll(savefile) end)\nend\n\n---@param savefile gh_Savefile\nfunction ManagerOptions.loadAll(savefile)\n if savefile.options.keepDiscardedCards ~= nil then\n ManagerOptions.keepDiscardedItems = --[[---@not nil]] savefile.options.keepDiscardedCards\n else\n ManagerOptions.keepDiscardedItems = true\n end\n\n ManagerOptions.difficulty = savefile.options.difficulty or \"Normal\"\n if savefile.options.enhanced then\n updateOptions(--[[---@not nil]] savefile.options.enhanced)\n Options.setValues(--[[---@not nil]] savefile.options.enhanced)\n end\n\n EventManager.triggerEvent(EventType.Loaded.Options)\nend\n\n---@param savefile gh_Savefile\nfunction ManagerOptions.saveAll(savefile)\n Logger.debug(\"Saving options\")\n\n savefile.options.keepDiscardedCards = ManagerOptions.keepDiscardedItems\n\n local battleGoals = Component.battleGoalsDeck().getObjects()\n savefile.options.loadExtendedBattleGoals = TableUtil.length(battleGoals) > 24\n savefile.options.enhanced = Options.getValues()\n savefile.options.difficulty = ManagerOptions.difficulty\n\n Task.completeSave(Task.Event.Saved.Options, savefile)\nend\n\nTask.registerSave(ManagerOptions.saveAll, Task.Event.Saved.Party)\n\nreturn ManagerOptions\n\nend)\n__bundle_register(\"campaign-manager.Task\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Task = {}\r\n\r\nTask.Event = EventType\r\n\r\nfunction Task.complete(event, ...)\r\n EventManager.triggerEvent(event, ...)\r\nend\r\n\r\n---@param handler fun(savefile: gh_Savefile): void\r\n---@param event string\r\nfunction Task.registerSave(handler, event)\r\n EventManager.addHandler(event, handler)\r\nend\r\n\r\n---@param event string\r\n---@param savefile gh_Savefile\r\nfunction Task.completeSave(event, savefile)\r\n EventManager.triggerEvent(event, savefile)\r\nend\r\n\r\n---@param handler fun(obj: tts__Object, player: number, character: gh_Save_Character): void\r\n---@param events string | string[]\r\nfunction Task.registerPerPlayer(handler, events)\r\n if type(events) == \"table\" then\r\n local perPlayer = {}\r\n local totalSize = #events\r\n local function callback(obj, player, character)\r\n local current = perPlayer[player]\r\n if current == nil then\r\n current = 0\r\n end\r\n current = current + 1\r\n perPlayer[player] = current\r\n\r\n if current == totalSize then\r\n perPlayer[player] = 0\r\n handler(obj, player, character)\r\n end\r\n end\r\n for _, event in ipairs(--[[---@type string[] ]] events) do\r\n EventManager.addHandler(event, callback)\r\n end\r\n else\r\n EventManager.addHandler(--[[---@type string]] events, handler)\r\n end\r\nend\r\n\r\nreturn Task\r\n\nend)\n__bundle_register(\"campaign-manager.Scenario\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\n\r\nlocal Logger = require(\"lib.Logger\")\r\nlocal Search = require(\"lib.Search\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\nlocal Game = require(\"campaign-manager.Game\")\r\nlocal ManagerOptions = require(\"campaign-manager.ManagerOptions\")\r\nlocal Unlocked = require(\"campaign-manager.Unlocked\")\r\n\r\nlocal Scenario = {}\r\n\r\n---@param savefile gh_Savefile\r\nfunction Scenario.setup(savefile)\r\n EventManager.addHandler(EventType.Loaded.Decks, function() Scenario.loadAll(savefile.global.scenarios) end)\r\nend\r\n\r\n---@type table\r\nlocal State = {\r\n Open = \"Open\",\r\n Done = \"Done\",\r\n Locked = \"Locked\",\r\n}\r\n\r\n---@param scenarios gh_Save_Scenarios\r\nfunction Scenario.loadAll(scenarios)\r\n local scenarioStickers = --[[---@type fantasySetup_Scenario_Stickers]] Component.map().getTable(\"tableML\")\r\n\r\n local randomScenarioDeck = Component.randomScenarioDeck()\r\n for number, state in pairs(scenarios) do\r\n local stickerGuid = Scenario.findStickerGuid(scenarioStickers, number)\r\n Scenario.placeSticker(stickerGuid, number, state)\r\n\r\n if Scenario.isRandomScenario(number) then\r\n Scenario.removeRandomScenario(randomScenarioDeck, number)\r\n end\r\n end\r\nend\r\n\r\n---@param scenarioStickers fantasySetup_Scenario_Stickers\r\n---@param number string\r\n---@return GUID\r\nfunction Scenario.findStickerGuid(scenarioStickers, number)\r\n for stickerGuid, info in pairs(scenarioStickers) do\r\n if tostring(info.number) == number then\r\n return stickerGuid\r\n end\r\n end\r\nend\r\n\r\n---@param stickerGuid GUID\r\n---@param number string\r\n---@param state gh_Scenario_State\r\nfunction Scenario.placeSticker(stickerGuid, number, state)\r\n Logger.verbose(\"Placing scenario \" .. number .. \" as \" .. state)\r\n local map = Component.map()\r\n if state == State.Open then\r\n map.call(\"clickedML\", stickerGuid)\r\n elseif state == State.Done then\r\n map.call(\"clickedML\", stickerGuid)\r\n map.call(\"oneClick\", { stickerGuid })\r\n elseif state == State.Locked then\r\n map.call(\"clickedML\", stickerGuid)\r\n map.call(\"triClick\", { stickerGuid })\r\n else\r\n Logger.error(\"Unknown scenario state '%s'\", state)\r\n end\r\nend\r\n\r\n---@param number string\r\n---@return boolean\r\nfunction Scenario.isRandomScenario(number)\r\n return Game.RandomScenarios[--[[---@not nil]] tonumber(number)] ~= nil\r\nend\r\n\r\n---@param randomScenarioDeck tts__Bag\r\n---@param number string\r\nfunction Scenario.removeRandomScenario(randomScenarioDeck, number)\r\n local scenarioCard = --[[---@not nil]] Search.inContainer(randomScenarioDeck, { description = tostring(number) })\r\n if not scenarioCard then\r\n Logger.error(\"Can't find the random scenario %d in the deck. Can't remove the card.\", number)\r\n return\r\n end\r\n\r\n randomScenarioDeck.takeObject({\r\n index = scenarioCard.index,\r\n position = Component.safePosition(),\r\n callback_function = function(obj)\r\n Component.discardScenario(--[[---@type tts__Card]]obj, ManagerOptions.keepDiscardedItems)\r\n end,\r\n })\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Scenario.saveAll(savefile)\r\n local map = Component.map()\r\n local scenarioStickers = --[[---@type fantasySetup_Scenario_Stickers]] map.getTable(\"tableML\")\r\n local unlockedScenarios = map.getTable(\"locations\")\r\n\r\n for stickerGuid, info in pairs(scenarioStickers) do\r\n if getObjectFromGUID(stickerGuid) ~= nil and info.number ~= nil then\r\n local state = State.Open\r\n if unlockedScenarios[stickerGuid] then\r\n if unlockedScenarios[stickerGuid][1] == 1 then\r\n state = State.Locked\r\n else\r\n state = State.Done\r\n end\r\n end\r\n savefile.global.scenarios[tostring(info.number)] = state\r\n end\r\n end\r\nend\r\n\r\n--- Creates a table from the given savefile that can be used for the Gloomhaven Scenario Tree\r\n---@param savefile gh_Savefile\r\n---@return gh_ScenarioTree\r\nfunction Scenario.saveScenarioTree(savefile)\r\n local scenarioTree = --[[---@type gh_ScenarioTree]] {\r\n nodes = {}, version = \"2\",\r\n }\r\n\r\n if savefile.global and savefile.global.scenarios then\r\n for id, state in pairs(savefile.global.scenarios) do\r\n local scenario = --[[---@type gh_ScenarioTree_Node]]{}\r\n scenario.id = id\r\n local treasures = --[[---@type gh_ScenarioTree_Treasures]] {}\r\n\r\n local scenarioTreasures = Game.Scenario[--[[---@not nil]] tonumber(id)]\r\n\r\n if not scenarioTreasures then\r\n scenarioTreasures = {}\r\n end\r\n\r\n for _, treasure in ipairs(scenarioTreasures) do\r\n if TableUtil.contains(savefile.unlocked.treasures, treasure) then\r\n local treasureId = string.format(\"%02d\", treasure)\r\n treasures[treasureId] = { looted = \"true\" }\r\n end\r\n end\r\n\r\n if TableUtil.isNotEmpty(treasures) then\r\n scenario.treasure = treasures\r\n end\r\n\r\n if state == State.Done then\r\n scenario.status = \"complete\"\r\n else\r\n if TableUtil.isNotEmpty(scenario.treasure) then\r\n scenario.status = \"attempted\"\r\n else\r\n scenario.status = \"incomplete\"\r\n end\r\n end\r\n\r\n table.insert(scenarioTree.nodes, scenario)\r\n end\r\n end\r\n\r\n return scenarioTree\r\nend\r\n\r\nreturn Scenario\r\n\nend)\n__bundle_register(\"campaign-manager.Retirement\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal Game = require(\"campaign-manager.Game\")\nlocal ManagerOptions = require(\"campaign-manager.ManagerOptions\")\nlocal Quest = require(\"campaign-manager.Quest\")\nlocal Task = require(\"campaign-manager.Task\")\n\nlocal Retirement = {}\n\nlocal MAX_ROWS = 24\n\nlocal function setupHandlers()\n Task.registerSave(Retirement.saveAll, Task.Event.Saved.Party)\nend\n\n---@param savefile gh_Savefile\nfunction Retirement.setup(savefile)\n EventManager.addHandler(EventType.Loaded.Decks, function() Retirement.unpackRetirementSheet(savefile.retired) end)\n EventManager.addHandler(EventType.Placed.RetirementSheet, function() Retirement.startLoading(savefile.retired) end)\nend\n\nlocal RetirementSheetColumns = 5\n\n---@param retired gh_Save_Retired[]\nfunction Retirement.unpackRetirementSheet(retired)\n if TableUtil.isNotEmpty(retired) then\n Component.placeRetirementSheet()\n end\nend\n\n---@param retired gh_Save_Retired[]\nfunction Retirement.startLoading(retired)\n Logger.info(\"Loading retired characters\")\n\n local retirementSheet = --[[---@not nil]] Component.retirementSheet()\n\n for i, retirement in ipairs(retired) do\n local row = i - 1\n local offset = row * RetirementSheetColumns\n\n if row < MAX_ROWS then\n retirementSheet.editInput({ index = offset + 0, value = retirement.player })\n retirementSheet.editInput({ index = offset + 1, value = retirement.character })\n retirementSheet.editInput({ index = offset + 2, value = retirement.class })\n retirementSheet.editInput({ index = offset + 3, value = retirement.level })\n retirementSheet.editInput({ index = offset + 4, value = retirement.perks })\n end\n\n if retirement.quest then\n Quest.take(retirement.quest, function(card)\n Component.discardQuest(--[[---@type tts__Card]] card, ManagerOptions.keepDiscardedItems) end)\n end\n end\n\n retirementSheet.call(\"forceSave\", {})\nend\n\n---@param savefile gh_Savefile\nfunction Retirement.saveAll(savefile)\n Retirement.saveRetirementSheet(savefile.retired)\n Retirement.saveCompletedQuests(savefile)\nend\n\n---@param retired gh_Save_Retired[]\nfunction Retirement.saveRetirementSheet(retired)\n local retirementSheet = Component.retirementSheet()\n if not retirementSheet then\n return\n end\n\n local retiredCharacter = --[[---@type gh_Save_Retired]] {}\n for i, input in pairs((--[[---@not nil]] retirementSheet).getInputs()) do\n local column = (i - 1) % RetirementSheetColumns\n if column == 0 then\n if TableUtil.isNotEmpty(retiredCharacter) then\n table.insert(retired, retiredCharacter)\n end\n retiredCharacter = --[[---@type gh_Save_Retired]] {}\n end\n\n if input.value ~= \"\" then\n if column == 0 then\n retiredCharacter.player = input.value\n elseif column == 1 then\n retiredCharacter.character = input.value\n elseif column == 2 then\n retiredCharacter.class = input.value\n elseif column == 3 then\n retiredCharacter.level = --[[---@not nil]] tonumber(input.value)\n elseif column == 4 then\n retiredCharacter.perks = --[[---@not nil]] tonumber(input.value)\n end\n end\n end\n if TableUtil.isNotEmpty(retiredCharacter) then\n table.insert(retired, retiredCharacter)\n end\nend\n\n---@param savefile gh_Savefile\nfunction Retirement.saveCompletedQuests(savefile)\n local completedQuests = --[[---@type table ]] {}\n for name, _ in pairs(Game.Quests) do\n completedQuests[name] = 1\n end\n\n local questDeck = Component.questDeck()\n for _, quest in pairs(questDeck.getObjects()) do\n local questInfo = --[[---@not nil]] Component.getQuestInfo(quest)\n if questInfo ~= nil then\n completedQuests[questInfo.name] = nil\n end\n end\n\n for _, character in pairs(savefile.party.characters) do\n if character.quest then\n local playerQuest = Game.quest(character.quest)\n completedQuests[playerQuest.name] = nil\n end\n end\n\n local i = 1\n for completed, _ in pairs(completedQuests) do\n if not savefile.retired[i] then\n savefile.retired[i] = --[[---@type gh_Save_Retired]] {}\n end\n savefile.retired[i].quest = completed\n i = i + 1\n end\nend\n\nsetupHandlers()\n\nreturn Retirement\n\nend)\n__bundle_register(\"campaign-manager.Quest\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Search = require(\"lib.Search\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal Game = require(\"campaign-manager.Game\")\n\nlocal Quest = {}\n\n---@param quest gh_Save_Quest\n---@param callback tts__ObjectCallbackFunction\n---@return boolean\nfunction Quest.take(quest, callback)\n local questDeck = Component.questDeck()\n local questCard, index = Search.inContainedObjects(questDeck, { name = Quest.getQuestName(quest) })\n if not questCard then\n return false\n end\n\n questDeck.takeObject({\n index = index,\n smooth = false,\n rotation = { 0, 180, 180 },\n callback_function = callback\n })\n return true\nend\n\n---@param quest gh_Save_Quest\n---@return string\nfunction Quest.getQuestName(quest)\n local questByName = Game.Quests[--[[---@type string]] quest]\n if questByName then\n return --[[---@type string]] quest\n end\n\n for questName, questInfo in pairs(Game.Quests) do\n if questInfo.number == quest then\n return questName\n end\n end\n return quest\nend\n\nreturn Quest\n\nend)\n__bundle_register(\"campaign-manager.Preparation\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Preparation = {}\r\n\r\nfunction Preparation.setup()\r\n EventManager.addHandler(EventType.Loaded.Start, function() Preparation.prepareLoad() end)\r\nend\r\n\r\nfunction Preparation.prepareLoad()\r\n Preparation.getDecks()\r\n Component.placeTreasureDeck()\r\n Component.placeLockedCharacter()\r\nend\r\n\r\nfunction Preparation.getDecks()\r\n Wait.time(function()\r\n EventManager.triggerEvent(EventType.Loaded.Decks)\r\n end, 2)\r\nend\r\n\r\nreturn Preparation\r\n\nend)\n__bundle_register(\"campaign-manager.Player\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal Notebook = require(\"lib.Notebook\")\r\nlocal StringUtil = require(\"lib.StringUtil\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Player = {}\r\n\r\nlocal OnScreenNotes = \"OnScreenNotes\"\r\nlocal IgnoredNotebooks = { \"Options\", \"Previous Options\", \"Quick Start Guide\", \"Mod Version\",\r\n \"Savefile\", \"New Savefile\", \"Scenario Tree\", OnScreenNotes }\r\n\r\n---@param notes tts__Notes_GetParameter\r\n---@return boolean\r\nlocal function hasContent(notes)\r\n return StringUtil.isNotEmpty(notes.body)\r\nend\r\n\r\n---@param notes tts__Notes_GetParameter\r\n---@return boolean\r\nlocal function isGlobalNotebook(notes)\r\n return not TableUtil.contains(IgnoredNotebooks, notes.title) and notes.color == \"Grey\"\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.setup(savefile)\r\n EventManager.addHandler(EventType.Loaded.Decks, function() Player.loadAll(savefile) end)\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.loadAll(savefile)\r\n Player.loadPlayers(savefile)\r\n Player.loadNotes(savefile)\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.loadPlayers(savefile)\r\n for player, content in pairs(savefile.players) do\r\n local playerNotes = StringUtil.decodeBase64(content.notes)\r\n Notebook.setContent(player, playerNotes, --[[---@type tts__PlayerColor]] player)\r\n end\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.loadNotes(savefile)\r\n for _, note in ipairs(savefile.notes) do\r\n if note.name == OnScreenNotes then\r\n Notes.setNotes(note.content)\r\n else\r\n Notebook.addContent(note.name, note.content)\r\n end\r\n end\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.saveAll(savefile)\r\n Player.savePlayers(savefile)\r\n Player.saveNotes(savefile)\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.savePlayers(savefile)\r\n for _, notes in ipairs(Notebook.getAll()) do\r\n if hasContent(notes) and TableUtil.contains(Component.PlayerColors, notes.title) then\r\n local playerNotes = StringUtil.encodeBase64(notes.body)\r\n savefile.players[--[[---@type tts__PlayerColor]] notes.title] = { notes = playerNotes }\r\n end\r\n end\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Player.saveNotes(savefile)\r\n for _, notes in ipairs(Notebook.getAll()) do\r\n if hasContent(notes) and isGlobalNotebook(notes) then\r\n table.insert(savefile.notes, { name = notes.title, content = notes.body })\r\n end\r\n end\r\n\r\n if StringUtil.isNotEmpty(Notes.getNotes()) then\r\n table.insert(savefile.notes, { name = OnScreenNotes, content = Notes.getNotes() })\r\n end\r\nend\r\n\r\nreturn Player\r\n\nend)\n__bundle_register(\"lib.Notebook\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Notebook = require(\"sebaestschjin-tts.Notebook\")\r\n\r\nreturn Notebook\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Notebook\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--- Helper methods to access to player notebooks within TTS.\r\nlocal Notebook = {}\r\n\r\n---@return tts__Notes_GetParameter[]\r\nfunction Notebook.getAll()\r\n return Notes.getNotebookTabs()\r\nend\r\n\r\n--- Returns the content of the first notebook with the given title.\r\n---@param title string\r\n---@return nil | string\r\nfunction Notebook.getContent(title)\r\n for _, notebook in pairs(Notes.getNotebookTabs()) do\r\n if notebook.title == title then\r\n return notebook.body\r\n end\r\n end\r\n return nil\r\nend\r\n\r\n--- Sets the content of a notebook with the given name. If no such notebook can be found a new one will be created.\r\n--- If the player color is given the existing or new notebook will be only readable by this player.\r\n---@overload fun(name: string, content: string): void\r\n---@param title string\r\n---@param content string\r\n---@param player tts__PlayerColor\r\nfunction Notebook.setContent(title, content, player)\r\n for _, notebook in pairs(Notes.getNotebookTabs()) do\r\n if notebook.title == title then\r\n Notes.editNotebookTab({\r\n index = notebook.index,\r\n body = content,\r\n color = player,\r\n })\r\n return\r\n end\r\n end\r\n Notebook.addContent(title, content, player)\r\nend\r\n\r\n--- Adds a notebook with the given name.\r\n---@overload fun(name: string, content: string): void\r\n---@param title string\r\n---@param content string\r\n---@param player tts__PlayerColor\r\nfunction Notebook.addContent(title, content, player)\r\n Notes.addNotebookTab({\r\n title = title,\r\n body = content,\r\n color = player,\r\n })\r\nend\r\n\r\nreturn Notebook\nend)\n__bundle_register(\"campaign-manager.Party\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Json = require(\"lib.Json\")\nlocal Logger = require(\"lib.Logger\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ExtensionHandler = require(\"ExtensionHandler\")\n\nlocal Character = require(\"campaign-manager.Character\")\nlocal Component = require(\"campaign-manager.Component\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal Options = require(\"campaign-manager.ManagerOptions\")\nlocal Task = require(\"campaign-manager.Task\")\n\nlocal Party = {}\n\n---@param savefile gh_Savefile\nlocal function fireClassesReady(savefile)\n -- TODO not the best place for this, but with the current weird event based logic of the CM, the best we got\n ExtensionHandler.call(\"onCampaignLoad\", savefile)\n\n for _, class in ipairs(savefile.context.classes) do\n EventManager.triggerEvent(EventType.Loaded.Class.Start, class)\n end\nend\n\n---@param savefile gh_Savefile\nfunction Party.setup(savefile)\n EventManager.addHandler(EventType.Loaded.Items, function() fireClassesReady(savefile) end)\n EventManager.addHandler(EventType.Loaded.Class.Enhanced, function(className) Party.mayLoadCharacter(savefile, className) end)\nend\n\n---@param content gh_Savefile\n---@param characterClass string\nfunction Party.mayLoadCharacter(content, characterClass)\n for id, character in pairs(content.party.characters) do\n if character.class == characterClass then\n if Character.isActive(id) then\n local player = --[[---@not nil]] tonumber(id)\n if Character.isSupported(id) then\n Character.load(player, character)\n else\n Logger.warn(\"Tried to load character number %s, but this version only supports %s players.\"\n .. \" The character will be loaded as an inactive character instead\", player, Component.playerCount())\n Character.loadInactive(TableUtil.length(content.party.characters), character)\n end\n else\n local _, _, place = id:find(\"(%d+)\")\n Character.loadInactive(--[[---@not nil]] tonumber(--[[---@type string]] place), character)\n end\n end\n end\nend\n\n---@param savefile gh_Savefile\nfunction Party.saveAll(savefile)\n local party = savefile.party\n\n party.characters = --[[---@type gh_Save_Characters ]]{}\n for player = 1, Component.playerCount() do\n Character.save(player, party.characters)\n end\n\n for i, inactiveCharacterBox in ipairs(Component.inactiveCharacters()) do\n local id = \"inactive\" .. tostring(i)\n party.characters[id] = Json.decode(inactiveCharacterBox.script_state)\n end\n\n Task.completeSave(Task.Event.Saved.Party, savefile)\nend\n\nreturn Party\n\nend)\n__bundle_register(\"campaign-manager.Character\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Chain = require(\"lib.Chain\")\nlocal Constant = require(\"lib.Constant\")\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal ObjectData = require(\"lib.ObjectData\")\nlocal Promise = require(\"lib.Promise\")\nlocal Search = require(\"lib.Search\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal SignalLoading = require(\"lib.SignalLoading\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal TtsBase = require(\"lib.promise.TtsBase\")\nlocal TtsObject = require(\"lib.promise.TtsObject\")\nlocal TtsWait = require(\"lib.promise.TtsWait\")\nlocal Utils = require(\"lib.Utils\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal R = require(\"api.Resource\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal Enhancement = require(\"campaign-manager.Enhancement\")\nlocal ManagerGame = require(\"campaign-manager.Game\")\nlocal Game = require(\"Game\")\nlocal Party = require(\"Party\")\nlocal Quest = require(\"campaign-manager.Quest\")\nlocal Shop = require(\"campaign-manager.Shop\")\nlocal OptionsApi = require(\"api.OptionsApi\")\nlocal ExtensionHandler = require(\"ExtensionHandler\")\n\nlocal ipairs = TableUtil.ipairs\n\nlocal Character = {}\n\n---@class __Character_this\nlocal this = {}\nlocal CharacterApi = --[[---@type CharacterApi]] ApiProvider(\"character\")\n\nlocal RELATIVE_POSITION = {\n AbilityDeck = { -3.2, -2.26, -15.17 },\n AttackModifierDeck = { -7, 0, -14.27 },\n CharacterBox = { 0.62, 0, -15.77 },\n CharacterFigure = { 0, 0, 23.23 },\n CharacterMat = { 2.18, -2.26, 16.95 },\n ExtraTokens = { 0, 0, -11 },\n}\n\n---@param player number\n---@param character gh_Save_Character\nfunction Character.load(player, character)\n Logger.info(\"Loading Character %s at %d\", character.class, player)\n\n Component.loadPlayer(player, function()\n Character.unpackCharacterBox(player, character)\n end)\nend\n\n---@param position integer\n---@param inactiveCharacter gh_Save_Character\nfunction Character.loadInactive(position, inactiveCharacter)\n local callback = function(box)\n Character.fillInactive(inactiveCharacter, box)\n end\n Component.placeInactiveCharacter(inactiveCharacter, callback, position)\nend\n\n---@param character gh_Save_Character\n---@param characterBox tts__Bag\nfunction Character.fillInactive(character, characterBox)\n local putIntoBox = function(o)\n characterBox.putObject(o)\n end\n\n if character.quest then\n Quest.take(character.quest, putIntoBox)\n end\n\n for _, items in pairs(character.items) do\n for _, itemName in ipairs(items) do\n if not Shop.takeItem(itemName, { callback = putIntoBox }) then\n Logger.error(\"The item '%s' does not exist. Won't load item.\", itemName)\n end\n end\n end\nend\n\n---@param player number\n---@param character gh_Save_Character\nfunction Character.unpackCharacterBox(player, character)\n local class = character.class\n local box = this.getNewClassBox(class)\n if not box then\n Logger.error(\"Did not find the class box for class %s! Is it part of the unlocked classes?\", class)\n return\n end\n\n this.unpackCharacterBoxContent(player, character, --[[---@not nil]] box)\nend\n\n---@param boxGuid GUID\n---@param character gh_Save_Character\nfunction Character.unpackInactiveCharacterBox(boxGuid, character)\n local inactiveCharacterBox = --[[---@type tts__Bag]] getObjectFromGUID(boxGuid)\n\n for player = 1, Component.playerCount() do\n if Component.isPlayerObject(player, inactiveCharacterBox) then\n local unpackChain = Chain()\n\n ---@param object tts__ObjectState\n ---@param deckSupplier fun(): (tts__Bag | seb_WrappedDeck)\n local function putObjectOnDeck(object, deckSupplier)\n unpackChain.add(function(chain)\n inactiveCharacterBox.takeObject({\n guid = object.GUID,\n smooth = false,\n callback_function = function(card)\n deckSupplier().putObject(--[[---@type tts__Card]] card)\n chain.proceed()\n end\n })\n end)\n end\n\n for _, object in ipairs(inactiveCharacterBox.getData().ContainedObjects) do\n if Component.isItemCard(object) then\n putObjectOnDeck(object, Component.shopDeck)\n elseif Component.isPersonalQuestCard(object) then\n putObjectOnDeck(object, Component.questDeck)\n end\n end\n\n unpackChain.add(function(chain)\n inactiveCharacterBox.destruct()\n chain.proceed()\n end)\n unpackChain.add(function(chain)\n local characterBox = --[[---@not nil]] this.getNewClassBox(character.class)\n for _, remaining in ipairs(inactiveCharacterBox.getData().ContainedObjects) do\n table.insert((--[[---@not nil]] characterBox).ContainedObjects, remaining)\n end\n\n this.unpackCharacterBoxContent(player, character, characterBox)\n chain.proceed()\n end)\n\n unpackChain.proceed()\n return\n end\n end\n\n Logger.error(\"The character box is not in a player zone. Put the box into a player zone of your choice and try again.\")\nend\n\nCharacterApi.getLevel = function(xp)\n return Character.getLevel(xp)\nend\n\n---@param class string\n---@return nil | tts__BagState\nfunction this.getNewClassBox(class)\n local classBox = --[[---@not nil]] Component.classBox(class)\n if not classBox then\n return nil\n end\n\n return --[[---@type tts__BagState]] classBox.getData().ContainedObjects[1]\nend\n\n---@param player integer\n---@param character gh_Save_Character\n---@param characterBox tts__BagState\nfunction this.unpackCharacterBoxContent(player, character, characterBox)\n local boxContent = characterBox.ContainedObjects\n\n if OptionsApi.enhancementSystem() == OptionsApi.EnhancementSystem.NonPermanent then\n Enhancement.applyOnCharacterBox(characterBox, character.class, character.enhancements or {})\n end\n\n this.placePlayerMat(player, boxContent)\n :next(function(playerMat)\n return Promise.all({\n this.placeCharacterMat(player, boxContent),\n this.placeItems(player, character),\n this.placeTrackers(playerMat, boxContent),\n this.placeSummons(playerMat, boxContent),\n this.placeQuest(player, character),\n this.placeCharacterFigure(playerMat, boxContent, player, character),\n this.placeAttackModifierDecks(playerMat, boxContent, player, character),\n this.placeAbilityDeck(boxContent, player, character),\n this.placeCharacterSheet(player, character, boxContent),\n })\n end)\n :next(function() this.placeCharacterSpecificComponents(boxContent, player, character) end)\n :next(function() this.callUnpackExtensions(boxContent, player, character) end)\n :next(function() this.placeCharacterBox(characterBox, player) end)\n :next(function() this.sortPlayerHand(player, character) end)\n :catch(function(reason) Logger.error(reason) end)\nend\n\n---@param player number\n---@param position tts__Vector\n---@return gh_Save_Character\nfunction Character.packCharacterBox(player, position)\n if not Component.characterSheet(player) then\n local playerMat = Component.playerMat(player)\n if playerMat then\n playerMat.destruct()\n end\n return\n end\n\n ---@return gh_Save_Character\n local function storeCharacter()\n local characters = --[[---@type gh_Save_Characters]] {}\n Character.save(player, characters)\n return characters[tostring(player)]\n end\n\n ---@param className string\n local function storeEnhancements(className)\n if OptionsApi.enhancementSystem() ~= OptionsApi.EnhancementSystem.Classic then\n return\n end\n\n local enhancements = --[[---@type gh_Save_Enhancements]] {}\n Enhancement.saveFromZone(player, enhancements)\n local classEnhancements = enhancements[className]\n if TableUtil.isNotEmpty(classEnhancements) then\n Enhancement.loadClass(className, classEnhancements)\n end\n return classEnhancements\n end\n\n --- Find all components within objects that are limited and should be placed into an inactive player box.\n ---@param limitedComponents tts__ObjectState[]\n ---@param objects seb_Object_Array\n local function findLimitedComponents(limitedComponents, objects)\n for _, obj in ipairs(--[[---@type seb_Object[] ]] objects) do\n if Component.isPersonalQuestCard(obj) or Component.isItemCard(obj) or Component.remainInCharacterBox(obj) then\n table.insert(limitedComponents, Object.data(obj))\n elseif Object.isContainer(obj) and not Object.hasTag(obj, R.Tag.Component.LockedContent) then\n findLimitedComponents(limitedComponents, Object.objects(--[[---@type seb_Object_Container]] obj))\n end\n end\n end\n\n ---@param playerObjects tts__Object[]\n local function removeObjects(playerObjects)\n for _, obj in ipairs(playerObjects) do\n obj.destruct()\n end\n end\n\n local function inputLimitedObjects(characterBox)\n local limitedComponents = {}\n local playerObjects = Component.playerObjects(player)\n findLimitedComponents(limitedComponents, playerObjects)\n\n local characterBoxData = characterBox.getData()\n characterBoxData.ContainedObjects = limitedComponents\n\n Object.respawn(characterBoxData)\n removeObjects(playerObjects)\n end\n\n local className = (--[[---@not nil]] Component.characterMat(player)).getDescription()\n local class = ManagerGame.class(className)\n if not class then\n Logger.warn(\"No registered class found for player %s\", player)\n return\n end\n\n Global.call(\"checkForBlessCurse\", player)\n Global.call(\"checkForAdditionalNeg1\", Component.attackModifiers(player))\n\n local character = storeCharacter()\n storeEnhancements(className)\n\n local pos = { x = position.x, y = position.y - 0.05, z = position.z - 8.17 }\n\n Component.placeInactiveCharacter(character, inputLimitedObjects, pos)\n\n return character\nend\n\n---@param content tts__ObjectState[]\n---@param finder fun(data: tts__ObjectState): boolean\nfunction this.takeContent(content, finder)\n for i, data in ipairs(content) do\n if finder(data) then\n table.remove(content, i)\n return data\n end\n end\n\n return nil\nend\n\n---@param content tts__ObjectState[]\n---@param finder fun(data: tts__ObjectState): boolean\n---@return tts__ObjectState[]\nfunction this.takeAllContent(content, finder)\n local takenContent = --[[---@type tts__ObjectState[] ]] {}\n\n local foundData = this.takeContent(content, finder)\n while foundData ~= nil do\n table.insert(takenContent, --[[---@not nil]] foundData)\n foundData = this.takeContent(content, finder)\n end\n\n return takenContent\nend\n\n---@param name string\n---@return fun(data: tts__ObjectState): boolean\nfunction this.byName(name)\n return function(data)\n return Object.name(data):find(name) ~= nil\n end\nend\n\n---@param name string\n---@return fun(data: tts__ObjectState): boolean\nfunction this.byExactName(name)\n return function(data)\n return Object.name(data) == name\n end\nend\n\n---@param tag string\n---@return fun(data: tts__ObjectState): boolean\nfunction this.byTag(tag)\n return function(data)\n return Object.hasTag(data, tag)\n end\nend\n\n---@param player integer\n---@param content tts__ObjectState[]\n---@return gloom_Promise\nfunction this.placePlayerMat(player, content)\n local playerMat = this.takeContent(content, Component.isPlayerMat)\n if not playerMat then\n return Promise.reject(\"Character box does not contain a valid Player mat\")\n end\n\n Component.adjustPlayerMatSnapPoints(--[[---@not nil]] playerMat)\n\n local currentMat = Component.playerMat(player)\n if currentMat then\n currentMat.destruct()\n end\n\n local matPosition = Component.playerMatPosition(player)\n\n return TtsBase.spawnObjectData({\n data = playerMat,\n position = matPosition,\n rotation = { 0, 180, 0 },\n callback_function = function(obj)\n obj.setLock(true)\n return obj\n end\n })\nend\n\n---@param player integer\n---@param content tts__ObjectState[]\nfunction this.placeCharacterMat(player, content)\n local characterMat = this.takeContent(content, this.byName(\"Character Mat\"))\n if not characterMat then\n return Promise.reject(\"Character box does not contain a valid Character mat\")\n end\n\n local position = Component.playerPosition(player, RELATIVE_POSITION.CharacterMat)\n\n return TtsBase.spawnObjectData({\n data = characterMat,\n position = position,\n callback_function = function(obj)\n obj.setLock(true)\n return obj\n end\n })\nend\n\n---@param playerMat tts__Object\n---@param content tts__ObjectState[]\nfunction this.placeTrackers(playerMat, content)\n local trackersBag = this.takeContent(content, this.byName(\"Trackers\"))\n if not trackersBag then\n return Promise.fulfill(nil)\n end\n\n local position = --[[---@not nil]] Utils.getSnapPosition(playerMat, 1)\n position:add(Vector(0, 1, 0))\n\n return TtsBase.spawnObjectData({ data = trackersBag, position = position, })\nend\n\n---@param playerMat tts__Object\n---@param content tts__ObjectState[]\nfunction this.placeSummons(playerMat, content)\n local summonsBag = this.takeContent(content, this.byName(\"Summons\"))\n if not summonsBag then\n return Promise.fulfill(nil)\n end\n\n local position = --[[---@not nil]] Utils.getSnapPosition(playerMat, 2)\n position:add(Vector(0, 1, 0))\n\n return TtsBase.spawnObjectData({ data = summonsBag, position = position, })\nend\n\n---@param player integer\n---@param character gh_Save_Character\nfunction this.placeQuest(player, character)\n if not character.quest then\n return\n end\n\n local questDeck = Component.questDeck()\n local questCard, index = Search.inContainedObjects(questDeck, { name = Quest.getQuestName(character.quest) })\n if not questCard then\n Logger.error(\"A quest named '%s' does not exist. Can't draw the quest from the deck.\", character.quest)\n return Promise.fulfill()\n end\n\n return TtsObject.takeObject(questDeck, {\n index = index,\n smooth = false,\n position = Component.questPosition(player),\n rotation = { 0, 180, 180 },\n })\nend\n\n---@param playerMat tts__Object\n---@param content tts__ObjectState[]\n---@param player integer\n---@param character gh_Save_Character\nfunction this.placeCharacterFigure(playerMat, content, player, character)\n ---@param object tts__ObjectState\n ---@return boolean\n local function isFigure(object)\n return Object.hasTag(object, R.Tag.Character) and Object.name(object) == character.class\n end\n\n local function placeFigure()\n local figure = this.takeContent(content, isFigure)\n if not figure then\n return Promise.reject(\"Can not find character figure\")\n end\n\n local position = Component.playerPosition(player, RELATIVE_POSITION.CharacterFigure)\n return TtsBase.spawnObjectData({ data = figure, position = position })\n :next(TtsWait.untilLoaded)\n end\n\n local healthProgression = (--[[---@not nil]] ManagerGame.class(character.class)).hp\n return this.placeCharacterDials(playerMat)\n :next(placeFigure)\n :next(function(figure) this.setFigureHealth(figure, character, healthProgression) end)\nend\n\nfunction this.placeCharacterDials(playerMat)\n ---@param name string\n ---@param bagGuid string\n ---@param snapPoint integer\n local function placeDialFromBag(name, bagGuid, snapPoint)\n local dialsBag = --[[---@type tts__Bag]] getObjectFromGUID(bagGuid)\n if not dialsBag then\n return Promise.reject(\"No dials bag found for \" .. name)\n end\n\n local position = --[[---@not nil]] Utils.getSnapPosition(playerMat, snapPoint)\n position:add(Vector(0, 1, 0))\n\n local dialData = dialsBag.getData().ContainedObjects[1]\n return TtsBase.spawnObjectData({ data = dialData, position = position, rotation = { 0, 180, 0 } })\n end\n\n return Promise.all({\n placeDialFromBag(\"HP\", \"6c775e\", 10),\n placeDialFromBag(\"XP\", \"0625c4\", 11),\n })\nend\n\n---@param figure tts__Object\n---@param character gh_Save_Character\n---@param healthProgression gloom_Class_HpProgression\nfunction this.setFigureHealth(figure, character, healthProgression)\n local level = Character.getLevel(character.xp)\n\n local function doSetHealth()\n local maxHealth = healthProgression[level]\n print(\"Level is \" .. level .. \" and max health is \" .. maxHealth)\n figure.call(\"setHp\", { value = maxHealth })\n return figure\n end\n\n return TtsWait.condition(TtsWait.untilLoaded):next(doSetHealth)\nend\n\n---@param playerMat tts__Object\n---@param player integer\n---@param content tts__ObjectState[]\n---@param character gh_Save_Character\nfunction this.placeAttackModifierDecks(playerMat, content, player, character)\n local function getBaseDeck()\n local guids = { \"bb85b9\", \"d23231\", \"74238d\", \"20d00f\" }\n local bag = --[[---@type tts__Bag]] getObjectFromGUID(guids[player])\n return --[[---@type tts__DeckState]] bag.getData().ContainedObjects[1]\n end\n\n local baseDeck = getBaseDeck()\n local baseDeckPosition = --[[---@not nil]] Utils.getSnapPosition(playerMat, 4)\n baseDeckPosition:add(Vector(0, 1, 0))\n\n local function spawnBaseDeck()\n return TtsBase.spawnObjectData({\n data = baseDeck,\n position = baseDeckPosition,\n rotation = { 0, 180, 180 },\n })\n end\n\n local function getExtraDeck()\n return --[[---@type tts__DeckState]] this.takeContent(content, this.byName(\"Attack Modifiers\"))\n end\n\n local extraDeck = getExtraDeck()\n local extraDeckPosition = Component.playerPosition(player, RELATIVE_POSITION.AttackModifierDeck)\n\n local function spawnExtraDeck()\n if #extraDeck.ContainedObjects == 0 then\n return Promise.fulfill()\n end\n if #extraDeck.ContainedObjects == 1 then\n extraDeck = ObjectData.takeObject(extraDeck, { index = 1 })\n end\n\n return TtsBase.spawnObjectData({\n data = extraDeck,\n position = extraDeckPosition,\n rotation = { 0, 180, 0 },\n })\n end\n\n local extraCards = {}\n\n local function spawnExtraCards()\n for i, perkCard in ipairs(extraCards) do\n local position = Vector(0.84 - (i - 1) * 0.45, 0.1, 1.11)\n local targetPosition = playerMat.positionToWorld(position)\n\n TtsBase.spawnObjectData({\n data = perkCard,\n position = targetPosition,\n rotation = { 0, 180, 0 },\n })\n end\n\n return Promise.resolve()\n end\n\n if not baseDeck or not extraDeck then\n return Promise.reject(\"No base or extra attack modfier deck found\")\n end\n\n this.resolvePerks(character, baseDeck, extraDeck, extraCards)\n\n ---@param deck tts__Object\n local function shuffleObject(deck)\n deck.shuffle()\n return deck\n end\n\n return spawnBaseDeck():next(shuffleObject):next(spawnExtraDeck):next(spawnExtraCards)\nend\n\n---@param character gh_Save_Character\n---@param baseDeck tts__DeckState\n---@param extraDeck tts__DeckState\n---@param extraCards tts__CardCustomState[]\nfunction this.resolvePerks(character, baseDeck, extraDeck, extraCards)\n local removed = {}\n local perkInfo = (--[[---@not nil]] ManagerGame.class(character.class)).perks\n for _, perk in ipairs(character.perks) do\n if perkInfo[perk] then\n this.resolvePerk(character.class, perkInfo[perk], baseDeck, extraDeck, removed, extraCards)\n else\n Logger.error(\"A perk '%s' does not exist for class '%s'.\", perk, character.class)\n end\n end\n\n for itemName, perk in pairs(ManagerGame.Items.WithPerks) do\n if Character.isItemEquipped(character, itemName) then\n this.resolvePerk(character.class, perk, baseDeck, extraDeck, removed, extraCards)\n end\n end\nend\n\n---@param characterClass string\n---@param perk gloom_Perk\n---@param baseDeck tts__DeckCustomState\n---@param extraDeck tts__DeckCustomState\n---@param removedDeck tts__CardCustomState[]\n---@param extra tts__CardCustomState[]\nfunction this.resolvePerk(characterClass, perk, baseDeck, extraDeck, removedDeck, extra)\n ---@param deck tts__DeckCustomState\n ---@param card string\n local function findModifierCard(deck, card)\n local search = { name = Component.modifierNamePattern(card), isPattern = true }\n local foundCard = Search.inContainerData(deck, search)\n return foundCard\n end\n\n ---@param deck tts__DeckCustomState\n ---@param card string\n local function removeCard(deck, card)\n local foundCard = findModifierCard(deck, card)\n if foundCard then\n return --[[---@type tts__CardState]] ObjectData.takeObject(deck, {\n guid = (--[[---@type tts__ObjectState]] foundCard).GUID\n })\n end\n end\n\n for _, card in ipairs(perk.add) do\n local cardData = removeCard(extraDeck, card)\n if cardData then\n ObjectData.putObject(baseDeck, cardData)\n else\n Logger.error(\"%s - Perks: Can't find modifier card %s to add.\", characterClass, card)\n end\n end\n\n for _, card in ipairs(perk.remove) do\n local cardData = removeCard(baseDeck, card)\n if cardData then\n table.insert(removedDeck, cardData)\n else\n Logger.error(\"%s - Perks: Can't find modifier card %s to remove.\", characterClass, card)\n end\n end\n\n if perk.unlock and type(perk.unlock) == \"string\" then\n perk.unlock = { --[[---@type string]] perk.unlock }\n end\n\n for _, card in ipairs(perk.unlock) do\n local perkCard = --[[---@type tts__CardCustomState]] Search.inContainerData(extraDeck, { name = card })\n if perkCard then\n perkCard = --[[---@not nil]] ObjectData.takeObject(extraDeck, { guid = perkCard.GUID })\n table.insert(extra, perkCard)\n else\n Logger.error(\"Can't find perk card %s\", card)\n end\n end\nend\n\n---@param content tts__ObjectState[]\n---@param player integer\n---@param character gh_Save_Character\nfunction this.placeAbilityDeck(content, player, character)\n ---@type set\n local handledAbilities = {}\n\n ---@param abilityCard tts__CardCustomState\n local function placeAbilityCard(abilityCard)\n local abilityName = this.getAbilityName(abilityCard)\n local targetHand = 1\n if TableUtil.isNotEmpty(character.hand) and not TableUtil.contains(character.hand, abilityName) then\n targetHand = 2\n end\n\n handledAbilities[abilityName] = true\n\n local targetPosition = Component.handPosition(player, targetHand)\n spawnObjectData({ data = abilityCard, position = targetPosition, rotation = { 0, 180, 0 } })\n end\n\n local startingAbilities = --[[---@type tts__DeckCustomState]] this.takeContent(content, this.byName(\"Starting Abilities\"))\n if startingAbilities then\n for i = #startingAbilities.ContainedObjects, 1, -1 do\n local abilityCard = ObjectData.takeObject(startingAbilities, { index = i })\n placeAbilityCard(abilityCard)\n end\n else\n Logger.warn(\"Couldn't find the starting abilities inside the character box. Is the deck named correctly?\")\n end\n\n local advancedAbilities = --[[---@type tts__DeckCustomState]] this.takeContent(content, this.byName(\"Advanced Abilities\"))\n local abilitiesPosition = Component.playerPosition(player, RELATIVE_POSITION.AbilityDeck)\n\n if advancedAbilities then\n for i = #advancedAbilities.ContainedObjects, 1, -1 do\n local abilityCard = --[[---@type tts__CardCustomState]] advancedAbilities.ContainedObjects[i]\n local abilityName = this.getAbilityName(abilityCard)\n if TableUtil.contains(character.abilities, abilityName) then\n abilityCard = ObjectData.takeObject(advancedAbilities, { index = i })\n placeAbilityCard(abilityCard)\n end\n end\n else\n Logger.warn(\"Couldn't find the advanced abilities inside the character box. Is the deck named correctly?\")\n end\n\n for _, abilityCard in ipairs(this.takeAllContent(content, this.byTag(R.Tag.Class.Ability))) do\n placeAbilityCard(--[[---@type tts__CardCustomState]] abilityCard)\n end\n\n --- Finds extra ability cards that might be contained somewhere else in the box (e.g. the CS M cards)\n ---@param containedContent tts__ObjectState[]\n ---@return nil | tts__CardCustomState\n local function findExtraAbility(containedContent)\n for _, object in ipairs(containedContent) do\n if Object.hasTag(object, R.Tag.Class.Ability) and this.getAbilityName(object) then\n return --[[---@type tts__CardCustomState]] object\n end\n\n if (--[[---@type tts__ContainerState]] object).ContainedObjects then\n local childFound = findExtraAbility((--[[---@type tts__ContainerState]] object).ContainedObjects)\n if childFound then\n return childFound\n end\n end\n end\n\n return nil\n end\n\n for _, abilityCard in ipairs(character.abilities) do\n if not handledAbilities[abilityCard] then\n local extraAbility = findExtraAbility(content)\n if extraAbility then\n placeAbilityCard(--[[---@not nil]] extraAbility)\n else\n Logger.warn(\"Can not find ability cards %s for class %s\", abilityCard, character.class)\n end\n end\n end\n\n -- deck is empty, nothing to spawn anymore\n if not abilitiesPosition or #advancedAbilities.ContainedObjects == 0 then\n return\n end\n\n -- last card of the deck, make it a card now\n if #advancedAbilities.ContainedObjects == 1 then\n advancedAbilities = ObjectData.takeObject(advancedAbilities, { index = 1 })\n end\n\n spawnObjectData({ data = advancedAbilities, position = abilitiesPosition, })\nend\n\n---@param abilityCard seb_Object\nfunction this.getAbilityName(abilityCard)\n return StringUtil.replace(Object.name(abilityCard), \"%s*%(.*%)\")\nend\n\n---@param player integer\n---@param character gh_Save_Character\n---@param content tts__ObjectState[]\nfunction this.placeCharacterSheet(player, character, content)\n local characterSheet = --[[---@not nil]] this.takeContent(content, Component.isCharacterSheet)\n if not characterSheet then\n return Promise.reject(\"Character box does not contain a valid Character sheet\")\n end\n\n ---@param notes string[]\n ---@return string\n local function notesToString(notes)\n return table.concat(notes, \"\\n\")\n end\n\n ---@param hiddenNotes string[]\n ---@return string\n local function hiddenNotesToString(hiddenNotes)\n if not hiddenNotes then\n return \"\"\n end\n\n local decodedNotes = --[[---@type string[] ]]{}\n for i, note in ipairs(hiddenNotes) do\n if StringUtil.isBase64(note) then\n decodedNotes[i] = StringUtil.decodeBase64(note)\n else\n decodedNotes[i] = note\n end\n end\n\n return notesToString(decodedNotes)\n end\n\n ---@param items gh_Save_Character_Items\n ---@return string\n local function getAllItems(items)\n local holdItems = --[[---@type string[] ]] {}\n\n for _, itemPosition in pairs(items) do\n for _, itemName in ipairs(itemPosition) do\n table.insert(holdItems, itemName)\n end\n end\n\n table.sort(holdItems)\n return table.concat(holdItems, \"\\n\")\n end\n\n local characterSheetState = --[[---@type fantasySetup_CharacterSheet]] {\n ui = {\n Name = character.name,\n xp = tostring(character.xp),\n gold = tostring(character.gold),\n Items = getAllItems(character.items),\n Notes = hiddenNotesToString(character.hiddenNotes),\n NotesFront = notesToString(character.notes),\n },\n buttons = {},\n inputs = {},\n }\n\n for i = 1, Character.getLevel(character.xp) do\n characterSheetState.buttons[\"level\" .. tostring(i)] = \"u{2717}\"\n end\n\n for i = 1, character.checkmarks do\n characterSheetState.buttons[\"notes\" .. tostring(i)] = \"u{2717}\"\n end\n\n for _, perk in ipairs(character.perks) do\n characterSheetState.buttons[\"perk\" .. tostring(perk)] = \"u{2717}\"\n end\n\n for _, masteries in ipairs(character.masteries) do\n characterSheetState.buttons[\"masteries\" .. tostring(masteries)] = \"u{2717}\"\n end\n\n characterSheet.LuaScriptState = JSON.encode(characterSheetState)\n\n return TtsBase.spawnObjectData({\n data = characterSheet,\n position = Component.characterSheetPosition(player),\n rotation = { 0, 180, 0 },\n })\nend\n\n---@param player integer\n---@param character gh_Save_Character\nfunction this.placeItems(player, character)\n local holdItems = --[[---@type string[] ]] {}\n\n for position, items in pairs(character.items) do\n local itemPosition = Component.itemPosition(player, position)\n\n for _, itemName in ipairs(items) do\n if Shop.takeItem(itemName, { toPosition = itemPosition, toRotation = Constant.Rotation.NORTH }) then\n table.insert(holdItems, itemName)\n Character.checkForNegativeItemEffects(character, itemName, itemPosition)\n else\n Logger.error(\"The item '%s' does not exist. Won't load item.\", itemName)\n end\n end\n end\nend\n\n---@param content tts__ObjectState[]\n---@param player integer\n---@param character gh_Save_Character\nfunction this.placeCharacterSpecificComponents(content, player, character)\n ---@param extraType string\n ---@return gloom_Class_ExtraTarget\n local function getDefaultTarget(extraType)\n if extraType == \"Card\" then\n return \"Hand\"\n end\n if extraType == \"Figurine\" then\n return \"PlayerMat\"\n end\n if extraType == \"Token\" or extraType == \"Rules\" or extraType == \"Deck\" then\n return \"CharacterSheet\"\n end\n\n return \"Hand\"\n end\n\n local classInfo = --[[---@not nil]] ManagerGame.class(character.class)\n local extras = classInfo.extra or --[[---@type gloom_Class_Extra[] ]] {}\n\n ---@type table\n local handledTargets = {}\n\n ---@param targetType string\n local function addTargetType(targetType)\n if not handledTargets[targetType] then\n handledTargets[targetType] = 0\n end\n handledTargets[targetType] = handledTargets[targetType] + 1\n return handledTargets[targetType]\n end\n\n ---@param target string\n ---@return nil | tts__Object\n local function getTargetObject(target)\n if target == \"CharacterMat\" then\n return Party.getCharacterInfo(player).objects.characterMat()\n end\n if target == \"CharacterSheet\" then\n return Party.getCharacterInfo(player).objects.characterSheet()\n end\n if target == \"PlayerMat\" then\n return Party.getCharacterInfo(player).objects.playerMat\n end\n\n return nil\n end\n\n ---@param target gloom_Class_ExtraTarget\n ---@return tts__VectorShape\n local function getRelativeTargetPosition(target)\n if target == \"PlayerMat\" then\n return { -0.4, 0.11, -0.79 }\n end\n if target == \"CharacterMat\" then\n return { 0.71, 0.11, -0.09 }\n end\n if target == \"CharacterSheet\" then\n return { 0.80, 0.11, -1.45 }\n end\n end\n\n ---@param target string | table\n local function getTargetPosition(target)\n if target == \"Hand\" then\n return Component.handPosition(player, 1) + Vector(7, 0, 0)\n end\n if target == \"HandTwo\" then\n return Component.handPosition(player, 2) + Vector(7, 0, 0)\n end\n\n ---@type gloom_Class_ExtraTarget\n local targetName\n ---@type tts__VectorShape\n local relativePosition\n local offset = Vector(0, 0, 0)\n if type(target) == \"table\" then\n local k, v = next(--[[---@type gloom_Class_ExtraTarget_Relative]] target)\n targetName = --[[---@not nil]] k\n relativePosition = --[[---@not nil]] v\n else\n targetName = --[[---@type gloom_Class_ExtraTarget]] target\n relativePosition = getRelativeTargetPosition(targetName)\n local targets = addTargetType(targetName)\n offset = Vector(3 * (targets - 1), 0, 0)\n end\n\n local targetObject = --[[---@not nil]] getTargetObject(targetName)\n if not targetObject then\n Logger.warn(\"Can not get target position for extra at target %s\", targetName)\n return { 0, 0, 0 }\n end\n\n return targetObject.positionToWorld(relativePosition):add(offset)\n end\n\n for _, extra in ipairs(extras) do\n -- extra conditions aren't unpacked this way anymore\n if extra.type ~= \"Condition\" then\n local postSpawn = function() end\n if extra.hp then\n postSpawn = function(obj) this.setFigureHealth(obj, character, --[[---@not nil]] extra.hp) end\n end\n\n local extraData = this.takeContent(content, this.byExactName(extra.name))\n if extraData then\n local target = extra.target or getDefaultTarget(extra.type)\n local targetPosition = getTargetPosition(target)\n\n if targetPosition then\n TtsBase.spawnObjectData({\n data = extraData,\n position = targetPosition, rotation = { 0, 180, 0 }\n }) :next(TtsWait.untilLoaded):next(postSpawn)\n else\n Logger.error(\"Can not determine target position for extra content %s\", extra.name)\n end\n else\n Logger.warn(\"No extra content named %s found in character box\", extra.name)\n end\n end\n end\nend\n\nfunction this.callUnpackExtensions(boxContent, player, character)\n ExtensionHandler.call(\"onCharacterLoad\", character, player, boxContent)\n return Promise.resolve()\nend\n\n---@param box tts__BagState\n---@param player integer\nfunction this.placeCharacterBox(box, player)\n local boxPosition = Component.playerPosition(player, RELATIVE_POSITION.CharacterBox)\n return TtsBase.spawnObjectData({ data = box, position = boxPosition, rotation = { 0, 270, 0 } })\nend\n\n---@param player integer\n---@param character gh_Save_Character\nfunction this.sortPlayerHand(player, character)\n ---@shape __sortPlayerHand_Sorted\n ---@field [1] tts__Object\n ---@field [2] tts__Vector\n\n local playerColor = Game.getPlayerInfo(player).Color\n local unsortedHand = Player[playerColor].getHandObjects()\n local totalCards = #unsortedHand\n\n local function doSortPlayerHand()\n local sortedHand = --[[---@type __sortPlayerHand_Sorted[] ]] {}\n local endIndex = #character.hand + 1\n\n ---@param obj tts__Object\n local function findInHand(obj)\n local abilityName = this.getAbilityName(obj)\n for i, hand in ipairs(character.hand) do\n if abilityName == hand then\n return i\n end\n end\n return nil\n end\n\n for _, obj in ipairs(unsortedHand) do\n local index = findInHand(obj)\n if not index then\n index = endIndex\n endIndex = endIndex + 1\n end\n\n if index > totalCards then\n index = totalCards\n end\n\n local position = unsortedHand[index].getPosition()\n sortedHand[index] = { obj, position }\n end\n\n for _, sorted in ipairs(sortedHand) do\n sorted[1].setPosition(sorted[2])\n end\n end\n\n Wait.condition(doSortPlayerHand, function()\n for _, obj in ipairs(unsortedHand) do\n if obj.loading_custom or obj.spawning then\n return false\n end\n end\n return true\n end)\nend\n\n---@param character gh_Save_Character\n---@param item string\n---@return boolean\nfunction Character.isItemEquipped(character, item)\n for slot, items in pairs(character.items) do\n if slot ~= \"Unequipped\" then\n for _, equippedItem in ipairs(items) do\n if equippedItem == item then\n return true\n end\n end\n end\n end\n\n return false\nend\n\n---@param character gh_Save_Character\n---@param itemName string\n---@param itemPosition tts__Vector\nfunction Character.checkForNegativeItemEffects(character, itemName, itemPosition)\n if not Character.hasNegateItemEffectsPerk(character) then\n local negativeEffect = ManagerGame.Items.NegativeEffects[itemName]\n for _ = 1, negativeEffect or 0 do\n Character.dealNegativeItemEffectCard(itemPosition)\n end\n end\nend\n\n---@param character gh_Save_Character\n---@return boolean\nfunction Character.hasNegateItemEffectsPerk(character)\n local perkInfo = ManagerGame.class(character.class).perks\n for _, perk in ipairs(character.perks) do\n if perkInfo[perk].ignore == \"I\" then\n -- TODO use constant\n return true\n end\n end\n return false\nend\n\n---@param itemPosition tts__Vector\nfunction Character.dealNegativeItemEffectCard(itemPosition)\n Component.minusOneDeck().takeObject({\n smooth = false,\n -- position set to ensure PLayer -1 cards placed on top of item\n position = itemPosition + Vector(0, 1, 0)\n })\nend\n\n---@param curXP number\n---@return number\nfunction Character.getLevel(curXP)\n if not curXP then\n curXP = 0\n end\n for i, xp in pairs(ManagerGame.XpRequirements) do\n if curXP < xp then\n return i - 1\n end\n end\n return 9\nend\n\n---@param player number\n---@param characters gh_Save_Characters\nfunction Character.save(player, characters)\n local characterMat = Component.characterMat(player)\n if not characterMat then\n return\n end\n\n local character = --[[---@type gh_Save_Character]] {}\n character.class = (--[[---@not nil]] characterMat).getDescription()\n local characterSheet = Component.characterSheet(player)\n local characterSheetButtons = characterSheet.getTable(\"buttons\")\n local characterSheetInputs = characterSheet.getTable(\"inputs\")\n\n if characterSheetInputs then\n -- this is an older version of the sheet, without the inputs where you can directly set the values\n character.name = characterSheetInputs.Name\n character.xp = --[[---@not nil]] tonumber(characterSheetButtons.xp.label)\n character.gold = --[[---@not nil]] tonumber(characterSheetButtons.gold.label)\n else\n character.name = characterSheet.UI.getAttribute(\"Name\", \"text\")\n character.xp = --[[---@not nil]] tonumber(characterSheet.UI.getAttribute(\"xp\", \"text\"))\n character.gold = --[[---@not nil]] tonumber(characterSheet.UI.getAttribute(\"gold\", \"text\"))\n character.notes = StringUtil.split(characterSheet.UI.getAttribute(\"NotesFront\", \"text\"), \"\\n\")\n character.hiddenNotes = Character.saveHiddenNotes(characterSheet)\n end\n\n local totalCheckmarks = 0\n for i = 1, 18 do\n if characterSheetButtons[\"notes\" .. i].label ~= \"\" then\n totalCheckmarks = totalCheckmarks + 1\n end\n end\n character.checkmarks = totalCheckmarks\n\n character.perks = {}\n for i = 1, 18 do\n if characterSheetButtons[\"perk\" .. i] ~= nil and characterSheetButtons[\"perk\" .. i].label ~= \"\" then\n table.insert(character.perks, i)\n end\n end\n\n character.masteries = {}\n for i = 1, 2 do\n if characterSheetButtons[\"masteries\" .. i] ~= nil and characterSheetButtons[\"masteries\" .. i].label ~= \"\" then\n table.insert(character.masteries, i)\n end\n end\n\n local class = ManagerGame.class(character.class)\n if not class then\n Logger.error(\"Unable to verify data when saving for class '%s' in player '%s'\", character.class, player)\n end\n\n character.items = Character.saveItems(player)\n character.abilities = Character.saveAbilities(character.class, player)\n character.hand = Character.saveHand(character.class, player)\n character.quest = Character.saveQuest(player)\n\n local enhancements = --[[---@type gh_Save_Enhancements]] {}\n Enhancement.saveFromZone(player, enhancements)\n\n character.enhancements = enhancements[character.class] or {}\n\n local updated = ExtensionHandler.call(\"onCharacterSave\", character, player)\n if updated then\n character = updated\n end\n\n characters[tostring(player)] = character\nend\n\n---@param characterSheet tts__Object\n---@return string[]\nfunction Character.saveHiddenNotes(characterSheet)\n local notes = StringUtil.split(characterSheet.UI.getAttribute(\"Notes\", \"text\"), \"\\n\")\n for i, note in ipairs(notes) do\n notes[i] = StringUtil.encodeBase64(note)\n end\n\n return notes\nend\n\n---@param player number\n---@return gh_Save_Character_Items\nfunction Character.saveItems(player)\n local playerMat = Component.playerMat(player)\n local items = --[[---@type gh_Save_Character_Items ]] {}\n local zoneObjects = Component.playerObjects(player)\n Character.saveItemFromObjects(zoneObjects, playerMat, items)\n\n for _, characterItems in pairs(items) do\n table.sort(characterItems)\n end\n\n return items\nend\n\n---@param objects tts__Object[] | tts__ObjectState[]\n---@param playerMat tts__Object\n---@param items gh_Save_Character_Items\nfunction Character.saveItemFromObjects(objects, playerMat, items)\n for _, object in ipairs(--[[---@type (tts__Object | tts__ObjectState)[] ]] objects) do\n if Component.isItemCard(object) then\n local position = Component.itemPositionName(playerMat, object)\n local positionalItems = items[position]\n if not positionalItems then\n positionalItems = {}\n items[position] = positionalItems\n end\n local itemName = Object.name(object)\n table.insert(positionalItems, itemName)\n elseif Object.isContainer(object) then\n Character.saveItemFromObjects(Object.objects(--[[---@type tts__ContainerState]] object), playerMat, items)\n end\n end\nend\n\n---@param className string\n---@param player number\n---@return string[]\nfunction Character.saveAbilities(className, player)\n local abilities = --[[---@type string[] ]] {}\n local zoneObjects = Component.playerObjects(player)\n Character.saveAbilitiesFromObjects(zoneObjects, className, abilities)\n\n table.sort(abilities)\n return abilities\nend\n\n---@param objects tts__Object[] | tts__ObjectState[]\n---@param className string\n---@param abilities string[]\nfunction Character.saveAbilitiesFromObjects(objects, className, abilities)\n local class = ManagerGame.class(className)\n for _, object in ipairs(--[[---@type (tts__Object | tts__ObjectState)[] ]] objects) do\n ---@type nil | string\n local abilityName\n if not class then\n abilityName = Component.getAbilityNameForUnknownClass(object, className)\n else\n abilityName = Component.getAbilityNameForClass(object, className)\n end\n if abilityName ~= nil then\n if (class and not Component.isStartingAbility(className, --[[---@not nil]] abilityName)) or not class then\n table.insert(abilities, --[[---@not nil]] abilityName)\n end\n elseif Object.isContainer(object) and not Component.isAbilityDeck(object) and not Object.hasTag(object, R.Tag.Component.LockedContent) then\n Character.saveAbilitiesFromObjects(Object.objects(--[[---@type tts__ContainerState]] object), className, abilities)\n end\n end\nend\n\n---@param className string\n---@param player number\n---@return string[]\nfunction Character.saveHand(className, player)\n local handAbilities = --[[---@type string[] ]] {}\n local class = ManagerGame.class(className)\n for _, object in ipairs(Component.playerHand(player)) do\n ---@type nil | string\n local abilityName\n if not class then\n abilityName = Component.getAbilityNameForUnknownClass(object, className)\n else\n abilityName = Component.getAbilityNameForClass(object, className)\n end\n if abilityName then\n table.insert(handAbilities, --[[---@not nil]] abilityName)\n end\n end\n\n return handAbilities\nend\n\n---@param player number\n---@return number\nfunction Character.saveQuest(player)\n local info = Character.findQuest(player)\n if info then\n if info.number then\n return (--[[---@not nil]] info).number\n else\n return (--[[---@not nil]] info).name\n end\n end\nend\n\n---@param objects tts__Object[] | tts__ObjectState[]\nlocal function findQuestInObjects(objects)\n for _, object in ipairs(--[[---@type (tts__Object | tts__ObjectState)[] ]] objects) do\n if Component.isPersonalQuestCard(object) then\n return Component.getQuestInfo(object)\n end\n\n if Object.isContainer(object) then\n local found = findQuestInObjects(Object.objects(--[[---@type tts__ContainerState]] object))\n if found then\n return found\n end\n end\n end\nend\n\n---@param player number\n---@return nil | gh_Game_Quest_Info\nfunction Character.findQuest(player)\n local object = findQuestInObjects(Component.playerObjects(player))\n\n if object then\n return Component.getQuestInfo(object)\n end\nend\n\n---@param id string\n---@return boolean\nfunction Character.isActive(id)\n return tonumber(id) ~= nil\nend\n\n---@param id string\n---@return boolean\nfunction Character.isSupported(id)\n local number = tonumber(id)\n return number == nil or number <= Component.playerCount()\nend\n\nreturn Character\n\nend)\n__bundle_register(\"campaign-manager.Enhancement\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal ObjectState = require(\"lib.ObjectData\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal EventType = require(\"campaign-manager.EventType\")\nlocal Game = require(\"campaign-manager.Game\")\nlocal OptionsApi = require(\"api.OptionsApi\")\n\nlocal Enhancement = {}\n\n---@param savefile gh_Savefile\nfunction Enhancement.setup(savefile)\n EventManager.addHandler(EventType.Loaded.Class.Unlocked, function(className) Enhancement.mayLoadForClass(savefile, className) end)\nend\n\n---@type table\nlocal Decals = {\n [\"Air\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726768884/CC8308C7D7DD6185F3EFA99F452C83101E1E95E3/\",\n [\"Earth\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726773039/E83D1E9F06938B3B5355F0A6A49D98B78B1F2594/\",\n [\"Fire\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726770166/CD369CF7C22FAD932A71C65BA811AE137234270D/\",\n [\"Frost\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726771564/3C69B7D1E66E8FC022B2BD77CC427E8AEC5D9331/\",\n [\"Dark\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726982499/9C14793623433565A9D6E5B3C608C1F6FBB84C76/\",\n [\"Light\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726774294/5BBAC718ED55E90563D2B24848DE3ACAEBD76FE9/\",\n [\"Any Element\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726984504/2F962A64E2A9888C90856D9C150EF81089745137/\",\n [\"Curse\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726987796/ABB2E197E22015077D39A378AA9A9E7A317C69FE/\",\n [\"Bless\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726986639/FDF514FDA20826ACA054BE5CBA579AE6FB25A263/\",\n [\"Disarm\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726989091/DB993A58BC37C60B64A2F2CCDAED888F6176BC43/\",\n [\"Immobilize\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726990928/14B27BE1DC8F226D05FDB7D7D5D420FFCA6919B0/\",\n [\"Muddle\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726993395/820BE84906894B87F0C0DAC77C3BFBCD9B1AD504/\",\n [\"Poison\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726994202/6EAECD4A8A6A56F42834D486A21219002B883C53/\",\n [\"Regenerate\"] = \"http://cloud-3.steamusercontent.com/ugc/791986676667649885/92F7F934D298524F6F72997095DDBA073D9A15BF/\",\n [\"Strengthen\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726995467/72FC8E8918F788646DBD0C571FDAB214D7A36445/\",\n [\"Stun\"] = \"http://cloud-3.steamusercontent.com/ugc/83721958671678940/616CC0951CB17024448D3CBC7E5CA9D3A05D7555/\",\n [\"Wound\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726992099/9B90EEDC5B68B4FFD04C0037008F9C96BA603C24/\",\n [\"Plus 1\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726761532/C3BB327E15466B74ED256A0BD7B66C20A8825861/\",\n [\"Area\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726997997/041C0C738331AA8B4D8281BE8E9BE2C2963D823D/\",\n [\"Jump\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726767406/7C34D7EA30D5E6FF90BBA88A767ACB98467FC557/\"\n}\nDecals[\"Ice\"] = Decals[\"Frost\"] -- makes more sense, since the element is also called Ice\nDecals[\"AoE Hex\"] = Decals[\"Area\"] -- it got renamed between versions\nDecals[\"AoE HEx\"] = Decals[\"Area\"] -- stupid bad spelling\n\n---@type number\nlocal MaxDistance = 0.1\n---@type number\nlocal DecalYCoordinate = 0.36\n---@type tts__VectorShape\nlocal DecalRotation = { 90, 180, 0 }\n---@type tts__VectorShape\nlocal DecalScale = { 0.16, 0.16, 3.07 }\n\n---@param savefile gh_Savefile\n---@param className string\nfunction Enhancement.mayLoadForClass(savefile, className)\n local classEnhancements = savefile.enhancements[className]\n if classEnhancements then\n Enhancement.loadClass(className, classEnhancements, Enhancement.finishLoadClass)\n return\n end\n\n Enhancement.finishLoadClass(className)\nend\n\n---@overload fun(className: string, abilities: gh_Save_EnhancedClass): void\n---@param className string\n---@param abilities gh_Save_EnhancedClass\n---@param callback fun(className: string): void\nfunction Enhancement.loadClass(className, abilities, callback)\n Logger.info(\"Loading enhancements for class %s\", className)\n\n local classBox = (--[[---@not nil]] Component.classBox(className)).getData()\n local characterBox = Component.characterBox(classBox)\n\n Enhancement.applyOnCharacterBox(characterBox, className, abilities)\n\n Object.respawn(classBox, function()\n Logger.info(\"Enhancements for class %s loaded\", className)\n if callback then\n callback(className)\n end\n end)\nend\n\n---@param characterBox tts__ContainerState\n---@param enhancements gh_Save_EnhancedClass\nfunction Enhancement.applyOnCharacterBox(characterBox, className, enhancements)\n for _, contained in pairs(characterBox.ContainedObjects) do\n if Component.isAbilityDeck(contained) then\n Enhancement.applyEnhancementsOnAbilityDeck(--[[---@type tts__DeckState]]contained, className, enhancements)\n end\n end\nend\n\n---@param className string\nfunction Enhancement.finishLoadClass(className)\n EventManager.triggerEvent(EventType.Loaded.Class.Enhanced, className)\nend\n\n---@param deck tts__DeckState\n---@param className string\n---@param abilities gh_Save_EnhancedClass\nfunction Enhancement.applyEnhancementsOnAbilityDeck(deck, className, abilities)\n for _, abilityCard in pairs(deck.ContainedObjects) do\n if OptionsApi.enhancementSystem() == OptionsApi.EnhancementSystem.NonPermanent then\n abilityCard.AttachedDecals = {}\n end\n for abilityName, ability in pairs(abilities) do\n if Object.name(abilityCard):find(StringUtil.escapePattern(abilityName)) then\n Enhancement.applyEnhancementsOnCard(abilityCard, className, abilityName, ability)\n break\n end\n end\n end\nend\n\n---@param abilityCard tts__ObjectState\n---@param className string\n---@param abilityName string\n---@param ability gh_Save_Enhanced_Ability\nfunction Enhancement.applyEnhancementsOnCard(abilityCard, className, abilityName, ability)\n ---@param object tts__ObjectState\n ---@param position tts__Vector\n ---@return nil | (tts__Object_Decal, number)\n local function findExistingDecalAt(object, position)\n for i, decal in ipairs(Object.decals(object)) do\n if decal.position:equals(position) then\n return decal, i\n end\n end\n end\n\n local enhancementsInfo = (--[[---@not nil]] Game.ability(className, abilityName)).enhancements\n for enhancementPosition, enhancement in pairs(ability) do\n if Decals[enhancement] then\n local position2d = enhancementsInfo[--[[---@not nil]] tonumber(enhancementPosition)].position\n local decal = {\n name = \"Enhancement \" .. enhancement,\n url = Decals[enhancement],\n position = { position2d[1], DecalYCoordinate, position2d[2] },\n }\n local existingDecal, index = findExistingDecalAt(abilityCard, Vector(decal.position))\n\n if existingDecal ~= nil then\n if (--[[---@not nil]] existingDecal).name ~= decal.name then\n Logger.warn(\"The ability '%s' of class %s already has an '%s' at position %d. This will now be overwritten with '%s'!\",\n abilityName, className, (--[[---@not nil]] existingDecal).name, enhancementPosition, decal.name)\n local attachedDecals = --[[---@not nil]] abilityCard.AttachedDecals\n attachedDecals[index].CustomDecal.ImageURL = decal.url\n attachedDecals[index].CustomDecal.Name = decal.name\n end\n else\n ObjectState.addDecal(abilityCard, {\n name = decal.name,\n url = decal.url,\n position = decal.position,\n rotation = DecalRotation,\n scale = DecalScale,\n })\n end\n else\n Logger.warn(\"Tried to apply enhancement %s on ability '%s' (%s), but no such enhancement is known.\"\n .. \" Possible enhancement names are [%s]. The enhancement will be ignored.\",\n enhancement, abilityName, className, table.concat(TableUtil.keys(Decals), \", \"))\n end\n end\nend\n\n---@param savefile gh_Savefile\nfunction Enhancement.saveAll(savefile)\n local enhancements = savefile.enhancements\n\n Enhancement.saveFromKnownClasses(enhancements)\nend\n\n---@param player number\n---@param enhancements gh_Save_Enhancements\nfunction Enhancement.saveFromZone(player, enhancements)\n local className = Component.playerClass(player)\n if not className then\n return\n end\n local class = Game.class(--[[---@not nil]] className)\n if not class then\n Logger.error(\"Unable to save enhancements for class '%s' in player '%s'\", className, player)\n return\n end\n Enhancement.saveFromContainer(Component.playerZone(player), --[[---@not nil]] className, enhancements)\nend\n\n---@param enhancements gh_Save_Enhancements\nfunction Enhancement.saveFromPackedCharacters(enhancements)\n for _, inactiveCharacter in pairs(Component.packedCharacters()) do\n local className = inactiveCharacter.getName()\n local class = Game.class(className)\n if not class then\n Logger.error(\"Unable to save enhancements for class '%s'\", className)\n return\n end\n Enhancement.saveFromContainer(inactiveCharacter.getData(), className, enhancements)\n end\nend\n\n---@param enhancements gh_Save_Enhancements\nfunction Enhancement.saveFromKnownClasses(enhancements)\n for className, _ in pairs(Game.classes()) do\n local classBox = Component.classBox(className)\n if classBox then\n local characterBox = Component.characterBox(--[[---@not nil]] classBox)\n Enhancement.saveFromContainer(characterBox, className, enhancements)\n end\n end\nend\n\n---@param container seb_Object_Container\n---@param className string\n---@param enhancements gh_Save_Enhancements\nfunction Enhancement.saveFromContainer(container, className, enhancements)\n for _, object in ipairs(--[[---@type seb_Object[] ]] Object.objects(container)) do\n local abilityName = Component.getAbilityNameForClass(object, className)\n if abilityName ~= nil and TableUtil.isNotEmpty(Object.decals(object)) then\n Enhancement.saveForCard(object, className, --[[---@not nil]] abilityName, enhancements)\n elseif Object.isContainer(object) then\n Enhancement.saveFromContainer(--[[---@type seb_Object_Container]] object, className, enhancements)\n end\n end\nend\n\n---@param card seb_Object\n---@param className string\n---@param abilityName string\n---@param enhancements gh_Save_Enhancements\nfunction Enhancement.saveForCard(card, className, abilityName, enhancements)\n Logger.verbose(\"Saving enhancements for \" .. Object.name(card))\n local enhancementInfo = (--[[---@not nil]] Game.ability(className, abilityName)).enhancements\n\n for _, decal in ipairs(Object.decals(card)) do\n local enhancementName = Enhancement.getName(decal.name)\n\n if not enhancementName then\n Logger.warn(\"The ability '%s' (%s) has an unknown enhancement named '%s'.\"\n .. \" It will be saved (so you can change them if needed) but ignored during load.\"\n .. \" Possible enhancements names are [%s].\",\n abilityName, className, decal.name, table.concat(TableUtil.keys(Decals), \", \"))\n enhancementName = decal.name\n end\n\n local nearestIndex, distance = Enhancement.findNearestIndex(decal.position, enhancementInfo)\n if Enhancement.saveEnhancement(enhancements, className, abilityName, nearestIndex, --[[---@not nil]] enhancementName) then\n if distance > MaxDistance then\n Logger.warn(\"Enhancement for ability \" .. className .. \"\\n\"\n .. \"The distance to any known enhancement position for this ability is bigger\"\n .. \" than expected (\" .. distance .. \"). This might mean, that the known\"\n .. \" information is outdated (e.g. a newer printing of the card exists),\"\n .. \" the card shouldn't have enhancements or that the decal was added\"\n .. \" rather sloppy. Please verify that the enhancement loads correctly.\")\n end\n end\n end\nend\n\n---@param decalName string\n---@return nil | string\nfunction Enhancement.getName(decalName)\n local baseName = StringUtil.replace(decalName, \"Enhancement \")\n if baseName:lower() == \"aoe hex\" then\n baseName = \"Area\"\n end\n\n if not Decals[baseName] then\n return nil\n end\n\n return baseName\nend\n\n---@param enhancements gh_Save_Enhancements\n---@param className string\n---@param abilityName string\n---@param position number\n---@param enhancementName string\n---@return boolean\nfunction Enhancement.saveEnhancement(enhancements, className, abilityName, position, enhancementName)\n ---@type gh_Save_EnhancedClass\n local classEnhancements = enhancements[className]\n if not classEnhancements then\n classEnhancements = {}\n enhancements[className] = classEnhancements\n end\n\n ---@type gh_Save_Enhanced_Ability\n local abilityEnhancement = classEnhancements[abilityName]\n if not abilityEnhancement then\n abilityEnhancement = {}\n classEnhancements[abilityName] = abilityEnhancement\n end\n\n local positionEnhancement = abilityEnhancement[tostring(position)]\n if positionEnhancement then\n return false\n end\n\n abilityEnhancement[tostring(position)] = enhancementName\n return true\nend\n\n---@param decalPosition tts__Vector\n---@param enhancementInfo gloom_Ability_Enhancement[]\n---@return (number, number)\nfunction Enhancement.findNearestIndex(decalPosition, enhancementInfo)\n local nearestDistance = 42\n local nearestIndex = 1\n decalPosition:setAt(\"y\", 0)\n for i, enhancement in ipairs(enhancementInfo) do\n local x, z = enhancement.position[1], enhancement.position[2]\n local distance = decalPosition:distance(Vector(x, 0, z))\n if distance < nearestDistance then\n nearestDistance = distance\n nearestIndex = i\n end\n end\n\n return nearestIndex, nearestDistance\nend\n\nreturn Enhancement\n\nend)\n__bundle_register(\"lib.promise.TtsWait\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TtsWait = require(\"kintastic.lib.Promise.TtsWait\")\r\n\r\nreturn TtsWait\r\n\nend)\n__bundle_register(\"kintastic.lib.Promise.TtsWait\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal SignalLoading = require(\"lib.SignalLoading\")\nlocal Promise = require(\"kintastic/lib/Promise\")\n\n--- Promise-based version of Wait methods.\nlocal TtsWait = { }\n\n---@alias Promise_Wait_Canceller fun(cancelReason: any): void\n\n--- Returns a promises that's fulfilled once the condition becomes true.\n--- It will be fulfilled with `value` (if provided).\n--- If `timeout` is provided, the promise becomes rejected with \"Timeout\" once the timeout occurs.\n--- If `timeout_reason` is provided, it will be used as the rejection reason instead of \"Timeout\".\n---\n---@generic V\n---@overload fun(cond_func: fun(): boolean): gloom_Promise\n---@overload fun(cond_func: (fun(): boolean), value: T): gloom_Promise\n---@param cond_func fun(): boolean\n---@param value nil | V\n---@param timeout number\n---@param timeout_reason any\n---@return gloom_Promise\nfunction TtsWait.condition(cond_func, value, timeout, timeout_reason)\n return --[[---@type gloom_Promise]] Promise:new(\n function(resolve, reject)\n local done = false\n local doneResolve = function()\n done = true\n resolve(value)\n end\n local doneReject = function(reason)\n done = true\n reject(reason)\n end\n\n if timeout == nil then\n Wait.condition(doneResolve, cond_func)\n else\n Wait.condition(doneResolve, cond_func, timeout, function() doneReject(timeout_reason or \"Timeout\") end)\n end\n end\n )\nend\n\n--- This function is identical to `TtsWait.condition` except a second value is returned.\n--- It consists of a function which can be used to cancel the wait.\n--- Calling this function will result in the promise becoming rejected.\n--- The rejection reason will be the value passed to the function, or \"Cancelled\" if none was provided.\n---\n---@generic V\n---@overload fun(cond_func: fun(): boolean): gloom_Promise\n---@overload fun(cond_func: (fun(): boolean), value: T): gloom_Promise\n---@param cond_func fun(): boolean\n---@param value nil | V\n---@param timeout number\n---@param timeout_reason any\n---@return gloom_Promise, Promise_Wait_Canceller\nfunction TtsWait.cond_with_cancel(cond_func, value, timeout, timeout_reason)\n local canceller\n local promise = --[[---@type gloom_Promise]] Promise:new(\n function(resolve, reject)\n local done = false\n local doneResolve = function()\n done = true\n resolve(value)\n end\n local doneReject = function(reason)\n done = true\n reject(reason)\n end\n\n local id\n if timeout == nil then\n id = Wait.condition(doneResolve, cond_func)\n else\n id = Wait.condition(doneResolve, cond_func, timeout, function() doneReject(timeout_reason or \"Timeout\") end)\n end\n\n canceller = function(cancel_reason)\n if not done then\n Wait.stop(id)\n reject(cancel_reason or \"Cancelled\")\n end\n end\n end\n )\n\n return promise, canceller\nend\n\n--- Returns a promises that's fulfilled after `frame_count` frames.\n--- It will be fulfilled with `value` (if provided).\n---\n---@generic V\n---@overload fun(frame_count: integer): gloom_Promise\n---@param frame_count integer\n---@param value V\n---@return gloom_Promise\nfunction TtsWait.frames(frame_count, value)\n return --[[---@type gloom_Promise]] Promise:new(\n function(resolve, _)\n Wait.frames(function() resolve(value) end, frame_count)\n end\n )\nend\n\n--- Like `TtsWait.frames`, but only wait one frame.\n---\n---@generic V\n---@overload fun(): gloom_Promise\n---@param value V\n---@return gloom_Promise\nfunction TtsWait.frame(value)\n return --[[---@type gloom_Promise]] Promise:new(\n function(resolve, _)\n Wait.frames(function() resolve(value) end, 1)\n end\n )\nend\n\n--- This function is identical to TtsWait.frames except a second value is returned.\n--- It consists of a function which can be used to cancel the wait.\n--- Calling this function will result in the promise becoming rejected.\n--- The rejection reason will be the value passed to the function, or \"Cancelled\" if none was provided.\n---\n---@generic V\n---@overload fun(frame_count: integer): gloom_Promise\n---@param frame_count integer\n---@param value V\n---@return gloom_Promise, Promise_Wait_Canceller\nfunction TtsWait.frames_with_cancel(frame_count, value)\n local canceller\n local promise = --[[---@type gloom_Promise]] Promise:new(\n function(resolve, reject)\n local done = false\n local doneResolve = function()\n done = true\n resolve(value)\n end\n\n local id = Wait.frames(function() doneResolve() end, frame_count)\n\n canceller = function(cancel_reason)\n if not done then\n Wait.stop(id)\n reject(cancel_reason or \"Cancelled\")\n end\n end\n end\n )\n\n return promise, canceller\nend\n\n--- Returns a promises that's fulfilled after `secs` (possibly-fractional) seconds.\n--- It will be fulfilled with `value` (if provided).\n---\n---@generic V\n---@overload fun(secs: number): gloom_Promise\n---@param secs number\n---@param value V\n---@return gloom_Promise\nfunction TtsWait.time(secs, value)\n return --[[---@type gloom_Promise]] Promise:new(\n function(resolve, _)\n Wait.time(function() resolve(value) end, secs)\n end\n )\nend\n\n\n--- This function is identical to TtsWait.time except a second value is returned.\n--- It consists of a function which can be used to cancel the wait.\n--- Calling this function will result in the promise becoming rejected.\n--- The rejection reason will be the value passed to the function, or \"Cancelled\" if none was provided.\n---\n---@generic V\n---@overload fun(secs: number): gloom_Promise\n---@param secs number\n---@param value V\n---@return gloom_Promise, Promise_Wait_Canceller\nfunction TtsWait.time_with_cancel(secs, value)\n local canceller\n local promise = --[[---@type gloom_Promise]] Promise:new(\n function(resolve, reject)\n local done = false\n\n local doneResolve = function()\n done = true\n resolve(value)\n end\n\n local id = Wait.time(function() doneResolve() end, secs)\n\n canceller = function(cancel_reason)\n if not done then\n Wait.stop(id)\n reject(cancel_reason or \"Cancelled\")\n end\n end\n end\n )\n\n return promise, canceller\nend\n\n--- Returns a Promise that is fulfilled once the given object finished loading.\n---@param obj tts__Object\n---@return gloom_Promise\nfunction TtsWait.untilPlaced(obj)\n local readyPromise = TtsWait.condition(function()\n return not obj.spawning and not obj.loading_custom and obj.resting\n end, obj)\n\n return readyPromise:next(function(o) return TtsWait.frames(10, o) end)\nend\n\n--- Returns a Promise that is fulfilled once the given object finished loading.\n---@param obj tts__Object\n---@return gloom_Promise\nfunction TtsWait.untilLoaded(obj)\n local readyPromise = TtsWait.condition(function()\n return SignalLoading.isObjectLoaded(obj)\n end, obj)\n\n return readyPromise:next(function(o) return TtsWait.frames(10, o) end)\nend\n\nreturn TtsWait\n\nend)\n__bundle_register(\"kintastic/lib/Promise\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Promise class\r\n-- The PromiseClass module provides the actual class, while this\r\n-- module provides ways of constructing instances of that class.\r\n-- This allows us to have constructors and instances methods with the same name.\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal Errors = require(\"kintastic/lib/Errors\")\r\nlocal Introspection = require(\"kintastic/lib/Introspection\")\r\nlocal Iters = require(\"kintastic/lib/Iters\")\r\nlocal PromiseClass = require(\"kintastic/lib/PromiseClass\")\r\n\r\n-- Imports.\r\nlocal is_callable = Introspection.is_callable\r\nlocal is_instance_of = Introspection.is_instance_of\r\nlocal ivalues = Iters.ivalues\r\n\r\n-- The class.\r\n-- Contains only constuctors.\r\n-- Never instantiated.\r\nlocal Promise = { }\r\n\r\n\r\n-- ================================================================================\r\n-- Public constructors\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:new\r\n\r\nfunction Promise.new(class, executor)\r\n return PromiseClass:new(executor)\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:try\r\n--\r\n-- Usage:\r\n-- local promise = Promise:try(\r\n-- function()\r\n-- ...\r\n-- error(reason)\r\n-- ...\r\n-- return x\r\n-- end\r\n-- )\r\n--\r\n-- The above example is equivalent to\r\n--\r\n-- local promise = Promise:new(\r\n-- function(resolve, reject)\r\n-- ...\r\n-- reject(reason)\r\n-- ...\r\n-- resolve(x)\r\n-- end\r\n-- )\r\n--\r\n-- This can be used in lieu of a Promise:new when\r\n-- either resolve() or reject() is always called\r\n-- before the callback returns.\r\n\r\nfunction Promise.try(class, func)\r\n return class:new(\r\n function(resolve, reject)\r\n resolve(func())\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:timeout\r\n--\r\n-- Create a promise that becomes rejected after a specified amount of time.\r\n\r\nfunction Promise.timeout(class, seconds)\r\n return class:new(\r\n function(resolve, reject)\r\n Wait.time(function() reject(Errors.ETIMEOUT) end, seconds)\r\n end\r\n )\r\nend\r\n\r\n\r\n--- Creates a promise that's fulfilled with the provided value.\r\n--- You normally want to use Promise:resolve() instead.\r\n---@generic V\r\n---@param value V\r\n---@return gloom_Promise\r\nfunction Promise.fulfill(value)\r\n local promise = Promise:new()\r\n promise:_transition(false, Promise.States.FULFILLED, value)\r\n return --[[---@type gloom_Promise]] promise\r\nend\r\n\r\n\r\n--- Creates a promise that's rejected.\r\n---@generic V\r\n---@param reason any\r\n---@return gloom_Promise\r\nfunction Promise.reject(reason)\r\n local promise = Promise:new()\r\n promise:_transition(false, Promise.States.REJECTED, reason)\r\n return --[[---@type gloom_Promise]] promise\r\nend\r\n\r\n\r\n--- Used to create a fulfilled promise, or to ensure that something is a Promise.\r\n---\r\n--- If `x` is a Promise, it's simply returned.\r\n--- Otherwise, a promised already fulfilled with the value of `x` is returned.\r\n---\r\n--- Usage:\r\n--- local promise = Promise.resolve(x)\r\n---\r\n---@generic V\r\n---@param x (fun(): V) | V | gloom_Promise\r\n---@return gloom_Promise\r\nfunction Promise.resolve(x)\r\n local success, rv = pcall(\r\n function()\r\n -- Return a prebuilt promise if one exists.\r\n do\r\n local promise = Promise.promises[ x == nil and Promise.NIL or x ]\r\n if promise then\r\n return promise\r\n end\r\n end\r\n\r\n if is_instance_of(x, PromiseClass) then\r\n return x\r\n end\r\n\r\n return Promise.fulfill(x)\r\n end\r\n )\r\n\r\n if success then\r\n return rv\r\n else\r\n return Promise.reject(rv)\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:all_settled\r\n--\r\n-- Usage:\r\n-- local promise = Promise:all_settled({ promise1, promise2, ... })\r\n--\r\n-- Returns a promise.\r\n-- The promise becomes fulfilled once all of the provided promises have become fulfilled or rejected.\r\n-- The fulfillment value is an array of information the fulfillment/rejection state of each promise.\r\n-- Passing an empty array returns a fulfilled promise.\r\n\r\nfunction Promise.all_settled(class, array)\r\n return class:new(\r\n function(resolve, reject)\r\n local results = { }\r\n local num_pending = 0\r\n for i, promise in ipairs(array) do\r\n promise = class:resolve(promise)\r\n num_pending = num_pending + 1\r\n promise:next(\r\n function(value)\r\n results[i] = { status = \"fulfilled\", value = value }\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n resolve(results)\r\n end\r\n end,\r\n function(reason)\r\n results[i] = { status = \"rejected\", reason = reason }\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n resolve(results)\r\n end\r\n end\r\n )\r\n end\r\n\r\n if num_pending == 0 then\r\n resolve(results)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n--- The promise becomes fulfilled once all of the provided promises have become fulfilled.\r\n--- The fulfillment value is an array of the fulfillment values of the fulfilled promises.\r\n--- The promise becomes rejected once any of the provided promises has become rejected.\r\n--- The rejection reason is the rejection reason of the rejected promise.\r\n--- Passing an empty array returns a promise fulfilled with an empty array.\r\n---\r\n--- Usage:\r\n--- local promise = Promise:all({ promise1, promise2, ... })\r\n---@param array table\r\n---@return gloom_Promise\r\nfunction Promise.all(array)\r\n return PromiseClass:new(\r\n function(resolve, reject)\r\n local values = { }\r\n local num_pending = 0\r\n for i, promise in ipairs(array) do\r\n promise = Promise.resolve(promise)\r\n num_pending = num_pending + 1\r\n promise:next(\r\n function(value)\r\n values[i] = value\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n resolve(values)\r\n end\r\n end,\r\n reject\r\n )\r\n end\r\n\r\n if num_pending == 0 then\r\n resolve(values)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:any\r\n--\r\n-- Usage:\r\n-- local promise = Promise:any({ promise1, promise2, ... })\r\n--\r\n-- Returns a promise.\r\n-- The promise becomes fulfilled once any of the provided promises has become fulfilled.\r\n-- The fulfillment value is the fulfillment value of the fulfilled promise.\r\n-- The promise becomes rejected once all of the provided promises have become rejected.\r\n-- The rejection reason an array of the rejection resons of the rejected promises.\r\n-- Passing an empty array returns a promise fulfilled with an empty array.\r\n\r\nfunction Promise.any(class, array)\r\n return class:new(\r\n function(resolve, reject)\r\n local reasons = { }\r\n local num_pending = 0\r\n for i, promise in ipairs(array) do\r\n promise = class:resolve(promise)\r\n num_pending = num_pending + 1\r\n promise:next(\r\n resolve,\r\n function(reason)\r\n reasons[i] = reason\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n reject(reasons)\r\n end\r\n end\r\n )\r\n end\r\n\r\n if num_pending == 0 then\r\n reject(reasons)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:race\r\n--\r\n-- Usage:\r\n-- local promise = Promise:race({ promise1, promise2, ... })\r\n--\r\n-- Returns a promise.\r\n-- The promise becomes fulfilled once any of the provided promises has become fulfilled.\r\n-- The fulfillment value is the fulfillment value of the fulfilled promise.\r\n-- The promise becomes rejected once any of the provided promises has become rejected.\r\n-- The rejection reason is the rejection reason of the rejected promise.\r\n-- Passing an empty array returns a fulfilled promise.\r\n\r\nfunction Promise.race(class, array)\r\n if #array <= 1 then\r\n return Promise:resolve(array[1])\r\n end\r\n\r\n return class:new(\r\n function(resolve, reject)\r\n for i, promise in ipairs(array) do\r\n promise = class:resolve(promise)\r\n promise:next(resolve, reject)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Public functions\r\n\r\n-- ----------------------------------------\r\n-- Public function Promise.all_settled_error_logger\r\n--\r\n-- Usage:\r\n-- Promise:all_settled(promises)\r\n-- :next(Promise.all_settled_error_logger)\r\n--\r\n-- Note that this isn't a method!\r\n\r\nfunction Promise.all_settled_error_logger(value)\r\n local values = { }\r\n for result in ivalues(value) do\r\n if result.status == \"rejected\" then\r\n -- XXX Should handle weird reasons better. Especially since this is in a loop.\r\n broadcastToAll(result.reason, Color.Red)\r\n else\r\n table.insert(values, result.value)\r\n end\r\n end\r\n\r\n return values\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Semi-public\r\n-- You probably shouldn't be using these directly.\r\n\r\n-- ----------------------------------------\r\n-- Semi-public state constants.\r\n\r\nPromise.States = PromiseClass.States\r\n\r\n\r\n-- ----------------------------------------\r\n-- Semi-public pregenerated promises.\r\n\r\n-- This is used as the key when looking for a prebuilt promise for `nil`.\r\nPromise.NIL = { }\r\n\r\nPromise.promises = {\r\n [ Promise.NIL ] = Promise:fulfill(nil),\r\n [ true ] = Promise:fulfill(true),\r\n [ false ] = Promise:fulfill(false),\r\n [ 0 ] = Promise:fulfill(0),\r\n [ \"\" ] = Promise:fulfill(\"\"),\r\n}\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn Promise\r\n\nend)\n__bundle_register(\"kintastic/lib/PromiseClass\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Promise class\r\n-- This module provides the actual class, while the Promise\r\n-- module provides ways of constructing instances of this class.\r\n-- This allows us to have constructors and instances methods with the same name.\r\n--\r\n-- Compliant with the Promises/A+ spec.\r\n-- https://promisesaplus.com/\r\n--\r\n-- Accomodations for the differences bewteen Lua and JavaScript:\r\n-- - `then` is a reserved word in Lua, so `next` is used intead. [throughout, incl 1.1 and 2.3.3.1]\r\n-- - The Lua equivalent to an object is a table. [1.2]\r\n-- - The Lua equivalent to a function object is a table with a `__call` metamethod. [1.2, 2.3.3]\r\n-- - While JS function can only return a single value, Lua functions can return a non-negative number of values.\r\n-- The value of a fulfilled promise is meant to represent what a function returns, so one could conceivably\r\n-- expect the value of a Lua promise to be a list of values. However, this module will adopt the JS-centric\r\n-- approach of having exactly one value. [2.2.7.1]\r\n-- - Lua doesn't have a TypeError class, so Errors.ETYPE is used instead. [2.3.1]\r\n-- - The Lua equivalent of calling a function as a method is to pass the invocant as the first argument. [2.3.3.3]\r\n--\r\n-- Resolution of Ambiguities:\r\n-- - It is understood that returning nothing is the same as returning `undefined` (JS) or `nil` (Lua). [2.2.7.1]\r\n-- - It is understood that returned values beyond the first are ignored.\r\n-- - It is understood that once a promise adopts the state of another, its own state can no longer be changed except to mirror the other. [2.3.1.1]\r\n-- - It is understood that being called with no arguments is the same as being called with `undefined` (JS) or `nil` (Lua). [2.3.3.3.1]\r\n-- - It is understood that arguments beyond the first are ignored. [2.3.3.3.1]\r\n--\r\n-- Optimizations:\r\n-- - `next` returns `self` if neither arguments are callable.\r\n-- - When resolving, a prebuilt promise may be returned. [2.2.7.1]\r\n--\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal Async = require(\"kintastic/lib/Async\")\r\nlocal Errors = require(\"kintastic/lib/Errors\")\r\nlocal Introspection = require(\"kintastic/lib/Introspection\")\r\nlocal Queue = require(\"kintastic/lib/Queue\")\r\n\r\n-- Imports.\r\nlocal async = Async.async\r\nlocal is_callable = Introspection.is_callable\r\nlocal is_instance_of = Introspection.is_instance_of\r\n\r\n-- Forward declarations for privates found at the bottom.\r\nlocal on_fulfilled_default, on_rejected_default\r\nlocal schedule\r\n\r\n-- The real class.\r\n-- This allows us to have constructors and\r\n-- instance methods with the same name.\r\n---@class gloom_Promise\r\nlocal PromiseClass = { }\r\n\r\nPromiseClass.mt = {\r\n __index = PromiseClass,\r\n}\r\n\r\n\r\n-- ================================================================================\r\n-- Public constructors\r\n\r\n-- ----------------------------------------\r\n-- Public constructor PromiseClass:new\r\n-- Also available as Promise:new\r\n--\r\n-- Usage:\r\n-- local promise = Promise:new(\r\n-- function(resolve, reject)\r\n-- ...\r\n-- resolve(x)\r\n-- ...\r\n-- reject(reason)\r\n-- ...\r\n-- end\r\n-- )\r\n--\r\n-- If `resolve` is called with a Promise or something Promise-like, `promise` becomes a mirror for the\r\n-- state of `x`, effectively inserting `x` into the \"chain\" of Promises.\r\n-- If `resolve` is called with something else, `promise` becomes fulfilled with the provided value.\r\n-- If `resolve` is called without arguments, the promise will become fulfilled with `nil`.\r\n-- `resolve` may be called asynchronously, after the provided function has returned.\r\n-- `resolve` needs not be called at all.\r\n-- Calls to `resolve` after `promise` has been fulfilled or rejected will be ignored.\r\n--\r\n-- If `reject` is called, the promise will become rejected with the provided reason.\r\n-- `reject` may be called asynchronously, after the provided function has returned.\r\n-- `reject` needs not be called at all.\r\n-- Calls to `reject` after `promise` has been fulfilled or rejected will be ignored.\r\n--\r\n-- If the provided function throws an exception, `promise` will become rejected with the exception message.\r\n--\r\n-- It's possible to call `new()` with no arguments. The following is equilvalent to the earlier example:\r\n--\r\n-- local promise = Promise:new()\r\n-- local success, rv = pcall(\r\n-- function(\r\n-- ...\r\n-- promise:resolve(x) -- or local resolve = promise:resolver(x) resolve()\r\n-- ...\r\n-- promise:reject(reason) -- or local reject = promise:rejecter(reason) reject()\r\n-- ...\r\n-- end\r\n-- )\r\n--\r\n-- if not success then\r\n-- promise:reject(rv)\r\n-- end\r\n---@generic T\r\nfunction PromiseClass.new(class, executor)\r\n ---@type gloom_Promise\r\n local self = setmetatable({}, class.mt)\r\n\r\n self.class = class -- :next() uses this create promises of the same class as its invocant.\r\n self.mirroring = false\r\n self.state = PromiseClass.States.PENDING\r\n self.value = nil -- value or reason\r\n self.queue = Queue:new()\r\n\r\n if executor then\r\n local success, rv = pcall(\r\n function()\r\n executor(\r\n function(x) self:resolve(x) end,\r\n function(reason) self:reject(reason) end\r\n )\r\n end\r\n )\r\n\r\n if not success then\r\n self:reject(rv)\r\n end\r\n end\r\n\r\n return self\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Public instance methods\r\n\r\n-- ----------------------------------------\r\n-- Public instance method promise:next\r\n--\r\n-- Usage:\r\n-- local promise2 = promise:next(\r\n-- function(value)\r\n-- ...\r\n-- end,\r\n-- function(reason)\r\n-- ...\r\n-- end\r\n-- )\r\n--\r\n-- \"Waits\" for `promise` to become fulfilled or rejected.\r\n--\r\n-- That is to say, it causes actions to be taken once the Promise `promise` becomes fulfilled or rejected.\r\n-- The first function will be called once the promise becomes fulfilled.\r\n-- The second function will be called once the promise becomes rejected.\r\n-- If the promise is already fulfilled or rejected, the functions will be called shortly after `next()` is called.\r\n-- These functions are called asynchronously, with only \"platform code\" on the stack.\r\n--\r\n-- If either function returns with a Promise or something Promise-like, `promise2` becomes a mirror for\r\n-- the state of this promise, effectively inserting the returned promise into the \"chain\" of Promises.\r\n-- If either function returns something else, `promise2` will become fulfilled with the first returned value.\r\n-- If either function returns nothing, `promise2` will become fulfilled with the value `nil`.\r\n-- If either function throws an error, `promise2` will become rejected with the exception as the reason.\r\n--\r\n-- The arguments are optional.\r\n-- The first defaults to a function that returns its argument.\r\n-- The second defaults to a function that calls error with its argument.\r\n\r\n---@generic R\r\n---@param on_fulfilled (fun(value: V): R) | (fun(): void)\r\n---@return gloom_Promise\r\nfunction PromiseClass:next(on_fulfilled, on_rejected)\r\n local on_fulfilled_callable = is_callable(on_fulfilled)\r\n local on_rejected_callable = is_callable(on_rejected)\r\n if not on_fulfilled_callable and not on_rejected_callable then\r\n return self\r\n end\r\n\r\n -- This promise will become fulfilled or rejected in on_state_change.\r\n local promise = self.class:new()\r\n\r\n self.queue:enqueue({\r\n on_fulfilled = on_fulfilled_callable and on_fulfilled or on_fulfilled_default,\r\n on_rejected = on_rejected_callable and on_rejected or on_rejected_default,\r\n next_promise = promise,\r\n })\r\n\r\n if self.state ~= PromiseClass.States.PENDING then\r\n schedule(self)\r\n end\r\n\r\n return promise\r\nend\r\n\r\n-- ----------------------------------------\r\n-- Public instance method promise:done\r\n--\r\n-- A sink used to ensure errors are reported.\r\n-- The arguments are optional.\r\n\r\nfunction PromiseClass:done(on_fulfilled, on_rejected)\r\n if on_fulfilled or on_rejected then\r\n self = self:next(on_fulfilled, on_rejected)\r\n end\r\n\r\n return\r\n self:next(\r\n nil,\r\n function(reason)\r\n broadcastToAll(reason, Color.Red)\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method promise:catch\r\n\r\n---@param on_rejected fun(reason: any): void\r\nfunction PromiseClass:catch(on_rejected)\r\n return self:next(nil, on_rejected)\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method promise:finally\r\n---@param on_finally fun(): void\r\nfunction PromiseClass:finally(on_finally)\r\n return self:next(\r\n function(value) on_finally() return value end,\r\n function(reason) on_finally() error(reason) end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method promise:sleep\r\n--\r\n-- WARNING: DEPRECATED/OBSOLETE.\r\n-- Instead of promise:sleep(secs), use promise:finally(TtsWait.time(secs))\r\n-- Instead of promise:sleep(secs, false), use promise:finally(TtsWait.time(secs))\r\n-- Instead of promise:sleep(secs, true), use promise:next(TtsWait.time(secs))\r\n-- TtsWait refers to the kintastic/Promise/TtsWait module.\r\n\r\nfunction PromiseClass:sleep(secs, only_sleep_on_resolve)\r\n return self:next(\r\n function(value)\r\n return self.class:new(\r\n function(resolve, reject)\r\n Wait.time(||resolve(value), secs)\r\n end\r\n )\r\n end,\r\n function(reason)\r\n if only_sleep_on_resolve then\r\n error(reason)\r\n end\r\n\r\n return self.class:new(\r\n function(resolve, reject)\r\n Wait.time(||reject(reason), secs)\r\n end\r\n )\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method promise:frame_skip\r\n--\r\n-- WARNING: DEPRECATED/OBSOLETE.\r\n-- Instead of promise:frame_skip(), use promise:finally(TtsWait.frames(1))\r\n-- TtsWait refers to the kintastic/Promise/TtsWait module.\r\n\r\nfunction PromiseClass:frame_skip()\r\n return self:next(\r\n function(value)\r\n return self.class:new(\r\n function(resolve, reject)\r\n Wait.frames(||resolve(value), 1)\r\n end\r\n )\r\n end,\r\n function(reason)\r\n return self.class:new(\r\n function(resolve, reject)\r\n Wait.frames(||reject(reason), 1)\r\n end\r\n )\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Semi-public\r\n-- You probably shouldn't be using these directly.\r\n\r\n-- ----------------------------------------\r\n-- Semi-public state constants.\r\n\r\nPromiseClass.States = {\r\n PENDING = \"pending\",\r\n FULFILLED = \"fulfilled\",\r\n REJECTED = \"rejected\",\r\n}\r\n\r\n\r\n-- ----------------------------------------\r\n-- Semi-public instance methods promise:resolver and promise:rejecter\r\n--\r\n-- These return functions that call resolve and reject repectively.\r\n-- Useful where a callback is expected.\r\n-- Arguments passed to the returned functions are ignored.\r\n\r\nfunction PromiseClass:resolver(x) return function() self:resolve(x) end end\r\nfunction PromiseClass:rejecter(reason) return function() self:reject(reason) end end\r\n\r\n\r\n-- ----------------------------------------\r\n-- Semi-public instance methods promise:fulfill and promise:reject\r\n-- ** These are different methods than class method Promise:fulfill and Promise:reject **\r\n--\r\n-- Marks the Promise as fulfilled or rejected if it's pending and if it isn't mirroring.\r\n\r\nfunction PromiseClass:fulfill(value) self:_transition(false, PromiseClass.States.FULFILLED, value) end\r\nfunction PromiseClass:reject(reason) self:_transition(false, PromiseClass.States.REJECTED, reason) end\r\n\r\n\r\n-- ----------------------------------------\r\n-- Semi-Public instance method promise:resolve\r\n-- ** This is a different method than class method Promise:resolve **\r\n--\r\n-- If `promise:resolve` is called with a Promise or something Promise-like, `promise` becomes a mirror for the\r\n-- state of `x`, effectively inserting `x` into the \"chain\" of Promises.\r\n-- If `promise:resolve` is called with something else, `promise` becomes fulfilled with the provided value.\r\n-- If `promise:resolve` is called without arguments, the promise will become fulfilled with `nil`.\r\n-- Calls to this function with an invocant (`self`) that has been fulfilled or rejected has no effect.\r\n-- Calls to this function with an invocant (`self`) that is mirroring has no effect.\r\n\r\nfunction PromiseClass:resolve(x)\r\n if self.state ~= PromiseClass.States.PENDING or self.mirroring then\r\n return\r\n end\r\n\r\n local success, rv = pcall(\r\n function()\r\n if is_instance_of(x, PromiseClass) then\r\n self:_adopt_promise(x)\r\n return\r\n end\r\n\r\n if type(x) == \"table\" then\r\n local next = x.next\r\n if is_callable(next) then\r\n self:_adopt_promiselike(x, next)\r\n return\r\n end\r\n end\r\n\r\n self:fulfill(x)\r\n end\r\n )\r\n\r\n if not success then\r\n self:reject(rv)\r\n end\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Privates\r\n\r\n-- ----------------------------------------\r\n-- Default values for `next`'s arguments.\r\n\r\nfunction on_fulfilled_default(value)\r\n return value\r\nend\r\n\r\n\r\nfunction on_rejected_default(reason)\r\n error(reason)\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private instance method promise:_adopt_promise\r\n--\r\n-- Causes the promise (`self`) to mirror the state of another Promise (`promise_to_mirror`).\r\n--\r\n-- Usage:\r\n-- if promise.state == PromiseClass.States.PENDING and not promise.mirroring then\r\n-- local success, rv = pcall(\r\n-- function()\r\n-- promise:_adopt_promise(promise_to_mirror)\r\n-- end\r\n-- end\r\n--\r\n-- if not success then\r\n-- promise:reject(rv)\r\n-- end\r\n-- end\r\n\r\nfunction PromiseClass:_adopt_promise(promise_to_mirror)\r\n if self == promise_to_mirror then\r\n self:reject(Errors.ETYPE) -- A promise can't adopt itself.\r\n return\r\n end\r\n\r\n -- `self`'s state is entirely dependent on `promise_to_mirror`'s now.\r\n self.mirroring = true\r\n\r\n -- Optimization\r\n if promise_to_mirror.state ~= PromiseClass.States.PENDING then\r\n self:_transition(true, promise_to_mirror.state, promise_to_mirror.value)\r\n return\r\n end\r\n\r\n promise_to_mirror:next(\r\n function(value)\r\n self:_transition(true, PromiseClass.States.FULFILLED, value)\r\n end,\r\n function(reason)\r\n self:_transition(true, PromiseClass.States.REJECTED, reason)\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private instance method promise:_adopt_promiselike\r\n--\r\n-- Causes the promise (`self`) to mirror the state of a Promise-like object (`promiselike_to_mirror`).\r\n--\r\n-- Usage:\r\n-- if promise.state == PromiseClass.States.PENDING and not promise.mirroring then\r\n-- local success, rv = pcall(\r\n-- function()\r\n-- promise:_adopt_promiselike(promiselike, promiselike.next)\r\n-- end\r\n-- end\r\n--\r\n-- if not success then\r\n-- promise:reject(rv)\r\n-- end\r\n-- end\r\n\r\nfunction PromiseClass:_adopt_promiselike(promiselike_to_mirror, next)\r\n if self == promiselike_to_mirror then\r\n self:reject(Errors.ETYPE) -- A promise can't adopt itself\r\n return\r\n end\r\n\r\n -- `self`'s state is entirely dependent on `promiselike_to_mirror`'s now.\r\n self.mirroring = true\r\n\r\n async(\r\n function()\r\n local success, rv = pcall(\r\n function()\r\n next(promiselike_to_mirror,\r\n function(y) self:resolve(y) end,\r\n function(reason) self:reject(reason) end\r\n )\r\n end\r\n )\r\n\r\n if not success then\r\n self:reject(rv)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private instance method promise:on_state_change\r\n--\r\n-- This method represents the core functionality of promises.\r\n--\r\n-- Once a Promise (`self`) becomes fulfilled or rejected, this is called.\r\n-- This method calls one of the callbacks provided to `self.next()`.\r\n-- This will lead to the Promise returned by `self.next()` (`next_promise`)\r\n-- to become fulfilled or rejected.\r\n--\r\n-- This can happen immediately on return of the callback, or it may happen\r\n-- later if the callback returns a Promise (or something Promise-like). If a\r\n-- Promise or similar is returned, `next_promise` becomes a mirror for the\r\n-- state of this promise, effectively inserting the returned promise into\r\n-- the \"chain\" of Promises.\r\n\r\nlocal function on_state_change(self, on_fulfilled, on_rejected, next_promise)\r\n local cb\r\n if self.state == PromiseClass.States.FULFILLED then\r\n cb = on_fulfilled\r\n else\r\n cb = on_rejected\r\n end\r\n\r\n local success, x = pcall(cb, self.value)\r\n if success then\r\n next_promise:resolve(x)\r\n else\r\n next_promise:reject(x)\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private instance method promise:schedule\r\n--\r\n-- Schedule the Promise's `.next()` callbacks for asynchronous execution.\r\n-- Called when a Promise becomes fulfilled or rejected, and when `.next()`\r\n-- is called on an already-fulfilled or already-rejected Promise.\r\n\r\nfunction schedule(self)\r\n local queue = self.queue\r\n while not queue:is_empty() do\r\n local data = queue:dequeue()\r\n async(on_state_change, self, data.on_fulfilled, data.on_rejected, data.next_promise)\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private instance method promise:_transition\r\n--\r\n-- Called to mark a Promise as fulfilled or rejected.\r\n-- It causes `.next()` callbacks to be scheduled.\r\n--\r\n-- If `self` has already become fulfilled or rejected\r\n-- calling this method has no effect.\r\n--\r\n-- If `self` is mirroring another Promise or promise-like object,\r\n-- calling this method has no effect unless `force` is true.\r\n\r\nfunction PromiseClass:_transition(force, state, value)\r\n if self.state == PromiseClass.States.PENDING and ( force or not self.mirroring ) then\r\n self.state = state\r\n self.value = value\r\n schedule(self)\r\n end\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn PromiseClass\r\n\nend)\n__bundle_register(\"kintastic/lib/Queue\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Queue class\r\n-- ================================================================================\r\n\r\n-- The class.\r\nlocal Queue = { }\r\n\r\nQueue.mt = {\r\n __index = Queue,\r\n}\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Queue:new\r\n--\r\n-- Usage:\r\n-- local q = Queue:new()\r\n\r\nfunction Queue.new(class)\r\n local self = setmetatable({}, class.mt)\r\n\r\n self.head = nil\r\n self.tail = nil\r\n\r\n return self\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method queue:enqueue\r\n\r\nfunction Queue:enqueue(value)\r\n local node = {\r\n value = value,\r\n next = nil,\r\n }\r\n\r\n if self.tail then\r\n self.tail.next = node\r\n else\r\n self.head = node\r\n end\r\n\r\n self.tail = node\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method queue:is_empty\r\n\r\nfunction Queue:is_empty()\r\n return self.head == nil\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public instance method queue:dequeue\r\n\r\nfunction Queue:dequeue()\r\n if self.head == nil then\r\n return\r\n end\r\n\r\n local value = self.head.value\r\n self.head = self.head.next\r\n if not self.head then\r\n self.tail = nil\r\n end\r\n\r\n return value\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn Queue\r\n\nend)\n__bundle_register(\"kintastic/lib/Introspection\", function(require, _LOADED, __bundle_register, __bundle_modules)\n--- Introspection utils\r\nlocal Introspection = { }\r\n\r\n--- Public function Introspection.get_class\r\nfunction Introspection.get_class(_)\r\n if type(_) ~= \"table\" then\r\n return nil\r\n end\r\n\r\n local mt = getmetatable(_)\r\n if not mt then\r\n return nil\r\n end\r\n\r\n local class = mt.__index\r\n\r\n if type(class) ~= \"table\" then\r\n return nil\r\n end\r\n\r\n return class\r\nend\r\n\r\n--- Returns true if the argument `_` is `class`, a subclass of `class`, an instance of `class`, or an instance of a subclass of `class`.\r\n---@return boolean\r\nfunction Introspection.is_instance_of(_, class)\r\n while _ ~= class do\r\n if type(_) ~= \"table\" then\r\n return false\r\n end\r\n\r\n local mt = getmetatable(_)\r\n if not mt then\r\n return false\r\n end\r\n\r\n _ = mt.__index\r\n end\r\n\r\n return true\r\nend\r\n\r\n--- Public function Introspection.is_callable\r\n---@return boolean\r\nfunction Introspection.is_callable(_)\r\n while true do\r\n local t = type(_)\r\n if t == \"function\" then return true end\r\n if t ~= \"table\" then return false end\r\n\r\n local mt = getmetatable(_)\r\n if not mt then\r\n return false\r\n end\r\n\r\n _ = mt.__call\r\n end\r\nend\r\n\r\nreturn Introspection\r\n\nend)\n__bundle_register(\"kintastic/lib/Errors\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Errors returned by this library.\r\n-- Currently just string, but could be one day become objects.\r\n-- So use these constants rather than the strings.\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal Const = require(\"kintastic/lib/Const\")\r\nlocal TableLock = require(\"kintastic/lib/TableLock\")\r\n\r\n-- Imports.\r\nlocal const = Const.const\r\nlocal lock = TableLock.lock\r\n\r\n-- The module.\r\nreturn const(lock({\r\n EDESTROYED = \"Destroyed\",\r\n EEMPTY = \"Empty container\",\r\n ENOTCONTAINER = \"Not a container\",\r\n ETIMEOUT = \"Timeout\",\r\n ETYPE = \"TypeError\",\r\n}))\r\n\nend)\n__bundle_register(\"kintastic/lib/TableLock\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Table locking tools\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal WarningHandlers = require(\"kintastic/lib/WarningHandlers\")\r\n\r\n-- Forward declarations for privates found at the bottom.\r\nlocal attach_self\r\n\r\n-- The module.\r\nlocal TableLock = { }\r\n\r\n-- Constant.\r\nlocal hook = \"__kintastic_TableLock\"\r\n\r\n\r\n-- ================================================================================\r\n-- Public on_violation handlers.\r\n\r\nfor k, v in pairs(WarningHandlers) do\r\n TableLock[k] = v\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Public attributes\r\n--\r\n-- TableLock.on_violation(msg) is called on a constraint violation.\r\n-- - TableLock.on_violation(msg) may call error() to abort execution.\r\n-- - If TableLock.on_violation(msg) returns something true, the operation will proceed.\r\n-- - If TableLock.on_violation(msg) returns something false, the operationg will fail silently.\r\n-- - Note that TableLock.on_violation(msg) is also called if declare, declare_and_set or merge is misused.\r\n-- - The functions provided by the WarningHandlers module may be used here. They are re-exported by this module.\r\n--\r\n-- TableLock.limited_functionality reports if checks performed on writes are skipped to avoid\r\n-- a bug fixed in 1.6.0.0 that could result in a mysterious \"Unexpected LuaType Tuple\" fatal error.\r\n-- https://github.com/moonsharp-devs/moonsharp/issues/133\r\n-- Since TTS now uses a newer version of MoonSharp, this is always false.\r\n\r\nTableLock.if_dev = nil -- OBSOLETE. Use a custom on_violation instead.\r\nTableLock.on_violation = TableLock.fatal_violation -- Editable.\r\nTableLock.limited_functionality = false -- Read-only.\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.declare\r\n--\r\n-- Access to the list of provided names will no\r\n-- longer be forbidden for the provided table.\r\n--\r\n-- This even works on tables that were never locked.\r\n\r\nfunction TableLock.declare(tbl, ...)\r\n local mt = getmetatable(tbl)\r\n local self = mt and mt[hook]\r\n if not self then\r\n return tbl\r\n end\r\n\r\n local c = select(\"#\", ...)\r\n for i = 1, c do\r\n local k = select(i, ...)\r\n self.declared[k] = true\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.declare_and_set\r\n--\r\n-- This even works on tables that were never locked.\r\n\r\nfunction TableLock.declare_and_set(tbl, k, v)\r\n TableLock.declare(tbl, k)\r\n tbl[k] = v\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.merge\r\n--\r\n-- This even works on tables that were never locked.\r\n\r\nfunction TableLock.merge(dst, src)\r\n local mt = getmetatable(dst)\r\n local self = mt and mt[hook]\r\n if self then\r\n for k, v in pairs(src) do\r\n self.declared[k] = true\r\n dst[k] = v\r\n end\r\n else\r\n for k, v in pairs(src) do\r\n dst[k] = v\r\n end\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.lock\r\n--\r\n-- Prevents any accesses or changes to undeclared fields of a table.\r\n-- Fields already in the table when this is called are considered declared.\r\n-- Fields can also be declared using `.declare()` and `.declare_and_set()`.\r\n--\r\n-- Locking a table that's already locked has no effect.\r\n\r\nfunction TableLock.lock(tbl)\r\n local mt, self = attach_self(tbl)\r\n if self.enabled then\r\n return tbl\r\n end\r\n\r\n for k in pairs(tbl) do\r\n self.declared[k] = true\r\n end\r\n\r\n self.enabled = true\r\n\r\n if not self.hooked then\r\n local old_index = mt.__index or rawget\r\n local old_newindex = mt.__newindex or rawset\r\n\r\n mt.__index = function(t, k)\r\n if not self.declared[k] and self.enabled then\r\n if not TableLock.on_violation(\"Undeclared access of \" .. tostring(k)) then\r\n return\r\n end\r\n end\r\n\r\n return old_index(t, k)\r\n end\r\n\r\n mt.__newindex = function(t, k, v)\r\n if not self.declared[k] and self.enabled then\r\n if not TableLock.on_violation(\"Undeclared write to \" .. tostring(k)) then\r\n return\r\n end\r\n end\r\n\r\n return old_newindex(t, k, v)\r\n end\r\n\r\n self.hooked = true\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.lock_if_dev\r\n--\r\n-- OBSOLETE. For backwards compatibility only.\r\n\r\nfunction TableLock.lock_if_dev(tbl)\r\n add_state(tbl)\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.rlock\r\n--\r\n-- A recursive version of TableLock.lock\r\n\r\nfunction TableLock.rlock(tbl)\r\n TableLock.lock(tbl)\r\n\r\n for k, v in pairs(tbl) do\r\n if type(v) == \"table\" then\r\n TableLock.rlock(v)\r\n end\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.rlock_if_dev\r\n--\r\n-- OBSOLETE. For backwards compatibility only.\r\n\r\nfunction TableLock.rlock_if_dev(tbl)\r\n add_state(tbl)\r\n\r\n for k, v in pairs(tbl) do\r\n if type(v) == \"table\" then\r\n TableLock.rlock_if_dev(v)\r\n end\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.unlock\r\n--\r\n-- For the extent of the call to the callback,\r\n-- no access to the provided table will be forbidden.\r\n--\r\n-- The first value returned by the callback is returned.\r\n-- This function is tolerant of exceptions in the callback.\r\n\r\nfunction TableLock.unlock(tbl, cb)\r\n local mt = getmetatable(tbl)\r\n local self = mt and mt[hook]\r\n if not self or not self.enabled then\r\n return cb()\r\n end\r\n\r\n local success, rv = pcall(\r\n function()\r\n self.enabled = false\r\n cb()\r\n self.enabled = true\r\n\r\n local declared = self.declared\r\n for k in pairs(tbl) do\r\n declared[k] = true\r\n end\r\n end\r\n )\r\n\r\n self.enabled = true\r\n\r\n if success then\r\n return rv\r\n else\r\n error(rv)\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableLock.declare_atom_symbols\r\n--\r\n-- Useful if you lock _G and use the TTS plugin for Atom.\r\n-- _G must be locked before calling this.\r\n\r\nfunction TableLock.declare_atom_symbols()\r\n TableLock.declare(_G,\r\n \"__atom_highlight_guid\",\r\n \"__atom_highlight_guids\"\r\n )\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Public on_violation handlers.\r\n\r\n-- ----------------------------------------\r\n-- Public on_violation handler TableLock.fatal_violation\r\n--\r\n-- Announce the violation and proceed normally.\r\n\r\nTableLock.fatal_violation = error\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public on_violation handler TableLock.announce_violation\r\n--\r\n-- Announce the violation to the host and proceed normally.\r\n\r\nfunction TableLock.announce_violation(msg)\r\n for i, player in ipairs(Player.getPlayers()) do\r\n if player.host then\r\n broadcastToColor(msg, player.color, Color.Red)\r\n break\r\n end\r\n end\r\n\r\n return true\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public on_violation handler TableLock.ignore_violation\r\n--\r\n-- Proceed normally.\r\n\r\nfunction TableLock.ignore_violation(msg)\r\n return true\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Privates\r\n\r\n-- ----------------------------------------\r\n-- Private function attach_self\r\n\r\nfunction attach_self(tbl)\r\n local mt = getmetatable(tbl)\r\n if not mt then\r\n mt = { }\r\n setmetatable(tbl, mt)\r\n end\r\n\r\n local self = mt[hook]\r\n if not self then\r\n self = {\r\n hooked = false,\r\n enabled = false,\r\n declared = { },\r\n }\r\n\r\n mt[hook] = self\r\n end\r\n\r\n return mt, self\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn TableLock\r\n\nend)\n__bundle_register(\"kintastic/lib/WarningHandlers\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Warning Handlers\r\n--\r\n-- This module should be minimal, being loaded by TableLock and Const.\r\n-- ================================================================================\r\n\r\n-- The module.\r\nlocal WarningHandlers = { }\r\n\r\n\r\n-- ================================================================================\r\n-- Public functions.\r\n--\r\n-- These function can be used where:\r\n-- - The function may thrown an exception.\r\n-- - The function may return something false to abort the operation silently.\r\n-- - The function may return something true to proceed normally.\r\n\r\n-- ----------------------------------------\r\n-- Public function WarningHandlers.fatal_violation\r\n--\r\n-- Throw an exception.\r\n\r\nWarningHandlers.fatal_violation = error\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function WarningHandlers.announce_violation\r\n--\r\n-- Announce the violation to the host and proceed normally.\r\n\r\nfunction WarningHandlers.announce_violation(msg)\r\n for i, player in ipairs(Player.getPlayers()) do\r\n if player.host then\r\n broadcastToColor(msg, player.color, Color.Red)\r\n break\r\n end\r\n end\r\n\r\n return true\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function WarningHandlers.announce_violation_to_all\r\n--\r\n-- Announce the violation to all and proceed normally.\r\n\r\nfunction WarningHandlers.announce_violation(msg)\r\n broadcastToAll(msg, player.color, Color.Red)\r\n return true\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function WarningHandlers.ignore_violation\r\n--\r\n-- Proceed normally.\r\n\r\nfunction WarningHandlers.ignore_violation(msg)\r\n return true\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn WarningHandlers\r\n\nend)\n__bundle_register(\"kintastic/lib/Const\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Constant tables\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal WarningHandlers = require(\"kintastic/lib/WarningHandlers\")\r\n\r\n-- Forward declarations for privates found at the bottom.\r\nlocal attach_self\r\n\r\n-- The module.\r\nlocal Const = { }\r\n\r\n-- Constant.\r\nlocal hook = \"__kintastic_Const\"\r\n\r\n\r\n-- ================================================================================\r\n-- Public on_violation handlers.\r\n\r\nfor k, v in pairs(WarningHandlers) do\r\n Const[k] = v\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Public attributes\r\n--\r\n-- Const.on_violation(msg) is called on a constraint violation.\r\n-- - Const.on_violation(msg) may call error() to abort execution.\r\n-- - If Const.on_violation(msg) returns something true, the operation will proceed.\r\n-- - If Const.on_violation(msg) returns something false, the operationg will fail silently.\r\n-- - Note that Const.on_violation(msg) is also called if declare, declare_and_set or merge is misused.\r\n-- - The functions provided by the WarningHandlers module may be used here. They are re-exported by this module.\r\n\r\nConst.is_dev = false -- OBSOLETE. Use a custom on_violation instead.\r\nConst.on_violation = Const.fatal_violation -- Editable.\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Const.const\r\n--\r\n-- Prevents any changes or additions to the provided table.\r\n\r\nfunction Const.const(tbl)\r\n local mt, self = attach_self(tbl)\r\n if not self.hooked then\r\n local old_newindex = mt.__newindex or rawset\r\n\r\n mt.__newindex = function(t, k, v)\r\n if not Const.on_violation(\"Can't modify constant table.\") then\r\n return\r\n end\r\n\r\n return old_newindex(t, k, v)\r\n end\r\n\r\n self.hooked = true\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Const.const_if_dev\r\n--\r\n-- OBSOLETE. For backwards compatibility only.\r\n\r\nfunction Const.const_if_dev(tbl)\r\n if Const.is_dev then\r\n Const.const(tbl)\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Const.rconst\r\n--\r\n-- A recursive version of Const.const\r\n\r\nfunction Const.rconst(tbl)\r\n for k, v in pairs(tbl) do\r\n if type(v) == \"table\" then\r\n Const.rconst(v)\r\n end\r\n end\r\n\r\n return Const.const(tbl)\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Const.rconst_if_dev\r\n--\r\n-- OBSOLETE. For backwards compatibility only.\r\n\r\nfunction Const.rconst_if_dev(tbl)\r\n if Const.is_dev then\r\n Const.rconst(tbl)\r\n end\r\n\r\n return tbl\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Privates\r\n\r\n-- ----------------------------------------\r\n-- Private function attach_self\r\n\r\nfunction attach_self(tbl)\r\n local mt = getmetatable(tbl)\r\n if not mt then\r\n mt = { }\r\n setmetatable(tbl, mt)\r\n end\r\n\r\n local self = mt[hook]\r\n if not self then\r\n self = {\r\n hooked = false,\r\n }\r\n\r\n mt[hook] = self\r\n end\r\n\r\n return mt, self\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn Const\r\n\nend)\n__bundle_register(\"kintastic/lib/Async\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Queue = require(\"kintastic/lib/Queue\")\r\n\r\n-- Forward declarations for privates found at the bottom.\r\nlocal initiated\r\nlocal queue\r\nlocal async_run\r\n\r\n--- Asynchronous Execution\r\nlocal Async = { }\r\n\r\n---@class Async_this\r\nlocal this = {}\r\n\r\n--- Runs the provided function asynchronously.\r\n--- Extra arguments passed to `async` will be passed to the function.\r\n---\r\n--- WARNING: A nil argument may cause subsequent arguments to be ignored.\r\n--\r\n--- On execution, the function's stack will be empty except for \"platform code\".\r\n--- As such, its execution won't start immediately, but shortly after.\r\nfunction Async.async(cb, ...)\r\n queue:enqueue({ cb, ... })\r\n if not initiated then\r\n initiated = true\r\n Wait.frames(async_run, 0)\r\n end\r\nend\r\n\r\n\r\ninitiated = false\r\nqueue = Queue:new()\r\n\r\nasync_run = function()\r\n initiated = false\r\n while not queue:is_empty() do\r\n local item = queue:dequeue()\r\n local success, rv = pcall(\r\n function()\r\n -- Do absolutely everything with user data inside of a pcall.\r\n item[1](unpack(item, 2))\r\n end\r\n )\r\n\r\n if not success then\r\n -- `rv` is also user data.\r\n local success = pcall(\r\n function()\r\n broadcastToAll(\"Error in async callback: \" .. rv, \"Red\")\r\n end\r\n )\r\n\r\n if not success then\r\n broadcastToAll(\"Error in async callback\", \"Red\")\r\n end\r\n end\r\n end\r\nend\r\n\r\nreturn Async\r\n\nend)\n__bundle_register(\"kintastic/lib/Iters\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Iters\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal ArrayUtils = require(\"kintastic/lib/ArrayUtils\")\r\nlocal DictUtils = require(\"kintastic/lib/DictUtils\")\r\n\r\n-- The module.\r\nlocal Iters = { }\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.ivalues\r\n\r\nIters.ivalues = ArrayUtils.ivalues\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.dipairs\r\n-- Public function Iters.ipairs_desc\r\n\r\nIters.dipairs = ArrayUtils.dipairs\r\nIters.ipairs_desc = ArrayUtils.ipairs_desc\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.divalues\r\n-- Public function Iters.ivalues_desc\r\n\r\nIters.divalues = ArrayUtils.divalues\r\nIters.ivalues_desc = ArrayUtils.ivalues_desc\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.sivalues\r\n-- Public function Iters.sorted_ivalues\r\n\r\nIters.sivalues = ArrayUtils.sivalues\r\nIters.sorted_ivalues = ArrayUtils.sorted_ivalues\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.keys\r\n\r\nIters.keys = DictUtils.keys\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.values\r\n\r\nIters.values = DictUtils.values\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function Iters.spairs\r\n-- Public function Iters.sorted_pairs\r\n\r\nIters.spairs = DictUtils.spairs\r\nIters.sorted_pairs = DictUtils.sorted_pairs\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn Iters\r\n\nend)\n__bundle_register(\"kintastic/lib/DictUtils\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Dict utils\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal TableUtils = require(\"kintastic/lib/TableUtils\")\r\n\r\n-- The module.\r\nlocal DictUtils = { }\r\n\r\n-- Inherit functions from TableUtils.\r\nsetmetatable(DictUtils, {\r\n __index = TableUtils,\r\n})\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.get_keys\r\n\r\nfunction DictUtils.get_keys(dict)\r\n local ks = { }\r\n for k in pairs(dict) do table.insert(ks, k) end\r\n return ks\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.get_values\r\n\r\nfunction DictUtils.get_values(dict)\r\n local vs = { }\r\n for k, v in pairs(dict) do table.insert(vs, v) end\r\n return vs\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.map_values\r\n--\r\n-- Equivalent to TableUtils.map(DictUtils.get_values(tbl, cb))\r\n\r\nfunction DictUtils.map_values(tbl, cb)\r\n local mapped = { }\r\n for k, v in pairs(tbl) do\r\n table.insert(mapped, cb(v))\r\n end\r\n\r\n return mapped\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.pick\r\n\r\nfunction DictUtils.pick(dict, test)\r\n local picked = { }\r\n for k, v in pairs(dict) do\r\n if test(k, v) then\r\n picked[k] = v\r\n end\r\n end\r\n\r\n return picked\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.select\r\n--\r\n-- WARNING: DEPRECATED/OBSLETE.\r\n\r\nDictUtils.select = DictUtils.pick\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.merge\r\n--\r\n-- Adds the elements of `tbl2` to `tbl1`.\r\n-- Shallow copy.\r\n-- Returns `tbl1`.\r\n\r\nfunction DictUtils.merge(tbl1, tbl2)\r\n for k, v in pairs(tbl2) do\r\n tbl1[k] = v\r\n end\r\n\r\n return tbl1\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.rmerge\r\n--\r\n-- Recursive version of DictUtils.merge.\r\n-- `tbl1` controls the recursion.\r\n-- Returns `tbl1`.\r\n\r\nfunction DictUtils.rmerge(tbl1, tbl2)\r\n for k, v in pairs(tbl2) do\r\n if type(tbl1[k]) == \"table\" then\r\n DictUtils.rmerge(tbl1[k], v)\r\n else\r\n tbl1[k] = v\r\n end\r\n end\r\n\r\n return tbl1\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.keys\r\n--\r\n-- For when you don't need the value.\r\n--\r\n-- Usage:\r\n-- for value in DictUtils.keys(dict) do ... end\r\n\r\nfunction DictUtils.keys(dict)\r\n local i = nil\r\n return function()\r\n i = next(dict, i)\r\n -- `i ~= nil` would cause issues in the event of destroyed TTS objects in the set.\r\n -- `type(i) ~= nil` would be most reliable, but surely slower.\r\n if i or i ~= nil then\r\n return i\r\n end\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.values\r\n--\r\n-- For when you don't need the key.\r\n--\r\n-- Usage:\r\n-- for value in DictUtils.values(dict) do ... end\r\n\r\nfunction DictUtils.values(dict)\r\n local i = nil\r\n return function()\r\n local v\r\n i, v = next(dict, i)\r\n -- `i ~= nil` would cause issues in the event of destroyed TTS objects in the set.\r\n -- `type(i) ~= nil` would be most reliable, but surely slower.\r\n if i or i ~= nil then\r\n return v\r\n end\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function DictUtils.spairs\r\n-- Public function DictUtils.sorted_pairs\r\n\r\n-- Iterates over a dictionary in ascending key order (default) or in a user-defined order.\r\n--\r\n-- Usage:\r\n-- for key, value in DictUtils.spairs(dict) do ... end\r\n-- for key, value in DictUtils.spairs(dict, compare_func) do ... end\r\n--\r\n-- Example:\r\n--\r\n-- local characters = {\r\n-- Red = { name = ..., ... },\r\n-- Blue = { name = ..., ... },\r\n-- ...\r\n-- }\r\n--\r\n-- for player_color, character in DictUtils.spairs(characters, |ka,va,kb,vb|va.name < vb.name) do\r\n-- ...\r\n-- end\r\n\r\nfunction DictUtils.spairs(dict, compare)\r\n local ks = get_keys(dict)\r\n table.sort(ks, |ka,kb|compare(ka, dict[ka], kb, dict[kb]))\r\n local i = 0 -- Iterator state\r\n return function() -- Iterator function\r\n i = i + 1\r\n -- `ks[i] ~= nil` would cause issues in the event of destroyed TTS objects in the set.\r\n -- `type(ks[i]) ~= nil` would be most reliable, but surely slower.\r\n if ks[i] or ks[i] ~= nil then\r\n return ks[i], dict[ks[i]]\r\n end\r\n end\r\nend\r\n\r\nDictUtils.sorted_pairs = DictUtils.spairs\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn DictUtils\r\n\nend)\n__bundle_register(\"kintastic/lib/TableUtils\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Table utils\r\n-- ================================================================================\r\n\r\n-- The module.\r\nlocal TableUtils = { }\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n\r\n-- ----------------------------------------\r\n-- Public function TableUtils.copy\r\n--\r\n-- Shallow copy.\r\n\r\nfunction TableUtils.copy(tbl)\r\n local copy = { }\r\n for k, v in pairs(tbl) do\r\n copy[k] = v\r\n end\r\n\r\n return copy\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableUtils.map\r\n\r\nfunction TableUtils.map(tbl, cb)\r\n local mapped = { }\r\n for k, v in pairs(tbl) do\r\n mapped[k] = cb(v)\r\n end\r\n\r\n return mapped\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TableUtils.count\r\n\r\nfunction TableUtils.count(tbl, cb)\r\n local count = 0\r\n for k, v in pairs(tbl) do\r\n if cb(v) then\r\n count = count + 1\r\n end\r\n end\r\n\r\n return count\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn TableUtils\r\n\nend)\n__bundle_register(\"kintastic/lib/ArrayUtils\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Array utils\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal TableUtils = require(\"kintastic/lib/TableUtils\")\r\n\r\n-- The module.\r\nlocal ArrayUtils = { }\r\n\r\n-- Inherit functions from TableUtils.\r\nsetmetatable(ArrayUtils, {\r\n __index = TableUtils,\r\n})\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.push\r\n--\r\n-- Returns the added value(s).\r\n\r\nfunction ArrayUtils.push(array, ...)\r\n local n = select(\"#\", ...)\r\n for i = 1, n do\r\n local val = select(i, ...)\r\n table.insert(array, val)\r\n end\r\n\r\n return ...\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.filter\r\n\r\nfunction ArrayUtils.filter(array, test)\r\n local filtered = { }\r\n for i, v in ipairs(array) do\r\n if test(i, v) then\r\n table.insert(filtered, v)\r\n end\r\n end\r\n\r\n return filtered\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.first\r\n\r\nfunction ArrayUtils.first(array, test)\r\n local filtered = { }\r\n for i, v in ipairs(array) do\r\n if test(i, v) then\r\n return v\r\n end\r\n end\r\n\r\n return nil\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.first_idx\r\n\r\nfunction ArrayUtils.first_idx(array, test)\r\n local filtered = { }\r\n for i, v in ipairs(array) do\r\n if test(i, v) then\r\n return i, v\r\n end\r\n end\r\n\r\n return\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.shuffle_inplace\r\n\r\nfunction ArrayUtils.shuffle_inplace(array)\r\n for i = #array, 2, -1 do\r\n local j = math.random(i)\r\n array[i], array[j] = array[j], array[i]\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.shuffle\r\n\r\nfunction ArrayUtils.shuffle(array)\r\n local copy = { }\r\n for i = 1, #array do\r\n copy[i] = array[i]\r\n end\r\n\r\n ArrayUtils.shuffle_inplace(copy)\r\n return copy\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.sum\r\n\r\nfunction ArrayUtils.sum(array)\r\n local sum = 0\r\n for i, val in ipairs(array) do\r\n sum = sum + 1\r\n end\r\n\r\n return sum\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.make_set\r\n\r\nfunction ArrayUtils.make_set(array, key_gen, val_gen)\r\n if key_gen == nil then\r\n key_gen = function(_) return _ end\r\n end\r\n\r\n if val_gen == nil then\r\n val_gen = function(_) return true end\r\n end\r\n\r\n local set = { }\r\n for i, v in ipairs(array) do\r\n set[key_gen(v)] = val_gen(v)\r\n end\r\n\r\n return set\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.format_item_list\r\n\r\nfunction ArrayUtils.format_item_list(array, locale)\r\n local n = #array\r\n if n == 0 then\r\n return \"none\"\r\n end\r\n\r\n if n == 1 then\r\n return array[1]\r\n end\r\n\r\n local s = array[1]\r\n for i = 2, n-1 do\r\n s = s .. \", \" .. array[i]\r\n end\r\n\r\n return s .. \" and \" .. array[n]\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.ivalues\r\n--\r\n-- For when you don't need the index.\r\n--\r\n-- Usage:\r\n-- for value in ArrayUtils.ivalues(array) do ... end\r\n\r\nfunction ArrayUtils.ivalues(array)\r\n local i = 0\r\n local n = #array\r\n return function()\r\n if i < n then\r\n i = i + 1\r\n return array[i]\r\n end\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.dipairs\r\n-- Public function ArrayUtils.ipairs_desc\r\n--\r\n-- Usage:\r\n-- for index, value in ArrayUtils.dipairs(array) do ... end\r\n\r\nfunction ArrayUtils.dipairs(array)\r\n local i = #array + 1\r\n return function()\r\n if i > 1 then\r\n i = i - 1\r\n return i, array[i]\r\n end\r\n end\r\nend\r\n\r\nArrayUtils.ipairs_desc = ArrayUtils.dipairs\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.divalues\r\n-- Public function ArrayUtils.ivalues_desc\r\n--\r\n-- Usage:\r\n-- for value in ArrayUtils.divalues(array) do ... end\r\n\r\nfunction ArrayUtils.divalues(array)\r\n local i = #array + 1\r\n return function()\r\n if i > 1 then\r\n i = i - 1\r\n return array[i]\r\n end\r\n end\r\nend\r\n\r\nArrayUtils.ivalues_desc = ArrayUtils.divalues\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function ArrayUtils.sivalues\r\n-- Public function ArrayUtils.sorted_ivalues\r\n\r\n-- Iterates over an array in ascending value order (default) or in a user-defined order.\r\n--\r\n-- Usage:\r\n-- for value in ArrayUtils.sivalues(array) do ... end\r\n-- for value in ArrayUtils.sivalues(array, compare_func) do ... end\r\n--\r\n-- Example:\r\n--\r\n-- local characters = {\r\n-- { name = ..., ... },\r\n-- { name = ..., ... },\r\n-- ...\r\n-- }\r\n--\r\n-- for character in ArrayUtils.sivalues(characters, |a,b|a.name < b.name) do\r\n-- ...\r\n-- end\r\n\r\nfunction ArrayUtils.sivalues(array, compare)\r\n local copy = ArrayUtils.copy(array)\r\n table.sort(copy, compare)\r\n local i = 0 -- Iterator state\r\n return function() -- Iterator function\r\n i = i + 1\r\n if copy[i] ~= nil then\r\n return copy[i]\r\n end\r\n end\r\nend\r\n\r\nArrayUtils.sorted_ivalues = ArrayUtils.sivalues\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn ArrayUtils\r\n\nend)\n__bundle_register(\"lib.SignalLoading\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal SignalLoading = {}\n\nlocal TAG = \"SIGNAL LOADING\"\nlocal finishedOnLoad = false\n\nlocal this = {}\n\n---@overload fun(): boolean\n---@param withUi boolean\n---@return boolean\nfunction SignalLoading.isLoaded(withUi)\n if not this.isObjectLoaded(self) then\n return false\n end\n\n if withUi == nil or withUi == true then\n return SignalLoading.isObjectUiLoaded(self)\n end\n\n return true\nend\n\nfunction SignalLoading.isObjectUiLoaded(obj)\n return obj.UI.getXml() ~= \"\" and not Global.UI.loading\nend\n\n---@param obj tts__Object\n---@return boolean\nfunction SignalLoading.isObjectLoaded(obj)\n if obj.hasTag(TAG) then\n return --[[---@type boolean]] obj.call(\"isOnLoadFinished\")\n end\n\n return this.isObjectLoaded(obj)\nend\n\nfunction SignalLoading.finishOnLoad()\n finishedOnLoad = true\nend\n\n---@param obj tts__Object\nfunction this.isObjectLoaded(obj)\n return not obj.spawning and not obj.loading_custom\nend\n\nfunction isOnLoadFinished()\n return finishedOnLoad\nend\n\nif self ~= Global then\n self.addTag(\"Signal Loading\")\nend\n\nreturn SignalLoading\n\nend)\n__bundle_register(\"lib.promise.TtsObject\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TtsObject = require(\"kintastic.lib.Promise.TtsObject\")\r\n\r\nreturn TtsObject\r\n\nend)\n__bundle_register(\"kintastic.lib.Promise.TtsObject\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Errors = require(\"kintastic/lib/Errors\")\r\nlocal Promise = require(\"kintastic/lib/Promise\")\r\nlocal TableUtils = require(\"kintastic/lib/TableUtils\")\r\nlocal BaseTtsObject = require(\"kintastic/lib/TtsObject\")\r\n\r\nlocal wait_for_instant_transform\r\nlocal wait_for_smooth_transform\r\nlocal wrap_instant_transform\r\nlocal wrap_smooth_transform\r\n\r\n--- Promise-based version of Object methods.\r\nlocal TtsObject = { }\r\n\r\n-- Inherit functions from base TtsZoneUtils.\r\nsetmetatable(TtsObject, {\r\n __index = BaseTtsObject,\r\n})\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n-- Thin, promise-returning wrappers.\r\n--\r\n-- Unless otherwise stated, each function returns\r\n-- a promise which will be fulfilled with `obj`\r\n-- or rejected with an error message.\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.setTransform\r\n-- Public function TtsObject.setTransformSmooth\r\n-- Public function TtsObject.setPosition\r\n-- Public function TtsObject.setPositionSmooth\r\n-- Public function TtsObject.translate\r\n-- Public function TtsObject.setRotation\r\n-- Public function TtsObject.setRotationSmooth\r\n-- Public function TtsObject.rotate\r\n-- Public function TtsObject.setScale\r\n-- Public function TtsObject.scale\r\n-- Public function TtsObject.moveAtop\r\n-- Public function TtsObject.moveAtopSmooth\r\n-- Public function TtsObject.moveToZoneTop WARNING: DEPRECATED/OBSLETE.\r\n-- Public function TtsObject.moveToZoneTopSmooth WARNING: DEPRECATED/OBSLETE.\r\n\r\nTtsObject.setTransform = function(obj, ...) wrap_instant_transform(obj, BaseTtsObject.setTransform, obj, ...) end\r\nTtsObject.setTransformSmooth = function(obj, ...) wrap_smooth_transform(obj, BaseTtsObject.setTransformSmooth, obj, ...) end\r\nTtsObject.setPosition = function(obj, ...) wrap_instant_transform(obj, obj.setPosition, ...) end\r\nTtsObject.setPositionSmooth = function(obj, ...) wrap_smooth_transform(obj, obj.setPositionSmooth, ...) end\r\nTtsObject.translate = function(obj, ...) wrap_smooth_transform(obj, obj.translate, ...) end\r\nTtsObject.setRotation = function(obj, ...) wrap_instant_transform(obj, obj.setRotation, ...) end\r\nTtsObject.setRotationSmooth = function(obj, ...) wrap_smooth_transform(obj, obj.setRotationSmooth, ...) end\r\nTtsObject.rotate = function(obj, ...) wrap_smooth_transform(obj, obj.rotate, ...) end\r\nTtsObject.setScale = function(obj, ...) wrap_instant_transform(obj, obj.setScale, ...) end\r\nTtsObject.scale = function(obj, ...) wrap_instant_transform(obj, obj.scale, ...) end\r\nTtsObject.flip = function(obj, ...) wrap_smooth_transform(obj, obj.flip, ...) end\r\nTtsObject.moveAtop = function(obj, ...) wrap_instant_transform(obj, BaseTtsObject.moveAtop, obj, ...) end\r\nTtsObject.moveAtopSmooth = function(obj, ...) wrap_smooth_transform(obj, BaseTtsObject.moveAtopSmooth, obj, ...) end\r\nTtsObject.moveToZoneTop = function(obj, ...) wrap_instant_transform(obj, BaseTtsObject.moveAtop, obj, ...) end\r\nTtsObject.moveToZoneTopSmooth = function(obj, ...) wrap_smooth_transform(obj, BaseTtsObject.moveAtopSmooth, obj, ...) end\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.takeObject\r\n--\r\n-- This is a thin wrapper around `obj.takeObject` that returns a promise that\r\n-- becomes fulfilled with the spawned object once the object has been created.\r\n--\r\n-- The promise is rejected with reason Errors.EEMPTY if `obj.takeObject` is empty.\r\n-- Note that empty infinite bags results in an exception.\r\n-- The promise is rejected with reason Errors.ENOTCONTAINER if `obj.takeObject` returns `nil`.\r\n--\r\n-- If an `immediate_callback` argument is provided, it will be called as soon\r\n-- as `spawnObjectJSON` returns, with the object as a parameter. It's return\r\n-- value is ignored.\r\n--\r\n-- If a `callback_function` argument is provided, it will be called once the\r\n-- object is spawned, and the promise returned by `TtsObject.takeObject`\r\n-- will adopt the value returned by the callback function.\r\n--\r\n-- WARNING: The `return_obj` argument is DEPERECATED/OBSOLETE.\r\n\r\n---@return gloom_Promise\r\nfunction TtsObject.takeObject(obj, args)\r\n local spawned_obj\r\n local promise = Promise:new(\r\n function(resolve, reject)\r\n args = args and TableUtils.copy(args) or { }\r\n\r\n if args.callback_function then\r\n local orig_cb = args.callback_function\r\n args.callback_function = function(spawned_obj)\r\n resolve(orig_cb(spawned_obj))\r\n end\r\n else\r\n args.callback_function = resolve\r\n end\r\n\r\n -- Empty containers result in an error rather than a return of nil.\r\n -- Let's try to provide a better result.\r\n if TtsObject.is_container(obj) and TtsObject.getQuantity(obj) == 0 then\r\n reject(Errors.EEMPTY)\r\n return\r\n end\r\n\r\n spawned_obj = obj.takeObject(args)\r\n if not spawned_obj then\r\n reject(Errors.ENOTCONTAINER)\r\n end\r\n end\r\n )\r\n\r\n if args.return_obj then\r\n return promise, spawned_obj\r\n else\r\n return promise\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.setState\r\n--\r\n-- This is a thin wrapper around `obj.setState` that returns a promise that\r\n-- becomes fulfilled once the object has changed state.\r\n--\r\n-- If the `return_obj` argument is provided and true, the new object will\r\n-- be returned as a second value.\r\n\r\nfunction TtsObject.setState(obj, state_id, return_obj)\r\n local new_obj\r\n local promise = Promise:new(\r\n function(resolve, reject)\r\n new_obj = obj.setState(state_id)\r\n wait_for_instant_transform(new_obj, resolve, reject)\r\n end\r\n )\r\n\r\n if return_obj then\r\n return promise, new_obj\r\n else\r\n return promise\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n\r\nfunction TtsObject.addForce() error(\"addForce isn't currently supported\") end\r\nfunction TtsObject.addTorque() error(\"addTorque isn't currently supported\") end\r\nfunction TtsObject.setVelocity() error(\"setVelocity isn't currently supported\") end\r\nfunction TtsObject.setAngularVelocity() error(\"setAngularVelocity isn't currently supported\") end\r\n\r\n\r\n-- ================================================================================\r\n-- Private helpers\r\n\r\n-- ----------------------------------------\r\n-- Private function wait_for_instant_transform\r\n--\r\n-- They're not quite instantaneous.\r\n\r\nfunction wait_for_instant_transform(obj, resolve, reject)\r\n Wait.frames(\r\n function()\r\n if obj == nil then\r\n reject(Errors.EDESTROYED)\r\n else\r\n resolve(obj)\r\n end\r\n end,\r\n 1\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private function wait_for_smooth_transform\r\n\r\nfunction wait_for_smooth_transform(obj, resolve, reject)\r\n Wait.condition(\r\n function()\r\n if obj == nil then\r\n reject(Errors.EDESTROYED)\r\n else\r\n resolve(obj)\r\n end\r\n end,\r\n function()\r\n return obj == nil or not obj.isSmoothMoving()\r\n end,\r\n 3,\r\n function()\r\n reject(Errors.ETIMEOUT)\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private function wrap_instant_transform\r\n\r\nfunction wrap_instant_transform(obj, f, ...)\r\n local args = { ... }\r\n return Promise:new(\r\n function(resolve, reject)\r\n f(unpack(args))\r\n wait_for_instant_transform(obj, resolve, reject)\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Private function wrap_smooth_transform\r\n\r\nfunction wrap_smooth_transform(obj, f, ...)\r\n local args = { ... }\r\n return Promise:new(\r\n function(resolve, reject)\r\n f(unpack(args))\r\n wait_for_smooth_transform(obj, resolve, reject)\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn TtsObject\r\n\nend)\n__bundle_register(\"kintastic/lib/TtsObject\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Extensions for TTS's Objects.\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal ArrayUtils = require(\"kintastic/lib/ArrayUtils\")\r\n\r\n-- Imports.\r\nlocal make_set = ArrayUtils.make_set\r\n\r\n-- The module.\r\nlocal TtsObject = { }\r\n\r\n\r\n-- ================================================================================\r\n-- Private constants\r\n\r\nlocal EMPTY_TABLE = { }\r\n\r\nlocal FINITE_CONTAINERS = { -- .name\r\n \"Bag\",\r\n \"CheckerStack\",\r\n \"ChipStack\",\r\n \"Custom_Model_Bag\",\r\n \"Custom_Model_Stack\",\r\n \"Custom_Token_Stack\",\r\n \"Deck\",\r\n \"DeckCustom\",\r\n}\r\n\r\nlocal INFINITE_CONTAINERS = { -- .name\r\n \"Custom_Model_Infinite_Bag\",\r\n \"go_game_bowl_black\",\r\n \"go_game_bowl_white\",\r\n \"Infinite_Bag\",\r\n}\r\n\r\nlocal ZONES = { -- .name\r\n \"FogOfWar\",\r\n \"FogOfWarTrigger\",\r\n \"RandomizeTrigger\",\r\n \"ScriptingTrigger\",\r\n -- No objects for hand zone.\r\n}\r\n\r\nlocal IS_INFINITE_CONTAINER_LOOKUP = make_set(INFINITE_CONTAINERS)\r\nlocal IS_FINITE_CONTAINER_LOOKUP = make_set(FINITE_CONTAINERS)\r\nlocal IS_ZONE_LOOKUP = make_set(ZONES)\r\n\r\n\r\n-- ================================================================================\r\n-- Public interface\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.getQuantity\r\n--\r\n-- `obj.getQuantity()` doesn't return the correct amount for\r\n-- containers that were reduced to one object since the last frame.\r\n--\r\n-- Returns `-1` for infinite containers. At this time,\r\n-- this is the case even if the infinite container contains\r\n-- nothing. This may return `0` in the future.\r\n--\r\n-- Returns `nil` for non-containers.\r\n\r\nfunction TtsObject.getQuantity(obj)\r\n local obj_type = obj.name\r\n if IS_INFINITE_CONTAINER_LOOKUP[obj_type] then\r\n return -1\r\n elseif IS_FINITE_CONTAINER_LOOKUP[obj_type] then\r\n local quantity = obj.getQuantity()\r\n return quantity > 0 and quantity or ( obj.remainder and 1 or 0 )\r\n else\r\n return nil\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.getBounds\r\n--\r\n-- The results are based on the object's current orientation.\r\n--\r\n-- `obj.getBounds()` doesn't return the\r\n-- correct size for zones, but this does.\r\n\r\nfunction TtsObject.getBounds(obj)\r\n local bounds = obj.getBounds()\r\n\r\n if TtsObject.is_zone(obj) then\r\n local rotation = obj.getRotation()\r\n local size =\r\n obj.getScale()\r\n :rotateOver('z', rotation.z)\r\n :rotateOver('x', rotation.x)\r\n :rotateOver('y', rotation.y)\r\n\r\n bounds.size = Vector(\r\n math.abs(size.x),\r\n math.abs(size.y),\r\n math.abs(size.z)\r\n )\r\n end\r\n\r\n return bounds\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.getBoundsNormalized\r\n--\r\n-- The result is based on the unrotated object.\r\n--\r\n-- `obj.getBoundsNormalized()` doesn't return the\r\n-- correct size for zones, but this does.\r\n\r\n\r\nfunction TtsObject.getBoundsNormalized(obj)\r\n local bounds = obj.getBounds()\r\n\r\n if TtsObject.is_zone(obj) then\r\n bounds.size = obj.getScale()\r\n end\r\n\r\n return bounds\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.getSize\r\n--\r\n-- The result is based on the object's current orientation.\r\n--\r\n-- `obj.getBounds().size` doesn't return the\r\n-- correct value for zones, but this does.\r\n\r\nfunction TtsObject.getSize(obj)\r\n if TtsObject.is_zone(obj) then\r\n local rotation = obj.getRotation()\r\n local size =\r\n obj.getScale()\r\n :rotateOver('z', rotation.z)\r\n :rotateOver('x', rotation.x)\r\n :rotateOver('y', rotation.y)\r\n\r\n return Vector(\r\n math.abs(size.x),\r\n math.abs(size.y),\r\n math.abs(size.z)\r\n )\r\n else\r\n return obj.getBounds().size\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.getSizeNormalized\r\n--\r\n-- The result is based on the unrotated object.\r\n--\r\n-- `obj.getBoundsNormalized().size` doesn't return\r\n-- the correct value for zones, but this does.\r\n\r\n\r\nfunction TtsObject.getSize(obj)\r\n if TtsObject.is_zone(obj) then\r\n return obj.getScale()\r\n else\r\n return obj.getBoundsNormalized().size\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.get_abs_width()\r\n-- Public function TtsObject.get_abs_height()\r\n-- Public function TtsObject.get_abs_depth()\r\n--\r\n-- These refer to the dimensions of the object\r\n-- along the x, y and z axes respectively.\r\n--\r\n-- The results are based on the object's current orientation.\r\n\r\nfunction TtsObject.get_abs_width(obj) return TtsObject.getSize(obj).x end\r\nfunction TtsObject.get_abs_height(obj) return TtsObject.getSize(obj).y end\r\nfunction TtsObject.get_abs_depth(obj) return TtsObject.getSize(obj).z end\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.get_top_pos\r\n--\r\n-- Returns the position which consists\r\n-- - the center x of the object's bounding box,\r\n-- - the center z of the object's bounding box, and\r\n-- - the highest y of the object's bounding box.\r\n--\r\n-- The result is based on the object's current orientation.\r\n\r\nfunction TtsObject.get_top_pos(obj)\r\n local bounds = TtsObject.getBounds(obj)\r\n local v = bounds.center\r\n v.y = v.y + bounds.size.y / 2\r\n return v\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.get_bot_pos\r\n--\r\n-- Returns the position which consists\r\n-- - the center x of the object's bounding box,\r\n-- - the center z of the object's bounding box, and\r\n-- - the lowest y of the object's bounding box.\r\n--\r\n-- The result is based on the object's current orientation.\r\n\r\nfunction TtsObject.get_bot_pos(obj)\r\n local bounds = TtsObject.getBounds(obj)\r\n local v = bounds.center\r\n v.y = v.y - bounds.size.y / 2\r\n return v\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.get_height_to_origin\r\n--\r\n-- Returns the difference between the object's origin and the lowest y of the object's box.\r\n--\r\n-- The result is based on the object's current orientation.\r\n\r\nfunction TtsObject.get_height_to_origin(obj)\r\n local bounds = TtsObject.getBounds(obj)\r\n return bounds.size.y / 2 + bounds.offset.y\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.get_drop_pos\r\n--\r\n-- Returns the position to which to move the object so it sits just above the container.\r\n-- The result is based on the current orientation of both the object and the container.\r\n--\r\n-- Options:\r\n-- protrusion: What portion of the object should be atop the container. (Default = 1)\r\n-- offset: A vector-like added to the result. (Default = {0,0,0})\r\n\r\nfunction TtsObject.get_drop_pos(obj, container, opts)\r\n opts = opts or EMPTY_TABLE\r\n\r\n local protrusion = opts.protrusion or 1\r\n local offset = opts.offset\r\n\r\n local pos = TtsObject.get_top_pos(container)\r\n local bounds = TtsObject.getBounds(obj)\r\n pos.y = pos.y + bounds.offset.y + bounds.size.y * ( protrusion - 0.5 )\r\n\r\n if offset then\r\n pos = pos + Vector(offset)\r\n end\r\n\r\n return pos\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.setTransform\r\n--\r\n-- Wraps setPosition, setRotation and setScale,\r\n-- allowing any combination to be performed at once.\r\n\r\nfunction TtsObject.setTransform(obj, args)\r\n args = args or EMPTY_TABLE\r\n\r\n local result = true\r\n if args.position then result = obj.setPosition( args.position ) and result end\r\n if args.rotation then result = obj.setRotation( args.rotation ) and result end\r\n if args.scale then result = obj.setScale( args.scale ) and result end\r\n return result\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.setTransformSmooth\r\n--\r\n-- Wraps setPositionSmooth and setRotationSmooth,\r\n-- allowing any combination to be performed at once.\r\n\r\nfunction TtsObject.setTransformSmooth(obj, args)\r\n args = args or EMPTY_TABLE\r\n\r\n local result = true\r\n if args.position then result = obj.setPositionSmooth( args.position, args.collide, args.fast ) and result end\r\n if args.rotation then result = obj.setRotationSmooth( args.rotation, args.collide, args.fast ) and result end\r\n return result\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.moveAtop\r\n--\r\n-- Moves an object so it sits atop another object.\r\n-- Uses instant movement.\r\n--\r\n-- Options:\r\n-- rotation: `false` to keep its current rotation (default),\r\n-- `true` to give the moved object the same rotation as the reference object, or\r\n-- a vector-like to give the object this rotation.\r\n-- protrusion: What portion of the object being moved should be atop the other. (Default = 1)\r\n-- offset: A vector-like used to modify the destination position. (Default = {0,0,0})\r\n\r\nfunction TtsObject.moveAtop(obj, ref_obj, opts)\r\n opts = opts or EMPTY_TABLE\r\n\r\n local result = true\r\n\r\n if opts.rotation then\r\n local rot = opts.rotation == true and ref_obj.getRotation() or opts.rotation\r\n result = obj.setRotation(rot) and result\r\n end\r\n\r\n local pos = TtsObject.get_drop_pos(obj, ref_obj, opts)\r\n result = obj.setPosition(pos) and result\r\n\r\n return result\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.moveAtopSmooth\r\n--\r\n-- Moves an object so it sits above the center point of a zone.\r\n-- Uses smooth movement.\r\n--\r\n-- Options:\r\n-- rotation: `false` to keep its current rotation (default),\r\n-- `true` to give the moved object the same rotation as the reference object, or\r\n-- a vector-like to give the object this rotation.\r\n-- protrusion: What portion of the object being moved should be atop the other. (Default = 1)\r\n-- offset: A vector-like used to modify the destination position. (Default = {0,0,0})\r\n-- collide: As the obj.setPositionSmooth/obj.setRotationSmooth argument.\r\n-- fast: As the obj.setPositionSmooth/obj.setRotationSmooth argument.\r\n\r\nfunction TtsObject.moveAtopSmooth(obj, ref_obj, opts)\r\n opts = opts or EMPTY_TABLE\r\n\r\n local result = true\r\n\r\n if opts.rotation then\r\n local old_rot = obj.getRotation()\r\n local new_rot = opts.rotation == true and ref_obj.getRotation() or opts.rotation\r\n result = obj.setRotation(new_rot) or result\r\n local pos = TtsObject.get_drop_pos(obj, ref_obj, opts)\r\n result = obj.setRotation(old_rot) or result\r\n\r\n result = obj.setPositionSmooth(pos, opts.collide, opts.fast) and result\r\n result = obj.setRotationSmooth(new_rot, opts.collide, opts.fast) and result\r\n else\r\n local pos = TtsObject.get_drop_pos(obj, ref_obj, opts)\r\n result = obj.setPositionSmooth(pos, opts.collide, opts.fast) and result\r\n end\r\n\r\n return result\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.moveToZoneTop\r\n-- Public function TtsObject.moveToZoneTopSmooth\r\n--\r\n-- WARNING: DEPRECATED/OBSLETE.\r\n\r\nTtsObject.moveToZoneTop = TtsObject.moveAtop\r\nTtsObject.moveToZoneTopSmooth = TtsObject.moveAtopSmooth\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.is_container\r\n-- Public function TtsObject.is_finite_container\r\n-- Public function TtsObject.is_infinite_container\r\n\r\nfunction TtsObject.is_container(obj)\r\n local name = obj.name\r\n return IS_FINITE_CONTAINER_LOOKUP[name] or IS_INFINITE_CONTAINER_LOOKUP[name]\r\nend\r\n\r\nfunction TtsObject.is_finite_container(obj)\r\n return IS_FINITE_CONTAINER_LOOKUP[ obj.name ]\r\nend\r\n\r\nfunction TtsObject.is_infinite_container(obj)\r\n return IS_INFINITE_CONTAINER_LOOKUP[ obj.name ]\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.is_stackable\r\n\r\nfunction TtsObject.is_stackable(obj)\r\n local custom_obj = obj.getCustomObject()\r\n return custom_obj and custom_obj.stackable or false\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.is_stack\r\n--\r\n-- We can't just use `obj.getQuantity()` because it\r\n-- can return a positive value for containers.\r\n\r\nfunction TtsObject.is_stack(obj)\r\n return TtsObject.is_stackable(obj) and obj.getQuantity() > 1\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public function TtsObject.is_zone\r\n\r\nfunction TtsObject.is_zone(obj)\r\n return IS_ZONE_LOOKUP[ obj.name ]\r\nend\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn TtsObject\r\n\nend)\n__bundle_register(\"lib.promise.TtsBase\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TtsBase = require(\"kintastic.lib.Promise.TtsBase\")\r\n\r\nreturn TtsBase\r\n\nend)\n__bundle_register(\"kintastic.lib.Promise.TtsBase\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Promise = require(\"kintastic/lib/Promise\")\r\nlocal TableUtils = require(\"kintastic/lib/TableUtils\")\r\n\r\n--- Promise-based version of base methods.\r\nlocal TtsBase = { }\r\n\r\n---@class __TtsBase_this\r\nlocal this = {}\r\n\r\n---@shape Promise_Spawn_Parameter\r\n---@field position nil | tts__VectorShape\r\n---@field rotation nil | tts__VectorShape\r\n---@field scale nil | tts__VectorShape\r\n---@field callback_function nil | fun(spawned: tts__Object): tts__Object\r\n\r\n---@shape Promise_Spawn_Parameter_Data : Promise_Spawn_Parameter\r\n---@field data tts__ObjectState\r\n\r\n--- This is a thin wrapper around `spawnObject` that returns a promise that becomes fulfilled with the spawned object once the object has been created.\r\n---\r\n--- If an `immediate_callback` argument is provided, it will be called as soon as `spawnObject` returns, with the object as a parameter.\r\n--- It's return value is ignored.\r\n---\r\n--- If a `callback_function` argument is provided, it will be called once the object is spawned, and the promise returned by `TtsBase.spawnObject` will adopt the value returned by the callback function.\r\n---\r\n---@return gloom_Promise\r\nfunction TtsBase.spawnObject(args)\r\n return this.spawner(spawnObject, args)\r\nend\r\n\r\n--- This is a thin wrapper around `spawnObjectData` that returns a promise that becomes fulfilled with the spawned object once the object has been created.\r\n---\r\n--- If an `immediate_callback` argument is provided, it will be called as soon as `spawnObjectData` returns, with the object as a parameter.\r\n--- It's return value is ignored.\r\n---\r\n--- If a `callback_function` argument is provided, it will be called once the object is spawned, and the promise returned by `TtsBase.spawnObjectData` will adopt the value returned by the callback function.\r\n---\r\n---@generic T\r\n---@return gloom_Promise\r\n---@param args Promise_Spawn_Parameter_Data\r\nfunction TtsBase.spawnObjectData(args)\r\n return this.spawner(spawnObjectData, args)\r\nend\r\n\r\n--- This is a thin wrapper around `spawnObjectJSON` that returns a promise that becomes fulfilled with the spawned object once the object has been created.\r\n---\r\n--- If an `immediate_callback` argument is provided, it will be called as soon as `spawnObjectJSON` returns, with the object as a parameter.\r\n--- It's return value is ignored.\r\n---\r\n--- If a `callback_function` argument is provided, it will be called once the object is spawned, and the promise returned by `TtsBase.spawnObjectJSON` will adopt the value returned by the callback function.\r\n---\r\n---@return gloom_Promise\r\nfunction TtsBase.spawnObjectJSON(args)\r\n return this.spawner(spawnObjectJSON, args)\r\nend\r\n\r\n---@return gloom_Promise\r\nfunction this.spawner(wrapped_function, args)\r\n local spawned_obj\r\n local promise = Promise:new(\r\n function(resolve, reject)\r\n args = args and TableUtils.copy(args) or { }\r\n\r\n if args.callback_function then\r\n local orig_cb = args.callback_function\r\n args.callback_function = function(obj)\r\n resolve(orig_cb(obj))\r\n end\r\n else\r\n args.callback_function = resolve\r\n end\r\n\r\n spawned_obj = wrapped_function(args)\r\n end\r\n )\r\n\r\n if args.immediate_callback then\r\n local success, rv = pcall(args.immediate_callback, spawned_obj)\r\n if not success then\r\n broadcastToAll(rv, \"Red\")\r\n end\r\n end\r\n\r\n return promise\r\nend\r\n\r\nreturn TtsBase\r\n\nend)\n__bundle_register(\"lib.Promise\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Promise = require(\"kintastic.lib.Promise\")\r\n\r\nreturn Promise\r\n\nend)\n__bundle_register(\"kintastic.lib.Promise\", function(require, _LOADED, __bundle_register, __bundle_modules)\n-- ================================================================================\r\n-- Promise class\r\n-- The PromiseClass module provides the actual class, while this\r\n-- module provides ways of constructing instances of that class.\r\n-- This allows us to have constructors and instances methods with the same name.\r\n-- ================================================================================\r\n\r\n-- Modules.\r\nlocal Errors = require(\"kintastic/lib/Errors\")\r\nlocal Introspection = require(\"kintastic/lib/Introspection\")\r\nlocal Iters = require(\"kintastic/lib/Iters\")\r\nlocal PromiseClass = require(\"kintastic/lib/PromiseClass\")\r\n\r\n-- Imports.\r\nlocal is_callable = Introspection.is_callable\r\nlocal is_instance_of = Introspection.is_instance_of\r\nlocal ivalues = Iters.ivalues\r\n\r\n-- The class.\r\n-- Contains only constuctors.\r\n-- Never instantiated.\r\nlocal Promise = { }\r\n\r\n\r\n-- ================================================================================\r\n-- Public constructors\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:new\r\n\r\nfunction Promise.new(class, executor)\r\n return PromiseClass:new(executor)\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:try\r\n--\r\n-- Usage:\r\n-- local promise = Promise:try(\r\n-- function()\r\n-- ...\r\n-- error(reason)\r\n-- ...\r\n-- return x\r\n-- end\r\n-- )\r\n--\r\n-- The above example is equivalent to\r\n--\r\n-- local promise = Promise:new(\r\n-- function(resolve, reject)\r\n-- ...\r\n-- reject(reason)\r\n-- ...\r\n-- resolve(x)\r\n-- end\r\n-- )\r\n--\r\n-- This can be used in lieu of a Promise:new when\r\n-- either resolve() or reject() is always called\r\n-- before the callback returns.\r\n\r\nfunction Promise.try(class, func)\r\n return class:new(\r\n function(resolve, reject)\r\n resolve(func())\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:timeout\r\n--\r\n-- Create a promise that becomes rejected after a specified amount of time.\r\n\r\nfunction Promise.timeout(class, seconds)\r\n return class:new(\r\n function(resolve, reject)\r\n Wait.time(function() reject(Errors.ETIMEOUT) end, seconds)\r\n end\r\n )\r\nend\r\n\r\n\r\n--- Creates a promise that's fulfilled with the provided value.\r\n--- You normally want to use Promise:resolve() instead.\r\n---@generic V\r\n---@param value V\r\n---@return gloom_Promise\r\nfunction Promise.fulfill(value)\r\n local promise = Promise:new()\r\n promise:_transition(false, Promise.States.FULFILLED, value)\r\n return --[[---@type gloom_Promise]] promise\r\nend\r\n\r\n\r\n--- Creates a promise that's rejected.\r\n---@generic V\r\n---@param reason any\r\n---@return gloom_Promise\r\nfunction Promise.reject(reason)\r\n local promise = Promise:new()\r\n promise:_transition(false, Promise.States.REJECTED, reason)\r\n return --[[---@type gloom_Promise]] promise\r\nend\r\n\r\n\r\n--- Used to create a fulfilled promise, or to ensure that something is a Promise.\r\n---\r\n--- If `x` is a Promise, it's simply returned.\r\n--- Otherwise, a promised already fulfilled with the value of `x` is returned.\r\n---\r\n--- Usage:\r\n--- local promise = Promise.resolve(x)\r\n---\r\n---@generic V\r\n---@param x (fun(): V) | V | gloom_Promise\r\n---@return gloom_Promise\r\nfunction Promise.resolve(x)\r\n local success, rv = pcall(\r\n function()\r\n -- Return a prebuilt promise if one exists.\r\n do\r\n local promise = Promise.promises[ x == nil and Promise.NIL or x ]\r\n if promise then\r\n return promise\r\n end\r\n end\r\n\r\n if is_instance_of(x, PromiseClass) then\r\n return x\r\n end\r\n\r\n return Promise.fulfill(x)\r\n end\r\n )\r\n\r\n if success then\r\n return rv\r\n else\r\n return Promise.reject(rv)\r\n end\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:all_settled\r\n--\r\n-- Usage:\r\n-- local promise = Promise:all_settled({ promise1, promise2, ... })\r\n--\r\n-- Returns a promise.\r\n-- The promise becomes fulfilled once all of the provided promises have become fulfilled or rejected.\r\n-- The fulfillment value is an array of information the fulfillment/rejection state of each promise.\r\n-- Passing an empty array returns a fulfilled promise.\r\n\r\nfunction Promise.all_settled(class, array)\r\n return class:new(\r\n function(resolve, reject)\r\n local results = { }\r\n local num_pending = 0\r\n for i, promise in ipairs(array) do\r\n promise = class:resolve(promise)\r\n num_pending = num_pending + 1\r\n promise:next(\r\n function(value)\r\n results[i] = { status = \"fulfilled\", value = value }\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n resolve(results)\r\n end\r\n end,\r\n function(reason)\r\n results[i] = { status = \"rejected\", reason = reason }\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n resolve(results)\r\n end\r\n end\r\n )\r\n end\r\n\r\n if num_pending == 0 then\r\n resolve(results)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n--- The promise becomes fulfilled once all of the provided promises have become fulfilled.\r\n--- The fulfillment value is an array of the fulfillment values of the fulfilled promises.\r\n--- The promise becomes rejected once any of the provided promises has become rejected.\r\n--- The rejection reason is the rejection reason of the rejected promise.\r\n--- Passing an empty array returns a promise fulfilled with an empty array.\r\n---\r\n--- Usage:\r\n--- local promise = Promise:all({ promise1, promise2, ... })\r\n---@param array table\r\n---@return gloom_Promise\r\nfunction Promise.all(array)\r\n return PromiseClass:new(\r\n function(resolve, reject)\r\n local values = { }\r\n local num_pending = 0\r\n for i, promise in ipairs(array) do\r\n promise = Promise.resolve(promise)\r\n num_pending = num_pending + 1\r\n promise:next(\r\n function(value)\r\n values[i] = value\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n resolve(values)\r\n end\r\n end,\r\n reject\r\n )\r\n end\r\n\r\n if num_pending == 0 then\r\n resolve(values)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:any\r\n--\r\n-- Usage:\r\n-- local promise = Promise:any({ promise1, promise2, ... })\r\n--\r\n-- Returns a promise.\r\n-- The promise becomes fulfilled once any of the provided promises has become fulfilled.\r\n-- The fulfillment value is the fulfillment value of the fulfilled promise.\r\n-- The promise becomes rejected once all of the provided promises have become rejected.\r\n-- The rejection reason an array of the rejection resons of the rejected promises.\r\n-- Passing an empty array returns a promise fulfilled with an empty array.\r\n\r\nfunction Promise.any(class, array)\r\n return class:new(\r\n function(resolve, reject)\r\n local reasons = { }\r\n local num_pending = 0\r\n for i, promise in ipairs(array) do\r\n promise = class:resolve(promise)\r\n num_pending = num_pending + 1\r\n promise:next(\r\n resolve,\r\n function(reason)\r\n reasons[i] = reason\r\n num_pending = num_pending - 1\r\n if num_pending == 0 then\r\n reject(reasons)\r\n end\r\n end\r\n )\r\n end\r\n\r\n if num_pending == 0 then\r\n reject(reasons)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ----------------------------------------\r\n-- Public constructor Promise:race\r\n--\r\n-- Usage:\r\n-- local promise = Promise:race({ promise1, promise2, ... })\r\n--\r\n-- Returns a promise.\r\n-- The promise becomes fulfilled once any of the provided promises has become fulfilled.\r\n-- The fulfillment value is the fulfillment value of the fulfilled promise.\r\n-- The promise becomes rejected once any of the provided promises has become rejected.\r\n-- The rejection reason is the rejection reason of the rejected promise.\r\n-- Passing an empty array returns a fulfilled promise.\r\n\r\nfunction Promise.race(class, array)\r\n if #array <= 1 then\r\n return Promise:resolve(array[1])\r\n end\r\n\r\n return class:new(\r\n function(resolve, reject)\r\n for i, promise in ipairs(array) do\r\n promise = class:resolve(promise)\r\n promise:next(resolve, reject)\r\n end\r\n end\r\n )\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Public functions\r\n\r\n-- ----------------------------------------\r\n-- Public function Promise.all_settled_error_logger\r\n--\r\n-- Usage:\r\n-- Promise:all_settled(promises)\r\n-- :next(Promise.all_settled_error_logger)\r\n--\r\n-- Note that this isn't a method!\r\n\r\nfunction Promise.all_settled_error_logger(value)\r\n local values = { }\r\n for result in ivalues(value) do\r\n if result.status == \"rejected\" then\r\n -- XXX Should handle weird reasons better. Especially since this is in a loop.\r\n broadcastToAll(result.reason, Color.Red)\r\n else\r\n table.insert(values, result.value)\r\n end\r\n end\r\n\r\n return values\r\nend\r\n\r\n\r\n-- ================================================================================\r\n-- Semi-public\r\n-- You probably shouldn't be using these directly.\r\n\r\n-- ----------------------------------------\r\n-- Semi-public state constants.\r\n\r\nPromise.States = PromiseClass.States\r\n\r\n\r\n-- ----------------------------------------\r\n-- Semi-public pregenerated promises.\r\n\r\n-- This is used as the key when looking for a prebuilt promise for `nil`.\r\nPromise.NIL = { }\r\n\r\nPromise.promises = {\r\n [ Promise.NIL ] = Promise:fulfill(nil),\r\n [ true ] = Promise:fulfill(true),\r\n [ false ] = Promise:fulfill(false),\r\n [ 0 ] = Promise:fulfill(0),\r\n [ \"\" ] = Promise:fulfill(\"\"),\r\n}\r\n\r\n\r\n-- ================================================================================\r\n\r\nreturn Promise\r\n\nend)\n__bundle_register(\"lib.Constant\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Constant = require(\"sebaestschjin-tts.Constant\")\r\n\r\nreturn Constant\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.Constant\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Constant = {}\r\n\r\nConstant.Rotation = {}\r\nConstant.Rotation.NORTH = Vector(0, 180, 0)\r\nConstant.Rotation.NORTH_FLIPPED = Vector(0, 180, 180)\r\nConstant.Rotation.EAST = Vector(0, 270, 0)\r\nConstant.Rotation.EAST_FLIPPED = Vector(0, 270, 180)\r\nConstant.Rotation.SOUTH = Vector(0, 0, 0)\r\nConstant.Rotation.SOUTH_FLIPPED = Vector(0, 0, 180)\r\nConstant.Rotation.WEST = Vector(0, 90, 0)\r\nConstant.Rotation.WEST_FLIPPED = Vector(0, 90, 180)\r\n\r\nConstant.Direction = {}\r\nConstant.Direction.UP = {0, 1, 0}\r\nConstant.Direction.DOWN = {0, -1, 0}\r\n\r\nConstant.Cast = {}\r\nConstant.Cast.RAY = 1\r\nConstant.Cast.SPHERE = 2\r\nConstant.Cast.BOX = 3\r\n\r\nreturn Constant\r\n\nend)\n__bundle_register(\"lib.Json\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Json = require(\"ge_tts.Json\")\r\n\r\nreturn Json\r\n\nend)\n__bundle_register(\"campaign-manager.Event\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Search = require(\"lib.Search\")\nlocal TtsObject = require(\"lib.promise.TtsObject\")\nlocal WrappedDeck = require(\"lib.WrappedDeck\")\n\nlocal Component = require(\"campaign-manager.Component\")\nlocal EventType = require(\"campaign-manager.EventType\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\n\nlocal Event = {}\n\nlocal Api = --[[---@type CampaignApi]] ApiProvider(\"campaign\")\nlocal this = {}\n\n---@param savefile gh_Savefile\nfunction Event.setup(savefile)\n EventManager.addHandler(EventType.Loaded.Decks, function() Event.loadAll(savefile.events) end)\nend\n\n---@param eventDecks gh_Save_EventDecks\nfunction Event.loadAll(eventDecks)\n for deckType, _ in pairs(Component.EventDecks) do\n local eventDeck = eventDecks[deckType]\n if eventDeck then\n Event.load(deckType, eventDeck)\n end\n end\nend\n\n---@param deckType string\n---@param eventDeck gh_Save_EventDeck\nfunction Event.load(deckType, eventDeck)\n Logger.info(\"Loading event deck %s\", deckType)\n local eventsInfo = Component.EventDecks[deckType]\n local initialEventsDeck = Component.initialEventDeck(eventsInfo)\n local completeEventsDeck = Component.eventDeck(eventsInfo)\n local finalDeck = Component.eventMatDeck(eventsInfo)\n\n local initialEventsAdded = false\n if initialEventsDeck then\n local initialDeckData = --[[---@type tts__DeckCustomState]] (--[[---@not nil]] initialEventsDeck).getData().ContainedObjects[1]\n local completeDeckData = completeEventsDeck.getData()\n\n for _, contained in ipairs(initialDeckData.ContainedObjects) do\n table.insert(completeDeckData.ContainedObjects, contained)\n end\n\n completeEventsDeck.destruct()\n spawnObjectData({\n data = completeDeckData,\n callback_function = function(obj)\n completeEventsDeck = --[[---@type tts__Bag]] obj\n initialEventsAdded = true\n end\n })\n\n local newInitialDeck = (--[[---@not nil]] initialEventsDeck).getData()\n newInitialDeck.ContainedObjects = {}\n (--[[---@not nil]] initialEventsDeck).destruct()\n spawnObjectData({ data = newInitialDeck })\n else\n initialEventsAdded = true\n end\n\n Wait.condition(function()\n local removedCards = Event.findEvents(eventDeck.remove, completeEventsDeck)\n local removedEventDeck = WrappedDeck(Vector(eventsInfo.removedDeckAt))\n removedEventDeck.setName(\"Removed \" .. deckType .. \" events\")\n Event.dealEvents(completeEventsDeck, removedCards, removedEventDeck)\n\n local bottomCards = Event.findEvents(eventDeck.bottomUp, completeEventsDeck)\n Event.dealEvents(completeEventsDeck, bottomCards, finalDeck)\n end, function()\n return initialEventsAdded\n end)\nend\n\n---@param events integer[]\n---@param deck tts__Bag\n---@return GUID[]\nfunction Event.findEvents(events, deck)\n local foundEvents = {}\n for _, event in TableUtil.pairs(events) do\n local eventCard = this.findEvent(deck, event)\n if eventCard then\n table.insert(foundEvents, eventCard)\n else\n Logger.warn(\"No event card %s found for deck %s\", event, deck.getName())\n end\n end\n\n return foundEvents\nend\n\n---@param source tts__Bag\n---@param eventCards GUID[]\n---@param target seb_WrappedDeck\nfunction Event.dealEvents(source, eventCards, target)\n for _, event in pairs(eventCards) do\n this.dealEvent(source, target, event)\n end\nend\n\n---@param savefile gh_Savefile\nfunction Event.saveAll(savefile)\n local events = savefile.events\n\n for name, info in pairs(Component.EventDecks) do\n local eventDeck = Event.saveEventsFromDeck(info)\n if eventDeck then\n events[name] = eventDeck\n end\n end\nend\n\n---@param info gh_EventDeckInfo\n---@return gh_Save_EventDeck\nfunction Event.saveEventsFromDeck(info)\n local activeEventsDeck = Component.eventMatDeck(info)\n local availableEventsDeck = Component.eventDeck(info)\n local savedDeckInfo = --[[---@type gh_Save_EventDeck]] { bottomUp = {}, remove = {} }\n\n ---@param eventNumber number\n ---@param eventDeck tts__Bag\n local function findEvent(eventNumber, eventDeck)\n local eventName = string.format(\"%02d\", eventNumber)\n return Search.inContainedObjects(eventDeck, { name = eventName })\n end\n\n ---@param eventNumber number\n ---@return boolean\n local function isRemoved(eventNumber)\n return not TableUtil.contains(savedDeckInfo.bottomUp, eventNumber)\n and findEvent(eventNumber, availableEventsDeck) == nil\n end\n\n for _, card in pairs(activeEventsDeck.getObjects()) do\n local eventNumber = Event.getNumber(card)\n if eventNumber == nil then\n eventNumber = Object.name(card)\n end\n if not TableUtil.contains(savedDeckInfo.bottomUp, eventNumber) then\n table.insert(savedDeckInfo.bottomUp, eventNumber)\n end\n end\n\n for _, count in pairs(info.counts) do\n for eventNumber = count.start, count.start + count.total - 1 do\n if isRemoved(eventNumber) then\n table.insert(savedDeckInfo.remove, eventNumber)\n end\n end\n end\n\n table.sort(savedDeckInfo.remove)\n\n return savedDeckInfo\nend\n\n---@param card tts__ObjectState\n---@return number\nfunction Event.getNumber(card)\n return --[[---@not nil]] tonumber(Object.name(card))\nend\n\nApi.unlockCityEvent = function(name, putOnTop)\n return this.unlockEvent(\"city\", name, putOnTop)\nend\n\nApi.unlockRoadEvent = function(name, putOnTop)\n return this.unlockEvent(\"road\", name, putOnTop)\nend\n\n---@overload fun(name: integer | string)\n---@param deckType string\n---@param name integer | string\n---@param putOnTop boolean\n---@return boolean\nfunction this.unlockEvent(deckType, name, putOnTop)\n local deckInfo = Component.EventDecks[deckType]\n local availableEvents = Component.eventDeck(deckInfo)\n local activeEvents = Component.eventMatDeck(deckInfo)\n\n local eventCardGuid = this.findEvent(availableEvents, name)\n if not eventCardGuid then\n Logger.warn(\"Tried to unlock %s event %s, but it doesn't exist in the available deck anymore\", deckType, name)\n return false\n end\n\n this.dealEvent(availableEvents, activeEvents, eventCardGuid)\n :next(function() if not putOnTop then activeEvents.shuffle() end end)\n\n return true\nend\n\n---@param deck tts__Bag\n---@param name integer | string\n---@return nil | GUID\nfunction this.findEvent(deck, name)\n ---@type string\n local eventName\n if type(name) == \"number\" then\n eventName = string.format(\"%02d\", name)\n else\n eventName = tostring(name)\n end\n\n for _, contained in ipairs(deck.getObjects()) do\n if eventName == Object.name(contained) then\n return contained.guid\n end\n end\n\n return nil\nend\n\n---@param source tts__Bag\n---@param target seb_WrappedDeck\n---@param eventGuid GUID\nfunction this.dealEvent(source, target, eventGuid)\n return TtsObject.takeObject(source, {\n guid = eventGuid,\n position = Component.safePosition(2),\n rotation = { 0, 180, 0 },\n smooth = false,\n }):next(function(eventCard) return target.putObject(eventCard) end)\nend\n\nreturn Event\n\nend)\n__bundle_register(\"campaign-manager.Achievement\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Achievement = {}\r\n\r\n---@param savefile gh_Savefile\r\nfunction Achievement.setup(savefile)\r\n EventManager.addHandler(EventType.Loaded.Start, function() Achievement.loadAll(savefile.global.achievements) end)\r\nend\r\n\r\n---@param achievements gh_Save_Achievements\r\nfunction Achievement.loadAll(achievements)\r\n ---@type boolean[]\r\n local readyGroups = { true }\r\n\r\n for i = 1, 5 do\r\n table.insert(readyGroups, false)\r\n local achievementGroup = {}\r\n for achievementName, count in pairs(achievements) do\r\n if count >= i then\r\n table.insert(achievementGroup, achievementName)\r\n end\r\n end\r\n\r\n if TableUtil.isNotEmpty(achievementGroup) then\r\n Wait.condition(function() Achievement.loadGroup(i, achievementGroup, readyGroups) end,\r\n function() return readyGroups[i] end)\r\n end\r\n end\r\nend\r\n\r\n---@param group number\r\n---@param achievementGroup string[]\r\n---@param readyGroups boolean[]\r\nfunction Achievement.loadGroup(group, achievementGroup, readyGroups)\r\n local globalAchievementsBag = Component.achievementsBag()\r\n\r\n globalAchievementsBag.takeObject({\r\n guid = Component.achievementBoardGuid(),\r\n callback_function = function(obj)\r\n for _, achievement in pairs(achievementGroup) do\r\n local index = --[[---@not nil]] Achievement.findIndex(obj, achievement)\r\n obj.call('clicked', index)\r\n end\r\n readyGroups[group + 1] = true\r\n end\r\n })\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Achievement.saveAll(savefile)\r\n local achievementBoardGuid = Component.achievementBoardGuid()\r\n local achievementBoard = getObjectFromGUID(achievementBoardGuid)\r\n if achievementBoard ~= nil then\r\n Achievement.doSave(savefile, --[[---@not nil]] achievementBoard)\r\n else\r\n local bag = Component.achievementsBag()\r\n bag.takeObject({\r\n guid = achievementBoardGuid,\r\n position = Component.achievementBoardPosition(),\r\n rotation = { 0, 180, 0 },\r\n callback_function = function(obj)\r\n Achievement.doSave(savefile, obj)\r\n bag.putObject(obj)\r\n end\r\n })\r\n end\r\nend\r\n\r\n---@param savefile gh_Savefile\r\n---@param achievementBoard tts__Object\r\nfunction Achievement.doSave(savefile, achievementBoard)\r\n local achievementInfo = --[[---@type fantasySetup_Achievement_Info[] ]] achievementBoard.getTable(\"flags\")\r\n\r\n local result = --[[---@type gh_Save_Achievements ]] {}\r\n for _, achievement in pairs(achievementInfo) do\r\n local maxIndex = 0\r\n for i, guid in pairs(achievement.guids) do\r\n if getObjectFromGUID(guid) ~= nil then\r\n maxIndex = i\r\n end\r\n end\r\n if maxIndex > 0 then\r\n result[achievement.name] = maxIndex\r\n end\r\n end\r\n (--[[---@not nil]] savefile.global).achievements = result\r\n EventManager.triggerEvent(EventType.Saved.Achievements, savefile)\r\nend\r\n\r\n---@param achievementBoard tts__Object\r\n---@param achievement string\r\n---@return nil | number\r\nfunction Achievement.findIndex(achievementBoard, achievement)\r\n local achievementInfo = --[[---@type fantasySetup_Achievement_Info[] ]] achievementBoard.getTable(\"flags\")\r\n\r\n for i, flag in pairs(achievementInfo) do\r\n if flag.name == achievement then\r\n return i\r\n end\r\n end\r\n return nil\r\nend\r\n\r\nreturn Achievement\r\n\nend)\n__bundle_register(\"campaign-manager.Cleanup\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal Component = require(\"campaign-manager.Component\")\r\nlocal EventType = require(\"campaign-manager.EventType\")\r\n\r\nlocal Cleanup = {}\r\n\r\n--- total number of unlocked class events\r\nlocal unlocked = 0\r\n\r\n---@param savefile gh_Savefile\r\nfunction Cleanup.setup(savefile)\r\n unlocked = 0\r\n EventManager.addHandler(EventType.Loaded.Class.Unlocked, function() Cleanup.onClassUnlocked(savefile) end)\r\n EventManager.addHandler(EventType.Loaded.Treasure, Cleanup.onTreasureLoaded)\r\nend\r\n\r\n\r\n---@param object nil | tts__Object\r\nlocal function placeIntoGamebox(object)\r\n if object then\r\n Component.gamebox().putObject(--[[---@not nil]] object)\r\n end\r\nend\r\n\r\n---@param savefile gh_Savefile\r\nfunction Cleanup.onClassUnlocked(savefile)\r\n unlocked = unlocked + 1\r\n if unlocked == TableUtil.length(savefile.context.classes) then\r\n placeIntoGamebox(Component.lockedClasses())\r\n end\r\nend\r\n\r\nfunction Cleanup.onTreasureLoaded()\r\n placeIntoGamebox(Component.treasureDeck())\r\nend\r\n\r\nreturn Cleanup\r\n\nend)\n__bundle_register(\"campaign-manager.Savefile\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal Notebook = require(\"lib.Notebook\")\nlocal ObjectState = require(\"lib.ObjectData\")\nlocal SaveManager = require(\"lib.SaveManager\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal Game = require(\"campaign-manager.Game\")\n\n-- Aliases for latest version\n---@alias gh_Savefile gh_Savefile_v3\n---@alias gh_Save_Party gh_Save_Party_v3\n---@alias gh_Save_Characters gh_Save_Characters_v3\n\n---@alias gh_Savefile_any gh_Savefile | gh_Savefile_v1 | gh_Savefile_v2 | gh_Savefile_v3\n---@alias gh_Save_Character_any gh_Save_Character | gh_Save_v1_Character\n\nlocal Savefile = {}\n\n---@class __Savefile_this\nlocal this = {}\n\n---@type string\nlocal NotebookName = \"Savefile\"\n---@type string\nlocal TagName = \"Gloomhaven Campaign\"\nlocal IconLocation = \"https://github.com/Sebaestschjin/gloomhaven-campaign-manager/raw/master/docs/save-icon.png\"\n---@type number[]\nlocal Version = { 3, 1 }\n\n---@param type string\n---@param input table\nfunction Savefile.setDefaultValues(type, input)\n if type == \"character\" then\n this.setCharacterDefaults(input)\n else\n Logger.warn(\"Unknown type '%s' to set default values\", type)\n end\nend\n\n---@return string\nlocal function currentDate()\n return --[[---@type string]] os.date(\"%Y-%m-%d'T'%H:%M\")\nend\n\n---@param savefile string\n---@return nil | gh_Savefile_any\nlocal function parseJson(savefile)\n local status, content = pcall(function()\n return JSON.decode(savefile)\n end)\n if not status then\n Logger.error(\"The provided save file contains errors.\"\n .. \" The error message from Lua is also very cryptic and doesn't really help. :-(\"\n .. \" Make sure your save file is a valid JSON file and try again.\"\n .. \"\\n\" .. content)\n return nil\n end\n\n return content\nend\n\n---@return nil | gh_Savefile_any\nlocal function readFromToken()\n local tokens = getObjectsWithTag(TagName)\n if TableUtil.isEmpty(tokens) then\n return nil\n end\n\n if TableUtil.length(tokens) > 1 then\n Logger.warn(\"Found more than 1 save file token! Don't know which one to use. Remove one of them and try again\")\n return nil\n end\n\n return parseJson(tokens[1].script_state)\nend\n\n---@return nil | gh_Savefile_any\nlocal function readFromNotebook()\n local savefile = Notebook.getContent(NotebookName)\n if not savefile or #savefile == 0 then\n return nil\n end\n\n return parseJson(--[[---@not nil]] savefile)\nend\n\n---@param name string\nlocal function setDefaultValue(content, name, default)\n local toCheck = content\n local args = StringUtil.split(name, \".\")\n local total = TableUtil.length(args)\n for i = 1, total - 1 do\n local n = args[i]\n if not toCheck[n] then\n toCheck[n] = {}\n end\n toCheck = toCheck[n]\n end\n\n if toCheck[args[total]] == nil then\n toCheck[args[total]] = default\n end\nend\n\n---@param content gh_Savefile_any\nlocal function setDefaultValues(content)\n setDefaultValue(content, \"enhancements\", {})\n\n setDefaultValue(content, \"campaign\", {})\n\n setDefaultValue(content, \"unlocked.classes\", {})\n setDefaultValue(content, \"unlocked.items\", {})\n setDefaultValue(content, \"unlocked.specialConditions\", {})\n setDefaultValue(content, \"unlocked.treasures\", {})\n -- events?\n setDefaultValue(content, \"global.achievements\", {})\n setDefaultValue(content, \"global.scenarios\", {})\n setDefaultValue(content, \"global.prosperity\", 0)\n\n setDefaultValue(content, \"party.name\", \"\")\n setDefaultValue(content, \"party.location\", \"\")\n setDefaultValue(content, \"party.notes\", {})\n setDefaultValue(content, \"party.achievements\", {})\n setDefaultValue(content, \"party.reputation\", 0)\n setDefaultValue(content, \"party.characters\", {})\n\n for _, character in pairs(--[[---@type gh_Save_Characters ]] content.party.characters) do\n this.setCharacterDefaults(character)\n end\n\n setDefaultValue(content, \"retired\", {})\n\n setDefaultValue(content, \"events\", {})\n\n setDefaultValue(content, \"players\", {})\n\n setDefaultValue(content, \"notes\", {})\n\n setDefaultValue(content, \"options\", {})\nend\n\nfunction this.setCharacterDefaults(character)\n setDefaultValue(character, \"checkmarks\", 0)\n setDefaultValue(character, \"xp\", 0)\n setDefaultValue(character, \"gold\", 0)\n setDefaultValue(character, \"abilities\", {})\n setDefaultValue(character, \"hand\", {})\n setDefaultValue(character, \"items\", {})\n setDefaultValue(character, \"perks\", {})\n setDefaultValue(character, \"masteries\", {})\n setDefaultValue(character, \"notes\", {})\n setDefaultValue(character, \"hiddenNotes\", {})\n setDefaultValue(character, \"enhancements\", {})\nend\n\n---@param content gh_Savefile\nlocal function setDefaultValuesLatest(content)\n ---@param className string\n local function addToUnlocked(className)\n if not TableUtil.contains(content.unlocked.classes, className) then\n local class = Game.findClass(className)\n if not class or not (--[[---@not nil]] class).isStartingClass then\n table.insert(content.unlocked.classes, className)\n end\n end\n end\n\n for _, character in pairs(content.party.characters) do\n addToUnlocked(character.class)\n this.setCharacterDefaults(character)\n end\n\n for className, _ in pairs(content.enhancements) do\n addToUnlocked(className)\n end\nend\n\n---@param savefile gh_Savefile_any\n---@return number, number\nlocal function getVersion(savefile)\n local metadata = savefile.metadata\n if not metadata or not metadata.version then\n return 1, 0\n end\n local version = StringUtil.split(metadata.version, \".\")\n return --[[---@not nil]] tonumber(version[1]), --[[---@not nil]] tonumber(version[2])\nend\n\n---@param content gh_Savefile_v1\n---@return gh_Savefile_v2\nlocal function upgradeToV2(content)\n local upgradedContent = --[[---@type gh_Savefile_v2]] {}\n\n --- enhancements\n local newEnhancements = --[[---@type gh_Save_Enhancements]] {}\n for _, enhancement in pairs(--[[---@type gh_Save_v1_EnhancedClass[] ]]content.enhancements) do\n local enhancedClass = --[[---@type gh_Save_EnhancedClass]] {}\n for _, ability in pairs(enhancement.abilities) do\n local enhancedAbility = --[[---@type gh_Save_Enhanced_Ability]] {}\n for _, abEnhancement in pairs(ability.enhancements) do\n enhancedAbility[tostring(abEnhancement.position)] = abEnhancement.enhancement\n end\n enhancedClass[ability.name] = enhancedAbility\n end\n newEnhancements[enhancement.class] = enhancedClass\n end\n upgradedContent.enhancements = newEnhancements\n\n --- unlocked\n upgradedContent.unlocked = content.unlocked\n\n --- global\n upgradedContent.global = --[[---@type gh_Save_Global]] {}\n upgradedContent.global.prosperity = content.global.prosperity\n\n local newScenarios = --[[---@type gh_Save_Scenarios]] {}\n for _, scenario in ipairs(--[[---@type gh_Save_v1_Scenario[] ]]content.global.scenarios) do\n newScenarios[tostring(scenario.number)] = scenario.state\n end\n upgradedContent.global.scenarios = newScenarios\n\n local newAchievements = --[[---@type gh_Save_Achievements]] {}\n for _, achievement in ipairs(--[[---@type gh_Save_v1_Achievement[] ]]content.global.achievements) do\n newAchievements[achievement.name] = achievement.count\n end\n upgradedContent.global.achievements = newAchievements\n\n --- party\n upgradedContent.party = --[[---@type gh_Save_Party_v2]] {}\n upgradedContent.party.name = content.party.name\n upgradedContent.party.location = content.party.location\n upgradedContent.party.notes = content.party.notes\n upgradedContent.party.achievements = content.party.achievements\n upgradedContent.party.reputation = content.party.reputation\n\n local newCharacters = --[[---@type gh_Save_Characters_v2 ]] {}\n for _, character in ipairs(--[[---@type gh_Save_v1_Character[] ]]content.party.characters) do\n local newCharacter = --[[---@type gh_Save_Character]] {}\n newCharacter.class = character.class\n newCharacter.name = character.name\n newCharacter.xp = character.xp\n newCharacter.gold = character.gold\n newCharacter.quest = character.quest\n newCharacter.checkmarks = character.checkmarks\n newCharacter.perks = character.perks\n newCharacter.abilities = character.abilities\n newCharacter.notes = character.notes\n newCharacter.hiddenNotes = character.hiddenNotes\n newCharacter.hand = character.hand\n\n local newItems = --[[---@type gh_Save_Character_Items]] {}\n for _, item in ipairs(character.items) do\n local itemPosition = newItems[item.position]\n if not itemPosition then\n itemPosition = {}\n newItems[item.position] = itemPosition\n end\n table.insert(itemPosition, item.name)\n end\n newCharacter.items = newItems\n\n table.insert(newCharacters, newCharacter)\n end\n upgradedContent.party.characters = newCharacters\n\n --- retired\n upgradedContent.retired = content.retired\n\n --- events\n local newEventDecks = --[[---@type gh_Save_EventDecks]] {}\n for _, eventDeck in ipairs(--[[---@type gh_Save_v1_EventDeck[] ]]content.events) do\n newEventDecks[eventDeck.deck] = {\n bottomUp = eventDeck.bottomUp,\n add = eventDeck.add,\n remove = eventDeck.remove,\n }\n end\n upgradedContent.events = newEventDecks\n\n --- metadata\n local date\n if content.metadata and content.metadata.date then\n date = content.metadata.date\n end\n upgradedContent.metadata = {\n version = \"2.1\",\n date = currentDate(),\n }\n\n return upgradedContent\nend\n\n---@param content gh_Savefile_v2\n---@return gh_Savefile_v3\nlocal function upgradeToV3(content)\n local upgradedContent = --[[---@type gh_Savefile_v3]] {}\n\n upgradedContent.enhancements = content.enhancements\n upgradedContent.events = content.events\n upgradedContent.global = content.global\n upgradedContent.notes = content.notes\n upgradedContent.options = content.options\n upgradedContent.players = content.players\n upgradedContent.retired = content.retired\n upgradedContent.unlocked = content.unlocked\n\n upgradedContent.party = --[[---@type gh_Save_Party_v3 ]] {}\n upgradedContent.party.achievements = content.party.achievements\n upgradedContent.party.location = content.party.location\n upgradedContent.party.name = content.party.name\n upgradedContent.party.notes = content.party.notes\n upgradedContent.party.reputation = content.party.reputation\n upgradedContent.party.characters = --[[---@type gh_Save_Characters_v3]] {}\n for i, character in ipairs(content.party.characters) do\n upgradedContent.party.characters[tostring(i)] = character\n end\n\n upgradedContent.metadata = {\n version = \"3.1\",\n date = currentDate(),\n }\n\n return upgradedContent\nend\n\n--- Undo the dumb version bump in one of the Beta version\n---@return gh_Savefile_v3\nlocal function downgradeFromV4(content)\n local newCharacters = {}\n for id, character in pairs(content.party.characters) do\n local newCharacter = character.character\n\n newCharacter.enhancements = character.enhancements\n newCharacters[id] = newCharacter\n end\n\n content.party.characters = newCharacters\n content.metadata.version = \"3.1\"\n\n return content\nend\n\n---@param savefile gh_Savefile_any\n---@return gh_Savefile\nlocal function upgrade(savefile)\n local major, minor = getVersion(savefile)\n if major == 1 then\n savefile = upgradeToV2(--[[---@type gh_Savefile_v1]] savefile)\n return upgrade(savefile)\n elseif major == 2 then\n savefile = upgradeToV3(--[[---@type gh_Savefile_v2]] savefile)\n return upgrade(savefile)\n elseif major == 4 and minor == 0 then\n savefile = downgradeFromV4(savefile)\n return upgrade(savefile)\n end\n\n return --[[---@type gh_Savefile]] savefile\nend\n\n--- Removes empty tables from the save file reducing unnecessary noise.\n---@param content table\nlocal function cleanup(content)\n for i, v in pairs(content) do\n if type(v) == \"table\" then\n cleanup(v)\n if TableUtil.isEmpty(v) then\n content[i] = nil\n end\n elseif type(v) == \"string\" then\n if not v or v == \"\" then\n content[i] = nil\n end\n end\n end\nend\n\n---@param savefile gh_Savefile\nlocal function createContext(savefile)\n ---@type set\n local classes = {}\n\n for _, unlockedClass in ipairs(savefile.unlocked.classes) do\n classes[unlockedClass] = true\n end\n for enhancedClass, _ in pairs(savefile.enhancements) do\n classes[enhancedClass] = true\n end\n for _, character in pairs(savefile.party.characters) do\n classes[character.class] = true\n end\n\n savefile.context = {\n classes = TableUtil.setToList(classes),\n }\nend\n\n---@param campaign nil | gh_Savefile_any\n---@return nil | gh_Savefile\nfunction Savefile.load(campaign)\n local savefile = campaign\n if not savefile then\n savefile = readFromToken()\n end\n if not savefile then\n savefile = readFromNotebook()\n end\n if not savefile then\n Logger.error(\"Can not find a save file to load. Either add a notebook named %s containing the save file or \" ..\n \"import a token containing you save file.\", NotebookName)\n return nil\n end\n\n setDefaultValues(--[[---@not nil]] savefile)\n local upgraded = upgrade(--[[---@not nil]] savefile)\n setDefaultValuesLatest(upgraded)\n createContext(upgraded)\n\n return upgraded\nend\n\n--- Creates an empty save file with tables already existing.\n---@return gh_Savefile\nfunction Savefile.create()\n return {\n campaign = {},\n enhancements = {},\n events = {},\n global = {\n achievements = {},\n prosperity = 0,\n scenarios = {},\n },\n party = {\n name = \"\",\n location = \"\",\n achievements = {},\n characters = {},\n notes = {},\n reputation = 0,\n },\n retired = {},\n unlocked = {\n classes = {},\n treasures = {},\n items = {},\n specialConditions = {},\n },\n players = {},\n options = {},\n notes = {},\n metadata = {\n date = currentDate(),\n version = table.concat(Version, \".\"),\n },\n context = {\n classes = {},\n },\n }\n\nend\n\n---@param obj tts__Object\nfunction showSaveContent(obj)\n if obj.getName() then\n Notebook.setContent(\"Savefile - \" .. obj.getName(), obj.script_state)\n else\n Notebook.setContent(\"Savefile - \", obj.script_state)\n end\nend\n\n---@param obj tts__Object\nlocal function addButton(obj)\n obj.createButton({\n click_function = \"showSaveContent\",\n function_owner = self,\n label = \"Show\",\n tooltip = \"Show the content of the save file by creating a notebook entry.\",\n position = { 0, 0.15, 0.65 },\n scale = { 1, 1, 1 },\n width = 1200,\n height = 500,\n font_size = 300,\n color = { 0.753, 0.671, 0.565, 1 },\n font_color = { 0.18, 0.047, 0.047, 1 },\n })\nend\n\nlocal function addButtons()\n for _, obj in ipairs(getObjectsWithTag(TagName)) do\n addButton(obj)\n end\nend\n\n---@param savefile gh_Savefile\nfunction Savefile.save(savefile)\n cleanup(savefile)\n local jsonContent = JSON.encode_pretty(savefile)\n jsonContent = jsonContent .. \"\\n\" -- to conform to POSIX :-)\n Notebook.setContent(\"New Savefile\", jsonContent)\n\n local disc = ObjectState.token({\n name = savefile.party.name,\n description = savefile.metadata.date,\n image = IconLocation,\n tags = { TagName },\n state = jsonContent\n })\n\n spawnObjectData({\n data = disc,\n position = { 0, 2, 0 },\n callback_function = function(obj)\n addButton(obj)\n end\n })\n\n printToAll(\"Savefile created!\", \"Green\")\nend\n\n---@param scenarioTree gh_ScenarioTree\nfunction Savefile.saveScenarioTree(scenarioTree)\n local jsonContent = JSON.encode_pretty(scenarioTree)\n Notebook.setContent(\"Scenario Tree\", jsonContent)\nend\n\nSaveManager.registerOnLoad(addButtons)\nEventManager.addHandler(\"onObjectSpawn\",\n ---@param obj tts__Object\n function(obj)\n if obj.hasTag(TagName) then\n addButton(obj)\n end\n end)\n\nreturn Savefile\n\nend)\n__bundle_register(\"Scripts\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal EventManager = require(\"lib.EventManager\")\nlocal Promise = require(\"lib.Promise\")\nlocal TtsObject = require(\"lib.promise.TtsObject\")\nlocal TtsSpawn = require(\"lib.promise.TtsBase\")\nlocal TtsWait = require(\"lib.promise.TtsWait\")\n\nlocal Game = require(\"Game\")\nlocal Party = require(\"Party\")\nlocal R = require(\"Resources\")\nlocal ScenarioApi = require(\"api.ScenarioApi\")\nlocal EnemyApi = require(\"api.EnemyApi\")\nlocal DialCounter = require(\"Component.internal.DialCounter\")\n\nlocal isLeaving = false\n---@type set\nlocal isSpawning = {}\nlocal tableToken = \"7ef9dd\"\nlocal Casts = {\n P1 = { -26.71, 1.88, -44.58 },\n P2 = { -2.71, 1.88, -44.59 },\n P3 = { 21.29, 1.88, -44.58 },\n P4 = { 45.29, 1.88, -44.59 }\n}\n\n---@class __Scripts_this\nlocal this = {}\n\n---@type table\nzoneToColor = {}\nfor _, playerInfo in ipairs(Game.PlayerInfo) do\n zoneToColor[playerInfo.Zone] = playerInfo.Color\nend\n\n---@shape __cleanArea_Params\n---@field [1] tts__Object\n---@field [2] tts__Object\n\n---@param params __cleanArea_Params\nfunction cleanArea(params)\n local pos = params[1].getPosition()\n local selfObject = params[2]\n local className = selfObject.getName()\n local player = this.playerCheck(pos.x)\n selfObject.destruct()\n __unpackCharacterBox({ player = player, character = { class = className } })\nend\n\nfunction this.putBack(params)\n for _, ho in pairs(Player[params[2]].getHandObjects(1)) do\n if ho.tag == \"Deck\" then\n if ho.getDescription():find(\"Player\") then\n ho.setPosition(params[3])\n else\n if params[4] ~= nil then\n params[4][1].setPosition(params[3])\n ho.setPosition(params[4][2])\n end\n end\n elseif ho.tag ~= \"Card\" and ho.getName() ~= \"Bear Companion\" then\n params[1].putObject(ho)\n end\n end\nend\n\nfunction this.playerCheck(x)\n if x < -20 then\n return 1\n end\n if x < 0 then\n return 2\n end\n if x < 20 then\n return 3\n end\n\n return 4\nend\n\nfunction this.exSetState(params)\n params[1].setState(params[2])\nend\n\nlocal function onObjectLeaveContainer(container, leave_object)\n local find = container.getName():find(\"Standee\")\n\n if find ~= nil then\n Wait.condition(function()\n this.leaving(container, leave_object, find)\n end, function()\n return not isLeaving\n end)\n end\nend\n\nfunction this.leaving(container, leave_object, find)\n isLeaving = true\n if TableUtil.contains(Object.tags(leave_object), \"Enemy\") then\n local monsterName = leave_object.getName()\n local max\n for k, v in pairs(getObjectsWithTag(\"MonsterStatSheet\")) do\n if v.getName():find(monsterName) then\n max = v.getVar(\"count\")\n end\n end\n if max == nil then\n broadcastToAll(\"Failed to maximum standee count for \" .. monsterName)\n max = 1\n end\n local numbers = {}\n if max == 0 then\n isLeaving = false\n return\n end\n for i = 1, max do\n table.insert(numbers, i)\n end\n local hitlist = getObjectsWithTag(\"Enemy\")\n local removedNumbers = \"\"\n for _, j in pairs(hitlist) do\n if j.getName():find(monsterName) then\n local str = j.getName():gsub(monsterName, \"\")\n local num = tonumber(str)\n if num ~= nil then\n for x, i in pairs(numbers) do\n if i == num then\n removedNumbers = removedNumbers .. x .. \"(\" .. i .. \")\" .. \"|\"\n table.remove(numbers, x)\n end\n end\n end\n end\n end\n\n local numbs = \"\"\n for x, i in pairs(numbers) do\n numbs = numbs .. x .. \"(\" .. i .. \")\" .. \"|\"\n end\n rand = math.random(1, #numbers)\n if numbers[rand] == nil then\n broadcastToAll(\"All \" .. monsterName .. \" are on the table!\", { 1, 0, 0 })\n isLeaving = false\n isSpawning[monsterName] = false\n leave_object.destruct()\n else\n leave_object.setName(leave_object.getName() .. \" \" .. numbers[rand])\n Wait.condition(function()\n Wait.frames(function()\n leave_object.call(\"updateNumber\")\n end, 1)\n end, function()\n return this.checkLoaded(leave_object)\n end)\n Wait.condition(function()\n Wait.frames(function()\n isLeaving = false\n end, 1)\n end, function()\n return this.checkLoaded(leave_object)\n end)\n end\n else\n isLeaving = false\n end\nend\n\n---@shape gloom_Spawn_Monster\n---@field monsterName string\n---@field monsterPlacement nil | boolean\n---@field monsterRotation nil | tts__VectorShape\n---@field monsterPosition nil | tts__VectorShape\n---@field level nil | integer\n---@field levelModifier nil | gloom_Formula\n---@field named nil | string\n---@field hp nil | gloom_Formula\n---@field hpMax nil | gloom_Formula\n---@field abilityDeck nil | string\n---@field monsterDifficulty nil | gloom_Enemy_Difficulty\n---@field softLock nil | boolean\n---@field summoner nil | GUID\n---@field stats nil | gloom_Stats_Set\n---@field team nil | string\n---@field base nil | string\n---@field tags nil | string[]\n\n---@shape gloom_Spawn_Monster_Components\n---@field statSheet tts__Object\n---@field figureBag tts__Object\n\n---@param params gloom_Spawn_Monster\nfunction spawnMonster(params)\n Wait.condition(function()\n this.startSpawn(params)\n end, function()\n return not isSpawning[params.monsterName]\n end)\nend\n\n---@param params gloom_Spawn_Monster\nfunction this.startSpawn(params)\n Logger.debug(\"Start spawning %s\", params.monsterName)\n isSpawning[params.monsterName] = true\n\n params.level = this.calculateMonsterLevel(params)\n\n this.getMonsterComponents(params)\n :next(function(components)\n return Promise.all({\n this.setStatSheetLevel(components, params),\n this.placeMonsterFigure(components, params),\n })\n end)\n :catch(function(reason)\n Logger.error(reason)\n Logger.debug(params)\n end)\n :finally(function()\n isSpawning[params.monsterName] = false\n Logger.debug(\"Spawning monster '%s' done\", params.monsterName)\n end)\nend\n\nfunction this.checkLoaded(obj)\n return not obj.loading_custom and not obj.spawning\nend\n\nfunction this.getMonsterPos(figureBagPos)\n local x = -1\n -- 1.71\n local z = -12.12\n if figureBagPos.x > -46 and figureBagPos.x < -45 or figureBagPos.x > 20 and figureBagPos.x < 21 then\n x = 1\n end\n if figureBagPos.z < -20 then\n z = -28.80\n end\n\n for i = 0, 9 do\n local pos = { figureBagPos.x + x * 1.75 * i, 1.71, z }\n local found = false\n local hitList = Physics.cast({\n origin = pos, direction = { 0, 1, 0 }, type = 2,\n size = { 1, 1, 1 }, max_distance = 0, debug = false\n })\n for _, entry in ipairs(hitList) do\n if entry.hit_object.guid ~= tableToken and entry.hit_object.guid ~= \"437cd7\" and entry.hit_object.guid ~= \"dff000\" and entry.hit_object.guid ~= \"42d2b6\" and entry.hit_object.getGUID() ~= \"577a97\" then\n found = true\n end\n end\n if not found then\n return pos\n end\n end\nend\n\nfunction changeHP(params)\n local className = params[1]\n local character = Party.getCharacterInfoByClass(className)\n if character and character.objects then\n local hpDial = (--[[---@not nil]] character.objects).hpDial()\n DialCounter.setValue(hpDial, params[2])\n end\nend\n\nfunction getBossHealth()\n Logger.warn(\"Using old function to calculate boss health. Please update the script of the boss mini. This function will always return 1\")\n return 1\nend\n\nfunction this.findMonster(monsterName)\n local hitlist = Physics.cast({\n origin = { 0, 1, 0 },\n direction = { 0, 1, 0 },\n type = 3,\n size = { 100, 2, 50 },\n max_distance = 0,\n debug = false\n })\n local nameList = {\n [monsterName .. \" Standee\"] = true,\n }\n for _, j in pairs(hitlist) do\n if nameList[j.hit_object.getName()] then\n return j.hit_object\n end\n end\n return false\nend\n\n---@return nil | tts__NumVectorShape\nfunction this.findFreeSpot()\n local start = { x = 26.25, y = 1.70, z = -16.67 }\n local z = { 0, 0, 1, 1, 0, 0, 1, 1 }\n ---@type tts__NumVectorShape\n local pos = {}\n for i = 1, 8 do\n pos = { (start.x + 14 * (i > 4 and 1 or 0)) * math.pow(-1, i - 1), start.y, (start.z - 7.58 * z[i]) }\n local hitlist = Physics.cast({\n origin = pos,\n direction = { 0, 1, 0 },\n type = 3,\n size = { 13, 1, 6.8 },\n max_distance = 0,\n debug = false\n })\n local foundMonster = false\n for _, j in pairs(hitlist) do\n if j.hit_object.hasTag(R.Tag.Monster.Mat) then\n foundMonster = true\n end\n end\n if not foundMonster then\n return pos\n end\n end\n\n return nil\nend\n\n---@param params gloom_Spawn_Monster\nfunction this.calculateMonsterLevel(params)\n local monsterLevel = params.level or ScenarioApi.getScenarioLevelSettings().monsterLevel\n if params.levelModifier ~= nil then\n monsterLevel = monsterLevel + ScenarioApi.calculateFormula(--[[---@not nil]] params.levelModifier)\n end\n\n if monsterLevel == nil then\n broadcastToAll(\"Don't know what level monster to spawn, so defaulting to level 0\")\n return 0\n end\n\n if monsterLevel > 7 then\n return 7\n end\n\n if monsterLevel < 0 then\n return 0\n end\n\n return monsterLevel\nend\n\n---@param name string\n---@return tts__BagState\nfunction this.getMonsterBag(name)\n local monsterBags = --[[---@type tts__Bag]] R.getInstance(R.Tag.Component.BagOfMonsters)\n\n for _, monsterBag in pairs(monsterBags.getData().ContainedObjects) do\n if Object.name(monsterBag) == name then\n return --[[---@type tts__BagState]] monsterBag\n end\n end\nend\n\n--- Returns the component with the given tag for an monster.\n--- Returns `nil` if it doesn't exist yet.\n---\n---@param monsterName string\n---@param tag string\n---@return nil | tts__Object\nfunction this.findMonsterComponent(monsterName, tag)\n for _, mat in ipairs(getObjectsWithTag(tag)) do\n if mat.memo == monsterName then\n return mat\n end\n end\n\n return nil\nend\n\n---@overload fun(monsterName: string, name: string): (nil | tts__ObjectState)\n---@param monsterName string\n---@param name string\n---@param type nil | tts__ObjectType\n---@return nil | tts__ObjectState\nfunction this.getMonsterBagContent(monsterName, name, type)\n local monsterBag = this.getMonsterBag(monsterName)\n if not monsterBag then\n return nil\n end\n\n for _, content in ipairs(monsterBag.ContainedObjects) do\n if Object.name(content):find(name) and (not type or Object.type(content) == type) then\n return content\n end\n end\n\n return nil\nend\n\n---@param params gloom_Spawn_Monster\n---@return gloom_Promise\nfunction this.getMonsterComponents(params)\n local monsterBag = this.getMonsterBag(params.monsterName)\n if not monsterBag then\n return Promise.reject(\"The Monster, '\" .. tostring(params.monsterName) .. \"' was not found!\")\n end\n\n local existingMat = this.findMonsterComponent(params.monsterName, R.Tag.Monster.Mat)\n if existingMat then\n local statSheet = --[[---@not nil]] this.findMonsterComponent(params.monsterName, R.Tag.Monster.StatSheet)\n local figureBag = --[[---@not nil]] this.findMonsterComponent(params.monsterName, R.Tag.Monster.Bag)\n return Promise.fulfill({ statSheet = statSheet, figureBag = figureBag })\n end\n\n ---@param monsterMat tts__Object\n local function prepareComponents(monsterMat)\n return Promise.all({\n this.prepareStatSheet(monsterMat, params),\n this.prepareFigureBag(monsterMat, params),\n this.prepareAbilityDeck(monsterMat, params)\n })\n end\n\n ---@param result tts__Object[]\n ---@return gloom_Spawn_Monster_Components\n local function mapResult(result)\n broadcastToAll('The Monster, \"' .. params.monsterName .. '\", has been prepared.', { 1, 1, 1 })\n return { statSheet = result[1], figureBag = result[2], }\n end\n\n return this.prepareMonsterMat(params):next(prepareComponents):next(mapResult)\nend\n\n--- Prepare the monster mat for the given monster type and return a Promise with the prepared mat.\n--- If the mat already exists, the promise is fulfilled already.\n--- Otherwise the mat will spawn and the Promise is fulfilled, once the mat finished spawning.\n---\n--- If no monster with the given name was found or all monster slots are occupied, the Promise will be rejected.\n---\n---@param params gloom_Spawn_Monster\n---@return gloom_Promise\nfunction this.prepareMonsterMat(params)\n local position = this.findFreeSpot()\n if not position then\n return Promise.reject(\"All monster slots are occupied!\")\n end\n\n local monsterMats = --[[---@type tts__Bag]] getObjectFromGUID(\"f01091\")\n local monsterMatData = monsterMats.getData().ContainedObjects[1]\n\n ---@param monsterMat tts__Object\n ---@return tts__Object\n local function postSpawn(monsterMat)\n monsterMat.lock()\n monsterMat.memo = params.monsterName\n return monsterMat\n end\n\n return TtsSpawn.spawnObjectData({\n data = monsterMatData,\n position = position,\n rotation = { 0, 180, 0 },\n }) :next(postSpawn)\nend\n\n---@param monsterMat tts__Object\n---@param params gloom_Spawn_Monster\nfunction this.prepareStatSheet(monsterMat, params)\n Logger.verbose(\"Preparing stat sheet for monster %s\", params.monsterName)\n\n local height = 0.1\n if params.level >= 4 then\n height = height + 0.13\n end\n\n local matPosition = monsterMat.getPosition()\n local position = matPosition + Vector(-0.63, height, 0.5)\n local statSheetData = this.getMonsterBagContent(params.monsterName, \"Stat Sheet\")\n if not statSheetData then\n return Promise.reject(\"No stat sheet found for monster \" .. params.monsterName)\n end\n\n ---@param statSheet tts__Object\n ---@return tts__Object\n local function postSpawn(statSheet)\n statSheet.lock()\n statSheet.memo = params.monsterName\n\n local monsterCount = statSheet.getVar(\"count\")\n this.placeStatSleeve(matPosition, monsterCount)\n\n return statSheet\n end\n\n return TtsSpawn.spawnObjectData({\n data = --[[---@not nil]] statSheetData,\n position = position,\n rotation = { 0, 180, 0 },\n }) :next(TtsWait.untilLoaded):next(postSpawn)\nend\n\n---@param position tts__Vector\n---@param monsterCount integer\nfunction this.placeStatSleeve(position, monsterCount)\n local sleeves = --[[---@type tts__Bag]] getObjectFromGUID(\"b26c16\")\n local sleeveData = sleeves.getData().ContainedObjects[1]\n local sleevePosition = position + Vector(-0.63, 0, 0.5)\n sleevePosition.y = 2.03\n local sleeveRotation = 0\n\n if monsterCount > 6 then\n sleeveRotation = 180\n end\n\n spawnObjectData({\n data = sleeveData,\n position = sleevePosition,\n rotation = { 0, 0, sleeveRotation },\n callback_function = function(obj)\n obj.setRotation({ 0, 0, sleeveRotation })\n Wait.frames(function()\n obj.lock()\n end, 10)\n end\n })\nend\n\n--- Prepares the figure bag for the given monster and returns a Promise that will be fulfilled once the figure bag is ready.\n--- If the bag already exists, the Promise is directly fulfilled, otherwise it will spawn a figure bag.\n---\n---@param monsterMat tts__Object @The monster mat where the figure bag will be placed, if not already existing\n---@param params gloom_Spawn_Monster @The monster parameters\n---@return gloom_Promise\nfunction this.prepareFigureBag(monsterMat, params)\n Logger.verbose(\"Preparing figure bag for monster %s\", params.monsterName)\n\n local figureBagData = --[[---@type tts__BagState]] this.getMonsterBagContent(params.monsterName, \"Standee\")\n if not figureBagData then\n return Promise.reject(\"No figure bag found for monster \" .. params.monsterName)\n end\n\n figureBagData.ContainedObjects[1].Memo = params.monsterName\n local position = monsterMat.getPosition() + Vector(-4.83, 0, 0.08)\n\n ---@param bag tts__Object\n ---@return tts__Object\n local function postSpawn(bag)\n bag.lock()\n bag.addTag(R.Tag.Monster.Bag)\n bag.memo = params.monsterName\n return bag\n end\n\n return TtsSpawn.spawnObjectData({\n data = --[[---@not nil]] figureBagData,\n position = position,\n rotation = { 0, 180, 0 },\n }) :next(TtsWait.untilPlaced):next(postSpawn)\nend\n\n---@param monsterMat tts__Object\n---@param params gloom_Spawn_Monster\nfunction this.prepareAbilityDeck(monsterMat, params)\n ---@param name string\n local function findAbilityDeck(name)\n local abilityDecks = --[[---@type tts__Bag]] R.getInstance(R.Tag.Component.BagOfMonsterAbilities)\n for _, deck in ipairs(abilityDecks.getData().ContainedObjects) do\n if deck.Nickname == name then\n return deck\n end\n end\n end\n\n ---@type tts__ObjectState\n local abilityDeckData\n\n -- try to find the one from the parameters\n if params.abilityDeck then\n abilityDeckData = findAbilityDeck(params.abilityDeck)\n if abilityDeckData then\n __setMonsterInfo({ name = params.monsterName, abilityDeck = abilityDeckData.Nickname })\n else\n Logger.warn(\"No ability deck named '%s' found for monster %s. Will use the default one.\", params.abilityDeck, params.monsterName)\n end\n end\n\n -- try to find the one in the global bag\n if not abilityDeckData then\n local enemyData = EnemyApi.getEnemy(params.monsterName)\n if enemyData then\n abilityDeckData = findAbilityDeck(enemyData.abilityDeck)\n end\n if abilityDeckData then\n --TODO hide this behind an API\n __setMonsterInfo({ name = params.monsterName, abilityDeck = nil })\n end\n end\n\n -- use the one inside the monster bag\n if not abilityDeckData then\n abilityDeckData = this.getMonsterBagContent(params.monsterName, \"\", \"Deck\")\n --TODO hide this behind an API\n __setMonsterInfo({ name = params.monsterName, abilityDeck = nil })\n end\n\n -- still nothing found\n if not abilityDeckData then\n return Promise.reject(\"No ability deck found for monster \" .. params.monsterName)\n end\n\n local position = monsterMat.getPosition() + Vector(4.36, 1, 1.86)\n\n ---@param deck tts__Deck\n ---@return tts__Deck\n local function postSpawn(deck)\n deck.setRotation({ 0, 180, 180 })\n deck.shuffle()\n return deck\n end\n\n return TtsSpawn.spawnObjectData({\n data = --[[---@not nil]] abilityDeckData,\n position = position,\n rotation = { 0, 180, 180 },\n }) :next(TtsWait.untilLoaded):next(postSpawn)\nend\n\n---@param components gloom_Spawn_Monster_Components\n---@param params gloom_Spawn_Monster\n---@return gloom_Promise\nfunction this.setStatSheetLevel(components, params)\n components.statSheet.call(\"setLevel\", { level = params.level })\n\n return Promise.resolve()\nend\n\n---@param components gloom_Spawn_Monster_Components\n---@param params gloom_Spawn_Monster\n---@return gloom_Promise\nfunction this.placeMonsterFigure(components, params)\n if params.monsterPlacement == false then\n return Promise.fulfill(nil)\n end\n\n Logger.verbose(\"Placing monster figure '%s'\", params.monsterName)\n\n ---@param figure tts__Object\n ---@return tts__Object\n local function softLock(figure)\n if params.softLock then\n figure.addTag(R.Tag.SoftLock)\n figure.use_gravity = false\n end\n\n return figure\n end\n\n ---@param figure tts__Object\n ---@return tts__Object\n local function postSpawn(figure)\n for _, tag in ipairs(params.tags or {}) do\n figure.addTag(tag)\n end\n\n figure.call(\"setTyp\", {\n params.monsterDifficulty or \"normal\",\n named = params.named,\n hp = params.hp,\n hpMax = params.hpMax,\n stats = params.stats,\n team = params.team,\n base = params.base,\n })\n\n -- TODO move this into setTyp\n if params.summoner then\n figure.UI.setAttribute(\"summonIcon\", \"active\", true)\n end\n\n return figure\n end\n\n local figureBag = components.figureBag\n local position = params.monsterPosition or this.getMonsterPos(figureBag.getPosition())\n local rotation = params.monsterRotation or { 0, 0, 0 }\n\n return TtsObject.takeObject(figureBag, {\n position = position,\n rotation = rotation,\n smooth = false\n }) :next(softLock):next(TtsWait.untilLoaded):next(postSpawn)\nend\n\nEventManager.addHandler(\"onObjectLeaveContainer\", onObjectLeaveContainer)\n\nend)\n__bundle_register(\"AutoSetup\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Object = require(\"lib.Object\")\nlocal SaveManager = require(\"lib.SaveManager\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ComponentFactory = require(\"ComponentFactory\")\nlocal Formula = require(\"Formula\")\nlocal Options = require(\"api.OptionsApi\")\nlocal Party = require(\"Party\")\nlocal R = require(\"Resources\")\nlocal ScenarioApi = require(\"api.ScenarioApi\")\nlocal ScenarioConstants = require(\"Scenarios.ScenarioConstants\")\nlocal Game = require(\"Game\")\n\nlocal String_TreasureIndexDeck = \"Treasure Index Deck - SPOILERS\"\nlocal Unlockable_guid = \"346ed5\"\nlocal treasureDeck_guid = \"bfef03\"\n\n---@alias __gloom_AutoSetup_DoorsToSpawn table\n\n---@shape __gloom_AutoSetup_ScenarioInformation\n---@field index gloom_Scenario_Index\n---@field info gloom_Scenario @The scenario definition\n---@field characterCount integer\n---@field supportedPlayerCounts table\n---@field gridType tts__Grid_Type\n\nlocal Api = --[[---@type ScenarioApi]] ApiProvider(\"scenario\")\n---@class __AutoSetup_this\nlocal this = {}\n\nmath.randomseed(os.time())\n\n--- The list of opened rooms\n---@type set\nlocal openedRooms = {}\n--- The ID of the currently loaded scenario\n---@type nil | gloom_Scenario_Index\nlocal currentScenario = nil\n\n---@shape __gloom_AutoSetup_RandomPoolData\n---@field card nil | string\n---@field available integer[]\n\n--- Available items of random pools inside the scenario\n--- Each entry stores a list of indices if items that can still be drawn from the pool.\n---@type table\nlocal randomPoolData = {}\n\n---@shape __gloom_AutoSetup_RandomPool\n---@field name string\n---@field objects gloom_Spawn_Execution[]\n---@field position nil | tts__VectorShape\n\n--- The list of elements for a given random pool.\n---@type table\nlocal randomPools = {}\n\nlocal function onSave()\n return json.serialize({\n currentScenario = currentScenario,\n openedRooms = openedRooms,\n randomPoolData = randomPoolData,\n })\nend\n\nlocal function onLoad(savedState)\n if savedState ~= nil and savedState ~= \"\" then\n local state = json.parse(savedState)\n\n if state.currentScenario then\n currentScenario = state.currentScenario\n end\n if state.openedRooms then\n openedRooms = state.openedRooms\n end\n if state.randomPoolData then\n randomPoolData = state.randomPoolData\n end\n end\nend\n\n---@param scenario gloom_Scenario\n---@return table\nlocal function getSupportedCharacterCount(scenario)\n ---@type table\n local playerCounts = {}\n for i = 1, Game.PlayerCount do\n playerCounts[i] = false\n end\n\n for _, room in ipairs(scenario.rooms) do\n for _, monster in ipairs(room.monsters or {}) do\n for _, tile in ipairs(monster.tiles) do\n for count, _ in pairs(tile.numCharacters) do\n playerCounts[count] = true\n end\n end\n end\n end\n\n return playerCounts\nend\n\n--- Returns the nearest player count given the current number of players in the party and the list of supported player\n--- counts from the scenario.\n---\n---@param current integer\n---@param supportedPlayerCounts table\n---@return integer\nlocal function findNearestSupported(current, supportedPlayerCounts)\n local minDiff = 10\n local min = current\n\n for playerCount, isSupported in pairs(supportedPlayerCounts) do\n if isSupported then\n local diff = math.abs(current - playerCount)\n if diff < minDiff then\n minDiff = diff\n min = playerCount\n end\n end\n end\n\n return min\nend\n\nApi.getActiveScenario = function()\n if not currentScenario then\n return nil\n end\n\n return this.getScenario()\nend\n\nApi.getCharacterCount = function()\n local scenario = ScenarioApi.getActiveScenario()\n if scenario then\n return (--[[---@not nil]] scenario).characterCount\n end\n\n return Party.getActiveCharacterCount()\nend\n\nApi.revealRooms = function(rooms)\n this.revealRooms(rooms)\nend\n\nApi.revealTreasure = function(treasure)\n return this.revealTreasure(treasure)\nend\n\n--- Returns the information about a scenario.\n---@return __gloom_AutoSetup_ScenarioInformation\nfunction this.getScenario()\n local scenarioIndex = --[[---@not nil]] currentScenario\n local scenarioInfo = --[[---@not nil]] ScenarioApi.getScenario(scenarioIndex)\n local characterCount = Party.getActiveCharacterCount()\n local supportedPlayerCounts = getSupportedCharacterCount(scenarioInfo)\n\n if not supportedPlayerCounts[characterCount] then\n characterCount = findNearestSupported(characterCount, supportedPlayerCounts)\n log(\"Selected \" .. characterCount .. \" for this scenario.\")\n end\n\n return {\n index = scenarioIndex,\n info = scenarioInfo,\n characterCount = --[[---@not nil]] characterCount,\n supportedPlayerCounts = supportedPlayerCounts,\n gridType = scenarioInfo.gridType,\n }\nend\n\n---@param rooms integer[]\nfunction this.revealRooms(rooms)\n local scenario = this.getScenario()\n\n -- required if the game was saved/loaded after creating the scenario\n if TableUtil.isEmpty(randomPools) then\n this.fillRandomPools(scenario)\n end\n\n Logger.debug(\"Revealing rooms: %s\", rooms)\n Logger.debug(\"Already opened rooms: %s\", openedRooms)\n\n ---@type set\n local roomsSpawned = {}\n for _, roomIndex in ipairs(rooms) do\n if not openedRooms[roomIndex] then\n local room = scenario.info.rooms[roomIndex]\n this.placeMapTiles(room)\n this.placeMonsters(room.monsters, scenario)\n this.placeOverlays(scenario, room.overlayTiles)\n this.placeSections(room.scenarioSections)\n this.placeExtraContent(room.extraContent)\n this.placeRandomObjects(room.randomObjects)\n this.placeFigures(scenario, room.figures)\n roomsSpawned[roomIndex] = true\n openedRooms[roomIndex] = true\n end\n end\n\n Logger.debug(\"Rooms that where spawned: %s\", roomsSpawned)\n this.placeDoors(scenario.info.doorTiles, roomsSpawned, false)\nend\n\n---@param treasure string\n---@return boolean\nfunction this.revealTreasure(treasure)\n local gameBox = --[[---@type tts__Bag]] getObjectFromGUID(Unlockable_guid)\n local targetPosition = { x = -22.312502, y = 1.771799, z = 3.788862 }\n local targetRotation = { x = 0, y = 180, z = 0 }\n\n local cardName\n if tonumber(treasure) and tonumber(treasure) < 10 then\n cardName = \"0\" .. treasure\n else\n cardName = treasure\n end\n Logger.debug(\"Looking for treasure card %s\", cardName)\n\n ---@param container tts__Container\n local function findInContainer(container)\n for _, obj in ipairs(container.getObjects()) do\n if obj.name:find(cardName) then\n container.takeObject({\n index = obj.index,\n position = targetPosition,\n rotation = targetRotation,\n smooth = false,\n })\n return true\n end\n end\n return false\n end\n\n --- Searches the bag with treasure chests for cards (e.g. for custom chest like SoX)\n local function findInTreasureChest()\n local treasureChest = --[[---@type tts__Bag]] getObjectFromGUID(R.Guid.Treasures)\n return findInContainer(treasureChest)\n end\n\n ---@return nil | tts__Deck\n local function placeTreasureDeck()\n for _, obj in ipairs(gameBox.getObjects()) do\n if obj.name == String_TreasureIndexDeck then\n local pos = gameBox.getPosition()\n pos.y = pos.y - 10\n return --[[---@type tts__Deck]] gameBox.takeObject({\n guid = obj.guid,\n position = pos,\n smooth = false\n })\n end\n end\n end\n\n ---@return nil | tts__Deck\n local function findTreasureDeckByName()\n for _, obj in pairs(getObjects()) do\n if obj.getName() == String_TreasureIndexDeck then\n return --[[---@type tts__Deck]] obj\n end\n end\n end\n\n ---@return nil | tts__Deck\n local function findTreasureDeck()\n local deck = getObjectFromGUID(treasureDeck_guid)\n if deck then\n if (--[[---@not nil]] deck).getName() ~= String_TreasureIndexDeck then\n return findTreasureDeckByName()\n else\n return --[[---@type tts__Deck]] deck\n end\n else\n return findTreasureDeckByName()\n end\n end\n\n ---@type boolean\n local deckPlaced = true\n local deck = placeTreasureDeck()\n if not deck then\n deck = findTreasureDeck()\n deckPlaced = false\n end\n\n if deck then\n local result = findInContainer(--[[---@not nil]] deck)\n Logger.debug(\"Found in treasure deck %s\", result)\n if deckPlaced then\n gameBox.putObject(--[[---@not nil]] deck)\n end\n if result then\n return true\n end\n end\n\n if findInTreasureChest() then\n Logger.debug(\"Found in treasure chest\")\n return true\n end\n\n broadcastToAll(\"Treasure Deck not found!\", \"Red\")\n return false\nend\n\n--- Returns all rooms that contains starting tiles in the given scenario.\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@return table\nlocal function getStartingRooms(scenario)\n local startingRooms = --[[---@type table]] {}\n for roomIndex, room in ipairs(scenario.info.rooms) do\n if room.startTiles then\n startingRooms[roomIndex] = room\n end\n end\n\n return startingRooms\nend\n\n---@param params gloom_Setup_CreateMap\nfunction setupScenario(params)\n local scenario = this.prepareScenario(params)\n\n this.placeScenario(scenario, params.HiddenRooms)\nend\n\n---@param room gloom_Scenario_Room\nfunction this.placeMapTiles(room)\n for _, mapTile in TableUtil.ipairs(room.mapTiles) do\n ComponentFactory.spawnElement({\n element = { type = R.ElementType.MapTile, name = mapTile.tile, characterCount = mapTile.characterCount },\n placement = { position = mapTile.position, rotation = mapTile.rotation, },\n })\n end\nend\n\n---@param startTable nil | gloom_Scenario_Overlay_Start\nfunction this.placeStartTiles(startTable)\n if startTable then\n local startTiles = --[[---@not nil]] startTable\n for _, tile in TableUtil.ipairs(startTiles.tiles) do\n ComponentFactory.spawnElement({\n element = { type = R.ElementType.Corridor, name = startTiles.name, characterCount = tile.characterCount },\n placement = { position = tile.position, rotation = tile.rotation,\n name = \"Start Area\", tags = { R.Tag.Tile.Start, } },\n action = { start = true, },\n })\n end\n end\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\nfunction this.placeMonsterList(scenario)\n for _, monsterName in TableUtil.pairs(scenario.info.monsterList) do\n local monsterInfo = this.getMonsterDefaults(scenario, monsterName)\n monsterInfo.monsterPlacement = false\n spawnMonster(monsterInfo)\n end\nend\n\n---@param monsters nil | gloom_Scenario_Overlay_Monster[]\n---@param scenario __gloom_AutoSetup_ScenarioInformation\nfunction this.placeMonsters(monsters, scenario)\n local monstersToSpawn = --[[---@type table ]] {}\n local characterCount = scenario.characterCount\n for _, monster in TableUtil.pairs(monsters) do\n for _, monsterTile in ipairs(monster.tiles) do\n if monsterTile.numCharacters[characterCount] ~= nil then\n local monsterDifficulty = this.getMonsterDifficulty(monsterTile, scenario)\n local monsterInfo = this.getMonsterDefaults(scenario, monster.name, monsterDifficulty)\n Logger.debug(monsterInfo)\n\n monsterInfo.monsterPosition = monsterTile.position\n monsterInfo.monsterRotation = monsterTile.rotation\n monsterInfo.levelModifier = monster.levelModifier or monsterInfo.levelModifier\n monsterInfo.named = monsterTile.named or monsterInfo.named\n monsterInfo.hp = monsterTile.hp or monsterInfo.hp\n monsterInfo.hpMax = monsterTile.hpMax or monsterInfo.hpMax\n monsterInfo.stats = monsterTile.stats or monsterInfo.stats\n monsterInfo.team = monsterTile.team or monsterInfo.team\n monsterInfo.base = monsterTile.base or monsterInfo.base\n monsterInfo.tags = monsterTile.tags or monsterInfo.tags\n\n if monstersToSpawn[monster.name] == nil then\n monstersToSpawn[monster.name] = {}\n end\n table.insert(monstersToSpawn[monster.name], monsterInfo)\n end\n end\n end\n\n for _, monster in pairs(monstersToSpawn) do\n for _, individual in pairs(monster) do\n spawnMonster(individual)\n end\n end\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@param overlayTypes nil | gloom_Scenario_Overlay[]\nfunction this.placeOverlays(scenario, overlayTypes)\n for _, overlayType in TableUtil.ipairs(overlayTypes) do\n for _, overlay in ipairs(--[[---@type gloom_Scenario_Overlay__[] ]] overlayType.type) do\n for _, tile in ipairs(--[[---@type gloom_Scenario_TilePosition[] ]] overlay.tiles) do\n local tileSettings = this.getOverlayDefaults(scenario, overlay.name)\n tile = TableUtil.merge(tileSettings, tile)\n\n local name = overlay.name\n local element = {\n type = overlayType.bag, name = name,\n hp = tile.hp, hpMax = tile.hpMax, tokens = tile.tokens,\n characterCount = tile.characterCount,\n }\n local placement = {\n position = tile.position, rotation = tile.rotation,\n tags = tile.tags, name = tile.name, memo = tile.memo, description = tile.description,\n }\n local action = tile.action\n\n if overlayType.bag == nil and name:find(\"Coin\") then\n element.type = R.ElementType.Coin\n elseif overlayType.bag == nil and name:find(\"Scenario Aid\") then\n element.type = R.ElementType.ScenarioAid\n element.name = tile.name\n elseif overlayType.bag == nil and name:find(\"Objective Token\") then\n element.type = R.ElementType.ObjectiveToken\n element.name = tile.name\n elseif overlayType.bag == R.ElementType.Treasure then\n local treasureTile = --[[---@type gloom_Scenario_Overlay_Treasure_TilePosition]] tile\n if not treasureTile.action and treasureTile.params then\n action = { treasure = treasureTile.params.buttonLabel, }\n end\n elseif overlayType.bag == ScenarioConstants.TypeOverlay_Traps then\n local trapOverlay = --[[---@type gloom_Scenario_Overlay_Trap]] overlay\n ---@type integer | 'Auto'\n local damage = tile.damage or 0\n local conditions = {}\n for _, status in TableUtil.ipairs(tile.conditions or tile.statuses or {}) do\n if status == \"Damage\" then\n damage = \"Auto\"\n else\n table.insert(conditions, status)\n end\n end\n\n element.damage = damage\n element.conditions = conditions\n end\n ComponentFactory.spawnElement({ element = element, placement = placement, action = action })\n end\n end\n end\nend\n\n---@param doorTiles nil | gloom_Scenario_Overlay_Door[]\n---@param roomsSpawned table\n---@param placeAlways boolean\nfunction this.placeDoors(doorTiles, roomsSpawned, placeAlways)\n local doorsToSpawn = --[[---@type __gloom_AutoSetup_DoorsToSpawn]] {}\n for _, doorType in TableUtil.pairs(doorTiles) do\n for _, doorTile in pairs(doorType.tiles) do\n local doorThisRoom = false\n for _, adjRoom in pairs(doorTile.rooms) do\n if roomsSpawned[adjRoom] then\n doorThisRoom = true\n end\n end\n if doorThisRoom or placeAlways then\n if doorsToSpawn[doorType.name] == nil then\n doorsToSpawn[doorType.name] = {}\n end\n table.insert(doorsToSpawn[doorType.name], {\n rooms = doorTile.rooms,\n position = doorTile.position,\n rotation = doorTile.rotation,\n customTag = doorTile.customTag,\n hp = doorTile.hp,\n hpMax = doorTile.hpMax,\n tokens = doorTile.tokens,\n characterCount = doorTile.characterCount,\n description = doorTile.description,\n memo = doorTile.memo,\n action = doorTile.action,\n })\n end\n end\n end\n this.placeDoor(doorsToSpawn)\nend\n\n---@param doorsToSpawn __gloom_AutoSetup_DoorsToSpawn\nfunction this.placeDoor(doorsToSpawn)\n for doorName, tiles in pairs(doorsToSpawn) do\n for _, tile in ipairs(tiles) do\n local tags = tile.tags or {}\n if tile.customTag then\n table.insert(tags, --[[---@not nil]] tile.customTag)\n end\n\n local doorAction = { rooms = tile.rooms, confirm = true }\n local action\n if tile.action then\n if tile.action.compound then\n action = tile.action\n table.insert(action.compound, 1, doorAction)\n else\n action = {\n compound = {\n doorAction,\n tile.action,\n }\n }\n end\n else\n action = doorAction\n end\n\n ComponentFactory.spawnElement({\n element = { type = R.ElementType.Door, name = doorName, hp = tile.hp, hpMax = tile.hpMax, tokens = tile.tokens, characterCount = tile.characterCount },\n placement = { position = tile.position, rotation = tile.rotation, tags = tags, name = tile.name, description = tile.description, memo = tile.memo },\n action = action,\n })\n end\n end\nend\n\n---@param extraContent nil | gloom_Scenario_ExtraContent[]\nfunction this.placeExtraContent(extraContent)\n local extraContentBag = --[[---@type tts__Bag]] R.getInstance(R.Tag.Component.BagOfExtraContent)\n for _, extra in ipairs(extraContent or {}) do\n for _, content in ipairs(extraContentBag.getData().ContainedObjects) do\n if content.Nickname == extra.name then\n spawnObjectData({\n data = content,\n position = extra.position,\n })\n end\n end\n end\nend\n\n---@param scenarioBook gloom_Scenario_Book\nlocal function placeScenarioBookPages(scenarioBook)\n local scenarioBookData = this.findBookData(scenarioBook.name, R.Tag.Book.Scenarios)\n if not scenarioBookData then\n return\n end\n\n ---@type tts__CharVectorShape\n local position = { x = -28.9, y = 1.8, z = 3.8 }\n local multiPageOffset = 10.5\n if not scenarioBook.pageCount then\n scenarioBook.pageCount = 1\n end\n\n if scenarioBook.pageCount == 2 then\n position.x = position.x - multiPageOffset\n end\n\n for i = 1, scenarioBook.pageCount do\n scenarioBookData.CustomPDF.PDFPage = scenarioBook.page + i - 2\n spawnObjectData({\n data = scenarioBookData,\n position = position,\n scale = { x = 3, y = 1, z = 3 }\n })\n position.x = position.x + multiPageOffset\n end\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\nfunction this.placeAllScenarioPages(scenario)\n local settings = Options.scenarioBookSettings()\n\n if settings == Options.ScenarioPage.Spawn then\n if scenario.info.scenarioBook then\n placeScenarioBookPages(--[[---@not nil]] scenario.info.scenarioBook)\n else\n this.placeScenarioPages(scenario.info.scenarioPages)\n end\n elseif settings == Options.ScenarioPage.Change then\n if scenario.info.scenarioBook then\n local scenarioBookInfo = --[[---@not nil]] scenario.info.scenarioBook\n local scenarioBooks = getObjectsWithAllTags({ R.Tag.Book.Scenarios, scenarioBookInfo.name })\n\n if #scenarioBooks > 0 then\n for _, scenarioBook in ipairs(scenarioBooks) do\n -- the -1 is used, because the current information assumes the page numbering written within the book\n -- and that the book has a front cover that isn't part of the numbering\n scenarioBook.Book.setPage(scenarioBookInfo.page - 1)\n end\n else\n local otherScenarioBooks = getObjectsWithTag(R.Tag.Book.Scenarios)\n if #otherScenarioBooks > 0 then\n placeScenarioBookPages(scenarioBookInfo)\n end\n end\n else\n this.placeScenarioPages(scenario.info.scenarioPages)\n end\n end\nend\n\n---@param scenarioPages gloom_Scenario_Page[]\nfunction this.placeScenarioPages(scenarioPages)\n if not scenarioPages then\n return\n end\n\n ---@type tts__CharVectorShape\n local position = { x = -28.937500, y = 1.799863, z = 3.788861 }\n local multiPageOffset = 10.5\n if #scenarioPages == 2 then\n position.x = position.x - multiPageOffset\n end\n\n for _, scenarioPage in ipairs(scenarioPages) do\n local scenarioPageToken = {\n type = \"Custom_Token\",\n image = scenarioPage.image,\n position = position,\n rotation = { x = 0, y = 180, z = 0 },\n scale = { x = 3, y = 1, z = 3 },\n sound = false,\n snap_to_grid = true,\n }\n\n spawnObject(scenarioPageToken).setCustomObject(scenarioPageToken)\n position.x = position.x + multiPageOffset\n end\nend\n\n---@param scenarioSections nil | gloom_Scenario_Section[]\nfunction this.placeSections(scenarioSections)\n for _, section in TableUtil.pairs(scenarioSections) do\n local action = section.action\n if not action then\n action = { section = --[[---@not nil]] section.buttonLabel, }\n end\n\n ComponentFactory.spawnElement({\n element = { type = R.ElementType.ScenarioSection, name = \"Section\",\n image = section.image, scale = section.scale, hidden = section.hidden or false,\n characterCount = section.characterCount },\n placement = { position = section.position, rotation = { 0, 180, 0 }, },\n action = action,\n })\n end\nend\n\n---@param conclusions nil | gloom_Scenario_Conclusion[]\nfunction this.placeScenarioConclusions(conclusions)\n if not conclusions then\n return\n end\n ---@type tts__CharVectorShape\n local position = { x = 29.55, y = 1.75, z = 2.19 }\n local multiPageOffset = 10.5\n\n if #conclusions >= 2 then\n position.x = position.x + multiPageOffset\n end\n\n for _, conclusion in ipairs(--[[---@not nil]] conclusions) do\n ComponentFactory.spawnElement({\n element = { type = R.ElementType.ScenarioSection, name = \"Section\",\n image = conclusion.image, scale = conclusion.scale, hidden = true,\n characterCount = conclusion.characterCount },\n placement = { position = position, rotation = { 0, 180, 0 }, },\n action = { section = conclusion.buttonLabel or \"Reveal Conclusion\" }\n })\n position.x = position.x - multiPageOffset\n end\nend\n\n---@shape __gloom_SetHealthFromEnemy_Params\n---@field enemy string\n---@field object GUID\n\n---@param params __gloom_SetHealthFromEnemy_Params\nfunction setHealthFromEnemy(params)\n -- TODO move this into the EnemyApi so its accessible for other parts as well\n -- TODO make this work for normal/elite as well\n ---@param enemy string\n ---@return nil | gloom_Enemy_Stats\n local function getBossStats(enemy)\n for _, statSheet in ipairs(getObjectsWithTag(R.Tag.EnemyStatSheet)) do\n if statSheet.getName():find(enemy) then\n local stats = --[[---@type gloom_Enemy_LevelStats]] statSheet.getTable(\"currentLevelStats\")\n if TableUtil.isNotEmpty(stats) then\n return stats.boss\n end\n end\n end\n\n return nil\n end\n\n Wait.condition(function()\n local stats = --[[---@not nil]] getBossStats(params.enemy)\n local healthFormula = Formula.parse(stats.health)\n local health = ScenarioApi.calculateFormula(healthFormula)\n local object = --[[---@not nil]] getObjectFromGUID(params.object)\n object.call(\"setHp\", { value = health })\n end, function() return getBossStats(params.enemy) ~= nil end,\n 3, function() Logger.warn(\"No stats found for enemy %s\", params.enemy) end)\nend\n\n---@param params gloom_Setup_CreateMap\n---@return __gloom_AutoSetup_ScenarioInformation\nfunction this.prepareScenario(params)\n openedRooms = {}\n currentScenario = params.NumScenario\n randomPoolData = {}\n\n local scenario = this.getScenario()\n\n this.fillRandomPools(scenario)\n\n return scenario\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@param hiddenSetup boolean\nfunction this.placeScenario(scenario, hiddenSetup)\n this.placeAllScenarioPages(scenario)\n this.placeSectionBook(scenario)\n\n if scenario.gridType == nil then\n broadcastToAll(\"Check the Scenario Book for this Scenario!\", \"Red\")\n return\n end\n Grid.type = scenario.gridType + 1\n\n local startingRooms = getStartingRooms(scenario)\n local roomsSpawned = --[[---@type table]] {}\n for roomIndex, _ in pairs(startingRooms) do\n roomsSpawned[roomIndex] = true\n end\n\n ---@type gloom_Scenario_Room[] | table\n local tilesToPlace = startingRooms\n if not hiddenSetup then\n tilesToPlace = scenario.info.rooms\n end\n\n for _, room in pairs(tilesToPlace) do\n this.placeMapTiles(room)\n end\n\n if Options.placeMonstersUpfront() then\n this.placeMonsterList(scenario)\n end\n\n this.placeExtraContent(scenario.info.extraContent)\n for roomNumber, room in pairs(startingRooms) do\n this.placeStartTiles(room.startTiles)\n this.placeMonsters(room.monsters, scenario)\n this.placeOverlays(scenario, room.overlayTiles)\n this.placeSections(room.scenarioSections)\n this.placeExtraContent(room.extraContent)\n this.placeRandomObjects(room.randomObjects)\n this.placeFigures(scenario, room.figures)\n openedRooms[roomNumber] = true\n end\n\n this.placeScenarioConclusions(scenario.info.scenarioConclusions)\n this.placeDoors(scenario.info.doorTiles, roomsSpawned, not hiddenSetup)\nend\n\n---@param monsterTile gloom_Scenario_Monster_TilePosition\n---@param scenarioSettings __gloom_AutoSetup_ScenarioInformation\nfunction this.getMonsterDifficulty(monsterTile, scenarioSettings)\n local characterCount = scenarioSettings.characterCount\n if monsterTile.numCharacters[characterCount] == ScenarioConstants.LevelMonster_Normal then\n return \"normal\"\n elseif monsterTile.numCharacters[characterCount] == ScenarioConstants.LevelMonster_Elite then\n return \"elite\"\n end\nend\n\n---@overload fun(scenario: __gloom_AutoSetup_ScenarioInformation, name: string): gloom_Spawn_Monster\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@param name string\n---@param difficulty nil | 'normal' | 'elite'\n---@return gloom_Spawn_Monster\nfunction this.getMonsterDefaults(scenario, name, difficulty)\n ---@type gloom_Spawn_Monster\n local baseSettings = { monsterName = name, monsterDifficulty = difficulty }\n local monsterSettings = --[[---@not nil]] scenario.info.monsters\n if not monsterSettings then\n return baseSettings\n end\n\n local thisMonster = monsterSettings[name]\n if not thisMonster then\n return baseSettings\n end\n\n ---@param settings gloom_Spawn_Monster\n ---@param otherSettings gloom_Scenario_MonsterDefault_Attributes\n local function mergeSettings(settings, otherSettings)\n settings.stats = TableUtil.merge(settings.stats or {}, otherSettings.stats or {})\n settings.levelModifier = otherSettings.levelModifier or settings.levelModifier\n settings.abilityDeck = otherSettings.abilityDeck or settings.abilityDeck\n settings.hp = otherSettings.hp or settings.hp\n settings.hpMax = otherSettings.hpMax or settings.hpMax\n settings.team = otherSettings.team or settings.team\n settings.base = otherSettings.base or settings.base\n settings.tags = otherSettings.tags or settings.tags\n end\n\n if thisMonster.all then\n mergeSettings(baseSettings, --[[---@not nil]] thisMonster.all)\n end\n\n if difficulty and thisMonster[--[[---@not nil]] difficulty] then\n local difficultySettings = --[[---@not nil]] thisMonster[--[[---@not nil]] difficulty]\n mergeSettings(baseSettings, difficultySettings)\n end\n\n return baseSettings\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@param name string\n---@return gloom_Scenario_Figure\nfunction this.getFigureDefaults(scenario, name)\n if scenario.info.figures then\n return (--[[---@not nil]] scenario.info.figures)[name] or {}\n end\n\n return {}\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@param name string\n---@return gloom_Scenario_TileSettings\nfunction this.getOverlayDefaults(scenario, name)\n if scenario.info.overlays then\n return (--[[---@not nil]] scenario.info.overlays)[name] or {}\n end\n\n return {}\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\nfunction this.fillRandomPools(scenario)\n randomPools = {}\n for _, randomPool in ipairs(scenario.info.randomPools or {}) do\n if randomPool.type == ScenarioApi.RandomType.Tokens then\n this.fillTokenRandomPool(--[[---@type gloom_Scenario_RandomPool_Tokens]] randomPool)\n elseif randomPool.type == ScenarioApi.RandomType.Objects then\n this.fillObjectRandomPool(--[[---@type gloom_Scenario_RandomPool_Objects]] randomPool)\n elseif randomPool.type == ScenarioApi.RandomType.Card then\n this.fillCardRandomPool(--[[---@type gloom_Scenario_RandomPool_Card]] randomPool)\n else\n Logger.warn(\"Random pool type '%s' is not supported\", randomPool.type)\n end\n end\nend\n\n---@param pool gloom_Scenario_RandomPool_Tokens\nfunction this.fillTokenRandomPool(pool)\n randomPools[pool.name] = { name = pool.name, objects = {} }\n\n for _, value in ipairs(pool.values) do\n table.insert(randomPools[pool.name].objects, {\n element = {\n type = R.ElementType.ObjectiveToken,\n name = tostring(value),\n },\n placement = {\n rotation = { 0, 180, 180 },\n name = pool.name,\n }\n })\n end\n\n if not randomPoolData[pool.name] then\n local available = TableUtil.range(TableUtil.length(pool.values))\n randomPoolData[pool.name] = { available = available }\n end\nend\n\n---@param pool gloom_Scenario_RandomPool_Objects\nfunction this.fillObjectRandomPool(pool)\n randomPools[pool.name] = { name = pool.name, objects = {} }\n for _, object in ipairs(pool.objects) do\n table.insert(randomPools[pool.name].objects, object)\n end\n\n if not randomPoolData[pool.name] then\n local available = TableUtil.range(TableUtil.length(pool.objects))\n randomPoolData[pool.name] = { available = available }\n end\nend\n\n---@param pool gloom_Scenario_RandomPool_Card\nfunction this.fillCardRandomPool(pool)\n ---@param values string[]\n ---@return string[]\n local function getPossibleCards(values)\n local existing = TableUtil.map(randomPoolData, function(v, _) return v.card end)\n local possible = TableUtil.filter(values, function(v) return not TableUtil.contains(existing, v) end)\n return possible\n end\n\n if randomPoolData[pool.name] and randomPoolData[pool.name].card then\n local cardName = --[[---@not nil]] randomPoolData[pool.name].card\n local elements = TableUtil.deepCopy(ScenarioApi.getRandomPool(cardName))\n randomPools[pool.name] = { name = pool.name, objects = --[[---@not nil]] elements, position = pool.position }\n else\n if not pool.values then\n pool.values = {}\n local pools = ScenarioApi.getRandomPools()\n for name, _ in pairs(pools) do\n table.insert(pool.values, name)\n end\n end\n\n local possibleCards = getPossibleCards(--[[---@not nil]] pool.values)\n if TableUtil.isEmpty(possibleCards) then\n Logger.error(\"All possible cards for random pool '%s' have already been taken.\", pool.name)\n return\n end\n\n local randomIndex = math.random(TableUtil.length(possibleCards))\n local randomCard = possibleCards[randomIndex]\n\n local elements = ScenarioApi.getRandomPool(randomCard)\n if not elements then\n Logger.error(\"Can not find a registered random pool named '%s'\", randomCard)\n return\n end\n\n randomPools[pool.name] = {\n name = pool.name,\n objects = TableUtil.deepCopy(--[[---@not nil]] elements),\n position = pool.position,\n }\n randomPoolData[pool.name] = { available = TableUtil.range(#elements), card = randomCard }\n end\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\nfunction this.placeSectionBook(scenario)\n if not scenario.info.sectionBook then\n return\n end\n\n local sectionBook = --[[---@not nil]] scenario.info.sectionBook\n if not sectionBook.name then\n return\n end\n\n local sectionBookData = --[[---@not nil]] this.findBookData(sectionBook.name, R.Tag.Book.Sections)\n if not sectionBookData then\n Logger.warn(\"Did not find the section book '%s'. Can not place it.\", sectionBook.name)\n return\n end\n\n local sections = {}\n for _, section in ipairs(sectionBook.sections) do\n table.insert(sections, section.id)\n end\n\n local pageData = --[[---@type gloom_SectionBook]] JSON.decode(sectionBookData.Memo or \"{}\")\n\n ---@type integer[]\n local pages = {}\n for pageNumber, page in pairs(pageData) do\n local sectionIds = TableUtil.keys(page.sections)\n for _, id in ipairs(sectionIds) do\n if TableUtil.contains(sections, id) then\n table.insert(pages, pageNumber)\n break\n end\n end\n end\n\n for i, pageNumber in ipairs(pages) do\n local pos = { x = -39.40 + (i - 1) * 10.6, y = 1.8, z = 16.62 }\n sectionBookData.CustomPDF.PDFPage = pageNumber\n spawnObjectData({\n data = sectionBookData,\n position = pos,\n scale = { 3, 1, 3 },\n callback_function = function(book)\n book.call(\"setActive\", sections)\n end\n })\n end\nend\n\n---@param pool __gloom_AutoSetup_RandomPool\n---@param cardName string\nfunction this.placeRandomPoolCard(pool, cardName)\n for _, obj in ipairs(getObjects()) do\n if obj.memo == cardName and obj.getName() == pool.name then\n return\n end\n end\n\n ---@param name string\n ---@return nil | tts__ObjectState\n --TODO move this somewhere else, at least the part about getting stuff from backup\n local function findCardData(name)\n local backup = --[[---@type tts__Bag]] getObjectFromGUID(R.Guid.Backup)\n for _, contained in ipairs(backup.getData().ContainedObjects) do\n if contained.GUID == \"8709e8\" then\n local randomDeck = --[[---@type tts__BagState]] contained\n local content = (--[[---@type tts__DeckState]] randomDeck.ContainedObjects[1])\n for _, card in ipairs(content.ContainedObjects) do\n if card.Nickname == name then\n return card\n end\n end\n end\n end\n\n return nil\n end\n\n local cardData = --[[---@not nil]] findCardData(cardName)\n if cardData then\n cardData.Nickname = pool.name\n spawnObjectData({\n data = --[[---@not nil]] cardData,\n position = pool.position,\n })\n end\nend\n\n---@param randomObjects nil | gloom_Scenario_RandomObject[]\nfunction this.placeRandomObjects(randomObjects)\n for _, randomObject in ipairs(randomObjects or {}) do\n local poolName = randomObject.pool\n local pool = randomPools[poolName]\n local poolData = randomPoolData[poolName]\n if pool and poolData then\n if poolData.card then\n this.placeRandomPoolCard(pool, --[[---@not nil]] poolData.card)\n end\n\n local available = poolData.available\n if not TableUtil.isEmpty(available) then\n local element\n if randomObject.index then\n local index = --[[---@not nil]] randomObject.index\n for i, avail in ipairs(available) do\n if avail == index then\n element = table.remove(available, i)\n break\n end\n end\n else\n local index = math.random(TableUtil.length(available))\n element = table.remove(available, index)\n end\n\n local theElement = randomPools[poolName].objects[element]\n Logger.debug(\"Spawning random element %s from pool %s\\n%s\", element, poolName, theElement)\n\n if not theElement.placement then\n theElement.placement = {}\n end\n theElement.placement.position = randomObject.position\n ComponentFactory.spawnElement(theElement)\n else\n Logger.error(\"The random pool '%s' has no more available elements. No element will be placed\", poolName)\n end\n else\n Logger.error(\"No random pool with the name '%s' exists.\", poolName)\n end\n end\nend\n\n---@param scenario __gloom_AutoSetup_ScenarioInformation\n---@param figures nil | gloom_Scenario_Figure_Overlay[]\nfunction this.placeFigures(scenario, figures)\n for _, figure in ipairs(figures or {}) do\n local figureSettings = TableUtil.merge(this.getFigureDefaults(scenario, figure.name), figure)\n\n local description = \"\"\n local tags = {}\n if figureSettings.ability then\n table.insert(tags, R.Tag.Trait.HasInitiative)\n local ability = --[[---@not nil]] figureSettings.ability\n description = ability.initiative .. \": \" .. ability.ability\n else\n description = figure.description\n end\n\n ComponentFactory.spawnElement({\n element = {\n type = R.ElementType.Figure, name = --[[---@not nil]] figureSettings.basedOn,\n stats = figureSettings.stats,\n characterCount = figureSettings.characterCount,\n base = figureSettings.base, team = figureSettings.team,\n },\n placement = {\n position = figure.position, rotation = figure.rotation,\n name = figure.name, description = description, memo = figure.name,\n tags = tags,\n lock = R.LockType.None,\n },\n action = figureSettings.action,\n })\n end\nend\n\n---@param name string\n---@param tag string\n---@return nil | tts__ObjectState\nfunction this.findBookData(name, tag)\n for _, obj in ipairs(getObjectsWithAllTags({ name, tag })) do\n return obj.getData()\n end\n\n local bookStand = --[[---@type tts__Bag]] getObjectFromGUID(\"17e420\")\n for _, obj in ipairs(bookStand.getData().ContainedObjects) do\n if Object.hasTag(obj, name) and Object.hasTag(obj, tag) then\n return obj\n end\n end\n\n return nil\nend\n\nSaveManager.registerOnSave(\"AutoSetup\", onSave)\nSaveManager.registerOnLoad(\"AutoSetup\", onLoad)\n\nend)\n__bundle_register(\"Scenarios.ScenarioConstants\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Constants = {\r\n Grid_Horizontal = 1,\r\n Grid_Vertical = 2,\r\n\r\n LevelMonster_Normal = 1,\r\n LevelMonster_Elite = 2,\r\n\r\n TypeOverlay_Corridors = 1,\r\n TypeOverlay_DifficultTerrain = 2,\r\n TypeOverlay_HazardousTerrain = 3,\r\n TypeOverlay_ObjectiveTokens = 4,\r\n TypeOverlay_Obstacles = 5,\r\n TypeOverlay_ScenarioAidTokens = 6,\r\n TypeOverlay_Traps = 7,\r\n TypeOverlay_TreasureChests = 8,\r\n}\r\n\r\nreturn Constants\r\n\nend)\n__bundle_register(\"ComponentFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal ObjectState = require(\"lib.ObjectData\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal TtsWait = require(\"lib.promise.TtsWait\")\n\nlocal Event = require(\"Event\")\nlocal R = require(\"Resources\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ScenarioApi = require(\"api.ScenarioApi\")\n\nlocal ComponentFactory = {}\n\nlocal Api = --[[---@type ComponentApi]] ApiProvider(\"component\")\n---@class __ComponentFactory_this\nlocal this = {}\n\n---@type table\nlocal Factories = {}\n\n---@param elementType gloom_Element_Type\n---@param factory gloom_ComponentFactory\nfunction ComponentFactory.register(elementType, factory)\n Factories[elementType] = factory\nend\n\n---@param elementType gloom_Element_Type\n---@param name string\n---@return nil | tts__ObjectState\nfunction ComponentFactory.getElementData(elementType, name)\n local factory = Factories[elementType]\n if not factory then\n Logger.error(\"No factory found to get data for element %s of type %s\", name, elementType)\n return nil\n end\n\n return factory.getElementData({ element = { type = elementType, name = name } })\nend\n\n--- Spawns the given element.\n---@param elementInfo gloom_Spawn_Execution\nfunction ComponentFactory.spawnElement(elementInfo)\n elementInfo.element = this.resolveCharacterCount(elementInfo.element)\n\n if elementInfo.element.name == R.Remove then\n -- Special name for elements that shouldn't be spawned at all\n return\n end\n\n if not elementInfo.element.type then\n Logger.error(\"No type given to spawn element %s\", elementInfo.element.name)\n return\n end\n\n local factory = Factories[elementInfo.element.type]\n if not factory then\n Logger.error(\"No factory found to spawn element %s of type %s\", elementInfo.element.name, elementInfo.element.type)\n return\n end\n\n if this.alreadyExists(elementInfo, factory.useExactMatch()) then\n Logger.verbose(\"Element %s already exists at position %s\", elementInfo.element.name, elementInfo.placement.position)\n return\n end\n\n local elementData = factory.getElementData(elementInfo)\n if not elementData then\n Logger.error(\"Can not find element data for element %s of type %s\", elementInfo.element.name, elementInfo.element.type)\n return\n end\n\n this.setBaseElementAttributes(elementData, elementInfo)\n\n Logger.debug(\"Spawning element %s\", elementInfo)\n\n local elementSpawn = factory.spawnElement(--[[---@not nil]] elementData, elementInfo)\n elementSpawn\n :next(TtsWait.untilLoaded)\n :next(function(element)\n factory.prepareElement(element, elementInfo)\n end)\n :catch(function(reason)\n Logger.error(reason)\n end)\nend\n\nApi.spawnElement = function(element)\n ComponentFactory.spawnElement(element)\nend\n\nApi.hasElement = function(elementType, elementName)\n return ComponentFactory.getElementData(elementType, elementName) ~= nil\nend\n\n---@param element gloom_Spawn_Element\n---@return gloom_Spawn_Element\nfunction this.resolveCharacterCount(element)\n if element.characterCount then\n local characterCount = ScenarioApi.getCharacterCount()\n local characterCountSettings = (--[[---@not nil]] element.characterCount)[characterCount]\n if characterCountSettings then\n if characterCountSettings == R.Remove then\n return R.EmptyElement\n end\n\n return TableUtil.merge(element, --[[---@not nil]] characterCountSettings)\n end\n end\n\n return element\nend\n\n--- Checks whether the given position already contains an object with the given name. If it does, the object is returned.\n---@overload fun(position: tts__VectorShape, name: string): nil | tts__Object\n---@param position tts__VectorShape\n---@param name string\n---@param exactMatch boolean\n---@return nil | tts__Object\nfunction this.findExistingObject(position, name, exactMatch)\n local hits = Physics.cast({\n origin = position,\n direction = { 0, 1, 0 },\n type = 3,\n size = { 0.5, 0.5, 0.5 },\n max_distance = 0,\n debug = false,\n })\n\n local checker = function(objectName)\n return objectName:find(name)\n end\n if exactMatch then\n checker = function(objectName)\n return objectName == name\n end\n end\n\n for _, hit in pairs(hits) do\n if checker(hit.hit_object.getName()) then\n return hit.hit_object\n end\n end\n\n return nil\nend\n\n---@param elementInfo gloom_Spawn_Execution\n---@param exactMatch boolean\n---@return boolean\nfunction this.alreadyExists(elementInfo, exactMatch)\n local nameToFind = elementInfo.placement.name or elementInfo.element.name\n local existingObject = this.findExistingObject(elementInfo.placement.position, nameToFind, exactMatch)\n return existingObject ~= nil\nend\n\n---@param element tts__ObjectState\n---@param elementInfo gloom_Spawn_Execution\nfunction this.setBaseElementAttributes(element, elementInfo)\n for _, tag in ipairs(elementInfo.placement.tags or {}) do\n ObjectState.addTag(element, tag)\n end\n element.Nickname = elementInfo.placement.name or element.Nickname\n element.Description = elementInfo.placement.description or element.Description\n element.Memo = elementInfo.placement.memo or element.Memo\n this.setLocked(element, elementInfo.placement.lock)\n\n for _, state in ipairs(element.States or {}) do\n this.setBaseElementAttributes(state, elementInfo)\n end\nend\n\n---@param objectData tts__ObjectState\n---@param lockType nil | gloom_Element_LockType\nfunction this.setLocked(objectData, lockType)\n if lockType == nil or lockType == R.LockType.Hard then\n objectData.Locked = true\n elseif lockType == R.LockType.Soft then\n ObjectState.setUseGravity(objectData, false)\n ObjectState.addTag(objectData, R.Tag.SoftLock)\n else\n objectData.Locked = false\n end\nend\n\n---@param object tts__Object\nfunction this.onObjectPickup(_, object)\n if object.hasTag(R.Tag.SoftLock) then\n object.removeTag(R.Tag.SoftLock)\n object.use_gravity = true\n end\nend\n\nEvent.registerHandler(\"onObjectPickUp\", this.onObjectPickup)\n\nreturn ComponentFactory\n\nend)\n__bundle_register(\"GameController\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal SaveManager = require(\"lib.SaveManager\")\nlocal State = require(\"lib.State\")\nlocal StringUtil = require(\"lib.StringUtil\")\n\nlocal InitiativeTracker = require(\"InitiativeTracker\")\nlocal Game = require(\"Game\")\nlocal Party = require(\"Party\")\nlocal R = require(\"api.Resource\")\nlocal DialCounter = require(\"Component.internal.DialCounter\")\n\nlocal AttackModifierMechanic = require(\"mechanic.attackmodifier.AttackModifierMechanic\")\n\nlocal GameController = {}\n\n---@class __GameController_this\nlocal this = {}\n\n---@shape gloom_Game_Monster_Info\n---@field abilityDeck string\n\n---@type table\nlocal monsterInfo = State.createState({})\n\nlocal roundInProgress = false\n\nfunction GameController.startRound()\n local playArea\n local hits = Physics.cast({\n origin = { 0.00, 1.73, -16.67 },\n direction = { 0, 1, 0 },\n type = 3,\n size = { 1, 1, 1 },\n max_distance = 0,\n debug = false\n })\n\n for _, v in pairs(hits) do\n if v.hit_object.getName() == \"Play Area\" then playArea = v.hit_object end\n end\n\n if playArea == nil then\n return\n end\n\n if roundInProgress then\n broadcastToAll(\"Round still in progress. Please end the previous round before starting a new one\", { 1, 1, 1 })\n return\n end\n\n for playerNumber, player in pairs(Game.PlayerInfo) do\n local characterInfo = Party.getCharacterInfo(playerNumber)\n\n if characterInfo.isPresent then\n local hitlist = Physics.cast({\n origin = player.Abilities[1],\n direction = { 0, 1, 0 },\n type = 3,\n size = { 1, 1, 1 },\n max_distance = 0,\n debug = true\n })\n -- do not remove this nil, even though it shouldn't be necessary!\n -- doing so will result in a Moonsharp bug and card will not be nil\n local card = nil\n for _, obj in pairs(hitlist) do\n if obj.hit_object.type == \"Card\" then\n card = obj.hit_object\n end\n end\n\n if card == nil then\n local found = InitiativeTracker.hasInitiativeForClass(characterInfo.class)\n if not found then\n local value = DialCounter.getValue((--[[---@not nil]] characterInfo.objects).hpDial())\n if value ~= 0 then\n broadcastToAll(characterInfo.class .. \" has no Initiative!\", { 1, 0, 0 })\n return\n end\n end\n end\n end\n end\n roundInProgress = true\n\n broadcastToAll(\"New Round started. All Initiative Cards revealed.\", { 1, 1, 1 })\n\n for i = 1, Game.PlayerCount do\n local cardNamePrimary = playArea.call(\"buttonClickedEx\", { \"P\" .. i .. \"FReveal\" })\n local cardNameSecondary = playArea.call(\"buttonClickedEx\", { \"P\" .. i .. \"SReveal\" })\n\n if cardNamePrimary then\n local characterInfo = Party.getCharacterInfo(i)\n local initPrimary = {}\n local initSecondary = {}\n\n cardNamePrimary:gsub(\"(%d+)\", function(a) table.insert(initPrimary, a) end)\n cardNameSecondary:gsub(\"(%d+)\", function(a) table.insert(initSecondary, a) end)\n\n for i = 1, #initPrimary do\n local second = initSecondary[1]\n if initSecondary[i] ~= nil then\n second = initSecondary[i]\n end\n\n InitiativeTracker.add({\n name = characterInfo.class,\n type = \"Character\",\n multi = #initPrimary > 1,\n first = tonumber(initPrimary[i]),\n second = tonumber(second)\n })\n end\n end\n end\n\n for _, o in pairs(getObjectsWithTag(R.Tag.Monster.Mat)) do\n local hitlist = Physics.cast({\n origin = o.getPosition(),\n direction = { 0, 1, 0 },\n type = 3,\n size = { 9, 2, 5 },\n max_distance = 0,\n debug = false\n })\n local monsterName\n for _, j in pairs(hitlist) do\n if j.hit_object.getName():find(\"Stat Sheet\") then\n monsterName = j.hit_object.memo\n end\n end\n\n if this.findMonster(monsterName) then\n local ini = o.call(\"drawCard\")\n if ini ~= nil then\n for k, v in pairs(ini) do\n InitiativeTracker.add({\n name = monsterName,\n type = \"Monster\",\n multi = #ini > 1,\n first = tonumber(k),\n second = 999,\n ability = v\n })\n end\n end\n end\n end\n\n ---@type table\n local initiativeFigures = {}\n for _, obj in ipairs(getObjectsWithTag(R.Tag.Trait.HasInitiative)) do\n initiativeFigures[obj.memo] = this.parseInitiative(obj.getDescription())\n end\n\n for name, figure in pairs(initiativeFigures) do\n for _, entry in ipairs(figure) do\n InitiativeTracker.add({\n name = name,\n type = \"Monster\",\n first = tonumber(entry.initiative),\n second = 999,\n ability = entry.ability,\n })\n end\n end\n\n InitiativeTracker.render()\nend\n\n---@param final boolean\nfunction GameController.endRound(final)\n local playArea\n local hitlist = Physics.cast({\n origin = { 0.00, 1.73, -16.67 },\n direction = { 0, 1, 0 },\n type = 3,\n size = { 1, 1, 1 },\n max_distance = 0,\n debug = false\n })\n\n for _, v in pairs(hitlist) do\n if v.hit_object.getName() == \"Play Area\" then playArea = v.hit_object end\n end\n\n if playArea == nil then\n return\n end\n roundInProgress = false\n\n if not final then\n broadcastToAll(\"Round ends.\", { 1, 1, 1 })\n end\n\n for _, o in pairs(getObjectsWithTag(R.Tag.Monster.Mat)) do\n local discard = o.call(\"getDiscard\")\n if discard then\n if discard.tag == \"Deck\" then\n for _, u in pairs(discard.getObjects()) do\n if u.description:find(\"shuffle\") then\n o.call(\"reshuffle\")\n end\n end\n elseif discard then\n if discard.getDescription():find(\"shuffle\") then\n o.call(\"reshuffle\")\n end\n end\n end\n end\n\n AttackModifierMechanic.endOfRoundCheck(\"Monster\")\n for _, character in pairs(Party.getActiveCharacters()) do\n AttackModifierMechanic.endOfRoundCheck(character)\n end\n\n local elements = { \"Fire\", \"Ice\", \"Air\", \"Earth\", \"Light\", \"Dark\" }\n for _, j in pairs(elements) do\n if final then\n playArea.call(\"buttonClickedEx\", { \"low\" .. j })\n end\n playArea.call(\"buttonClickedEx\", { \"low\" .. j })\n end\n\n if not final then\n moveRoundTracker()\n end\n\n InitiativeTracker.reset()\nend\n\n---@param monsterName string\n---@param abilityDeck string\nfunction this.setMonsterInfo(monsterName, abilityDeck)\n monsterInfo[monsterName] = { abilityDeck = abilityDeck }\nend\n\nfunction this.findMonster(monsterName)\n local hits = Physics.cast({\n origin = {0.00, 2, 7},\n direction = {0, 1, 0},\n type = 3,\n size = {93, 10, 35},\n max_distance = 0,\n debug = false\n })\n\n for _, j in pairs(hits) do\n if j.hit_object.memo == monsterName then\n return j.hit_object\n end\n end\n return false\nend\n\n---@param input string\nfunction this.parseInitiative(input)\n local initiative = {}\n local lines = StringUtil.split(input, \"\\n\")\n\n for _, line in ipairs(lines) do\n line = StringUtil.strip(line)\n local ini, ability = line:match(\"^(%d+)%s*:(.*)\")\n\n table.insert(initiative, {\n initiative = ini,\n ability = ability,\n })\n end\n\n return initiative\nend\n\n---@shape __setMonsterInfo_Params\n---@field name string\n---@field abilityDeck string\n\n---@param params __setMonsterInfo_Params\nfunction __setMonsterInfo(params)\n this.setMonsterInfo(params.name, params.abilityDeck)\nend\n\n---@param name string\n---@return nil | string\nfunction __getMonsterAbilityDeck(name)\n local info = monsterInfo[name]\n if info then\n return info.abilityDeck\n end\n\n return nil\nend\n\nlocal function onLoad(saveState)\n monsterInfo.load(saveState)\nend\n\nlocal function onSave()\n local data = monsterInfo.save()\n return data\nend\n\nSaveManager.registerOnLoad(\"gloom.GameController\", onLoad)\nSaveManager.registerOnSave(\"gloom.GameController\", onSave)\n\nreturn GameController\n\nend)\n__bundle_register(\"lib.State\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal State = require(\"sebaestschjin-tts.State\")\r\n\r\nreturn State\r\n\nend)\n__bundle_register(\"sebaestschjin-tts.State\", function(require, _LOADED, __bundle_register, __bundle_modules)\n---@shape seb_State\r\n---@field save fun(): nil | string\r\n---@field load fun(savedState: nil | string): void\r\n\r\nlocal State = {}\r\n\r\n---@generic T: seb_State\r\n---@param initial T\r\n---@return T\r\nfunction State.createState(initial)\r\n local self = --[[---@type T]] {}\r\n\r\n local data = initial\r\n local isDirty = true\r\n ---@type nil | string\r\n local representation = nil\r\n\r\n ---@return nil | string\r\n function self.save()\r\n if isDirty and data then\r\n isDirty = false\r\n representation = json.serialize(data)\r\n end\r\n\r\n return representation\r\n end\r\n\r\n ---@param savedState nil | string\r\n function self.load(savedState)\r\n if savedState and savedState ~= \"\" then\r\n data = json.parse(--[[---@not nil]] savedState)\r\n representation = savedState\r\n end\r\n end\r\n\r\n setmetatable(self, {\r\n __index = function(_, key)\r\n return data[key]\r\n end,\r\n\r\n __newindex = function(_, key, value)\r\n isDirty = true\r\n data[key] = value\r\n end,\r\n })\r\n\r\n return self\r\nend\r\n\r\nreturn State\r\n\nend)\n__bundle_register(\"Game.Frosthaven.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"Game.Frosthaven.Conditions\")\r\nrequire(\"Game.Frosthaven.Theme\")\r\n\nend)\n__bundle_register(\"Game.Frosthaven.Theme\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ThemeApi = require(\"api.ThemeApi\")\nlocal UiApi = require(\"api.UiApi\")\n\n---@type gloom_Theme\nlocal theme = {\n id = \"frosthaven\",\n name = \"Frosthaven\",\n conditions = {\n Wound = {\n icon = \"http://cloud-3.steamusercontent.com/ugc/1751310104237980213/87D33B6CEDF63D59916B7D1F96029C5CE548EFE9/\",\n tokenStack = \"http://cloud-3.steamusercontent.com/ugc/1794142325079191700/8E39059E3C72ED6A28A270846BC82E1837B1BC82/\",\n }\n },\n effects = {\n Fly = {\n renderedMarkup = UiApi.solidText(\"\\u{E00E}\")\n },\n Heal = {\n renderedMarkup = UiApi.solidText(\"\\u{E005}\")\n },\n Jump = {\n renderedMarkup = UiApi.solidText(\"\\u{E01F}\")\n },\n Move = {\n renderedMarkup = UiApi.solidText(\"\\u{E003}\")\n },\n Push = {\n renderedMarkup = UiApi.conditionText(\"\\u{E0B2}\", \"#505050\")\n },\n Pull = {\n renderedMarkup = UiApi.conditionText(\"\\u{E0B3}\", \"#505050\")\n },\n Range = {\n renderedMarkup = UiApi.solidText(\"\\u{E009}\")\n },\n Shield = {\n renderedMarkup = UiApi.solidText(\"\\u{E00F}\")\n },\n }\n}\n\nThemeApi.registerTheme(theme)\n\nend)\n__bundle_register(\"api.ThemeApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal ThemeApi = ApiConsumer(\"theme\")\n .withApi(\"registerTheme\")\n .withApi(\"getThemes\")\n .withApi(\"selectThemes\")\n .withApi(\"getConditionTheme\")\n .withApi(\"getEffectTheme\")\n\nreturn ThemeApi\n\nend)\n__bundle_register(\"Game.Frosthaven.Conditions\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ConditionApi = require(\"api.ConditionApi\")\r\nlocal UiApi = require(\"api.UiApi\")\r\n\r\nConditionApi.registerCondition(\"Bane\", {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1751310104237974843/789962F15529BA6B690C15AC69EF2DD01657EC27/\",\r\n immunity = {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1753609293981077874/F203F3302EA532506317E53D9C2B568D67D3913D/\"\r\n },\r\n renderedMarkup = UiApi.conditionText(\"\\u{E0A4}\", \"#252528\"),\r\n})\r\n\r\nConditionApi.registerCondition(\"Brittle\", {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1756937968345531733/027D45CA1FAFCE3E9ED9D97D170F661B5CE560CA/\",\r\n immunity = {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1753609293981079023/2D287D537218DA389E7FDA9B67CA0D544CCCF403/\"\r\n },\r\n renderedMarkup = UiApi.coloredText(\"\\u{E09F}\", \"#FFF\") .. UiApi.coloredText(\"\\u{E0A1}\", \"#24949F\"),\r\n})\r\n\r\nConditionApi.registerCondition(\"Injure\", {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1751310104237975751/009A9EDE81999911EE4DF9A24833FD5DCAF217EE/\",\r\n renderedMarkup = UiApi.conditionText(\"\\u{E0A6}\", \"#80394A\"),\r\n})\r\n\r\nConditionApi.registerCondition(\"Ward\", {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1751310104237976362/B10899C97992FF30CE9CEF05C9B89D5A8254F4F8/\",\r\n renderedMarkup = UiApi.conditionText(\"\\u{E0AE}\", \"#C63B95\"),\r\n})\r\n\nend)\n__bundle_register(\"Game.ForgottenCircles.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"Game.ForgottenCircles.Conditions\")\r\n\nend)\n__bundle_register(\"Game.ForgottenCircles.Conditions\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ConditionApi = require(\"api.ConditionApi\")\r\nlocal UiApi = require(\"api.UiApi\")\r\n\r\nConditionApi.registerCondition(\"Regenerate\", {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/791986676667649885/92F7F934D298524F6F72997095DDBA073D9A15BF/\",\r\n renderedMarkup = UiApi.conditionText(\"\\u{E0AB}\", \"#C63B95\")\r\n})\r\n\r\n\r\nConditionApi.registerEffect({\r\n name = \"Teleport\",\r\n image = \"http://cloud-3.steamusercontent.com/ugc/1785137125158431200/B36FB80A2F1E7B00D2156009796EA07FD829B7B0/\",\r\n renderedMarkup = UiApi.solidText(\"\\u{E019}\")\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"Game.Gloomhaven.Conditions\")\r\nrequire(\"Game.Gloomhaven.Monsters\")\r\nrequire(\"Game.Gloomhaven.Overlays\")\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Overlays\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ComponentApi = require(\"api.ComponentApi\")\r\n\r\nlocal baseUrl = \"https://github.com/any2cards/gloomhaven/raw/\"\r\nlocal commitId = \"5a840817052fb7da3e299b01158e05782c363c68\"\r\nlocal any2CardsToken = baseUrl .. commitId .. \"/images/tokens/gloomhaven\"\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Hot Coals\",\r\n image = any2CardsToken .. \"/hazardous-terrain/gh-hot-coals.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Thorns\",\r\n image = any2CardsToken .. \"/hazardous-terrain/gh-thorns.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Boulder\",\r\n image = any2CardsToken .. \"/obstacles/gh-boulder-1.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Boulder 3\",\r\n image = any2CardsToken .. \"/obstacles/gh-boulder-3.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Rock Column\",\r\n image = any2CardsToken .. \"/obstacles/gh-rock-column.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Bear Trap\",\r\n image = any2CardsToken .. \"/traps/gh-bear-trap.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Spike Trap\",\r\n image = any2CardsToken .. \"/traps/gh-spike-pit-trap.png\",\r\n})\r\n\r\nComponentApi.registerOverlay({\r\n name = \"Poison Gas\",\r\n image = any2CardsToken .. \"/traps/gh-poison-gas-trap.png\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"Game.Gloomhaven.Monsters.AncientArtillery.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Archer.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Ashblade.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.CaveBear.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Cultist.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.DeepTerror.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.EarthDemon.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.FlameDemon.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.FrostDemon.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.GiantViper.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Guard.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.HarrowerInfester.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Hound.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Imp.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.LivingBones.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.LivingCorpse.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.LivingSpirit.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Lurker.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.NightDemon.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Ooze.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.RendingDrake.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Savage.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.SavvasIcestorm.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.SavvasLavaflow.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Scout.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Shaman.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.SpittingDrake.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.StoneGolem.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.SunDemon.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Tracker.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.WindDemon.MonsterInfo\")\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.WindDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Wind Demon\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/wind-demon/ma-wd-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Wind Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794648/D429CD2EBC04580DD1E889DEC223CB870F9F9191/\",\r\n abilityDeck = \"Wind Demon\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Tracker.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Tracker\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-2.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n conditions = { \"Disarm\" },\r\n },\r\n }\r\n },\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/tracker/ma-tr-8.png\",\r\n },\r\n },\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Tracker.ValrathTracker.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Tracker.ValrathTracker.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Valrath Tracker\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794425/01417DD16099A21D59AB09FCE89508725CD2C283/\",\r\n abilityDeck = \"Tracker\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.SunDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Sun Demon\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/sun-demon/ma-sud-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Sun Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793740/29F986B3E91A9374FAD51F1D1C951A967B8D3CF0/\",\r\n abilityDeck = \"Sun Demon\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.StoneGolem.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Stone Golem\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/stone-golem/ma-st-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Stone Golem\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793676/7DC70CFB746F0BBEB27E87448DEE04D6B62EF745/\",\r\n abilityDeck = \"Stone Golem\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.SpittingDrake.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Spitting Drake\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/spitting-drake/ma-spd-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Spitting Drake\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793533/A3146223C8B533BAE33DC44EC0D3C352F3268E3D/\",\r\n abilityDeck = \"Spitting Drake\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Shaman.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Shaman\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/shaman/ma-sh-8.png\",\r\n },\r\n },\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Shaman.InoxShaman.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Shaman.VermlingShaman.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Shaman.VermlingShaman.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Vermling Shaman\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794584/6EC02A7469FEE833FA7F3C4DE8B95F32F6BDB20A/\",\r\n abilityDeck = \"Shaman\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Shaman.InoxShaman.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Inox Shaman\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792482/7561A8A88E15E1466B4F57227E998ADC7FD8035D/\",\r\n abilityDeck = \"Shaman\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Scout.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Scout\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/scout/ma-sc-8.png\",\r\n },\r\n },\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Scout.AestherScout.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Scout.VermlingScout.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Scout.VermlingScout.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Vermling Scout\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794517/59283ED2501E794483DBD15D29AAC02A25176969/\",\r\n abilityDeck = \"Scout\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Scout.AestherScout.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Aesther Scout\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075790673/5BD04C7C21C652AB81775BB82E89D937A8C8058A/\",\r\n abilityDeck = \"Scout\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.SavvasLavaflow.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Savvas Lavaflow\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-1.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n },\r\n }\r\n },\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-2.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Earth Demon\",\r\n },\r\n }\r\n },\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-lavaflow/ma-sl-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Savvas Lavaflow\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793395/7C7800FCE74AB093DB432EBE133E8AB24EF075D2/\",\r\n abilityDeck = \"Savvas Lavaflow\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.SavvasIcestorm.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Savvas Icestorm\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-2.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n },\r\n }\r\n },\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-3.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Frost Demon\",\r\n },\r\n }\r\n },\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savvas-icestorm/ma-si-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Savvas Icestorm\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793326/74D5786298803BAFE1C56EE458B3B7D5221C79A3/\",\r\n abilityDeck = \"Savvas Icestorm\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Savage.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Savage\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/savage/ma-sv-8.png\",\r\n },\r\n },\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Savage.ValrathSavage.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Savage.ValrathSavage.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Valrath Savage\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794357/9A96CE3508EDE74CD1E1DDB1BFC3E55D679650D9/\",\r\n abilityDeck = \"Savage\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.RendingDrake.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Rending Drake\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/rending-drake/ma-rd-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Rending Drake\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793261/01D0635E65793BBD00B658704A4A5FC236A4D467/\",\r\n abilityDeck = \"Rending Drake\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Ooze.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Ooze\", {\r\n image = \"http://cloud-3.steamusercontent.com/ugc/929312733724517391/E35078AED60DB563D1DACC7B3E92ADB40480F9D3/\",\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-5.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n },\r\n }\r\n },\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-6.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Ooze\",\r\n },\r\n }\r\n },\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ooze/ma-oo-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Ooze\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793113/F1D318278D58D232862264C8905E2FED7509EC57/\",\r\n abilityDeck = \"Ooze\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.NightDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Night Demon\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/night-demon/ma-nd-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Night Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793046/7935292BB62942797F22234A7712540437C1BEA0/\",\r\n abilityDeck = \"Night Demon\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Lurker.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Lurker\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/lurker/ma-lu-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Lurker\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792821/D2EE06A46938B05DCAB369DE964C17739DB6D65A/\",\r\n abilityDeck = \"Lurker\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.LivingSpirit.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Living Spirit\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-spirit/ma-ls-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Living Spirit\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792760/5A8E8CDC94499E6FDF9537E6267E0C2A7E070A1F/\",\r\n abilityDeck = \"Living Spirit\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.LivingCorpse.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Living Corpse\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-corpse/ma-lc-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Living Corpse\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792695/499848E84DB9E2501DA0FE91B4AF19FA503048D2/\",\r\n abilityDeck = \"Living Corpse\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.LivingBones.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Living Bones\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/living-bones/ma-lb-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Living Bones\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792631/D652524FDD5CD95C47CCCFB1374FD586457C2F10/\",\r\n abilityDeck = \"Living Bones\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Imp.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Imp\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/imp/ma-im-8.png\",\r\n },\r\n },\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Imp.BlackImp.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Imp.ForestImp.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Imp.ForestImp.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Forest Imp\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791818/A81BB0DF31AE70905511E33A557D8F3B74A65E36/\",\r\n abilityDeck = \"Imp\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Imp.BlackImp.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Black Imp\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791022/1875A69A635FA600DD918608392AD36182F62F43/\",\r\n abilityDeck = \"Imp\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Hound.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Hound\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/hound/ma-ho-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Hound\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792088/9B6639C6009C97C81461B05E4C14D4746333DFE0/\",\r\n abilityDeck = \"Hound\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.HarrowerInfester.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Harrower Infester\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/harrower-infester/ma-hi-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Harrower Infester\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792028/450134CD91ED41181881969207CEF1CE2A8D0E52/\",\r\n abilityDeck = \"Harrower Infester\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Guard.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Guard\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/guard/ma-gu-8.png\",\r\n },\r\n },\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Guard.BanditGuard.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Guard.CityGuard.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Guard.InoxGuard.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Guard.InoxGuard.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Inox Guard\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792402/0735AB9615F4D5A48634258D08E3EF07E9423230/\",\r\n abilityDeck = \"Guard\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Guard.CityGuard.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"City Guard\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791313/23008484069CCEA8F4734134256A5CD4741D6EDE/\",\r\n abilityDeck = \"Guard\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Guard.BanditGuard.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Bandit Guard\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075790950/BC8FF7FAC787DC1F0587EEDED3B34FCBCAEFCA35/\",\r\n abilityDeck = \"Guard\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.GiantViper.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Giant Viper\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/giant-viper/ma-gv-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Giant Viper\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791955/BAB43543950168C20BE643F1C465593BC02031BE/\",\r\n abilityDeck = \"Giant Viper\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.FrostDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Frost Demon\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/frost-demon/ma-frd-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Frost Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791890/5C58977413D62D16B899D929F6C267C062F7D87C/\",\r\n abilityDeck = \"Frost Demon\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.FlameDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Flame Demon\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/flame-demon/ma-fld-8.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = 4,\r\n },\r\n }\r\n },\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Flame Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791754/C8F7BB8EE2F9747B4826628B0DAB088D2D326476/\",\r\n abilityDeck = \"Flame Demon\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.EarthDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Earth Demon\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/earth-demon/ma-ed-8.png\",\r\n },\r\n },\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Earth Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791625/0B37CFE711F4D78EDB480B97A9E9E89729B9504E/\",\r\n abilityDeck = \"Earth Demon\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.DeepTerror.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Deep Terror\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-7.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Deep Terror\",\r\n },\r\n }\r\n },\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/deep-terror/ma-dt-8.png\",\r\n },\r\n }\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Deep Terror\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791552/184035C5C279758683B0FEBE09ED6F2E84F3C75B/\",\r\n abilityDeck = \"Deep Terror\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Cultist.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Cultist\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-6.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n },\r\n }\r\n },\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-7.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n },\r\n }\r\n },\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cultist/ma-cu-8.png\",\r\n },\r\n }\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Cultist\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791401/EA5104067C832F25EF2F4B942BFD100145A59242/\",\r\n abilityDeck = \"Cultist\"\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.CaveBear.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Cave Bear\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/cave-bear/ma-cb-8.png\",\r\n },\r\n }\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Cave Bear\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791165/8FF711ADA7941FCD8D0140ABB92C583D0D63D492/\",\r\n abilityDeck = \"Cave Bear\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Boss\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/boss/ma-bo-8.png\",\r\n },\r\n }\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.BanditCommander.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.CaptainOfTheGuard.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.DarkRider.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.ElderDrake.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.HumanCommander.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.InoxBodyGuard.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.Jekserah.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.ManifestationOfCorruption.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.MercilessOverseer.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.PrimeDemon.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.TheBetrayer.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.TheColorless.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.TheGloom.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.TheSightlessEye.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.ValrathCommander.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Boss.WingedHorror.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.WingedHorror.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Winged Horror\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794733/6CBB778169272A5BB7A0E0DDBDCA7FBA00A5AE02/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n },\r\n },\r\n -- TODO egg\r\n },\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.ValrathCommander.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Valrath Commander\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794290/B58FE4D8D214B4C40855BC0FFF08A0339D45543B/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Tracker\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Valrath Savage\",\r\n },\r\n },\r\n },\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.TheSightlessEye.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"The Sightless Eye\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075794029/BEBA0196A38539E3C3B18EFD40608DD69B99EB0F/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Deep Terror\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.TheGloom.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerBossEnemy(\"The Gloom\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793925/DC055D7E7A3FA73F09D6411DFEF944F642DB9DEE/\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.TheColorless.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"The Colorless\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793862/F60344147C15302CE4505BBE607635259D2AEA74/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Sun Demon\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.TheBetrayer.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"The Betrayer\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793800/7E6A7025C04E542410DB9AA6A833E39D048818A4/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Giant Viper\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.PrimeDemon.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Prime Demon\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075793197/A46A41ADF493309B517058D3905052C721C51A6D/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Frost Demon\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Wind Demon\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Night Demon\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Earth Demon\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Flame Demon\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Sun Demon\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.MercilessOverseer.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Merciless Overseer\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792966/E8305F06E7CB85FF7E8C2C492094D2EB682C7151/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Vermling Scout\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.ManifestationOfCorruption.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\nlocal R = require(\"api.Resource\")\n\nEnemyApi.registerBossEnemy(\"Manifestation of Corruption\", {\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792891/AE700EEAC70651C34F095E4F14D032B39EF76C38/\",\n abilityDeck = \"Manifestation of Corruption\",\n spawn = {\n {\n element = {\n type = R.ElementType.Enemy,\n name = \"Flame Demon\",\n },\n },\n {\n element = {\n type = R.ElementType.Enemy,\n name = \"Night Demon\",\n },\n },\n {\n element = {\n type = R.ElementType.Enemy,\n name = \"Valrath Tracker\",\n },\n },\n {\n element = {\n type = R.ElementType.Enemy,\n name = \"Valrath Savage\",\n },\n },\n {\n element = {\n type = R.ElementType.Enemy,\n name = \"Aesther Scout\",\n },\n },\n {\n element = {\n type = R.ElementType.Enemy,\n name = \"Black Imp\",\n },\n },\n {\n element = {\n type = R.ElementType.Trap,\n name = \"Spike Trap\",\n conditions = { \"Disarm\", \"Poison\" },\n },\n },\n -- TODO Naaret\n }\n})\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.Jekserah.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Jekserah\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792555/6409C9B92DEECD843A2667502C81536FA29113AD/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Corpse\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.InoxBodyGuard.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerBossEnemy(\"Inox Bodyguard\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792320/2F86EFC0ECEDC159DD7B48683D04CC8AA87C4F3E/\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.HumanCommander.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Human Commander\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792165/CDF8372CB62104BBAD0F732DE96442B0156251A3/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Guard\",\r\n },\r\n },\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"City Archer\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.ElderDrake.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerBossEnemy(\"Elder Drake\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791689/28A1B47C29581EC00A58647A10416C0350BDB4C5/\",\r\n -- TODO Zephyr\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.DarkRider.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Dark Rider\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791470/77C4F9434F8700B67B25522AACD1D16FFEB6600D/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Forest Imp\",\r\n },\r\n },\r\n }\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.CaptainOfTheGuard.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerBossEnemy(\"Captain of the Guard\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791097/63118851072A989B6B9432D9AF9CB62261BEB461/\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Boss.BanditCommander.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerBossEnemy(\"Bandit Commander\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075790870/264D1A7A6C93A37894B204DD6CDB555F11B8B5BD/\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Enemy,\r\n name = \"Living Bones\",\r\n },\r\n },\r\n },\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Ashblade.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Ashblade\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/ashblade/ma-ab-8.png\",\r\n },\r\n }\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Ashblade.AestherAshblade.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Ashblade.AestherAshblade.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Aesther Ashblade\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075790596/A723A4F1B5F6E9149A6C69D89AC29FF30492247C/\",\r\n abilityDeck = \"Ashblade\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Archer.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Archer\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-7.png\",\r\n spawn = {\r\n {\r\n element = {\r\n type = R.ElementType.Trap,\r\n name = \"Spike Trap\",\r\n damage = 3,\r\n },\r\n }\r\n },\r\n },\r\n [8] = {\r\n image = \"https://raw.githubusercontent.com/any2cards/gloomhaven/master/images/monster-ability-cards/archer/ma-ar-8.png\",\r\n },\r\n }\r\n})\r\n\r\nrequire(\"Game.Gloomhaven.Monsters.Archer.BanditArcher.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Archer.CityArcher.MonsterInfo\")\r\nrequire(\"Game.Gloomhaven.Monsters.Archer.InoxArcher.MonsterInfo\")\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Archer.InoxArcher.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Inox Archer\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075792236/3D8BA2902D7920E02B3782DA0E379442601DA1C8/\",\r\n abilityDeck = \"Archer\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Archer.CityArcher.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"City Archer\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075791235/1B01BBBC4EF8BE68D12391AAED6309BF39833FA3/\",\r\n abilityDeck = \"Archer\",\r\n})\r\n\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.Archer.BanditArcher.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemy(\"Bandit Archer\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075790807/F181B084F5CA91BC0E0F75C682C6993C4BCBDA43/\",\r\n abilityDeck = \"Archer\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Monsters.AncientArtillery.MonsterInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EnemyApi = require(\"api.EnemyApi\")\r\n\r\nEnemyApi.registerEnemyAbilityDeck(\"Ancient Artillery\", {\r\n abilities = {\r\n [1] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-1.png\",\r\n },\r\n [2] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-2.png\",\r\n },\r\n [3] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-3.png\",\r\n },\r\n [4] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-4.png\",\r\n },\r\n [5] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-5.png\",\r\n },\r\n [6] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-6.png\",\r\n },\r\n [7] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-7.png\",\r\n },\r\n [8] = {\r\n image = \"https://github.com/any2cards/gloomhaven/master/images/monster-ability-cards/ancient-artillery/ma-aa-8.png\",\r\n },\r\n }\r\n})\r\n\r\nEnemyApi.registerEnemy(\"Ancient Artillery\", {\r\n icon = \"http://cloud-3.steamusercontent.com/ugc/1688272925075790738/993479463520298E911DB4BFB7CD732FA29344A7/\",\r\n abilityDeck = \"Ancient Artillery\",\r\n})\nend)\n__bundle_register(\"Game.Gloomhaven.Conditions\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ConditionApi = require(\"api.ConditionApi\")\nlocal UiApi = require(\"api.UiApi\")\n\n-- TODO add image for missing effects\n\nConditionApi.registerCondition(\"Disarm\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671672270/3DD70341FE7C762F57930DCB123FC3B3F669E70D/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766656381/EF716F5A021E59A8E5A2AB05AAD7A2779C4564A8/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0A3}\", \"#69797C\"),\n})\n\nConditionApi.registerCondition(\"Immobilize\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671673391/877AC6C5C993070B4ACE2F0BA540FD277C9FC174/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766654664/84F766538593F1248F4F57907CB7A416F65C5282/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0A5}\", \"#9B342E\"),\n})\n\nConditionApi.registerCondition(\"Invisible\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671674221/401A435DA860B2CA7E87D570905F58622C69229E/\",\n renderedMarkup = UiApi.conditionText(\"\\u{E0A7}\", \"#141413\"),\n})\n\nConditionApi.registerCondition(\"Muddle\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671681965/01EAE5A55C229B9F1067AF64A398667F3F25E841/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766649540/623C36ABECA67CE85A271B5E43644FD914300341/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0A8}\", \"#6F5744\"),\n})\n\nConditionApi.registerCondition(\"Poison\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671677797/DEAEC4FAB2B54FEA069B0C8995DB7E97160767F3/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766650742/006AE5584AEB4E7E7343E14C0749B0B8F5D0CF62/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0AA}\", \"#7B7E67\"),\n})\n\nConditionApi.registerCondition(\"Strengthen\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671675865/B73B248633B5C6A8AC44EC7BBBDE56D6632FD208/\",\n renderedMarkup = UiApi.conditionText(\"\\u{E0AC}\", \"#4A96D2\"),\n})\n\nConditionApi.registerCondition(\"Stun\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671678940/616CC0951CB17024448D3CBC7E5CA9D3A05D7555/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766657508/F15D80FEA22D73B64C8BD23F49EC3C08018FFEBE/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0AD}\", \"#2D4668\"),\n})\n\nConditionApi.registerCondition(\"Wound\", {\n image = \"http://cloud-3.steamusercontent.com/ugc/83721958671672778/8AD44E86BD73836627A25E71B6B3DA081A0E64D6/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766651868/C5A8B87571357AFD137EC217C7A66D6E698E840F/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0AF}\", \"#E46125\"),\n})\n\nConditionApi.registerEffect({\n name = \"Advantage\",\n text = \"A\",\n})\n\nConditionApi.registerEffect({\n name = \"Attack\",\n text = \"Atk\",\n renderedMarkup = UiApi.solidText(\"\\u{E000}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Attackers gain Disadvantage\",\n text = \"AgD\",\n})\n\nConditionApi.registerEffect({\n name = \"Bless\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726986639/FDF514FDA20826ACA054BE5CBA579AE6FB25A263/\",\n renderedMarkup = UiApi.conditionText(\"\\u{E0A0}\", \"#D6A529\"),\n})\n\nlocal anyElement = UiApi.coloredText(\"\\u{E078}\", \"#1E2B35\")\n .. UiApi.coloredText(\"\\u{E079}\", \"#EEAC1E\")\n .. UiApi.coloredText(\"\\u{E07A}\", \"#89A43C\")\n .. UiApi.coloredText(\"\\u{E07B}\", \"#E05325\")\n .. UiApi.coloredText(\"\\u{E07C}\", \"#1AC2EC\")\n .. UiApi.coloredText(\"\\u{E07D}\", \"#9AB0B7\")\n .. UiApi.solidText(\"\\u{E076}\")\nlocal consume = UiApi.coloredText(\"\\u{E067}\", \"#E16868\") .. UiApi.coloredText(\"\\u{E077}\", \"#FFF\")\n\nConditionApi.registerEffect({\n name = \"Consume Air\",\n text = \"CAir\",\n renderedMarkup = UiApi.consumeElementText(\"\\u{E070}\", \"#9AB0B7\")\n})\n\nConditionApi.registerEffect({\n name = \"Consume Any\",\n text = \"CAir\",\n renderedMarkup = anyElement .. consume\n})\n\nConditionApi.registerEffect({\n name = \"Consume Dark\",\n text = \"CDar\",\n renderedMarkup = UiApi.consumeElementText(\"\\u{E071}\", \"#1E2B35\")\n})\n\nConditionApi.registerEffect({\n name = \"Consume Earth\",\n text = \"CEar\",\n renderedMarkup = UiApi.consumeElementText(\"\\u{E072}\", \"#89A43C\")\n})\n\nConditionApi.registerEffect({\n name = \"Consume Fire\",\n text = \"CFir\",\n renderedMarkup = UiApi.consumeElementText(\"\\u{E073}\", \"#E05325\")\n})\n\nConditionApi.registerEffect({\n name = \"Consume Ice\",\n text = \"CIce\",\n renderedMarkup = UiApi.consumeElementText(\"\\u{E074}\", \"#1AC2EC\")\n})\n\nConditionApi.registerEffect({\n name = \"Consume Light\",\n text = \"CLig\",\n renderedMarkup = UiApi.consumeElementText(\"\\u{E075}\", \"#EEAC1E\")\n})\n\nConditionApi.registerEffect({\n name = \"Curse\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335237445/F10A40684FA3670D0E110191611676C44F489748/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766658589/7243BAFBA97A26003905F2F528B2A1287ED3F628/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0A2}\", \"#7C57A3\"),\n})\n\nConditionApi.registerEffect({\n name = \"Damage\",\n text = \"Dmg\",\n renderedMarkup = UiApi.solidText(\"\\u{E018}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Fly\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335240329/FEB35AD038A6F0645485F8B62F7D67EB8DEEFE77/\",\n renderedMarkup = UiApi.solidText(\"\\u{E00D}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Generate Air\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726768884/CC8308C7D7DD6185F3EFA99F452C83101E1E95E3/\",\n renderedMarkup = UiApi.elementText(\"\\u{E070}\", \"#9AB0B7\")\n})\n\nConditionApi.registerEffect({\n name = \"Generate Any\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726984504/2F962A64E2A9888C90856D9C150EF81089745137/\",\n renderedMarkup = anyElement,\n})\n\nConditionApi.registerEffect({\n name = \"Generate Dark\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726982499/9C14793623433565A9D6E5B3C608C1F6FBB84C76/\",\n renderedMarkup = UiApi.elementText(\"\\u{E071}\", \"#1E2B35\")\n})\n\nConditionApi.registerEffect({\n name = \"Generate Earth\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726773039/E83D1E9F06938B3B5355F0A6A49D98B78B1F2594/\",\n renderedMarkup = UiApi.elementText(\"\\u{E072}\", \"#89A43C\")\n})\n\nConditionApi.registerEffect({\n name = \"Generate Fire\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726770166/CD369CF7C22FAD932A71C65BA811AE137234270D/\",\n renderedMarkup = UiApi.elementText(\"\\u{E073}\", \"#E05325\")\n})\n\nConditionApi.registerEffect({\n name = \"Generate Ice\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726771564/3C69B7D1E66E8FC022B2BD77CC427E8AEC5D9331/\",\n renderedMarkup = UiApi.elementText(\"\\u{E074}\", \"#1AC2EC\")\n})\n\nConditionApi.registerEffect({\n name = \"Generate Light\",\n image = \"http://cloud-3.steamusercontent.com/ugc/938309024726774294/5BBAC718ED55E90563D2B24848DE3ACAEBD76FE9/\",\n renderedMarkup = UiApi.elementText(\"\\u{E075}\", \"#EEAC1E\")\n})\n\nConditionApi.registerEffect({\n name = \"Generate Light or Dark\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1478820976197633400/506D08CF7FBB2F6100F15A80CD54D9EE2BF35181/\",\n})\n\nConditionApi.registerEffect({\n name = \"Heal\",\n text = \"Heal\",\n renderedMarkup = UiApi.solidText(\"\\u{E004}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Jump\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1948388036162552120/8D950282276104420AB74B5BB3F898382C1774BA/\",\n renderedMarkup = UiApi.solidText(\"\\u{E008}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Loot\",\n text = \"Loot\",\n renderedMarkup = UiApi.solidText(\"\\u{E007}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Move\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1948388036162545819/12E976BAB0EFB30F147805B7172AF46F4A055821/\",\n renderedMarkup = UiApi.solidText(\"\\u{E002}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Pierce\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335248486/100217496DD5B5D150F31B0FFB080E7C7283705D/\",\n renderedMarkup = UiApi.conditionText(\"\\u{E0A9}\", \"#C78B4E\"),\n})\n\nConditionApi.registerEffect({\n name = \"Pull\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335249804/4C73073477F29074DE05B08F3F86D9FEF2705881/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766660950/32B6123D44A700D801FB6D9506A1869CADC997F0/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0B0}\", \"#4F504F\"),\n})\n\nConditionApi.registerEffect({\n name = \"Push\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335251134/78C03FAA36481E66B09206BC95EDD19F0C0FF812/\",\n immunity = {\n image = \"http://cloud-3.steamusercontent.com/ugc/1719786285766659957/983E46854AE174881307D59D5752A4869C6CB656/\",\n },\n renderedMarkup = UiApi.conditionText(\"\\u{E0B1}\", \"#4F504F\"),\n})\n\nConditionApi.registerEffect({\n name = \"Range\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1948388036162544849/C5D3C835BAD31C6988B5E6E38B13632EB6FB3384/\",\n renderedMarkup = UiApi.solidText(\"\\u{E001}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Retaliate\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335255371/35023C8E2969F322C01150148B92D827E6DB560C/\",\n renderedMarkup = UiApi.solidText(\"\\u{E00A}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Shield\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335259632/505B1D168C648AF41FC04DB3FE78E9EC27531346/\",\n renderedMarkup = UiApi.solidText(\"\\u{E017}\"),\n})\n\nConditionApi.registerEffect({\n name = \"Target\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1019444160335261306/4CA039FF943FF9446707CA42E15ED87986D4E734/\",\n renderedMarkup = UiApi.solidText(\"\\u{E00C}\"),\n})\n\nend)\n__bundle_register(\"ContentHandler\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal Version = require(\"lib.Version\")\n\nlocal R = require(\"api.Resource\")\n\nrequire(\"asset.ParticleEffectsHandler\")\nrequire(\"asset.AssetManager\")\nrequire(\"asset.CustomContentManager\")\n\nrequire(\"api.Deprecated\")\n\nlocal ContentHandler = {}\n\n---@shape __gloom_ApiCheck\n---@field caller GUID\n---@field version seb_Version\n\n---@param apiCheck __gloom_ApiCheck\nfunction checkApiVersion(apiCheck)\n Logger.debug(\"%s is using api version %s\", apiCheck.caller, apiCheck.version)\n\n local contentName = (--[[---@not nil]] getObjectFromGUID(apiCheck.caller)).getName()\n local apiVersion = Version(apiCheck.version)\n local version = Version(R.Version)\n\n if apiVersion.isBeta and not version.isBeta then\n Logger.warn(\"The custom content '%s' was build using the Beta version of the mod, so it might not work correctly.\"\n .. \"\\nPlease update the custom content or inform its creator to update it.\", contentName)\n end\n\n if apiVersion.isAfter(version) then\n Logger.warn(\"The custom content '%s' was build using a newer version of the API: %s.\"\n .. \"\\nThis mod only supports versions up to %s, so the content might not work correctly.\"\n .. \"\\nPlease update to the correct mod version to use the content.\",\n contentName, tostring(apiVersion), tostring(version))\n end\n\n if apiVersion.major < version.major\n or apiVersion.minor < version.minor\n or (apiVersion.isBefore(version) and apiVersion.isBeta and version.isBeta)\n then\n Logger.warn(\"The custom content '%s' was build using an older version of the API: %s.\"\n .. \"\\nThe content might not work correctly.\"\n .. \"\\nPlease update the custom content or inform its creator to update it.\",\n contentName, tostring(apiVersion))\n end\nend\n\nreturn ContentHandler\n\nend)\n__bundle_register(\"api.Deprecated\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\n\nlocal Deprecated = {}\n\n---@type set\nlocal warnedOn = {}\n\nlocal function logWarning(base, name, obj)\n local fullName = base .. name\n if not warnedOn[fullName] then\n Logger.warn(\"Using the API '%s' with function '%s' is deprecated (from object %s).\\n\"\n .. \"Please update the API script to use the latest version.\\n\"\n .. \"It will still work as it redirects to the correct function, but will get removed in the future.\", base, name, obj)\n warnedOn[fullName] = true\n end\nend\n\nlocal function deprecated(base, name, newName, paramNames)\n _G[name] = function(params)\n logWarning(base, name, params.__caller or \"Global\")\n\n local newParams = {}\n for i, paramName in ipairs(paramNames) do\n newParams[i] = params[paramName]\n end\n return _G[\"api_\" .. base .. \"_\" .. newName](newParams)\n end\nend\n\ndeprecated(\"character\", \"__getPlayerObjects\", \"getPlayerObjects\", { \"player\" })\ndeprecated(\"character\", \"__placePlayerObject\", \"placePlayerObject\", { \"player\", \"object\", \"placement\" })\ndeprecated(\"character\", \"__getCharacterLevel\", \"getLevel\", { \"xp\" })\n\ndeprecated(\"class\", \"registerCustomClass\", \"registerClass\", { \"name\", \"info\" })\ndeprecated(\"class\", \"getClasses\", \"getClasses\", {})\ndeprecated(\"class\", \"getClass\", \"getClass\", { \"className\" })\ndeprecated(\"class\", \"getAbility\", \"getAbility\", { \"className\", \"abilityName\" })\ndeprecated(\"class\", \"getAbilityForUnknownClass\", \"getAbilityForUnknownClass\", { \"card\" })\ndeprecated(\"class\", \"__getSpawnableElementsForClass\", \"getSpawnableElements\", { \"className\" })\n\ndeprecated(\"component\", \"__spawnElement\", \"spawnElement\", { \"element\" })\ndeprecated(\"component\", \"__hasElement\", \"hasElement\", { \"elementType\", \"elementName\" })\ndeprecated(\"component\", \"__registerOverlay\", \"registerOverlay\", { \"overlay\" })\ndeprecated(\"component\", \"__getOverlays\", \"getOverlays\", {})\ndeprecated(\"component\", \"__registerSummon\", \"registerSummon\", { 1 })\ndeprecated(\"component\", \"__getSummons\", \"getSummons\", {})\ndeprecated(\"component\", \"notifyCustomAssetsChanged\", \"requestAssetUpdate\", {})\n\ndeprecated(\"condition\", \"__registerCondition\", \"registerCondition\", { \"name\", \"conditionInfo\" })\ndeprecated(\"condition\", \"__registerTracker\", \"registerTracker\", { \"className\", \"trackerInfo\" })\ndeprecated(\"condition\", \"__registerEffect\", \"registerEffect\", { \"effect\" })\ndeprecated(\"condition\", \"__getConditions\", \"getConditions\", {})\ndeprecated(\"condition\", \"__getCondition\", \"getCondition\", { 1 })\ndeprecated(\"condition\", \"__getClassTrackers\", \"getClassTrackers\", {})\ndeprecated(\"condition\", \"__getClassTracker\", \"getClassTracker\", { \"className\" })\ndeprecated(\"condition\", \"__getEffects\", \"getEffects\", {})\ndeprecated(\"condition\", \"__getEffect\", \"getEffect\", { \"name\" })\ndeprecated(\"condition\", \"__getImmunities\", \"getImmunities\", { \"immunities\" })\n\ndeprecated(\"enemy\", \"__getSpawnableElementsForEnemy\", \"getSpawnableElements\", { \"enemyName\" })\ndeprecated(\"enemy\", \"__registerEnemyAbilityDeck\", \"registerEnemyAbilityDeck\", { \"name\", \"abilities\" })\ndeprecated(\"enemy\", \"__registerEnemy\", \"registerEnemy\", { \"name\", \"enemyInfo\" })\ndeprecated(\"enemy\", \"__registerBossEnemy\", \"registerBossEnemy\", { \"name\", \"bossInfo\" })\ndeprecated(\"enemy\", \"__getEnemies\", \"getEnemies\", {})\ndeprecated(\"enemy\", \"__getEnemy\", \"getEnemy\", { 1 })\n\ndeprecated(\"options\", \"__registerOptionGroup\", \"registerOptionGroup\", { \"optionGroup\" })\ndeprecated(\"options\", \"__registerOption\", \"registerOption\", { \"groupName\", \"optionName\", \"option\" })\ndeprecated(\"options\", \"__getOptions\", \"getOptions\", { })\ndeprecated(\"options\", \"__getOptionValues\", \"getValues\", { })\ndeprecated(\"options\", \"__setOptionValues\", \"setValues\", { \"values\" })\ndeprecated(\"options\", \"__getOption\", \"getOption\", { \"name\" })\ndeprecated(\"options\", \"__getOptionValue\", \"getValue\", { \"name\" })\ndeprecated(\"options\", \"__setOptionValue\", \"setValue\", { 1, 2 })\n\ndeprecated(\"scenario\", \"registerScenario\", \"registerScenario\", { \"name\", \"scenario\" })\ndeprecated(\"scenario\", \"__registerRandomPool\", \"registerRandomPool\", { \"randomPool\" })\ndeprecated(\"scenario\", \"__getCampaigns\", \"getCampaigns\", {})\ndeprecated(\"scenario\", \"loadCampaign\", \"loadCampaign\", { 1 })\ndeprecated(\"scenario\", \"unlockScenario\", \"unlockScenario\", { \"name\" })\ndeprecated(\"scenario\", \"__calculateFormula\", \"calculateFormula\", { \"formula\", \"context\" })\ndeprecated(\"scenario\", \"__getScenarioLevel\", \"getScenarioLevel\", {})\ndeprecated(\"scenario\", \"__getActiveScenario\", \"getActiveScenario\", {})\ndeprecated(\"scenario\", \"__getCharacterCount\", \"getCharacterCount\", {})\ndeprecated(\"scenario\", \"__getRandomPool\", \"getRandomPool\", { \"name\" })\ndeprecated(\"scenario\", \"__getRandomPools\", \"getRandomPools\", {})\ndeprecated(\"scenario\", \"revealRoom\", \"revealRooms\", { \"room\" })\ndeprecated(\"scenario\", \"getTreasureCard\", \"revealTreasure\", { \"card\" })\n\ndeprecated(\"theme\", \"__registerTheme\", \"registerTheme\", { \"theme\" })\ndeprecated(\"theme\", \"__getThemes\", \"getThemes\", {})\ndeprecated(\"theme\", \"__selectThemes\", \"selectThemes\", { \"themes\" })\ndeprecated(\"theme\", \"__getConditionTheme\", \"getConditionTheme\", { \"condition\", \"element\" })\ndeprecated(\"theme\", \"__getEffectTheme\", \"getEffectTheme\", { 1, 2 })\n\ndeprecated(\"ui\", \"__renderText\", \"renderText\", { 1, 2, 3 })\ndeprecated(\"ui\", \"__renderAreaTo\", \"renderAreaTo\", { 1, 2, 3 })\n\n_G[\"__createActionButton\"] = function(params)\n local obj = getObjectFromGUID(params.element)\n logWarning(\"action\", \"__createActionButton\", params.__caller)\n _G[\"api_action_createActionButton\"](obj, params.action, params.buttonPosition, params.functionName)\nend\n\n_G[\"__performAction\"] = function(params)\n local obj = getObjectFromGUID(params.element)\n logWarning(\"action\", \"__performAction\", params.__caller)\n if params.done then\n _G[\"api_action_performDoneAction\"](obj, params.action)\n else\n _G[\"api_action_performAction\"](obj, params.action, params.execute)\n end\nend\n\n_G[\"__postPerformAction\"] = function(params)\n local obj = getObjectFromGUID(params.element)\n logWarning(\"action\", \"__postPerformAction\", params.__caller)\n _G[\"api_action_postPerformAction\"](obj, params.action)\nend\n\nreturn Deprecated\n\nend)\n__bundle_register(\"asset.CustomContentManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\nlocal Logger = require(\"lib.Logger\")\n\nlocal ContentCache = require(\"asset.content.ContentCache\")\nlocal ContentTarget = require(\"asset.content.ContentTarget\")\nlocal BagTarget = require(\"asset.content.BagTarget\")\nlocal BackupBagTarget = require(\"asset.content.BackupBagTarget\")\nlocal ClassBoxContentTarget = require(\"asset.content.ClassBoxContentTarget\")\nlocal ConditionalContentTarget = require(\"asset.content.ConditionalContentTarget\")\nlocal ItemDesignContentTarget = require(\"asset.content.ItemDesignContentTarget\")\nlocal NestedBagTarget = require(\"asset.content.NestedBagTarget\")\n\nlocal Event = require(\"Event\")\nlocal R = require(\"Resources\")\nlocal PlayArea = require(\"PlayArea\")\n\nlocal CustomContentManager = {}\n\n---@class __CustomContentManager_this\nlocal this = {}\n\n--- Targets for custom content by tag.\n---@type table\nlocal ContentBags = {\n -- Classes\n [R.Tag.Class.Envelope] = { ClassBoxContentTarget() },\n [R.Tag.Class.Summon] = { BagTarget(R.Tag.Component.BagOfSummons) },\n [R.Tag.PersonalQuest] = { BackupBagTarget(R.Tag.Component.PersonalQuests) },\n -- Scenarios\n [R.Tag.Scenario.Definition] = { BagTarget(R.Tag.Component.BagOfUnlockedScenarios) },\n [R.Tag.Scenario.ExtraContent] = { BagTarget(R.Tag.Component.BagOfExtraContent) },\n -- Monsters\n [R.Tag.Monster.Envelope] = { BagTarget(R.Tag.Component.BagOfMonsters) },\n [R.Tag.Monster.Abilities] = { BagTarget(R.Tag.Component.BagOfMonsterAbilities) },\n -- Overlays\n [R.Tag.Overlay.Corridor] = { BagTarget(\"359306\"), NestedBagTarget(\"022434\", 1) },\n [R.Tag.Overlay.Door] = { BagTarget(\"21be0e\"), NestedBagTarget(\"792794\", 1) },\n [R.Tag.Overlay.DifficultTerrain] = { BagTarget(\"4efdc4\"), NestedBagTarget(\"039913\", 1) },\n [R.Tag.Overlay.HazardousTerrain] = { BagTarget(\"0b51bb\"), NestedBagTarget(\"140183\", 1) },\n [R.Tag.Overlay.Map] = { BagTarget(\"4895cd\"), NestedBagTarget(\"a598b4\", 1) },\n [R.Tag.Overlay.Obstacle] = { BagTarget(\"c912f5\"), NestedBagTarget(\"3af8e9\", 1) },\n [R.Tag.Overlay.Trap] = { BagTarget(\"0a3abe\"), NestedBagTarget(\"45411b\", 1) },\n [R.Tag.Overlay.TreasureChest] = { BagTarget(\"fe14ef\"), NestedBagTarget(\"da6ae9\", 1) },\n [R.Tag.Overlay.Figure] = { NestedBagTarget(R.Guid.Backup, R.Tag.Overlay.Figure) },\n -- Events\n [R.Tag.Event.Road] = { BackupBagTarget(R.Tag.Component.RoadEvents) },\n [R.Tag.Event.City] = { BackupBagTarget(R.Tag.Component.CityEvents) },\n -- Items\n [R.Tag.Item.Item] = {\n ConditionalContentTarget()\n .forTag(R.Tag.Item.Design, ItemDesignContentTarget())\n .forTag(R.Tag.Item.Reward, BackupBagTarget(R.Tag.Component.RewardItems, true))\n .forTag(R.Tag.Item.Solo, BackupBagTarget(R.Tag.Component.SoloRewardItems, true))\n .withDefault(BackupBagTarget(R.Tag.Component.ShopItems, true))\n },\n -- Other\n [R.Tag.Component.Treasure] = { BagTarget(\"fe14ef\") },\n [R.Tag.ConditionStack] = { NestedBagTarget(\"10a47a\", 1) },\n [R.Tag.Component.Book] = { BagTarget(\"17e420\") },\n [R.Tag.Book.Scenarios] = { BagTarget(\"17e420\") },\n [R.Tag.Book.Sections] = { BagTarget(\"17e420\") },\n}\n\n---@type table\nlocal ContentHandlers = {\n [R.Tag.ConditionStack] = PlayArea.createConditionStacks\n}\n\n--- Targets for custom content by tag that also have the \"Locked Content\" tag.\n---@type table\nlocal ContentBagsLocked = {\n -- Classes\n [R.Tag.Class.Envelope] = { NestedBagTarget(R.Guid.Gamebox, R.Tag.Component.BagOfLockedCharacters) },\n -- Scenario\n [R.Tag.Scenario.Definition] = { BagTarget(R.Tag.Component.BagOfLockedScenarios) },\n}\n\n---@param object tts__Object\nfunction this.onObjectSpawn(object)\n if object.hasTag(R.Tag.CustomContent) and Object.memo(object) ~= \"Unpacked\" then\n Wait.condition(function()\n this.unpackCustomContent(object)\n end,\n function()\n return Object.isLoaded(object)\n end)\n elseif object.hasTag(R.Tag.Component.Mock) then\n destroyObject(object)\n end\nend\n\nfunction this.unpackCustomContent(object)\n ContentCache.clear()\n local objectData = --[[---@type tts__ContainerState]] object.getData()\n local unpacked = this.unpackContainerContent(objectData)\n\n for guid, targetData in pairs(ContentCache.getAll()) do\n local targetObject = --[[---@not nil]] getObjectFromGUID(guid)\n targetObject.destruct()\n spawnObjectData({ data = targetData })\n end\n\n local totalCount = 0\n for _, count in pairs(unpacked) do\n totalCount = totalCount + count\n end\n\n Logger.info(\"Registered custom content '%s' with %s elements.\", Object.name(object), totalCount)\n object.memo = \"Unpacked\"\n\n for tag, _ in pairs(unpacked) do\n local postHandler = ContentHandlers[tag]\n if postHandler then\n postHandler()\n end\n end\nend\n\n---@param objectData tts__ContainerState\nfunction this.unpackContainerContent(objectData)\n local function getContentHandler(content)\n local contentBags\n if Object.hasTag(content, R.Tag.Component.LockedContent) then\n contentBags = ContentBagsLocked\n else\n contentBags = ContentBags\n end\n\n for _, tag in ipairs(Object.tags(content)) do\n local contentBagDefinition = contentBags[tag]\n if contentBagDefinition then\n return tag, contentBagDefinition\n end\n end\n end\n\n ---@type table\n local unpacked = {}\n\n ---@param tag string\n ---@param count integer\n local function addToUnpacked(tag, count)\n if not unpacked[tag] then\n unpacked[tag] = 0\n end\n\n unpacked[tag] = unpacked[tag] + count\n end\n\n for _, content in ipairs(objectData.ContainedObjects or {}) do\n local tag, targets = getContentHandler(content)\n if targets then\n this.putItAway(content, tag, targets)\n addToUnpacked(tag, 1)\n end\n\n if Object.isContainer(content) then\n local subUnpacked = this.unpackContainerContent(--[[---@type tts__ContainerState]] content)\n for unpackedTag, unpackedCount in pairs(subUnpacked) do\n addToUnpacked(unpackedTag, unpackedCount)\n end\n end\n end\n\n return unpacked\nend\n\n---@param content tts__ObjectState\n---@param targets gloom_ContentTarget[]\nfunction this.putItAway(content, tag, targets)\n local contentName = Object.name(content)\n for _, target in ipairs(targets) do\n Logger.debug(\"Placing '%s' into target '%s'\", Object.name(content), target.getName())\n\n local result = target.place(content, tag)\n if result == ContentTarget.Result.Duplicate then\n Logger.warn(\"Content with name '%s' already exists for type %s\", contentName, tag)\n elseif result == ContentTarget.Result.Error then\n Logger.warn(\"Tried to add custom content '%s' at target %s, but couldn't locate it\", contentName, target.getName())\n else\n Logger.debug(\"placed custom content '%s' of type %s\", contentName, tag)\n end\n end\nend\n\nEvent.registerHandler(\"onObjectSpawn\", this.onObjectSpawn)\n\nreturn CustomContentManager\n\nend)\n__bundle_register(\"PlayArea\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal R = require(\"api.Resource\")\n\nlocal PlayArea = {}\n\nlocal this = {}\n\nlocal CONDITION_STACKS_GUID = \"10a47a\"\n\nfunction PlayArea.createConditionStacks()\n this.destroyConditionStacks()\n this.unpackConditionStacks()\nend\n\nfunction this.destroyConditionStacks()\n for _, obj in ipairs(getObjectsWithTag(R.Tag.ConditionStack)) do\n obj.destruct()\n end\nend\n\nfunction this.unpackConditionStacks()\n local bag = --[[---@type tts__Bag]] getObjectFromGUID(CONDITION_STACKS_GUID)\n local bagContent = --[[---@type tts__BagState]] bag.getData().ContainedObjects[1]\n\n local rotationTable = {}\n rotationTable[\"ad805d\"] = { 0, 90, 0 }\n rotationTable[\"c0dd7c\"] = { 0, 90, 0 }\n rotationTable[\"2b445e\"] = { 0, 90, 0 }\n rotationTable[\"33bf9e\"] = { 0, 90, 0 }\n rotationTable[\"5fbb5e\"] = { 0, 90, 0 }\n\n for i, stack in ipairs(bagContent.ContainedObjects) do\n local c = i - 1\n local xPos = 0\n if c > 0 then\n if c % 2 == 0 then\n xPos = (c / 2) * 1.75\n else\n xPos = ((c + 1) / 2) * -1.75\n end\n end\n\n stack.Locked = true\n\n spawnObjectData({\n data = stack,\n position = { xPos, 1.65, -13.30 },\n rotation = rotationTable[stack.GUID] or { 0, 0, 0 },\n })\n end\nend\n\nreturn PlayArea\n\nend)\n__bundle_register(\"asset.content.NestedBagTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\r\nlocal Search = require(\"lib.Search\")\r\n\r\nlocal ContentTarget = require(\"asset.content.ContentTarget\")\r\n\r\n----@class gloom_NestedBagContentTarget : gloom_ContentTarget\r\n\r\n---@class gloom_NestedBagContentTarget_static\r\n---@overload fun(parentId: gloom_ContentTarget_Id, childId: gloom_ContentTarget_ChildId): gloom_NestedBagContentTarget\r\n---@overload fun(parentId: gloom_ContentTarget_Id, childId: gloom_ContentTarget_ChildId, allowDuplicates: boolean): gloom_NestedBagContentTarget\r\nlocal NestedBagTarget = {}\r\n\r\n---@param parentId gloom_ContentTarget_Id\r\n---@param childId gloom_ContentTarget_ChildId\r\n---@param allowDuplicates boolean\r\n---@return gloom_NestedBagContentTarget\r\nlocal function new(parentId, childId, allowDuplicates)\r\n local self = --[[---@type gloom_NestedBagContentTarget]] ContentTarget()\r\n\r\n function self.getName()\r\n return \"Nested Bag \" .. childId .. \" at \" .. parentId\r\n end\r\n\r\n ---@param data tts__ContainerState\r\n ---@return tts__ContainerState\r\n local function getChildData(data)\r\n if type(childId) == \"number\" then\r\n return --[[---@type tts__ContainerState]] data.ContainedObjects[--[[---@type integer]] childId]\r\n end\r\n\r\n for _, child in ipairs(data.ContainedObjects) do\r\n if Object.hasTag(child, --[[---@type tts__Object_Tag]] childId) then\r\n return --[[---@type tts__ContainerState]] child\r\n end\r\n end\r\n end\r\n\r\n function self.place(content)\r\n local data = self.getObjectData(parentId)\r\n if not data then\r\n return ContentTarget.Result.Error\r\n end\r\n\r\n local childData = getChildData(data)\r\n if not childData then\r\n return ContentTarget.Result.Error\r\n end\r\n\r\n childData.ContainedObjects = childData.ContainedObjects or {}\r\n\r\n if not allowDuplicates\r\n and Search.inContainerData(data, { name = Object.name(content) }) ~= nil\r\n then\r\n return ContentTarget.Result.Duplicate\r\n end\r\n\r\n table.insert(childData.ContainedObjects, content)\r\n\r\n return ContentTarget.Result.Success\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(NestedBagTarget, {\r\n ---@param parentId gloom_ContentTarget_Id\r\n ---@param childId gloom_ContentTarget_ChildId\r\n ---@param allowDuplicates boolean\r\n ---@return gloom_NestedBagContentTarget\r\n __call = function(_, parentId, childId, allowDuplicates)\r\n return new(parentId, childId, allowDuplicates)\r\n end\r\n})\r\n\r\nreturn NestedBagTarget\r\n\nend)\n__bundle_register(\"asset.content.ContentTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal StringUtil = require(\"lib.StringUtil\")\r\n\r\nlocal ContentCache = require(\"asset.content.ContentCache\")\r\n\r\n---@class gloom_ContentTarget_static\r\n---@overload fun(cache: gloom_ContentTarget_Cache): gloom_ContentTarget\r\nlocal ContentTarget = {}\r\n\r\n---@class gloom_ContentTarget\r\n\r\n---@alias gloom_ContentTarget_Id GUID | tts__Object_Tag\r\n---@alias gloom_ContentTarget_ChildId gloom_ContentTarget_Id | integer\r\n\r\n---@alias gloom_ContentTarget_Cache table\r\n\r\n---@alias gloom_ContentTarget_Result 1 | 2 | 3\r\nContentTarget.Result = {\r\n Success = 1,\r\n Error = 2,\r\n Duplicate = 3,\r\n}\r\n\r\nlocal function new()\r\n local self = --[[---@type gloom_ContentTarget]] {}\r\n\r\n ---@return string\r\n function self.getName()\r\n return \"nil\"\r\n end\r\n\r\n ---@param content tts__ObjectState\r\n ---@param tag tts__Object_Tag\r\n ---@return gloom_ContentTarget_Result\r\n function self.place(content, tag)\r\n return ContentTarget.Result.Error\r\n end\r\n\r\n ---@param targetId gloom_ContentTarget_Id\r\n ---@return tts__ContainerState\r\n function self.getObjectData(targetId)\r\n ---@type tts__Bag\r\n local object\r\n\r\n if StringUtil.isGuid(targetId) then\r\n object = --[[---@type tts__Bag]] getObjectFromGUID(--[[---@type GUID]] targetId)\r\n else\r\n object = --[[---@type tts__Bag]] getObjectsWithTag(--[[---@type tts__Object_Tag]] targetId)[1]\r\n end\r\n\r\n if not object then\r\n return nil\r\n end\r\n\r\n local cachedData = ContentCache.get(object)\r\n if cachedData then\r\n return --[[---@not nil]] cachedData\r\n end\r\n\r\n local data = object.getData()\r\n ContentCache.set(object, data)\r\n return data\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(ContentTarget, {\r\n ---@return gloom_ContentTarget\r\n __call = function(_)\r\n return new()\r\n end\r\n})\r\n\r\nreturn ContentTarget\r\n\nend)\n__bundle_register(\"asset.content.ContentCache\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ContentCache = {}\r\n\r\n---@type table\r\nlocal cache = {}\r\n\r\nfunction ContentCache.clear()\r\n cache = {}\r\nend\r\n\r\n---@param object tts__Object\r\n---@return nil | tts__ContainerState\r\nfunction ContentCache.get(object)\r\n local cached = cache[object.getGUID()]\r\n return cached\r\nend\r\n\r\n---@param object tts__Object\r\n---@param data tts__ContainerState\r\nfunction ContentCache.set(object, data)\r\n cache[object.getGUID()] = data\r\nend\r\n\r\nfunction ContentCache.getAll()\r\n return cache\r\nend\r\n\r\nreturn ContentCache\r\n\nend)\n__bundle_register(\"asset.content.ItemDesignContentTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\r\n\r\nlocal BackupBagTarget = require(\"asset.content.BackupBagTarget\")\r\nlocal R = require(\"Resources\")\r\n\r\n----@class gloom_ItemDesignContentTarget : gloom_ContentTarget\r\n\r\n---@class gloom_ItemDesignContentTarget_static\r\n---@overload fun(): gloom_ItemDesignContentTarget\r\nlocal ItemDesignContentTarget = {}\r\n\r\nlocal function new()\r\n local self = --[[---@type gloom_ItemDesignContentTarget]] BackupBagTarget(R.Tag.Component.ItemDesigns, true)\r\n\r\n local superPlace = self.place\r\n\r\n function self.place(content, tag)\r\n if Object.isContainer(content) then\r\n return superPlace(content, tag)\r\n end\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(ItemDesignContentTarget, {\r\n ---@return gloom_ItemDesignContentTarget\r\n __call = function(_)\r\n return new()\r\n end\r\n})\r\n\r\nreturn ItemDesignContentTarget\r\n\nend)\n__bundle_register(\"asset.content.BackupBagTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ContentTarget = require(\"asset.content.ContentTarget\")\r\nlocal BagTarget = require(\"asset.content.BagTarget\")\r\nlocal NestedBagTarget = require(\"asset.content.NestedBagTarget\")\r\nlocal R = require(\"Resources\")\r\n\r\n----@class gloom_BackupBagContentTarget : gloom_ContentTarget\r\n\r\n---@class gloom_BackupBagContentTarget_static\r\n---@overload fun(objectId: gloom_ContentTarget_Id): gloom_BagContentTarget\r\n---@overload fun(objectId: gloom_ContentTarget_Id, allowDuplicates: boolean): gloom_BagContentTarget\r\nlocal BackupBagTarget = {}\r\n\r\n---@param objectId gloom_ContentTarget_Id\r\n---@param allowDuplicates boolean\r\n---@return gloom_BackupBagContentTarget\r\nlocal function new(objectId, allowDuplicates)\r\n local self = --[[---@type gloom_BackupBagContentTarget]] ContentTarget()\r\n\r\n local regularTarget = BagTarget(objectId, allowDuplicates)\r\n local backupTarget = NestedBagTarget(R.Guid.Backup, objectId, allowDuplicates)\r\n\r\n function self.getName()\r\n return \"Backup Bag \" .. objectId\r\n end\r\n\r\n function self.place(content, tag)\r\n local regularPlace = regularTarget.place(content, tag)\r\n if regularPlace ~= ContentTarget.Result.Success then\r\n return regularPlace\r\n end\r\n\r\n return backupTarget.place(content, tag)\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(BackupBagTarget, {\r\n ---@param objectId gloom_ContentTarget_Id\r\n ---@param allowDuplicates boolean\r\n ---@return gloom_BackupBagContentTarget\r\n __call = function(_, objectId, allowDuplicates)\r\n return new(objectId, allowDuplicates)\r\n end\r\n})\r\n\r\nreturn BackupBagTarget\r\n\nend)\n__bundle_register(\"asset.content.BagTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ContentTarget = require(\"asset.content.ContentTarget\")\r\n\r\n----@class gloom_BagContentTarget : gloom_ContentTarget\r\n\r\n---@class gloom_BagContentTarget_static\r\n---@overload fun(objectId: gloom_ContentTarget_Id): gloom_BagContentTarget\r\n---@overload fun(objectId: gloom_ContentTarget_Id, allowDuplicates: boolean): gloom_BagContentTarget\r\nlocal BagTarget = {}\r\n\r\n---@param objectId gloom_ContentTarget_Id\r\n---@param allowDuplicates boolean\r\n---@return gloom_BagContentTarget\r\nlocal function new(objectId, allowDuplicates)\r\n local self = --[[---@type gloom_BagContentTarget]] ContentTarget()\r\n\r\n function self.getName()\r\n return \"Bag \" .. objectId\r\n end\r\n\r\n function self.place(content)\r\n local data = self.getObjectData(objectId)\r\n if not data then\r\n return ContentTarget.Result.Error\r\n end\r\n\r\n data.ContainedObjects = data.ContainedObjects or {}\r\n\r\n ---@return boolean\r\n local function contentExists()\r\n for _, contained in ipairs(data.ContainedObjects) do\r\n if contained.Nickname == content.Nickname then\r\n return true\r\n end\r\n end\r\n return false\r\n end\r\n\r\n if not allowDuplicates and contentExists() then\r\n return ContentTarget.Result.Duplicate\r\n end\r\n\r\n table.insert(data.ContainedObjects, content)\r\n\r\n return ContentTarget.Result.Success\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(BagTarget, {\r\n ---@param objectId gloom_ContentTarget_Id\r\n ---@param allowDuplicates boolean\r\n ---@return gloom_BagContentTarget\r\n __call = function(_, objectId, allowDuplicates)\r\n return new(objectId, allowDuplicates)\r\n end\r\n})\r\n\r\nreturn BagTarget\r\n\nend)\n__bundle_register(\"asset.content.ConditionalContentTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\r\n\r\nlocal ContentTarget = require(\"asset.content.ContentTarget\")\r\n\r\n----@class gloom_ConditionalContentTarget : gloom_ContentTarget\r\n\r\n---@class gloom_ConditionalContentTarget_static\r\n---@overload fun(): gloom_ConditionalContentTarget\r\nlocal ConditionalContentTarget = {}\r\n\r\nlocal function new()\r\n local self = --[[---@type gloom_ConditionalContentTarget]] ContentTarget()\r\n\r\n ---@type table\r\n local conditions = {}\r\n\r\n ---@type nil | gloom_ContentTarget\r\n local default = nil\r\n\r\n ---@param tag tts__Object_Tag\r\n ---@param target gloom_ContentTarget\r\n ---@return gloom_ConditionalContentTarget\r\n function self.forTag(tag, target)\r\n conditions[tag] = target\r\n return self\r\n end\r\n\r\n ---@param target gloom_ContentTarget\r\n ---@return gloom_ConditionalContentTarget\r\n function self.withDefault(target)\r\n default = target\r\n return self\r\n end\r\n\r\n function self.place(content, previousTag)\r\n for tag, target in pairs(conditions) do\r\n if Object.hasTag(content, tag) then\r\n return target.place(content, tag)\r\n end\r\n end\r\n\r\n if default then\r\n return (--[[---@not nil]] default).place(content, previousTag)\r\n end\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(ConditionalContentTarget, {\r\n ---@return gloom_ConditionalContentTarget\r\n __call = function(_)\r\n return new()\r\n end\r\n})\r\n\r\nreturn ConditionalContentTarget\r\n\nend)\n__bundle_register(\"asset.content.ClassBoxContentTarget\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal ContentTarget = require(\"asset.content.ContentTarget\")\r\n\r\n----@class gloom_ClassBoxContentTarget : gloom_ContentTarget\r\n\r\n---@class gloom_ClassBoxContentTarget_static\r\n---@overload fun(): gloom_ClassBoxContentTarget\r\nlocal ClassBoxContentTarget = {}\r\n\r\n---@type number\r\nlocal latestCharacterSlot = 0\r\n\r\nlocal function new()\r\n local self = --[[---@type gloom_ClassBoxContentTarget]] ContentTarget()\r\n\r\n local diffBetweenBoxes = 1.75\r\n local lastBoxPosition = Vector(-64.41, 4.5, 11.81)\r\n local boxSize = { 7.36, 4.5, 0.72 }\r\n local foundPosition\r\n local maxCharacterSlots = 30\r\n\r\n function self.getName()\r\n return \"Class Box\"\r\n end\r\n\r\n function self.place(content, tag)\r\n for _, obj in ipairs(getObjectsWithTag(tag)) do\r\n if obj.getName() == name then\r\n return ContentTarget.Result.Duplicate\r\n end\r\n end\r\n\r\n repeat\r\n latestCharacterSlot = latestCharacterSlot + 1\r\n foundPosition = lastBoxPosition + Vector(0, 0, diffBetweenBoxes * latestCharacterSlot)\r\n local hits = Physics.cast({\r\n type = 3,\r\n direction = { 0, 1, 0 },\r\n origin = foundPosition,\r\n size = boxSize,\r\n max_distance = 0,\r\n })\r\n until TableUtil.isEmpty(hits) or latestCharacterSlot >= maxCharacterSlots\r\n\r\n if latestCharacterSlot < maxCharacterSlots then\r\n local obj = spawnObjectData({ data = content, position = foundPosition, rotation = { 0, 270, 0 }, })\r\n obj.lock()\r\n return ContentTarget.Result.Success\r\n else\r\n Logger.warn(\"Can't find a valid slot to place the character box. Is the area occupied?\")\r\n latestCharacterSlot = 0\r\n return ContentTarget.Result.Error\r\n end\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(ClassBoxContentTarget, {\r\n ---@return gloom_ClassBoxContentTarget\r\n __call = function(_)\r\n return new()\r\n end\r\n})\r\n\r\nreturn ClassBoxContentTarget\r\n\nend)\n__bundle_register(\"asset.AssetManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal SaveManager = require(\"lib.SaveManager\")\nlocal Logger = require(\"lib.Logger\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal XmlUi = require(\"lib.XmlUi\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ThemeApi = require(\"api.ThemeApi\")\nlocal R = require(\"api.Resource\")\n\nlocal Event = require(\"Event\")\n\nlocal Api = --[[---@type ComponentApi]] ApiProvider(\"component\")\n---@class __AssetManager_this\nlocal this = {}\n\nlocal AssetManager = {}\n\nlocal customAssetsChanged = false\n\nApi.requestAssetUpdate = function()\n customAssetsChanged = true\nend\n\nfunction this.onLoad()\n local ui = XmlUi(Global)\n Event.triggerUpdateAssets(ui)\n Event.triggerCreateGlobalUi(ui)\n this.clearDuplicateAssets(ui)\n\n if ui.updateUiAssets() then\n Logger.info(\"Updated assets!\")\n end\n\n Wait.condition(function()\n Wait.frames(function()\n ui.update()\n Wait.time(this.updateCustomAssets, 2, -1)\n end, 60)\n end, this.isAssetLoadingDone)\nend\n\n---@param ui seb_XmlUi\nfunction this.clearDuplicateAssets(ui)\n ---@type table\n local byUrl = {}\n\n local assets = TableUtil.deepCopy(ui.getAssets())\n for _, asset in ipairs(assets) do\n if byUrl[asset.url] then\n Logger.warn(\"Tried to add asset with name '%s', but another asset '%s' already exists with the same URL.\\n\" ..\n \"This will break the UI, so the asset will be ignored.\\n\" ..\n \"If you really want to use the same image with different asset names, you need to upload it twice and use different URLs.\",\n asset.name, byUrl[asset.url].name)\n ui.removeAsset(asset.name)\n else\n byUrl[asset.url] = asset\n end\n end\nend\n\nfunction this.updateCustomAssets()\n if customAssetsChanged then\n Logger.debug(\"Updating custom assets\")\n customAssetsChanged = false\n\n local ui = XmlUi(Global)\n Event.triggerUpdateAssets(ui)\n\n this.clearDuplicateAssets(ui)\n\n if ui.updateUiAssets() then\n Logger.info(\"Updated custom assets\")\n Wait.condition(function()\n Wait.frames(this.reloadObjects)\n end, this.isAssetLoadingDone)\n end\n end\nend\n\nfunction this.reloadObjects()\n for _, obj in ipairs(getObjectsWithTag(R.Tag.Trait.CanReload)) do\n obj.call(\"reload\")\n end\nend\n\nfunction this.isAssetLoadingDone()\n return not Global.UI.loading\nend\n\n---@param selectedThemes set\nfunction this.onThemeChanged(selectedThemes)\n local themes = {}\n\n for id, selected in pairs(selectedThemes) do\n if selected then\n table.insert(themes, id)\n end\n end\n\n ThemeApi.selectThemes(themes)\nend\n\nSaveManager.registerOnLoad(this.onLoad)\nEvent.registerHandler(Event.EventType.ThemeChanged, this.onThemeChanged)\n\nreturn AssetManager\n\nend)\n__bundle_register(\"asset.ParticleEffectsHandler\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal EventManager = require(\"lib.EventManager\")\r\nlocal SaveManager = require(\"lib.SaveManager\")\r\nlocal TtsWait = require(\"lib.promise.TtsWait\")\r\n\r\nlocal OptionsApi = require(\"api.OptionsApi\")\r\n\r\n--- This module is used to disable all particle effects on models.\r\n--- On Mac with those often lead to weird looking smoke when used with the \"Metal\" rendering system.\r\nlocal ParticleEffectsHandler = {}\r\n\r\n---@class __ParticleEffects_this\r\nlocal this = {}\r\n\r\n---@param object tts__Object\r\nfunction this.disableParticleEffects(object)\r\n for _, comp in ipairs(object.getComponentsInChildren(\"ParticleSystemRenderer\") or {}) do\r\n TtsWait.untilLoaded(object)\r\n :next(function() comp.set(\"enabled\", false) end)\r\n end\r\nend\r\n\r\nfunction this.onLoad()\r\n if not OptionsApi.use3DModels(OptionsApi.Use3DModels.ParticleEffects) then\r\n for _, obj in ipairs(getObjects()) do\r\n this.disableParticleEffects(obj)\r\n end\r\n end\r\nend\r\n\r\n---@param object tts__Object\r\nfunction this.onObjectSpawn(object)\r\n if not OptionsApi.use3DModels(OptionsApi.Use3DModels.ParticleEffects) then\r\n this.disableParticleEffects(object)\r\n end\r\nend\r\n\r\nEventManager.addHandler(\"onObjectSpawn\", this.onObjectSpawn)\r\nSaveManager.registerOnLoad(this.onLoad)\r\n\r\nreturn ParticleEffectsHandler\r\n\nend)\n__bundle_register(\"Options\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Json = require(\"lib.Json\")\nlocal Logger = require(\"lib.Logger\")\nlocal SaveManager = require(\"lib.SaveManager\")\nlocal StringUtil = require(\"lib.StringUtil\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal Event = require(\"Event\")\nlocal OptionsApi = require(\"api.OptionsApi\")\nlocal ThemeApi = require(\"api.ThemeApi\")\n\nlocal Options = {}\n\nlocal Api = --[[---@type OptionsApi]] ApiProvider(\"options\")\n---@class __Options_this\nlocal this = {}\n\n---@type gloom_Options\nlocal options = {}\nlocal state = \"\"\nlocal dirty = false\n\nlocal function createSave()\n dirty = true\nend\n\n---@return string\nlocal function onSave()\n if dirty then\n dirty = false\n local values = this.getValues()\n Logger.verbose(\"Savings options\\n%s\", values)\n state = Json.encode(values)\n end\n return state\nend\n\n---@param savedState nil | string\nlocal function onLoad(savedState)\n this.registerThemeOption()\n\n if StringUtil.isNotEmpty(savedState) then\n local loadedState = Json.decode(--[[---@not nil]] savedState)\n for name, value in pairs(loadedState) do\n this.setOptionValue(--[[---@type string]] name, value)\n end\n end\n createSave()\nend\n\n---@param option gloom_Option\n---@param value any\n---@return boolean\nlocal function validateValue(option, value)\n ---@param choices gloom_Option_Text_Choices\n ---@param testValue any\n ---@return boolean\n local function hasChoice(choices, testValue)\n for _, choice in ipairs(choices.values) do\n if choice.value == testValue then\n return true\n end\n end\n return false\n end\n\n if option.type == OptionsApi.OptionType.Toggle then\n return type(value) == \"boolean\"\n elseif option.type == OptionsApi.OptionType.Text then\n local textOption = --[[---@type gloom_Option_Text]] option\n if textOption.choices then\n if textOption.choices.multiple then\n if type(value) ~= \"table\" then\n return false\n end\n for _, v in ipairs(value) do\n if not hasChoice(textOption.choices, v) then\n return false\n end\n end\n return true\n else\n return hasChoice(textOption.choices, value)\n end\n end\n elseif option.type == OptionsApi.OptionType.Image then\n local imageOption = --[[---@type gloom_Option_Image]] option\n for _, choice in ipairs(imageOption.choices) do\n if choice.image == value then\n return true\n end\n end\n return false\n end\n\n Logger.warn(\"Unknown option type '%s'\", option.type)\n return false\nend\n\nApi.registerOptionGroup = function(optionGroup)\n this.registerOptionGroup(optionGroup)\nend\n\nApi.registerOption = function(option)\n this.registerOption(option)\nend\n\nApi.getOptions = function()\n return options\nend\n\nApi.getValues = function()\n return this.getValues()\nend\n\nApi.setValues = function(values)\n for name, value in pairs(values) do\n this.setOptionValue(name, value)\n end\nend\n\nApi.getOption = function(optionName)\n return this.getOption(optionName)\nend\n\nApi.getValue = function(optionName)\n return this.getOptionValue(optionName)\nend\n\nApi.setValue = function(optionName, value)\n this.setOptionValue(optionName, value)\nend\n\n---@param optionGroup gloom_OptionGroup_Input\nfunction this.registerOptionGroup(optionGroup)\n options[optionGroup.name] = {\n displayName = optionGroup.displayName,\n description = optionGroup.description,\n options = {},\n }\nend\n\n---@param option gloom_Option_Input\nfunction this.registerOption(option)\n local optionGroup = options[option.group]\n if not optionGroup then\n Logger.error(\"No option group %s available while registering option %s\", option.group, option.name)\n return\n end\n optionGroup.options[option.name] = option\nend\n\nfunction this.registerThemeOption()\n local themes = ThemeApi.getThemes()\n local themeValues = {}\n for _, theme in pairs(themes) do\n table.insert(themeValues, {\n name = theme.name,\n value = theme.id,\n })\n end\n this.registerOption({\n group = \"appearance\",\n name = \"themes\",\n type = OptionsApi.OptionType.Text,\n displayName = \"Themes\",\n value = {},\n choices = {\n multiple = true,\n values = themeValues,\n },\n event = Event.EventType.ThemeChanged,\n description = [[\nSelect which themes should be applied to alter the appearance of some elements.]]\n })\nend\n\nfunction this.getValues()\n local values = --[[---@type gloom_Option_Values]] {}\n for groupName, group in pairs(options) do\n for name, set in pairs(group.options) do\n values[groupName .. \".\" .. name] = set.value\n end\n end\n\n return values\nend\n\n---@param fullName string\n---@return nil | gloom_Option\nfunction this.getOption(fullName)\n local path = StringUtil.split(fullName, \".\")\n local group, name = path[1], path[2]\n local groupOptions = options[group]\n if not groupOptions then\n return nil\n end\n\n return groupOptions.options[name]\nend\n\n---@param fullName string\nfunction this.getOptionValue(fullName)\n local option = this.getOption(fullName)\n if not option then\n Logger.error(\"No option named '%s' available!\", fullName)\n else\n return (--[[---@not nil]] option).value\n end\nend\n\n---@param fullName string\n---@param value any\nfunction this.setOptionValue(fullName, value)\n local option = this.getOption(fullName)\n if not option then\n Logger.error(\"No option named '%s' available!\", fullName)\n return nil\n end\n\n local theOption = --[[---@not nil]] option\n if not this.isValueValid(theOption, value) then\n Logger.warn(\"%s is invalid for option %s\", value, fullName)\n else\n theOption.value = value\n createSave()\n if theOption.event then\n Event.triggerEvent(--[[---@not nil]] theOption.event, value)\n end\n end\nend\n\n---@param option gloom_Option\n---@param value any\n---@return boolean\nfunction this.isValueValid(option, value)\n return validateValue(option, value)\nend\n\nthis.registerOptionGroup({\n name = \"setup\",\n displayName = \"Setup\",\n description = \"\",\n})\n\nthis.registerOption({\n group = \"setup\",\n name = \"scenarioPage\",\n type = OptionsApi.OptionType.Text,\n displayName = \"Scenario Page\",\n value = \"change\",\n choices = {\n multiple = false,\n values = {\n { name = \"Spawn\", value = \"spawn\" },\n { name = \"Change\", value = \"change\" },\n { name = \"None\", value = \"none\" },\n },\n },\n description = [[\n- Change:\nIf you have the Scenario Book on the table, it will change the PDF\npage to the current scenario.\n- Spawn:\nThe scenario page will appear as an object next to the set up\nscenario, and can be deleted after the scenario is finished.\n- None:\nTurns off both of the above options.]],\n})\n\nthis.registerOption({\n group = \"setup\",\n name = \"useHiddenSetup\",\n type = OptionsApi.OptionType.Toggle,\n displayName = \"Hidden Setup\",\n value = true,\n description = [[\nWhen this option is selected the default setup method used by\ndouble-clicking on a scenario sticker will use the Hidden Room Setup.]]\n})\n\nthis.registerOption({\n group = \"setup\", name = \"use3D\",\n type = OptionsApi.OptionType.Text,\n displayName = \"Use 3D models\",\n value = { [\"tree\"] = true, [\"door\"] = true, [\"openDoor\"] = true, [\"other\"] = true, [\"particle\"] = true },\n choices = {\n multiple = true,\n values = {\n { name = \"Closed doors\", value = \"door\", description = \"Use 3D models for closed doors.\" },\n { name = \"Opened doors\", value = \"openDoor\", description = \"Use 3D doors for opened doors.\" },\n { name = \"Trees\", value = \"tree\", description = \"Use 3D models for trees.\" },\n { name = \"Other\", value = \"other\", description = \"Use 3D models for all other types of overlays.\" },\n { name = \"Use Particle Effects\", value = \"particle\", description = \"When unchecked, all particle effects of objects will be disabled automatically (requires reload).\" },\n },\n },\n description = [[\nSelect for which type of overlays you want to use 3D models.]]\n})\n\nthis.registerOption({\n group = \"setup\",\n name = \"loadMonstersUpfront\",\n type = OptionsApi.OptionType.Toggle,\n displayName = \"Preload monsters\",\n value = true,\n description = [[\nWhen this option is selected the monster bags for all monsters in the\nscenario will be placed when the scenario is loaded. Otherwise monster\nbags will be placed on demand, when a new room is opened.]]\n})\n\nthis.registerOptionGroup({\n name = \"scenario\",\n displayName = \"Scenario\",\n description = \"\",\n})\n\nthis.registerOption({\n group = \"scenario\",\n name = \"dealBattleGoals\",\n type = OptionsApi.OptionType.Text,\n displayName = \"Deal battle goals\",\n value = \"2\",\n choices = {\n multiple = false,\n values = {\n { name = \"0\", value = \"0\" },\n { name = \"1\", value = \"1\" },\n { name = \"2\", value = \"2\" },\n { name = \"3\", value = \"3\" },\n { name = \"4\", value = \"4\" },\n },\n },\n description = [[\nThis option defines the number of battle goals drawn per person at the start of a scenario.]]\n})\n\nthis.registerOption({\n group = \"scenario\",\n name = \"hideAbilityCards\",\n type = OptionsApi.OptionType.Toggle,\n displayName = \"Hide ability cards\",\n value = true,\n description = [[\nWhen this option is set, Ability cards set using the hotkey functions\nwill be placed face-down. Otherwise they will be placed face-up.]]\n})\n\nthis.registerOptionGroup({\n name = \"variants\",\n displayName = \"Variants\",\n description = \"\",\n})\nthis.registerOption({\n group = \"variants\",\n name = \"openInformation\",\n type = OptionsApi.OptionType.Toggle,\n displayName = \"Solo/open information\",\n value = false,\n description = [[\nIf you play solo or with open information, it is recommended that\nyou increase the scenario level by one for traps and monsters,\nbut not for gold and XP.\nThis change is also shown on the scenario level chart below the\nParty Sheet (requires that you change the level once, after\nchanging this setting).]]\n})\nthis.registerOption({\n group = \"variants\",\n name = \"enhancementSystem\",\n type = OptionsApi.OptionType.Text,\n displayName = \"Enhancements\",\n value = \"classic\",\n choices = {\n multiple = false,\n values = {\n { name = \"Classic\", value = \"classic\", description = \"Enhancements are permanent for all characters of a class.\" },\n { name = \"Non-permanent\", value = \"nonPermanent\", description = \"Enhancements are cheaper but only apply to one character.\" },\n },\n },\n description = [[\nDecide which enhancement system you want to use, the classic one\nor the one introduced by the Digital version of the game.\n\nWhen \"Classic\" is used enhancements will automatically applied\nto the character box inside its class box when a character of\nthis class is packed up.]]\n})\n\nthis.registerOptionGroup({\n name = \"appearance\",\n displayName = \"Appearance\",\n description = \"\",\n})\n\nthis.registerOption({\n group = \"appearance\",\n name = \"background\",\n type = OptionsApi.OptionType.Image,\n displayName = \"Background\",\n value = \"http://cloud-3.steamusercontent.com/ugc/1751317540805826116/76A381381F9C02D0032F7A0174564D659E6EE75B/\",\n choices = {\n {\n name = \"Skull Logo (Red)\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1751317540805826116/76A381381F9C02D0032F7A0174564D659E6EE75B/\",\n thumbnail = \"http://cloud-3.steamusercontent.com/ugc/1692776269029816766/D4CFB162FBAE84CC94508102C4B8A965E1ED3DA9/\",\n },\n {\n name = \"GH Box Art (Brown)\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1751317714661074499/3DD895C88D1A6F6429FC638BF246E803027BF264/\",\n thumbnail = \"http://cloud-3.steamusercontent.com/ugc/1692776269029816416/A2B04B6A75F9F9C576354DE5A37F0E8CDA05A381/\",\n },\n {\n name = \"FC Box Art (Purple)\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1751317540805831736/388DF774E5614F9D33385F1EF7BABEBED92A3480/\",\n thumbnail = \"http://cloud-3.steamusercontent.com/ugc/1692776269029816666/856391292E07195D26446B15D734D90B2B7C9FDA/\",\n },\n {\n name = \"JotL Poker Table (Grey)\",\n image = \"http://cloud-3.steamusercontent.com/ugc/1751317540805807917/9D581E20638E82B61E27C9B8451F0E5D0D55437A/\",\n thumbnail = \"http://cloud-3.steamusercontent.com/ugc/1692776269029816504/17CE4CE6C3A2CD904E980AA7706731E72E34E3B6/\",\n },\n },\n event = Event.EventType.BackgroundChanged,\n description = [[\nThe image displayed on the table.]]\n})\n\nSaveManager.registerOnSave(\"Options\", onSave)\nSaveManager.registerOnLoad(\"Options\", onLoad)\n\nreturn Options\n\nend)\n__bundle_register(\"factory.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"factory.AidTokenFactory\")\r\nrequire(\"factory.CoinFactory\")\r\nrequire(\"factory.FigureFactory\")\r\nrequire(\"factory.MapTileFactory\")\r\nrequire(\"factory.MonsterFactory\")\r\nrequire(\"factory.OverlayFactory\")\r\nrequire(\"factory.ScriptFactory\")\r\nrequire(\"factory.SectionFactory\")\r\nrequire(\"factory.SummonFactory\")\r\nrequire(\"factory.TrapFactory\")\r\n\r\nrequire(\"Action.Action\")\r\n\nend)\n__bundle_register(\"Action.Action\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ActionApi = require(\"api.ActionApi\")\nlocal ActionFactory = require(\"Action.ActionFactory\")\n\nrequire(\"Action.CallAction\")\nrequire(\"Action.CompoundAction\")\nrequire(\"Action.DoorAction\")\nrequire(\"Action.RemoveAction\")\nrequire(\"Action.SectionAction\")\nrequire(\"Action.StartAction\")\nrequire(\"Action.SpawnAction\")\nrequire(\"Action.StartAction\")\nrequire(\"Action.TreasureAction\")\n\nlocal Action = {}\n\nlocal Api = --[[---@type ActionApi]] ApiProvider(\"action\")\nlocal this = {}\n\n---@alias gloom_Action gloom_Action_Definition_Call | gloom_Action_Definition_Compound | gloom_Action_Definition_Door | gloom_Action_Definition_Remove | gloom_Action_Definition_Section | gloom_Action_Definition_Spawn | gloom_Action_Definition_Start | gloom_Action_Definition_Treasure\n\n---@shape __gloom_Spawn_ButtonStyle\n---@field color tts__ColorShape\n---@field height integer\n---@field width integer\n---@field fontSize integer\n\n---@type table\nlocal ActionStyle = {\n [ActionApi.Style.Door] = {\n color = { 0.408, 0.525, 0.910 },\n height = 250, width = 500,\n fontSize = 200,\n },\n [ActionApi.Style.PressurePlate] = {\n color = { 187 / 255, 98 / 255, 55 / 255 },\n height = 250, width = 750,\n fontSize = 100,\n },\n [ActionApi.Style.Section] = {\n color = { 1, 1, 1 },\n height = 250, width = 1000,\n fontSize = 100,\n },\n [ActionApi.Style.Start] = {\n color = { 0.612, 0.678, 0.741 },\n height = 250, width = 500,\n fontSize = 200,\n },\n [ActionApi.Style.Treasure] = {\n color = { 0.941, 0.694, 0.027 },\n height = 250, width = 280,\n fontSize = 200,\n },\n}\n\nApi.createActionButton = function(element, action, buttonPosition, functionName)\n this.createActionButton(element, action, buttonPosition, functionName)\nend\n\nApi.performAction = function(element, action, execute)\n return this.performAction(element, action, execute, false)\nend\n\nApi.performDoneAction = function(element, action)\n this.performAction(element, action, nil, true)\nend\n\nApi.postPerformAction = function(element, action)\n this.postPerformAction(element, action)\nend\n\n---@param element tts__Object\n---@param action gloom_Action_Definition\n---@param buttonPosition integer\n---@param functionName string\nfunction this.createActionButton(element, action, buttonPosition, functionName)\n Logger.debug(\"Creating action button for %s\\n%s\", element, action)\n local actionClass = ActionFactory.create(action)\n\n local actionStyle = actionClass.getStyleName()\n ---@type __gloom_Spawn_ButtonStyle\n local buttonStyle\n if ActionStyle[actionStyle] then\n buttonStyle = ActionStyle[actionStyle]\n else\n buttonStyle = ActionStyle[ActionApi.Style.PressurePlate]\n end\n\n local buttonParameters = {\n label = actionClass.getName(),\n function_owner = element,\n click_function = functionName,\n position = { 0, buttonPosition, 0 },\n rotation = { 0, 180 - element.getRotation().y, 0 },\n color = buttonStyle.color,\n width = buttonStyle.width,\n height = buttonStyle.height,\n font_size = buttonStyle.fontSize,\n }\n element.createButton(--[[---@type tts__CreateButtonParameters]] buttonParameters)\nend\n\n---@param element tts__Object\n---@param action gloom_Action_Definition\n---@param execute nil | gloom_Action_Execute\n---@param done boolean\n---@return boolean\nfunction this.performAction(element, action, execute, done)\n Logger.debug(\"Performing action on %s\\n%s\", element, action)\n local actionClass = ActionFactory.create(action)\n\n if done then\n actionClass.performDone(element)\n return true\n end\n\n return actionClass.perform(element, --[[---@not nil]] execute)\nend\n\n---@param element tts__Object\n---@param action gloom_Action_Definition\nfunction this.postPerformAction(element, action)\n local actionClass = ActionFactory.create(action)\n\n if element and not element.isDestroyed() then\n actionClass.postPerform(element)\n end\nend\n\nreturn Action\n\nend)\n__bundle_register(\"Action.TreasureAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\nlocal Scenario = require(\"api.ScenarioApi\")\r\n\r\n---@shape gloom_Action_Definition_Treasure : gloom_Action_Definition\r\n---@field treasure integer | string\r\n\r\n---@class gloom_Action_Treasure : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition_Treasure): gloom_Action_Treasure\r\nlocal TreasureAction = {}\r\n\r\nsetmetatable(TreasureAction, {\r\n ---@param action gloom_Action_Definition_Treasure\r\n ---@return gloom_Action_Treasure\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Treasure]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n return {\r\n name = tostring(action.treasure),\r\n style = ActionApi.Style.Treasure,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n ---@return boolean\r\n function this.perform(object, execute)\r\n local treasure = action.treasure\r\n if treasure == \"G\" then\r\n return false\r\n end\r\n\r\n return Scenario.revealTreasure(treasure)\r\n end\r\n\r\n function this.postPerform(object)\r\n destroyObject(object)\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"treasure\", TreasureAction)\r\n\r\nreturn TreasureAction\r\n\nend)\n__bundle_register(\"Action.BaseAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\n\r\n---@shape gloom_Action_Definition\r\n---@field name nil | string\r\n---@field confirm nil | boolean\r\n---@field style nil | string\r\n---@field state nil | gloom_Action_State\r\n\r\n---@shape gloom_Action_Execute\r\n---@field player tts__PlayerColor\r\n---@field button integer\r\n\r\n---@shape gloom_Action_State\r\n---@field done boolean\r\n\r\n---@class gloom_Action_Base\r\n---@field definition gloom_Action_Definition\r\n---@overload fun(actionDefinition: gloom_Action_Definition): gloom_Action_Base\r\nlocal BaseAction = {}\r\n\r\nsetmetatable(BaseAction, {\r\n ---@param definition gloom_Action_Definition\r\n ---@return gloom_Action_Base\r\n __call = function(_, definition)\r\n local this = --[[---@type gloom_Action_Base]] {}\r\n\r\n this.definition = definition\r\n\r\n function this.getDefaults()\r\n return {}\r\n end\r\n\r\n ---@return boolean\r\n function this.isDone()\r\n return definition.state.done\r\n end\r\n\r\n function this.getName()\r\n return definition.name\r\n end\r\n\r\n ---@return string\r\n function this.getStyleName()\r\n return --[[---@not nil]] definition.style\r\n end\r\n\r\n ---@param object tts__Object\r\n function this.performDone(object)\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n ---@return boolean\r\n function this.perform(object, execute)\r\n return false\r\n end\r\n\r\n ---@param object tts__Object\r\n function this.postPerform(object)\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"__DEFAULT__\", BaseAction)\r\n\r\nreturn BaseAction\nend)\n__bundle_register(\"Action.ActionFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal TableUtil = require(\"lib.TableUtil\")\r\n\r\nlocal ActionFactory = {}\r\n\r\n---@alias gloom_Action_FactoryMethod fun(action: gloom_Action_Definition): gloom_Action_Base\r\n\r\n---@type table\r\nlocal actions = {}\r\n\r\n---@param id string\r\n---@param factoryMethod gloom_Action_FactoryMethod\r\nfunction ActionFactory.register(id, factoryMethod)\r\n actions[id] = factoryMethod\r\nend\r\n\r\n---@param actionDefinition gloom_Action_Definition\r\n---@return gloom_Action_Definition\r\nfunction ActionFactory.applyDefaults(actionDefinition)\r\n local action = ActionFactory.create(actionDefinition)\r\n\r\n for name, defaultValue in pairs(action.getDefaults()) do\r\n if actionDefinition[name] == nil then\r\n actionDefinition[name] = defaultValue\r\n end\r\n end\r\n\r\n return actionDefinition\r\nend\r\n\r\n---@param action gloom_Action_Definition\r\n---@return gloom_Action_Base\r\nfunction ActionFactory.create(action)\r\n for id, factoryMethod in pairs(actions) do\r\n if action[id] then\r\n return factoryMethod(action)\r\n end\r\n end\r\n\r\n Logger.warn(\"No concrete action found for:\\n%s\\nAvailable actions are: %s\\nWill use default (empty) action\",\r\n action, TableUtil.keys(actions))\r\n return actions[\"__DEFAULT__\"](action)\r\nend\r\n\r\nreturn ActionFactory\r\n\nend)\n__bundle_register(\"api.ActionApi\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ApiConsumer = require(\"api.ApiConsumer\")\n\nlocal ActionApi = --[[---@type ActionApi]] ApiConsumer(\"action\")\n .withApi(\"createActionButton\")\n .withApi(\"performAction\")\n .withApi(\"performDoneAction\")\n .withApi(\"postPerformAction\")\n\nActionApi.Style = {\n Door = \"Door\",\n PressurePlate = \"PressurePlate\",\n Section = \"Section\",\n Start = \"Start\",\n Treasure = \"Treasure\",\n}\n\nreturn ActionApi\n\nend)\n__bundle_register(\"Action.StartAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@shape gloom_Action_Definition_Start : gloom_Action_Definition\r\n---@field start boolean\r\n\r\n---@class gloom_Action_Start : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition): gloom_Action_Start\r\nlocal StartAction = {}\r\n\r\nsetmetatable(StartAction, {\r\n ---@param action gloom_Action_Definition\r\n ---@return gloom_Action_Start\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Start]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n return {\r\n name = \"Start\",\r\n style = ActionApi.Style.Start,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n function this.perform(object, execute)\r\n for _, obj in ipairs(getObjectsWithTag(R.Tag.Tile.Start)) do\r\n obj.destruct()\r\n end\r\n\r\n return true\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"start\", StartAction)\r\n\r\nreturn StartAction\r\n\nend)\n__bundle_register(\"Action.SpawnAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\nlocal ComponentApi = require(\"api.ComponentApi\")\r\n\r\n---@shape gloom_Action_Definition_Spawn : gloom_Action_Definition\r\n---@field spawn gloom_Spawn_Execution\r\n\r\n---@class gloom_Action_Spawn : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition_Spawn): gloom_Action_Spawn\r\nlocal SpawnAction = {}\r\n\r\nsetmetatable(SpawnAction, {\r\n ---@param action gloom_Action_Definition_Spawn\r\n ---@return gloom_Action_Spawn\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Spawn]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n return {\r\n name = \"Execute\",\r\n style = ActionApi.Style.PressurePlate,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n function this.perform(object, execute)\r\n ComponentApi.spawnElement(action.spawn)\r\n\r\n return true\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"spawn\", SpawnAction)\r\n\r\nreturn SpawnAction\r\n\nend)\n__bundle_register(\"Action.SectionAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\n\r\n---@shape gloom_Action_Definition_Section : gloom_Action_Definition\r\n---@field section integer | string\r\n\r\n---@class gloom_Action_Section : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition): gloom_Action_Section\r\nlocal SectionAction = {}\r\n\r\nsetmetatable(SectionAction, {\r\n ---@param action gloom_Action_Definition_Section\r\n ---@return gloom_Action_Section\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Section]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n local name\r\n if tonumber(action.section) then\r\n name = \"Section \" .. action.section\r\n else\r\n name = action.section\r\n end\r\n\r\n return {\r\n name = name,\r\n style = ActionApi.Style.Section,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n function this.perform(object, execute)\r\n object.setColorTint({ r = 1, b = 1, g = 1 })\r\n\r\n return true\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"section\", SectionAction)\r\n\r\nreturn SectionAction\r\n\nend)\n__bundle_register(\"Action.RemoveAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\n\r\n---@shape gloom_Action_Definition_Remove : gloom_Action_Definition\r\n---@field remove gloom_Action_Definition_Remove_Parameters\r\n\r\n---@shape gloom_Action_Definition_Remove_Parameters\r\n---@field name nil | string\r\n---@field tag nil | string\r\n\r\n---@class gloom_Action_Remove : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition_Remove): gloom_Action_Remove\r\nlocal RemoveAction = {}\r\n\r\nsetmetatable(RemoveAction, {\r\n ---@param action gloom_Action_Definition_Remove\r\n ---@return gloom_Action_Remove\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Remove]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n return {\r\n name = \"Execute\",\r\n style = ActionApi.Style.PressurePlate,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n function this.perform(object, execute)\r\n if not action.remove.name and not action.remove.tag then\r\n return false\r\n end\r\n\r\n ---@type tts__Object[]\r\n local objects\r\n\r\n if action.remove.tag then\r\n objects = getObjectsWithTag(--[[---@not nil]] action.remove.tag)\r\n else\r\n objects = getObjects()\r\n end\r\n\r\n for _, obj in ipairs(objects) do\r\n if not action.remove.name or obj.getName() == action.remove.name then\r\n obj.destruct()\r\n end\r\n end\r\n\r\n return true\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"remove\", RemoveAction)\r\n\r\nreturn RemoveAction\r\n\nend)\n__bundle_register(\"Action.DoorAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\nlocal Options = require(\"api.OptionsApi\")\r\nlocal Scenario = require(\"api.ScenarioApi\")\r\n\r\n---@shape gloom_Action_Definition_Door : gloom_Action_Definition\r\n---@field rooms integer[]\r\n\r\n---@class gloom_Action_Door : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition_Door): gloom_Action_Door\r\nlocal DoorAction = {}\r\n\r\n---@param action gloom_Action_Definition_Door\r\n---@return gloom_Action_Door\r\nlocal function new(action)\r\n local this = --[[---@type gloom_Action_Door]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n return {\r\n name = \"Open\",\r\n confirm = true,\r\n style = ActionApi.Style.Door,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n function this.performDone(object)\r\n if object.AssetBundle then\r\n object.AssetBundle.playTriggerEffect(0)\r\n end\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute gloom_Action_Execute\r\n ---@return boolean\r\n function this.perform(object, execute)\r\n if object.AssetBundle then\r\n object.AssetBundle.playTriggerEffect(0)\r\n end\r\n\r\n Scenario.revealRooms(action.rooms)\r\n\r\n return true\r\n end\r\n\r\n ---@param object tts__Object\r\n function this.postPerform(object)\r\n local stateId = object.getStateId()\r\n if stateId == -1 then\r\n return\r\n end\r\n\r\n if stateId > 1 then\r\n if not Options.use3DModels(Options.Use3DModels.OpenDoors) then\r\n local newState = object.setState(1)\r\n newState.setLock(true)\r\n end\r\n else\r\n if Options.use3DModels(Options.Use3DModels.OpenDoors) then\r\n local newState = object.setState(2)\r\n newState.setLock(true)\r\n end\r\n end\r\n end\r\n\r\n return this\r\nend\r\n\r\nsetmetatable(DoorAction, {\r\n __call = function(_, action) return new(action) end\r\n})\r\n\r\nActionFactory.register(\"rooms\", DoorAction)\r\n\r\nreturn DoorAction\r\n\nend)\n__bundle_register(\"Action.CompoundAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\n\r\n---@shape gloom_Action_Definition_Compound : gloom_Action_Definition\r\n---@field compound gloom_Action[]\r\n\r\n---@class gloom_Action_Compound : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition): gloom_Action_Compound\r\nlocal CompoundAction = {}\r\n\r\nsetmetatable(CompoundAction, {\r\n ---@param action gloom_Action_Definition_Compound\r\n ---@return gloom_Action_Compound\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Compound]] BaseAction(action)\r\n ---@type gloom_Action_Base[]\r\n local actions = {}\r\n\r\n for _, childAction in ipairs(action.compound) do\r\n table.insert(actions, ActionFactory.create(childAction))\r\n end\r\n\r\n function this.getDefaults()\r\n return actions[1].getDefaults()\r\n end\r\n\r\n function this.performDone(object)\r\n for _, childAction in ipairs(actions) do\r\n childAction.performDone(object);\r\n end\r\n end\r\n\r\n function this.perform(object, execute)\r\n for _, childAction in ipairs(actions) do\r\n childAction.perform(object, execute);\r\n end\r\n\r\n return true\r\n end\r\n\r\n function this.postPerform(object)\r\n for _, childAction in ipairs(actions) do\r\n childAction.postPerform(object)\r\n end\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"compound\", CompoundAction)\r\n\r\nreturn CompoundAction\r\n\nend)\n__bundle_register(\"Action.CallAction\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ActionApi = require(\"api.ActionApi\")\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseAction = require(\"Action.BaseAction\")\r\n\r\n---@shape gloom_Action_Definition_Call : gloom_Action_Definition\r\n---@field call gloom_Action_Definition_Call_Parameters\r\n\r\n---@shape gloom_Action_Definition_Call_Parameters\r\n---@field functionName string\r\n---@field owner nil | GUID\r\n---@field parameters nil | table\r\n\r\n---@class gloom_Action_Call : gloom_Action_Base\r\n---@overload fun(actionDefinition: gloom_Action_Definition_Call): gloom_Action_Call\r\nlocal CallAction = {}\r\n\r\nsetmetatable(CallAction, {\r\n ---@param action gloom_Action_Definition_Call\r\n ---@return gloom_Action_Call\r\n __call = function(_, action)\r\n local this = --[[---@type gloom_Action_Call]] BaseAction(action)\r\n\r\n function this.getDefaults()\r\n return {\r\n name = \"Execute\",\r\n style = ActionApi.Style.PressurePlate,\r\n }\r\n end\r\n\r\n ---@param object tts__Object\r\n ---@param execute nil | gloom_Action_Execute\r\n function this.perform(object, execute)\r\n ---@type tts__Object\r\n local callObject\r\n if action.call.owner then\r\n callObject = --[[---@not nil]] getObjectFromGUID(--[[---@not nil]] action.call.owner)\r\n else\r\n callObject = Global\r\n end\r\n\r\n local parameters = action.call.parameters or {}\r\n if execute then\r\n parameters.player = (--[[---@not nil]] execute).player\r\n parameters.button = (--[[---@not nil]] execute).button\r\n end\r\n\r\n local result = callObject.call(action.call.functionName, action.call.parameters or {})\r\n if result ~= nil and result == true then\r\n return true\r\n end\r\n\r\n return false\r\n end\r\n\r\n return this\r\n end\r\n})\r\n\r\nActionFactory.register(\"call\", CallAction)\r\n\r\nreturn CallAction\r\n\nend)\n__bundle_register(\"factory.TrapFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ComponentFactory = require(\"ComponentFactory\")\nlocal OverlayFactory = require(\"factory.OverlayFactory\")\nlocal R = require(\"api.Resource\")\nlocal Scenario = require(\"Scenario\")\n\nlocal TrapFactory = {}\n\n---@class gloom_TrapFactory : gloom_BaseFactory\n\nlocal TrapBagGuid = \"0a3abe\"\n\n---@return gloom_TrapFactory\nlocal function new()\n local self = --[[---@type gloom_TrapFactory]] OverlayFactory(TrapBagGuid)\n\n local super = {\n prepareElement = self.prepareElement\n }\n\n function self.prepareElement(element, elementInfo)\n super.prepareElement(element, elementInfo)\n\n if elementInfo.element.damage == \"Auto\" then\n elementInfo.element.damage = Scenario.getScenarioLevelSettings().trap\n end\n\n if elementInfo.element.damage then\n for _ = 1, elementInfo.element.damage do\n element.call(\"addCondition\", \"Damage\")\n end\n end\n end\n\n return self\nend\n\nComponentFactory.register(R.ElementType.Trap, new())\n\nreturn TrapFactory\n\nend)\n__bundle_register(\"factory.OverlayFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableUtil = require(\"lib.TableUtil\")\r\nlocal ObjectState = require(\"lib.ObjectData\")\r\nlocal Object = require(\"lib.Object\")\r\nlocal TtsBase = require(\"lib.promise.TtsBase\")\r\n\r\nlocal BaseFactory = require(\"factory.BaseFactory\")\r\nlocal ComponentFactory = require(\"ComponentFactory\")\r\nlocal OptionsApi = require(\"api.OptionsApi\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@class gloom_OverlayFactory_static\r\n---@overload fun(bagGuid: GUID): gloom_OverlayFactory\r\nlocal OverlayFactory = {}\r\n\r\n---@class gloom_OverlayFactory : gloom_BaseFactory\r\n\r\n---@class __OverlayFactory_this\r\nlocal this = {}\r\n\r\n---@param bagGuid GUID\r\n---@return gloom_OverlayFactory\r\nlocal function new(bagGuid)\r\n local self = --[[---@type gloom_OverlayFactory]] BaseFactory()\r\n\r\n function self.getElementData(elementInfo)\r\n return this.findInInfiniteBag(bagGuid, elementInfo.element.name)\r\n end\r\n\r\n function self.spawnElement(elementData, elementInfo)\r\n elementData = this.selectState(elementData, elementInfo.element.type)\r\n\r\n return TtsBase.spawnObjectData({\r\n data = elementData,\r\n position = elementInfo.placement.position,\r\n rotation = elementInfo.placement.rotation,\r\n })\r\n end\r\n\r\n return self\r\nend\r\n\r\n--- Returns the object data for the given type and name.\r\n---@param bagGuid GUID\r\n---@param name string\r\n---@return tts__ObjectState\r\nfunction this.findInInfiniteBag(bagGuid, name)\r\n local bag = --[[---@type tts__Bag]] getObjectFromGUID(bagGuid)\r\n local bagData = bag.getData()\r\n\r\n for _, content in ipairs(bagData.ContainedObjects) do\r\n if content.Nickname == name then\r\n return content\r\n end\r\n end\r\nend\r\n\r\n--- Returns the correct object state (2D or 3D) depending on the current options.\r\n---@param objectData tts__ObjectState\r\n---@param elementType gloom_Element_Type\r\n---@return tts__ObjectState\r\nfunction this.selectState(objectData, elementType)\r\n if not objectData.States then\r\n return objectData\r\n end\r\n\r\n ---@type gloom_Options_Setup_3DModels\r\n local modelType = OptionsApi.Use3DModels.Other\r\n if elementType == R.ElementType.Door then\r\n modelType = OptionsApi.Use3DModels.ClosedDoors\r\n elseif elementType == R.ElementType.Obstacle and Object.name(objectData):find(\"Tree\") then\r\n modelType = OptionsApi.Use3DModels.Tree\r\n end\r\n\r\n if OptionsApi.use3DModels(modelType) then\r\n local states = TableUtil.length(objectData.States)\r\n local randomState = math.random(states) + 1\r\n return ObjectState.setState(objectData, randomState)\r\n else\r\n return ObjectState.setState(objectData, 1)\r\n end\r\nend\r\n\r\n\r\nsetmetatable(OverlayFactory, {\r\n ---@param bagGuid GUID\r\n ---@return gloom_OverlayFactory\r\n __call = function(_, bagGuid)\r\n return new(bagGuid)\r\n end\r\n})\r\n\r\nComponentFactory.register(R.ElementType.Corridor, OverlayFactory(\"359306\"))\r\nComponentFactory.register(R.ElementType.DifficultTerrain, OverlayFactory(\"4efdc4\"))\r\nComponentFactory.register(R.ElementType.HazardousTerrain, OverlayFactory(\"0b51bb\"))\r\nComponentFactory.register(R.ElementType.Door, OverlayFactory(\"21be0e\"))\r\nComponentFactory.register(R.ElementType.Obstacle, OverlayFactory(\"c912f5\"))\r\nComponentFactory.register(R.ElementType.Treasure, OverlayFactory(\"fe14ef\"))\r\nComponentFactory.register(R.ElementType.Start, OverlayFactory(\"359306\"))\r\n\r\nreturn OverlayFactory\r\n\nend)\n__bundle_register(\"factory.BaseFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal Promise = require(\"lib.Promise\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ActionFactory = require(\"Action.ActionFactory\")\nlocal Scenario = require(\"Scenario\")\nlocal R = require(\"api.Resource\")\n\n---@class gloom_BaseFactory_static\n---@overload fun(): gloom_BaseFactory\nlocal BaseFactory = {}\n\n---@class __BaseFactory_this\nlocal this = {}\n\n---@class gloom_BaseFactory : gloom_ComponentFactory\n\n---@generic E : gloom_Spawn_Element\n---@return gloom_BaseFactory\nlocal function new()\n local self = --[[---@type gloom_BaseFactory]] {}\n\n function self.getElementData(elementInfo)\n return nil\n end\n\n function self.spawnElement(elementData, elementInfo)\n return Promise.reject(\"Not implemented!\")\n end\n\n function self.prepareElement(element, elementInfo)\n this.applyTraits(element, elementInfo)\n end\n\n function self.useExactMatch()\n return false\n end\n\n return self\nend\n\n---@param object tts__Object\n---@param info gloom_Spawn_Execution\nfunction this.applyTraits(object, info)\n local function hasTrait(traitName, tag, trait)\n if not trait then\n return false\n end\n\n if not object.hasTag(tag) then\n Logger.warn(\"Tried to apply trait '%s' on object '%s' (%s), but it doesn't support them!\",\n traitName, object.getName(), object.getGUID())\n return false\n end\n\n return true\n end\n\n if hasTrait(\"action\", R.Tag.Trait.HasAction, info.action) then\n local action = ActionFactory.applyDefaults(--[[---@not nil]] info.action)\n Logger.debug(\"Setting action on element %s\\n%s\", object.getGUID(), action)\n object.call(\"setAction\", action)\n end\n\n if hasTrait(\"conditions\", R.Tag.Trait.HasConditions, info.element.conditions) then\n for _, condition in TableUtil.ipairs(info.element.conditions) do\n object.call(\"addCondition\", condition)\n end\n end\n\n if hasTrait(\"stats\", R.Tag.Trait.HasStats, info.element.stats) then\n object.call(\"setStats\", info.element.stats)\n else\n if hasTrait(\"hp\", R.Tag.Trait.HasHealth, info.element.hp) then\n local health = Scenario.calculate(--[[---@not nil]] info.element.hp, object)\n\n local maxHealth\n if info.element.hpMax then\n maxHealth = Scenario.calculate(--[[---@not nil]] info.element.hpMax, object)\n end\n\n object.call(\"setHp\", { value = health, max = maxHealth })\n end\n\n if hasTrait(\"aid tokens\", R.Tag.Trait.HasAidTokens, info.element.tokens) then\n object.call(\"setScenarioAidTokens\", { tokens = info.element.tokens })\n end\n end\nend\n\n\nsetmetatable(BaseFactory, {\n ---@generic E : gloom_Spawn_Element\n ---@return gloom_BaseFactory\n __call = function(_)\n return new()\n end\n})\n\n\nreturn BaseFactory\n\nend)\n__bundle_register(\"factory.SummonFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal Object = require(\"lib.Object\")\nlocal Search = require(\"lib.Search\")\nlocal TtsBase = require(\"lib.promise.TtsBase\")\n\nlocal BaseFactory = require(\"factory.BaseFactory\")\nlocal ComponentFactory = require(\"ComponentFactory\")\nlocal Enhancements = require(\"Enhancements\")\n\nlocal ClassApi = require(\"api.ClassApi\")\nlocal R = require(\"api.Resource\")\n\n---@class gloom_SummonFactory : gloom_BaseFactory\n\nlocal SummonFactory = {}\n\n---@class __SummonFactory_this\nlocal this = {}\n\n---@return gloom_SummonFactory\nlocal function new()\n local self = --[[---@type gloom_SummonFactory]] BaseFactory()\n\n function self.getElementData(elementInfo)\n local bagOfSummons = this.getBagOfSummons()\n local summon, _ = Search.inContainedObjects(bagOfSummons, { name = elementInfo.element.name })\n if summon and Object.isContainer(--[[---@not nil]] summon) then\n return (--[[---@type tts__ContainerState]] summon).ContainedObjects[1]\n end\n\n return summon\n end\n\n function self.spawnElement(elementData, elementInfo)\n return TtsBase.spawnObjectData({\n data = elementData,\n position = elementInfo.placement.position,\n rotation = elementInfo.placement.rotation or { 0, 180, 0 },\n })\n end\n\n function self.prepareElement(element, elementInfo)\n if not elementInfo.sourceObject then\n return\n end\n local enhancements = this.getEnhancementStats(--[[---@not nil]] elementInfo.sourceObject)\n this.updateStats(element, enhancements)\n end\n\n return self\nend\n\n---@return tts__Bag\nfunction this.getBagOfSummons()\n return --[[---@type tts__Bag]] getObjectsWithTag(R.Tag.Component.BagOfSummons)[1]\nend\n\n---@param card tts__Object\nfunction this.getEnhancementStats(card)\n local _, abilityCard = ClassApi.getAbilityForUnknownClass(card);\n\n local existingEnhancements = {\n ---@type number\n hp = 0,\n ---@type number\n move = 0,\n ---@type number\n attack = 0,\n ---@type number\n range = 0,\n }\n\n if abilityCard then\n for _, decal in ipairs(Object.decals(card)) do\n local nearestIndex, _ = Enhancements.findNearestIndex(decal.position, abilityCard.enhancements)\n local enhancement = abilityCard.enhancements[nearestIndex]\n\n if enhancement.type == ClassApi.AbilityType.SummonHp then\n existingEnhancements.hp = existingEnhancements.hp + 1\n elseif enhancement.type == ClassApi.AbilityType.SummonMove then\n existingEnhancements.move = existingEnhancements.move + 1\n elseif enhancement.type == ClassApi.AbilityType.SummonAttack then\n existingEnhancements.attack = existingEnhancements.attack + 1\n elseif enhancement.type == ClassApi.AbilityType.SummonRange then\n existingEnhancements.range = existingEnhancements.range + 1\n end\n end\n else\n Logger.debug(\"Unable to get enhancement data for card %s.\", card.getName())\n end\n\n return existingEnhancements\nend\n\n---@param element tts__Object\nfunction this.updateStats(element, enhancementStats)\n local stats = element.getTable(\"stats\")\n if type(stats.health) == \"number\" then\n stats.health = stats.health + enhancementStats.hp\n end\n\n if type(stats.move) == \"number\" then\n stats.move = stats.move + enhancementStats.move\n elseif type(stats.move) == \"table\" and type(stats.move.value) == \"number\" then\n stats.move.value = stats.move.value + enhancementStats.move\n end\n\n if type(stats.attack) == \"number\" then\n stats.attack = stats.attack + enhancementStats.attack\n elseif type(stats.attack) == \"table\" and type(stats.attack.value) == \"number\" then\n stats.attack.value = stats.attack.value + enhancementStats.attack\n end\n\n if type(stats.range) == \"number\" then\n stats.range = stats.range + enhancementStats.range\n end\n\n element.setTable(\"stats\", stats)\n element.call(\"setTyp\")\nend\n\nComponentFactory.register(R.ElementType.Summon, new())\n\nreturn SummonFactory\n\nend)\n__bundle_register(\"Enhancements\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\n\nlocal Enhancements = {}\n\n---@type number\nlocal DecalYCoordinate = 0.40\n\n---@type table\nEnhancements.Decals = {\n [1] = \"http://cloud-3.steamusercontent.com/ugc/929311677225889607/DFA4EF43DC2D1A8B6F1C7F22E7C31718E35CCB31/\",\n [2] = \"http://cloud-3.steamusercontent.com/ugc/929311677225890374/9B9B38BE2BB6A23EC21C977911FEC8915C74FF19/\",\n [3] = \"http://cloud-3.steamusercontent.com/ugc/929311677225891903/93FF8C7F051FDD06DAEBD17F2BBD917960CB1779/\",\n [4] = \"http://cloud-3.steamusercontent.com/ugc/929311677225892191/F2D0FC8179C04D3E4BFD2A546FAD7BC76D813091/\",\n [5] = \"http://cloud-3.steamusercontent.com/ugc/929311677225892469/3DC5E471FC5F2CB1D6DBFC91DF5ADB2751A52E12/\",\n [6] = \"http://cloud-3.steamusercontent.com/ugc/929311677225892918/3720A0E91CA48BB3972BBE924B26A34FFA34F57F/\",\n [\"Air\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726768884/CC8308C7D7DD6185F3EFA99F452C83101E1E95E3/\",\n [\"Earth\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726773039/E83D1E9F06938B3B5355F0A6A49D98B78B1F2594/\",\n [\"Fire\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726770166/CD369CF7C22FAD932A71C65BA811AE137234270D/\",\n [\"Frost\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726771564/3C69B7D1E66E8FC022B2BD77CC427E8AEC5D9331/\",\n [\"Ice\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726771564/3C69B7D1E66E8FC022B2BD77CC427E8AEC5D9331/\",\n [\"Dark\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726982499/9C14793623433565A9D6E5B3C608C1F6FBB84C76/\",\n [\"Light\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726774294/5BBAC718ED55E90563D2B24848DE3ACAEBD76FE9/\",\n [\"Any Element\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726984504/2F962A64E2A9888C90856D9C150EF81089745137/\",\n [\"Any\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726984504/2F962A64E2A9888C90856D9C150EF81089745137/\",\n [\"Curse\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726987796/ABB2E197E22015077D39A378AA9A9E7A317C69FE/\",\n [\"Bless\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726986639/FDF514FDA20826ACA054BE5CBA579AE6FB25A263/\",\n [\"Disarm\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726989091/DB993A58BC37C60B64A2F2CCDAED888F6176BC43/\",\n [\"Immobilize\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726990928/14B27BE1DC8F226D05FDB7D7D5D420FFCA6919B0/\",\n [\"Muddle\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726993395/820BE84906894B87F0C0DAC77C3BFBCD9B1AD504/\",\n [\"Poison\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726994202/6EAECD4A8A6A56F42834D486A21219002B883C53/\",\n [\"Regenerate\"] = \"http://cloud-3.steamusercontent.com/ugc/791986676667649885/92F7F934D298524F6F72997095DDBA073D9A15BF/\",\n [\"Strengthen\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726995467/72FC8E8918F788646DBD0C571FDAB214D7A36445/\",\n [\"Stun\"] = \"http://cloud-3.steamusercontent.com/ugc/83721958671678940/616CC0951CB17024448D3CBC7E5CA9D3A05D7555/\",\n [\"Wound\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726992099/9B90EEDC5B68B4FFD04C0037008F9C96BA603C24/\",\n [\"Plus 1\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726761532/C3BB327E15466B74ED256A0BD7B66C20A8825861/\",\n [\"Area\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726997997/041C0C738331AA8B4D8281BE8E9BE2C2963D823D/\",\n [\"Jump\"] = \"http://cloud-3.steamusercontent.com/ugc/938309024726767406/7C34D7EA30D5E6FF90BBA88A767ACB98467FC557/\",\n}\nEnhancements.Decals[\"AoE Hex\"] = Enhancements.Decals[\"Area\"] -- it got renamed between versions\nEnhancements.Decals[\"AoE HEx\"] = Enhancements.Decals[\"Area\"] -- stupid bad spelling\n\n---@param decalPosition tts__Vector\n---@param enhancementInfo gloom_Ability_Enhancement[]\n---@return (number, number)\nfunction Enhancements.findNearestIndex(decalPosition, enhancementInfo)\n local nearestDistance = 42\n local nearestIndex = 1\n decalPosition:setAt(\"y\", 0)\n for i, enhancement in ipairs(enhancementInfo) do\n local x, z = enhancement.position[1], enhancement.position[2]\n local distance = decalPosition:distance(Vector(x, 0, z))\n if distance < nearestDistance then\n nearestDistance = distance\n nearestIndex = i\n end\n end\n\n return nearestIndex, nearestDistance\nend\n\nfunction Enhancements.addNumberDecal(obj, name, info)\n local position = { info.position[1], DecalYCoordinate, info.position[2] }\n obj.addDecal({\n name = tostring(name),\n url = Enhancements.Decals[name],\n position = position,\n rotation = { 90, 180, 0 },\n scale = { 0.16, 0.16, 3.08 }\n })\nend\n\n---@param name string\nfunction Enhancements.addEnhancementDecal(obj, name, info)\n name = name:gsub(\"Generate \", \"\")\n\n local position = { info.position[1], info.y or DecalYCoordinate, info.position[2] }\n obj.addDecal({\n name = \"Enhancement \" .. tostring(name),\n url = Enhancements.Decals[name],\n position = position,\n rotation = { 90, 180, 0 },\n scale = info.scale or { 0.16, 0.16, 3.08 }\n })\nend\n\nfunction Enhancements.removeDecalsByName(card, name)\n local decals = Object.decals(card)\n local decalsToKeep = {}\n for _, decal in ipairs(decals) do\n if not (decal.name == name or decal.name == \"Enhancement \" .. name) then\n table.insert(decalsToKeep, decal)\n end\n end\n card.setDecals(decalsToKeep)\nend\n\nfunction Enhancements.removeNonEnhancementDecals(card)\n local decals = Object.decals(card)\n local decalsToKeep = {}\n for _, decal in ipairs(decals) do\n if decal.name:find(\"Enhancement \") then\n table.insert(decalsToKeep, decal)\n end\n end\n card.setDecals(decalsToKeep)\nend\n\nreturn Enhancements\n\nend)\n__bundle_register(\"factory.SectionFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TtsBase = require(\"lib.promise.TtsBase\")\n\nlocal BaseFactory = require(\"factory.BaseFactory\")\nlocal ComponentFactory = require(\"ComponentFactory\")\nlocal ActionFactory = require(\"Action.ActionFactory\")\nlocal R = require(\"api.Resource\")\n\n---@class gloom_SectionFactory : gloom_BaseFactory\nlocal SectionFactory = {}\n\n---@class __SectionFactory_this\nlocal this = {}\n\nlocal SectionBagGuid = \"359306\"\n\n---@return gloom_SectionFactory\nlocal function new()\n local self = --[[---@type gloom_SectionFactory]] BaseFactory()\n\n function self.getElementData(elementInfo)\n return this.findElement()\n end\n\n function self.spawnElement(elementData, elementInfo)\n elementData.CustomImage.ImageURL = elementInfo.element.image\n elementData.Nickname = \"\"\n elementData.Locked = true\n\n return TtsBase.spawnObjectData({\n data = elementData,\n position = elementInfo.placement.position,\n rotation = elementInfo.placement.rotation,\n scale = elementInfo.element.scale,\n })\n end\n\n function self.prepareElement(element, elementInfo)\n if elementInfo.element.hidden then\n element.setColorTint({ r = 0 / 255, g = 0 / 255, b = 0 / 255 })\n local action = ActionFactory.applyDefaults(--[[---@not nil]] elementInfo.action)\n element.call(\"setAction\", action)\n end\n end\n\n return self\nend\n\nfunction this.findElement()\n local bag = --[[---@type tts__Bag]] getObjectFromGUID(SectionBagGuid)\n local bagData = bag.getData()\n\n for _, content in ipairs(bagData.ContainedObjects) do\n if content.Nickname == \"Section\" then\n return content\n end\n end\nend\n\nComponentFactory.register(R.ElementType.ScenarioSection, new())\n\nreturn SectionFactory\n\nend)\n__bundle_register(\"factory.ScriptFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TableOutput = require(\"lib.TableOutput\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal ScriptFactory = {}\r\n\r\n---@class __ScriptFactory_this\r\nlocal this = {}\r\n\r\n---@alias gloom_ScriptType 'Figure' | 'FigureCharacter' | 'FigureMonster' | 'FigureSummon' | 'ScenarioToken' | 'StatSheet' | 'SummonCard'\r\n---@alias gloom_ScriptParams table\r\n\r\n---@shape gloom_ScriptFactory_Scripts\r\n---@field lua string\r\n---@field xml string\r\n---@field state string\r\n\r\nScriptFactory.Script = {\r\n ScenarioToken = \"ScenarioToken\",\r\n StatSheet = \"StatSheet\",\r\n SummonCard = \"SummonCard\",\r\n Figure = \"Figure\",\r\n CharacterFigure = \"FigureCharacter\",\r\n MonsterFigure = \"FigureMonster\",\r\n SummonFigure = \"FigureSummon\",\r\n}\r\n\r\n---@overload fun(table: table): string\r\n---@param table table\r\n---@param depth integer\r\n---@param check gloom_TableOutput_check\r\n---@return string\r\nfunction ScriptFactory.tableScript(table, depth, check)\r\n local dump = TableOutput.createOutput(table, depth, check)\r\n local extra = (depth or 0) * 2\r\n local result = dump:sub(3 + extra, -3)\r\n return result\r\nend\r\n\r\n---@overload fun(object: tts__ObjectState, script: gloom_ScriptType): void\r\n---@param object tts__ObjectState\r\n---@param script gloom_ScriptType\r\n---@param params gloom_ScriptParams\r\nfunction ScriptFactory.applyScripts(object, script, params)\r\n local scripts = ScriptFactory.getScripts(script, params)\r\n object.LuaScript = scripts.lua\r\n object.LuaScriptState = scripts.state\r\n object.XmlUI = scripts.xml\r\nend\r\n\r\n---@param script gloom_ScriptType\r\n---@param params gloom_ScriptParams\r\n---@return gloom_ScriptFactory_Scripts\r\nfunction ScriptFactory.getScripts(script, params)\r\n local source = this.getScriptSource(script)\r\n local lua = --[[---@not nil]] source.LuaScript\r\n\r\n if params then\r\n for name, value in pairs(params) do\r\n lua = lua:gsub('\"~' .. name .. '~\"', tostring(value))\r\n lua = lua:gsub(\"~~\" .. name .. \"~~\", tostring(value))\r\n end\r\n end\r\n\r\n return {\r\n lua = lua,\r\n xml = source.XmlUI,\r\n state = \"\"\r\n }\r\nend\r\n\r\n---@param name string\r\n---@return tts__ObjectState\r\nfunction this.getScriptSource(name)\r\n local scriptSources = --[[---@type tts__BagState]] R.Object.Backup(\"ScriptSource\")\r\n\r\n for _, source in ipairs(scriptSources.ContainedObjects) do\r\n if source.Nickname == name then\r\n return source\r\n end\r\n end\r\nend\r\n\r\nreturn ScriptFactory\r\n\nend)\n__bundle_register(\"lib.TableOutput\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Introspection = require(\"lib.Introspection\")\r\nlocal StringUtil = require(\"lib.StringUtil\")\r\n\r\nlocal TableOutput = {}\r\n\r\n---@class __TableOutput_this\r\nlocal this = {}\r\n\r\nlocal TYPE_STRINGIFIERS = {\r\n [\"nil\"] = function(_)\r\n return \"nil\"\r\n end,\r\n boolean = function(v)\r\n return tostring(v)\r\n end,\r\n number = function(v)\r\n return tostring(v)\r\n end,\r\n string = function(v)\r\n return '\"' .. v .. '\"'\r\n end,\r\n}\r\n\r\n---@shape gloom_TableOutput_check\r\n---@field keepOneLine fun(path: string, input: table): boolean\r\n---@field keepNumberedKeys fun(path: string): boolean\r\n---@field mapPath fun(value: any, path: string): nil | any\r\n\r\n---@overload fun(table: table): string\r\n---@param table table\r\n---@param depth integer\r\n---@param check gloom_TableOutput_check\r\n---@return string\r\nfunction TableOutput.createOutput(table, depth, check)\r\n if not depth then\r\n depth = 0\r\n end\r\n if not check then\r\n check = {}\r\n end\r\n check.keepOneLine = check.keepOneLine or this.keepOneLine\r\n check.keepNumberedKeys = check.keepNumberedKeys or this.keepNumberedKeys\r\n check.mapPath = check.mapPath or this.mapPath\r\n\r\n return this.outputTable(table, depth, \"\", check)\r\nend\r\n\r\n---@generic K, V\r\n---@param input table | V[]\r\n---@param depth integer\r\n---@param path string\r\n---@param check gloom_TableOutput_check\r\nfunction this.outputTable(input, depth, path, check)\r\n local indentation = string.rep(' ', depth)\r\n local str = '{'\r\n local useOneLine = check.keepOneLine(path, input)\r\n\r\n local function deepenPath(element)\r\n if not path or path == \"\" then\r\n return tostring(element)\r\n end\r\n return path .. \".\" .. tostring(element)\r\n end\r\n\r\n local function startLine()\r\n if useOneLine then\r\n str = str .. \" \"\r\n else\r\n str = str .. \"\\n\" .. indentation\r\n end\r\n end\r\n\r\n local function handleValue(value, deepPath)\r\n local valueType = type(value)\r\n if valueType == \"table\" then\r\n str = str .. this.outputTable(value, depth + 1, deepPath, check) .. \",\"\r\n else\r\n local mapped = check.mapPath(value, deepPath)\r\n if mapped then\r\n str = str .. mapped .. \",\"\r\n else\r\n str = str .. TYPE_STRINGIFIERS[valueType](value) .. \",\"\r\n end\r\n end\r\n end\r\n\r\n ---@type set\r\n local ordered_keys = {}\r\n\r\n ---@param value any\r\n ---@param index integer\r\n local function addNumberedKey(value, index)\r\n local deepPath = deepenPath(index)\r\n ordered_keys[index] = true\r\n startLine()\r\n\r\n if check.keepNumberedKeys(path) then\r\n str = str .. \"[\" .. index .. \"] = \"\r\n end\r\n\r\n handleValue(value, deepPath)\r\n end\r\n\r\n --special handling for zero to show before other number index values as it is quite common\r\n if input[0] then\r\n addNumberedKey(input[0], 0)\r\n end\r\n for i, v in ipairs(--[[---@type V[] ]] input) do\r\n addNumberedKey(v, i)\r\n end\r\n\r\n for k, v in pairs(--[[---@type table]] input) do\r\n local deepPath = deepenPath(k)\r\n if not ordered_keys[--[[---@type number]] k] then\r\n startLine()\r\n\r\n local keyType = type(k)\r\n local keyEntry\r\n if keyType == \"string\" and StringUtil.isIdentifier(k) then\r\n keyEntry = k\r\n else\r\n keyEntry = \"[\" .. TYPE_STRINGIFIERS[keyType](k) .. \"]\"\r\n end\r\n\r\n str = str .. keyEntry .. ' = '\r\n\r\n handleValue(v, deepPath)\r\n end\r\n end\r\n\r\n if useOneLine then\r\n str = str .. ' }'\r\n else\r\n str = str .. '\\n' .. string.rep(' ', depth - 1) .. '}'\r\n end\r\n\r\n return str\r\nend\r\n\r\n--- Given a value from the \"enum\" enum it returns the name\r\n --- E.g. a gridType of 1 will return \"ScenarioApi.GridType.Horizontal\".\r\n ---@param value any @The value from the enum.\r\n ---@param baseName string\r\n ---@param enum string @The name of the enum inside ScenarioApi\r\n ---@return string\r\nfunction TableOutput.enumName(value, base, baseName, enum)\r\n for name, enumValue in pairs(base[enum]) do\r\n if enumValue == value then\r\n return baseName .. \".\" .. enum .. \".\" .. name\r\n end\r\n end\r\n\r\n log(\"Missing enum name for value \" .. value .. \" for enum \" .. enum)\r\n return tostring(value)\r\nend\r\n\r\n--- Returns true, when the given table should be output on one line instead of using one line per key.\r\n---@param input table\r\n---@return boolean\r\nfunction this.keepOneLine(path, input)\r\n if Introspection.isVectorLike(input) then\r\n return true\r\n end\r\n\r\n return false\r\nend\r\n\r\n---@param path string\r\n ---@return boolean\r\nfunction this.keepNumberedKeys(path)\r\n return false\r\nend\r\n\r\nfunction this.mapPath(value, path)\r\n return nil\r\nend\r\n\r\nreturn TableOutput\r\n\nend)\n__bundle_register(\"lib.Introspection\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Introspection = {}\r\n\r\n---@class __Introspection_this\r\nlocal this = {}\r\n\r\nfunction Introspection.isVectorLike(input)\r\n if not type(input) == \"table\" then\r\n return false\r\n end\r\n\r\n return this.isNumber(input.x) and this.isNumber(input.y) and this.isNumber(input.z)\r\nend\r\n\r\nfunction this.isNumber(value)\r\n return tonumber(value) ~= nil\r\nend\r\n\r\nreturn Introspection\r\n\nend)\n__bundle_register(\"factory.MonsterFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\r\nlocal Promise = require(\"lib.Promise\")\r\n\r\nlocal BaseFactory = require(\"factory.BaseFactory\")\r\nlocal ComponentFactory = require(\"ComponentFactory\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n--- Factory to spawn a coin object.\r\n---@class gloom_MonsterFactory : gloom_BaseFactory\r\n\r\n--TODO probably move the stuff from scripts here\r\n\r\nlocal BagOfMonstersGuid = \"6173aa\"\r\n\r\n---@return gloom_MonsterFactory\r\nlocal function new()\r\n local self = --[[---@type gloom_MonsterFactory]] BaseFactory()\r\n\r\n function self.getElementData(elementInfo)\r\n local bagOfMonsters = --[[---@type tts__Bag]] getObjectFromGUID(BagOfMonstersGuid)\r\n for _, monsterBag in ipairs(bagOfMonsters.getData().ContainedObjects) do\r\n if Object.name(monsterBag) == elementInfo.element.name then\r\n for _, content in ipairs((--[[---@type tts__BagState]] monsterBag).ContainedObjects) do\r\n if Object.name(content):find(\"Standee\") then\r\n local standeeBag = --[[---@type tts__BagState]] content\r\n return standeeBag.ContainedObjects[1]\r\n end\r\n end\r\n end\r\n end\r\n\r\n return nil\r\n end\r\n\r\n function self.spawnElement(elementData, elementInfo)\r\n spawnMonster(--[[---@type gloom_Spawn_Monster]] {\r\n monsterName = elementInfo.element.name,\r\n monsterDifficulty = elementInfo.element.difficulty or \"normal\",\r\n monsterPosition = elementInfo.placement.position,\r\n monsterRotation = elementInfo.placement.rotation,\r\n levelModifier = elementInfo.element.levelModifier,\r\n named = elementInfo.element.named,\r\n hp = elementInfo.element.hp,\r\n hpMax = elementInfo.element.hpMax,\r\n stats = elementInfo.element.stats,\r\n team = elementInfo.element.team,\r\n base = elementInfo.element.base,\r\n tags = elementInfo.placement.tags,\r\n softLock = elementInfo.placement.lock == R.LockType.Soft,\r\n summoner = elementInfo.placement.summoner,\r\n })\r\n\r\n -- this is just a dummy for now, as the actual spawning happens in the\r\n -- Scripts object and we can't really pass the promise from there now\r\n return Promise.resolve(--[[---@type tts__Object]] Global)\r\n end\r\n\r\n return self\r\nend\r\n\r\nComponentFactory.register(R.ElementType.Enemy, new())\r\n\nend)\n__bundle_register(\"factory.MapTileFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ComponentFactory = require(\"ComponentFactory\")\r\nlocal OverlayFactory = require(\"factory.OverlayFactory\")\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal MapTileFactory = {}\r\n\r\n---@class gloom_MapTileFactory : gloom_OverlayFactory\r\n\r\nlocal MapTileBagGuid = \"4895cd\"\r\n\r\n---@return gloom_MapTileFactory\r\nlocal function new()\r\n local self = --[[---@type gloom_MapTileFactory]] OverlayFactory(MapTileBagGuid)\r\n\r\n function self.prepareElement(element, elementInfo)\r\n -- The position is set again because setPosition (or takeObject like before) also does some correction to the\r\n -- position. The positions in the scenario files for tiles are not always accurate and spawning at that position\r\n -- might lead to weird behaviour (e.g. when unlocking the first tile in scenario 1)\r\n -- That prevents tiles to change position when you accidentally unlock them\r\n element.setPosition(elementInfo.placement.position)\r\n end\r\n\r\n return self\r\nend\r\n\r\nComponentFactory.register(R.ElementType.MapTile, new())\r\n\r\nreturn MapTileFactory\r\n\nend)\n__bundle_register(\"factory.FigureFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TtsBase = require(\"lib.promise.TtsBase\")\r\n\r\nlocal ActionFactory = require(\"Action.ActionFactory\")\r\nlocal BaseFactory = require(\"factory.BaseFactory\")\r\nlocal ScriptFactory = require(\"factory.ScriptFactory\")\r\nlocal ComponentFactory = require(\"ComponentFactory\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@class gloom_FigureFactory : gloom_BaseFactory\r\n\r\nlocal FigureFactory = {}\r\n\r\n---@type gloom_Element_Type[]\r\nlocal elementTypesToCheck = { R.ElementType.Summon, R.ElementType.Enemy, R.ElementType.ObjectiveToken, R.ElementType.ScenarioAid }\r\n\r\n---@return gloom_FigureFactory\r\nlocal function new()\r\n local self = --[[---@type gloom_FigureFactory]] BaseFactory()\r\n\r\n function self.getElementData(elementInfo)\r\n local function findFigureData()\r\n local figures = --[[---@type tts__BagState]] R.Object.Backup(\"Figures\")\r\n for _, figure in ipairs(figures.ContainedObjects or {}) do\r\n if figure.Nickname == elementInfo.element.name then\r\n return figure\r\n end\r\n end\r\n\r\n for _, elementType in ipairs(elementTypesToCheck) do\r\n local elementData = ComponentFactory.getElementData(elementType, elementInfo.element.name)\r\n if elementData then\r\n return elementData\r\n end\r\n end\r\n\r\n return nil\r\n end\r\n\r\n local figureData = findFigureData()\r\n if figureData then\r\n ScriptFactory.applyScripts(--[[---@not nil]] figureData, ScriptFactory.Script.Figure)\r\n end\r\n\r\n return figureData\r\n end\r\n\r\n function self.spawnElement(elementData, elementInfo)\r\n return TtsBase.spawnObjectData({\r\n data = elementData,\r\n position = elementInfo.placement.position,\r\n rotation = elementInfo.placement.rotation,\r\n })\r\n end\r\n\r\n function self.prepareElement(element, elementInfo)\r\n if elementInfo.element.stats then\r\n element.call(\"setStats\", elementInfo.element.stats)\r\n element.call(\"setBaseColor\", { base = elementInfo.element.base, team = elementInfo.element.team })\r\n end\r\n\r\n if elementInfo.action then\r\n local action = ActionFactory.applyDefaults(--[[---@not nil]] elementInfo.action)\r\n element.call(\"setAction\", action)\r\n end\r\n end\r\n\r\n return self\r\nend\r\n\r\nComponentFactory.register(R.ElementType.Figure, new())\r\n\r\nreturn FigureFactory\r\n\nend)\n__bundle_register(\"factory.CoinFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal TtsObject = require(\"lib.promise.TtsObject\")\r\n\r\nlocal BaseFactory = require(\"factory.BaseFactory\")\r\nlocal ComponentFactory = require(\"ComponentFactory\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n---@class gloom_CoinFactory : gloom_BaseFactory\r\n\r\nlocal CoinStackGuid = \"a41a54\"\r\n\r\n---@return gloom_CoinFactory\r\nlocal function new()\r\n local self = --[[---@type gloom_CoinFactory]] BaseFactory()\r\n\r\n function self.getElementData(elementInfo)\r\n local coinStack = --[[---@type tts__Bag]] getObjectFromGUID(CoinStackGuid)\r\n return coinStack.getData().ContainedObjects[1]\r\n end\r\n\r\n function self.spawnElement(_, elementInfo)\r\n local coinsBag = --[[---@type tts__Bag]] getObjectFromGUID(CoinStackGuid)\r\n\r\n return TtsObject.takeObject(coinsBag, {\r\n position = elementInfo.placement.position,\r\n rotation = elementInfo.placement.rotation,\r\n smooth = false,\r\n })\r\n end\r\n\r\n return self\r\nend\r\n\r\nComponentFactory.register(R.ElementType.Coin, new())\r\n\nend)\n__bundle_register(\"factory.AidTokenFactory\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\nlocal ObjectState = require(\"lib.ObjectData\")\r\nlocal TtsBase = require(\"lib.promise.TtsBase\")\r\n\r\nlocal BaseFactory = require(\"factory.BaseFactory\")\r\nlocal ComponentFactory = require(\"ComponentFactory\")\r\nlocal R = require(\"api.Resource\")\r\n\r\n--- Factory to spawn a coin object.\r\n---@class gloom_AidTokenFactory : gloom_ComponentFactory\r\n\r\n---@class __AidTokenFactory_this\r\nlocal this = {}\r\n\r\nlocal ScenarioAidStackGuid = \"3e4319\"\r\nlocal ObjectiveTokenStackGuid = \"cc25ae\"\r\n\r\n--- State id's for the different symbols on a Scenario Aid token\r\nlocal ScenarioAidTokenStates = {\r\n [\"1\"] = 1, [\"2\"] = 2, [\"3\"] = 3, [\"4\"] = 4, [\"a\"] = 5, [\"b\"] = 6, [\"c\"] = 7, [\"d\"] = 8, [\"e\"] = 9, [\"f\"] = 10,\r\n [\"g\"] = 11, [\"h\"] = 12, [\"i\"] = 13, [\"j\"] = 14,\r\n}\r\n\r\n---@return gloom_AidTokenFactory\r\nlocal function new()\r\n local self = --[[---@type gloom_AidTokenFactory]] BaseFactory()\r\n\r\n function self.getElementData(elementInfo)\r\n local bagGuid = this.getBagGuid(elementInfo)\r\n\r\n local tokenBag = --[[---@type tts__Bag]] getObjectFromGUID(bagGuid)\r\n local tokenData = tokenBag.getData().ContainedObjects[1]\r\n local requiredState = this.getRequiredState(elementInfo)\r\n if this.hasState(requiredState, elementInfo) then\r\n tokenData = ObjectState.setState(tokenData, requiredState)\r\n tokenData.States = {}\r\n return tokenData\r\n end\r\n\r\n return nil\r\n end\r\n\r\n function self.spawnElement(elementData, elementInfo)\r\n return TtsBase.spawnObjectData({\r\n data = elementData,\r\n position = elementInfo.placement.position,\r\n rotation = elementInfo.placement.rotation,\r\n })\r\n end\r\n\r\n function self.useExactMatch()\r\n return true\r\n end\r\n\r\n return self\r\nend\r\n\r\n---@param elementInfo gloom_Spawn_Execution\r\nfunction this.getRequiredState(elementInfo)\r\n local tokenName = elementInfo.element.name\r\n\r\n if elementInfo.element.type == R.ElementType.ScenarioAid then\r\n return ScenarioAidTokenStates[tokenName]\r\n end\r\n\r\n local requiredState = tonumber(tokenName)\r\n if not requiredState or requiredState < 1 or requiredState > 12 then\r\n Logger.warn(\"Tried to set objective token %s, but it doesn't exist! Will use '1' instead\", tokenName)\r\n return 1\r\n end\r\n\r\n return requiredState\r\nend\r\n\r\n---@param state integer\r\n---@param elementInfo gloom_Spawn_Execution\r\n---@return boolean\r\nfunction this.hasState(state, elementInfo)\r\n if elementInfo.element.type == R.ElementType.ScenarioAid then\r\n return state ~= nil\r\n end\r\n\r\n return state ~= nil and state >= 1 and state <= 12\r\nend\r\n\r\n---@param elementInfo gloom_Spawn_Execution\r\nfunction this.getBagGuid(elementInfo)\r\n if elementInfo.element.type == R.ElementType.ScenarioAid then\r\n return ScenarioAidStackGuid\r\n end\r\n return ObjectiveTokenStackGuid\r\nend\r\n\r\nComponentFactory.register(R.ElementType.ScenarioAid, new())\r\nComponentFactory.register(R.ElementType.ObjectiveToken, new())\r\n\nend)\n__bundle_register(\"registry.Config\", function(require, _LOADED, __bundle_register, __bundle_modules)\nrequire(\"registry.ClassInfo\")\nrequire(\"registry.ConditionRegistry\")\nrequire(\"registry.EnemyInfo\")\nrequire(\"registry.OverlayInfo\")\nrequire(\"registry.ScenarioSetup\")\nrequire(\"registry.SummonRegistry\")\nrequire(\"registry.ThemeRegistry\")\n\nreturn {}\n\nend)\n__bundle_register(\"registry.ThemeRegistry\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ThemeManager = require(\"asset.ThemeManager\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ComponentApi = require(\"api.ComponentApi\")\n\nlocal ThemeRegistry = {}\n\nlocal Api = --[[---@type ThemeApi]] ApiProvider(\"theme\")\n---@class __ThemeRegistry_this\nlocal this = {}\n\n---@type table\nlocal registeredThemes = {}\n\n---@type gloom_Theme\nlocal currentTheme = {\n id = \"current\",\n name = \"Current Theme\",\n conditions = {},\n effects = {},\n}\n\n---@param theme gloom_Theme\nfunction ThemeRegistry.registerTheme(theme)\n if not this.validateTheme(theme) then return\n end\n\n registeredThemes[theme.id] = theme\nend\n\nfunction ThemeRegistry.getThemes()\n return registeredThemes\nend\n\n---@param themes string[]\nfunction ThemeRegistry.selectThemes(themes)\n this.resetCurrentTheme()\n\n ---@param theme gloom_Theme\n ---@param group string\n local function mergeTheme(theme, group)\n if theme[group] then\n for element, elementTheme in pairs(theme[group]) do\n if currentTheme[group][element] then\n currentTheme[group][element] = TableUtil.merge(currentTheme[group][element], elementTheme)\n else\n currentTheme[group][element] = elementTheme\n end\n end\n end\n end\n\n for _, themeId in ipairs(themes) do\n local theme = registeredThemes[themeId]\n if theme then\n mergeTheme(theme, \"conditions\")\n mergeTheme(theme, \"effects\")\n else\n Logger.error(\"Theme with id %s doesn't exist!\", themeId)\n end\n end\n\n ComponentApi.requestAssetUpdate()\n ThemeManager.updateTheme()\nend\n\n---@param condition string\n---@param element gloom_ConditionTheme_Element\n---@return nil | URL\nfunction ThemeRegistry.getConditionTheme(condition, element)\n local theme = (--[[---@not nil]] currentTheme.conditions)[condition]\n if theme then\n return theme[element]\n end\n\n return nil\nend\n\n---@param effect string\n---@param element gloom_EffectTheme_Element\n---@return nil | URL\nfunction ThemeRegistry.getEffectTheme(effect, element)\n local theme = (--[[---@not nil]] currentTheme.effects)[effect]\n if theme then\n return theme[element]\n end\n\n return nil\nend\n\nApi.registerTheme = function(theme)\n ThemeRegistry.registerTheme(theme)\nend\n\nApi.getThemes = function()\n return ThemeRegistry.getThemes()\nend\n\nApi.selectThemes = function(themes)\n ThemeRegistry.selectThemes(themes)\nend\n\nApi.getConditionTheme = function(condition, element)\n return ThemeRegistry.getConditionTheme(condition, element)\nend\n\nApi.getEffectTheme = function(effect, element)\n return ThemeRegistry.getEffectTheme(effect, element)\nend\n\n---@param theme gloom_Theme\n---@return boolean\nfunction this.validateTheme(theme)\n return theme ~= nil\nend\n\nfunction this.resetCurrentTheme()\n currentTheme.conditions = {}\nend\n\nreturn ThemeRegistry\n\nend)\n__bundle_register(\"asset.ThemeManager\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal ConditionThemeHandler = require(\"asset.theme.ConditionThemeHandler\")\r\n\r\nlocal R = require(\"api.Resource\")\r\n\r\nlocal ThemeManager = {}\r\n\r\nlocal handlers = {\r\n ConditionThemeHandler\r\n}\r\n\r\nfunction ThemeManager.updateTheme()\r\n for _, themedObject in ipairs(getObjectsWithTag(R.Tag.Trait.HasTheme)) do\r\n local data = themedObject.getData()\r\n local changed = false\r\n for _, handler in ipairs(handlers) do\r\n local handlerChanged = handler.apply(data)\r\n changed = changed or handlerChanged\r\n end\r\n\r\n if changed then\r\n themedObject.destruct()\r\n spawnObjectData({ data = data })\r\n end\r\n end\r\nend\r\n\r\nreturn ThemeManager\r\n\nend)\n__bundle_register(\"asset.theme.ConditionThemeHandler\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\n\nlocal ThemeApi = require(\"api.ThemeApi\")\nlocal R = require(\"api.Resource\")\n\nlocal ConditionThemeHandler = {}\n\n---@class __ConditionThemeHandler_this\nlocal this = {}\n\n---@param object tts__ObjectState\n---@return boolean\nfunction ConditionThemeHandler.apply(object)\n if not Object.hasTag(object, R.Tag.ConditionStack) then\n return false\n end\n\n local tokenStackImage = ThemeApi.getConditionTheme(Object.name(object), \"tokenStack\")\n if this.replaceIcon(object, { \"CustomMesh\", \"DiffuseURL\" }, tokenStackImage) then\n\n local tokenImage = ThemeApi.getConditionTheme(Object.name(object), \"icon\")\n local token = object.ContainedObjects[1]\n this.replaceIcon(token, { \"CustomImage\", \"ImageURL\" }, tokenImage)\n\n return true\n end\n\n return false\nend\n\nfunction this.replaceIcon(object, path, image)\n local function getValueAtPath()\n local v = object\n for _, part in ipairs(path) do\n v = v[part]\n end\n return v\n end\n\n local function setValueAtPath(value)\n local v = object\n for i = 1, #path - 1 do\n v = v[path[i]]\n end\n v[path[#path]] = value\n end\n\n if image then\n if not object.Memo then\n object.Memo = getValueAtPath()\n end\n setValueAtPath(image)\n return true\n elseif object.Memo then\n setValueAtPath(object.Memo)\n object.Memo = nil\n return true\n end\n\n return false\nend\n\nreturn ConditionThemeHandler\n\nend)\n__bundle_register(\"registry.SummonRegistry\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Event = require(\"Event\")\nlocal Validator = require(\"validation.Validator\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ComponentApi = require(\"api.ComponentApi\")\n\nlocal SummonRegistry = {}\n\nlocal Api = --[[---@type ComponentApi]] ApiProvider(\"component\")\n---@class __SummonRegistry_this\nlocal this = {}\n\n---@type table\nlocal summons = {}\n\nApi.registerSummon = function(summon)\n return this.registerSummon(summon)\nend\n\nApi.getSummons = function()\n return this.getSummons()\nend\n\n---@param summon gloom_Summon_Input\n---@return nil | gloom_Summon\nfunction this.registerSummon(summon)\n if not this.validateSummon(summon) then\n return nil\n end\n\n ---@type gloom_Summon\n local summonInfo = {\n name = summon.name,\n image = {\n url = summon.image,\n name = \"Summon-\" .. summon.name\n }\n }\n\n summons[summon.name] = summonInfo\n\n ComponentApi.requestAssetUpdate()\n\n return summonInfo\nend\n\n---@return table\nfunction this.getSummons()\n return summons\nend\n\n---@param summon gloom_Summon_Input\n---@return boolean\nfunction this.validateSummon(summon)\n return Validator(summon, \"Summon Info\")\n .notEmpty(\"name\")\n .notEmpty(\"image\")\n .validate()\nend\n\n---@param ui seb_XmlUi\nlocal function registerAssets(ui)\n for _, summon in pairs(summons) do\n ui.updateAsset(summon.image.name, summon.image.url)\n end\nend\n\nEvent.registerForUpdateAssets(registerAssets)\n\nreturn SummonRegistry\n\nend)\n__bundle_register(\"validation.Validator\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\r\n\r\n\r\n---@class gloom_Validator_static\r\n---@overload fun(input: table, name: string): gloom_Validator\r\nlocal Validator = {}\r\n\r\n---@param input table\r\n---@param name string\r\n---@return gloom_Validator\r\nlocal function new(input, name)\r\n ---@class gloom_Validator\r\n local self = {}\r\n\r\n local isValid = true\r\n local data = input\r\n ---@type string\r\n local message\r\n\r\n ---@param field string\r\n ---@return gloom_Validator\r\n function self.notEmpty(field)\r\n if isValid then\r\n if not data[field] or data[field] == \"\" then\r\n isValid = false\r\n message = \"Field '\" .. tostring(field) .. \"' of \" .. tostring(name) .. \" must not be empty.\"\r\n end\r\n end\r\n return self\r\n end\r\n\r\n ---@return boolean\r\n function self.validate()\r\n if not isValid and message then\r\n Logger.error(message)\r\n end\r\n\r\n return isValid\r\n end\r\n\r\n return self\r\nend\r\n\r\nsetmetatable(Validator, {\r\n ---@param input table\r\n ---@param name string\r\n ---@return gloom_Validator\r\n __call = function (_, input, name)\r\n return new(input, name)\r\n end\r\n})\r\n\r\n---@param input table\r\nfunction Validator.create(input)\r\nend\r\n\r\nreturn Validator\r\n\nend)\n__bundle_register(\"registry.ScenarioSetup\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\n\n--- Complete Scenario Information Variable\n---@type table\nScenarioInformation_All = {}\n\nlocal Api = --[[---@type ScenarioApi]] ApiProvider(\"scenario\")\nlocal this = {}\n\n---@shape gloom_Scenario_Identifier\n---@field id gloom_Scenario_Index\n---@field name string\n---@field icon nil | string\n\nlocal OTHER = \"Other\"\n\n---@shape __registerScenario_Params\n---@field name gloom_Scenario_Index\n---@field scenario gloom_Scenario\n\nApi.registerScenario = function(id, scenario)\n ScenarioInformation_All[id] = scenario\nend\n\nApi.getCampaigns = function()\n ---@type table\n local campaigns = {}\n\n for id, scenario in pairs(ScenarioInformation_All) do\n local campaignName = scenario.campaign or OTHER\n\n if not campaigns[campaignName] then\n campaigns[campaignName] = {}\n end\n\n table.insert(campaigns[campaignName], {\n id = id,\n name = scenario.name,\n icon = scenario.icon,\n })\n end\n\n return campaigns\nend\n\nApi.getScenario = function(id)\n return ScenarioInformation_All[id]\nend\n\nApi.unlockScenario = function(id)\n this.unlockScenario(id)\nend\n\nfunction this.unlockScenario(id)\n local unlocked = --[[---@type tts__Bag]] getObjectsWithTag(\"UnlockedScenarios\")[1]\n local locked = --[[---@type tts__Bag]] getObjectsWithTag(\"LockedScenarios\")[1]\n this.moveScenario(id, locked, unlocked)\nend\n\nfunction this.lockScenario(id)\n local unlocked = --[[---@type tts__Bag]] getObjectsWithTag(\"UnlockedScenarios\")[1]\n local locked = --[[---@type tts__Bag]] getObjectsWithTag(\"LockedScenarios\")[1]\n this.moveScenario(id, unlocked, locked)\nend\n\n---@param id gloom_Scenario_Index\n---@param from tts__Bag\n---@param to tts__Bag\nfunction this.moveScenario(id, from, to)\n local found\n for _, v in pairs(from.getData().ContainedObjects or {}) do\n local name = Object.name(v)\n if type(id) == \"number\" then\n if id == tonumber(name) then\n found = v\n break\n end\n elseif id == name then\n found = v\n break\n end\n end\n\n if found ~= nil then\n from.takeObject({ guid = found.GUID, callback_function = function(o) to.putObject(o) end })\n end\nend\n\nend)\n__bundle_register(\"registry.OverlayInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Event = require(\"Event\")\nlocal Validator = require(\"validation.Validator\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\n\nlocal OverlayInfo = {}\n\nlocal Api = --[[---@type ComponentApi]] ApiProvider(\"component\")\n---@class __OverlayInfo_this\nlocal this = {}\n\n---@type table\nlocal overlays = {}\n\nApi.registerOverlay = function(overlay)\n return this.registerOverlay(overlay)\nend\n\nApi.getOverlays = function()\n return this.getOverlays()\nend\n\n---@param overlay gloom_Overlay_Input\n---@return nil | gloom_Overlay\nfunction this.registerOverlay(overlay)\n if not this.validateOverlay(overlay) then\n return nil\n end\n\n ---@type gloom_Overlay\n local overlayInfo = {\n name = overlay.name,\n image = {\n url = overlay.image,\n name = \"Overlay-\" .. overlay.name\n }\n }\n\n overlays[overlay.name] = overlayInfo\n\n return overlayInfo\nend\n\n---@return table\nfunction this.getOverlays()\n return overlays\nend\n\n---@param overlay gloom_Overlay_Input\n---@return boolean\nfunction this.validateOverlay(overlay)\n return Validator(overlay, \"Overlay Info\")\n .notEmpty(\"name\")\n .notEmpty(\"image\")\n .validate()\nend\n\n---@param ui seb_XmlUi\nlocal function registerAssets(ui)\n for _, overlay in pairs(overlays) do\n ui.updateAsset(overlay.image.name, overlay.image.url)\n end\nend\n\nEvent.registerForUpdateAssets(registerAssets)\n\nreturn OverlayInfo\n\nend)\n__bundle_register(\"registry.EnemyInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal Event = require(\"Event\")\nlocal ComponentApi = require(\"api.ComponentApi\")\n\nlocal EnemyInfo = {}\n\nlocal Api = --[[---@type EnemyApi]] ApiProvider(\"enemy\")\n---@class __EnemyInfo_this\nlocal this = {}\n\n---@type table\nlocal abilityDecks = {}\n---@type table\nlocal enemies = {}\n---@type table\nlocal bosses = {}\n\n\nApi.registerEnemyAbilityDeck = function(name, abilities)\n this.registerAbilityDeck(name, abilities)\nend\n\nApi.registerEnemy = function(name, enemy)\n return this.registerEnemy(name, enemy)\nend\n\nApi.registerBossEnemy = function(name, boss)\n this.registerBossEnemy(name, boss)\nend\n\nApi.getSpawnableElements = function(enemyName)\n return this.getSpawnableElements(enemyName)\nend\n\nApi.getEnemies = function()\n return this.getEnemies()\nend\n\nApi.getEnemy = function(name)\n return this.getEnemy(name)\nend\n\n---@param name string\n---@param abilities gloom_Enemy_AbilityDeck\nfunction this.registerAbilityDeck(name, abilities)\n if not name then\n Logger.error(\"No name provided while registering enemy ability deck\")\n return\n end\n\n if not abilities then\n Logger.error(\"No info provided while registering enemy ability deck %s\", name)\n return\n end\n\n abilityDecks[name] = abilities\n\n ComponentApi.requestAssetUpdate()\nend\n\n---@param name string\n---@param info gloom_Enemy_Input\n---@return nil | gloom_Enemy\nfunction this.registerEnemy(name, info)\n if not this.validateEnemy(name, info) then\n return nil\n end\n\n ---@type nil | gloom_Image\n local image = nil\n if info.icon then\n image = {\n name = \"Enemy-Icon-\" .. name,\n url = --[[---@not nil]] info.icon\n }\n end\n\n ---@type gloom_Enemy\n local enemyInfo = {\n name = name,\n abilityDeck = info.abilityDeck,\n image = image,\n }\n\n enemies[name] = enemyInfo\n\n ComponentApi.requestAssetUpdate()\n\n return enemyInfo\nend\n\n---@param name string\n---@param info gloom_BossEnemy_Input\nfunction this.registerBossEnemy(name, info)\n if not this.validateBossEnemy(name, info) then\n return nil\n end\n\n local image\n if info.icon then\n image = {\n name = \"Enemy-Icon-\" .. name,\n url = --[[---@not nil]] info.icon\n }\n end\n\n ---@type gloom_BossEnemy\n local boss = {\n name = name,\n image = image,\n spawn = info.spawn,\n abilityDeck = info.abilityDeck or \"Boss\",\n }\n\n bosses[name] = boss\nend\n\n---@param name string\n---@param info gloom_Enemy_Input\n---@return boolean\nfunction this.validateEnemy(name, info)\n if not name then\n Logger.error(\"No name provided while registering enemy\")\n return false\n end\n\n if not info then\n Logger.error(\"No info provided while registering enemy %s\", name)\n return false\n end\n\n if not info.abilityDeck or not abilityDecks[info.abilityDeck] then\n Logger.error(\"An ability deck named %s does not exist while trying to register enemy %s\",\n info.abilityDeck, name)\n return false\n end\n\n return true\nend\n\n---@param name string\n---@param info gloom_BossEnemy_Input\n---@return boolean\nfunction this.validateBossEnemy(name, info)\n if not name then\n Logger.error(\"No name provided while registering boss enemy\")\n return false\n end\n\n if not info then\n Logger.error(\"No info provided while registering boss enemy %s\", name)\n return false\n end\n\n return true\nend\n\n---@return table\nfunction this.getEnemies()\n return enemies\nend\n\n---@param name string\n---@return nil | gloom_Enemy | gloom_BossEnemy\nfunction this.getEnemy(name)\n if enemies[name] then\n return enemies[name]\n end\n\n return bosses[name]\nend\n\n---@param enemyName string\n---@return gloom_Spawn_Definition[]\nfunction this.getSpawnableElements(enemyName)\n local bossInfo = bosses[enemyName]\n if bossInfo then\n return bossInfo.spawn or {}\n end\n\n local enemyInfo = enemies[enemyName]\n if not enemyInfo then\n Logger.debug(\"No enemy information for '%s' while searching for summons.\", enemyName)\n return {}\n end\n\n -- TODO hide this behind an API\n local abilityDeckName = __getMonsterAbilityDeck(enemyName) or enemyInfo.abilityDeck\n local abilityDeck = abilityDecks[abilityDeckName]\n if not abilityDeck then\n Logger.debug(\"No enemy ability deck information for '%s' while searching for summons.\", enemyName)\n return {}\n end\n\n ---@type gloom_Spawn_Definition[]\n local elements = {}\n for _, ability in pairs(abilityDeck.abilities) do\n for _, spawn in TableUtil.ipairs(ability.spawn) do\n if not TableUtil.contains(elements, spawn, TableUtil.areEqual) then\n table.insert(elements, spawn)\n end\n end\n end\n\n return elements\nend\n\n---@param ui seb_XmlUi\nlocal function registerAssets(ui)\n for _, info in pairs(enemies) do\n if info.image then\n local image = --[[---@not nil]] info.image\n ui.updateAsset(image.name, image.url)\n end\n end\nend\n\nEvent.registerForUpdateAssets(registerAssets)\n\nreturn EnemyInfo\n\nend)\n__bundle_register(\"registry.ConditionRegistry\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Logger = require(\"lib.Logger\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal TableUtil = require(\"lib.TableUtil\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal Event = require(\"Event\")\nlocal ComponentApi = require(\"api.ComponentApi\")\nlocal ThemeApi = require(\"api.ThemeApi\")\n\nlocal Api = --[[---@type ConditionApi]] ApiProvider(\"condition\")\n---@class __ConditionRegistry_this\nlocal this = {}\n\nlocal none = TableUtil.none\nlocal filter = TableUtil.filter\n\nlocal TrackerPrefix = \"Tracker\"\n\n--- Registered conditions.\n---@type table\nlocal Conditions = {}\n\n--- Registered class trackers.\n---@type gloom_Condition_ClassTrackers\nlocal Classes = {}\n\n--- Registered effects.\n---@type table\nlocal Effects = {}\n\n---@alias gloom_Condition_ClassTrackers table\n\n---@shape gloom_Condition_ClassTracker\n---@field tracker string\n---@field max nil | integer\n\n\n---@param name string\n---@return nil | gloom_Condition | gloom_Effect\nlocal function getConditionOrEffect(name)\n local value = Conditions[name]\n if value then\n return value\n end\n\n return Effects[name]\nend\n\nlocal function updateAssets()\n ComponentApi.requestAssetUpdate()\nend\n\nApi.registerCondition = function(name, condition)\n if not this.validateCondition(name, condition) then\n return nil\n end\n\n return this.registerCondition(name, condition)\nend\n\nApi.registerTracker = function(className, tracker)\n if not this.validateTracker(className, tracker) then\n return nil\n end\n\n return this.registerTracker(className, tracker)\nend\n\nApi.registerEffect = function(effect)\n if not this.validateEffect(effect) then\n return nil\n end\n return this.registerEffect(effect)\nend\n\nApi.getConditions = function()\n return this.getConditions()\nend\n\nApi.getCondition = function(name)\n return Conditions[name]\nend\n\nApi.getClassTrackers = function()\n return this.getClassTrackers()\nend\n\nApi.getClassTracker = function(className)\n for trackerClass, trackerInfo in pairs(Classes) do\n if trackerClass == className then\n return TrackerPrefix .. trackerInfo.tracker\n end\n end\nend\n\nApi.getEffects = function()\n return this.getAllEffects()\nend\n\nApi.getEffect = function(name)\n if not name then\n Logger.error(\"No name given while getting effect info.\")\n return nil\n end\n return Effects[name]\nend\n\nApi.getImmunities = function(immunityNames)\n local immunities = {}\n\n for _, immunityName in ipairs(immunityNames) do\n local effect = getConditionOrEffect(immunityName)\n if not effect or not effect.immunity then\n Logger.warn(\"Can not find a registered condition or effect for immunity '%s'\", immunityName)\n else\n this.fillImmunity(immunities, effect.immunity)\n end\n end\n\n return immunities\nend\n\n---@param name string\n---@param condition gloom_Condition_Input\nfunction this.registerCondition(name, condition)\n Logger.debug(\"Registering condition %s\\n%s\", name, condition)\n\n ---@type gloom_Condition\n local conditionData = {\n name = name,\n image = {\n url = condition.image,\n name = name,\n },\n renderedMarkup = condition.renderedMarkup or name,\n max = condition.max or 1,\n immunity = this.createImmunity(name, condition.immunity),\n }\n\n Conditions[name] = conditionData\n\n updateAssets()\n\n return conditionData\nend\n\n---@param className string\n---@param tracker gloom_Tracker_Input\nfunction this.registerTracker(className, tracker)\n Classes[className] = {\n tracker = className,\n }\n\n local conditionName = TrackerPrefix .. className\n ---@type gloom_Condition\n local condition = {\n name = conditionName,\n max = tracker.max or 1,\n image = {\n url = tracker.image,\n name = conditionName,\n },\n renderedMarkup = tracker.renderedMarkup or className\n }\n\n Conditions[conditionName] = condition\n\n updateAssets()\n\n return condition\nend\n\n---@param effect gloom_Effect_Input\nfunction this.registerEffect(effect)\n ---@type nil | gloom_Image\n local image = nil\n if effect.image then\n image = {\n url = --[[---@not nil]] effect.image,\n name = \"Effect-\" .. effect.name,\n }\n end\n\n ---@type gloom_Effect\n local effectData = {\n name = effect.name,\n text = effect.text,\n image = image,\n renderedMarkup = effect.renderedMarkup or effect.name,\n immunity = this.createImmunity(effect.name, effect.immunity),\n }\n\n Effects[effect.name] = effectData\n\n updateAssets()\n\n return effectData\nend\n\n---@return table\nfunction this.getConditions()\n return filter(Conditions, function(_, k)\n return not this.isTracker(k)\n end)\nend\n\n---@return table\nfunction this.getClassTrackers()\n return --[[---@type table]] filter(Conditions, function(_, k)\n return this.isTracker(k)\n end)\nend\n\n---@return table\nfunction this.getEffects()\n return Effects\nend\n\n---@return table\nfunction this.getAllEffects()\n local combined = TableUtil.merge(Conditions, Effects)\n\n Logger.debug(\"All effects %s\", combined)\n\n return --[[---@type table]] combined\nend\n\n---@param name string\n---@param condition gloom_Condition_Input\n---@return boolean\nfunction this.validateCondition(name, condition)\n if not name then\n Logger.error(\"No name provided while registering condition\")\n return false\n end\n\n if not condition then\n Logger.error(\"No info provided while registering condition %s\", name)\n return false\n end\n\n if StringUtil.isEmpty(condition.image) then\n Logger.error(\"No image URL provided while registering condition %s\", name)\n return false\n end\n\n if condition.immunity and not this.validateImmunity(name, --[[---@not nil]] condition.immunity) then\n Logger.warn(\"Immunity settings for condition '%s' are not valid. They will be ignored.\")\n condition.immunity = nil\n end\n\n return true\nend\n\n---@param name string\n---@param immunity gloom_Immunity_Input\n---@return boolean\nfunction this.validateImmunity(name, immunity)\n if StringUtil.isEmpty(immunity.image) then\n Logger.error(\"No image URL provided while registering immunity %s\", name)\n return false\n end\n\n ---@type string[]\n local validatedSharedWith = {}\n for _, shared in ipairs(immunity.sharedWith or {}) do\n local appliedOn = getConditionOrEffect(shared)\n if not appliedOn then\n Logger.warn(\"The shared immunity '%s' from immunity '%s' doesn't exist (yet).\"\n .. \" It will be ignored. \"\n .. \" Where the conditions/effects registered in the right order?\",\n shared, name)\n else\n if not appliedOn.immunity then\n Logger.warn(\"The condition or effect '%s' that should be share a immunity from '%s' has not immunity settings defined..\"\n .. \" It will be ignored. \",\n shared, name)\n else\n table.insert(validatedSharedWith, shared)\n end\n end\n end\n\n immunity.sharedWith = validatedSharedWith\n\n return true\nend\n\n---@param className string\n---@param tracker gloom_Tracker_Input\n---@return boolean\nfunction this.validateTracker(className, tracker)\n if not className then\n Logger.error(\"No class name provided while registering tracker\")\n return false\n end\n\n if not tracker then\n Logger.error(\"No info provided while registering tracker for class %s\", className)\n return false\n end\n\n if StringUtil.isEmpty(tracker.image) then\n Logger.error(\"No image URL provided while registering tracker for class %s\", className)\n return false\n end\n\n return true\nend\n\n---@param effect gloom_Effect_Input\n---@return boolean\nfunction this.validateEffect(effect)\n if not effect or not effect.name then\n Logger.error(\"No info provided while registering an effect.\")\n return false\n end\n\n if StringUtil.isEmpty(effect.image) and StringUtil.isEmpty(effect.text) then\n Logger.error(\"No image URL or text provided while registering effect with name '%s'\", effect.name)\n return false\n end\n\n return true\nend\n\n---@param immunities table\n---@param immunity gloom_Immunity\nfunction this.fillImmunity(immunities, immunity)\n if immunities[immunity.name] == nil then\n immunities[immunity.name] = immunity\n\n for _, otherImmunity in ipairs(immunity.applyAlso) do\n this.fillImmunity(immunities, otherImmunity)\n end\n end\nend\n\n---@param ui seb_XmlUi\nfunction this.onUpdateAssets(ui)\n this.updateEffectAssets(ui)\n this.updateConditionAssets(ui)\n this.updateTrackerAssets(ui)\nend\n\n---@param ui seb_XmlUi\nfunction this.updateEffectAssets(ui)\n for _, effect in pairs(this.getEffects()) do\n if effect.image then\n local themeIcon = ThemeApi.getEffectTheme(effect.name, \"icon\")\n local image = --[[---@not nil]] effect.image\n ui.updateAsset(image.name, themeIcon or image.url)\n end\n\n if effect.immunity then\n local themeIcon = ThemeApi.getEffectTheme(effect.name, \"icon\")\n local immunity = --[[---@not nil]] effect.immunity\n ui.updateAsset(immunity.image.name, themeIcon or immunity.image.url)\n end\n\n local renderedMarkup = ThemeApi.getEffectTheme(effect.name, \"renderedMarkup\")\n if renderedMarkup then\n effect.renderedMarkup = renderedMarkup\n end\n end\nend\n\n---@param ui seb_XmlUi\nfunction this.updateConditionAssets(ui)\n for _, condition in pairs(this.getConditions()) do\n local themeIcon = ThemeApi.getConditionTheme(condition.name, \"icon\")\n ui.updateAsset(condition.image.name, themeIcon or condition.image.url)\n\n if condition.immunity then\n themeIcon = ThemeApi.getConditionTheme(condition.name, \"immunity\")\n local immunity = --[[---@not nil]] condition.immunity\n ui.updateAsset(immunity.image.name, themeIcon or immunity.image.url)\n end\n\n local renderedMarkup = ThemeApi.getConditionTheme(condition.name, \"renderedMarkup\")\n if renderedMarkup then\n condition.renderedMarkup = renderedMarkup\n end\n end\nend\n\n---@param ui seb_XmlUi\nfunction this.updateTrackerAssets(ui)\n for _, tracker in pairs(this.getClassTrackers()) do\n ui.updateAsset(tracker.image.name, tracker.image.url)\n end\nend\n\n---@param name string\n---@param immunityInfo nil | gloom_Immunity_Input\n---@return nil | gloom_Immunity\nfunction this.createImmunity(name, immunityInfo)\n if not immunityInfo then\n return nil\n end\n\n ---@type gloom_Immunity\n local immunity = {\n name = name,\n image = {\n url = (--[[---@not nil]] immunityInfo).image,\n name = name .. \"-immunity\"\n },\n applyAlso = {},\n }\n\n for _, shared in ipairs((--[[---@not nil]] immunityInfo).sharedWith or {}) do\n local otherEffect = getConditionOrEffect(shared).immunity.applyAlso\n\n if none(otherEffect, function(o)\n return o.name == name\n end) then\n table.insert(otherEffect, immunity)\n end\n end\n\n return immunity\nend\n\n---@param condition string\n---@return boolean\nfunction this.isTracker(condition)\n return condition:find(TrackerPrefix) ~= nil\nend\n\n\nEvent.registerForUpdateAssets(this.onUpdateAssets)\n\nreturn {}\n\nend)\n__bundle_register(\"registry.ClassInfo\", function(require, _LOADED, __bundle_register, __bundle_modules)\nlocal Object = require(\"lib.Object\")\nlocal StringUtil = require(\"lib.StringUtil\")\nlocal TableUtil = require(\"lib.TableUtil\")\nlocal Logger = require(\"lib.Logger\")\n\nlocal ApiProvider = require(\"api.ApiProvider\")\nlocal ConditionApi = require(\"api.ConditionApi\")\n\n--- This stores all the information relating to the classes in the game.\nlocal ClassInfo = {}\n\nlocal Api = --[[---@type ClassApi]] ApiProvider(\"class\")\nlocal this = {}\n\nlocal Names = {\n AttackModifierCard = \"Attack Modifier\",\n}\n\nlocal FoundAbility = {\n None = 0,\n Exact = 1,\n Nearest = 2,\n Extra = 3,\n}\n\n---@shape gloom_Class_GetAbilityForUnknownClassParams\n---@field card tts__Object\n\n---@param name string\n---@return gloom_Class\nfunction ClassInfo.class(name)\n return ClassInfo.Classes[name]\nend\n\n---@param className string\n---@param abilityName string\nlocal function isExtraCard(className, abilityName)\n local classInfo = ClassInfo.class(className)\n if classInfo then\n if classInfo.extra then\n for _, v in TableUtil.pairs(classInfo.extra) do\n if (v.type == \"Card\" or v.type == \"Deck\") and v.name == abilityName then\n return true\n end\n end\n end\n end\n return false\nend\n\n---@param className string\n---@param abilityName string\nfunction ClassInfo.ability(className, abilityName)\n local ability, state = ClassInfo.nearestAbility(className, abilityName)\n\n if state == FoundAbility.Extra then\n Logger.debug(\"Found extra card '%s' for class '%s'\", abilityName, className)\n elseif state == FoundAbility.Nearest then\n Logger.warn(\"Tried to get ability information for '%s' of class '%s', but it doesn't exist.\"\n .. \" However, found the ability '%s' which is similar in name which will be used instead.\"\n .. \" Maybe there's a typo in the ability name.\"\n .. \" Please verify that this assumption is correct.\",\n abilityName, className, (--[[---@not nil]] ability).name)\n elseif state == FoundAbility.None then\n local classInfo = ClassInfo.class(className)\n if classInfo then\n local allNames = table.sort(TableUtil.keys(classInfo.abilities))\n Logger.error(\"Tried to get ability information for '%s' of class '%s', but it doesn't exist!\"\n .. \" Also couldn't find any ability that is similar. Avaible abilities are: [%s].\",\n abilityName, className, table.concat(allNames, \", \"))\n end\n ability = abilityName\n end\n\n return ability\nend\n\n---@param className string\n---@param abilityName string\nfunction ClassInfo.exactAbility(className, abilityName)\n local classInfo = ClassInfo.class(className)\n local state = FoundAbility.None\n if classInfo then\n if isExtraCard(className, abilityName) then\n return nil, FoundAbility.Extra\n end\n\n local abilityInfo = classInfo.abilities[abilityName]\n if abilityInfo then\n abilityInfo.name = abilityName\n state = FoundAbility.Exact\n end\n\n return abilityInfo, state\n end\n return nil, state\nend\n\n---@param className string\n---@param abilityName string\n---@return (nil | gloom_Ability), number\nfunction ClassInfo.nearestAbility(className, abilityName)\n local classInfo = ClassInfo.class(className)\n local state = FoundAbility.None\n if classInfo then\n if isExtraCard(className, abilityName) then\n return nil, FoundAbility.Extra\n end\n\n local abilityInfo = classInfo.abilities[abilityName]\n if not abilityInfo then\n local allNames = TableUtil.keys(classInfo.abilities)\n local nearestName = StringUtil.findNearest(abilityName, allNames, 3)\n if nearestName then\n abilityInfo = classInfo.abilities[--[[---@not nil]]nearestName]\n abilityInfo.name = nearestName\n state = FoundAbility.Nearest\n end\n else\n abilityInfo.name = abilityName\n state = FoundAbility.Exact\n end\n\n return abilityInfo, state\n end\n return nil, state\nend\n\n---@param className string\n---@return gloom_Spawn_Definition[]\nfunction ClassInfo.getSpawnableElements(className)\n local classInfo = ClassInfo.Classes[className]\n if not classInfo then\n Logger.debug(\"No class information for '%s' while searching for summons.\", className)\n return {}\n end\n\n local elements = {}\n if classInfo.spawn then\n for _, spawnable in ipairs(--[[---@not nil]] classInfo.spawn) do\n spawnable.source = className\n table.insert(elements, spawnable)\n end\n end\n\n for abilityName, ability in pairs(classInfo.abilities) do\n if ability.spawn then\n for _, spawnable in ipairs(--[[---@not nil]] ability.spawn) do\n spawnable.source = abilityName\n table.insert(elements, spawnable)\n end\n end\n end\n\n return elements\nend\n\nApi.registerClass = function(name, info)\n this.registerClass(name, info)\nend\n\nApi.getClasses = function()\n return this.getClasses()\nend\n\nApi.getClass = function(name)\n return this.getClass(name)\nend\n\nApi.getAbility = function(className, abilityName)\n return this.getAbility(className, abilityName)\nend\n\nApi.getAbilityForUnknownClass = function(card)\n return this.getAbilityForUnknownClass(card)\nend\n\nApi.getSpawnableElements = function(className)\n return this.getSpawnableElements(className)\nend\n\n---@param name string\n---@param info gloom_Class_Register\nfunction this.registerClass(name, info)\n if not name then\n Logger.error(\"Class name must be provided for registration!\")\n return\n end\n if not info then\n Logger.error(\"Details not provided for class '%s'!\", name)\n return\n end\n\n if info.isSpecialClass ~= false then\n info.isSpecialClass = true\n end\n ClassInfo.Classes[name] = info\n if info.tracker ~= nil then\n ConditionApi.registerTracker(name, --[[---@not nil]] info.tracker)\n end\nend\n\n---@return table\nfunction this.getClasses()\n return ClassInfo.Classes\nend\n\n---@param name string\n---@return gloom_Class\nfunction this.getClass(name)\n return ClassInfo.class(name)\nend\n\nfunction this.getAbility(className, abilityName)\n return ClassInfo.ability(className, abilityName)\nend\n\n---@param card tts__Object\nfunction this.getAbilityForUnknownClass(card)\n local name = Object.name(card)\n if not Object.isCard(card)\n or name:find(Names.AttackModifierCard)\n then\n return nil\n end\n\n local cardName = StringUtil.replace(name, \"%s?%([%d%/]+%)\")\n\n for className, _ in pairs(ClassInfo.Classes) do\n local ability, state = ClassInfo.exactAbility(className, cardName)\n\n if state == FoundAbility.Exact then\n return className, ability\n end\n end\nend\n\n---@return gloom_Spawn_Definition[]\nfunction this.getSpawnableElements(className)\n return ClassInfo.getSpawnableElements(className)\nend\n\n--- Information about all character classes.\n---@type gloom_Classes\nClassInfo.Classes = {}\n\nreturn ClassInfo\n\nend)\nreturn __bundle_require(\"__root\")\n", "LuaScriptState": "", - "XmlUI": "\n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \r\n \r\n \r\n \n \n \n \n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n\n\n", + "XmlUI": "", "CustomUIAssets": [ { "Name": "Fonts", diff --git a/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json b/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json new file mode 100644 index 0000000..d34dc59 --- /dev/null +++ b/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json @@ -0,0 +1,1344 @@ +{ + "CameraStates": [], + "Decals": [], + "SaveName": "Gloomhaven - TTS Enhanced", + "EpochTime": 1658147616, + "Date": "7/18/2022 2:33:36 PM", + "VersionNumber": "v13.1.1", + "GameMode": "Gloomhaven - TTS Enhanced", + "GameType": "Game", + "GameComplexity": "Low Complexity", + "PlayingTime": [ + 0, + 120 + ], + "PlayerCounts": [ + 0, + 0 + ], + "Tags": [ + "Board Games", + "Cooperative Games", + "Strategy Games", + "Figurines", + "Scripting: Automated", + "Cards", + "Rules" + ], + "Gravity": 0.5, + "PlayArea": 1, + "Table": "Table_None", + "Sky": "Sky_Regal", + "SkyURL": "http://cloud-3.steamusercontent.com/ugc/861734447427622808/DB9EEA3B7977B0D4912A0C4C3A76E36FC3A6062E/", + "Note": "", + "TabStates": { + "0": { + "title": "White", + "body": "", + "color": "White", + "visibleColor": { + "r": 1, + "g": 1, + "b": 1 + }, + "id": 0 + }, + "1": { + "title": "Red", + "body": "", + "color": "Red", + "visibleColor": { + "r": 0.856, + "g": 0.1, + "b": 0.094 + }, + "id": 1 + }, + "2": { + "title": "Green", + "body": "", + "color": "Green", + "visibleColor": { + "r": 0.192, + "g": 0.701, + "b": 0.168 + }, + "id": 2 + }, + "3": { + "title": "Blue", + "body": "", + "color": "Blue", + "visibleColor": { + "r": 0.118, + "g": 0.53, + "b": 1 + }, + "id": 3 + }, + "4": { + "title": "Black", + "body": "", + "color": "Black", + "visibleColor": { + "r": 0.25, + "g": 0.25, + "b": 0.25 + }, + "id": 4 + } + }, + "MusicPlayer": { + "RepeatSong": false, + "PlaylistEntry": 3, + "CurrentAudioTitle": "Death", + "CurrentAudioURL": "http://cloud-3.steamusercontent.com/ugc/792010997184374182/B0FF76DCB3D979C6A3A630F45A9FD58E7390D6C6/", + "AudioLibrary": [ + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184359809/DA27CF611C537C0C6A94893C0330B24358296475/", + "Item2": "Awakening" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184371128/145B47EC47D331B4AE1CB59FDE75E0116E10280D/", + "Item2": "Calm" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184371906/F8E4FC7CBD21DD2430F906F77055819AD0474996/", + "Item2": "Day Travel" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184374182/B0FF76DCB3D979C6A3A630F45A9FD58E7390D6C6/", + "Item2": "Death" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184376716/0407BB616E681D199DDEC384058B3CBF3DD3FEAD/", + "Item2": "Epilude" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184379961/314F9E57AF4CF475B86BAF58AD5A006DB16CE86D/", + "Item2": "Goblin Combat" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184385296/1A83AFF3AF0959546E8444D9A3F4174BC35E5336/", + "Item2": "Human Combat 1" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184387091/0554504FC54BACCAA83CAB542EB33FC41C87346B/", + "Item2": "Human Combat 2" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184389261/F753ACB43BA28C799B808388D6A6E4A71BDB78CB/", + "Item2": "Intrigue" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184392871/C1ACD1C3E628B836F6DEFF643B618418A339EECC/", + "Item2": "Map" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184394727/6212F5FF59B6D980AA95CCF1B06BE32F8DFF3366/", + "Item2": "Mistique" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184397131/B462582B327D41981E3A3893AED045C9DFA37D68/", + "Item2": "Night Travel" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184398544/F8246EDA891BDFDE3CE9183A139D514F78A582CF/", + "Item2": "Orc Combat 1" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184400189/F25ADB6B0FA920C0D9F6A67546DAD85C70C9504E/", + "Item2": "Orc Combat 2" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184402108/415A2E2F690E8241A23FBBAFB8D9BAB0D469F8F4/", + "Item2": "Prelude" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184404898/8956C1A3A9A41B0F54B4DE400EC76F7723332310/", + "Item2": "Reflective" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184406070/21B923240147A1785E49CAE3AFA27C045405694C/", + "Item2": "Setting Up" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184407878/A2458FAB2EE78A3AA4B26271485982FA02665FDA/", + "Item2": "Tension" + }, + { + "Item1": "http://cloud-3.steamusercontent.com/ugc/792010997184409426/32FB9FCA1D2DD24CF88150A31E16108CEB21559D/", + "Item2": "Undead Combat" + } + ] + }, + "Grid": { + "Type": 1, + "Lines": false, + "Color": { + "r": 0, + "g": 0, + "b": 0 + }, + "Opacity": 0.1482, + "ThickLines": false, + "Snapping": false, + "Offset": false, + "BothSnapping": true, + "xSize": 1.75, + "ySize": 1.75, + "PosOffset": { + "x": 0, + "y": 1, + "z": 0 + } + }, + "Lighting": { + "LightIntensity": 0.3479, + "LightColor": { + "r": 1, + "g": 0.9649, + "b": 0.8902 + }, + "AmbientIntensity": 1.908, + "AmbientType": 1, + "AmbientSkyColor": { + "r": 0.4824, + "g": 0.4549, + "b": 0.4078 + }, + "AmbientEquatorColor": { + "r": 0.4824, + "g": 0.4549, + "b": 0.4078 + }, + "AmbientGroundColor": { + "r": 0.4824, + "g": 0.4549, + "b": 0.4078 + }, + "ReflectionIntensity": 0, + "LutIndex": 26, + "LutContribution": 0.616, + "LutURL": "" + }, + "Hands": { + "Enable": true, + "DisableUnused": false, + "Hiding": 0 + }, + "ComponentTags": { + "labels": [ + { + "displayed": "Inactive Character", + "normalized": "inactive_character" + }, + { + "displayed": "Bag of Summons", + "normalized": "bag_of_summons" + }, + { + "displayed": "Available Rift Events", + "normalized": "available_rift_events" + }, + { + "displayed": "Character", + "normalized": "character" + }, + { + "displayed": "Enemy", + "normalized": "enemy" + }, + { + "displayed": "Gloomhaven Campaign", + "normalized": "gloomhaven_campaign" + }, + { + "displayed": "MonsterBag", + "normalized": "monsterbag" + }, + { + "displayed": "MonsterMat", + "normalized": "monstermat" + }, + { + "displayed": "MonsterStatSheet", + "normalized": "monsterstatsheet" + }, + { + "displayed": "Tile - Start Area", + "normalized": "tile_-_start_area" + }, + { + "displayed": "ItemBoots", + "normalized": "itemboots" + }, + { + "displayed": "ItemHead", + "normalized": "itemhead" + }, + { + "displayed": "ItemChest", + "normalized": "itemchest" + }, + { + "displayed": "ItemTwoHanded", + "normalized": "itemtwohanded" + }, + { + "displayed": "ItemOneHanded", + "normalized": "itemonehanded" + }, + { + "displayed": "ItemConsumable", + "normalized": "itemconsumable" + }, + { + "displayed": "Soft-Lock", + "normalized": "soft-lock" + }, + { + "displayed": "Has Health", + "normalized": "has_health" + }, + { + "displayed": "Summon", + "normalized": "summon" + }, + { + "displayed": "LockedScenarios", + "normalized": "lockedscenarios" + }, + { + "displayed": "UnlockedScenarios", + "normalized": "unlockedscenarios" + }, + { + "displayed": "ActiveScenario", + "normalized": "activescenario" + }, + { + "displayed": "Party Sheet", + "normalized": "party_sheet" + }, + { + "displayed": "Shop Items", + "normalized": "shop_items" + }, + { + "displayed": "Item Designs", + "normalized": "item_designs" + }, + { + "displayed": "Reward Items", + "normalized": "reward_items" + }, + { + "displayed": "Solo Reward Items", + "normalized": "solo_reward_items" + }, + { + "displayed": "Personal Quests", + "normalized": "personal_quests" + }, + { + "displayed": "Available City Events", + "normalized": "available_city_events" + }, + { + "displayed": "Available Road Events", + "normalized": "available_road_events" + }, + { + "displayed": "LockedCharacters", + "normalized": "lockedcharacters" + }, + { + "displayed": "Book", + "normalized": "book" + }, + { + "displayed": "Has Conditions", + "normalized": "has_conditions" + }, + { + "displayed": "Class Envelope", + "normalized": "class_envelope" + }, + { + "displayed": "Item", + "normalized": "item" + }, + { + "displayed": "Item Solo Reward", + "normalized": "item_solo_reward" + }, + { + "displayed": "Guide Book", + "normalized": "guide_book" + }, + { + "displayed": "Has Actions", + "normalized": "has_actions" + }, + { + "displayed": "Has Aid Tokens", + "normalized": "has_aid_tokens" + }, + { + "displayed": "Has Action", + "normalized": "has_action" + }, + { + "displayed": "Bag of Extra Content", + "normalized": "bag_of_extra_content" + }, + { + "displayed": "Bag of Monster Abilities", + "normalized": "bag_of_monster_abilities" + } + ] + }, + "Turns": { + "Enable": false, + "Type": 0, + "TurnOrder": [], + "Reverse": false, + "SkipEmpty": false, + "DisableInteractions": false, + "PassTurns": true, + "TurnColor": "" + }, + "DecalPallet": [ + { + "Name": "Enhancement Jump", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726767406/7C34D7EA30D5E6FF90BBA88A767ACB98467FC557/", + "Size": 0.2 + }, + { + "Name": "Enhancement Air", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726768884/CC8308C7D7DD6185F3EFA99F452C83101E1E95E3/", + "Size": 0.2 + }, + { + "Name": "Enhancement Fire", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726770166/CD369CF7C22FAD932A71C65BA811AE137234270D/", + "Size": 0.2 + }, + { + "Name": "Enhancement Frost", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726771564/3C69B7D1E66E8FC022B2BD77CC427E8AEC5D9331/", + "Size": 0.2 + }, + { + "Name": "Enhancement Earth", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726773039/E83D1E9F06938B3B5355F0A6A49D98B78B1F2594/", + "Size": 0.2 + }, + { + "Name": "Enhancement Light", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726774294/5BBAC718ED55E90563D2B24848DE3ACAEBD76FE9/", + "Size": 0.2 + }, + { + "Name": "Enhancement Dark", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726982499/9C14793623433565A9D6E5B3C608C1F6FBB84C76/", + "Size": 0.2 + }, + { + "Name": "Enhancement Any Element", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726984504/2F962A64E2A9888C90856D9C150EF81089745137/", + "Size": 0.2 + }, + { + "Name": "Enhancement Bless", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726986639/FDF514FDA20826ACA054BE5CBA579AE6FB25A263/", + "Size": 0.2 + }, + { + "Name": "Enhancement Curse", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726987796/ABB2E197E22015077D39A378AA9A9E7A317C69FE/", + "Size": 0.2 + }, + { + "Name": "Enhancement Disarm", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726989091/DB993A58BC37C60B64A2F2CCDAED888F6176BC43/", + "Size": 0.2 + }, + { + "Name": "Enhancement Wound", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726992099/9B90EEDC5B68B4FFD04C0037008F9C96BA603C24/", + "Size": 0.2 + }, + { + "Name": "Enhancement Poison", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726994202/6EAECD4A8A6A56F42834D486A21219002B883C53/", + "Size": 0.2 + }, + { + "Name": "Enhancement Strengthen", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726995467/72FC8E8918F788646DBD0C571FDAB214D7A36445/", + "Size": 0.2 + }, + { + "Name": "Enhancement Plus 1", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726761532/C3BB327E15466B74ED256A0BD7B66C20A8825861/", + "Size": 0.2 + }, + { + "Name": "Enhancement Muddle", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726993395/820BE84906894B87F0C0DAC77C3BFBCD9B1AD504/", + "Size": 0.2 + }, + { + "Name": "Enhancement Stun", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/83721958671678940/616CC0951CB17024448D3CBC7E5CA9D3A05D7555/", + "Size": 0.2 + }, + { + "Name": "Enhancement Regenerate", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/791986676667649885/92F7F934D298524F6F72997095DDBA073D9A15BF/", + "Size": 0.2 + }, + { + "Name": "Damage Counter", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535264491742665/50D53957D9109C2E0A48AA6A7A01131D4D080199/", + "Size": 1 + }, + { + "Name": "12", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739361729/5B6504267450F62683B6CAF71C3D95CF99A2DB3D/", + "Size": 1 + }, + { + "Name": "Exit B Horizontal", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739478814/42020D0729D7CF9B4E6A4265FBC569523F5457B5/", + "Size": 1.7 + }, + { + "Name": "Enter B Horizontal", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739477335/EEAA5A0D8700CAEAA2449E0D97A80899C1F9A03E/", + "Size": 1.7 + }, + { + "Name": "Enhancement Immobilize", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726990928/14B27BE1DC8F226D05FDB7D7D5D420FFCA6919B0/", + "Size": 0.4 + }, + { + "Name": "Enter B Vertical", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739352844/1E6C3779FE86257F10F65EE014BDD3996B93B3BE/", + "Size": 1.7 + }, + { + "Name": "Exit B Vertical", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739354096/ED74BC5FF4EEE0355B75C75F9B70C8CB23A2058E/", + "Size": 1.7 + }, + { + "Name": "1", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225889607/DFA4EF43DC2D1A8B6F1C7F22E7C31718E35CCB31/", + "Size": 1 + }, + { + "Name": "2", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225890374/9B9B38BE2BB6A23EC21C977911FEC8915C74FF19/", + "Size": 1 + }, + { + "Name": "3", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225891903/93FF8C7F051FDD06DAEBD17F2BBD917960CB1779/", + "Size": 1 + }, + { + "Name": "4", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225892191/F2D0FC8179C04D3E4BFD2A546FAD7BC76D813091/", + "Size": 1 + }, + { + "Name": "6", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225892918/3720A0E91CA48BB3972BBE924B26A34FFA34F57F/", + "Size": 1 + }, + { + "Name": "5", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225892469/3DC5E471FC5F2CB1D6DBFC91DF5ADB2751A52E12/", + "Size": 1 + }, + { + "Name": "7", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225893323/DBBE037A62CFA34AD9E05F34A035BDE7A22995EB/", + "Size": 1 + }, + { + "Name": "8", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225893738/3DAE234411D631E7076F3639F4389ECE2019FB73/", + "Size": 1 + }, + { + "Name": "9", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225894076/3DF6535BBE81D65752CBCF9EBC0119A6A41696FC/", + "Size": 1 + }, + { + "Name": "10", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/929311677225894415/18B81F2763921B7C9F11090AB9E00CABE4DDF344/", + "Size": 1 + }, + { + "Name": "11", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739361090/2257A478E8860FCD13E3732A7240309ED42D0483/", + "Size": 1 + }, + { + "Name": "Enter A Horizontal", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739476269/44FF19D8D66A122FBC2FA7354DC5521EDC01969E/", + "Size": 1.7 + }, + { + "Name": "Enter A Vertical", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739352080/21F9805C7808B9F06B1D4555ACA1D4377AD0254D/", + "Size": 1.7 + }, + { + "Name": "Exit A Horizontal", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739478197/1F3C111CF0FE44F395C7A44573B76D6088B62486/", + "Size": 1.7 + }, + { + "Name": "Exit A Vertical", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/1717535337739353452/1F6647331281E380A0B2CD7626C5C74A10EF0160/", + "Size": 1.7 + }, + { + "Name": "Enhancement AoE Hex", + "ImageURL": "http://cloud-3.steamusercontent.com/ugc/938309024726997997/041C0C738331AA8B4D8281BE8E9BE2C2963D823D/", + "Size": 0.2 + } + ], + "LuaScript": "", + "LuaScriptState": "", + "XmlUI": "\n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \r\n \r\n \r\n \n \n \n \n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n\n\n", + "CustomUIAssets": [ + { + "Name": "Fonts", + "URL": "http://cloud-3.steamusercontent.com/ugc/1785137125151946867/17CE13C819659BEFD29663308656B11B21A5C811/", + "Type": 1 + }, + { + "Name": "Checkmark", + "URL": "http://cloud-3.steamusercontent.com/ugc/930437282873621352/B1F762326F5A796804428304D3453196F1FE72F8/" + }, + { + "Name": "Solo Mode", + "URL": "http://cloud-3.steamusercontent.com/ugc/930437282869951537/A7F6D5FDA5A3257A9BABF914136016F38DDB67B4/" + }, + { + "Name": "Radio Button", + "URL": "http://cloud-3.steamusercontent.com/ugc/957460441258181927/C92E3147B5B842EAB9E3A217D7BDF355D6CD84C0/" + }, + { + "Name": "Radio Button Selected", + "URL": "http://cloud-3.steamusercontent.com/ugc/957460441258182404/4A6DA7F067D83ABD1C53B10AEA37B140133424BA/" + }, + { + "Name": "Want Delete", + "URL": "http://cloud-3.steamusercontent.com/ugc/957460441258251718/60E4E1B63BC787C5A12AD00B5DE53087CCF758A5/" + }, + { + "Name": "Yes", + "URL": "http://cloud-3.steamusercontent.com/ugc/957460441258252246/B2CF6D25D406E56E89EFAE376401C2B068EDF5E7/" + }, + { + "Name": "No", + "URL": "http://cloud-3.steamusercontent.com/ugc/957460441258252698/2598B558AEB39BCF7F5319C55C6A4366FEFC024F/" + }, + { + "Name": "S", + "URL": "http://cloud-3.steamusercontent.com/ugc/1025077659114306575/9D076EE8D1CE57FD0C010E18ED2FA470231AA667/" + }, + { + "Name": "Add Target", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335238589/BD19ECEF872BB4DF3D3B1E6234FF636A5A3F7774/" + }, + { + "Name": "Attack-W", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335234958/383031842C15BBF4A07639649ED0191F3BC961F9/" + }, + { + "Name": "Loot-W", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335244550/762F7E4C11F371564644CA3C4777C92A946B24BC/" + }, + { + "Name": "Health-W", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335242112/FA9A26B8CDEE3ADC9F23B0D340FA85E58197FB49/" + }, + { + "Name": "Range-W", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335253340/C5D3C835BAD31C6988B5E6E38B13632EB6FB3384/" + }, + { + "Name": "Move-W", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335287116/12E976BAB0EFB30F147805B7172AF46F4A055821/" + }, + { + "Name": "NewUpdateNotification", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647137528722610/01C306A63CC50F260095F8FD338EAD2E8DBD36A7/" + }, + { + "Name": "option.header", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647367272110432/0AB247600FF0D59D4AEE13117DA4FB489412AD97/" + }, + { + "Name": "option.row", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647367272108923/3BFACF28BE4EB95718A65E34A2979AB480FE335C/" + }, + { + "Name": "element.checkbox.false", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647321317707575/5C669F905FEA1F581A362EA8E9CD166CA6CED57C/" + }, + { + "Name": "element.checkbox.true", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647367272105183/C6D3D021348DB6BFD891A871BC4EA6D78E11DDFD/" + }, + { + "Name": "element.close", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647321317706979/A877AF6E9D184213AE72408651251933C037E136/" + }, + { + "Name": "element.dropdown", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647321317708245/87000840B16EC9B07C76C43DEC7B4F86E9154450/" + }, + { + "Name": "element.dropdown.check", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647367272106475/D85EBA610CEE366AABFD005AA313F433B3526233/" + }, + { + "Name": "element.dropdown.arrow", + "URL": "http://cloud-3.steamusercontent.com/ugc/1691647367272117711/B0B88A739E64F627742F5714865E4DE663E8EF6B/" + }, + { + "Name": "element.imagechooser.asset_appearance.background_Skull Logo (Red)", + "URL": "http://cloud-3.steamusercontent.com/ugc/1692776269029816766/D4CFB162FBAE84CC94508102C4B8A965E1ED3DA9/" + }, + { + "Name": "element.imagechooser.asset_appearance.background_GH Box Art (Brown)", + "URL": "http://cloud-3.steamusercontent.com/ugc/1692776269029816416/A2B04B6A75F9F9C576354DE5A37F0E8CDA05A381/" + }, + { + "Name": "element.imagechooser.asset_appearance.background_FC Box Art (Purple)", + "URL": "http://cloud-3.steamusercontent.com/ugc/1692776269029816666/856391292E07195D26446B15D734D90B2B7C9FDA/" + }, + { + "Name": "element.imagechooser.asset_appearance.background_JotL Poker Table (Grey)", + "URL": "http://cloud-3.steamusercontent.com/ugc/1692776269029816504/17CE4CE6C3A2CD904E980AA7706731E72E34E3B6/" + }, + { + "Name": "Damage", + "URL": "http://cloud-3.steamusercontent.com/ugc/912421276945957660/E015539C26B82D831A6361BFE476DB0C8810D132/" + }, + { + "Name": "Overlay-Hot Coals", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/hazardous-terrain/gh-hot-coals.png" + }, + { + "Name": "Overlay-Thorns", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/hazardous-terrain/gh-thorns.png" + }, + { + "Name": "Overlay-Boulder", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/obstacles/gh-boulder-1.png" + }, + { + "Name": "Overlay-Boulder 3", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/obstacles/gh-boulder-3.png" + }, + { + "Name": "Overlay-Rock Column", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/obstacles/gh-rock-column.png" + }, + { + "Name": "Overlay-Bear Trap", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/traps/gh-bear-trap.png" + }, + { + "Name": "Overlay-Spike Trap", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/traps/gh-spike-pit-trap.png" + }, + { + "Name": "Overlay-Poison Gas", + "URL": "https://github.com/any2cards/gloomhaven/raw/5a840817052fb7da3e299b01158e05782c363c68/images/tokens/gloomhaven/traps/gh-poison-gas-trap.png" + }, + { + "Name": "ScenarioAid_1", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855709507/E9FBF8A18ABEF63F046D0C665CA38315E79765D8/" + }, + { + "Name": "ScenarioAid_2", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855709728/DEDA6F0850D4BE3C5C647F02A8893258633B07CF/" + }, + { + "Name": "ScenarioAid_3", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855710013/F94EFF3A9C97D29C3829433012F6A8208DDAA610/" + }, + { + "Name": "ScenarioAid_4", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855710238/3F261915C15B2924743CAE4FB1CD4EAB64B7568D/" + }, + { + "Name": "ScenarioAid_a", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855710452/7F215E78C2652D795415D0C9DF97788D269E99A8/" + }, + { + "Name": "ScenarioAid_b", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855710644/D5B39624A18BB588FF22A16805E4237E3532AFA2/" + }, + { + "Name": "ScenarioAid_c", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855710813/859300C2A3798F50C95FEDE9EAFAD30419F20139/" + }, + { + "Name": "ScenarioAid_d", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855711013/0089277091E642F2417165DD0866D3966FDFB243/" + }, + { + "Name": "ScenarioAid_e", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855711219/892035BB2267DA06B560D1D2234D77A0C71D318B/" + }, + { + "Name": "ScenarioAid_f", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855711443/5FDA080A43EBF4DAFDB188DB608FE2763174B3B0/" + }, + { + "Name": "ScenarioAid_g", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855711637/82FF003F7A39033AADCE32A463E981AC367FF644/" + }, + { + "Name": "ScenarioAid_h", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855711827/A2A65B43B211903499A28A3CA7770E328B1F66FE/" + }, + { + "Name": "ScenarioAid_i", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855712052/CCD5C53C3093AAB6AE45A56C142D75DEAF68729C/" + }, + { + "Name": "ScenarioAid_j", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855712324/F67E259576B24287C669E0FB0F56E9BEDA45EFA3/" + }, + { + "Name": "ScenarioAid_Obj_1", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855706901/377CB0A18DB9C06B9E6F9F0EA512A0B2FFBB9505/" + }, + { + "Name": "ScenarioAid_Obj_2", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855707721/4AFB20447F9C338AFCCDE8B4CDCC0CB34D9AACB6/" + }, + { + "Name": "ScenarioAid_Obj_3", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855693376/2766E8E8EE1B19934BC0A6781D4233FE397F19A5/" + }, + { + "Name": "ScenarioAid_Obj_4", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855707911/176A38A0EB1066E2C95B1DA19ABFC478C343F2ED/" + }, + { + "Name": "ScenarioAid_Obj_5", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855708096/70AD1F0422F09280C535E784EF5FF14D890E2413/" + }, + { + "Name": "ScenarioAid_Obj_6", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855708285/C055CC3DA7077E9A71F9AE445C727F444155603E/" + }, + { + "Name": "ScenarioAid_Obj_7", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855708488/AB9C7CF64A74614616FF74B5028EF13C5E0E23FD/" + }, + { + "Name": "ScenarioAid_Obj_8", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855708904/610FE768EC52BACE5F583518CD2A2E636B06A005/" + }, + { + "Name": "ScenarioAid_Obj_9", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855709098/20764DE0290A53F9109BCD71EABD4D2D32EA5295/" + }, + { + "Name": "ScenarioAid_Obj_10", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855709285/08E945818FD9A70FFBE9771AE35950888A3008B9/" + }, + { + "Name": "ScenarioAid_Obj_11", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855707309/61B70C58BB54A56CE82347BF9194EE8F9AA8D214/" + }, + { + "Name": "ScenarioAid_Obj_12", + "URL": "http://cloud-3.steamusercontent.com/ugc/878629555855707539/5A33C6661B5ACE29451D7EF4FA1A8B1518454E31/" + }, + { + "Name": "TrackerBrute", + "URL": "http://cloud-3.steamusercontent.com/ugc/83722391140264566/DC437F88C225F04C6CCE924EA4C3BB31FCD3F3A9/" + }, + { + "Name": "TrackerCragheart", + "URL": "http://cloud-3.steamusercontent.com/ugc/83722391140272850/20C525FCF70B280811A95F2631CB8597B69FA32B/" + }, + { + "Name": "TrackerMindthief", + "URL": "http://cloud-3.steamusercontent.com/ugc/83722391140273516/BC25E0469BCF4E7D7455FD1D8E5D983A64015CF9/" + }, + { + "Name": "spawn.element.default", + "URL": "http://cloud-3.steamusercontent.com/ugc/1816633112929215487/DB175CC19718DFCE3859E43ACB73D523EACF79B3/" + }, + { + "Name": "TrackerScoundrel", + "URL": "http://cloud-3.steamusercontent.com/ugc/83722391140274314/EBC76552C60E3E1281BBFAA96EF535709E0DDD71/" + }, + { + "Name": "TrackerDiviner", + "URL": "http://cloud-3.steamusercontent.com/ugc/958605698971032961/FC87136CA9193E423627CF26F69CA9E9C166FACB/" + }, + { + "Name": "TrackerTinkerer", + "URL": "http://cloud-3.steamusercontent.com/ugc/83722391140275444/E0B72DC69A07699D5DB852D63155F502989283E0/" + }, + { + "Name": "TrackerSpellweaver", + "URL": "http://cloud-3.steamusercontent.com/ugc/83722391140274964/143D5F8B9F7C47FB83BBAF900BE9FDFA368CE513/" + }, + { + "Name": "TrackerHatchet", + "URL": "http://cloud-3.steamusercontent.com/ugc/1023946882429626180/E31CBEE29EFDC7A723ED10EEDDE769A2597A6483/" + }, + { + "Name": "TrackerRed Guard", + "URL": "http://cloud-3.steamusercontent.com/ugc/1023946882429627820/0C301D833410A3167A541E18EE679DC84639E1F9/" + }, + { + "Name": "TrackerVoidwarden", + "URL": "http://cloud-3.steamusercontent.com/ugc/1023946882429633403/27CCE91CC914E113FA5D4C32866442D7B14819A5/" + }, + { + "Name": "TrackerDemolitionist", + "URL": "http://cloud-3.steamusercontent.com/ugc/1023946882429620732/8AF086FA26F0D1B4F594701608942D5853B8C63A/" + }, + { + "Name": "TrackerBeast Tyrant", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138553938610861/F8B7EF453D80232795BDE36FA070AE3F32D115EE/" + }, + { + "Name": "TrackerBerserker", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138762281835723/8A322E4924315CA590305543DF46896F89E22535/" + }, + { + "Name": "TrackerDoomstalker", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138553939035626/893D92412263CE1A9C4E257D03A298E4586F6F6B/" + }, + { + "Name": "TrackerElementalist", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138762291579437/6A1873B7FB1DB0F7D379A6628F6166B529180B1D/" + }, + { + "Name": "TrackerNightshroud", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138762281738085/65B88B611F776E7FD2334409D404E4C750A33934/" + }, + { + "Name": "TrackerPlagueherald", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138762291876706/8A821E683D786FFA0F04A9AA2A947F85778ADBC2/" + }, + { + "Name": "TrackerQuartermaster", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138762291809141/D48BD51510E9C15C05E726BF750EEA0B5A6BA3D0/" + }, + { + "Name": "TrackerSoothsinger", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138762291715667/2D2D8638E8721C86EF0A38C230C8CD73CF6A61CC/" + }, + { + "Name": "TrackerSummoner", + "URL": "http://cloud-3.steamusercontent.com/ugc/930432409048775148/48101BC74B8653EFB2C5BF3C5C814121FA3D5735/" + }, + { + "Name": "TrackerSunkeeper", + "URL": "http://cloud-3.steamusercontent.com/ugc/2439138553938828796/730FE7530468D1BBA8D74FFC1316FB47C52301E5/" + }, + { + "Name": "TrackerBladeswarm", + "URL": "http://cloud-3.steamusercontent.com/ugc/957469956542531294/89F4466B4A39A27032CA047ECFAC19131AE103BD/" + }, + { + "Name": "Disarm", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671672270/3DD70341FE7C762F57930DCB123FC3B3F669E70D/" + }, + { + "Name": "Disarm-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766656381/EF716F5A021E59A8E5A2AB05AAD7A2779C4564A8/" + }, + { + "Name": "Immobilize", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671673391/877AC6C5C993070B4ACE2F0BA540FD277C9FC174/" + }, + { + "Name": "Immobilize-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766654664/84F766538593F1248F4F57907CB7A416F65C5282/" + }, + { + "Name": "Invisible", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671674221/401A435DA860B2CA7E87D570905F58622C69229E/" + }, + { + "Name": "Muddle", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671681965/01EAE5A55C229B9F1067AF64A398667F3F25E841/" + }, + { + "Name": "Muddle-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766649540/623C36ABECA67CE85A271B5E43644FD914300341/" + }, + { + "Name": "Poison", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671677797/DEAEC4FAB2B54FEA069B0C8995DB7E97160767F3/" + }, + { + "Name": "Poison-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766650742/006AE5584AEB4E7E7343E14C0749B0B8F5D0CF62/" + }, + { + "Name": "Strengthen", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671675865/B73B248633B5C6A8AC44EC7BBBDE56D6632FD208/" + }, + { + "Name": "Stun", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671678940/616CC0951CB17024448D3CBC7E5CA9D3A05D7555/" + }, + { + "Name": "Stun-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766657508/F15D80FEA22D73B64C8BD23F49EC3C08018FFEBE/" + }, + { + "Name": "Wound", + "URL": "http://cloud-3.steamusercontent.com/ugc/83721958671672778/8AD44E86BD73836627A25E71B6B3DA081A0E64D6/" + }, + { + "Name": "Wound-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766651868/C5A8B87571357AFD137EC217C7A66D6E698E840F/" + }, + { + "Name": "Regenerate", + "URL": "http://cloud-3.steamusercontent.com/ugc/791986676667649885/92F7F934D298524F6F72997095DDBA073D9A15BF/" + }, + { + "Name": "Bane", + "URL": "http://cloud-3.steamusercontent.com/ugc/1751310104237974843/789962F15529BA6B690C15AC69EF2DD01657EC27/" + }, + { + "Name": "Bane-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1753609293981077874/F203F3302EA532506317E53D9C2B568D67D3913D/" + }, + { + "Name": "Brittle", + "URL": "http://cloud-3.steamusercontent.com/ugc/1756937968345531733/027D45CA1FAFCE3E9ED9D97D170F661B5CE560CA/" + }, + { + "Name": "Brittle-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1753609293981079023/2D287D537218DA389E7FDA9B67CA0D544CCCF403/" + }, + { + "Name": "Injure", + "URL": "http://cloud-3.steamusercontent.com/ugc/1751310104237975751/009A9EDE81999911EE4DF9A24833FD5DCAF217EE/" + }, + { + "Name": "Ward", + "URL": "http://cloud-3.steamusercontent.com/ugc/1751310104237976362/B10899C97992FF30CE9CEF05C9B89D5A8254F4F8/" + }, + { + "Name": "Effect-Curse", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335237445/F10A40684FA3670D0E110191611676C44F489748/" + }, + { + "Name": "Curse-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766658589/7243BAFBA97A26003905F2F528B2A1287ED3F628/" + }, + { + "Name": "Effect-Fly", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335240329/FEB35AD038A6F0645485F8B62F7D67EB8DEEFE77/" + }, + { + "Name": "Effect-Generate Air", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726768884/CC8308C7D7DD6185F3EFA99F452C83101E1E95E3/" + }, + { + "Name": "Effect-Generate Any", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726984504/2F962A64E2A9888C90856D9C150EF81089745137/" + }, + { + "Name": "Effect-Generate Dark", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726982499/9C14793623433565A9D6E5B3C608C1F6FBB84C76/" + }, + { + "Name": "Effect-Generate Earth", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726773039/E83D1E9F06938B3B5355F0A6A49D98B78B1F2594/" + }, + { + "Name": "Effect-Generate Fire", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726770166/CD369CF7C22FAD932A71C65BA811AE137234270D/" + }, + { + "Name": "Effect-Generate Ice", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726771564/3C69B7D1E66E8FC022B2BD77CC427E8AEC5D9331/" + }, + { + "Name": "Effect-Generate Light", + "URL": "http://cloud-3.steamusercontent.com/ugc/938309024726774294/5BBAC718ED55E90563D2B24848DE3ACAEBD76FE9/" + }, + { + "Name": "Effect-Generate Light or Dark", + "URL": "http://cloud-3.steamusercontent.com/ugc/1478820976197633400/506D08CF7FBB2F6100F15A80CD54D9EE2BF35181/" + }, + { + "Name": "Effect-Jump", + "URL": "http://cloud-3.steamusercontent.com/ugc/1948388036162552120/8D950282276104420AB74B5BB3F898382C1774BA/" + }, + { + "Name": "Effect-Move", + "URL": "http://cloud-3.steamusercontent.com/ugc/1948388036162545819/12E976BAB0EFB30F147805B7172AF46F4A055821/" + }, + { + "Name": "Effect-Pierce", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335248486/100217496DD5B5D150F31B0FFB080E7C7283705D/" + }, + { + "Name": "Effect-Pull", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335249804/4C73073477F29074DE05B08F3F86D9FEF2705881/" + }, + { + "Name": "Pull-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766660950/32B6123D44A700D801FB6D9506A1869CADC997F0/" + }, + { + "Name": "Effect-Push", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335251134/78C03FAA36481E66B09206BC95EDD19F0C0FF812/" + }, + { + "Name": "Push-immunity", + "URL": "http://cloud-3.steamusercontent.com/ugc/1719786285766659957/983E46854AE174881307D59D5752A4869C6CB656/" + }, + { + "Name": "Effect-Range", + "URL": "http://cloud-3.steamusercontent.com/ugc/1948388036162544849/C5D3C835BAD31C6988B5E6E38B13632EB6FB3384/" + }, + { + "Name": "Effect-Retaliate", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335255371/35023C8E2969F322C01150148B92D827E6DB560C/" + }, + { + "Name": "Effect-Shield", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335259632/505B1D168C648AF41FC04DB3FE78E9EC27531346/" + }, + { + "Name": "Effect-Target", + "URL": "http://cloud-3.steamusercontent.com/ugc/1019444160335261306/4CA039FF943FF9446707CA42E15ED87986D4E734/" + }, + { + "Name": "Enemy-Icon-Ancient Artillery", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075790738/993479463520298E911DB4BFB7CD732FA29344A7/" + }, + { + "Name": "Enemy-Icon-Bandit Archer", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075790807/F181B084F5CA91BC0E0F75C682C6993C4BCBDA43/" + }, + { + "Name": "Enemy-Icon-City Archer", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791235/1B01BBBC4EF8BE68D12391AAED6309BF39833FA3/" + }, + { + "Name": "Enemy-Icon-Inox Archer", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792236/3D8BA2902D7920E02B3782DA0E379442601DA1C8/" + }, + { + "Name": "Enemy-Icon-Aesther Ashblade", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075790596/A723A4F1B5F6E9149A6C69D89AC29FF30492247C/" + }, + { + "Name": "Enemy-Icon-Cave Bear", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791165/8FF711ADA7941FCD8D0140ABB92C583D0D63D492/" + }, + { + "Name": "Enemy-Icon-Cultist", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791401/EA5104067C832F25EF2F4B942BFD100145A59242/" + }, + { + "Name": "Enemy-Icon-Deep Terror", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791552/184035C5C279758683B0FEBE09ED6F2E84F3C75B/" + }, + { + "Name": "Enemy-Icon-Earth Demon", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791625/0B37CFE711F4D78EDB480B97A9E9E89729B9504E/" + }, + { + "Name": "Enemy-Icon-Flame Demon", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791754/C8F7BB8EE2F9747B4826628B0DAB088D2D326476/" + }, + { + "Name": "Enemy-Icon-Frost Demon", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791890/5C58977413D62D16B899D929F6C267C062F7D87C/" + }, + { + "Name": "Enemy-Icon-Giant Viper", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791955/BAB43543950168C20BE643F1C465593BC02031BE/" + }, + { + "Name": "Enemy-Icon-Bandit Guard", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075790950/BC8FF7FAC787DC1F0587EEDED3B34FCBCAEFCA35/" + }, + { + "Name": "Enemy-Icon-City Guard", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791313/23008484069CCEA8F4734134256A5CD4741D6EDE/" + }, + { + "Name": "Enemy-Icon-Inox Guard", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792402/0735AB9615F4D5A48634258D08E3EF07E9423230/" + }, + { + "Name": "Enemy-Icon-Harrower Infester", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792028/450134CD91ED41181881969207CEF1CE2A8D0E52/" + }, + { + "Name": "Enemy-Icon-Hound", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792088/9B6639C6009C97C81461B05E4C14D4746333DFE0/" + }, + { + "Name": "Enemy-Icon-Black Imp", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791022/1875A69A635FA600DD918608392AD36182F62F43/" + }, + { + "Name": "Enemy-Icon-Forest Imp", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075791818/A81BB0DF31AE70905511E33A557D8F3B74A65E36/" + }, + { + "Name": "Enemy-Icon-Living Bones", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792631/D652524FDD5CD95C47CCCFB1374FD586457C2F10/" + }, + { + "Name": "Enemy-Icon-Living Corpse", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792695/499848E84DB9E2501DA0FE91B4AF19FA503048D2/" + }, + { + "Name": "Enemy-Icon-Living Spirit", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792760/5A8E8CDC94499E6FDF9537E6267E0C2A7E070A1F/" + }, + { + "Name": "Enemy-Icon-Lurker", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792821/D2EE06A46938B05DCAB369DE964C17739DB6D65A/" + }, + { + "Name": "Enemy-Icon-Night Demon", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793046/7935292BB62942797F22234A7712540437C1BEA0/" + }, + { + "Name": "Enemy-Icon-Ooze", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793113/F1D318278D58D232862264C8905E2FED7509EC57/" + }, + { + "Name": "Enemy-Icon-Rending Drake", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793261/01D0635E65793BBD00B658704A4A5FC236A4D467/" + }, + { + "Name": "Enemy-Icon-Valrath Savage", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075794357/9A96CE3508EDE74CD1E1DDB1BFC3E55D679650D9/" + }, + { + "Name": "Enemy-Icon-Savvas Icestorm", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793326/74D5786298803BAFE1C56EE458B3B7D5221C79A3/" + }, + { + "Name": "Enemy-Icon-Savvas Lavaflow", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793395/7C7800FCE74AB093DB432EBE133E8AB24EF075D2/" + }, + { + "Name": "Enemy-Icon-Aesther Scout", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075790673/5BD04C7C21C652AB81775BB82E89D937A8C8058A/" + }, + { + "Name": "Enemy-Icon-Vermling Scout", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075794517/59283ED2501E794483DBD15D29AAC02A25176969/" + }, + { + "Name": "Enemy-Icon-Inox Shaman", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075792482/7561A8A88E15E1466B4F57227E998ADC7FD8035D/" + }, + { + "Name": "Enemy-Icon-Vermling Shaman", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075794584/6EC02A7469FEE833FA7F3C4DE8B95F32F6BDB20A/" + }, + { + "Name": "Enemy-Icon-Spitting Drake", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793533/A3146223C8B533BAE33DC44EC0D3C352F3268E3D/" + }, + { + "Name": "Enemy-Icon-Stone Golem", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793676/7DC70CFB746F0BBEB27E87448DEE04D6B62EF745/" + }, + { + "Name": "Enemy-Icon-Sun Demon", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075793740/29F986B3E91A9374FAD51F1D1C951A967B8D3CF0/" + }, + { + "Name": "Enemy-Icon-Valrath Tracker", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075794425/01417DD16099A21D59AB09FCE89508725CD2C283/" + }, + { + "Name": "Enemy-Icon-Wind Demon", + "URL": "http://cloud-3.steamusercontent.com/ugc/1688272925075794648/D429CD2EBC04580DD1E889DEC223CB870F9F9191/" + } + ], + "SnapPoints": [ + { + "Position": { + "x": -36.0014, + "y": 1.6801, + "z": -40 + }, + "Rotation": { + "x": -0.0001, + "y": 180.0016, + "z": 0 + } + }, + { + "Position": { + "x": -12.001, + "y": 1.6801, + "z": -40 + }, + "Rotation": { + "x": -0.0003, + "y": 180.0298, + "z": -0.0001 + } + }, + { + "Position": { + "x": 12.001, + "y": 1.6801, + "z": -40 + }, + "Rotation": { + "x": -0.0003, + "y": 180.0294, + "z": 0 + } + }, + { + "Position": { + "x": 36.0009, + "y": 1.6801, + "z": -40 + }, + "Rotation": { + "x": -0.0003, + "y": 180.0302, + "z": -0.0001 + } + } + ], + "ObjectStates": [] +}