diff --git a/config.json b/config.json
index 566b00505..ad891dbec 100644
--- a/config.json
+++ b/config.json
@@ -3,7 +3,6 @@
"ComponentTags_path": "ComponentTags.json",
"CustomUIAssets_path": "CustomUIAssets.json",
"DecalPallet_path": "DecalPallet.json",
- "Decals": [],
"GameComplexity": "",
"GameMode": "Arkham Horror LCG - Super Complete Edition",
"GameType": "",
@@ -84,6 +83,7 @@
"Trash.5f896a",
"Trash.147e80",
"Trash.f7b6c8",
+ "PatchNotes.f47225",
"RulesReference.d99993",
"LatestFAQ.faqfaq",
"Doomtokens.16724b",
@@ -207,7 +207,6 @@
"Tokencache_Curse.16a9a7",
"Tokencache_Frost.b2b7be",
"PhysicsDetector.b300d8",
- "ArkhamSCE390-06302024-Page1.bd6b3e",
"Neutral.834ad5",
"Neutral.a84ae4",
"Neutral.762df8",
diff --git a/objects/ArkhamSCE390-06302024-Page1.bd6b3e.json b/objects/ArkhamSCE390-06302024-Page1.bd6b3e.json
deleted file mode 100644
index c63f78865..000000000
--- a/objects/ArkhamSCE390-06302024-Page1.bd6b3e.json
+++ /dev/null
@@ -1,137 +0,0 @@
-{
- "AltLookAngle": {
- "x": 0,
- "y": 0,
- "z": 0
- },
- "Autoraise": true,
- "ColorDiffuse": {
- "b": 1,
- "g": 1,
- "r": 1
- },
- "Description": "Thanks for downloading Arkham SCE 3.9.0!\n\n- Added confirmation dialog for discard hotkey (e.g. for locations)\r\n- Added new action / ability tokens\r\n- Added automated discarding for Patrice\r\n- Added new option to enable all card helpers\r\n- Added new option to load class-specific playermats\r",
- "DragSelectable": true,
- "GMNotes": "",
- "GUID": "bd6b3e",
- "Grid": true,
- "GridProjection": false,
- "Hands": false,
- "HideWhenFaceDown": false,
- "IgnoreFoW": false,
- "LayoutGroupSortIndex": 0,
- "Locked": false,
- "LuaScript": "",
- "LuaScriptState": "",
- "MeasureMovement": false,
- "Name": "Notecard",
- "Nickname": "Arkham SCE 3.9.0 - 06/30/2024 - Page 1",
- "Snap": true,
- "States": {
- "2": {
- "AltLookAngle": {
- "x": 0,
- "y": 0,
- "z": 0
- },
- "Autoraise": true,
- "ColorDiffuse": {
- "b": 1,
- "g": 1,
- "r": 1
- },
- "Description": "\n- Performed a small clean up of the bottom corners of the table\r\n- Updated 'Additional Cards Bag' / 'Player Card Panel' with better handling for fan-made cards\r\n- Updated Clean Up Helper, Drawing Tool, Hand Helper and Search Assistant\r\n- Updated Token Arranger",
- "DragSelectable": true,
- "GMNotes": "",
- "GUID": "522604",
- "Grid": true,
- "GridProjection": false,
- "Hands": false,
- "HideWhenFaceDown": false,
- "IgnoreFoW": false,
- "LayoutGroupSortIndex": 0,
- "Locked": false,
- "LuaScript": "",
- "LuaScriptState": "",
- "MeasureMovement": false,
- "Name": "Notecard",
- "Nickname": "Arkham SCE 3.9.0 - 06/30/2024 - Page 2",
- "Snap": true,
- "Sticky": true,
- "Tooltip": true,
- "Transform": {
- "posX": -27,
- "posY": 1.551,
- "posZ": -56.165,
- "rotX": 0,
- "rotY": 90,
- "rotZ": 0,
- "scaleX": 3,
- "scaleY": 1,
- "scaleZ": 3
- },
- "Value": 0,
- "XmlUI": ""
- },
- "3": {
- "AltLookAngle": {
- "x": 0,
- "y": 0,
- "z": 0
- },
- "Autoraise": true,
- "ColorDiffuse": {
- "b": 1,
- "g": 1,
- "r": 1
- },
- "Description": "\n- Implemented menu to redraw tokens for specific cards like Heavy Furs and Wendy Adams\r\n- Bugfix for attempting to draw an encounter card while there is no deck\r\n- Bugfix for Navigation Overlay: now checks if playmat is occupied\r\n- Bugfix for Phase Tracker broadcasting\r\n- Performance improvements",
- "DragSelectable": true,
- "GMNotes": "",
- "GUID": "522877",
- "Grid": true,
- "GridProjection": false,
- "Hands": false,
- "HideWhenFaceDown": false,
- "IgnoreFoW": false,
- "LayoutGroupSortIndex": 0,
- "Locked": false,
- "LuaScript": "",
- "LuaScriptState": "",
- "MeasureMovement": false,
- "Name": "Notecard",
- "Nickname": "Arkham SCE 3.9.0 - 06/30/2024 - Page 3",
- "Snap": true,
- "Sticky": true,
- "Tooltip": true,
- "Transform": {
- "posX": -27,
- "posY": 1.551,
- "posZ": -56.165,
- "rotX": 0,
- "rotY": 90,
- "rotZ": 0,
- "scaleX": 3,
- "scaleY": 1,
- "scaleZ": 3
- },
- "Value": 0,
- "XmlUI": ""
- }
- },
- "Sticky": true,
- "Tooltip": true,
- "Transform": {
- "posX": -27,
- "posY": 1.551,
- "posZ": -56.165,
- "rotX": 0,
- "rotY": 90,
- "rotZ": 0,
- "scaleX": 3,
- "scaleY": 1,
- "scaleZ": 3
- },
- "Value": 0,
- "XmlUI": ""
-}
diff --git a/objects/PatchNotes.f47225.json b/objects/PatchNotes.f47225.json
new file mode 100644
index 000000000..e34274f29
--- /dev/null
+++ b/objects/PatchNotes.f47225.json
@@ -0,0 +1,113 @@
+{
+ "AltLookAngle": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "AttachedDecals": [
+ {
+ "CustomDecal": {
+ "ImageURL": "http://cloud-3.steamusercontent.com/ugc/2501268517218943111/803E57A7B3E9765DF342050EE6C71D69473A7388/",
+ "Name": "Image #1",
+ "Size": 1
+ },
+ "Transform": {
+ "posX": -0.93,
+ "posY": 0.105,
+ "posZ": 0.66,
+ "rotX": 90,
+ "rotY": 180,
+ "rotZ": 0,
+ "scaleX": 0.6,
+ "scaleY": 0.6,
+ "scaleZ": 1
+ }
+ },
+ {
+ "CustomDecal": {
+ "ImageURL": "http://cloud-3.steamusercontent.com/ugc/2037357792052848566/5DA900C430E97D3DFF2C9B8A3DB1CB2271791FC7/",
+ "Name": "Image #2",
+ "Size": 1
+ },
+ "Transform": {
+ "posX": -1.05,
+ "posY": 0.105,
+ "posZ": -0.567,
+ "rotX": 90,
+ "rotY": 205,
+ "rotZ": 0,
+ "scaleX": 0.3,
+ "scaleY": 0.3,
+ "scaleZ": 1
+ }
+ },
+ {
+ "CustomDecal": {
+ "ImageURL": "http://cloud-3.steamusercontent.com/ugc/2501268517219098388/0936FEE03B410319658B5E05DB5D486CEDDE98F5/",
+ "Name": "Image #3",
+ "Size": 1
+ },
+ "Transform": {
+ "posX": 0,
+ "posY": 0.105,
+ "posZ": -0.81,
+ "rotX": 90,
+ "rotY": 180,
+ "rotZ": 0,
+ "scaleX": 2.4,
+ "scaleY": 0.009,
+ "scaleZ": 1
+ }
+ }
+ ],
+ "Autoraise": true,
+ "ColorDiffuse": {
+ "b": 1,
+ "g": 1,
+ "r": 1
+ },
+ "CustomImage": {
+ "CustomTile": {
+ "Stackable": false,
+ "Stretch": true,
+ "Thickness": 0.1,
+ "Type": 0
+ },
+ "ImageScalar": 1,
+ "ImageSecondaryURL": "http://sfwallpaper.com/images/parchment-paper-wallpaper-10.jpg",
+ "ImageURL": "http://sfwallpaper.com/images/parchment-paper-wallpaper-10.jpg",
+ "WidthScale": 0
+ },
+ "Description": "Thanks for downloading! We're happy to present you a rather big update this time :-)\n\nNew things\n- automated discarding for Patrice\n- confirmation dialog for discard hotkey (e.g. for locations)\n- helpers for cards that redraw tokens and Kohaku\n- displaying of token count for cards that seal tokens\r\n- new action / ability tokens (replacing the old ones)\r\n- option to enable all card helpers (e.g. Heavy Furs)\r\n- option to load class-colored playermat backgrounds\n- coloring for player names in broadcasts\n- right-click option for RBW button on Player Card Panel to specify trait(s)\n\nUpdates\r\n- performed a small clean up of the bottom corners of the table\n- \"Numpad 9\" to rearranges present tokens (on top of adding a resource)\n- Scroll of Secrets context menu helper now displays player names instead of colors\r\n- Player Card Panel can display fan-made cards with a new \"custom\" cycle button)\n- \"Discard object\" gamekey works for selected objects\r\n- updated a bunch of tools like Clean Up Helper, Drawing Tool,\nHand Helper, Token Arranger and Search Assistant\n\nFixes\r\r\n- Bugfix for attempting to draw an encounter card while there is no deck\r\n- Bugfix for Navigation Overlay: now checks if playmat is occupied\r\n- Bugfix for Phase Tracker broadcasting\r\n- Performance and file size improvements (e.g. by adding download\nfunctions for CYOA campaign guides and Arkham Fantasy standees)",
+ "DragSelectable": true,
+ "GMNotes": "",
+ "GUID": "f47225",
+ "Grid": true,
+ "GridProjection": false,
+ "Hands": false,
+ "HideWhenFaceDown": false,
+ "IgnoreFoW": false,
+ "LayoutGroupSortIndex": 0,
+ "Locked": false,
+ "LuaScriptState_path": "PatchNotes.f47225.luascriptstate",
+ "LuaScript_path": "PatchNotes.f47225.ttslua",
+ "MeasureMovement": false,
+ "Name": "Custom_Tile",
+ "Nickname": "Patch Notes",
+ "Snap": true,
+ "Sticky": true,
+ "Tooltip": true,
+ "Transform": {
+ "posX": -27,
+ "posY": 1.481,
+ "posZ": -56.165,
+ "rotX": 0,
+ "rotY": 270,
+ "rotZ": 0,
+ "scaleX": 7.5,
+ "scaleY": 1,
+ "scaleZ": 7.5
+ },
+ "Value": 0,
+ "XmlUI": ""
+}
diff --git a/objects/PatchNotes.f47225.luascriptstate b/objects/PatchNotes.f47225.luascriptstate
new file mode 100644
index 000000000..63e472ba7
--- /dev/null
+++ b/objects/PatchNotes.f47225.luascriptstate
@@ -0,0 +1 @@
+{"checks":[],"decals":[{"locked":false,"name":"Arkham SCE logo","pos":{"x":3.1,"y":2.2},"rotation":0,"scale":{"x":"2","y":"2"},"tooltip":"None","url":"http://cloud-3.steamusercontent.com/ugc/2501268517218943111/803E57A7B3E9765DF342050EE6C71D69473A7388/"},{"locked":false,"name":"Bootlegger Finn","pos":{"x":3.5,"y":-1.89},"rotation":"25","scale":{"x":"1","y":"1"},"tooltip":"None","url":"http://cloud-3.steamusercontent.com/ugc/2037357792052848566/5DA900C430E97D3DFF2C9B8A3DB1CB2271791FC7/"},{"locked":false,"name":"black bar","pos":{"x":0,"y":-2.7},"rotation":0,"scale":{"x":"8","y":"0.03"},"tooltip":"None","url":"http://cloud-3.steamusercontent.com/ugc/2501268517219098388/0936FEE03B410319658B5E05DB5D486CEDDE98F5/"}],"fields":[{"align":3,"array":{"x":"1","y":"1"},"counter":"False","distance":{"x":"1","y":"1"},"fieldColor":{"a":0,"b":1,"g":1,"r":1},"font":"200","locked":false,"name":"Patch Notes","pos":{"x":"0","y":-2.9},"role":"Normal Field","size":{"x":"3750","y":"250"},"textColor":{"a":1,"b":0,"g":0,"r":0},"tooltip":"None","value":["Arkham Horror LCG SCE 3.9.0 - 07/05/2024"]},{"align":2,"array":{"x":"1","y":1},"distance":{"x":"1","y":"1"},"fieldColor":{"a":0,"b":1,"g":1,"r":1},"font":"90","locked":false,"name":"Details","pos":{"x":"0","y":0.4},"role":"Set object's description","size":{"x":"3750","y":"2750"},"textColor":{"a":1,"b":0,"g":0,"r":0},"tooltip":"None","value":["Thanks for downloading! We're happy to present you a rather big update this time :-)\n\nNew things\n- automated discarding for Patrice\n- confirmation dialog for discard hotkey (e.g. for locations)\n- helpers for cards that redraw tokens and Kohaku\n- displaying of token count for cards that seal tokens\r\n- new action / ability tokens (replacing the old ones)\r\n- option to enable all card helpers (e.g. Heavy Furs)\r\n- option to load class-colored playermat backgrounds\n- coloring for player names in broadcasts\n- right-click option for RBW button on Player Card Panel to specify trait(s)\n\nUpdates\r\n- performed a small clean up of the bottom corners of the table\n- \"Numpad 9\" to rearranges present tokens (on top of adding a resource)\n- Scroll of Secrets context menu helper now displays player names instead of colors\r\n- Player Card Panel can display fan-made cards with a new \"custom\" cycle button)\n- \"Discard object\" gamekey works for selected objects\r\n- updated a bunch of tools like Clean Up Helper, Drawing Tool,\nHand Helper, Token Arranger and Search Assistant\n\nFixes\r\r\n- Bugfix for attempting to draw an encounter card while there is no deck\r\n- Bugfix for Navigation Overlay: now checks if playmat is occupied\r\n- Bugfix for Phase Tracker broadcasting\r\n- Performance and file size improvements (e.g. by adding download\nfunctions for CYOA campaign guides and Arkham Fantasy standees)"]}],"flip":"False","height":"0.1","locks":{"checks":false,"decals":false,"fields":false},"nudgeDistance":0.01,"scale":{"x":"0.3","y":"0.3"},"sheetLocked":true}
diff --git a/objects/PatchNotes.f47225.ttslua b/objects/PatchNotes.f47225.ttslua
new file mode 100644
index 000000000..2bd9c956a
--- /dev/null
+++ b/objects/PatchNotes.f47225.ttslua
@@ -0,0 +1,3513 @@
+---@diagnostic disable
+function onload(saved_data)
+ sheetLocked = self.script_state.sheetLocked or false
+ local inverseScale = { x = math.floor(100 / self.getScale().x) / 100, y = math.floor(100 / self.getScale().z) / 100 }
+ scale = self.script_state.scale or inverseScale
+ flip = self.script_state.flip or "False"
+ fields = self.script_state.fields or {}
+ checks = self.script_state.checks or {}
+ decals = self.script_state.decals or {}
+ height = self.script_state.height or 0.5
+ locks = self.script_state.locks or { fields = false, checks = false, decals = false }
+ lookupInputIndexToInfo = {}
+ lookupButtonIndexToInfo = {}
+ lookupFieldIndices = {}
+ lookupCheckIndices = {}
+ lookupDecalIndices = {}
+ lookupSelectionButtonIndices = {}
+ lastFieldLockedMessage = 0
+ buttonIndex = 0
+ inputIndex = 0
+ if saved_data ~= "" then
+ local loadedData = JSON.decode(saved_data)
+ sheetLocked = loadedData.sheetLocked or false
+ flip = loadedData.flip or "False"
+ height = loadedData.height or 0.5
+ fields = loadedData.fields or {}
+ checks = loadedData.checks or {}
+ decals = loadedData.decals or {}
+ scale = loadedData.scale or inverseScale
+ locks = loadedData.locks or { fields = false, checks = false, decals = false }
+ end
+ if (not getCommited()) then
+ self.addContextMenuItem("Edit Layout", showEditPanel)
+ self.addContextMenuItem("Commit Layout", showCommitPanel)
+ nudgeDistance = self.script_state.nudgeDistance or 0.1
+ page = 1
+ editingSheet = false
+ selectedId = 0
+ selectedArrayId = 1
+ selectedType = ""
+ selectedMax = 5
+ creating = false
+ else
+ makeContextMenuItems()
+ end
+ createAll()
+end
+
+function makeContextMenuItems()
+ self.clearContextMenu()
+ if (not sheetLocked) then
+ local getLocked = function(element)
+ if (locks[element]) then return "◆ Lock" else return "◇ Lock" end
+ end
+ local allLocked = "Lock"
+ if (locks.fields and locks.checks and locks.decals) then allLocked = "Unlock" end
+ if (#fields > 0 and #checks > 0 and #decals > 0) then
+ self.addContextMenuItem(allLocked .. " everything", toggleAllLocks)
+ end
+ for k, v in pairs(fields) do
+ if (v.locked ~= "True") then
+ self.addContextMenuItem(getLocked("fields") .. " texts", toggleLockFields)
+ break
+ end
+ end
+ for k, v in pairs(checks) do
+ if (v.locked ~= "True") then
+ self.addContextMenuItem(getLocked("checks") .. " checkboxes", toggleLockChecks)
+ break
+ end
+ end
+ for k, v in pairs(decals) do
+ if (v.locked ~= "True") then
+ self.addContextMenuItem(getLocked("decals") .. " images", toggleLockDecals)
+ break
+ end
+ end
+ end
+end
+
+function toggleAllLocks(ply, pos, obj)
+ if (locks.fields and locks.checks and locks.decals) then
+ locks.fields = false
+ locks.checks = false
+ locks.decals = false
+ broadcastToColor("Unlocked everything", ply)
+ else
+ locks.fields = true
+ locks.checks = true
+ locks.decals = true
+ broadcastToColor("Locked everything", ply)
+ end
+ updateSave()
+ makeContextMenuItems()
+ refresh()
+end
+
+function toggleLockFields(ply, pos, obj)
+ if (locks.fields) then
+ locks.fields = false
+ broadcastToColor("Unlocked texts", ply)
+ else
+ locks.fields = true
+ broadcastToColor("Locked texts", ply)
+ end
+ updateSave()
+ makeContextMenuItems()
+ refresh()
+end
+
+function toggleLockChecks(ply, pos, obj)
+ if (locks.checks) then
+ locks.checks = false
+ broadcastToColor("Unlocked checkboxes", ply)
+ else
+ locks.checks = true
+ broadcastToColor("Locked checkboxes", ply)
+ end
+ updateSave()
+ makeContextMenuItems()
+ refresh()
+end
+
+function toggleLockDecals(ply, pos, obj)
+ if (locks.decals) then
+ locks.decals = false
+ broadcastToColor("Unlocked images", ply)
+ else
+ locks.decals = true
+ broadcastToColor("Locked images", ply)
+ end
+ updateSave()
+ makeContextMenuItems()
+ refresh()
+end
+
+function updateSave()
+ local data_to_save = {
+ scale = scale,
+ height = height,
+ fields = fields,
+ checks = checks,
+ decals = decals,
+ flip = flip,
+ sheetLocked = sheetLocked,
+ locks = locks
+ }
+ if (not getCommited()) then
+ data_to_save.nudgeDistance = nudgeDistance
+ else
+ data_to_save.nudgeDistance = nil
+ end
+ saved_data = JSON.encode(data_to_save)
+ self.script_state = saved_data
+end
+
+function null() end
+
+function refresh()
+ if (not creating) then
+ self.clearInputs()
+ self.clearButtons()
+ inputIndex = 0
+ buttonIndex = 0
+ if (editingSheet) then
+ createSelectionHighlight()
+ end
+ createAll()
+ end
+end
+
+function createAll()
+ lookupInputIndexToInfo = {}
+ lookupButtonIndexToInfo = {}
+ lookupFieldIndices = {}
+ lookupCheckIndices = {}
+ lookupDecalIndices = {}
+ startLuaCoroutine(self, "createAllCoroutine")
+end
+
+function createAllCoroutine()
+ if (not getCommited()) then
+ UI.setAttribute(getPanelId("Loading"), "active", "True")
+ end
+ coroutine.yield(0)
+ creating = true
+ for fieldID, field in pairs(fields) do
+ lookupFieldIndices[fieldID] = {
+ inputs = {},
+ totals = {},
+ counterButtons = {},
+ selectionButtons = {}
+ }
+ local posx = field.pos.x
+ local posy = field.pos.y
+ local func = "edit"
+ if (field.vsum == "True") then
+ func = 'MarumEditableRecalculateSum_' .. fieldID
+ _G[func] = function(obj, ply, input_value, selected)
+ local fID = fieldID
+ edit(obj, ply, input_value, selected)
+ obj.call("recalculateVSums", fID)
+ end
+ else
+ local iIndex = inputIndex
+ if (field.locked == "True" or sheetLocked or locks.fields) then
+ func = 'MarumEditableRevert_' .. fieldID
+ _G[func] = function(obj, ply, input_value, selected)
+ local fID = fieldID
+ local iID = iIndex
+ local sel = selected
+ obj.call("revertField", { fID, ply, iID, sel })
+ end
+ end
+ end
+
+ local fieldScale = { x = scale.x, y = 1, z = scale.y }
+ local rotation = { x = 0, y = 0, z = 0 }
+ local posMulx = 1
+ local flipped = 1
+ if (flip == "True") then
+ rotation.y = 180
+ flipped = -1
+ end
+ local upright = self.getTransformUp().y > 0
+ local shouldFlip = (field.locked == "True" or sheetLocked or locks.fields) and upright
+ local unlockedRotation = { x = rotation.x, y = rotation.y, z = rotation.z }
+ if (editingSheet or shouldFlip) then
+ rotation.z = 180
+ posMulx = -posMulx
+ fieldScale.x = -fieldScale.x
+ end
+ local vsum = 0
+ local fontSize = field.font
+ fontSize = math.min(field.size.y - 24, fontSize)
+ local color = getFieldTextColor(fieldID)
+ for x = 1, field.array.x do
+ vsum = 0
+ for y = 1, field.array.y do
+ local arrayID = x + (y - 1) * field.array.x
+ local pos = getFieldPosition(fieldID, x, y)
+ self.createInput({
+ value = field.value[arrayID],
+ tooltip = getFieldTooltip(fieldID, arrayID),
+ input_function = func,
+ function_owner = self,
+ alignment = field.align,
+ position = pos,
+ width = field.size.x,
+ height = field.size.y,
+ rotation = rotation,
+ font_size = fontSize,
+ scale = fieldScale,
+ font_color = color,
+ color = field.fieldColor,
+ tab = 2
+ })
+ if (field.counter == "True") then
+ local counterButtonWidth = fontSize * 0.75
+ _G['MarumEditableCounterIncrease_' .. fieldID .. '_' .. arrayID] = function(obj, ply, alt)
+ local fID = fieldID
+ local aID = arrayID
+ obj.call("increaseCounter", { fID, aID, ply })
+ end
+ buttonIndex = buttonIndex + 1
+ lookupButtonIndexToInfo[buttonIndex] = { type = "counter", id = fieldID }
+ table.insert(lookupFieldIndices[fieldID].counterButtons, { index = buttonIndex, x = x, y = y, side = 1 })
+ self.createButton({
+ click_function = 'MarumEditableCounterIncrease_' .. fieldID .. '_' .. arrayID,
+ tooltip = "↑ Increase ↑",
+ function_owner = self,
+ label = "[b]+[/b]",
+ position = { x = pos.x + (field.size.x + counterButtonWidth) / 1000 * fieldScale.x * posMulx * flipped, y = pos.y, z = pos.z },
+ rotation = unlockedRotation,
+ scale = fieldScale,
+ width = counterButtonWidth,
+ height = counterButtonWidth,
+ font_size = fontSize / 2,
+ font_color = color,
+ color = field.fieldColor,
+ })
+
+ _G['MarumEditableCounterDecrease_' .. fieldID .. '_' .. arrayID] = function(obj, ply, alt)
+ local fID = fieldID
+ local aID = arrayID
+ obj.call("decreaseCounter", { fID, aID, ply })
+ end
+ buttonIndex = buttonIndex + 1
+ table.insert(lookupFieldIndices[fieldID].counterButtons, { index = buttonIndex, x = x, y = y, side = -1 })
+ self.createButton({
+ click_function = 'MarumEditableCounterDecrease_' .. fieldID .. '_' .. arrayID,
+ tooltip = "↓ Decrease ↓",
+ function_owner = self,
+ label = "[b]-[/b]",
+ position = { x = pos.x - (field.size.x + counterButtonWidth) / 1000 * fieldScale.x * posMulx * flipped, y = pos.y, z = pos.z },
+ unlockedRotation = rotation,
+ scale = fieldScale,
+ width = counterButtonWidth,
+ height = counterButtonWidth,
+ font_size = fontSize / 2,
+ font_color = color,
+ color = field.fieldColor,
+ })
+ end
+ inputIndex = inputIndex + 1
+ lookupInputIndexToInfo[inputIndex] = { type = "field", id = fieldID, arrayID = arrayID }
+ table.insert(lookupFieldIndices[fieldID].inputs, { index = inputIndex, arrayID = arrayID, x = x, y = y })
+ if (field.vsum == "True") then
+ if (tonumber(field.value[arrayID])) then
+ vsum = vsum + tonumber(field.value[arrayID])
+ end
+ end
+
+ if (inputIndex % 10 == 0) then coroutine.yield(0) end
+ end
+
+ if (field.vsum == "True") then
+ local pos = getFieldPosition(fieldID, x, field.array.y + 1)
+ local func = 'MarumEditableRevertSum_' .. fieldID
+ _G[func] = function(obj, ply, input_value, selected)
+ obj.call("revertFieldSum", { fieldID, ply, iIndex, selected })
+ end
+ self.createInput({
+ value = "[u]" .. vsum .. "[/u]",
+ tooltip = "[Sum]",
+ input_function = func,
+ function_owner = self,
+ alignment = field.align,
+ position = pos,
+ width = field.size.x,
+ height = field.size.y,
+ rotation = { x = unlockedRotation.x, y = unlockedRotation.y, z = unlockedRotation.z + 180 },
+ font_size = fontSize,
+ scale = { x = -scale.x, y = 1, z = scale.y },
+ font_color = color,
+ color = field.fieldColor,
+ tab = 0
+ })
+ inputIndex = inputIndex + 1
+ table.insert(lookupFieldIndices[fieldID].totals, { index = inputIndex, x = x })
+ end
+ end
+ end
+
+ createDecals()
+ for decalID, decal in pairs(decals) do
+ lookupDecalIndices[decalID] = {
+ inputs = {},
+ selectionButtons = {}
+ }
+ local pos = getDecalPosition(decalID)
+ if (decal.locked ~= "True" and not sheetLocked and not locks.decals and not editingSheet) then
+ local func = 'MarumEditableSetURL_' .. decalID
+ _G[func] = function(obj, ply, alt)
+ obj.call("showImageURLPanel", { decalID, ply })
+ end
+ local tooltip = getDecalTooltip(decalID)
+ self.createButton({
+ value = decal.url,
+ tooltip = tooltip,
+ click_function = func,
+ function_owner = self,
+ position = pos,
+ width = 490,
+ height = 490,
+ rotation = { x = 0, y = 0, z = 0 },
+ font_size = 10,
+ scale = { x = decal.scale.x * scale.x, y = decal.scale.y * scale.y, z = decal.scale.y * scale.y },
+ font_color = { r = 0, g = 0, b = 0, a = 0 },
+ color = { r = 0, g = 0, b = 0, a = 0 },
+ })
+ buttonIndex = buttonIndex + 1
+ lookupButtonIndexToInfo[buttonIndex] = { type = "decal", id = decalID }
+ table.insert(lookupDecalIndices[decalID].inputs, { index = buttonIndex })
+ end
+
+ if (inputIndex % 10 == 0) then coroutine.yield(0) end
+ end
+
+ for checkID, check in pairs(checks) do
+ lookupCheckIndices[checkID] = {
+ buttons = {},
+ selectionButtons = {}
+ }
+ local rotationZ = 0
+ local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y }
+ local upright = self.getTransformUp().y > 0
+ local shouldFlip = (check.locked == "True" or sheetLocked or locks.checks) and upright
+ if (editingSheet or shouldFlip) then
+ checkScale.x = -checkScale.x
+ rotationZ = 180
+ end
+ for x = 1, check.array.x do
+ for y = 1, check.array.y do
+ local arrayID = x + (y - 1) * check.array.x
+ local func = "MarumEditableClickCheckbox_" .. checkID .. "_" .. arrayID
+ if (check.locked == "True" or sheetLocked or locks.checks) then
+ func = "null"
+ end
+ local rotationY = 0
+ local posMul = 1
+ if (flip == "True") then
+ rotationY = 180
+ posMul = -posMul
+ end
+ local pos = getCheckPosition(checkID, x, y)
+ local bindex = buttonIndex
+ _G['MarumEditableClickCheckbox_' .. checkID .. '_' .. arrayID] = function(obj, ply, alt)
+ local fID = checkID
+ local aID = arrayID
+ local bi = bindex
+ obj.call("clickcheck", { fID, aID, alt, bi, ply })
+ end
+
+ local tooltip = getCheckTooltip(checkID)
+ local label, color, alphaCorrectedColor = getCheckLabelAndColor(checkID, arrayID)
+ buttonIndex = buttonIndex + 1
+ lookupButtonIndexToInfo[buttonIndex] = { type = "check", id = checkID, arrayID = arrayID }
+ table.insert(lookupCheckIndices[checkID].buttons, { index = buttonIndex, x = x, y = y, arrayID = arrayID })
+ self.createButton({
+ label = label,
+ tooltip = tooltip,
+ click_function = func,
+ function_owner = self,
+ alignment = 3,
+ position = pos,
+ width = 250,
+ height = 250,
+ rotation = { x = 0, y = rotationY, z = rotationZ },
+ font_size = check.font,
+ scale = { x = checkScale.x, y = checkScale.y, z = checkScale.z },
+ font_color = alphaCorrectedColor,
+ color = check.checkColor,
+ tab = 0
+ })
+
+ if (buttonIndex % 10 == 0) then coroutine.yield(0) end
+ end
+ end
+ end
+ if (not getCommited() and editingSheet) then
+ createSelectionButtons()
+ end
+ creating = false
+ return 1
+end
+
+function revertField(args)
+ local fieldID = args[1]
+ local ply = args[2]
+ local selected = args[4]
+ if (not selected) then
+ Wait.frames(
+ function()
+ for k, v in pairs(lookupFieldIndices[fieldID].inputs) do
+ self.editInput({ index = v.index - 1, value = fields[fieldID].value[v.arrayID] })
+ end
+ end,
+ 1
+ )
+ else
+ if (lastFieldLockedMessage ~= fieldID) then
+ broadcastToColor("This text is locked", ply, { r = 1, g = 0.5, b = 0 })
+ lastFieldLockedMessage = fieldID
+ end
+ end
+end
+
+function recalculateVSums(fieldID)
+ local field = fields[fieldID]
+ for k, v in pairs(lookupFieldIndices[fieldID].totals) do
+ local vsum = 0
+ for y = 1, field.array.y do
+ local arrayID = v.x + (y - 1) * field.array.x
+ if (tonumber(field.value[arrayID])) then
+ vsum = vsum + tonumber(field.value[arrayID])
+ end
+ end
+ self.editInput({ index = v.index - 1, value = "[u]" .. vsum .. "[/u]" })
+ end
+end
+
+function revertFieldSum(args)
+ local fieldID = args[1]
+ local ply = args[2]
+ local selected = args[4]
+ if (not selected) then
+ Wait.frames(
+ function()
+ recalculateVSums(fieldID)
+ end,
+ 1
+ )
+ else
+ if (lastFieldLockedMessage ~= fieldID) then
+ broadcastToColor("This text is reserved for the total sum", ply, { r = 1, g = 1, b = 0 })
+ lastFieldLockedMessage = fieldID
+ end
+ end
+end
+
+function createDecals()
+ local decalParameters = {}
+ for decalID, decal in pairs(decals) do
+ local rotationAdd = 180
+ if (flip == "True") then
+ rotationAdd = 0
+ end
+ local pos = getDecalPosition(decalID)
+ table.insert(decalParameters, {
+ url = decal.url,
+ name = "Image #" .. decalID,
+ position = { x = -pos.x, y = pos.y, z = pos.z },
+ rotation = { x = 90, y = rotationAdd + decal.rotation, z = 0 },
+ scale = { x = decal.scale.x * scale.x, y = decal.scale.y * scale.y, z = 1 }
+ })
+ end
+ self.setDecals(decalParameters)
+end
+
+function getFieldPosition(fieldID, x, y)
+ local field = fields[fieldID]
+ local mul = 1
+ if (flip == "True") then mul = -1 end
+ return {
+ x = (field.pos.x + (x - 1) * field.distance.x) * scale.x * mul,
+ y = height + 0.002,
+ z = (field.pos.y + (y - 1) * field.distance.y) * scale.y * mul
+ }
+end
+
+function getFieldTooltip(fieldID, arrayID)
+ local field = fields[fieldID]
+ local tooltip = ""
+ if (not field.locked) then
+ if (field.tooltip == nil or field.tooltip:find("name")) then
+ tooltip = field.name or ""
+ elseif (field.tooltip:find("content")) then
+ tooltip = field.value[arrayID]
+ end
+ end
+ return tooltip
+end
+
+function getCheckPosition(checkID, x, y)
+ local check = checks[checkID]
+ local mul = 1
+ if (flip == "True") then mul = -1 end
+ return {
+ x = (check.pos.x + (x - 1) * check.distance.x) * mul * scale.x,
+ y = height + 0.002,
+ z = (check.pos.y + (y - 1) * check.distance.y) *
+ mul * scale.y
+ }
+end
+
+function getDecalPosition(decalID)
+ local decal = decals[decalID]
+ local mul = 1
+ if (flip == "True") then mul = -1 end
+ return { x = decal.pos.x * mul * scale.x, y = height + 0.005, z = decal.pos.y * mul * scale.y }
+end
+
+function getDecalTooltip(decalID)
+ local decal = decals[decalID]
+ local tooltip = ""
+ if (decal.tooltip == nil) then
+ tooltip = decal.name or ""
+ elseif (decal.tooltip:find("name")) then
+ tooltip = decal.name or ""
+ elseif (decal.tooltip:find("hint")) then
+ tooltip = "Click to change image"
+ end
+ return tooltip
+end
+
+function increaseCounter(args)
+ local fieldID = tonumber(args[1])
+ local arrayID = tonumber(args[2])
+ local ply = args[3]
+ local field = fields[fieldID]
+ if (field.value[arrayID] == nil or field.value[arrayID] == "") then
+ field.value[arrayID] = 0
+ end
+ if (tonumber(field.value[arrayID])) then
+ field.value[arrayID] = tonumber(field.value[arrayID]) + 1
+ updateFieldNameContentAndTooltip(fieldID)
+ if (field.vsum == "True") then
+ recalculateVSums(fieldID)
+ end
+ updateSave()
+ else
+ broadcastToColor("Field does not contain a valid number", ply)
+ end
+end
+
+function decreaseCounter(args)
+ local fieldID = tonumber(args[1])
+ local arrayID = tonumber(args[2])
+ local ply = args[3]
+ local field = fields[fieldID]
+ if (field.value[arrayID] == nil or field.value[arrayID] == "") then
+ field.value[arrayID] = 0
+ end
+ if (tonumber(field.value[arrayID])) then
+ field.value[arrayID] = tonumber(field.value[arrayID]) - 1
+ updateFieldNameContentAndTooltip(fieldID)
+ if (field.vsum == "True") then
+ recalculateVSums(fieldID)
+ end
+ updateSave()
+ else
+ broadcastToColor("Field does not contain a valid number", ply)
+ end
+end
+
+function edit(obj, ply, value, selected)
+ for k, v in pairs(obj.getInputs()) do
+ if (lookupInputIndexToInfo[k] ~= nil) then
+ if (lookupInputIndexToInfo[k].type == "field") then
+ local field = fields[lookupInputIndexToInfo[k].id]
+ if (v.value ~= field.value[lookupInputIndexToInfo[k].arrayID]) then
+ field.value[lookupInputIndexToInfo[k].arrayID] = v.value
+ if (field.tooltip ~= nil) then
+ if (field.tooltip:find("content")) then
+ self.editInput({ index = k - 1, tooltip = v.value })
+ end
+ end
+ if (field.role ~= nil) then
+ if (field.role:find("name")) then
+ self.setName(value)
+ elseif (field.role:find("description")) then
+ self.setDescription(value)
+ end
+ end
+ updateSave()
+ break
+ end
+ end
+ end
+ end
+end
+
+function editUrl(obj, ply, value, selected)
+ for k, v in pairs(obj.getInputs()) do
+ if (lookupInputIndexToInfo[k] ~= nil) then
+ if (lookupInputIndexToInfo[k].type == "decal") then
+ if (v.value ~= decals[lookupInputIndexToInfo[k].id].url) then
+ decals[lookupInputIndexToInfo[k].id].url = v.value
+ updateSave()
+ createDecals()
+ break
+ end
+ end
+ end
+ end
+end
+
+function clickcheck(args)
+ local checkID = tonumber(args[1])
+ local arrayID = tonumber(args[2])
+ local alt_click = args[3]
+ local buttonIndex = args[4]
+ local ply = args[5]
+ local check = checks[checkID]
+ local value = tonumber(check.value[arrayID])
+ if (value == nil) then value = 1 end
+ if (alt_click) then
+ if (value > 0) then
+ check.value[arrayID] = 0
+ else
+ check.value[arrayID] = 1
+ end
+ else
+ if (value == 1) then
+ check.value[arrayID] = 2
+ else
+ if (value == 2) then
+ check.value[arrayID] = 1
+ elseif (check.fillFromDisabled == "True") then
+ check.value[arrayID] = 2
+ else
+ broadcastToColor("This checkbox is disabled. You can enable/disable checkboxes with Right click.", ply)
+ end
+ end
+ end
+
+ local label, color, alphaCorrectedColor = getCheckLabelAndColor(checkID, arrayID)
+ self.editButton({ index = buttonIndex, label = label, font_color = alphaCorrectedColor })
+ updateSave()
+end
+
+function getFieldTextColor(fieldID)
+ local field = fields[fieldID]
+ local textAlpha = field.textColor.a
+ if (tonumber(field.fieldColor.a) > 0) then
+ textAlpha = tonumber(field.textColor.a) / tonumber(field.fieldColor.a)
+ else
+ textAlpha = tonumber(field.textColor.a) * 100
+ end
+ return { r = field.textColor.r, g = field.textColor.g, b = field.textColor.b, a = textAlpha }
+end
+
+function getCheckTooltip(checkID)
+ local check = checks[checkID]
+ local tooltip = ""
+ if (not check.locked) then
+ if (check.tooltip == nil or check.tooltip:find("name")) then
+ tooltip = check.name
+ elseif (check.tooltip:find("hint")) then
+ tooltip = "Left click to toggle, Right click to enable/disable"
+ end
+ end
+ return tooltip
+end
+
+function getCheckLabelAndColor(checkID, arrayID)
+ local check = checks[checkID]
+ local label = check.characters.empty
+ if (check.value[arrayID] == 0) then
+ label = check.characters.disabled
+ elseif (check.value[arrayID] == 2) then
+ label = check.characters.filled
+ end
+
+ local color = nil
+ if (check.separateColors == "True") then
+ if (check.value[arrayID] == 0) then
+ color = check.textColorDisabled or check.textColor
+ elseif (check.value[arrayID] == 1) then
+ color = check.textColorOff or check.textColor
+ elseif (check.value[arrayID] == 2) then
+ color = check.textColorOn or check.textColor
+ else
+ color = check.textColorOff or check.textColor
+ end
+ else
+ color = check.textColorOn or check.textColor
+ end
+
+ local alpha = 1
+ local checkAlpha = math.max(1 / 255, check.checkColor.a)
+ if (tonumber(check.checkColor.a) > 0) then
+ alpha = tonumber(color.a) / tonumber(checkAlpha)
+ else
+ alpha = tonumber(color.a) * 100
+ end
+ return label, color, { r = color.r, g = color.g, b = color.b, a = color.a * alpha }
+end
+
+function split(inputstr, sep)
+ if sep == nil then
+ sep = "%s"
+ end
+ local t = {}
+ for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
+ table.insert(t, str)
+ end
+ return t
+end
+
+function onRotate(spin, flip, player_color, old_spin, old_flip)
+ updateLockedFieldOrientation(flip < 90)
+end
+
+function onDrop(ply)
+ updateLockedFieldOrientation(self.getTransformUp().y >= 0)
+end
+
+function updateLockedFieldOrientation(upright)
+ for k, v in pairs(fields) do
+ if (v.locked == "True" or sheetLocked or locks.fields or v.vsum == "True") then
+ local field = v
+ local fieldScale = { x = scale.x, y = 1, z = scale.y }
+ local rotation = { x = 0, y = 0, z = 0 }
+ if (flip == "True") then
+ rotation.y = 180
+ end
+ if (upright) then
+ rotation.z = 180
+ fieldScale.x = -scale.x
+ end
+ if (v.locked == "True" or sheetLocked or locks.fields) then
+ for arrayID, inp in pairs(lookupFieldIndices[k].inputs) do
+ self.editInput({ index = inp.index - 1, rotation = rotation, scale = fieldScale })
+ end
+ end
+ for arrayID, inp in pairs(lookupFieldIndices[k].totals) do
+ self.editInput({ index = inp.index - 1, rotation = rotation, scale = fieldScale })
+ end
+ end
+ end
+ for k, v in pairs(checks) do
+ if (v.locked == "True" or sheetLocked or locks.fields) then
+ local check = v
+ local rotationZ = 0
+ local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y }
+ if (upright) then
+ checkScale.x = -checkScale.x
+ rotationZ = 180
+ end
+ local rotationY = 0
+ if (flip == "True") then
+ rotationY = 180
+ end
+ for arrayID, inp in pairs(lookupCheckIndices[k].buttons) do
+ self.editButton({
+ index = inp.index - 1,
+ rotation = { x = 0, y = rotationY, z = rotationZ },
+ scale = checkScale
+ })
+ end
+ end
+ end
+end
+
+function showImageURLPanel(args)
+ local decalID = args[1]
+ local ply = args[2]
+ Player[ply].showInputDialog("Set image URL",
+ function(text, player_color)
+ decals[decalID].url = text
+ createDecals()
+ end
+ )
+end
+
+function updateFieldNameContentAndTooltip(fieldID)
+ local field = fields[fieldID]
+ for k, v in pairs(lookupFieldIndices[fieldID].inputs) do
+ self.editInput({
+ index = v.index - 1,
+ value = field.value[v.arrayID],
+ tooltip = getFieldTooltip(fieldID,
+ v.arrayID)
+ })
+ end
+ local name = "T" .. fieldID
+ local tooltip = "Select " .. (field.name or name)
+ for k, v in pairs(lookupFieldIndices[fieldID].selectionButtons) do
+ self.editButton({ index = v.index - 1, tooltip = tooltip })
+ end
+end
+
+function getCommited()
+ return true
+end
+
+--$
+
+function getCommited()
+ return false
+end
+
+function get_line_count(str)
+ local lines = 1
+ for i = 1, #str do
+ local c = str:sub(i, i)
+ if c == 'n' then lines = lines + 1 end
+ end
+ return lines
+end
+
+function createSelectionButtons()
+ coroutine.yield(0)
+ local rotation = { x = 0, y = 0, z = 0 }
+ if (flip == "True") then
+ rotation.y = 180
+ end
+
+ for fieldID, field in pairs(fields) do
+ local posx = field.pos.x
+ local posy = field.pos.y
+ local fieldScale = { x = scale.x, y = 1, z = scale.y }
+ local posMul = 1
+ if (flip == "True") then
+ posMul = -1
+ end
+
+ local fontSize = field.font
+ fontSize = math.min(field.size.y - 23, fontSize)
+ for x = 1, field.array.x do
+ for y = 1, field.array.y do
+ local name = "T" .. fieldID
+ local arrayID = x + (y - 1) * field.array.x
+ local tooltip = "Select " .. (field.name or name)
+ local pos = {
+ x = (posx + (x - 1) * field.distance.x) * fieldScale.x * posMul,
+ y = height + 0.005,
+ z = (posy + (y - 1) * field.distance.y) * fieldScale.z * posMul
+ }
+ buttonIndex = buttonIndex + 1
+ table.insert(lookupFieldIndices[fieldID].selectionButtons, { index = buttonIndex, x = x, y = y })
+ self.createButton({
+ click_function = 'MarumEditableSheet_SelectField/' .. fieldID .. '_' .. arrayID,
+ tooltip = tooltip,
+ function_owner = self,
+ label = "",
+ position = pos,
+ rotation = rotation,
+ scale = { x = fieldScale.x * 0.25, y = 1, z = fieldScale.z * 0.25 },
+ width = field.size.x * 4 + 200,
+ height = field.size.y * 4 + 200,
+ font_size = field.font,
+ font_color = { r = 1, g = 1, b = 1, a = 1 },
+ color = { r = 0, g = 0.5, b = 1, a = 0.5 },
+ hover_color = { r = 0, g = 0.5, b = 1, a = 0.8 }
+ })
+ _G['MarumEditableSheet_SelectField/' .. fieldID .. '_' .. arrayID] = function(obj, ply, alt)
+ obj.call("selectField", { id = fieldID, arrayId = arrayID })
+ end
+ end
+ end
+ if (buttonIndex % 10 == 0) then coroutine.yield(0) end
+ end
+
+ for decalID, decal in pairs(decals) do
+ local name = "D" .. decalID
+ local tooltip = "Select " .. (decal.name or name)
+ local pos = getDecalPosition(decalID)
+ buttonIndex = buttonIndex + 1
+ table.insert(lookupDecalIndices[decalID].selectionButtons, { index = buttonIndex, x = x, y = y })
+ self.createButton({
+ click_function = 'MarumEditableSheet_SelectDecal_' .. decalID,
+ tooltip = tooltip,
+ function_owner = self,
+ label = "",
+ position = pos,
+ rotation = rotation,
+ scale = { x = decal.scale.x * scale.x * 0.25, y = decal.scale.y * scale.y * 0.25, z = decal.scale.y * scale.y * 0.25 },
+ width = 2000,
+ height = 2000,
+ font_size = 100,
+ font_color = { r = 1, g = 1, b = 1, a = 1 },
+ color = { r = 0, g = 0.5, b = 1, a = 0.5 },
+ hover_color = { r = 0, g = 0.5, b = 1, a = 0.8 }
+ })
+ _G['MarumEditableSheet_SelectDecal_' .. decalID] = function(obj, ply, alt)
+ obj.call("selectDecal", decalID)
+ end
+ if (buttonIndex % 10 == 0) then coroutine.yield(0) end
+ end
+
+ for checkID, check in pairs(checks) do
+ local posx = check.pos.x
+ local posy = check.pos.y
+ local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y }
+ local name = "C" .. checkID
+ local tooltip = "Select " .. (check.name or name)
+ for x = 1, check.array.x do
+ for y = 1, check.array.y do
+ local arrayID = x + (y - 1) * check.array.x
+ local func = "MarumEditableClickCheckbox_" .. checkID .. '_' .. arrayID
+ local rotationAdd = 0
+ local posMul = 1
+ if (flip == "True") then
+ rotationAdd = 180
+ posMul = -1
+ end
+ local pos = {
+ x = (posx + (x - 1) * check.distance.x) * posMul * scale.x,
+ y = height + 0.002,
+ z = (posy + (y - 1) * check.distance.y) * posMul * scale.y
+ }
+ buttonIndex = buttonIndex + 1
+ table.insert(lookupCheckIndices[checkID].selectionButtons, {
+ index = buttonIndex,
+ x = x,
+ y = y,
+ arrayID = arrayID
+ })
+ self.createButton({
+ label = "",
+ tooltip = tooltip,
+ click_function = func,
+ function_owner = self,
+ alignment = 3,
+ position = pos,
+ width = 1000,
+ height = 1000,
+ rotation = { x = 0, y = rotationAdd, z = 0 },
+ font_size = check.font / 3,
+ scale = { x = checkScale.x * 0.25, y = checkScale.y, z = checkScale.z * 0.25 },
+ font_color = { r = 1, g = 1, b = 1, a = 1 },
+ color = { r = 0, g = 0.5, b = 1, a = 0.5 },
+ hover_color = { r = 0, g = 0.5, b = 1, a = 0.8 },
+ tab = 0
+ })
+ _G['MarumEditableClickCheckbox_' .. checkID .. '_' .. arrayID] = function(obj, ply, alt)
+ obj.call("selectCheck", { id = checkID, arrayId = arrayID })
+ end
+ end
+ end
+ if (buttonIndex % 10 == 0) then coroutine.yield(0) end
+ end
+
+ UI.setAttribute(getPanelId("Loading"), "active", "False")
+end
+
+function createSelectionHighlight(edit)
+ local topLeft = { x = 0, y = 0, z = 0 }
+ local topRight = { x = 0, y = 0, z = 0 }
+ local bottomLeft = { x = 0, y = 0, z = 0 }
+ local bottomRight = { x = 0, y = 0, z = 0 }
+ local scale = { x = scale.x, y = 1, z = scale.y }
+ local rotation = { x = 0, y = 0, z = 0 }
+ if (flip == "True") then
+ rotation.y = 180
+ end
+
+ if (selectedType == "field") then
+ local field = fields[selectedId]
+ local posx = field.pos.x
+ local posy = field.pos.y
+ local posMul = 1
+ if (flip == "True") then
+ posMul = -1
+ posy = posy - 0.1
+ end
+ local x = (selectedArrayId - 1) % field.array.x
+ local y = math.floor((selectedArrayId - 1) / field.array.x)
+ local gridpos = { x = x * field.distance.x, y = y * field.distance.y }
+ bottomLeft = {
+ x = (posx - field.size.x / 1000 - 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy + field.size.y / 1000 + 0.1 + gridpos.y) * scale.z * posMul
+ }
+ bottomRight = {
+ x = (posx + field.size.x / 1000 + 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy + field.size.y / 1000 + 0.1 + gridpos.y) * scale.z * posMul
+ }
+ topLeft = {
+ x = (posx - field.size.x / 1000 - 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy - field.size.y / 1000 + gridpos.y) * scale.z * posMul
+ }
+ topRight = {
+ x = (posx + field.size.x / 1000 + 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy - field.size.y / 1000 + gridpos.y) * scale.z * posMul
+ }
+ elseif (selectedType == "decal") then
+ local decal = decals[selectedId]
+ local posx = decal.pos.x
+ local posy = decal.pos.y
+ local posMul = 1
+ if (flip == "True") then
+ posMul = -1
+ posy = posy - 0.1
+ end
+ bottomLeft = {
+ x = (posx - decal.scale.x / 2 - 0.1) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy + decal.scale.y / 2 + 0.1) * scale.z * posMul
+ }
+ bottomRight = {
+ x = (posx + decal.scale.x / 2 + 0.1) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy + decal.scale.y / 2 + 0.1) * scale.z * posMul
+ }
+ topLeft = {
+ x = (posx - decal.scale.x / 2 - 0.1) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy - decal.scale.y / 2) * scale.z * posMul
+ }
+ topRight = {
+ x = (posx + decal.scale.x / 2 + 0.1) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy - decal.scale.y / 2) * scale.z * posMul
+ }
+ elseif (selectedType == "check") then
+ local check = checks[selectedId]
+ local posx = check.pos.x
+ local posy = check.pos.y
+ local posMul = 1
+ if (flip == "True") then
+ posMul = -1
+ posy = posy - 0.1
+ end
+ local x = (selectedArrayId - 1) % check.array.x
+ local y = math.floor((selectedArrayId - 1) / check.array.x)
+ local gridpos = { x = x * check.distance.x, y = y * check.distance.y }
+ bottomLeft = {
+ x = (posx - check.size.x * 0.25 - 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy + check.size.y * 0.25 + 0.1 + gridpos.y) * scale.z * posMul
+ }
+ bottomRight = {
+ x = (posx + check.size.x * 0.25 + 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy + check.size.y * 0.25 + 0.1 + gridpos.y) * scale.z * posMul
+ }
+ topLeft = {
+ x = (posx - check.size.x * 0.25 - 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy - check.size.y * 0.25 + gridpos.y) * scale.z * posMul
+ }
+ topRight = {
+ x = (posx + check.size.x * 0.25 + 0.1 + gridpos.x) * scale.x * posMul,
+ y = height + 0.005,
+ z = (posy - check.size.y * 0.25 + gridpos.y) * scale.z * posMul
+ }
+ end
+ if (edit) then
+ self.editButton({ index = 0, scale = scale, position = bottomLeft })
+ self.editButton({ index = 1, scale = scale, position = topLeft })
+ self.editButton({ index = 2, scale = scale, position = bottomRight })
+ self.editButton({ index = 3, scale = scale, position = topRight })
+ else
+ local label = "┗"
+ if (flip == "True") then label = "┓" end
+ self.createButton({
+ click_function = 'null',
+ function_owner = self,
+ label = label,
+ position = bottomLeft,
+ rotation = rotation,
+ scale = scale,
+ width = 0,
+ height = 0,
+ font_size = 200,
+ font_color = { r = 0, g = 0.5, b = 1, a = 100 },
+ color = { r = 0, g = 0, b = 0, a = 0.01 },
+ })
+ label = "┏"
+ if (flip == "True") then label = "┛" end
+ self.createButton({
+ click_function = 'null',
+ function_owner = self,
+ label = label,
+ position = topLeft,
+ rotation = rotation,
+ scale = scale,
+ width = 0,
+ height = 0,
+ font_size = 200,
+ font_color = { r = 0, g = 0.5, b = 1, a = 100 },
+ color = { r = 0, g = 0, b = 0, a = 0.01 },
+ })
+ label = "┛"
+ if (flip == "True") then label = "┏" end
+ self.createButton({
+ click_function = 'null',
+ function_owner = self,
+ label = label,
+ position = bottomRight,
+ rotation = rotation,
+ scale = scale,
+ width = 0,
+ height = 0,
+ font_size = 200,
+ font_color = { r = 0, g = 0.5, b = 1, a = 100 },
+ color = { r = 0, g = 0, b = 0, a = 0.01 },
+ })
+ label = "┓"
+ if (flip == "True") then label = "┗" end
+ self.createButton({
+ click_function = 'null',
+ function_owner = self,
+ label = label,
+ position = topRight,
+ rotation = rotation,
+ scale = scale,
+ width = 0,
+ height = 0,
+ font_size = 200,
+ font_color = { r = 0, g = 0.5, b = 1, a = 100 },
+ color = { r = 0, g = 0, b = 0, a = 0.01 },
+ })
+ buttonIndex = buttonIndex + 4
+ end
+end
+
+function selectField(args)
+ selectedId = args.id
+ selectedArrayId = args.arrayId
+ selectedType = "field"
+ createSelectionHighlight(true)
+ UI.hide(attrId("EmptyPrompt"))
+ UI.show(attrId("SelectionPanel"))
+ refreshEditPanel()
+end
+
+function selectDecal(id)
+ selectedId = id
+ selectedType = "decal"
+ createSelectionHighlight(true)
+ UI.hide(attrId("EmptyPrompt"))
+ UI.show(attrId("SelectionPanel"))
+ refreshEditPanel()
+end
+
+function selectCheck(args)
+ selectedId = args.id
+ selectedArrayId = args.arrayId
+ selectedType = "check"
+ createSelectionHighlight(true)
+ UI.hide(attrId("EmptyPrompt"))
+ UI.show(attrId("SelectionPanel"))
+ refreshEditPanel()
+end
+
+function refreshEditPanel()
+ local page = selectedId
+ local subpages = 0
+ if (selectedType == "field") then
+ local field = fields[selectedId]
+ local name = ""
+ if (field.name ~= "" and field.name ~= nil) then
+ name = " - " .. field.name
+ end
+ local value = field.value[selectedArrayId]
+ subpages = field.array.x * field.array.y
+ local sub = ""
+ if (subpages > 1) then
+ sub = ";" .. selectedArrayId
+ end
+ UI.setAttribute(attrId("Field/ID"), "text", "Text #" .. tostring(selectedId) .. sub .. name .. "")
+ UI.setAttribute(attrId("Field/name"), "text", field.name)
+ UI.setAttribute(attrId("Field/content"), "text", value or "")
+ UI.setAttribute(attrId("Field/font"), "text", field.font)
+ UI.setAttribute(attrId("Field/pos/x"), "text", field.pos.x)
+ UI.setAttribute(attrId("Field/pos/y"), "text", field.pos.y)
+ UI.setAttribute(attrId("Field/size/x"), "text", field.size.x)
+ UI.setAttribute(attrId("Field/size/y"), "text", field.size.y)
+ UI.setAttribute(attrId("Field/array/x"), "text", field.array.x)
+ UI.setAttribute(attrId("Field/array/y"), "text", field.array.y)
+ UI.setAttribute(attrId("Field/distance/x"), "text", field.distance.x)
+ UI.setAttribute(attrId("Field/distance/y"), "text", field.distance.y)
+
+ local textColor = "rgba(" .. field.textColor.r .. "," .. field.textColor.g .. "," .. field.textColor.b .. ",1)"
+ local textColor2 = "rgba(" ..
+ (field.textColor.r * 0.5 + 0.2) .. "," .. (field.textColor.g * 0.5 + 0.2) ..
+ "," .. (field.textColor.b * 0.5 + 0.2) .. ",1)"
+ UI.setAttribute(attrId("Field/textColor"), "colors", textColor ..
+ "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor)
+ UI.setAttribute(attrId("Field/textColor/a"), "percentage", field.textColor.a * 100)
+
+ local fieldColor = "rgba(" .. field.fieldColor.r .. "," .. field.fieldColor.g .. "," .. field.fieldColor.b ..
+ ",1)"
+ local fieldColor2 = "rgba(" ..
+ (field.fieldColor.r * 0.5 + 0.2) .. "," .. (field.fieldColor.g * 0.5 + 0.2) ..
+ "," .. (field.fieldColor.b * 0.5 + 0.2) .. ",1)"
+ UI.setAttribute(attrId("Field/fieldColor"), "colors",
+ fieldColor .. "|" .. fieldColor2 .. "|" .. fieldColor2 .. "|" .. fieldColor)
+ UI.setAttribute(attrId("Field/fieldColor/a"), "percentage", field.fieldColor.a * 100)
+
+ UI.setAttribute(attrId("Field/counter"), "isOn", field.counter == "True")
+ UI.setAttribute(attrId("Field/vsum"), "isOn", field.vsum == "True")
+ UI.setAttribute(attrId("Field/locked"), "isOn", field.locked == "True")
+
+ local role = 0
+ if (field.role ~= nil) then
+ if (field.role:find("name")) then
+ role = 1
+ elseif (field.role:find("desc")) then
+ role = 2
+ end
+ end
+ UI.setAttribute(attrId("Field/role"), "value", role)
+
+ local tooltip = 0
+ if (field.tooltip ~= nil) then
+ if (field.tooltip:find("name")) then
+ tooltip = 1
+ elseif (field.tooltip:find("content")) then
+ tooltip = 2
+ end
+ end
+ UI.setAttribute(attrId("Field/tooltip"), "value", tooltip)
+
+ local align = 0
+ if (field.align == 3) then
+ align = 1
+ elseif (field.align == 4) then
+ align = 2
+ end
+ UI.setAttribute(attrId("Field/align"), "value", align)
+
+ UI.hide(attrId("CheckPanel"))
+ UI.hide(attrId("DecalPanel"))
+ UI.show(attrId("FieldPanel"))
+ elseif (selectedType == "check") then
+ local check = checks[selectedId]
+ local name = ""
+ if (check.name ~= "" and check.name ~= "") then
+ name = " - " .. check.name
+ end
+ subpages = check.array.x * check.array.y
+ local sub = ""
+ if (subpages > 1) then
+ sub = ";" .. selectedArrayId
+ end
+ UI.setAttribute(attrId("Check/ID"), "text", "Checkbox #" .. tostring(selectedId) .. sub .. name .. "")
+ UI.setAttribute(attrId("Check/name"), "text", check.name)
+ UI.setAttribute(attrId("Check/font"), "text", check.font)
+ UI.setAttribute(attrId("Check/pos/x"), "text", check.pos.x)
+ UI.setAttribute(attrId("Check/pos/y"), "text", check.pos.y)
+ UI.setAttribute(attrId("Check/size/x"), "text", check.size.x)
+ UI.setAttribute(attrId("Check/size/y"), "text", check.size.y)
+ UI.setAttribute(attrId("Check/array/x"), "text", check.array.x)
+ UI.setAttribute(attrId("Check/array/y"), "text", check.array.y)
+ UI.setAttribute(attrId("Check/distance/x"), "text", check.distance.x)
+ UI.setAttribute(attrId("Check/distance/y"), "text", check.distance.y)
+
+ local tc = check.textColorOn or check.textColor
+ local textColor = "rgba(" .. tc.r .. "," .. tc.g .. "," .. tc.b .. ",1)"
+ local textColor2 = "rgba(" .. (tc.r * 0.5 + 0.2) .. "," .. (tc.g * 0.5 + 0.2) .. "," .. (tc.b * 0.5 + 0.2) ..
+ ",1)"
+ UI.setAttribute(attrId("Check/textColorOn"), "colors",
+ textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor)
+ UI.setAttribute(attrId("Check/textColorOn/a"), "percentage", tc.a * 100)
+
+ local tc = check.textColorOff or check.textColor
+ local textColor = "rgba(" .. tc.r .. "," .. tc.g .. "," .. tc.b .. ",1)"
+ local textColor2 = "rgba(" .. (tc.r * 0.5 + 0.2) .. "," .. (tc.g * 0.5 + 0.2) .. "," .. (tc.b * 0.5 + 0.2) ..
+ ",1)"
+ UI.setAttribute(attrId("Check/textColorOff"), "colors",
+ textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor)
+ UI.setAttribute(attrId("Check/textColorOff/a"), "percentage", tc.a * 100)
+
+ local tc = check.textColorDisabled or check.textColor
+ local textColor = "rgba(" .. tc.r .. "," .. tc.g .. "," .. tc.b .. ",1)"
+ local textColor2 = "rgba(" .. (tc.r * 0.5 + 0.2) .. "," .. (tc.g * 0.5 + 0.2) .. "," .. (tc.b * 0.5 + 0.2) ..
+ ",1)"
+ UI.setAttribute(attrId("Check/textColorDisabled"), "colors",
+ textColor .. "|" .. textColor2 .. "|" .. textColor2 .. "|" .. textColor)
+ UI.setAttribute(attrId("Check/textColorDisabled/a"), "percentage", tc.a * 100)
+
+ local checkColor = "rgba(" .. check.checkColor.r .. "," .. check.checkColor.g .. "," .. check.checkColor.b ..
+ ",1)"
+ local checkColor2 = "rgba(" ..
+ (check.checkColor.r * 0.5 + 0.2) .. "," .. (check.checkColor.g * 0.5 + 0.2) ..
+ "," .. (check.checkColor.b * 0.5 + 0.2) .. ",1)"
+ UI.setAttribute(attrId("Check/checkColor"), "colors",
+ checkColor .. "|" .. checkColor2 .. "|" .. checkColor2 .. "|" .. checkColor)
+ UI.setAttribute(attrId("Check/checkColor/a"), "percentage", check.checkColor.a * 100)
+
+ UI.setAttribute(attrId("Check/characters/empty"), "text", check.characters.empty)
+ UI.setAttribute(attrId("Check/characters/filled"), "text", check.characters.filled)
+ UI.setAttribute(attrId("Check/characters/disabled"), "text", check.characters.disabled)
+
+ if (check.separateColors == "True") then
+ UI.setAttribute(attrId("CheckOnColorLabel"), "text", "On color")
+ UI.setAttribute(attrId("CheckOffColorRow"), "active", "True")
+ UI.setAttribute(attrId("CheckDisabledColorRow"), "active", "True")
+ UI.setAttribute(attrId("CheckSeparateColorsSpacer1"), "active", "False")
+ UI.setAttribute(attrId("CheckSeparateColorsSpacer2"), "active", "False")
+ else
+ UI.setAttribute(attrId("CheckOnColorLabel"), "text", "Color")
+ UI.setAttribute(attrId("CheckOffColorRow"), "active", "False")
+ UI.setAttribute(attrId("CheckDisabledColorRow"), "active", "False")
+ UI.setAttribute(attrId("CheckSeparateColorsSpacer1"), "active", "True")
+ UI.setAttribute(attrId("CheckSeparateColorsSpacer2"), "active", "True")
+ end
+ UI.setAttribute(attrId("Check/separateColors"), "isOn", check.separateColors == "True")
+ UI.setAttribute(attrId("Check/fillFromDisabled"), "isOn", check.fillFromDisabled == "True")
+
+ local option = 0
+ if (check.value[selectedArrayId] == 0) then option = 2 end
+ if (check.value[selectedArrayId] == 1) then option = 0 end
+ if (check.value[selectedArrayId] == 2) then option = 1 end
+ UI.setAttribute(attrId("Check/value"), "value", option)
+
+ local tooltip = 0
+ if (check.tooltip ~= nil) then
+ if (check.tooltip:find("name")) then
+ tooltip = 1
+ elseif (check.tooltip:find("hint")) then
+ tooltip = 2
+ end
+ end
+ UI.setAttribute(attrId("Check/tooltip"), "value", tooltip)
+ UI.setAttribute(attrId("Check/locked"), "isOn", check.locked == "True")
+
+ UI.hide(attrId("DecalPanel"))
+ UI.hide(attrId("FieldPanel"))
+ UI.show(attrId("CheckPanel"))
+
+ page = selectedId + #fields
+ elseif (selectedType == "decal") then
+ local decal = decals[selectedId]
+ local slot = 1
+ UI.setAttribute(attrId("Decal/ID"), "text", "Decal #" .. tostring(selectedId) .. "")
+ UI.setAttribute(attrId("Decal/name"), "text", decal.name)
+ UI.setAttribute(attrId("Decal/url"), "text", decal.url)
+ UI.setAttribute(attrId("Decal/pos/x"), "text", decal.pos.x)
+ UI.setAttribute(attrId("Decal/pos/y"), "text", decal.pos.y)
+ UI.setAttribute(attrId("Decal/scale/x"), "text", decal.scale.x)
+ UI.setAttribute(attrId("Decal/scale/y"), "text", decal.scale.y)
+ UI.setAttribute(attrId("Decal/rotation"), "text", decal.rotation)
+
+ UI.setAttribute(attrId("Decal/locked"), "isOn", decal.locked == "True")
+
+ local tooltip = 0
+ if (decal.tooltip ~= nil) then
+ if (decal.tooltip:find("name")) then
+ tooltip = 1
+ elseif (decal.tooltip:find("hint")) then
+ tooltip = 2
+ end
+ end
+ UI.setAttribute(attrId("Decal/tooltip"), "value", tooltip)
+
+ UI.hide(attrId("CheckPanel"))
+ UI.hide(attrId("FieldPanel"))
+ UI.show(attrId("DecalPanel"))
+ page = selectedId + #fields + #checks
+ else
+ UI.show(attrId("EmptyPrompt"))
+ UI.hide(attrId("SelectionPanel"))
+ end
+
+ -- Pages
+ local pageCount = getPageCount()
+ UI.setAttribute(attrId("LastPage"), "interactable", tostring(page < pageCount))
+ UI.setAttribute(attrId("NextPage"), "interactable", tostring(page < pageCount))
+ UI.setAttribute(attrId("PreviousPage"), "interactable", tostring(page > 1))
+ UI.setAttribute(attrId("FirstPage"), "interactable", tostring(page > 1))
+ if (subpages > 1) then
+ UI.setAttribute(attrId("Pages"), "text", page .. " (" .. selectedArrayId .. "/" .. subpages ..
+ ") / " .. pageCount)
+ else
+ UI.setAttribute(attrId("Pages"), "text", page .. " / " .. pageCount)
+ end
+end
+
+function commit(ply)
+ local codeSplit = split(self.script_code, "$")
+ local uncutLines = get_line_count(codeSplit[1])
+ local cutLines = get_line_count(codeSplit[2] .. codeSplit[3])
+ broadcastToColor("Layout commited. Code reduced from " .. (uncutLines + cutLines + 1) .. " lines to " .. uncutLines,
+ ply)
+ if (Global.getVar("MarumEditableSheetGUID", self.guid)) then
+ Global.setVar("MarumEditableSheetGUID", nil)
+ end
+ self.script_code = codeSplit[1]
+ self.reload()
+end
+
+function onToggleSheetLocked(obj, ply, value, selected)
+ sheetLocked = not sheetLocked
+ updateSave()
+end
+
+function closePanel(ply, value, id)
+ if (editingSheet) then
+ editingSheet = false
+ refresh()
+ end
+ UI.setAttribute(getPanelId("main"), "active", "False")
+end
+
+function forceClosePanel()
+ if (editingSheet) then
+ editingSheet = false
+ refresh()
+ end
+end
+
+function getPageCount()
+ return #fields + #checks + #decals
+end
+
+function getPanelId(panel)
+ return "MarumEditableSheet/" .. panel
+end
+
+function showEditPanel(ply, value, id)
+ createPanelIfNecessary(ply)
+ waitForUiLoaded(function()
+ UI.setAttribute(getPanelId("main"), "active", "True")
+ UI.setAttribute(getPanelId("main"), "visibility", ply)
+ if (selectedId == 0) then
+ UI.show(attrId("EmptyPrompt"))
+ UI.hide(attrId("SelectionPanel"))
+ else
+ UI.hide(attrId("EmptyPrompt"))
+ UI.show(attrId("SelectionPanel"))
+ end
+ editingSheet = true
+ local forcedSelectFirst = false
+ if (selectedId == 0) then
+ if (#fields > 0) then
+ selectedId = 1
+ selectedArrayId = 1
+ selectedType = "field"
+ forcedSelectFirst = true
+ elseif (#checks > 0) then
+ selectedId = 1
+ selectedArrayId = 1
+ selectedType = "check"
+ forcedSelectFirst = true
+ elseif (#decals > 0) then
+ selectedId = 1
+ selectedType = "decal"
+ forcedSelectFirst = true
+ end
+ end
+ refresh()
+ if (forcedSelectFirst) then
+ UI.hide(attrId("EmptyPrompt"))
+ UI.show(attrId("SelectionPanel"))
+ end
+ refreshEditPanel()
+ end)
+end
+
+function showCommitPanel(ply, value, id)
+ local previousGUID = Global.getVar("MarumEditableSheetGUID")
+ if (previousGUID) then
+ local obj = getObjectFromGUID(previousGUID)
+ if (obj ~= nil) then
+ getObjectFromGUID(previousGUID).call("closePanel")
+ end
+ end
+ Player[ply].showConfirmDialog(
+ "Do you want to lock the current layout and remove editing related code? You will still be able to change the contents of unlocked fields.",
+ commit)
+end
+
+function removeMESPanel()
+ local tbl = UI.getXmlTable()
+ local removing = false
+ local newTable = {}
+ for k, v in pairs(tbl) do
+ local isTag = false
+ if (v.attributes ~= nil) then
+ if (v.attributes.id == "MESStart") then
+ removing = true
+ isTag = true
+ elseif (v.attributes.id == "MESEnd") then
+ removing = false
+ isTag = true
+ end
+ end
+ if (not isTag and not removing) then
+ table.insert(newTable, v)
+ end
+ end
+ UI.setXmlTable({ newTable })
+end
+
+function createPanelIfNecessary(color)
+ local guid = self.getGUID()
+ local previousGUID = Global.getVar("MarumEditableSheetGUID")
+ if (guid ~= previousGUID) then
+ if (previousGUID ~= nil) then
+ local obj = getObjectFromGUID(previousGUID)
+ if (obj) then
+ getObjectFromGUID(previousGUID).call("forceClosePanel")
+ end
+ removeMESPanel()
+ end
+ waitForUiLoaded(createMainPanel)
+ end
+end
+
+function createMainPanel()
+ local guid = self.getGUID()
+ Global.setVar("MarumEditableSheetGUID", guid)
+ local xml = [[
+
+
+ Editable Sheet - by Marum
+
+
+
+
+
+
+
+ Document settings: |
+ Height |
+ ]] ..
+ height .. [[ |
+ Scale |
+ ]] ..
+ scale.x .. [[ |
+ ]] ..
+ scale.y .. [[ |
+ |
+ |
+ Lock everything |
+ Flip |
+
+
+
+
+
+
+
+
+
+
+
+
+ ]] .. selectedId ..
+ " / " .. getPageCount() .. [[
+
+
+
+
+ Add a new element using the buttons above. You can then select elements by clicking on them on the sheet, or with the navigation buttons above.
+
+
+ ]] .. getFieldPanel() .. [[
+ ]] .. getCheckPanel() .. [[
+ ]] .. getDecalPanel() .. [[
+
+
+
+
+ Loading...
+
+
+
+ ]]
+ UI.setXml(UI.getXml() .. xml)
+end
+
+function onScaleReset()
+ scale = { x = math.floor(100 / self.getScale().x) / 100, y = math.floor(100 / self.getScale().z) / 100 }
+ UI.setAttribute(attrId("ScaleX"), "text", scale.x)
+ UI.setAttribute(attrId("ScaleY"), "text", scale.y)
+ refreshAllPositionsAndSize()
+ refreshEditPanel()
+end
+
+function firstPage()
+ if (#fields > 0) then
+ selectField({ id = 1, arrayId = 1 })
+ elseif (#checks > 0) then
+ selectCheck({ id = 1, arrayId = 1 })
+ elseif (#decals > 0) then
+ selectDecal(1)
+ end
+end
+
+function previousPage()
+ if (selectedType == "decal") then
+ if (selectedId > 1) then
+ selectedId = selectedId - 1
+ selectDecal(selectedId)
+ else
+ if (#checks > 0) then
+ selectedType = "check"
+ selectedId = #checks
+ selectCheck({ id = selectedId, arrayId = checks[selectedId].array.x * checks[selectedId].array.y })
+ elseif (#fields > 0) then
+ selectedType = "field"
+ selectedId = #fields
+ selectField({ id = selectedId, arrayId = fields[selectedId].array.x * fields[selectedId].array.y })
+ end
+ end
+ elseif (selectedType == "check") then
+ if (selectedArrayId > 1) then
+ selectedArrayId = selectedArrayId - 1
+ selectCheck({ id = selectedId, arrayId = selectedArrayId })
+ elseif (selectedId > 1) then
+ selectedId = selectedId - 1
+ selectCheck({ id = selectedId, arrayId = checks[selectedId].array.x * checks[selectedId].array.y })
+ else
+ if (#fields > 0) then
+ selectedType = "field"
+ selectedId = #fields
+ selectField({ id = selectedId, arrayId = fields[selectedId].array.x * fields[selectedId].array.y })
+ end
+ end
+ elseif (selectedType == "field") then
+ if (selectedArrayId > 1) then
+ selectedArrayId = selectedArrayId - 1
+ selectField({ id = selectedId, arrayId = selectedArrayId })
+ elseif (selectedId > 1) then
+ selectedId = selectedId - 1
+ selectField({ id = selectedId, arrayId = fields[selectedId].array.x * fields[selectedId].array.y })
+ end
+ end
+end
+
+function nextPage()
+ if (selectedType == "field") then
+ if (fields[selectedId].array.x * fields[selectedId].array.y > selectedArrayId) then
+ selectedArrayId = selectedArrayId + 1
+ selectField({ id = selectedId, arrayId = selectedArrayId })
+ elseif (#fields > selectedId) then
+ selectedId = selectedId + 1
+ selectField({ id = selectedId, arrayId = 1 })
+ else
+ if (#checks > 0) then
+ selectedType = "check"
+ selectedId = 1
+ selectCheck({ id = selectedId, arrayId = 1 })
+ elseif (#decals > 0) then
+ selectedType = "decal"
+ selectedId = 1
+ selectDecal(1)
+ end
+ end
+ elseif (selectedType == "check") then
+ if (checks[selectedId].array.x * checks[selectedId].array.y > selectedArrayId) then
+ selectedArrayId = selectedArrayId + 1
+ selectCheck({ id = selectedId, arrayId = selectedArrayId })
+ elseif (#checks > selectedId) then
+ selectedId = selectedId + 1
+ selectCheck({ id = selectedId, arrayId = 1 })
+ else
+ if (#decals > 0) then
+ selectedType = "decal"
+ selectedId = 1
+ selectDecal(1)
+ end
+ end
+ elseif (selectedType == "decal") then
+ if (#decals > selectedId) then
+ selectedId = selectedId + 1
+ selectDecal(selectedId)
+ end
+ end
+end
+
+function lastPage()
+ if (#decals > 0) then
+ selectDecal(#decals)
+ elseif (#checks > 0) then
+ local last = checks[#checks]
+ selectCheck({ id = #checks, arrayId = last.array.x * last.array.y })
+ elseif (#fields > 0) then
+ local last = fields[#fields]
+ selectField({ id = #fields, arrayId = last.array.x * last.array.y })
+ end
+end
+
+function onNudgeChanged(ply, value, id)
+ nudgeDistance = value
+ UI.setAttribute(attrId("Field_nudge"), "text", nudgeDistance)
+ UI.setAttribute(attrId("Check_nudge"), "text", nudgeDistance)
+ UI.setAttribute(attrId("Decal_nudge"), "text", nudgeDistance)
+ updateSave()
+end
+
+function compareString(str, list)
+ for k, v in pairs(list) do
+ if (str == v) then return true end
+ end
+end
+
+function onValueEdited(ply, value, id)
+ local spl = split(id, "/")
+ local parameter = spl[2]
+ local tbl = nil
+ if (selectedType == "field") then tbl = fields end
+ if (selectedType == "check") then tbl = checks end
+ if (selectedType == "decal") then tbl = decals end
+ if (parameter == "content") then
+ tbl[selectedId].value[selectedArrayId] = value
+ else
+ if (#spl > 2) then
+ tbl[selectedId][parameter][spl[3]] = value
+ else
+ tbl[selectedId][parameter] = value
+ end
+ end
+ updateSave()
+
+ if (selectedType == "field") then
+ if (compareString(parameter, { "pos", "distance", "size" })) then
+ updateFieldPositionAndSize(selectedId)
+ elseif (compareString(parameter, { "name", "content", "tooltip" })) then
+ updateFieldNameContentAndTooltip(selectedId)
+ elseif (parameter == "font") then
+ updateFieldFontAndColor(selectedId)
+ else
+ refresh()
+ end
+ elseif (selectedType == "check") then
+ if (compareString(parameter, { "pos", "distance", "size" })) then
+ updateCheckPositionAndSize(selectedId)
+ elseif (parameter == "name") then
+ updateCheckNameContentAndTooltip(selectedId)
+ elseif (parameter == "font") then
+ updateCheckFontAndColor(selectedId)
+ else
+ refresh()
+ end
+ elseif (selectedType == "decal") then
+ if (compareString(parameter, { "pos", "scale", "rotation" })) then
+ updateDecalPositionAndSize(selectedId)
+ elseif (parameter == "url") then
+ createDecals()
+ else
+ refresh()
+ end
+ end
+end
+
+function updateFieldPositionAndSize(fieldID)
+ local field = fields[fieldID]
+ local lookup = lookupFieldIndices[fieldID]
+ local fieldScale = { x = -scale.x, y = 1, z = scale.y }
+ local flipped = 1
+ local rotation = { x = 0, y = 0, z = 180 }
+ if (flip == "True") then
+ rotation.y = 180
+ flipped = -1
+ end
+ local fontSize = math.min(field.size.y - 24, field.font)
+ for k, v in pairs(lookup.inputs) do
+ local pos = getFieldPosition(fieldID, v.x, v.y)
+ self.editInput({
+ index = v.index - 1,
+ position = pos,
+ scale = fieldScale,
+ width = field.size.x,
+ height = field.size.y,
+ font_size = fontSize,
+ rotation = rotation
+ })
+ end
+ for k, v in pairs(lookup.totals) do
+ local pos = getFieldPosition(fieldID, v.x, field.array.y + 1)
+ self.editInput({
+ index = v.index - 1,
+ position = pos,
+ scale = fieldScale,
+ width = field.size.x,
+ height = field.size.y,
+ rotation = rotation,
+ font_size = fontSize
+ })
+ end
+ for k, v in pairs(lookup.counterButtons) do
+ local pos = getFieldPosition(fieldID, v.x, v.y)
+ local offset = (field.size.x + fontSize * 0.75) / 1000 * scale.x
+ self.editButton({
+ index = v.index - 1,
+ position = { x = pos.x + offset * v.side * flipped, y = pos.y, z = pos.z },
+ scale = fieldScale,
+ rotation = rotation
+ })
+ end
+ for k, v in pairs(lookup.selectionButtons) do
+ local pos = getFieldPosition(fieldID, v.x, v.y)
+ self.editButton({
+ index = v.index - 1,
+ position = pos,
+ scale = { x = scale.x * 0.25, y = 1, z = scale.y * 0.25 },
+ width = field.size.x * 4 + 200,
+ height = field.size.y * 4 + 200
+ })
+ end
+ createSelectionHighlight(true)
+end
+
+function updateFieldFontAndColor(fieldID)
+ local field = fields[fieldID]
+ local fontColor = getFieldTextColor(fieldID)
+ local fontSize = math.min(field.size.y - 24, field.font)
+ local lookup = lookupFieldIndices[fieldID]
+ for k, v in pairs(lookup.inputs) do
+ -- Do not question the ways of the tabletop, as if it requests for font_color to be assigned twice, then it shall be so
+ self.editInput({
+ index = v.index - 1,
+ font_color = fontColor
+ })
+ self.editInput({
+ index = v.index - 1,
+ font_size = fontSize,
+ color = field.fieldColor,
+ font_color = fontColor
+ })
+ self.editInput({
+ index = v.index - 1
+ })
+ end
+ for k, v in pairs(lookup.counterButtons) do
+ self.editButton({
+ index = v.index - 1,
+ color = field.fieldColor,
+ font_color = fontColor,
+ font_size = fontSize / 2
+ })
+ end
+ for k, v in pairs(lookup.totals) do
+ self.editInput({
+ index = v.index - 1,
+ font_size = fontSize,
+ color = field.fieldColor,
+ font_color = fontColor
+ })
+ end
+end
+
+function updateCheckNameContentAndTooltip(checkID)
+ local check = checks[checkID]
+ for k, v in pairs(lookupCheckIndices[checkID].buttons) do
+ local label = getCheckLabelAndColor(checkID, v.arrayID)
+ self.editButton({ index = v.index - 1, label = label, tooltip = getCheckTooltip(checkID) })
+ end
+ local name = "C" .. checkID
+ local tooltip = "Select " .. (check.name or name)
+ for k, v in pairs(lookupCheckIndices[checkID].selectionButtons) do
+ self.editButton({ index = v.index - 1, tooltip = tooltip })
+ end
+end
+
+function updateCheckPositionAndSize(checkID)
+ local check = checks[checkID]
+ local lookup = lookupCheckIndices[checkID]
+ local rotation = { x = 0, y = 0, z = 0 }
+ if (flip == "True") then
+ rotation.y = 180
+ end
+ for k, v in pairs(lookup.buttons) do
+ local pos = getCheckPosition(checkID, v.x, v.y)
+ local checkScale = { x = scale.x * check.size.x, y = 1, z = scale.y * check.size.y }
+ self.editButton({ index = v.index - 1, position = pos, scale = checkScale, rotation = rotation })
+ end
+ for k, v in pairs(lookup.selectionButtons) do
+ local pos = getCheckPosition(checkID, v.x, v.y)
+ local checkScale = { x = scale.x * check.size.x * 0.25, y = 1, z = scale.y * check.size.y * 0.25 }
+ self.editButton({ index = v.index - 1, position = pos, scale = checkScale })
+ end
+ createSelectionHighlight(true)
+end
+
+function updateCheckFontAndColor(checkID)
+ local check = checks[checkID]
+ for k, v in pairs(lookupCheckIndices[checkID].buttons) do
+ local label, color, alphaCorrectedColor = getCheckLabelAndColor(checkID, v.arrayID)
+ -- Do not question the ways of the tabletop, as if it requests for font_color to be assigned twice, then it shall be so
+ self.editButton({
+ index = v.index - 1,
+ font_size = check.font,
+ color = check.checkColor,
+ font_color = alphaCorrectedColor
+ })
+ end
+end
+
+function updateDecalPositionAndSize(decalID)
+ local decal = decals[decalID]
+ local lookup = lookupDecalIndices[decalID]
+ local decalScale = {
+ x = decal.scale.x * scale.x * 0.25,
+ y = decal.scale.y * scale.y * 0.25,
+ z = decal.scale.y *
+ scale.y * 0.25
+ }
+ for k, v in pairs(lookup.inputs) do
+ local pos = getDecalPosition(decalID, v.x, v.y)
+ self.editInput({ index = v.index - 1, position = pos, scale = decalScale, rotation = decal.rotation })
+ end
+ for k, v in pairs(lookup.selectionButtons) do
+ local pos = getDecalPosition(decalID, v.x, v.y)
+ self.editButton({ index = v.index - 1, position = pos, scale = decalScale })
+ end
+ createSelectionHighlight(true)
+ createDecals()
+end
+
+function onCheckValueEdited(ply, value, id)
+ local spl = split(id, "/")
+ local checkParameter = spl[2]
+ if (#spl > 2) then
+ checks[selectedId][checkParameter][spl[3]] = value
+ else
+ checks[selectedId][checkParameter] = value
+ end
+
+ if (compareString(checkParameter, { "pos", "size" })) then
+ updateCheckPositionAndSize(selectedId)
+ elseif (checkParameter == "characters") then
+ updateCheckNameContentAndTooltip(selectedId)
+ else
+ refresh()
+ end
+
+ updateSave()
+end
+
+function onDecalValueEdited(ply, value, id)
+ local spl = split(id, "/")
+ local decalID = tonumber(spl[1])
+ local decalParameter = spl[2]
+ if (#spl > 2) then
+ decals[decalID][decalParameter][spl[3]] = value
+ else
+ decals[decalID][decalParameter] = value
+ end
+
+ updateSave()
+ refresh()
+end
+
+function onToggleChanged(ply, value, id)
+ local spl = split(id, "/")
+ local tbl = nil
+ if (selectedType == "field") then tbl = fields end
+ if (selectedType == "check") then tbl = checks end
+ if (selectedType == "decal") then tbl = decals end
+ tbl[selectedId][spl[2]] = value
+ if (spl[2] == "separateColors") then
+ updateCheckFontAndColor(selectedId)
+ refreshEditPanel()
+ else
+ if (not compareString(spl[2], { "locked", "fillFromDisabled" })) then
+ refresh()
+ end
+ end
+ updateSave()
+end
+
+function onFieldDropdownSelected(ply, option, id)
+ local spl = split(id, "/")
+ local parameterID = spl[2]
+ local shouldRefresh = false
+ if (parameterID == "align") then
+ shouldRefresh = true
+ if (option == "Auto") then
+ option = 1
+ elseif (option == "Left") then
+ option = 2
+ elseif (option == "Center") then
+ option = 3
+ elseif (option == "Right") then
+ option = 4
+ elseif (option == "Justified") then
+ option = 5
+ end
+ end
+ fields[selectedId][parameterID] = option
+ updateFieldFontAndColor(selectedId)
+ updateSave()
+ if (shouldRefresh) then
+ -- I did my best to not refresh here, but it seems like editInput doesn't care about alignment
+ refresh()
+ end
+end
+
+function onCheckDropdownSelected(ply, option, id)
+ local spl = split(id, "/")
+ local parameterID = spl[2]
+ if (parameterID == "value") then
+ local value = 0
+ if (option == "Off") then value = 1 elseif (option == "On") then value = 2 end
+ local check = checks[selectedId]
+ check.value[selectedArrayId] = value
+ updateCheckNameContentAndTooltip(selectedId)
+ updateCheckFontAndColor(selectedId)
+ updateSave()
+ elseif (parameterID == "tooltip") then
+ checks[selectedId].tooltip = option
+ updateSave()
+ else
+ checks[selectedId][parameterID] = option
+ updateSave()
+ refresh()
+ end
+end
+
+function onDecalDropdownSelected(ply, option, id)
+ local spl = split(id, "/")
+ local parameterID = spl[2]
+ if (parameterID == "tooltip") then
+ decals[selectedId].tooltip = option
+ updateSave()
+ else
+ decals[selectedId][parameterID] = option
+ updateSave()
+ refresh()
+ end
+end
+
+function onColorButtonPressed(ply, value, id)
+ local spl = split(id, "/")
+ local parameterID = spl[2]
+ local tbl = nil
+ if (selectedType == "field") then tbl = fields end
+ if (selectedType == "check") then tbl = checks end
+ local startingColor = tbl[selectedId][parameterID]
+ if (startingColor == nil) then
+ if (parameterID:find("textColor")) then
+ startingColor = tbl[selectedId].textColor
+ end
+ end
+
+ ply.showColorDialog(startingColor,
+ function(color, player_color)
+ tbl[selectedId][parameterID] = color
+ if (selectedType == "field") then
+ updateFieldFontAndColor(selectedId)
+ elseif (selectedType == "check") then
+ updateCheckFontAndColor(selectedId)
+ end
+ local c = "rgba(" .. color.r .. "," .. color.g .. "," .. color.b .. ",1)"
+ local c2 = "rgba(" .. (color.r * 0.5 + 0.2) .. "," .. (color.g * 0.5 + 0.2) .. "," ..
+ (color.b * 0.5 + 0.2) .. ",1)"
+ UI.setAttribute(id, "colors", c .. "|" .. c2 .. "|" .. c2 .. "|" .. c)
+ UI.setAttribute(id .. "/a", "percentage", color.a * 100)
+ updateSave()
+ end
+ )
+end
+
+function refreshAllPositionsAndSize()
+ for k, v in pairs(fields) do
+ updateFieldPositionAndSize(k)
+ end
+ for k, v in pairs(checks) do
+ updateCheckPositionAndSize(k)
+ end
+ for k, v in pairs(decals) do
+ updateDecalPositionAndSize(k)
+ end
+end
+
+function onScaleChangedX(ply, value)
+ scale.x = value
+ updateSave()
+ refreshAllPositionsAndSize()
+end
+
+function onScaleChangedY(ply, value)
+ scale.y = value
+ updateSave()
+ refreshAllPositionsAndSize()
+end
+
+function onHeightChanged(ply, value)
+ height = value
+ updateSave()
+ refreshAllPositionsAndSize()
+end
+
+function onFlip(ply, value)
+ flip = value
+ updateSave()
+ refreshAllPositionsAndSize()
+ local label = "┗"
+ if (flip == "True") then label = "┓" end
+ self.editButton({ index = 0, label = label })
+ label = "┏"
+ if (flip == "True") then label = "┛" end
+ self.editButton({ index = 1, label = label })
+ label = "┛"
+ if (flip == "True") then label = "┏" end
+ self.editButton({ index = 2, label = label })
+ label = "┓"
+ if (flip == "True") then label = "┗" end
+ self.editButton({ index = 3, label = label })
+end
+
+function onCheckToggleChanged(ply, value, id)
+ local spl = split(id, "/")
+ local checkID = tonumber(spl[1])
+ checks[checkID][spl[2]] = value
+
+ updateSave()
+ refresh()
+end
+
+function onDecalToggleChanged(ply, value, id)
+ local spl = split(id, "/")
+ local decalID = tonumber(spl[1])
+ decals[decalID][spl[2]] = value
+
+ updateSave()
+ refresh()
+end
+
+function getDeselectButton(panelID)
+ if (selectedId > 0) then
+ return [[
]]
+ end
+ return ""
+end
+
+function deselect()
+ selectedId = 0
+ selectedType = ""
+ refresh()
+end
+
+function correctCheckboxesAndDecals()
+ for decalID, decal in pairs(decals) do
+ decal.pos.x = decal.pos.x / scale.x
+ decal.pos.y = decal.pos.y / scale.y
+ end
+ for checkID, check in pairs(checks) do
+ check.pos.x = check.pos.x / scale.x
+ check.pos.y = check.pos.y / scale.y
+ check.distance.x = check.distance.x / scale.x
+ check.distance.y = check.distance.y / scale.y
+ end
+ refresh()
+end
+
+function attrId(str)
+ return "MarumEditorSheetAttribute_" .. str
+end
+
+function getFieldPanel()
+ local guid = self.getGUID()
+ return [[
+
+
+
+
+
+ Text ?
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+ Text
+ |
+
+
+
+ Name
+ |
+
+
+ |
+
+
+
+
+ Content
+ |
+
+
+ |
+
+
+
+
+ Tooltip
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+ On edit
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+
+ Font and color
+ |
+
+
+
+ Font size
+ |
+
+
+ |
+
+
+
+
+ Align
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+ Text color
+ |
+
+
+
+
+
+ |
+
+
+
+
+ Background Color
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+ Position and Size
+ |
+
+
+
+
+
+
+
+ Nudge
+ |
+
+
+
+ ]] ..
+ nudgeDistance .. [[
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+ |
+ |
+ |
+
+
+ |
+ |
+ |
+
+
+
+ |
+
+
+
+ Pos
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Size
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ Array
+ |
+
+
+
+ Columns/Rows
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Spacing
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ Toggles
+ |
+
+
+
+ Counter
+ |
+
+ Total sum
+ |
+
+ Lock
+ |
+
+
+
+
+
+ ]]
+end
+
+function getCheckPanel()
+ local guid = self.getGUID()
+ return [[
+
+
+
+
+
+ Checkbox ?
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+ Checkbox
+ |
+
+
+
+ Name
+ |
+
+
+ |
+
+
+
+
+ State
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+ Tooltip
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ Off
+ |
+
+
+ |
+
+ On
+ |
+
+
+ |
+
+ Disabled
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ Font size
+ |
+
+
+ |
+
+
+
+ Separate colors
+ |
+
+
+
+ On color
+ |
+
+
+
+
+
+ |
+
+
+
+ Off color
+ |
+
+
+
+
+
+ |
+
+
+
+ Disabled color
+ |
+
+
+
+
+
+ |
+
+
+
+ Background Color
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Position and Scale
+ |
+
+
+
+
+
+
+
+ Nudge
+ |
+
+
+
+ ]] ..
+ nudgeDistance .. [[
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+ |
+ |
+ |
+
+
+ |
+ |
+ |
+
+
+
+ |
+
+
+
+ Pos
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Scale
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ Array
+ |
+
+
+
+ Columns/Rows
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Spacing
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ Toggles
+ |
+
+
+
+ Can fill even if disabled
+ |
+
+ Lock
+ |
+
+
+
+
+
+ ]]
+end
+
+function getDecalPanel()
+ local guid = self.getGUID()
+ return [[
+
+
+
+
+
+ Image ?
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+ Image
+ |
+
+
+
+
+ Name
+ |
+
+
+ |
+
+
+
+
+ URL
+ |
+
+
+ |
+
+
+
+
+ Tooltip
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Position and Scale
+ |
+
+
+
+
+
+
+
+ Nudge
+ |
+
+
+
+ ]] ..
+ nudgeDistance .. [[
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+ |
+ |
+
+
+ |
+ |
+ |
+ |
+
+
+ |
+ |
+ |
+
+
+
+ |
+
+
+
+ Pos
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Scale
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Rotation
+ |
+
+
+ |
+
+
+
+
+
+ Toggles
+ |
+
+
+
+ Lock
+ |
+
+
+
+
+
+
+ ]]
+end
+
+function onCheckPresetButton(ply, value, id)
+ local pres = { "◌ ○ ●", " ◇ ◆", " □ ■", "/ △ ▴", " ◎ ◉", "- x", " ◾ ✦", " x ♥" }
+ ply.showOptionsDialog("Select checkbox preset", pres, 1,
+ function(text, index, player_color)
+ local disabled = pres[index]:sub(1, 1)
+ local empty = pres[index]:sub(3, 3)
+ local filled = pres[index]:sub(5, 5)
+ checks[selectedId].characters.disabled = disabled
+ checks[selectedId].characters.empty = empty
+ checks[selectedId].characters.filled = filled
+ updateCheckNameContentAndTooltip(selectedId)
+ refreshEditPanel()
+ updateSave()
+ end)
+end
+
+function nudgeSet1() updateNudgeDistance(1) end
+
+function nudgeSet01() updateNudgeDistance(0.1) end
+
+function nudgeSet001() updateNudgeDistance(0.01) end
+
+function nudgeSet0001() updateNudgeDistance(0.001) end
+
+function updateNudgeDistance(value)
+ nudgeDistance = value
+ UI.setAttribute(attrId("Field_nudge"), "text", nudgeDistance)
+ UI.setAttribute(attrId("Check_nudge"), "text", nudgeDistance)
+ UI.setAttribute(attrId("Decal_nudge"), "text", nudgeDistance)
+ updateSave()
+end
+
+function nudgeLeft(obj, value, id)
+ local newX = fields[selectedId].pos.x - tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Field/pos/x"), "text", newX)
+ fields[selectedId].pos.x = newX
+ updateFieldPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeUp(obj, value, id)
+ local newY = fields[selectedId].pos.y - tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Field/pos/y"), "text", newY)
+ fields[selectedId].pos.y = newY
+ updateFieldPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeRight(obj, value, id)
+ local newX = fields[selectedId].pos.x + tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Field/pos/x"), "text", newX)
+ fields[selectedId].pos.x = newX
+ updateFieldPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeDown(obj, value, id)
+ local newY = fields[selectedId].pos.y + tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Field/pos/y"), "text", newY)
+ fields[selectedId].pos.y = newY
+ updateFieldPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeDecalLeft(obj, value, id)
+ local newX = decals[selectedId].pos.x - tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Decal/pos/x"), "text", newX)
+ decals[selectedId].pos.x = newX
+ updateDecalPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeDecalUp(obj, value, id)
+ local newY = decals[tonumber(selectedId)].pos.y - tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Decal/pos/y"), "text", newY)
+ decals[tonumber(selectedId)].pos.y = newY
+ updateDecalPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeDecalRight(obj, value, id)
+ local newX = decals[tonumber(selectedId)].pos.x + tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Decal/pos/x"), "text", newX)
+ decals[tonumber(selectedId)].pos.x = newX
+ updateDecalPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeDecalDown(obj, value, id)
+ local newY = decals[tonumber(selectedId)].pos.y + tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Decal/pos/y"), "text", newY)
+ decals[tonumber(selectedId)].pos.y = newY
+ updateDecalPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeCheckLeft(obj, value, id)
+ local newX = checks[tonumber(selectedId)].pos.x - tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Check/pos/x"), "text", newX)
+ checks[tonumber(selectedId)].pos.x = newX
+ updateCheckPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeCheckUp(obj, value, id)
+ local newY = checks[tonumber(selectedId)].pos.y - tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Check/pos/y"), "text", newY)
+ checks[tonumber(selectedId)].pos.y = newY
+ updateCheckPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeCheckRight(obj, value, id)
+ local newX = checks[tonumber(selectedId)].pos.x + tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Check/pos/x"), "text", newX)
+ checks[tonumber(selectedId)].pos.x = newX
+ updateCheckPositionAndSize(selectedId)
+ updateSave()
+end
+
+function nudgeCheckDown(obj, value, id)
+ local newY = checks[tonumber(selectedId)].pos.y + tonumber(nudgeDistance)
+ UI.setAttribute(attrId("Check/pos/y"), "text", newY)
+ checks[tonumber(selectedId)].pos.y = newY
+ updateCheckPositionAndSize(selectedId)
+ updateSave()
+end
+
+function showEmptyPrompt()
+ selectedId = 0
+ selectedType = ""
+ refreshEditPanel()
+end
+
+function duplicateField(ply, value, id)
+ broadcastToColor("Text duplicated", ply.color)
+ local newField = JSON.decode(JSON.encode(fields[tonumber(selectedId)]))
+ table.insert(fields, newField)
+ updateSave()
+ selectField({ id = #fields, arrayId = 1 })
+ refresh()
+end
+
+function deleteField(ply, value, id)
+ broadcastToColor("Text deleted", ply.color)
+ table.remove(fields, tonumber(selectedId))
+ updateSave()
+ if (#fields > 0) then
+ selectField({ id = math.max(selectedId - 1, 1), arrayId = 1 })
+ else
+ if (#checks > 0) then
+ selectCheck({ id = 1, arrayId = 1 })
+ elseif (#decals > 0) then
+ selectDecal(1)
+ else
+ showEmptyPrompt()
+ end
+ end
+ refresh()
+end
+
+function duplicateCheck(ply, value, id)
+ broadcastToColor("Check duplicated", ply.color)
+ local newCheck = JSON.decode(JSON.encode(checks[tonumber(selectedId)]))
+ table.insert(checks, newCheck)
+ updateSave()
+ selectCheck({ id = #checks, arrayId = 1 })
+ refresh()
+end
+
+function deleteCheck(ply, value, id)
+ broadcastToColor("Check deleted", ply.color)
+ table.remove(checks, tonumber(selectedId))
+ updateSave()
+ if (#checks > 0) then
+ selectCheck({ id = math.max(selectedId - 1, 1), arrayId = 1 })
+ else
+ if (#fields > 0) then
+ selectField({ id = 1, arrayId = 1 })
+ elseif (#decals > 0) then
+ selectDecal(1)
+ else
+ showEmptyPrompt()
+ end
+ end
+ refresh()
+end
+
+function duplicateDecal(ply, value, id)
+ broadcastToColor("Image duplicated", ply.color)
+ local newDecal = JSON.decode(JSON.encode(decals[tonumber(selectedId)]))
+ table.insert(decals, newDecal)
+ updateSave()
+ selectDecal(#decals)
+ refresh()
+end
+
+function deleteDecal(ply, value, id)
+ broadcastToColor("Image deleted", ply.color)
+ table.remove(decals, tonumber(selectedId))
+ updateSave()
+ if (#decals > 0) then
+ selectDecal(math.max(selectedId - 1, 1))
+ else
+ if (#fields > 0) then
+ selectField({ id = 1, arrayId = 1 })
+ elseif (#checks > 0) then
+ selectCheck({ id = 1, arrayId = 1 })
+ else
+ showEmptyPrompt()
+ end
+ end
+ refresh()
+end
+
+function addField(obj, player_clicker_color, alt_click)
+ local newField = {
+ value = { "?" },
+ name = "",
+ tooltip = "name",
+ role = "Normal Field",
+ textColor = { r = 0, g = 0, b = 0, a = 1 },
+ fieldColor = { r = 1, g = 1, b = 1, a = 1 },
+ font = 150,
+ align = 3,
+ pos = { x = 0, y = 0 },
+ size = { x = 250, y = 250 },
+ array = { x = 1, y = 1 },
+ distance = { x = 1, y = 1 },
+ locked = false
+ }
+ table.insert(fields, newField)
+ selectField({ id = #fields, arrayId = 1 })
+ updateSave()
+ refresh()
+end
+
+function addCheck(obj, player_clicker_color, alt_click)
+ local newCheck = {
+ name = "",
+ tooltip = "hint",
+ value = { 1 },
+ characters = { disabled = "◌", empty = "○", filled = "●" },
+ textColor = { r = 0, g = 0, b = 0, a = 1 },
+ textColorOff = { r = 0, g = 0, b = 0, a = 1 },
+ textColorDisabled = { r = 0.5, g = 0.5, b = 0.5, a = 1 },
+ separateColors = false,
+ fillFromDisabled = false,
+ checkColor = { r = 1, g = 1, b = 1, a = 1 },
+ pos = { x = 0, y = 0 },
+ size = { x = 1, y = 1 },
+ array = { x = 1, y = 1 },
+ distance = { x = 1, y = 1 },
+ font = 500,
+ locked = false
+ }
+ table.insert(checks, newCheck)
+ selectCheck({ id = #checks, arrayId = 1 })
+ updateSave()
+ refresh()
+end
+
+function addDecal(obj, player_clicker_color, alt_click)
+ local newDecal = {
+ name = "",
+ url = "https://api.tabletopsimulator.com/img/TSIcon.png",
+ tooltip = "name",
+ pos = { x = 0, y = 0 },
+ rotation = 0,
+ scale = { x = 1, y = 1 },
+ locked = false
+ }
+ table.insert(decals, newDecal)
+ selectDecal(#decals)
+ updateSave()
+ refresh()
+end
+
+function onDestroy()
+ closePanel()
+end
+
+function cutAtWord(inputStr, delimiter)
+ local t = {}
+ local pattern = "(.-)" .. delimiter
+ for str in inputStr:gmatch(pattern) do
+ table.insert(t, str)
+ end
+ if (#t > 0) then
+ return t[1]
+ else
+ return false
+ end
+end
+
+function waitForUiLoaded(callback)
+ if UI.loading == false then
+ callback()
+ return nil
+ end
+
+ return Wait.condition(
+ function()
+ callback()
+ end,
+ function()
+ return UI.loading == false
+ end
+ )
+end