-
Notifications
You must be signed in to change notification settings - Fork 0
/
command_node_2024.json
executable file
·547 lines (547 loc) · 64.9 KB
/
command_node_2024.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
{
"SaveName": "KT Command Node",
"EpochTime": 1688346564,
"Date": "7/2/2023 6:09:24 PM",
"VersionNumber": "v13.2.2",
"GameMode": "KT Command Node",
"GameType": "Utility",
"GameComplexity": "Low Complexity",
"PlayingTime": [
0,
0
],
"PlayerCounts": [
0,
0
],
"Tags": [
"Miniature Games",
"Wargames",
"Scripting",
"User Interfaces",
"English"
],
"Gravity": 0.5,
"PlayArea": 0.5,
"Table": "Table_Poker",
"Sky": "Sky_Cathedral",
"Note": "",
"Grid": {
"Type": 0,
"Lines": false,
"Color": {
"r": 0.0,
"g": 0.0,
"b": 0.0
},
"Opacity": 0.75,
"ThickLines": false,
"Snapping": false,
"Offset": false,
"BothSnapping": false,
"xSize": 2.0,
"ySize": 2.0,
"PosOffset": {
"x": 0.0,
"y": 1.0,
"z": 0.0
}
},
"Lighting": {
"LightIntensity": 0.54,
"LightColor": {
"r": 1.0,
"g": 0.9804,
"b": 0.8902
},
"AmbientIntensity": 1.3,
"AmbientType": 0,
"AmbientSkyColor": {
"r": 0.5,
"g": 0.5,
"b": 0.5
},
"AmbientEquatorColor": {
"r": 0.5,
"g": 0.5,
"b": 0.5
},
"AmbientGroundColor": {
"r": 0.5,
"g": 0.5,
"b": 0.5
},
"ReflectionIntensity": 1.0,
"LutIndex": 0,
"LutContribution": 1.0
},
"Hands": {
"Enable": true,
"DisableUnused": false,
"Hiding": 0
},
"ComponentTags": {
"labels": [
{
"displayed": "Command Node",
"normalized": "command_node"
},
{
"displayed": "Faction data",
"normalized": "faction_data"
}
]
},
"Turns": {
"Enable": false,
"Type": 0,
"TurnOrder": [],
"Reverse": false,
"SkipEmpty": false,
"DisableInteractions": false,
"PassTurns": true,
"TurnColor": ""
},
"DecalPallet": [],
"LuaScript": "--[[ Lua code. See documentation: https://api.tabletopsimulator.com/ --]]\n\n--[[ The onLoad event is called after the game save finishes loading. --]]\nfunction onLoad()\n --[[ print('onLoad!') --]]\nend\n\n--[[ The onUpdate event is called once per frame. --]]\nfunction onUpdate()\n --[[ print('onUpdate loop!') --]]\nend",
"LuaScriptState": "",
"XmlUI": "<!-- Xml UI. See documentation: https://api.tabletopsimulator.com/ui/introUI/ -->",
"ObjectStates": [
{
"GUID": "ec6a3d",
"Name": "HandTrigger",
"Transform": {
"posX": -34.8913651,
"posY": 3.86852837,
"posZ": 5.64683628,
"rotX": 0.0,
"rotY": 120.000008,
"rotZ": 0.0,
"scaleX": 9.63174,
"scaleY": 7.581566,
"scaleZ": 5.59226942
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.856,
"g": 0.09999997,
"b": 0.09399996,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Red",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "1e1903",
"Name": "HandTrigger",
"Transform": {
"posX": -6.529909,
"posY": 3.86852837,
"posZ": 14.4225407,
"rotX": 0.0,
"rotY": 180.0,
"rotZ": 0.0,
"scaleX": 9.631735,
"scaleY": 7.581566,
"scaleZ": 5.59226465
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.905,
"g": 0.898,
"b": 0.171999961,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Yellow",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "fc7f48",
"Name": "HandTrigger",
"Transform": {
"posX": 34.7233047,
"posY": 3.86852837,
"posZ": 5.86803627,
"rotX": 0.0,
"rotY": 240.0,
"rotZ": 0.0,
"scaleX": 9.631729,
"scaleY": 7.581566,
"scaleZ": 5.59226036
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.627,
"g": 0.124999978,
"b": 0.941,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Purple",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "fd950e",
"Name": "HandTrigger",
"Transform": {
"posX": 21.6225224,
"posY": 3.86852837,
"posZ": 14.3200254,
"rotX": 0.0,
"rotY": 180.0,
"rotZ": 0.0,
"scaleX": 9.631729,
"scaleY": 7.581566,
"scaleZ": 5.59226274
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.117999978,
"g": 0.53,
"b": 1.0,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Blue",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "da4a08",
"Name": "HandTrigger",
"Transform": {
"posX": -29.9293633,
"posY": 3.86852837,
"posZ": -11.5780945,
"rotX": 0.0,
"rotY": 30.0000019,
"rotZ": 0.0,
"scaleX": 9.631727,
"scaleY": 7.58156729,
"scaleZ": 5.592262
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "White",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "9ca7f7",
"Name": "HandTrigger",
"Transform": {
"posX": 6.82584763,
"posY": 3.86852837,
"posZ": 14.45097,
"rotX": 0.0,
"rotY": 180.0,
"rotZ": 0.0,
"scaleX": 9.631727,
"scaleY": 7.581566,
"scaleZ": 5.59226036
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.191999972,
"g": 0.701,
"b": 0.167999953,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Green",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "1d073b",
"Name": "HandTrigger",
"Transform": {
"posX": 30.18831,
"posY": 3.86852837,
"posZ": -11.2837267,
"rotX": 0.0,
"rotY": 330.0,
"rotZ": 0.0,
"scaleX": 9.631727,
"scaleY": 7.581566,
"scaleZ": 5.59226036
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.96,
"g": 0.438999981,
"b": 0.807,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Pink",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "bd371d",
"Name": "HandTrigger",
"Transform": {
"posX": -21.7052059,
"posY": 3.86852837,
"posZ": 14.4821453,
"rotX": 0.0,
"rotY": 180.0,
"rotZ": 0.0,
"scaleX": 9.631738,
"scaleY": 7.581566,
"scaleZ": 5.592267
},
"Nickname": "",
"Description": "",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.9559999,
"g": 0.39199996,
"b": 0.112999953,
"a": 0.0
},
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": true,
"Grid": false,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"FogColor": "Orange",
"LuaScript": "",
"LuaScriptState": "",
"XmlUI": ""
},
{
"GUID": "6edf26",
"Name": "Custom_Tile",
"Transform": {
"posX": -0.0454121158,
"posY": 0.9599999,
"posZ": -1.96999991,
"rotX": -1.3220133E-05,
"rotY": 180.0,
"rotZ": 2.3024284E-05,
"scaleX": 1.0,
"scaleY": 1.0,
"scaleZ": 1.0
},
"Nickname": "KT Command Node",
"Description": "This is the command node for your team. It is the central hub for helper scripts that help you play KT2024 on TTS.\n\nRight click on models for basic controls.\n\nHover over a model and type a number on the number row to measure in a circle from its base.\n\nHover over a model and press the [b]R[/b] key to toggle the range templates.",
"GMNotes": "",
"AltLookAngle": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"ColorDiffuse": {
"r": 0.199999154,
"g": 0.199999154,
"b": 0.199999154
},
"Tags": [
"Command Node"
],
"LayoutGroupSortIndex": 0,
"Value": 0,
"Locked": false,
"Grid": true,
"Snap": true,
"IgnoreFoW": false,
"MeasureMovement": false,
"DragSelectable": true,
"Autoraise": true,
"Sticky": true,
"Tooltip": true,
"GridProjection": false,
"HideWhenFaceDown": false,
"Hands": false,
"CustomImage": {
"ImageURL": "http://cloud-3.steamusercontent.com/ugc/1681518722987693624/C19BB145DB4C70E131F1184AC1C81EC5EC446D31/",
"ImageSecondaryURL": "",
"ImageScalar": 1.0,
"WidthScale": 0.0,
"CustomTile": {
"Type": 1,
"Thickness": 0.2,
"Stackable": false,
"Stretch": true
}
},
"LuaScript": "\n-- icons by game-icons.net\n\ntriangle = \"(1\\\")\"\ncircle = \"(2\\\")\"\nsquare = \"(3\\\")\"\npentagon = \"(6\\\")\"\n\npickModelForId = nil\n\npanelToggles = {}\n\nteamApiUrl = [[https://datateamapp.azurewebsites.net/api/toTTS/]]\nteamApiCode = \"\"\n\nversionUrl = [[https://datateamapp.azurewebsites.net/api/Scripts/Version]]\nscriptUrl = [[https://datateamapp.azurewebsites.net/api/Scripts/]]\n\n--anything from special profiles with these typenames will be added to the names of operatives\nspecialNames = {\n \"Boon of Tzeentch\"\n}\n\nspecialSelections = {\n \"Tzeentch\",\n \"Khorne\",\n \"Nurgle\",\n \"Slaanesh\",\n \"Undivided\"\n}\n\nstate = {\n teamkey = nil,\n name = \"New Team\",\n playerid = nil,\n models = nil,\n modelOrder = nil,\n positions = {},\n catalogues = {},\n step = nil,\n version={\n model = 1,\n node = 1\n },\n modelscript = [[]]\n}\n\nbaseDimensions = {\n {x = 25, z = 25},\n {x = 28.5, z = 28.5},\n {x = 32, z = 32},\n {x = 40, z = 40},\n {x = 50, z = 50},\n {x = 55, z = 55},\n {x = 60, z = 35},\n {x = 35, z = 60},\n {x = 60, z = 60},\n {x = 100, z = 100},\n {x = 25, z = 75},\n {x = 75, z = 25},\n {x = 120, z = 92},\n {x = 92, z = 120},\n {x = 170, z = 105},\n {x = 105, z = 170}\n}\n\nfunction panelToggleCallback(player, value, id)\n -- print(id..\" toggle buton pressed\")\n local pid = panelToggles[id]\n if pid then\n -- print(\"toggling \"..pid[1])\n if pid[2] then\n pid[2] = false\n -- print(\"OFF\")\n self.UI.hide(pid[1])\n else\n pid[2] = true\n -- print(\"ON\")\n self.UI.show(pid[1])\n end\n end\nend\n\nfunction panelToggle(btnid, panelid, df)\n panelToggles[btnid] = {panelid, df}\n -- print(string.format(\"toggle button: %s => %s\", btnid, panelid))\n return \"panelToggleCallback\"\nend\n\nfunction makeGuiid( tbl )\n return \"ktcnid-\"..table.concat(tbl,\"-\")\nend\n\nfunction fieldGuiid(t, name)\n return makeGuiid({name, \"field\"})\nend\n\nfunction readGuiid( guiid )\n return splitString(guiid, '%-')\nend\n\nfunction textColorXml( color, text )\n return string.format(\"<textcolor color=\\\"#%s\\\">%s</textcolor>\", color, text)\nend\n\nfunction textColorMd( color, text )\n return string.format(\"[%s]%s[-]\", color, text)\nend\n\nfunction textAttr( text, attr )\n return {\n tag=\"Text\",\n attributes=attr,\n value=text\n }\nend\n\nfunction xt(tag, attributes, children, value)\n return {\n tag=tag,\n attributes=attributes,\n children=children,\n value=value\n }\nend\n\nfunction rcall(target, fname, args)\n if target.getVar(fname) then\n target.call(fname, args)\n end\nend\n\ntext_subs = {\n [\"1&&\"] = textColorXml(\"000000\", triangle),\n [\"2&&\"] = textColorXml(\"ffffff\", circle),\n [\"3&&\"] = textColorXml(\"1E87FF\", square),\n [\"6&&\"] = textColorXml(\"DA1A18\", pentagon),\n [\"%(R%)\"] = textColorXml(\"1E87FF\", \"R\"),\n [\"%(M%)\"] = textColorXml(\"F4641D\", \"M\")\n}\n\nmd_subs = {\n [\"1&&\"] = textColorMd(\"000000\", triangle),\n [\"2&&\"] = textColorMd(\"ffffff\", circle),\n [\"3&&\"] = textColorMd(\"1E87FF\", square),\n [\"6&&\"] = textColorMd(\"DA1A18\", pentagon),\n [\"%(R%)\"] = textColorMd(\"1E87FF\", \"R\"),\n [\"%(M%)\"] = textColorMd(\"F4641D\", \"M\")\n}\n\nfunction subsymbol(s, tbl)\n local st = s\n for o, sub in pairs(tbl) do\n st = string.gsub(st, o, sub)\n end\n return st\nend\n\nfunction startsWith(st, match)\n return string.sub(st, 1, string.len(match)) == match\nend\n\nfunction checkOwner(p)\n if p.steam_id == state.playerid then\n return true\n else\n p.broadcast(\"Only the command node's owner can do that\")\n return false\n end\nend\n\nfunction checkHost(p)\n if p.host then\n return true\n else\n p.broadcast(\"Only the host can do that\")\n end\nend\n\nupdateButtonId = makeGuiid({\"update\",\"script\",\"button\"})\n\nremoteVersion = {\n model=0,\n node=0\n}\n\nfunction findBase(obj)\n local base = obj.getTable(\"modelBase\")\n\n if base == nil then\n local bounds = obj.getBoundsNormalized()\n local baseX = 0\n local baseZ = 0\n\n if bounds.size.x == 0 then\n bounds = obj.getBounds()\n end\n\n if bounds.size.x > 0 then\n local boundsX = bounds.size.x * 25.4\n local boundsZ = bounds.size.z * 25.4\n local baseError = 999999\n for i, dim in pairs(baseDimensions) do\n local difx = (dim.x - boundsX)\n local difz = (dim.z - boundsZ)\n local dimError = difx*difx + difz*difz\n if dimError < baseError then\n baseError = dimError\n baseX = dim.x\n baseZ = dim.z\n end\n end\n else\n printToOwner(\"Could not detect base size for this model. You will need to set it manually.\")\n baseX = 32\n baseZ = 32\n end\n base = {x = baseX, z = baseZ}\n end\n return base\nend\n\nfunction needsUpdate()\n return remoteVersion.model > state.version.model or remoteVersion.node > state.version.node\nend\n\nfunction getVersions()\n WebRequest.get(versionUrl, function(req)\n if req.is_error then\n log(req.error)\n else\n remoteVersion = JSON.decode(req.text)\n if needsUpdate() then\n self.UI.show(updateButtonId)\n broadcastToOwner(\"A new version of the Command Node is available!\\nRight click the node and select [b]Update Scripts[/b] to update\")\n self.addContextMenuItem(\"Update Scripts\", tryUpdate)\n end\n end\n end)\nend\n\nfunction broadcastToOwner(s)\n if state.playerid == nil then\n broadcastToAll(s)\n return\n end\n for _,player in pairs(Player.getPlayers()) do\n if player.steam_id == state.playerid then\n player.broadcast(s)\n return\n end\n end\nend\n\nfunction onPlayerChangeColor(pc)\n if pc ~= \"Grey\" and state.playerid and Player[pc].steam_id == state.playerid then -- error when player leaves server\n self.setColorTint(Color.fromString(pc))\n end\nend\n\nfunction callback_claimNode(player, value, id)\n state.playerid = player.steam_id\n self.setColorTint(Color.fromString(player.color))\n player.broadcast(string.format(\"Welcome, %s.\\nSelect your roster to get started.\", player.steam_name))\n saveState()\n generateGui()\nend\n\nfunction generateUIDefaults()\n return xt(\"Defaults\",{},{\n xt(\"Text\",{\n class=\"mainTitle\",\n fontSize=\"30\",\n fontStyle=\"BoldAndItalic\"\n }),\n xt(\"Text\",{\n class=\"inputTitle\",\n fontSize=\"20\",\n fontStyle=\"Bold\"\n }),\n xt(\"Panel\",{\n class=\"helpPanel\",\n color=\"#8B8B8B\",\n showAnimation=\"FadeIn\",\n hideAnimation=\"FadeOut\",\n active=false\n }),\n xt(\"Button\",{\n class=\"helpButton\",\n width=30, height=30,\n resizeTextForBestFit=true,\n text=\"?\"\n })\n })\nend\n\nfunction generateClaimUI(active, id)\n return xt(\"Panel\", {\n active=active,\n id=id,\n width=150,\n height=60,\n position=\"0 120 -10\",\n rotation=\"0 0 180\"\n },\n {\n xt(\"Button\", {\n resizeTextForBestFit=true,\n onClick=\"callback_claimNode\",\n text=\"New team\"\n })\n })\nend\n\nfunction callback_teamCode( player, value, id )\n teamApiCode = value\nend\n\nloadTeamButtonId = makeGuiid({\"team\", \"load\", \"button\"})\nfunction loadTeamRequest(request)\n if request.is_error then\n log(request.error)\n broadcastToAll(\"Failed to load team - check the system log\")\n self.UI.setXmlTable({generateUIDefaults(), generateTeamSelectUI(true, false)})\n else\n local status, dc = pcall(JSON.decode, request.text)\n if status then\n state.name = dc[\"roster\"][\"@name\"]\n self.setName(state.name)\n local force = dc[\"roster\"][\"forces\"][\"force\"]\n local models = {}\n local unpackModels = function(l)\n for _, v in pairs(l) do\n local vt = v.categories.category[\"@name\"]\n if vt == nil or (vt ~= \"Configuration\" and vt ~= \"Reference\") then\n models[v[\"@id\"]] = v\n end\n end\n end\n if force[\"@id\"] then\n --roster mode\n unpackModels(force.selections.selection)\n else\n --fire team mode\n for _, v in pairs(force) do\n unpackModels(v.selections.selection)\n end\n end\n state.models = models\n saveState()\n -- log(generateModelScriptUI(true, models))\n self.UI.setXmlTable({generateUIDefaults(), generateModelScriptUI(true, models), generateTeamSelectUI(true, true)})\n else\n broadcastToOwner(\"That code is not valid. Please copy your roster code from [b]datateam.app/encode[/b].\")\n self.UI.setXmlTable({generateUIDefaults(), generateTeamSelectUI(true, false)})\n end\n end\nend\n\nfunction callback_loadTeam( player, value, id )\n if state.teamkey and state.teamkey == teamApiCode then\n player.broadcast(\"That team is already loaded\")\n return\n end\n self.UI.setAttribute(id, \"interactable\", false)\n self.UI.setAttribute(id, \"text\", \"LOADING...\")\n state.teamkey = teamApiCode\n saveState()\n WebRequest.get(teamApiUrl .. teamApiCode, loadTeamRequest)\nend\n\nfunction modelSelections(model)\n local snames = {}\n sfloop(model.selections.selection, function(v)\n table.insert(snames, v[\"@name\"])\n end)\n return table.concat(snames, \", \")\nend\n\nfunction callback_pickModel( player, value, id )\n if player.steam_id == state.playerid then\n pickModelForId = id\n local model = state.models[id]\n player.broadcast(string.format(\"Choose a model for [b]%s[/b] with %s\", model[\"@name\"], modelSelections(model)))\n else\n player.broadcast(\"Only the team's owner can pick models.\")\n end\nend\n\nfunction callback_tweakBase( player, value, id )\n local idi = readGuiid(id)\n local base = baseDimensions[tonumber(idi[4])]\n local so = player.getSelectedObjects()\n if next(so) ~= nil then\n for k,v in pairs(so) do\n rcall(v, \"comSetBase\", base)\n end\n else\n player.broadcast(\"Select some operatives first\")\n end\nend\n\nfunction callback_autoScale( player, value, id )\n local so = player.getSelectedObjects()\n if next(so) ~= nil then\n for k,v in pairs(so) do\n rcall(v, \"comAutoSize\")\n end\n else\n player.broadcast(\"Select some operatives first\")\n end\nend\n\nfunction callback_finishLayout( player, value, id )\n if checkOwner(player) then\n local np = {}\n for k,v in pairs(state.positions) do\n local m = getObjectsWithTag(k)\n if next(m) ~= nil then\n local o = m[1]\n local vr = o.getRotation().y - self.getRotation().y\n np[k] = {\n position=self.positionToLocal(o.getPosition()),\n rotation=vr\n }\n o.call(\"comSetUIAngle\", {uiAngle=vr})\n end\n end\n state.positions = np\n saveState()\n generateGui()\n end\nend\n\nfunction generateTeamLayoutUI(active, id)\n local tweakPanel = function()\n local bh = 25\n local sep = 4\n local border = 6\n local width = 150\n local height = border - sep\n\n local baseButton = function(t, h, k, v)\n local btext = (v.x == v.z) and string.format(\"%d\", v.x) or string.format(\"%d by %d\", v.x, v.z)\n table.insert(t,\n xt(\"Button\", {\n text=btext,\n width=width-border*2,\n height=bh,\n rectAlignment=\"UpperCenter\",\n id=makeGuiid({\"tweak\",\"base\",tostring(k)}),\n onClick=\"callback_tweakBase\",\n offsetXY=string.format(\"0 %d\", -h)\n }))\n return h + bh\n end\n\n local pchildren = {}\n\n table.insert(pchildren,\n xt(\"Text\", {\n class=\"inputTitle\",\n resizeTextForBestFit=true,\n text=\"Adjust base size\",\n width=width-border*2,\n height=30,\n rectAlignment=\"UpperCenter\",\n offsetXY=\"0 \"..(-border)\n }))\n height = height + 30\n\n for k,v in pairs(baseDimensions) do\n height=baseButton(pchildren, height+sep, k, v)\n end\n\n height = height + 50 + border\n\n table.insert(pchildren,\n xt(\"Button\", {\n text=\"AUTO SCALE\",\n resizeTextForBestFit=true,\n width=width-border*2,\n height=35,\n rectAlignment=\"LowerCenter\",\n offsetXY=\"0 \"..border,\n onClick=\"callback_autoScale\"\n }))\n\n return xt(\"Panel\", {\n color=\"#ffffff\",\n width=width,\n height=height,\n position=string.format(\"%d %d -50\", -(width*0.5 + 200), -(height*0.5)),\n rotation=\"0 0 180\"\n }, pchildren)\n end\n\n local layoutArea = function(w, h, t, o)\n return xt(\"Panel\", {\n width=w,\n height=h,\n position=string.format(\"0 %d -10\", h*0.5 + o),\n rotation=\"0 0 180\"\n }, {\n xt(\"Panel\", {\n color=\"#F4641D\",\n height=t,\n rectAlignment=\"UpperCenter\"\n }),\n xt(\"Panel\", {\n color=\"#F4641D\",\n height=t,\n rectAlignment=\"LowerCenter\"\n }),\n xt(\"Panel\", {\n color=\"#F4641D\",\n height=(h-t*2),\n width=t,\n rectAlignment=\"MiddleLeft\"\n }),\n xt(\"Panel\", {\n color=\"#F4641D\",\n height=(h-t*2),\n width=t,\n rectAlignment=\"MiddleRight\"\n }),\n xt(\"Text\", {\n color=\"#F4641D\",\n text=\"Arrange your team here\",\n fontSize=40\n })\n })\n end\n\n return xt(\"Panel\", {},{\n tweakPanel(),\n layoutArea(1600, 900, 15, 75),\n xt(\"Panel\", {\n active=active, id=id,\n width=310, height=225,\n position=\"0 -100 -200\",\n color=\"#FFFFFF\",\n rotation=\"45 0 180\"\n },\n {\n xt(\"VerticalLayout\",{\n width=300, height=120,\n offsetXY=\"0 -5\",\n rectAlignment=\"UpperCenter\"\n },{\n xt(\"Text\",{class=\"inputTitle\", text=\"Finish Your Team\"}),\n xt(\"Text\",{text=\"Make sure all your operatives are on the right bases (see the <b>Adjust base size</b> panel)\"}),\n xt(\"Text\",{text=\"When you're done, arrange your team in the orange area. When you're happy with the team's layout, click the FINISH button.\"})\n }),\n xt(\"Button\",{\n width=300, height=40,\n resizeTextForBestFit=true,\n rectAlignment=\"UpperCenter\",\n text=\"FINISH\",\n offsetXY=\"0 -180\",\n onClick=\"callback_finishLayout\"\n })\n })\n })\nend\n\nfunction generateModelScriptUI(active, models, id)\n local mpw = 250\n local mph = 200\n local th = 50\n local bh = 50\n local mps = 25\n\n local mcw = mpw+mps\n local mch = mph+mps\n local hmps = mps*0.5\n\n local vofs = 200\n\n local mids = {}\n for k, m in pairs(models) do\n table.insert(mids, {guid=k, sel=modelSelections(m), name=m[\"@name\"]})\n end\n table.sort( mids, function(A, B) \n if A.name ~= B.name then\n return A.name < B.name\n end\n if A.sel ~= B.sel then\n return A.sel < B.sel\n end\n return A.guid < B.guid\n end)\n local mcount = #mids\n local mcs = math.floor(math.sqrt(mcount))\n local mw = math.ceil(mcount/mcs)\n\n local tcx = 0\n local tcy = 0\n\n local modelPanel = function(tbl, x, y, mid)\n local guid = mids[mid].guid\n local em = getObjectsWithTag(guid)\n local mod = models[guid]\n local lx = x*(mpw + mps)+hmps\n local ly = -y*(mph + mps)-hmps\n\n if next(em) ~= nil then\n makeOperative(em[1], guid)\n end\n\n table.insert(tbl,\n xt(\"Panel\", {\n class=\"modelPanel\",\n color= (#em > 0) and \"#808080\" or \"#8F5757\",\n rectAlignment=\"UpperLeft\",\n width=mpw, height = mph,\n offsetXY=string.format(\"%d %d\", lx, ly),\n id=guid..\"_panel\"\n },{\n xt(\"Text\",{\n height=th,\n alignment=\"MiddleCenter\",\n rectAlignment=\"UpperCenter\",\n resizeTextForBestFit=true,\n text=mod[\"@customName\"] or mod[\"@name\"]\n }),\n xt(\"Text\",{\n height=mph - th - bh,\n rectAlignment=\"UpperCenter\",\n offsetXY=string.format(\"0 %d\", -th),\n text=mids[mid].sel\n }),\n xt(\"Button\",{\n height=bh,\n rectAlignment=\"LowerCenter\",\n text=\"Choose Model\",\n onClick=\"callback_pickModel\",\n id=guid\n })\n }))\n end\n\n state.positions = {}\n local mpanels = {}\n local panelw = (mpw+mps)*mw\n local panelh = (mph+mps)*mcs\n\n local i = 1\n local mi = 0\n while i <= mcount do\n local mx = math.floor(mi%mw)\n local my = math.floor(mi/mw)\n while not pcall(modelPanel, mpanels, mx, my, i) do\n i = i + 1\n end\n if i <= mcount then\n state.positions[mids[i].guid] = {\n position=Vector(\n (panelw*0.5 - mx*mcw - (mcw)*0.5)*0.01,\n 1,\n (vofs + my*mch + th + mps*0.5)*0.01),\n rotation=0\n }\n end\n i = i + 1\n mi = mi + 1\n end\n state.models = models\n saveState()\n\n return xt(\"Panel\",{\n active=active, id=id,\n -- color=\"#ffffff\",\n width=panelw,\n height=panelh,\n position=string.format(\"0 %d -10\", panelh/2 + vofs),\n rotation=\"0 0 180\"\n }, mpanels)\nend\n\nfunction callback_doTeamLayout( player, value, id )\n if checkOwner(player) then\n self.UI.setXmlTable({generateUIDefaults(), generateTeamLayoutUI(true)})\n resetOperativePositions()\n end\nend\n\nfunction callback_doLoadNewRoster( player, value, id )\n if checkOwner(player) then\n self.UI.setXmlTable({generateUIDefaults(), generateTeamSelectUI(true, true), generateModelScriptUI(true, state.models)})\n resetOperativePositions()\n end\nend\n\nfunction generateTeamSelectUI(active, allowFinish, id)\n local selectHelpButton = makeGuiid({\"team\",\"select\",\"help\",\"button\"})\n local selectHelpPanel = makeGuiid({\"team\",\"select\",\"help\",\"panel\"})\n return xt(\"Panel\", {\n active=active, id=id,\n width=310, height=225,\n position=\"0 0 -100\",\n color=\"#FFFFFF\",\n rotation=\"0 0 180\"\n },\n {\n xt(\"VerticalLayout\",{\n width=300, height=120,\n offsetXY=\"0 -5\",\n rectAlignment=\"UpperCenter\"\n },{\n xt(\"Text\",{class=\"inputTitle\", text=\"Enter Team Code\"}),\n xt(\"InputField\",{\n placeholder=\"Team Code\",\n alignment=\"MiddleCenter\",\n fontSize=20,\n onValueChanged=\"callback_teamCode\",\n characterLimit=16,\n text=teamApiCode\n })\n }),\n xt(\"Button\",{\n width=300, height=40,\n resizeTextForBestFit=true,\n rectAlignment=\"UpperCenter\",\n text=\"LOAD TEAM\",\n offsetXY=\"0 -135\",\n id=loadTeamButtonId,\n onClick=\"callback_loadTeam\"\n }),\n xt(\"Button\",{\n width=300, height=40,\n resizeTextForBestFit=true,\n rectAlignment=\"UpperCenter\",\n text=\"FINISH\",\n offsetXY=\"0 -180\",\n interactable=allowFinish,\n onClick=\"callback_doTeamLayout\"\n }),\n xt(\"Panel\",{\n class=\"helpPanel\",\n id=selectHelpPanel,\n width=350, height=400,\n rectAlignment=\"LowerRight\",\n offsetXY=\"355 5\"\n },{\n xt(\"Text\",{\n alignment=\"UpperLeft\",\n width=344, height=394\n },{},[[<textsize size=\"18\"><b>How To Make a Team</b></textsize>\\n\n <b>STEP 1)</b> Create your roster in battlescribe\\n\n <b>STEP 2)</b> Go to https://datateam.app/encode\\n\n <b>STEP 3)</b> Upload your team's \".rosz\" file\"\\n\n <b>STEP 4)</b> Copy your team code\\n\n <b>STEP 5)</b> Paste your team code into the Command Node and press the <b>LOAD TEAM</b> button\\n\n <b>STEP 6)</b> Select models for your operatives\\n\n <b>STEP 7)</b> Press the <b>FINISH</b> button\\n\n <b>STEP 8)</b> Your team is ready to play!]])\n }),\n xt(\"Button\",{\n width=50, height=50,\n rectAlignment=\"UpperRight\",\n offsetXY=\"-5 -5\",\n class=\"helpButton\",\n id=selectHelpButton,\n onClick=panelToggle(selectHelpButton, selectHelpPanel, false)\n })\n })\nend\n\nfunction splitString(inputstr, sep)\n if sep == nil then\n sep = \"%s\"\n end\n local t={}\n for str in string.gmatch(inputstr, \"([^\"..sep..\"]+)\") do\n table.insert(t, str)\n end\n return t\nend\n\nfunction getKeys(tbl)\n local r = {}\n local n = 1\n for k, v in pairs(tbl) do\n r[n] = k\n n = n + 1\n end\n return r, n-1\nend\n\nfunction callback_saveAll( player, value, id )\n if checkOwner(player) then\n allOperatives(function(op)\n for i,v in ipairs(op) do\n v.call(\"comSavePosition\", {})\n end\n end)\n end\nend\n\nfunction callback_loadAll( player, value, id )\n if checkOwner(player) then\n allOperatives(function(op)\n for i,v in ipairs(op) do\n v.call(\"comLoadPosition\")\n end\n end)\n end\nend\n\nfunction callback_backToMain( player, value, id )\n if checkOwner(player) then\n generateGui()\n end\nend\n\nfunction generatePlayUI( active )\n local width = 350\n local border = 6\n local cw = width-border*2\n local sep = 15\n local bh = 40\n local h = border\n local pc = {}\n\n local button = function(text, callback, id)\n table.insert(pc, xt(\"Button\", {\n width = cw,\n height = bh,\n onClick = callback,\n text=text,\n fontSize=12,\n id=id,\n rectAlignment=\"UpperCenter\",\n offsetXY=\"0 \"..(-h)\n }))\n h = h + bh + sep\n end\n\n button(\"Save all positions\", \"callback_saveAll\")\n button(\"Load all positions\", \"callback_loadAll\")\n button(\"Back to main menu\", \"callback_backToMain\")\n\n return xt(\"Panel\", {\n color=\"#ffffff\",\n height = h + border - sep,\n width=width,\n position=string.format(\"0 %d -100\", -h/2),\n rotation=\"0 0 180\"\n }, pc)\nend\n\nfunction callback_resetModelPositions( player, value, id )\n if checkOwner(player) then\n resetOperativePositions()\n end\nend\n\nfunction callback_updateScripts( player, value, id )\n if checkOwner(player) then\n tryUpdate(player.color)\n end\nend\n\nfunction callback_play( player, value, id )\n if checkOwner(player) then\n self.UI.setXmlTable({generateUIDefaults(), generatePlayUI(true)})\n end\nend\n\nfunction generateMainMenuUI( active )\n local width = 350\n local border = 6\n local cw = width-border*2\n local sep = 15\n local bh = 40\n local h = border\n local pc = {}\n\n local button = function(text, callback, id)\n table.insert(pc, xt(\"Button\", {\n width = cw,\n height = bh,\n onClick = callback,\n text=text,\n fontSize=12,\n id=id,\n rectAlignment=\"UpperCenter\",\n offsetXY=\"0 \"..(-h)\n }))\n h = h + bh + sep\n end\n\n table.insert(pc, xt(\"Text\", {\n class=\"mainTitle\",\n resizeTextForBestFit=true,\n text=state.name,\n width = cw,\n height=30,\n rectAlignment=\"UpperCenter\",\n offsetXY=\"0 \"..(-h)\n }))\n h = h + 30 + sep\n\n button(\"Play\", \"callback_play\")\n button(\"Recall models\", \"callback_resetModelPositions\")\n button(\"Adjust Team\", \"callback_doTeamLayout\")\n button(\"Load new roster\", \"callback_doLoadNewRoster\")\n\n table.insert(pc, xt(\"Button\", {\n resizeTextForBestFit=true,\n text=\"Update Scripts\",\n width=200, height=80,\n rectAlignment=\"UpperCenter\",\n offsetXY=\"0 90\",\n color=\"#F4641D\",\n id=updateButtonId,\n showAnimation=\"Grow\",\n onClick=\"callback_updateScripts\",\n active=needsUpdate()\n }))\n\n return xt(\"Panel\", {\n color=\"#ffffff\",\n height = h + border - sep,\n width=width,\n position=string.format(\"0 %d -100\", -h/2),\n rotation=\"0 0 180\"\n }, pc)\nend\n\nfunction generateGui()\n local defaults = generateUIDefaults()\n if state.playerid then\n if state.models then\n self.UI.setXmlTable({defaults, generateMainMenuUI(true)})\n else\n self.UI.setXmlTable({defaults, generateTeamSelectUI(true, false)})\n end\n else\n -- self.UI.setXmlTable({defaults, generateClaimUI(true)})\n self.UI.setXmlTable({defaults, generateClaimUI(true)})\n end\nend\n\nfunction saveState()\n self.script_state = JSON.encode(state)\nend\n\nfunction loadState()\n local ds = JSON.decode(self.script_state)\n if ds then state = ds end\nend\n\nfunction tryUpdate(pc)\n if needsUpdate() then\n local rq = scriptUrl\n local updateModel = remoteVersion.model > state.version.model\n local updateNode = remoteVersion.node > state.version.node\n if updateModel then rq = rq .. \"Model\"end\n if updateNode then rq = rq .. \"Node\"end\n\n WebRequest.get(rq, function(req)\n if req.is_error then\n log(req.error)\n broadcastToOwner(\"Failed to update scripts. Check the log.\")\n else\n state.version = remoteVersion\n local data = JSON.decode(req.text)\n self.clearContextMenu()\n\n if updateModel then\n broadcastToOwner(\"Updating models...\")\n state.modelscript = data.model\n allOperatives(function(ops)\n for i=2,#ops do\n ops[i].destruct()\n end\n ops[1].setLuaScript(data.model)\n ops[1].reload()\n end)\n end\n saveState()\n if updateNode then\n broadcastToOwner(\"Updating node...\")\n self.setLuaScript(data.node)\n end\n resetOperativePositions()\n self.reload()\n broadcastToOwner(\"Finished update. You should save your team again.\")\n end\n end)\n end\nend\n\nfunction sfloop(cat, func)\n if cat then\n if cat[\"@id\"] then\n func(cat)\n else\n for i,v in ipairs(cat) do\n func(v)\n end\n end\n end\nend\n\nfunction loopSelections(res, bso)\n if (bso and bso.selections and bso.selections.selection) == nil then return end\n local profileFuncs = {\n [\"Weapons\"] = function(p)\n local wstats = {}\n\n sfloop(p.characteristics.characteristic, function( c )\n wstats[c[\"@name\"]] = c[\"#text\"]\n end)\n\n table.insert(res.weapons, {\n name=p[\"@name\"],\n stats=wstats\n })\n end,\n [\"Psychic Power\"] = function(p)\n table.insert(res.psychic, {\n name=p[\"@name\"],\n text=p.characteristics.characteristic[\"#text\"]\n })\n end\n }\n sfloop(bso.selections.selection, function( v )\n local t = v[\"@type\"]\n local n = v[\"@name\"]\n if t == \"upgrade\" then\n table.insert(res.upgrades, n)\n\n if v.rules then\n sfloop(v.rules.rule, function( r )\n res.rules[r[\"@name\"]] = r.description\n end) end\n\n if v.profiles then\n sfloop(v.profiles.profile, function( p )\n local pf = profileFuncs[p[\"@typeName\"]]\n if pf then \n pf(p) \n else\n local tb = res.special[p[\"@typeName\"]] or {}\n table.insert(tb, {\n name=p[\"@name\"],\n text=p.characteristics.characteristic[\"#text\"]\n })\n res.special[p[\"@typeName\"]] = tb\n end\n end) end\n end\n loopSelections(res, v)\n end)\nend\n\nfunction modelToInfo(m)\n local res = {\n name = m[\"@customName\"] or m[\"@name\"],\n id = m[\"@id\"],\n stats = {},\n weapons = {},\n actions = {},\n abilities = {},\n psychic = {},\n upgrades = {},\n special = {},\n rules = {},\n categories = {}\n }\n\n sfloop(m.profiles.profile, function( v )\n local tn = v[\"@typeName\"]\n local n = v[\"@name\"]\n if tn == \"Operative\" then\n res.modelType = n\n sfloop(v.characteristics.characteristic, function(c)\n res.stats[c[\"@name\"]] = c[\"#text\"]\n end)\n\n elseif tn == \"Unique Actions\" then\n table.insert(res.actions, {\n name=n,\n text=v.characteristics.characteristic[\"#text\"]\n })\n elseif tn == \"Abilities\" then\n table.insert(res.abilities, {\n name=n,\n text=v.characteristics.characteristic[\"#text\"]\n })\n end\n end)\n\n loopSelections(res, m)\n\n sfloop(m.categories.category, function( v )\n table.insert(res.categories, v[\"@name\"])\n end)\n return res\nend\n\nfunction makeOperative(target, id)\n if state.models[id] then\n for _,m in pairs(getObjectsWithTag(id)) do\n m.destruct()\n end\n\n local getDescription = function(inf)\n local desc = {}\n\n local catex = function(cat, title, func, sep)\n if next(cat) ~= nil then\n table.insert(desc, title)\n local ot = {}\n for k,v in pairs(cat) do\n table.insert(ot, func(k,v))\n end\n table.insert(desc, table.concat(ot, sep or '\\n'))\n end\n end\n\n if inf.modelType then\n table.insert(desc, inf.modelType)\n end\n table.insert(desc, string.format(\"[D36B3E][[84E680]M[-] [ffffff]%s[-]] [[84E680]APL[-] [ffffff]%s[-]] [[84E680]GA[-] [ffffff]%s[-]]\\n[[84E680]DF[-] [ffffff]%s[-]] [[84E680]SV[-] [ffffff]%s[-]] [[84E680]W[-] [ffffff]%s[-]][-]\",\n inf.stats.M or \"X\", inf.stats.APL or \"X\", inf.stats.GA or \"X\", inf.stats.DF or \"X\", inf.stats.SV or \"X\", inf.stats.W or \"X\"))\n table.insert(desc, \"[C5C5C5]\"..table.concat( inf.categories, \", \")..\"[-]\")\n\n catex(inf.weapons, \"[31B32B]Weapons[-]\", function(k, v)\n -- log(v)\n local vs = v.stats\n local A = vs[\"A\"] or '-'\n local WB = vs[\"WS/BS\"] or '-'\n local D = vs[\"D\"] or \"-/-\"\n local SR = vs[\"SR\"]\n local CR = vs[\"!\"]\n\n local ostr = string.format(\"%s\\n[84E680]A[-] %s [84E680]WS/BS[-] %s [84E680]D[-] %s\", v.name or \"X\", A or \"X\", WB or \"X\", D or \"X\")\n\n if SR and SR ~= '-' then \n ostr = ostr..string.format(\"\\n[84E680]SR[-]: %s\", SR)\n end\n\n if CR and CR ~= '-' then \n ostr = ostr..string.format(\"\\n[84E680]![-]: %s\", CR)\n end\n\n return ostr\n end, \"\\n\\n\")\n\n table.insert(desc, '---')\n \n for k,sc in pairs(inf.special) do\n catex(sc, \"[31B32B]\"..k..\"[-]\", function(k, v)\n return string.format(\"- [EF8450]%s[-]\", v.name)\n end)\n end\n\n catex(inf.psychic, \"[31B32B]Psychic Powers[-]\", function( k, v )\n return string.format(\"- [EF8450]%s[-]\", v.name)\n -- return string.format(\"[EF8450]%s[-]\\n%s\\n\", v.name, v.text)\n end)\n\n catex(inf.abilities, \"[31B32B]Abilities[-]\", function( k, v )\n return string.format(\"- [EF8450]%s[-]\", v.name)\n -- return string.format(\"[EF8450]%s[-]\\n%s\\n\", v.name, v.text)\n end)\n\n catex(inf.actions, \"[31B32B]Actions[-]\", function( k, v )\n return string.format(\"- [D46D6C]%s[-]\", v.name)\n -- return string.format(\"[D46D6C]%s[-]\\n%s\\n\", v.name, v.text)\n end)\n\n return(subsymbol(table.concat( desc, \"\\n\"), md_subs))\n end\n\n local getNickname = function(inf)\n local nameSpecials = {}\n for i,v in ipairs(specialNames) do\n local sc = inf.special[v]\n if sc and next(sc) ~= nil then\n for _,f in ipairs(inf.special[v]) do\n table.insert(nameSpecials, f.name)\n end\n end\n end\n for _, sel in ipairs(specialSelections) do\n for _,upgrade in ipairs(inf.upgrades) do\n if upgrade == sel then\n table.insert(nameSpecials, sel)\n end\n end\n end\n if next(nameSpecials) ~= nil then\n return string.format(\"{%d/%d} %s (%s)\", inf.stats.W or 0, inf.stats.W or 0, inf.name, table.concat( nameSpecials, \", \"))\n end\n return string.format(\"{%d/%d} %s\", inf.stats.W or 0, inf.stats.W or 0, inf.name)\n end\n\n self.UI.setAttribute(id..\"_panel\", \"color\", \"#808080\")\n target.highlightOn(Color.Green, 0.5)\n local tdata = target.getData()\n\n local base = findBase(target)\n\n local model = state.models[id]\n local inf = modelToInfo(model)\n\n local stats = {}\n for k,v in pairs(inf.stats) do\n stats[k] = tonumber(string.match(v, \"%d+\"))\n end\n\n tdata.LuaScriptState = JSON.encode({\n base = base,\n owner = state.playerid,\n node = self.getGUID(),\n modelid = id,\n name = model[\"@name\"],\n wounds = stats.W or 0,\n stats = stats,\n uiHeight = 2,\n info = inf,\n roles = {},\n hiddenRoles = {},\n items = {},\n holding = false,\n uiAngle = 0\n })\n\n tdata.LuaScript = state.modelscript\n tdata.Description = getDescription(inf)\n tdata.Nickname = getNickname(inf)\n tdata.Autoraise = true\n tdata.Tooltip = true\n if tdata.CustomMesh then\n tdata.CustomMesh.TypeIndex = 1\n elseif tdata.CustomAssetbundle then\n tdata.CustomAssetbundle.TypeIndex = 1\n end\n\n local position = state.positions[id]\n spawnObjectData({\n data=tdata,\n position=self.positionToWorld(position.position),\n rotation=Vector(0, self.getRotation().y + position.rotation, 0),\n callback_function=function(o)\n o.setVelocity(Vector(0,10,0))\n end\n })\n end\nend\n\nfunction onPlayerAction(player, action, targets)\n if pickModelForId and player.steam_id == state.playerid then\n local target = targets[1]\n if target.name == \"Custom_Assetbundle\" or target.name == \"Custom_Model\" or target.name == \"Figurine_Custom\" then\n makeOperative(target, pickModelForId)\n -- pickedModels[pickModelForId] = tdata\n pickModelForId = nil\n return false\n end\n end\n return true\n end\n\nfunction onLoad()\n self.setTags({\"Command Node\"})\n getVersions()\n if state.modelscript == \"\" then loadState() end\n teamApiCode = state.teamkey or teamApiCode\n generateGui()\n if state.playerid then\n for i,v in ipairs(Player.getPlayers()) do\n if v.steam_id == state.playerid then\n self.setColorTint(Color.fromString(v.color))\n break\n end\n end\n end\nend\n\nfunction allOperatives(f)\n for k,v in pairs(state.positions) do\n local ops = getObjectsWithTag(k)\n if next(ops) ~= nil then\n f(ops)\n end\n end\nend\n\nfunction resetOperativePositions()\n for k,v in pairs(state.positions) do\n local ops = getObjectsWithTag(k)\n if next(ops) ~= nil then\n for i=2,#ops do\n ops[i].destruct()\n end\n resetPosition(k, ops[1])\n end\n end\nend\n\nfunction resetPosition(id, op)\n local p = state.positions[id]\n if p then\n local lp = self.positionToWorld(p.position) + Vector(0, 0.5, 0)\n local lr = Vector(0, self.getRotation().y + p.rotation, 0)\n op.setPositionSmooth(lp, false, true)\n op.setRotationSmooth(lr, false, true)\n end\nend\n\nfunction comResetPosition( t )\n local id = t.id\n local op = t.operative\n if id and op then\n resetPosition(id, op)\n end\nend\n",
"LuaScriptState": "{\"catalogues\":[],\"modelscript\":\"\\n-- icons by game-icons.net\\n\\nstate = {}\\n\\nself.max_typed_number=99\\n\\nranges = {\\n triangle={\\n color=Color(0.10,0.10,0.09),\\n range=1\\n },\\n circle={\\n color=Color(1,1,1),\\n range=2\\n },\\n square={\\n color=Color(0,0.36,0.62),\\n range=3\\n },\\n pentagon={\\n color=Color(0.80,0.08,0.09),\\n range=6\\n }\\n}\\n\\ntriangle = \\\"(1\\\\\\\")\\\"\\ncircle = \\\"(2\\\\\\\")\\\"\\nsquare = \\\"(3\\\\\\\")\\\"\\npentagon = \\\"(6\\\\\\\")\\\"\\n\\nfunction textColorXml( color, text )\\n return string.format(\\\"<textcolor color=\\\\\\\"#%s\\\\\\\">%s</textcolor>\\\", color, text)\\nend\\n\\nfunction textColorMd( color, text )\\n return string.format(\\\"[%s]%s[-]\\\", color, text)\\nend\\n\\nsecrets = {\\n \\\"ktcnid-status-hiddenRole\\\"\\n}\\n\\ntext_subs = {\\n [\\\"1&&\\\"] = textColorXml(\\\"000000\\\", triangle),\\n [\\\"2&&\\\"] = textColorXml(\\\"ffffff\\\", circle),\\n [\\\"3&&\\\"] = textColorXml(\\\"1E87FF\\\", square),\\n [\\\"6&&\\\"] = textColorXml(\\\"DA1A18\\\", pentagon),\\n [\\\"%(R%)\\\"] = textColorXml(\\\"1E87FF\\\", \\\"R\\\"),\\n [\\\"%(M%)\\\"] = textColorXml(\\\"F4641D\\\", \\\"M\\\")\\n}\\n\\nmd_subs = {\\n [\\\"1&&\\\"] = textColorMd(\\\"000000\\\", triangle),\\n [\\\"2&&\\\"] = textColorMd(\\\"ffffff\\\", circle),\\n [\\\"3&&\\\"] = textColorMd(\\\"1E87FF\\\", square),\\n [\\\"6&&\\\"] = textColorMd(\\\"DA1A18\\\", pentagon),\\n [\\\"%(R%)\\\"] = textColorMd(\\\"1E87FF\\\", \\\"R\\\"),\\n [\\\"%(M%)\\\"] = textColorMd(\\\"F4641D\\\", \\\"M\\\")\\n}\\n\\nfunction subsymbol(s, tbl)\\n local st = s\\n for o, sub in pairs(tbl) do\\n st = string.gsub(st, o, sub)\\n end\\n return st\\nend\\n\\nfunction xt(tag, attributes, children, value)\\n return {\\n tag=tag,\\n attributes=attributes,\\n children=children,\\n value=value\\n }\\nend\\n\\nfunction secretVisibility()\\n local p = getOwningPlayer()\\n if p == nil then return \\\"\\\" end\\n return table.concat( {\\\"Jokers\\\", p.color}, \\\"|\\\" )\\nend\\n\\nfunction hideSecrets()\\n local sv = secretVisibility()\\n for i,v in ipairs(secrets) do\\n self.UI.setAttribute(v, \\\"visibility\\\", sv)\\n end\\nend\\n\\nmodelMeasureLineRadius = 0.05\\nbase = {}\\nbaseLineRadius = 0.0125\\nbaseLineHeight = 0.2\\n\\nrangeShown = false\\nmeasureColor = nil\\nmeasureRange = 0\\n\\nfunction onNumberTyped( pc, n )\\n rangeShown = n > 0\\n measureColor = Color.fromString(pc)\\n measureRange = n\\n refreshVectors()\\n Player[pc].broadcast(string.format(\\\"%d\\\\\\\"\\\", measureRange))\\nend\\n\\nfunction saveState()\\n self.script_state = JSON.encode(state)\\nend\\n\\nfunction loadState()\\n state = JSON.decode(self.script_state)\\nend\\n\\nfunction savePosition(p, r)\\n local savePos = {\\n position=p or self.getPosition(),\\n rotation=r or self.getRotation()\\n }\\n state.savePos = savePos\\n saveState()\\n self.highlightOn(Color(0.19, 0.63, 0.87), 0.5)\\nend\\n\\nfunction loadPosition()\\n local sp = state.savePos\\n if sp then\\n self.setPositionSmooth(sp.position, false, true)\\n self.setRotationSmooth(sp.rotation, false, true)\\n self.highlightOn(Color(0.87, 0.43, 0.19), 0.5)\\n end\\nend\\n\\nfunction refreshWounds()\\n local w = state.wounds\\n local m = state.stats.W\\n\\n local uiwstring = function()\\n if w == 0 then\\n return textColorXml(\\\"DA1A18\\\", \\\"DEAD\\\")\\n end\\n return string.format(\\\"%d/%d\\\", w, m)\\n end\\n\\n local namewstring = function()\\n if w == 0 then\\n return \\\"{[DA1A18]DEAD[-]}\\\"\\n elseif w < m/2 then\\n return string.format(\\\"{[9A1111]*[-]%d/%d[9A1111]*[-]}\\\", w, m)\\n end\\n return string.format(\\\"{%d/%d}\\\", w, m)\\n end\\n\\n self.UI.setValue(\\\"ktcnid-status-wounds\\\", uiwstring())\\n\\n local nname = self.getName()\\n if string.find(nname, \\\"%b{}\\\") == nil then\\n nname = \\\"{} \\\"..nname\\n end\\n\\n self.setName(string.gsub(nname, \\\"%b{}\\\", namewstring()))\\nend\\n\\nfunction callback_role(player, value, id)\\n player.broadcast(table.concat( state.roles, \\\", \\\" ))\\nend\\n\\nfunction callback_hiddenRole(player, value, id)\\n player.broadcast(table.concat( state.hiddenRoles, \\\", \\\" ))\\nend\\n\\nfunction callback_item(player, value, id)\\n player.broadcast(table.concat( state.items, \\\", \\\" ))\\nend\\n\\nfunction refreshUI()\\n local sc = self.getScale()\\n local scaleFactorX = 1/sc.x\\n local scaleFactorY = 1/sc.y\\n local scaleFactorZ = 1/sc.z\\n\\n local circOffset = function(d, a)\\n local ra = math.rad(a)\\n return string.format(\\\"%d %d\\\", math.cos(ra)*d, math.sin(ra)*d)\\n end\\n\\n local uid = 50\\n\\n local sv = secretVisibility()\\n\\n self.UI.setXmlTable({\\n xt(\\\"Defaults\\\", {},{\\n xt(\\\"Image\\\",{\\n class=\\\"statusDisplay\\\",\\n hideAnimation=\\\"Shrink\\\",\\n showAnimation=\\\"Grow\\\"\\n })\\n }),\\n xt(\\\"Panel\\\", {\\n position = \\\"0 0 -\\\"..tostring(state.uiHeight*100*scaleFactorZ),\\n width=100,\\n height=100,\\n rotation=\\\"0 0 \\\"..(state.uiAngle or 0),\\n scale=string.format(\\\"%f %f %f\\\", scaleFactorX, scaleFactorY, scaleFactorZ)\\n },{\\n xt(\\\"Panel\\\", {\\n id=\\\"ktcnid-status-display-ring\\\"\\n }, {\\n xt(\\\"Image\\\",{\\n class=\\\"statusDisplay\\\",\\n image=\\\"role\\\",\\n color=\\\"#F3961C\\\",\\n width=30, height=30,\\n offsetXY=circOffset(uid, 90),\\n id=\\\"ktcnid-status-role\\\",\\n onClick=\\\"callback_role\\\",\\n active = next(state.roles) ~= nil\\n }),\\n xt(\\\"Image\\\",{\\n class=\\\"statusDisplay\\\",\\n image=\\\"role\\\",\\n color=\\\"#2C20CA\\\",\\n width=30, height=30,\\n offsetXY=circOffset(uid, 45),\\n id=\\\"ktcnid-status-hiddenRole\\\",\\n onClick=\\\"callback_hiddenRole\\\",\\n active = next(state.hiddenRoles) ~= nil,\\n visibility=sv\\n }),\\n xt(\\\"Image\\\",{\\n class=\\\"statusDisplay\\\",\\n image=\\\"item\\\",\\n color=\\\"#F3961C\\\",\\n width=30, height=30,\\n offsetXY=circOffset(uid, 135),\\n id=\\\"ktcnid-status-holding\\\",\\n active = state.holding\\n })\\n }),\\n xt(\\\"Image\\\",{\\n class=\\\"statusDisplay\\\",\\n image=\\\"engage\\\",\\n width=75, height=75,\\n color=\\\"#FF5500\\\",\\n active=false,\\n id=\\\"ktcnid-status-order\\\"\\n }),\\n xt(\\\"Panel\\\",{\\n color=\\\"#808080\\\",\\n outline=\\\"#FF5500\\\",\\n outlineSize=\\\"2 2\\\",\\n width=50,\\n height=25,\\n offsetXY=circOffset(40, 270)\\n },{ \\n xt(\\\"Image\\\",{\\n image=\\\"wound\\\",\\n class=\\\"statusDisplay\\\",\\n color=\\\"#921110\\\",\\n width=30, height=30,\\n rectAlignment=\\\"MiddleLeft\\\",\\n offsetXY=\\\"-35 0\\\",\\n id=\\\"ktcnid-status-injured\\\",\\n active = state.stats.W and state.wounds < state.stats.W/2 or false\\n }),\\n xt(\\\"Image\\\",{\\n image=\\\"item\\\",\\n class=\\\"statusDisplay\\\",\\n color=\\\"#713B17\\\",\\n width=30, height=30,\\n rectAlignment=\\\"MiddleRight\\\",\\n offsetXY=\\\"35 0\\\",\\n id=\\\"ktcnid-status-item\\\",\\n active= next(state.items) ~= nil,\\n onClick=\\\"callback_item\\\"\\n }),\\n xt(\\\"Text\\\",{\\n text=string.format(\\\"%d/%d\\\", state.wounds or 0, state.stats.W or 0),\\n resizeTextForBestFit=true,\\n color=\\\"#ffffff\\\",\\n id=\\\"ktcnid-status-wounds\\\"\\n })\\n })\\n })\\n })\\nend\\n\\nfunction createUI( )\\n self.UI.setCustomAssets({\\n -- {name=\\\"conceal\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139373439/322556886BB52F6618257B7670C16DFCF234491C/]=]},\\n -- {name=\\\"engage\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139373385/10EBAC6E2A9A0226C23790B4D6C3FAF0222CFBD2/]=]},\\n -- {name=\\\"item\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139373338/32A408D41A6CF96B31F8E41032664CAD756665A4/]=]},\\n -- {name=\\\"role\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139373274/67450F5CD734514E71F9A6B0C61E52D0A0D48358/]=]},\\n {name=\\\"wound\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139373232/CA1024D61CAE8AA810E3D70D58BE0823D6F63FCF/]=]}\\n -- {name=\\\"dead\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139373167/775C3F30A3EB854CB0FC7B5454EAFDA59A701E9F/]=]},\\n -- {name=\\\"roster\\\", url=[=[http://cloud-3.steamusercontent.com/ugc/1613967569139450440/D4CAF07C20088B4611666FAEFD5C3E22DEB9FF78/]=]}\\n })\\n\\n refreshUI()\\nend\\n\\nfunction isInjured()\\n return state.stats.W and (state.wounds < state.stats.W/2) or false\\nend\\n\\nfunction notify(pc, message)\\n local owner = getOwningPlayer()\\n if pc == owner.color then\\n owner.broadcast(message)\\n else\\n owner.broadcast(string.format(\\\"%s: %s\\\", Player[pc].name, message))\\n Player[pc].broadcast(message)\\n end\\nend\\n\\nfunction damage(pc)\\n local si = isInjured()\\n state.wounds = math.max(0, (state.wounds or 0) - 1)\\n if not si and isInjured() then\\n self.UI.show(\\\"ktcnid-status-injured\\\")\\n end\\n saveState()\\n refreshWounds()\\n notify(pc, string.format(\\\"%s took damage\\\", self.getName()))\\nend\\n\\nfunction heal(pc)\\n local si = isInjured()\\n state.wounds = math.min((state.stats.W or 0), (state.wounds or 0) + 1)\\n if si and not isInjured() then\\n self.UI.hide(\\\"ktcnid-status-injured\\\")\\n end\\n saveState()\\n refreshWounds()\\n notify(pc, string.format(\\\"%s recovered\\\", self.getName()))\\nend\\n\\nfunction kill(pc)\\n state.wounds = 0\\n saveState()\\n refreshWounds()\\n notify(pc, string.format(\\\"%s KO\\\", self.getName()))\\nend\\n\\nfunction updateStats(pc)\\n if getOwningPlayer().color ~= pc then\\n notify(pc, \\\"Only the model's owner can update stats\\\")\\n return\\n end\\n notify(pc, \\\"Updating stats from values in description\\\")\\n local statsub = {}\\n local prevW = state.stats.W or 0\\n local wounds = state.wounds or 0\\n local desc = self.getDescription() or \\\"\\\"\\n local innerUpdate = function(stat)\\n local sstring = \\\"%[84E680%]\\\" .. stat .. \\\"%[%-%]%s*%[ffffff%]%s*(%d+).*%[%-%]\\\"\\n for match in string.gmatch(desc, \\\"%b[]\\\") do\\n local s = match:match(sstring)\\n if s then\\n local ss = state.stats[stat]\\n table.insert(statsub, string.format(\\\"%s = %s\\\", stat, s))\\n if ss and ss == tonumber(s) then return false end\\n state.stats[stat] = tonumber(s)\\n\\n -- notify(pc, string.format(\\\"%s set to %s\\\", stat, s))\\n return true\\n end\\n end\\n table.insert(statsub, string.format(\\\"%s = [ff0000]X[-]\\\", stat))\\n return false\\n end\\n innerUpdate(\\\"M\\\")\\n innerUpdate(\\\"APL\\\")\\n innerUpdate(\\\"GA\\\")\\n innerUpdate(\\\"DF\\\")\\n innerUpdate(\\\"SV\\\")\\n if innerUpdate(\\\"W\\\") then\\n if wounds == prevW then\\n state.wounds = state.stats.W or 0\\n else\\n state.wounds = min(state.stats.W or 0)\\n end\\n refreshWounds()\\n end\\n saveState()\\n notify(pc, table.concat( statsub, \\\", \\\"))\\nend\\n\\nfunction onLoad(ls)\\n loadState()\\n\\n self.addContextMenuItem(\\\"Take damage\\\", damage, true)\\n self.addContextMenuItem(\\\"Restore wounds\\\", heal, true)\\n self.addContextMenuItem(\\\"Kill\\\", kill)\\n self.addContextMenuItem(\\\"Save place\\\", function(pc) savePosition() end)\\n self.addContextMenuItem(\\\"Load place\\\", function(pc) loadPosition() end)\\n self.addContextMenuItem(\\\"Update stats\\\", updateStats)\\n\\n local taglist = {state.modelid, \\\"Operative\\\"}\\n for _,category in pairs(state.info.categories) do\\n table.insert(taglist, category)\\n end\\n self.setTags(taglist)\\n createUI()\\n\\n refreshVectors()\\nend\\n\\nfunction onPickUp(pc)\\n if rangeShown then\\n refreshVectors(true)\\n end\\nend\\n\\nfunction tryRandomize(pc)\\n rangeShown = not rangeShown\\n measureColor = nil\\n measureRange = 0\\n refreshVectors()\\n\\n return false\\nend\\n\\nfunction getOwningPlayer()\\n for _, player in ipairs(Player.getPlayers()) do\\n if player.steam_id == state.owner then\\n return player\\n end\\n end\\n return nil\\nend\\n\\nfunction onPlayerChangeColor(color)\\n if color ~= \\\"Grey\\\" then\\n local p = Player[color]\\n if p.steam_id == state.owner then\\n refreshVectors()\\n hideSecrets()\\n end\\n end\\nend\\n\\nfunction refreshVectors(norotate)\\n local op = getOwningPlayer()\\n local circ = {}\\n local scaleFactor = 1/self.getScale().x\\n \\n local rotation = self.getRotation()\\n\\n local newLines = {\\n {\\n points = getCircleVectorPoints(0 - baseLineRadius, baseLineHeight),\\n color = op and Color.fromString(op.color) or {0.5, 0.5, 0.5},\\n thickness = baseLineRadius*2*scaleFactor\\n }\\n }\\n\\n if rangeShown then\\n if measureRange > 0 then\\n table.insert(newLines,{\\n points=getCircleVectorPoints(measureRange - modelMeasureLineRadius + 0.05, 0.125),\\n color = measureColor,\\n thickness = modelMeasureLineRadius*2*scaleFactor,\\n rotation = (norotate and {0, 0, 0} or {-rotation.x, 0, -rotation.z})\\n })\\n else\\n for _,r in pairs(ranges) do\\n local range = r.range\\n table.insert(newLines,{\\n points=getCircleVectorPoints(range - modelMeasureLineRadius + 0.05, 0.125),\\n color = r.color,\\n thickness = modelMeasureLineRadius*2*scaleFactor,\\n rotation = (norotate and {0, 0, 0} or {-rotation.x, 0, -rotation.z})\\n })\\n end\\n end\\n end\\n\\n self.setVectorLines(newLines)\\nend\\n\\nfunction getCircleVectorPoints(radius, height, segments)\\n local bounds = self.getBoundsNormalized()\\n local result = {}\\n local scaleFactorX = 1/self.getScale().x\\n local scaleFactorY = 1/self.getScale().y\\n local scaleFactorZ = 1/self.getScale().z\\n local steps = segments or 64\\n local degrees,sin,cos,toRads = 360/steps, math.sin, math.cos, math.rad\\n local modelBase = state.base\\n\\n local mtoi = 0.0393701\\n local baseX = modelBase.x * 0.5 * mtoi\\n local baseZ = modelBase.z * 0.5 * mtoi\\n\\n for i = 0,steps do\\n table.insert(result,{\\n x = cos(toRads(degrees*i))*((radius+baseX)*scaleFactorX),\\n z = sin(toRads(degrees*i))*((radius+baseZ)*scaleFactorZ),\\n y = height*scaleFactorY\\n })\\n end\\n\\n return result\\nend\\n\\nfunction doAutoSize()\\n local nx = state.base.x\\n local nz = state.base.z\\n local bounds = self.getBoundsNormalized()\\n if bounds.size.x == 0 or bounds.size.y == 0 then\\n local r = self.getRotation()\\n self.setRotation(Vector(0,0,0))\\n bounds = self.getBounds()\\n self.setRotation(r)\\n end\\n local scale = self.getScale()\\n local xi = nx / 25.4\\n local zi = nz / 25.4\\n local xs = (xi / bounds.size.x) * scale.x\\n local zs = (zi / bounds.size.z) * scale.z\\n \\n self.setScale(Vector(xs, (xs + zs) / 2, zs))\\n refreshVectors()\\nend\\n\\nfunction setBaseSize( x, z )\\n state.base = {x=x, z=z}\\n -- state.uiHeight=((x + z)/25)\\n saveState()\\n refreshVectors()\\n refreshUI()\\nend\\n\\nfunction addRole( role, hidden )\\n local rg = hidden and state.hiddenRoles or state.roles\\n local empty = next(rg) == nil\\n table.insert(rg, role)\\n if empty then \\n self.UI.show(hidden and \\\"ktcnid-status-hiddenRole\\\" or \\\"ktcnid-status-role\\\") \\n end\\n saveState()\\nend\\n\\nfunction removeRole( role )\\n local rri = function( rg, id )\\n local nr = {}\\n for i,v in ipairs(rg) do\\n if v ~= role then table.insert(nr, v) end\\n end\\n rg = nr\\n if next(rg) == nil then\\n self.UI.hide(id)\\n end\\n end\\n rri(state.roles, \\\"ktcnid-status-role\\\")\\n rri(state.hiddenRoles, \\\"ktcnid-status-hiddenRole\\\")\\nend\\n\\nfunction revealRole( role )\\n removeRole(role)\\n addRole(role, false)\\nend\\n\\nfunction comCheckOwner(t)\\n return t[1] == state.owner\\nend\\n\\nfunction comBaseSize()\\n return state.base\\nend\\n\\nfunction comSetBase(t)\\n setBaseSize(t.x, t.z)\\nend\\n\\nfunction comAutoSize()\\n doAutoSize()\\n refreshUI()\\nend\\n\\nfunction comSavePosition( t )\\n savePosition(t.position, t.rotation)\\nend\\n\\nfunction comLoadPosition()\\n loadPosition()\\nend\\n\\nfunction comAddRole( t )\\n addRole(t.role, t.hidden)\\nend\\n\\nfunction comRemoveRole( t )\\n removeRole(t.role)\\nend\\n\\nfunction comRevealRole( t )\\n revealRole(t.role)\\nend\\n\\nfunction comSetUIAngle( t )\\n state.uiAngle = t.uiAngle\\n saveState()\\n refreshUI()\\nend\",\"name\":\"New Team\",\"positions\":[],\"version\":{\"model\":15,\"node\":23}}",
"XmlUI": ""
}
]
}