diff --git a/config.json b/config.json index 566b0050..ad891dbe 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 c63f7886..00000000 --- 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 00000000..e34274f2 --- /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 00000000..63e472ba --- /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 00000000..2bd9c956 --- /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