SaveNameLCT - v1.10.6EpochTimeqphjDate7/28/2026 12:03:45 PMVersionNumberv14.2.1GameModeLCT - v1.10.6GameTypeGameGameComplexityHigh ComplexityPlayingTime0x1PlayerCounts01Tags|0Strategy Games1 Wargames2Miniature Games3Map4Base5 Map Base6 CompetitiveGravity@?PlayArea?Table Table_NoneSky Sky_MuseumNoteTabStateso 0KtitleGaming instructionsbody- **Red, Blue, or Spectator seats** - Choose a player side or watch the game. Picking other colors won't work. - **1v1 and Solo modes** - Set up a standard match or use the table alone. (I ll add Crusade/Narrative & Combat patrol soon) - **Multiple battlefield sizes** - Includes Combat Patrol, Incursion, Strike Force, Onslaught, Boarding Actions, and King of the Colosseum sizes. - **Disposition selection** - Each player selects a disposition, which is used to prepare suitable primary missions and deployment choices. - **Mission generation** - Deals each player's primary mission and presents matching deployment cards. **IMPORTANT: If you see missing dispotition data that's because we dont have all the layouts but the mission matrix works as intended** - **Tactical or Fixed secondaries** - **The mode is resolved when GAME STARTS (Lock Secondaries)**. This works as toggle mode, if you want to use Tactical secondaries you dont need to do anything, just wait for the game to start. If you want to use Fixed Secondaries, you have to flip the Toggle card so that it show "Fixed" and then flip the 2 secondaries that you want to use as fixed. - **Force Start Game: **If for some reason something bugs out, spam pressing start button will start the game regardless. - **Deployment selection** - Pick from the offered deployment layouts or choose one randomly. - **Automatic deployment zones** - The selected deployment zone is drawn on the battlefield and set automatically when a map is loaded. - **Automatic objective and ruin markers** - Markers are placed according to the selected layout and deployment zone and when the game starts - **Table appearance controls** - Choose a green, blue, grey, or purple mat (more to come)colorGreyvisibleColor&r?g?b?id1titleCreditsbodyBig thanks to the following guys * ThePants999 the creator of the original FTC table * Hutber who gave me permission to fork his 10E table * Battlemaster/Superwutz, Cra5hNatural, NConroy, Alvaricus, Zim, Izar, Team Belgium for their 11e maps * TrashPanda and MothmyTitania for their tokens * Kvothe for his LOS tool * Ricu for Statshelper * Blackhole Trash Bin by MrStump * Chess clock by Gizmo, MoonkeyMod and LiyarincolorGreyvisibleColor&r?g?b?idGridTypeLinesColor&rgbOpacity?ThickLinesSnappingOffsetBothSnappingxSize?ySize?PosOffset&xy?zLightingjLightIntensity@33?LightColor&r?g?bQ?AmbientIntensity?AmbientTypeAmbientSkyColor&r@33?g@33?b(?AmbientEquatorColor&r?g?b?AmbientGroundColor&r?g?b?ReflectionIntensity?LutIndexLutContribution?Hands*EnableDisableUnusedHidingComponentTagslabels0<displayed primaryCardnormalized primaryCard1@displayedsecondaryCardnormalizedsecondaryCard2@displayedsecondaryCardnormalizedsecondaryCard3BdisplayeddeploymentCardnormalizeddeploymentCard4:displayed gambitCardnormalized gambitCard5@displayedsecondaryCardnormalizedsecondarycard6:displayed gambitCardnormalized gambitcard7<displayed primaryCardnormalized primarycard8BdisplayeddeploymentCardnormalizeddeploymentcard9<displayed missionCardnormalized missioncardTurnsEnableTypeTurnOrder0Red1BlueReverseSkipEmptyDisableInteractionsPassTurnsTurnColorCameraStates0Position&xyzRotation&x*Q@ysV@zDistanceU@ZoomedAbsolutePosition&xd>yS@zɻ 1 2 3 4 5 6 7 8 9DecalPalletLuaScript--"LuaScript":\s*"(?:\\.|[^\\"])*" --[[ Lua code. See documentation: https://api.tabletopsimulator.com/ --]] -- Having all these GUIDs centrally managed helps to ensure that any changes to -- them only need to be made in one place, and hard-to-find stuff isn't inadvertently -- broken. -- -- If you're looking at this and thinking "surely this should be a table" - why, -- yes it should. Unfortunately, I saw the documentation for Object.getVar said -- "Cannot return a table", and missed that Object.getTable existed, and now I'm -- too lazy to change it all again. centerCircle_GUID = "51ee2f" quarterCircle_GUID = "51ee3f" startMenu_GUID = "738804" redDiceMat_GUID = "c57d70" -- diceTable blueDiceMat_GUID = "a84ed2" -- diceTable redMissionManager_GUID = "cff35b" blueMissionManager_GUID = "471de1" chessClock_GUID = "f4ee71" templateObjective_Hutber_GUID = "a8aca1" redControlBoard_GUID = "5a328f" blueControlBoard_GUID = "32ed4c" RedSelectionHighlighter = "27de4f" BlueSelectionHighlighter = "84c3a4" HUD_PANEL_PREFIXES = {"RedHud_", "BlueHud_", "GreyHud_"} function setHudAttribute(params) if not params or not params.id or not params.attr then return end for _, prefix in ipairs(HUD_PANEL_PREFIXES) do pcall(function() Global.UI.setAttribute(prefix .. params.id, params.attr, params.value) end) end end function setHudValue(params) if not params or not params.id then return end for _, prefix in ipairs(HUD_PANEL_PREFIXES) do pcall(function() local uiId = prefix .. params.id Global.UI.setValue(uiId, params.value) Global.UI.setAttribute(uiId, "text", params.value) end) end end playerHudSettings = { Red = {settingsOpen = false, scoringOverlayOpen = false, clockHidden = false, hudVisible = true, scale = "Medium"}, Blue = {settingsOpen = false, scoringOverlayOpen = false, clockHidden = false, hudVisible = true, scale = "Medium"}, Grey = {settingsOpen = false, scoringOverlayOpen = false, clockHidden = false, hudVisible = true, scale = "Medium"}, } function getDefaultHudSettings() return {settingsOpen = false, scoringOverlayOpen = false, clockHidden = false, hudVisible = true, scale = "Medium"} end function ensureHudSettings(color) if color ~= "Red" and color ~= "Blue" and color ~= "Grey" then color = "Blue" end if not playerHudSettings then playerHudSettings = {} end if not playerHudSettings[color] then playerHudSettings[color] = getDefaultHudSettings() end local defaults = getDefaultHudSettings() for key, value in pairs(defaults) do if playerHudSettings[color][key] == nil then playerHudSettings[color][key] = value end end return playerHudSettings[color] end function getHudColorFromCallback(player, id) if id then local fromId = string.match(id, "^(Red)Hud_") or string.match(id, "^(Blue)Hud_") or string.match(id, "^(Grey)Hud_") if fromId then return fromId end end if type(player) == "table" and player.color then if player.color == "Red" or player.color == "Blue" or player.color == "Grey" then return player.color end elseif player == "Red" or player == "Blue" or player == "Grey" then return player end return "Blue" end function setHudAttributeForColor(color, params) if not params or not params.id or not params.attr then return end local prefix = color .. "Hud_" pcall(function() Global.UI.setAttribute(prefix .. params.id, params.attr, params.value) end) end -- ===== HUD CP action row (PASS TURN / GAIN CP / USE CP) ===== -- GAIN CP / USE CP mutate the player's own CP counter widget directly -- (e446f7 Red / deb9f2 Blue). The cpCounter poller then syncs the HUD CPWatch -- text and posts the same chat line as the overlay +/- buttons, so nothing else -- needs to be updated here. GAIN CP is capped at once per battle round per -- player; USE CP is unlimited. PASS TURN is wired straight to the mission -- manager's hudPassTurn (738804) in the XML, matching the ROUND badge. cpGainRoundUsed = cpGainRoundUsed or {} -- player color -> battle round GAIN CP was last used function cpCounterForColor(color) if color == "Red" then return getObjectFromGUID(redCPCounter_GUID) end if color == "Blue" then return getObjectFromGUID(blueCPCounter_GUID) end return nil end function counterValue(counter) if not counter then return 0 end local ok, value = pcall(function() return counter.call("getValue") end) if ok and value ~= nil then return tonumber(value) or 0 end if counter.Counter then return counter.Counter.getValue() end return 0 end function counterSetValue(counter, value) if not counter then return 0 end local ok, result = pcall(function() return counter.call("setValue", value) end) if ok and result ~= nil then return tonumber(result) or 0 end if counter.Counter then counter.Counter.setValue(value) return counter.Counter.getValue() end return 0 end function counterIncrement(counter) if not counter then return 0 end local ok, result = pcall(function() return counter.call("increment") end) if ok and result ~= nil then return tonumber(result) or counterValue(counter) end if counter.Counter then counter.Counter.increment() return counter.Counter.getValue() end return 0 end function counterDecrement(counter) if not counter then return 0 end local ok, result = pcall(function() return counter.call("decrement") end) if ok and result ~= nil then return tonumber(result) or counterValue(counter) end if counter.Counter then counter.Counter.decrement() return counter.Counter.getValue() end return 0 end function currentBattleRound() local roundObj = getObjectFromGUID(gameTurnCounter_GUID) return counterValue(roundObj) end -- Shared GAIN CP logic. Used by both the HUD overlay button (hudGainCP) and the -- physical "Gain CP" tokens (af8032 Red / 5e882b Blue). Because both paths read -- and write the same cpGainRoundUsed table here, the once-per-battle-round cap is -- shared: gaining via either the token or the overlay locks out the other for the -- rest of that round. Returns true if a CP was granted. function gainCPForColor(color) if color ~= "Red" and color ~= "Blue" then return false end local counter = cpCounterForColor(color) if not counter then return false end local round = currentBattleRound() if cpGainRoundUsed[color] == round then broadcastToColor("You can't gain more CP this round.", color, "Orange") return false end cpGainRoundUsed[color] = round counterIncrement(counter) return true end function hudGainCP(player, value, id) gainCPForColor(getHudColorFromCallback(player, id)) end function hudUseCP(player, value, id) local color = getHudColorFromCallback(player, id) if color ~= "Red" and color ~= "Blue" then return end local counter = cpCounterForColor(color) if not counter then return end counterDecrement(counter) end -- ===== Minimalist HUD (per player, toggled by the "-" control button) ===== -- Minimalist mode hides the PASS/GAIN/USE CP action row (Row3) and both per-turn -- phase-icon strips, then resizes the panel + control column to fit. Red and Blue -- are independent (separate panels); Grey has no "-" button so it is never minimal. hudMinimalist = hudMinimalist or {} -- player color -> bool -- Per-mode geometry. Minimalist drops the panel width by the phase-strip width and -- the height by the Row3 line, and shrinks the 4-button control column (and its -- font) so the glyphs still fit. width/height are visual estimates — tune in-game. local HUD_FULL = { width = "1216", height = "228", mainWidth = "1160", mainHeight = "228", controlsH = "172", btnFont = "28", shown = "true" } local HUD_MIN = { width = "740", height = "155", mainWidth = "740", mainHeight = "155", controlsH = "143", btnFont = "24", shown = "false" } -- The clock readout + its steppers and play/pause buttons (same id set the -- clock-hide toggle uses). local HUD_CLOCK_IDS = {"ClockSubPanel1", "ClockSubPanel2", "ChsStpPlus1", "ChsStpMinus1", "ChsStpPlus2", "ChsStpMinus2", "ChsStpPlay1", "ChsStpPause1", "ChsStpPlay2", "ChsStpPause2"} function applyMinimalistForColor(color) if color ~= "Red" and color ~= "Blue" then return end local minimal = hudMinimalist[color] local d = minimal and HUD_MIN or HUD_FULL local p = color .. "Hud_" -- Minimalist always hides the clock; full mode hands it back to the player's -- own clock toggle so we don't override a manually hidden clock. local clockShown = (not minimal) and (not ensureHudSettings(color).clockHidden) pcall(function() Global.UI.setAttribute(p .. "Row3", "active", d.shown) Global.UI.setAttribute(p .. "PhaseRow1", "active", d.shown) Global.UI.setAttribute(p .. "PhaseRow2", "active", d.shown) Global.UI.setAttribute(p .. "RightControls", "active", d.shown) for _, id in ipairs(HUD_CLOCK_IDS) do Global.UI.setAttribute(p .. id, "active", clockShown and "true" or "false") end Global.UI.setAttribute(p .. "VPScores", "width", d.width) Global.UI.setAttribute(p .. "VPScores", "height", d.height) Global.UI.setAttribute(p .. "MainPanel", "preferredWidth", d.mainWidth) Global.UI.setAttribute(p .. "MainPanel", "minWidth", d.mainWidth) Global.UI.setAttribute(p .. "MainPanel", "preferredHeight", d.mainHeight) Global.UI.setAttribute(p .. "MainPanel", "minHeight", d.mainHeight) Global.UI.setAttribute(color .. "HudControls", "preferredHeight", d.controlsH) for _, b in ipairs({"SettingsBtn", "LockBtn", "ClockBtn", "MinusBtn"}) do Global.UI.setAttribute(p .. b, "fontSize", d.btnFont) end end) end function hudToggleMinimalist(player, value, id) local color = getHudColorFromCallback(player, id) if color ~= "Red" and color ~= "Blue" then return end hudMinimalist[color] = not hudMinimalist[color] applyMinimalistForColor(color) end function applyHudPreferencesForColor(color) local settings = ensureHudSettings(color) local settingsActive = settings.settingsOpen and "true" or "false" local overlayActive = settings.hudVisible and "true" or "false" local scoringOverlayActive = settings.scoringOverlayOpen and "true" or "false" local clockActive = not settings.clockHidden setHudAttributeForColor(color, {id = "Settings", attr = "active", value = settingsActive}) setHudAttributeForColor(color, {id = "VPScores", attr = "active", value = overlayActive}) setHudAttributeForColor(color, {id = "ScoringOverlay", attr = "active", value = scoringOverlayActive}) setHudAttributeForColor(color, {id = "LockBtn", attr = "color", value = "#FFFFFF"}) setHudAttributeForColor(color, {id = "ClockBtn", attr = "color", value = "#FFFFFF"}) for _, id in ipairs({"ClockSubPanel1", "ClockSubPanel2", "ChsStpPlus1", "ChsStpMinus1", "ChsStpPlus2", "ChsStpMinus2", "ChsStpPlay1", "ChsStpPause1", "ChsStpPlay2", "ChsStpPause2"}) do setHudAttributeForColor(color, {id = id, attr = "active", value = clockActive and "true" or "false"}) end end function applyHudPreferences() applyHudPreferencesForColor("Red") applyHudPreferencesForColor("Blue") applyHudPreferencesForColor("Grey") end function hudToggleSettings(player, value, id) local color = getHudColorFromCallback(player, id) local settings = ensureHudSettings(color) settings.settingsOpen = false applyHudPreferencesForColor(color) if type(player) == "table" and player.color then printToColor("Settings Panel is coming soon :)", player.color, {1, 1, 1}) else broadcastToAll("Settings Panel is coming soon :)", {1, 1, 1}) end end function hudCloseSettings(player, value, id) local color = getHudColorFromCallback(player, id) local settings = ensureHudSettings(color) settings.settingsOpen = false applyHudPreferencesForColor(color) end function hudToggleLock(player, value, id) local color = getHudColorFromCallback(player, id) local settings = ensureHudSettings(color) settings.scoringOverlayOpen = not settings.scoringOverlayOpen if settings.scoringOverlayOpen then -- The freshly-opened board renders from XML defaults, so drop the cache to -- force a full repaint (cached values would otherwise be skipped as "current"). invalidateScoringCache() refreshScoringOverlay() scheduleScoringOverlayRefreshLoop() end applyHudPreferencesForColor(color) end function hudCloseScoringOverlay(player, value, id) local color = getHudColorFromCallback(player, id) local settings = ensureHudSettings(color) settings.scoringOverlayOpen = false applyHudPreferencesForColor(color) end -- Reached via obj.call from the "Show Scoreboard" hotkey (registered in -- customDiceTable.ttslua). obj.call passes a single params table, so we wrap -- hudToggleLock which expects (player, value, id). function hotkeyToggleScoringOverlay(params) hudToggleLock(params.playerColor, nil, nil) end -- IMPORTANT: overlay text values must stay PLAIN (no ///rich-text). -- setHudValue writes each value into a Global.UI `text="..."` attribute, and -- yellowscribe rebuilds the whole Global UI via getXmlTable()/setXmlTable(). Raw -- <, >, |, # in an attribute corrupt that round-trip and the overlay renders huge -- with default text. Keep markup out of these strings. function formatSecondaryListColumns(names, cap, numbered, emptyLabel) if not names or #names == 0 then return (emptyLabel or "None"), "" end local count = #names if cap and count > cap then count = cap end local splitAt = math.ceil(count / 2) local left = {} local right = {} for i = 1, count do local line if numbered then line = i .. ". " .. names[i] else line = "• " .. names[i] end if i <= splitAt then left[#left + 1] = line else right[#right + 1] = line end end return table.concat(left, "\n"), table.concat(right, "\n") end function isAnyScoringOverlayOpen() for _, color in ipairs({"Red", "Blue", "Grey"}) do local settings = ensureHudSettings(color) if settings.scoringOverlayOpen then return true end end return false end function scheduleScoringOverlayRefreshLoop() if scoringOverlayRefreshScheduled then return end scoringOverlayRefreshScheduled = true -- Cancel any stale handle before scheduling a fresh tick so timers can never -- accumulate if this loop is ever (re)started out of band. if scoringOverlayRefreshTimer then Wait.stop(scoringOverlayRefreshTimer) scoringOverlayRefreshTimer = nil end scoringOverlayRefreshTimer = Wait.time(function() scoringOverlayRefreshScheduled = false scoringOverlayRefreshTimer = nil if not isAnyScoringOverlayOpen() then return end refreshScoringOverlay() scheduleScoringOverlayRefreshLoop() end, 2) end function getActiveSecondaryNames(color) local zoneGuids = (color == "Red") and redSecondaryCardZone_GUIDs or blueSecondaryCardZone_GUIDs local names = {} for _, guid in ipairs(zoneGuids) do local zone = getObjectFromGUID(guid) local name = getSecondaryCardName(zone) if name then names[#names + 1] = name end end return names end function getRemainingDeckSecondaryNames(color) local deck = getSecondaryDeckByColor(color) if not deck then return {} end local names = {} local contents = deck.getObjects() if contents then for _, descriptor in ipairs(contents) do if descriptor.name and descriptor.name ~= "" then names[#names + 1] = descriptor.name end end end table.sort(names) return names end -- `objects` is an optional pre-fetched getAllObjects() list so a single scan can -- serve both colors (the scoring overlay refresh calls this for Red and Blue). function getDiscardedSecondaryNames(color, objects) local pos = getSecondaryDiscardPosition(color) if not pos then return {} end local names = {} for _, obj in ipairs(objects or getAllObjects()) do local tag = obj.tag if tag == "Card" or tag == "Deck" then local objPos = obj.getPosition() if math.abs(objPos.x - pos.x) < 2.5 and math.abs(objPos.z - pos.z) < 2.5 then if tag == "Card" then local n = obj.getName() if n and n ~= "" then names[#names + 1] = n end else local contents = obj.getObjects() if contents then for _, descriptor in ipairs(contents) do if descriptor.name and descriptor.name ~= "" then names[#names + 1] = descriptor.name end end end end end end end return names end -- Static-per-game context shown in the scoring overlay header/columns (Idea 1 & 2): -- per-player disposition + primary mission, and the shared deployment zone name. -- These are frozen once the game begins, so startMenu pushes them in once at -- startGame via setScoringContext rather than the overlay recomputing them on every -- 2s refresh. Cached here so reopening the overlay re-renders them. scoringContext = scoringContext or { redDisposition = "", blueDisposition = "", redPrimary = "", bluePrimary = "", deployZone = "", } function setScoringContext(params) if type(params) ~= "table" then return end for _, key in ipairs({"redDisposition", "blueDisposition", "redPrimary", "bluePrimary", "deployZone"}) do if params[key] ~= nil then scoringContext[key] = tostring(params[key]) end end -- Only repaint boards that are actually open; a board opened later force-repaints -- from this context via hudToggleLock, so there's no need to write to closed copies. if isAnyScoringOverlayOpen() then refreshScoringOverlay() end end -- ===== Scoreboard (scoring overlay) scoped, idempotent HUD writers ===== -- Unlike setHudValue/setHudAttribute (which deliberately fan out to all three -- viewer copies because round/turn/CP/VP must update every player's HUD), the -- scoreboard is a large *draggable* panel: any rewrite re-anchors the drag back -- to its XML default position. So scoreboard writes (a) target only the colours -- whose scoreboard is actually open and (b) skip writes whose value is unchanged, -- which removes the idle 2s reflow that was snapping the panel back. NOTE: this -- does not guarantee a dragged position survives a genuine content change that -- alters a section's preferredHeight (item counts changing) — TTS exposes no way -- to read a live drag offset, so re-layout on real changes can still snap it. -- -- A generation counter logically clears the whole cache whenever Global UI may -- have been reset underneath us (see invalidateScoringCache). The cache is also -- naturally empty on a fresh script load (the `or {}` init below). scoringHudCache = scoringHudCache or {} scoringHudCacheGen = scoringHudCacheGen or 0 -- Bump the generation so every cached key is considered stale. Call on any path -- that can reset Global UI state: a player opening their board (forces a full -- repaint), Global onLoad, and after any external getXmlTable()/setXmlTable() -- rebuild (e.g. yellowscribe) that we can hook. function invalidateScoringCache() scoringHudCache = {} scoringHudCacheGen = scoringHudCacheGen + 1 end function isYellowscribeGlobalCard(element) if type(element) ~= "table" or type(element.attributes) ~= "table" then return false end local id = element.attributes.id return type(id) == "string" and string.match(id, "^ymc%-") ~= nil end function xmlElementHasCallbackGuid(element, guid) if type(element) ~= "table" or not guid then return false end if type(element.attributes) == "table" then for _, value in pairs(element.attributes) do if type(value) == "string" and string.find(value, guid .. "/", 1, true) then return true end end end if type(element.children) == "table" then for _, child in ipairs(element.children) do if xmlElementHasCallbackGuid(child, guid) then return true end end end return false end function removeYellowscribeGlobalCards(targetGuid) local ok, ui = pcall(function() return Global.UI.getXmlTable() end) if not ok or type(ui) ~= "table" then return end local filtered = {} local changed = false for _, element in ipairs(ui) do local remove = isYellowscribeGlobalCard(element) if targetGuid then remove = remove and xmlElementHasCallbackGuid(element, targetGuid) end if remove then changed = true else filtered[#filtered + 1] = element end end if changed then pcall(function() Global.UI.setXmlTable(filtered) end) invalidateScoringCache() end end -- The colours whose scoreboard is currently open (Grey = spectators included). function scoringTargetColors() local colors = {} for _, color in ipairs({"Red", "Blue", "Grey"}) do if ensureHudSettings(color).scoringOverlayOpen then colors[#colors + 1] = color end end return colors end -- Cache only AFTER a successful write so a push dropped before the overlay is -- live is retried on the next refresh rather than being assumed applied. function setScoringHudValue(colors, id, value) for _, color in ipairs(colors) do local uiId = color .. "Hud_" .. id local key = scoringHudCacheGen .. "|" .. uiId .. "|text" if scoringHudCache[key] ~= value then local ok = pcall(function() Global.UI.setValue(uiId, value) Global.UI.setAttribute(uiId, "text", value) end) if ok then scoringHudCache[key] = value end end end end function setScoringHudAttribute(colors, id, attr, value) for _, color in ipairs(colors) do local uiId = color .. "Hud_" .. id local key = scoringHudCacheGen .. "|" .. uiId .. "|" .. attr if scoringHudCache[key] ~= value then local ok = pcall(function() Global.UI.setAttribute(uiId, attr, value) end) if ok then scoringHudCache[key] = value end end end end -- Writes a two-column secondary list (L/R) into the scoreboard, scoped to the open colours. function setScoringListColumns(colors, id, names, cap, numbered, emptyLabel) local left, right = formatSecondaryListColumns(names, cap, numbered, emptyLabel) setScoringHudValue(colors, id .. "L", left) setScoringHudValue(colors, id .. "R", right) end -- Disposition + mission on one line, middot separator. PLAIN text (the bold styling -- comes from the Text element's fontStyle, not rich-text) — see formatSecondaryListColumns. function formatDispositionLine(disposition, mission) disposition = disposition or "" mission = mission or "" if disposition ~= "" and mission ~= "" then return disposition .. " · " .. mission end return disposition .. mission end -- Writes only the static context fields. The deploy zone now lives in the title -- (see refreshScoringOverlay) and each disposition line carries that player's -- primary mission, so this just writes the two combined disposition lines. Split -- out of refreshScoringOverlay so setScoringContext can update them without paying -- for the full secondary-card scan. function applyScoringContextToOverlay(colors) colors = colors or scoringTargetColors() setScoringHudValue(colors, "ScoringOverlay_RedDisposition", formatDispositionLine(scoringContext.redDisposition, scoringContext.redPrimary)) setScoringHudValue(colors, "ScoringOverlay_BlueDisposition", formatDispositionLine(scoringContext.blueDisposition, scoringContext.bluePrimary)) end function refreshScoringOverlay() -- Only the open boards are written; computing the (shared) summary is skipped -- entirely when nothing is open. local colors = scoringTargetColors() if #colors == 0 then return end local sheet = getObjectFromGUID(scoresheet_GUID) if not sheet then return end local summary = sheet.call("getMatchSummary") if not summary then return end applyScoringContextToOverlay(colors) local roundCounter = getObjectFromGUID(gameTurnCounter_GUID) local roundNum = counterValue(roundCounter) -- Title: "⚔ - Round N ⚔" (deploy zone is static, set at game start). local zone = scoringContext.deployZone or "" local titleText = (zone ~= "") and ("⚔ " .. zone .. " - Round " .. roundNum .. " ⚔") or ("⚔ Round " .. roundNum .. " ⚔") setScoringHudValue(colors, "ScoringOverlay_Title", titleText) -- Player name (plain) + VP in a pill. setScoringHudValue(colors, "ScoringOverlay_RedName", summary.red.name or "Red") setScoringHudValue(colors, "ScoringOverlay_BlueName", summary.blue.name or "Blue") setScoringHudValue(colors, "ScoringOverlay_RedVP", (summary.red.total or 0) .. " VP") setScoringHudValue(colors, "ScoringOverlay_BlueVP", (summary.blue.total or 0) .. " VP") -- Per-round table: fixed PRI/SEC/TOT columns with dimmed labels, the current -- battle round highlighted (item 1). Colour tags are kept un-nested so Unity -- rich text renders each segment correctly. local function buildRounds(rounds) local lines = {} for j = 1, 5 do local rr = (rounds and rounds[j]) or {} local pri = rr.primary or 0 local sec = rr.secondary or 0 local tot = rr.total or (pri + sec) -- Active round flagged with a leading arrow; inactive rows pad with -- matching spaces so every line still centres alike. PLAIN text only -- (no /) — see formatSecondaryListColumns for why. local marker = (j == roundNum) and "▸ " or " " lines[#lines + 1] = string.format( "%sRound %d PRI %d | SEC %d | SUM %d", marker, j, pri, sec, tot) end return table.concat(lines, "\n") end setScoringHudValue(colors, "ScoringOverlay_RedRounds", buildRounds(summary.red.rounds)) setScoringHudValue(colors, "ScoringOverlay_BlueRounds", buildRounds(summary.blue.rounds)) local redActive = getActiveSecondaryNames("Red") local blueActive = getActiveSecondaryNames("Blue") -- Scan the whole scene once and reuse it for both discard piles. local allObjects = getAllObjects() local redDiscard = getDiscardedSecondaryNames("Red", allObjects) local blueDiscard = getDiscardedSecondaryNames("Blue", allObjects) local redDeck = getRemainingDeckSecondaryNames("Red") local blueDeck = getRemainingDeckSecondaryNames("Blue") -- Counts in the section headers (item 4). setScoringHudValue(colors, "ScoringOverlay_RedActiveHdr", "ACTIVE SECONDARIES (" .. #redActive .. ")") setScoringHudValue(colors, "ScoringOverlay_BlueActiveHdr", "ACTIVE SECONDARIES (" .. #blueActive .. ")") setScoringHudValue(colors, "ScoringOverlay_RedDiscardHdr", "DISCARD PILE (" .. #redDiscard .. ")") setScoringHudValue(colors, "ScoringOverlay_BlueDiscardHdr", "DISCARD PILE (" .. #blueDiscard .. ")") setScoringHudValue(colors, "ScoringOverlay_RedDeckHdr", "REMAINING SECONDARIES (" .. #redDeck .. ")") setScoringHudValue(colors, "ScoringOverlay_BlueDeckHdr", "REMAINING SECONDARIES (" .. #blueDeck .. ")") setScoringListColumns(colors, "ScoringOverlay_RedActive", redActive, 8, true, "None drawn yet") setScoringListColumns(colors, "ScoringOverlay_BlueActive", blueActive, 8, true, "None drawn yet") setScoringListColumns(colors, "ScoringOverlay_RedDiscard", redDiscard, nil, false, "Empty") setScoringListColumns(colors, "ScoringOverlay_BlueDiscard", blueDiscard, nil, false, "Empty") setScoringListColumns(colors, "ScoringOverlay_RedDeck", redDeck, nil, false, "All drawn") setScoringListColumns(colors, "ScoringOverlay_BlueDeck", blueDeck, nil, false, "All drawn") -- Dynamic, symmetric section heights based on content count. -- Both sides get the same height per section (max of red/blue) so columns line up. local lineHeight = 27 -- matches the 22pt list font (item 3) local minSection = 30 -- room for the empty-state placeholder local function sectionH(count) if count <= 0 then return minSection end return math.ceil(count / 2) * lineHeight + 4 end local activeCount = math.max(math.min(#redActive, 8), math.min(#blueActive, 8)) local discardCount = math.max(#redDiscard, #blueDiscard) local deckCount = math.max(#redDeck, #blueDeck) local activeH = sectionH(activeCount) local discardH = sectionH(discardCount) local deckH = sectionH(deckCount) -- If total exceeds the panel's content budget, scale all sections down proportionally. -- Trimmed to account for the taller column header (name band + breakdown line) and -- the separate section-header/divider rows now above each list (items 3,4,8,9). local contentBudget = 680 local needed = activeH + discardH + deckH if needed > contentBudget then local scale = contentBudget / needed activeH = math.floor(activeH * scale) discardH = math.floor(discardH * scale) deckH = math.floor(deckH * scale) end -- preferredHeight is the layout-reflow attribute that re-anchors the drag, so -- the idempotent setter skipping unchanged values is what actually stops the -- idle snap-back: a steady board never re-writes these. setScoringHudAttribute(colors, "ScoringOverlay_RedActive", "preferredHeight", tostring(activeH)) setScoringHudAttribute(colors, "ScoringOverlay_BlueActive", "preferredHeight", tostring(activeH)) setScoringHudAttribute(colors, "ScoringOverlay_RedDiscard", "preferredHeight", tostring(discardH)) setScoringHudAttribute(colors, "ScoringOverlay_BlueDiscard", "preferredHeight", tostring(discardH)) setScoringHudAttribute(colors, "ScoringOverlay_RedDeck", "preferredHeight", tostring(deckH)) setScoringHudAttribute(colors, "ScoringOverlay_BlueDeck", "preferredHeight", tostring(deckH)) end function hudToggleClock(player, value, id) local color = getHudColorFromCallback(player, id) local settings = ensureHudSettings(color) settings.clockHidden = not settings.clockHidden applyHudPreferencesForColor(color) end function toggleHudOverlayForColor(params) local color = params and params.color or "Blue" if color ~= "Red" and color ~= "Blue" and color ~= "Grey" then color = "Blue" end local settings = ensureHudSettings(color) settings.hudVisible = not settings.hudVisible if not settings.hudVisible then settings.settingsOpen = false end applyHudPreferencesForColor(color) end function hudHideOverlay(player, value, id) local color = getHudColorFromCallback(player, id) if color ~= "Red" and color ~= "Blue" and color ~= "Grey" then return end local settings = ensureHudSettings(color) settings.hudVisible = false settings.settingsOpen = false applyHudPreferencesForColor(color) end -- ===== Custom bubble input overlay (screen space, 1-60) ===== -- Each player has their OWN panel (bubbleOverlay_Red / bubbleOverlay_Blue) with -- fixed visibility in ftc_base_ui.xml, so one player's overlay is never shown to -- the other and both can be open at once. Opened by the dice-mat "123" button or -- the "Custom Bubble Size" game key; the result is applied on that player's mat. bubbleOverlayPending = {} -- player color -> typed string (live, from onValueChanged) bubbleOverlayMat = {} -- player color -> dice mat GUID to apply on bubbleOverlayLock = {} -- player color -> debounce flag function diceMatForColor(color) if color == "Red" then return redCustomDiceMat_GUID end if color == "Blue" then return blueCustomDiceMat_GUID end return nil end function openBubbleInput(params) local color = params.color if color ~= "Red" and color ~= "Blue" then return end bubbleOverlayMat[color] = params.mat or diceMatForColor(color) bubbleOverlayPending[color] = nil Global.UI.setAttribute("bubbleInputField_" .. color, "text", "") Global.UI.setAttribute("bubbleOverlay_" .. color, "active", "true") end function cacheBubbleInput(player, value, id) if player then bubbleOverlayPending[player.color] = value end end function closeBubbleInput(color) Global.UI.setAttribute("bubbleOverlay_" .. color, "active", "false") Global.UI.setAttribute("bubbleInputField_" .. color, "text", "") bubbleOverlayPending[color] = nil end -- UI callback for the overlay's red "X" close button. function closeBubbleOverlay(player, value, id) local color = player and player.color if color == "Red" or color == "Blue" then closeBubbleInput(color) end end -- Called by both Enter (InputField onEndEdit) and the OK button (onClick). -- Pressing OK also blurs the field, so both can fire; debounce to apply once. function submitBubbleInput(player, value, id) local color = player and player.color if color ~= "Red" and color ~= "Blue" then return end if bubbleOverlayLock[color] then return end bubbleOverlayLock[color] = true Wait.time(function() bubbleOverlayLock[color] = false end, 0.4) local matGuid = bubbleOverlayMat[color] or diceMatForColor(color) local mat = matGuid and getObjectFromGUID(matGuid) local n = tonumber(bubbleOverlayPending[color]) -- 0 or a blank value clears the bubbles on the player's selected models. if not n or n <= 0 then if mat then mat.call("clearBubblesFromOverlay", {color = color}) end closeBubbleInput(color) return end if n > 60 then n = 60 end if mat then mat.call("applyBubbleFromOverlay", {radius = n, color = color}) end closeBubbleInput(color) end redCustomDiceMat_GUID = "acae21" -- diceTable blueCustomDiceMat_GUID = "839fcc" -- diceTable redCustomDiceRoller_GUID = "17ca2b" -- kustom40kDiceRoller blueCustomDiceRoller_GUID = "927ca1" -- kustom40kDiceRoller redLethalHits_GUID = "7ac8ba" blueLethalHits_GUID = "71ab21" RedVortexGUID = "be2cdb" BlueVortexGUID = "4b1a7b" RedTokenCounterGUID = "42c161" BlueTokenCounterGUID = "42c164" scoreSheet_GUID = "06d627" redCPCounter_GUID = "e446f7" blueCPCounter_GUID = "deb9f2" purpleCPCounter_GUID = "61c2f8" redTurnCounter_GUID = "055302" blueTurnCounter_GUID = "7e4111" gameTurnCounter_GUID = "ee92cf" scoresheet_GUID = "06d627" -- Master activation/wound tracker tokens cloned by the Spawn buttons on the -- "Spawn game tools" object (a76485). activation_GUID = "229946" wounds_GUID = "ad63ba" table_GUID = "948ce5" felt_GUID = "28865a" mat_GUID = "4ee1f2" matURLDisplay_GUID = "c5e288" flexControl_GUID = "bd69bd" tableLeg1_GUID = "afc863" tableLeg2_GUID = "c8edca" tableLeg3_GUID = "393bf7" tableLeg4_GUID = "12c65e" tableSideBottom_GUID = "f938a2" tableSideTop_GUID = "35b95f" tableSideLeft_GUID = "9f95fd" tableSideRight_GUID = "5af8f2" extractTerrain_GUID = "70b9f6" redHandZone_GUID = "f7d85a" blueHandZone_GUID = "731345" redHiddenZone_GUID = "28419e" blueHiddenZone_GUID = "e1e91a" deploymentCardZone_GUID = "dcf95b" primaryCardZone_GUID = "740abc" secondary11CardZone_GUID = "c1e001" secondary12CardZone_GUID = "c1e002" secondary21CardZone_GUID = "b1e001" secondary22CardZone_GUID = "b1e002" redSecondaryCardZone_GUIDs = { "c1e001", "c1e002", "c1e003", "c1e004", "c1e005", "c1e006", "c1e007", "c1e008", } redSecondaryName_GUIDs = { "5423ba", "5423bb", "5423bc", "5423bd", "5423be", } blueSecondaryCardZone_GUIDs = { "b1e001", "b1e002", "b1e003", "b1e004", "b1e005", "b1e006", "b1e007", "b1e008", } blueSecondaryName_GUIDs = { "5423a5", "5423a6", "5423a7", "5423a8", "5423a9", } challengerCardZone_GUID = "cdecf2" primaryDeck_GUID = "d26e9c" edition11RedSecondaryDeck_GUID = "8104e1" edition11BlueSecondaryDeck_GUID = "994347" redSecondaryDeckPosition = {x = 62.54, y = 0.98, z = 8.45} blueSecondaryDeckPosition = {x = 62.54, y = 0.98, z = -8.45} hiddenMissionDeckPosition = {85.0, -5.0, 4.75} -- Blue player sits on the -Z side, red on +Z (matches the mission managers, -- dice rollers and primary card slots). The deck nearest each player feeds it. bluePrimaryDeck_GUID = "e64441" -- z = -22 (blue side) redPrimaryDeck_GUID = "25c0d8" -- z = +22 (red side) -- Blue slots are the -Z pair, red slots the +Z pair (see deck note above). -- NOTE: the in-world ScriptingTrigger nicknames (d1e001="BluePrimary1" etc.) -- are still labelled the old, inverted way -- cosmetic only, the GUIDs below -- are what the code uses. Swap those nicknames in the save if you want them -- to read correctly in the editor. missionCardZone_BluePrimary1_GUID = "d1e003" -- z = -2.55 (blue side) missionCardZone_BluePrimary2_GUID = "d1e004" -- z = -4.80 (blue side) missionCardZone_RedPrimary1_GUID = "d1e001" -- z = 4.80 (red side) missionCardZone_RedPrimary2_GUID = "d1e002" -- z = 2.55 (red side) missionCardZone_Deployment_GUID = "d1e005" missionCardZone_Twist_GUID = "d1e006" -- ===================================================================== -- Disposition matchup -> primary missions -- --------------------------------------------------------------------- -- Directional: the result depends on which disposition each player chose. -- Disposition indices (must match dispositionValues order in startMenu): -- 1: Disruption 2: Priority Assets 3: Purge the Foe -- 4: Reconnaissance 5: Take and Hold -- -- Key is "_". Each entry names the PRIMARY MISSION for -- each player. At generation time the mission's 1-2 cards are pulled from that -- player's own deck BY NAME (a trailing " 1"/" 2" on the card is optional), -- and any empty slot is padded with "Blank Primary 1"/"Blank Primary 2". -- -- Any missing key, or a mission whose cards aren't in the deck, falls back to -- blanks. "Placeholder" is intentionally not a real card yet, so those slots -- resolve to blanks until a card with that name is added. -- red deck = redPrimaryDeck_GUID (25c0d8) -- blue deck = bluePrimaryDeck_GUID (e64441) -- ===================================================================== PLACEHOLDER_MISSION = "Blank Primary" missionMatchups = { -- Disruption (1) is red ["1_1"] = { red = "Outmaneuver", blue = "Outmaneuver" }, ["1_2"] = { red = "Locate and Deny", blue = "Extract Relic" }, ["1_3"] = { red = "Delaying Action", blue = "Punishment" }, ["1_4"] = { red = "Smoke and Mirrors", blue = "Surveil the Foe" }, ["1_5"] = { red = "Death Trap", blue = "Determined Acquisition" }, -- Priority Assets (2) is red ["2_1"] = { red = "Extract Relic", blue = "Locate and Deny" }, ["2_2"] = { red = "Sabotage", blue = "Sabotage" }, ["2_3"] = { red = "Vital Link", blue = "Destroyer's Wrath" }, ["2_4"] = { red = "Vanguard Operation", blue = "Search and Scour" }, ["2_5"] = { red = "Secure Asset", blue = "Inescapable Dominion" }, -- Purge the Foe (3) is red ["3_1"] = { red = "Punishment", blue = "Delaying Action" }, ["3_2"] = { red = "Destroyer's Wrath", blue = "Vital Link" }, ["3_3"] = { red = "Meatgrinder", blue = "Meatgrinder" }, ["3_4"] = { red = "Consecrate", blue = "Triangulation" }, ["3_5"] = { red = "Unstoppable Force", blue = "Immovable Object" }, -- Reconnaissance (4) is red ["4_1"] = { red = "Surveil the Foe", blue = "Smoke and Mirrors" }, ["4_2"] = { red = "Search and Scour", blue = "Vanguard Operation" }, ["4_3"] = { red = "Triangulation", blue = "Consecrate" }, ["4_4"] = { red = "Gather Intel", blue = "Gather Intel" }, ["4_5"] = { red = "Reconnaissance Sweep", blue = "Purge and Secure" }, -- Take and Hold (5) is red ["5_1"] = { red = "Determined Acquisition", blue = "Death Trap" }, ["5_2"] = { red = "Inescapable Dominion", blue = "Secure Asset" }, ["5_3"] = { red = "Immovable Object", blue = "Unstoppable Force" }, ["5_4"] = { red = "Purge and Secure", blue = "Reconnaissance Sweep" }, ["5_5"] = { red = "Battlefield Dominance", blue = "Battlefield Dominance" }, } interactiveButtonTableQuarters_State = 1 interactiveButtonAreaDenial = 1 interactiveButtonStratReserves = 1 interactiveButtonDeploymentZones = 1 interactiveButtonTerritoryLine = 1 interactiveButtonShowObjectives = 1 interactiveButtonShowObjectivesNextClickAt = 0 teamModels = { Red = {}, Blue = {} } Global.setTable("teamModels", teamModels) --[[ GUID reference for primary / mission / deployment cards Yeah, I could use variables but I didn't, oh well Pariah Nexus: ------------- Burden of Trust 85718e Linchpin 540fd0 Purge the Foe d9d35a Scorched Earth 6860de Supply Drop 981226 Take and Hold bffd57 Terraform 7fa6d0 Hidden Supplies 85718f The Ritual 7067e2 Unexploded Ordnance 41f293 Crucible of Battle e96d18 Dawn of War 7f45dc Hammer and Anvil 97f3f0 Sweeping Engagement c6edf3 Tipping Point 77cde6 ]]-- MISSION_PACK_PARIAH_NEXUS = 1 -- Player-facing version + patch notes. The compiler rewrites these three lines -- on every build from CHANGELOG.md; the `@@LCT_...@@` markers must stay intact. -- Left as-is, an uncompiled / dev build simply shows "DEV". -- GAME_VERSION : build stamp ("test" or the release version) - chat + save name -- GAME_PATCH : latest CHANGELOG version - shown on the splash overlay -- GAME_CHANGELOG: latest CHANGELOG bullets - shown on the splash overlay GAME_VERSION = "v1.10.6" -- @@LCT_VERSION@@ GAME_PATCH = "v1.10.6" -- @@LCT_PATCH@@ GAME_CHANGELOG = {"Battlemaster,LCT Map, Cra5hNatural and T5S2 packs are updated based on July Dataslate.", "Updated Event companion pdf"} -- @@LCT_CHANGELOG@@ -- Build-wide debug switch. The compiler stamps this true for `--test` builds and -- false for `--release`. Other object scripts read it with Global.getVar("DEBUG"). DEBUG = false -- @@LCT_DEBUG@@ -- Reset during script registration; onLoad sets this true only after Global has -- restored all state and references used by object scripts. FTC_Global_Ready = false -- Per-map metadata baked from data/map_manifest.csv at build time (see -- bake_map_index in scripts/compile.py). Keyed by card GUID: -- {creator=, display=, eligible=} -- Read via Global.getTable("MAP_INDEX") by mission generation + the map filter. -- The empty default below keeps an uncompiled/dev build valid; the @@MAP_INDEX@@ -- marker line must stay intact for the compiler to replace it. MAP_INDEX = {["b3537f"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["f7026e"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["32ec37"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["0574f5"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["9a0779"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["936fc5"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["b200c7"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["366693"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["2db2a2"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["812e71"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["af501d"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["f2c13b"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["bc8eb5"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["0538a9"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["c26edc"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["5fdb47"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["cb907f"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["3998a5"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["3eb69e"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["6277f1"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["dbf11e"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["3fc11c"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["796c92"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["bdfb2a"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["f1a6b5"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["b3addc"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["002262"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["f9769a"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["f83312"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["4a79c4"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["78d12d"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["29061a"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["33a96e"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["262746"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["e4117b"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["229fed"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["8fc0f1"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["cb843a"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["0d4f36"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["653d63"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["bc3814"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["80af80"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["104223"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["5fa95f"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["71a63a"]={creator="cr5sh",display="Cra5hNatural",type="comp",eligible=true},["06b679"]={creator="zim",display="Zim",type="comp",eligible=true},["945feb"]={creator="zim",display="Zim",type="comp",eligible=true},["defc8f"]={creator="zim",display="Zim",type="comp",eligible=true},["c4782f"]={creator="zim",display="Zim",type="comp",eligible=true},["9f44a5"]={creator="zim",display="Zim",type="comp",eligible=true},["70011f"]={creator="zim",display="Zim",type="comp",eligible=true},["c856b5"]={creator="zim",display="Zim",type="comp",eligible=true},["66ed4a"]={creator="zim",display="Zim",type="comp",eligible=true},["7a0751"]={creator="zim",display="Zim",type="comp",eligible=true},["ecaa0b"]={creator="zim",display="Zim",type="comp",eligible=true},["66e290"]={creator="zim",display="Zim",type="comp",eligible=true},["f3c287"]={creator="zim",display="Zim",type="comp",eligible=true},["ea06a1"]={creator="zim",display="Zim",type="comp",eligible=true},["7c3b59"]={creator="zim",display="Zim",type="comp",eligible=true},["2f34c6"]={creator="zim",display="Zim",type="comp",eligible=true},["fd3d94"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["e69b2b"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["0b3687"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["0c3db6"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["5e599f"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["c2a804"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["3009f9"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["7c89be"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["3cfaa1"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["0d4f55"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["e34918"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["5d905e"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["6d2b99"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["c20b72"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["f11470"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["bba0b7"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["1f97e2"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["6f2518"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["0c8d39"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["c77fbd"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["e76af7"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["736799"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["32423f"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["413eff"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["6e52ef"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["83314d"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["6a9d75"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["145490"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["ff5fec"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["630339"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["f93cc0"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["e02c82"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["7e2c7f"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["fd109d"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["49f0ee"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["090279"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["3eb346"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["bd56b3"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["728ceb"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["1518b6"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["c7a443"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["51f916"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["ef1b89"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["e40491"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["b1073f"]={creator="t5s2",display="T5S2",type="comp",eligible=true},["b7205e"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["e5be08"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["7f0d9e"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["01ca21"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["2929f5"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["cc45b2"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["ac68ae"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["9a1cb2"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["952278"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["763b3a"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["b062ba"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["447426"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["8e9f5b"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["1eee89"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["48d805"]={creator="lct1",display="LCT-Pack-1",type="comp",eligible=true},["9f7e72"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["413840"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["e953b8"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["57c176"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["5f13c8"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["da0cfa"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["0e04f3"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["2ec366"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["c88d77"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["c5c785"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["d38c4a"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["320302"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["bb31b6"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["d90245"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["a25c0d"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["3f6f41"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["e7418b"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["6d425d"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["13a610"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["7db140"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["f87054"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["64782f"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["534d7c"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["990d13"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["9faf4d"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["31680f"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["378f44"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["b369f2"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["40fdcb"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["a8ea5a"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["3ef8e8"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["3910d8"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["881c35"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["18bea0"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["480b13"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["264eae"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["eeee76"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["26e994"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["95dd29"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["69a514"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["fd0877"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["1c5646"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["ce7518"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["f4ebc8"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["74288f"]={creator="battlemaster_bttf_ruins",display="Battlemaster - BTTF Ruins",type="comp",eligible=true},["cd39b6"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["8c5d4d"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["80bfac"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["a9b84e"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["0505ec"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["df7f0b"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["835b0b"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["81fa8b"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["7b1f6e"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["8623eb"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["c7e6f2"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["2a1ec7"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["10e710"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["95dee9"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["539fce"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["e62901"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["5f769c"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["029fd2"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["ec9c0c"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["a1f79d"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["2389b0"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["0ef0fc"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["146724"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["8d100a"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["cd34a3"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["f75cd9"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["b4d977"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["0b805b"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["a25194"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["378d01"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["e0684f"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["a7b12e"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["e225b5"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["405536"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["b6c65d"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["5e6152"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["e273d7"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["677866"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["7c3bf0"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["3ea7a7"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["ea791d"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["b756e2"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["b30171"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["817dd1"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["a69f72"]={creator="battlemaster_armageddon_desert",display="Battlemaster - Desert",type="comp",eligible=true},["cd7de1"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["57029b"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["ea252c"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["f606fe"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["c35129"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["aa0107"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["d0adc5"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["93ef0b"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["6863a8"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["5ba4a2"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["b9e38c"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["79360d"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["7c6cfe"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["98039d"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["60047b"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["b07cc4"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["765ace"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["cb7a61"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["ab7fa6"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["00d52b"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["add2c0"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["af0b15"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["906185"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["bc8b66"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["d75669"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["a20f65"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["02000b"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["2b6dd0"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["901373"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["4e759a"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["945fe9"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["c91180"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["c51558"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["5475f9"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["5b4e60"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["a4c19b"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["92f8e3"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["895b18"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["4fc8e8"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["ab94c4"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["71cc47"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["debdc6"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["5858eb"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["2f035a"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true},["daa11f"]={creator="battlemaster_bttf",display="BTTF",type="comp",eligible=true}} -- @@MAP_INDEX@@ LOCKED_IN_SECONDARIES = 0 packSelected = nil missionPackData = { { id = MISSION_PACK_PARIAH_NEXUS, name = "Chapter Approved 2025", primaryDeck_GUID = "d26e9c", fixedSecondaryDeck_GUID = "b55459", redSecondaryDeck_GUID = "8104e1", blueSecondaryDeck_GUID = "994347", }, } turnOrder = {} nonPlaying = {"White", "Brown","Orange","Yellow","Green","Teal","Purple","Pink"} allowMenu = true allowAutoSeat = true redPlayerID = "" bluePlayerID = "" redDiceSpawnTime = "fast" blueDiceSpawnTime = "fast" -- storage for the latest edits feedbackName = "" feedbackEmail = "" feedbackType = "" feedbackLike = "Yes" feedbackMessage = "" -- callback for the Name field function onNameChanged(player, newText) feedbackName = newText end -- callback for the Email field function onEmailChanged(player, newText) feedbackEmail = newText end -- callback for the Type dropdown function onTypeChanged(player, newValue) feedbackType = newValue end -- callback for the Like dropdown function onLikeChanged(player, newValue) feedbackLike = newValue end -- callback for the Message field function onMessageChanged(player, newText) feedbackMessage = newText end local monitoredID = "5c328f" -- Original injection detector local monitoredID2 = "899f22" -- Malicious script detected object local warningInterval local pollInterval local detectedMissing = false -- Function to check if the objects exist in the game function checkForMonitoredID() local obj = getObjectFromGUID(monitoredID) local obj2 = getObjectFromGUID(monitoredID2) if obj2 ~= nil then -- If monitoredID2 is found, it means a malicious script was detected if not detectedMissing then detectedMissing = true broadcastToAll("Reminder!!! A infected object has been loaded.... please leave the game and clean all objects.", "Red") -- Stop further polling stopPolling() end elseif obj == nil then -- If monitoredID is missing, it means the detector was deleted, possible cheating if not detectedMissing then detectedMissing = true broadcastToAll("Warning: Somebody is cheating!", "Red") -- Start issuing warnings every 10 seconds warningInterval = Wait.time(function() broadcastToAll("Warning: Somebody is cheating!", "Red") end, 10, -1) -- Stop further polling stopPolling() end end end -- Start polling every 30 seconds function startPolling() checkForMonitoredID() -- Initial check immediately pollInterval = Wait.time(checkForMonitoredID, 30, -1) end -- Stop polling when needed function stopPolling() if pollInterval ~= nil then Wait.stop(pollInterval) pollInterval = nil end if warningInterval ~= nil then Wait.stop(warningInterval) warningInterval = nil end end function onSave() redSecondaryDeckGUID = nil blueSecondaryDeckGUID = nil if packSelected then redSecondaryDeckGUID = missionPackData[packSelected].redSecondaryDeck_GUID blueSecondaryDeckGUID = missionPackData[packSelected].blueSecondaryDeck_GUID end saved_data = JSON.encode({ svredPlayerID = redPlayerID, svbluePlayerID = bluePlayerID, svPackSelected = packSelected, svRedSecondaryDeckGUID = redSecondaryDeckGUID, svBlueSecondaryDeckGUID = blueSecondaryDeckGUID, svPlayerHudSettings = playerHudSettings, svVersion = GAME_VERSION }) --saved_data = "" return saved_data end -- Bare patch number (no leading "v") for display, e.g. "v1.0.0" -> "1.0.0". function patchNumber() return (tostring(GAME_PATCH):gsub("^v", "")) end -- Short chat banner on load. A test build announces itself; a release build -- just states its patch. Patch notes live on the splash overlay, not chat. function announceVersion() if GAME_VERSION == "test" then printToAll("LCT - TEST BUILD (Patch " .. patchNumber() .. ")", "Yellow") else printToAll("LCT - Patch " .. patchNumber(), "Yellow") end end -- Push the latest patch number + notes (injected from CHANGELOG.md) into the -- splash intro overlay. Runs on every load so the menu always reflects the -- build's CHANGELOG, regardless of test/release. function updateSplashOverlay() Global.UI.setAttribute("SplashPatch", "text", "Patch " .. patchNumber()) if GAME_CHANGELOG and #GAME_CHANGELOG > 0 then local lines = {} for _, note in ipairs(GAME_CHANGELOG) do lines[#lines + 1] = "* " .. note end Global.UI.setValue("SplashChangelogText", table.concat(lines, "\n")) end end function updateTeamModel(obj, team) local teamModels = Global.getTable("teamModels") or { Red = {}, Blue = {} } -- Remove the object's GUID from any team it might already be in. for _, t in ipairs({"Red", "Blue"}) do for i = #teamModels[t], 1, -1 do if teamModels[t][i] == obj.getGUID() then table.remove(teamModels[t], i) end end end -- Add the object to the proper team. table.insert(teamModels[team], obj.getGUID()) Global.setTable("teamModels", teamModels) end function tagSelectedAsRed(playerColor, target) local objs = Player[playerColor].getSelectedObjects() for _, obj in ipairs(objs) do obj.setGMNotes("Red") updateTeamModel(obj, "Red") end printToColor("Tagged selected objects as Red", playerColor, "Red") end function tagSelectedAsBlue(playerColor, target) local objs = Player[playerColor].getSelectedObjects() for _, obj in ipairs(objs) do obj.setGMNotes("Blue") updateTeamModel(obj, "Blue") end printToColor("Tagged selected objects as Blue", playerColor, "Blue") end function hideMissionDeckUntilSelected(deckGUID) local deck = getObjectFromGUID(deckGUID) if not deck then return end deck.setPosition(hiddenMissionDeckPosition) deck.locked = true deck.tooltip = false deck.interactable = false end function activateMissionDeck(deckGUID, position, name) local deck = getObjectFromGUID(deckGUID) if not deck then return nil end deck.setPosition(position) deck.setRotation({0, 270, 180}) if name then deck.setName(name) end deck.locked = false deck.tooltip = true deck.interactable = true return deck end function destroyObjectByGUID(guid) local obj = getObjectFromGUID(guid) if obj then destroyObject(obj) end end function onObjectDestroy(obj) if not obj or not obj.getGUID then return end -- Dice can never carry a yellowscribe (ymc-) card, so skip the sweep: it -- starts with a full Global.UI.getXmlTable() marshal, and mass dice -- destruction (vortex, Clear Mat, bag entry) would pay it once per die. if obj.tag == "Dice" then return end removeYellowscribeGlobalCards(obj.getGUID()) end function onLoad(saved_data) --addHotkey("Tag Selected as Red", tagSelectedAsRed) --addHotkey("Tag Selected as Blue", tagSelectedAsBlue) -- Global UI was just (re)built from XML, so any scoreboard cache from a prior -- script instance is stale. (The cache is normally empty on a fresh load; this -- also covers a yellowscribe setXmlTable rebuild that happens during load.) invalidateScoringCache() removeYellowscribeGlobalCards() Turns.enable=false --- load vars from saved if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) redPlayerID = loaded_data.svredPlayerID bluePlayerID = loaded_data.svbluePlayerID packSelected = loaded_data.svPackSelected playerHudSettings = loaded_data.svPlayerHudSettings or playerHudSettings ensureHudSettings("Red") ensureHudSettings("Blue") if packSelected then missionPackData[packSelected].redSecondaryDeck_GUID = loaded_data.svRedSecondaryDeckGUID or edition11RedSecondaryDeck_GUID missionPackData[packSelected].blueSecondaryDeck_GUID = loaded_data.svBlueSecondaryDeckGUID or edition11BlueSecondaryDeck_GUID end else redPlayerID = "" bluePlayerID = "" packSelected = nil ensureHudSettings("Red") ensureHudSettings("Blue") end if not packSelected then hideMissionDeckUntilSelected(edition11RedSecondaryDeck_GUID) hideMissionDeckUntilSelected(edition11BlueSecondaryDeck_GUID) end ---- end loading -- Refresh the splash overlay from this build's CHANGELOG, and drop a short -- version banner in chat. Wait.frames(updateSplashOverlay, 1) Wait.time(announceVersion, 3) if allowMenu then if allowAutoSeat and redPlayerID ~= "" and bluePlayerID ~= "" then -- if the game is not started dont autoseat autoSeatAll() else Global.UI.setAttribute("main", "active", "true") local presentPersons= Player.getPlayers() for i, person in ipairs(presentPersons) do person.team="Diamonds" end presentPersons= Player.getSpectators() for i, person in ipairs(presentPersons) do person.team="Diamonds" end showHideRedBlueBtn() end else Global.UI.setAttribute("main", "active", "false") end applyHudPreferences() --startPolling() Wait.time(function() -- Stat Helper: move to active positions local redStatHelper = getObjectFromGUID("7c7c20") if redStatHelper then redStatHelper.setPosition({-4.078736, 0.87, 34.7377625}) end local blueStatHelper = getObjectFromGUID("b7ac7a") if blueStatHelper then blueStatHelper.setPosition({4.078736, 0.86, -34.7377625}) end -- Vortex: raise to Y=0.6 local redVortex = getObjectFromGUID(RedVortexGUID) if redVortex then local pos = redVortex.getPosition() redVortex.setPosition({pos.x, 0.6, pos.z}) end local blueVortex = getObjectFromGUID(BlueVortexGUID) if blueVortex then local pos = blueVortex.getPosition() blueVortex.setPosition({pos.x, 0.6, pos.z}) end -- Token counters: keep them tucked just inside each side's vortex. local redCounter = getObjectFromGUID(RedTokenCounterGUID) if redCounter and redVortex then local pos = redVortex.getPosition() redCounter.setPosition({pos.x - 4.52, 1.12, pos.z}) end local blueCounter = getObjectFromGUID(BlueTokenCounterGUID) if blueCounter and blueVortex then local pos = blueVortex.getPosition() blueCounter.setPosition({pos.x + 4.52, 1.12, pos.z}) end refreshRedSecondaryNames() refreshBlueSecondaryNames() end, 2) -- Shared debug panel: the DebugTools XML panel ships hidden in every build; -- reveal it only on --test (DEBUG) builds. if DEBUG == true then Global.UI.setAttribute("DebugTools", "active", "true") end -- Object scripts can load before Global (TTS gives no ordering guarantee, and -- a guest joining mid-load widens the window), so their load-time -- Global.getVar reads can see nil. They gate those reads on this flag via a -- local whenGlobalReady helper (ported from upstream comp-ftc PR #62). FTC_Global_Ready = true end -- ── SHARED DEBUG PANEL (--test builds only) ────────────────────────────────── -- Every debug tool lives on the DebugTools screen-space XML panel (ftc_base_ui.xml), -- hidden by default and revealed from onLoad only on DEBUG builds. The panel routes -- its onClick/onValueChanged handlers to the functions below (Global owns the UI, so -- no per-object script is needed). XML callbacks pass (player, value, id), so the -- handlers take the Player object and read its .color. BATTLEMASTER_SPAWNER_GUID = "b4d10a" BATTLEMASTER_BTTF_THEME_ID = "tts-theme-grimdark-calibrated-v1" BATTLEMASTER_BTTF_RUINS_THEME_ID = "tts-theme-0c82349e-6c8d-4ef6-95ba-4ee3c2d6a5a5" BATTLEMASTER_ARMAGEDDON_DESERT_THEME_ID = "tts-theme-6c414c7a-9827-48cf-a89e-aa8ddff66491" -- Where the "Dump selected GUIDs" tile lands, and its name so stale dumps can be -- cleared before spawning a fresh one (see debugDumpSelectedGuids). GUID_DUMP_POS = {68.0, 1.1, -78} GUID_DUMP_NAME = "Selected GUIDs" -- "Set mat from URL" row: cache keystrokes, apply the cached URL on demand. The -- swap itself is owned by startMenu (it holds the map mat + reload/flag logic and -- re-checks DEBUG). onDebugMatApply fires from both the button (onClick) and Enter -- (onEndEdit); startMenu's setMatTextureFromUrl no-ops if the URL is unchanged. debugMatUrlPending = "" function cacheDebugMatUrl(player, value, id) debugMatUrlPending = value or "" end function onDebugMatApply(player, value, id) local menu = getObjectFromGUID(startMenu_GUID) if menu then menu.call("setMatTextureFromUrl", debugMatUrlPending) end end -- Corner button on the DebugTools panel. Instead of fully hiding the panel (which -- can't be reopened until the next reload, since onLoad is the only place it's shown), -- this collapses it to its title bar: the DebugToolsBody layout is deactivated and the -- panel shrinks to header height. Clicking again restores it. Glyph/tooltip toggle too. debugToolsMinimized = false function minimizeDebugTools(player, value, id) debugToolsMinimized = not debugToolsMinimized if debugToolsMinimized then Global.UI.setAttribute("DebugToolsBody", "active", "false") Global.UI.setAttribute("DebugTools", "height", "84") Global.UI.setAttribute("DebugToolsMinBtn", "text", "+") Global.UI.setAttribute("DebugToolsMinBtn", "tooltip", "Restore") else Global.UI.setAttribute("DebugToolsBody", "active", "true") Global.UI.setAttribute("DebugTools", "height", "740") Global.UI.setAttribute("DebugToolsMinBtn", "text", "—") Global.UI.setAttribute("DebugToolsMinBtn", "tooltip", "Minimize") end end function debugPerfScanRed(player) local playerColor = player and player.color local mat = getObjectFromGUID(redCustomDiceMat_GUID) if not mat then printToColor("[DEBUG] Red dice mat not found.", playerColor, {1, 0.3, 0.3}); return end mat.call("runDiceScanBenchmark", {color = playerColor}) end function debugPerfScanBlue(player) local playerColor = player and player.color local mat = getObjectFromGUID(blueCustomDiceMat_GUID) if not mat then printToColor("[DEBUG] Blue dice mat not found.", playerColor, {1, 0.3, 0.3}); return end mat.call("runDiceScanBenchmark", {color = playerColor}) end function debugRngTest(player) local playerColor = player and player.color local roller = getObjectFromGUID(redCustomDiceRoller_GUID) if not roller then printToColor("[DEBUG] Red dice roller not found.", playerColor, {1, 0.3, 0.3}); return end roller.call("debugRngCheck") end function debugPopulateBattlemasterCache(player) local playerColor = player and player.color if DEBUG ~= true then return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if not spawner then printToColor("[DEBUG] Battlemaster spawner not found.", playerColor, {1, 0.3, 0.3}) return end printToColor("[DEBUG] Battlemaster cache population started. Save the mod/table after it completes to bake the cache.", playerColor, {0.55, 0.85, 1}) spawner.call("debugPopulateFullCache", {playerColor = playerColor}) end function debugPopulateBattlemasterBttfRuinsCache(player) local playerColor = player and player.color if DEBUG ~= true then return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if not spawner then printToColor("[DEBUG] Battlemaster spawner not found.", playerColor, {1, 0.3, 0.3}) return end printToColor("[DEBUG] Battlemaster BTTF Ruins cache population started. Save the mod/table after it completes.", playerColor, {0.78, 0.72, 1}) spawner.call("debugPopulateFullCache", {playerColor = playerColor, themeId = BATTLEMASTER_BTTF_RUINS_THEME_ID, themeName = "BTTF Ruins"}) end function debugPopulateBattlemasterBttfCache(player) local playerColor = player and player.color if DEBUG ~= true then return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if not spawner then printToColor("[DEBUG] Battlemaster spawner not found.", playerColor, {1, 0.3, 0.3}) return end printToColor("[DEBUG] Battlemaster BTTF cache population started. Save the mod/table after it completes.", playerColor, {0.55, 0.85, 1}) spawner.call("debugPopulateFullCache", {playerColor = playerColor, themeId = BATTLEMASTER_BTTF_THEME_ID, themeName = "BTTF"}) end function debugPopulateBattlemasterArmageddonDesertCache(player) local playerColor = player and player.color if DEBUG ~= true then return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if not spawner then printToColor("[DEBUG] Battlemaster spawner not found.", playerColor, {1, 0.3, 0.3}) return end printToColor("[DEBUG] Battlemaster Armageddon Desert cache population started. Save the mod/table after it completes.", playerColor, {1, 0.78, 0.42}) spawner.call("debugPopulateFullCache", {playerColor = playerColor, themeId = BATTLEMASTER_ARMAGEDDON_DESERT_THEME_ID, themeName = "armageddon desert"}) end -- "All 3" button: runs the Ruins/Desert/BTTF populates back to back on the -- spawner, each archived under its theme id (see BM_THEME_ARCHIVES in -- battlemasterDynamicSpawner.ttslua), so a single save afterward carries every -- theme's import data and import_battlemaster_static_maps.py --all-themes can -- do all three imports without three separate populate/save round trips. function debugPopulateBattlemasterAllCache(player) local playerColor = player and player.color if DEBUG ~= true then return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if not spawner then printToColor("[DEBUG] Battlemaster spawner not found.", playerColor, {1, 0.3, 0.3}) return end printToColor("[DEBUG] Battlemaster all-theme cache population started (Ruins, Desert, BTTF). Save the mod/table after it completes.", playerColor, {0.65, 0.9, 1}) spawner.call("debugPopulateAllThemes", { playerColor = playerColor, themes = { {id = BATTLEMASTER_BTTF_RUINS_THEME_ID, name = "BTTF Ruins"}, {id = BATTLEMASTER_ARMAGEDDON_DESERT_THEME_ID, name = "armageddon desert"}, {id = BATTLEMASTER_BTTF_THEME_ID, name = "BTTF"}, }, }) end function debugCheckBattlemasterCache(player) local playerColor = player and player.color if DEBUG ~= true then return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if not spawner then printToColor("[DEBUG] Battlemaster spawner not found.", playerColor, {1, 0.3, 0.3}) return end spawner.call("debugReportCacheState", {playerColor = playerColor}) end -- Collect the GUIDs of whatever the clicking player has selected and bake them, -- one per line, into a spawned tile's description. Chat_copy is unreliable on -- Linux, so this gives a copyable text field instead. A previous dump is cleared -- first so the tiles don't accumulate across clicks. function debugDumpSelectedGuids(player) local playerColor = player and player.color local selected = player and player.getSelectedObjects() or {} if #selected == 0 then printToColor("[DEBUG] No objects selected. Select objects first, then click.", playerColor, {1, 0.3, 0.3}) return end local guids = {} for _, obj in ipairs(selected) do guids[#guids + 1] = obj.getGUID() end local text = table.concat(guids, "\n") for _, o in ipairs(getAllObjects()) do if o.getName() == GUID_DUMP_NAME then o.destroy() end end spawnObject({ type = "BlockSquare", position = GUID_DUMP_POS, rotation = {0, 0, 0}, scale = {1.6, 0.2, 1.6}, sound = false, callback_function = function(tile) tile.setName(GUID_DUMP_NAME) tile.setDescription(text) tile.setColorTint({0.10, 0.18, 0.10}) tile.setLock(true) end, }) printToColor(string.format("[DEBUG] Dumped %d GUID(s) into the '%s' tile description.", #guids, GUID_DUMP_NAME), playerColor, {0.4, 1, 0.4}) end function setMissionPack(params) packSelected = params.pack local packData = missionPackData[packSelected] if not packData then return end packData.redSecondaryDeck_GUID = params.redSecondaryDeck_GUID or edition11RedSecondaryDeck_GUID packData.blueSecondaryDeck_GUID = params.blueSecondaryDeck_GUID or edition11BlueSecondaryDeck_GUID end function useEdition11SecondaryDecks() if not packSelected or not missionPackData[packSelected] then return end missionPackData[packSelected].redSecondaryDeck_GUID = edition11RedSecondaryDeck_GUID missionPackData[packSelected].blueSecondaryDeck_GUID = edition11BlueSecondaryDeck_GUID activateMissionDeck(edition11RedSecondaryDeck_GUID, redSecondaryDeckPosition, "Red secondary mission cards") activateMissionDeck(edition11BlueSecondaryDeck_GUID, blueSecondaryDeckPosition, "Blue secondary mission cards") end function autoSeatPerson(_person) if _person.steam_id == redPlayerID then if Player.Red.seated then Player.Red.changeColor("Grey") end _person.changeColor("Red") _person.team="None" return end if _person.steam_id == bluePlayerID then if Player.Blue.seated then Player.Blue.changeColor("Grey") end _person.changeColor("Blue") _person.team="None" return end --_person.changeColor("Grey") _person.team="None" end function autoSeatGroup(persons) for i, person in ipairs(persons) do autoSeatPerson(person) end end function autoSeatAll() if redPlayerID=="" or bluePlayerID=="" then -- if the game is not started dont autoseat return end local presents = Player.getPlayers() autoSeatGroup(presents) presents = Player.getSpectators() autoSeatGroup(presents) end function recordPlayers() redPlayerID = Player.Red.steam_id bluePlayerID = Player.Blue.steam_id end function onPlayerChangeColor(player_color) promotePlayers() --demotePlayers() -- RIC showHideRedBlueBtn() local scoresheet = getObjectFromGUID(scoresheet_GUID) if scoresheet then scoresheet.call("syncPlayerTitlesFromSeats") end end function onPlayerConnect(player_id) if allowMenu then if allowAutoSeat and redPlayerID ~= "" and bluePlayerID ~= "" then -- if the game is not started dont autoseat autoSeatPerson(player_id) else player_id.team="Diamonds" end end end function promotePlayers() local colors={"Red", "Blue", "Orange", "Yellow", "Purple", "Teal"} for i, color in ipairs(colors) do if Player[color].seated and Player[color].host == false and not Player[color].promoted then Player[color].promote() end end end function demotePlayers() for i, color in ipairs(nonPlaying) do if Player[color].seated and Player[color].host == false then Player[color].promote(false) end end local spectators=Player.getSpectators() for i, person in ipairs(spectators) do if person.host == false then person.promote(false) end end end function showHideRedBlueBtn() if allowMenu then if Player.Red.seated == true then Global.UI.setAttribute("redBtn", "active", "false") else Global.UI.setAttribute("redBtn", "active", "true") end if Player.Blue.seated == true then Global.UI.setAttribute("blueBtn", "active", "false") else Global.UI.setAttribute("blueBtn", "active", "true") end end end function setViewForPlayer(player, color) if color=="Grey" then return end local pos= {0,0,0} if color == "Red" then pos = getObjectFromGUID(redDiceMat_GUID).getPosition() end if color == "Blue" then pos = getObjectFromGUID(blueDiceMat_GUID).getPosition() end player.lookAt({ position = pos, pitch = 25, yaw = 180, distance = 20, }) end function onScriptingButtonDown(index, playerColor) if index == 1 then -- If '=' is bound to scripting button 1 local diceMat = getObjectFromGUID(redCustomDiceMat_GUID) diceMat.call("changeSustainedHitsThreshold", { 1 }) elseif index == 2 then -- If '-' is bound to scripting button 2 local diceMat = getObjectFromGUID(redCustomDiceMat_GUID) diceMat.call("changeSustainedHitsThreshold", { -1 }) end end function placeToColor(player, color) player.changeColor(color) player.team="None" --setViewForPlayer(player, color) --bugged end function placeToRed(player, value, id) placeToColor(player, "Red") --player.changeColor("Red") --player.team="None" end function placeToBlue(player, value, id) placeToColor(player, "Blue") --player.changeColor("Blue") --player.team="None" end function placeToGray(player, value, id) placeToColor(player, "Grey") local selectPariahNexus = Global.getVar("selectPariahNexus") if selectPariahNexus then selectPariahNexus() else print("Error: selectPariahNexus function not found!") end end function placeToSpectator(player, value, id) placeToColor(player, "Grey") end function closeMenu(player, value, id) player.team="None" end backPosition={{0,0,0},{0,0,0},{0,0,0},{0,0,0}} function getSecondaryCardZones(zoneGuids) local zones = {} for index, guid in ipairs(zoneGuids) do local zone = getObjectFromGUID(guid) if zone then table.insert(zones, { index = index, guid = guid, zone = zone, }) end end return zones end -- zone.getObjects() can miss cards that were script-moved into a slot and -- then locked, especially during/after smooth movement. Combine the zone query -- with a positional scan (same approach as the mission primary slots) so -- occupancy, labels, discard and recycle all see cards that are visibly there. function getCardsInSecondarySlot(zone) if not zone then return {} end local found = {} local seen = {} local function add(obj) if obj and (obj.tag == "Card" or obj.tag == "Deck" or obj.type == "Card" or obj.type == "Deck") and not seen[obj.getGUID()] then seen[obj.getGUID()] = true found[#found + 1] = obj end end for _, obj in ipairs(zone.getObjects()) do add(obj) end local bounds = zone.getBounds() local center = bounds.center local halfX = bounds.size.x / 2 local halfZ = bounds.size.z / 2 for _, obj in ipairs(getAllObjects()) do local pos = obj.getPosition() if math.abs(pos.x - center.x) <= halfX and math.abs(pos.z - center.z) <= halfZ and pos.y > 0.5 and pos.y < 3.0 then add(obj) end end return found end function getSecondaryCardZoneByIndex(color, index) local zoneGuids = (color == "Red") and redSecondaryCardZone_GUIDs or blueSecondaryCardZone_GUIDs local zoneGuid = zoneGuids[index] if not zoneGuid then return nil end return getObjectFromGUID(zoneGuid) end function getSecondarySlotCardPosition(zone) local pos = zone.getPosition() pos.y = pos.y + 0.15 return pos end function getSecondaryDeckByColor(color) if not packSelected or not missionPackData[packSelected] then return nil end local deckGuidKey = (color == "Red") and "redSecondaryDeck_GUID" or "blueSecondaryDeck_GUID" return getObjectFromGUID(missionPackData[packSelected][deckGuidKey]) end function getSecondaryDiscardPosition(color) if color == "Red" then return {x = 62.54, y = 0.98, z = 16.60} end return {x = 62.54, y = 0.98, z = -16.60} end function refreshSecondaryNamesForColor(color) if color == "Red" then refreshRedSecondaryNames() else refreshBlueSecondaryNames() end end function isSecondaryCardZone(zone, zoneGuids) if not zone then return false end local zoneGuid = zone.getGUID() for _, guid in ipairs(zoneGuids) do if zoneGuid == guid then return true end end return false end function isBlueSecondaryCardZone(zone) return isSecondaryCardZone(zone, blueSecondaryCardZone_GUIDs) end function isRedSecondaryCardZone(zone) return isSecondaryCardZone(zone, redSecondaryCardZone_GUIDs) end function getSecondaryCardName(zone) if not zone then return nil end for _, object in ipairs(getCardsInSecondarySlot(zone)) do local name = object.getName() if name and name ~= "" then return name end end return nil end function getSecondaryCardNameForColor(color, index, zone) if not zone then return nil end for _, object in ipairs(getCardsInSecondarySlotForColor(color, index, zone)) do local name = object.getName() if name and name ~= "" then return name end end return nil end function setSecondaryName(nameGuids, index, cardName) local labelObj = getObjectFromGUID(nameGuids[index]) if not labelObj then return end local text = tostring(index) .. "." if cardName and cardName ~= "" then text = text .. " " .. cardName end labelObj.setValue(text) end function setBlueSecondaryName(index, cardName) setSecondaryName(blueSecondaryName_GUIDs, index, cardName) end function setRedSecondaryName(index, cardName) setSecondaryName(redSecondaryName_GUIDs, index, cardName) end function refreshSecondaryNames(zoneGuids, nameGuids, setter, color) for index = 1, #nameGuids do local zoneGuid = zoneGuids[index] local zone = getObjectFromGUID(zoneGuid) local cardName = getSecondaryCardNameForColor(color, index, zone) setter(index, cardName) end end function refreshBlueSecondaryNames() refreshSecondaryNames(blueSecondaryCardZone_GUIDs, blueSecondaryName_GUIDs, setBlueSecondaryName, "Blue") end function refreshRedSecondaryNames() refreshSecondaryNames(redSecondaryCardZone_GUIDs, redSecondaryName_GUIDs, setRedSecondaryName, "Red") end function refreshBlueSecondaryNamesSoon() Wait.time(refreshBlueSecondaryNames, 0.25) end function refreshRedSecondaryNamesSoon() Wait.time(refreshRedSecondaryNames, 0.25) end function getSecondaryZoneColorAndIndex(zone) if not zone then return nil, nil end local zoneGuid = zone.getGUID() for index, guid in ipairs(redSecondaryCardZone_GUIDs) do if zoneGuid == guid then return "Red", index end end for index, guid in ipairs(blueSecondaryCardZone_GUIDs) do if zoneGuid == guid then return "Blue", index end end return nil, nil end function onObjectEnterScriptingZone(zone, object) local color, index = getSecondaryZoneColorAndIndex(zone) if color and isSecondaryCardObject(object) then assignSecondarySlotCard(color, index, object) if color == "Red" then refreshRedSecondaryNamesSoon() else refreshBlueSecondaryNamesSoon() end end end function onObjectLeaveScriptingZone(zone, object) local color, index = getSecondaryZoneColorAndIndex(zone) if not color or not isSecondaryCardObject(object) then return end local assignedCard = getAssignedSecondarySlotCard(color, index) if assignedCard == object then clearSecondarySlotAssignment(color, index) end if color == "Red" then refreshRedSecondaryNamesSoon() else refreshBlueSecondaryNamesSoon() end end secondaryDrawBusy = {Red = false, Blue = false} secondarySlotAssignments = {Red = {}, Blue = {}} function isSecondaryCardObject(obj) return obj and (obj.tag == "Card" or obj.tag == "Deck" or obj.type == "Card" or obj.type == "Deck") end function addUniqueSecondaryCard(cards, seen, obj) if not isSecondaryCardObject(obj) then return end local guid = obj.getGUID and obj.getGUID() or nil if guid and seen[guid] then return end if guid then seen[guid] = true end cards[#cards + 1] = obj end function assignSecondarySlotCard(color, index, card) if not color or not index or not card or not card.getGUID then return end secondarySlotAssignments[color] = secondarySlotAssignments[color] or {} secondarySlotAssignments[color][index] = card.getGUID() end function clearSecondarySlotAssignment(color, index) if secondarySlotAssignments[color] then secondarySlotAssignments[color][index] = nil end end function getAssignedSecondarySlotCard(color, index) local assignments = secondarySlotAssignments[color] local guid = assignments and assignments[index] or nil if not guid then return nil end local card = getObjectFromGUID(guid) if isSecondaryCardObject(card) then return card end clearSecondarySlotAssignment(color, index) return nil end function getCardsInSecondarySlotForColor(color, index, zone) local cards = {} local seen = {} addUniqueSecondaryCard(cards, seen, getAssignedSecondarySlotCard(color, index)) for _, card in ipairs(getCardsInSecondarySlot(zone)) do addUniqueSecondaryCard(cards, seen, card) end return cards end function clearSecondarySlotAssignmentsForColor(color) secondarySlotAssignments[color] = {} end function drawSecondaryToFirstOpenSlot(params) if secondaryDrawBusy[params.color] then broadcastToColor("Hold on - previous draw still in progress.", params.color, "Yellow") return end if not packSelected or not missionPackData[packSelected] then broadcastToColor("Select a mission pack before drawing secondary cards.", params.color, "Yellow") return end local deckObj = getObjectFromGUID(missionPackData[packSelected][params.deckGuidKey]) if not deckObj then broadcastToColor(params.deckMissingMessage, params.color, "Red") return end -- Collect up to maxCards open slots upfront (defaults to 2). Smooth takeObject -- means the first card hasn't physically landed when the second draw fires, so -- re-querying for "first open" would return the same slot twice. local maxCards = params.maxCards or 2 local openSlots = {} for _, slot in ipairs(getSecondaryCardZones(params.zoneGuids)) do if #getCardsInSecondarySlotForColor(params.color, slot.index, slot.zone) == 0 then openSlots[#openSlots + 1] = slot if #openSlots >= maxCards then break end end end if #openSlots == 0 then broadcastToColor(params.fullMessage, params.color, "Yellow") return end secondaryDrawBusy[params.color] = true deckObj.shuffle() for _, slot in ipairs(openSlots) do local slotIndex = slot.index deckObj.takeObject({ position = getSecondarySlotCardPosition(slot.zone), rotation = {x = 0, y = 270, z = 180}, flip = true, smooth = true, callback_function = function(card) assignSecondarySlotCard(params.color, slotIndex, card) Wait.frames(function() card.locked = true params.refreshNames() end) end }) end Wait.time(function() secondaryDrawBusy[params.color] = false end, 1.5) end function drawBlueSecondaryToFirstOpenSlot() drawSecondaryToFirstOpenSlot({ color = "Blue", zoneGuids = blueSecondaryCardZone_GUIDs, deckGuidKey = "blueSecondaryDeck_GUID", refreshNames = refreshBlueSecondaryNames, fullMessage = "Blue already has 8 active secondary cards.", deckMissingMessage = "Blue secondary deck could not be found.", }) end function drawRedSecondaryToFirstOpenSlot() drawSecondaryToFirstOpenSlot({ color = "Red", zoneGuids = redSecondaryCardZone_GUIDs, deckGuidKey = "redSecondaryDeck_GUID", refreshNames = refreshRedSecondaryNames, fullMessage = "Red already has 8 active secondary cards.", deckMissingMessage = "Red secondary deck could not be found.", }) end function drawBlueSecondaryToFirstOpenSlotSingle() drawSecondaryToFirstOpenSlot({ color = "Blue", zoneGuids = blueSecondaryCardZone_GUIDs, deckGuidKey = "blueSecondaryDeck_GUID", refreshNames = refreshBlueSecondaryNames, fullMessage = "Blue already has 8 active secondary cards.", deckMissingMessage = "Blue secondary deck could not be found.", maxCards = 1, }) end function drawRedSecondaryToFirstOpenSlotSingle() drawSecondaryToFirstOpenSlot({ color = "Red", zoneGuids = redSecondaryCardZone_GUIDs, deckGuidKey = "redSecondaryDeck_GUID", refreshNames = refreshRedSecondaryNames, fullMessage = "Red already has 8 active secondary cards.", deckMissingMessage = "Red secondary deck could not be found.", maxCards = 1, }) end function sortSecondarySlots(params) local color = params.color if secondaryDrawBusy[color] then broadcastToColor("Hold on - secondary cards are still moving.", color, "Yellow") return end local zoneGuids = (color == "Red") and redSecondaryCardZone_GUIDs or blueSecondaryCardZone_GUIDs local zones = getSecondaryCardZones(zoneGuids) local occupiedSlots = {} for _, slot in ipairs(zones) do local cards = getCardsInSecondarySlotForColor(color, slot.index, slot.zone) if #cards > 0 then occupiedSlots[#occupiedSlots + 1] = cards end end local movedAny = false secondaryDrawBusy[color] = true clearSecondarySlotAssignmentsForColor(color) for targetIndex, cards in ipairs(occupiedSlots) do local targetZone = zones[targetIndex] and zones[targetIndex].zone if targetZone then local targetPosition = getSecondarySlotCardPosition(targetZone) for _, card in ipairs(cards) do local position = card.getPosition() if math.abs(position.x - targetPosition.x) > 0.1 or math.abs(position.z - targetPosition.z) > 0.1 then card.locked = false card.setPositionSmooth(targetPosition, false, true) movedAny = true end assignSecondarySlotCard(color, targetIndex, card) end end end Wait.time(function() for targetIndex = 1, #occupiedSlots do local targetZone = zones[targetIndex] and zones[targetIndex].zone for _, card in ipairs(getCardsInSecondarySlotForColor(color, targetIndex, targetZone)) do card.locked = true end end refreshSecondaryNamesForColor(color) secondaryDrawBusy[color] = false end, movedAny and 1.5 or 0.1) end function discardSecondarySlot(params) local color = params.color local zone = getSecondaryCardZoneByIndex(color, params.index) if not zone then broadcastToColor("Secondary slot could not be found.", color, "Red") return end local discardPosition = getSecondaryDiscardPosition(color) for _, object in ipairs(getCardsInSecondarySlotForColor(color, params.index, zone)) do object.locked = false object.setPositionSmooth(discardPosition) end clearSecondarySlotAssignment(color, params.index) Wait.time(function() refreshSecondaryNamesForColor(color) end, 0.25) end function recycleSecondarySlot(params) local color = params.color local zone = getSecondaryCardZoneByIndex(color, params.index) if not zone then broadcastToColor("Secondary slot could not be found.", color, "Red") return end local deckObj = getSecondaryDeckByColor(color) if not deckObj then broadcastToColor(color .. " secondary deck could not be found.", color, "Red") return end local recycledAny = false for _, object in ipairs(getCardsInSecondarySlotForColor(color, params.index, zone)) do object.locked = false deckObj = deckObj.putObject(object) or deckObj recycledAny = true end clearSecondarySlotAssignment(color, params.index) if not recycledAny then return end deckObj.shuffle() Wait.frames(function() deckObj.takeObject({ position = getSecondarySlotCardPosition(zone), rotation = {x = 0, y = 270, z = 180}, flip = true, smooth = true, callback_function = function(card) assignSecondarySlotCard(color, params.index, card) Wait.frames(function() card.locked = true refreshSecondaryNamesForColor(color) end) end }) end, 2) end LuaScriptState{"svPlayerHudSettings":{"Blue":{"clockHidden":false,"hudVisible":true,"scale":"Medium","scoringOverlayOpen":false,"settingsOpen":false},"Grey":{"clockHidden":false,"hudVisible":true,"scale":"Medium","scoringOverlayOpen":false,"settingsOpen":false},"Red":{"clockHidden":false,"hudVisible":true,"scale":"Medium","scoringOverlayOpen":false,"settingsOpen":false}},"svredPlayerID":"","svVersion":"v1.10.6"}XmlUI1  Custom Bubble Size Values:1-60. Blank or 0 clears. Battlemaster cache Custom table mat URL Name Stats Count Attacks Range 8[GUIDb7ac7aName Chip_100TransformposX P@posY?posZo^ArotXCrotY4?rotZ@+ߡ>scaleX?scaleY?scaleZ?NicknameBlue WH Stats HelperDescriptionThis is a helper for Warhammer stats made by Ricu. It will show you the stats of the weapon you select. You can also draw a range to see how far it is.GMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsLuaScriptR-- FTC-GUID: b7ac7a,7c7c20 version = "2.14.2" currentSelection = {} weaponColors = {} spawnedDiceCount = 0 savedPositions = {} spawnPosition = nil customDieData = nil useCustomTrayDice = false activeCustomTrayDiceConfig = false function onSave() return JSON.encode({ spawnPosition = spawnPosition, customDieData = customDieData }) end function onLoad(data) self.addContextMenuItem("Set Custom Dice", onSetCustomDiceClicked) self.addContextMenuItem("Reset Custom Dice", onResetCustomDiceClicked) self.addContextMenuItem("Set Spawn Position", onSetSpawnPositionClicked) self.addContextMenuItem("Reset Spawn Position", onResetSpawnPositionClicked) self.addContextMenuItem("Version", onVersionClicked) restoreSavedData(data) setupStatsHelperTopRow() end function restoreSavedData(data) if data == nil or data == "" then return end local parsedData = JSON.decode(data) if parsedData then spawnPosition = parsedData.spawnPosition customDieData = parsedData.customDieData end end function setupStatsHelperTopRow() local ui = self.UI.getXmlTable() if not ui or not ui[2] or not ui[2].children or not ui[2].children[1] then return end ui[2].children[1].children = { { tag = "Button", attributes = { text = "Show weapon stats", onClick = "onCountClicked", fontSize = "60", preferredWidth = "1800", preferredHeight = "100" } }, { tag = "Button", attributes = { text = customTrayDiceToggleText(), id = "CustomTrayDiceToggle", onClick = "toggleCustomTrayDice", fontSize = "36", preferredWidth = "450", preferredHeight = "100", color = customTrayDiceToggleColor(), textColor = "#000000" } } } self.UI.setXmlTable(ui) end function customTrayDiceToggleText() return useCustomTrayDice and "☑ Custom Dice" or "☐ Custom Dice" end function customTrayDiceToggleColor() return useCustomTrayDice and "#38C172" or "#FFFFFF" end function toggleCustomTrayDice(player, value, id) useCustomTrayDice = not useCustomTrayDice self.UI.setAttribute("CustomTrayDiceToggle", "color", customTrayDiceToggleColor()) self.UI.setAttribute("CustomTrayDiceToggle", "text", customTrayDiceToggleText()) end function getCustomTrayDiceConfig() if activeCustomTrayDiceConfig ~= false then return activeCustomTrayDiceConfig end return readCustomTrayDiceConfig() end function readCustomTrayDiceConfig() if not useCustomTrayDice then return nil end local mat = getStatsHelperCustomDiceMat() if not mat then return nil end return copyCustomTrayDiceConfig(mat.call("getStatsHelperDiceConfig")) end function copyCustomTrayDiceConfig(config) if type(config) ~= "table" then return nil end return { diceType = config.diceType, selectedColor = copyColorValue(config.selectedColor), customDiceImage = config.customDiceImage } end function copyColorValue(value) if type(value) ~= "table" then return value end return {value[1], value[2], value[3], value[4]} end function getStatsHelperCustomDiceMat() local guid if self.getGUID() == "7c7c20" then guid = Global.getVar("redCustomDiceMat_GUID") elseif self.getGUID() == "b7ac7a" then guid = Global.getVar("blueCustomDiceMat_GUID") elseif self.getPosition().z > 0 then guid = Global.getVar("redCustomDiceMat_GUID") else guid = Global.getVar("blueCustomDiceMat_GUID") end return guid and getObjectFromGUID(guid) or nil end function onResetSpawnPositionClicked() spawnPosition = nil printMessage("Spawn position reset complete!") end function onSetSpawnPositionClicked() local marker = findSpawnPositionMarker() if marker == nil then createSpawnPositionMarker() printMessage("Place the marker on the desired position and click \"Set Spawn Position\" again") else saveSpawnPosition(marker) printMessage("Spawn position successfully configured!") end end function saveSpawnPosition(marker) spawnPosition = {marker.getPosition()[1] - 1, marker.getPosition()[2], marker.getPosition()[3] - 1} marker.destruct() end function createSpawnPositionMarker() spawnObject({ type = "go_game_piece_white", position = getPositionAboveMe(), callback_function = function(obj) obj.setName("Place me on the spawn position and click \"Set Spawn\" again") obj.addTag("SpawnMarker") end }) end function findSpawnPositionMarker() local objs = getAllObjects() for i,v in ipairs(objs) do if v.hasTag("SpawnMarker") then return v end end end function onVersionClicked() print(version) end function onResetCustomDiceClicked() customDieData = nil printMessage("Custom die data reset complete!") end function onSetCustomDiceClicked() local die = findCustomDie() if die == nil then printMessage("Place your die in the white zone") else saveCustomDie(die) die.destruct() printMessage("Custom Die successfully configured!") end end function getPositionAboveMe() return self.positionToWorld({0, 3, 8}) end function findCustomDie() local cast = Physics.cast({ debug = true, direction = {0, -1, 0}, origin = getPositionAboveMe(), type = 3, size = {4, 1, 4} }) for i,v in ipairs(cast) do if v.hit_object.tag == "Dice" then return v.hit_object end end end function saveCustomDie(die) customDieData = {} customDieData.colorTint = die.getColorTint() customDieData.image = die.getCustomObject().image end function setCurrentSelection(player) if (#player.getSelectedObjects() == 0) then printMessage("Select some models first!") return false end currentSelection = player.getSelectedObjects() return true end function onCountClicked(player) cleanUpWeaponsData() if setCurrentSelection(player) then drawWeaponData(player.getSelectedObjects()) end end function cleanUpWeaponsData() drawWeaponData({}) spawnedDiceCount = 0 end function drawWeaponData(figures) drawWeaponsDataUI(createWeaponsMap(createFiguresData(figures))) end function drawWeaponsDataUI(weaponMap) local ui = self.UI.getXmlTable() ui[2].children[3].children = createWeaponTableRows(weaponMap) self.UI.setXmlTable(ui) end function createWeaponTableRows(weaponMap) local result = {} for i,v in pairs(weaponMap) do table.insert(result, createWeaponRowUI(v)) end return result end function createWeaponRowUI(weaponData) return { tag = "Row", attributes = { preferredHeight = 130 }, children = { createTextCell(weaponData.data.name), createTextCell(weaponData.data.stats), createTextCell(weaponData.count), createButtonCell(getAttackInfo(weaponData), getAttackButtonId(weaponData), "onAttackClicked", weaponData.side), createButtonCell(getRangeText(weaponData.data.range), "rangeButton_"..weaponData.data.name, "onRangeClicked"), } } end function getAttackButtonId(weaponData) local acc = weaponData.data.accuracy local str = weaponData.data.strength local ap = weaponData.data.ap local d = weaponData.data.damage return "attackButton__"..weaponData.data.name.."__"..acc.."__"..str.."__"..ap.."__"..d end function getWeaponDataFromAttackButtonId(id) local arr = splitStr(id, "__") return { name = arr[2], accuracy = arr[3], strength = arr[4], ap = arr[5], damage = arr[6] } end function getAttackInfo(weaponData) local result = ""; if weaponData.attack != 0 then result = result..tostring(weaponData.attack) end if weaponData.data.bonusAttack then local pref = weaponData.count > 1 and weaponData.count..'*' or '' return pref..weaponData.data.bonusAttack end return result end -- Lay weapon dice out using the dice mat's own bounded 5x5 grid -- the same -- contained layout the mat's +Nd6 buttons use -- instead of the old open-ended -- 20-wide march. Big batches (e.g. 27 then 48 attacks) used to overflow the mat -- in both axes and land where the mat's getDice() can no longer see them, so -- they couldn't be rolled or cleared. Anchoring on the mat keeps every die -- inside the mat's footprint. Per-weapon colour and the AP/weapon tooltip are -- unchanged. The mat object is only READ here (positionToWorld/getRotation); no -- mat script is modified. function spawnDices(count, color, weaponName, offset, ap, strength, dmg, melta, devastating, ignoreCover) local previousTrayDiceConfig = activeCustomTrayDiceConfig activeCustomTrayDiceConfig = readCustomTrayDiceConfig() -- Anchor the grid on the dice mat so dice land where the mat can manage them. -- Fall back to this object only if the mat can't be resolved. local anchor = getStatsHelperCustomDiceMat() or self local anchorRotation = anchor.getRotation() + Vector(0.0, 180.0, 0.0) -- Hard-cap total stats dice at 128, matching the mat's own spawn limit, so a -- huge attack count can never exceed the grid's footprint. local diceLimit = 128 if offset + count > diceLimit then printMessage("Dice limit reached!") count = math.max(0, diceLimit - offset) end -- Advance grid/row/col to the running offset so each weapon's batch tiles -- after the previous one (5 columns x 5 rows per grid block). local grid, row, col = 0, 0, 0 for i = 1, offset do col = col + 1 if col > 4 then col = 0; row = row + 1 if row > 4 then row = 0; grid = grid + 1 end end end local apStr = tostring(ap or "0") local meltaStr = (melta and melta ~= "") and " [ffffff](Melta "..melta..")[-]" or "" local devStr = devastating == true and " [ffffff](Dev)[-]" or "" local igorStr = ignoreCover == true and " [ffffff](Ig Cover)[-]" or "" local tooltip = "[000000]|[-][ffff00] AP: "..apStr.." "..weaponName.."[-]"..devStr..meltaStr..igorStr for i = 1, count do local die = spawnObject({ type = getDiceType(), position = anchor.positionToWorld({4 - grid * 1.5 - col * 0.25, 0.5, -0.3 + row * 0.25}), rotation = anchorRotation, callback_function = function(dice) setDieTint(dice, color) dice.setName(tooltip) end }) setDieCustomObject(die) col = col + 1 if col > 4 then col = 0; row = row + 1 if row > 4 then row = 0; grid = grid + 1 end end end activeCustomTrayDiceConfig = previousTrayDiceConfig end function setDieCustomObject(die) local trayConfig = getCustomTrayDiceConfig() if trayConfig and trayConfig.customDiceImage then die.setCustomObject({ image = trayConfig.customDiceImage, type = 1 }) return end if customDieData and customDieData.image then die.setCustomObject({ image = customDieData.image, type = 1 }) end end function setDieTint(die, color) local trayConfig = getCustomTrayDiceConfig() if trayConfig and trayConfig.selectedColor then die.setColorTint(trayConfig.selectedColor) return end local customTint = customDieData and customDieData.colorTint die.setColorTint(customTint or color) end function getDiceType() local trayConfig = getCustomTrayDiceConfig() if trayConfig and trayConfig.diceType then return trayConfig.diceType end if customDieData and customDieData.image then return "Custom_Dice" end return "Die_6" end function onAttackClicked(player, _, id) if (#currentSelection == 0) then return end spawnDicesForWeaponByFigures(getWeaponDataFromAttackButtonId(id), currentSelection) end function spawnDicesForWeaponByFigures(weaponStats, figures) local weaponMap = createWeaponsMap(createFiguresData(figures)) local weaponData = findWeaponByStats(weaponMap, weaponStats) if weaponData != nil then spawnDicesForWeapon(weaponData) end end function findWeaponByStats(weaponMap, stats) local key = getWeaponKey(stats) for i,v in pairs(weaponMap) do if i == key then return v end end end function spawnDicesForWeapon(weaponMapData) local ap = weaponMapData.data.ap or "0" local strength = weaponMapData.data.strength or "?" local dmg = weaponMapData.data.damage or "?" local melta = weaponMapData.data.melta or "" local devastating = weaponMapData.data.devastating or false local ignoreCover = weaponMapData.data.ignoreCover or false local attacks = getWeaponMapAttacks(weaponMapData) spawnDices(attacks, getWeaponColor(getWeaponKey(weaponMapData.data)), getWeaponDescription(weaponMapData.data), spawnedDiceCount, ap, strength,dmg, melta, devastating, ignoreCover) spawnedDiceCount = spawnedDiceCount + attacks end function getWeaponMapAttacks(weaponMapData) if weaponMapData.data.bonusAttack then return getBonusAttackValueForCount(weaponMapData.data.bonusAttack, weaponMapData.count) end return weaponMapData.attack end function getBonusAttackAppendum(appendum) if tonumber(appendum) ~= nil then return tonumber(appendum) end if string.find(appendum, "D") ~= nil then local split = splitStr(appendum, "D") local count = (#split == 2) and tonumber(split[1]) or 1 local value = (#split == 2) and tonumber(split[2]) or tonumber(split[1]) local result = 0 -- Roll the dice and display each result for i = 1, count do local roll = math.random(value) printMessage("Rolling one D" .. value .. ", the value is: " .. roll) result = result + roll end return result end return 0 end function getBonusAttackValueForCount(bonusAttack, count) local result = 0 for i = 1, count do result = result + getBonusAttackValue(bonusAttack) end return result end function getBonusAttackValue(bonusAttack) local appendums = splitStr(bonusAttack, "+") local result = 0 for i,app in ipairs(appendums) do result = result + getBonusAttackAppendum(app) end return result end function getBonusAttackAppendum(appendum) if tonumber(appendum) != nil then return tonumber(appendum) end if string.find(appendum, "D") != nil then local split = splitStr(appendum, "D") local count = (#split == 2) and tonumber(split[1]) or 1 local value = (#split == 2) and tonumber(split[2]) or tonumber(split[1]) local result = 0 for i = 1, count do local rnd = math.random(value) printMessage("Rolling one D"..value..", the value is: "..rnd) result = result + rnd end return result end return 0 end function getWeaponDescription(weaponData) return "[c6c930]"..weaponData.name.." [-]" end function getWeaponColor(name) if weaponColors[name] == nil then local color = {math.random(), math.random(), math.random()} weaponColors[name] = color end return weaponColors[name] end function onRangeClicked(player, _, id) end function getRangeText(range) if range == nil then return "-" end return "″"..tostring(range) end function createButtonCell(text, id, click, side) return { tag = "Cell", children = { { tag = "Button", attributes = { text = text, id = id, onClick = click, color = side and "#FFD6E8" or "#FFFFFF", tooltip = side and "Can't shoot together with main weapon" or nil, tooltipBackgroundColor = "#141414FB", tooltipTextColor = "#CCCCCC", tooltipFontSize = "60" } } } } end function createTextCell(text) return { tag = "Cell", children = { { tag = "Text", attributes = { text = text } } } } end function createFiguresData(figures) local result = {} for i,v in ipairs(figures) do table.insert(result, parseFigureData(v)) end return result end function createWeaponsMap(figuresData) local result = {} for i,v in ipairs(figuresData) do convertWeaponDataToMap(v.ranged, result) end for i,v in ipairs(figuresData) do convertWeaponDataToMap(v.melee, result) end return result end function convertWeaponDataToMap(weaponsData, resultData) for i,v in ipairs(weaponsData) do local key = getWeaponKey(v) if resultData[key] == nil then resultData[key] = { count = 0, attack = 0, data = v } end resultData[key].count = resultData[key].count + 1 resultData[key].attack = resultData[key].attack + v.attack resultData[key].side = v.side end end function getWeaponKey(weaponData) local acc = weaponData.accuracy local str = weaponData.strength local ap = weaponData.ap local d = weaponData.damage return weaponData.name..acc..str..ap..d end function parseFigureData(figure) local trimmed = removeEmptyLines(figure.getDescription()) local arr = splitStr(trimmed, "\n") local result = { ranged = {}, melee = {} } for i,v in ipairs(arr) do if (getBlockName(v) != nil) and (getBlockName(v) != "abilities") then result[getBlockName(v)] = parseWeaponBlock(arr, i) end end markPistols(result.ranged) local statsHeaders = {"m", "t", "sv", "w", "ld", "oc"} local stats = splitStr(arr[2] or "", " ") for i,v in ipairs(statsHeaders) do result[v] = stats[i] end return result end function removeEmptyLines(str) return string.gsub(str, "\n%s*\n", "\n") end function markPistols(weaponsData) local haveNonPistols = haveNonPistols(weaponsData) for i,v in ipairs(weaponsData) do if isPistol(v) and haveNonPistols then v.name = v.name.." (Side)" v.side = true end end end function haveNonPistols(weaponsData) for i,v in ipairs(weaponsData) do if isPistol(v) == false then return true end end return false end function isPistol(weaponData) local pistolTags = {"pl", "pistol"} for i,v in ipairs(pistolTags) do if string.find(string.lower(weaponData.stats), v) then return true end end return false end function parseWeaponBlock(arr, fromIndex) local result = {} local i = fromIndex while i < #arr do local name = parseWeaponName(arr[i + 1]) local statsRaw = removeColorTags(arr[i + 2]) local bracket = statsRaw:match("%[(.-)%]") local melta = nil local devastating = false local ignoreCover = false if bracket then for rule in bracket:lower():gmatch("([^,]+)") do rule = TrimString(rule) if rule:find("melta") then local m = bracket:match("[Mm]elta%s*(%d+)") if m then melta = tonumber(m) end elseif rule:find("devastating wounds") then devastating = true elseif rule:find("ignore cover") or rule:find("ignores cover") then ignoreCover = true end end end table.insert(result, { name = name, stats = statsRaw, range = parseRange(statsRaw), attack = parseWeaponAttack(statsRaw), bonusAttack = parseWeaponBonusAttack(statsRaw), accuracy = parseWeaponAccuracy(statsRaw) or "", strength = getWeaponStatValue(statsRaw, "S") or "", ap = getWeaponStatValue(statsRaw, "AP") or "", damage = getWeaponStatValue(statsRaw, "D") or "", melta = melta, devastating = devastating, ignoreCover = ignoreCover }) if getBlockName(arr[i + 3]) ~= nil then return result end i = i + 2 end return result end function removeColorTags(str) str = string.gsub(str, "%[7bc596%]", "") str = string.gsub(str, "%[%-%]", "") return str end function parseWeaponName(data) local trimmedSpaces = string.match(data, "^(.-)%s*$") return string.sub(trimmedSpaces, 9, -4) end function parseWeaponAccuracy(stats) return getWeaponStatValue(stats, "BS") or getWeaponStatValue(stats, "WS") end function parseWeaponAttack(stats) local stat = getWeaponStatValue(stats, "A") return tonumber(stat) or 0 end function parseWeaponBonusAttack(stats) local stat = getWeaponStatValue(stats, "A") if tonumber(stat) == nil then return stat end return nil end function getWeaponStatValue(stats, statName) local statPairs = splitStr(stats, " ") for i,v in ipairs(statPairs) do local stat = splitStr(v, ":") if stat[1] == statName then return stat[2] end end end function parseRange(stats) local inches = string.find(stats, "″") or string.find(stats, '"') if inches != nil then return tonumber(string.sub(stats, 1, inches - 1)) end end function getBlockName(str) if str == nil then return end if (string.find(str, "[rR]anged [wW]eapons") != nil) then return "ranged" end if (string.find(str, "[mM]elee [wW]eapons") != nil) then return "melee" end if (string.find(str, "[aA]bilities") != nil) then return "abilities" end end function splitStr(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 printMessage(text, color) color = color or {1, 0.5, 0} broadcastToAll(text, color) end function TrimString(s) local a = s:match("^%s*()") local b = s:match("()%s*$", a) return s:sub(a, b - 1) end LuaScriptState[]XmlUI( Name Stats Count Attacks Range 95 GUID8c71abName Custom_TokenTransformposX,F@posY?posZ2@rotX`UrotY@Ap@rotZ^躾scaleX`O(?scaleY?scaleZ`O(?Nickname"Interactive Buttons - Area DenialDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18395342256881191528/F49462F67144A18AA0377B410ECDAAE1A039905B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScript-- FTC-GUID: 8c71ab -- Shared variables deployLineHeight = 2.1 deployLineYPos = deployLineHeight-0.1 sizeMulti = 36 local onState = 'https://steamusercontent-a.akamaihd.net/ugc/13208748527118320669/CB6C3A4B6E2A966FE0DEF4F816E3DAF80B9EF7B3/' local offState = 'https://steamusercontent-a.akamaihd.net/ugc/18395342256881191528/F49462F67144A18AA0377B410ECDAAE1A039905B/' function onLoad(saved_data) self.createButton({ click_function = "toggleAeraDeny", function_owner = self, label = "", position = {0, 0.2, 0}, width = 2000, height = 2000, color = {0, 0, 0, 0} }) end function toggleAeraDeny() local state = Global.getVar("interactiveButtonAreaDenial") if state == 1 then Global.setVar("interactiveButtonAreaDenial", 2) drawCircle({color="White", fromCenter=6}, "areaDeny") drawCircle({color="White", fromCenter=3}, "areaDeny") params = { image = onState, } else Global.setVar("interactiveButtonAreaDenial", 1) params = { image = offState, } for i, obj in ipairs(getAllObjects()) do if obj.getGMNotes() == "areaDeny" then obj.destroy() end end end self.setCustomObject(params) self.reload() end function drawCircle(drawData, type) local originalCircle=getObjectFromGUID(Global.getVar("centerCircle_GUID")) local circleObj = originalCircle.clone({ position = {0, -5, 0}}) if circleObj then circleObj.setLock(true) circleObj.interactable = false circleObj.setScale({drawData.fromCenter, deployLineHeight, drawData.fromCenter}) circleObj.setPosition({0, deployLineYPos, 0}) circleObj.setColorTint(drawData.color) circleObj.setGMNotes(type) circleObj.setName("") local blockComp = circleObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = circleObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end end end LuaScriptStateXmlUI10\GUID7b7ab8Name Custom_TokenTransformposX,F@posY?posZ$@rotX`UrotY@Ap@rotZ^躾scaleX`O(?scaleY?scaleZ`O(?Nickname%Interactive Buttons - Strat ReservesDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12916446593392721363/53FD560EC918104B95C6D7702DA914B390DFB492/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScript -- FTC-GUID: 7b7ab8 -- Shared variables deployLineHeight = 2.1 sizeMulti = 36 local onState = 'https://steamusercontent-a.akamaihd.net/ugc/9587781878370741630/EB92F79C5AFA714E4ED2561F81FA54EB8B099C98/' local offState = 'https://steamusercontent-a.akamaihd.net/ugc/12916446593392721363/53FD560EC918104B95C6D7702DA914B390DFB492/' function onLoad(saved_data) self.createButton({ click_function = "showHideStratreserves", function_owner = self, label = "", position = {0, 0.2, 0}, width = 2000, height = 2000, color = {0, 0, 0, 0} }) end function showHideStratreserves() local state = Global.getVar("interactiveButtonStratReserves") if state == 1 then Global.setVar("interactiveButtonStratReserves", 2) params = { image = onState, } drawLines() else Global.setVar("interactiveButtonStratReserves", 1) params = { image = offState, } removeLines() end self.setCustomObject(params) self.reload() end -- Draw the two quarter‐lines function drawLines() -- Resolve at click time: a load-time capture can race Global's onLoad. local mat = getObjectFromGUID(Global.getVar("mat_GUID")) local lineScale = {x = 0, y = deployLineHeight, z = 0.02} local mapBase = mat.getScale().z * sizeMulti -- short edge local mapHeight = mat.getScale().x * sizeMulti -- long edge -- Strategic reserves lines --(linePos, lineRot, lineScale, color, orientation, type) lineScale.x = mapHeight spawnLine({0, deployLineHeight, (mapBase / 2) - 6}, {0, 0, 0}, lineScale, "White", "stratreserves") spawnLine({0, deployLineHeight, -((mapBase / 2) - 6)}, {0, 0, 0}, lineScale, "White", "stratreserves") lineScale.x = mapBase spawnLine({(mapHeight / 2) - 6, deployLineHeight, 0}, {0, 90, 0}, lineScale, "White", "stratreserves") spawnLine({-((mapHeight / 2) - 6), deployLineHeight, 0}, {0, 90, 0}, lineScale, "White", "stratreserves") end -- Remove any “quarter” GMNotes function removeLines() for i, obj in ipairs(getAllObjects()) do if obj.getGMNotes() == "stratreserves" then obj.destroy() end end end -- Helper: spawn a BlockSquare with a given GMNote function spawnLine(linePos, lineRot, lineScale, color, gmNote) local lineObj = spawnObject({ type = "BlockSquare", position = linePos, rotation = lineRot, scale = lineScale }) if lineObj then lineObj.setLock(true) lineObj.interactable = false lineObj.setGMNotes(gmNote) lineObj.setColorTint(color) local blockComp = lineObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = lineObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end end end LuaScriptStateXmlUI11GUID8aba11Name Custom_TokenTransformposX,F@posY?posZ2rotX`UrotY@Ap@rotZ^躾scaleX`O(?scaleY?scaleZ`O(?Nickname&Interactive Buttons - Deployment ZoneDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18084074511729353541/6EFF7064E85559205053CF58B73FEEB5D6EC4A6A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScript.-- FTC-GUID: 8aba11 -- Shared variables deployLineHeight = 2.1 sizeMulti = 36 local visibleState = 'https://steamusercontent-a.akamaihd.net/ugc/11888931357205845979/07DA5D6924B1019561DD4092059BB77E610FCE97/' local hiddenState = 'https://steamusercontent-a.akamaihd.net/ugc/18084074511729353541/6EFF7064E85559205053CF58B73FEEB5D6EC4A6A/' function onLoad(saved_data) self.createButton({ click_function = "toggleQuarters", function_owner = self, label = "", position = {0, 0.2, 0}, width = 2000, height = 2000, color = {0, 0, 0, 0} }) end function toggleQuarters() local manager = getObjectFromGUID("738804") manager.call("showHideIngameDeployment") syncDeploymentZoneButtonImage() end function syncDeploymentZoneButtonImage() local state = Global.getVar("interactiveButtonDeploymentZones") local params = { image = state == 1 and hiddenState or visibleState, } self.setCustomObject(params) self.reload() end LuaScriptStateXmlUI12-GUID59decfName Custom_TokenTransformposX,F@posY?posZ$rotX`UrotY@Ap@rotZ^躾scaleX`O(?scaleY?scaleZ`O(?Nickname%Interactive Buttons - Table QuartersDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14757168286520639251/5C85FDDAEEC5C058E7E11536DB93A94C59F20CEB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScript -- FTC-GUID: 59decf -- Shared variables deployLineHeight = 2.1 sizeMulti = 36 local onState = 'https://steamusercontent-a.akamaihd.net/ugc/15097634417370475591/2AAD029EBF3BEC3AD5C81C811310A26009273429/' local offState = 'https://steamusercontent-a.akamaihd.net/ugc/14757168286520639251/5C85FDDAEEC5C058E7E11536DB93A94C59F20CEB/' function onLoad(saved_data) self.createButton({ click_function = "toggleQuarters", function_owner = self, label = "", position = {0, 0.2, 0}, width = 2000, height = 2000, color = {0, 0, 0, 0} }) end function toggleQuarters() local state = Global.getVar("interactiveButtonTableQuarters_State") if state == 1 then Global.setVar("interactiveButtonTableQuarters_State", 2) params = { image = onState, } drawLines() else Global.setVar("interactiveButtonTableQuarters_State", 1) params = { image = offState, } removeLines() end self.setCustomObject(params) self.reload() end -- Draw the two quarter‐lines function drawLines() -- Resolve at click time: a load-time capture can race Global's onLoad. local mat = getObjectFromGUID(Global.getVar("mat_GUID")) if not mat then return end local lineScale = { x = 1, y = deployLineHeight, z = 0.02 } local mapBase = mat.getScale().z * sizeMulti local mapHeight = mat.getScale().x * sizeMulti -- Long edge line lineScale.x = mapHeight spawnLine({0, deployLineHeight, 0}, {0, 0, 0}, lineScale, "White", "quarter") -- Short edge line lineScale.x = mapBase spawnLine({0, deployLineHeight, 0}, {0, 90, 0}, lineScale, "White", "quarter") end -- Remove any “quarter” GMNotes function removeLines() for _, obj in ipairs(getAllObjects()) do if obj.getGMNotes() == "quarter" then obj.destroy() end end end -- Helper: spawn a BlockSquare with a given GMNote function spawnLine(linePos, lineRot, lineScale, color, gmNote) local lineObj = spawnObject({ type = "BlockSquare", position = linePos, rotation = lineRot, scale = lineScale }) if lineObj then lineObj.setLock(true) lineObj.interactable = false lineObj.setGMNotes(gmNote) lineObj.setColorTint(color) local blockComp = lineObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = lineObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end end end LuaScriptStateXmlUI13 GUID15d652Name Custom_TokenTransformposX,F@posY?posZ:rotX`UrotY@Ap@rotZ^躾scaleX`O(?scaleY?scaleZ`O(?Nickname&Interactive Buttons - Show ObjectivesDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10740144134536449694/39CFD91BE19B3842102254DD674F222A8BF8A274/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScript( -- FTC-GUID: 15d652 local onState = 'https://steamusercontent-a.akamaihd.net/ugc/15473991661851235143/8834767C61229AE6ED0741E4051392B6D4922AE5/' local offState = 'https://steamusercontent-a.akamaihd.net/ugc/10740144134536449694/39CFD91BE19B3842102254DD674F222A8BF8A274/' local managerGUID = "738804" local clickCooldownSeconds = 0.5 local objectivesOn = false function onLoad(saved_data) if saved_data and saved_data ~= "" then local loaded_data = JSON.decode(saved_data) objectivesOn = loaded_data.objectivesOn == true end Global.setVar("interactiveButtonShowObjectives", objectivesOn and 2 or 1) self.createButton({ click_function = "toggleObjectives", function_owner = self, label = "", position = {0, 0.2, 0}, width = 2000, height = 2000, color = {0, 0, 0, 0} }) -- RNG Test moved to the shared debug panel (Global, --test builds only). end function onSave() return JSON.encode({ objectivesOn = objectivesOn, }) end function setObjectivesVisible(visible) local manager = getObjectFromGUID(managerGUID) if not manager then printToAll("Cannot toggle objectives: objectives manager not found.", {1, 0.25, 0.25}) return false end objectivesOn = visible == true Global.setVar("interactiveButtonShowObjectives", objectivesOn and 2 or 1) local params if objectivesOn then manager.call("placeObjectives") params = { image = onState, } else manager.call("destroyAllObjectives") params = { image = offState, } end self.script_state = onSave() self.setCustomObject(params) self.reload() return true end function turnObjectivesOn() setObjectivesVisible(true) end function turnObjectivesOff() setObjectivesVisible(false) end function toggleObjectives() local now = Time.time local nextClickAt = Global.getVar("interactiveButtonShowObjectivesNextClickAt") or 0 if now < nextClickAt then return end -- Stored globally because changing the custom-token image reloads this script. Global.setVar("interactiveButtonShowObjectivesNextClickAt", now + clickCooldownSeconds) setObjectivesVisible(not objectivesOn) end LuaScriptState{"objectivesOn":false}XmlUI145GUIDd26e9cNameDeckTransformposX`;OV@posYposZrhrotXfƙrotYp@rotZf@scaleX?scaleY?scaleZ?NicknamePrimary mission cardsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsSidewaysCardDeckIDsK0,e1Xf2e3e4d5lk6dd7$8f9dCustomDeckg 2598FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/15580039741952439474/EC914819B18763CC8BF9043EB810BA41E202957B/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2628FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/14291210713567780903/0C51EF11C7FDF7B488B8ECFF2963F37C7A90C33D/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2608FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/14135435845873047318/44B34F9FE8A3C44111AD9CD64CD8411DDE391FF8/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2618FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/16639253551370182140/D411B9B356DF3353A0FC64CAAF9BC7F74442D64D/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2568FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/12149867986627686319/E0DFC312F785EB48A846EE683F9AE62DD63A6F57/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2758FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/12186530208390897102/DDC62379A172CB5A3A7157A069035BE92DDC8582/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2577FaceURLjhttps://steamusercontent-a.akamaihd.net/ugc/9862748901361883298/2407D615F4A76A3845DE3E15D1813D35D10DE59B/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType17778FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/13651956500254414952/58EA4907765FE43D062309AB41E0B4E01D8E60D9/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2638FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/16072122049852277708/B6DE148C68449C0259F8539E39CE37DAD54CD63B/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackType2588FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/14899737739543793918/4D7BDA6FCFD26971B09421187DAF57D9866C0296/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUIContainedObjectsI&0GUID6860deName CardCustomTransformposX:posY 9?posZ&@rotXrotYf@rotZf@scaleX?scaleY?scaleZ?NicknameScorched EarthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardID,eSidewaysCardCustomDeckB2598FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/15580039741952439474/EC914819B18763CC8BF9043EB810BA41E202957B/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI1GUID7fa6d0Name CardCustomTransformposXtposY?posZV&@rotX lrotYf@rotZf@scaleX?scaleY?scaleZ?Nickname TerraformDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDXfSidewaysCardCustomDeckB2628FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/14291210713567780903/0C51EF11C7FDF7B488B8ECFF2963F37C7A90C33D/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI2GUID981226Name CardCustomTransformposXIposY ?posZ&@rotX`YrotY`f@rotZ@pf@scaleX?scaleY?scaleZ?Nickname Supply DropDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDeSidewaysCardCustomDeckB2608FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/14135435845873047318/44B34F9FE8A3C44111AD9CD64CD8411DDE391FF8/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI3GUIDbffd57Name CardCustomTransformposXposY=?posZ'@rotXrotYf@rotZ` f@scaleX?scaleY?scaleZ?NicknameTake And HoldDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDeSidewaysCardCustomDeckB2618FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/16639253551370182140/D411B9B356DF3353A0FC64CAAF9BC7F74442D64D/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI4GUID85718eName CardCustomTransformposX posY CA?posZ '@rotX ]rotYf@rotZf@scaleX?scaleY?scaleZ?NicknameBurden Of TrustDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDdSidewaysCardCustomDeckB2568FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/12149867986627686319/E0DFC312F785EB48A846EE683F9AE62DD63A6F57/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI5GUID85718fName CardCustomTransformposX posY CA?posZ '@rotX ]rotYf@rotZf@scaleX?scaleY?scaleZ?NicknameHidden SuppliesDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDlkSidewaysCardCustomDeckB2758FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/12186530208390897102/DDC62379A172CB5A3A7157A069035BE92DDC8582/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI6GUID540fd0Name CardCustomTransformposX posY`?posZ&@rotX`>rotYf@rotZf@scaleX?scaleY?scaleZ?Nickname LinchpinDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDddSidewaysCardCustomDeckA2577FaceURLjhttps://steamusercontent-a.akamaihd.net/ugc/9862748901361883298/2407D615F4A76A3845DE3E15D1813D35D10DE59B/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI7GUID7067e2Name CardCustomTransformposX posYM?posZF'@rotXT`rotYf@rotZf@scaleX?scaleY?scaleZ?Nickname The RitualDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardID$SidewaysCardCustomDeckB2648FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/13651956500254414952/58EA4907765FE43D062309AB41E0B4E01D8E60D9/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI8GUID41f293Name CardCustomTransformposX`posYE??posZ'@rotXk|>rotYf@rotZf@scaleX?scaleY?scaleZ?NicknameUnexploded OrdnanceDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDfSidewaysCardCustomDeckB2638FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/16072122049852277708/B6DE148C68449C0259F8539E39CE37DAD54CD63B/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI9GUIDd9d35aName CardCustomTransformposX͆posY@'?posZ@C&@rotX`ir>rotYf@rotZf@scaleX?scaleY?scaleZ?NicknamePurge The FoeDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?Tags0 primaryCardLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardIDdSidewaysCardCustomDeckB2588FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/14899737739543793918/4D7BDA6FCFD26971B09421187DAF57D9866C0296/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/17866002263556473400/9780BDC761912293B7D3AFE0C0E3681240630523/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptLuaScriptStateXmlUI15GUID90cbe7Name Custom_PDFTransformposXY@posY#@posZ@rotXF@rotY@p@rotZ>scaleX@scaleY?scaleZ@Nickname11e Core RulesDescription GMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomPDFPDFUrlkhttps://steamusercontent-a.akamaihd.net/ugc/12100271967775319931/EB7F5F820F6183849575A62B78569017BDE79337/PDFPasswordPDFPagePDFPageOffsetLuaScriptbookmarks = {} inputText = "" showBookmarks = false function onLoad(save_state) if save_state ~= "" then saved_data = JSON.decode(save_state) if saved_data.bookmarks then bookmarks = saved_data.bookmarks end end createBookmarks() showBookmarks = true end function checkLoaded() if not self.loading_custom and not self.spawning then return true end return false end function onSave() local save_state = JSON.encode({bookmarks = bookmarks}) self.script_state = save_state end function createToggle() self.createButton({ click_function = "toggleState", function_owner = self, label = ">", position = {1.6, 0, -1.9}, font_size = 60, width = 100, height = 100 }) end function toggleState() if showBookmarks then removeBookmarks() self.editButton({index = 0, label = ">"}) else createInputs() createBookmarks() self.editButton({index = 0, label = "<"}) end showBookmarks = not showBookmarks end function removeBookmarks() buttons = self.getButtons() inputs = self.getInputs() for i = #buttons - 1, 1, -1 do self.removeButton(i) end for i = #inputs -1, 0, -1 do self.removeInput(i) end end function createInputs() -- Bookmark name box self.createInput({ input_function = "updateText", function_owner = self, label = "Bookmark Name", position = {2.1, 0, -1.5}, width = 500, height = 100, font_size = 60, alignment = 2 }) -- Add bookmark button self.createButton({ click_function = "addBookmark", function_owner = self, label = "Add Bookmark", position = {3.2, 0, -1.5}, font_size = 60, width = 500, height = 100 }) end function updateText(obj, player_clicker_color, input_value, selected) inputText = input_value end function clearBookmarks() local buttons = self.getButtons() local numBookmarks = #buttons - 1 -- iterate if numBookmarks == 0 then return end for i = 1, numBookmarks do self.removeButton(i) end end function createBookmarks() if #bookmarks == 0 then return end local vOffset = 0.3 for i=1, #bookmarks do local vertPos = -1 + (vOffset * (i - 1)) self.createButton({ click_function = "clickBookmark"..i, function_owner = self, label = bookmarks[i].name, position = {2.1, 0, vertPos}, font_size = 60, width = 500, height = 100 }) end end function addBookmark(obj, player_clicker_color, alt_click) if #bookmarks >= 10 then print("Max bookmarks reached") return end if inputText == "" then print("Enter a bookmark name prior to saving.") else table.insert(bookmarks, {name = inputText, page = self.Book.getPage()}) self.editInput({index = 0, value = ""}) inputText = "" clearBookmarks() createBookmarks() end end function clickBookmark1(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[1].page) end function clickBookmark2(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[2].page) end function clickBookmark3(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[3].page) end function clickBookmark4(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[4].page) end function clickBookmark5(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[5].page) end function clickBookmark6(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[6].page) end function clickBookmark7(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[7].page) end function clickBookmark8(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[8].page) end function clickBookmark9(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[9].page) end function clickBookmark10(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[10].page) end function removeBookmark1(obj, player_clicker_color, alt_click) table.remove(bookmarks, 1) clearBookmarks() createBookmarks() end function removeBookmark2(obj, player_clicker_color, alt_click) table.remove(bookmarks, 2) clearBookmarks() createBookmarks() end function removeBookmark3(obj, player_clicker_color, alt_click) table.remove(bookmarks, 3) clearBookmarks() createBookmarks() end function removeBookmark4(obj, player_clicker_color, alt_click) table.remove(bookmarks, 4) clearBookmarks() createBookmarks() end function removeBookmark5(obj, player_clicker_color, alt_click) table.remove(bookmarks, 5) clearBookmarks() createBookmarks() end function removeBookmark6(obj, player_clicker_color, alt_click) table.remove(bookmarks, 6) clearBookmarks() createBookmarks() end function removeBookmark7(obj, player_clicker_color, alt_click) table.remove(bookmarks, 7) clearBookmarks() createBookmarks() end function removeBookmark8(obj, player_clicker_color, alt_click) table.remove(bookmarks, 8) clearBookmarks() createBookmarks() end function removeBookmark9(obj, player_clicker_color, alt_click) table.remove(bookmarks, 9) clearBookmarks() createBookmarks() end function removeBookmark10(obj, player_clicker_color, alt_click) table.remove(bookmarks, 10) clearBookmarks() createBookmarks() endLuaScriptStateb{"bookmarks":[{"name":"Attack Sequence","page":17},{"name":"Command Phase","page":29},{"name":"Movement Phase","page":31},{"name":"Shooting Phase","page":33},{"name":"Charge Phase","page":35},{"name":"Fight Phase","page":37},{"name":"Advanced Rules","page":60},{"name":"Transports","page":63},{"name":"Actions","page":57},{"name":"Keywords","page":78}]}XmlUI16GUID251e3cName Custom_TileTransformposX@" Y@posY@33 @posZI@rotXF@rotY(p@rotZ@9?scaleX$@scaleY?scaleZ$@NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImagemImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11711505307268485653/16E5A8F3B14B3CC815C2B2A903012708CDEEA663/ImageSecondaryURLkhttps://steamusercontent-a.akamaihd.net/ugc/11711505307268485653/16E5A8F3B14B3CC815C2B2A903012708CDEEA663/ImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUIPhysicsMaterialrStaticFrictionDynamicFrictionBouncinessFrictionCombineBounceCombineRigidbodyCMassDragAngularDragUseGravity17rGUIDcda0b3Name Custom_TokenTransformposXB@posYbfposZ6 =@rotX 롾rotYp@rotZ f@scaleXscaleX?scaleY?scaleZ?NicknameBlue dice cameraDescriptionGMNotesAltLookAngle&xyzColorDiffuse&rgbLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundle!AssetbundleURL\https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/cameras/cam_p2_screenh.unity3dAssetbundleSecondaryURLUhttps://raw.githubusercontent.com/RobMayer/TTSLibrary/master/cameras/cam_mat.unity3dMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI19GUID7ac8baName Custom_TileTransformposX@posY?posZ7@rotXrotYrotZscaleX.@scaleY?scaleZ?NicknameRed Lethal Hit ZoneDescription#Detection zone for red lethal hitsGMNotesAltLookAngle&xyzColorDiffuse&r?gbLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10365186186880024371/7CFC32CC352FD562D93EB11DBF649FC510E46D73/ImageSecondaryURLImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUI20GUID71ab21Name Custom_TileTransformposXposY?posZ7rotXrotYrotZscaleX.@scaleY?scaleZ?NicknameBlue Lethal Hit ZoneDescription$Detection zone for blue lethal hitsGMNotesAltLookAngle&xyzColorDiffuse&rgb?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10365186186880024371/7CFC32CC352FD562D93EB11DBF649FC510E46D73/ImageSecondaryURLImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUI21iGUID7aca71NameBagTransformposXcB@posY?posZ@C@rotX ?rotY Wf@rotZscaleX ?scaleY ?scaleZ ?Nickname"Legacy Tokens (Red) by TrashpandaDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r-?g-?b-?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScriptQ?-- FTC-GUID: 7aca71,10a26a,446a11,9d6b2f -- Utility memory bag by Directsun -- Version 2.5 -- Fork of Memory Bag 2.0 by MrStump function updateSave() local data_to_save = {["ml"]=memoryList} saved_data = JSON.encode(data_to_save) self.script_state = saved_data end function combineMemoryFromBagsWithin() local bagObjList = self.getObjects() for _, bagObj in ipairs(bagObjList) do local data = bagObj.lua_script_state if data ~= nil then local j = JSON.decode(data) if j ~= nil and j.ml ~= nil then for guid, entry in pairs(j.ml) do memoryList[guid] = entry end end end end end function updateMemoryWithMoves() print("updating memory with moves") memoryList = memoryListBackup --get the first transposed object's coordinates local obj = getObjectFromGUID(moveGuid) -- p1 is where needs to go, p2 is where it was local refObjPos = memoryList[moveGuid].pos local deltaPos = findOffsetDistance(obj.getPosition(), refObjPos, nil) local movedRotation = obj.getRotation() for guid, entry in pairs(memoryList) do memoryList[guid].pos.x = entry.pos.x - deltaPos.x memoryList[guid].pos.y = entry.pos.y - deltaPos.y memoryList[guid].pos.z = entry.pos.z - deltaPos.z -- memoryList[guid].rot.x = movedRotation.x -- memoryList[guid].rot.y = movedRotation.y -- memoryList[guid].rot.z = movedRotation.z end --theList[obj.getGUID()] = { -- pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, -- rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, -- lock=obj.getLock() --} moveList = {} end function onload(saved_data) fresh = true if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) --Set up information off of loaded_data memoryList = loaded_data.ml else --Set up information for if there is no saved saved data memoryList = {} end moveList = {} moveGuid = nil if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end end --Beginning Setup --Make setup button function createSetupButton() self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --Triggered by Transpose button function buttonClick_transpose() moveGuid = nil broadcastToAll("Select one object and move it- all objects will move relative to the new location", {0.75, 0.75, 1}) memoryListBackup = duplicateTable(memoryList) memoryList = {} moveList = {} self.clearButtons() createButtonsOnAllObjects(true) createSetupActionButtons(true) end --Triggered by setup button, function buttonClick_setup() memoryListBackup = duplicateTable(memoryList) memoryList = {} self.clearButtons() createButtonsOnAllObjects(false) createSetupActionButtons(false) end function getAllObjectsInMemory() local objTable = {} local curObj = {} for guid in pairs(memoryListBackup) do curObj = getObjectFromGUID(guid) table.insert(objTable, curObj) end return objTable -- return getAllObjects() end --Creates selection buttons on objects function createButtonsOnAllObjects(move) local howManyButtons = 0 local objsToHaveButtons = {} if move == true then objsToHaveButtons = getAllObjectsInMemory() else objsToHaveButtons = getAllObjects() end for _, obj in ipairs(objsToHaveButtons) do if obj ~= self then local dummyIndex = howManyButtons --On a normal bag, the button positions aren't the same size as the bag. globalScaleFactor = 1.25 * 1/self.getScale().x --Super sweet math to set button positions local selfPos = self.getPosition() local objPos = obj.getPosition() local deltaPos = findOffsetDistance(selfPos, objPos, obj) local objPos = rotateLocalCoordinates(deltaPos, self) objPos.x = -objPos.x * globalScaleFactor objPos.y = objPos.y * globalScaleFactor objPos.z = objPos.z * globalScaleFactor --Offset rotation of bag local rot = self.getRotation() rot.y = -rot.y + 180 --Create function local funcName = "selectButton_" .. howManyButtons local func = function() buttonClick_selection(dummyIndex, obj, move) end local color = {0.75,0.25,0.25,0.6} local colorMove = {0,0,1,0.6} if move == true then color = colorMove end self.setVar(funcName, func) self.createButton({ click_function=funcName, function_owner=self, position=objPos, rotation=rot, height=1000, width=1000, color=color, }) howManyButtons = howManyButtons + 1 end end end --Creates submit and cancel buttons function createSetupActionButtons(move) self.createButton({ label="Cancel", click_function="buttonClick_cancel", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Submit", click_function="buttonClick_submit", function_owner=self, position={0,0.3,-2.8}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) if move == false then self.createButton({ label="Add", click_function="buttonClick_add", function_owner=self, position={0,0.3,-3.6}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.25,1,0.25} }) if fresh == false then self.createButton({ label="Set New", click_function="buttonClick_setNew", function_owner=self, position={0,0.3,-4.4}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) self.createButton({ label="Remove", click_function="buttonClick_remove", function_owner=self, position={0,0.3,-5.2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,0.25,0.25} }) end end self.createButton({ label="Reset", click_function="buttonClick_reset", function_owner=self, position={-2,0.3,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --During Setup --Checks or unchecks buttons function buttonClick_selection(index, obj, move) local colorMove = {0,0,1,0.6} local color = {0,1,0,0.6} previousGuid = selectedGuid selectedGuid = obj.getGUID() theList = memoryList if move == true then theList = moveList if previousGuid ~= nil and previousGuid ~= selectedGuid then local prevObj = getObjectFromGUID(previousGuid) prevObj.highlightOff() self.editButton({index=previousIndex, color=colorMove}) theList[previousGuid] = nil end previousIndex = index end if theList[selectedGuid] == nil then self.editButton({index=index, color=color}) --Adding pos/rot to memory table local pos, rot = obj.getPosition(), obj.getRotation() --I need to add it like this or it won't save due to indexing issue theList[obj.getGUID()] = { pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock() } obj.highlightOn({0,1,0}) else color = {0.75,0.25,0.25,0.6} if move == true then color = colorMove end self.editButton({index=index, color=color}) theList[obj.getGUID()] = nil obj.highlightOff() end end --Cancels selection process function buttonClick_cancel() memoryList = memoryListBackup moveList = {} self.clearButtons() if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end removeAllHighlights() broadcastToAll("Selection Canceled", {1,1,1}) moveGuid = nil end --Saves selections function buttonClick_submit() fresh = false if next(moveList) ~= nil then for guid in pairs(moveList) do moveGuid = guid end if memoryListBackup[moveGuid] == nil then broadcastToAll("Item selected for moving is not already in memory", {1, 0.25, 0.25}) else broadcastToAll("Moving all items in memory relative to new objects position!", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(moveList) do moveGuid = guid count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end updateMemoryWithMoves() updateSave() buttonClick_place() end elseif next(memoryList) == nil and moveGuid == nil then memoryList = memoryListBackup broadcastToAll("No selections made.", {0.75, 0.25, 0.25}) end combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() moveGuid = nil end function combineTables(first_table, second_table) for k,v in pairs(second_table) do first_table[k] = v end end function buttonClick_add() fresh = false combineTables(memoryList, memoryListBackup) broadcastToAll("Adding internal bags and selections to existing memory", {0.25, 0.75, 0.25}) combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() end function buttonClick_remove() broadcastToAll("Removing Selected Entries From Memory", {1.0, 0.25, 0.25}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 memoryListBackup[guid] = nil local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Removed", {1,1,1}) memoryList = memoryListBackup updateSave() end function buttonClick_setNew() broadcastToAll("Setting new position relative to items in memory", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for _, obj in ipairs(getAllObjects()) do guid = obj.guid if memoryListBackup[guid] ~= nil then count = count + 1 memoryListBackup[guid].pos = obj.getPosition() memoryListBackup[guid].rot = obj.getRotation() memoryListBackup[guid].lock = obj.getLock() end end broadcastToAll(count.." Objects Saved", {1,1,1}) memoryList = memoryListBackup updateSave() end --Resets bag to starting status function buttonClick_reset() fresh = true memoryList = {} self.clearButtons() createSetupButton() removeAllHighlights() broadcastToAll("Tool Reset", {1,1,1}) updateSave() end --After Setup function createMemoryActionButtons() local guid = self.getGUID() local isTokenBag = (guid == "7aca71") or (guid == "10a26a") or (guid == "446a11") or (guid == "9d6b2f") local showLabel = isTokenBag and "Show Tokens" or "Show Maps" local showWidth = isTokenBag and 1600 or 1400 local hasToolTip = isTokenBag and "Place Tokens" or "" self.createButton({ label=showLabel, click_function="buttonClick_place", function_owner=self, position={0,0.2,-2}, rotation={0,180,0}, height=350, width=showWidth, tooltip=hasToolTip, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Recall", click_function="buttonClick_recall", function_owner=self, position={0,0.2,-2.8}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={-2,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Move", click_function="buttonClick_transpose", function_owner=self, position={-2.8,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) end --Sends objects from bag/table to their saved position/rotation function buttonClick_place() local bagObjList = self.getObjects() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) --If obj is out on the table, move it to the saved pos/rot if obj ~= nil then obj.setPositionSmooth(entry.pos) obj.setRotationSmooth(entry.rot) obj.setLock(entry.lock) else --If obj is inside of the bag for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then local item = self.takeObject({ guid=guid, position=entry.pos, rotation=entry.rot, smooth=false }) item.setLock(entry.lock) break end end end end local obj = getObjectFromGUID("7abc5a") if(obj) then destroyObject(obj) end broadcastToAll("Objects Placed", {1,1,1}) end --Recalls objects to bag from table function buttonClick_recall() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then self.putObject(obj) end end broadcastToAll("Objects Recalled", {1,1,1}) end --Utility functions --Find delta (difference) between 2 x/y/z coordinates function findOffsetDistance(p1, p2, obj) local yOffset = 0 if obj ~= nil then local bounds = obj.getBounds() yOffset = (bounds.size.y - bounds.offset.y) end local deltaPos = {} deltaPos.x = (p2.x-p1.x) deltaPos.y = (p2.y-p1.y) + yOffset deltaPos.z = (p2.z-p1.z) return deltaPos end --Used to rotate a set of coordinates by an angle function rotateLocalCoordinates(desiredPos, obj) local objPos, objRot = obj.getPosition(), obj.getRotation() local angle = math.rad(objRot.y) local x = desiredPos.x * math.cos(angle) - desiredPos.z * math.sin(angle) local z = desiredPos.x * math.sin(angle) + desiredPos.z * math.cos(angle) --return {x=objPos.x+x, y=objPos.y+desiredPos.y, z=objPos.z+z} return {x=x, y=desiredPos.y, z=z} end --Coroutine delay, in seconds --Duplicates a table (needed to prevent it making reference to the same objects) function duplicateTable(oldTable) local newTable = {} for k, v in pairs(oldTable) do newTable[k] = v end return newTable end --Moves scripted highlight from all objects function removeAllHighlights() for _, obj in ipairs(getAllObjects()) do obj.highlightOff() end end --Round number (num) to the Nth decimal (dec) function round(num, dec) local mult = 10^(dec or 0) return math.floor(num * mult + 0.5) / mult end LuaScriptStatek {"ml":{"0bd055":{"lock":false,"pos":{"x":34.4842,"y":1.2302,"z":45.949},"rot":{"x":90,"y":180.2637,"z":0}},"15cecb":{"lock":false,"pos":{"x":28.529,"y":1.2302,"z":46.1388},"rot":{"x":90,"y":179.3449,"z":0}},"1c0491":{"lock":false,"pos":{"x":33.8689,"y":0.95,"z":50.5283},"rot":{"x":0,"y":359.9937,"z":0}},"1feb11":{"lock":false,"pos":{"x":32.4688,"y":0.95,"z":48.1438},"rot":{"x":0,"y":359.9905,"z":0}},"22a40b":{"lock":false,"pos":{"x":32.4688,"y":0.95,"z":46.1438},"rot":{"x":0,"y":359.9905,"z":0}},"234560":{"lock":false,"pos":{"x":26.4615,"y":0.95,"z":44.0303},"rot":{"x":0,"y":0.0082,"z":0}},"2591a2":{"lock":false,"pos":{"x":38.4614,"y":0.95,"z":46.0303},"rot":{"x":0,"y":0.0082,"z":0}},"2ebd59":{"lock":false,"pos":{"x":38.4959,"y":0.95,"z":48.0709},"rot":{"x":0,"y":359.9905,"z":0}},"467efc":{"lock":false,"pos":{"x":32.4412,"y":0.95,"z":44.1403},"rot":{"x":0,"y":359.9905,"z":0}},"48f8c9":{"lock":false,"pos":{"x":24.4615,"y":0.95,"z":46.0303},"rot":{"x":0,"y":0.0082,"z":0}},"5a6642":{"lock":false,"pos":{"x":30.4687,"y":0.95,"z":48.1438},"rot":{"x":0,"y":359.9908,"z":0}},"5bc61d":{"lock":false,"pos":{"x":26.4615,"y":0.95,"z":48.0303},"rot":{"x":0,"y":0.0082,"z":0}},"5ee87a":{"lock":false,"pos":{"x":20.4209,"y":0.95,"z":46.0412},"rot":{"x":0,"y":359.9685,"z":0}},"6222a0":{"lock":false,"pos":{"x":40.5732,"y":0.95,"z":45.9456},"rot":{"x":0,"y":359.9905,"z":0}},"6e9642":{"lock":false,"pos":{"x":36.4614,"y":0.95,"z":48.0303},"rot":{"x":0,"y":0.0082,"z":0}},"71509d":{"lock":false,"pos":{"x":36.4614,"y":0.95,"z":46.0303},"rot":{"x":0,"y":0.0082,"z":0}},"74b3e7":{"lock":false,"pos":{"x":20.4591,"y":0.95,"z":48.111},"rot":{"x":0,"y":359.9904,"z":0}},"85116a":{"lock":false,"pos":{"x":26.4615,"y":0.95,"z":46.0303},"rot":{"x":0,"y":0.0082,"z":0}},"852dec":{"lock":false,"pos":{"x":30.4687,"y":0.95,"z":46.1438},"rot":{"x":0,"y":359.9905,"z":0}},"8ac546":{"lock":false,"pos":{"x":22.4615,"y":0.95,"z":44.0303},"rot":{"x":0,"y":0.0082,"z":0}},"91a1ba":{"lock":false,"pos":{"x":30.4412,"y":0.95,"z":44.1403},"rot":{"x":0,"y":359.9905,"z":0}},"9f1d70":{"lock":false,"pos":{"x":24.4615,"y":0.95,"z":48.0303},"rot":{"x":0,"y":0.0082,"z":0}},"a0ff43":{"lock":false,"pos":{"x":22.4591,"y":0.95,"z":48.111},"rot":{"x":0,"y":359.9905,"z":0}},"bd356a":{"lock":false,"pos":{"x":38.5132,"y":0.95,"z":44.01},"rot":{"x":0,"y":359.9913,"z":0}},"e1e1e9":{"lock":false,"pos":{"x":22.4396,"y":0.95,"z":46.0429},"rot":{"x":0,"y":359.9871,"z":0}},"e205d6":{"lock":false,"pos":{"x":40.4614,"y":0.95,"z":44.0303},"rot":{"x":0,"y":0.0082,"z":0}},"e593f6":{"lock":false,"pos":{"x":40.6237,"y":0.95,"z":47.9198},"rot":{"x":0,"y":359.9904,"z":0}},"e6d4c7":{"lock":false,"pos":{"x":24.4615,"y":0.95,"z":44.0303},"rot":{"x":0,"y":0.0082,"z":0}},"eaf1dc":{"lock":false,"pos":{"x":36.4614,"y":0.95,"z":44.0303},"rot":{"x":0,"y":0.0082,"z":0}},"fbf1d6":{"lock":false,"pos":{"x":20.4615,"y":0.95,"z":44.0303},"rot":{"x":0,"y":0.0082,"z":0}}}}XmlUIContainedObjects&0 GUIDe205d6NameCustom_Model_Infinite_BagTransformposX ;D@posYaf?posZF@rotX>rotY`)ˀ?rotZnscaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Fell Back[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID3e4f69Name Custom_TokenTransformposX@R@posYaf?posZA@rotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Fell Back[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/1016068753798926835/328D9FD93B72CDAA71E33D26E5F72658CF02C2F2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd5dName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/1016068753798926835/328D9FD93B72CDAA71E33D26E5F72658CF02C2F2/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI1 GUID6222a0NameCustom_Model_Infinite_BagTransformposX^ID@posYaf?posZ` F@rotXM>rotY v@rotZ fԡscaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Stealth[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0wGUID3c9222Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Stealth[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028986581/B898667066E94C59D6FEEBAC07D96551EF8B802F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd83Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028986581/B898667066E94C59D6FEEBAC07D96551EF8B802F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI2 GUIDe593f6NameCustom_Model_Infinite_BagTransformposX`OD@posYaf?posZG@rotX$h>rotYv@rotZ5scaleX`ff?scaleY?scaleZ`ff?Nickname4[b][i]Does not receive the benefit of cover[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID3f807fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname4[b][i]Does not receive the benefit of cover[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655405230/B19FF142D107018DC0668D67D4E868A1FD20AC8A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd98Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655405230/B19FF142D107018DC0668D67D4E868A1FD20AC8A/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI3 GUID2591a2NameCustom_Model_Infinite_BagTransformposX ;C@posYaf?posZG@rotX>rotY)ˀ?rotZscaleX`ff?scaleY?scaleZ`ff?Nickname][i]Fights first[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID345d26Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname][i]Fights first[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655424098/DC4191383D8DC236F7684D0062619C68DECDE47E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd77Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655424098/DC4191383D8DC236F7684D0062619C68DECDE47E/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI4 GUID71509dNameCustom_Model_Infinite_BagTransformposX ;B@posYaf?posZG@rotX )J>rotY@)ˀ?rotZ`A#scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Lethal Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0{GUIDde62afName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Lethal Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028981243/327E0BCD6630A22226A5C917E94D76B5D9081627/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd60Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028981243/327E0BCD6630A22226A5C917E94D76B5D9081627/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI5& GUID6e9642NameCustom_Model_Infinite_BagTransformposX ;B@posYaf?posZH@rotX~{>rotY@)ˀ?rotZ@r@scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Sustained Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0~GUIDde62afName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Sustained Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028982671/D6E73CAD0C9BB789A5169AD66B9BFB60CCE8A6C2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd60Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028982671/D6E73CAD0C9BB789A5169AD66B9BFB60CCE8A6C2/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI6 GUID2ebd59NameCustom_Model_Infinite_BagTransformposXy?C@posYaf?posZ@ H@rotX`b|>rotY v@rotZtğscaleX`ff?scaleY?scaleZ`ff?Nickname,[b][i]Ignore modifiers to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID380dbeName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname,[b][i]Ignore modifiers to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655393920/245F6D5DD302B1FB86F053B81A76DC65CA46977F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd80Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655393920/245F6D5DD302B1FB86F053B81A76DC65CA46977F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI7wGUIDbd356aName Custom_TokenTransformposXAC@posYaf?posZGF@rotX@>rotY`v@rotZ$u>scaleX?scaleY?scaleZ?Nickname[b][i]Stealth[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028986581/B898667066E94C59D6FEEBAC07D96551EF8B802F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI8. GUIDeaf1dcNameCustom_Model_Infinite_BagTransformposX ;B@posYbf?posZF@rotX`b>rotY)ˀ?rotZ !scaleX`ff?scaleY?scaleZ`ff?Nickname![b][i]Devastating Wounds[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDde62afName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname![b][i]Devastating Wounds[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9377321986448328066/6D0DEFFA62285F521712420640A05E55B8667301/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd60Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028980142/CC7FC98416C5BBA90591F9A26128B7C2E95BAF44/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI9 GUID22a40bNameCustom_Model_Infinite_BagTransformposX<@@posYaf?posZhG@rotXh>rotY v@rotZ@rscaleX`ff?scaleY?scaleZ`ff?Nickname [b][i]Re-roll 1s to hit[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID9c9e41Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname [b][i]Re-roll 1s to hit[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655374861/F822DE2C0E05C957E115EBDE787CCF07ECC1A8DD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd84Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655374861/F822DE2C0E05C957E115EBDE787CCF07ECC1A8DD/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI10 GUID1feb11NameCustom_Model_Infinite_BagTransformposX<@@posYaf?posZhH@rotX>rotY v@rotZscaleX`ff?scaleY?scaleZ`ff?Nickname![b][i]Add 1 to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID4d3cf4Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname![b][i]Add 1 to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655431989/DED03E54891BA850BD955AF5E25FABDC60E9DC19/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd82Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655431989/DED03E54891BA850BD955AF5E25FABDC60E9DC19/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI11 GUID5a6642NameCustom_Model_Infinite_BagTransformposXw>@posYaf?posZhH@rotX c>rotY`v@rotZxscaleX`ff?scaleY?scaleZ`ff?Nickname([b][i]Subtract 1 from hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID2c9222Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname([b][i]Subtract 1 from hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655433467/BD0AF1590F592484681A62491E13122FA8F96752/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd83Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655433467/BD0AF1590F592484681A62491E13122FA8F96752/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI12 GUID852decNameCustom_Model_Infinite_BagTransformposXw>@posYbf?posZhG@rotX`>rotY v@rotZiscaleX`ff?scaleY?scaleZ`ff?Nickname [b][i]Re-roll hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID0fe174Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname [b][i]Re-roll hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655378696/86EF52A80BED6B2DACC5529B87A7885DFFC2245F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd85Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655378696/86EF52A80BED6B2DACC5529B87A7885DFFC2245F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI13 GUID91a1baNameCustom_Model_Infinite_BagTransformposXp>@posYbf?posZ`F@rotXE >rotY v@rotZkscaleX`ff?scaleY?scaleZ`ff?Nickname"[b][i]Re-roll wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd6a6c9Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname"[b][i]Re-roll wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655380805/881AE6700BA7922413AD4252161C8FFC06372CC5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd93Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655380805/881AE6700BA7922413AD4252161C8FFC06372CC5/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI14 GUID467efcNameCustom_Model_Infinite_BagTransformposX@y8@@posYbf?posZ`F@rotX ,>rotY v@rotZ@scaleX`ff?scaleY?scaleZ`ff?Nickname"[b][i]Re-roll 1s to wound[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd21bafName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname"[b][i]Re-roll 1s to wound[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655377225/88BA346E94528E97B6DA88D25570AE64B4140EB3/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd92Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655377225/88BA346E94528E97B6DA88D25570AE64B4140EB3/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI15 GUID9f1d70NameCustom_Model_Infinite_BagTransformposX$v8@posYbf?posZH@rotX">rotY`)ˀ?rotZBscaleX`ff?scaleY?scaleZ`ff?Nickname][i]-1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID5a61ceName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname][i]-1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650246793/34328049F5F1D627B59B86FE84EF3EAAEF3BE6A8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd69Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650246793/34328049F5F1D627B59B86FE84EF3EAAEF3BE6A8/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI16 GUID5bc61dNameCustom_Model_Infinite_BagTransformposX$v:@posYaf?posZH@rotXi߹>rotY )ˀ?rotZ<՜scaleX`ff?scaleY?scaleZ`ff?Nickname][i]+1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID5a61ceName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname][i]+1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650255866/4F520BBA410C99C39240EDF0A5D344D62931E2CD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd69Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650255866/4F520BBA410C99C39240EDF0A5D344D62931E2CD/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI17 GUID85116aNameCustom_Model_Infinite_BagTransformposX$v:@posYbf?posZG@rotX>rotY(ˀ?rotZq3scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]+1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0{GUID60b24fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]+1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650254931/B907B13B97EC40B148F177B515CF50695E34C2B7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd68Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650254931/B907B13B97EC40B148F177B515CF50695E34C2B7/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI18 GUID234560NameCustom_Model_Infinite_BagTransformposX$v:@posY bf?posZF@rotX@ >rotY)ˀ?rotZ@scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]+1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID2dc321Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]+1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650252125/7C750F237A45E45D30FD149DC75BC8FE9F977F7A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd63Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650252125/7C750F237A45E45D30FD149DC75BC8FE9F977F7A/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI19 GUIDe6d4c7NameCustom_Model_Infinite_BagTransformposX$v8@posY bf?posZF@rotX V>rotY(ˀ?rotZ9šscaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]-1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID2dc221Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]-1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650241487/80FEC7E6D929EBDEF35CB8CE9BB81C45534C2F47/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd63Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650241487/80FEC7E6D929EBDEF35CB8CE9BB81C45534C2F47/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI20 GUID48f8c9NameCustom_Model_Infinite_BagTransformposX$v8@posYbf?posZG@rotX}M>rotY@)ˀ?rotZscaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]-1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0{GUID60b24fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]-1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650245242/9588CD37FED74D5D22D0D4ED90C68F96E004935B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd68Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650245242/9588CD37FED74D5D22D0D4ED90C68F96E004935B/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI21 GUIDe1e1e9NameCustom_Model_Infinite_BagTransformposXp6@posY bf?posZ}G@rotX>rotY v@rotZČscaleX`ff?scaleY?scaleZ`ff?Nickname.[b][i]Improve armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID13c4acName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname.[b][i]Improve armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615853848/B1E9455E6D6130E82D82E560B02C26789E4AF43F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd99Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615853848/B1E9455E6D6130E82D82E560B02C26789E4AF43F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI22( GUID8ac546NameCustom_Model_Infinite_BagTransformposX$v6@posY@bf?posZF@rotX>>rotY`)ˀ?rotZ TϠscaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Movement buffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID911363Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Movement buffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615817682/977975CB724F3D8BC92E92EF1C264863678093C0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd61Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615817682/977975CB724F3D8BC92E92EF1C264863678093C0/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI23 GUIDa0ff43NameCustom_Model_Infinite_BagTransformposXu6@posYbf?posZ@5H@rotX 9>rotY v@rotZscaleX`ff?scaleY?scaleZ`ff?Nickname#[b][i]Add 1 to wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID31d14dName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname#[b][i]Add 1 to wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655435465/48A858D836363DCBB5CF148A23A2B08BE0070865/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd90Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655435465/48A858D836363DCBB5CF148A23A2B08BE0070865/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI24 GUID74b3e7NameCustom_Model_Infinite_BagTransformposXu4@posYbf?posZ@5H@rotX`>rotYv@rotZ+8scaleX`ff?scaleY?scaleZ`ff?Nickname*[b][i]Subtract 1 from wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID4ae26fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname*[b][i]Subtract 1 from wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655437739/F04EE88F53A2E094E034889D5C2D7169DCC617E2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd91Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655437739/F04EE88F53A2E094E034889D5C2D7169DCC617E2/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI25 GUID5ee87aNameCustom_Model_Infinite_BagTransformposX k4@posY@bf?posZFG@rotX@>rotYv@rotZ EgscaleX`ff?scaleY?scaleZ`ff?Nickname-[b][i]Worsen armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID39a561Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname-[b][i]Worsen armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615856842/FEEEFA356BD2F29D25F159CF345C376F7D5C6680/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd9aName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615856842/FEEEFA356BD2F29D25F159CF345C376F7D5C6680/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI26, GUIDfbf1d6NameCustom_Model_Infinite_BagTransformposX$v4@posY@bf?posZF@rotXչ>rotY`)ˀ?rotZTscaleX`ff?scaleY?scaleZ`ff?Nickname [b][i]Movement debuffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDcb3cc2Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname [b][i]Movement debuffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615819624/3392AFA0F54AFCE70087E101B52009594E3AFD5E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd62Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615819624/3392AFA0F54AFCE70087E101B52009594E3AFD5E/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI27 GUID103a0eNameCustom_Model_Infinite_BagTransformposX@<@posYaf?posZFI@rotX >rotY@v@rotZࡡscaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDb20417Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13010387546801221302/5C4BD741352EA2EFD9EB642178AAACA4F853518E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd75Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13010387546801221302/5C4BD741352EA2EFD9EB642178AAACA4F853518E/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI28 GUID98e463NameCustom_Model_Infinite_BagTransformposXq?@posY@posZ`)CI@rotX $>rotYv@rotZ@皾scaleX`ff?scaleY?scaleZ`ff?Nickname![b][i]Action-In-Progress[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDb20417Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname![b][i]Action-In-Progress[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12413768925086392778/385D1E7F4CF527EE13008502130B0288E17AF648/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd75Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12413768925086392778/385D1E7F4CF527EE13008502130B0288E17AF648/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI22GUID446a11NameBagTransformposXKpB@posY?posZA@rotX籾rotYGf@rotZ>scaleX?scaleY?scaleZ?NicknameTokens by Mothmy Titania (Red)Description By DirectsunGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScriptQ?-- FTC-GUID: 7aca71,10a26a,446a11,9d6b2f -- Utility memory bag by Directsun -- Version 2.5 -- Fork of Memory Bag 2.0 by MrStump function updateSave() local data_to_save = {["ml"]=memoryList} saved_data = JSON.encode(data_to_save) self.script_state = saved_data end function combineMemoryFromBagsWithin() local bagObjList = self.getObjects() for _, bagObj in ipairs(bagObjList) do local data = bagObj.lua_script_state if data ~= nil then local j = JSON.decode(data) if j ~= nil and j.ml ~= nil then for guid, entry in pairs(j.ml) do memoryList[guid] = entry end end end end end function updateMemoryWithMoves() print("updating memory with moves") memoryList = memoryListBackup --get the first transposed object's coordinates local obj = getObjectFromGUID(moveGuid) -- p1 is where needs to go, p2 is where it was local refObjPos = memoryList[moveGuid].pos local deltaPos = findOffsetDistance(obj.getPosition(), refObjPos, nil) local movedRotation = obj.getRotation() for guid, entry in pairs(memoryList) do memoryList[guid].pos.x = entry.pos.x - deltaPos.x memoryList[guid].pos.y = entry.pos.y - deltaPos.y memoryList[guid].pos.z = entry.pos.z - deltaPos.z -- memoryList[guid].rot.x = movedRotation.x -- memoryList[guid].rot.y = movedRotation.y -- memoryList[guid].rot.z = movedRotation.z end --theList[obj.getGUID()] = { -- pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, -- rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, -- lock=obj.getLock() --} moveList = {} end function onload(saved_data) fresh = true if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) --Set up information off of loaded_data memoryList = loaded_data.ml else --Set up information for if there is no saved saved data memoryList = {} end moveList = {} moveGuid = nil if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end end --Beginning Setup --Make setup button function createSetupButton() self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --Triggered by Transpose button function buttonClick_transpose() moveGuid = nil broadcastToAll("Select one object and move it- all objects will move relative to the new location", {0.75, 0.75, 1}) memoryListBackup = duplicateTable(memoryList) memoryList = {} moveList = {} self.clearButtons() createButtonsOnAllObjects(true) createSetupActionButtons(true) end --Triggered by setup button, function buttonClick_setup() memoryListBackup = duplicateTable(memoryList) memoryList = {} self.clearButtons() createButtonsOnAllObjects(false) createSetupActionButtons(false) end function getAllObjectsInMemory() local objTable = {} local curObj = {} for guid in pairs(memoryListBackup) do curObj = getObjectFromGUID(guid) table.insert(objTable, curObj) end return objTable -- return getAllObjects() end --Creates selection buttons on objects function createButtonsOnAllObjects(move) local howManyButtons = 0 local objsToHaveButtons = {} if move == true then objsToHaveButtons = getAllObjectsInMemory() else objsToHaveButtons = getAllObjects() end for _, obj in ipairs(objsToHaveButtons) do if obj ~= self then local dummyIndex = howManyButtons --On a normal bag, the button positions aren't the same size as the bag. globalScaleFactor = 1.25 * 1/self.getScale().x --Super sweet math to set button positions local selfPos = self.getPosition() local objPos = obj.getPosition() local deltaPos = findOffsetDistance(selfPos, objPos, obj) local objPos = rotateLocalCoordinates(deltaPos, self) objPos.x = -objPos.x * globalScaleFactor objPos.y = objPos.y * globalScaleFactor objPos.z = objPos.z * globalScaleFactor --Offset rotation of bag local rot = self.getRotation() rot.y = -rot.y + 180 --Create function local funcName = "selectButton_" .. howManyButtons local func = function() buttonClick_selection(dummyIndex, obj, move) end local color = {0.75,0.25,0.25,0.6} local colorMove = {0,0,1,0.6} if move == true then color = colorMove end self.setVar(funcName, func) self.createButton({ click_function=funcName, function_owner=self, position=objPos, rotation=rot, height=1000, width=1000, color=color, }) howManyButtons = howManyButtons + 1 end end end --Creates submit and cancel buttons function createSetupActionButtons(move) self.createButton({ label="Cancel", click_function="buttonClick_cancel", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Submit", click_function="buttonClick_submit", function_owner=self, position={0,0.3,-2.8}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) if move == false then self.createButton({ label="Add", click_function="buttonClick_add", function_owner=self, position={0,0.3,-3.6}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.25,1,0.25} }) if fresh == false then self.createButton({ label="Set New", click_function="buttonClick_setNew", function_owner=self, position={0,0.3,-4.4}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) self.createButton({ label="Remove", click_function="buttonClick_remove", function_owner=self, position={0,0.3,-5.2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,0.25,0.25} }) end end self.createButton({ label="Reset", click_function="buttonClick_reset", function_owner=self, position={-2,0.3,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --During Setup --Checks or unchecks buttons function buttonClick_selection(index, obj, move) local colorMove = {0,0,1,0.6} local color = {0,1,0,0.6} previousGuid = selectedGuid selectedGuid = obj.getGUID() theList = memoryList if move == true then theList = moveList if previousGuid ~= nil and previousGuid ~= selectedGuid then local prevObj = getObjectFromGUID(previousGuid) prevObj.highlightOff() self.editButton({index=previousIndex, color=colorMove}) theList[previousGuid] = nil end previousIndex = index end if theList[selectedGuid] == nil then self.editButton({index=index, color=color}) --Adding pos/rot to memory table local pos, rot = obj.getPosition(), obj.getRotation() --I need to add it like this or it won't save due to indexing issue theList[obj.getGUID()] = { pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock() } obj.highlightOn({0,1,0}) else color = {0.75,0.25,0.25,0.6} if move == true then color = colorMove end self.editButton({index=index, color=color}) theList[obj.getGUID()] = nil obj.highlightOff() end end --Cancels selection process function buttonClick_cancel() memoryList = memoryListBackup moveList = {} self.clearButtons() if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end removeAllHighlights() broadcastToAll("Selection Canceled", {1,1,1}) moveGuid = nil end --Saves selections function buttonClick_submit() fresh = false if next(moveList) ~= nil then for guid in pairs(moveList) do moveGuid = guid end if memoryListBackup[moveGuid] == nil then broadcastToAll("Item selected for moving is not already in memory", {1, 0.25, 0.25}) else broadcastToAll("Moving all items in memory relative to new objects position!", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(moveList) do moveGuid = guid count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end updateMemoryWithMoves() updateSave() buttonClick_place() end elseif next(memoryList) == nil and moveGuid == nil then memoryList = memoryListBackup broadcastToAll("No selections made.", {0.75, 0.25, 0.25}) end combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() moveGuid = nil end function combineTables(first_table, second_table) for k,v in pairs(second_table) do first_table[k] = v end end function buttonClick_add() fresh = false combineTables(memoryList, memoryListBackup) broadcastToAll("Adding internal bags and selections to existing memory", {0.25, 0.75, 0.25}) combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() end function buttonClick_remove() broadcastToAll("Removing Selected Entries From Memory", {1.0, 0.25, 0.25}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 memoryListBackup[guid] = nil local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Removed", {1,1,1}) memoryList = memoryListBackup updateSave() end function buttonClick_setNew() broadcastToAll("Setting new position relative to items in memory", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for _, obj in ipairs(getAllObjects()) do guid = obj.guid if memoryListBackup[guid] ~= nil then count = count + 1 memoryListBackup[guid].pos = obj.getPosition() memoryListBackup[guid].rot = obj.getRotation() memoryListBackup[guid].lock = obj.getLock() end end broadcastToAll(count.." Objects Saved", {1,1,1}) memoryList = memoryListBackup updateSave() end --Resets bag to starting status function buttonClick_reset() fresh = true memoryList = {} self.clearButtons() createSetupButton() removeAllHighlights() broadcastToAll("Tool Reset", {1,1,1}) updateSave() end --After Setup function createMemoryActionButtons() local guid = self.getGUID() local isTokenBag = (guid == "7aca71") or (guid == "10a26a") or (guid == "446a11") or (guid == "9d6b2f") local showLabel = isTokenBag and "Show Tokens" or "Show Maps" local showWidth = isTokenBag and 1600 or 1400 local hasToolTip = isTokenBag and "Place Tokens" or "" self.createButton({ label=showLabel, click_function="buttonClick_place", function_owner=self, position={0,0.2,-2}, rotation={0,180,0}, height=350, width=showWidth, tooltip=hasToolTip, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Recall", click_function="buttonClick_recall", function_owner=self, position={0,0.2,-2.8}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={-2,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Move", click_function="buttonClick_transpose", function_owner=self, position={-2.8,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) end --Sends objects from bag/table to their saved position/rotation function buttonClick_place() local bagObjList = self.getObjects() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) --If obj is out on the table, move it to the saved pos/rot if obj ~= nil then obj.setPositionSmooth(entry.pos) obj.setRotationSmooth(entry.rot) obj.setLock(entry.lock) else --If obj is inside of the bag for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then local item = self.takeObject({ guid=guid, position=entry.pos, rotation=entry.rot, smooth=false }) item.setLock(entry.lock) break end end end end local obj = getObjectFromGUID("7abc5a") if(obj) then destroyObject(obj) end broadcastToAll("Objects Placed", {1,1,1}) end --Recalls objects to bag from table function buttonClick_recall() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then self.putObject(obj) end end broadcastToAll("Objects Recalled", {1,1,1}) end --Utility functions --Find delta (difference) between 2 x/y/z coordinates function findOffsetDistance(p1, p2, obj) local yOffset = 0 if obj ~= nil then local bounds = obj.getBounds() yOffset = (bounds.size.y - bounds.offset.y) end local deltaPos = {} deltaPos.x = (p2.x-p1.x) deltaPos.y = (p2.y-p1.y) + yOffset deltaPos.z = (p2.z-p1.z) return deltaPos end --Used to rotate a set of coordinates by an angle function rotateLocalCoordinates(desiredPos, obj) local objPos, objRot = obj.getPosition(), obj.getRotation() local angle = math.rad(objRot.y) local x = desiredPos.x * math.cos(angle) - desiredPos.z * math.sin(angle) local z = desiredPos.x * math.sin(angle) + desiredPos.z * math.cos(angle) --return {x=objPos.x+x, y=objPos.y+desiredPos.y, z=objPos.z+z} return {x=x, y=desiredPos.y, z=z} end --Coroutine delay, in seconds --Duplicates a table (needed to prevent it making reference to the same objects) function duplicateTable(oldTable) local newTable = {} for k, v in pairs(oldTable) do newTable[k] = v end return newTable end --Moves scripted highlight from all objects function removeAllHighlights() for _, obj in ipairs(getAllObjects()) do obj.highlightOff() end end --Round number (num) to the Nth decimal (dec) function round(num, dec) local mult = 10^(dec or 0) return math.floor(num * mult + 0.5) / mult end LuaScriptState/){"ml":{"05300b":{"lock":false,"pos":{"x":28.7979,"y":0.9712,"z":59.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"05fa41":{"lock":false,"pos":{"x":28.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"069205":{"lock":false,"pos":{"x":0.7979,"y":0.9712,"z":56.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"06b970":{"lock":false,"pos":{"x":21.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"0a2837":{"lock":false,"pos":{"x":32.7979,"y":0.9712,"z":62.1268},"rot":{"x":0,"y":359.9942,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"14a553":{"lock":false,"pos":{"x":4.3255,"y":0.9712,"z":58.6988},"rot":{"x":0,"y":0.0007,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1819c7":{"lock":false,"pos":{"x":14.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"19ce32":{"lock":false,"pos":{"x":4.7987,"y":0.9712,"z":66.1262},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"274c9f":{"lock":false,"pos":{"x":0.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"2a476f":{"lock":false,"pos":{"x":-2.7021,"y":0.9712,"z":67.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"2e672a":{"lock":false,"pos":{"x":21.7979,"y":0.9712,"z":62.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"34d1f4":{"lock":false,"pos":{"x":16.7979,"y":0.9712,"z":61.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"352897":{"lock":false,"pos":{"x":25.7979,"y":0.9712,"z":61.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"357a27":{"lock":false,"pos":{"x":25.7979,"y":0.9712,"z":63.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"381a8f":{"lock":false,"pos":{"x":18.7979,"y":0.9712,"z":64.1268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"41094f":{"lock":false,"pos":{"x":21.7979,"y":0.9712,"z":60.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"424a1c":{"lock":false,"pos":{"x":19.2978,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"48242f":{"lock":false,"pos":{"x":30.7979,"y":0.9712,"z":58.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"48ebae":{"lock":false,"pos":{"x":6.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"4a98fe":{"lock":false,"pos":{"x":-4.7021,"y":0.9712,"z":64.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"4be729":{"lock":false,"pos":{"x":16.7979,"y":0.9712,"z":58.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"555636":{"lock":false,"pos":{"x":28.7979,"y":0.9712,"z":61.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"55e414":{"lock":false,"pos":{"x":28.7979,"y":0.9712,"z":57.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"577332":{"lock":false,"pos":{"x":6.3285,"y":0.9712,"z":58.7244},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"585aa8":{"lock":false,"pos":{"x":-0.7021,"y":0.9712,"z":64.6268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"59c506":{"lock":false,"pos":{"x":30.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"5db037":{"lock":false,"pos":{"x":28.7979,"y":0.9712,"z":65.1268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"5e7dd7":{"lock":false,"pos":{"x":11.7979,"y":0.9712,"z":63.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"5fae0c":{"lock":false,"pos":{"x":32.7979,"y":0.9712,"z":60.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"644030":{"lock":false,"pos":{"x":32.2998,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"652ae2":{"lock":false,"pos":{"x":-2.7021,"y":0.9712,"z":64.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6a96d9":{"lock":false,"pos":{"x":24.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6e1ec8":{"lock":false,"pos":{"x":23.7979,"y":0.9712,"z":62.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"71ecef":{"lock":false,"pos":{"x":18.7979,"y":0.9712,"z":58.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"71f9c0":{"lock":false,"pos":{"x":28.7979,"y":0.9712,"z":63.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"74b749":{"lock":false,"pos":{"x":4.2977,"y":0.9712,"z":60.6477},"rot":{"x":0,"y":0.0007,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"74d9c3":{"lock":false,"pos":{"x":11.7979,"y":0.9712,"z":59.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"76ea00":{"lock":false,"pos":{"x":24.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7d846e":{"lock":false,"pos":{"x":12.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"816186":{"lock":false,"pos":{"x":28.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"84fa2f":{"lock":false,"pos":{"x":4.798,"y":0.9712,"z":68.1267},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"88450c":{"lock":false,"pos":{"x":30.7979,"y":0.9712,"z":62.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"89a5b5":{"lock":false,"pos":{"x":2.7979,"y":0.9712,"z":56.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8d0f6a":{"lock":false,"pos":{"x":13.7979,"y":0.9712,"z":63.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8e14fc":{"lock":false,"pos":{"x":18.7979,"y":0.9712,"z":61.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8eeabc":{"lock":false,"pos":{"x":30.7979,"y":0.9712,"z":60.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"90cbbc":{"lock":false,"pos":{"x":16.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"91024c":{"lock":false,"pos":{"x":2.7979,"y":0.9712,"z":64.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9386de":{"lock":false,"pos":{"x":23.7979,"y":0.9712,"z":58.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"95d480":{"lock":false,"pos":{"x":2.8,"y":0.9712,"z":66.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9b9a45":{"lock":false,"pos":{"x":11.7979,"y":0.9712,"z":61.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a0305e":{"lock":false,"pos":{"x":19.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a70083":{"lock":false,"pos":{"x":16.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a7ce27":{"lock":false,"pos":{"x":23.7979,"y":0.9712,"z":64.1268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"aae7e6":{"lock":false,"pos":{"x":25.7979,"y":0.9712,"z":57.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"af30ee":{"lock":false,"pos":{"x":26.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"b00393":{"lock":false,"pos":{"x":2.3237,"y":0.9712,"z":58.7256},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"baa3e6":{"lock":false,"pos":{"x":2.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"bfd5a7":{"lock":false,"pos":{"x":21.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c46462":{"lock":false,"pos":{"x":13.7979,"y":0.9712,"z":59.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c51f21":{"lock":false,"pos":{"x":4.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c580b1":{"lock":false,"pos":{"x":25.7979,"y":0.9712,"z":65.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c605da":{"lock":false,"pos":{"x":-0.7021,"y":0.9712,"z":67.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c89580":{"lock":false,"pos":{"x":23.7979,"y":0.9712,"z":60.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"cc8fe7":{"lock":false,"pos":{"x":5.7979,"y":0.9712,"z":56.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"cd3c45":{"lock":false,"pos":{"x":30.2946,"y":0.9712,"z":50.6278},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"d500b5":{"lock":false,"pos":{"x":12.2979,"y":0.9712,"z":53.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"d6205a":{"lock":false,"pos":{"x":2.8006,"y":0.9712,"z":68.127},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"d958ee":{"lock":false,"pos":{"x":17.7946,"y":0.9712,"z":66.1269},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"d99e42":{"lock":false,"pos":{"x":16.7979,"y":0.9712,"z":64.1268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"dedb3b":{"lock":false,"pos":{"x":4.7979,"y":0.9712,"z":64.1268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"dfdd8c":{"lock":false,"pos":{"x":25.7979,"y":0.9712,"z":59.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e018a4":{"lock":false,"pos":{"x":14.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e0c0cc":{"lock":false,"pos":{"x":7.7919,"y":0.9712,"z":66.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e505b6":{"lock":false,"pos":{"x":-4.7021,"y":0.9712,"z":67.6268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e7c22a":{"lock":false,"pos":{"x":7.7979,"y":0.9712,"z":56.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e9756b":{"lock":false,"pos":{"x":13.7979,"y":0.9712,"z":61.1268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"ecb5ed":{"lock":false,"pos":{"x":8.2946,"y":0.9712,"z":53.6278},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"edec80":{"lock":false,"pos":{"x":26.2979,"y":0.9712,"z":50.6268},"rot":{"x":0,"y":-0.0055,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"fef505":{"lock":false,"pos":{"x":30.7979,"y":0.9712,"z":64.1268},"rot":{"x":0,"y":-0.0054,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}}}}XmlUIContainedObjects0y GUID644030NameCustom_Model_Infinite_BagTransformposX_&@@posYv?posZ;PI@rotX >rotY@ vrotZ죾scaleX.?scaleY?scaleZ.?Nickname 5+ CritsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectss0kGUID68eb62Name Custom_TokenTransformposX-#@posY@?posZ(?rotXi@?rotY@[rotZ`D scaleXU?scaleY?scaleZU?Nickname 5+ CritsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13117039322806659776/61FF6E760D7B545A59ABFDE18B253D9BD5590F8A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID0a1547Name Custom_TokenTransformposXposYposZrotXV$rotY̾rotZ ܥ=scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13117039322806659776/61FF6E760D7B545A59ABFDE18B253D9BD5590F8A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI1"GUIDcd3c45NameCustom_Model_Infinite_BagTransformposXjK>@posYv?posZ[PI@rotXa>rotY`vrotZuξscaleX.?scaleYv=?scaleZ.?Nickname Lethal HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID870649Name Custom_TokenTransformposXk,@posYÛ?posZ$@rotX)A?rotY`>rotZhsscaleXU?scaleY?scaleZU?Nickname Lethal HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12240846766101287574/A5ABC380D57B64C61C79EDD2E2C4FB002A65BD7F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0 GUID4564ceNameCustom_Model_BagTransformposXposYposZrotXBټrotYrotZS;scaleX?scaleYt?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexNumberCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsBagOrderLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDa433f7Name Custom_TokenTransformposXposYposZrotX`-=rotYN>rotZ E@;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12240846766101287574/A5ABC380D57B64C61C79EDD2E2C4FB002A65BD7F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI2 GUID816186NameCustom_Model_Infinite_BagTransformposX CL<@posYv?posZ;PI@rotXRɹ>rotY vrotZ@?scaleX.?scaleY?scaleZ.?NicknameSustained HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID8baa89Name Custom_TokenTransformposX`1@posY`?posZ>'@rotX@nldrotYC?rotZv@scaleXU?scaleY?scaleZU?NicknameSustained HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15427546906165424368/8E69222BC102AAA8098F8571631A1A6348B71AA7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID4054a2Name Custom_TokenTransformposXposYposZrotXDH<rotY 6>rotZ`8,!;scaleX ?scaleY@$@scaleZ ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15427546906165424368/8E69222BC102AAA8098F8571631A1A6348B71AA7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3 GUIDedec80NameCustom_Model_Infinite_BagTransformposX CL:@posYv?posZ;PI@rotXQ>rotY` vrotZD`scaleX.?scaleY?scaleZ.?NicknameGains Devastating WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID8e72d5Name Custom_TokenTransformposXG@posYG?posZu8)@rotX@C?rotY >rotZ`l SscaleXU?scaleY?scaleZU?NicknameGains Devastating WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9883319023707143681/1744874734F928AC66A6D8A567EDA9280FBC5F18/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDd519b4Name Custom_TokenTransformposXposYposZrotX<rotY(>rotZнX;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9883319023707143681/1744874734F928AC66A6D8A567EDA9280FBC5F18/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4 GUID76ea00NameCustom_Model_Infinite_BagTransformposX CL8@posYv?posZ;PI@rotX^T>rotY vrotZscaleX.?scaleY?scaleZ.?NicknameGains PrecisionDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUIDd3367cName Custom_TokenTransformposXu!@posYY?posZI)@rotX FurotY@3?rotZv@scaleXU?scaleY?scaleZU?NicknameGains PrecisionDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14178284958638010811/507A7575F85056D82655EF4C74C39CAE3F7768B4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID3b47f5Name Custom_TokenTransformposXposYposZrotXo<rotY#>rotZ T;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14178284958638010811/507A7575F85056D82655EF4C74C39CAE3F7768B4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI5 GUIDbfd5a7NameCustom_Model_Infinite_BagTransformposX CL5@posYw?posZ;PI@rotX@ں>rotY vrotZ scaleX.?scaleY?scaleZ.?NicknameGains HazardousDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUIDb2035fName Custom_TokenTransformposX V@posY`ؚ?posZ \(@rotXXXrotY o>rotZ>nscaleXU?scaleY?scaleZU?NicknameGains HazardousDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17625694776023051311/5B1C35BDDB69F3C2E6697AAF956B9CA96823C795/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID2d8417Name Custom_TokenTransformposXposYposZrotX`3<rotY >rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17625694776023051311/5B1C35BDDB69F3C2E6697AAF956B9CA96823C795/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI6 GUID424a1cNameCustom_Model_Infinite_BagTransformposXrotY@ vrotZ"scaleX.?scaleY?scaleZ.?NicknameCannot OverwatchDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDebaa5eName Custom_TokenTransformposX$#@posYR?posZ8h?rotX@hrotY"t?rotZ Pv@scaleXU?scaleY?scaleZU?NicknameCannot OverwatchDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16579474896655980827/63EC35D5AF76E25E076CEE35143E312FB3A81139/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID5a57beName Custom_TokenTransformposXposYposZrotX!rotYrotZ ܥ=scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16579474896655980827/63EC35D5AF76E25E076CEE35143E312FB3A81139/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI7 GUID90cbbcNameCustom_Model_Infinite_BagTransformposX CL0@posYw?posZ;PI@rotX@>rotY@ vrotZscaleX.?scaleY?scaleZ.?Nickname SmokescreenDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDe50aadName Custom_TokenTransformposX%@posY?posZ@r%@rotX'?rotYxO>rotZ`scaleXU?scaleY?scaleZU?Nickname SmokescreenDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16920264032639971783/000A2AFBC35D008F68D0E617D818B9EA306434A4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7d7dd4Name Custom_TokenTransformposXposYposZrotX [<rotY`>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16920264032639971783/000A2AFBC35D008F68D0E617D818B9EA306434A4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI8} GUIDe018a4NameCustom_Model_Infinite_BagTransformposX`,@posY w?posZ;PI@rotX@>rotY@ vrotZ`NscaleX.?scaleY?scaleZ.?NicknameStealthDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUID53b506Name Custom_TokenTransformposX#@posY?posZ !%@rotX@ rotY>rotZbscaleXU?scaleY?scaleZU?NicknameStealthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9454404294032079729/54602BBC1E3F44337E1A47A73B4BB2E57C4FAD10/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID716fbbName Custom_TokenTransformposXposYposZrotX <rotY@ >rotZ:R;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9454404294032079729/54602BBC1E3F44337E1A47A73B4BB2E57C4FAD10/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI9 GUID7d846eNameCustom_Model_Infinite_BagTransformposX`(@posY w?posZ;PI@rotXƺ>rotY vrotZscaleX.?scaleY?scaleZ.?NicknameFights on DeathDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUID4a3fedName Custom_TokenTransformposX@posY?posZB?rotXvrotY>rotZ`v@scaleXU?scaleY?scaleZU?NicknameFights on DeathDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11674428794801050354/386A4B6C050A0CE2DCCED64BF9367844C6F77FFF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID892122Name Custom_TokenTransformposXposYposZrotXa+rotY ־rotZܥ,scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11674428794801050354/386A4B6C050A0CE2DCCED64BF9367844C6F77FFF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI10 GUID59c506NameCustom_Model_Infinite_BagTransformposX CL>@posYv?posZ;J@rotXj>rotY vrotZ`scaleX.?scaleY?scaleZ.?NicknameReroll Hits of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDffb1b6Name Custom_TokenTransformposX`q4@posY?posZ%@rotX<v@rotY>rotZ@r?scaleXU?scaleY?scaleZU?NicknameReroll Hits of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16573592287641184879/BECC50E0D7BF470ACE1026FF49D694FE08116275/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDe8e076Name Custom_TokenTransformposXposYposZrotX Vc<rotY2>rotZ_P;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16573592287641184879/BECC50E0D7BF470ACE1026FF49D694FE08116275/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI11 GUID05fa41NameCustom_Model_Infinite_BagTransformposX CL<@posYv?posZ;J@rotX>rotY vrotZscaleX.?scaleY?scaleZ.?Nickname Reroll HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDc9e637Name Custom_TokenTransformposX@5@posY@o?posZG$@rotXlؾrotY>rotZ scaleXU?scaleY?scaleZU?Nickname Reroll HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13702994610357544702/5D8174C93A2B92D5C3640170DADB163AD61DD428/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9798a1Name Custom_TokenTransformposXposYposZrotX<rotYZI>rotZww:scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13702994610357544702/5D8174C93A2B92D5C3640170DADB163AD61DD428/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI12 GUIDaf30eeNameCustom_Model_Infinite_BagTransformposX CL:@posYv?posZ;J@rotX.>rotY vrotZʘscaleX.?scaleY?scaleZ.?NicknameReroll Wounds of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects}0uGUIDf3c807Name Custom_TokenTransformposX@posY?posZ@rotXoC?rotY`)þrotZPscaleXU?scaleY?scaleZU?NicknameReroll Wounds of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10596262547218845676/80BAF98E755A30DE66E70E8B26B7431C90B656F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7d06e2Name Custom_TokenTransformposXposYposZrotX5<rotY >rotZ`rotY vrotZξscaleX.?scaleY?scaleZ.?NicknameReroll WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsx0pGUIDcbc745Name Custom_TokenTransformposX@"@posY?posZ` @rotXnaӾrotY>rotZ@kscaleXU?scaleY?scaleZU?NicknameReroll WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16879464771115311829/92FD9A31DF701FA755052F16FFC2B77FAC97113F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDfa95b6Name Custom_TokenTransformposXposYposZrotXa<rotY >rotZ@=U;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16879464771115311829/92FD9A31DF701FA755052F16FFC2B77FAC97113F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI14 GUID06b970NameCustom_Model_Infinite_BagTransformposX CL5@posYv?posZ;J@rotX )>rotY vrotZ@|scaleX.?scaleY?scaleZ.?NicknameBenefit of CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUID55285cName Custom_TokenTransformposXՙ$@posY`4?posZ@{(@rotX {?rotY =>rotZ@!scaleXU?scaleY?scaleZU?NicknameBenefit of CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13129253881179680642/AEB18C3A5901A5E1FC216A0043BB54AADD96E2F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9c6487Name Custom_TokenTransformposXposYposZrotX !=rotYP>rotZ`%};scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13129253881179680642/AEB18C3A5901A5E1FC216A0043BB54AADD96E2F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI15 GUIDa0305eNameCustom_Model_Infinite_BagTransformposX CL3@posYv?posZ;J@rotX>rotY vrotZ:scaleX.?scaleY?scaleZ.?NicknameDoes Not Receive CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUIDb584a2Name Custom_TokenTransformposXRl,@posYB?posZ$@rotX1?rotYb>rotZ`(?scaleXU?scaleY?scaleZU?NicknameDoes Not Receive CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9639889861660117737/0B5D565B0CD65E5C6CFA51C5F288F6124025E8A0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID148cf1Name Custom_TokenTransformposXposYposZrotX`=rotY3>rotZZ;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9639889861660117737/0B5D565B0CD65E5C6CFA51C5F288F6124025E8A0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI16GUIDa70083NameCustom_Model_Infinite_BagTransformposX CL0@posYv?posZ;J@rotXq>rotY vrotZscaleX.?scaleY?scaleZ.?NicknameHiddenDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects 0 GUID7e7cf7Name Custom_TokenTransformposX=@posY?posZ( rotXe9?rotYrotZd?scaleXU?scaleY?scaleZU?Nickname Hidden @ 6"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13111807940430769035/CBD479C969522BBBDB4953C9C332980F947417EB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates[ 2oGUID82ed6bName Custom_TokenTransformposX,4@posY(?posZsrotX5zrotYf@rotZ`!scaleXU?scaleY?scaleZU?Nickname Hidden @ 12"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10577368540891568977/A9D18639DAB97BFB6BB7478061E8A43AF13343A3/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3oGUID56fbd2Name Custom_TokenTransformposX,4@posY(?posZsrotX3RprotYf@rotZ`׉oscaleXU?scaleY?scaleZU?Nickname Hidden @ 15"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15408236362422630855/5B2EF93025FA2AAA08F7A3718C8EF674A79987BD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4oGUIDfa4578Name Custom_TokenTransformposX,4@posY(?posZsrotXtrotYf@rotZ|HyscaleXU?scaleY?scaleZU?Nickname Hidden @ 18"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12898265374851711210/0D3BA9B40B7694C5C8A0E0BB676ECD39F7C5A6F1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID84d4bfName Custom_TokenTransformposXposYposZrotX`(=rotY >rotZ೿;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17012583816697795177/A7AA6AA08ED24A1ABF503047C27EEEEC80882CE0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI17GUID1819c7NameCustom_Model_Infinite_BagTransformposX`,@posYv?posZ;J@rotX9>rotY vrotZrscaleX.?scaleY?scaleZ.?NicknameDetection Range ExtendedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects} 0u GUIDd31c8eName Custom_TokenTransformposX`U:@posY`?posZ\A rotX6K?rotY`㾿rotZ` ?scaleXU?scaleY?scaleZU?NicknameDetection Range +3"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18116541524647504932/67C008E0C26D38A48107A612A2CB8054E20A03F4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates2vGUID8d164dName Custom_TokenTransformposX@)4)@posY(?posZsrotXಘj>rotYf@rotZ -scaleXU?scaleY?scaleZU?NicknameDetection Range +6"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13578796208054333397/208B15C4CD723905CB2C071A22EC3AF8C9E74D84/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3vGUID7ae190Name Custom_TokenTransformposX@)4)@posY(?posZsrotX}uwrotYf@rotZ@\?>scaleXU?scaleY?scaleZU?NicknameDetection Range +9"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13343950658874597448/8C16114AB202447150E38194AEC35A73A8588C1C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID0f8253Name Custom_TokenTransformposXposYposZrotX0=rotY`>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12040488905665960210/DB17D7F6800714273F5C47B19EAC8E8E6768773C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI18 GUIDd500b5NameCustom_Model_Infinite_BagTransformposX`(@posYv?posZ;J@rotX :>rotY` vrotZr唾scaleX.?scaleY?scaleZ.?NicknameRemains HiddenDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID14d9a1Name Custom_TokenTransformposX~#@posYn?posZrotX`j?rotY?rotZHM?scaleXU?scaleY?scaleZU?NicknameRemains HiddenDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16281145395570577750/AAD619DFE3C79EA0A4D02365EA2124B53DD74C8F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7934beName Custom_TokenTransformposXposYposZrotX /=rotYR>rotZ Vg;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16281145395570577750/AAD619DFE3C79EA0A4D02365EA2124B53DD74C8F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI19 GUIDecb5edNameCustom_Model_Infinite_BagTransformposXՖ @posYw?posZ[J@rotX'>rotY@ vrotZ`$scaleX.?scaleY?scaleZ.?NicknameReactive Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects}0uGUIDf9d515Name Custom_TokenTransformposXN4@posY?posZmrotX\?rotYrrotZ0;Q?scaleXU?scaleY?scaleZU?NicknameReactive Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9945488395228674043/177739F2E29ECA9B5EB8D1C03593D41AC3D61CD4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID657cbdName Custom_TokenTransformposXposYposZrotX`6<rotY >rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9945488395228674043/177739F2E29ECA9B5EB8D1C03593D41AC3D61CD4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI20 GUID48ebaeNameCustom_Model_Infinite_BagTransformposX 1@posYw?posZ;J@rotX@$G>rotY.vrotZ`scaleX.?scaleY?scaleZ.?NicknameSurge Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUID3b97fcName Custom_TokenTransformposXű6@posYݜ?posZnrotX>rotY[>rotZ@SscaleXU?scaleY?scaleZU?NicknameSurge Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17315242775941199100/98DE7544551B308FF2931DEA446FD9EDD93CF103/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID84b18eName Custom_TokenTransformposXposYposZrotX]<rotY>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17315242775941199100/98DE7544551B308FF2931DEA446FD9EDD93CF103/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI21 GUIDc51f21NameCustom_Model_Infinite_BagTransformposX 1@posY w?posZ;J@rotX%>rotYQvrotZ scaleX.?scaleY?scaleZ.?NicknameOne-Shot ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID8ef373Name Custom_TokenTransformposX:@posY`?posZ`I:,@rotX?rotY`>rotZ"scaleXU?scaleY?scaleZU?NicknameOne-Shot ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15705922047993856303/1DA72B9807A496E1D5DCEAB0D1B4A670F8A171B7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID5532f5Name Custom_TokenTransformposXposYposZrotX=rotY`>rotZk_;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15705922047993856303/1DA72B9807A496E1D5DCEAB0D1B4A670F8A171B7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI22 GUIDbaa3e6NameCustom_Model_Infinite_BagTransformposXb@posY w?posZ;J@rotX>rotYvrotZscaleX.?scaleY?scaleZ.?NicknameHas Used CP ReductionDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUID4c8061Name Custom_TokenTransformposX' @posY`ۛ?posZ3rotX@dU?rotY@8rotZO!HscaleXU?scaleY?scaleZU?NicknameHas Used CP ReductionDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10920939702060286966/EE1CBC5E43E3ECA87FD2AC30D153130F979FCD4D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID041223Name Custom_TokenTransformposXposYposZrotX u<rotY',>rotZG;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10920939702060286966/EE1CBC5E43E3ECA87FD2AC30D153130F979FCD4D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI23 GUID274c9fNameCustom_Model_Infinite_BagTransformposX?posY w?posZ;J@rotX@o>rotY@vrotZ@YscaleX.?scaleY?scaleZ.?NicknameTargeted By StratagemDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUID2ab1edName Custom_TokenTransformposXR`&@posY@}?posZ-@rotX-(rotY>rotZ@ KTscaleXU?scaleY?scaleZU?NicknameTargeted By StratagemDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16399290930890555419/EB65FE669AD62D4CEAE8AADB4BA2C87691A9C36F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7a113cName Custom_TokenTransformposXposYposZrotX@_<rotY>rotZbOR;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16399290930890555419/EB65FE669AD62D4CEAE8AADB4BA2C87691A9C36F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI24 GUID069205NameCustom_Model_Infinite_BagTransformposX f?posYw?posZ;L@rotXN>rotY`vrotZ scaleX.?scaleY?scaleZ.?Nickname Must TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDc35bd2Name Custom_TokenTransformposX"@posY#?posZ,@rotX]X?rotYU>rotZ4XI?scaleXU?scaleY?scaleZU?Nickname Must TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10751720015503100727/2E21B57300D3ADEE1C2B42CAF7E5E48B02D8F58D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID5646e8Name Custom_TokenTransformposXposYposZrotX@c<rotY>rotZQ;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10751720015503100727/2E21B57300D3ADEE1C2B42CAF7E5E48B02D8F58D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI25 GUID89a5b5NameCustom_Model_Infinite_BagTransformposX`b@posYv?posZ;L@rotX8>rotY` vrotZ, scaleX.?scaleY?scaleZ.?NicknameDamage RedirectedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects|0tGUID3ffc48Name Custom_TokenTransformposX1z&@posY o?posZ@'@rotX:?rotY>rotZURscaleXU?scaleY?scaleZU?NicknameDamage RedirectedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16302826763490189699/4FD199B8CB9C00D0B5CC4282CD0DE0375B030A66/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID70501dName Custom_TokenTransformposXposYposZrotX<rotY>rotZO;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16302826763490189699/4FD199B8CB9C00D0B5CC4282CD0DE0375B030A66/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI26 GUIDcc8fe7NameCustom_Model_Infinite_BagTransformposX 1@posYv?posZ;L@rotX ܸ>rotY@ vrotZ,scaleX.?scaleY?scaleZ.?NicknameRevivedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID38cc1cName Custom_TokenTransformposX 1@posY`?posZO rotX2v@rotY$?rotZ{v@scaleXU?scaleY?scaleZU?NicknameRevivedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12108649957143102648/B86B42D08DA25323EF2575B93309BCEE8B5AF17D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID81dfe5Name Custom_TokenTransformposXposYposZrotX%=rotYe>rotZe;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12108649957143102648/B86B42D08DA25323EF2575B93309BCEE8B5AF17D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI27 GUIDe7c22aNameCustom_Model_Infinite_BagTransformposX 1@posYv?posZ;L@rotX/|>rotY`ՓvrotZUscaleX.?scaleY?scaleZ.?NicknameReinforcements GrantedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID427544Name Custom_TokenTransformposX(@posY~?posZ-@rotXv@rotY<;?rotZujv@scaleXU?scaleY?scaleZU?NicknameReinforcements GrantedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10547314673171727786/63CFA3D86438F8257E8791778EB6D9EC35FBF8B2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID85043fName Custom_TokenTransformposXposYposZrotX <rotY@W>rotZ:scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10547314673171727786/63CFA3D86438F8257E8791778EB6D9EC35FBF8B2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI28 GUID55e414NameCustom_Model_Infinite_BagTransformposX C<@posY`v?posZ;L@rotXB>rotY@ vrotZ r~scaleX.?scaleY?scaleZ.?Nickname + AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID911896Name Custom_TokenTransformposX0@posY`~?posZٜ@rotX ظ?rotYTrotZ au?scaleXU?scaleY?scaleZU?Nickname + AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16271260079635507156/277568FF9E8415D6B84470B89254EAB9336770F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID8c3039Name Custom_TokenTransformposXposYposZrotXn<rotY >rotZ`W;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16271260079635507156/277568FF9E8415D6B84470B89254EAB9336770F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI29{ GUID48242fNameCustom_Model_Infinite_BagTransformposX C>@posY`v?posZ;M@rotX`S>rotY vrotZ@裾scaleX.?scaleY?scaleZ.?Nickname+1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUIDa8e784Name Custom_TokenTransformposX 1@posY|?posZ#!@rotX`QrotYd rotZCscaleXU?scaleY?scaleZU?Nickname+1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13452547071002326686/A5A8312740867EB44DE818EBBC13B0DC3E7CBEB9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDded110Name Custom_TokenTransformposXposYposZrotX@l,=rotY`[Q>rotZg;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13452547071002326686/A5A8312740867EB44DE818EBBC13B0DC3E7CBEB9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI30 GUID05300bNameCustom_Model_Infinite_BagTransformposX C<@posY@v?posZ;M@rotX@t>rotY vrotZrscaleX.?scaleY?scaleZ.?Nickname + StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID3b4a72Name Custom_TokenTransformposX@*5@posY ?posZr@rotX " ?rotY DrotZRa?scaleXU?scaleY?scaleZU?Nickname + StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10049358500231104139/B10C58B14CB8D2C666635D3CC65B906FE82F0182/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID579dacName Custom_TokenTransformposXposYposZrotXrotY>rotZrscaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10049358500231104139/B10C58B14CB8D2C666635D3CC65B906FE82F0182/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI31{ GUID8eeabcNameCustom_Model_Infinite_BagTransformposX C>@posY v?posZ;N@rotX !>rotY` vrotZ`/scaleX.?scaleY?scaleZ.?Nickname+1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID1418fdName Custom_TokenTransformposX5@posY?posZ[l @rotX`KrotY-D>rotZ ascaleXU?scaleY?scaleZU?Nickname+1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10482415152197510341/5D0FC3E9FE9431F09A9F9EFFA4F72829BDA21B1A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9c9affName Custom_TokenTransformposXposYposZrotXb<rotY>rotZ$S;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10482415152197510341/5D0FC3E9FE9431F09A9F9EFFA4F72829BDA21B1A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI32 GUID5fae0cNameCustom_Model_Infinite_BagTransformposX!f@@posY v?posZ;N@rotX(>rotY` vrotZjНscaleX.?scaleY?scaleZ.?Nickname +1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID03ebdbName Custom_TokenTransformposX@ٽ&@posYʜ?posZlP!@rotXU^?rotY`iU>rotZYHscaleXU?scaleY?scaleZU?Nickname +1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16724993230547231552/75E836EC8E685E10F6BA37BF7BCCD910F32CD675/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDec9a64Name Custom_TokenTransformposXposYposZrotX $=rotYQ~>rotZ3;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16724993230547231552/75E836EC8E685E10F6BA37BF7BCCD910F32CD675/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI33 GUID0a2837NameCustom_Model_Infinite_BagTransformposX!f@@posYv?posZ;O@rotX >rotY@v@rotZH]scaleX.?scaleY?scaleZ.?Nickname +1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID0e555dName Custom_TokenTransformposX`},@posY?posZ֢ @rotX@!*rotYrotZ`ZascaleXU?scaleY?scaleZU?Nickname +1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16189872790619466777/BAF37F4E6E6A8B70C21BA5C183D8910E289F9A20/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID868130Name Custom_TokenTransformposXposYposZrotX֑rotY@hrotZ ܥ<=scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16189872790619466777/BAF37F4E6E6A8B70C21BA5C183D8910E289F9A20/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI34{ GUID88450cNameCustom_Model_Infinite_BagTransformposX C>@posY v?posZ;O@rotX U>rotY vrotZBscaleX.?scaleY?scaleZ.?Nickname+1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUIDc34ee0Name Custom_TokenTransformposX!@posY6?posZ@rotX ?)rotY;rotZ$?scaleXU?scaleY?scaleZU?Nickname+1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13741902694431974409/713853EEA37AEA1DED423CAC8A9867472274333F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDedff80Name Custom_TokenTransformposXposYposZrotXB<rotY >rotZW;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13741902694431974409/713853EEA37AEA1DED423CAC8A9867472274333F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI35 GUID555636NameCustom_Model_Infinite_BagTransformposX C<@posY v?posZ;N@rotX>rotY vrotZ BscaleX.?scaleY?scaleZ.?Nickname + ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID25254dName Custom_TokenTransformposXm@posY?posZ`F @rotX G??rotYݠrotZ`.ascaleXU?scaleY?scaleZU?Nickname + ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11864317304319829640/DD29F55FBD4AD875E79CBD7D5BDAE679DF3395D0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID8edccdName Custom_TokenTransformposXposYposZrotXWT<rotY>rotZ`AX;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11864317304319829640/DD29F55FBD4AD875E79CBD7D5BDAE679DF3395D0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI36y GUID71f9c0NameCustom_Model_Infinite_BagTransformposX C<@posYv?posZ;O@rotX@V>rotY vrotZN$scaleX.?scaleY?scaleZ.?Nickname+ LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID26f382Name Custom_TokenTransformposX.@posYڜ?posZv@rotX<.rotY >rotZQscaleXU?scaleY?scaleZU?Nickname+ LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15152501747465676254/3B23F35DB58C97F2786B599BD3BF50A3CC58C680/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDd2efafName Custom_TokenTransformposXposYposZrotX"F<rotY>rotZU;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15152501747465676254/3B23F35DB58C97F2786B599BD3BF50A3CC58C680/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI37{ GUIDfef505NameCustom_Model_Infinite_BagTransformposX C>@posYv?posZP@rotXʹ>rotY@/vrotZscaleX.?scaleY?scaleZ.?Nickname+1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID53919dName Custom_TokenTransformposX`*@posYJ?posZ@i@rotX|Q?rotYj>rotZ SI"scaleXU?scaleY?scaleZU?Nickname+1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14007571777471691581/91FE25A3E7DEF2CB33D764AA10EC891B21D2372C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID566080Name Custom_TokenTransformposXposYposZrotX =rotY5>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14007571777471691581/91FE25A3E7DEF2CB33D764AA10EC891B21D2372C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI38y GUID5db037NameCustom_Model_Infinite_BagTransformposX C<@posYv?posZHP@rotX`Ƈ>rotY@0vrotZb%scaleX.?scaleY?scaleZ.?Nickname+ OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID1863ddName Custom_TokenTransformposXf%@posY@?posZ@rotXtzR?rotY%>rotZ^HHscaleXU?scaleY?scaleZU?Nickname+ OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10092036182535191754/568874EF434ADC1BD0AFEAAD8B750AE16760874D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDcdd97bName Custom_TokenTransformposXposYposZrotX grotY@x>rotZ`vscaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10092036182535191754/568874EF434ADC1BD0AFEAAD8B750AE16760874D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI39 GUIDaae7e6NameCustom_Model_Infinite_BagTransformposX C9@posYv?posZ;L@rotX>rotY vrotZ`/nscaleX.?scaleY?scaleZ.?Nickname - AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID12ed37Name Custom_TokenTransformposX3@posY?posZv@rotX<4C?rotY>rotZZscaleXU?scaleY?scaleZU?Nickname - AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10215664029825245805/D9D2E3338C0B7C6E10BB5E7D116C273A1ACD5774/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9eaf0bName Custom_TokenTransformposXposYposZrotXw<rotY`>rotZ U;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10215664029825245805/D9D2E3338C0B7C6E10BB5E7D116C273A1ACD5774/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI40 GUIDdfdd8cNameCustom_Model_Infinite_BagTransformposX C9@posY`v?posZ;M@rotXͺ>rotY@ vrotZ䜾scaleX.?scaleY?scaleZ.?Nickname - StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID1b2782Name Custom_TokenTransformposX_[7@posY`?posZ@@rotX`.$rotYD>rotZQscaleXU?scaleY?scaleZU?Nickname - StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18159368108062106036/E003AE3043B8EF18F602EA067DD23200BBD8B5D8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID024ee2Name Custom_TokenTransformposXposYposZrotX4rotY@BܾrotZ`ܥ,=scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18159368108062106036/E003AE3043B8EF18F602EA067DD23200BBD8B5D8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI41 GUID352897NameCustom_Model_Infinite_BagTransformposX C9@posY@v?posZ;N@rotX>rotY vrotZ scaleX.?scaleY?scaleZ.?Nickname - ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDcba2fbName Custom_TokenTransformposX( "@posY?posZ Xk@rotX`j?rotY`UrotZ*hjscaleXU?scaleY?scaleZU?Nickname - ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15943989863304312894/260EE7D9D6A965285740964CC970CF38868933CD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDf96824Name Custom_TokenTransformposXposYposZrotX@j&=rotYE>rotZ`(;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15943989863304312894/260EE7D9D6A965285740964CC970CF38868933CD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI42y GUID357a27NameCustom_Model_Infinite_BagTransformposX C9@posY v?posZ;O@rotX T2>rotY@ vrotZ bscaleX.?scaleY?scaleZ.?Nickname- LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID058b9cName Custom_TokenTransformposX`E0@posY ?posZ:=@rotX O?rotYa4>rotZoscaleXU?scaleY?scaleZU?Nickname- LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16292828654295729178/E750C8E2E37C8CAB56D7AE7E29B8DF5E32B1B4BB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDf1ecabName Custom_TokenTransformposXposYposZrotXH~,=rotYI>rotZ?%;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16292828654295729178/E750C8E2E37C8CAB56D7AE7E29B8DF5E32B1B4BB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI43y GUIDc580b1NameCustom_Model_Infinite_BagTransformposX C9@posYv?posZHP@rotXC>rotY vrotZwscaleX.?scaleY?scaleZ.?Nickname- OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID13653cName Custom_TokenTransformposX6Q(@posY"?posZ} @rotX*^?rotYrotZ 0yv@scaleXU?scaleY?scaleZU?Nickname- OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10213060837264217490/55BFE727ED0F450BAB85C969A2A1BEF4FFFFD342/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDa9ef4bName Custom_TokenTransformposXposYposZrotX`@k<rotY>rotZ`WS;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10213060837264217490/55BFE727ED0F450BAB85C969A2A1BEF4FFFFD342/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI44{ GUID9386deNameCustom_Model_Infinite_BagTransformposX C7@posYv?posZ;M@rotX/>rotY vrotZRscaleX.?scaleY?scaleZ.?Nickname-1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUIDc3bb29Name Custom_TokenTransformposX5@posY@%?posZ`8?!@rotXmeF?rotYT>rotZsFscaleXU?scaleY?scaleZU?Nickname-1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13568336362575666097/C1F5742E1B4CAEBE32F1481FDF1B8F7B2BE491B0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDc42f24Name Custom_TokenTransformposXposYposZrotX=rotY>rotZQm;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13568336362575666097/C1F5742E1B4CAEBE32F1481FDF1B8F7B2BE491B0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI45{ GUIDc89580NameCustom_Model_Infinite_BagTransformposX C7@posY@v?posZ;N@rotX +>rotY vrotZ@scaleX.?scaleY?scaleZ.?Nickname-1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID2df84bName Custom_TokenTransformposXv@posYy?posZQ@rotX+rotY@&WrotZ`E0dscaleXU?scaleY?scaleZU?Nickname-1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10096175161667092493/A6287ECD0E8BCECD2BB8F114807109BEB151A377/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDf168cdName Custom_TokenTransformposXposYposZrotX@?N߼rotYΡrotZ u#;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10096175161667092493/A6287ECD0E8BCECD2BB8F114807109BEB151A377/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI46 GUID41094fNameCustom_Model_Infinite_BagTransformposX C5@posY`v?posZ;N@rotX\>rotY` vrotZ=scaleX.?scaleY?scaleZ.?Nickname -1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID675125Name Custom_TokenTransformposX`_'@posY?posZ="@rotXխ?rotY+>rotZ@v@scaleXU?scaleY?scaleZU?Nickname -1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15453432006274208987/8073A853E98F75AE685947D725F88804399A63C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID705d37Name Custom_TokenTransformposXposYposZrotX s<rotY>rotZ4Q;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15453432006274208987/8073A853E98F75AE685947D725F88804399A63C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI47 GUID2e672aNameCustom_Model_Infinite_BagTransformposX C5@posY@v?posZ;O@rotXo>rotY vrotZ@룾scaleX.?scaleY?scaleZ.?Nickname -1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID55fc40Name Custom_TokenTransformposX /@posYC?posZe@rotXX0?rotY N?rotZ ?scaleXU?scaleY?scaleZU?Nickname -1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11399839414502113854/C943D330CF4C35BAE7266CB3D233B5C3DA1FB522/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDe311b7Name Custom_TokenTransformposXposYposZrotX=rotY`p>rotZk;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11399839414502113854/C943D330CF4C35BAE7266CB3D233B5C3DA1FB522/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI48y GUID6e1ec8NameCustom_Model_Infinite_BagTransformposX C7@posY@v?posZ;O@rotX>rotY` vrotZˢscaleX.?scaleY?scaleZ.?Nickname-1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID5b32f3Name Custom_TokenTransformposX':'@posY}?posZ@rotX`-DrotY`a?rotZoK?scaleXU?scaleY?scaleZU?Nickname-1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9728900852482551829/9D6FEED89DBC1F57BE077F9C52DEA0AE5FCE520C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID13852eName Custom_TokenTransformposXposYposZrotX`=rotY@jv>rotZ@rDp;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9728900852482551829/9D6FEED89DBC1F57BE077F9C52DEA0AE5FCE520C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI49{ GUIDa7ce27NameCustom_Model_Infinite_BagTransformposX C7@posY v?posZP@rotX`ţ>rotY`0vrotZ#옾scaleX.?scaleY?scaleZ.?Nickname-1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID82cd8fName Custom_TokenTransformposXJ;.@posY?posZ@rotX>rotY >rotZ`J?scaleXU?scaleY?scaleZU?Nickname-1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11191857537696704764/9A1D8E9EE932AF88EFC323FA7E9511A2E95A478B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDe65300Name Custom_TokenTransformposXposYposZrotX@ =rotY2>rotZfx;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11191857537696704764/9A1D8E9EE932AF88EFC323FA7E9511A2E95A478B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI50GUID71ecefNameCustom_Model_Infinite_BagTransformposX C2@posYv?posZ;M@rotX >rotY@ vrotZscaleX.?scaleY?scaleZ.?NicknameInvulnerable SaveDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUIDad3a15Name Custom_TokenTransformposX@posY g?posZ`ErotXڐK?rotYM*l?rotZ~fscaleXU?scaleY?scaleZU?NicknameInvulnerable Save 2++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12009848354843382953/7FA10C06EFD168359F395B5A0386DCA26A0A9021/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates 2xGUID69828dName Custom_TokenTransformposX4@posY(?posZ@߿rotX '>rotYf@rotZ숾scaleXU?scaleY?scaleZU?NicknameInvulnerable Save 3++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16574075971847558735/A45728C8B46A93E7D851C72031821D1306E03B27/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3xGUIDf3efc8Name Custom_TokenTransformposX4@posY(?posZ@߿rotX,>rotYf@rotZ xc>scaleXU?scaleY?scaleZU?NicknameInvulnerable Save 4++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17695437947738351576/2CF97D4266D04FE765E658FE8204720BE5EC81BC/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4xGUID45ac85Name Custom_TokenTransformposX4@posY(?posZ@߿rotX @wrotYf@rotZ`Xt>scaleXU?scaleY?scaleZU?NicknameInvulnerable Save 5++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15189500708022316921/0DEE6EF800E57B936D2170E5ADBA36C33C829F9C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI5xGUID129d53Name Custom_TokenTransformposX4@posY(?posZ@߿rotX y>rotYf@rotZ@`SscaleXU?scaleY?scaleZU?NicknameInvulnerable Save 6++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11525460984140145217/1F22571DA69F5C00B3563943121726B76B226812/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDcd9bc8Name Custom_TokenTransformposXposYposZrotX =rotY mz>rotZILp;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16277335046009663357/3EA844CCF9609BF787C77D40144C0A5C2E93C43F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI51GUID4be729NameCustom_Model_Infinite_BagTransformposX C0@posYv?posZ;M@rotXA>rotY` vrotZpscaleX.?scaleY?scaleZ.?Nickname Feel No PainDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects 0 GUID4becadName Custom_TokenTransformposX*@posY ՜?posZ`:DrotXQ?rotY@R>rotZr0scaleXU?scaleY?scaleZU?NicknameFeel No Pain 3+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16858664454600794287/58B272FA41B40D14F6FF38C3D2D49D0D2A27274C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStatesj 2tGUID5db7cdName Custom_TokenTransformposX`h1@posY(?posZrotX@frotYf@rotZ`ljscaleXU?scaleY?scaleZU?NicknameFeel No Pain 4+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18098782768342139719/58F0449D22E5DB7A78A4F863E690BC5B9EB17D55/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3tGUIDab97bdName Custom_TokenTransformposX`h1@posY(?posZrotX.qrotYf@rotZerscaleXU?scaleY?scaleZU?NicknameFeel No Pain 5+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13215125677209945797/8CB4F5ECA2408950AB42F9170C7D6644B29CBBED/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4tGUID1c250aName Custom_TokenTransformposX`h1@posY(?posZrotX0GrotYf@rotZ?zscaleXU?scaleY?scaleZU?NicknameFeel No Pain 6+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15872277241649270468/57AAFB6A95185A6811D5DE8CC50D7080C1E50D6F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID08f3baName Custom_TokenTransformposXposYposZrotX/=rotY y>rotZ v;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15207957715838043874/25795D562205CD5513DAB82A81F9224EDCD9EC0B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI52 GUID34d1f4NameCustom_Model_Infinite_BagTransformposX C0@posY`v?posZ;N@rotX(>rotY vrotZ@Y5scaleX.?scaleY?scaleZ@.?NicknameIgnore ModifiersDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDe131f5Name Custom_TokenTransformposX46@posYӜ?posZDW?rotXq*C?rotY оrotZ`YscaleXU?scaleY?scaleZU?NicknameIgnore ModifiersDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10164397719206957151/D451EC2BF34215FEE0E5822984B697E1AAED239E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID0a61c9Name Custom_TokenTransformposXposYposZrotX<rotYoI>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10164397719206957151/D451EC2BF34215FEE0E5822984B697E1AAED239E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI53mGUID8e14fcNameCustom_Model_Infinite_BagTransformposX C2@posY`v?posZ;N@rotX\>rotY` vrotZ=scaleX.?scaleY?scaleZ.?NicknameOC SetDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsb 0Z GUID991eb4Name Custom_TokenTransformposX`#'@posY@ț?posZx+@rotX`BSrotY`?rotZ@;scaleXU?scaleY?scaleZU?Nickname OC Set to 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17350645795018514721/238B1C2FA469454BE59F4428BD890E480CA0C846/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates2lGUID63d51eName Custom_TokenTransformposX5@posY(?posZ`( @rotX KjrotY@f@rotZ@>scaleXU?scaleY?scaleZU?Nickname OC HalvedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17748053706262387093/0AE5E8CC3E60B7DDB33A641702CC167C97E58213/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3mGUID54c6a2Name Custom_TokenTransformposX5@posY(?posZ`( @rotX:vrotY@f@rotZ scaleXU?scaleY?scaleZU?Nickname OC DoubledDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18283895009815519438/0CADB1DD445663D1495D8638459AEA6F1EB8E508/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDb9089eName Custom_TokenTransformposXposYposZrotX=rotY>rotZq;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9892587370100184732/C5B6BA51E2BEEAAD09B86A30A28DA9CBDE99ED21/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI54GUID381a8fNameCustom_Model_Infinite_BagTransformposX C2@posY@v?posZP@rotX*>rotY/vrotZ@ۥscaleX.?scaleY?scaleZ@.?NicknameMovement BuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects 0 GUID887fcbName Custom_TokenTransformposXc@posYposYh%posZ=rotX<rotY,>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10888346467352832421/2D21AC1F8EDE549F4853843CC799BBD494244820/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI55GUIDd99e42NameCustom_Model_Infinite_BagTransformposX C0@posY@v?posZP@rotXü>rotY.vrotZ@؞scaleX.?scaleY?scaleZ@.?NicknameMovement DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0 GUID73bd57Name Custom_TokenTransformposX|[+@posY`b?posZ`?rotX`frotYѾrotZv@scaleXU?scaleY?scaleZU?NicknameMove DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17482041595606334591/634D51CF193D41F3A8904782FE74C7C01CF6DA0C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates 2yGUIDe27c26Name Custom_TokenTransformposX`h1@posY(?posZ݈?rotX Q rotYf@rotZ8΂>scaleXU?scaleY?scaleZU?NicknameMove + Advance DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9858426759466265784/7F1CFB05FDD791B9D189B298AF6E93D879A5670E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3yGUID18578dName Custom_TokenTransformposX`h1@posY(?posZ݈?rotXQrotYf@rotZJs>scaleXU?scaleY?scaleZU?NicknameMove + Charge DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16675767374373495035/CF8E4092718B704BD42740E368682B613B043064/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4GUID1a0382Name Custom_TokenTransformposX`h1@posY(?posZ݈?rotX`5krotYf@rotZz^>scaleXU?scaleY?scaleZU?Nickname!Move + Advance + Charge DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12558882588344479665/10258EB2135ECE48D7C1FEC724E6E4B52FD43565/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID17c88cName Custom_TokenTransformposXposY=posZYkp>rotX |<rotY@>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17482041595606334591/634D51CF193D41F3A8904782FE74C7C01CF6DA0C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI56} GUIDd958eeNameCustom_Model_Infinite_BagTransformposXj1@posY v?posZ P@rotXݺ>rotY@ vrotZ@<͛scaleX.?scaleY?scaleZ.?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUID1d5c0dName Custom_TokenTransformposX1@posY@?posZ|rotX@protY ErotZt?scaleXU?scaleY?scaleZU?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10441700730326708145/B6D253C9C9122D44192091AFFE016459D492D3FB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDa243afName Custom_TokenTransformposXposYposZrotX<rotY@F>rotZ|T;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10441700730326708145/B6D253C9C9122D44192091AFFE016459D492D3FB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI57 GUIDc46462NameCustom_Model_Infinite_BagTransformposX`+@posYv?posZ;M@rotX=>rotY vrotZdscaleX.?scaleY?scaleZ.?Nickname Can ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID0bdeb3Name Custom_TokenTransformposX``$@posY?posZ B rotX`arotYԾrotZ`+?scaleXU?scaleY?scaleZU?Nickname Can ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15233406613328206719/EB9E555B2E4D140F3EFA507CB196B84763DDD80C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID24fcc2Name Custom_TokenTransformposXposYposZrotX!=rotY@i>rotZ@ |;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15233406613328206719/EB9E555B2E4D140F3EFA507CB196B84763DDD80C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI58 GUID74d9c3NameCustom_Model_Infinite_BagTransformposX`'@posYv?posZ;M@rotXٻ>rotY@ vrotZ$scaleX.?scaleY?scaleZ.?Nickname Cannot ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsw0oGUID7ca300Name Custom_TokenTransformposXkf+@posY@?posZ}urotX U?rotY ǾrotZvscaleXU?scaleY?scaleZU?Nickname Cannot ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17200337404271520271/C46350DFA84E0EE38DF25A4AA35714016461F932/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDfac961Name Custom_TokenTransformposXposYposZrotX <rotYB>rotZT;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17200337404271520271/C46350DFA84E0EE38DF25A4AA35714016461F932/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI59 GUID9b9a45NameCustom_Model_Infinite_BagTransformposX`'@posYv?posZ;N@rotX ?>rotY vrotZBۛscaleX.?scaleY?scaleZ.?NicknameCannot Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUIDaac69cName Custom_TokenTransformposX y0@posY`?posZtrotX >?rotY >rotZ QscaleXU?scaleY?scaleZU?NicknameCannot Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17450529432768595047/BFF2692756B5A7DC96B2058A388C224AFAEE4935/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID36c7a9Name Custom_TokenTransformposXposYposZrotX >& =rotY@~>rotZr;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17450529432768595047/BFF2692756B5A7DC96B2058A388C224AFAEE4935/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI60 GUIDe9756bNameCustom_Model_Infinite_BagTransformposX`+@posY`v?posZ;N@rotX@>rotY` vrotZ"scaleX.?scaleY?scaleZ.?NicknameCan Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects~0vGUID77f9f4Name Custom_TokenTransformposX@Q[.@posY?posZL+rotXprotY ?rotZ#{v@scaleXU?scaleY?scaleZU?NicknameCan Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12655439807939350284/0198BC44483D3AA4A4A08C175CDC14C7671D1E8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDec7b75Name Custom_TokenTransformposXposYposZrotXLz<rotY@:>rotZ@R;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12655439807939350284/0198BC44483D3AA4A4A08C175CDC14C7671D1E8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI61 GUID8d0f6aNameCustom_Model_Infinite_BagTransformposX`+@posY`v?posZ;O@rotX`;>rotY@ vrotZ7scaleX.?scaleY?scaleZ.?Nickname Can ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID48e214Name Custom_TokenTransformposX`$4@posY 6?posZ DrotX?rotY@?rotZ?scaleXU?scaleY?scaleZU?Nickname Can ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12421409741468501606/0F82967969CDFE27B95DD79D1469CE63190E7157/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID910389Name Custom_TokenTransformposXposYposZrotX<rotY>rotZ p2W;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12421409741468501606/0F82967969CDFE27B95DD79D1469CE63190E7157/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI62 GUID5e7dd7NameCustom_Model_Infinite_BagTransformposX`'@posY`v?posZ;O@rotX@~q>rotY vrotZ}<scaleX.?scaleY?scaleZ.?NicknameCannot ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsx0pGUIDa8d69cName Custom_TokenTransformposXMh5@posYH?posZprotX`,R?rotY@>rotZh+MscaleXU?scaleY?scaleZU?NicknameCannot ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10702833170741215872/6867BC43303787DDD722026CDE3BBC1C51D68C5B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID83c5e1Name Custom_TokenTransformposXposYposZrotX{<rotYҔ>rotZU;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10702833170741215872/6867BC43303787DDD722026CDE3BBC1C51D68C5B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI63 GUID577332NameCustom_Model_Infinite_BagTransformposX bP@posYv?posZ \M@rotX >rotY`}vrotZ UscaleX.?scaleY?scaleZ.?NicknameHas Used Once Per TurnDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID932331Name Custom_TokenTransformposXq#@posY`͜?posZirotX`NrotY >rotZ0xQscaleXU?scaleY?scaleZU?NicknameHas Used Once Per TurnDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15594386820679284179/B2DA68F50F401975B9DE21AAD5B0BC55210784C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID97b748Name Custom_TokenTransformposXposYposZrotX<rotYϳ>rotZߩU;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15594386820679284179/B2DA68F50F401975B9DE21AAD5B0BC55210784C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI64 GUID14a553NameCustom_Model_Infinite_BagTransformposX`PM@posYw?posZ rYM@rotXrotY`K?rotZY>scaleX.?scaleY?scaleZ.?NicknameHas Used Once Per Battle RoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd5b47dName Custom_TokenTransformposX)@posYŗ?posZrotXٱt?rotY Rb?rotZ;Hv?scaleXU?scaleY?scaleZU?NicknameHas Used Once Per Battle RoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9600819078001643173/398EDD5C6384C391DEA01C5FE4B561430CE55825/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDac7315Name Custom_TokenTransformposXposYposZrotX9 =rotYƳ}>rotZ.;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9600819078001643173/398EDD5C6384C391DEA01C5FE4B561430CE55825/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI65 GUIDb00393NameCustom_Model_Infinite_BagTransformposX@posYv?posZ\M@rotX@9$>rotY.vrotZiscaleX.?scaleY?scaleZ.?NicknameHas Used Once Per PhaseDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUIDc53e6aName Custom_TokenTransformposXb@posY`R?posZK?rotX N?rotY}rotZ {v@scaleXU?scaleY?scaleZU?NicknameHas Used Once Per PhaseDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15804422053123139112/E89856F127D0721F6394AD42A5503F7E77066CC7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID083c4dName Custom_TokenTransformposXposYposZrotX ]!rotY ̾rotZ ܥscaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15804422053123139112/E89856F127D0721F6394AD42A5503F7E77066CC7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI66GUID74b749NameCustom_Model_Infinite_BagTransformposX0@posYv?posZRN@rotX ?rotY@B?rotZ@ scaleX.?scaleY?scaleZ.?NicknameUses RemainingDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDfea083Name Custom_TokenTransformposX'@posY՜?posZ \rotX N?rotY(<>rotZ@EscaleXU?scaleY?scaleZU?NicknameOne Use RemainingDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10073072590831742101/ADB9738C042290E51787235E30354A6B4E46224C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates}2uGUID6f9f3bName Custom_TokenTransformposX~0@posY(?posZv7rotX`q=grotYf@rotZ]hscaleXU?scaleY?scaleZU?NicknameTwo Uses RemainingDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12737274812260215970/A0D73C0EE73CC848DF55AECEDBBD432224F1484A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7546d1Name Custom_TokenTransformposXposYposZrotX/n<rotY>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10073072590831742101/ADB9738C042290E51787235E30354A6B4E46224C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI67 GUIDe0c0ccNameCustom_Model_Infinite_BagTransformposX*@posY@v?posZP@rotX(>rotY vrotZ4ȗscaleX.?scaleY?scaleZ.?Nickname Battle-ShockDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDcb297bName Custom_TokenTransformposXM@posY؜?posZT@rotX`O?rotY >rotZ`cMscaleXU?scaleY?scaleZU?Nickname Battle-ShockDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9581741158556778672/9546E93DAFB582D6780F81231DA739EEEB9C7CD1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID217df0Name Custom_TokenTransformposXposYposZrotXt40=rotY>rotZ;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9581741158556778672/9546E93DAFB582D6780F81231DA739EEEB9C7CD1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI68 GUIDdedb3bNameCustom_Model_Infinite_BagTransformposX 1@posY`v?posZP@rotX@>rotY.vrotZ`)scaleX.?scaleY?scaleZ.?NicknameObjective Secured (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID35ff89Name Custom_TokenTransformposX@K+@posY@?posZ&@rotX>y?rotYSrotZp?scaleXU?scaleY?scaleZU?NicknameObjective Secured (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16558249251381302600/88A752C75F310EC7F63AB02CA0A74BC3393F6779/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUIDff5331Name Custom_TokenTransformposXposYposZrotX?&rotY+ھrotZ`ܥ=scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16558249251381302600/88A752C75F310EC7F63AB02CA0A74BC3393F6779/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI69 GUID91024cNameCustom_Model_Infinite_BagTransformposXb@posYv?posZP@rotX kƼ>rotYԓvrotZ` >scaleX.?scaleY?scaleZ.?NicknameObjective Secured (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUIDd9df6eName Custom_TokenTransformposX@0@posY`?posZ ?rotX`*rotY`ǾrotZxscaleXU?scaleY?scaleZU?NicknameObjective Secured (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9449365886267227774/7C03E09C9035E64A7E2886C895A7FDC869CDBA56/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsp0hGUID8d5ba8Name Custom_TokenTransformposXposYposZrotXUc1=rotYO>rotZ;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9449365886267227774/7C03E09C9035E64A7E2886C895A7FDC869CDBA56/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI70 GUID95d480NameCustom_Model_Infinite_BagTransformposX`ff@posY`v?posZP@rotX>rotY` vrotZn씾scaleX.?scaleY?scaleZ.?NicknameOperation Marker (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID0b215bName Custom_TokenTransformposXy@posY )?posZ ܿrotXP?rotY߾rotZ@5)7scaleXU?scaleY?scaleZU?NicknameOperation Marker (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10210344779140932848/DBE82D90EE5889EE362AD76A938F3B11BCCCFD37/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUIDeac428Name Custom_TokenTransformposXposYposZrotX IrotY>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10210344779140932848/DBE82D90EE5889EE362AD76A938F3B11BCCCFD37/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI71 GUID19ce32NameCustom_Model_Infinite_BagTransformposX`1@posY`v?posZP@rotX1>rotY/vrotZ`scaleX.?scaleY?scaleZ.?NicknameOperation Marker (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID9f42e4Name Custom_TokenTransformposX:@posY`֜?posZ q?rotXv@rotYJ?rotZVD?scaleXU?scaleY?scaleZU?NicknameOperation Marker (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10714603952554854969/65EF56C24B3CB77D6FBADEC8365516D351422754/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID73c82bName Custom_TokenTransformposXposYposZrotX`rotY`>rotZeqscaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10714603952554854969/65EF56C24B3CB77D6FBADEC8365516D351422754/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI72 GUID84fa2fNameCustom_Model_Infinite_BagTransformposX&1@posY@v?posZQ@rotXӏ>rotY/vrotZ3ޣscaleX.?scaleY?scaleZ@.?NicknameAction in Progress (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID8224a1Name Custom_TokenTransformposX6@posY ?posZ@rotXfOrotYtϾrotZ DscaleXU?scaleY?scaleZU?NicknameAction in Progress (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9853997720246942774/6A6EFA7B7377D39A0282649DF035678DC85B5DA1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDaec404Name Custom_TokenTransformposXposYposZrotX <rotY`h>rotZk9W;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9853997720246942774/6A6EFA7B7377D39A0282649DF035678DC85B5DA1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI73 GUIDd6205aNameCustom_Model_Infinite_BagTransformposXg@posY@v?posZ Q@rotX>rotYQvrotZ scaleX.?scaleY?scaleZ.?NicknameAction in Progress (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID3bfe64Name Custom_TokenTransformposX~'@posY?posZ,rotX v@rotY@7D?rotZ`|v@scaleXU?scaleY?scaleZU?NicknameAction in Progress (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13865207090087953670/CF59DD723DB3BDBED2D24F3B3A44392C1B3BF084/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDaa9e02Name Custom_TokenTransformposXposYposZrotX<rotY>rotZW;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13865207090087953670/CF59DD723DB3BDBED2D24F3B3A44392C1B3BF084/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI74 GUID585aa8NameCustom_Model_Infinite_BagTransformposX`wposYv?posZ(P@rotX0>rotY.vrotZscaleX.?scaleY?scaleZ.?NicknameEnemy ObservedDescriptionFor Observe Enemy PrimaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd9541dName Custom_TokenTransformposXN@posYజ?posZrotXL?rotY ZվrotZv@scaleXU?scaleY?scaleZU?NicknameEnemy ObservedDescriptionFor Observe Enemy PrimaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18127329257998381222/9886083004DE1C22C15CA60B8EA98C1B47DFFE7C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID6990e9Name Custom_TokenTransformposXposYposZrotX\rotYa>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18127329257998381222/9886083004DE1C22C15CA60B8EA98C1B47DFFE7C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI75 GUID652ae2NameCustom_Model_Infinite_BagTransformposXposYv?posZ(P@rotX@>rotY@ vrotZώscaleX.?scaleY?scaleZ.?NicknameUnit ConsecratedDescriptionFor Consecration PrimaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID1f0a1eName Custom_TokenTransformposXܜ$@posY?posZqrotX@-FT?rotY`~>rotZ7A?scaleXU?scaleY?scaleZU?NicknameUnit ConsecratedDescriptionFor Consecration PrimaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9265616858586819768/93E99DF19BC0AE86C9881BFB7643F987E94961AB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsp0hGUID791435Name Custom_TokenTransformposXposYposZrotXrotYAZrrotZO2;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9265616858586819768/93E99DF19BC0AE86C9881BFB7643F987E94961AB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI76 GUID4a98feNameCustom_Model_Infinite_BagTransformposX@posYv?posZ(P@rotXਐ>rotY` vrotZ2scaleX.?scaleY?scaleZ.?Nickname Unit DoomedDescriptionFor Punishment PrimaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID75b67eName Custom_TokenTransformposX 8(@posY Ӝ?posZ rotXYi?rotY [xrotZ`PdscaleXU?scaleY?scaleZU?Nickname Unit DoomedDescriptionFor Punishment PrimaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12876601956845860071/153C47538A64E0A6AEFE19C4AA49344777BCB6F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID1d5c0dName Custom_TokenTransformposXYkposY`L2=posZ WS=rotX Z<rotY>rotZ ܥscaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12876601956845860071/153C47538A64E0A6AEFE19C4AA49344777BCB6F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI77 GUIDe505b6NameCustom_Model_Infinite_BagTransformposX@posY`v?posZP@rotX 6>rotY.vrotZ:ٙscaleX.?scaleY?scaleZ.?NicknameHolding BeaconDescriptionFor Beacon SecondaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID87068fName Custom_TokenTransformposX2@posY?posZ rotX8,?rotY ?ľrotZFJscaleXU?scaleY?scaleZU?NicknameHolding BeaconDescriptionFor Beacon SecondaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11343351190458973473/6241E0383085CA6E3DCA92F43B95BF48E96C9302/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID8da8cfName Custom_TokenTransformposXposYposZrotXCrotYਉrotZ@c;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11343351190458973473/6241E0383085CA6E3DCA92F43B95BF48E96C9302/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI78 GUID2a476fNameCustom_Model_Infinite_BagTransformposXposY`v?posZP@rotX [D>rotY vrotZ@ϦscaleX.?scaleY?scaleZ.?NicknameGuarding ObjectiveDescriptionFor Burden of Trust SecondaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID7e6c37Name Custom_TokenTransformposX`:/@posY@Ŝ?posZ 7rotX3?rotY m޾rotZbscaleXU?scaleY?scaleZU?NicknameGuarding ObjectiveDescriptionFor Burden of Trust SecondaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13896626996681093367/50231335816CB8157BCFD5F4DB92BC9FC7EAC6B5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUIDaa597fName Custom_TokenTransformposXYk`>posY`TposZ Q@rotX zrotY >rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13896626996681093367/50231335816CB8157BCFD5F4DB92BC9FC7EAC6B5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI79 GUIDc605daNameCustom_Model_Infinite_BagTransformposX`wposY`v?posZP@rotXZ>rotY vrotZ`gscaleX.?scaleY?scaleZ@.?NicknameObjective TemptedDescriptionFor Tempting Target SecondaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDe2b7ecName Custom_TokenTransformposX {,@posY@?posZhrotXgzVrotYPվrotZ`G?scaleXU?scaleY?scaleZU?NicknameObjective TemptedDescriptionFor Tempting Target SecondaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16403052389472970811/417348AEF7D07556BF306F133AEE1FCF845BFDEC/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID64ab9aName Custom_TokenTransformposXposYposZrotX@_5=rotYb>rotZ;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16403052389472970811/417348AEF7D07556BF306F133AEE1FCF845BFDEC/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI23GUID9d6b2fNameBagTransformposX;kBposY?posZIArotXډ>rotYֹ`rotZ MscaleX?scaleY?scaleZ?Nickname Tokens by Mothmy Titania (Blue)Description By DirectsunGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScriptQ?-- FTC-GUID: 7aca71,10a26a,446a11,9d6b2f -- Utility memory bag by Directsun -- Version 2.5 -- Fork of Memory Bag 2.0 by MrStump function updateSave() local data_to_save = {["ml"]=memoryList} saved_data = JSON.encode(data_to_save) self.script_state = saved_data end function combineMemoryFromBagsWithin() local bagObjList = self.getObjects() for _, bagObj in ipairs(bagObjList) do local data = bagObj.lua_script_state if data ~= nil then local j = JSON.decode(data) if j ~= nil and j.ml ~= nil then for guid, entry in pairs(j.ml) do memoryList[guid] = entry end end end end end function updateMemoryWithMoves() print("updating memory with moves") memoryList = memoryListBackup --get the first transposed object's coordinates local obj = getObjectFromGUID(moveGuid) -- p1 is where needs to go, p2 is where it was local refObjPos = memoryList[moveGuid].pos local deltaPos = findOffsetDistance(obj.getPosition(), refObjPos, nil) local movedRotation = obj.getRotation() for guid, entry in pairs(memoryList) do memoryList[guid].pos.x = entry.pos.x - deltaPos.x memoryList[guid].pos.y = entry.pos.y - deltaPos.y memoryList[guid].pos.z = entry.pos.z - deltaPos.z -- memoryList[guid].rot.x = movedRotation.x -- memoryList[guid].rot.y = movedRotation.y -- memoryList[guid].rot.z = movedRotation.z end --theList[obj.getGUID()] = { -- pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, -- rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, -- lock=obj.getLock() --} moveList = {} end function onload(saved_data) fresh = true if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) --Set up information off of loaded_data memoryList = loaded_data.ml else --Set up information for if there is no saved saved data memoryList = {} end moveList = {} moveGuid = nil if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end end --Beginning Setup --Make setup button function createSetupButton() self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --Triggered by Transpose button function buttonClick_transpose() moveGuid = nil broadcastToAll("Select one object and move it- all objects will move relative to the new location", {0.75, 0.75, 1}) memoryListBackup = duplicateTable(memoryList) memoryList = {} moveList = {} self.clearButtons() createButtonsOnAllObjects(true) createSetupActionButtons(true) end --Triggered by setup button, function buttonClick_setup() memoryListBackup = duplicateTable(memoryList) memoryList = {} self.clearButtons() createButtonsOnAllObjects(false) createSetupActionButtons(false) end function getAllObjectsInMemory() local objTable = {} local curObj = {} for guid in pairs(memoryListBackup) do curObj = getObjectFromGUID(guid) table.insert(objTable, curObj) end return objTable -- return getAllObjects() end --Creates selection buttons on objects function createButtonsOnAllObjects(move) local howManyButtons = 0 local objsToHaveButtons = {} if move == true then objsToHaveButtons = getAllObjectsInMemory() else objsToHaveButtons = getAllObjects() end for _, obj in ipairs(objsToHaveButtons) do if obj ~= self then local dummyIndex = howManyButtons --On a normal bag, the button positions aren't the same size as the bag. globalScaleFactor = 1.25 * 1/self.getScale().x --Super sweet math to set button positions local selfPos = self.getPosition() local objPos = obj.getPosition() local deltaPos = findOffsetDistance(selfPos, objPos, obj) local objPos = rotateLocalCoordinates(deltaPos, self) objPos.x = -objPos.x * globalScaleFactor objPos.y = objPos.y * globalScaleFactor objPos.z = objPos.z * globalScaleFactor --Offset rotation of bag local rot = self.getRotation() rot.y = -rot.y + 180 --Create function local funcName = "selectButton_" .. howManyButtons local func = function() buttonClick_selection(dummyIndex, obj, move) end local color = {0.75,0.25,0.25,0.6} local colorMove = {0,0,1,0.6} if move == true then color = colorMove end self.setVar(funcName, func) self.createButton({ click_function=funcName, function_owner=self, position=objPos, rotation=rot, height=1000, width=1000, color=color, }) howManyButtons = howManyButtons + 1 end end end --Creates submit and cancel buttons function createSetupActionButtons(move) self.createButton({ label="Cancel", click_function="buttonClick_cancel", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Submit", click_function="buttonClick_submit", function_owner=self, position={0,0.3,-2.8}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) if move == false then self.createButton({ label="Add", click_function="buttonClick_add", function_owner=self, position={0,0.3,-3.6}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.25,1,0.25} }) if fresh == false then self.createButton({ label="Set New", click_function="buttonClick_setNew", function_owner=self, position={0,0.3,-4.4}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) self.createButton({ label="Remove", click_function="buttonClick_remove", function_owner=self, position={0,0.3,-5.2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,0.25,0.25} }) end end self.createButton({ label="Reset", click_function="buttonClick_reset", function_owner=self, position={-2,0.3,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --During Setup --Checks or unchecks buttons function buttonClick_selection(index, obj, move) local colorMove = {0,0,1,0.6} local color = {0,1,0,0.6} previousGuid = selectedGuid selectedGuid = obj.getGUID() theList = memoryList if move == true then theList = moveList if previousGuid ~= nil and previousGuid ~= selectedGuid then local prevObj = getObjectFromGUID(previousGuid) prevObj.highlightOff() self.editButton({index=previousIndex, color=colorMove}) theList[previousGuid] = nil end previousIndex = index end if theList[selectedGuid] == nil then self.editButton({index=index, color=color}) --Adding pos/rot to memory table local pos, rot = obj.getPosition(), obj.getRotation() --I need to add it like this or it won't save due to indexing issue theList[obj.getGUID()] = { pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock() } obj.highlightOn({0,1,0}) else color = {0.75,0.25,0.25,0.6} if move == true then color = colorMove end self.editButton({index=index, color=color}) theList[obj.getGUID()] = nil obj.highlightOff() end end --Cancels selection process function buttonClick_cancel() memoryList = memoryListBackup moveList = {} self.clearButtons() if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end removeAllHighlights() broadcastToAll("Selection Canceled", {1,1,1}) moveGuid = nil end --Saves selections function buttonClick_submit() fresh = false if next(moveList) ~= nil then for guid in pairs(moveList) do moveGuid = guid end if memoryListBackup[moveGuid] == nil then broadcastToAll("Item selected for moving is not already in memory", {1, 0.25, 0.25}) else broadcastToAll("Moving all items in memory relative to new objects position!", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(moveList) do moveGuid = guid count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end updateMemoryWithMoves() updateSave() buttonClick_place() end elseif next(memoryList) == nil and moveGuid == nil then memoryList = memoryListBackup broadcastToAll("No selections made.", {0.75, 0.25, 0.25}) end combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() moveGuid = nil end function combineTables(first_table, second_table) for k,v in pairs(second_table) do first_table[k] = v end end function buttonClick_add() fresh = false combineTables(memoryList, memoryListBackup) broadcastToAll("Adding internal bags and selections to existing memory", {0.25, 0.75, 0.25}) combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() end function buttonClick_remove() broadcastToAll("Removing Selected Entries From Memory", {1.0, 0.25, 0.25}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 memoryListBackup[guid] = nil local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Removed", {1,1,1}) memoryList = memoryListBackup updateSave() end function buttonClick_setNew() broadcastToAll("Setting new position relative to items in memory", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for _, obj in ipairs(getAllObjects()) do guid = obj.guid if memoryListBackup[guid] ~= nil then count = count + 1 memoryListBackup[guid].pos = obj.getPosition() memoryListBackup[guid].rot = obj.getRotation() memoryListBackup[guid].lock = obj.getLock() end end broadcastToAll(count.." Objects Saved", {1,1,1}) memoryList = memoryListBackup updateSave() end --Resets bag to starting status function buttonClick_reset() fresh = true memoryList = {} self.clearButtons() createSetupButton() removeAllHighlights() broadcastToAll("Tool Reset", {1,1,1}) updateSave() end --After Setup function createMemoryActionButtons() local guid = self.getGUID() local isTokenBag = (guid == "7aca71") or (guid == "10a26a") or (guid == "446a11") or (guid == "9d6b2f") local showLabel = isTokenBag and "Show Tokens" or "Show Maps" local showWidth = isTokenBag and 1600 or 1400 local hasToolTip = isTokenBag and "Place Tokens" or "" self.createButton({ label=showLabel, click_function="buttonClick_place", function_owner=self, position={0,0.2,-2}, rotation={0,180,0}, height=350, width=showWidth, tooltip=hasToolTip, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Recall", click_function="buttonClick_recall", function_owner=self, position={0,0.2,-2.8}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={-2,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Move", click_function="buttonClick_transpose", function_owner=self, position={-2.8,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) end --Sends objects from bag/table to their saved position/rotation function buttonClick_place() local bagObjList = self.getObjects() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) --If obj is out on the table, move it to the saved pos/rot if obj ~= nil then obj.setPositionSmooth(entry.pos) obj.setRotationSmooth(entry.rot) obj.setLock(entry.lock) else --If obj is inside of the bag for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then local item = self.takeObject({ guid=guid, position=entry.pos, rotation=entry.rot, smooth=false }) item.setLock(entry.lock) break end end end end local obj = getObjectFromGUID("7abc5a") if(obj) then destroyObject(obj) end broadcastToAll("Objects Placed", {1,1,1}) end --Recalls objects to bag from table function buttonClick_recall() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then self.putObject(obj) end end broadcastToAll("Objects Recalled", {1,1,1}) end --Utility functions --Find delta (difference) between 2 x/y/z coordinates function findOffsetDistance(p1, p2, obj) local yOffset = 0 if obj ~= nil then local bounds = obj.getBounds() yOffset = (bounds.size.y - bounds.offset.y) end local deltaPos = {} deltaPos.x = (p2.x-p1.x) deltaPos.y = (p2.y-p1.y) + yOffset deltaPos.z = (p2.z-p1.z) return deltaPos end --Used to rotate a set of coordinates by an angle function rotateLocalCoordinates(desiredPos, obj) local objPos, objRot = obj.getPosition(), obj.getRotation() local angle = math.rad(objRot.y) local x = desiredPos.x * math.cos(angle) - desiredPos.z * math.sin(angle) local z = desiredPos.x * math.sin(angle) + desiredPos.z * math.cos(angle) --return {x=objPos.x+x, y=objPos.y+desiredPos.y, z=objPos.z+z} return {x=x, y=desiredPos.y, z=z} end --Coroutine delay, in seconds --Duplicates a table (needed to prevent it making reference to the same objects) function duplicateTable(oldTable) local newTable = {} for k, v in pairs(oldTable) do newTable[k] = v end return newTable end --Moves scripted highlight from all objects function removeAllHighlights() for _, obj in ipairs(getAllObjects()) do obj.highlightOff() end end --Round number (num) to the Nth decimal (dec) function round(num, dec) local mult = 10^(dec or 0) return math.floor(num * mult + 0.5) / mult end LuaScriptState){"ml":{"09deb2":{"lock":false,"pos":{"x":-13.8047,"y":0.9713,"z":-59.2012},"rot":{"x":0,"y":179.9764,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"0c18e5":{"lock":false,"pos":{"x":-21.8047,"y":0.9713,"z":-62.2012},"rot":{"x":0,"y":179.997,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1087bc":{"lock":false,"pos":{"x":-11.8047,"y":0.9713,"z":-61.2012},"rot":{"x":0,"y":180.0282,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1b0656":{"lock":false,"pos":{"x":-21.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.0035,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1b0cb6":{"lock":false,"pos":{"x":-28.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9744,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1c4e10":{"lock":false,"pos":{"x":-18.8047,"y":0.9713,"z":-58.2012},"rot":{"x":0,"y":179.9786,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"23e8ed":{"lock":false,"pos":{"x":-24.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"25b3fc":{"lock":false,"pos":{"x":-7.8047,"y":0.9713,"z":-56.2012},"rot":{"x":0,"y":180.0307,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"269346":{"lock":false,"pos":{"x":-8.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.0155,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"269d00":{"lock":false,"pos":{"x":-12.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"27e749":{"lock":false,"pos":{"x":4.6952,"y":0.9713,"z":-67.6946},"rot":{"x":0,"y":180,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"2c6bd0":{"lock":false,"pos":{"x":-26.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9832,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"2fa111":{"lock":false,"pos":{"x":-4.8048,"y":0.9713,"z":-66.1975},"rot":{"x":0,"y":180.0001,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"324055":{"lock":false,"pos":{"x":-19.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":179.9991,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"3300ae":{"lock":false,"pos":{"x":-24.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9885,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"36dd99":{"lock":false,"pos":{"x":-18.8047,"y":0.9713,"z":-61.2012},"rot":{"x":0,"y":180.026,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"3a3b4e":{"lock":false,"pos":{"x":-30.8047,"y":0.9713,"z":-62.2012},"rot":{"x":0,"y":180.017,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"3a5369":{"lock":false,"pos":{"x":-2.8047,"y":0.9713,"z":-66.1978},"rot":{"x":0,"y":180.0001,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"3a7547":{"lock":false,"pos":{"x":-23.8047,"y":0.9713,"z":-62.2012},"rot":{"x":0,"y":179.9999,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"3c1e62":{"lock":false,"pos":{"x":-13.8047,"y":0.9713,"z":-63.2012},"rot":{"x":0,"y":180.0226,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"47c0eb":{"lock":false,"pos":{"x":-30.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.0176,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"49c73b":{"lock":false,"pos":{"x":-2.8047,"y":0.9713,"z":-64.2012},"rot":{"x":0,"y":180.0023,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"4d6a72":{"lock":false,"pos":{"x":-23.8047,"y":0.9713,"z":-58.2012},"rot":{"x":0,"y":180.0035,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"4f9f9b":{"lock":false,"pos":{"x":-4.3879,"y":0.9713,"z":-58.4048},"rot":{"x":0,"y":180.0193,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"53e4da":{"lock":false,"pos":{"x":-32.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9736,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"549178":{"lock":false,"pos":{"x":-23.8047,"y":0.9713,"z":-60.2012},"rot":{"x":0,"y":179.9772,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"5a45b8":{"lock":false,"pos":{"x":-30.8047,"y":0.9713,"z":-60.2012},"rot":{"x":0,"y":180.032,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6397fa":{"lock":false,"pos":{"x":-18.8047,"y":0.9713,"z":-64.2012},"rot":{"x":0,"y":179.998,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"649125":{"lock":false,"pos":{"x":2.6958,"y":0.9713,"z":-67.6939},"rot":{"x":0,"y":179.9998,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"688d0b":{"lock":false,"pos":{"x":-16.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.021,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"68c00c":{"lock":false,"pos":{"x":-5.8047,"y":0.9713,"z":-56.2012},"rot":{"x":0,"y":180.0166,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6971a6":{"lock":false,"pos":{"x":-23.8047,"y":0.9713,"z":-64.2012},"rot":{"x":0,"y":179.9717,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6a7c2b":{"lock":false,"pos":{"x":-7.8049,"y":0.9713,"z":-66.1997},"rot":{"x":0,"y":180,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"712ff4":{"lock":false,"pos":{"x":-2.3047,"y":0.9713,"z":-58.7012},"rot":{"x":0,"y":179.9994,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"729faf":{"lock":false,"pos":{"x":-16.8047,"y":0.9713,"z":-61.2012},"rot":{"x":0,"y":180.0157,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"747d8c":{"lock":false,"pos":{"x":-28.8047,"y":0.9713,"z":-57.2012},"rot":{"x":0,"y":179.9994,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7676a9":{"lock":false,"pos":{"x":-25.8047,"y":0.9713,"z":-57.2012},"rot":{"x":0,"y":180.0052,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7bab6e":{"lock":false,"pos":{"x":2.6953,"y":0.9713,"z":-64.7012},"rot":{"x":0,"y":179.9732,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7d5a0c":{"lock":false,"pos":{"x":-28.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.0096,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7faf0c":{"lock":false,"pos":{"x":-4.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":179.9764,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"80cda6":{"lock":false,"pos":{"x":-21.8047,"y":0.9713,"z":-60.2012},"rot":{"x":0,"y":179.9823,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"83919c":{"lock":false,"pos":{"x":-16.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9828,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"86f1ad":{"lock":false,"pos":{"x":-4.8047,"y":0.9713,"z":-64.2012},"rot":{"x":0,"y":179.9749,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"87584a":{"lock":false,"pos":{"x":-6.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":179.9863,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"886e1e":{"lock":false,"pos":{"x":-30.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9725,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8a7871":{"lock":false,"pos":{"x":-28.8047,"y":0.9713,"z":-61.2012},"rot":{"x":0,"y":180.007,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8bee95":{"lock":false,"pos":{"x":-11.8047,"y":0.9713,"z":-63.2012},"rot":{"x":0,"y":179.978,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8e3852":{"lock":false,"pos":{"x":-4.8047,"y":0.9713,"z":-68.2012},"rot":{"x":0,"y":179.9999,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"92a02c":{"lock":false,"pos":{"x":-28.8047,"y":0.9713,"z":-63.2012},"rot":{"x":0,"y":180.0276,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9da719":{"lock":false,"pos":{"x":-11.8047,"y":0.9713,"z":-59.2012},"rot":{"x":0,"y":179.9851,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9db18c":{"lock":false,"pos":{"x":-32.8047,"y":0.9713,"z":-60.2012},"rot":{"x":0,"y":180.0139,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9de4c6":{"lock":false,"pos":{"x":-0.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.0113,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9f5497":{"lock":false,"pos":{"x":-2.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":179.9999,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a483ac":{"lock":false,"pos":{"x":-4.3861,"y":0.9713,"z":-60.354},"rot":{"x":0,"y":180.0106,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a71004":{"lock":false,"pos":{"x":-12.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9992,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a8a9e9":{"lock":false,"pos":{"x":-26.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":179.9766,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"abe733":{"lock":false,"pos":{"x":-2.8047,"y":0.9713,"z":-68.2012},"rot":{"x":0,"y":180.0001,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"b055b7":{"lock":false,"pos":{"x":-6.3375,"y":0.9713,"z":-58.7029},"rot":{"x":0,"y":179.9751,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"b4c4d4":{"lock":false,"pos":{"x":-0.8047,"y":0.9713,"z":-56.2012},"rot":{"x":0,"y":180.0023,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"be0a96":{"lock":false,"pos":{"x":-30.8047,"y":0.9713,"z":-64.2012},"rot":{"x":0,"y":179.9719,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"be6b99":{"lock":false,"pos":{"x":-30.8047,"y":0.9713,"z":-58.2012},"rot":{"x":0,"y":180.022,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c035c5":{"lock":false,"pos":{"x":-14.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":180.0311,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"cf6e83":{"lock":false,"pos":{"x":0.6953,"y":0.9713,"z":-67.6945},"rot":{"x":0,"y":180.0001,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"cf8637":{"lock":false,"pos":{"x":0.6953,"y":0.9713,"z":-64.7012},"rot":{"x":0,"y":179.9914,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"d7a2ca":{"lock":false,"pos":{"x":-28.8047,"y":0.9713,"z":-65.2012},"rot":{"x":0,"y":180.0242,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"da3988":{"lock":false,"pos":{"x":-25.8047,"y":0.9713,"z":-63.2012},"rot":{"x":0,"y":180.0194,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"db6736":{"lock":false,"pos":{"x":-21.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":180.0007,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"dbeafb":{"lock":false,"pos":{"x":-16.8047,"y":0.9713,"z":-58.2012},"rot":{"x":0,"y":180.0285,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"df092c":{"lock":false,"pos":{"x":-25.8047,"y":0.9713,"z":-61.2012},"rot":{"x":0,"y":180.0217,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e00be2":{"lock":false,"pos":{"x":-32.8047,"y":0.9713,"z":-62.2012},"rot":{"x":0,"y":180.0149,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e052b5":{"lock":false,"pos":{"x":-2.8047,"y":0.9713,"z":-56.2012},"rot":{"x":0,"y":179.9931,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e587fc":{"lock":false,"pos":{"x":-16.8047,"y":0.9713,"z":-64.2012},"rot":{"x":0,"y":180,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"eaa233":{"lock":false,"pos":{"x":-25.8047,"y":0.9713,"z":-59.2012},"rot":{"x":0,"y":179.9814,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"eb8ba7":{"lock":false,"pos":{"x":-19.3047,"y":0.9713,"z":-50.7012},"rot":{"x":0,"y":179.9992,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"f045a1":{"lock":false,"pos":{"x":-25.8047,"y":0.9713,"z":-65.2012},"rot":{"x":0,"y":180.0148,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"f65266":{"lock":false,"pos":{"x":4.6953,"y":0.9713,"z":-64.7012},"rot":{"x":0,"y":180.0015,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"faf803":{"lock":false,"pos":{"x":-28.8047,"y":0.9713,"z":-59.2012},"rot":{"x":0,"y":180.0164,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"fb6846":{"lock":false,"pos":{"x":-14.3047,"y":0.9713,"z":-53.7012},"rot":{"x":0,"y":180.0096,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"fbb884":{"lock":false,"pos":{"x":-13.8047,"y":0.9713,"z":-61.2012},"rot":{"x":0,"y":180.0228,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"fea9b9":{"lock":false,"pos":{"x":-17.8048,"y":0.9713,"z":-66.1975},"rot":{"x":0,"y":179.9999,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}}}}XmlUIContainedObjects0y GUID53e4daNameCustom_Model_Infinite_BagTransformposX`'@posY}?posZYIrotX@pNrotY'f@rotZ`>scaleX.?scaleY?scaleZ.?Nickname 5+ CritsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectss0kGUID68eb62Name Custom_TokenTransformposX-#@posY@?posZ(?rotXi@?rotY@[rotZ`D scaleXU?scaleY?scaleZU?Nickname 5+ CritsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13117039322806659776/61FF6E760D7B545A59ABFDE18B253D9BD5590F8A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID0a1547Name Custom_TokenTransformposXposYposZrotXV$rotY̾rotZ ܥ=scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13117039322806659776/61FF6E760D7B545A59ABFDE18B253D9BD5590F8A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI1"GUID886e1eNameCustom_Model_Infinite_BagTransformposXN>posY~?posZYIrotXrotYf@rotZa>scaleX.?scaleYv=?scaleZ.?Nickname Lethal HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID870649Name Custom_TokenTransformposXk,@posYÛ?posZ$@rotX)A?rotY`>rotZhsscaleXU?scaleY?scaleZU?Nickname Lethal HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12240846766101287574/A5ABC380D57B64C61C79EDD2E2C4FB002A65BD7F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0 GUID4564ceNameCustom_Model_BagTransformposXposYposZrotXBټrotYrotZS;scaleX?scaleYt?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexNumberCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsBagOrderLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDa433f7Name Custom_TokenTransformposXposYposZrotX`-=rotYN>rotZ E@;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12240846766101287574/A5ABC380D57B64C61C79EDD2E2C4FB002A65BD7F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI2 GUID1b0cb6NameCustom_Model_Infinite_BagTransformposXN<posY}?posZYIrotXsrotY@.f@rotZ [>scaleX.?scaleY?scaleZ.?NicknameSustained HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID8baa89Name Custom_TokenTransformposX`1@posY`?posZ>'@rotX@nldrotYC?rotZv@scaleXU?scaleY?scaleZU?NicknameSustained HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15427546906165424368/8E69222BC102AAA8098F8571631A1A6348B71AA7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID4054a2Name Custom_TokenTransformposXposYposZrotXDH<rotY 6>rotZ`8,!;scaleX ?scaleY@$@scaleZ ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15427546906165424368/8E69222BC102AAA8098F8571631A1A6348B71AA7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3 GUID2c6bd0NameCustom_Model_Infinite_BagTransformposXN:posY}?posZYIrotX\trotY`vf@rotZ}>scaleX.?scaleY?scaleZ.?NicknameGains Devastating WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID8e72d5Name Custom_TokenTransformposXG@posYG?posZu8)@rotX@C?rotY >rotZ`l SscaleXU?scaleY?scaleZU?NicknameGains Devastating WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9883319023707143681/1744874734F928AC66A6D8A567EDA9280FBC5F18/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDd519b4Name Custom_TokenTransformposXposYposZrotX<rotY(>rotZнX;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9883319023707143681/1744874734F928AC66A6D8A567EDA9280FBC5F18/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4 GUID3300aeNameCustom_Model_Infinite_BagTransformposXN8posY}?posZYIrotXsʹrotYf@rotZ)x>scaleX.?scaleY?scaleZ.?NicknameGains PrecisionDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUIDd3367cName Custom_TokenTransformposXu!@posYY?posZI)@rotX FurotY@3?rotZv@scaleXU?scaleY?scaleZU?NicknameGains PrecisionDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14178284958638010811/507A7575F85056D82655EF4C74C39CAE3F7768B4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID3b47f5Name Custom_TokenTransformposXposYposZrotXo<rotY#>rotZ T;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14178284958638010811/507A7575F85056D82655EF4C74C39CAE3F7768B4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI5 GUIDdb6736NameCustom_Model_Infinite_BagTransformposXN5posY}?posZYIrotX rotYf@rotZ`>scaleX.?scaleY?scaleZ.?NicknameGains HazardousDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUIDb2035fName Custom_TokenTransformposX V@posY`ؚ?posZ \(@rotXXXrotY o>rotZ>nscaleXU?scaleY?scaleZU?NicknameGains HazardousDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17625694776023051311/5B1C35BDDB69F3C2E6697AAF956B9CA96823C795/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID2d8417Name Custom_TokenTransformposXposYposZrotX`3<rotY >rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17625694776023051311/5B1C35BDDB69F3C2E6697AAF956B9CA96823C795/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI6 GUIDeb8ba7NameCustom_Model_Infinite_BagTransformposXN3posY}?posZYIrotXrQrotYf@rotZ`>scaleX.?scaleY?scaleZ.?NicknameCannot OverwatchDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDebaa5eName Custom_TokenTransformposX$#@posYR?posZ8h?rotX@hrotY"t?rotZ Pv@scaleXU?scaleY?scaleZU?NicknameCannot OverwatchDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16579474896655980827/63EC35D5AF76E25E076CEE35143E312FB3A81139/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID5a57beName Custom_TokenTransformposXposYposZrotX!rotYrotZ ܥ=scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16579474896655980827/63EC35D5AF76E25E076CEE35143E312FB3A81139/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI7} GUIDc035c5NameCustom_Model_Infinite_BagTransformposX,posY}?posZYIrotX`4hrotYf@rotZ`=ɣ>scaleX.?scaleY?scaleZ.?NicknameStealthDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUID53b506Name Custom_TokenTransformposX#@posY?posZ !%@rotX@ rotY>rotZbscaleXU?scaleY?scaleZU?NicknameStealthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9454404294032079729/54602BBC1E3F44337E1A47A73B4BB2E57C4FAD10/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID716fbbName Custom_TokenTransformposXposYposZrotX <rotY@ >rotZ:R;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9454404294032079729/54602BBC1E3F44337E1A47A73B4BB2E57C4FAD10/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI8 GUID83919cNameCustom_Model_Infinite_BagTransformposXN0posY}?posZYIrotXfrotY sf@rotZ >scaleX.?scaleY?scaleZ.?Nickname SmokescreenDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDe50aadName Custom_TokenTransformposX%@posY?posZ@r%@rotX'?rotYxO>rotZ`scaleXU?scaleY?scaleZU?Nickname SmokescreenDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16920264032639971783/000A2AFBC35D008F68D0E617D818B9EA306434A4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7d7dd4Name Custom_TokenTransformposXposYposZrotX [<rotY`>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16920264032639971783/000A2AFBC35D008F68D0E617D818B9EA306434A4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI9 GUIDa71004NameCustom_Model_Infinite_BagTransformposX(posY}?posZYIrotX@rotYf@rotZ&a>scaleX.?scaleY?scaleZ.?NicknameFights on DeathDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUID4a3fedName Custom_TokenTransformposX@posY?posZB?rotXvrotY>rotZ`v@scaleXU?scaleY?scaleZU?NicknameFights on DeathDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11674428794801050354/386A4B6C050A0CE2DCCED64BF9367844C6F77FFF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID892122Name Custom_TokenTransformposXposYposZrotXa+rotY ־rotZܥ,scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11674428794801050354/386A4B6C050A0CE2DCCED64BF9367844C6F77FFF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI10 GUID47c0ebNameCustom_Model_Infinite_BagTransformposXN>posY~?posZJrotX@?}rotY f@rotZ8>scaleX.?scaleY?scaleZ.?NicknameReroll Hits of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDffb1b6Name Custom_TokenTransformposX`q4@posY?posZ%@rotX<v@rotY>rotZ@r?scaleXU?scaleY?scaleZU?NicknameReroll Hits of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16573592287641184879/BECC50E0D7BF470ACE1026FF49D694FE08116275/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDe8e076Name Custom_TokenTransformposXposYposZrotX Vc<rotY2>rotZ_P;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16573592287641184879/BECC50E0D7BF470ACE1026FF49D694FE08116275/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI11 GUID7d5a0cNameCustom_Model_Infinite_BagTransformposXN<posY~?posZJrotXrotYNf@rotZ5<>scaleX.?scaleY?scaleZ.?Nickname Reroll HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDc9e637Name Custom_TokenTransformposX@5@posY@o?posZG$@rotXlؾrotY>rotZ scaleXU?scaleY?scaleZU?Nickname Reroll HitsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13702994610357544702/5D8174C93A2B92D5C3640170DADB163AD61DD428/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9798a1Name Custom_TokenTransformposXposYposZrotX<rotYZI>rotZww:scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13702994610357544702/5D8174C93A2B92D5C3640170DADB163AD61DD428/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI12 GUIDa8a9e9NameCustom_Model_Infinite_BagTransformposXN:posY}?posZJrotX@yrotY@@f@rotZh&>scaleX.?scaleY?scaleZ.?NicknameReroll Wounds of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects}0uGUIDf3c807Name Custom_TokenTransformposX@posY?posZ@rotXoC?rotY`)þrotZPscaleXU?scaleY?scaleZU?NicknameReroll Wounds of 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10596262547218845676/80BAF98E755A30DE66E70E8B26B7431C90B656F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7d06e2Name Custom_TokenTransformposXposYposZrotX5<rotY >rotZ`scaleX.?scaleY?scaleZ.?NicknameReroll WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsx0pGUIDcbc745Name Custom_TokenTransformposX@"@posY?posZ` @rotXnaӾrotY>rotZ@kscaleXU?scaleY?scaleZU?NicknameReroll WoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16879464771115311829/92FD9A31DF701FA755052F16FFC2B77FAC97113F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDfa95b6Name Custom_TokenTransformposXposYposZrotXa<rotY >rotZ@=U;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16879464771115311829/92FD9A31DF701FA755052F16FFC2B77FAC97113F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI14 GUID1b0656NameCustom_Model_Infinite_BagTransformposXN5posY}?posZJrotX@оrotYf@rotZ§>scaleX.?scaleY?scaleZ.?NicknameBenefit of CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUID55285cName Custom_TokenTransformposXՙ$@posY`4?posZ@{(@rotX {?rotY =>rotZ@!scaleXU?scaleY?scaleZU?NicknameBenefit of CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13129253881179680642/AEB18C3A5901A5E1FC216A0043BB54AADD96E2F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9c6487Name Custom_TokenTransformposXposYposZrotX !=rotYP>rotZ`%};scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13129253881179680642/AEB18C3A5901A5E1FC216A0043BB54AADD96E2F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI15 GUID324055NameCustom_Model_Infinite_BagTransformposXN3posY}?posZJrotX%rotYf@rotZ L<>scaleX.?scaleY?scaleZ.?NicknameDoes Not Receive CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUIDb584a2Name Custom_TokenTransformposXRl,@posYB?posZ$@rotX1?rotYb>rotZ`(?scaleXU?scaleY?scaleZU?NicknameDoes Not Receive CoverDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9639889861660117737/0B5D565B0CD65E5C6CFA51C5F288F6124025E8A0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID148cf1Name Custom_TokenTransformposXposYposZrotX`=rotY3>rotZZ;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9639889861660117737/0B5D565B0CD65E5C6CFA51C5F288F6124025E8A0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI16GUID688d0bNameCustom_Model_Infinite_BagTransformposXN0posY}?posZJrotX`/rotY`f@rotZ [>scaleX.?scaleY?scaleZ.?NicknameHiddenDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects 0 GUID7e7cf7Name Custom_TokenTransformposX=@posY?posZ( rotXe9?rotYrotZd?scaleXU?scaleY?scaleZU?Nickname Hidden @ 6"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13111807940430769035/CBD479C969522BBBDB4953C9C332980F947417EB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates[ 2oGUID82ed6bName Custom_TokenTransformposX,4@posY(?posZsrotX5zrotYf@rotZ`!scaleXU?scaleY?scaleZU?Nickname Hidden @ 12"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10577368540891568977/A9D18639DAB97BFB6BB7478061E8A43AF13343A3/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3oGUID56fbd2Name Custom_TokenTransformposX,4@posY(?posZsrotX3RprotYf@rotZ`׉oscaleXU?scaleY?scaleZU?Nickname Hidden @ 15"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15408236362422630855/5B2EF93025FA2AAA08F7A3718C8EF674A79987BD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4oGUIDfa4578Name Custom_TokenTransformposX,4@posY(?posZsrotXtrotYf@rotZ|HyscaleXU?scaleY?scaleZU?Nickname Hidden @ 18"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12898265374851711210/0D3BA9B40B7694C5C8A0E0BB676ECD39F7C5A6F1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID84d4bfName Custom_TokenTransformposXposYposZrotX`(=rotY >rotZ೿;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17012583816697795177/A7AA6AA08ED24A1ABF503047C27EEEEC80882CE0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI17GUIDfb6846NameCustom_Model_Infinite_BagTransformposX,posY}?posZJrotX۹rotYNf@rotZ >scaleX.?scaleY?scaleZ.?NicknameDetection Range ExtendedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects} 0u GUIDd31c8eName Custom_TokenTransformposX`U:@posY`?posZ\A rotX6K?rotY`㾿rotZ` ?scaleXU?scaleY?scaleZU?NicknameDetection Range +3"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18116541524647504932/67C008E0C26D38A48107A612A2CB8054E20A03F4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates2vGUID8d164dName Custom_TokenTransformposX@)4)@posY(?posZsrotXಘj>rotYf@rotZ -scaleXU?scaleY?scaleZU?NicknameDetection Range +6"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13578796208054333397/208B15C4CD723905CB2C071A22EC3AF8C9E74D84/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3vGUID7ae190Name Custom_TokenTransformposX@)4)@posY(?posZsrotX}uwrotYf@rotZ@\?>scaleXU?scaleY?scaleZU?NicknameDetection Range +9"DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13343950658874597448/8C16114AB202447150E38194AEC35A73A8588C1C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID0f8253Name Custom_TokenTransformposXposYposZrotX0=rotY`>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12040488905665960210/DB17D7F6800714273F5C47B19EAC8E8E6768773C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI18 GUID269d00NameCustom_Model_Infinite_BagTransformposX(posY}?posZJrotX YrotY`f@rotZL>scaleX.?scaleY?scaleZ.?NicknameRemains HiddenDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID14d9a1Name Custom_TokenTransformposX~#@posYn?posZrotX`j?rotY?rotZHM?scaleXU?scaleY?scaleZU?NicknameRemains HiddenDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16281145395570577750/AAD619DFE3C79EA0A4D02365EA2124B53DD74C8F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7934beName Custom_TokenTransformposXposYposZrotX /=rotYR>rotZ Vg;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16281145395570577750/AAD619DFE3C79EA0A4D02365EA2124B53DD74C8F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI19 GUID269346NameCustom_Model_Infinite_BagTransformposX posY}?posZJrotXȼrotY~f@rotZCأ>scaleX.?scaleY?scaleZ.?NicknameReactive Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects}0uGUIDf9d515Name Custom_TokenTransformposXN4@posY?posZmrotX\?rotYrrotZ0;Q?scaleXU?scaleY?scaleZU?NicknameReactive Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9945488395228674043/177739F2E29ECA9B5EB8D1C03593D41AC3D61CD4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID657cbdName Custom_TokenTransformposXposYposZrotX`6<rotY >rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9945488395228674043/177739F2E29ECA9B5EB8D1C03593D41AC3D61CD4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI20 GUID87584aNameCustom_Model_Infinite_BagTransformposX@8posY}?posZJrotX`$rotYf@rotZ>scaleX.?scaleY?scaleZ.?NicknameSurge Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUID3b97fcName Custom_TokenTransformposXű6@posYݜ?posZnrotX>rotY[>rotZ@SscaleXU?scaleY?scaleZU?NicknameSurge Move ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17315242775941199100/98DE7544551B308FF2931DEA446FD9EDD93CF103/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID84b18eName Custom_TokenTransformposXposYposZrotX]<rotY>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17315242775941199100/98DE7544551B308FF2931DEA446FD9EDD93CF103/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI21 GUID7faf0cNameCustom_Model_Infinite_BagTransformposX@8posY}?posZJrotXrotY>f@rotZ@.>scaleX.?scaleY?scaleZ.?NicknameOne-Shot ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID8ef373Name Custom_TokenTransformposX:@posY`?posZ`I:,@rotX?rotY`>rotZ"scaleXU?scaleY?scaleZU?NicknameOne-Shot ReadyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15705922047993856303/1DA72B9807A496E1D5DCEAB0D1B4A670F8A171B7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID5532f5Name Custom_TokenTransformposXposYposZrotX=rotY`>rotZk_;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15705922047993856303/1DA72B9807A496E1D5DCEAB0D1B4A670F8A171B7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI22 GUID9f5497NameCustom_Model_Infinite_BagTransformposXpposY}?posZJrotX2rotY f@rotZ@>scaleX.?scaleY?scaleZ.?NicknameHas Used CP ReductionDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUID4c8061Name Custom_TokenTransformposX' @posY`ۛ?posZ3rotX@dU?rotY@8rotZO!HscaleXU?scaleY?scaleZU?NicknameHas Used CP ReductionDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10920939702060286966/EE1CBC5E43E3ECA87FD2AC30D153130F979FCD4D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID041223Name Custom_TokenTransformposXposYposZrotX u<rotY',>rotZG;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10920939702060286966/EE1CBC5E43E3ECA87FD2AC30D153130F979FCD4D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI23 GUID9de4c6NameCustom_Model_Infinite_BagTransformposX@4ӿposY}?posZJrotX`墸rotY@\f@rotZ֡>scaleX.?scaleY?scaleZ.?NicknameTargeted By StratagemDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUID2ab1edName Custom_TokenTransformposXR`&@posY@}?posZ-@rotX-(rotY>rotZ@ KTscaleXU?scaleY?scaleZU?NicknameTargeted By StratagemDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16399290930890555419/EB65FE669AD62D4CEAE8AADB4BA2C87691A9C36F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7a113cName Custom_TokenTransformposXposYposZrotX@_<rotY>rotZbOR;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16399290930890555419/EB65FE669AD62D4CEAE8AADB4BA2C87691A9C36F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI24 GUIDb4c4d4NameCustom_Model_Infinite_BagTransformposX@posY}?posZLrotX@4hrotYf@rotZ9>scaleX.?scaleY?scaleZ.?Nickname Must TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDc35bd2Name Custom_TokenTransformposX"@posY#?posZ,@rotX]X?rotYU>rotZ4XI?scaleXU?scaleY?scaleZU?Nickname Must TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10751720015503100727/2E21B57300D3ADEE1C2B42CAF7E5E48B02D8F58D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID5646e8Name Custom_TokenTransformposXposYposZrotX@c<rotY>rotZQ;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10751720015503100727/2E21B57300D3ADEE1C2B42CAF7E5E48B02D8F58D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI25 GUIDe052b5NameCustom_Model_Infinite_BagTransformposXpposY}?posZLrotXƹrotYf@rotZ>scaleX.?scaleY?scaleZ.?NicknameDamage RedirectedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects|0tGUID3ffc48Name Custom_TokenTransformposX1z&@posY o?posZ@'@rotX:?rotY>rotZURscaleXU?scaleY?scaleZU?NicknameDamage RedirectedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16302826763490189699/4FD199B8CB9C00D0B5CC4282CD0DE0375B030A66/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID70501dName Custom_TokenTransformposXposYposZrotX<rotY>rotZO;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16302826763490189699/4FD199B8CB9C00D0B5CC4282CD0DE0375B030A66/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI26 GUID68c00cNameCustom_Model_Infinite_BagTransformposX@8posY}?posZLrotX@G rotYf@rotZ›>scaleX.?scaleY?scaleZ.?NicknameRevivedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID38cc1cName Custom_TokenTransformposX 1@posY`?posZO rotX2v@rotY$?rotZ{v@scaleXU?scaleY?scaleZU?NicknameRevivedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12108649957143102648/B86B42D08DA25323EF2575B93309BCEE8B5AF17D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID81dfe5Name Custom_TokenTransformposXposYposZrotX%=rotYe>rotZe;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12108649957143102648/B86B42D08DA25323EF2575B93309BCEE8B5AF17D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI27 GUID25b3fcNameCustom_Model_Infinite_BagTransformposX@8posY}?posZLrotX]^rotYf@rotZ>scaleX.?scaleY?scaleZ.?NicknameReinforcements GrantedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID427544Name Custom_TokenTransformposX(@posY~?posZ-@rotXv@rotY<;?rotZujv@scaleXU?scaleY?scaleZU?NicknameReinforcements GrantedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10547314673171727786/63CFA3D86438F8257E8791778EB6D9EC35FBF8B2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID85043fName Custom_TokenTransformposXposYposZrotX <rotY@W>rotZ:scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10547314673171727786/63CFA3D86438F8257E8791778EB6D9EC35FBF8B2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI28 GUID747d8cNameCustom_Model_Infinite_BagTransformposX<posY@~?posZLrotX@罾rotY f@rotZ >scaleX.?scaleY?scaleZ.?Nickname + AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID911896Name Custom_TokenTransformposX0@posY`~?posZٜ@rotX ظ?rotYTrotZ au?scaleXU?scaleY?scaleZU?Nickname + AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16271260079635507156/277568FF9E8415D6B84470B89254EAB9336770F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID8c3039Name Custom_TokenTransformposXposYposZrotXn<rotY >rotZ`W;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16271260079635507156/277568FF9E8415D6B84470B89254EAB9336770F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI29{ GUIDbe6b99NameCustom_Model_Infinite_BagTransformposX>posY`~?posZMrotXGrotY@f@rotZ>scaleX.?scaleY?scaleZ.?Nickname+1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUIDa8e784Name Custom_TokenTransformposX 1@posY|?posZ#!@rotX`QrotYd rotZCscaleXU?scaleY?scaleZU?Nickname+1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13452547071002326686/A5A8312740867EB44DE818EBBC13B0DC3E7CBEB9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDded110Name Custom_TokenTransformposXposYposZrotX@l,=rotY`[Q>rotZg;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13452547071002326686/A5A8312740867EB44DE818EBBC13B0DC3E7CBEB9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI30{ GUID5a45b8NameCustom_Model_Infinite_BagTransformposX>posY~?posZNrotX#rotY f@rotZ\>scaleX.?scaleY?scaleZ.?Nickname+1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID1418fdName Custom_TokenTransformposX5@posY?posZ[l @rotX`KrotY-D>rotZ ascaleXU?scaleY?scaleZU?Nickname+1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10482415152197510341/5D0FC3E9FE9431F09A9F9EFFA4F72829BDA21B1A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9c9affName Custom_TokenTransformposXposYposZrotXb<rotY>rotZ$S;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10482415152197510341/5D0FC3E9FE9431F09A9F9EFFA4F72829BDA21B1A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI31 GUID9db18cNameCustom_Model_Infinite_BagTransformposX`g@posY~?posZNrotXxrotYqf@rotZ>scaleX.?scaleY?scaleZ.?Nickname +1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID03ebdbName Custom_TokenTransformposX@ٽ&@posYʜ?posZlP!@rotXU^?rotY`iU>rotZYHscaleXU?scaleY?scaleZU?Nickname +1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16724993230547231552/75E836EC8E685E10F6BA37BF7BCCD910F32CD675/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDec9a64Name Custom_TokenTransformposXposYposZrotX $=rotYQ~>rotZ3;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16724993230547231552/75E836EC8E685E10F6BA37BF7BCCD910F32CD675/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI32 GUIDe00be2NameCustom_Model_Infinite_BagTransformposX`g@posY~?posZOrotX{rotYzf@rotZ v>scaleX.?scaleY?scaleZ.?Nickname +1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID0e555dName Custom_TokenTransformposX`},@posY?posZ֢ @rotX@!*rotYrotZ`ZascaleXU?scaleY?scaleZU?Nickname +1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16189872790619466777/BAF37F4E6E6A8B70C21BA5C183D8910E289F9A20/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID868130Name Custom_TokenTransformposXposYposZrotX֑rotY@hrotZ ܥ<=scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16189872790619466777/BAF37F4E6E6A8B70C21BA5C183D8910E289F9A20/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI33{ GUID3a3b4eNameCustom_Model_Infinite_BagTransformposX>posY~?posZOrotXCrotY@f@rotZ">scaleX.?scaleY?scaleZ.?Nickname+1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUIDc34ee0Name Custom_TokenTransformposX!@posY6?posZ@rotX ?)rotY;rotZ$?scaleXU?scaleY?scaleZU?Nickname+1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13741902694431974409/713853EEA37AEA1DED423CAC8A9867472274333F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDedff80Name Custom_TokenTransformposXposYposZrotXB<rotY >rotZW;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13741902694431974409/713853EEA37AEA1DED423CAC8A9867472274333F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI34{ GUIDbe0a96NameCustom_Model_Infinite_BagTransformposX>posY~?posZ ProtXtrotYf@rotZ`>scaleX.?scaleY?scaleZ.?Nickname+1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID53919dName Custom_TokenTransformposX`*@posYJ?posZ@i@rotX|Q?rotYj>rotZ SI"scaleXU?scaleY?scaleZU?Nickname+1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14007571777471691581/91FE25A3E7DEF2CB33D764AA10EC891B21D2372C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID566080Name Custom_TokenTransformposXposYposZrotX =rotY5>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14007571777471691581/91FE25A3E7DEF2CB33D764AA10EC891B21D2372C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI35 GUIDfaf803NameCustom_Model_Infinite_BagTransformposX<posY`~?posZMrotXrotY`f@rotZ>scaleX.?scaleY?scaleZ.?Nickname + StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID3b4a72Name Custom_TokenTransformposX@*5@posY ?posZr@rotX " ?rotY DrotZRa?scaleXU?scaleY?scaleZU?Nickname + StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10049358500231104139/B10C58B14CB8D2C666635D3CC65B906FE82F0182/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID579dacName Custom_TokenTransformposXposYposZrotXrotY>rotZrscaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10049358500231104139/B10C58B14CB8D2C666635D3CC65B906FE82F0182/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI36 GUID8a7871NameCustom_Model_Infinite_BagTransformposX<posY~?posZNrotXrotY`9f@rotZe>scaleX.?scaleY?scaleZ.?Nickname + ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID25254dName Custom_TokenTransformposXm@posY?posZ`F @rotX G??rotYݠrotZ`.ascaleXU?scaleY?scaleZU?Nickname + ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11864317304319829640/DD29F55FBD4AD875E79CBD7D5BDAE679DF3395D0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID8edccdName Custom_TokenTransformposXposYposZrotXWT<rotY>rotZ`AX;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11864317304319829640/DD29F55FBD4AD875E79CBD7D5BDAE679DF3395D0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI37y GUID92a02cNameCustom_Model_Infinite_BagTransformposX<posY~?posZOrotX`rotY f@rotZ>>scaleX.?scaleY?scaleZ.?Nickname+ LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID26f382Name Custom_TokenTransformposX.@posYڜ?posZv@rotX<.rotY >rotZQscaleXU?scaleY?scaleZU?Nickname+ LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15152501747465676254/3B23F35DB58C97F2786B599BD3BF50A3CC58C680/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDd2efafName Custom_TokenTransformposXposYposZrotX"F<rotY>rotZU;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15152501747465676254/3B23F35DB58C97F2786B599BD3BF50A3CC58C680/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI38y GUIDd7a2caNameCustom_Model_Infinite_BagTransformposX<posY~?posZLProtXKrotY@ƀf@rotZ>scaleX.?scaleY?scaleZ.?Nickname+ OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID1863ddName Custom_TokenTransformposXf%@posY@?posZ@rotXtzR?rotY%>rotZ^HHscaleXU?scaleY?scaleZU?Nickname+ OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10092036182535191754/568874EF434ADC1BD0AFEAAD8B750AE16760874D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDcdd97bName Custom_TokenTransformposXposYposZrotX grotY@x>rotZ`vscaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10092036182535191754/568874EF434ADC1BD0AFEAAD8B750AE16760874D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI39 GUID7676a9NameCustom_Model_Infinite_BagTransformposX9posY ~?posZLrotX`7շrotY*f@rotZ჏>scaleX.?scaleY?scaleZ.?Nickname - AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID12ed37Name Custom_TokenTransformposX3@posY?posZv@rotX<4C?rotY>rotZZscaleXU?scaleY?scaleZU?Nickname - AttacksDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10215664029825245805/D9D2E3338C0B7C6E10BB5E7D116C273A1ACD5774/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID9eaf0bName Custom_TokenTransformposXposYposZrotXw<rotY`>rotZ U;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10215664029825245805/D9D2E3338C0B7C6E10BB5E7D116C273A1ACD5774/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI40{ GUID4d6a72NameCustom_Model_Infinite_BagTransformposX7posY@~?posZMrotXrotYf@rotZ>scaleX.?scaleY?scaleZ.?Nickname-1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUIDc3bb29Name Custom_TokenTransformposX5@posY@%?posZ`8?!@rotXmeF?rotYT>rotZsFscaleXU?scaleY?scaleZU?Nickname-1 BSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13568336362575666097/C1F5742E1B4CAEBE32F1481FDF1B8F7B2BE491B0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDc42f24Name Custom_TokenTransformposXposYposZrotX=rotY>rotZQm;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13568336362575666097/C1F5742E1B4CAEBE32F1481FDF1B8F7B2BE491B0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI41 GUIDeaa233NameCustom_Model_Infinite_BagTransformposX9posY@~?posZMrotX $rotYgf@rotZ@">scaleX.?scaleY?scaleZ.?Nickname - StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID1b2782Name Custom_TokenTransformposX_[7@posY`?posZ@@rotX`.$rotYD>rotZQscaleXU?scaleY?scaleZU?Nickname - StrengthDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18159368108062106036/E003AE3043B8EF18F602EA067DD23200BBD8B5D8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID024ee2Name Custom_TokenTransformposXposYposZrotX4rotY@BܾrotZ`ܥ,=scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18159368108062106036/E003AE3043B8EF18F602EA067DD23200BBD8B5D8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI42{ GUID549178NameCustom_Model_Infinite_BagTransformposX7posY@~?posZNrotXOrotY@Ef@rotZ4>scaleX.?scaleY?scaleZ.?Nickname-1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID2df84bName Custom_TokenTransformposXv@posYy?posZQ@rotX+rotY@&WrotZ`E0dscaleXU?scaleY?scaleZU?Nickname-1 WSDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10096175161667092493/A6287ECD0E8BCECD2BB8F114807109BEB151A377/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDf168cdName Custom_TokenTransformposXposYposZrotX@?N߼rotYΡrotZ u#;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10096175161667092493/A6287ECD0E8BCECD2BB8F114807109BEB151A377/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI43 GUID80cda6NameCustom_Model_Infinite_BagTransformposX5posY@~?posZNrotXKſrotYof@rotZp>scaleX.?scaleY?scaleZ.?Nickname -1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID675125Name Custom_TokenTransformposX`_'@posY?posZ="@rotXխ?rotY+>rotZ@v@scaleXU?scaleY?scaleZU?Nickname -1 to HitDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15453432006274208987/8073A853E98F75AE685947D725F88804399A63C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID705d37Name Custom_TokenTransformposXposYposZrotX s<rotY>rotZ4Q;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15453432006274208987/8073A853E98F75AE685947D725F88804399A63C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI44 GUID0c18e5NameCustom_Model_Infinite_BagTransformposX5posY`~?posZOrotX !rotY`f@rotZ>scaleX.?scaleY?scaleZ.?Nickname -1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID55fc40Name Custom_TokenTransformposX /@posYC?posZe@rotXX0?rotY N?rotZ ?scaleXU?scaleY?scaleZU?Nickname -1 to WoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11399839414502113854/C943D330CF4C35BAE7266CB3D233B5C3DA1FB522/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDe311b7Name Custom_TokenTransformposXposYposZrotX=rotY`p>rotZk;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11399839414502113854/C943D330CF4C35BAE7266CB3D233B5C3DA1FB522/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI45y GUID3a7547NameCustom_Model_Infinite_BagTransformposX7posY`~?posZOrotX rotY f@rotZ>scaleX.?scaleY?scaleZ.?Nickname-1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID5b32f3Name Custom_TokenTransformposX':'@posY}?posZ@rotX`-DrotY`a?rotZoK?scaleXU?scaleY?scaleZU?Nickname-1 APDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9728900852482551829/9D6FEED89DBC1F57BE077F9C52DEA0AE5FCE520C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID13852eName Custom_TokenTransformposXposYposZrotX`=rotY@jv>rotZ@rDp;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9728900852482551829/9D6FEED89DBC1F57BE077F9C52DEA0AE5FCE520C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI46 GUIDdf092cNameCustom_Model_Infinite_BagTransformposX9posY`~?posZNrotX2rotYf@rotZ`o>scaleX.?scaleY?scaleZ.?Nickname - ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDcba2fbName Custom_TokenTransformposX( "@posY?posZ Xk@rotX`j?rotY`UrotZ*hjscaleXU?scaleY?scaleZU?Nickname - ToughnessDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15943989863304312894/260EE7D9D6A965285740964CC970CF38868933CD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDf96824Name Custom_TokenTransformposXposYposZrotX@j&=rotYE>rotZ`(;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15943989863304312894/260EE7D9D6A965285740964CC970CF38868933CD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI47y GUIDda3988NameCustom_Model_Infinite_BagTransformposX9posY~?posZOrotXཾrotYf@rotZ9\>scaleX.?scaleY?scaleZ.?Nickname- LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID058b9cName Custom_TokenTransformposX`E0@posY ?posZ:=@rotX O?rotYa4>rotZoscaleXU?scaleY?scaleZU?Nickname- LDDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16292828654295729178/E750C8E2E37C8CAB56D7AE7E29B8DF5E32B1B4BB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDf1ecabName Custom_TokenTransformposXposYposZrotXH~,=rotYI>rotZ?%;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16292828654295729178/E750C8E2E37C8CAB56D7AE7E29B8DF5E32B1B4BB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI48y GUIDf045a1NameCustom_Model_Infinite_BagTransformposX9posY~?posZLProtX GrotY@yf@rotZvv>scaleX.?scaleY?scaleZ.?Nickname- OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID13653cName Custom_TokenTransformposX6Q(@posY"?posZ} @rotX*^?rotYrotZ 0yv@scaleXU?scaleY?scaleZU?Nickname- OCDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10213060837264217490/55BFE727ED0F450BAB85C969A2A1BEF4FFFFD342/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDa9ef4bName Custom_TokenTransformposXposYposZrotX`@k<rotY>rotZ`WS;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10213060837264217490/55BFE727ED0F450BAB85C969A2A1BEF4FFFFD342/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI49{ GUID6971a6NameCustom_Model_Infinite_BagTransformposX7posY~?posZ ProtXarotY f@rotZ>scaleX.?scaleY?scaleZ.?Nickname-1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsp0hGUID82cd8fName Custom_TokenTransformposXJ;.@posY?posZ@rotX>rotY >rotZ`J?scaleXU?scaleY?scaleZU?Nickname-1 SVDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11191857537696704764/9A1D8E9EE932AF88EFC323FA7E9511A2E95A478B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDe65300Name Custom_TokenTransformposXposYposZrotX@ =rotY2>rotZfx;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11191857537696704764/9A1D8E9EE932AF88EFC323FA7E9511A2E95A478B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI50GUID1c4e10NameCustom_Model_Infinite_BagTransformposX2posY ~?posZMrotXrotYPf@rotZA>scaleX.?scaleY?scaleZ.?NicknameInvulnerable SaveDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUIDad3a15Name Custom_TokenTransformposX@posY g?posZ`ErotXڐK?rotYM*l?rotZ~fscaleXU?scaleY?scaleZU?NicknameInvulnerable Save 2++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12009848354843382953/7FA10C06EFD168359F395B5A0386DCA26A0A9021/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates 2xGUID69828dName Custom_TokenTransformposX4@posY(?posZ@߿rotX '>rotYf@rotZ숾scaleXU?scaleY?scaleZU?NicknameInvulnerable Save 3++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16574075971847558735/A45728C8B46A93E7D851C72031821D1306E03B27/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3xGUIDf3efc8Name Custom_TokenTransformposX4@posY(?posZ@߿rotX,>rotYf@rotZ xc>scaleXU?scaleY?scaleZU?NicknameInvulnerable Save 4++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17695437947738351576/2CF97D4266D04FE765E658FE8204720BE5EC81BC/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4xGUID45ac85Name Custom_TokenTransformposX4@posY(?posZ@߿rotX @wrotYf@rotZ`Xt>scaleXU?scaleY?scaleZU?NicknameInvulnerable Save 5++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15189500708022316921/0DEE6EF800E57B936D2170E5ADBA36C33C829F9C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI5xGUID129d53Name Custom_TokenTransformposX4@posY(?posZ@߿rotX y>rotYf@rotZ@`SscaleXU?scaleY?scaleZU?NicknameInvulnerable Save 6++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11525460984140145217/1F22571DA69F5C00B3563943121726B76B226812/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDcd9bc8Name Custom_TokenTransformposXposYposZrotX =rotY mz>rotZILp;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16277335046009663357/3EA844CCF9609BF787C77D40144C0A5C2E93C43F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI51GUIDdbeafbNameCustom_Model_Infinite_BagTransformposX0posY ~?posZMrotX`.rotYf@rotZţ>scaleX.?scaleY?scaleZ.?Nickname Feel No PainDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects 0 GUID4becadName Custom_TokenTransformposX*@posY ՜?posZ`:DrotXQ?rotY@R>rotZr0scaleXU?scaleY?scaleZU?NicknameFeel No Pain 3+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16858664454600794287/58B272FA41B40D14F6FF38C3D2D49D0D2A27274C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStatesj 2tGUID5db7cdName Custom_TokenTransformposX`h1@posY(?posZrotX@frotYf@rotZ`ljscaleXU?scaleY?scaleZU?NicknameFeel No Pain 4+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18098782768342139719/58F0449D22E5DB7A78A4F863E690BC5B9EB17D55/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3tGUIDab97bdName Custom_TokenTransformposX`h1@posY(?posZrotX.qrotYf@rotZerscaleXU?scaleY?scaleZU?NicknameFeel No Pain 5+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13215125677209945797/8CB4F5ECA2408950AB42F9170C7D6644B29CBBED/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4tGUID1c250aName Custom_TokenTransformposX`h1@posY(?posZrotX0GrotYf@rotZ?zscaleXU?scaleY?scaleZU?NicknameFeel No Pain 6+++DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15872277241649270468/57AAFB6A95185A6811D5DE8CC50D7080C1E50D6F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID08f3baName Custom_TokenTransformposXposYposZrotX/=rotY y>rotZ v;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15207957715838043874/25795D562205CD5513DAB82A81F9224EDCD9EC0B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI52 GUID729fafNameCustom_Model_Infinite_BagTransformposX0posY@~?posZNrotXTrotYf@rotZ X>scaleX.?scaleY?scaleZ@.?NicknameIgnore ModifiersDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDe131f5Name Custom_TokenTransformposX46@posYӜ?posZDW?rotXq*C?rotY оrotZ`YscaleXU?scaleY?scaleZU?NicknameIgnore ModifiersDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10164397719206957151/D451EC2BF34215FEE0E5822984B697E1AAED239E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID0a61c9Name Custom_TokenTransformposXposYposZrotX<rotYoI>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10164397719206957151/D451EC2BF34215FEE0E5822984B697E1AAED239E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI53mGUID36dd99NameCustom_Model_Infinite_BagTransformposX2posY@~?posZNrotX@FXrotY@Ԁf@rotZ j>scaleX.?scaleY?scaleZ.?NicknameOC SetDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsb 0Z GUID991eb4Name Custom_TokenTransformposX`#'@posY@ț?posZx+@rotX`BSrotY`?rotZ@;scaleXU?scaleY?scaleZU?Nickname OC Set to 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17350645795018514721/238B1C2FA469454BE59F4428BD890E480CA0C846/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates2lGUID63d51eName Custom_TokenTransformposX5@posY(?posZ`( @rotX KjrotY@f@rotZ@>scaleXU?scaleY?scaleZU?Nickname OC HalvedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17748053706262387093/0AE5E8CC3E60B7DDB33A641702CC167C97E58213/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3mGUID54c6a2Name Custom_TokenTransformposX5@posY(?posZ`( @rotX:vrotY@f@rotZ scaleXU?scaleY?scaleZU?Nickname OC DoubledDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18283895009815519438/0CADB1DD445663D1495D8638459AEA6F1EB8E508/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDb9089eName Custom_TokenTransformposXposYposZrotX=rotY>rotZq;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9892587370100184732/C5B6BA51E2BEEAAD09B86A30A28DA9CBDE99ED21/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI54GUID6397faNameCustom_Model_Infinite_BagTransformposX2posY`~?posZ ProtX^rotYf@rotZ@>scaleX.?scaleY?scaleZ@.?NicknameMovement BuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects 0 GUID887fcbName Custom_TokenTransformposXc@posYposYh%posZ=rotX<rotY,>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10888346467352832421/2D21AC1F8EDE549F4853843CC799BBD494244820/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI55GUIDe587fcNameCustom_Model_Infinite_BagTransformposX0posY`~?posZ ProtX3rotYf@rotZ#>scaleX.?scaleY?scaleZ@.?NicknameMovement DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0 GUID73bd57Name Custom_TokenTransformposX|[+@posY`b?posZ`?rotX`frotYѾrotZv@scaleXU?scaleY?scaleZU?NicknameMove DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17482041595606334591/634D51CF193D41F3A8904782FE74C7C01CF6DA0C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates 2yGUIDe27c26Name Custom_TokenTransformposX`h1@posY(?posZ݈?rotX Q rotYf@rotZ8΂>scaleXU?scaleY?scaleZU?NicknameMove + Advance DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9858426759466265784/7F1CFB05FDD791B9D189B298AF6E93D879A5670E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI3yGUID18578dName Custom_TokenTransformposX`h1@posY(?posZ݈?rotXQrotYf@rotZJs>scaleXU?scaleY?scaleZU?NicknameMove + Charge DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16675767374373495035/CF8E4092718B704BD42740E368682B613B043064/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI4GUID1a0382Name Custom_TokenTransformposX`h1@posY(?posZ݈?rotX`5krotYf@rotZz^>scaleXU?scaleY?scaleZU?Nickname!Move + Advance + Charge DebuffedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12558882588344479665/10258EB2135ECE48D7C1FEC724E6E4B52FD43565/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID17c88cName Custom_TokenTransformposXposY=posZYkp>rotX |<rotY@>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17482041595606334591/634D51CF193D41F3A8904782FE74C7C01CF6DA0C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI56} GUIDfea9b9NameCustom_Model_Infinite_BagTransformposX`1posY~?posZ࣌ProtX6>rotY f@rotZ@ta>scaleX.?scaleY?scaleZ.?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUID1d5c0dName Custom_TokenTransformposX1@posY@?posZ|rotX@protY ErotZt?scaleXU?scaleY?scaleZU?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10441700730326708145/B6D253C9C9122D44192091AFFE016459D492D3FB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDa243afName Custom_TokenTransformposXposYposZrotX<rotY@F>rotZ|T;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10441700730326708145/B6D253C9C9122D44192091AFFE016459D492D3FB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI57 GUID09deb2NameCustom_Model_Infinite_BagTransformposX+posY ~?posZMrotXrotY>f@rotZ1B>scaleX.?scaleY?scaleZ.?Nickname Can ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUID0bdeb3Name Custom_TokenTransformposX``$@posY?posZ B rotX`arotYԾrotZ`+?scaleXU?scaleY?scaleZU?Nickname Can ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15233406613328206719/EB9E555B2E4D140F3EFA507CB196B84763DDD80C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID24fcc2Name Custom_TokenTransformposXposYposZrotX!=rotY@i>rotZ@ |;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15233406613328206719/EB9E555B2E4D140F3EFA507CB196B84763DDD80C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI58 GUID9da719NameCustom_Model_Infinite_BagTransformposX'posY~?posZMrotXţrotY`f@rotZ>scaleX.?scaleY?scaleZ.?Nickname Cannot ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsw0oGUID7ca300Name Custom_TokenTransformposXkf+@posY@?posZ}urotX U?rotY ǾrotZvscaleXU?scaleY?scaleZU?Nickname Cannot ShootDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17200337404271520271/C46350DFA84E0EE38DF25A4AA35714016461F932/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDfac961Name Custom_TokenTransformposXposYposZrotX <rotYB>rotZT;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17200337404271520271/C46350DFA84E0EE38DF25A4AA35714016461F932/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI59 GUIDb055b7NameCustom_Model_Infinite_BagTransformposXYposY}?posZYMrotX@ḾrotY3f@rotZV>scaleX.?scaleY?scaleZ.?NicknameHas Used Once Per TurnDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID932331Name Custom_TokenTransformposXq#@posY`͜?posZirotX`NrotY >rotZ0xQscaleXU?scaleY?scaleZU?NicknameHas Used Once Per TurnDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15594386820679284179/B2DA68F50F401975B9DE21AAD5B0BC55210784C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID97b748Name Custom_TokenTransformposXposYposZrotX<rotYϳ>rotZߩU;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15594386820679284179/B2DA68F50F401975B9DE21AAD5B0BC55210784C5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI60 GUID4f9f9bNameCustom_Model_Infinite_BagTransformposX5posY}?posZ3MrotX,rotY f@rotZ x>scaleX.?scaleY?scaleZ.?NicknameHas Used Once Per Battle RoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd5b47dName Custom_TokenTransformposX)@posYŗ?posZrotXٱt?rotY Rb?rotZ;Hv?scaleXU?scaleY?scaleZU?NicknameHas Used Once Per Battle RoundDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9600819078001643173/398EDD5C6384C391DEA01C5FE4B561430CE55825/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDac7315Name Custom_TokenTransformposXposYposZrotX9 =rotYƳ}>rotZ.;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9600819078001643173/398EDD5C6384C391DEA01C5FE4B561430CE55825/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI61 GUID712ff4NameCustom_Model_Infinite_BagTransformposXpposY}?posZYMrotX7rotY f@rotZN>scaleX.?scaleY?scaleZ.?NicknameHas Used Once Per PhaseDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUIDc53e6aName Custom_TokenTransformposXb@posY`R?posZK?rotX N?rotY}rotZ {v@scaleXU?scaleY?scaleZU?NicknameHas Used Once Per PhaseDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15804422053123139112/E89856F127D0721F6394AD42A5503F7E77066CC7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID083c4dName Custom_TokenTransformposXposYposZrotX ]!rotY ̾rotZ ܥscaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15804422053123139112/E89856F127D0721F6394AD42A5503F7E77066CC7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI62GUIDa483acNameCustom_Model_Infinite_BagTransformposX]posY~?posZO-NrotXf&rotYVf@rotZ>scaleX.?scaleY?scaleZ.?NicknameUses RemainingDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDfea083Name Custom_TokenTransformposX'@posY՜?posZ \rotX N?rotY(<>rotZ@EscaleXU?scaleY?scaleZU?NicknameOne Use RemainingDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10073072590831742101/ADB9738C042290E51787235E30354A6B4E46224C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIStates}2uGUID6f9f3bName Custom_TokenTransformposX~0@posY(?posZv7rotX`q=grotYf@rotZ]hscaleXU?scaleY?scaleZU?NicknameTwo Uses RemainingDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12737274812260215970/A0D73C0EE73CC848DF55AECEDBBD432224F1484A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID7546d1Name Custom_TokenTransformposXposYposZrotX/n<rotY>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10073072590831742101/ADB9738C042290E51787235E30354A6B4E46224C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI63 GUIDfbb884NameCustom_Model_Infinite_BagTransformposX+posY ~?posZNrotX`rotYf@rotZrD>scaleX.?scaleY?scaleZ.?NicknameCan Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects~0vGUID77f9f4Name Custom_TokenTransformposX@Q[.@posY?posZL+rotXprotY ?rotZ#{v@scaleXU?scaleY?scaleZU?NicknameCan Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12655439807939350284/0198BC44483D3AA4A4A08C175CDC14C7671D1E8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDec7b75Name Custom_TokenTransformposXposYposZrotXLz<rotY@:>rotZ@R;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12655439807939350284/0198BC44483D3AA4A4A08C175CDC14C7671D1E8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI64 GUID1087bcNameCustom_Model_Infinite_BagTransformposX'posY ~?posZNrotX<rotY`f@rotZ k>scaleX.?scaleY?scaleZ.?NicknameCannot Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUIDaac69cName Custom_TokenTransformposX y0@posY`?posZtrotX >?rotY >rotZ QscaleXU?scaleY?scaleZU?NicknameCannot Perform ActionsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17450529432768595047/BFF2692756B5A7DC96B2058A388C224AFAEE4935/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID36c7a9Name Custom_TokenTransformposXposYposZrotX >& =rotY@~>rotZr;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17450529432768595047/BFF2692756B5A7DC96B2058A388C224AFAEE4935/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI65 GUID3c1e62NameCustom_Model_Infinite_BagTransformposX+posY@~?posZOrotXhrotY f@rotZq>scaleX.?scaleY?scaleZ.?Nickname Can ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID48e214Name Custom_TokenTransformposX`$4@posY 6?posZ DrotX?rotY@?rotZ?scaleXU?scaleY?scaleZU?Nickname Can ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12421409741468501606/0F82967969CDFE27B95DD79D1469CE63190E7157/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID910389Name Custom_TokenTransformposXposYposZrotX<rotY>rotZ p2W;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12421409741468501606/0F82967969CDFE27B95DD79D1469CE63190E7157/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI66 GUID8bee95NameCustom_Model_Infinite_BagTransformposX'posY@~?posZOrotXηrotYKf@rotZ@l>scaleX.?scaleY?scaleZ.?NicknameCannot ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsx0pGUIDa8d69cName Custom_TokenTransformposXMh5@posYH?posZprotX`,R?rotY@>rotZh+MscaleXU?scaleY?scaleZU?NicknameCannot ChargeDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10702833170741215872/6867BC43303787DDD722026CDE3BBC1C51D68C5B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUID83c5e1Name Custom_TokenTransformposXposYposZrotX{<rotYҔ>rotZU;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10702833170741215872/6867BC43303787DDD722026CDE3BBC1C51D68C5B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI67 GUID86f1adNameCustom_Model_Infinite_BagTransformposX@8posY@~?posZ ProtXnrotY`2f@rotZ5@>scaleX.?scaleY?scaleZ.?NicknameObjective Secured (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID35ff89Name Custom_TokenTransformposX@K+@posY@?posZ&@rotX>y?rotYSrotZp?scaleXU?scaleY?scaleZU?NicknameObjective Secured (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16558249251381302600/88A752C75F310EC7F63AB02CA0A74BC3393F6779/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUIDff5331Name Custom_TokenTransformposXposYposZrotX?&rotY+ھrotZ`ܥ=scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16558249251381302600/88A752C75F310EC7F63AB02CA0A74BC3393F6779/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI68 GUID6a7c2bNameCustom_Model_Infinite_BagTransformposX78posY`~?posZnjProtXorotYf@rotZ`>scaleX.?scaleY?scaleZ.?Nickname Battle-ShockDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDcb297bName Custom_TokenTransformposXM@posY؜?posZT@rotX`O?rotY >rotZ`cMscaleXU?scaleY?scaleZU?Nickname Battle-ShockDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9581741158556778672/9546E93DAFB582D6780F81231DA739EEEB9C7CD1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUID217df0Name Custom_TokenTransformposXposYposZrotXt40=rotY>rotZ;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9581741158556778672/9546E93DAFB582D6780F81231DA739EEEB9C7CD1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI69 GUID49c73bNameCustom_Model_Infinite_BagTransformposXpposY ~?posZ ProtX`IrotYf@rotZƒ>scaleX.?scaleY?scaleZ.?NicknameObjective Secured (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUIDd9df6eName Custom_TokenTransformposX@0@posY`?posZ ?rotX`*rotY`ǾrotZxscaleXU?scaleY?scaleZU?NicknameObjective Secured (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9449365886267227774/7C03E09C9035E64A7E2886C895A7FDC869CDBA56/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsp0hGUID8d5ba8Name Custom_TokenTransformposXposYposZrotXUc1=rotYO>rotZ;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9449365886267227774/7C03E09C9035E64A7E2886C895A7FDC869CDBA56/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI70 GUID3a5369NameCustom_Model_Infinite_BagTransformposXpposY@~?posZProtX`GrotYf@rotZ>scaleX.?scaleY?scaleZ.?NicknameOperation Marker (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID0b215bName Custom_TokenTransformposXy@posY )?posZ ܿrotXP?rotY߾rotZ@5)7scaleXU?scaleY?scaleZU?NicknameOperation Marker (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10210344779140932848/DBE82D90EE5889EE362AD76A938F3B11BCCCFD37/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUIDeac428Name Custom_TokenTransformposXposYposZrotX IrotY>rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10210344779140932848/DBE82D90EE5889EE362AD76A938F3B11BCCCFD37/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI71 GUID2fa111NameCustom_Model_Infinite_BagTransformposX8posY@~?posZ࣌ProtXrotYf@rotZ@t>scaleX.?scaleY?scaleZ.?NicknameOperation Marker (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID9f42e4Name Custom_TokenTransformposX:@posY`֜?posZ q?rotXv@rotYJ?rotZVD?scaleXU?scaleY?scaleZU?NicknameOperation Marker (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10714603952554854969/65EF56C24B3CB77D6FBADEC8365516D351422754/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID73c82bName Custom_TokenTransformposXposYposZrotX`rotY`>rotZeqscaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10714603952554854969/65EF56C24B3CB77D6FBADEC8365516D351422754/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI72 GUID8e3852NameCustom_Model_Infinite_BagTransformposX@8posY`~?posZ QrotXGXrotY f@rotZV<>scaleX.?scaleY?scaleZ@.?NicknameAction in Progress (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0zGUID8224a1Name Custom_TokenTransformposX6@posY ?posZ@rotXfOrotYtϾrotZ DscaleXU?scaleY?scaleZU?NicknameAction in Progress (Red)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9853997720246942774/6A6EFA7B7377D39A0282649DF035678DC85B5DA1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsx0pGUIDaec404Name Custom_TokenTransformposXposYposZrotX <rotY`h>rotZk9W;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9853997720246942774/6A6EFA7B7377D39A0282649DF035678DC85B5DA1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI73 GUIDabe733NameCustom_Model_Infinite_BagTransformposXpposY`~?posZ QrotXrotYf@rotZ@>scaleX.?scaleY?scaleZ.?NicknameAction in Progress (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID3bfe64Name Custom_TokenTransformposX~'@posY?posZ,rotX v@rotY@7D?rotZ`|v@scaleXU?scaleY?scaleZU?NicknameAction in Progress (Blue)DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13865207090087953670/CF59DD723DB3BDBED2D24F3B3A44392C1B3BF084/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDaa9e02Name Custom_TokenTransformposXposYposZrotX<rotY>rotZW;scaleX?scaleY@$@scaleZ?NicknameCustom Token 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13865207090087953670/CF59DD723DB3BDBED2D24F3B3A44392C1B3BF084/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI74 GUIDcf8637NameCustom_Model_Infinite_BagTransformposX??posY ~?posZ,ProtXrotYf@rotZ6>scaleX.?scaleY?scaleZ.?NicknameEnemy ObservedDescriptionFor Observe Enemy PrimaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd9541dName Custom_TokenTransformposXN@posYజ?posZrotXL?rotY ZվrotZv@scaleXU?scaleY?scaleZU?NicknameEnemy ObservedDescriptionFor Observe Enemy PrimaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18127329257998381222/9886083004DE1C22C15CA60B8EA98C1B47DFFE7C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID6990e9Name Custom_TokenTransformposXposYposZrotX\rotYa>rotZ ܥ<scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18127329257998381222/9886083004DE1C22C15CA60B8EA98C1B47DFFE7C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI75 GUID7bab6eNameCustom_Model_Infinite_BagTransformposX@posY ~?posZ,ProtX rotY$f@rotZ@>scaleX.?scaleY?scaleZ.?NicknameUnit ConsecratedDescriptionFor Consecration PrimaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID1f0a1eName Custom_TokenTransformposXܜ$@posY?posZqrotX@-FT?rotY`~>rotZ7A?scaleXU?scaleY?scaleZU?NicknameUnit ConsecratedDescriptionFor Consecration PrimaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9265616858586819768/93E99DF19BC0AE86C9881BFB7643F987E94961AB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsp0hGUID791435Name Custom_TokenTransformposXposYposZrotXrotYAZrrotZO2;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9265616858586819768/93E99DF19BC0AE86C9881BFB7643F987E94961AB/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI76 GUIDf65266NameCustom_Model_Infinite_BagTransformposX@posY~?posZ,ProtXvrotY@ f@rotZ@V>scaleX.?scaleY?scaleZ.?Nickname Unit DoomedDescriptionFor Punishment PrimaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID75b67eName Custom_TokenTransformposX 8(@posY Ӝ?posZ rotXYi?rotY [xrotZ`PdscaleXU?scaleY?scaleZU?Nickname Unit DoomedDescriptionFor Punishment PrimaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12876601956845860071/153C47538A64E0A6AEFE19C4AA49344777BCB6F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID1d5c0dName Custom_TokenTransformposXYkposY`L2=posZ WS=rotX Z<rotY>rotZ ܥscaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12876601956845860071/153C47538A64E0A6AEFE19C4AA49344777BCB6F9/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI77 GUIDcf6e83NameCustom_Model_Infinite_BagTransformposX??posY@~?posZrProtX`rotYf@rotZA>scaleX.?scaleY?scaleZ@.?NicknameObjective TemptedDescriptionFor Tempting Target SecondaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDe2b7ecName Custom_TokenTransformposX {,@posY@?posZhrotXgzVrotYPվrotZ`G?scaleXU?scaleY?scaleZU?NicknameObjective TemptedDescriptionFor Tempting Target SecondaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16403052389472970811/417348AEF7D07556BF306F133AEE1FCF845BFDEC/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID64ab9aName Custom_TokenTransformposXposYposZrotX@_5=rotYb>rotZ;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16403052389472970811/417348AEF7D07556BF306F133AEE1FCF845BFDEC/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI78 GUID649125NameCustom_Model_Infinite_BagTransformposX@posY@~?posZhProtXrotY@f@rotZh>scaleX.?scaleY?scaleZ.?NicknameGuarding ObjectiveDescriptionFor Burden of Trust SecondaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID7e6c37Name Custom_TokenTransformposX`:/@posY@Ŝ?posZ 7rotX3?rotY m޾rotZbscaleXU?scaleY?scaleZU?NicknameGuarding ObjectiveDescriptionFor Burden of Trust SecondaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13896626996681093367/50231335816CB8157BCFD5F4DB92BC9FC7EAC6B5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUIDaa597fName Custom_TokenTransformposXYk`>posY`TposZ Q@rotX zrotY >rotZ ܥ,scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13896626996681093367/50231335816CB8157BCFD5F4DB92BC9FC7EAC6B5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI79 GUID27e749NameCustom_Model_Infinite_BagTransformposX@posY@~?posZ`tProtX?VrotYf@rotZ>scaleX.?scaleY?scaleZ.?NicknameHolding BeaconDescriptionFor Beacon SecondaryGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID87068fName Custom_TokenTransformposX2@posY?posZ rotX8,?rotY ?ľrotZFJscaleXU?scaleY?scaleZU?NicknameHolding BeaconDescriptionFor Beacon SecondaryGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11343351190458973473/6241E0383085CA6E3DCA92F43B95BF48E96C9302/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID8da8cfName Custom_TokenTransformposXposYposZrotXCrotYਉrotZ@c;scaleX?scaleY@$@scaleZ?NicknameMobileDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11343351190458973473/6241E0383085CA6E3DCA92F43B95BF48E96C9302/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScriptLuaScriptStateXmlUI24VGUID10a26aNameBagTransformposXcBposY?posZ@CrotX ?rotYDž?rotZ scaleX ?scaleY ?scaleZ ?Nickname#Legacy Tokens (Blue) by TrashpandaDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r-?g-?b-?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScriptQ?-- FTC-GUID: 7aca71,10a26a,446a11,9d6b2f -- Utility memory bag by Directsun -- Version 2.5 -- Fork of Memory Bag 2.0 by MrStump function updateSave() local data_to_save = {["ml"]=memoryList} saved_data = JSON.encode(data_to_save) self.script_state = saved_data end function combineMemoryFromBagsWithin() local bagObjList = self.getObjects() for _, bagObj in ipairs(bagObjList) do local data = bagObj.lua_script_state if data ~= nil then local j = JSON.decode(data) if j ~= nil and j.ml ~= nil then for guid, entry in pairs(j.ml) do memoryList[guid] = entry end end end end end function updateMemoryWithMoves() print("updating memory with moves") memoryList = memoryListBackup --get the first transposed object's coordinates local obj = getObjectFromGUID(moveGuid) -- p1 is where needs to go, p2 is where it was local refObjPos = memoryList[moveGuid].pos local deltaPos = findOffsetDistance(obj.getPosition(), refObjPos, nil) local movedRotation = obj.getRotation() for guid, entry in pairs(memoryList) do memoryList[guid].pos.x = entry.pos.x - deltaPos.x memoryList[guid].pos.y = entry.pos.y - deltaPos.y memoryList[guid].pos.z = entry.pos.z - deltaPos.z -- memoryList[guid].rot.x = movedRotation.x -- memoryList[guid].rot.y = movedRotation.y -- memoryList[guid].rot.z = movedRotation.z end --theList[obj.getGUID()] = { -- pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, -- rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, -- lock=obj.getLock() --} moveList = {} end function onload(saved_data) fresh = true if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) --Set up information off of loaded_data memoryList = loaded_data.ml else --Set up information for if there is no saved saved data memoryList = {} end moveList = {} moveGuid = nil if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end end --Beginning Setup --Make setup button function createSetupButton() self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --Triggered by Transpose button function buttonClick_transpose() moveGuid = nil broadcastToAll("Select one object and move it- all objects will move relative to the new location", {0.75, 0.75, 1}) memoryListBackup = duplicateTable(memoryList) memoryList = {} moveList = {} self.clearButtons() createButtonsOnAllObjects(true) createSetupActionButtons(true) end --Triggered by setup button, function buttonClick_setup() memoryListBackup = duplicateTable(memoryList) memoryList = {} self.clearButtons() createButtonsOnAllObjects(false) createSetupActionButtons(false) end function getAllObjectsInMemory() local objTable = {} local curObj = {} for guid in pairs(memoryListBackup) do curObj = getObjectFromGUID(guid) table.insert(objTable, curObj) end return objTable -- return getAllObjects() end --Creates selection buttons on objects function createButtonsOnAllObjects(move) local howManyButtons = 0 local objsToHaveButtons = {} if move == true then objsToHaveButtons = getAllObjectsInMemory() else objsToHaveButtons = getAllObjects() end for _, obj in ipairs(objsToHaveButtons) do if obj ~= self then local dummyIndex = howManyButtons --On a normal bag, the button positions aren't the same size as the bag. globalScaleFactor = 1.25 * 1/self.getScale().x --Super sweet math to set button positions local selfPos = self.getPosition() local objPos = obj.getPosition() local deltaPos = findOffsetDistance(selfPos, objPos, obj) local objPos = rotateLocalCoordinates(deltaPos, self) objPos.x = -objPos.x * globalScaleFactor objPos.y = objPos.y * globalScaleFactor objPos.z = objPos.z * globalScaleFactor --Offset rotation of bag local rot = self.getRotation() rot.y = -rot.y + 180 --Create function local funcName = "selectButton_" .. howManyButtons local func = function() buttonClick_selection(dummyIndex, obj, move) end local color = {0.75,0.25,0.25,0.6} local colorMove = {0,0,1,0.6} if move == true then color = colorMove end self.setVar(funcName, func) self.createButton({ click_function=funcName, function_owner=self, position=objPos, rotation=rot, height=1000, width=1000, color=color, }) howManyButtons = howManyButtons + 1 end end end --Creates submit and cancel buttons function createSetupActionButtons(move) self.createButton({ label="Cancel", click_function="buttonClick_cancel", function_owner=self, position={0,0.3,-2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Submit", click_function="buttonClick_submit", function_owner=self, position={0,0.3,-2.8}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) if move == false then self.createButton({ label="Add", click_function="buttonClick_add", function_owner=self, position={0,0.3,-3.6}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.25,1,0.25} }) if fresh == false then self.createButton({ label="Set New", click_function="buttonClick_setNew", function_owner=self, position={0,0.3,-4.4}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) self.createButton({ label="Remove", click_function="buttonClick_remove", function_owner=self, position={0,0.3,-5.2}, rotation={0,180,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,0.25,0.25} }) end end self.createButton({ label="Reset", click_function="buttonClick_reset", function_owner=self, position={-2,0.3,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --During Setup --Checks or unchecks buttons function buttonClick_selection(index, obj, move) local colorMove = {0,0,1,0.6} local color = {0,1,0,0.6} previousGuid = selectedGuid selectedGuid = obj.getGUID() theList = memoryList if move == true then theList = moveList if previousGuid ~= nil and previousGuid ~= selectedGuid then local prevObj = getObjectFromGUID(previousGuid) prevObj.highlightOff() self.editButton({index=previousIndex, color=colorMove}) theList[previousGuid] = nil end previousIndex = index end if theList[selectedGuid] == nil then self.editButton({index=index, color=color}) --Adding pos/rot to memory table local pos, rot = obj.getPosition(), obj.getRotation() --I need to add it like this or it won't save due to indexing issue theList[obj.getGUID()] = { pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock() } obj.highlightOn({0,1,0}) else color = {0.75,0.25,0.25,0.6} if move == true then color = colorMove end self.editButton({index=index, color=color}) theList[obj.getGUID()] = nil obj.highlightOff() end end --Cancels selection process function buttonClick_cancel() memoryList = memoryListBackup moveList = {} self.clearButtons() if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end removeAllHighlights() broadcastToAll("Selection Canceled", {1,1,1}) moveGuid = nil end --Saves selections function buttonClick_submit() fresh = false if next(moveList) ~= nil then for guid in pairs(moveList) do moveGuid = guid end if memoryListBackup[moveGuid] == nil then broadcastToAll("Item selected for moving is not already in memory", {1, 0.25, 0.25}) else broadcastToAll("Moving all items in memory relative to new objects position!", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(moveList) do moveGuid = guid count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end updateMemoryWithMoves() updateSave() buttonClick_place() end elseif next(memoryList) == nil and moveGuid == nil then memoryList = memoryListBackup broadcastToAll("No selections made.", {0.75, 0.25, 0.25}) end combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() moveGuid = nil end function combineTables(first_table, second_table) for k,v in pairs(second_table) do first_table[k] = v end end function buttonClick_add() fresh = false combineTables(memoryList, memoryListBackup) broadcastToAll("Adding internal bags and selections to existing memory", {0.25, 0.75, 0.25}) combineMemoryFromBagsWithin() self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() end function buttonClick_remove() broadcastToAll("Removing Selected Entries From Memory", {1.0, 0.25, 0.25}) self.clearButtons() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 memoryListBackup[guid] = nil local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Removed", {1,1,1}) memoryList = memoryListBackup updateSave() end function buttonClick_setNew() broadcastToAll("Setting new position relative to items in memory", {0.75, 0.75, 1}) self.clearButtons() createMemoryActionButtons() local count = 0 for _, obj in ipairs(getAllObjects()) do guid = obj.guid if memoryListBackup[guid] ~= nil then count = count + 1 memoryListBackup[guid].pos = obj.getPosition() memoryListBackup[guid].rot = obj.getRotation() memoryListBackup[guid].lock = obj.getLock() end end broadcastToAll(count.." Objects Saved", {1,1,1}) memoryList = memoryListBackup updateSave() end --Resets bag to starting status function buttonClick_reset() fresh = true memoryList = {} self.clearButtons() createSetupButton() removeAllHighlights() broadcastToAll("Tool Reset", {1,1,1}) updateSave() end --After Setup function createMemoryActionButtons() local guid = self.getGUID() local isTokenBag = (guid == "7aca71") or (guid == "10a26a") or (guid == "446a11") or (guid == "9d6b2f") local showLabel = isTokenBag and "Show Tokens" or "Show Maps" local showWidth = isTokenBag and 1600 or 1400 local hasToolTip = isTokenBag and "Place Tokens" or "" self.createButton({ label=showLabel, click_function="buttonClick_place", function_owner=self, position={0,0.2,-2}, rotation={0,180,0}, height=350, width=showWidth, tooltip=hasToolTip, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Recall", click_function="buttonClick_recall", function_owner=self, position={0,0.2,-2.8}, rotation={0,180,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={-2,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Move", click_function="buttonClick_transpose", function_owner=self, position={-2.8,0.2,0}, rotation={0,270,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) end --Sends objects from bag/table to their saved position/rotation function buttonClick_place() local bagObjList = self.getObjects() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) --If obj is out on the table, move it to the saved pos/rot if obj ~= nil then obj.setPositionSmooth(entry.pos) obj.setRotationSmooth(entry.rot) obj.setLock(entry.lock) else --If obj is inside of the bag for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then local item = self.takeObject({ guid=guid, position=entry.pos, rotation=entry.rot, smooth=false }) item.setLock(entry.lock) break end end end end local obj = getObjectFromGUID("7abc5a") if(obj) then destroyObject(obj) end broadcastToAll("Objects Placed", {1,1,1}) end --Recalls objects to bag from table function buttonClick_recall() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then self.putObject(obj) end end broadcastToAll("Objects Recalled", {1,1,1}) end --Utility functions --Find delta (difference) between 2 x/y/z coordinates function findOffsetDistance(p1, p2, obj) local yOffset = 0 if obj ~= nil then local bounds = obj.getBounds() yOffset = (bounds.size.y - bounds.offset.y) end local deltaPos = {} deltaPos.x = (p2.x-p1.x) deltaPos.y = (p2.y-p1.y) + yOffset deltaPos.z = (p2.z-p1.z) return deltaPos end --Used to rotate a set of coordinates by an angle function rotateLocalCoordinates(desiredPos, obj) local objPos, objRot = obj.getPosition(), obj.getRotation() local angle = math.rad(objRot.y) local x = desiredPos.x * math.cos(angle) - desiredPos.z * math.sin(angle) local z = desiredPos.x * math.sin(angle) + desiredPos.z * math.cos(angle) --return {x=objPos.x+x, y=objPos.y+desiredPos.y, z=objPos.z+z} return {x=x, y=desiredPos.y, z=z} end --Coroutine delay, in seconds --Duplicates a table (needed to prevent it making reference to the same objects) function duplicateTable(oldTable) local newTable = {} for k, v in pairs(oldTable) do newTable[k] = v end return newTable end --Moves scripted highlight from all objects function removeAllHighlights() for _, obj in ipairs(getAllObjects()) do obj.highlightOff() end end --Round number (num) to the Nth decimal (dec) function round(num, dec) local mult = 10^(dec or 0) return math.floor(num * mult + 0.5) / mult end LuaScriptState {"ml":{"02ebc4":{"lock":false,"pos":{"x":-36.4797,"y":0.95,"z":-48.1368},"rot":{"x":0,"y":179.9915,"z":0}},"0c536a":{"lock":false,"pos":{"x":-40.642,"y":0.95,"z":-48.0263},"rot":{"x":0,"y":179.9914,"z":0}},"0cf80c":{"lock":false,"pos":{"x":-28.5446,"y":1.2302,"z":-46.2453},"rot":{"x":90,"y":359.4167,"z":0}},"1a4215":{"lock":false,"pos":{"x":-34.0084,"y":0.95,"z":-50.4516},"rot":{"x":0,"y":179.9914,"z":0}},"24c450":{"lock":false,"pos":{"x":-38.4797,"y":0.95,"z":-46.1368},"rot":{"x":0,"y":179.9915,"z":0}},"2924aa":{"lock":false,"pos":{"x":-24.4798,"y":0.95,"z":-46.1368},"rot":{"x":0,"y":179.9915,"z":0}},"2fce45":{"lock":false,"pos":{"x":-38.507,"y":0.95,"z":-44.1161},"rot":{"x":0,"y":179.9883,"z":0}},"34e075":{"lock":false,"pos":{"x":-20.4774,"y":0.95,"z":-48.2175},"rot":{"x":0,"y":179.9916,"z":0}},"38b87c":{"lock":false,"pos":{"x":-36.4797,"y":0.95,"z":-46.1368},"rot":{"x":0,"y":179.9915,"z":0}},"39a367":{"lock":false,"pos":{"x":-26.4798,"y":0.95,"z":-44.1368},"rot":{"x":0,"y":179.9915,"z":0}},"4930e4":{"lock":false,"pos":{"x":-24.4798,"y":0.95,"z":-48.1368},"rot":{"x":0,"y":179.9915,"z":0}},"5a4941":{"lock":false,"pos":{"x":-20.4394,"y":0.95,"z":-46.1468},"rot":{"x":0,"y":179.9915,"z":0}},"6a76c1":{"lock":false,"pos":{"x":-32.4595,"y":0.95,"z":-44.2468},"rot":{"x":0,"y":179.9916,"z":0}},"6ae1c4":{"lock":false,"pos":{"x":-29.2153,"y":0.95,"z":-50.4559},"rot":{"x":0,"y":180.0299,"z":0}},"70af5a":{"lock":false,"pos":{"x":-26.4798,"y":0.95,"z":-46.1368},"rot":{"x":0,"y":179.9915,"z":0}},"a9d027":{"lock":false,"pos":{"x":-20.4797,"y":0.95,"z":-44.1375},"rot":{"x":0,"y":179.9915,"z":0}},"aa0f7e":{"lock":false,"pos":{"x":-24.5056,"y":0.95,"z":-44.0949},"rot":{"x":0,"y":179.9818,"z":0}},"aadb4a":{"lock":false,"pos":{"x":-34.4998,"y":1.2302,"z":-46.0555},"rot":{"x":90,"y":359.4167,"z":0}},"b43316":{"lock":false,"pos":{"x":-40.5915,"y":0.95,"z":-46.0521},"rot":{"x":0,"y":179.9916,"z":0}},"bf22a5":{"lock":false,"pos":{"x":-32.4871,"y":0.95,"z":-46.2503},"rot":{"x":0,"y":179.9916,"z":0}},"c15e43":{"lock":false,"pos":{"x":-31.5851,"y":0.95,"z":-50.448},"rot":{"x":0,"y":179.9955,"z":0}},"c233ef":{"lock":false,"pos":{"x":-22.4579,"y":0.95,"z":-46.1494},"rot":{"x":0,"y":179.9916,"z":0}},"c60778":{"lock":false,"pos":{"x":-22.4798,"y":0.95,"z":-44.1368},"rot":{"x":0,"y":179.9915,"z":0}},"cbbff6":{"lock":false,"pos":{"x":-40.4797,"y":0.95,"z":-44.1368},"rot":{"x":0,"y":179.9915,"z":0}},"ccd8a9":{"lock":false,"pos":{"x":-22.4774,"y":0.95,"z":-48.2175},"rot":{"x":0,"y":179.9916,"z":0}},"d01b30":{"lock":false,"pos":{"x":-30.4853,"y":0.95,"z":-44.2049},"rot":{"x":0,"y":179.9818,"z":0}},"d1e9cb":{"lock":false,"pos":{"x":-30.487,"y":0.95,"z":-46.2503},"rot":{"x":0,"y":179.9916,"z":0}},"e3707e":{"lock":false,"pos":{"x":-38.5142,"y":0.95,"z":-48.1774},"rot":{"x":0,"y":179.9916,"z":0}},"e4b032":{"lock":false,"pos":{"x":-26.4798,"y":0.95,"z":-48.1368},"rot":{"x":0,"y":179.9915,"z":0}},"e7c017":{"lock":false,"pos":{"x":-36.4797,"y":0.95,"z":-44.1368},"rot":{"x":0,"y":179.9915,"z":0}},"ee4cb1":{"lock":false,"pos":{"x":-30.487,"y":0.95,"z":-48.2503},"rot":{"x":0,"y":179.9916,"z":0}},"f5a0f3":{"lock":false,"pos":{"x":-32.4871,"y":0.95,"z":-48.2503},"rot":{"x":0,"y":179.9916,"z":0}}}}XmlUIContainedObjects.0 GUIDcbbff6NameCustom_Model_Infinite_BagTransformposXf=DposYhf?posZFrotX@ْrotY`f@rotZX >scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Fell Back[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID3e4f69Name Custom_TokenTransformposX@R@posYaf?posZA@rotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Fell Back[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/1016068753798926835/328D9FD93B72CDAA71E33D26E5F72658CF02C2F2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd5dName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/1016068753798926835/328D9FD93B72CDAA71E33D26E5F72658CF02C2F2/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI1 GUIDb43316NameCustom_Model_Infinite_BagTransformposX@KDposYhf?posZ@GrotX@ܹrotY f@rotZ #>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Stealth[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0wGUID3c9222Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Stealth[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028986581/B898667066E94C59D6FEEBAC07D96551EF8B802F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd83Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028986581/B898667066E94C59D6FEEBAC07D96551EF8B802F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI2 GUID0c536aNameCustom_Model_Infinite_BagTransformposX-RDposYhf?posZ]HrotX@1rotYf@rotZ>scaleX`ff?scaleY?scaleZ`ff?Nickname4[b][i]Does not receive the benefit of cover[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID3f807fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname4[b][i]Does not receive the benefit of cover[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655405230/B19FF142D107018DC0668D67D4E868A1FD20AC8A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd98Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655405230/B19FF142D107018DC0668D67D4E868A1FD20AC8A/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI3 GUIDe3707eNameCustom_Model_Infinite_BagTransformposX@ACposYhf?posZHrotXWrotY f@rotZA>scaleX`ff?scaleY?scaleZ`ff?Nickname,[b][i]Ignore modifiers to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID380dbeName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname,[b][i]Ignore modifiers to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655393920/245F6D5DD302B1FB86F053B81A76DC65CA46977F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd80Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655393920/245F6D5DD302B1FB86F053B81A76DC65CA46977F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI4 GUID24c450NameCustom_Model_Infinite_BagTransformposXf=CposYhf?posZGrotXIrotY`f@rotZw>scaleX`ff?scaleY?scaleZ`ff?Nickname][i]Fights first[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID345d26Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname][i]Fights first[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655424098/DC4191383D8DC236F7684D0062619C68DECDE47E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd77Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655424098/DC4191383D8DC236F7684D0062619C68DECDE47E/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI5 GUID38b87cNameCustom_Model_Infinite_BagTransformposXf=BposYhf?posZGrotX򦻾rotY`f@rotZ6>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Lethal Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0{GUIDde62afName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Lethal Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028981243/327E0BCD6630A22226A5C917E94D76B5D9081627/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd60Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028981243/327E0BCD6630A22226A5C917E94D76B5D9081627/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI6. GUIDe7c017NameCustom_Model_Infinite_BagTransformposXf=BposYhf?posZFrotX rotY`f@rotZi>scaleX`ff?scaleY?scaleZ`ff?Nickname![b][i]Devastating Wounds[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDde62afName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname![b][i]Devastating Wounds[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028980142/CC7FC98416C5BBA90591F9A26128B7C2E95BAF44/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd60Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028980142/CC7FC98416C5BBA90591F9A26128B7C2E95BAF44/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI7wGUID2fce45Name Custom_TokenTransformposX`@CposYhf?posZ`FrotX̾rotY`f@rotZ@scaleX?scaleY?scaleZ?Nickname[b][i]Stealth[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028986581/B898667066E94C59D6FEEBAC07D96551EF8B802F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI8& GUID02ebc4NameCustom_Model_Infinite_BagTransformposXf=BposYhf?posZHrotX ҹrotY`f@rotZ`>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Sustained Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0~GUIDde62afName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Sustained Hits[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028982671/D6E73CAD0C9BB789A5169AD66B9BFB60CCE8A6C2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd60Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2023852627028982671/D6E73CAD0C9BB789A5169AD66B9BFB60CCE8A6C2/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI9 GUIDbf22a5NameCustom_Model_Infinite_BagTransformposX@Y>@posYhf?posZ GrotXA$rotYf@rotZ,>scaleX`ff?scaleY?scaleZ`ff?Nickname [b][i]Re-roll 1s to hit[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID9c9e41Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname [b][i]Re-roll 1s to hit[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655374861/F822DE2C0E05C957E115EBDE787CCF07ECC1A8DD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd84Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655374861/F822DE2C0E05C957E115EBDE787CCF07ECC1A8DD/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI10 GUID6a76c1NameCustom_Model_Infinite_BagTransformposX:@posY`hf?posZ FrotX_srotY@f@rotZ1>scaleX`ff?scaleY?scaleZ`ff?Nickname"[b][i]Re-roll 1s to wound[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd21bafName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname"[b][i]Re-roll 1s to wound[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655377225/88BA346E94528E97B6DA88D25570AE64B4140EB3/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd92Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655377225/88BA346E94528E97B6DA88D25570AE64B4140EB3/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI11 GUIDd01b30NameCustom_Model_Infinite_BagTransformposX<|>posY`hf?posZ :FrotX 0rotYjf@rotZ z>scaleX`ff?scaleY?scaleZ`ff?Nickname"[b][i]Re-roll wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDd6a6c9Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname"[b][i]Re-roll wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655380805/881AE6700BA7922413AD4252161C8FFC06372CC5/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd93Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655380805/881AE6700BA7922413AD4252161C8FFC06372CC5/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI12 GUIDd1e9cbNameCustom_Model_Infinite_BagTransformposX|>posYhf?posZ GrotXqrotY f@rotZ*>scaleX`ff?scaleY?scaleZ`ff?Nickname [b][i]Re-roll hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID0fe174Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname [b][i]Re-roll hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655378696/86EF52A80BED6B2DACC5529B87A7885DFFC2245F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd85Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655378696/86EF52A80BED6B2DACC5529B87A7885DFFC2245F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI13 GUIDee4cb1NameCustom_Model_Infinite_BagTransformposX|>posYhf?posZ HrotX`ӺrotY f@rotZ>scaleX`ff?scaleY?scaleZ`ff?Nickname([b][i]Subtract 1 from hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID2c9222Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname([b][i]Subtract 1 from hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655433467/BD0AF1590F592484681A62491E13122FA8F96752/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd83Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655433467/BD0AF1590F592484681A62491E13122FA8F96752/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI14 GUIDf5a0f3NameCustom_Model_Infinite_BagTransformposX@Y>@posYhf?posZ HrotX 5rotY f@rotZ49>scaleX`ff?scaleY?scaleZ`ff?Nickname![b][i]Add 1 to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID4d3cf4Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname![b][i]Add 1 to hit rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655431989/DED03E54891BA850BD955AF5E25FABDC60E9DC19/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd82Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655431989/DED03E54891BA850BD955AF5E25FABDC60E9DC19/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI15 GUID70af5aNameCustom_Model_Infinite_BagTransformposX z:posY`hf?posZGrotX꼾rotY`f@rotZ+4>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]+1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0{GUID60b24fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]+1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650254931/B907B13B97EC40B148F177B515CF50695E34C2B7/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd68Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650254931/B907B13B97EC40B148F177B515CF50695E34C2B7/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI16 GUIDe4b032NameCustom_Model_Infinite_BagTransformposX z:posYhf?posZHrotXL4rotY`f@rotZ>scaleX`ff?scaleY?scaleZ`ff?Nickname][i]+1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID5a61ceName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname][i]+1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650255866/4F520BBA410C99C39240EDF0A5D344D62931E2CD/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd69Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650255866/4F520BBA410C99C39240EDF0A5D344D62931E2CD/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI17 GUID39a367NameCustom_Model_Infinite_BagTransformposX z:posY@hf?posZFrotX@rotY`f@rotZ>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]+1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID2dc321Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]+1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650252125/7C750F237A45E45D30FD149DC75BC8FE9F977F7A/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd63Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650252125/7C750F237A45E45D30FD149DC75BC8FE9F977F7A/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI18 GUIDaa0f7eNameCustom_Model_Infinite_BagTransformposXo8posY@hf?posZ% FrotXrotYjf@rotZX>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]-1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0yGUID2dc221Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]-1 attack[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650241487/80FEC7E6D929EBDEF35CB8CE9BB81C45534C2F47/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd63Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650241487/80FEC7E6D929EBDEF35CB8CE9BB81C45534C2F47/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI19 GUID2924aaNameCustom_Model_Infinite_BagTransformposX z8posY`hf?posZGrotX`ٷrotY`f@rotZ m>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]-1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0{GUID60b24fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]-1 strength[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650245242/9588CD37FED74D5D22D0D4ED90C68F96E004935B/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd68Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650245242/9588CD37FED74D5D22D0D4ED90C68F96E004935B/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI20 GUID4930e4NameCustom_Model_Infinite_BagTransformposX z8posYhf?posZHrotXtrotY`f@rotZ>scaleX`ff?scaleY?scaleZ`ff?Nickname][i]-1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0|GUID5a61ceName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname][i]-1 toughness[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650246793/34328049F5F1D627B59B86FE84EF3EAAEF3BE6A8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd69Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2035105196650246793/34328049F5F1D627B59B86FE84EF3EAAEF3BE6A8/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI21 GUIDccd8a9NameCustom_Model_Infinite_BagTransformposX6z6posYhf?posZHrotXProtY f@rotZ@ >scaleX`ff?scaleY?scaleZ`ff?Nickname#[b][i]Add 1 to wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID31d14dName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname#[b][i]Add 1 to wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655435465/48A858D836363DCBB5CF148A23A2B08BE0070865/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd90Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655435465/48A858D836363DCBB5CF148A23A2B08BE0070865/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI22 GUIDc233efNameCustom_Model_Infinite_BagTransformposX9u6posY`hf?posZGrotXrotY f@rotZ]>scaleX`ff?scaleY?scaleZ`ff?Nickname.[b][i]Improve armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID13c4acName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname.[b][i]Improve armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615853848/B1E9455E6D6130E82D82E560B02C26789E4AF43F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd99Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615853848/B1E9455E6D6130E82D82E560B02C26789E4AF43F/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI23( GUIDc60778NameCustom_Model_Infinite_BagTransformposX z6posY@hf?posZFrotX`BrotY`f@rotZZ>scaleX`ff?scaleY?scaleZ`ff?Nickname[b][i]Movement buffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID911363Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname[b][i]Movement buffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615817682/977975CB724F3D8BC92E92EF1C264863678093C0/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd61Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615817682/977975CB724F3D8BC92E92EF1C264863678093C0/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI24 GUID34e075NameCustom_Model_Infinite_BagTransformposX6z4posY`hf?posZHrotX)rotY f@rotZ ~>scaleX`ff?scaleY?scaleZ`ff?Nickname*[b][i]Subtract 1 from wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID4ae26fName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname*[b][i]Subtract 1 from wound rolls[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655437739/F04EE88F53A2E094E034889D5C2D7169DCC617E2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd91Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2050859734655437739/F04EE88F53A2E094E034889D5C2D7169DCC617E2/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI25 GUID5a4941NameCustom_Model_Infinite_BagTransformposX|p4posY@hf?posZ`GrotX :%rotY`f@rotZ@Kٜ>scaleX`ff?scaleY?scaleZ`ff?Nickname-[b][i]Worsen armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUID39a561Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname-[b][i]Worsen armour penetration by 1[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615856842/FEEEFA356BD2F29D25F159CF345C376F7D5C6680/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd9aName Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615856842/FEEEFA356BD2F29D25F159CF345C376F7D5C6680/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI26, GUIDa9d027NameCustom_Model_Infinite_BagTransformposXz4posY hf?posZFrotX@ rotY`f@rotZ@Ο>scaleX`ff?scaleY?scaleZ`ff?Nickname [b][i]Movement debuffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071991282/5C391C2599AE5F6972D6EFA7BFC04B41D6E821E3/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDcb3cc2Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX?scaleY?scaleZ?Nickname [b][i]Movement debuffed[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615819624/3392AFA0F54AFCE70087E101B52009594E3AFD5E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID9cdd62Name Custom_TokenTransformposXposYposZrotXrotYrotZscaleX ?scaleY@scaleZ &?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/2019350432615819624/3392AFA0F54AFCE70087E101B52009594E3AFD5E/ImageSecondaryURLImageScalar?WidthScaleLuaScriptLuaScriptStateXmlUI25iGUIDf7d85aName HandTriggerTransformposXEposY?posZpE@rotXrotYp@rotZscaleX>@scaleY"@scaleZQI@NicknameDescriptionReds Hand AreaGMNotesAltLookAngle&xyzColorDiffuse1r Zd?g ?ba?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsFogColorRedLuaScriptLuaScriptStateXmlUI26jGUID731345Name HandTriggerTransformposXEposY?posZOrotXrotYp@rotZscaleX>@scaleY"@scaleZQI@NicknameDescriptionBlue Hand AreaGMNotesAltLookAngle&xyzColorDiffuse1r ?5?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsFogColorBlueLuaScriptLuaScriptStateXmlUI27\GUID309ceaName HandTriggerTransformposXEposY?posZVrotXrotYrotZscaleX= ?scaleY?scaleZ@?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?5?b l?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsFogColorTealLuaScriptLuaScriptStateXmlUI28^GUIDf9d618Name HandTriggerTransformposX@>posY?posZVrotXrotYrotZscaleX ? ?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r@b?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsFogColorPurpleLuaScriptLuaScriptStateXmlUI29^GUIDa47132Name HandTriggerTransformposX@>posY?posZV@rotXrotYf@rotZscaleXB ?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r ?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsFogColorOrangeLuaScriptLuaScriptStateXmlUI30^GUIDf7cd38Name HandTriggerTransformposXDposY?posZV@rotXrotYf@rotZscaleX= ?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?gj?b@?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsFogColorYellowLuaScriptLuaScriptStateXmlUI31GUID839fccName Custom_TileTransformposXposY@ ?posZ=rotXrotYf@rotZscaleX@scaleY?scaleZ@NicknameBlue Custom Dice TableDescription6{ "Cumulative": "no", "Update_Frequency": "0.2" }GMNotesAltLookAngle&xyzColorDiffuse&rgbLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15639768040285504200/A9DBDD932E0FC47D3E40041093AE95710F43BF3D/ImageSecondaryURLImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScript-- FTC-GUID: acae21,839fcc function none() end Yoffset=0.1 toolButtonBorderThickness = 70 -- TTS world buttons have no outline property, so use a larger backing button. function createToolButtonWithBorder(params, borderColor) local border = {} for k, v in pairs(params) do border[k] = v end border.label = "" border.font_size = 1 border.height = (params.height or 0) + toolButtonBorderThickness border.width = (params.width or 0) + toolButtonBorderThickness local resolvedBorderColor = borderColor or {0.1, 0.1, 0.1, 1} border.color = resolvedBorderColor border.hover_color = resolvedBorderColor border.press_color = resolvedBorderColor self.createButton(border) local top = {} for k, v in pairs(params) do top[k] = v end top.position = {params.position[1], params.position[2] + 0.01, params.position[3]} self.createButton(top) end local currentMat if self.getPosition().z < 0 then currentMat="Blue" else currentMat="Red" end local MM_TO_INCH = 0.0393701 local MEASURING_RING_Y_OFFSET = 0.17 local VALID_BASE_SIZES_IN_MM = { {x = 25, z = 25}, {x = 28, z = 28}, {x = 30, z = 30}, {x = 32, z = 32}, {x = 40, z = 40}, {x = 50, z = 50}, {x = 55, z = 55}, {x = 60, z = 60}, {x = 65, z = 65}, {x = 80, z = 80}, {x = 90, z = 90}, {x = 100, z = 100}, {x = 130, z = 130}, {x = 160, z = 160}, {x = 25, z = 75}, {x = 75, z = 25}, {x = 35.5, z = 60}, {x = 60, z = 35.5}, {x = 40, z = 95}, {x = 95, z = 40}, {x = 52, z = 90}, {x = 90, z = 52}, {x = 70, z = 105}, {x = 105, z = 70}, {x = 92, z = 120}, {x = 120, z = 92}, {x = 95, z = 150}, {x = 150, z = 95}, {x = 109, z = 170}, {x = 170, z = 109} } clearAllDiceBtn={ label="Clear mat\n(right click)", click_function="clearDiceAll", function_owner=self, position={-4.6, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=400, font_size=60, color={0,0,0}, font_color={1,1,1} } rollAllBtn={ label="Roll all dice", tooltip="Roll all dice on the mat.", click_function="RollAllDice", function_owner=self, position={-3.5, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=520, font_size=60, color={1,1,1}, font_color={0,0,0} } orderedRollLabel = "Ordered roll" orderedRollTooltip = "Roll all dice, and display them in the order they're rolled in rather than by results." orderedRollBtn={ label=orderedRollLabel, tooltip=orderedRollTooltip, click_function="RollDiceOrdered", function_owner=self, position={-2.4, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=520, font_size=60, color={1,1,1}, font_color={0,0,0} } orderedRollGroup2Label = "In 2s" orderedRollGroup2Tooltip = "Roll all dice, and display them in the order they're rolled in rather than by results, grouped into 2s." orderedRollGroup2Btn={ label=orderedRollGroup2Label, tooltip=orderedRollGroup2Tooltip, click_function="RollDiceOrderedGroup2", function_owner=self, position={-1.6, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={1,1,1}, font_color={0,0,0} } orderedRollGroup3Label = "In 3s" orderedRollGroup3Tooltip = "Roll all dice, and display them in the order they're rolled in rather than by results, grouped into 3s." orderedRollGroup3Btn={ label=orderedRollGroup3Label, tooltip=orderedRollGroup3Tooltip, click_function="RollDiceOrderedGroup3", function_owner=self, position={-1.1, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={1,1,1}, font_color={0,0,0} } add1d6Btn={ label="+ 1d6", click_function="spawnDice1", function_owner=self, tooltip="Left click: Spawns 1d6\nRight click: Quick-roll 1d6 (instant)", position={-0.5, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add2d6Btn={ label="+ 2d6", click_function="spawnDice2", function_owner=self, tooltip="Left click: Spawns 2d6\nRight click: Quick-roll 2d6 (instant)", position={-0.03, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add3d6Btn={ label="+ 3d6", click_function="spawnDice3", function_owner=self, tooltip="Left click: Spawns 3d6\nRight click: Quick-roll 3d6 (instant)", position={0.44, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add4d6Btn={ label="+ 4d6", click_function="spawnDice4", function_owner=self, tooltip="Left click: Spawns 4d6\nRight click: Quick-roll 4d6 (instant)", position={0.91, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add5d6Btn={ label="+ 5d6", click_function="spawnDice5", function_owner=self, tooltip="Left click: Spawns 5d6\nRight click: Quick-roll 5d6 (instant)", position={1.38, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add10d6Btn={ label="+ 10d6", click_function="spawnDice10", function_owner=self, tooltip="Spawns 10d6 dice", position={1.85, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add25d6Btn={ label="+ 25d6", click_function="spawnDice25", function_owner=self, tooltip="Spawns 25d6 dice", position={2.32, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add1d3Btn={ label="+ 1d3", click_function="spawnDice1d3", function_owner=self, tooltip="Left click: Spawns 1d3\nRight click: Quick-roll 1d3 (instant)", position={2.79, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={1,1,1}, font_color={0,0,0} } colorBtnDimension=150 colorBtnOffset=3.2 color1Btn={ label="", click_function="modifyMenu1", function_owner=self, position={colorBtnOffset, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Red", font_color={0,0,0} } color2Btn={ label="", click_function="modifyMenu2", function_owner=self, position={colorBtnOffset+0.35, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Blue", font_color={0,0,0} } color3Btn={ label="", click_function="modifyMenu3", function_owner=self, position={colorBtnOffset+0.35*2, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Black", font_color={0,0,0} } color4Btn={ label="", click_function="modifyMenu4", function_owner=self, position={colorBtnOffset+0.35*3, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Green", font_color={0,0,0} } -- Pip-colour toggle, sitting between the Green swatch and Dice+. Click flips every -- spawned die between white pips (Die_6_Rounded) and black pips (Die_6) through -- setDiceType(); revertRoll() also reads pipColorBtn.color. Its .index is assigned -- at runtime in onload (it's created last) so swapPipColor's editButton targets it. pipColorBtn= { label="White", click_function="swapPipColor", function_owner=self, position={colorBtnOffset + 0.35 * 4 + 0.1, Yoffset, -0.87}, rotation={0, 0, 0}, height=50, width=250, font_size=60, color="White", font_color="Black" } -- "Dice+" sits to the right of the pip toggle. It expands a second row of extra -- dice colours below the Red/Blue/Black/Green swatches, and collapses (destroys) -- them when clicked again. dicePlusBtn={ label="Dice+", tooltip="Show/hide extra dice colours", click_function="toggleDicePlus", function_owner=self, position={colorBtnOffset + 0.35 * 4 + 0.6, Yoffset, -0.87}, rotation={0, 0, 0}, height=50, width=250, font_size=60, color={1, 1, 1}, font_color={0, 0, 0} } -- Extra colour squares revealed by Dice+. Same X as the four base swatches, Z -- dropped by 1.5 so they form a second row. Created/destroyed on toggle. dicePlusExpanded = false -- Button positions are in the mat's LOCAL space and the mat is scaled 3.75x, so a -- local Z step of 0.4 lands ~1.5 world units apart (0.4 * 3.75). Row 1 sits below -- the base swatches; row 2 another equal step further out. -- Colours are tuned to a mid luminance band (~WCAG 0.12-0.30) so each die stays -- legible with EITHER white or black pips: dark hues are lifted, light hues are -- muddied (e.g. yellow -> mustard) since a vivid light colour can't hold white pips. dicePlusColors = { -- Row 1 (Z = -1.27) { fn = "dicePlusPurple", color = {0.5569, 0.2667, 0.6784}, x = colorBtnOffset, z = -0.87 - 0.4, tip = "Purple #8E44AD" }, { fn = "dicePlusYellow", color = {0.6510, 0.5255, 0.0392}, x = colorBtnOffset + 0.35, z = -0.87 - 0.4, tip = "Mustard #A6860A" }, { fn = "dicePlusTeal", color = {0.9922, 0.8157, 0.0706}, x = colorBtnOffset + 0.35 * 2, z = -0.87 - 0.4, tip = "Yellow #FDD012" }, { fn = "dicePlusOrange", color = {0.8000, 0.4196, 0.1176}, x = colorBtnOffset + 0.35 * 3, z = -0.87 - 0.4, tip = "Orange #CC6B1E" }, { fn = "dicePlusSlate", color = {0.4392, 0.5020, 0.5647}, x = colorBtnOffset + 0.35 * 4, z = -0.87 - 0.4, tip = "Slate Gray #708090" }, { fn = "dicePlusSteel", color = {0.2745, 0.5098, 0.7059}, x = colorBtnOffset + 0.35 * 5, z = -0.87 - 0.4, tip = "Steel Blue #4682B4" }, -- Row 2 (Z = -1.67) { fn = "dicePlusBordeaux", color = {0.9922, 0.9137, 0.7490}, x = colorBtnOffset, z = -0.87 - 0.8, tip = "Beige #FDE9BF" }, { fn = "dicePlusAzure", color = {0.0784, 0.5804, 0.5373}, x = colorBtnOffset + 0.35, z = -0.87 - 0.8, tip = "Turquoise #149489" }, { fn = "dicePlusLtBrown", color = {0.6275, 0.4157, 0.2353}, x = colorBtnOffset + 0.35 * 2, z = -0.87 - 0.8, tip = "Light Brown #A06A3C" }, { fn = "dicePlusDkGold", color = {0.7961, 0.2902, 0.5451}, x = colorBtnOffset + 0.35 * 3, z = -0.87 - 0.8, tip = "Pink #CB4A8B" }, { fn = "dicePlusOlive", color = {0.4196, 0.5569, 0.1373}, x = colorBtnOffset + 0.35 * 4, z = -0.87 - 0.8, tip = "Olive #6B8E23" }, { fn = "dicePlusIndigo", color = {0.3529, 0.3098, 0.8118}, x = colorBtnOffset + 0.35 * 5, z = -0.87 - 0.8, tip = "Indigo #5A4FCF" }, } -- Generate one click handler per swatch from the table above, so each colour is -- defined in exactly one place (the handler and the button share dicePlusColors). for _, dpc in ipairs(dicePlusColors) do _G[dpc.fn] = function() modifyMenu(dpc.color) end end customDiceBtn={ label="Custom Dice", click_function="customDice", function_owner=self, position={colorBtnOffset+0.35*9, Yoffset ,-0.85}, rotation={0,0,0}, height=50, width=420, font_size=60, color={1,1,1}, font_color={0,0,0} } clearDiceBtnIncrement=0.28 clearDiceBtnLabel="Del\n" clearDiceBtntooltip="LMB Delete this row\nRMB Delete this row and lowers/uppers" clearDiceBtn={ label=clearDiceBtnLabel, tooltip=clearDiceBtntooltip, click_function="filledInLater", function_owner=self, position={-5.8, Yoffset ,-0.85}, rotation={0,0,0}, height=1000, width=1300, scale={0.1,0.1,0.1}, font_size=350, color="Black", font_color="Red" } rollOrSelectDiceBtn={ label="filled in later", tooltip="filled in later", click_function="filledInLater", function_owner=self, position={-5.45, Yoffset ,-0.85}, rotation={0,0,0}, height=1000, width=1300, scale={0.1,0.1,0.1}, font_size=350, color="White", font_color={0,0,0} } lowerUpper=true oneTimeUpper=true lowerSign=" (-)" upperSign=" (+)" lowerUpperBtn={ label="RMB\n- / +", tooltip="Changes to upper for the next click", click_function="toggleLowerUpper", function_owner=self, position={-5.62, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=2800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } warningLbl="NO FAST REPETITIONS" warningLbl="" warningBtn={ label=warningLbl, click_function="modifyMenu4", function_owner=self, position={-1.75, Yoffset ,-0.67}, rotation={0,0,0}, height=0, width=0, font_size=50, color="White", font_color="Red" } lowerUpper=true oneTimeUpper=true lethalButtonsOffset = -4.2 lethalLabel = "Lethal Hits" lethalTip = "Left-click: move dice >= 6. Right-click: move dice >= 5." lethalHitsBtn={ label=lethalLabel, tooltip=lethalTip, click_function="lethalHitsAction", function_owner=self, position={lethalButtonsOffset, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=2800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } susLabel = "Sustained Hits" susTip = "Left-click: move dice >= 6. Right-click: move dice >= 5." sustainedHitsBtn={ label=susLabel, tooltip=susTip, click_function="sustainedHitsAction", function_owner=self, position={lethalButtonsOffset + 0.705, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=3800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } susLethalLabel = "Sustained & Lethal" susLethalTip = "Left-click: move dice >= 6. Right-click: move dice >= 5. Applies to sustained and lethal combined." sustainedLethalHitsBtn = { label=susLethalLabel, tooltip=susLethalTip, click_function="sustainedLethalHitsAction", function_owner=self, position={lethalButtonsOffset + 1.61, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=4800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } copyOthersDiceBtn = { tooltip="Copy opposite player's dice", click_function = "importOpposDice", function_owner = self, position = {-7.5, Yoffset, 0.86}, rotation = { 0, 0, 0 }, width = 1300, height = 300, font_size = 150, color = (currentMat == "Red" and "Blue" or "Red"), -- Button changes color based on current player font_color = {1, 1, 1}, -- White text for high contrast hover_color = (currentMat == "Red") and {0, 0, 1, 1} or {1, 0, 0, 1}, label = (currentMat == "Red") and "Copy Blue's Dice" or "Copy Red's Dice", press_color = {0.27, 0.27, 0.27, 1}, scale = {0.5, 0.5, 0.5} } savePositionLabel = "Save" saveBtn={ label=savePositionLabel, tooltip=savePositionTip, click_function="save", function_owner=self, position={-7.5, Yoffset ,-0.84}, rotation={0,0,0}, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } restorePositionLabel = "Restore Position" restoreBtn={ label=restorePositionLabel, click_function="restore", function_owner=self, position={-7.5, Yoffset ,-0.50}, rotation={0,0,0}, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } gap2Btn={ label='1"/2" Gap', tooltip='LMB: 1" Gap\nRMB: 2" Gap', click_function='arrangeModelsWithGap', function_owner=self, position={-6.24, Yoffset ,-1.65}, rotation={0,0,0}, height=1000, width=3200, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } clearMeasureAllBtn={ label='Clear Bubbles', tooltip='LEFT CLICK: Clear all measuring bubbles from the table\nRIGHT CLICK: Clear all measuring bubbles from highlighted objects only', click_function='clearMeasuringCircles', function_owner=self, position={-7.93, Yoffset ,-1.65}, rotation={0,0,0}, height=1000, width=4200, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } clearLinesBtn={ label='Clear Lines', tooltip='Clear all hand-drawn lines, keep measuring bubbles', click_function='clearHandDrawnLines', function_owner=self, position={-7.03, Yoffset ,-1.65}, rotation={0,0,0}, height=1000, width=4200, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } checkCoherencyBtn={ label='Coherency', tooltip='LEFT CLICK: Show coherency of selected models\n RIGHT CLICK: Toggle off lines\nDrawing new lines will be weird while this is on', click_function='checkCoherency', function_owner=self, position={-7.20, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=3300, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } engagementRangeBtn={ label='Engagement', tooltip='LEFT CLICK: Highlight every model within 2" of the selected model(s) for 15s\nRIGHT CLICK: Clear the highlights now', click_function='showEngagementRange', function_owner=self, position={-7.97, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=3800, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } highlightBtn={ label='Highlight', tooltip='LEFT CLICK: Highlight the selected model(s) in the swatch color (permanent)\nRIGHT CLICK: Remove ALL highlights from every object on the table', click_function='applyHighlightPaint', function_owner=self, position={-6.50, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=3100, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } -- Colour swatch for the Highlight button. Its face IS the currently chosen -- colour; clicking cycles the palette (recoloured live via refreshHighlightSwatch). highlightSwatchBtn={ label='', tooltip='Highlight color\nLEFT CLICK: next color\nRIGHT CLICK: previous color', click_function='cycleHighlightColor', function_owner=self, position={-6.02, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=1000, scale={0.1,0.1,0.1}, font_size=700, color={1,1,0}, font_color={0,0,0} } measure1Btn={ label='1"', tooltip='Toggle 1" bubble', click_function='toggle1Bubble', function_owner=self, position={-8.23, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure2Btn={ label='2"', tooltip='Toggle 2" bubble', click_function='toggle2Bubble', function_owner=self, position={-7.93, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure3Btn={ label='3"', tooltip='Toggle 3" bubble', click_function='toggle3Bubble', function_owner=self, position={-7.63, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure6Btn={ label='6"', tooltip='Toggle 6" bubble', click_function='toggle6Bubble', function_owner=self, position={-7.33, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure8Btn={ label='8"', tooltip='Toggle 8" bubble', click_function='toggle8Bubble', function_owner=self, position={-7.03, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure9Btn={ label='9"', tooltip='Toggle 9" bubble', click_function='toggle9Bubble', function_owner=self, position={-6.73, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure12Btn={ label='12"', tooltip='Toggle 12" bubble', click_function='toggle12Bubble', function_owner=self, position={-6.41, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1600, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure15Btn={ label='15"', tooltip='Toggle 15" bubble', click_function='toggle15Bubble', function_owner=self, position={-6.07, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1600, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } -- Custom Bubble Input BG customBubbleInputBG = { label = "", click_function = "none", function_owner = self, position = {-5.78, Yoffset, -1.40}, rotation = {0, 0, 0}, width = 1100, height = 1000, font_size = 100, color = {0.95, 0.95, 0.95, 1}, scale = {0.1, 0.1, 0.1} } -- Custom Bubble Input Field customBubbleInput = { label = "Size", input_function = "setCustomBubbleSize", function_owner = self, position = {-5.78, Yoffset, -1.40}, rotation = {0, 0, 0}, width = 900, height = 800, font_size = 500, alignment = 3, scale = {0.1, 0.1, 0.1}, color = {1, 1, 1, 1}, font_color = {0.2, 0.2, 0.2} } -- Custom Bubble Button -- LEFT CLICK applies the size typed in the on-mat input box. -- RIGHT CLICK (alt) opens the on-screen size overlay; 0 or a blank value there -- clears the bubbles on the selected models. customBubbleBtn = { label = "Go", tooltip = "LEFT CLICK: apply size from the box\nRIGHT CLICK: on-screen size entry (0/blank clears selected)", click_function = "applyCustomBubble", function_owner = self, position = {-5.58, Yoffset, -1.40}, rotation = {0, 0, 0}, width = 800, height = 1000, font_size = 500, color = {0.13, 0.13, 0.13, 1}, font_color = {1, 1, 1}, hover_color = {0.2, 0.2, 0.2, 1}, scale = {0.1, 0.1, 0.1} } function save(obj, player_color, alt) local selected = Player[player_color].getSelectedObjects() if not selected or #selected == 0 then printToColor("Please first select a model to save its position", player_color) return end -- Reset and capture all selected objects in one go savedPositions = {} for _, o in ipairs(selected) do table.insert(savedPositions, { guid = o.getGUID(), position = o.getPosition(), rotation = o.getRotation() }) end -- Announce on screen (centre) and in chat for everyone, tinted by the saving -- player's seat colour (Blue for blue, Red for red, Yellow, Grey, ...). broadcastToAll(#savedPositions .. " objects saved", player_color) end function restore(playerColor) if not savedPositions or #savedPositions == 0 then broadcastToColor("No saved positions to restore.", playerColor, {1, 0.5, 0}) return end for i, v in ipairs(savedPositions) do local obj = getObjectFromGUID(v.guid) if obj ~= nil then obj.setRotation(v.rotation) obj.setPositionSmooth(v.position, false, true) end end end local auto2D6 = { label = "Show/Hide UI", tooltip="Show or hide your VP overlay", click_function = "toggleHudOverlay", function_owner = self, position = {-7.5, Yoffset, 0.52}, rotation = { 0, 0, 0 }, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } local revertRollBtn = { label = "Revert Roll", tooltip="It takes 1 second after rolling to 'lock in'", click_function = "revertRoll", function_owner = self, position = {-7.5, Yoffset, 0.18}, rotation = { 0, 0, 0 }, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } -- Modern Black "Last 5 Rolls" Button local customDiceSpawnButton = { label = "Last 5 Rolls", click_function = "callLast5Rolls", function_owner = self, position = {-7.5, Yoffset, -0.16}, rotation = {0, 0, 0}, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, font_color = {1, 1, 1}, hover_color = {0.2, 0.2, 0.2, 1}, press_color = {0.27, 0.27, 0.27, 1}, scale = {0.5, 0.5, 0.5} } lethalHitsThreshold = 6 sustainedHitsThreshold = 6 sustainedLethalHitsThreshold = 6 selectedColor = "White" diceType = "Die_6_Rounded" customDiceImage = nil scaleBtn = 0.1 previousRollData = {} previousRollStack = {} previousRollIndex = 0 maxPreviousRolls = 10 -- Resolved by initRoller once Global is ready (this script can load before -- Global's onLoad has set the *_GUID variables). rollerGUID = nil roller = nil -- Defer a callback until Global's onLoad has run (it sets FTC_Global_Ready -- last). After 10s run it anyway rather than hang a broken load forever. function whenGlobalReady(callback) Wait.condition(callback, function() return Global.getVar("FTC_Global_Ready") == true end, 10, callback) end function initRoller() rollerGUID = Global.getVar("redCustomDiceRoller_GUID") if self.guid == Global.getVar("blueCustomDiceMat_GUID") then rollerGUID = Global.getVar("blueCustomDiceRoller_GUID") end -- rollerGUID is nil when a click races Global's onLoad (resolveRoller calls -- this lazily); getObjectFromGUID(nil) would error out of the click handler. roller = rollerGUID and getObjectFromGUID(rollerGUID) or nil end function resolveRoller() if not roller then initRoller() end return roller end lastClickTimestamp = 0 local function nowSeconds() if Time and Time.time then return Time.time end return os.clock() end -- Ignore Roll All presses for 0.2 seconds after an accepted press, then coalesce -- the accepted press for 0.1 seconds before scanning resting dice. rollAllPending = { scheduled = false, player = nil } local rollAllDelay = 0.1 local rollAllCooldown = 0.2 local lastRollAllTimestamp = -math.huge local function scheduleRollAll(player) local currentTime = nowSeconds() if currentTime - lastRollAllTimestamp < rollAllCooldown then return end lastRollAllTimestamp = currentTime rollAllPending.player = player if rollAllPending.scheduled then return end rollAllPending.scheduled = true Wait.time(function() local queuedPlayer = rollAllPending.player rollAllPending.scheduled = false rollAllPending.player = nil if queuedPlayer then Player[queuedPlayer].clearSelectedObjects() end local dice = getDice() if #dice > 0 then moveDiceToRoller(queuedPlayer or player, dice) end end, rollAllDelay) end function importOpposDice(obj, player, alt) -- clear existing dice for _, d in ipairs(getDice()) do d.destroy() end -- get opposite mat and its bounds local oppMatGUID = (currentMat=="Red") and Global.getVar("blueCustomDiceMat_GUID") or Global.getVar("redCustomDiceMat_GUID") local oppMat = getObjectFromGUID(oppMatGUID) if not oppMat then printToColor("Opposite dice mat not found.", player, "Orange") return end local mb = oppMat.getBounds() local mL, mR = mb.center.x - mb.size.x/2, mb.center.x + mb.size.x/2 local mLo, mUp = mb.center.z - mb.size.z/2, mb.center.z + mb.size.z/2 local mYl, mYh = mb.center.y - mb.size.y/2, mb.center.y + mb.size.y/2 + 3 -- get opposite lethal zone and its bounds local oppLethalGUID = (currentMat=="Red") and Global.getVar("blueLethalHits_GUID") or Global.getVar("redLethalHits_GUID") local oppLethal = getObjectFromGUID(oppLethalGUID) local lb, lL, lR, lLo, lUp, lYl, lYh if oppLethal then lb = oppLethal.getBounds() lL, lR = lb.center.x - lb.size.x/2, lb.center.x + lb.size.x/2 lLo, lUp = lb.center.z - lb.size.z/2, lb.center.z + lb.size.z/2 lYl, lYh = lb.center.y - lb.size.y/2, lb.center.y + lb.size.y/2 + 3 else printToColor("Warning: opposite lethal zone not found.", player) end local toImport = {} local seen = {} -- gather dice: check lethal zone first, then main mat for _, o in ipairs(getAllObjects()) do if o.tag=="Dice" and o.resting then local p = o.getPosition() -- lethal zone? if lb and p.x > lL and p.x < lR and p.z > lLo and p.z < lUp and p.y > lYl and p.y < lYh then seen[o.getGUID()] = true table.insert(toImport, { die=o, origin=lb.center, lethal=true }) -- main mat (excluding any already seen in lethal) elseif p.x > mL and p.x < mR and p.z > mLo and p.z < mUp and p.y > mYl and p.y < mYh and not seen[o.getGUID()] then seen[o.getGUID()] = true table.insert(toImport, { die=o, origin=mb.center, lethal=false }) end end end if #toImport == 0 then printToColor("No dice found on the opposite mat.", player, "Yellow") return end -- clone into this mat: shift lethal dice by (-3, -3) in X/Z local mc = self.getBounds().center local count = 0 for _, entry in ipairs(toImport) do local dp = entry.die.getPosition() local cb = entry.origin local relX, relY, relZ = -(dp.x - cb.x), dp.y - cb.y, -(dp.z - cb.z) local nx, ny, nz = mc.x + relX, mc.y + relY, mc.z + relZ if entry.lethal then local dx = (currentMat == "Red") and -6 or 6 local dz = (currentMat == "Red") and 4.2 or -4.2 nx = nx + dx nz = nz + dz end entry.die.clone({ position={nx, ny, nz}, snap_to_grid=false }) count = count + 1 end printToAll("Imported "..count.." dice from the opposite mat, including lethal hits.", {1,1,1}) end function storeCurrentDiceAsPreviousRoll() -- Build a snapshot of the current dice on the mat (positions, values, tints) local snapshot = {} for _, die in ipairs(getDice()) do local c = die.getCustomObject() local d = { pos = die.getPosition(), rot = die.getRotation(), val = die.getValue(), tint = die.getColorTint(), customImage = nil } if c then d.customImage = c.image end table.insert(snapshot, d) end -- If we've undone some rolls and then store a new roll, drop any future redo history if previousRollIndex < #previousRollStack then for i = #previousRollStack, previousRollIndex + 1, -1 do table.remove(previousRollStack, i) end end table.insert(previousRollStack, snapshot) -- Trim stack to maximum size while #previousRollStack > maxPreviousRolls do table.remove(previousRollStack, 1) end previousRollIndex = #previousRollStack previousRollData = previousRollStack[previousRollIndex] or {} end function revertRoll(obj, player, alt) -- Undo one step in the previousRollStack. Use previousRollIndex to track position. if previousRollIndex == 0 or not previousRollStack or #previousRollStack == 0 then printToColor("No previous roll found.", player, "Red") return end local snapshot = previousRollStack[previousRollIndex] if not snapshot or #snapshot == 0 then printToColor("No previous roll data available.", player, "Red") return end -- Destroy current dice and spawn snapshot dice for _, die in ipairs(getDice()) do die.destroy() end for _, data in ipairs(snapshot) do local diceTypeToSpawn = "Die_6_Rounded" if data.customImage then diceTypeToSpawn = "Custom_Dice" elseif pipColorBtn.color == "Black" then diceTypeToSpawn = "Die_6" end local spawned = spawnObject({ type = diceTypeToSpawn, position = data.pos, rotation = data.rot }) if data.customImage then spawned.setCustomObject({ image = data.customImage, type = 1 }) end spawned.setColorTint(data.tint) spawned.setValue(data.val) end printToColor("Reverted to roll #" .. tostring(previousRollIndex) .. " of " .. tostring(#previousRollStack), player, "Green") -- Move pointer back so another click reverts another step previousRollIndex = math.max(0, previousRollIndex - 1) previousRollData = previousRollStack[previousRollIndex] or {} end function moveDiceToRoller(color, dice) if #dice == 0 then return false end local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", color, "Yellow") return false end activeRoller.call("setPlayerColor", { color = color }) local rollId = activeRoller.call("beginRollBatch", { color = color, count = #dice, }) if not rollId then printToAll("Dice roller busy: roll was not started. No dice were moved.", {1, 0.5, 0}) return false end -- Re-rolled dice are no longer lethal hits; drop them from lethal tracking so -- a later "clear mat" can't reach back and destroy them wherever they end up. forgetLethalHitsForDice(dice) for _, die in ipairs(dice) do activeRoller.call("setLastHolder", { obj = die, color = color, rollId = rollId }) activeRoller.putObject(die) end storeCurrentDiceAsPreviousRoll() return true end -- Evaluate a simple arithmetic expression safely (digits, + - * / and parentheses) -- Returns number on success, or nil + error message on failure -- Reset the last spawn position and dice count when the dice area is cleared function callLast5Rolls(obj, player, alt) local r = resolveRoller() if r then r.call("printLast5Innovative") end end -- Bounds of the current mat's lethal-hits zone object, or nil if not found. -- Single source of truth: getDice() EXCLUDES dice in this box and -- cleanupLethalHits() DESTROYS dice in this box, so they must agree on what -- "in the zone" means -- otherwise a die can fall between the two (excluded -- from clearing, not recognised for cleanup) and become unclearable. local function getLethalZoneBounds() local guid = (currentMat == "Red") and Global.getVar("redLethalHits_GUID") or Global.getVar("blueLethalHits_GUID") local zone = getObjectFromGUID(guid) if not zone then return nil end local lb = zone.getBounds() return { minx = lb.center.x - lb.size.x/2, maxx = lb.center.x + lb.size.x/2, minz = lb.center.z - lb.size.z/2, maxz = lb.center.z + lb.size.z/2, miny = lb.center.y - lb.size.y/2, maxy = lb.center.y + lb.size.y/2, } end local function isInBounds(p, b) return b ~= nil and p.x > b.minx and p.x < b.maxx and p.z > b.minz and p.z < b.maxz and p.y > b.miny and p.y < b.maxy end -- Dice on this mat: settled (resting), not held, and outside the lethal zone. function getDice() local dice = {} local b = self.getBounds() local left = b.center.x - b.size.x/2 local right = b.center.x + b.size.x/2 local back = b.center.z - b.size.z/2 local front = b.center.z + b.size.z/2 local ybot = b.center.y - b.size.y/2 local ytop = b.center.y + b.size.y/2 + 3 if currentMat == "Red" then right = right - 5 back = back + 2.9 front = front - 0.5 else left = left + 5 back = back + 0.5 front = front - 2.9 end local lbound = getLethalZoneBounds() for _, o in ipairs(getAllObjects()) do if o.tag == "Dice" and o.resting and o.held_by_color == nil then local p = o.getPosition() if p.x > left and p.x < right and p.z > back and p.z < front and p.y > ybot and p.y < ytop and not isInBounds(p, lbound) then table.insert(dice, o) end end end return dice end function customDice(obj, player, alt) found = false newImage = nil newTint = nil dice = getDice() for _, die in ipairs(dice) do local image = die.getCustomObject()["image"] if image ~= nil then found = true newTint = die.getColorTint() newImage = image printToColor("You have set a custom die!", player, "Yellow") break else found = true newTint = die.getColorTint() printToColor("You have set a custom colour!", player, "Yellow") end end if found then customDiceImage = newImage selectedColor = newTint setDiceType() end end function toggleLowerUpper() setLowerUpper(not lowerUpper) end function setLowerUpper( value ) lowerUpper= value modifyBtns() end function modifyBtns() local sign="" for i=1, 6 do if lowerUpper then sign = lowerSign else sign = upperSign end self.editButton({ index = (17+i*2), label = clearDiceBtnLabel .. i .. sign }) self.editButton({ index = (18+i*2), label = "Roll\n" .. i .. sign }) end end -- Reached via obj.call from the "Roll All Dice" hotkey so the right player's -- mat handles the roll (each mat scans its own bounds in getDice()). function hotkeyRollAllDice(params) RollAllDice(nil, params.playerColor, false) end -- Reached via obj.call from the "Clear Mat" hotkey. alt=true bypasses the -- "Use right click!" guard in clearDiceAll so a single keypress clears. function hotkeyClearMat(params) clearDiceAll(nil, params.playerColor, true) end function RollAllDice(obj, player, alt) scheduleRollAll(player) end function RollDiceOrdered(_, player, _) Player[player].clearSelectedObjects() local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", player, "Yellow") return end activeRoller.setVar("orderedOnNextRoll", true) moveDiceToRoller(player, getDice()) end function RollDiceOrderedGroup2(obj, player, alt) local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", player, "Yellow") return end activeRoller.setVar("groupSizeOnNextRoll", 2) RollDiceOrdered(obj, player, alt) end function RollDiceOrderedGroup3(obj, player, alt) local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", player, "Yellow") return end activeRoller.setVar("groupSizeOnNextRoll", 3) RollDiceOrdered(obj, player, alt) end function RollOrSelectDeleteDice1(obj, color, alt) SelectDeleteDiceX("Sel", 1, alt, lowerUpper, color) end function RollOrSelectDeleteDice2(obj, color, alt) SelectDeleteDiceX("Sel", 2, alt, lowerUpper, color) end function RollOrSelectDeleteDice3(obj, color, alt) SelectDeleteDiceX("Sel", 3, alt, lowerUpper, color) end function RollOrSelectDeleteDice4(obj, color, alt) SelectDeleteDiceX("Sel", 4, alt, lowerUpper, color) end function RollOrSelectDeleteDice5(obj, color, alt) SelectDeleteDiceX("Sel", 5, alt, lowerUpper, color) end function RollOrSelectDeleteDice6(obj, color, alt) SelectDeleteDiceX("Sel", 6, alt, lowerUpper, color) end function SelectDeleteDiceX(action, n, selectOthers, lowUp, color) Player[color].clearSelectedObjects() selDel = true if action == "Del" then selDel = false end local counter = {0,0,0,0,0,0} local numberName = {"ones", "twos", "threes", "fours", "fives", "sixes"} local text = Player[color].steam_name .. " deleted " local somethingToPrint = false for i, die in ipairs(getDice()) do if die.getValue() == n or (selectOthers and lowUp and die.getValue()< n) or (selectOthers and not lowUp and die.getValue()> n) then if selDel then die.addToPlayerSelection(color) else die.destroy() counter[die.getValue()]=counter[die.getValue()]+1 end end end for i, val in ipairs(counter) do if val > 0 then text = text.." \u{00B7} " .. counter[i] .. " " .. numberName[i] somethingToPrint = true end end if somethingToPrint then printToAll(text , "Yellow") end if oneTimeUpper then setLowerUpper( true ) end if selDel then moveDiceToRoller(color, Player[color].getSelectedObjects()) end end function clearDiceAll(obj, color, alt) if not alt then printToColor("Use right click!", color, "Orange") return end cleanupLethalHits() clearDiceX(6, true, color) end function clearDice1(obj, color, alt) SelectDeleteDiceX("Del", 1, alt, lowerUpper, color) end function clearDice2(obj, color, alt) SelectDeleteDiceX("Del", 2, alt, lowerUpper, color) end function clearDice3(obj, color, alt) SelectDeleteDiceX("Del", 3, alt, lowerUpper, color) end function clearDice4(obj, color, alt) SelectDeleteDiceX("Del", 4, alt, lowerUpper, color) end function clearDice5(obj, color, alt) SelectDeleteDiceX("Del", 5, alt, lowerUpper, color) end function clearDice6(obj, color, alt) SelectDeleteDiceX("Del", 6, alt, lowerUpper, color) end function clearDiceX(n, deleteLower, color) local counter={0,0,0,0,0,0} local numberName={"ones", "twos", "threes", "fours", "fives", "sixes"} local text = Player[color].steam_name .. " deleted " local somethingToPrint=false for i, die in ipairs(getDice()) do if die.getValue() == n or (deleteLower and die.getValue()< n) then die.destroy() counter[die.getValue()]=counter[die.getValue()]+1 end end for i, val in ipairs(counter) do if val > 0 then text=text.." \u{00B7} "..counter[i].." "..numberName[i] somethingToPrint=true end end if somethingToPrint then printToAll(text , "Yellow") end end function spawnDice1(obj, player, alt) spawnDice(1,player,alt) end function getHudOverlayColorForThisMat() if self.getGUID() == Global.getVar("redCustomDiceMat_GUID") then return "Red" end if self.getGUID() == Global.getVar("blueCustomDiceMat_GUID") then return "Blue" end return nil end function toggleHudOverlay(obj, player, alt) local color = getHudOverlayColorForThisMat() if not color then printToColor("Could not determine overlay owner for this dice tray.", player, "Orange") return end Global.call("toggleHudOverlayForColor", {color = color}) end function spawnDice2(obj, player, alt) spawnDice(2,player,alt) end function spawnDice3(obj, player, alt) spawnDice(3,player,alt) end function spawnDice4(obj, player, alt) spawnDice(4,player,alt) end function spawnDice5(obj, player, alt) spawnDice(5,player,alt) end function spawnDice10(obj, player, alt) spawnDice(10,player,alt) end function spawnDice25(obj, player, alt) spawnDice(25,player,alt) end function spawnDice1d3(obj, player, alt) spawnD3(1, player, alt) end function spawnD3(n, player, alt) local playerDiceSpeed = player == "Red" and Global.getVar("redDiceSpawnTime") or Global.getVar("blueDiceSpawnTime") local currentTime = nowSeconds() local disableTime = (playerDiceSpeed == "slow") and 0.6 or 0.2 if currentTime - lastClickTimestamp < disableTime then printToColor("Wait a bit!", player, "Yellow") return end lastClickTimestamp = currentTime local offset = #getDice() if offset + n > 128 then printToColor("Dice limit reached!", player, "Orange") end local grid, row, col = 0, 0, 0 for i = 1, offset do col = col + 1 if col > 4 then col = 0; row = row + 1 if row > 4 then row = 0; grid = grid + 1 end end end if offset + n > 128 then n = 128 - offset end local d3Bag = getObjectFromGUID("2d9705") if not d3Bag then printToColor("D3 bag not found!", player, "Red") return end local spawnedDice = {} for i = 1, n do local pos = self.positionToWorld({4 - grid*1.5 - col*0.25, 0.5, -0.3 + row*0.25}) d3Bag.takeObject({ position = pos, rotation = self.getRotation() + Vector(0.0, 180.0, 0.0), smooth = false, callback_function = function(obj) if selectedColor ~= nil then obj.setColorTint(selectedColor) end table.insert(spawnedDice, obj) -- Right-click (alt) quick-rolls just like the +Nd6 buttons: set values -- from the roller's xoshiro stream (faces=3) without ever entering the -- bag. Dice arrive via async takeObject callbacks, so fire once the last -- one lands. Left-click stays spawn-only (press Roll All to roll). if alt and #spawnedDice == n then quickRollSpawnedDice(player, spawnedDice, 3, "d3") end end }) col = col + 1 if col > 4 then col = 0; row = row + 1 if row > 4 then row = 0; grid = grid + 1 end end end end function spawnDice(n,player,alt) local playerDiceSpeed = player == "Red" and Global.getVar("redDiceSpawnTime") or Global.getVar("blueDiceSpawnTime") local currentTime = nowSeconds() local disableTime = (playerDiceSpeed == "slow") and 0.6 or 0.2 if currentTime-lastClickTimestamp128 then printToColor("Dice limit reached!", player, "Orange") end local grid=0 local row=0 local col=0 for i=1, offset do col=col+1 if col>4 then col=0 row= row+1 if row > 4 then row=0 grid=grid+1 end end end if offset+n>128 then n=128-offset end local spawnedDice = {} for i=1, n do local die=spawnObject({ type = diceType, position = self.positionToWorld({4-grid*1.5-col*0.25,0.5,-0.3+row*0.25}), rotation = self.getRotation() + Vector(0.0, 180.0, 0.0), }) col=col+1 if col>4 then col=0 row= row+1 if row > 4 then row=0 grid=grid+1 end end if customDiceImage ~= nil then die.setCustomObject({ image = customDiceImage, type = 1 }) end if selectedColor ~= nil then die.setColorTint(selectedColor) end table.insert(spawnedDice, die) end -- Left-click spawns only (press Roll All to roll through the bag). Right-click -- (alt) on the SMALL spawners (1d6-5d6) "quick rolls": the dice are spawned -- already showing values from the roller's xoshiro RNG and never enter the bag -- (the legacy quick-roll trick) -- instant, and immune to the bag's -- loop/strand/nil-faces failure modes. Gated to n<=5; +10/+25/+1d3 stay -- spawn-only. if alt and n >= 1 and n <= 5 and #spawnedDice > 0 then quickRollSpawnedDice(player, spawnedDice) end end -- Fixed palette for quick-roll dice: one colour is picked per quick-roll and -- shared by every die in that roll, so consecutive quick-rolls are easy to tell -- apart. Cosmetic only, so plain math.random is fine here (dice VALUES still use -- the roller's xoshiro). -- Red and blue are intentionally excluded: those are the players' main dice -- colours, so quick-roll dice stay visually distinct from them. -- Darker/deeper hues so the white pips on the dice contrast clearly. local quickRollPalette = { {0.72, 0.38, 0.08}, -- burnt orange {0.68, 0.52, 0.08}, -- dark gold {0.16, 0.46, 0.22}, -- dark green {0.10, 0.42, 0.48}, -- dark teal {0.40, 0.20, 0.58}, -- dark purple {0.68, 0.18, 0.42}, -- dark magenta/pink } local lastQuickRollColorIndex = 0 -- Set already-spawned dice to values from the roller's xoshiro stream so they -- appear instantly rolled, without ever entering the bag. Falls back to -- math.random only if the roller object can't be reached. All dice in the roll -- share one random palette colour. function quickRollSpawnedDice(player, dice, faces, dieLabel) faces = faces or 6 dieLabel = dieLabel or "d6" local vals if roller then vals = roller.call("rollFacesForQuick", { n = #dice, faces = faces }) end -- Pick one palette colour for the whole roll, never repeating the previous -- roll's colour so batches stay visually distinct. local idx = math.random(#quickRollPalette) if #quickRollPalette > 1 and idx == lastQuickRollColorIndex then idx = (idx % #quickRollPalette) + 1 end lastQuickRollColorIndex = idx local tint = quickRollPalette[idx] local total = 0 for i, die in ipairs(dice) do local v = (vals and tonumber(vals[i])) or math.random(faces) die.setColorTint(tint) -- Custom dice are spawned with setCustomObject, which reloads the object -- asynchronously. Setting the value synchronously here races that reload -- and the value is lost when the die snaps back to its spawn orientation, -- so every die shows the same default face. Defer the value (and snap the -- rotation explicitly, like the bag roll's setValueCallback does) until the -- reload has settled. Plain dice don't reload, but the short defer is -- harmless for them. Wait.frames(function() die.setValue(v) local rotValues = die.getRotationValues() if rotValues and rotValues[v] then die.setRotation(rotValues[v].rotation + Vector(0, 180, 0)) end end, 2) total = total + v end printToAll(Player[player].steam_name .. " quick-rolled " .. tostring(total) .. " on " .. #dice .. dieLabel, "White") end -- Cross-object entry point for the stats board. Object.call() can only pass a -- single argument, so the stats board sends a {n, player, alt} table which we -- unpack into the positional spawnDice signature. function spawnStatsDice(params) if type(params) ~= "table" then return end spawnDice(params.n, params.player, params.alt) end -- Sustained hits = one extra attack per hit of `threshold`+. The dice on the mat -- ARE the roll, so we operate on the current mat dice directly. This replaced an -- earlier scheme that matched against a stored snapshot by value+tint+position, -- which was fragile to physics drift and a post-roll timing race and could either -- mass-clone everything or silently under-count. local function collectSustainedDice(threshold) local dice, positions = {}, {} for _, die in ipairs(getDice()) do if die.getValue() >= threshold then table.insert(dice, die) table.insert(positions, die.getPosition()) end end return dice, positions end local function cloneSustainedDice(dice, positions) local directionMultiplier = (currentMat == "Blue") and 1 or -1 for i, die in ipairs(dice) do local p = positions[i] p.x = p.x + (8.5 * directionMultiplier) die.clone({position = p, snap_to_grid = false}) end return #dice end function sustainedHitsAction(_, color, alt) -- Left-click: apply 6+. Right-click (alt): apply 5+. No toggle. local threshold = alt and 5 or 6 local dice, positions = collectSustainedDice(threshold) local count = cloneSustainedDice(dice, positions) printToAll("Sustained Hits duplicated: " .. tostring(count) .. " (threshold " .. tostring(threshold) .. "+)") end -- New function for combined action function sustainedLethalHitsAction(_, color, alt) -- Left-click: apply 6+. Right-click: apply 5+. No toggle. local threshold = alt and 5 or 6 -- Capture the dice and their mat positions BEFORE lethalHitsAction moves them -- off to the lethal zone, then clone at those saved positions so the sustained -- extra-attack dice stay on the mat while the originals go to the lethal zone. local dice, positions = collectSustainedDice(threshold) lethalHitsAction('sustainedAndLethal', color, alt) local count = cloneSustainedDice(dice, positions) printToAll("Sustained & Lethal duplicated: " .. tostring(count) .. " (threshold " .. tostring(threshold) .. "+)") end function getObjectsPositionToSave(player, hoveredObject) if #player.getSelectedObjects() != 0 then return player.getSelectedObjects() elseif hoveredObject != nil then return {hoveredObject} end return nil end function savePosition(obj) table.insert(savedPositions, { guid = obj.getGUID(), position = obj.getPosition(), rotation = obj.getRotation() }) end function savePositionClicked(playerColor, hoveredObject) savedPositions = {} local objects = getObjectsPositionToSave(Player[playerColor], hoveredObject) if objects then for _, v in ipairs(objects) do savePosition(v) end -- Announce on screen (centre) and in chat for everyone, tinted by the -- saving player's seat colour (Blue for blue, Red for red, Yellow, ...). broadcastToAll(#objects .. " objects saved", playerColor) end end function restorePositionClicked(playerColor) for i,v in ipairs(savedPositions) do local obj = getObjectFromGUID(v.guid) if obj != nil then obj.setRotation(v.rotation) obj.setPositionSmooth(v.position, false, true) end end end function onload() -- addHotkey("Toggle Floating Icon", toggleFloatingIcon) whenGlobalReady(initRoller) -- Remove any coherency distance labels that leaked into the save. cleanupOrphanCoherencyLabels() -- TTS sorts the Options -> Game Keys list alphabetically by label (call order -- is ignored), so these six are prefixed 1.-6. to pin them to the top in the -- desired order. The numeric prefixes change the labels, which orphans any -- prior player bindings -- hence the "RESET ALL YOUR TTS HOTKEYS" broadcast. addHotkey("1. Save Position", savePositionClicked) addHotkey("2. Restore Position", restorePositionClicked) addHotkey("3. Custom Bubble", function(playerColor, _, _) if playerColor ~= "Red" and playerColor ~= "Blue" then return end local matGUID = (playerColor == "Red") and Global.getVar("redCustomDiceMat_GUID") or Global.getVar("blueCustomDiceMat_GUID") Global.call("openBubbleInput", {color = playerColor, mat = matGUID}) end) addHotkey("4. Coherency Check", checkCoherency) addHotkey("5. Engagement Range", showEngagementRange) addHotkey("6. Roll All Dice", function(playerColor, _, _) local matGUID = (playerColor == "Red") and Global.getVar("redCustomDiceMat_GUID") or Global.getVar("blueCustomDiceMat_GUID") local mat = getObjectFromGUID(matGUID) if mat then mat.call("hotkeyRollAllDice", { playerColor = playerColor }) end end) -- Remaining hotkeys; these sort alphabetically below the numbered group. addHotkey("Auto 1 Inch Gap", arrangeModelsWith1Inch) addHotkey("Auto 2 Inch Gap", arrangeModelsWith2Inch) addHotkey("Clear Bubbles", function(playerColor, _, _) clearMeasuringCircles(nil, playerColor, false) end) addHotkey("Clear Bubbles (Selected)", function(playerColor, _, _) clearMeasuringCircles(nil, playerColor, true) end) addHotkey("Clear Lines", function(playerColor, _, _) clearHandDrawnLines(nil, playerColor, false) end) addHotkey("Clear Mat", function(playerColor, _, _) if playerColor ~= "Red" and playerColor ~= "Blue" then return end local matGUID = (playerColor == "Red") and Global.getVar("redCustomDiceMat_GUID") or Global.getVar("blueCustomDiceMat_GUID") local mat = getObjectFromGUID(matGUID) if mat then mat.call("hotkeyClearMat", { playerColor = playerColor }) end end) addHotkey("Show Scoreboard", function(playerColor, _, _) Global.call("hotkeyToggleScoringOverlay", { playerColor = playerColor }) end) addHotkey("Show/Hide UI", function(playerColor, _, _) if playerColor ~= "Red" and playerColor ~= "Blue" and playerColor ~= "Grey" then return end Global.call("toggleHudOverlayForColor", { color = playerColor }) end) addHotkey("Toggle 1 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(1, target, nil, playerColor) end) addHotkey("Toggle 12 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(12.01, target, nil, playerColor) end) addHotkey("Toggle 2 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(2, target, nil, playerColor) end) addHotkey("Toggle 3 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(3, target, nil, playerColor) end) addHotkey("Toggle 6 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(6, target, nil, playerColor) end) addHotkey("Toggle 8 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(8, target, nil, playerColor) end) addHotkey("Toggle 9 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(9, target, nil, playerColor) end) lastClickTimestamp=nowSeconds() defaultFontSize=500 fontSize=defaultFontSize lineCount=0 defaultDivisionLabel="|\n|" self.createButton({ label="---", click_function="none", function_owner=Global, position={-6.4,0.11,0.01}, rotation={0,0,0}, height=0, width=0, font_size=fontSize, font_color={1, 1, 1}, scale={0.25, 0.25, 0.25}, alignment=2 }) self.createButton({ label="", click_function="none", function_owner=Global, position={0.75,0.11,0.01}, rotation={0,0,0}, height=0, width=0, font_size=500, font_color={1, 1, 1}, scale={0.25,2,1}, alignment=2 }) if self.getDescription() == '' then setDefaultState() end emptyText = ' ' data2 = JSON.decode(self.getDescription()) Wait.time(checkDice, data2.Update_Frequency*1,-1) self.createButton(clearAllDiceBtn) self.createButton(rollAllBtn) self.createButton(orderedRollBtn) self.createButton(orderedRollGroup2Btn) self.createButton(orderedRollGroup3Btn) self.createButton(add1d3Btn) self.createButton(add1d6Btn) self.createButton(add2d6Btn) self.createButton(add5d6Btn) self.createButton(add10d6Btn) self.createButton(add25d6Btn) self.createButton(color1Btn) self.createButton(color2Btn) self.createButton(color3Btn) self.createButton(color4Btn) self.createButton(warningBtn) self.createButton(dicePlusBtn) local sign="" for i=1, 6 do if lowerUpper then sign= lowerSign else sign=upperSign end clearDiceBtn.position[3]=clearDiceBtn.position[3]+clearDiceBtnIncrement clearDiceBtn.click_function="clearDice"..i clearDiceBtn.label=clearDiceBtnLabel..i..sign self.createButton(clearDiceBtn) rollOrSelectDiceBtn.position[3] = rollOrSelectDiceBtn.position[3] + clearDiceBtnIncrement rollOrSelectDiceBtn.label = "Roll\n" .. i .. sign rollOrSelectDiceBtn.tooltip = "LMB Re-rolls this row\nRMB Re-rolls this row and lowers/uppers" rollOrSelectDiceBtn.click_function = "RollOrSelectDeleteDice" .. i self.createButton(rollOrSelectDiceBtn) end self.createButton(lowerUpperBtn) self.createButton(customDiceBtn) self.createButton(add3d6Btn) self.createButton(add4d6Btn) createToolButtonWithBorder(lethalHitsBtn) createToolButtonWithBorder(sustainedHitsBtn) createToolButtonWithBorder(sustainedLethalHitsBtn) self.createButton(copyOthersDiceBtn) self.createButton(auto2D6) self.createButton(revertRollBtn) -- Add this near the end of the button creations self.createButton(saveBtn) self.createButton(restoreBtn) createToolButtonWithBorder(measure1Btn) createToolButtonWithBorder(measure2Btn) createToolButtonWithBorder(measure3Btn) createToolButtonWithBorder(measure6Btn) createToolButtonWithBorder(measure8Btn) createToolButtonWithBorder(measure9Btn) createToolButtonWithBorder(measure12Btn) createToolButtonWithBorder(measure15Btn) self.createButton(customBubbleInputBG) self.createInput(customBubbleInput) self.createButton(customBubbleBtn) createToolButtonWithBorder(clearMeasureAllBtn) createToolButtonWithBorder(clearLinesBtn) createToolButtonWithBorder(checkCoherencyBtn) createToolButtonWithBorder(engagementRangeBtn) createToolButtonWithBorder(highlightBtn) createToolButtonWithBorder(highlightSwatchBtn) -- The swatch is recoloured at runtime. Its border shares the click_function, -- so grab the TOP button: the one created later, i.e. the higher index. for _, b in ipairs(self.getButtons() or {}) do if b.click_function == "cycleHighlightColor" then if highlightSwatchIndex == nil or b.index > highlightSwatchIndex then highlightSwatchIndex = b.index end end end refreshHighlightSwatch() createToolButtonWithBorder(gap2Btn) self.createButton(customDiceSpawnButton) -- Created last so it doesn't shift the fixed indices modifyMenu() relies on; -- capture its real index so swapPipColor's editButton flips the right button. self.createButton(pipColorBtn) for _, b in ipairs(self.getButtons() or {}) do if b.click_function == "swapPipColor" then pipColorBtn.index = b.index break end end if self.getPosition().z < 0 then selectedColor="Blue" else selectedColor="Red" end modifyMenu(selectedColor) end local originalPositions = {} local isMoving = false local lethalTextDisplays = { Red = nil, Blue = nil } local lethalTextConfig = { Red = {position = {18, 0.97, 22}, rotation = {90, 180, 0}}, Blue = {position = {-18, 0.97, -22}, rotation = {90, 0, 0}}, } function ensureLethalTextDisplay(mat) if lethalTextDisplays[mat] then return end local cfg = lethalTextConfig[mat] local t = spawnObject({ type = "3DText", position = cfg.position, rotation = cfg.rotation, scale = {1, 1, 1}, nickname = "LethalHitsText" .. mat, }) t.TextTool.setValue("Lethal Hits (0)") t.TextTool.setFontSize(50) t.TextTool.setFontColor({1, 1, 1}) lethalTextDisplays[mat] = t end function updateLethalTextDisplay(mat, count) ensureLethalTextDisplay(mat) lethalTextDisplays[mat].TextTool.setValue("Lethal Hits (" .. tostring(count) .. ")") end local lethalStartPos = { Red = {x = 18.1, y = 1.37, z = 23.25}, Blue = {x = -19.1, y = 1.47, z = -23.75}, } local lethalSpacing = 1.1 local lethalRowLimit = 25 function moveLethalHits(isSustainedAndLethal) if isMoving then return end isMoving = true local threshold = isSustainedAndLethal and sustainedLethalHitsThreshold or lethalHitsThreshold local directionMultiplier = (currentMat == "Blue") and 1 or -1 local start = lethalStartPos[currentMat] local diceInZone = 0 for _ in pairs(originalPositions) do diceInZone = diceInZone + 1 end local currentRow = math.floor(diceInZone / lethalRowLimit) local currentCol = diceInZone % lethalRowLimit local movedCount = 0 for _, die in ipairs(getDice()) do if die.getValue() >= threshold then local parked = { x = start.x + (currentCol * lethalSpacing * directionMultiplier), y = start.y, z = start.z + (currentRow * lethalSpacing), } -- Track this die as a lethal hit. cleanupLethalHits decides whether to -- destroy it by its CURRENT position (is it still in the zone?), not by -- a stored slot, so we just need a truthy marker keyed by GUID here. originalPositions[die.getGUID()] = true die.setPosition(parked) movedCount = movedCount + 1 currentCol = currentCol + 1 if currentCol >= lethalRowLimit then currentCol = 0 currentRow = currentRow + 1 end end end isMoving = false return movedCount end function cleanupLethalHits() -- Only destroy dice that are STILL inside the lethal-hits zone -- using the -- exact same bounds getDice() uses to EXCLUDE them. A die tagged lethal but -- since dragged back onto the mat or below it is no longer ours to delete -- here: on-mat dice are handled by the clearDiceX that runs right after this -- in clearDiceAll, and off-mat dice should survive. Sharing one definition -- means no die can fall between the two (excluded from clearing yet -- unrecognised for cleanup) and become permanently unclearable. local zone = getLethalZoneBounds() for guid, _ in pairs(originalPositions) do local die = getObjectFromGUID(guid) if die and isInBounds(die.getPosition(), zone) then die.destruct() end end originalPositions = {} for mat, t in pairs(lethalTextDisplays) do if t then t.destruct() end lethalTextDisplays[mat] = nil end end -- Stop tracking dice as lethal hits once they are sent back to the roller to be -- re-rolled. Without this their GUIDs would linger in originalPositions and keep -- inflating the lethal count even though they are no longer in the lethal zone. function forgetLethalHitsForDice(dice) local changed = false for _, die in ipairs(dice) do local guid = die.getGUID() if originalPositions[guid] then originalPositions[guid] = nil changed = true end end if changed and lethalTextDisplays[currentMat] then local total = 0 for _ in pairs(originalPositions) do total = total + 1 end updateLethalTextDisplay(currentMat, total) end end function lethalHitsAction(lethalHits, color, alt) local lethalNumbers = (lethalHits == 'sustainedAndLethal') local oldLethal = lethalHitsThreshold local oldSustainedLethal = sustainedLethalHitsThreshold if lethalNumbers then sustainedLethalHitsThreshold = alt and 5 or 6 else lethalHitsThreshold = alt and 5 or 6 end local moved = moveLethalHits(lethalNumbers) lethalHitsThreshold = oldLethal sustainedLethalHitsThreshold = oldSustainedLethal local total = 0 for _ in pairs(originalPositions) do total = total + 1 end updateLethalTextDisplay(currentMat, total) printToAll("Lethal hits moved: " .. tostring(moved) .. " (threshold " .. tostring(alt and 5 or 6) .. "+)") end function modifyMenu1() modifyMenu("Red") end function modifyMenu2() modifyMenu("Blue") end function modifyMenu3() modifyMenu("Black") end function modifyMenu4() modifyMenu("Green") end function modifyMenu(color) customDiceImage = nil selectedColor = color setDiceType() self.editButton({index=7, color=color}) self.editButton({index=8, color=color}) self.editButton({index=9, color=color}) self.editButton({index=10, color=color}) self.editButton({index=11, color=color}) self.editButton({index=12, color=color}) self.editButton({index=33, color=color}) self.editButton({index=34, color=color}) self.editButton({index=35, color="White"}) self.editButton({index=36, color="White"}) self.editButton({index=37, color="White"}) end function toggleDicePlus() if dicePlusExpanded then -- Identify our extra squares by click_function and remove them. Descending -- index order so each removal can't invalidate the indices still to remove. local mine = {} for _, c in ipairs(dicePlusColors) do mine[c.fn] = true end local indices = {} for _, b in ipairs(self.getButtons() or {}) do if mine[b.click_function] then table.insert(indices, b.index) end end table.sort(indices, function(a, b) return a > b end) for _, idx in ipairs(indices) do self.removeButton(idx) end dicePlusExpanded = false else for _, c in ipairs(dicePlusColors) do self.createButton({ label = "", tooltip = c.tip, click_function = c.fn, function_owner = self, position = {c.x, Yoffset, c.z}, rotation = {0, 0, 0}, height = colorBtnDimension, width = colorBtnDimension, font_size = 60, color = c.color, font_color = {0, 0, 0} }) end dicePlusExpanded = true end end function setDiceType() if customDiceImage ~= nil then diceType = "Custom_Dice" elseif pipColorBtn.color == "White" then diceType = "Die_6_Rounded" else diceType = "Die_6" end end function getStatsHelperDiceConfig() setDiceType() return { diceType = diceType, selectedColor = selectedColor, customDiceImage = customDiceImage } end function setDefaultState() self.setDescription(JSON.encode_pretty({ Cumulative = 'no',Update_Frequency = '0.2' })) end function checkDice() local data = JSON.decode(self.getDescription()) if not data then setDefaultState() data = JSON.decode(self.getDescription()) printToAll('Warning - invalid description. Restored default configuration.', {0.8,0.5,0}) end if not self.resting then return end local b = self.getBounds() local leftBound = b.center.x - b.size.x/2 local rightBound = b.center.x + b.size.x/2 local lowerBound = b.center.z - b.size.z/2 local upperBound = b.center.z + b.size.z/2 if currentMat == "Red" then rightBound = rightBound - 5 lowerBound = lowerBound + 2.9 upperBound = upperBound - 0.5 else leftBound = leftBound + 5 lowerBound = lowerBound + 0.5 upperBound = upperBound - 2.9 end local ylowerBound = b.center.y - b.size.y/2 local valueToCounter, valuesToSort, totaldice, diceSum = {}, {}, 0, 0 for _, obj in pairs(getAllObjects()) do if obj.tag == 'Dice' and obj.resting then local p = obj.getPosition() if p.x > leftBound and p.x < rightBound and p.z > lowerBound and p.z < upperBound and p.y > ylowerBound then local v = obj.getValue() if v then if not valueToCounter[v] then valueToCounter[v] = 0 table.insert(valuesToSort, v) end valueToCounter[v] = valueToCounter[v] + 1 diceSum = diceSum + v totaldice = totaldice + 1 end end end end local textLines, lineCount = {}, 0 if data.Cumulative == 'yes' then table.sort(valuesToSort) for i = 1, 12 do valueToCounter[i] = valueToCounter[i] or 0 end for i = 11, 1, -1 do valueToCounter[i] = valueToCounter[i] + valueToCounter[i+1] end for _, v in ipairs(valuesToSort) do local suffix = (v == 1) and "'s: " or "+: " table.insert(textLines, tostring(v) .. suffix .. valueToCounter[v]) lineCount = lineCount + 1 end else table.sort(valuesToSort) for _, v in ipairs(valuesToSort) do table.insert(textLines, "#" .. v .. ": " .. valueToCounter[v]) lineCount = lineCount + 1 end end for i = lineCount+1, 6 do table.insert(textLines, " ") end local text = table.concat(textLines, "\n") if text == "" then text = emptyText end local totaltext = (totaldice == 0) and " " or ("\n" .. totaldice .. " ∑ " .. diceSum) if lineCount > 6 then fontSize = defaultFontSize - ((lineCount-6)*37) divisionLabel = defaultDivisionLabel else fontSize = defaultFontSize divisionLabel = "" end self.editButton({ index = 0, label = text .. totaltext, font_size = fontSize }) self.editButton({ index = 1, label = divisionLabel }) end -- ── DEBUG-ONLY PERF DIAGNOSTIC ─────────────────────────────────────────────── -- Invoked from the shared debug panel (Global, --test builds only) via -- mat.call("runDiceScanBenchmark", {color=}). Benchmarks the always-on -- getAllObjects() scan that checkDice() runs at 5Hz, and compares it against an -- area-limited Physics.cast box over this mat's bounds, so the cost of the -- whole-scene scan is visible and the box-cast win is provable in-game. -- Timed with os.clock() (real wall time) NOT Time.time (frame time, which is -- constant inside a single callback and would read zero). function runDiceScanBenchmark(params) local playerColor = params and params.color local ITER = 200 -- bump if the scene is small and timings read ~0 -- scene population is the real independent variable for getAllObjects() local sceneCount = #getAllObjects() -- mat box (same bounds checkDice uses to filter dice) local b = self.getBounds() local origin = { x = b.center.x, y = b.center.y, z = b.center.z } local size = { x = b.size.x, y = b.size.y + 6, z = b.size.z } local orient = { x = 0, y = self.getRotation().y, z = 0 } -- NOTE: TTS's Lua (MoonSharp) returns nil for collectgarbage("count"), so -- per-call heap allocation can't be measured here. Timing is the metric; -- collectgarbage("collect") still works and we use it to settle GC first. -- getAllObjects(): avg time per call collectgarbage("collect") local gaoT0 = os.clock() for _ = 1, ITER do getAllObjects() end local gaoMs = (os.clock() - gaoT0) / ITER * 1000 -- Physics.cast box over the mat: avg time per call collectgarbage("collect") local castT0 = os.clock() local lastHits = 0 for _ = 1, ITER do local hits = Physics.cast({ origin = origin, direction = { x = 0, y = 1, z = 0 }, type = 3, size = size, orientation = orient, max_distance = 0, }) lastHits = #hits end local castMs = (os.clock() - castT0) / ITER * 1000 collectgarbage("collect") -- always-on extrapolation: 2 mats * 5Hz = 10 scans/sec across the session local CALLS_PER_SEC = 10 local speedup = (castMs > 0) and (gaoMs / castMs) or 0 local out = string.format( "── checkDice scan benchmark (%d iters) ──\n" .. "scene objects: %d box hits: %d\n" .. "getAllObjects: %.4f ms/call\n" .. "Physics.cast : %.4f ms/call\n" .. "speedup: %.1fx\n" .. "always-on (10 scans/s): %.3f ms/s vs %.3f ms/s", ITER, sceneCount, lastHits, gaoMs, castMs, speedup, gaoMs * CALLS_PER_SEC, castMs * CALLS_PER_SEC) if playerColor then printToColor(out, playerColor, { 0.4, 0.9, 1 }) else printToAll(out, { 0.4, 0.9, 1 }) end print(out) -- also to the host's System Console / log end function swapPipColor() customDiceImage = nil if pipColorBtn.color == "Black" then pipColorBtn.label = "White" pipColorBtn.color = "White" pipColorBtn.font_color = "Black" else pipColorBtn.label = "Black" pipColorBtn.color = "Black" pipColorBtn.font_color = "White" end setDiceType() self.editButton(pipColorBtn) end function arrangeModelsWith2Inch(a, b, c) -- Support being called from a button or a hotkey. When invoked via a button -- the first argument is the object and the second is the player color. -- When invoked via a hotkey the first argument is the player color. local playerColor if type(a) == "string" then playerColor = a else playerColor = b end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. playerColor) return end local objs = playerObj.getSelectedObjects() if #objs < 2 then print("Less than 2 objects selected; nothing to arrange.") return end local minPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } local maxPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } for i = 2, #objs do local p = objs[i].getPosition() if p.x < minPos.x then minPos.x = p.x end if p.z < minPos.z then minPos.z = p.z end if p.x > maxPos.x then maxPos.x = p.x end if p.z > maxPos.z then maxPos.z = p.z end end local dx = maxPos.x - minPos.x local dz = maxPos.z - minPos.z local mag = math.sqrt(dx * dx + dz * dz) local d = {} if mag < 0.0001 then d.x, d.z = 1, 0 else d.x, d.z = dx / mag, dz / mag end local items = {} for _, obj in ipairs(objs) do local pos = obj.getPosition() local half = obj.getBoundsNormalized().size.x / 2 local proj = pos.x * d.x + pos.z * d.z table.insert(items, { obj = obj, pos = pos, half = half, proj = proj }) end table.sort(items, function(a, b) return a.proj < b.proj end) local offsetCount = 0 if #items > 7 then offsetCount = 2 end local startIndex = 1 if offsetCount == 2 then startIndex = 3 end local anchorPos = { x = items[startIndex].pos.x, z = items[startIndex].pos.z } items[startIndex].newPos = { x = anchorPos.x, y = items[startIndex].pos.y, z = anchorPos.z } for i = startIndex + 1, #items - offsetCount do local prev = items[i - 1] local gapDist = prev.half + 2 + items[i].half items[i].newPos = { x = prev.newPos.x + d.x * gapDist, y = items[i].pos.y, z = prev.newPos.z + d.z * gapDist } end if offsetCount == 2 then local p = { x = -d.z, z = d.x } -- start offsets: arrange three items as a triangle so pairwise edge gap ~= 2" local baseStart = items[startIndex].newPos -- apex on main line local a = items[startIndex] local b = items[1] local c = items[2] local desiredGap = 2 local baseSep = b.half + c.half + desiredGap local baseHalf = baseSep / 2 local s1 = a.half + b.half + desiredGap local s2 = a.half + c.half + desiredGap -- compute apex->baseCenter distance from triangle right-triangle: t = sqrt(s1^2 - baseHalf^2) local t1 = math.sqrt(math.max(0, s1 * s1 - baseHalf * baseHalf)) local t2 = math.sqrt(math.max(0, s2 * s2 - baseHalf * baseHalf)) local t = (t1 + t2) / 2 local baseCenter = { x = baseStart.x - d.x * t, z = baseStart.z - d.z * t } items[1].newPos = { x = baseCenter.x + p.x * baseHalf, y = b.pos.y, z = baseCenter.z + p.z * baseHalf } items[2].newPos = { x = baseCenter.x - p.x * baseHalf, y = c.pos.y, z = baseCenter.z - p.z * baseHalf } -- debug: print the starting three indices and pairwise distances if true then local function dist(a,b) local dx = a.x - b.x; local dz = a.z - b.z; return math.sqrt(dx*dx + dz*dz) end local Apos = a.pos; local Bpos = b.pos; local Cpos = c.pos local Anew = a.newPos; local Bnew = b.newPos; local Cnew = c.newPos print(string.format("Start triangle items indexes: apex=%s base1=%s base2=%s", a.obj.getGUID(), b.obj.getGUID(), c.obj.getGUID())) print(string.format("Before: AB=%.2f AC=%.2f BC=%.2f", dist(Apos,Bpos), dist(Apos,Cpos), dist(Bpos,Cpos))) print(string.format("After: AB=%.2f AC=%.2f BC=%.2f", dist(Anew,Bnew), dist(Anew,Cnew), dist(Bnew,Cnew))) end -- end offsets local baseEnd = items[#items - offsetCount].newPos -- end offsets: triangle at the end (apex on main line, base beyond) local a_e = items[#items - offsetCount] -- apex at end of line local b_e = items[#items - 1] local c_e = items[#items] local desiredGap = 2 local baseSep_e = b_e.half + c_e.half + desiredGap local baseHalf_e = baseSep_e / 2 local s1_e = a_e.half + b_e.half + desiredGap local s2_e = a_e.half + c_e.half + desiredGap local t1_e = math.sqrt(math.max(0, s1_e * s1_e - baseHalf_e * baseHalf_e)) local t2_e = math.sqrt(math.max(0, s2_e * s2_e - baseHalf_e * baseHalf_e)) local t_e = (t1_e + t2_e) / 2 local baseCenter_e = { x = baseEnd.x + d.x * t_e, z = baseEnd.z + d.z * t_e } items[#items - 1].newPos = { x = baseCenter_e.x + p.x * baseHalf_e, y = b_e.pos.y, z = baseCenter_e.z + p.z * baseHalf_e } items[#items].newPos = { x = baseCenter_e.x - p.x * baseHalf_e, y = c_e.pos.y, z = baseCenter_e.z - p.z * baseHalf_e } -- debug: print the final three indices and pairwise distances if true then local function dist(a,b) local dx = a.x - b.x; local dz = a.z - b.z; return math.sqrt(dx*dx + dz*dz) end local Apos = a_e.pos; local Bpos = b_e.pos; local Cpos = c_e.pos local Anew = a_e.newPos; local Bnew = b_e.newPos; local Cnew = c_e.newPos print(string.format("End triangle items indexes: apex=%s base1=%s base2=%s", a_e.obj.getGUID(), b_e.obj.getGUID(), c_e.obj.getGUID())) print(string.format("Before: AB=%.2f AC=%.2f BC=%.2f", dist(Apos,Bpos), dist(Apos,Cpos), dist(Bpos,Cpos))) print(string.format("After: AB=%.2f AC=%.2f BC=%.2f", dist(Anew,Bnew), dist(Anew,Cnew), dist(Bnew,Cnew))) end end for _, item in ipairs(items) do item.obj.setPositionSmooth(item.newPos) end print("Arranged " .. #items .. " models with a 2-inch gap along the detected orientation.") end -- Single button handler: left-click arranges with a 1" gap, right-click with 2". -- For button clicks the third argument (alt_click) is true on a right-click. function arrangeModelsWithGap(obj, playerColor, alt_click) if alt_click then arrangeModelsWith2Inch(obj, playerColor, alt_click) else arrangeModelsWith1Inch(obj, playerColor, alt_click) end end function arrangeModelsWith1Inch(a, b, c) local playerColor if type(a) == "string" then playerColor = a else playerColor = b end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. playerColor) return end local objs = playerObj.getSelectedObjects() if #objs < 2 then print("Less than 2 objects selected; nothing to arrange.") return end local minPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } local maxPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } for i = 2, #objs do local p = objs[i].getPosition() if p.x < minPos.x then minPos.x = p.x end if p.z < minPos.z then minPos.z = p.z end if p.x > maxPos.x then maxPos.x = p.x end if p.z > maxPos.z then maxPos.z = p.z end end local dx = maxPos.x - minPos.x local dz = maxPos.z - minPos.z local mag = math.sqrt(dx * dx + dz * dz) local d = {} if mag < 0.0001 then d.x, d.z = 1, 0 else d.x, d.z = dx / mag, dz / mag end local items = {} for _, obj in ipairs(objs) do local pos = obj.getPosition() local half = obj.getBoundsNormalized().size.x / 2 local proj = pos.x * d.x + pos.z * d.z table.insert(items, { obj = obj, pos = pos, half = half, proj = proj }) end table.sort(items, function(a, b) return a.proj < b.proj end) local offsetCount = 0 if #items > 7 then offsetCount = 2 end local startIndex = 1 if offsetCount == 2 then startIndex = 3 end local anchorPos = { x = items[startIndex].pos.x, z = items[startIndex].pos.z } items[startIndex].newPos = { x = anchorPos.x, y = items[startIndex].pos.y, z = anchorPos.z } for i = startIndex + 1, #items - offsetCount do local prev = items[i - 1] local gapDist = prev.half + 1 + items[i].half items[i].newPos = { x = prev.newPos.x + d.x * gapDist, y = items[i].pos.y, z = prev.newPos.z + d.z * gapDist } end if offsetCount == 2 then local p = { x = -d.z, z = d.x } -- start offsets: arrange three items as a triangle so pairwise edge gap ~= 1" local baseStart = items[startIndex].newPos -- apex on main line local a = items[startIndex] local b = items[1] local c = items[2] local desiredGap = 1 local baseSep = b.half + c.half + desiredGap local baseHalf = baseSep / 2 local s1 = a.half + b.half + desiredGap local s2 = a.half + c.half + desiredGap local t1 = math.sqrt(math.max(0, s1 * s1 - baseHalf * baseHalf)) local t2 = math.sqrt(math.max(0, s2 * s2 - baseHalf * baseHalf)) local t = (t1 + t2) / 2 local baseCenter = { x = baseStart.x - d.x * t, z = baseStart.z - d.z * t } items[1].newPos = { x = baseCenter.x + p.x * baseHalf, y = b.pos.y, z = baseCenter.z + p.z * baseHalf } items[2].newPos = { x = baseCenter.x - p.x * baseHalf, y = c.pos.y, z = baseCenter.z - p.z * baseHalf } -- end offsets: triangle at the end (apex on main line, base beyond) local baseEnd = items[#items - offsetCount].newPos local a_e = items[#items - offsetCount] -- apex at end of line local b_e = items[#items - 1] local c_e = items[#items] local desiredGap_e = 1 local baseSep_e = b_e.half + c_e.half + desiredGap_e local baseHalf_e = baseSep_e / 2 local s1_e = a_e.half + b_e.half + desiredGap_e local s2_e = a_e.half + c_e.half + desiredGap_e local t1_e = math.sqrt(math.max(0, s1_e * s1_e - baseHalf_e * baseHalf_e)) local t2_e = math.sqrt(math.max(0, s2_e * s2_e - baseHalf_e * baseHalf_e)) local t_e = (t1_e + t2_e) / 2 local baseCenter_e = { x = baseEnd.x + d.x * t_e, z = baseEnd.z + d.z * t_e } items[#items - 1].newPos = { x = baseCenter_e.x + p.x * baseHalf_e, y = b_e.pos.y, z = baseCenter_e.z + p.z * baseHalf_e } items[#items].newPos = { x = baseCenter_e.x - p.x * baseHalf_e, y = c_e.pos.y, z = baseCenter_e.z - p.z * baseHalf_e } end for _, item in ipairs(items) do item.obj.setPositionSmooth(item.newPos) end print("Arranged " .. #items .. " models with a 1-inch gap along the detected orientation.") end function determineBaseInInches(model) local savedBase = model.getTable("chosenBase") if savedBase ~= nil then return savedBase.base else local chosenBase = VALID_BASE_SIZES_IN_MM[1] local modelSize = model.getBoundsNormalized().size local modelSizeX = modelSize.x local modelSizeZ = modelSize.z local closestSum = 10000000000 local chosenBaseIdx = 1 for k, base in pairs(VALID_BASE_SIZES_IN_MM) do local baseInchX = (MM_TO_INCH - 0.001) * base.x local baseInchZ = (MM_TO_INCH - 0.001) * base.z if modelSizeX > baseInchX and modelSizeZ > baseInchZ then local distSum = (modelSizeX - baseInchX) + (modelSizeZ - baseInchZ) if distSum < closestSum then closestSum = distSum chosenBase = base chosenBaseIdx = k end end end if chosenBase == nil then chosenBase = { x=modelSizeX/2, z=modelSizeZ/2} else chosenBase = { x = (chosenBase.x * MM_TO_INCH)/2, z = (chosenBase.z * MM_TO_INCH)/2 } end model.setTable("chosenBase", { baseIdx=chosenBaseIdx, base=chosenBase }) return chosenBase end end function getCircleVectorPoints(radius, baseX, baseZ, obj) local result = {} local scaleFactor = 1/obj.getScale().x local steps = 64 local degrees,sin,cos,toRads = 360/steps, math.sin, math.cos, math.rad for i = 0,steps do table.insert(result,{ x = cos(toRads(degrees*i))*((radius+baseX)*scaleFactor), z = MEASURING_RING_Y_OFFSET, y = sin(toRads(degrees*i))*((radius+baseZ)*scaleFactor) }) end return result end function getRectangleVectorPoints(radius, sizeX, sizeZ, obj) local result = {} local scaleFactor = 1/obj.getScale().x sizeX = sizeX*scaleFactor sizeZ = sizeZ*scaleFactor radius = radius*scaleFactor local steps = 65 local degrees,sin,cos,toRads = 360/(steps-1), math.sin, math.cos, math.rad local xOffset,zOffset = sizeX,sizeZ table.insert(result,{ x = (cos(toRads(degrees*0))*radius)+sizeX-0.001, y = (sin(toRads(degrees*0))*radius)+sizeZ, z = MEASURING_RING_Y_OFFSET }) for i = 1,steps-1 do if i == 16 then table.insert(result,{ x= sizeX, y=(radius+sizeZ), z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=-sizeX, y=(radius+sizeZ), z=MEASURING_RING_Y_OFFSET }) xOffset = -sizeX elseif i == 33 then table.insert(result,{ x=-radius-sizeX, y= sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=-radius-sizeX-0.001, y=-sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=-radius-sizeX, y=-sizeZ, z=MEASURING_RING_Y_OFFSET }) zOffset = -sizeZ elseif i == 49 then table.insert(result,{ x=-sizeX, y=-radius-sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x= sizeX, y=-radius-sizeZ, z=MEASURING_RING_Y_OFFSET }) xOffset = sizeX elseif i == 65 then table.insert(result,{ x=radius+sizeX, y=-sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=radius+sizeX-0.001, y= sizeZ, z=MEASURING_RING_Y_OFFSET }) else table.insert(result,{ x = (cos(toRads(degrees*i))*radius)+xOffset, y = (sin(toRads(degrees*i))*radius)+zOffset, z = MEASURING_RING_Y_OFFSET }) end end table.insert(result,{ x = (cos(toRads(degrees*0))*radius)+sizeX-0.001, y = (sin(toRads(degrees*0))*radius)+sizeZ, z = MEASURING_RING_Y_OFFSET }) return result end function toggleMeasurementCircleForSelection(radius, playerColor) local playerObj = Player[playerColor] if not playerObj then return end local objs = playerObj.getSelectedObjects() for _, obj in ipairs(objs) do changeMeasurementCircle(radius, obj, nil, playerColor) end end function toggle1Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(1, playerColor) end function toggle2Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(2, playerColor) end function toggle3Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(3, playerColor) end function toggle6Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(6, playerColor) end function toggle8Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(8, playerColor) end function toggle9Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(9, playerColor) end function toggle12Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(12.01, playerColor) end function toggle15Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(15, playerColor) end local customBubbleSize = 0 function setCustomBubbleSize(_, _, value, _) customBubbleSize = tonumber(value) or 0 end function applyCustomBubble(_, playerColor, alt) if alt then -- Right-click opens the on-screen typed-size overlay instead. requestBubbleInput(_, playerColor, alt) return end if customBubbleSize > 0 then toggleMeasurementCircleForSelection(customBubbleSize, playerColor) else printToColor("Please enter a valid bubble size", playerColor, "Red") end end -- ===== Screen-space custom bubble input overlay (1-60) ===== -- The overlay panel + its input/submit callbacks live on the Global canvas -- (global.ttslua / ftc_base_ui.xml). This button just asks Global to open it, -- passing this mat's GUID so Global can call applyBubbleFromOverlay back here. function requestBubbleInput(_, playerColor, _) Global.call("openBubbleInput", {color = playerColor, mat = self.getGUID()}) end -- Invoked by Global once the player confirms a size in the overlay. function applyBubbleFromOverlay(params) toggleMeasurementCircleForSelection(params.radius, params.color) end -- Invoked by Global when the overlay value is 0 / blank: clear bubbles on the -- player's selected models (same as the "Clear Bubbles (Selected)" action). function clearBubblesFromOverlay(params) clearMeasuringCircles(nil, params.color, true) end function changeMeasurementCircle(newRequestedRadius, target, presetBase, ownerColor) if target == nil then return end local measuringRings = target.getTable("ym-measuring-circles") local currentColor = "Teal" local currentColorRadius if measuringRings == nil then measuringRings = {} currentColorRadius = 0 else for idx = #measuringRings, 1, -1 do if (measuringRings[idx].name == currentColor) or (measuringRings[idx].name == nil and currentColor == nil) then currentColorRadius = measuringRings[idx].radius table.remove(measuringRings, idx) elseif measuringRings[idx].name == "base" then table.remove(measuringRings, idx) end end if currentColorRadius == nil then currentColorRadius = 0 end end local newRadius if currentColorRadius == newRequestedRadius then newRadius = 0 else newRadius = newRequestedRadius end if newRadius ~= 0 then local isRectangular = target.hasTag("rectangularMeasuring") local measuring = { name = currentColor, color = Color.fromString(currentColor), radius = newRadius, thickness = 0.5, rotation = {270, 0, 0}, owner = ownerColor -- track who created this measuring circle } local base = { name = "base", color = Color.fromString(currentColor), thickness = 0.2, rotation = {270, 0, 0}, owner = ownerColor } local measuringPoints, basePoints if isRectangular then local modelBounds = target.getBoundsNormalized() measuringPoints = getRectangleVectorPoints(newRadius, modelBounds.size.x / 2, modelBounds.size.z / 2, target) basePoints = getRectangleVectorPoints(0, modelBounds.size.x / 2, modelBounds.size.z / 2, target) else local baseRadiuses = (presetBase == nil) and determineBaseInInches(target) or presetBase measuringPoints = getCircleVectorPoints(newRadius, baseRadiuses.x, baseRadiuses.z, target) basePoints = getCircleVectorPoints(0, baseRadiuses.x, baseRadiuses.z, target) end measuring.points = measuringPoints base.points = basePoints table.insert(measuringRings, measuring) table.insert(measuringRings, base) end target.setVectorLines(measuringRings) target.setTable("ym-measuring-circles", measuringRings) end -- Clear measuring circles helper. -- LMB (alt=false): clear bubbles on every object on the table. -- RMB (alt=true): clear bubbles only on the player's currently selected objects. function clearMeasuringCircles(obj, playerColor, alt) local targets if alt then local playerObj = Player[playerColor] if not playerObj then return end targets = playerObj.getSelectedObjects() if #targets == 0 then printToColor("No objects selected. Right-click clears bubbles on selected objects only.", playerColor, "Yellow") return end else targets = getAllObjects() end local cleared = 0 for _, o in ipairs(targets) do local bubbles = o.getTable("ym-measuring-circles") if bubbles ~= nil then o.setVectorLines({}) o.setTable("ym-measuring-circles", nil) cleared = cleared + 1 end end if alt then printToColor("Cleared measuring bubbles on "..cleared.." selected object(s).", playerColor, {1,1,1}) else printToAll("Cleared measuring bubbles on "..cleared.." objects.", {1,1,1}) end end function clearHandDrawnLines(obj, playerColor, alt) local clearedAll = 0 if coherencyMonitorActive then stopCoherencyMonitor() end Global.setVectorLines({}) for _, o in ipairs(getAllObjects()) do if o.getTable("ym-measuring-circles") == nil then local lines = o.getVectorLines() if lines and #lines > 0 then o.setVectorLines({}) clearedAll = clearedAll + 1 end end end printToAll("Cleared hand-drawn lines on "..clearedAll.." objects.", {1,1,1}) end -- Highlight ownership coordination with SelectionHighlighter. While our monitors -- glow a model, SelectionHighlighter must leave that model alone, otherwise it -- snapshots our colour as the model's "original" and re-applies it on deselect -- (so the glow survives the monitor). hold/release tell it to stand off and -- resume. No-ops when the highlighter object isn't present. local SELECTION_HIGHLIGHTER_GUID = "27de4f" local function holdSelectionHighlight(guid) local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) if sh ~= nil then sh.call("holdHighlight", {guid = guid}) end end local function releaseSelectionHighlight(guid) local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) if sh ~= nil then sh.call("releaseHighlight", {guid = guid}) end end -- Live coherency / range monitor for a captured set of models. -- Measures base-to-base (edge-to-edge) on the table plane (x/z), ignoring height, -- and enforces two rules: -- * Coherency: every model must have at least one other model within 2". -- * Range: every model must be within 9" of every other model. -- While active, a repeating timer re-reads the models' live positions and -- redraws the breach lines so they follow the models (and appear/disappear as -- breaches resolve), like the measuring bubbles. Lines are global vectors. -- * orange = model out of 2" coherency (drawn to its nearest model) -- * red = a pair of models more than 9" apart coherencyMonitorActive = false coherencyMonitorGuids = {} coherencyMonitorTimer = nil coherencyTimeoutTimer = nil -- one-shot auto-stop timer coherencyLabels = {} -- pool of 3DText label entries {obj,key,pos,text,color,active} -- Cached pairwise geometry so the live monitor doesn't recompute the whole -- O(n^2) base-gap matrix every tick. Indices are 1..#coherencyMonitorGuids and -- stay aligned with coherencyDesc; a deletion triggers a full rebuild that -- compacts the dead entry out. Routine movement only refreshes the moved -- models' rows (O(moving * n)) via the motion loop below. coherencyDesc = {} -- desc[i] = base descriptor for monitored model i coherencyGap = {} -- gap[i][j] = cached base-to-base gap coherencyCp = {} -- cp[i][j] = base-edge point on i toward j (for drawing) coherencyIndexByGuid = {} -- guid -> current index, for O(1) event filtering coherencyMotionTimer = nil -- runs ONLY while monitored models are moving coherencyMotionTicks = 0 -- motion-loop tick counter, for the label cadence coherencyPrevMoving = {} -- set of indices that were moving last tick coherencyHighlighted = {} -- guid -> true: monitored models currently glowing green local COHERENCY_RANGE = 2 -- inches, base-to-base local COHERENCY_OK_COLOR = {0, 1, 0} -- green glow on a model that is in coherency local COHERENCY_MAX = 9 -- inches, base-to-base local COHERENCY_LIFT = 0.2 -- raise lines slightly so they sit above the bases local COHERENCY_LABEL_LIFT = 0.5 -- raise distance text a little above the lines local COHERENCY_REFRESH = 0.1 -- seconds between redraws WHILE models are moving local COHERENCY_TIMEOUT = 15 -- auto-stop if left on this many seconds -- Soft backstop on selection size. The monitor is now event-driven (idle costs -- nothing; a moving model only re-measures its own row, O(moving * n)), so this -- can safely be raised well beyond the old O(n^2)-per-tick limit if desired. local COHERENCY_MAX_MODELS = 30 -- Hard cap on how many breach lines/labels are ever drawn at once. A unit split -- into two clusters >9" apart breaches on a*b pairs (a 12+12 split = 144 lines), -- and each line is a vector line + a 3DText label -- enough to choke the table. -- We cap the rendered set so the draw cost stays bounded no matter the spread. local COHERENCY_MAX_LINES = 24 -- The 3DText distance labels are far costlier than the (batched) vector lines: -- each is a separate object needing setPosition + TextTool calls. So update them -- only every Nth motion tick (lines still follow every tick), and never more -- than a couple of them. Labels are diffed and parked (never destroyed mid-run). local COHERENCY_LABEL_EVERY = 2 -- update labels every Nth motion tick (lines: every tick) local COHERENCY_LABEL_PARK = {0, -50, 0} -- offscreen spot for unused (parked) labels local COHERENCY_LABEL_TAG = "coherencyLabel" -- GMNotes marker for sweep-on-load -- Spawn a flat, locked 3DText object to use as a floating distance label. -- Tagged via GMNotes so any label that leaks into a save is swept on load. local function spawnCoherencyLabel(pos, text, color) return spawnObjectData({ data = { Name = "3DText", Transform = { posX = pos[1], posY = pos[2], posZ = pos[3], rotX = 90, rotY = 0, rotZ = 0, scaleX = 1, scaleY = 1, scaleZ = 1 }, Text = { Text = text, colorstate = { r = color[1], g = color[2], b = color[3] }, fontSize = 32 }, Locked = true, GMNotes = COHERENCY_LABEL_TAG } }) end -- Destroy any coherency labels left in the world (e.g. from a save taken while -- the monitor was active). Identified by their GMNotes marker. Run on load. function cleanupOrphanCoherencyLabels() for _, o in ipairs(getAllObjects()) do if o.getGMNotes() == COHERENCY_LABEL_TAG then destroyObject(o) end end end -- Reconcile the label pool with the requested specs ({key, pos, text, color}). -- Each spec is keyed by its breach (rule + model pair) so a label keeps a stable -- pool slot across ticks and only its OWN object's API calls fire when its pos/ -- text/colour actually change (diffing). Labels for breaches that vanished are -- PARKED offscreen and blanked -- never destroyed -- so an oscillating breach -- count during a drag causes no spawn/destroy churn. local function applyLabel(e, spec, force) local o = e.obj if o == nil or o.isDestroyed() then return end if force or e.pos[1] ~= spec.pos[1] or e.pos[2] ~= spec.pos[2] or e.pos[3] ~= spec.pos[3] then o.setPosition(spec.pos) e.pos[1], e.pos[2], e.pos[3] = spec.pos[1], spec.pos[2], spec.pos[3] end if force or e.text ~= spec.text then o.TextTool.setValue(spec.text) e.text = spec.text end if force or e.color[1] ~= spec.color[1] or e.color[2] ~= spec.color[2] or e.color[3] ~= spec.color[3] then o.TextTool.setFontColor(spec.color) e.color[1], e.color[2], e.color[3] = spec.color[1], spec.color[2], spec.color[3] end end local function updateCoherencyLabels(specs) -- Index currently-active labels by key for O(1) reuse. local byKey = {} for _, e in ipairs(coherencyLabels) do if e.active and e.key ~= nil then byKey[e.key] = e end end local usedKeys = {} for _, spec in ipairs(specs) do usedKeys[spec.key] = true local e = byKey[spec.key] if e ~= nil then applyLabel(e, spec, false) -- same breach as last tick: diff, skip unchanged else -- Reuse a parked entry if one exists, else spawn a new label object. local parked = nil for _, cand in ipairs(coherencyLabels) do if not cand.active then parked = cand; break end end if parked ~= nil then parked.key = spec.key parked.active = true applyLabel(parked, spec, true) -- repurposed: force all fields byKey[spec.key] = parked else local lbl = spawnCoherencyLabel(spec.pos, spec.text, spec.color) local entry = { obj = lbl, key = spec.key, active = true, pos = {spec.pos[1], spec.pos[2], spec.pos[3]}, text = spec.text, color = {spec.color[1], spec.color[2], spec.color[3]}, } coherencyLabels[#coherencyLabels + 1] = entry byKey[spec.key] = entry end end end -- Park any label whose breach is no longer shown: blank it and move it -- offscreen once, then leave it idle for later reuse (no destroy). for _, e in ipairs(coherencyLabels) do if e.active and not usedKeys[e.key] then e.active = false e.key = nil local o = e.obj if o ~= nil and not o.isDestroyed() then if e.text ~= "" then o.TextTool.setValue(""); e.text = "" end o.setPosition(COHERENCY_LABEL_PARK) e.pos[1], e.pos[2], e.pos[3] = COHERENCY_LABEL_PARK[1], COHERENCY_LABEL_PARK[2], COHERENCY_LABEL_PARK[3] end end end end function stopCoherencyMonitor() coherencyMonitorActive = false if coherencyMonitorTimer ~= nil then Wait.stop(coherencyMonitorTimer) coherencyMonitorTimer = nil end if coherencyMotionTimer ~= nil then Wait.stop(coherencyMotionTimer) coherencyMotionTimer = nil end if coherencyTimeoutTimer ~= nil then Wait.stop(coherencyTimeoutTimer) coherencyTimeoutTimer = nil end -- Drop the green coherency glow from every model we lit, and hand ownership -- back to SelectionHighlighter. for guid in pairs(coherencyHighlighted) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then o.highlightOff() end releaseSelectionHighlight(guid) end coherencyHighlighted = {} coherencyMonitorGuids = {} coherencyDesc = {} coherencyGap = {} coherencyCp = {} coherencyIndexByGuid = {} coherencyMotionTicks = 0 coherencyPrevMoving = {} for _, e in ipairs(coherencyLabels) do if e.obj ~= nil and not e.obj.isDestroyed() then destroyObject(e.obj) end end coherencyLabels = {} Global.setVectorLines({}) end -- Build a lightweight descriptor for a model's base: world centre, the base's -- two semi-radii (a along local x, b along local z, in inches) and the model's -- local x/z unit axes. The base is treated as an ellipse that rotates with the -- model -- getTransformRight()/Forward() are unit vectors of the model's local -- axes, so this is rotation-correct for ovals without any handedness guess. local function buildBaseDescriptor(o) local base = determineBaseInInches(o) return { pos = o.getPosition(), a = base.x, b = base.z, right = o.getTransformRight(), forward = o.getTransformForward() } end -- Radius of the descriptor's ellipse base along world unit direction (dx,dz), -- via the ellipse polar equation. Exact for circles and for ovals along the -- centre-to-centre line. local function baseRadiusInDir(d, dx, dz) local lx = dx * d.right.x + dz * d.right.z -- direction in local x (unit axis) local lz = dx * d.forward.x + dz * d.forward.z -- direction in local z (unit axis) return 1 / math.sqrt((lx * lx) / (d.a * d.a) + (lz * lz) / (d.b * d.b)) end -- Base-to-base (edge-to-edge) gap between two descriptors, measured along the -- centre-to-centre line, plus the base-edge point on each (for drawing). O(1). local function baseGap(d1, d2) local dx, dz = d2.pos.x - d1.pos.x, d2.pos.z - d1.pos.z local centre = math.sqrt(dx * dx + dz * dz) if centre < 1e-6 then return 0, d1.pos, d2.pos -- coincident centres end local ux, uz = dx / centre, dz / centre local r1 = baseRadiusInDir(d1, ux, uz) local r2 = baseRadiusInDir(d2, -ux, -uz) local gap = centre - r1 - r2 if gap < 0 then gap = 0 end -- bases touching/overlapping local p1 = { x = d1.pos.x + ux * r1, z = d1.pos.z + uz * r1 } local p2 = { x = d2.pos.x - ux * r2, z = d2.pos.z - uz * r2 } return gap, p1, p2 end -- Recompute just one model's row/column of the cached gap/cp matrices after its -- descriptor (coherencyDesc[i]) has been refreshed. O(n). Used by the motion -- loop so a moving model only re-measures against the others, not every pair. local function recomputeCoherencyRow(i) local di = coherencyDesc[i] if di == nil then return end for j = 1, #coherencyDesc do if j ~= i and coherencyDesc[j] ~= nil then local g, pi, pj = baseGap(di, coherencyDesc[j]) coherencyGap[i][j], coherencyGap[j][i] = g, g coherencyCp[i][j], coherencyCp[j][i] = pi, pj end end end -- Full O(n^2) rebuild of the cache: resolve the monitored set to live objects -- (compacting any that were deleted so indices stay dense and aligned), then -- recompute every pairwise gap once. Returns the live model count. Called on -- start and when a monitored model is deleted -- NOT every tick. local function coherencyFullRecompute() local guids, desc, indexByGuid = {}, {}, {} for _, guid in ipairs(coherencyMonitorGuids) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then guids[#guids + 1] = guid desc[#desc + 1] = buildBaseDescriptor(o) indexByGuid[guid] = #guids end end coherencyMonitorGuids = guids coherencyDesc = desc coherencyIndexByGuid = indexByGuid local n = #desc coherencyGap, coherencyCp = {}, {} for i = 1, n do coherencyGap[i] = {}; coherencyCp[i] = {} end for i = 1, n do for j = i + 1, n do local g, pi, pj = baseGap(desc[i], desc[j]) coherencyGap[i][j], coherencyGap[j][i] = g, g coherencyCp[i][j], coherencyCp[j][i] = pi, pj end end return n end -- Build the breach lines/labels from the CACHED gap/cp (no base-gap math here) -- and push them to the table. Lines update on every call; labels are far costlier -- (per-object 3DText API calls) so they're only reconciled when updateLabels is -- true (see the motion loop's cadence) and we draw a number on only a couple of -- lines -- the coherency (orange) breaches plus the single worst range (red) one. -- The rendered line set is bounded by COHERENCY_MAX_LINES. -- * orange = a model with no other model within 2" (drawn to its nearest) -- * red = a model out of 9" range (drawn to its single worst partner) local function drawCoherency(updateLabels) if not coherencyMonitorActive then return end local desc, gap, cp = coherencyDesc, coherencyGap, coherencyCp local n = #desc if n < 2 then stopCoherencyMonitor() return end -- Collect breach candidates, then render only the most important ones (up to -- COHERENCY_MAX_LINES) so a wide spread can't flood the table with lines. local breaches = {} -- { i, j, d, color, critical } local function add(i, j, d, color, critical) breaches[#breaches + 1] = { i = i, j = j, d = d, color = color, critical = critical } end -- Rule 1: every model needs at least one other model within 2" base-to-base. -- If none qualifies, flag the gap to its nearest neighbour (orange, critical). -- coherent[i] records the per-model status so we can glow it green below. local coherent = {} for i = 1, n do local hasBuddy = false local nearest, nearestDist = nil, math.huge for j = 1, n do if i ~= j then local d = gap[i][j] if d ~= nil then if d <= COHERENCY_RANGE then hasBuddy = true end if d < nearestDist then nearestDist = d; nearest = j end end end end coherent[i] = hasBuddy if not hasBuddy and nearest then add(i, nearest, nearestDist, {1, 0.5, 0}, true) end end -- Glow each monitored model green while it is in coherency (in-2" of a buddy) -- for at-a-glance clarity; drop the glow when it falls out. We take ownership -- from SelectionHighlighter while glowing (hold) and hand it back when we stop -- (release), so the green can't be snapshotted and restored on deselect. for i = 1, n do local guid = coherencyMonitorGuids[i] local o = guid and getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then if coherent[i] then if not coherencyHighlighted[guid] then holdSelectionHighlight(guid) o.highlightOn(COHERENCY_OK_COLOR) coherencyHighlighted[guid] = true end elseif coherencyHighlighted[guid] then o.highlightOff() releaseSelectionHighlight(guid) coherencyHighlighted[guid] = nil end end end -- Rule 2: every model must be within 9" of every other. Drawing a line per -- breaching PAIR is O(n^2) lines (a 12+12 split = 144); instead flag at most -- one line per model -- to its single worst out-of-range partner, deduped -- -- so a split unit draws ~n lines while still marking every offending model. local redSeen = {} for i = 1, n do local worstJ, worstD = nil, COHERENCY_MAX for j = 1, n do if i ~= j then local d = gap[i][j] if d ~= nil and d > worstD then worstD = d; worstJ = j end end end if worstJ then local key = (i < worstJ) and (i * 100000 + worstJ) or (worstJ * 100000 + i) if not redSeen[key] then redSeen[key] = true add(i, worstJ, worstD, {1, 0, 0}, false) end end end -- Render the most important breaches up to the hard cap: critical coherency -- (orange) breaches first, then the most severe range (red) breaches by gap. table.sort(breaches, function(a, b) if a.critical ~= b.critical then return a.critical end return a.d > b.d end) local shown = math.min(#breaches, COHERENCY_MAX_LINES) local lines = {} local labelSpecs = updateLabels and {} or nil local redLabelDrawn = false for k = 1, shown do local br = breaches[k] local pi, pj = cp[br.i][br.j], cp[br.j][br.i] if pi ~= nil and pj ~= nil then local yi, yj = desc[br.i].pos.y, desc[br.j].pos.y lines[#lines + 1] = { points = {{pi.x, yi + COHERENCY_LIFT, pi.z}, {pj.x, yj + COHERENCY_LIFT, pj.z}}, color = br.color, thickness = 0.15, } -- Number only the orange breaches and the single worst red one. if updateLabels then local wantLabel = br.critical if not br.critical and not redLabelDrawn then wantLabel = true; redLabelDrawn = true end if wantLabel then local lo, hi = br.i, br.j if lo > hi then lo, hi = hi, lo end labelSpecs[#labelSpecs + 1] = { key = (br.critical and "O" or "R") .. lo .. ":" .. hi, pos = {(pi.x + pj.x) / 2, (yi + yj) / 2 + COHERENCY_LABEL_LIFT, (pi.z + pj.z) / 2}, text = string.format("%.1f\"", br.d), color = br.color, } end end end end Global.setVectorLines(lines) if updateLabels then updateCoherencyLabels(labelSpecs) end end -- Motion loop. Runs ONLY while one or more monitored models are in motion -- (held or not yet resting). Each tick it refreshes just the moving models' -- descriptors and recomputes their rows (O(moving * n)), then redraws. When -- nothing is moving it stops itself; the cached lines stay on the table until -- the next pickup/drop. A deleted model triggers a one-off full rebuild. local function coherencyMotionTick() if not coherencyMonitorActive then return end coherencyMotionTicks = coherencyMotionTicks + 1 local movingIdx, movingObj, needsRebuild = {}, {}, false for i = 1, #coherencyMonitorGuids do local o = getObjectFromGUID(coherencyMonitorGuids[i]) if o == nil or o.isDestroyed() then needsRebuild = true elseif o.held_by_color ~= nil or o.resting == false then movingIdx[i] = true movingObj[i] = o end end if needsRebuild then coherencyPrevMoving = {} if coherencyFullRecompute() < 2 then stopCoherencyMonitor() else drawCoherency(true) end return end -- Recompute the union of models moving now and last tick, so the tick a model -- comes to rest its final position is captured before the loop stops. local refresh = {} for i in pairs(movingIdx) do refresh[i] = true end for i in pairs(coherencyPrevMoving) do if movingIdx[i] == nil then local o = getObjectFromGUID(coherencyMonitorGuids[i]) if o ~= nil and not o.isDestroyed() then movingObj[i] = o; refresh[i] = true end end end for i in pairs(refresh) do coherencyDesc[i] = buildBaseDescriptor(movingObj[i]) end for i in pairs(refresh) do recomputeCoherencyRow(i) end coherencyPrevMoving = movingIdx if next(movingIdx) == nil then -- Everything is at rest: draw once WITH labels (accurate final numbers), -- then stop polling. Cache and drawn lines persist until the next pickup. drawCoherency(true) coherencyPrevMoving = {} if coherencyMotionTimer ~= nil then Wait.stop(coherencyMotionTimer) coherencyMotionTimer = nil end return end -- Lines every tick (cheap, batched); labels only every Nth tick (costly). drawCoherency(coherencyMotionTicks % COHERENCY_LABEL_EVERY == 0) end local function startCoherencyMotionLoop() if not coherencyMonitorActive then return end if coherencyMotionTimer ~= nil then return end -- already running coherencyMotionTimer = Wait.time(coherencyMotionTick, COHERENCY_REFRESH, -1) end -- Manual full redraw / back-compat entry point. The live monitor no longer -- calls this every tick -- routine movement updates only the moved rows via the -- motion loop; this rebuilds the whole cache and redraws on demand. function refreshCoherencyLines() if not coherencyMonitorActive then return end if coherencyFullRecompute() < 2 then stopCoherencyMonitor() return end drawCoherency(true) end -- Object events (delivered for ALL objects). Kept cheap: bail immediately when -- the monitor is off or the object isn't part of the monitored set. function onObjectPickUp(player_color, obj) if not coherencyMonitorActive then return end if coherencyIndexByGuid[obj.getGUID()] == nil then return end startCoherencyMotionLoop() end function onObjectDrop(player_color, obj) if not coherencyMonitorActive then return end local i = coherencyIndexByGuid[obj.getGUID()] if i == nil then return end -- Snap the lines to the model's new spot immediately (covers a drop that -- lands already-resting), then run the motion loop to track any physics -- settle; it self-stops once everything is at rest. coherencyDesc[i] = buildBaseDescriptor(obj) recomputeCoherencyRow(i) drawCoherency(true) startCoherencyMotionLoop() end function onObjectDestroy(obj) if not coherencyMonitorActive then return end if coherencyIndexByGuid[obj.getGUID()] == nil then return end -- The object still exists during this event; defer a frame so the rebuild's -- getObjectFromGUID skips it, then recompute the smaller set (or stop). Wait.frames(function() if not coherencyMonitorActive then return end if coherencyFullRecompute() < 2 then stopCoherencyMonitor() else drawCoherency(true) end end, 1) end -- Drive the live coherency monitor. -- LEFT click -> (re)start the monitor on the player's selected models. -- RIGHT click -> stop it and remove the lines/labels. function checkCoherency(a, b, c) -- Support button (a=object, b=color, c=altClick) and hotkey (a=color) -- invocation, mirroring arrangeModelsWith2Inch. local playerColor, alt if type(a) == "string" then playerColor = a -- hotkey: no right-click concept alt = false else playerColor = b -- button alt = c and true or false end -- Right click removes the lines (stops the monitor). if alt then if coherencyMonitorActive then stopCoherencyMonitor() if playerColor then printToColor("Live coherency check OFF.", playerColor, {1, 1, 1}) end elseif playerColor then printToColor("No coherency check is running.", playerColor, {1, 1, 1}) end return end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. tostring(playerColor)) return end local objs = playerObj.getSelectedObjects() if #objs < 2 then -- Leave any running monitor untouched on an empty/short selection. printToColor("Select at least 2 models to check coherency.", playerColor, {1, 0.5, 0}) return end -- Soft cap so an accidental whole-army box-select doesn't pay a big one-off -- O(n^2) recompute. Leave any running monitor untouched. if #objs > COHERENCY_MAX_MODELS then broadcastToColor(string.format("Coherency check skipped: %d models selected (max %d). Select a single unit.", #objs, COHERENCY_MAX_MODELS), playerColor, {1, 0.3, 0.3}) return end -- Left click (re)starts on the current selection. Stop any prior run first -- so a fresh selection fully replaces the old monitored set. if coherencyMonitorActive then stopCoherencyMonitor() end -- Capture the monitored set by GUID so it keeps tracking these models even -- after they are deselected or moved. coherencyMonitorGuids = {} for _, o in ipairs(objs) do table.insert(coherencyMonitorGuids, o.getGUID()) end coherencyMonitorActive = true coherencyMotionTicks = 0 coherencyPrevMoving = {} -- One full O(n^2) recompute now; after this the lines are only refreshed -- when a monitored model actually moves (onObjectPickUp/onObjectDrop start -- the motion loop), so an idle check costs nothing. if coherencyFullRecompute() < 2 then stopCoherencyMonitor() printToColor("Coherency needs at least 2 live models.", playerColor, {1, 0.5, 0}) return end drawCoherency(true) -- Auto-stop after COHERENCY_TIMEOUT seconds so a forgotten monitor does not -- keep redrawing forever. Manual stop / restart cancels this in stopCoherencyMonitor. coherencyTimeoutTimer = Wait.time(function() if coherencyMonitorActive then stopCoherencyMonitor() if playerColor then printToColor(string.format("Live coherency check auto-stopped after %ds.", COHERENCY_TIMEOUT), playerColor, {1, 1, 1}) end end end, COHERENCY_TIMEOUT) printToColor(string.format("Live coherency check ON for %d models. Orange = out of 2\" coherency, Red = out of 9\" range (one line per offending model). Lines follow models as you move them; auto-stops after %ds or RIGHT-CLICK Coherency to remove them.", #objs, COHERENCY_TIMEOUT), playerColor, {0, 1, 0}) end -- ==================================================================== -- Engagement Range -- Highlight every model on the table whose base is within ENGAGEMENT_RANGE -- inches of ANY currently-selected model, then keep that set live for -- ENGAGEMENT_HIGHLIGHT seconds: it re-scans every ENGAGEMENT_REFRESH seconds -- so models that move into range light up and models that move out go dark. -- Distances reuse the same base-to-base geometry (buildBaseDescriptor + -- baseGap) as the coherency monitor, so an oval base is measured edge-to-edge, -- rotation-correct. "Models" are every Figurine/Generic/Custom_Model on the -- table; the captured selection is excluded, and each neighbour counts once. -- ==================================================================== local ENGAGEMENT_RANGE = 2 -- inches, base-to-base local ENGAGEMENT_HIGHLIGHT = 15 -- seconds the live highlight runs local ENGAGEMENT_REFRESH = 0.15 -- seconds between re-scans during the window local ENGAGEMENT_COLOR = {1, 0.15, 0.15} -- red glow on neighbours in range local ENGAGEMENT_MAX_SELECTED = 30 -- skip an accidental whole-army box-select -- A table object counts as a "model" using the same tag set as -- SelectionHighlighter (Figurine / Generic / Custom_Model). local function isModelObject(o) local t = o.tag return t == "Figurine" or t == "Generic" or t == "Custom_Model" end -- Live monitor state. Selection is captured by GUID so it keeps tracking the -- same models as they (or their neighbours) move during the window. engagementMonitorActive = false engagementSelGuids = {} -- captured selection engagementHighlighted = {} -- guid -> true: models we are currently glowing engagementTimer = nil -- repeating ENGAGEMENT_REFRESH tick engagementTicksLeft = 0 -- ticks remaining before auto-stop function stopEngagementMonitor() engagementMonitorActive = false if engagementTimer ~= nil then Wait.stop(engagementTimer) engagementTimer = nil end -- Drop only the highlights we applied (leave any we never touched alone), and -- hand ownership back to SelectionHighlighter. for guid in pairs(engagementHighlighted) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then o.highlightOff() end releaseSelectionHighlight(guid) end engagementHighlighted = {} engagementSelGuids = {} engagementTicksLeft = 0 end -- One scan pass: resolve the captured selection to live models, find every -- other model within range, and reconcile glow on/off against last tick. -- Returns the in-range count, or nil if the whole selection is gone. local function engagementScan() local selectedSet, selDesc = {}, {} for _, guid in ipairs(engagementSelGuids) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then selectedSet[guid] = true selDesc[#selDesc + 1] = buildBaseDescriptor(o) end end if #selDesc == 0 then return nil end -- every selected model deleted local nowSet, count = {}, 0 for _, o in ipairs(getAllObjects()) do local guid = o.getGUID() if isModelObject(o) and not selectedSet[guid] and not nowSet[guid] then local od = buildBaseDescriptor(o) for _, sd in ipairs(selDesc) do if baseGap(sd, od) <= ENGAGEMENT_RANGE then nowSet[guid] = true count = count + 1 break end end end end -- Reconcile: only toggle models that crossed the threshold this tick. Take -- ownership from SelectionHighlighter while glowing (hold) and return it when -- the model leaves range (release), so the red can't be restored on deselect. for guid in pairs(engagementHighlighted) do if not nowSet[guid] then local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then o.highlightOff() end releaseSelectionHighlight(guid) end end for guid in pairs(nowSet) do if not engagementHighlighted[guid] then local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then holdSelectionHighlight(guid) o.highlightOn(ENGAGEMENT_COLOR) end end end engagementHighlighted = nowSet return count end function showEngagementRange(a, b, c) -- Support button (a=object, b=color, c=altClick) and hotkey (a=color) -- invocation, mirroring checkCoherency. local playerColor, alt if type(a) == "string" then playerColor = a alt = false else playerColor = b alt = c and true or false end -- Right click stops the monitor and clears the glow before the window ends. if alt then if engagementMonitorActive then stopEngagementMonitor() if playerColor then printToColor("Engagement range OFF.", playerColor, {1, 1, 1}) end elseif playerColor then printToColor("No engagement range is running.", playerColor, {1, 1, 1}) end return end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. tostring(playerColor)) return end local sel = playerObj.getSelectedObjects() if #sel < 1 then printToColor("Select at least one model to check engagement range.", playerColor, {1, 0.5, 0}) return end if #sel > ENGAGEMENT_MAX_SELECTED then broadcastToColor(string.format("Engagement range skipped: %d models selected (max %d).", #sel, ENGAGEMENT_MAX_SELECTED), playerColor, {1, 0.3, 0.3}) return end -- A fresh click fully replaces any prior run. if engagementMonitorActive then stopEngagementMonitor() end engagementSelGuids = {} for _, o in ipairs(sel) do engagementSelGuids[#engagementSelGuids + 1] = o.getGUID() end engagementMonitorActive = true engagementHighlighted = {} engagementTicksLeft = math.ceil(ENGAGEMENT_HIGHLIGHT / ENGAGEMENT_REFRESH) local count = engagementScan() or 0 if count == 0 then printToColor(string.format("No models within %g\" of the selection.", ENGAGEMENT_RANGE), playerColor, {1, 1, 1}) else broadcastToColor(string.format("%d model(s) within %g\" of the selection -- live for %ds (refreshing every %.1fs). RIGHT-CLICK Engagement Range to clear early.", count, ENGAGEMENT_RANGE, ENGAGEMENT_HIGHLIGHT, ENGAGEMENT_REFRESH), playerColor, {0, 1, 0}) end -- Refresh loop: re-scan every ENGAGEMENT_REFRESH seconds, auto-stop after -- the window elapses or once the whole selection is gone. engagementTimer = Wait.time(function() if not engagementMonitorActive then return end if engagementScan() == nil then stopEngagementMonitor() return end engagementTicksLeft = engagementTicksLeft - 1 if engagementTicksLeft <= 0 then stopEngagementMonitor() printToAll(string.format("Engagement range check finished (%ds elapsed).", ENGAGEMENT_HIGHLIGHT), {1, 1, 1}) end end, ENGAGEMENT_REFRESH, -1) end -- ==================================================================== -- Highlight painter -- LEFT-clicking Highlight paints the player's selected models with the colour -- currently shown on the swatch next to it; the swatch cycles its palette on -- click (right-click cycles backwards) and its face IS the chosen colour. -- Paint is permanent for the session: a paint applied while a model is selected -- is also written into SelectionHighlighter's restore-on-deselect snapshot -- (setPersistentHighlight), so deselecting restores the paint instead of wiping -- it. RIGHT-clicking Highlight removes highlights from every object on the -- table -- except models a live coherency/engagement monitor is glowing (their -- externalHolds in SelectionHighlighter), which clean up when the monitor stops. -- ==================================================================== local HIGHLIGHT_PALETTE = { {name = "Yellow", rgb = {1, 1, 0}}, {name = "Orange", rgb = {1, 0.5, 0}}, {name = "Red", rgb = {1, 0, 0}}, {name = "Pink", rgb = {1, 0.4, 0.7}}, {name = "Purple", rgb = {0.7, 0.2, 1}}, {name = "Blue", rgb = {0.2, 0.4, 1}}, {name = "Teal", rgb = {0, 0.9, 0.9}}, {name = "Green", rgb = {0, 1, 0}}, {name = "White", rgb = {1, 1, 1}}, } highlightColorIndex = 1 highlightSwatchIndex = nil -- resolved in onload after the swatch is created -- Repaint the swatch's top button to the chosen colour (hover/press too, so it -- reads as a solid colour chip) and name the colour in its tooltip. function refreshHighlightSwatch() if highlightSwatchIndex == nil then return end local c = HIGHLIGHT_PALETTE[highlightColorIndex] self.editButton({ index = highlightSwatchIndex, color = c.rgb, hover_color = c.rgb, press_color = c.rgb, tooltip = "Highlight color: " .. c.name .. "\nLEFT CLICK: next color\nRIGHT CLICK: previous color", }) end function cycleHighlightColor(_, _, alt) local step = alt and -1 or 1 highlightColorIndex = ((highlightColorIndex - 1 + step) % #HIGHLIGHT_PALETTE) + 1 refreshHighlightSwatch() end -- Remove highlights from every object on the table (both players' paint, and -- any stray glow), then reset SelectionHighlighter's tracking so currently -- selected models fall back to the plain player-colour selection glow on its -- next tick. Models glowed by a live monitor (held in SelectionHighlighter's -- externalHolds) are left alone -- the monitor owns their highlight lifecycle. function clearAllHighlightPaint(playerColor) local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) local holds = {} if sh ~= nil then holds = sh.call("getExternalHolds") or {} sh.call("clearPersistentHighlights") end local n = 0 for _, o in ipairs(getAllObjects()) do if holds[o.getGUID()] == nil then if o.getHighlightColor() ~= nil then n = n + 1 end o.highlightOff() end end if playerColor then printToColor(string.format("Removed highlights from %d object(s).", n), playerColor, {1, 1, 1}) end end -- LEFT click: paint the selection in the swatch colour. RIGHT click: clear all. function applyHighlightPaint(a, b, c) -- Support button (a=object, b=color, c=altClick) and hotkey (a=color) -- invocation, mirroring checkCoherency. local playerColor, alt if type(a) == "string" then playerColor = a alt = false else playerColor = b alt = c and true or false end if alt then clearAllHighlightPaint(playerColor) return end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. tostring(playerColor)) return end local sel = playerObj.getSelectedObjects() if #sel == 0 then printToColor("Select at least one model to highlight.", playerColor, {1, 0.5, 0}) return end local col = HIGHLIGHT_PALETTE[highlightColorIndex] local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) local n = 0 for _, o in ipairs(sel) do if not o.isDestroyed() then o.highlightOn(col.rgb) -- The model is selected, so SelectionHighlighter has snapshotted (or -- is about to snapshot) an "original" colour to restore on deselect; -- point that snapshot at our paint so deselecting keeps it. if sh ~= nil then sh.call("setPersistentHighlight", {guid = o.getGUID(), color = col.rgb}) end n = n + 1 end end printToColor(string.format("Highlighted %d model(s) in %s. RIGHT-CLICK Highlight to remove all highlights.", n, col.name), playerColor, col.rgb) end LuaScriptStateXmlUIAttachedSnapPoints=05Position&x @y?z32GUIDe446f7Name Custom_TokenTransformposXI@posY?posZ"@rotXrotYp@rotZscaleX?scaleY?scaleZ?NicknameCommand PointsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14512843363392006048/A97EBC67BEA8F30EFA83B95065EA18F268EF0412/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness@z?MergeDistancePixels.@StandUpStackableLuaScript7-- FTC-GUID: e446f7,deb9f2 -- Clean-token CP counter. Polls the token value every 0.5s and pushes changes -- to the VP Scores HUD. This catches direct token interactions that bypass -- startMenu's updateCpHUD(). FONT_COLOR_LIGHT = {1, 1, 1, 100} FONT_COLOR_DARK = {0, 0, 0, 100} FONT_SIZE = 750 FONT_SIZE_SMALL = 500 VALUE_POS = {0, 0.1, -0.45} VALUE_POS_SMALL = {0, 0.1, -0.2} TRANSPARENT = {0, 0, 0, 0} local _lastValue = nil local _watchId = nil local _timerId = nil function onLoad(save_state) loadCounterState(save_state) createUserInterface() _lastValue = getValue() local isP1 = (self.getGUID() == "deb9f2") _watchId = isP1 and "CPWatch1" or "CPWatch2" updateHUD() -- Re-push once Global.UI is guaranteed live: this object's onLoad can run -- before the (non-persistent) overlay is ready, in which case the immediate -- push above is silently dropped by the pcall in Global.setHudAttribute. Wait.time(updateHUD, 1) _timerId = Wait.time(checkValue, 0.5, -1) end function onSave() return JSON.encode(script_state) end function onDestroy() if _timerId then Wait.stop(_timerId) _timerId = nil end end function loadCounterState(save_state) if save_state == nil or save_state == "" then script_state = defaultCounterState() return end local ok, decoded = pcall(JSON.decode, save_state) script_state = (ok and type(decoded) == "table") and decoded or defaultCounterState() normalizeCounterState() end function defaultCounterState() return { value = 0, label = self.getName(), use_prefix = false, light_font = true, } end function normalizeCounterState() if script_state == nil then script_state = defaultCounterState() end script_state.value = tonumber(script_state.value) or 0 if script_state.label == nil or script_state.label == "" then script_state.label = self.getName() end if script_state.use_prefix == nil then script_state.use_prefix = false end if script_state.light_font == nil then script_state.light_font = true end end function createUserInterface() self.createButton({ click_function = "onIncrementDecrementClick", tooltip = "+/-", function_owner = self, position = {0, 0.1, 1}, height = 600, width = 1500, font_size = 250, color = TRANSPARENT }) self.createButton({ click_function = "onIncrementDecrementClick", function_owner = self, label = getValue(), position = VALUE_POS, height = 800, width = 1500, font_size = FONT_SIZE, color = TRANSPARENT, font_color = FONT_COLOR_LIGHT }) self.addContextMenuItem("Toggle Prefix", togglePrefix, false) self.addContextMenuItem("Toggle Font Color", toggleFont, false) refreshTokenUI() end function togglePrefix() script_state.use_prefix = not script_state.use_prefix refreshTokenUI() end function toggleFont() script_state.light_font = not script_state.light_font refreshTokenUI() end function onIncrementDecrementClick(obj, color, alt) if alt then decrement() else increment() end end function refreshTokenUI() script_state.label = self.getName() local display_font_size = FONT_SIZE local display_value_pos = VALUE_POS if script_state.use_prefix then if math.abs(script_state.value) > 99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end else if script_state.value > 999 or script_state.value < -99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end end local display_font_color = script_state.light_font and FONT_COLOR_LIGHT or FONT_COLOR_DARK local display_value = tostring(script_state.value) if script_state.use_prefix then display_value = (script_state.value >= 0 and "+" or "") .. tostring(script_state.value) end self.editButton({ index = 0, label = "", font_color = display_font_color, }) self.editButton({ index = 1, label = display_value, font_size = display_font_size, position = display_value_pos, font_color = display_font_color, }) end function checkValue() local current = getValue() if current ~= _lastValue then local previous = _lastValue _lastValue = current Global.call("setHudAttribute", {id = _watchId, attr = "text", value = "CP: " .. current}) announceCpChange(previous, current) end end -- Push the current CP value into the overlay without announcing it in chat. -- Used on load (the overlay is non-persistent and resets to "CP: 0") and shares -- the same id mapping as the change poller above. function updateHUD() Global.call("setHudAttribute", {id = _watchId, attr = "text", value = "CP: " .. getValue()}) end -- Called by startMenu right after an automatic turn-pass CP gain so this poller -- treats the new value as its baseline and doesn't re-announce it as a manual change. function markBaseline() _lastValue = getValue() end -- Announce a manual CP change (HUD +/- buttons, overlay, or clicking the token). -- Does not track who clicked - only which player's CP value changed and the -- direction - so it also covers one player editing the other player's counter. function announceCpChange(previous, current) local playerColor = (self.getGUID() == "deb9f2") and "Blue" or "Red" local action = (current > previous) and "added" or "removed" local startMenu = getObjectFromGUID(Global.getVar("startMenu_GUID")) local label = (startMenu and startMenu.call("getTurnStateLabel")) or "" printToAll(label .. " - Player " .. playerColor .. " " .. action .. " a CP " .. previous .. " -> " .. current, "Yellow") end function getValue() normalizeCounterState() return script_state.value end function setValue(value) normalizeCounterState() script_state.value = tonumber(value) or 0 refreshTokenUI() return script_state.value end function increment() return setValue(getValue() + 1) end function decrement() return setValue(getValue() - 1) end LuaScriptStateJ{"label":"Command Points","light_font":true,"use_prefix":false,"value":0}XmlUI33GUIDdeb9f2Name Custom_TokenTransformposXI@posY?posZ"rotXrotYp@rotZscaleX?scaleY?scaleZ?NicknameCommand PointsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9295168801665435989/B13632CE65F8308D85AF8A1C0582D2F8632A933E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness@z?MergeDistancePixels.@StandUpStackableLuaScript7-- FTC-GUID: e446f7,deb9f2 -- Clean-token CP counter. Polls the token value every 0.5s and pushes changes -- to the VP Scores HUD. This catches direct token interactions that bypass -- startMenu's updateCpHUD(). FONT_COLOR_LIGHT = {1, 1, 1, 100} FONT_COLOR_DARK = {0, 0, 0, 100} FONT_SIZE = 750 FONT_SIZE_SMALL = 500 VALUE_POS = {0, 0.1, -0.45} VALUE_POS_SMALL = {0, 0.1, -0.2} TRANSPARENT = {0, 0, 0, 0} local _lastValue = nil local _watchId = nil local _timerId = nil function onLoad(save_state) loadCounterState(save_state) createUserInterface() _lastValue = getValue() local isP1 = (self.getGUID() == "deb9f2") _watchId = isP1 and "CPWatch1" or "CPWatch2" updateHUD() -- Re-push once Global.UI is guaranteed live: this object's onLoad can run -- before the (non-persistent) overlay is ready, in which case the immediate -- push above is silently dropped by the pcall in Global.setHudAttribute. Wait.time(updateHUD, 1) _timerId = Wait.time(checkValue, 0.5, -1) end function onSave() return JSON.encode(script_state) end function onDestroy() if _timerId then Wait.stop(_timerId) _timerId = nil end end function loadCounterState(save_state) if save_state == nil or save_state == "" then script_state = defaultCounterState() return end local ok, decoded = pcall(JSON.decode, save_state) script_state = (ok and type(decoded) == "table") and decoded or defaultCounterState() normalizeCounterState() end function defaultCounterState() return { value = 0, label = self.getName(), use_prefix = false, light_font = true, } end function normalizeCounterState() if script_state == nil then script_state = defaultCounterState() end script_state.value = tonumber(script_state.value) or 0 if script_state.label == nil or script_state.label == "" then script_state.label = self.getName() end if script_state.use_prefix == nil then script_state.use_prefix = false end if script_state.light_font == nil then script_state.light_font = true end end function createUserInterface() self.createButton({ click_function = "onIncrementDecrementClick", tooltip = "+/-", function_owner = self, position = {0, 0.1, 1}, height = 600, width = 1500, font_size = 250, color = TRANSPARENT }) self.createButton({ click_function = "onIncrementDecrementClick", function_owner = self, label = getValue(), position = VALUE_POS, height = 800, width = 1500, font_size = FONT_SIZE, color = TRANSPARENT, font_color = FONT_COLOR_LIGHT }) self.addContextMenuItem("Toggle Prefix", togglePrefix, false) self.addContextMenuItem("Toggle Font Color", toggleFont, false) refreshTokenUI() end function togglePrefix() script_state.use_prefix = not script_state.use_prefix refreshTokenUI() end function toggleFont() script_state.light_font = not script_state.light_font refreshTokenUI() end function onIncrementDecrementClick(obj, color, alt) if alt then decrement() else increment() end end function refreshTokenUI() script_state.label = self.getName() local display_font_size = FONT_SIZE local display_value_pos = VALUE_POS if script_state.use_prefix then if math.abs(script_state.value) > 99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end else if script_state.value > 999 or script_state.value < -99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end end local display_font_color = script_state.light_font and FONT_COLOR_LIGHT or FONT_COLOR_DARK local display_value = tostring(script_state.value) if script_state.use_prefix then display_value = (script_state.value >= 0 and "+" or "") .. tostring(script_state.value) end self.editButton({ index = 0, label = "", font_color = display_font_color, }) self.editButton({ index = 1, label = display_value, font_size = display_font_size, position = display_value_pos, font_color = display_font_color, }) end function checkValue() local current = getValue() if current ~= _lastValue then local previous = _lastValue _lastValue = current Global.call("setHudAttribute", {id = _watchId, attr = "text", value = "CP: " .. current}) announceCpChange(previous, current) end end -- Push the current CP value into the overlay without announcing it in chat. -- Used on load (the overlay is non-persistent and resets to "CP: 0") and shares -- the same id mapping as the change poller above. function updateHUD() Global.call("setHudAttribute", {id = _watchId, attr = "text", value = "CP: " .. getValue()}) end -- Called by startMenu right after an automatic turn-pass CP gain so this poller -- treats the new value as its baseline and doesn't re-announce it as a manual change. function markBaseline() _lastValue = getValue() end -- Announce a manual CP change (HUD +/- buttons, overlay, or clicking the token). -- Does not track who clicked - only which player's CP value changed and the -- direction - so it also covers one player editing the other player's counter. function announceCpChange(previous, current) local playerColor = (self.getGUID() == "deb9f2") and "Blue" or "Red" local action = (current > previous) and "added" or "removed" local startMenu = getObjectFromGUID(Global.getVar("startMenu_GUID")) local label = (startMenu and startMenu.call("getTurnStateLabel")) or "" printToAll(label .. " - Player " .. playerColor .. " " .. action .. " a CP " .. previous .. " -> " .. current, "Yellow") end function getValue() normalizeCounterState() return script_state.value end function setValue(value) normalizeCounterState() script_state.value = tonumber(value) or 0 refreshTokenUI() return script_state.value end function increment() return setValue(getValue() + 1) end function decrement() return setValue(getValue() - 1) end LuaScriptStateJ{"label":"Command Points","light_font":true,"use_prefix":false,"value":0}XmlUI34pGUIDee92cfName Custom_TokenTransformposXI@posY?posZrotX JrotYp@rotZ`;scaleX?scaleY?scaleZ?Nickname Game RoundsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18162471125803326904/D62AD73C8A44A435542FE4894ED7059F9FD51E0C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness@z?MergeDistancePixels.@StandUpStackableLuaScript-- FTC-GUID: ee92cf FONT_COLOR_LIGHT = {1, 1, 1, 100} FONT_COLOR_DARK = {0, 0, 0, 100} FONT_SIZE = 750 FONT_SIZE_SMALL = 500 VALUE_POS = {0, 0.1, -0.45} VALUE_POS_SMALL = {0, 0.1, -0.2} TRANSPARENT = {0, 0, 0, 0} local _lastValue = nil local _timerId = nil titleBtn={ index=1, label="Game rounds", click_function="none", function_owner=self, position={0,0.3,0.23}, rotation={0,180,0}, height=0, width=00, font_size=40, color={0,0,0}, font_color={1,1,1} } btnHeight=300 btnWidth=500 btnXPos=0.7 btnYPos=0.25 btnZPos=-0.68 endLbl="Game Over" continueLbl="Continue\n3+" endBtn={ index=1, label=endLbl, click_function="gameOver", function_owner=self, position={-btnXPos,btnYPos,btnZPos}, rotation={0,180,0}, height=btnHeight, width=btnWidth, font_size=90, color={1,0,0}, font_color={1,1,1} } continueBtn={ index=1, label=continueLbl, click_function="gameContinues", function_owner=self, position={btnXPos,btnYPos,btnZPos}, rotation={0,180,0}, height=btnHeight, width=btnWidth, font_size=90, color={0,0.6,0}, font_color={1,1,1} } function onLoad(save_state) loadCounterState(save_state) createUserInterface() startObject=getObjectFromGUID(Global.getVar("startMenu_GUID")) scoreSheet=getObjectFromGUID(Global.getVar("scoreSheet_GUID")) _lastValue = getValue() updateHUD() -- Re-push once Global.UI is guaranteed live (see note in cpCounter): the -- overlay is non-persistent and the immediate push can lose the load race. Wait.time(updateHUD, 1) _timerId = Wait.time(checkValue, 0.5, -1) end function onSave() return JSON.encode(script_state) end function onDestroy() if _timerId then Wait.stop(_timerId) _timerId = nil end end function loadCounterState(save_state) if save_state == nil or save_state == "" then script_state = defaultCounterState() return end local ok, decoded = pcall(JSON.decode, save_state) script_state = (ok and type(decoded) == "table") and decoded or defaultCounterState() normalizeCounterState() end function defaultCounterState() return { value = 0, label = self.getName(), use_prefix = false, light_font = true, } end function normalizeCounterState() if script_state == nil then script_state = defaultCounterState() end script_state.value = tonumber(script_state.value) or 0 if script_state.label == nil or script_state.label == "" then script_state.label = self.getName() end if script_state.use_prefix == nil then script_state.use_prefix = false end if script_state.light_font == nil then script_state.light_font = true end end function createUserInterface() self.createButton({ click_function = "onIncrementDecrementClick", tooltip = "+/-", function_owner = self, position = {0, 0.1, 1}, height = 600, width = 1500, font_size = 250, color = TRANSPARENT }) self.createButton({ click_function = "onIncrementDecrementClick", function_owner = self, label = getValue(), position = VALUE_POS, height = 800, width = 1500, font_size = FONT_SIZE, color = TRANSPARENT, font_color = FONT_COLOR_LIGHT }) self.addContextMenuItem("Toggle Prefix", togglePrefix, false) self.addContextMenuItem("Toggle Font Color", toggleFont, false) refreshTokenUI() end function togglePrefix() script_state.use_prefix = not script_state.use_prefix refreshTokenUI() end function toggleFont() script_state.light_font = not script_state.light_font refreshTokenUI() end function onIncrementDecrementClick(obj, color, alt) if alt then decrement() else increment() end end function refreshTokenUI() script_state.label = self.getName() local display_font_size = FONT_SIZE local display_value_pos = VALUE_POS if script_state.use_prefix then if math.abs(script_state.value) > 99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end else if script_state.value > 999 or script_state.value < -99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end end local display_font_color = script_state.light_font and FONT_COLOR_LIGHT or FONT_COLOR_DARK local display_value = tostring(script_state.value) if script_state.use_prefix then display_value = (script_state.value >= 0 and "+" or "") .. tostring(script_state.value) end self.editButton({ index = 0, label = "", font_color = display_font_color, }) self.editButton({ index = 1, label = display_value, font_size = display_font_size, position = display_value_pos, font_color = display_font_color, }) end function checkValue() local current = getValue() if current ~= _lastValue then _lastValue = current updateHUD() if startObject then startObject.call("newRoundStarted") end if current == 6 then broadcastToAll("GAME OVER - feel free to continue playing!", "Yellow") end end end function updateHUD() _lastValue = getValue() Global.call("setHudValue", {id = "RoundWatch", value = tostring(_lastValue)}) end function getValue() normalizeCounterState() return script_state.value end function setValue(value) normalizeCounterState() script_state.value = tonumber(value) or 0 refreshTokenUI() return script_state.value end function increment() return setValue(getValue() + 1) end function decrement() return setValue(getValue() - 1) end function gameOver() self.clearButtons() startObject.setVar("checkEnd", false) startObject.call("writeMenus") local scoreA = scoreSheet.call("playerSum", {1}) local scoreB = scoreSheet.call("playerSum", {2}) if scoreA > scoreB then broadcastToAll("Red Player WINS !", "Green") end if scoreA < scoreB then broadcastToAll("Blue Player WINS !", "Green") end if scoreA == scoreB then broadcastToAll("Game ends in a DRAW !", "Green") end end function gameContinues() self.clearButtons() startObject.setVar("checkEnd", false) startObject.call("writeMenus") end function none() end LuaScriptStateG{"label":"Game Rounds","light_font":true,"use_prefix":false,"value":0}XmlUI35(GUID055302Name Custom_TokenTransformposXI@posY?posZ@rotX̦ >rotYp@rotZscaleX?scaleY?scaleZ?Nickname Red TurnsDescriptionee92cfGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15939313885044172990/AD8C9297B670569ADE12B1F36A7A9AC5E946EEF1/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness@z?MergeDistancePixels.@StandUpStackableLuaScripts-- FTC-GUID: 7e4111,055302 -- Description contains the guid of game turn counter FONT_COLOR_LIGHT = {1, 1, 1, 100} FONT_COLOR_DARK = {0, 0, 0, 100} FONT_SIZE = 750 FONT_SIZE_SMALL = 500 VALUE_POS = {0, 0.1, -0.45} VALUE_POS_SMALL = {0, 0.1, -0.2} TRANSPARENT = {0, 0, 0, 0} local _lastValue = nil local _watchId = nil local _timerId = nil function onLoad(save_state) loadCounterState(save_state) createUserInterface() _lastValue = getValue() _watchId = (self.getGUID() == "7e4111") and "TVWatch1" or "TVWatch2" updateHUD() -- Re-push once Global.UI is guaranteed live (see note in cpCounter): the -- overlay is non-persistent and the immediate push can lose the load race. Wait.time(updateHUD, 1) _timerId = Wait.time(checkValue, 0.5, -1) end function onSave() return JSON.encode(script_state) end function onDestroy() if _timerId then Wait.stop(_timerId) _timerId = nil end end function loadCounterState(save_state) if save_state == nil or save_state == "" then script_state = defaultCounterState() return end local ok, decoded = pcall(JSON.decode, save_state) script_state = (ok and type(decoded) == "table") and decoded or defaultCounterState() normalizeCounterState() end function defaultCounterState() return { value = 0, label = self.getName(), use_prefix = false, light_font = true, } end function normalizeCounterState() if script_state == nil then script_state = defaultCounterState() end script_state.value = tonumber(script_state.value) or 0 if script_state.label == nil or script_state.label == "" then script_state.label = self.getName() end if script_state.use_prefix == nil then script_state.use_prefix = false end if script_state.light_font == nil then script_state.light_font = true end end function createUserInterface() self.createButton({ click_function = "onIncrementDecrementClick", tooltip = "+/-", function_owner = self, position = {0, 0.1, 1}, height = 600, width = 1500, font_size = 250, color = TRANSPARENT }) self.createButton({ click_function = "onIncrementDecrementClick", function_owner = self, label = getValue(), position = VALUE_POS, height = 800, width = 1500, font_size = FONT_SIZE, color = TRANSPARENT, font_color = FONT_COLOR_LIGHT }) self.addContextMenuItem("Toggle Prefix", togglePrefix, false) self.addContextMenuItem("Toggle Font Color", toggleFont, false) refreshTokenUI() end function togglePrefix() script_state.use_prefix = not script_state.use_prefix refreshTokenUI() end function toggleFont() script_state.light_font = not script_state.light_font refreshTokenUI() end function onIncrementDecrementClick(obj, color, alt) if alt then decrement() else increment() end end function refreshTokenUI() script_state.label = self.getName() local display_font_size = FONT_SIZE local display_value_pos = VALUE_POS if script_state.use_prefix then if math.abs(script_state.value) > 99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end else if script_state.value > 999 or script_state.value < -99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end end local display_font_color = script_state.light_font and FONT_COLOR_LIGHT or FONT_COLOR_DARK local display_value = tostring(script_state.value) if script_state.use_prefix then display_value = (script_state.value >= 0 and "+" or "") .. tostring(script_state.value) end self.editButton({ index = 0, label = "", font_color = display_font_color, }) self.editButton({ index = 1, label = display_value, font_size = display_font_size, position = display_value_pos, font_color = display_font_color, }) end function checkValue() local current = getValue() if current ~= _lastValue then _lastValue = current updateHUD() end end function updateHUD() _lastValue = getValue() Global.call("setHudAttribute", {id = _watchId, attr = "text", value = "Turn " .. _lastValue}) end function increaseSelf() increment() updateHUD() gameUpdate() end function gameUpdate() guidGC = self.getDescription() gameTurn = getObjectFromGUID(guidGC) -- required to inform startMenu of change in Round startMenu = getObjectFromGUID(Global.getVar("startMenu_GUID")) if gameTurn == nil then else if getValue() > gameTurn.call("getValue") then gameTurn.call("setValue", getValue()) if startMenu then startMenu.call("newRoundStarted") end end end end function getValue() normalizeCounterState() return script_state.value end function setValue(value) normalizeCounterState() script_state.value = tonumber(value) or 0 refreshTokenUI() return script_state.value end function increment() return setValue(getValue() + 1) end function decrement() return setValue(getValue() - 1) end LuaScriptStateE{"label":"Red Turns","light_font":true,"use_prefix":false,"value":0}XmlUI36*GUID7e4111Name Custom_TokenTransformposXI@posY?posZrotX̦ >rotYp@rotZscaleX?scaleY?scaleZ?Nickname Blue TurnsDescriptionee92cfGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10750067844035422829/07D45CB5BFA2797712B9D14914AC0C06AE8B4F9E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness@z?MergeDistancePixels.@StandUpStackableLuaScripts-- FTC-GUID: 7e4111,055302 -- Description contains the guid of game turn counter FONT_COLOR_LIGHT = {1, 1, 1, 100} FONT_COLOR_DARK = {0, 0, 0, 100} FONT_SIZE = 750 FONT_SIZE_SMALL = 500 VALUE_POS = {0, 0.1, -0.45} VALUE_POS_SMALL = {0, 0.1, -0.2} TRANSPARENT = {0, 0, 0, 0} local _lastValue = nil local _watchId = nil local _timerId = nil function onLoad(save_state) loadCounterState(save_state) createUserInterface() _lastValue = getValue() _watchId = (self.getGUID() == "7e4111") and "TVWatch1" or "TVWatch2" updateHUD() -- Re-push once Global.UI is guaranteed live (see note in cpCounter): the -- overlay is non-persistent and the immediate push can lose the load race. Wait.time(updateHUD, 1) _timerId = Wait.time(checkValue, 0.5, -1) end function onSave() return JSON.encode(script_state) end function onDestroy() if _timerId then Wait.stop(_timerId) _timerId = nil end end function loadCounterState(save_state) if save_state == nil or save_state == "" then script_state = defaultCounterState() return end local ok, decoded = pcall(JSON.decode, save_state) script_state = (ok and type(decoded) == "table") and decoded or defaultCounterState() normalizeCounterState() end function defaultCounterState() return { value = 0, label = self.getName(), use_prefix = false, light_font = true, } end function normalizeCounterState() if script_state == nil then script_state = defaultCounterState() end script_state.value = tonumber(script_state.value) or 0 if script_state.label == nil or script_state.label == "" then script_state.label = self.getName() end if script_state.use_prefix == nil then script_state.use_prefix = false end if script_state.light_font == nil then script_state.light_font = true end end function createUserInterface() self.createButton({ click_function = "onIncrementDecrementClick", tooltip = "+/-", function_owner = self, position = {0, 0.1, 1}, height = 600, width = 1500, font_size = 250, color = TRANSPARENT }) self.createButton({ click_function = "onIncrementDecrementClick", function_owner = self, label = getValue(), position = VALUE_POS, height = 800, width = 1500, font_size = FONT_SIZE, color = TRANSPARENT, font_color = FONT_COLOR_LIGHT }) self.addContextMenuItem("Toggle Prefix", togglePrefix, false) self.addContextMenuItem("Toggle Font Color", toggleFont, false) refreshTokenUI() end function togglePrefix() script_state.use_prefix = not script_state.use_prefix refreshTokenUI() end function toggleFont() script_state.light_font = not script_state.light_font refreshTokenUI() end function onIncrementDecrementClick(obj, color, alt) if alt then decrement() else increment() end end function refreshTokenUI() script_state.label = self.getName() local display_font_size = FONT_SIZE local display_value_pos = VALUE_POS if script_state.use_prefix then if math.abs(script_state.value) > 99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end else if script_state.value > 999 or script_state.value < -99 then display_font_size = FONT_SIZE_SMALL display_value_pos = VALUE_POS_SMALL end end local display_font_color = script_state.light_font and FONT_COLOR_LIGHT or FONT_COLOR_DARK local display_value = tostring(script_state.value) if script_state.use_prefix then display_value = (script_state.value >= 0 and "+" or "") .. tostring(script_state.value) end self.editButton({ index = 0, label = "", font_color = display_font_color, }) self.editButton({ index = 1, label = display_value, font_size = display_font_size, position = display_value_pos, font_color = display_font_color, }) end function checkValue() local current = getValue() if current ~= _lastValue then _lastValue = current updateHUD() end end function updateHUD() _lastValue = getValue() Global.call("setHudAttribute", {id = _watchId, attr = "text", value = "Turn " .. _lastValue}) end function increaseSelf() increment() updateHUD() gameUpdate() end function gameUpdate() guidGC = self.getDescription() gameTurn = getObjectFromGUID(guidGC) -- required to inform startMenu of change in Round startMenu = getObjectFromGUID(Global.getVar("startMenu_GUID")) if gameTurn == nil then else if getValue() > gameTurn.call("getValue") then gameTurn.call("setValue", getValue()) if startMenu then startMenu.call("newRoundStarted") end end end end function getValue() normalizeCounterState() return script_state.value end function setValue(value) normalizeCounterState() script_state.value = tonumber(value) or 0 refreshTokenUI() return script_state.value end function increment() return setValue(getValue() + 1) end function decrement() return setValue(getValue() - 1) end LuaScriptStateF{"label":"Blue Turns","light_font":true,"use_prefix":false,"value":0}XmlUI372GUIDafc863NameCustom_AssetbundleTransformposXf=posY \#posZTrotX>rotY@WrotZ@K$оscaleX?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978795929/723C50F43FAB3DE3DC12CB8460536E8CB34B60A3/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI382GUIDc8edcaNameCustom_AssetbundleTransformposXf=posY \#posZT@rotX>rotYV@rotZ@K$оscaleX?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978795929/723C50F43FAB3DE3DC12CB8460536E8CB34B60A3/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI392GUID393bf7NameCustom_AssetbundleTransformposX@Q@posY \#posZT@rotX>rotYf@rotZ@K$оscaleX?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978795929/723C50F43FAB3DE3DC12CB8460536E8CB34B60A3/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI402GUID12c65eNameCustom_AssetbundleTransformposX@Q@posY \#posZTrotX>rotYp@rotZ@K$оscaleX?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978795929/723C50F43FAB3DE3DC12CB8460536E8CB34B60A3/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI412GUIDf938a2NameCustom_AssetbundleTransformposX4@posY \#posZTrotX ܥ<rotYrotZscaleX`ff@scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978796471/14ED0DBD593370733A0309B0950004F33EB9FACA/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI422GUID9f95fdNameCustom_AssetbundleTransformposX f=posY \#posZ+irotXrotYV@rotZscaleX` @scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978796471/14ED0DBD593370733A0309B0950004F33EB9FACA/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI432GUID35b95fNameCustom_AssetbundleTransformposX4@posY \#posZT@rotX ܥ<rotYf@rotZscaleX`ff@scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978796471/14ED0DBD593370733A0309B0950004F33EB9FACA/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI442GUID5af8f2NameCustom_AssetbundleTransformposX@Q@posY \#posZпrotXrotYp@rotZscaleX}@scaleY?scaleZ ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomAssetbundleAssetbundleURLihttps://steamusercontent-a.akamaihd.net/ugc/879750610978796471/14ED0DBD593370733A0309B0950004F33EB9FACA/AssetbundleSecondaryURLMaterialIndexTypeIndexLoopingEffectIndexLuaScriptLuaScriptStateXmlUI456GUID70b9f6NameCustom_Model_BagTransformposXD@posYposZVrotXrotYf@rotZscaleX?scaleY?scaleZ?NicknameExtract terrainDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexNumberCustomMeshMeshURLhhttps://steamusercontent-a.akamaihd.net/ugc/94979895601488369/894F4E9B36D9116E12A24DAAFE1B4FD26A1880D2/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsBagOrderLuaScriptD3-- FTC-GUID: 70b9f6 keepForTerrainEditor = true playZone=nil playZoneData={ type = "ScriptingTrigger", position = {x=0, y=10, z=0}, rotation = {x=0, y=0, z=0}, scale = {x=72.2, y=100, z=48.2}, sound = false, snap_to_grid = false, } playZoneScale= {{x=48.2, y=100, z=48.2} , {x=72.2, y=100, z=48.2}, {x=96.2, y=100, z=48.2}, {x=30.2, y=100, z=22.2}, {x=72.2, y=100, z=72.2}, {x=96.2, y=100, z=72.2} } selectedMat=2 -- Resolved by assignVariables once Global is ready (this script can load -- before Global's onLoad has set the *_GUID variables). mat_GUID = nil felt_GUID = nil tableSideBottom_GUID = nil -- Defer a callback until Global's onLoad has run (it sets FTC_Global_Ready -- last). After 10s run it anyway rather than hang a broken load forever. function whenGlobalReady(callback) Wait.condition(callback, function() return Global.getVar("FTC_Global_Ready") == true end, 10, callback) end function onLoad() whenGlobalReady(assignVariables) --createExtractButton() self.setPosition({40,-2,-90}) self.setRotation({0,180,0}) self.interactable = false self.setScale({1,0.5,1}) end extractLbl="EXTRACT\nTERRAIN" subtitleLbl="Confirm size first" subtitleLbl="" scaleBtn={6,6,6} offsetX=-5 extractBtn={ label=extractLbl, click_function="showYNButtonsExtract", function_owner=self, position={0+offsetX,0,17}, rotation={0,0,0}, height=750, width=2000, font_size=300, color={0.8,0.8,0.8}, font_color={0,0,0}, scale=scaleBtn } subtitleBtn={ label=subtitleLbl, click_function="none", function_owner=self, position={0+offsetX,0,20.8}, rotation={0,0,0}, height=0, width=0, font_size=100, color={0.8,0.8,0.8}, font_color="Red", scale=scaleBtn } backgroundBtn={ label="", click_function="none", function_owner=self, position={0+offsetX,-1,18}, rotation={0,0,0}, height=2000, width=2500, font_size=300, color={0.4,0.4,0.4,0.7}, font_color={1,1,1}, scale=scaleBtn } confirmYesBtnExtract={ label="Yes", click_function="extract", function_owner=self, position={-7+offsetX,0,29}, rotation={0,0,0}, height=750, width=820, font_size=300, color={0,0.6,0}, font_color={1,1,1}, scale=scaleBtn } confirmNoBtnExtract={ label="No", click_function="hideYNButtonsExtract", function_owner=self, position={7+offsetX,0,29}, rotation={0,0,0}, height=750, width=820, font_size=300, color={0.6,0,0}, font_color={1,1,1}, scale=scaleBtn } infoBtn={ label="TERRAIN\nEXPORTING\nINSTRUCTIONS", click_function="showInstructions", function_owner=self, position={0+offsetX,0,36}, rotation={0,0,0}, height=1000, width=2000, font_size=200, color={0,0,0}, font_color={1,1,1}, scale=scaleBtn } deleteLbl="DELETE\nTERRAIN" deleteBtn={ label=deleteLbl, click_function="showYNButtonsDelete", function_owner=self, position={0+offsetX,0,46}, rotation={0,0,0}, height=750, width=2000, font_size=300, color={0.8,0.8,0.8}, font_color={0,0,0}, scale=scaleBtn } confirmYesBtnDelete={ label="Yes", click_function="deleteTerrain", function_owner=self, position={-7+offsetX,0,57}, rotation={0,0,0}, height=750, width=820, font_size=300, color={0,0.6,0}, font_color={1,1,1}, scale=scaleBtn } confirmNoBtnDelete={ label="No", click_function="hideYNButtonsDelete", function_owner=self, position={7+offsetX,0,57}, rotation={0,0,0}, height=750, width=820, font_size=300, color={0.6,0,0}, font_color={1,1,1}, scale=scaleBtn } mirrorLbl="MIRROR\nTERRAIN" mirrorBtn={ label=mirrorLbl, click_function="checkMirror", function_owner=self, position={-36+offsetX,0,17}, rotation={0,0,0}, height=750, width=2000, font_size=300, color={0.8,0.8,0.8}, font_color={0,0,0}, scale=scaleBtn } confirmYesBtnMirror={ label="Yes", click_function="mirrorTerrain", function_owner=self, position={-43+offsetX,0,29}, rotation={0,0,0}, height=750, width=820, font_size=300, color={0,0.6,0}, font_color={1,1,1}, scale=scaleBtn } confirmNoBtnMirror={ label="No", click_function="hideYNButtonsMirror", function_owner=self, position={-29+offsetX,0,29}, rotation={0,0,0}, height=750, width=820, font_size=300, color={0.6,0,0}, font_color={1,1,1}, scale=scaleBtn } function assignVariables() mat_GUID = Global.getVar("mat_GUID") felt_GUID = Global.getVar("felt_GUID") tableSideBottom_GUID = Global.getVar("tableSideBottom_GUID") table=getObjectFromGUID(Global.getVar("table_GUID")) mat=getObjectFromGUID(mat_GUID) if mat then mat.setPosition({0,mat.getPosition().y,mat.getPosition().z}) end matUrlDisplay=getObjectFromGUID(Global.getVar("matURLDisplay_GUID")) Wait.frames(hideOther, 5) if matUrlDisplay then matUrlDisplay.setPosition({185,-6,0}) end flexControl=getObjectFromGUID(Global.getVar("flexControl_GUID")) YNButtonsExtract=false YNButtonsDelete=false label0="E X P O R T\nT E R R A I N" adjust=3.5 scaleBtn=6 offsetBtn=-8.2*adjust offsetYBtn=2.8*adjust txtSize=120 end function hideOther() matUrlDisplay.setInvisibleTo({"Red", "Blue", "Black", "Grey", "White"}) end function showInstructions() Global.call("openTerrain") end function writeMenu() self.clearButtons() if YNButtonsExtract==false then extractBtn.label=extractLbl else extractBtn.label="Are you sure?" self.createButton(confirmYesBtnExtract) self.createButton(confirmNoBtnExtract) end if YNButtonsDelete == false then deleteBtn.label=deleteLbl else deleteBtn.label="Are you sure?" self.createButton(confirmYesBtnDelete) self.createButton(confirmNoBtnDelete) end if YNButtonsMirror==false then mirrorBtn.label=mirrorLbl else mirrorBtn.label="Are you sure?" self.createButton(confirmYesBtnMirror) self.createButton(confirmNoBtnMirror) end createMenu() end function createMenu() createExtractButton() createDeleteButton() createMirrorButton() end function createExtractButton() --self.createButton(backgroundBtn) if extractBtn.label == extractLbl then self.createButton(subtitleBtn) end self.createButton(extractBtn) --self.createButton(infoBtn) end function showYNButtonsExtract() YNButtonsExtract = true writeMenu() end function hideYNButtonsExtract() YNButtonsExtract = false writeMenu() end function createDeleteButton() self.createButton(deleteBtn) end function showYNButtonsDelete() YNButtonsDelete = true writeMenu() end function hideYNButtonsDelete() YNButtonsDelete = false writeMenu() end function createMirrorButton() self.createButton(mirrorBtn) end function hideYNButtonsMirror() YNButtonsMirror = false writeMenu() end function extract() broadcastToAll("EXTRACTING TERRAIN\nwait a few seconds ...", "Yellow") mat=getObjectFromGUID(Global.getVar("mat_GUID")) createDebugMat() local startMenu=getObjectFromGUID(Global.getVar("startMenu_GUID")) startMenu.call("recallAll") Wait.time(extract2, 2) end function extract2() matUrlDisplay.call('retriveUrl') matUrlDisplay.clearInputs() matUrlDisplay.clearButtons() matUrlDisplay.setPosition({-38,-0.53,0}) matUrlDisplay.setInvisibleTo({"Red", "Blue", "Black", "Grey", "White"}) matUrlDisplay.interactable=false spawnPlayZone() Wait.frames(extract3, 3) end function extract3() local allObj=getAllObjects() local zoneObj=playZone.getObjects() for i, objA in ipairs(allObj) do local delete=true for j, objZ in ipairs (zoneObj) do if objA == objZ or objA == self or objA == matUrlDisplay then delete=false objZ.setLock(true) --objZ.setDescription("") end end if delete then objA.destroy() end end deleteTable() Wait.frames(injectLua, 2) --self.setPosition({37,2,0}) --self.destroy() end function createDebugMat() local customInfo = mat.getCustomObject() --print("MAT :"..customInfo.diffuse..":") local clone=spawnObject({ type = "Custom_Model", position = mat.getPosition(), params = {mesh="https://steamusercontent-a.akamaihd.net/ugc/879750610978796176/4A5A65543B98BCFBF57E910D06EC984208223D38/",diffuse="https://steamusercontent-a.akamaihd.net/ugc/786374678935692054/F32489A74087FB9D9E26AC5F9461841B8C40B37A/"}, }) clone.setLuaScript("") --clone.setCustomObject(mat.getCustomObject()) clone.setCustomObject({mesh="https://steamusercontent-a.akamaihd.net/ugc/879750610978796176/4A5A65543B98BCFBF57E910D06EC984208223D38/", diffuse=mat.getCustomObject().diffuse, material = 3}) clone.setScale(mat.getScale()) clone.setLock(true) --clone.interactable=false clone.setName("Mat url debug") end function deleteTable() local guidList = { Global.getVar("tableLeg1_GUID"), Global.getVar("tableLeg2_GUID"), Global.getVar("tableLeg3_GUID"), Global.getVar("tableLeg4_GUID"), tableSideBottom_GUID, Global.getVar("tableSideTop_GUID"), Global.getVar("tableSideLeft_GUID"), Global.getVar("tableSideRight_GUID"), Global.getVar("flexControl_GUID"), mat_GUID, felt_GUID } for i, tbl in ipairs(guidList) do local obj = getObjectFromGUID(tbl) if obj ~= nil then obj.destroy() end end end function deleteSelf() broadcastToAll("DONE", "Yellow") self.destroy() end function deleteTerrain() broadcastToAll("DELETING TERRAIN\nwait a few seconds ...", "Red") local startMenu=getObjectFromGUID(Global.getVar("startMenu_GUID")) startMenu.call("recallAll") Wait.time(deleteTerrain2, 2) end function deleteTerrain2() spawnPlayZone() Wait.frames(deleteTerrain3, 3) end function deleteTerrain3() local zoneObj=playZone.getObjects() for i, obj in ipairs(zoneObj) do if obj.getGUID() ~= mat_GUID and obj.getGUID() ~= felt_GUID and obj.getGUID() ~= tableSideBottom_GUID then obj.destroy() end end broadcastToAll("DONE", "Red") end function collectTerrainObjects() local zoneObj=playZone.getObjects() local lockedTerrain = {} local unlockedTerrain = {} for i, obj in ipairs(zoneObj) do if obj.getGUID() ~= mat_GUID and obj.getGUID() ~= felt_GUID and obj.getGUID() ~= tableSideBottom_GUID then if obj.getGMNotes() ~= "deployZone" and obj.getGMNotes() ~= "objective" then if obj.locked then lockedTerrain[i] = obj else unlockedTerrain[i] = obj end end end end return lockedTerrain, unlockedTerrain end -- callback hell, required to give PlayZone time to spawn function checkMirror() spawnPlayZone() Wait.frames(checkMirror2, 3) end function checkMirror2() local lockedTerrain = {} local unlockedTerrain = {} lockedTerrain, unlockedTerrain = collectTerrainObjects() for i, obj in pairs(unlockedTerrain) do obj.highlightOn({1, 0, 0}, 10.0) end YNButtonsMirror = true writeMenu() if not (next(unlockedTerrain) == nil) then broadcastToAll("WARNING: Unlocked Terrain will be deleted", "Red") end end function mirrorTerrain() local lockedTerrain = {} local unlockedTerrain = {} lockedTerrain, unlockedTerrain = collectTerrainObjects() for i, obj in pairs(unlockedTerrain) do obj.destroy() end for i, obj in pairs(lockedTerrain) do local newPos = obj.getPosition() local newRot = obj.getRotation() newPos.x = -newPos.x newPos.z = -newPos.z newRot.y = newRot.y + 180 -- object close to center aligned, mirror objects not at center. if math.abs(newPos.x) < 1 and math.abs(newPos.z) < 1 then obj.setPosition({0, newPos.y, 0}) else local newObj = obj.clone({ position=newPos, rotation=newRot }) newObj.locked = false end end YNButtonsMirror = false writeMenu() broadcastToAll("DONE", "Red") end function spawnPlayZone() if playZone ~= nil then return end playZoneData.scale=mat.getScale() playZoneData.scale.x=playZoneData.scale.x*36.02+0.2 playZoneData.scale.y=100 playZoneData.scale.z=playZoneData.scale.z*35.83+0.2 playZone=spawnObject(playZoneData) end function injectLua() local objList = getAllObjects() local oldLua = "" local addLua = '\nBCBtype = "terrain"\n' local newLua = "" for i, obj in ipairs(objList) do if obj.getVar("BCBtype") == "terrain" or obj == matUrlDisplay then else oldLua = obj.getLuaScript() newLua = addLua .. oldLua obj.setLuaScript(newLua) end end Wait.frames(deleteSelf, 1) end function none() endLuaScriptStateXmlUI46GUID27de4fNameQuarterTransformposXR@posY`posZrotXrotYrotZscaleX?scaleY?scaleZ?NicknameRed Selection HighlighterDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsLuaScript-- FTC-GUID: 27de4f,84c3a4 -- This script highlights objects in green when they are selected by a player. local selectedObjects = {} local externalHolds = {} -- guids another script owns; we won't touch their highlight local checkInterval = 0.25 local checkWaitId = nil local activeHighlighterGuid = "27de4f" function onLoad() if self.getGUID() ~= activeHighlighterGuid then self.interactable = false return end Wait.frames(function() self.interactable = false end, 30) checkWaitId = Wait.time(checkSelections, checkInterval, -1) end local colorMapping = { Red = {1, 0, 0}, Blue = {0, 0, 1}, Green = {0, 1, 0}, Yellow = {1, 1, 0}, Purple = {0.5, 0, 0.5}, Orange = {1, 0.5, 0}, White = {1, 1, 1}, Black = {0, 0, 0} } function getHighlightColorForPlayer(playerColor) return colorMapping[playerColor] or {0.5, 0.5, 0.5} end function onDestroy() if checkWaitId then Wait.stop(checkWaitId) checkWaitId = nil end clearAllHighlights() end -- Public API for other scripts (e.g. the coherency / engagement monitors) to -- take exclusive ownership of a model's highlight. While held, this highlighter -- ignores the model entirely -- it won't snapshot, recolour, or restore it -- so -- the owning script's glow can't be captured as an "original" and re-applied on -- deselect. The owner must releaseHighlight when it's done. function holdHighlight(params) local guid = params.guid externalHolds[guid] = true selectedObjects[guid] = nil -- stop tracking it as a selection highlight end function releaseHighlight(params) externalHolds[params.guid] = nil end -- Public API for the Highlight painter (customDiceTable). A paint applied while -- a model is selected would normally be wiped on deselect (we restore the -- pre-selection snapshot); this points the tracked snapshot at the paint colour -- instead, so deselecting restores the paint. No-op if the model isn't tracked -- yet -- the next checkSelections tick snapshots the already-applied paint as -- the original, which achieves the same thing. function setPersistentHighlight(params) local e = selectedObjects[params.guid] if e ~= nil then e.originalColor = params.color end end -- Forget every tracked selection snapshot. The painter's clear-all calls this -- right before highlightOff-ing the table, so the next tick re-tracks whatever -- is selected with clean (nil) snapshots and re-applies the selection glow. function clearPersistentHighlights() selectedObjects = {} end -- Guids currently owned by other scripts (live monitors). The painter's -- clear-all skips these so it doesn't fight a running monitor's glow. function getExternalHolds() local t = {} for guid in pairs(externalHolds) do t[guid] = true end return t end function checkSelections() local currentSelections = {} for _, player in ipairs(Player.getPlayers()) do local selected = player.getSelectedObjects() or {} local playerColor = player.color local highlightColor = getHighlightColorForPlayer(playerColor) -- Get color based on player for _, obj in ipairs(selected) do local guid = obj.getGUID() if (obj.tag == "Figurine" or obj.tag == "Generic" or obj.tag == "Custom_Model") and not selectedObjects[guid] and not externalHolds[guid] then selectedObjects[guid] = { object = obj, originalColor = obj.getHighlightColor() } obj.highlightOn(highlightColor) -- Highlight in player's color end currentSelections[guid] = true end end for guid, data in pairs(selectedObjects) do if not currentSelections[guid] then local obj = data.object if obj then if data.originalColor then obj.highlightOn(data.originalColor) else obj.highlightOff() end end selectedObjects[guid] = nil end end end function clearAllHighlights() for guid, data in pairs(selectedObjects) do local obj = data.object if obj then -- If it had an original color, restore it if data.originalColor then obj.highlightOn(data.originalColor) else obj.highlightOff() end end end selectedObjects = {} end LuaScriptStateXmlUIRotationValues0FValueHeadsRotation&xyV@zf@1FValueTailsRotation&xyVz47GUID84c3a4NameQuarterTransformposXR@posY?posZrotXrotYrotZscaleX?scaleY?scaleZ?NicknameBlue Selection HighligherDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsLuaScript-- FTC-GUID: 27de4f,84c3a4 -- This script highlights objects in green when they are selected by a player. local selectedObjects = {} local externalHolds = {} -- guids another script owns; we won't touch their highlight local checkInterval = 0.25 local checkWaitId = nil local activeHighlighterGuid = "27de4f" function onLoad() if self.getGUID() ~= activeHighlighterGuid then self.interactable = false return end Wait.frames(function() self.interactable = false end, 30) checkWaitId = Wait.time(checkSelections, checkInterval, -1) end local colorMapping = { Red = {1, 0, 0}, Blue = {0, 0, 1}, Green = {0, 1, 0}, Yellow = {1, 1, 0}, Purple = {0.5, 0, 0.5}, Orange = {1, 0.5, 0}, White = {1, 1, 1}, Black = {0, 0, 0} } function getHighlightColorForPlayer(playerColor) return colorMapping[playerColor] or {0.5, 0.5, 0.5} end function onDestroy() if checkWaitId then Wait.stop(checkWaitId) checkWaitId = nil end clearAllHighlights() end -- Public API for other scripts (e.g. the coherency / engagement monitors) to -- take exclusive ownership of a model's highlight. While held, this highlighter -- ignores the model entirely -- it won't snapshot, recolour, or restore it -- so -- the owning script's glow can't be captured as an "original" and re-applied on -- deselect. The owner must releaseHighlight when it's done. function holdHighlight(params) local guid = params.guid externalHolds[guid] = true selectedObjects[guid] = nil -- stop tracking it as a selection highlight end function releaseHighlight(params) externalHolds[params.guid] = nil end -- Public API for the Highlight painter (customDiceTable). A paint applied while -- a model is selected would normally be wiped on deselect (we restore the -- pre-selection snapshot); this points the tracked snapshot at the paint colour -- instead, so deselecting restores the paint. No-op if the model isn't tracked -- yet -- the next checkSelections tick snapshots the already-applied paint as -- the original, which achieves the same thing. function setPersistentHighlight(params) local e = selectedObjects[params.guid] if e ~= nil then e.originalColor = params.color end end -- Forget every tracked selection snapshot. The painter's clear-all calls this -- right before highlightOff-ing the table, so the next tick re-tracks whatever -- is selected with clean (nil) snapshots and re-applies the selection glow. function clearPersistentHighlights() selectedObjects = {} end -- Guids currently owned by other scripts (live monitors). The painter's -- clear-all skips these so it doesn't fight a running monitor's glow. function getExternalHolds() local t = {} for guid in pairs(externalHolds) do t[guid] = true end return t end function checkSelections() local currentSelections = {} for _, player in ipairs(Player.getPlayers()) do local selected = player.getSelectedObjects() or {} local playerColor = player.color local highlightColor = getHighlightColorForPlayer(playerColor) -- Get color based on player for _, obj in ipairs(selected) do local guid = obj.getGUID() if (obj.tag == "Figurine" or obj.tag == "Generic" or obj.tag == "Custom_Model") and not selectedObjects[guid] and not externalHolds[guid] then selectedObjects[guid] = { object = obj, originalColor = obj.getHighlightColor() } obj.highlightOn(highlightColor) -- Highlight in player's color end currentSelections[guid] = true end end for guid, data in pairs(selectedObjects) do if not currentSelections[guid] then local obj = data.object if obj then if data.originalColor then obj.highlightOn(data.originalColor) else obj.highlightOff() end end selectedObjects[guid] = nil end end end function clearAllHighlights() for guid, data in pairs(selectedObjects) do local obj = data.object if obj then -- If it had an original color, restore it if data.originalColor then obj.highlightOn(data.originalColor) else obj.highlightOff() end end end selectedObjects = {} end LuaScriptStateXmlUIRotationValues0FValueHeadsRotation&xyV@zf@1FValueTailsRotation&xyVz48GUIDacae21Name Custom_TileTransformposXposY@ ?posZ=@rotXrotYrotZscaleX@scaleY?scaleZ@NicknameRed Custom Dice TableDescription6{ "Cumulative": "no", "Update_Frequency": "0.2" }GMNotesAltLookAngle&xyzColorDiffuse&rgbLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16390643166952417281/FF7592F2338D23B469342C87FECAF6FC824D05BE/ImageSecondaryURLImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScript-- FTC-GUID: acae21,839fcc function none() end Yoffset=0.1 toolButtonBorderThickness = 70 -- TTS world buttons have no outline property, so use a larger backing button. function createToolButtonWithBorder(params, borderColor) local border = {} for k, v in pairs(params) do border[k] = v end border.label = "" border.font_size = 1 border.height = (params.height or 0) + toolButtonBorderThickness border.width = (params.width or 0) + toolButtonBorderThickness local resolvedBorderColor = borderColor or {0.1, 0.1, 0.1, 1} border.color = resolvedBorderColor border.hover_color = resolvedBorderColor border.press_color = resolvedBorderColor self.createButton(border) local top = {} for k, v in pairs(params) do top[k] = v end top.position = {params.position[1], params.position[2] + 0.01, params.position[3]} self.createButton(top) end local currentMat if self.getPosition().z < 0 then currentMat="Blue" else currentMat="Red" end local MM_TO_INCH = 0.0393701 local MEASURING_RING_Y_OFFSET = 0.17 local VALID_BASE_SIZES_IN_MM = { {x = 25, z = 25}, {x = 28, z = 28}, {x = 30, z = 30}, {x = 32, z = 32}, {x = 40, z = 40}, {x = 50, z = 50}, {x = 55, z = 55}, {x = 60, z = 60}, {x = 65, z = 65}, {x = 80, z = 80}, {x = 90, z = 90}, {x = 100, z = 100}, {x = 130, z = 130}, {x = 160, z = 160}, {x = 25, z = 75}, {x = 75, z = 25}, {x = 35.5, z = 60}, {x = 60, z = 35.5}, {x = 40, z = 95}, {x = 95, z = 40}, {x = 52, z = 90}, {x = 90, z = 52}, {x = 70, z = 105}, {x = 105, z = 70}, {x = 92, z = 120}, {x = 120, z = 92}, {x = 95, z = 150}, {x = 150, z = 95}, {x = 109, z = 170}, {x = 170, z = 109} } clearAllDiceBtn={ label="Clear mat\n(right click)", click_function="clearDiceAll", function_owner=self, position={-4.6, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=400, font_size=60, color={0,0,0}, font_color={1,1,1} } rollAllBtn={ label="Roll all dice", tooltip="Roll all dice on the mat.", click_function="RollAllDice", function_owner=self, position={-3.5, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=520, font_size=60, color={1,1,1}, font_color={0,0,0} } orderedRollLabel = "Ordered roll" orderedRollTooltip = "Roll all dice, and display them in the order they're rolled in rather than by results." orderedRollBtn={ label=orderedRollLabel, tooltip=orderedRollTooltip, click_function="RollDiceOrdered", function_owner=self, position={-2.4, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=520, font_size=60, color={1,1,1}, font_color={0,0,0} } orderedRollGroup2Label = "In 2s" orderedRollGroup2Tooltip = "Roll all dice, and display them in the order they're rolled in rather than by results, grouped into 2s." orderedRollGroup2Btn={ label=orderedRollGroup2Label, tooltip=orderedRollGroup2Tooltip, click_function="RollDiceOrderedGroup2", function_owner=self, position={-1.6, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={1,1,1}, font_color={0,0,0} } orderedRollGroup3Label = "In 3s" orderedRollGroup3Tooltip = "Roll all dice, and display them in the order they're rolled in rather than by results, grouped into 3s." orderedRollGroup3Btn={ label=orderedRollGroup3Label, tooltip=orderedRollGroup3Tooltip, click_function="RollDiceOrderedGroup3", function_owner=self, position={-1.1, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={1,1,1}, font_color={0,0,0} } add1d6Btn={ label="+ 1d6", click_function="spawnDice1", function_owner=self, tooltip="Left click: Spawns 1d6\nRight click: Quick-roll 1d6 (instant)", position={-0.5, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add2d6Btn={ label="+ 2d6", click_function="spawnDice2", function_owner=self, tooltip="Left click: Spawns 2d6\nRight click: Quick-roll 2d6 (instant)", position={-0.03, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add3d6Btn={ label="+ 3d6", click_function="spawnDice3", function_owner=self, tooltip="Left click: Spawns 3d6\nRight click: Quick-roll 3d6 (instant)", position={0.44, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add4d6Btn={ label="+ 4d6", click_function="spawnDice4", function_owner=self, tooltip="Left click: Spawns 4d6\nRight click: Quick-roll 4d6 (instant)", position={0.91, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add5d6Btn={ label="+ 5d6", click_function="spawnDice5", function_owner=self, tooltip="Left click: Spawns 5d6\nRight click: Quick-roll 5d6 (instant)", position={1.38, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add10d6Btn={ label="+ 10d6", click_function="spawnDice10", function_owner=self, tooltip="Spawns 10d6 dice", position={1.85, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add25d6Btn={ label="+ 25d6", click_function="spawnDice25", function_owner=self, tooltip="Spawns 25d6 dice", position={2.32, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={0.7,0.7,0.7}, font_color={0,0,0} } add1d3Btn={ label="+ 1d3", click_function="spawnDice1d3", function_owner=self, tooltip="Left click: Spawns 1d3\nRight click: Quick-roll 1d3 (instant)", position={2.79, Yoffset ,-0.87}, rotation={0,0,0}, height=50, width=250, font_size=60, color={1,1,1}, font_color={0,0,0} } colorBtnDimension=150 colorBtnOffset=3.2 color1Btn={ label="", click_function="modifyMenu1", function_owner=self, position={colorBtnOffset, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Red", font_color={0,0,0} } color2Btn={ label="", click_function="modifyMenu2", function_owner=self, position={colorBtnOffset+0.35, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Blue", font_color={0,0,0} } color3Btn={ label="", click_function="modifyMenu3", function_owner=self, position={colorBtnOffset+0.35*2, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Black", font_color={0,0,0} } color4Btn={ label="", click_function="modifyMenu4", function_owner=self, position={colorBtnOffset+0.35*3, Yoffset ,-0.87}, rotation={0,0,0}, height=colorBtnDimension, width=colorBtnDimension, font_size=60, color="Green", font_color={0,0,0} } -- Pip-colour toggle, sitting between the Green swatch and Dice+. Click flips every -- spawned die between white pips (Die_6_Rounded) and black pips (Die_6) through -- setDiceType(); revertRoll() also reads pipColorBtn.color. Its .index is assigned -- at runtime in onload (it's created last) so swapPipColor's editButton targets it. pipColorBtn= { label="White", click_function="swapPipColor", function_owner=self, position={colorBtnOffset + 0.35 * 4 + 0.1, Yoffset, -0.87}, rotation={0, 0, 0}, height=50, width=250, font_size=60, color="White", font_color="Black" } -- "Dice+" sits to the right of the pip toggle. It expands a second row of extra -- dice colours below the Red/Blue/Black/Green swatches, and collapses (destroys) -- them when clicked again. dicePlusBtn={ label="Dice+", tooltip="Show/hide extra dice colours", click_function="toggleDicePlus", function_owner=self, position={colorBtnOffset + 0.35 * 4 + 0.6, Yoffset, -0.87}, rotation={0, 0, 0}, height=50, width=250, font_size=60, color={1, 1, 1}, font_color={0, 0, 0} } -- Extra colour squares revealed by Dice+. Same X as the four base swatches, Z -- dropped by 1.5 so they form a second row. Created/destroyed on toggle. dicePlusExpanded = false -- Button positions are in the mat's LOCAL space and the mat is scaled 3.75x, so a -- local Z step of 0.4 lands ~1.5 world units apart (0.4 * 3.75). Row 1 sits below -- the base swatches; row 2 another equal step further out. -- Colours are tuned to a mid luminance band (~WCAG 0.12-0.30) so each die stays -- legible with EITHER white or black pips: dark hues are lifted, light hues are -- muddied (e.g. yellow -> mustard) since a vivid light colour can't hold white pips. dicePlusColors = { -- Row 1 (Z = -1.27) { fn = "dicePlusPurple", color = {0.5569, 0.2667, 0.6784}, x = colorBtnOffset, z = -0.87 - 0.4, tip = "Purple #8E44AD" }, { fn = "dicePlusYellow", color = {0.6510, 0.5255, 0.0392}, x = colorBtnOffset + 0.35, z = -0.87 - 0.4, tip = "Mustard #A6860A" }, { fn = "dicePlusTeal", color = {0.9922, 0.8157, 0.0706}, x = colorBtnOffset + 0.35 * 2, z = -0.87 - 0.4, tip = "Yellow #FDD012" }, { fn = "dicePlusOrange", color = {0.8000, 0.4196, 0.1176}, x = colorBtnOffset + 0.35 * 3, z = -0.87 - 0.4, tip = "Orange #CC6B1E" }, { fn = "dicePlusSlate", color = {0.4392, 0.5020, 0.5647}, x = colorBtnOffset + 0.35 * 4, z = -0.87 - 0.4, tip = "Slate Gray #708090" }, { fn = "dicePlusSteel", color = {0.2745, 0.5098, 0.7059}, x = colorBtnOffset + 0.35 * 5, z = -0.87 - 0.4, tip = "Steel Blue #4682B4" }, -- Row 2 (Z = -1.67) { fn = "dicePlusBordeaux", color = {0.9922, 0.9137, 0.7490}, x = colorBtnOffset, z = -0.87 - 0.8, tip = "Beige #FDE9BF" }, { fn = "dicePlusAzure", color = {0.0784, 0.5804, 0.5373}, x = colorBtnOffset + 0.35, z = -0.87 - 0.8, tip = "Turquoise #149489" }, { fn = "dicePlusLtBrown", color = {0.6275, 0.4157, 0.2353}, x = colorBtnOffset + 0.35 * 2, z = -0.87 - 0.8, tip = "Light Brown #A06A3C" }, { fn = "dicePlusDkGold", color = {0.7961, 0.2902, 0.5451}, x = colorBtnOffset + 0.35 * 3, z = -0.87 - 0.8, tip = "Pink #CB4A8B" }, { fn = "dicePlusOlive", color = {0.4196, 0.5569, 0.1373}, x = colorBtnOffset + 0.35 * 4, z = -0.87 - 0.8, tip = "Olive #6B8E23" }, { fn = "dicePlusIndigo", color = {0.3529, 0.3098, 0.8118}, x = colorBtnOffset + 0.35 * 5, z = -0.87 - 0.8, tip = "Indigo #5A4FCF" }, } -- Generate one click handler per swatch from the table above, so each colour is -- defined in exactly one place (the handler and the button share dicePlusColors). for _, dpc in ipairs(dicePlusColors) do _G[dpc.fn] = function() modifyMenu(dpc.color) end end customDiceBtn={ label="Custom Dice", click_function="customDice", function_owner=self, position={colorBtnOffset+0.35*9, Yoffset ,-0.85}, rotation={0,0,0}, height=50, width=420, font_size=60, color={1,1,1}, font_color={0,0,0} } clearDiceBtnIncrement=0.28 clearDiceBtnLabel="Del\n" clearDiceBtntooltip="LMB Delete this row\nRMB Delete this row and lowers/uppers" clearDiceBtn={ label=clearDiceBtnLabel, tooltip=clearDiceBtntooltip, click_function="filledInLater", function_owner=self, position={-5.8, Yoffset ,-0.85}, rotation={0,0,0}, height=1000, width=1300, scale={0.1,0.1,0.1}, font_size=350, color="Black", font_color="Red" } rollOrSelectDiceBtn={ label="filled in later", tooltip="filled in later", click_function="filledInLater", function_owner=self, position={-5.45, Yoffset ,-0.85}, rotation={0,0,0}, height=1000, width=1300, scale={0.1,0.1,0.1}, font_size=350, color="White", font_color={0,0,0} } lowerUpper=true oneTimeUpper=true lowerSign=" (-)" upperSign=" (+)" lowerUpperBtn={ label="RMB\n- / +", tooltip="Changes to upper for the next click", click_function="toggleLowerUpper", function_owner=self, position={-5.62, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=2800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } warningLbl="NO FAST REPETITIONS" warningLbl="" warningBtn={ label=warningLbl, click_function="modifyMenu4", function_owner=self, position={-1.75, Yoffset ,-0.67}, rotation={0,0,0}, height=0, width=0, font_size=50, color="White", font_color="Red" } lowerUpper=true oneTimeUpper=true lethalButtonsOffset = -4.2 lethalLabel = "Lethal Hits" lethalTip = "Left-click: move dice >= 6. Right-click: move dice >= 5." lethalHitsBtn={ label=lethalLabel, tooltip=lethalTip, click_function="lethalHitsAction", function_owner=self, position={lethalButtonsOffset, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=2800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } susLabel = "Sustained Hits" susTip = "Left-click: move dice >= 6. Right-click: move dice >= 5." sustainedHitsBtn={ label=susLabel, tooltip=susTip, click_function="sustainedHitsAction", function_owner=self, position={lethalButtonsOffset + 0.705, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=3800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } susLethalLabel = "Sustained & Lethal" susLethalTip = "Left-click: move dice >= 6. Right-click: move dice >= 5. Applies to sustained and lethal combined." sustainedLethalHitsBtn = { label=susLethalLabel, tooltip=susLethalTip, click_function="sustainedLethalHitsAction", function_owner=self, position={lethalButtonsOffset + 1.61, Yoffset ,1.1}, rotation={0,0,0}, height=1000, width=4800, scale={0.1,0.1,0.1}, font_size=500, color="White", font_color={0,0,0} } copyOthersDiceBtn = { tooltip="Copy opposite player's dice", click_function = "importOpposDice", function_owner = self, position = {-7.5, Yoffset, 0.86}, rotation = { 0, 0, 0 }, width = 1300, height = 300, font_size = 150, color = (currentMat == "Red" and "Blue" or "Red"), -- Button changes color based on current player font_color = {1, 1, 1}, -- White text for high contrast hover_color = (currentMat == "Red") and {0, 0, 1, 1} or {1, 0, 0, 1}, label = (currentMat == "Red") and "Copy Blue's Dice" or "Copy Red's Dice", press_color = {0.27, 0.27, 0.27, 1}, scale = {0.5, 0.5, 0.5} } savePositionLabel = "Save" saveBtn={ label=savePositionLabel, tooltip=savePositionTip, click_function="save", function_owner=self, position={-7.5, Yoffset ,-0.84}, rotation={0,0,0}, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } restorePositionLabel = "Restore Position" restoreBtn={ label=restorePositionLabel, click_function="restore", function_owner=self, position={-7.5, Yoffset ,-0.50}, rotation={0,0,0}, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } gap2Btn={ label='1"/2" Gap', tooltip='LMB: 1" Gap\nRMB: 2" Gap', click_function='arrangeModelsWithGap', function_owner=self, position={-6.24, Yoffset ,-1.65}, rotation={0,0,0}, height=1000, width=3200, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } clearMeasureAllBtn={ label='Clear Bubbles', tooltip='LEFT CLICK: Clear all measuring bubbles from the table\nRIGHT CLICK: Clear all measuring bubbles from highlighted objects only', click_function='clearMeasuringCircles', function_owner=self, position={-7.93, Yoffset ,-1.65}, rotation={0,0,0}, height=1000, width=4200, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } clearLinesBtn={ label='Clear Lines', tooltip='Clear all hand-drawn lines, keep measuring bubbles', click_function='clearHandDrawnLines', function_owner=self, position={-7.03, Yoffset ,-1.65}, rotation={0,0,0}, height=1000, width=4200, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } checkCoherencyBtn={ label='Coherency', tooltip='LEFT CLICK: Show coherency of selected models\n RIGHT CLICK: Toggle off lines\nDrawing new lines will be weird while this is on', click_function='checkCoherency', function_owner=self, position={-7.20, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=3300, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } engagementRangeBtn={ label='Engagement', tooltip='LEFT CLICK: Highlight every model within 2" of the selected model(s) for 15s\nRIGHT CLICK: Clear the highlights now', click_function='showEngagementRange', function_owner=self, position={-7.97, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=3800, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } highlightBtn={ label='Highlight', tooltip='LEFT CLICK: Highlight the selected model(s) in the swatch color (permanent)\nRIGHT CLICK: Remove ALL highlights from every object on the table', click_function='applyHighlightPaint', function_owner=self, position={-6.50, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=3100, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } -- Colour swatch for the Highlight button. Its face IS the currently chosen -- colour; clicking cycles the palette (recoloured live via refreshHighlightSwatch). highlightSwatchBtn={ label='', tooltip='Highlight color\nLEFT CLICK: next color\nRIGHT CLICK: previous color', click_function='cycleHighlightColor', function_owner=self, position={-6.02, Yoffset ,-1.15}, rotation={0,0,0}, height=1000, width=1000, scale={0.1,0.1,0.1}, font_size=700, color={1,1,0}, font_color={0,0,0} } measure1Btn={ label='1"', tooltip='Toggle 1" bubble', click_function='toggle1Bubble', function_owner=self, position={-8.23, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure2Btn={ label='2"', tooltip='Toggle 2" bubble', click_function='toggle2Bubble', function_owner=self, position={-7.93, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure3Btn={ label='3"', tooltip='Toggle 3" bubble', click_function='toggle3Bubble', function_owner=self, position={-7.63, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure6Btn={ label='6"', tooltip='Toggle 6" bubble', click_function='toggle6Bubble', function_owner=self, position={-7.33, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure8Btn={ label='8"', tooltip='Toggle 8" bubble', click_function='toggle8Bubble', function_owner=self, position={-7.03, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure9Btn={ label='9"', tooltip='Toggle 9" bubble', click_function='toggle9Bubble', function_owner=self, position={-6.73, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1400, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure12Btn={ label='12"', tooltip='Toggle 12" bubble', click_function='toggle12Bubble', function_owner=self, position={-6.41, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1600, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } measure15Btn={ label='15"', tooltip='Toggle 15" bubble', click_function='toggle15Bubble', function_owner=self, position={-6.07, Yoffset ,-1.40}, rotation={0,0,0}, height=1000, width=1600, scale={0.1,0.1,0.1}, font_size=700, color='White', font_color={0,0,0} } -- Custom Bubble Input BG customBubbleInputBG = { label = "", click_function = "none", function_owner = self, position = {-5.78, Yoffset, -1.40}, rotation = {0, 0, 0}, width = 1100, height = 1000, font_size = 100, color = {0.95, 0.95, 0.95, 1}, scale = {0.1, 0.1, 0.1} } -- Custom Bubble Input Field customBubbleInput = { label = "Size", input_function = "setCustomBubbleSize", function_owner = self, position = {-5.78, Yoffset, -1.40}, rotation = {0, 0, 0}, width = 900, height = 800, font_size = 500, alignment = 3, scale = {0.1, 0.1, 0.1}, color = {1, 1, 1, 1}, font_color = {0.2, 0.2, 0.2} } -- Custom Bubble Button -- LEFT CLICK applies the size typed in the on-mat input box. -- RIGHT CLICK (alt) opens the on-screen size overlay; 0 or a blank value there -- clears the bubbles on the selected models. customBubbleBtn = { label = "Go", tooltip = "LEFT CLICK: apply size from the box\nRIGHT CLICK: on-screen size entry (0/blank clears selected)", click_function = "applyCustomBubble", function_owner = self, position = {-5.58, Yoffset, -1.40}, rotation = {0, 0, 0}, width = 800, height = 1000, font_size = 500, color = {0.13, 0.13, 0.13, 1}, font_color = {1, 1, 1}, hover_color = {0.2, 0.2, 0.2, 1}, scale = {0.1, 0.1, 0.1} } function save(obj, player_color, alt) local selected = Player[player_color].getSelectedObjects() if not selected or #selected == 0 then printToColor("Please first select a model to save its position", player_color) return end -- Reset and capture all selected objects in one go savedPositions = {} for _, o in ipairs(selected) do table.insert(savedPositions, { guid = o.getGUID(), position = o.getPosition(), rotation = o.getRotation() }) end -- Announce on screen (centre) and in chat for everyone, tinted by the saving -- player's seat colour (Blue for blue, Red for red, Yellow, Grey, ...). broadcastToAll(#savedPositions .. " objects saved", player_color) end function restore(playerColor) if not savedPositions or #savedPositions == 0 then broadcastToColor("No saved positions to restore.", playerColor, {1, 0.5, 0}) return end for i, v in ipairs(savedPositions) do local obj = getObjectFromGUID(v.guid) if obj ~= nil then obj.setRotation(v.rotation) obj.setPositionSmooth(v.position, false, true) end end end local auto2D6 = { label = "Show/Hide UI", tooltip="Show or hide your VP overlay", click_function = "toggleHudOverlay", function_owner = self, position = {-7.5, Yoffset, 0.52}, rotation = { 0, 0, 0 }, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } local revertRollBtn = { label = "Revert Roll", tooltip="It takes 1 second after rolling to 'lock in'", click_function = "revertRoll", function_owner = self, position = {-7.5, Yoffset, 0.18}, rotation = { 0, 0, 0 }, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, -- Black button (not pure black for smoother look) font_color = {1, 1, 1}, -- White text for high contrast hover_color = {0.2, 0.2, 0.2, 1}, -- Dark gray when hovered press_color = {0.27, 0.27, 0.27, 1}, -- Slightly lighter gray when pressed scale = {0.5, 0.5, 0.5} } -- Modern Black "Last 5 Rolls" Button local customDiceSpawnButton = { label = "Last 5 Rolls", click_function = "callLast5Rolls", function_owner = self, position = {-7.5, Yoffset, -0.16}, rotation = {0, 0, 0}, width = 1300, height = 300, font_size = 150, color = {0.13, 0.13, 0.13, 1}, font_color = {1, 1, 1}, hover_color = {0.2, 0.2, 0.2, 1}, press_color = {0.27, 0.27, 0.27, 1}, scale = {0.5, 0.5, 0.5} } lethalHitsThreshold = 6 sustainedHitsThreshold = 6 sustainedLethalHitsThreshold = 6 selectedColor = "White" diceType = "Die_6_Rounded" customDiceImage = nil scaleBtn = 0.1 previousRollData = {} previousRollStack = {} previousRollIndex = 0 maxPreviousRolls = 10 -- Resolved by initRoller once Global is ready (this script can load before -- Global's onLoad has set the *_GUID variables). rollerGUID = nil roller = nil -- Defer a callback until Global's onLoad has run (it sets FTC_Global_Ready -- last). After 10s run it anyway rather than hang a broken load forever. function whenGlobalReady(callback) Wait.condition(callback, function() return Global.getVar("FTC_Global_Ready") == true end, 10, callback) end function initRoller() rollerGUID = Global.getVar("redCustomDiceRoller_GUID") if self.guid == Global.getVar("blueCustomDiceMat_GUID") then rollerGUID = Global.getVar("blueCustomDiceRoller_GUID") end -- rollerGUID is nil when a click races Global's onLoad (resolveRoller calls -- this lazily); getObjectFromGUID(nil) would error out of the click handler. roller = rollerGUID and getObjectFromGUID(rollerGUID) or nil end function resolveRoller() if not roller then initRoller() end return roller end lastClickTimestamp = 0 local function nowSeconds() if Time and Time.time then return Time.time end return os.clock() end -- Ignore Roll All presses for 0.2 seconds after an accepted press, then coalesce -- the accepted press for 0.1 seconds before scanning resting dice. rollAllPending = { scheduled = false, player = nil } local rollAllDelay = 0.1 local rollAllCooldown = 0.2 local lastRollAllTimestamp = -math.huge local function scheduleRollAll(player) local currentTime = nowSeconds() if currentTime - lastRollAllTimestamp < rollAllCooldown then return end lastRollAllTimestamp = currentTime rollAllPending.player = player if rollAllPending.scheduled then return end rollAllPending.scheduled = true Wait.time(function() local queuedPlayer = rollAllPending.player rollAllPending.scheduled = false rollAllPending.player = nil if queuedPlayer then Player[queuedPlayer].clearSelectedObjects() end local dice = getDice() if #dice > 0 then moveDiceToRoller(queuedPlayer or player, dice) end end, rollAllDelay) end function importOpposDice(obj, player, alt) -- clear existing dice for _, d in ipairs(getDice()) do d.destroy() end -- get opposite mat and its bounds local oppMatGUID = (currentMat=="Red") and Global.getVar("blueCustomDiceMat_GUID") or Global.getVar("redCustomDiceMat_GUID") local oppMat = getObjectFromGUID(oppMatGUID) if not oppMat then printToColor("Opposite dice mat not found.", player, "Orange") return end local mb = oppMat.getBounds() local mL, mR = mb.center.x - mb.size.x/2, mb.center.x + mb.size.x/2 local mLo, mUp = mb.center.z - mb.size.z/2, mb.center.z + mb.size.z/2 local mYl, mYh = mb.center.y - mb.size.y/2, mb.center.y + mb.size.y/2 + 3 -- get opposite lethal zone and its bounds local oppLethalGUID = (currentMat=="Red") and Global.getVar("blueLethalHits_GUID") or Global.getVar("redLethalHits_GUID") local oppLethal = getObjectFromGUID(oppLethalGUID) local lb, lL, lR, lLo, lUp, lYl, lYh if oppLethal then lb = oppLethal.getBounds() lL, lR = lb.center.x - lb.size.x/2, lb.center.x + lb.size.x/2 lLo, lUp = lb.center.z - lb.size.z/2, lb.center.z + lb.size.z/2 lYl, lYh = lb.center.y - lb.size.y/2, lb.center.y + lb.size.y/2 + 3 else printToColor("Warning: opposite lethal zone not found.", player) end local toImport = {} local seen = {} -- gather dice: check lethal zone first, then main mat for _, o in ipairs(getAllObjects()) do if o.tag=="Dice" and o.resting then local p = o.getPosition() -- lethal zone? if lb and p.x > lL and p.x < lR and p.z > lLo and p.z < lUp and p.y > lYl and p.y < lYh then seen[o.getGUID()] = true table.insert(toImport, { die=o, origin=lb.center, lethal=true }) -- main mat (excluding any already seen in lethal) elseif p.x > mL and p.x < mR and p.z > mLo and p.z < mUp and p.y > mYl and p.y < mYh and not seen[o.getGUID()] then seen[o.getGUID()] = true table.insert(toImport, { die=o, origin=mb.center, lethal=false }) end end end if #toImport == 0 then printToColor("No dice found on the opposite mat.", player, "Yellow") return end -- clone into this mat: shift lethal dice by (-3, -3) in X/Z local mc = self.getBounds().center local count = 0 for _, entry in ipairs(toImport) do local dp = entry.die.getPosition() local cb = entry.origin local relX, relY, relZ = -(dp.x - cb.x), dp.y - cb.y, -(dp.z - cb.z) local nx, ny, nz = mc.x + relX, mc.y + relY, mc.z + relZ if entry.lethal then local dx = (currentMat == "Red") and -6 or 6 local dz = (currentMat == "Red") and 4.2 or -4.2 nx = nx + dx nz = nz + dz end entry.die.clone({ position={nx, ny, nz}, snap_to_grid=false }) count = count + 1 end printToAll("Imported "..count.." dice from the opposite mat, including lethal hits.", {1,1,1}) end function storeCurrentDiceAsPreviousRoll() -- Build a snapshot of the current dice on the mat (positions, values, tints) local snapshot = {} for _, die in ipairs(getDice()) do local c = die.getCustomObject() local d = { pos = die.getPosition(), rot = die.getRotation(), val = die.getValue(), tint = die.getColorTint(), customImage = nil } if c then d.customImage = c.image end table.insert(snapshot, d) end -- If we've undone some rolls and then store a new roll, drop any future redo history if previousRollIndex < #previousRollStack then for i = #previousRollStack, previousRollIndex + 1, -1 do table.remove(previousRollStack, i) end end table.insert(previousRollStack, snapshot) -- Trim stack to maximum size while #previousRollStack > maxPreviousRolls do table.remove(previousRollStack, 1) end previousRollIndex = #previousRollStack previousRollData = previousRollStack[previousRollIndex] or {} end function revertRoll(obj, player, alt) -- Undo one step in the previousRollStack. Use previousRollIndex to track position. if previousRollIndex == 0 or not previousRollStack or #previousRollStack == 0 then printToColor("No previous roll found.", player, "Red") return end local snapshot = previousRollStack[previousRollIndex] if not snapshot or #snapshot == 0 then printToColor("No previous roll data available.", player, "Red") return end -- Destroy current dice and spawn snapshot dice for _, die in ipairs(getDice()) do die.destroy() end for _, data in ipairs(snapshot) do local diceTypeToSpawn = "Die_6_Rounded" if data.customImage then diceTypeToSpawn = "Custom_Dice" elseif pipColorBtn.color == "Black" then diceTypeToSpawn = "Die_6" end local spawned = spawnObject({ type = diceTypeToSpawn, position = data.pos, rotation = data.rot }) if data.customImage then spawned.setCustomObject({ image = data.customImage, type = 1 }) end spawned.setColorTint(data.tint) spawned.setValue(data.val) end printToColor("Reverted to roll #" .. tostring(previousRollIndex) .. " of " .. tostring(#previousRollStack), player, "Green") -- Move pointer back so another click reverts another step previousRollIndex = math.max(0, previousRollIndex - 1) previousRollData = previousRollStack[previousRollIndex] or {} end function moveDiceToRoller(color, dice) if #dice == 0 then return false end local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", color, "Yellow") return false end activeRoller.call("setPlayerColor", { color = color }) local rollId = activeRoller.call("beginRollBatch", { color = color, count = #dice, }) if not rollId then printToAll("Dice roller busy: roll was not started. No dice were moved.", {1, 0.5, 0}) return false end -- Re-rolled dice are no longer lethal hits; drop them from lethal tracking so -- a later "clear mat" can't reach back and destroy them wherever they end up. forgetLethalHitsForDice(dice) for _, die in ipairs(dice) do activeRoller.call("setLastHolder", { obj = die, color = color, rollId = rollId }) activeRoller.putObject(die) end storeCurrentDiceAsPreviousRoll() return true end -- Evaluate a simple arithmetic expression safely (digits, + - * / and parentheses) -- Returns number on success, or nil + error message on failure -- Reset the last spawn position and dice count when the dice area is cleared function callLast5Rolls(obj, player, alt) local r = resolveRoller() if r then r.call("printLast5Innovative") end end -- Bounds of the current mat's lethal-hits zone object, or nil if not found. -- Single source of truth: getDice() EXCLUDES dice in this box and -- cleanupLethalHits() DESTROYS dice in this box, so they must agree on what -- "in the zone" means -- otherwise a die can fall between the two (excluded -- from clearing, not recognised for cleanup) and become unclearable. local function getLethalZoneBounds() local guid = (currentMat == "Red") and Global.getVar("redLethalHits_GUID") or Global.getVar("blueLethalHits_GUID") local zone = getObjectFromGUID(guid) if not zone then return nil end local lb = zone.getBounds() return { minx = lb.center.x - lb.size.x/2, maxx = lb.center.x + lb.size.x/2, minz = lb.center.z - lb.size.z/2, maxz = lb.center.z + lb.size.z/2, miny = lb.center.y - lb.size.y/2, maxy = lb.center.y + lb.size.y/2, } end local function isInBounds(p, b) return b ~= nil and p.x > b.minx and p.x < b.maxx and p.z > b.minz and p.z < b.maxz and p.y > b.miny and p.y < b.maxy end -- Dice on this mat: settled (resting), not held, and outside the lethal zone. function getDice() local dice = {} local b = self.getBounds() local left = b.center.x - b.size.x/2 local right = b.center.x + b.size.x/2 local back = b.center.z - b.size.z/2 local front = b.center.z + b.size.z/2 local ybot = b.center.y - b.size.y/2 local ytop = b.center.y + b.size.y/2 + 3 if currentMat == "Red" then right = right - 5 back = back + 2.9 front = front - 0.5 else left = left + 5 back = back + 0.5 front = front - 2.9 end local lbound = getLethalZoneBounds() for _, o in ipairs(getAllObjects()) do if o.tag == "Dice" and o.resting and o.held_by_color == nil then local p = o.getPosition() if p.x > left and p.x < right and p.z > back and p.z < front and p.y > ybot and p.y < ytop and not isInBounds(p, lbound) then table.insert(dice, o) end end end return dice end function customDice(obj, player, alt) found = false newImage = nil newTint = nil dice = getDice() for _, die in ipairs(dice) do local image = die.getCustomObject()["image"] if image ~= nil then found = true newTint = die.getColorTint() newImage = image printToColor("You have set a custom die!", player, "Yellow") break else found = true newTint = die.getColorTint() printToColor("You have set a custom colour!", player, "Yellow") end end if found then customDiceImage = newImage selectedColor = newTint setDiceType() end end function toggleLowerUpper() setLowerUpper(not lowerUpper) end function setLowerUpper( value ) lowerUpper= value modifyBtns() end function modifyBtns() local sign="" for i=1, 6 do if lowerUpper then sign = lowerSign else sign = upperSign end self.editButton({ index = (17+i*2), label = clearDiceBtnLabel .. i .. sign }) self.editButton({ index = (18+i*2), label = "Roll\n" .. i .. sign }) end end -- Reached via obj.call from the "Roll All Dice" hotkey so the right player's -- mat handles the roll (each mat scans its own bounds in getDice()). function hotkeyRollAllDice(params) RollAllDice(nil, params.playerColor, false) end -- Reached via obj.call from the "Clear Mat" hotkey. alt=true bypasses the -- "Use right click!" guard in clearDiceAll so a single keypress clears. function hotkeyClearMat(params) clearDiceAll(nil, params.playerColor, true) end function RollAllDice(obj, player, alt) scheduleRollAll(player) end function RollDiceOrdered(_, player, _) Player[player].clearSelectedObjects() local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", player, "Yellow") return end activeRoller.setVar("orderedOnNextRoll", true) moveDiceToRoller(player, getDice()) end function RollDiceOrderedGroup2(obj, player, alt) local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", player, "Yellow") return end activeRoller.setVar("groupSizeOnNextRoll", 2) RollDiceOrdered(obj, player, alt) end function RollDiceOrderedGroup3(obj, player, alt) local activeRoller = resolveRoller() if not activeRoller then printToColor("Dice roller is still loading. Try again in a moment.", player, "Yellow") return end activeRoller.setVar("groupSizeOnNextRoll", 3) RollDiceOrdered(obj, player, alt) end function RollOrSelectDeleteDice1(obj, color, alt) SelectDeleteDiceX("Sel", 1, alt, lowerUpper, color) end function RollOrSelectDeleteDice2(obj, color, alt) SelectDeleteDiceX("Sel", 2, alt, lowerUpper, color) end function RollOrSelectDeleteDice3(obj, color, alt) SelectDeleteDiceX("Sel", 3, alt, lowerUpper, color) end function RollOrSelectDeleteDice4(obj, color, alt) SelectDeleteDiceX("Sel", 4, alt, lowerUpper, color) end function RollOrSelectDeleteDice5(obj, color, alt) SelectDeleteDiceX("Sel", 5, alt, lowerUpper, color) end function RollOrSelectDeleteDice6(obj, color, alt) SelectDeleteDiceX("Sel", 6, alt, lowerUpper, color) end function SelectDeleteDiceX(action, n, selectOthers, lowUp, color) Player[color].clearSelectedObjects() selDel = true if action == "Del" then selDel = false end local counter = {0,0,0,0,0,0} local numberName = {"ones", "twos", "threes", "fours", "fives", "sixes"} local text = Player[color].steam_name .. " deleted " local somethingToPrint = false for i, die in ipairs(getDice()) do if die.getValue() == n or (selectOthers and lowUp and die.getValue()< n) or (selectOthers and not lowUp and die.getValue()> n) then if selDel then die.addToPlayerSelection(color) else die.destroy() counter[die.getValue()]=counter[die.getValue()]+1 end end end for i, val in ipairs(counter) do if val > 0 then text = text.." \u{00B7} " .. counter[i] .. " " .. numberName[i] somethingToPrint = true end end if somethingToPrint then printToAll(text , "Yellow") end if oneTimeUpper then setLowerUpper( true ) end if selDel then moveDiceToRoller(color, Player[color].getSelectedObjects()) end end function clearDiceAll(obj, color, alt) if not alt then printToColor("Use right click!", color, "Orange") return end cleanupLethalHits() clearDiceX(6, true, color) end function clearDice1(obj, color, alt) SelectDeleteDiceX("Del", 1, alt, lowerUpper, color) end function clearDice2(obj, color, alt) SelectDeleteDiceX("Del", 2, alt, lowerUpper, color) end function clearDice3(obj, color, alt) SelectDeleteDiceX("Del", 3, alt, lowerUpper, color) end function clearDice4(obj, color, alt) SelectDeleteDiceX("Del", 4, alt, lowerUpper, color) end function clearDice5(obj, color, alt) SelectDeleteDiceX("Del", 5, alt, lowerUpper, color) end function clearDice6(obj, color, alt) SelectDeleteDiceX("Del", 6, alt, lowerUpper, color) end function clearDiceX(n, deleteLower, color) local counter={0,0,0,0,0,0} local numberName={"ones", "twos", "threes", "fours", "fives", "sixes"} local text = Player[color].steam_name .. " deleted " local somethingToPrint=false for i, die in ipairs(getDice()) do if die.getValue() == n or (deleteLower and die.getValue()< n) then die.destroy() counter[die.getValue()]=counter[die.getValue()]+1 end end for i, val in ipairs(counter) do if val > 0 then text=text.." \u{00B7} "..counter[i].." "..numberName[i] somethingToPrint=true end end if somethingToPrint then printToAll(text , "Yellow") end end function spawnDice1(obj, player, alt) spawnDice(1,player,alt) end function getHudOverlayColorForThisMat() if self.getGUID() == Global.getVar("redCustomDiceMat_GUID") then return "Red" end if self.getGUID() == Global.getVar("blueCustomDiceMat_GUID") then return "Blue" end return nil end function toggleHudOverlay(obj, player, alt) local color = getHudOverlayColorForThisMat() if not color then printToColor("Could not determine overlay owner for this dice tray.", player, "Orange") return end Global.call("toggleHudOverlayForColor", {color = color}) end function spawnDice2(obj, player, alt) spawnDice(2,player,alt) end function spawnDice3(obj, player, alt) spawnDice(3,player,alt) end function spawnDice4(obj, player, alt) spawnDice(4,player,alt) end function spawnDice5(obj, player, alt) spawnDice(5,player,alt) end function spawnDice10(obj, player, alt) spawnDice(10,player,alt) end function spawnDice25(obj, player, alt) spawnDice(25,player,alt) end function spawnDice1d3(obj, player, alt) spawnD3(1, player, alt) end function spawnD3(n, player, alt) local playerDiceSpeed = player == "Red" and Global.getVar("redDiceSpawnTime") or Global.getVar("blueDiceSpawnTime") local currentTime = nowSeconds() local disableTime = (playerDiceSpeed == "slow") and 0.6 or 0.2 if currentTime - lastClickTimestamp < disableTime then printToColor("Wait a bit!", player, "Yellow") return end lastClickTimestamp = currentTime local offset = #getDice() if offset + n > 128 then printToColor("Dice limit reached!", player, "Orange") end local grid, row, col = 0, 0, 0 for i = 1, offset do col = col + 1 if col > 4 then col = 0; row = row + 1 if row > 4 then row = 0; grid = grid + 1 end end end if offset + n > 128 then n = 128 - offset end local d3Bag = getObjectFromGUID("2d9705") if not d3Bag then printToColor("D3 bag not found!", player, "Red") return end local spawnedDice = {} for i = 1, n do local pos = self.positionToWorld({4 - grid*1.5 - col*0.25, 0.5, -0.3 + row*0.25}) d3Bag.takeObject({ position = pos, rotation = self.getRotation() + Vector(0.0, 180.0, 0.0), smooth = false, callback_function = function(obj) if selectedColor ~= nil then obj.setColorTint(selectedColor) end table.insert(spawnedDice, obj) -- Right-click (alt) quick-rolls just like the +Nd6 buttons: set values -- from the roller's xoshiro stream (faces=3) without ever entering the -- bag. Dice arrive via async takeObject callbacks, so fire once the last -- one lands. Left-click stays spawn-only (press Roll All to roll). if alt and #spawnedDice == n then quickRollSpawnedDice(player, spawnedDice, 3, "d3") end end }) col = col + 1 if col > 4 then col = 0; row = row + 1 if row > 4 then row = 0; grid = grid + 1 end end end end function spawnDice(n,player,alt) local playerDiceSpeed = player == "Red" and Global.getVar("redDiceSpawnTime") or Global.getVar("blueDiceSpawnTime") local currentTime = nowSeconds() local disableTime = (playerDiceSpeed == "slow") and 0.6 or 0.2 if currentTime-lastClickTimestamp128 then printToColor("Dice limit reached!", player, "Orange") end local grid=0 local row=0 local col=0 for i=1, offset do col=col+1 if col>4 then col=0 row= row+1 if row > 4 then row=0 grid=grid+1 end end end if offset+n>128 then n=128-offset end local spawnedDice = {} for i=1, n do local die=spawnObject({ type = diceType, position = self.positionToWorld({4-grid*1.5-col*0.25,0.5,-0.3+row*0.25}), rotation = self.getRotation() + Vector(0.0, 180.0, 0.0), }) col=col+1 if col>4 then col=0 row= row+1 if row > 4 then row=0 grid=grid+1 end end if customDiceImage ~= nil then die.setCustomObject({ image = customDiceImage, type = 1 }) end if selectedColor ~= nil then die.setColorTint(selectedColor) end table.insert(spawnedDice, die) end -- Left-click spawns only (press Roll All to roll through the bag). Right-click -- (alt) on the SMALL spawners (1d6-5d6) "quick rolls": the dice are spawned -- already showing values from the roller's xoshiro RNG and never enter the bag -- (the legacy quick-roll trick) -- instant, and immune to the bag's -- loop/strand/nil-faces failure modes. Gated to n<=5; +10/+25/+1d3 stay -- spawn-only. if alt and n >= 1 and n <= 5 and #spawnedDice > 0 then quickRollSpawnedDice(player, spawnedDice) end end -- Fixed palette for quick-roll dice: one colour is picked per quick-roll and -- shared by every die in that roll, so consecutive quick-rolls are easy to tell -- apart. Cosmetic only, so plain math.random is fine here (dice VALUES still use -- the roller's xoshiro). -- Red and blue are intentionally excluded: those are the players' main dice -- colours, so quick-roll dice stay visually distinct from them. -- Darker/deeper hues so the white pips on the dice contrast clearly. local quickRollPalette = { {0.72, 0.38, 0.08}, -- burnt orange {0.68, 0.52, 0.08}, -- dark gold {0.16, 0.46, 0.22}, -- dark green {0.10, 0.42, 0.48}, -- dark teal {0.40, 0.20, 0.58}, -- dark purple {0.68, 0.18, 0.42}, -- dark magenta/pink } local lastQuickRollColorIndex = 0 -- Set already-spawned dice to values from the roller's xoshiro stream so they -- appear instantly rolled, without ever entering the bag. Falls back to -- math.random only if the roller object can't be reached. All dice in the roll -- share one random palette colour. function quickRollSpawnedDice(player, dice, faces, dieLabel) faces = faces or 6 dieLabel = dieLabel or "d6" local vals if roller then vals = roller.call("rollFacesForQuick", { n = #dice, faces = faces }) end -- Pick one palette colour for the whole roll, never repeating the previous -- roll's colour so batches stay visually distinct. local idx = math.random(#quickRollPalette) if #quickRollPalette > 1 and idx == lastQuickRollColorIndex then idx = (idx % #quickRollPalette) + 1 end lastQuickRollColorIndex = idx local tint = quickRollPalette[idx] local total = 0 for i, die in ipairs(dice) do local v = (vals and tonumber(vals[i])) or math.random(faces) die.setColorTint(tint) -- Custom dice are spawned with setCustomObject, which reloads the object -- asynchronously. Setting the value synchronously here races that reload -- and the value is lost when the die snaps back to its spawn orientation, -- so every die shows the same default face. Defer the value (and snap the -- rotation explicitly, like the bag roll's setValueCallback does) until the -- reload has settled. Plain dice don't reload, but the short defer is -- harmless for them. Wait.frames(function() die.setValue(v) local rotValues = die.getRotationValues() if rotValues and rotValues[v] then die.setRotation(rotValues[v].rotation + Vector(0, 180, 0)) end end, 2) total = total + v end printToAll(Player[player].steam_name .. " quick-rolled " .. tostring(total) .. " on " .. #dice .. dieLabel, "White") end -- Cross-object entry point for the stats board. Object.call() can only pass a -- single argument, so the stats board sends a {n, player, alt} table which we -- unpack into the positional spawnDice signature. function spawnStatsDice(params) if type(params) ~= "table" then return end spawnDice(params.n, params.player, params.alt) end -- Sustained hits = one extra attack per hit of `threshold`+. The dice on the mat -- ARE the roll, so we operate on the current mat dice directly. This replaced an -- earlier scheme that matched against a stored snapshot by value+tint+position, -- which was fragile to physics drift and a post-roll timing race and could either -- mass-clone everything or silently under-count. local function collectSustainedDice(threshold) local dice, positions = {}, {} for _, die in ipairs(getDice()) do if die.getValue() >= threshold then table.insert(dice, die) table.insert(positions, die.getPosition()) end end return dice, positions end local function cloneSustainedDice(dice, positions) local directionMultiplier = (currentMat == "Blue") and 1 or -1 for i, die in ipairs(dice) do local p = positions[i] p.x = p.x + (8.5 * directionMultiplier) die.clone({position = p, snap_to_grid = false}) end return #dice end function sustainedHitsAction(_, color, alt) -- Left-click: apply 6+. Right-click (alt): apply 5+. No toggle. local threshold = alt and 5 or 6 local dice, positions = collectSustainedDice(threshold) local count = cloneSustainedDice(dice, positions) printToAll("Sustained Hits duplicated: " .. tostring(count) .. " (threshold " .. tostring(threshold) .. "+)") end -- New function for combined action function sustainedLethalHitsAction(_, color, alt) -- Left-click: apply 6+. Right-click: apply 5+. No toggle. local threshold = alt and 5 or 6 -- Capture the dice and their mat positions BEFORE lethalHitsAction moves them -- off to the lethal zone, then clone at those saved positions so the sustained -- extra-attack dice stay on the mat while the originals go to the lethal zone. local dice, positions = collectSustainedDice(threshold) lethalHitsAction('sustainedAndLethal', color, alt) local count = cloneSustainedDice(dice, positions) printToAll("Sustained & Lethal duplicated: " .. tostring(count) .. " (threshold " .. tostring(threshold) .. "+)") end function getObjectsPositionToSave(player, hoveredObject) if #player.getSelectedObjects() != 0 then return player.getSelectedObjects() elseif hoveredObject != nil then return {hoveredObject} end return nil end function savePosition(obj) table.insert(savedPositions, { guid = obj.getGUID(), position = obj.getPosition(), rotation = obj.getRotation() }) end function savePositionClicked(playerColor, hoveredObject) savedPositions = {} local objects = getObjectsPositionToSave(Player[playerColor], hoveredObject) if objects then for _, v in ipairs(objects) do savePosition(v) end -- Announce on screen (centre) and in chat for everyone, tinted by the -- saving player's seat colour (Blue for blue, Red for red, Yellow, ...). broadcastToAll(#objects .. " objects saved", playerColor) end end function restorePositionClicked(playerColor) for i,v in ipairs(savedPositions) do local obj = getObjectFromGUID(v.guid) if obj != nil then obj.setRotation(v.rotation) obj.setPositionSmooth(v.position, false, true) end end end function onload() -- addHotkey("Toggle Floating Icon", toggleFloatingIcon) whenGlobalReady(initRoller) -- Remove any coherency distance labels that leaked into the save. cleanupOrphanCoherencyLabels() -- TTS sorts the Options -> Game Keys list alphabetically by label (call order -- is ignored), so these six are prefixed 1.-6. to pin them to the top in the -- desired order. The numeric prefixes change the labels, which orphans any -- prior player bindings -- hence the "RESET ALL YOUR TTS HOTKEYS" broadcast. addHotkey("1. Save Position", savePositionClicked) addHotkey("2. Restore Position", restorePositionClicked) addHotkey("3. Custom Bubble", function(playerColor, _, _) if playerColor ~= "Red" and playerColor ~= "Blue" then return end local matGUID = (playerColor == "Red") and Global.getVar("redCustomDiceMat_GUID") or Global.getVar("blueCustomDiceMat_GUID") Global.call("openBubbleInput", {color = playerColor, mat = matGUID}) end) addHotkey("4. Coherency Check", checkCoherency) addHotkey("5. Engagement Range", showEngagementRange) addHotkey("6. Roll All Dice", function(playerColor, _, _) local matGUID = (playerColor == "Red") and Global.getVar("redCustomDiceMat_GUID") or Global.getVar("blueCustomDiceMat_GUID") local mat = getObjectFromGUID(matGUID) if mat then mat.call("hotkeyRollAllDice", { playerColor = playerColor }) end end) -- Remaining hotkeys; these sort alphabetically below the numbered group. addHotkey("Auto 1 Inch Gap", arrangeModelsWith1Inch) addHotkey("Auto 2 Inch Gap", arrangeModelsWith2Inch) addHotkey("Clear Bubbles", function(playerColor, _, _) clearMeasuringCircles(nil, playerColor, false) end) addHotkey("Clear Bubbles (Selected)", function(playerColor, _, _) clearMeasuringCircles(nil, playerColor, true) end) addHotkey("Clear Lines", function(playerColor, _, _) clearHandDrawnLines(nil, playerColor, false) end) addHotkey("Clear Mat", function(playerColor, _, _) if playerColor ~= "Red" and playerColor ~= "Blue" then return end local matGUID = (playerColor == "Red") and Global.getVar("redCustomDiceMat_GUID") or Global.getVar("blueCustomDiceMat_GUID") local mat = getObjectFromGUID(matGUID) if mat then mat.call("hotkeyClearMat", { playerColor = playerColor }) end end) addHotkey("Show Scoreboard", function(playerColor, _, _) Global.call("hotkeyToggleScoringOverlay", { playerColor = playerColor }) end) addHotkey("Show/Hide UI", function(playerColor, _, _) if playerColor ~= "Red" and playerColor ~= "Blue" and playerColor ~= "Grey" then return end Global.call("toggleHudOverlayForColor", { color = playerColor }) end) addHotkey("Toggle 1 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(1, target, nil, playerColor) end) addHotkey("Toggle 12 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(12.01, target, nil, playerColor) end) addHotkey("Toggle 2 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(2, target, nil, playerColor) end) addHotkey("Toggle 3 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(3, target, nil, playerColor) end) addHotkey("Toggle 6 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(6, target, nil, playerColor) end) addHotkey("Toggle 8 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(8, target, nil, playerColor) end) addHotkey("Toggle 9 Inch Measurment Circle", function(playerColor, target) changeMeasurementCircle(9, target, nil, playerColor) end) lastClickTimestamp=nowSeconds() defaultFontSize=500 fontSize=defaultFontSize lineCount=0 defaultDivisionLabel="|\n|" self.createButton({ label="---", click_function="none", function_owner=Global, position={-6.4,0.11,0.01}, rotation={0,0,0}, height=0, width=0, font_size=fontSize, font_color={1, 1, 1}, scale={0.25, 0.25, 0.25}, alignment=2 }) self.createButton({ label="", click_function="none", function_owner=Global, position={0.75,0.11,0.01}, rotation={0,0,0}, height=0, width=0, font_size=500, font_color={1, 1, 1}, scale={0.25,2,1}, alignment=2 }) if self.getDescription() == '' then setDefaultState() end emptyText = ' ' data2 = JSON.decode(self.getDescription()) Wait.time(checkDice, data2.Update_Frequency*1,-1) self.createButton(clearAllDiceBtn) self.createButton(rollAllBtn) self.createButton(orderedRollBtn) self.createButton(orderedRollGroup2Btn) self.createButton(orderedRollGroup3Btn) self.createButton(add1d3Btn) self.createButton(add1d6Btn) self.createButton(add2d6Btn) self.createButton(add5d6Btn) self.createButton(add10d6Btn) self.createButton(add25d6Btn) self.createButton(color1Btn) self.createButton(color2Btn) self.createButton(color3Btn) self.createButton(color4Btn) self.createButton(warningBtn) self.createButton(dicePlusBtn) local sign="" for i=1, 6 do if lowerUpper then sign= lowerSign else sign=upperSign end clearDiceBtn.position[3]=clearDiceBtn.position[3]+clearDiceBtnIncrement clearDiceBtn.click_function="clearDice"..i clearDiceBtn.label=clearDiceBtnLabel..i..sign self.createButton(clearDiceBtn) rollOrSelectDiceBtn.position[3] = rollOrSelectDiceBtn.position[3] + clearDiceBtnIncrement rollOrSelectDiceBtn.label = "Roll\n" .. i .. sign rollOrSelectDiceBtn.tooltip = "LMB Re-rolls this row\nRMB Re-rolls this row and lowers/uppers" rollOrSelectDiceBtn.click_function = "RollOrSelectDeleteDice" .. i self.createButton(rollOrSelectDiceBtn) end self.createButton(lowerUpperBtn) self.createButton(customDiceBtn) self.createButton(add3d6Btn) self.createButton(add4d6Btn) createToolButtonWithBorder(lethalHitsBtn) createToolButtonWithBorder(sustainedHitsBtn) createToolButtonWithBorder(sustainedLethalHitsBtn) self.createButton(copyOthersDiceBtn) self.createButton(auto2D6) self.createButton(revertRollBtn) -- Add this near the end of the button creations self.createButton(saveBtn) self.createButton(restoreBtn) createToolButtonWithBorder(measure1Btn) createToolButtonWithBorder(measure2Btn) createToolButtonWithBorder(measure3Btn) createToolButtonWithBorder(measure6Btn) createToolButtonWithBorder(measure8Btn) createToolButtonWithBorder(measure9Btn) createToolButtonWithBorder(measure12Btn) createToolButtonWithBorder(measure15Btn) self.createButton(customBubbleInputBG) self.createInput(customBubbleInput) self.createButton(customBubbleBtn) createToolButtonWithBorder(clearMeasureAllBtn) createToolButtonWithBorder(clearLinesBtn) createToolButtonWithBorder(checkCoherencyBtn) createToolButtonWithBorder(engagementRangeBtn) createToolButtonWithBorder(highlightBtn) createToolButtonWithBorder(highlightSwatchBtn) -- The swatch is recoloured at runtime. Its border shares the click_function, -- so grab the TOP button: the one created later, i.e. the higher index. for _, b in ipairs(self.getButtons() or {}) do if b.click_function == "cycleHighlightColor" then if highlightSwatchIndex == nil or b.index > highlightSwatchIndex then highlightSwatchIndex = b.index end end end refreshHighlightSwatch() createToolButtonWithBorder(gap2Btn) self.createButton(customDiceSpawnButton) -- Created last so it doesn't shift the fixed indices modifyMenu() relies on; -- capture its real index so swapPipColor's editButton flips the right button. self.createButton(pipColorBtn) for _, b in ipairs(self.getButtons() or {}) do if b.click_function == "swapPipColor" then pipColorBtn.index = b.index break end end if self.getPosition().z < 0 then selectedColor="Blue" else selectedColor="Red" end modifyMenu(selectedColor) end local originalPositions = {} local isMoving = false local lethalTextDisplays = { Red = nil, Blue = nil } local lethalTextConfig = { Red = {position = {18, 0.97, 22}, rotation = {90, 180, 0}}, Blue = {position = {-18, 0.97, -22}, rotation = {90, 0, 0}}, } function ensureLethalTextDisplay(mat) if lethalTextDisplays[mat] then return end local cfg = lethalTextConfig[mat] local t = spawnObject({ type = "3DText", position = cfg.position, rotation = cfg.rotation, scale = {1, 1, 1}, nickname = "LethalHitsText" .. mat, }) t.TextTool.setValue("Lethal Hits (0)") t.TextTool.setFontSize(50) t.TextTool.setFontColor({1, 1, 1}) lethalTextDisplays[mat] = t end function updateLethalTextDisplay(mat, count) ensureLethalTextDisplay(mat) lethalTextDisplays[mat].TextTool.setValue("Lethal Hits (" .. tostring(count) .. ")") end local lethalStartPos = { Red = {x = 18.1, y = 1.37, z = 23.25}, Blue = {x = -19.1, y = 1.47, z = -23.75}, } local lethalSpacing = 1.1 local lethalRowLimit = 25 function moveLethalHits(isSustainedAndLethal) if isMoving then return end isMoving = true local threshold = isSustainedAndLethal and sustainedLethalHitsThreshold or lethalHitsThreshold local directionMultiplier = (currentMat == "Blue") and 1 or -1 local start = lethalStartPos[currentMat] local diceInZone = 0 for _ in pairs(originalPositions) do diceInZone = diceInZone + 1 end local currentRow = math.floor(diceInZone / lethalRowLimit) local currentCol = diceInZone % lethalRowLimit local movedCount = 0 for _, die in ipairs(getDice()) do if die.getValue() >= threshold then local parked = { x = start.x + (currentCol * lethalSpacing * directionMultiplier), y = start.y, z = start.z + (currentRow * lethalSpacing), } -- Track this die as a lethal hit. cleanupLethalHits decides whether to -- destroy it by its CURRENT position (is it still in the zone?), not by -- a stored slot, so we just need a truthy marker keyed by GUID here. originalPositions[die.getGUID()] = true die.setPosition(parked) movedCount = movedCount + 1 currentCol = currentCol + 1 if currentCol >= lethalRowLimit then currentCol = 0 currentRow = currentRow + 1 end end end isMoving = false return movedCount end function cleanupLethalHits() -- Only destroy dice that are STILL inside the lethal-hits zone -- using the -- exact same bounds getDice() uses to EXCLUDE them. A die tagged lethal but -- since dragged back onto the mat or below it is no longer ours to delete -- here: on-mat dice are handled by the clearDiceX that runs right after this -- in clearDiceAll, and off-mat dice should survive. Sharing one definition -- means no die can fall between the two (excluded from clearing yet -- unrecognised for cleanup) and become permanently unclearable. local zone = getLethalZoneBounds() for guid, _ in pairs(originalPositions) do local die = getObjectFromGUID(guid) if die and isInBounds(die.getPosition(), zone) then die.destruct() end end originalPositions = {} for mat, t in pairs(lethalTextDisplays) do if t then t.destruct() end lethalTextDisplays[mat] = nil end end -- Stop tracking dice as lethal hits once they are sent back to the roller to be -- re-rolled. Without this their GUIDs would linger in originalPositions and keep -- inflating the lethal count even though they are no longer in the lethal zone. function forgetLethalHitsForDice(dice) local changed = false for _, die in ipairs(dice) do local guid = die.getGUID() if originalPositions[guid] then originalPositions[guid] = nil changed = true end end if changed and lethalTextDisplays[currentMat] then local total = 0 for _ in pairs(originalPositions) do total = total + 1 end updateLethalTextDisplay(currentMat, total) end end function lethalHitsAction(lethalHits, color, alt) local lethalNumbers = (lethalHits == 'sustainedAndLethal') local oldLethal = lethalHitsThreshold local oldSustainedLethal = sustainedLethalHitsThreshold if lethalNumbers then sustainedLethalHitsThreshold = alt and 5 or 6 else lethalHitsThreshold = alt and 5 or 6 end local moved = moveLethalHits(lethalNumbers) lethalHitsThreshold = oldLethal sustainedLethalHitsThreshold = oldSustainedLethal local total = 0 for _ in pairs(originalPositions) do total = total + 1 end updateLethalTextDisplay(currentMat, total) printToAll("Lethal hits moved: " .. tostring(moved) .. " (threshold " .. tostring(alt and 5 or 6) .. "+)") end function modifyMenu1() modifyMenu("Red") end function modifyMenu2() modifyMenu("Blue") end function modifyMenu3() modifyMenu("Black") end function modifyMenu4() modifyMenu("Green") end function modifyMenu(color) customDiceImage = nil selectedColor = color setDiceType() self.editButton({index=7, color=color}) self.editButton({index=8, color=color}) self.editButton({index=9, color=color}) self.editButton({index=10, color=color}) self.editButton({index=11, color=color}) self.editButton({index=12, color=color}) self.editButton({index=33, color=color}) self.editButton({index=34, color=color}) self.editButton({index=35, color="White"}) self.editButton({index=36, color="White"}) self.editButton({index=37, color="White"}) end function toggleDicePlus() if dicePlusExpanded then -- Identify our extra squares by click_function and remove them. Descending -- index order so each removal can't invalidate the indices still to remove. local mine = {} for _, c in ipairs(dicePlusColors) do mine[c.fn] = true end local indices = {} for _, b in ipairs(self.getButtons() or {}) do if mine[b.click_function] then table.insert(indices, b.index) end end table.sort(indices, function(a, b) return a > b end) for _, idx in ipairs(indices) do self.removeButton(idx) end dicePlusExpanded = false else for _, c in ipairs(dicePlusColors) do self.createButton({ label = "", tooltip = c.tip, click_function = c.fn, function_owner = self, position = {c.x, Yoffset, c.z}, rotation = {0, 0, 0}, height = colorBtnDimension, width = colorBtnDimension, font_size = 60, color = c.color, font_color = {0, 0, 0} }) end dicePlusExpanded = true end end function setDiceType() if customDiceImage ~= nil then diceType = "Custom_Dice" elseif pipColorBtn.color == "White" then diceType = "Die_6_Rounded" else diceType = "Die_6" end end function getStatsHelperDiceConfig() setDiceType() return { diceType = diceType, selectedColor = selectedColor, customDiceImage = customDiceImage } end function setDefaultState() self.setDescription(JSON.encode_pretty({ Cumulative = 'no',Update_Frequency = '0.2' })) end function checkDice() local data = JSON.decode(self.getDescription()) if not data then setDefaultState() data = JSON.decode(self.getDescription()) printToAll('Warning - invalid description. Restored default configuration.', {0.8,0.5,0}) end if not self.resting then return end local b = self.getBounds() local leftBound = b.center.x - b.size.x/2 local rightBound = b.center.x + b.size.x/2 local lowerBound = b.center.z - b.size.z/2 local upperBound = b.center.z + b.size.z/2 if currentMat == "Red" then rightBound = rightBound - 5 lowerBound = lowerBound + 2.9 upperBound = upperBound - 0.5 else leftBound = leftBound + 5 lowerBound = lowerBound + 0.5 upperBound = upperBound - 2.9 end local ylowerBound = b.center.y - b.size.y/2 local valueToCounter, valuesToSort, totaldice, diceSum = {}, {}, 0, 0 for _, obj in pairs(getAllObjects()) do if obj.tag == 'Dice' and obj.resting then local p = obj.getPosition() if p.x > leftBound and p.x < rightBound and p.z > lowerBound and p.z < upperBound and p.y > ylowerBound then local v = obj.getValue() if v then if not valueToCounter[v] then valueToCounter[v] = 0 table.insert(valuesToSort, v) end valueToCounter[v] = valueToCounter[v] + 1 diceSum = diceSum + v totaldice = totaldice + 1 end end end end local textLines, lineCount = {}, 0 if data.Cumulative == 'yes' then table.sort(valuesToSort) for i = 1, 12 do valueToCounter[i] = valueToCounter[i] or 0 end for i = 11, 1, -1 do valueToCounter[i] = valueToCounter[i] + valueToCounter[i+1] end for _, v in ipairs(valuesToSort) do local suffix = (v == 1) and "'s: " or "+: " table.insert(textLines, tostring(v) .. suffix .. valueToCounter[v]) lineCount = lineCount + 1 end else table.sort(valuesToSort) for _, v in ipairs(valuesToSort) do table.insert(textLines, "#" .. v .. ": " .. valueToCounter[v]) lineCount = lineCount + 1 end end for i = lineCount+1, 6 do table.insert(textLines, " ") end local text = table.concat(textLines, "\n") if text == "" then text = emptyText end local totaltext = (totaldice == 0) and " " or ("\n" .. totaldice .. " ∑ " .. diceSum) if lineCount > 6 then fontSize = defaultFontSize - ((lineCount-6)*37) divisionLabel = defaultDivisionLabel else fontSize = defaultFontSize divisionLabel = "" end self.editButton({ index = 0, label = text .. totaltext, font_size = fontSize }) self.editButton({ index = 1, label = divisionLabel }) end -- ── DEBUG-ONLY PERF DIAGNOSTIC ─────────────────────────────────────────────── -- Invoked from the shared debug panel (Global, --test builds only) via -- mat.call("runDiceScanBenchmark", {color=}). Benchmarks the always-on -- getAllObjects() scan that checkDice() runs at 5Hz, and compares it against an -- area-limited Physics.cast box over this mat's bounds, so the cost of the -- whole-scene scan is visible and the box-cast win is provable in-game. -- Timed with os.clock() (real wall time) NOT Time.time (frame time, which is -- constant inside a single callback and would read zero). function runDiceScanBenchmark(params) local playerColor = params and params.color local ITER = 200 -- bump if the scene is small and timings read ~0 -- scene population is the real independent variable for getAllObjects() local sceneCount = #getAllObjects() -- mat box (same bounds checkDice uses to filter dice) local b = self.getBounds() local origin = { x = b.center.x, y = b.center.y, z = b.center.z } local size = { x = b.size.x, y = b.size.y + 6, z = b.size.z } local orient = { x = 0, y = self.getRotation().y, z = 0 } -- NOTE: TTS's Lua (MoonSharp) returns nil for collectgarbage("count"), so -- per-call heap allocation can't be measured here. Timing is the metric; -- collectgarbage("collect") still works and we use it to settle GC first. -- getAllObjects(): avg time per call collectgarbage("collect") local gaoT0 = os.clock() for _ = 1, ITER do getAllObjects() end local gaoMs = (os.clock() - gaoT0) / ITER * 1000 -- Physics.cast box over the mat: avg time per call collectgarbage("collect") local castT0 = os.clock() local lastHits = 0 for _ = 1, ITER do local hits = Physics.cast({ origin = origin, direction = { x = 0, y = 1, z = 0 }, type = 3, size = size, orientation = orient, max_distance = 0, }) lastHits = #hits end local castMs = (os.clock() - castT0) / ITER * 1000 collectgarbage("collect") -- always-on extrapolation: 2 mats * 5Hz = 10 scans/sec across the session local CALLS_PER_SEC = 10 local speedup = (castMs > 0) and (gaoMs / castMs) or 0 local out = string.format( "── checkDice scan benchmark (%d iters) ──\n" .. "scene objects: %d box hits: %d\n" .. "getAllObjects: %.4f ms/call\n" .. "Physics.cast : %.4f ms/call\n" .. "speedup: %.1fx\n" .. "always-on (10 scans/s): %.3f ms/s vs %.3f ms/s", ITER, sceneCount, lastHits, gaoMs, castMs, speedup, gaoMs * CALLS_PER_SEC, castMs * CALLS_PER_SEC) if playerColor then printToColor(out, playerColor, { 0.4, 0.9, 1 }) else printToAll(out, { 0.4, 0.9, 1 }) end print(out) -- also to the host's System Console / log end function swapPipColor() customDiceImage = nil if pipColorBtn.color == "Black" then pipColorBtn.label = "White" pipColorBtn.color = "White" pipColorBtn.font_color = "Black" else pipColorBtn.label = "Black" pipColorBtn.color = "Black" pipColorBtn.font_color = "White" end setDiceType() self.editButton(pipColorBtn) end function arrangeModelsWith2Inch(a, b, c) -- Support being called from a button or a hotkey. When invoked via a button -- the first argument is the object and the second is the player color. -- When invoked via a hotkey the first argument is the player color. local playerColor if type(a) == "string" then playerColor = a else playerColor = b end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. playerColor) return end local objs = playerObj.getSelectedObjects() if #objs < 2 then print("Less than 2 objects selected; nothing to arrange.") return end local minPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } local maxPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } for i = 2, #objs do local p = objs[i].getPosition() if p.x < minPos.x then minPos.x = p.x end if p.z < minPos.z then minPos.z = p.z end if p.x > maxPos.x then maxPos.x = p.x end if p.z > maxPos.z then maxPos.z = p.z end end local dx = maxPos.x - minPos.x local dz = maxPos.z - minPos.z local mag = math.sqrt(dx * dx + dz * dz) local d = {} if mag < 0.0001 then d.x, d.z = 1, 0 else d.x, d.z = dx / mag, dz / mag end local items = {} for _, obj in ipairs(objs) do local pos = obj.getPosition() local half = obj.getBoundsNormalized().size.x / 2 local proj = pos.x * d.x + pos.z * d.z table.insert(items, { obj = obj, pos = pos, half = half, proj = proj }) end table.sort(items, function(a, b) return a.proj < b.proj end) local offsetCount = 0 if #items > 7 then offsetCount = 2 end local startIndex = 1 if offsetCount == 2 then startIndex = 3 end local anchorPos = { x = items[startIndex].pos.x, z = items[startIndex].pos.z } items[startIndex].newPos = { x = anchorPos.x, y = items[startIndex].pos.y, z = anchorPos.z } for i = startIndex + 1, #items - offsetCount do local prev = items[i - 1] local gapDist = prev.half + 2 + items[i].half items[i].newPos = { x = prev.newPos.x + d.x * gapDist, y = items[i].pos.y, z = prev.newPos.z + d.z * gapDist } end if offsetCount == 2 then local p = { x = -d.z, z = d.x } -- start offsets: arrange three items as a triangle so pairwise edge gap ~= 2" local baseStart = items[startIndex].newPos -- apex on main line local a = items[startIndex] local b = items[1] local c = items[2] local desiredGap = 2 local baseSep = b.half + c.half + desiredGap local baseHalf = baseSep / 2 local s1 = a.half + b.half + desiredGap local s2 = a.half + c.half + desiredGap -- compute apex->baseCenter distance from triangle right-triangle: t = sqrt(s1^2 - baseHalf^2) local t1 = math.sqrt(math.max(0, s1 * s1 - baseHalf * baseHalf)) local t2 = math.sqrt(math.max(0, s2 * s2 - baseHalf * baseHalf)) local t = (t1 + t2) / 2 local baseCenter = { x = baseStart.x - d.x * t, z = baseStart.z - d.z * t } items[1].newPos = { x = baseCenter.x + p.x * baseHalf, y = b.pos.y, z = baseCenter.z + p.z * baseHalf } items[2].newPos = { x = baseCenter.x - p.x * baseHalf, y = c.pos.y, z = baseCenter.z - p.z * baseHalf } -- debug: print the starting three indices and pairwise distances if true then local function dist(a,b) local dx = a.x - b.x; local dz = a.z - b.z; return math.sqrt(dx*dx + dz*dz) end local Apos = a.pos; local Bpos = b.pos; local Cpos = c.pos local Anew = a.newPos; local Bnew = b.newPos; local Cnew = c.newPos print(string.format("Start triangle items indexes: apex=%s base1=%s base2=%s", a.obj.getGUID(), b.obj.getGUID(), c.obj.getGUID())) print(string.format("Before: AB=%.2f AC=%.2f BC=%.2f", dist(Apos,Bpos), dist(Apos,Cpos), dist(Bpos,Cpos))) print(string.format("After: AB=%.2f AC=%.2f BC=%.2f", dist(Anew,Bnew), dist(Anew,Cnew), dist(Bnew,Cnew))) end -- end offsets local baseEnd = items[#items - offsetCount].newPos -- end offsets: triangle at the end (apex on main line, base beyond) local a_e = items[#items - offsetCount] -- apex at end of line local b_e = items[#items - 1] local c_e = items[#items] local desiredGap = 2 local baseSep_e = b_e.half + c_e.half + desiredGap local baseHalf_e = baseSep_e / 2 local s1_e = a_e.half + b_e.half + desiredGap local s2_e = a_e.half + c_e.half + desiredGap local t1_e = math.sqrt(math.max(0, s1_e * s1_e - baseHalf_e * baseHalf_e)) local t2_e = math.sqrt(math.max(0, s2_e * s2_e - baseHalf_e * baseHalf_e)) local t_e = (t1_e + t2_e) / 2 local baseCenter_e = { x = baseEnd.x + d.x * t_e, z = baseEnd.z + d.z * t_e } items[#items - 1].newPos = { x = baseCenter_e.x + p.x * baseHalf_e, y = b_e.pos.y, z = baseCenter_e.z + p.z * baseHalf_e } items[#items].newPos = { x = baseCenter_e.x - p.x * baseHalf_e, y = c_e.pos.y, z = baseCenter_e.z - p.z * baseHalf_e } -- debug: print the final three indices and pairwise distances if true then local function dist(a,b) local dx = a.x - b.x; local dz = a.z - b.z; return math.sqrt(dx*dx + dz*dz) end local Apos = a_e.pos; local Bpos = b_e.pos; local Cpos = c_e.pos local Anew = a_e.newPos; local Bnew = b_e.newPos; local Cnew = c_e.newPos print(string.format("End triangle items indexes: apex=%s base1=%s base2=%s", a_e.obj.getGUID(), b_e.obj.getGUID(), c_e.obj.getGUID())) print(string.format("Before: AB=%.2f AC=%.2f BC=%.2f", dist(Apos,Bpos), dist(Apos,Cpos), dist(Bpos,Cpos))) print(string.format("After: AB=%.2f AC=%.2f BC=%.2f", dist(Anew,Bnew), dist(Anew,Cnew), dist(Bnew,Cnew))) end end for _, item in ipairs(items) do item.obj.setPositionSmooth(item.newPos) end print("Arranged " .. #items .. " models with a 2-inch gap along the detected orientation.") end -- Single button handler: left-click arranges with a 1" gap, right-click with 2". -- For button clicks the third argument (alt_click) is true on a right-click. function arrangeModelsWithGap(obj, playerColor, alt_click) if alt_click then arrangeModelsWith2Inch(obj, playerColor, alt_click) else arrangeModelsWith1Inch(obj, playerColor, alt_click) end end function arrangeModelsWith1Inch(a, b, c) local playerColor if type(a) == "string" then playerColor = a else playerColor = b end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. playerColor) return end local objs = playerObj.getSelectedObjects() if #objs < 2 then print("Less than 2 objects selected; nothing to arrange.") return end local minPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } local maxPos = { x = objs[1].getPosition().x, z = objs[1].getPosition().z } for i = 2, #objs do local p = objs[i].getPosition() if p.x < minPos.x then minPos.x = p.x end if p.z < minPos.z then minPos.z = p.z end if p.x > maxPos.x then maxPos.x = p.x end if p.z > maxPos.z then maxPos.z = p.z end end local dx = maxPos.x - minPos.x local dz = maxPos.z - minPos.z local mag = math.sqrt(dx * dx + dz * dz) local d = {} if mag < 0.0001 then d.x, d.z = 1, 0 else d.x, d.z = dx / mag, dz / mag end local items = {} for _, obj in ipairs(objs) do local pos = obj.getPosition() local half = obj.getBoundsNormalized().size.x / 2 local proj = pos.x * d.x + pos.z * d.z table.insert(items, { obj = obj, pos = pos, half = half, proj = proj }) end table.sort(items, function(a, b) return a.proj < b.proj end) local offsetCount = 0 if #items > 7 then offsetCount = 2 end local startIndex = 1 if offsetCount == 2 then startIndex = 3 end local anchorPos = { x = items[startIndex].pos.x, z = items[startIndex].pos.z } items[startIndex].newPos = { x = anchorPos.x, y = items[startIndex].pos.y, z = anchorPos.z } for i = startIndex + 1, #items - offsetCount do local prev = items[i - 1] local gapDist = prev.half + 1 + items[i].half items[i].newPos = { x = prev.newPos.x + d.x * gapDist, y = items[i].pos.y, z = prev.newPos.z + d.z * gapDist } end if offsetCount == 2 then local p = { x = -d.z, z = d.x } -- start offsets: arrange three items as a triangle so pairwise edge gap ~= 1" local baseStart = items[startIndex].newPos -- apex on main line local a = items[startIndex] local b = items[1] local c = items[2] local desiredGap = 1 local baseSep = b.half + c.half + desiredGap local baseHalf = baseSep / 2 local s1 = a.half + b.half + desiredGap local s2 = a.half + c.half + desiredGap local t1 = math.sqrt(math.max(0, s1 * s1 - baseHalf * baseHalf)) local t2 = math.sqrt(math.max(0, s2 * s2 - baseHalf * baseHalf)) local t = (t1 + t2) / 2 local baseCenter = { x = baseStart.x - d.x * t, z = baseStart.z - d.z * t } items[1].newPos = { x = baseCenter.x + p.x * baseHalf, y = b.pos.y, z = baseCenter.z + p.z * baseHalf } items[2].newPos = { x = baseCenter.x - p.x * baseHalf, y = c.pos.y, z = baseCenter.z - p.z * baseHalf } -- end offsets: triangle at the end (apex on main line, base beyond) local baseEnd = items[#items - offsetCount].newPos local a_e = items[#items - offsetCount] -- apex at end of line local b_e = items[#items - 1] local c_e = items[#items] local desiredGap_e = 1 local baseSep_e = b_e.half + c_e.half + desiredGap_e local baseHalf_e = baseSep_e / 2 local s1_e = a_e.half + b_e.half + desiredGap_e local s2_e = a_e.half + c_e.half + desiredGap_e local t1_e = math.sqrt(math.max(0, s1_e * s1_e - baseHalf_e * baseHalf_e)) local t2_e = math.sqrt(math.max(0, s2_e * s2_e - baseHalf_e * baseHalf_e)) local t_e = (t1_e + t2_e) / 2 local baseCenter_e = { x = baseEnd.x + d.x * t_e, z = baseEnd.z + d.z * t_e } items[#items - 1].newPos = { x = baseCenter_e.x + p.x * baseHalf_e, y = b_e.pos.y, z = baseCenter_e.z + p.z * baseHalf_e } items[#items].newPos = { x = baseCenter_e.x - p.x * baseHalf_e, y = c_e.pos.y, z = baseCenter_e.z - p.z * baseHalf_e } end for _, item in ipairs(items) do item.obj.setPositionSmooth(item.newPos) end print("Arranged " .. #items .. " models with a 1-inch gap along the detected orientation.") end function determineBaseInInches(model) local savedBase = model.getTable("chosenBase") if savedBase ~= nil then return savedBase.base else local chosenBase = VALID_BASE_SIZES_IN_MM[1] local modelSize = model.getBoundsNormalized().size local modelSizeX = modelSize.x local modelSizeZ = modelSize.z local closestSum = 10000000000 local chosenBaseIdx = 1 for k, base in pairs(VALID_BASE_SIZES_IN_MM) do local baseInchX = (MM_TO_INCH - 0.001) * base.x local baseInchZ = (MM_TO_INCH - 0.001) * base.z if modelSizeX > baseInchX and modelSizeZ > baseInchZ then local distSum = (modelSizeX - baseInchX) + (modelSizeZ - baseInchZ) if distSum < closestSum then closestSum = distSum chosenBase = base chosenBaseIdx = k end end end if chosenBase == nil then chosenBase = { x=modelSizeX/2, z=modelSizeZ/2} else chosenBase = { x = (chosenBase.x * MM_TO_INCH)/2, z = (chosenBase.z * MM_TO_INCH)/2 } end model.setTable("chosenBase", { baseIdx=chosenBaseIdx, base=chosenBase }) return chosenBase end end function getCircleVectorPoints(radius, baseX, baseZ, obj) local result = {} local scaleFactor = 1/obj.getScale().x local steps = 64 local degrees,sin,cos,toRads = 360/steps, math.sin, math.cos, math.rad for i = 0,steps do table.insert(result,{ x = cos(toRads(degrees*i))*((radius+baseX)*scaleFactor), z = MEASURING_RING_Y_OFFSET, y = sin(toRads(degrees*i))*((radius+baseZ)*scaleFactor) }) end return result end function getRectangleVectorPoints(radius, sizeX, sizeZ, obj) local result = {} local scaleFactor = 1/obj.getScale().x sizeX = sizeX*scaleFactor sizeZ = sizeZ*scaleFactor radius = radius*scaleFactor local steps = 65 local degrees,sin,cos,toRads = 360/(steps-1), math.sin, math.cos, math.rad local xOffset,zOffset = sizeX,sizeZ table.insert(result,{ x = (cos(toRads(degrees*0))*radius)+sizeX-0.001, y = (sin(toRads(degrees*0))*radius)+sizeZ, z = MEASURING_RING_Y_OFFSET }) for i = 1,steps-1 do if i == 16 then table.insert(result,{ x= sizeX, y=(radius+sizeZ), z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=-sizeX, y=(radius+sizeZ), z=MEASURING_RING_Y_OFFSET }) xOffset = -sizeX elseif i == 33 then table.insert(result,{ x=-radius-sizeX, y= sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=-radius-sizeX-0.001, y=-sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=-radius-sizeX, y=-sizeZ, z=MEASURING_RING_Y_OFFSET }) zOffset = -sizeZ elseif i == 49 then table.insert(result,{ x=-sizeX, y=-radius-sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x= sizeX, y=-radius-sizeZ, z=MEASURING_RING_Y_OFFSET }) xOffset = sizeX elseif i == 65 then table.insert(result,{ x=radius+sizeX, y=-sizeZ, z=MEASURING_RING_Y_OFFSET }) table.insert(result,{ x=radius+sizeX-0.001, y= sizeZ, z=MEASURING_RING_Y_OFFSET }) else table.insert(result,{ x = (cos(toRads(degrees*i))*radius)+xOffset, y = (sin(toRads(degrees*i))*radius)+zOffset, z = MEASURING_RING_Y_OFFSET }) end end table.insert(result,{ x = (cos(toRads(degrees*0))*radius)+sizeX-0.001, y = (sin(toRads(degrees*0))*radius)+sizeZ, z = MEASURING_RING_Y_OFFSET }) return result end function toggleMeasurementCircleForSelection(radius, playerColor) local playerObj = Player[playerColor] if not playerObj then return end local objs = playerObj.getSelectedObjects() for _, obj in ipairs(objs) do changeMeasurementCircle(radius, obj, nil, playerColor) end end function toggle1Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(1, playerColor) end function toggle2Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(2, playerColor) end function toggle3Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(3, playerColor) end function toggle6Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(6, playerColor) end function toggle8Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(8, playerColor) end function toggle9Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(9, playerColor) end function toggle12Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(12.01, playerColor) end function toggle15Bubble(obj, playerColor, alt) toggleMeasurementCircleForSelection(15, playerColor) end local customBubbleSize = 0 function setCustomBubbleSize(_, _, value, _) customBubbleSize = tonumber(value) or 0 end function applyCustomBubble(_, playerColor, alt) if alt then -- Right-click opens the on-screen typed-size overlay instead. requestBubbleInput(_, playerColor, alt) return end if customBubbleSize > 0 then toggleMeasurementCircleForSelection(customBubbleSize, playerColor) else printToColor("Please enter a valid bubble size", playerColor, "Red") end end -- ===== Screen-space custom bubble input overlay (1-60) ===== -- The overlay panel + its input/submit callbacks live on the Global canvas -- (global.ttslua / ftc_base_ui.xml). This button just asks Global to open it, -- passing this mat's GUID so Global can call applyBubbleFromOverlay back here. function requestBubbleInput(_, playerColor, _) Global.call("openBubbleInput", {color = playerColor, mat = self.getGUID()}) end -- Invoked by Global once the player confirms a size in the overlay. function applyBubbleFromOverlay(params) toggleMeasurementCircleForSelection(params.radius, params.color) end -- Invoked by Global when the overlay value is 0 / blank: clear bubbles on the -- player's selected models (same as the "Clear Bubbles (Selected)" action). function clearBubblesFromOverlay(params) clearMeasuringCircles(nil, params.color, true) end function changeMeasurementCircle(newRequestedRadius, target, presetBase, ownerColor) if target == nil then return end local measuringRings = target.getTable("ym-measuring-circles") local currentColor = "Teal" local currentColorRadius if measuringRings == nil then measuringRings = {} currentColorRadius = 0 else for idx = #measuringRings, 1, -1 do if (measuringRings[idx].name == currentColor) or (measuringRings[idx].name == nil and currentColor == nil) then currentColorRadius = measuringRings[idx].radius table.remove(measuringRings, idx) elseif measuringRings[idx].name == "base" then table.remove(measuringRings, idx) end end if currentColorRadius == nil then currentColorRadius = 0 end end local newRadius if currentColorRadius == newRequestedRadius then newRadius = 0 else newRadius = newRequestedRadius end if newRadius ~= 0 then local isRectangular = target.hasTag("rectangularMeasuring") local measuring = { name = currentColor, color = Color.fromString(currentColor), radius = newRadius, thickness = 0.5, rotation = {270, 0, 0}, owner = ownerColor -- track who created this measuring circle } local base = { name = "base", color = Color.fromString(currentColor), thickness = 0.2, rotation = {270, 0, 0}, owner = ownerColor } local measuringPoints, basePoints if isRectangular then local modelBounds = target.getBoundsNormalized() measuringPoints = getRectangleVectorPoints(newRadius, modelBounds.size.x / 2, modelBounds.size.z / 2, target) basePoints = getRectangleVectorPoints(0, modelBounds.size.x / 2, modelBounds.size.z / 2, target) else local baseRadiuses = (presetBase == nil) and determineBaseInInches(target) or presetBase measuringPoints = getCircleVectorPoints(newRadius, baseRadiuses.x, baseRadiuses.z, target) basePoints = getCircleVectorPoints(0, baseRadiuses.x, baseRadiuses.z, target) end measuring.points = measuringPoints base.points = basePoints table.insert(measuringRings, measuring) table.insert(measuringRings, base) end target.setVectorLines(measuringRings) target.setTable("ym-measuring-circles", measuringRings) end -- Clear measuring circles helper. -- LMB (alt=false): clear bubbles on every object on the table. -- RMB (alt=true): clear bubbles only on the player's currently selected objects. function clearMeasuringCircles(obj, playerColor, alt) local targets if alt then local playerObj = Player[playerColor] if not playerObj then return end targets = playerObj.getSelectedObjects() if #targets == 0 then printToColor("No objects selected. Right-click clears bubbles on selected objects only.", playerColor, "Yellow") return end else targets = getAllObjects() end local cleared = 0 for _, o in ipairs(targets) do local bubbles = o.getTable("ym-measuring-circles") if bubbles ~= nil then o.setVectorLines({}) o.setTable("ym-measuring-circles", nil) cleared = cleared + 1 end end if alt then printToColor("Cleared measuring bubbles on "..cleared.." selected object(s).", playerColor, {1,1,1}) else printToAll("Cleared measuring bubbles on "..cleared.." objects.", {1,1,1}) end end function clearHandDrawnLines(obj, playerColor, alt) local clearedAll = 0 if coherencyMonitorActive then stopCoherencyMonitor() end Global.setVectorLines({}) for _, o in ipairs(getAllObjects()) do if o.getTable("ym-measuring-circles") == nil then local lines = o.getVectorLines() if lines and #lines > 0 then o.setVectorLines({}) clearedAll = clearedAll + 1 end end end printToAll("Cleared hand-drawn lines on "..clearedAll.." objects.", {1,1,1}) end -- Highlight ownership coordination with SelectionHighlighter. While our monitors -- glow a model, SelectionHighlighter must leave that model alone, otherwise it -- snapshots our colour as the model's "original" and re-applies it on deselect -- (so the glow survives the monitor). hold/release tell it to stand off and -- resume. No-ops when the highlighter object isn't present. local SELECTION_HIGHLIGHTER_GUID = "27de4f" local function holdSelectionHighlight(guid) local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) if sh ~= nil then sh.call("holdHighlight", {guid = guid}) end end local function releaseSelectionHighlight(guid) local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) if sh ~= nil then sh.call("releaseHighlight", {guid = guid}) end end -- Live coherency / range monitor for a captured set of models. -- Measures base-to-base (edge-to-edge) on the table plane (x/z), ignoring height, -- and enforces two rules: -- * Coherency: every model must have at least one other model within 2". -- * Range: every model must be within 9" of every other model. -- While active, a repeating timer re-reads the models' live positions and -- redraws the breach lines so they follow the models (and appear/disappear as -- breaches resolve), like the measuring bubbles. Lines are global vectors. -- * orange = model out of 2" coherency (drawn to its nearest model) -- * red = a pair of models more than 9" apart coherencyMonitorActive = false coherencyMonitorGuids = {} coherencyMonitorTimer = nil coherencyTimeoutTimer = nil -- one-shot auto-stop timer coherencyLabels = {} -- pool of 3DText label entries {obj,key,pos,text,color,active} -- Cached pairwise geometry so the live monitor doesn't recompute the whole -- O(n^2) base-gap matrix every tick. Indices are 1..#coherencyMonitorGuids and -- stay aligned with coherencyDesc; a deletion triggers a full rebuild that -- compacts the dead entry out. Routine movement only refreshes the moved -- models' rows (O(moving * n)) via the motion loop below. coherencyDesc = {} -- desc[i] = base descriptor for monitored model i coherencyGap = {} -- gap[i][j] = cached base-to-base gap coherencyCp = {} -- cp[i][j] = base-edge point on i toward j (for drawing) coherencyIndexByGuid = {} -- guid -> current index, for O(1) event filtering coherencyMotionTimer = nil -- runs ONLY while monitored models are moving coherencyMotionTicks = 0 -- motion-loop tick counter, for the label cadence coherencyPrevMoving = {} -- set of indices that were moving last tick coherencyHighlighted = {} -- guid -> true: monitored models currently glowing green local COHERENCY_RANGE = 2 -- inches, base-to-base local COHERENCY_OK_COLOR = {0, 1, 0} -- green glow on a model that is in coherency local COHERENCY_MAX = 9 -- inches, base-to-base local COHERENCY_LIFT = 0.2 -- raise lines slightly so they sit above the bases local COHERENCY_LABEL_LIFT = 0.5 -- raise distance text a little above the lines local COHERENCY_REFRESH = 0.1 -- seconds between redraws WHILE models are moving local COHERENCY_TIMEOUT = 15 -- auto-stop if left on this many seconds -- Soft backstop on selection size. The monitor is now event-driven (idle costs -- nothing; a moving model only re-measures its own row, O(moving * n)), so this -- can safely be raised well beyond the old O(n^2)-per-tick limit if desired. local COHERENCY_MAX_MODELS = 30 -- Hard cap on how many breach lines/labels are ever drawn at once. A unit split -- into two clusters >9" apart breaches on a*b pairs (a 12+12 split = 144 lines), -- and each line is a vector line + a 3DText label -- enough to choke the table. -- We cap the rendered set so the draw cost stays bounded no matter the spread. local COHERENCY_MAX_LINES = 24 -- The 3DText distance labels are far costlier than the (batched) vector lines: -- each is a separate object needing setPosition + TextTool calls. So update them -- only every Nth motion tick (lines still follow every tick), and never more -- than a couple of them. Labels are diffed and parked (never destroyed mid-run). local COHERENCY_LABEL_EVERY = 2 -- update labels every Nth motion tick (lines: every tick) local COHERENCY_LABEL_PARK = {0, -50, 0} -- offscreen spot for unused (parked) labels local COHERENCY_LABEL_TAG = "coherencyLabel" -- GMNotes marker for sweep-on-load -- Spawn a flat, locked 3DText object to use as a floating distance label. -- Tagged via GMNotes so any label that leaks into a save is swept on load. local function spawnCoherencyLabel(pos, text, color) return spawnObjectData({ data = { Name = "3DText", Transform = { posX = pos[1], posY = pos[2], posZ = pos[3], rotX = 90, rotY = 0, rotZ = 0, scaleX = 1, scaleY = 1, scaleZ = 1 }, Text = { Text = text, colorstate = { r = color[1], g = color[2], b = color[3] }, fontSize = 32 }, Locked = true, GMNotes = COHERENCY_LABEL_TAG } }) end -- Destroy any coherency labels left in the world (e.g. from a save taken while -- the monitor was active). Identified by their GMNotes marker. Run on load. function cleanupOrphanCoherencyLabels() for _, o in ipairs(getAllObjects()) do if o.getGMNotes() == COHERENCY_LABEL_TAG then destroyObject(o) end end end -- Reconcile the label pool with the requested specs ({key, pos, text, color}). -- Each spec is keyed by its breach (rule + model pair) so a label keeps a stable -- pool slot across ticks and only its OWN object's API calls fire when its pos/ -- text/colour actually change (diffing). Labels for breaches that vanished are -- PARKED offscreen and blanked -- never destroyed -- so an oscillating breach -- count during a drag causes no spawn/destroy churn. local function applyLabel(e, spec, force) local o = e.obj if o == nil or o.isDestroyed() then return end if force or e.pos[1] ~= spec.pos[1] or e.pos[2] ~= spec.pos[2] or e.pos[3] ~= spec.pos[3] then o.setPosition(spec.pos) e.pos[1], e.pos[2], e.pos[3] = spec.pos[1], spec.pos[2], spec.pos[3] end if force or e.text ~= spec.text then o.TextTool.setValue(spec.text) e.text = spec.text end if force or e.color[1] ~= spec.color[1] or e.color[2] ~= spec.color[2] or e.color[3] ~= spec.color[3] then o.TextTool.setFontColor(spec.color) e.color[1], e.color[2], e.color[3] = spec.color[1], spec.color[2], spec.color[3] end end local function updateCoherencyLabels(specs) -- Index currently-active labels by key for O(1) reuse. local byKey = {} for _, e in ipairs(coherencyLabels) do if e.active and e.key ~= nil then byKey[e.key] = e end end local usedKeys = {} for _, spec in ipairs(specs) do usedKeys[spec.key] = true local e = byKey[spec.key] if e ~= nil then applyLabel(e, spec, false) -- same breach as last tick: diff, skip unchanged else -- Reuse a parked entry if one exists, else spawn a new label object. local parked = nil for _, cand in ipairs(coherencyLabels) do if not cand.active then parked = cand; break end end if parked ~= nil then parked.key = spec.key parked.active = true applyLabel(parked, spec, true) -- repurposed: force all fields byKey[spec.key] = parked else local lbl = spawnCoherencyLabel(spec.pos, spec.text, spec.color) local entry = { obj = lbl, key = spec.key, active = true, pos = {spec.pos[1], spec.pos[2], spec.pos[3]}, text = spec.text, color = {spec.color[1], spec.color[2], spec.color[3]}, } coherencyLabels[#coherencyLabels + 1] = entry byKey[spec.key] = entry end end end -- Park any label whose breach is no longer shown: blank it and move it -- offscreen once, then leave it idle for later reuse (no destroy). for _, e in ipairs(coherencyLabels) do if e.active and not usedKeys[e.key] then e.active = false e.key = nil local o = e.obj if o ~= nil and not o.isDestroyed() then if e.text ~= "" then o.TextTool.setValue(""); e.text = "" end o.setPosition(COHERENCY_LABEL_PARK) e.pos[1], e.pos[2], e.pos[3] = COHERENCY_LABEL_PARK[1], COHERENCY_LABEL_PARK[2], COHERENCY_LABEL_PARK[3] end end end end function stopCoherencyMonitor() coherencyMonitorActive = false if coherencyMonitorTimer ~= nil then Wait.stop(coherencyMonitorTimer) coherencyMonitorTimer = nil end if coherencyMotionTimer ~= nil then Wait.stop(coherencyMotionTimer) coherencyMotionTimer = nil end if coherencyTimeoutTimer ~= nil then Wait.stop(coherencyTimeoutTimer) coherencyTimeoutTimer = nil end -- Drop the green coherency glow from every model we lit, and hand ownership -- back to SelectionHighlighter. for guid in pairs(coherencyHighlighted) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then o.highlightOff() end releaseSelectionHighlight(guid) end coherencyHighlighted = {} coherencyMonitorGuids = {} coherencyDesc = {} coherencyGap = {} coherencyCp = {} coherencyIndexByGuid = {} coherencyMotionTicks = 0 coherencyPrevMoving = {} for _, e in ipairs(coherencyLabels) do if e.obj ~= nil and not e.obj.isDestroyed() then destroyObject(e.obj) end end coherencyLabels = {} Global.setVectorLines({}) end -- Build a lightweight descriptor for a model's base: world centre, the base's -- two semi-radii (a along local x, b along local z, in inches) and the model's -- local x/z unit axes. The base is treated as an ellipse that rotates with the -- model -- getTransformRight()/Forward() are unit vectors of the model's local -- axes, so this is rotation-correct for ovals without any handedness guess. local function buildBaseDescriptor(o) local base = determineBaseInInches(o) return { pos = o.getPosition(), a = base.x, b = base.z, right = o.getTransformRight(), forward = o.getTransformForward() } end -- Radius of the descriptor's ellipse base along world unit direction (dx,dz), -- via the ellipse polar equation. Exact for circles and for ovals along the -- centre-to-centre line. local function baseRadiusInDir(d, dx, dz) local lx = dx * d.right.x + dz * d.right.z -- direction in local x (unit axis) local lz = dx * d.forward.x + dz * d.forward.z -- direction in local z (unit axis) return 1 / math.sqrt((lx * lx) / (d.a * d.a) + (lz * lz) / (d.b * d.b)) end -- Base-to-base (edge-to-edge) gap between two descriptors, measured along the -- centre-to-centre line, plus the base-edge point on each (for drawing). O(1). local function baseGap(d1, d2) local dx, dz = d2.pos.x - d1.pos.x, d2.pos.z - d1.pos.z local centre = math.sqrt(dx * dx + dz * dz) if centre < 1e-6 then return 0, d1.pos, d2.pos -- coincident centres end local ux, uz = dx / centre, dz / centre local r1 = baseRadiusInDir(d1, ux, uz) local r2 = baseRadiusInDir(d2, -ux, -uz) local gap = centre - r1 - r2 if gap < 0 then gap = 0 end -- bases touching/overlapping local p1 = { x = d1.pos.x + ux * r1, z = d1.pos.z + uz * r1 } local p2 = { x = d2.pos.x - ux * r2, z = d2.pos.z - uz * r2 } return gap, p1, p2 end -- Recompute just one model's row/column of the cached gap/cp matrices after its -- descriptor (coherencyDesc[i]) has been refreshed. O(n). Used by the motion -- loop so a moving model only re-measures against the others, not every pair. local function recomputeCoherencyRow(i) local di = coherencyDesc[i] if di == nil then return end for j = 1, #coherencyDesc do if j ~= i and coherencyDesc[j] ~= nil then local g, pi, pj = baseGap(di, coherencyDesc[j]) coherencyGap[i][j], coherencyGap[j][i] = g, g coherencyCp[i][j], coherencyCp[j][i] = pi, pj end end end -- Full O(n^2) rebuild of the cache: resolve the monitored set to live objects -- (compacting any that were deleted so indices stay dense and aligned), then -- recompute every pairwise gap once. Returns the live model count. Called on -- start and when a monitored model is deleted -- NOT every tick. local function coherencyFullRecompute() local guids, desc, indexByGuid = {}, {}, {} for _, guid in ipairs(coherencyMonitorGuids) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then guids[#guids + 1] = guid desc[#desc + 1] = buildBaseDescriptor(o) indexByGuid[guid] = #guids end end coherencyMonitorGuids = guids coherencyDesc = desc coherencyIndexByGuid = indexByGuid local n = #desc coherencyGap, coherencyCp = {}, {} for i = 1, n do coherencyGap[i] = {}; coherencyCp[i] = {} end for i = 1, n do for j = i + 1, n do local g, pi, pj = baseGap(desc[i], desc[j]) coherencyGap[i][j], coherencyGap[j][i] = g, g coherencyCp[i][j], coherencyCp[j][i] = pi, pj end end return n end -- Build the breach lines/labels from the CACHED gap/cp (no base-gap math here) -- and push them to the table. Lines update on every call; labels are far costlier -- (per-object 3DText API calls) so they're only reconciled when updateLabels is -- true (see the motion loop's cadence) and we draw a number on only a couple of -- lines -- the coherency (orange) breaches plus the single worst range (red) one. -- The rendered line set is bounded by COHERENCY_MAX_LINES. -- * orange = a model with no other model within 2" (drawn to its nearest) -- * red = a model out of 9" range (drawn to its single worst partner) local function drawCoherency(updateLabels) if not coherencyMonitorActive then return end local desc, gap, cp = coherencyDesc, coherencyGap, coherencyCp local n = #desc if n < 2 then stopCoherencyMonitor() return end -- Collect breach candidates, then render only the most important ones (up to -- COHERENCY_MAX_LINES) so a wide spread can't flood the table with lines. local breaches = {} -- { i, j, d, color, critical } local function add(i, j, d, color, critical) breaches[#breaches + 1] = { i = i, j = j, d = d, color = color, critical = critical } end -- Rule 1: every model needs at least one other model within 2" base-to-base. -- If none qualifies, flag the gap to its nearest neighbour (orange, critical). -- coherent[i] records the per-model status so we can glow it green below. local coherent = {} for i = 1, n do local hasBuddy = false local nearest, nearestDist = nil, math.huge for j = 1, n do if i ~= j then local d = gap[i][j] if d ~= nil then if d <= COHERENCY_RANGE then hasBuddy = true end if d < nearestDist then nearestDist = d; nearest = j end end end end coherent[i] = hasBuddy if not hasBuddy and nearest then add(i, nearest, nearestDist, {1, 0.5, 0}, true) end end -- Glow each monitored model green while it is in coherency (in-2" of a buddy) -- for at-a-glance clarity; drop the glow when it falls out. We take ownership -- from SelectionHighlighter while glowing (hold) and hand it back when we stop -- (release), so the green can't be snapshotted and restored on deselect. for i = 1, n do local guid = coherencyMonitorGuids[i] local o = guid and getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then if coherent[i] then if not coherencyHighlighted[guid] then holdSelectionHighlight(guid) o.highlightOn(COHERENCY_OK_COLOR) coherencyHighlighted[guid] = true end elseif coherencyHighlighted[guid] then o.highlightOff() releaseSelectionHighlight(guid) coherencyHighlighted[guid] = nil end end end -- Rule 2: every model must be within 9" of every other. Drawing a line per -- breaching PAIR is O(n^2) lines (a 12+12 split = 144); instead flag at most -- one line per model -- to its single worst out-of-range partner, deduped -- -- so a split unit draws ~n lines while still marking every offending model. local redSeen = {} for i = 1, n do local worstJ, worstD = nil, COHERENCY_MAX for j = 1, n do if i ~= j then local d = gap[i][j] if d ~= nil and d > worstD then worstD = d; worstJ = j end end end if worstJ then local key = (i < worstJ) and (i * 100000 + worstJ) or (worstJ * 100000 + i) if not redSeen[key] then redSeen[key] = true add(i, worstJ, worstD, {1, 0, 0}, false) end end end -- Render the most important breaches up to the hard cap: critical coherency -- (orange) breaches first, then the most severe range (red) breaches by gap. table.sort(breaches, function(a, b) if a.critical ~= b.critical then return a.critical end return a.d > b.d end) local shown = math.min(#breaches, COHERENCY_MAX_LINES) local lines = {} local labelSpecs = updateLabels and {} or nil local redLabelDrawn = false for k = 1, shown do local br = breaches[k] local pi, pj = cp[br.i][br.j], cp[br.j][br.i] if pi ~= nil and pj ~= nil then local yi, yj = desc[br.i].pos.y, desc[br.j].pos.y lines[#lines + 1] = { points = {{pi.x, yi + COHERENCY_LIFT, pi.z}, {pj.x, yj + COHERENCY_LIFT, pj.z}}, color = br.color, thickness = 0.15, } -- Number only the orange breaches and the single worst red one. if updateLabels then local wantLabel = br.critical if not br.critical and not redLabelDrawn then wantLabel = true; redLabelDrawn = true end if wantLabel then local lo, hi = br.i, br.j if lo > hi then lo, hi = hi, lo end labelSpecs[#labelSpecs + 1] = { key = (br.critical and "O" or "R") .. lo .. ":" .. hi, pos = {(pi.x + pj.x) / 2, (yi + yj) / 2 + COHERENCY_LABEL_LIFT, (pi.z + pj.z) / 2}, text = string.format("%.1f\"", br.d), color = br.color, } end end end end Global.setVectorLines(lines) if updateLabels then updateCoherencyLabels(labelSpecs) end end -- Motion loop. Runs ONLY while one or more monitored models are in motion -- (held or not yet resting). Each tick it refreshes just the moving models' -- descriptors and recomputes their rows (O(moving * n)), then redraws. When -- nothing is moving it stops itself; the cached lines stay on the table until -- the next pickup/drop. A deleted model triggers a one-off full rebuild. local function coherencyMotionTick() if not coherencyMonitorActive then return end coherencyMotionTicks = coherencyMotionTicks + 1 local movingIdx, movingObj, needsRebuild = {}, {}, false for i = 1, #coherencyMonitorGuids do local o = getObjectFromGUID(coherencyMonitorGuids[i]) if o == nil or o.isDestroyed() then needsRebuild = true elseif o.held_by_color ~= nil or o.resting == false then movingIdx[i] = true movingObj[i] = o end end if needsRebuild then coherencyPrevMoving = {} if coherencyFullRecompute() < 2 then stopCoherencyMonitor() else drawCoherency(true) end return end -- Recompute the union of models moving now and last tick, so the tick a model -- comes to rest its final position is captured before the loop stops. local refresh = {} for i in pairs(movingIdx) do refresh[i] = true end for i in pairs(coherencyPrevMoving) do if movingIdx[i] == nil then local o = getObjectFromGUID(coherencyMonitorGuids[i]) if o ~= nil and not o.isDestroyed() then movingObj[i] = o; refresh[i] = true end end end for i in pairs(refresh) do coherencyDesc[i] = buildBaseDescriptor(movingObj[i]) end for i in pairs(refresh) do recomputeCoherencyRow(i) end coherencyPrevMoving = movingIdx if next(movingIdx) == nil then -- Everything is at rest: draw once WITH labels (accurate final numbers), -- then stop polling. Cache and drawn lines persist until the next pickup. drawCoherency(true) coherencyPrevMoving = {} if coherencyMotionTimer ~= nil then Wait.stop(coherencyMotionTimer) coherencyMotionTimer = nil end return end -- Lines every tick (cheap, batched); labels only every Nth tick (costly). drawCoherency(coherencyMotionTicks % COHERENCY_LABEL_EVERY == 0) end local function startCoherencyMotionLoop() if not coherencyMonitorActive then return end if coherencyMotionTimer ~= nil then return end -- already running coherencyMotionTimer = Wait.time(coherencyMotionTick, COHERENCY_REFRESH, -1) end -- Manual full redraw / back-compat entry point. The live monitor no longer -- calls this every tick -- routine movement updates only the moved rows via the -- motion loop; this rebuilds the whole cache and redraws on demand. function refreshCoherencyLines() if not coherencyMonitorActive then return end if coherencyFullRecompute() < 2 then stopCoherencyMonitor() return end drawCoherency(true) end -- Object events (delivered for ALL objects). Kept cheap: bail immediately when -- the monitor is off or the object isn't part of the monitored set. function onObjectPickUp(player_color, obj) if not coherencyMonitorActive then return end if coherencyIndexByGuid[obj.getGUID()] == nil then return end startCoherencyMotionLoop() end function onObjectDrop(player_color, obj) if not coherencyMonitorActive then return end local i = coherencyIndexByGuid[obj.getGUID()] if i == nil then return end -- Snap the lines to the model's new spot immediately (covers a drop that -- lands already-resting), then run the motion loop to track any physics -- settle; it self-stops once everything is at rest. coherencyDesc[i] = buildBaseDescriptor(obj) recomputeCoherencyRow(i) drawCoherency(true) startCoherencyMotionLoop() end function onObjectDestroy(obj) if not coherencyMonitorActive then return end if coherencyIndexByGuid[obj.getGUID()] == nil then return end -- The object still exists during this event; defer a frame so the rebuild's -- getObjectFromGUID skips it, then recompute the smaller set (or stop). Wait.frames(function() if not coherencyMonitorActive then return end if coherencyFullRecompute() < 2 then stopCoherencyMonitor() else drawCoherency(true) end end, 1) end -- Drive the live coherency monitor. -- LEFT click -> (re)start the monitor on the player's selected models. -- RIGHT click -> stop it and remove the lines/labels. function checkCoherency(a, b, c) -- Support button (a=object, b=color, c=altClick) and hotkey (a=color) -- invocation, mirroring arrangeModelsWith2Inch. local playerColor, alt if type(a) == "string" then playerColor = a -- hotkey: no right-click concept alt = false else playerColor = b -- button alt = c and true or false end -- Right click removes the lines (stops the monitor). if alt then if coherencyMonitorActive then stopCoherencyMonitor() if playerColor then printToColor("Live coherency check OFF.", playerColor, {1, 1, 1}) end elseif playerColor then printToColor("No coherency check is running.", playerColor, {1, 1, 1}) end return end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. tostring(playerColor)) return end local objs = playerObj.getSelectedObjects() if #objs < 2 then -- Leave any running monitor untouched on an empty/short selection. printToColor("Select at least 2 models to check coherency.", playerColor, {1, 0.5, 0}) return end -- Soft cap so an accidental whole-army box-select doesn't pay a big one-off -- O(n^2) recompute. Leave any running monitor untouched. if #objs > COHERENCY_MAX_MODELS then broadcastToColor(string.format("Coherency check skipped: %d models selected (max %d). Select a single unit.", #objs, COHERENCY_MAX_MODELS), playerColor, {1, 0.3, 0.3}) return end -- Left click (re)starts on the current selection. Stop any prior run first -- so a fresh selection fully replaces the old monitored set. if coherencyMonitorActive then stopCoherencyMonitor() end -- Capture the monitored set by GUID so it keeps tracking these models even -- after they are deselected or moved. coherencyMonitorGuids = {} for _, o in ipairs(objs) do table.insert(coherencyMonitorGuids, o.getGUID()) end coherencyMonitorActive = true coherencyMotionTicks = 0 coherencyPrevMoving = {} -- One full O(n^2) recompute now; after this the lines are only refreshed -- when a monitored model actually moves (onObjectPickUp/onObjectDrop start -- the motion loop), so an idle check costs nothing. if coherencyFullRecompute() < 2 then stopCoherencyMonitor() printToColor("Coherency needs at least 2 live models.", playerColor, {1, 0.5, 0}) return end drawCoherency(true) -- Auto-stop after COHERENCY_TIMEOUT seconds so a forgotten monitor does not -- keep redrawing forever. Manual stop / restart cancels this in stopCoherencyMonitor. coherencyTimeoutTimer = Wait.time(function() if coherencyMonitorActive then stopCoherencyMonitor() if playerColor then printToColor(string.format("Live coherency check auto-stopped after %ds.", COHERENCY_TIMEOUT), playerColor, {1, 1, 1}) end end end, COHERENCY_TIMEOUT) printToColor(string.format("Live coherency check ON for %d models. Orange = out of 2\" coherency, Red = out of 9\" range (one line per offending model). Lines follow models as you move them; auto-stops after %ds or RIGHT-CLICK Coherency to remove them.", #objs, COHERENCY_TIMEOUT), playerColor, {0, 1, 0}) end -- ==================================================================== -- Engagement Range -- Highlight every model on the table whose base is within ENGAGEMENT_RANGE -- inches of ANY currently-selected model, then keep that set live for -- ENGAGEMENT_HIGHLIGHT seconds: it re-scans every ENGAGEMENT_REFRESH seconds -- so models that move into range light up and models that move out go dark. -- Distances reuse the same base-to-base geometry (buildBaseDescriptor + -- baseGap) as the coherency monitor, so an oval base is measured edge-to-edge, -- rotation-correct. "Models" are every Figurine/Generic/Custom_Model on the -- table; the captured selection is excluded, and each neighbour counts once. -- ==================================================================== local ENGAGEMENT_RANGE = 2 -- inches, base-to-base local ENGAGEMENT_HIGHLIGHT = 15 -- seconds the live highlight runs local ENGAGEMENT_REFRESH = 0.15 -- seconds between re-scans during the window local ENGAGEMENT_COLOR = {1, 0.15, 0.15} -- red glow on neighbours in range local ENGAGEMENT_MAX_SELECTED = 30 -- skip an accidental whole-army box-select -- A table object counts as a "model" using the same tag set as -- SelectionHighlighter (Figurine / Generic / Custom_Model). local function isModelObject(o) local t = o.tag return t == "Figurine" or t == "Generic" or t == "Custom_Model" end -- Live monitor state. Selection is captured by GUID so it keeps tracking the -- same models as they (or their neighbours) move during the window. engagementMonitorActive = false engagementSelGuids = {} -- captured selection engagementHighlighted = {} -- guid -> true: models we are currently glowing engagementTimer = nil -- repeating ENGAGEMENT_REFRESH tick engagementTicksLeft = 0 -- ticks remaining before auto-stop function stopEngagementMonitor() engagementMonitorActive = false if engagementTimer ~= nil then Wait.stop(engagementTimer) engagementTimer = nil end -- Drop only the highlights we applied (leave any we never touched alone), and -- hand ownership back to SelectionHighlighter. for guid in pairs(engagementHighlighted) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then o.highlightOff() end releaseSelectionHighlight(guid) end engagementHighlighted = {} engagementSelGuids = {} engagementTicksLeft = 0 end -- One scan pass: resolve the captured selection to live models, find every -- other model within range, and reconcile glow on/off against last tick. -- Returns the in-range count, or nil if the whole selection is gone. local function engagementScan() local selectedSet, selDesc = {}, {} for _, guid in ipairs(engagementSelGuids) do local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then selectedSet[guid] = true selDesc[#selDesc + 1] = buildBaseDescriptor(o) end end if #selDesc == 0 then return nil end -- every selected model deleted local nowSet, count = {}, 0 for _, o in ipairs(getAllObjects()) do local guid = o.getGUID() if isModelObject(o) and not selectedSet[guid] and not nowSet[guid] then local od = buildBaseDescriptor(o) for _, sd in ipairs(selDesc) do if baseGap(sd, od) <= ENGAGEMENT_RANGE then nowSet[guid] = true count = count + 1 break end end end end -- Reconcile: only toggle models that crossed the threshold this tick. Take -- ownership from SelectionHighlighter while glowing (hold) and return it when -- the model leaves range (release), so the red can't be restored on deselect. for guid in pairs(engagementHighlighted) do if not nowSet[guid] then local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then o.highlightOff() end releaseSelectionHighlight(guid) end end for guid in pairs(nowSet) do if not engagementHighlighted[guid] then local o = getObjectFromGUID(guid) if o ~= nil and not o.isDestroyed() then holdSelectionHighlight(guid) o.highlightOn(ENGAGEMENT_COLOR) end end end engagementHighlighted = nowSet return count end function showEngagementRange(a, b, c) -- Support button (a=object, b=color, c=altClick) and hotkey (a=color) -- invocation, mirroring checkCoherency. local playerColor, alt if type(a) == "string" then playerColor = a alt = false else playerColor = b alt = c and true or false end -- Right click stops the monitor and clears the glow before the window ends. if alt then if engagementMonitorActive then stopEngagementMonitor() if playerColor then printToColor("Engagement range OFF.", playerColor, {1, 1, 1}) end elseif playerColor then printToColor("No engagement range is running.", playerColor, {1, 1, 1}) end return end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. tostring(playerColor)) return end local sel = playerObj.getSelectedObjects() if #sel < 1 then printToColor("Select at least one model to check engagement range.", playerColor, {1, 0.5, 0}) return end if #sel > ENGAGEMENT_MAX_SELECTED then broadcastToColor(string.format("Engagement range skipped: %d models selected (max %d).", #sel, ENGAGEMENT_MAX_SELECTED), playerColor, {1, 0.3, 0.3}) return end -- A fresh click fully replaces any prior run. if engagementMonitorActive then stopEngagementMonitor() end engagementSelGuids = {} for _, o in ipairs(sel) do engagementSelGuids[#engagementSelGuids + 1] = o.getGUID() end engagementMonitorActive = true engagementHighlighted = {} engagementTicksLeft = math.ceil(ENGAGEMENT_HIGHLIGHT / ENGAGEMENT_REFRESH) local count = engagementScan() or 0 if count == 0 then printToColor(string.format("No models within %g\" of the selection.", ENGAGEMENT_RANGE), playerColor, {1, 1, 1}) else broadcastToColor(string.format("%d model(s) within %g\" of the selection -- live for %ds (refreshing every %.1fs). RIGHT-CLICK Engagement Range to clear early.", count, ENGAGEMENT_RANGE, ENGAGEMENT_HIGHLIGHT, ENGAGEMENT_REFRESH), playerColor, {0, 1, 0}) end -- Refresh loop: re-scan every ENGAGEMENT_REFRESH seconds, auto-stop after -- the window elapses or once the whole selection is gone. engagementTimer = Wait.time(function() if not engagementMonitorActive then return end if engagementScan() == nil then stopEngagementMonitor() return end engagementTicksLeft = engagementTicksLeft - 1 if engagementTicksLeft <= 0 then stopEngagementMonitor() printToAll(string.format("Engagement range check finished (%ds elapsed).", ENGAGEMENT_HIGHLIGHT), {1, 1, 1}) end end, ENGAGEMENT_REFRESH, -1) end -- ==================================================================== -- Highlight painter -- LEFT-clicking Highlight paints the player's selected models with the colour -- currently shown on the swatch next to it; the swatch cycles its palette on -- click (right-click cycles backwards) and its face IS the chosen colour. -- Paint is permanent for the session: a paint applied while a model is selected -- is also written into SelectionHighlighter's restore-on-deselect snapshot -- (setPersistentHighlight), so deselecting restores the paint instead of wiping -- it. RIGHT-clicking Highlight removes highlights from every object on the -- table -- except models a live coherency/engagement monitor is glowing (their -- externalHolds in SelectionHighlighter), which clean up when the monitor stops. -- ==================================================================== local HIGHLIGHT_PALETTE = { {name = "Yellow", rgb = {1, 1, 0}}, {name = "Orange", rgb = {1, 0.5, 0}}, {name = "Red", rgb = {1, 0, 0}}, {name = "Pink", rgb = {1, 0.4, 0.7}}, {name = "Purple", rgb = {0.7, 0.2, 1}}, {name = "Blue", rgb = {0.2, 0.4, 1}}, {name = "Teal", rgb = {0, 0.9, 0.9}}, {name = "Green", rgb = {0, 1, 0}}, {name = "White", rgb = {1, 1, 1}}, } highlightColorIndex = 1 highlightSwatchIndex = nil -- resolved in onload after the swatch is created -- Repaint the swatch's top button to the chosen colour (hover/press too, so it -- reads as a solid colour chip) and name the colour in its tooltip. function refreshHighlightSwatch() if highlightSwatchIndex == nil then return end local c = HIGHLIGHT_PALETTE[highlightColorIndex] self.editButton({ index = highlightSwatchIndex, color = c.rgb, hover_color = c.rgb, press_color = c.rgb, tooltip = "Highlight color: " .. c.name .. "\nLEFT CLICK: next color\nRIGHT CLICK: previous color", }) end function cycleHighlightColor(_, _, alt) local step = alt and -1 or 1 highlightColorIndex = ((highlightColorIndex - 1 + step) % #HIGHLIGHT_PALETTE) + 1 refreshHighlightSwatch() end -- Remove highlights from every object on the table (both players' paint, and -- any stray glow), then reset SelectionHighlighter's tracking so currently -- selected models fall back to the plain player-colour selection glow on its -- next tick. Models glowed by a live monitor (held in SelectionHighlighter's -- externalHolds) are left alone -- the monitor owns their highlight lifecycle. function clearAllHighlightPaint(playerColor) local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) local holds = {} if sh ~= nil then holds = sh.call("getExternalHolds") or {} sh.call("clearPersistentHighlights") end local n = 0 for _, o in ipairs(getAllObjects()) do if holds[o.getGUID()] == nil then if o.getHighlightColor() ~= nil then n = n + 1 end o.highlightOff() end end if playerColor then printToColor(string.format("Removed highlights from %d object(s).", n), playerColor, {1, 1, 1}) end end -- LEFT click: paint the selection in the swatch colour. RIGHT click: clear all. function applyHighlightPaint(a, b, c) -- Support button (a=object, b=color, c=altClick) and hotkey (a=color) -- invocation, mirroring checkCoherency. local playerColor, alt if type(a) == "string" then playerColor = a alt = false else playerColor = b alt = c and true or false end if alt then clearAllHighlightPaint(playerColor) return end local playerObj = Player[playerColor] if not playerObj then print("Player object not found for color: " .. tostring(playerColor)) return end local sel = playerObj.getSelectedObjects() if #sel == 0 then printToColor("Select at least one model to highlight.", playerColor, {1, 0.5, 0}) return end local col = HIGHLIGHT_PALETTE[highlightColorIndex] local sh = getObjectFromGUID(SELECTION_HIGHLIGHTER_GUID) local n = 0 for _, o in ipairs(sel) do if not o.isDestroyed() then o.highlightOn(col.rgb) -- The model is selected, so SelectionHighlighter has snapshotted (or -- is about to snapshot) an "original" colour to restore on deselect; -- point that snapshot at our paint so deselecting keeps it. if sh ~= nil then sh.call("setPersistentHighlight", {guid = o.getGUID(), color = col.rgb}) end n = n + 1 end end printToColor(string.format("Highlighted %d model(s) in %s. RIGHT-CLICK Highlight to remove all highlights.", n, col.name), playerColor, col.rgb) end LuaScriptStateXmlUIAttachedSnapPoints=05Position&x @y?z49WGUID738804NameBlockRectangleTransformposX;J@posYposZ@F rotXrotYp@rotZscaleX?scaleY?scaleZ?Nickname Start MenuDescriptionGMNotesAltLookAngle&xyzColorDiffuse&rg?b`ZZ?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsLuaScript-- FTC-GUID: 738804 -- CONTENT: -- size changer manager -- deploy zones manager -- objectives manager -- mission manager -- game manager menuX = 18 menuZ = 1.05-1.05 -- Resolved by initGlobalRefs once Global is ready (this script can load before -- Global's onLoad has set its variables). MISSION_PACK_PARIAH_NEXUS = nil keepForTerrainEditor = true mat_GUID = nil -- Defer a callback until Global's onLoad has run (it sets FTC_Global_Ready -- last). After 10s run it anyway rather than hang a broken load forever. function whenGlobalReady(callback) Wait.condition(callback, function() return Global.getVar("FTC_Global_Ready") == true end, 10, callback) end function initGlobalRefs() MISSION_PACK_PARIAH_NEXUS = Global.getVar("MISSION_PACK_PARIAH_NEXUS") mat_GUID = Global.getVar("mat_GUID") objectiveDebug = Global.getVar("DEBUG") == true end ttsDebugEnabled = false dispositionValues = {"Disruption", "Priority Assets", "Purge the Foe", "Reconnaissance", "Take and Hold"} blueDispositionSelected = 1 redDispositionSelected = 3 matTextureSelected = 1 missionSetupControlsVisible = false -- True for the per-size singles buttons (Colosseum / Strike Force / Boarding -- Action). Like Combat Patrol these run with no mission-matrix bags/tiles and a -- missions-only Generate (no fb4b5d layout art), but without the Combat Patrol map -- bag. gameModeUsesMatrixProps() folds this in alongside combatPatrolMode. matrixPropsDisabled = false mapMat_GUID = "28865a" -- Random city-mat image URLs, baked from data/all_mats.csv at compile time. The -- empty default keeps an uncompiled/dev build valid for the manual picker. CITY_MAT_URLS = {"https://steamusercontent-a.akamaihd.net/ugc/1043093746446003085/E258C52017E6CFA3D2BD5053FDE292706E6C1399/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445968121/F8160E0B97B075F107621F16DE3115580F38A19E/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445987621/9B1ECE8D5021326E91910E2E8D1716449C2371B8/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445966009/17627D39B4ECA43259F47177B2665EF213931D3B/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446002091/8D5C1A992B3430C31C8A7CE0C0BC52D676B3FBA4/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445977393/B6352D4A17B7CDFEF839F6D922A9EA0D164FE2E0/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446003935/7D5DE85C43BB1A9BAA5822D0D7F3910DF9400799/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445978663/72F0816759615062577B3F3C564AE975D2BA8E18/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445941162/9CE302C814E42354A742A668A7719C22E6222DDA/","https://steamusercontent-a.akamaihd.net/ugc/12624046404907267997/73874096A20FFDF5A6EC9DC400A9863D88F818F3/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445993785/76099AB45F8127F2172E1B9BEBDE4AF712C3B160/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445940009/8986860615D6EA3BDC766C3E70C9BB3CE0E4C90F/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445950892/66E74AD325C33BAE5ADFC3209A7C033ECF6024C3/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446001043/FD8732C59F057971B64B19988EF3DAAFBAA8362E/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445994782/529EA647E3EF162F6070AD29A21E06360087F00D/","https://steamusercontent-a.akamaihd.net/ugc/15717006480269992155/1D5D9D3EABC863CCAFC6A064E7815EEAE2D031CD/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445965043/AFF0BD148A484AB11F29E4B9C93365641ECBC2A4/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445953054/0A18AB4BF3A859BAFD83F16637ACD25BB10B5CCD/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445974108/59E23F0B6EB7538258A741955B86471F33783E30/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445958677/29E9FCD5998DBB1480386958C5310EE2D7E99735/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445986729/1FE89BF5A768B4115BBECE46B02DBBB3A3216835/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445960863/134D15495D71E42F3CD0197D3B5D4F8DB88A9E87/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445991519/6329B7A9D8B768F1DFF1A86BF3F59BD9CD9A799B/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445973071/4B9CE8AAD20D3681936B0C37855331D463FF6B92/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445951990/C688D63C1A67D1E06C6B3AB790DA6821BFA98517/","https://steamusercontent-a.akamaihd.net/ugc/14527255997544029385/E2130C37081E5977372E0D0C8693B7DCF7C18D32/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446000024/F1D7DFA0C0F2E0EF87516AED01F4C48DC7F4E917/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445975346/C84BE52FAEF185977514F199B9F113DD2A2FAAF1/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445926763/BDDA0A5D597C633C522001B869DAC6FFD5F3E42C/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445932228/E99449B8FF6C40FDB21B63D2E314486D6DD3E45A/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445979672/56124908188BA5736A8D7C33C43BAF5E6035B9DF/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445935246/14E522400874F5749E765D7CC148F6DD5D2769F1/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445931276/3F1FB65A71C230B725DF3E27FDD7243C7D920425/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445976301/BEDEAA465D0B7170B489927E7B9A0BEA1664744B/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445955187/501CF1A14573EC0BE1F5AC993A5BFC458840DA92/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445999073/49142AFD3E0BFF27CB4BE8D8B31F94FB3AE749CB/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445984219/8B02A6B86E739738C8152E09EA21A54DCE8F8A19/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445957111/55A94AA5D99709209A8D16A43FD27AC09B4AD68B/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445926144/1D63AAC188B5CAEAF514B041B3B4850A4F591715/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445962871/88014E0B982FE3F32ECAD5D7E98B703D25BC6341/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445996709/03CA12FF258E8E7AB6F5037F060142CA6F4B67B9/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446003935/7D5DE85C43BB1A9BAA5822D0D7F3910DF9400799/","https://steamusercontent-a.akamaihd.net/ugc/13075875539072127810/A106CFB44A2B064173E39ED207DD1D675374E25B/","https://steamusercontent-a.akamaihd.net/ugc/11172686107304348932/E8206C52E92A1B36AB7F1E21324EDE5A060D49EB/","https://steamusercontent-a.akamaihd.net/ugc/12998532639957617969/505FDF8F18C2B68A9287733F17F1652E6856B52F/"} -- @@CITY_MAT_URLS@@ -- Display names paralleling CITY_MAT_URLS (same order), used to label the Mat -- Randomizer picker. Baked from the all_mats.csv "terrain" column. CITY_MAT_NAMES = {"Badlands 1","Badlands 2","City 1","City 2","City 3","City 4","City 5","City 6","City 7","City 8","Desert 1","Desert 2","Desert 3","Desert 4","Desert 5","Desert 6","Desert City","Desert City 2","Forest City 1","Grass 1","Grass 2","Grass 3","Grass 4","Lava 1","Lava 2","Lunar Base 1","Mars 1","Mars 2","Mars 3","Mars 4","Moon 1","Moon 2","Moon 3","Platform 1","Platform 2","Platform 3","Railway","Snow 1","Snow 2","Snow 3","Snow 4","Snow City 1","Snow City 2","Swamp 1","Swamp 2"} -- @@CITY_MAT_NAMES@@ -- Curated mat image pools for automatic Battlemaster theme reskins. BM_MAT_RANDOMIZER_ENABLED = false -- @@BM_MAT_RANDOMIZER_ENABLED@@ BTTF_RUINS_MAT_URLS = {"https://steamusercontent-a.akamaihd.net/ugc/1043093746446003085/E258C52017E6CFA3D2BD5053FDE292706E6C1399/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445968121/F8160E0B97B075F107621F16DE3115580F38A19E/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445951990/C688D63C1A67D1E06C6B3AB790DA6821BFA98517/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445926144/1D63AAC188B5CAEAF514B041B3B4850A4F591715/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445962871/88014E0B982FE3F32ECAD5D7E98B703D25BC6341/","https://steamusercontent-a.akamaihd.net/ugc/11172686107304348932/E8206C52E92A1B36AB7F1E21324EDE5A060D49EB/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445979672/56124908188BA5736A8D7C33C43BAF5E6035B9DF/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445935246/14E522400874F5749E765D7CC148F6DD5D2769F1/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445975346/C84BE52FAEF185977514F199B9F113DD2A2FAAF1/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445932228/E99449B8FF6C40FDB21B63D2E314486D6DD3E45A/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446002091/8D5C1A992B3430C31C8A7CE0C0BC52D676B3FBA4/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445977393/B6352D4A17B7CDFEF839F6D922A9EA0D164FE2E0/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446003935/7D5DE85C43BB1A9BAA5822D0D7F3910DF9400799/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445974108/59E23F0B6EB7538258A741955B86471F33783E30/"} -- @@BTTF_RUINS_MAT_URLS@@ BTTF_RUINS_MAT_NAMES = {"Badlands 1","Badlands 2","Lava 2","Snow 2","Snow 3","Swamp 1","Moon 1","Moon 2","Mars 2","Mars 4","City 3","City 4","City 5","Forest City 1"} -- @@BTTF_RUINS_MAT_NAMES@@ DESERT_MAT_URLS = {"https://steamusercontent-a.akamaihd.net/ugc/1043093746445993785/76099AB45F8127F2172E1B9BEBDE4AF712C3B160/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445940009/8986860615D6EA3BDC766C3E70C9BB3CE0E4C90F/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445950892/66E74AD325C33BAE5ADFC3209A7C033ECF6024C3/","https://steamusercontent-a.akamaihd.net/ugc/1043093746446001043/FD8732C59F057971B64B19988EF3DAAFBAA8362E/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445994782/529EA647E3EF162F6070AD29A21E06360087F00D/","https://steamusercontent-a.akamaihd.net/ugc/15717006480269992155/1D5D9D3EABC863CCAFC6A064E7815EEAE2D031CD/","https://steamusercontent-a.akamaihd.net/ugc/1043093746445953054/0A18AB4BF3A859BAFD83F16637ACD25BB10B5CCD/"} -- @@DESERT_MAT_URLS@@ DESERT_MAT_NAMES = {"Desert 1","Desert 2","Desert 3","Desert 4","Desert 5","Desert 6","Desert City 2"} -- @@DESERT_MAT_NAMES@@ -- LCT Pack 1 map mat pool, intentionally separate from the manual all-mats pool. LCT_MAT_RANDOMIZER_ENABLED = true -- @@LCT_MAT_RANDOMIZER_ENABLED@@ LCT_PACK_1_MAT_URLS = {"https://steamusercontent-a.akamaihd.net/ugc/1043093746446003935/7D5DE85C43BB1A9BAA5822D0D7F3910DF9400799/","https://steamusercontent-a.akamaihd.net/ugc/12624046404907267997/73874096A20FFDF5A6EC9DC400A9863D88F818F3/","https://steamusercontent-a.akamaihd.net/ugc/13075875539072127810/A106CFB44A2B064173E39ED207DD1D675374E25B/"} -- @@LCT_PACK_1_MAT_URLS@@ LCT_PACK_1_MAT_NAMES = {"Snow City 1","City 8","Snow City 2"} -- @@LCT_PACK_1_MAT_NAMES@@ defaultMatTextureURL = "https://steamusercontent-a.akamaihd.net/ugc/11325734395194619417/872CDB652BC3D15E79DBC768D03D9298C36DB992/" gameModeMatTextureURL = "https://steamusercontent-a.akamaihd.net/ugc/12078323260570499959/EC326002CC534A9BD75EE9157C5D509072A7AE01/" -- "Back to Selection" restore point. Captured automatically the instant a map -- is loaded (before its wipe), so a wrong map can be undone without reloading -- the mod. restorePointMenuState = the pre-load menu vars; restorePointObjects = -- {guid = JSON} of the persistent selection objects (in case the wipe destroys -- them; respawning preserves the original GUID since the source was destroyed). restorePointAvailable = false restorePointMenuState = nil restorePointObjects = nil -- GUIDs (subset of restorePointObjects) of the loose mission cards -- deployment -- maps, their layout art, and the dealt primaries. Unlike the persistent -- selection objects these get scattered by a map load, so the restore destroys -- every live copy and respawns them from the snapshot instead of relying on the -- "respawn only if missing" guard alone. restorePointMissionCardGuids = nil matTextures = { {name = "Green", color = {0.00, 0.31, 0.03}, url = "https://steamusercontent-a.akamaihd.net/ugc/11079691797763465477/D6702E876654301F44E0DC5417C1A99B13C53F0E/"}, {name = "Blue", color = {0.00, 0.31, 0.44}, url = "https://steamusercontent-a.akamaihd.net/ugc/15793090662305209631/DE9F6615DECEF086ACDEF61B8EDF4DBC700D6781/"}, {name = "Grey", color = {0.38, 0.40, 0.34}, url = "https://steamusercontent-a.akamaihd.net/ugc/12631014249723827365/F940CC388DB67806FB4FC81A4CEEFFB20C1FDBE8/"}, {name = "Purple", color = {0.36, 0.26, 0.41}, url = "https://steamusercontent-a.akamaihd.net/ugc/12602332143542612703/BC19D77679D80E92322FC9E22379165CBAE9DC32/"}, {name = "Red Wood", color = {0.35, 0.16, 0.10}, url = "https://steamusercontent-a.akamaihd.net/ugc/14567327019918795708/2B395E8CAA9F9A19E99B915113B7542882FCB946/"}, {name = "Grey Stone", color = {0.36, 0.36, 0.34}, url = "https://steamusercontent-a.akamaihd.net/ugc/15897725006336448025/83574B6805DF4E6CEA44EFAB7D5B2B912163D898/"}, {name = "Space", color = {0.10, 0.12, 0.23}, url = "https://steamusercontent-a.akamaihd.net/ugc/12558773339039292347/4E36E9A8342FC6189D40D5A286325BA8CCBC381A/"}, {name = "Wood", color = {0.42, 0.29, 0.16}, url = "https://steamusercontent-a.akamaihd.net/ugc/12419861650777715682/8DB8967B909015805A234DBEBDA41CBADD0B8CB8/"}, {name = "Graffiti", color = {0.42, 0.16, 0.35}, url = "https://steamusercontent-a.akamaihd.net/ugc/12776196614710430971/A650BC26568627868595D243ADC23D600F27B107/"}, {name = "Green 2", color = {0.16, 0.35, 0.10}, url = "https://steamusercontent-a.akamaihd.net/ugc/12197125747641380246/0A4FA198199CF25E6984355BC676F062F2C050F8/"}, {name = "Stripes", color = {0.29, 0.29, 0.32}, url = "https://steamusercontent-a.akamaihd.net/ugc/12743545711768789296/FECD0141A7A79CF8AEF46BA73D606CD1445B7FAF/"}, {name = "Yellow", color = {0.50, 0.42, 0.08}, url = "https://steamusercontent-a.akamaihd.net/ugc/14942685497117844413/942EF15E06DA92A166A51323BE72769CC423A836/"}, } function ttsDebugLog(message) if ttsDebugEnabled then print(message) end end function onLoad(saved_data) math.randomseed(os.time()) --self.setPosition({40,-4,0}) --self.setRotation({0,270,0}) -- load from saved --getData() deploySelected = nil deployMode = nil combatPatrolMode = false matrixPropsDisabled = false if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) gameMode = loaded_data.svgameMode singlesMode = loaded_data.svsSinglesMode combatPatrolMode = loaded_data.svCombatPatrolMode or false matrixPropsDisabled = loaded_data.svMatrixPropsDisabled or false inGame = loaded_data.svinGame currentTurn = loaded_data.svcurrentTurn currentPhase = loaded_data.svcurrentPhase mapSizeSelected = loaded_data.svmapSizeSelected sizeConfirmed = loaded_data.svsizeConfirmed deploySelected = loaded_data.svdeploySelected deployMode = loaded_data.svdeployMode packSelected = loaded_data.svPackSelected blueDispositionSelected = loaded_data.svblueDispositionSelected or blueDispositionSelected redDispositionSelected = loaded_data.svredDispositionSelected or redDispositionSelected matTextureSelected = loaded_data.svMatTextureSelected or matTextureSelected missionSetupControlsVisible = loaded_data.svMissionSetupControlsVisible if missionSetupControlsVisible == nil then missionSetupControlsVisible = gameMode == "game" and not inGame end -- Restore point is in-memory only (see onSave); a save+reload clears it. debugMapTagData = loaded_data.svDebugMapTagData or {} end if sizeConfirmed then if isValidDeployMode(deployMode) then setDeployZonesMode(deployMode, true) else setDeployZonesForSize(true) end if not deploySelected or not DeployZonesData[deploySelected] then deploySelected = #DeployZonesData end else deploySelected = #DeployZonesData hideStartupMapCards() hideForceDispositionToken() end rebuildOverlayTracking() syncTerritoryLineButton() -- Everything below reads Global's variables (mat_GUID, counters, -- missionPackData, pack constants), so it waits for Global's onLoad. Losing -- that race used to silently clear packSelected on load (see the debug log -- below) and leave the mat/counter references nil. whenGlobalReady(function() initGlobalRefs() mat = getObjectFromGUID(mat_GUID) applyMatTexture(false) if simulation then ttsDebugLog("THIS IS "..currentTurn.." TURN") end redTurnCounter = getObjectFromGUID(Global.getVar("redTurnCounter_GUID")) blueTurnCounter = getObjectFromGUID(Global.getVar("blueTurnCounter_GUID")) gameTurnCounter = getObjectFromGUID(Global.getVar("gameTurnCounter_GUID")) redCpCounter = getObjectFromGUID(Global.getVar("redCPCounter_GUID")) blueCpCounter = getObjectFromGUID(Global.getVar("blueCPCounter_GUID")) if inGame == false then Notes.setNotes(notePadTxt) else Notes.setNotes("") end if packSelected then local missionPackTable = Global.getTable("missionPackData") if missionPackTable and missionPackTable[packSelected] then missionPackData = missionPackTable[packSelected] else packSelected = nil missionPackData = nil ttsDebugLog("missionPackData not available during onLoad; clearing packSelected") end end if shouldPlaceGameModeObjectsOnLoad() then placeGameModeObjects() else hideGameModeObjects() end -- Combat Patrol map bag follows the same hidden-until-selected rule. If a -- 1v1/Solo game destroyed it earlier, getObjectFromGUID is nil and both -- helpers no-op. if combatPatrolMode then showCombatPatrolBag() else hideCombatPatrolBag() end if not gameTurnCounter then ttsDebugLog("gameTurnCounter not found during onLoad") end writeMenus() -- The phase highlight and active-turn strip colours live only in the -- non-persistent Global overlay, so they reset to the XML defaults on reload. -- Re-push them from the restored game state (immediate + a race-safe retry in -- case the overlay isn't live yet when this object's onLoad runs). Guarded by -- inGame so the pre-game setup screen keeps all phase glyphs dimmed. if inGame then updateGameStateHUD() Wait.time(updateGameStateHUD, 1) end end) end function onSave() saved_data = JSON.encode({ svinGame = inGame, svgameMode = gameMode, svsSinglesMode = singlesMode, svCombatPatrolMode = combatPatrolMode, svMatrixPropsDisabled = matrixPropsDisabled, svcurrentTurn = currentTurn, svcurrentPhase = currentPhase, svmapSizeSelected = mapSizeSelected, svsizeConfirmed = sizeConfirmed, svdeploySelected = deploySelected, svdeployMode = deployMode, svPackSelected = packSelected, svblueDispositionSelected = blueDispositionSelected, svredDispositionSelected = redDispositionSelected, svMatTextureSelected = matTextureSelected, svMissionSetupControlsVisible = missionSetupControlsVisible, -- DEBUG tag-editing accumulator. Small (~45 maps x a few markers), so -- safe to persist on every save -- this is the whole point: it carries an -- editing session out to scripts/apply_map_tags_from_save.py. svDebugMapTagData = debugMapTagData, -- NOTE: the restore point (restorePointObjects in particular) is held in -- memory only and deliberately NOT persisted here. It can reach ~5 MB -- (the game-mode objects are large decks), and JSON.encode on every -- autosave over that blob stalls the table. A pending undo is transient -- by nature, so losing it on a full save+reload is an acceptable trade. }) return saved_data end -- ******************** -- SIZE CHANGER MANAGER -- ******************** positionValue={0,-9.52,0} sizeData={ {id=1, name='King of the Colosseum - 36" x 36"', scale={1,1,1}, defaultObjectives=0}, {id=2, name='C o m b a t P a t r o l - 44" x 30"', scale={1.22,1,0.83}, defaultObjectives=0}, {id=4, name='S t r i k e F o r c e - 44" x 60"', scale={0.83*2,1,1.22}, defaultObjectives=0}, {id=5, name='O n s l a u g h t - 44" x 90"', scale={0.83*3,1,1.22}, defaultObjectives=0}, {id=6, name='B o a r d i n g A c t i o n - 28" x 48"', scale={1.33,1,0.77}, defaultObjectives=0}, } menuSizeX = 0 menuSizeY = 5 menuSizeZ = 16 arrowOffset=9.5 -- Default to Strike Force (id 4). Resolved by id (not a literal index) so it -- survives the Incursion removal and any future reordering of sizeData. mapSizeSelected=1 for i, s in ipairs(sizeData) do if s.id == 4 then mapSizeSelected = i break end end numberSizes=#sizeData sizeConfirmed=false -- Select a battlefield size by its stable sizeData id (not table index). The -- per-size buttons call this so they keep working regardless of sizeData order. function setSelectedSizeById(id) for i, s in ipairs(sizeData) do if s.id == id then mapSizeSelected = i return end end end -- The old "SELECT BATTLEFIELD SIZE" up/down picker has been replaced by one -- button per battlefield size. Every button below starts a singles-style game -- (no seat/turn enforcement) via its confirm* handler; the size buttons pick their -- size first. Buttons are laid out in a two-column grid: -- row 1: 1v1 | Solo (standard full-matrix game; Strike Force size) -- row 2: Combat Patrol | Onslaught (each has its own dedicated logic) -- row 3: Colosseum | Boarding Action (Combat-Patrol-style: no bags, -- missions-only Generate) -- (There is no separate Strike Force button: 1v1/Solo already cover that size.) local menuColLeft = -menuSizeX - 6.0 local menuColRight = -menuSizeX + 6.0 local menuRow1 = menuSizeZ + 0.0 local menuRow2 = menuSizeZ + 2.65 local menuRow3 = menuSizeZ + 5.30 confirmBtn = { index = i, label = "1v1", click_function = "confirmStandardGame", function_owner = self, position = { menuColLeft, menuSizeY, menuRow1 }, rotation = { 0, 0, 0 }, height = 810, width = 4500, scale = { 1, 1, 1 }, font_size = 375, color = { 0.191999942, 0.701, 0.167999923}, -- Rich Forest Green font_color = { 1.0, 1.0, 1.0 }, -- Pure White hover_color = { 0.25, 0.78, 0.22, 1 }, press_color = { 0.13, 0.55, 0.12, 1 } } confirmBtn4 = { index = i, label = "Solo", click_function = "confirmForSingles", function_owner = self, position = { menuColRight, menuSizeY, menuRow1 }, rotation = { 0, 0, 0 }, height = 810, width = 4500, scale = { 1, 1, 1 }, font_size = 375, color = { 0.117999949, 0.53, 1}, -- Vibrant Royal Blue font_color = { 1.0, 1.0, 1.0 }, -- Pure White hover_color = { 0.18, 0.6, 1, 1 }, press_color = { 0.08, 0.4, 0.82, 1 } } confirmBtn5 = { index = i, label = "Combat Patrol", click_function = "confirmCombatPatrol", function_owner = self, position = { menuColLeft, menuSizeY, menuRow2 }, rotation = { 0, 0, 0 }, height = 810, width = 4500, scale = { 1, 1, 1 }, font_size = 375, color = { 0.70, 0.16, 0.16 }, -- Red font_color = { 1.0, 1.0, 1.0 }, -- Pure White hover_color = { 0.82, 0.22, 0.22, 1 }, press_color = { 0.55, 0.10, 0.10, 1 } } -- Onslaught (purple) sits next to Combat Patrol. Like Solo it runs singles-style, -- but forces the 44"x90" battlefield, keeps the plain default mat art, and applies -- the full Onslaught board reshuffle (matrix props dropped, menus relocated). confirmBtnOnslaught = { index = i, label = "Onslaught", click_function = "confirmOnslaught", function_owner = self, position = { menuColRight, menuSizeY, menuRow2 }, rotation = { 0, 0, 0 }, height = 810, width = 4500, scale = { 1, 1, 1 }, font_size = 375, color = { 0.55, 0.27, 0.78 }, -- Purple font_color = { 1.0, 1.0, 1.0 }, -- Pure White hover_color = { 0.63, 0.35, 0.85, 1 }, press_color = { 0.42, 0.18, 0.62, 1 } } confirmBtnColosseum = { index = i, label = "Colosseum", click_function = "confirmColosseum", function_owner = self, position = { menuColLeft, menuSizeY, menuRow3 }, rotation = { 0, 0, 0 }, height = 810, width = 4500, scale = { 1, 1, 1 }, font_size = 375, color = { 0.85, 0.55, 0.15 }, -- Amber font_color = { 1.0, 1.0, 1.0 }, -- Pure White hover_color = { 0.93, 0.63, 0.22, 1 }, press_color = { 0.68, 0.43, 0.10, 1 } } confirmBtnBoardingAction = { index = i, label = "Boarding Action", click_function = "confirmBoardingAction", function_owner = self, position = { menuColRight, menuSizeY, menuRow3 }, rotation = { 0, 0, 0 }, height = 810, width = 4500, scale = { 1, 1, 1 }, font_size = 375, color = { 0.40, 0.45, 0.55 }, -- Slate font_color = { 1.0, 1.0, 1.0 }, -- Pure White hover_color = { 0.48, 0.53, 0.63, 1 }, press_color = { 0.30, 0.34, 0.43, 1 } } -- "GAME MODES" header above row 1, styled like the Deployment Zone picker (white -- fill, black text, bordered) but with no arrows. Centered between the two columns -- and wide enough to span from the left edge of 1v1 to the right edge of Solo. -- height/font_size mirror deployPickerButtonHeight/deployPickerButtonFontSize as -- literals (those vars are defined later in the chunk, after this table). gameModesHeaderBtn = { index = i, label = "GAME MODES", click_function = "none", function_owner = self, position = { -menuSizeX, menuSizeY, menuSizeZ - 2.05 }, rotation = { 0, 0, 0 }, height = 810, width = 9900, scale = { 1, 1, 1 }, font_size = 640, color = { 0.17, 0.17, 0.19 }, -- Charcoal font_color = { 1, 1, 1 } -- White text } function refreshMat() mat = getObjectFromGUID(mat_GUID) end function normalizeMatTextureIndex(index) if index > #matTextures then return 1 end if index < 1 then return #matTextures end return index end function applyMatTexture(rewriteMenu) matTextureSelected = normalizeMatTextureIndex(matTextureSelected) local selected = matTextures[matTextureSelected] local mapMat = getObjectFromGUID(mapMat_GUID) if not mapMat or not selected then return end local customInfo = mapMat.getCustomObject() if customInfo.image ~= selected.url then customInfo.image = selected.url -- reload() respawns the mat, which resets its interaction flags back to the -- object's saved defaults -- making it clickable/draggable again after a theme -- change. Snapshot the current flags and restore them once the reloaded copy -- finishes spawning, so the mat stays locked and uninteractable as it is now. local wasLocked = mapMat.getLock() local wasInteractable = mapMat.interactable mapMat.setCustomObject(customInfo) restoreMatFlags(mapMat.reload(), wasLocked, wasInteractable) end if rewriteMenu then Wait.frames(function() writeMenus() end, 2) end end -- Re-apply the mat's lock/interactable/tooltip after a reload. reload() is async -- (the object respawns over a few frames), so wait for spawning to finish before -- writing the flags; otherwise they land on the old, about-to-be-destroyed copy. function restoreMatFlags(reloaded, locked, interactable) local function mat() return reloaded or getObjectFromGUID(mapMat_GUID) end Wait.condition(function() local m = mat() if not m then return end m.setLock(locked) m.interactable = interactable m.tooltip = interactable end, function() local m = mat() return m ~= nil and not m.spawning end) end function setMatTexture(index) matTextureSelected = normalizeMatTextureIndex(index) applyMatTexture(true) end function setMatTexture1() setMatTexture(1) end function setMatTexture2() setMatTexture(2) end function setMatTexture3() setMatTexture(3) end function setMatTexture4() setMatTexture(4) end function setMatTexture5() setMatTexture(5) end function setMatTexture6() setMatTexture(6) end function setMatTexture7() setMatTexture(7) end function setMatTexture8() setMatTexture(8) end function setMatTexture9() setMatTexture(9) end function setMatTexture10() setMatTexture(10) end function setMatTexture11() setMatTexture(11) end function setMatTexture12() setMatTexture(12) end -- DEBUG-only: apply an arbitrary image URL to the map mat, bypassing the -- matTextures presets. Mirrors applyMatTexture's reload + flag-restore so the mat -- stays locked/uninteractable across the swap. matTextureSelected is left -- untouched (this URL is not persisted), so a save+reload reverts to the selected -- Table Theme swatch. Used by the "Custom URL" row on the Map Mat Picker overlay. function setMatTextureFromUrl(url) if Global.getVar("DEBUG") ~= true then return end if type(url) ~= "string" then return end url = url:gsub("^%s+", ""):gsub("%s+$", "") if url == "" then return end local mapMat = getObjectFromGUID(mapMat_GUID) if not mapMat then return end local customInfo = mapMat.getCustomObject() if customInfo.image == url then return end customInfo.image = url local wasLocked = mapMat.getLock() local wasInteractable = mapMat.interactable mapMat.setCustomObject(customInfo) restoreMatFlags(mapMat.reload(), wasLocked, wasInteractable) end -- Read by the Table Theme swatches in mapFilter.ttslua (GUID 369707) so its XML -- buttons can mark the active theme. The swatch buttons there forward back to the -- setMatTexture functions above. function matTextureSelectedIndex() return matTextureSelected end function writeSizeMenu() self.clearButtons() if sizeConfirmed then return end createButtonWithBorder(startBtnDisabled) -- Borders are a lightened tint of each button's own fill so they read against -- the dark navy table (the default near-black border would disappear there). createButtonWithBorder(gameModesHeaderBtn, { 0.55, 0.58, 0.66, 1 }) -- light slate createButtonWithBorder(confirmBtn, { 0.45, 0.90, 0.40, 1 }) -- bright green createButtonWithBorder(confirmBtn4, { 0.43, 0.66, 1.00, 1 }) -- sky blue createButtonWithBorder(confirmBtn5, { 0.94, 0.42, 0.42, 1 }) -- light red createButtonWithBorder(confirmBtnOnslaught, { 0.78, 0.55, 0.95, 1 }) -- light purple createButtonWithBorder(confirmBtnColosseum, { 1.00, 0.78, 0.40, 1 }) -- light amber createButtonWithBorder(confirmBtnBoardingAction, { 0.70, 0.76, 0.86, 1 }) -- light slate-blue -- The selection screen always shows the mat at Strike Force size, regardless of -- which size was last played (Back to Selection can leave mapSizeSelected on, -- say, Onslaught). Each button then sets its own scale on confirm via -- confirmSizeMat(). setSelectedSizeById(4) mat.setScale(sizeData[mapSizeSelected].scale) mat.setPosition(positionValue) end -- The standard full-matrix game on the Strike Force battlefield. Every start -- button now runs singles-style (no seat/turn enforcement); 1v1 and Solo are the -- two labels for this same standard game (matrix props + Generate Missions & -- Layouts). Force Strike Force (id 4) so it no longer depends on a size picker. function confirmForSingles() singlesMode = "singles" combatPatrolMode = false matrixPropsDisabled = false setSelectedSizeById(4) deployMode = "edition11" loadGameWithoutMapContainers() destroyCombatPatrolBag() end function confirmStandardGame() confirmForSingles() end -- Combat Patrol plays like Solo (one player drives both sides), but uses the -- Combat Patrol deployment zones and drops the mission-matrix props -- those -- reference bags/tiles aren't used in a Combat Patrol game. combatPatrolMode is -- the flag that makes Generate Missions skip the matchup maps + fb4b5d layout art -- and deal from the Combat Patrol map bag instead. function confirmCombatPatrol() singlesMode = "singles" combatPatrolMode = true matrixPropsDisabled = false deployMode = "combatPatrolGame" -- Combat Patrol is always played on the 44" x 30" battlefield. Force that size -- so it applies regardless of which button was last used; confirmSizeMat() (via -- loadGameWithoutMapContainers) then scales the mat to match. setSelectedSizeById(2) loadGameWithoutMapContainers() destroyGameModeObjects() showCombatPatrolBag() end -- Onslaught keeps its own dedicated logic: a singles game on the 44"x90" -- battlefield that still uses the full mission matrix (Generate Missions & -- Layouts), but relocates the menus and drops the matrix reference props to clear -- the longer board (applyOnslaughtButtonLayout). Default mat art is applied by -- confirmSizeMat() because selectedSizeIsOnslaught() is true. function confirmOnslaught() singlesMode = "singles" combatPatrolMode = false matrixPropsDisabled = false setSelectedSizeById(ONSLAUGHT_SIZE_ID) deployMode = "edition11" loadGameWithoutMapContainers() destroyCombatPatrolBag() applyOnslaughtButtonLayout() end -- The remaining per-size buttons (Colosseum / Boarding Action) all -- behave like Combat Patrol minus its map bag: a singles game scaled to the chosen -- battlefield, no mission-matrix bags/tiles (destroyGameModeObjects), a blank/ -- default mat, and a missions-only Generate (no matchup maps, no fb4b5d layout -- art). matrixPropsDisabled drives all of that via gameModeUsesMatrixProps(). sizeDeployModeById = { [1] = "colosseum", [4] = "strikeForce", [6] = "boardingAction", } function confirmSizedSinglesGame(sizeId) singlesMode = "singles" combatPatrolMode = false matrixPropsDisabled = true setSelectedSizeById(sizeId) deployMode = sizeDeployModeById[sizeId] or "strikeForce" loadGameWithoutMapContainers() destroyGameModeObjects() destroyCombatPatrolBag() end function confirmColosseum() confirmSizedSinglesGame(1) end function confirmBoardingAction() confirmSizedSinglesGame(6) end -- Permanently remove the mission-matrix bags/tiles for this session (Combat -- Patrol). They normally only hide at y=-5; here we destroy them outright. function destroyGameModeObjects() for _, entry in ipairs(GAME_MODE_OBJECTS) do destroyObjectByGUID(entry.guid) end end function loadGameWithoutMapContainers() missionsGenerated = false destroyLegacyPrimaryDeck() placeStartupMapCards() placeForceDispositionToken() confirmSizeMat() end legacyPrimaryDeck_GUID = "d26e9c" function destroyLegacyPrimaryDeck() destroyObjectByGUID(legacyPrimaryDeck_GUID) end -- The three startup workshop-link tiles were removed from the save; nothing to -- place or hide here now. Kept as an empty table so the place/hide loops no-op. STARTUP_MAP_CARDS = {} -- Normalized mission-matrix layout: 15 matchup bags + their 15 art tiles on a -- square grid (6.6u column/row spacing), each tile offset -3 in x from its bag. -- These positions are kept in lockstep with the Transform values baked into -- ftc_base.json. (The mission_matrix backdrop tile 9c6ce8 is intentionally NOT in -- this list: it is never spawned/placed/hidden/destroyed by Lua and simply stays -- at its baked ftc_base.json transform from map load.) GAME_MODE_OBJECTS = { {guid = "6e0d78", pos = {-25.4612, 0.683948, 17.5906}, rot = {0, 180, 0}}, {guid = "642d0d", pos = {-28.4612, 0.961499, 17.5906}, rot = {0, 270, 0}}, {guid = "eae80b", pos = {-25.4612, 0.683948, 10.9906}, rot = {0, 180, 0}}, {guid = "3f56e1", pos = {-28.4612, 0.961499, 10.9906}, rot = {0, 270, 0}}, {guid = "cfeba5", pos = {-25.4612, 0.683948, 4.3906}, rot = {0, 180, 0}}, {guid = "97d33f", pos = {-28.4612, 0.961499, 4.3906}, rot = {0, 270, 0}}, {guid = "1e6711", pos = {-25.4612, 0.683948, -2.2094}, rot = {0, 180, 0}}, {guid = "65439e", pos = {-28.4612, 0.961499, -2.2094}, rot = {0, 270, 0}}, {guid = "109a6b", pos = {-25.4612, 0.683948, -8.8094}, rot = {0, 180, 0}}, {guid = "80eda8", pos = {-28.4612, 0.961499, -8.8094}, rot = {0, 270, 0}}, {guid = "3ebbd6", pos = {-18.8613, 0.683948, 17.5906}, rot = {0, 0, 0}}, {guid = "444551", pos = {-21.8613, 0.961499, 17.5906}, rot = {0, 270, 0}}, {guid = "dc8738", pos = {-18.8613, 0.683948, 10.9906}, rot = {0, 0, 0}}, {guid = "703a8c", pos = {-21.8613, 0.961499, 10.9906}, rot = {0, 270, 0}}, {guid = "a22c33", pos = {-18.8613, 0.683948, 4.3906}, rot = {0, 0, 0}}, {guid = "41bf2c", pos = {-21.8613, 0.961499, 4.3906}, rot = {0, 270, 0}}, {guid = "9ac38f", pos = {-18.8613, 0.683948, -2.2094}, rot = {0, 0, 0}}, {guid = "2aef7f", pos = {-21.8613, 0.961499, -2.2094}, rot = {0, 270, 0}}, {guid = "4409ba", pos = {-12.2613, 0.683948, 17.5906}, rot = {0, 0, 0}}, {guid = "6a1d02", pos = {-15.2613, 0.961499, 17.5906}, rot = {0, 270, 0}}, {guid = "2c7cd1", pos = {-12.2613, 0.683948, 10.9906}, rot = {0, 0, 0}}, {guid = "888613", pos = {-15.2613, 0.961499, 10.9906}, rot = {0, 270, 0}}, {guid = "f1e22b", pos = {-12.2613, 0.683948, 4.3906}, rot = {0, 0, 0}}, {guid = "9a274c", pos = {-15.2613, 0.961499, 4.3906}, rot = {0, 270, 0}}, {guid = "32e34a", pos = {-5.6613, 0.683948, 17.5906}, rot = {0, 0, 0}}, {guid = "e88089", pos = {-8.6613, 0.961499, 17.5906}, rot = {0, 270, 0}}, {guid = "7b5ba7", pos = {-5.6613, 0.683948, 10.9906}, rot = {0, 0, 0}}, {guid = "4a1bbd", pos = {-8.6613, 0.961499, 10.9906}, rot = {0, 270, 0}}, {guid = "793bc0", pos = {0.9387, 0.683948, 17.5906}, rot = {0, 0, 0}}, {guid = "066fd3", pos = {-2.0613, 0.961499, 17.5906}, rot = {0, 270, 0}}, } function hideStartupMapCards() for _, card in ipairs(STARTUP_MAP_CARDS) do local obj = getObjectFromGUID(card.guid) if obj then obj.setPosition({card.pos[1], -5.0, card.pos[3]}) obj.setLock(true) obj.tooltip = false obj.interactable = false end end end function hideGameModeObjects() for _, entry in ipairs(GAME_MODE_OBJECTS) do local obj = getObjectFromGUID(entry.guid) if obj then obj.setPosition({entry.pos[1], -5.0, entry.pos[3]}) obj.setLock(true) obj.tooltip = false obj.interactable = false end end end function placeStartupMapCards() for _, card in ipairs(STARTUP_MAP_CARDS) do placeObjectAtPosition(card.guid, card.pos, card.rot) end end function placeGameModeObjects() -- The mission-matrix bags/tiles are reference props, not handled during play, -- so lock them on placement to stop them drifting or being dragged off the mat. for _, entry in ipairs(GAME_MODE_OBJECTS) do placeObjectAtPosition(entry.guid, entry.pos, entry.rot, true) end end -- ******************** -- COMBAT PATROL MAP POOL -- ******************** -- Self-contained bag of Combat Patrol map cards (merged from the legacy save by -- scripts/import_combat_patrol_maps.py; ignored by the map validator). It is -- hidden like the mission-matrix props until a mode is chosen: destroyed on -- 1v1/Solo, shown on Combat Patrol. On Generate Mission in Combat Patrol mode, -- 3 of its cards are dealt at random into the deployment map slots. This whole -- section is additive -- nothing here runs unless combatPatrolMode is set. COMBAT_PATROL_BAG_GUID = "fdf6e7" combatPatrolBagPosition = {-10.5798788, 0.776465, -4.463058} combatPatrolBagRotation = {0, 0, 0} -- Runtime-only tag on a dealt CP map card so a re-press can recall it. Added -- in-game, never in source JSON, so the map validator never sees it. combatPatrolMapCardTag = "combat_patrol_map_dealt" -- Where the 3 dealt Combat Patrol map cards land, and their facing. These are -- CP-ONLY and read by nothing else -- edit them freely without touching the -- matchup system's deployment slots (missionSlots). Order is slot 1, 2, 3; they -- are seeded to the standard deployment-slot positions so behavior is unchanged -- until you move them. {x, y, z}. combatPatrolMapSlotPositions = { {4.50, 1.00, 8.80}, {4.50, 1.00, 0.00}, {4.50, 1.00, -8.80}, } combatPatrolMapCardRotation = {0, 180, 0} function showCombatPatrolBag() local bag = getObjectFromGUID(COMBAT_PATROL_BAG_GUID) if not bag then return end bag.setPosition(combatPatrolBagPosition) bag.setRotation(combatPatrolBagRotation) bag.setLock(true) bag.tooltip = true bag.interactable = true end function hideCombatPatrolBag() local bag = getObjectFromGUID(COMBAT_PATROL_BAG_GUID) if not bag then return end bag.setPosition({combatPatrolBagPosition[1], -5.0, combatPatrolBagPosition[3]}) bag.setLock(true) bag.tooltip = false bag.interactable = false end function destroyCombatPatrolBag() destroyObjectByGUID(COMBAT_PATROL_BAG_GUID) end -- "Force dispositions" reference token was removed from the save. forceDispositionToken_GUID = nil forceDispositionTokenPosition = {-15.7271786, 1.06149852, -0.134739235} forceDispositionTokenRotation = {0, 270.020081, 0} function placeForceDispositionToken() if forceDispositionToken_GUID then placeObjectAtPosition(forceDispositionToken_GUID, forceDispositionTokenPosition, forceDispositionTokenRotation) end end function hideForceDispositionToken() if not forceDispositionToken_GUID then return end local obj = getObjectFromGUID(forceDispositionToken_GUID) if obj then obj.setPosition({forceDispositionTokenPosition[1], -5.0, forceDispositionTokenPosition[3]}) obj.setLock(true) obj.tooltip = false obj.interactable = false end end function placeObjectAtPosition(guid, position, rotation, lock) local obj = getObjectFromGUID(guid) if not obj then broadcastToAll("Object with GUID " .. guid .. " not found.", {1, 0, 0}) return end obj.setPosition(position) obj.setRotation(rotation or {0, 180, 0}) obj.setLock(lock == true) obj.tooltip = true obj.interactable = true end function destroyObjectByGUID(guid) local obj = getObjectFromGUID(guid) if obj then destroyObject(obj) end end function applySurfaceMatTexture(url) mat = getObjectFromGUID(mat_GUID) if not mat or not url then return end local customInfo = mat.getCustomObject() if customInfo.diffuse ~= url then customInfo.diffuse = url mat.setCustomObject(customInfo) mat = mat.reload() end end -- The mission-matrix reference props/logos (GAME_MODE_OBJECTS), the printed -- game-mode mat art, and the Generate step's deployment maps + fb4b5d layout art -- are used only by the standard 1v1/Solo (and Onslaught) game. Combat Patrol and -- the per-size singles modes run on a blank mat with a missions-only Generate, so -- they place/deal none of it. This predicate gates all of those behaviors. function gameModeUsesMatrixProps() return not (combatPatrolMode or matrixPropsDisabled) end -- GAME_MODE_OBJECTS are setup-screen reference props. If they were deliberately -- destroyed (Onslaught/singles) or the save is already in-game, do not try to -- place them again on load; that just creates noisy "GUID not found" messages. function shouldPlaceGameModeObjectsOnLoad() return packSelected and gameModeUsesMatrixProps() and not inGame and not selectedSizeIsOnslaught() end function applyGameModeMatTexture() applySurfaceMatTexture(gameModeMatTextureURL) end function applyDefaultMatTexture() applySurfaceMatTexture(defaultMatTextureURL) end function confirmSizeMat() gameMode= "game" missionSetupControlsVisible = true -- Onslaught drops the mission-matrix props (see applyOnslaughtButtonLayout), so -- it uses the plain default mat art rather than the matrix-themed game-mode art. if gameModeUsesMatrixProps() and not selectedSizeIsOnslaught() then applyGameModeMatTexture() else applyDefaultMatTexture() end sizeConfirmed=true mat.setScale(sizeData[mapSizeSelected].scale) self.setRotation({0,270,0}) if deployMode and isValidDeployMode(deployMode) then setDeployZonesMode(deployMode, false) else setDeployZonesForSize(false) end writeMenus() end -- Onslaught (id 5, 44"x90") is long enough that the default deployment toggle -- button row sits over the battlefield. On a 1v1 Onslaught game only, relocate -- the six interactive buttons into a single column off the long edge (x=50, -- y=-1) with evenly stepped z. Matched by sizeData id so it survives reordering. -- Buttons reset to their baked ftc_base.json transforms on game reload. ONSLAUGHT_SIZE_ID = 5 onslaughtButtonLayout = { {guid = "7b7ab8", pos = {50, 1, 19}}, -- Strat Reserves {guid = "8c71ab", pos = {50, 1, 26}}, -- Area Denial {guid = "7e7711", pos = {50, 1, 33}}, -- Territory Line {guid = "59decf", pos = {50, 1, -19}}, -- Table Quarters {guid = "8aba11", pos = {50, 1, -26}}, -- Deployment Zone {guid = "15d652", pos = {50, 1, -33}}, -- Show Objectives {guid = "369707", pos = {61, 0, -25.5}}, -- Map Mat Picker overlay (MAP_FILTER_GUID) } -- Mission-matrix props (matchup bags + their art tiles) that Onslaught doesn't use; -- destroyed on an Onslaught 1v1/Solo confirm. onslaughtDestroyGuids = { "066fd3", "793bc0", "32e34a", "7b5ba7", "4a1bbd", "e88089", "4409ba", "2c7cd1", "f1e22b", "9a274c", "888613", "6a1d02", "3ebbd6", "444551", "dc8738", "703a8c", "a22c33", "41bf2c", "9ac38f", "2aef7f", "109a6b", "1e6711", "cfeba5", "eae80b", "6e0d78", "642d0d", "3f56e1", "97d33f", "65439e", "80eda8", } function selectedSizeIsOnslaught() local entry = sizeData[mapSizeSelected] return entry ~= nil and entry.id == ONSLAUGHT_SIZE_ID end function applyOnslaughtButtonLayout() destroyObjectByGUID(ONSLAUGHT_SETUP_GUID_TO_REMOVE) for _, guid in ipairs(onslaughtDestroyGuids) do destroyObjectByGUID(guid) end for _, b in ipairs(onslaughtButtonLayout) do local obj = getObjectFromGUID(b.guid) if obj then obj.setPosition(b.pos) end end broadcastToAll("Onslaught mode enabled, menus are moved to the top of the table", {1, 0.6, 0.1}) end -- On a 1v1/Solo Onslaught game the discord tile (8f2be2) is removed and the -- mission-setup controls are repositioned to clear the longer battlefield: -- * MID row -- Generate Missions & Layouts, Random Single Layout, Dispositions -- -- is lifted +X so it sits just above the CP/Turn/Round counter row (the Game -- Rounds token ee92cf is at world X=67). MID +39: Random->70.5, Disposition-> -- 72.5, Generate->73.6. (This leaves the printed LAYOUTS/MISSIONS bracket art -- on backdrop 28865a behind -- expected.) -- * TOP row -- Start Game, First-player toggle, Deployment Zone picker (+ arrows -- and the Clear Table / Back to Selection flanks) -- is lifted +X to sit just -- above the MID row (Generate/Dispositions). DX is tuned so the cluster lands a -- few X-units above the MID row rather than out past the table edge. -- Applied at render time so it tracks the selected size and survives reload. ONSLAUGHT_SETUP_GUID_TO_REMOVE = "8f2be2" ONSLAUGHT_MID_ROW_X_OFFSET = 39 -- world +X for the Generate/Random/Disposition row ONSLAUGHT_TOP_ROW_DX = 19 -- world +X delta for the Start/First/Deployment cluster ONSLAUGHT_TOP_ROW_DZ = 0 -- world +Z delta (0 = directly above the MID row) -- True only once a game mode is confirmed (gameMode == "game") and the chosen size -- is Onslaught -- so nothing shifts during size preview or for other sizes. function onslaughtSetupActive() return gameMode == "game" and selectedSizeIsOnslaught() end -- World +X the MID row is shifted by (0 when not an Onslaught game). function setupMidRowXOffset() return onslaughtSetupActive() and ONSLAUGHT_MID_ROW_X_OFFSET or 0 end -- World (dx, dz) the TOP-row cluster is translated by (0,0 when not Onslaught). function topRowDX() return onslaughtSetupActive() and ONSLAUGHT_TOP_ROW_DX or 0 end function topRowDZ() return onslaughtSetupActive() and ONSLAUGHT_TOP_ROW_DZ or 0 end -- The menu object is rotated rotY=270, so world +X = local -Z and world +Z = local -- +X. Returns a copy of a top-row button definition translated by (topRowDX, -- topRowDZ) in world space, preserving the cluster's internal layout. Returns the -- original table when there is no translation. function withSetupXOffset(btnDef) local dx, dz = topRowDX(), topRowDZ() if dx == 0 and dz == 0 then return btnDef end local copy = {} for k, v in pairs(btnDef) do copy[k] = v end copy.position = {btnDef.position[1] + dz, btnDef.position[2], btnDef.position[3] - dx} return copy end function updateDeployObjectivesSelection() deploySelected = #DeployZonesData end function selectedDeployment() return DeployZonesData and DeployZonesData[deploySelected] or nil end function selectedDeploymentSpawnsOverlay() local zone = selectedDeployment() return zone ~= nil and zone.draw and zone.draw.type ~= "none" end -- END size changer -- ******************** -- DEPLOY ZONES MANAGER -- ******************** DeployZonesStrikeForce = { --Chapter Approved 2025 Missions {name = "Hammer and Anvil", objectivesID = 101, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromSide = 18}, --[[2]]{type = "line", color = "Teal", position = "-x", fromSide = 18}}}, {name = "Dawn of War", objectivesID = 102, draw = { --[[1]]{type = "line", color = "Teal", position = "-z", fromSide = 12}, --[[2]]{type = "line", color = "Red", position = "z", fromSide = 12}}}, {name = "Sweeping Engagement", objectivesID = 103, draw = { --[[1]]{type = "stepped", color = "Teal", position = "-z", steps = {{fromSide = 8}, {fromSide = 14}}}, --[[2]]{type = "stepped", color = "Red", position = "z", steps = {{fromSide = 14}, {fromSide = 8}}}}}, {name = "Search and Destroy", objectivesID = 104, draw = { --[[1]]{type = "quarter", color = "Teal", position = "-x-z", fromCenter = 9}, --[[2]]{type = "quarter", color = "Red", position = "xz", fromCenter = 9}, --[[3]]{type = "circle", color = "White", fromCenter = 9}}}, {name = "Crucible of Battle", objectivesID = 105, draw = { --[[1]]{type = "triangle", color = "Red", position = "x"}, --[[2]]{type = "triangle", color = "Teal", position = "-x"}}}, {name = "Tipping Point", objectivesID = 106, draw = { --[[1]]{type = "stepped", color = "Teal", position = "-x", steps = {{fromSide = 12}, {fromSide = 20}}}, --[[2]]{type = "stepped", color = "Red", position = "x", steps = {{fromSide = 20}, {fromSide = 12}}}}}, --Hidden Supplies variants {name = "Hammer and Anvil (HS)", objectivesID = 107, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromSide = 18}, --[[2]]{type = "line", color = "Teal", position = "-x", fromSide = 18}}}, {name = "Hammer and Anvil (HS2)", objectivesID = 108, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromSide = 18}, --[[2]]{type = "line", color = "Teal", position = "-x", fromSide = 18}}}, {name = "Dawn of War (HS)", objectivesID = 109, draw = { --[[1]]{type = "line", color = "Teal", position = "-z", fromSide = 12}, --[[2]]{type = "line", color = "Red", position = "z", fromSide = 12}}}, {name = "Dawn of War (HS2)", objectivesID = 110, draw = { --[[1]]{type = "line", color = "Teal", position = "-z", fromSide = 12}, --[[2]]{type = "line", color = "Red", position = "z", fromSide = 12}}}, {name = "Sweeping Engagement (HS)", objectivesID = 111, draw = { --[[1]]{type = "stepped", color = "Teal", position = "-z", steps = {{fromSide = 8}, {fromSide = 14}}}, --[[2]]{type = "stepped", color = "Red", position = "z", steps = {{fromSide = 14}, {fromSide = 8}}}}}, {name = "Sweeping Engagement (HS2)", objectivesID = 112, draw = { --[[1]]{type = "stepped", color = "Teal", position = "-z", steps = {{fromSide = 8}, {fromSide = 14}}}, --[[2]]{type = "stepped", color = "Red", position = "z", steps = {{fromSide = 14}, {fromSide = 8}}}}}, {name = "Search and Destroy (HS)", objectivesID = 113, draw = { --[[1]]{type = "quarter", color = "Teal", position = "-x-z", fromCenter = 9}, --[[2]]{type = "quarter", color = "Red", position = "xz", fromCenter = 9}, --[[3]]{type = "circle", color = "White", fromCenter = 9}}}, {name = "Search and Destroy (HS2)", objectivesID = 114, draw = { --[[1]]{type = "quarter", color = "Teal", position = "-x-z", fromCenter = 9}, --[[2]]{type = "quarter", color = "Red", position = "xz", fromCenter = 9}, --[[3]]{type = "circle", color = "White", fromCenter = 9}}}, {name = "Crucible of Battle (HS)", objectivesID = 115, draw = { --[[1]]{type = "triangle", color = "Red", position = "x"}, --[[2]]{type = "triangle", color = "Teal", position = "-x"}}}, {name = "Crucible of Battle (HS2)", objectivesID = 116, draw = { --[[1]]{type = "triangle", color = "Red", position = "x"}, --[[2]]{type = "triangle", color = "Teal", position = "-x"}}}, {name = "Tipping Point (HS)", objectivesID = 117, draw = { --[[1]]{type = "stepped", color = "Teal", position = "-x", steps = {{fromSide = 12}, {fromSide = 20}}}, --[[2]]{type = "stepped", color = "Red", position = "x", steps = {{fromSide = 20}, {fromSide = 12}}}}}, {name = "Tipping Point (HS2)", objectivesID = 118, draw = { --[[1]]{type = "stepped", color = "Teal", position = "-x", steps = {{fromSide = 12}, {fromSide = 20}}}, --[[2]]{type = "stepped", color = "Red", position = "x", steps = {{fromSide = 20}, {fromSide = 12}}}}}, {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesCombatPatrol = { {name = "(CP) Clash of Patrols", objectivesID = 22, draw = { --[[1]]{type = "line", color = "Red", position = "-z", fromCenter = 10 }, --[[2]]{type = "line", color = "Teal", position = "z", fromCenter = 10}}}, {name = "(CP) Archeotech Recovery", objectivesID = 23, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromCenter = 12 }, --[[2]]{type = "line", color = "Teal", position = "-x", fromCenter = 12}}}, {name = "(CP) Forward Outpost", objectivesID = 24, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromCenter = 12 }, --[[2]]{type = "line", color = "Teal", position = "-x", fromCenter = 12}}}, {name = "(CP) Scorched Earth", objectivesID = 25, draw = { --[[1]]{type = "triangle", color = "Red", position = "x"}, --[[2]]{type = "triangle", color = "Teal", position = "-x"}}}, {name = "(CP) Sweeping Raid", objectivesID = 26, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromCenter = 12 }, --[[2]]{type = "line", color = "Teal", position = "-x", fromCenter = 12}}}, {name = "(CP) Display of Might", objectivesID = 27, draw = { --[[1]]{type = "line", color = "Red", position = "x", fromCenter = 12 }, --[[2]]{type = "line", color = "Teal", position = "-x", fromCenter = 12}}}, {name = "(CP) Four Corners", objectivesID = 28, draw = { --[[1]]{type = "cornerRectangle", color = "Yellow", position = "xz", fromCenter = 9}, --[[2]]{type = "cornerRectangle", color = "Blue", position = "x-z", fromCenter = 9}, --[[3]]{type = "cornerRectangle", color = "Red", position = "-xz", fromCenter = 9}, --[[4]]{type = "cornerRectangle", color = "Black", position = "-x-z", fromCenter = 9}}}, {name = "(CP) Three Circles", objectivesID = 29, draw = { --[[1]]{type = "circleDeployment", color = "Blue", position = "z", fromCenter = 13, radius = 9}, --[[2]]{type = "circleDeployment", color = "Black", position = "-x", fromCenter = 16, fromCenterZ = 13, radius = 9}, --[[3]]{type = "circleDeployment", color = "Yellow", position = "x", fromCenter = 16, fromCenterZ = 13, radius = 9}}}, {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesCombatPatrolGame = { {name = "Combat Patrol 1", draw = { -- Points are measured like the mission image: red (0,0) is the lower-left corner, -- x runs across the 30" width, y runs up the 44" height. Teal mirrors this. --[[1]]{type = "cornerPolyline", color = "Red", position = "bottomLeft", points = {{0, 22}, {15, 11}, {15, 0}}}, --[[2]]{type = "cornerPolyline", color = "Teal", position = "topRight", points = {{0, 22}, {15, 11}, {15, 0}}}}, territory = {{position = "bottomLeft", color = "Yellow", points = {{0, 22}, {30, 22}}}}}, {name = "Combat Patrol 2", draw = { --[[1]]{type = "cornerPolyline", color = "Red", position = "bottomLeft", points = {{0, 16}, {30, 0}}}, --[[2]]{type = "cornerPolyline", color = "Teal", position = "topRight", points = {{0, 16}, {30, 0}}}}, territory = {{position = "bottomLeft", color = "Yellow", points = {{0, 22}, {30, 22}}}}}, {name = "Combat Patrol 3", draw = { --[[1]]{type = "cornerPolyline", color = "Red", position = "bottomLeft", points = {{0, 11}, {14, 11}, {14, 0}}}, --[[2]]{type = "cornerPolyline", color = "Teal", position = "topRight", points = {{0, 11}, {14, 11}, {14, 0}}}}, territory = {{position = "bottomLeft", color = "Yellow", points = {{0, 22}, {30, 22}}}}}, {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesColosseum = { {name = "King of the Colosseum", objectivesID = 301, draw = { --[[1]]{type = "line", color = "Teal", position = "-z", fromSide = 9}, --[[2]]{type = "line", color = "Red", position = "z", fromSide = 9}}}, {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesEdition11 = { {name = "Hammer and Anvil", draw = { --[[1]]{type = "line", color = "Red", position = "x", fromSide = 18}, --[[2]]{type = "line", color = "Teal", position = "-x", fromSide = 18}}}, {name = "Dawn of War", draw = { --[[1]]{type = "line", color = "Teal", position = "-z", fromSide = 12}, --[[2]]{type = "line", color = "Red", position = "z", fromSide = 12}}}, {name = "Sweeping Engagement", draw = { --[[1]]{type = "stepped", color = "Teal", position = "-z", steps = {{fromSide = 8}, {fromSide = 14}}}, --[[2]]{type = "stepped", color = "Red", position = "z", steps = {{fromSide = 14}, {fromSide = 8}}}}}, {name = "Search and Destroy", draw = { --[[1]]{type = "quarter", color = "Teal", position = "-x-z", fromCenter = 9}, --[[2]]{type = "quarter", color = "Red", position = "xz", fromCenter = 9}, --[[3]]{type = "circle", color = "White", fromCenter = 9}}}, {name = "Crucible of Battle", draw = { --[[1]]{type = "triangle", color = "Red", position = "x"}, --[[2]]{type = "triangle", color = "Teal", position = "-x"}}}, {name = "Tipping Point", draw = { --[[1]]{type = "stepped", color = "Teal", position = "-x", steps = {{fromSide = 12}, {fromSide = 20}}}, --[[2]]{type = "stepped", color = "Red", position = "x", steps = {{fromSide = 20}, {fromSide = 12}}}}}, {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesDefault = { {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesBoardingAction = { {name = "No Deployment Zone", draw = {type = "none"}}, } DeployZonesData = DeployZonesDefault deployMode = "strikeForce" function isValidDeployMode(mode) return mode == "colosseum" or mode == "combatPatrol" or mode == "combatPatrolGame" or mode == "strikeForce" or mode == "boardingAction" or mode == "edition11" end function setDeployZonesForSize(preserveSelected) -- Use Strike Force data if we have an invalid mapSizeSelected local sizeEntry = sizeData[mapSizeSelected] or sizeData[4] local sizeId = sizeEntry.id if sizeId == 1 then setDeployZonesMode("colosseum", preserveSelected) elseif sizeId == 2 then setDeployZonesMode("combatPatrol", preserveSelected) elseif sizeId == 4 or sizeId == 5 then setDeployZonesMode("strikeForce", preserveSelected) elseif sizeId == 6 then setDeployZonesMode("boardingAction", preserveSelected) else -- Default to Strike Force here as well setDeployZonesMode("strikeForce", preserveSelected) end end function setDeployZonesMode(mode, preserveSelected) local byMode = { colosseum = DeployZonesColosseum, combatPatrol = DeployZonesCombatPatrol, combatPatrolGame = DeployZonesCombatPatrolGame, strikeForce = DeployZonesStrikeForce, boardingAction = DeployZonesBoardingAction, edition11 = DeployZonesEdition11, } deployMode = mode DeployZonesData = byMode[mode] or DeployZonesDefault if preserveSelected and deploySelected and DeployZonesData[deploySelected] then return end deploySelected = #DeployZonesData end deployLineHeight = 2.1 deployLineYPos = deployLineHeight-0.1 sizeMulti = 36 -- Arrow offset/size mirror the disposition picker (dispositionArrowOffset 5.75, -- arrow width 600, font 300) so the Deployment Zone row matches them visually. arrowOffset = 5.75 deployPickerButtonHeight = 562.5 deployPickerButtonFontSize = 320 deployPickerArrowFontSize = 300 defaultDeployHeight=2 deployOffset = 0 deploymentOverlayGUIDs = {} nineInchOverlayGUIDs = {} territoryOverlayGUIDs = {} secondRowOffset= 1.2 deployHeaderBtn = { index = 1, label = "Deployment", click_function = "none", function_owner = self, position = { -menuX, 5, -0.5 }, rotation = {0, 0, 0}, height = 0, width = 6000, font_size = 600, color = {0, 0, 0}, font_color = {1, 1, 1} } -- Menu object is rotated rotY=270, so worldX = menuX - localZ. Adding 3.5 to local -- z shifts these -3.5 in world X. Clear Table / Back to Selection read -- deployBtn.position[3], so they follow. deployBtn = { index = i, label = "", click_function = "none", function_owner = self, position = { 0, 5, 6.5}, rotation = {0, 0, 0}, height = deployPickerButtonHeight, width = 4500, font_size = deployPickerButtonFontSize, color = {1, 1, 1}, font_color = {0, 0, 0} } deployUpBtn = { index = 1, label = "->", click_function = "deployUp", function_owner = self, position = { arrowOffset, 5, 6.5}, rotation = {0, 0, 0}, height = deployPickerButtonHeight, width = 600, font_size = deployPickerArrowFontSize, color = {0, 0, 0}, font_color = {1, 1, 1} } deployDownBtn = { index = 1, label = "<-", click_function = "deployDown", function_owner = self, position = { -arrowOffset, 5, 6.5}, rotation = {0, 0, 0}, height = deployPickerButtonHeight, width = 600, font_size = deployPickerArrowFontSize, color = {0, 0, 0}, font_color = {1, 1, 1} } dispositionControlWorldX = 33.50 blueDispositionControlWorldZ = 12.1 redDispositionControlWorldZ = -12.1 dispositionControlWorldY = 1.0 function normalizeDispositionIndex(index) if index > #dispositionValues then return 1 end if index < 1 then return #dispositionValues end return index end dispositionScale = 0.4125 dispositionButtonHeight = deployPickerButtonHeight dispositionButtonWidth = 4500 dispositionArrowHeight = 562.5 dispositionArrowWidth = 600 dispositionArrowFontSize = 300 dispositionButtonFontSize = 380 dispositionArrowOffset = 5.75 -- CLEAR TABLE and BACK TO SELECTION flank the Deployment Zone picker on its row -- (world X/Y taken from deployBtn), but are aligned to the same world Z as the -- disposition buttons directly below them so the two rows line up vertically: -- CLEAR TABLE over the red disposition (left, -12.1), BACK TO SELECTION over the -- blue disposition (right, +12.1). Placed via positionToLocal off those world Z -- values, mirroring createDispositionSelector, so the alignment holds under any -- menu scale/rotation. function deployFlankButtonLocal(dispositionWorldZ) -- Translate by the same (dx, dz) as the rest of the top-row cluster so these -- stay glued to the (possibly relocated) Deployment Zone picker row. local row = self.positionToWorld({0, deployBtn.position[2], deployBtn.position[3] - topRowDX()}) return self.positionToLocal({row.x, row.y, dispositionWorldZ + topRowDZ()}) end dispositionButtonColors = { ["Disruption"] = {0.36, 0.62, 1.00}, ["Priority Assets"] = {1.00, 0.92, 0.45}, ["Purge the Foe"] = {1.00, 0.58, 0.58}, ["Reconnaissance"] = {0.55, 0.95, 0.90}, ["Take and Hold"] = {0.38, 0.76, 0.34}, } function getDispositionButtonColor(selectedValue) return dispositionButtonColors[selectedValue] or {1, 1, 1} end function createDispositionSelector(label, selectedIndex, centerWorldZ, downFunction, upFunction) local selectedValue = dispositionValues[selectedIndex] or dispositionValues[1] local selectedColor = getDispositionButtonColor(selectedValue) local center = self.positionToLocal({dispositionControlWorldX + setupMidRowXOffset(), dispositionControlWorldY, centerWorldZ}) local cx, cy, cz = center.x, center.y, center.z local dispositionButton = { label = selectedValue, click_function = "none", function_owner = self, position = {cx, cy, cz}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = dispositionButtonWidth, font_size = dispositionButtonFontSize, color = selectedColor, font_color = {0, 0, 0}, tooltip = label, } createButtonWithBorder(dispositionButton) self.createButton({ label = "->", click_function = upFunction, function_owner = self, position = {cx + dispositionArrowOffset, cy, cz}, rotation = {0, 0, 0}, height = dispositionArrowHeight, width = dispositionArrowWidth, font_size = dispositionArrowFontSize, color = {0, 0, 0}, font_color = {1, 1, 1}, tooltip = "Next " .. label, }) self.createButton({ label = "<-", click_function = downFunction, function_owner = self, position = {cx - dispositionArrowOffset, cy, cz}, rotation = {0, 0, 0}, height = dispositionArrowHeight, width = dispositionArrowWidth, font_size = dispositionArrowFontSize, color = {0, 0, 0}, font_color = {1, 1, 1}, tooltip = "Previous " .. label, }) end function writeDispositionMenu() if gameMode ~= "game" or not missionSetupControlsVisible then return end -- Once a map is loaded (restore point captured in onMapCardLoaded), hide the -- mission-setup controls -- both disposition selectors + their arrows, Generate -- Missions & Layouts, and Random Single Layout -- so they can't be re-run on top -- of a loaded board. Back to Selection clears restorePointAvailable and re-runs -- writeMenus, which brings them all back; Start Game drops the whole deploy menu, -- so they are gone in-game without any extra handling here. if restorePointAvailable then return end blueDispositionSelected = normalizeDispositionIndex(blueDispositionSelected) redDispositionSelected = normalizeDispositionIndex(redDispositionSelected) createDispositionSelector("Blue Disposition", blueDispositionSelected, blueDispositionControlWorldZ, "blueDispositionDown", "blueDispositionUp") createDispositionSelector("Red Disposition", redDispositionSelected, redDispositionControlWorldZ, "redDispositionDown", "redDispositionUp") writeGenerateMissionButton() writeSetMissionsButton() end -- Mat wipe area + survivors, mirroring the map cards' own Clear/Load wipe so -- this standalone button behaves identically (mats survive, everything else on -- the mat is destroyed). Keep this whitelist in sync with the map cards. matClearZoneScale = {x = 60.12, y = 73, z = 44.04} matClearKeepGuids = {["28865a"] = true, ["4ee1f2"] = true, ["6012bf"] = true, ["948ce5"] = true, ["e7ca6e"] = true} -- Shown immediately during setup; hidden once the game starts. function writeClearMatButton() if inGame then return end local pos = deployFlankButtonLocal(redDispositionControlWorldZ) createButtonWithBorder({ label = "Clear Table", click_function = "clearMat", function_owner = self, position = {pos.x, pos.y, pos.z}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = dispositionButtonWidth, font_size = dispositionButtonFontSize, color = {0.90, 0.35, 0.35}, font_color = {0, 0, 0}, tooltip = "Remove everything placed on the table", }) end -- Always shown before the game starts; enabled once a map load captures a restore point. -- Sits just right of the Deployment Zone picker, mirroring CLEAR TABLE on the left. function writeBackToSelectionButton() if inGame then return end local enabled = restorePointAvailable local pos = deployFlankButtonLocal(blueDispositionControlWorldZ) createButtonWithBorder({ label = "Back to Selection", click_function = enabled and "backToSelection" or "none", function_owner = self, position = {pos.x, pos.y, pos.z}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = dispositionButtonWidth, font_size = dispositionButtonFontSize, color = enabled and {0.90, 0.62, 0.20} or {0.55, 0.55, 0.55}, font_color = enabled and {0, 0, 0} or {0.20, 0.20, 0.20}, tooltip = enabled and "Undo the loaded map and return to map selection" or "Load a map to enable this button", }) end -- ****************** -- DEBUG: SAVE TAGS (objective-marker tagging tool, --test builds only) -- ****************** -- Build-wide debug flag, true on --test builds only (mirrors objectiveDebug). -- The whole tag-editing tool below is gated on this so it never renders for -- players in a release build. function mapTagDebugEnabled() return Global.getVar("DEBUG") == true end -- Accumulated tag edits, keyed by map card GUID: -- debugMapTagData[cardGuid] = { name = mapName, objects = { [objGuid] = {tags, nickname, pos} } } -- Persisted in onSave so a whole editing session survives into the save file, -- where scripts/apply_map_tags_from_save.py reads it back. Small by construction -- (~45 maps x a handful of markers), so unlike the restore point it is cheap to -- JSON.encode on every autosave. debugMapTagData = debugMapTagData or {} -- Drops onto the (now-free) disposition row: once a map is loaded the disposition -- selectors are hidden (writeDispositionMenu early-returns on restorePointAvailable), -- so this sits centered under the Deployment Zone picker without overlapping anything. function writeBackToSelectionDebugButton() if inGame then return end if not mapTagDebugEnabled() then return end if not restorePointAvailable then return end local center = self.positionToLocal({dispositionControlWorldX, dispositionControlWorldY, 0}) createButtonWithBorder({ label = "Back to Selection (SAVE TAGS)", click_function = "backToSelectionDebug", function_owner = self, position = {center.x, center.y, center.z}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = dispositionButtonWidth, font_size = dispositionButtonFontSize, color = {0.85, 0.45, 0.95}, font_color = {0, 0, 0}, tooltip = "DEBUG: record this map's obj_* tags, then return to selection", }) end -- Record every object on the table carrying an obj_* tag into debugMapTagData -- under the currently loaded map. The live object's GUID equals the baked -- objectJSON GUID (only one map is loaded at a time, so the GUID is free and -- preserved on spawn) -- that is the join key the Python transfer uses; nickname -- and position are stored as a fallback / human-readable check. function collectObjectiveTagEdits() local objects = {} local count = 0 for _, obj in ipairs(getAllObjects()) do if obj.getTags then local tags = obj.getTags() local hasObjTag = false for _, tag in ipairs(tags) do if string.sub(tag, 1, 4) == "obj_" then hasObjTag = true break end end if hasObjTag then local pos = obj.getPosition() objects[obj.getGUID()] = { tags = tags, nickname = obj.getName(), pos = {x = pos.x, y = pos.y, z = pos.z}, } count = count + 1 end end end return objects, count end -- DEBUG button entry point: snapshot this map's obj_* tags, then return to -- selection via the normal restore flow (debugMapTagData is plain Lua state and -- survives the wipe). Load the next map and repeat; one save at the end carries -- every map's edits out to apply_map_tags_from_save.py. function backToSelectionDebug() if not mapTagDebugEnabled() then return end if not restorePointAvailable then return end if not debugCurrentMapGuid then broadcastToAll("SAVE TAGS: no loaded map tracked; cannot record.", {1, 0.4, 0.4}) return end local objects, count = collectObjectiveTagEdits() debugMapTagData[debugCurrentMapGuid] = { name = debugCurrentMapName or "?", objects = objects, } broadcastToAll("SAVE TAGS: recorded " .. tostring(count) .. " obj_* object(s) for \"" .. tostring(debugCurrentMapName) .. "\". Returning to selection.", {0.85, 0.45, 0.95}) backToSelection() end -- Deferred wipe: destroy every object the scripting zone overlaps except the -- mats, MapExclude-tagged objects, and this menu, then remove the zone itself. function matClearZoneCallback(zone) Wait.frames(function() for _, obj in ipairs(zone.getObjects()) do if obj ~= self and not matClearKeepGuids[obj.getGUID()] and obj.getGMNotes() ~= "MapExclude" then obj.destruct() end end zone.destruct() end, 2) end function clearMat() returnLayoutCardsToDeck() applyDefaultMatTexture() missionSetupControlsVisible = false destroyObjectByGUID("8f2be2") writeMenus() spawnObject({ position = {x = 0, y = 26, z = 0}, scale = matClearZoneScale, type = "ScriptingTrigger", callback_function = matClearZoneCallback, callback_owner = self, }) end -- ****************** -- BACK TO SELECTION (undo a wrong map load without reloading the mod) -- ****************** -- The persistent objects that make up the selection screen. They live in the -- save permanently and are only repositioned (shown/hidden) -- but a map card's -- wipe destroys whatever is on the mat, so we snapshot their JSON as insurance. function getSelectionObjectGuids() local guids = {} for _, card in ipairs(STARTUP_MAP_CARDS) do table.insert(guids, card.guid) end for _, entry in ipairs(GAME_MODE_OBJECTS) do table.insert(guids, entry.guid) end if forceDispositionToken_GUID then table.insert(guids, forceDispositionToken_GUID) end -- Combat Patrol runs on a blank mat but keeps its own map bag on the table, -- inside the wipe area and not a GAME_MODE_OBJECT. Snapshot it (and its dealt -- cards, below) so Back to Selection respawns it instead of wiping it for good. if combatPatrolMode and COMBAT_PATROL_BAG_GUID then table.insert(guids, COMBAT_PATROL_BAG_GUID) end return guids end -- Capture the pre-load state: the menu variables that define where we are in the -- flow, plus the JSON of every selection object still on the table. function captureRestorePoint() restorePointMenuState = { gameMode = gameMode, inGame = inGame, singlesMode = singlesMode, combatPatrolMode = combatPatrolMode, matrixPropsDisabled = matrixPropsDisabled, sizeConfirmed = sizeConfirmed, mapSizeSelected = mapSizeSelected, deployMode = deployMode, deploySelected = deploySelected, packSelected = packSelected, redDispositionSelected = redDispositionSelected, blueDispositionSelected = blueDispositionSelected, matTextureSelected = matTextureSelected, } restorePointObjects = {} for _, guid in ipairs(getSelectionObjectGuids()) do local obj = getObjectFromGUID(guid) if obj then restorePointObjects[guid] = obj.getJSON() end end -- Snapshot the loose mission cards in their current on-board display state. -- Captured before the load's own moves (paired art to the side board, -- primaries off to the in-game slots), so every card is still at its post- -- Generate display position and scale here. Tracked in -- restorePointMissionCardGuids so the restore can purge their scattered live -- copies before respawning them. restorePointMissionCardGuids = {} local function snapshotMissionCard(obj) local guid = obj.getGUID() restorePointObjects[guid] = obj.getJSON() restorePointMissionCardGuids[guid] = true end -- Map cards and layout art carry tags, so catch them wherever a player moved -- them (limiting to slots would lose manually moved cards during undo). Cards -- still inside bags are already included in their source bag JSON. -- Dealt Combat Patrol map cards carry no "map" tag (only the runtime -- combatPatrolMapCardTag) and live loose on the mat; the CP bag holds just -- three cards, all dealt at generate time, so the loose copies must be -- snapshotted too or Back to Selection loses them along with the (now-empty) bag. for _, obj in ipairs(getAllObjects()) do if obj.tag == "Card" and obj.hasTag and (obj.hasTag("map") or obj.hasTag(layoutArtCardTag) or obj.hasTag(combatPatrolMapCardTag)) then snapshotMissionCard(obj) end end -- Primary cards have no tag; take them straight from their primary slots. for _, player in ipairs({"Red", "Blue"}) do local _, cards = placedPrimaryCardEntries(player) for _, card in ipairs(cards) do snapshotMissionCard(card) end end restorePointAvailable = true end -- Remove every live copy of a snapshotted mission card so respawnSelectionObjects -- recreates it from JSON (at the captured display position + 2.5 scale) instead of -- finding a stale survivor. After a load they are scattered: maps loose on the mat, -- the paired layout art parked off-board, its siblings back inside deck fb4b5d, and -- the primaries parked off-board at 1.0 scale -- only the on-mat ones would be hit -- by the wipe. Loose copies are destructed directly; deck-held copies are taken out -- and destructed. Runs before the wipe + restore waits, leaving ample time for the -- async takeObject to settle before the freed GUIDs are reused. function purgeRestoredMissionCards() for guid in pairs(restorePointMissionCardGuids or {}) do local loose, container = findDeploymentCard(guid) if loose then pcall(function() loose.setLock(false) loose.destruct() end) elseif container and container.takeObject then pcall(function() container.takeObject({ guid = guid, smooth = false, callback_function = function(card) if card then card.destruct() end end, }) end) end end loadedLayoutArtCardGuid = nil end -- Entry point for the button: clear the (wrong) map, then restore the snapshot. function backToSelection() if not restorePointAvailable then return end broadcastToAll("Returning to map selection...", {0.3, 1.0, 0.3}) self.clearButtons() -- prevent a double-press while the restore runs -- Cancel a still-pending loaded-card auto-destruct; otherwise (on a fast -- undo) it would fire after we've respawned the card and destroy it again. if loadedMapCardDestroyTimerId then Wait.stop(loadedMapCardDestroyTimerId) loadedMapCardDestroyTimerId = nil end destroyAllObjectives() destroyDeployZones() -- Wipe out the scattered live copies (incl. the loaded layout art and the -- deck-returned siblings) so they are all respawned uniformly from the snapshot. purgeRestoredMissionCards() spawnObject({ position = {x = 0, y = 26, z = 0}, scale = matClearZoneScale, type = "ScriptingTrigger", callback_function = restoreWipeCallback, callback_owner = self, }) end -- Same wipe as matClearZoneCallback, but once the board is clear it kicks off the -- respawn instead of just removing the zone. function restoreWipeCallback(zone) Wait.frames(function() for _, obj in ipairs(zone.getObjects()) do if obj ~= self and not matClearKeepGuids[obj.getGUID()] and obj.getGMNotes() ~= "MapExclude" then obj.destruct() end end zone.destruct() -- Respawn only after the destruction has settled, so freshly spawned -- selection objects aren't caught by the same wipe. Wait.time(respawnSelectionObjects, 0.4) end, 2) end -- Bring back any selection object the wipe destroyed. Spawning from the captured -- JSON reuses the original GUID (free now that the source was destroyed), which -- the rest of the codebase relies on. function respawnSelectionObjects() for guid, json in pairs(restorePointObjects or {}) do if not getObjectFromGUID(guid) then spawnObjectJSON({json = json}) end end -- Let the spawned objects register before we reposition them. Wait.time(applyRestoredSelectionState, 0.5) end -- Restore the menu variables and re-place the selection objects, mirroring the -- relevant parts of onLoad/confirmSizeMat so positions are normalized regardless -- of where the captured JSON put them. function applyRestoredSelectionState() local s = restorePointMenuState or {} gameMode = s.gameMode or "game" inGame = s.inGame or false singlesMode = s.singlesMode combatPatrolMode = s.combatPatrolMode or false matrixPropsDisabled = s.matrixPropsDisabled or false sizeConfirmed = s.sizeConfirmed mapSizeSelected = s.mapSizeSelected or mapSizeSelected deployMode = s.deployMode deploySelected = s.deploySelected packSelected = s.packSelected redDispositionSelected = s.redDispositionSelected or redDispositionSelected blueDispositionSelected = s.blueDispositionSelected or blueDispositionSelected matTextureSelected = s.matTextureSelected or matTextureSelected if sizeConfirmed then if isValidDeployMode(deployMode) then setDeployZonesMode(deployMode, true) else setDeployZonesForSize(true) end if not deploySelected or not DeployZonesData[deploySelected] then deploySelected = #DeployZonesData end end placeStartupMapCards() if packSelected then local missionPackTable = Global.getTable("missionPackData") if missionPackTable and missionPackTable[packSelected] then missionPackData = missionPackTable[packSelected] end if gameModeUsesMatrixProps() then placeGameModeObjects() else hideGameModeObjects() end else hideGameModeObjects() end placeForceDispositionToken() applyMatTexture(false) restorePointAvailable = false restorePointObjects = nil restorePointMenuState = nil restorePointMissionCardGuids = nil writeMenus() broadcastToAll("Back to map selection.", {0.3, 1.0, 0.3}) end function updateDispositionSelection(color, add) if color == "Blue" then blueDispositionSelected = normalizeDispositionIndex(blueDispositionSelected + add) elseif color == "Red" then redDispositionSelected = normalizeDispositionIndex(redDispositionSelected + add) end writeMenus() end function blueDispositionUp() updateDispositionSelection("Blue", 1) end function blueDispositionDown() updateDispositionSelection("Blue", -1) end function redDispositionUp() updateDispositionSelection("Red", 1) end function redDispositionDown() updateDispositionSelection("Red", -1) end generateMissionWorldX = 34.60 generateMissionWorldY = 1.0 generateMissionWorldZ = 0.0 -- "primary" slots are filled deterministically from the disposition matchup -- (player + index decide which mission card lands here). Red is the +Z pair, -- blue the -Z pair (see the deck/zone notes in global.ttslua). Each deployment -- map slot has a neighboring layout-art slot fed by layoutArtDeck_GUID. layoutArtDeck_GUID = "fb4b5d" missionSlots = { {role = "primary", player = "Red", index = 1, zoneVar = "missionCardZone_RedPrimary1_GUID", deckVar = "redPrimaryDeck_GUID", position = {65, 1.01, 4.67}, displayPosition = {13.00, 1.00, 10.85}}, {role = "primary", player = "Red", index = 2, zoneVar = "missionCardZone_RedPrimary2_GUID", deckVar = "redPrimaryDeck_GUID", position = {65, 1.01, 2.55}, displayPosition = {13.00, 1.00, 5.68}}, {role = "deployment", index = 1, position = {23.70, 1.02, 17.65}}, {role = "layout", index = 1, deckVar = "layoutArtDeck_GUID", position = {23.70, 1.02, 12.35}}, {role = "deployment", index = 2, position = {23.70, 1.02, 2.65}}, {role = "layout", index = 2, deckVar = "layoutArtDeck_GUID", position = {23.70, 1.02, -2.65}}, {role = "deployment", index = 3, position = {23.70, 1.02, -12.35}}, {role = "layout", index = 3, deckVar = "layoutArtDeck_GUID", position = {23.70, 1.02, -17.65}}, {role = "primary", player = "Blue", index = 1, zoneVar = "missionCardZone_BluePrimary1_GUID", deckVar = "bluePrimaryDeck_GUID", position = {65, 1.01, -2.55}, displayPosition = {13.00, 1.00, -5.68}}, {role = "primary", player = "Blue", index = 2, zoneVar = "missionCardZone_BluePrimary2_GUID", deckVar = "bluePrimaryDeck_GUID", position = {65, 1.01, -4.67}, displayPosition = {13.00, 1.00, -10.85}}, } deploymentMatrixDecks = { ["5_5"] = { guid = "6e0d78", name = "Take and Hold vs Take and Hold", cards = { {guid = "b3537f", name = "TnH vs TnH 1 - TP"}, {guid = "f7026e", name = "TnH vs TnH 2 - DoW"}, {guid = "32ec37", name = "TnH vs TnH 3 - SnD"}, }, }, ["5_2"] = { guid = "eae80b", name = "Take and Hold vs Priority Assets", cards = { {guid = "936fc5", name = "TnH vs PA 1 - CoB"}, {guid = "9a0779", name = "TnH vs PA 2 - HnA"}, {guid = "0574f5", name = "TnH vs PA 3 - DoW"}, }, }, ["2_5"] = { guid = "eae80b", name = "Take and Hold vs Priority Assets", cards = { {guid = "936fc5", name = "TnH vs PA 1 - CoB"}, {guid = "9a0779", name = "TnH vs PA 2 - HnA"}, {guid = "0574f5", name = "TnH vs PA 3 - DoW"}, }, }, ["5_1"] = { guid = "cfeba5", name = "Take and Hold vs Disruption", cards = { {guid = "2db2a2", name = "TnH vs Dis 1 - SE"}, {guid = "366693", name = "TnH vs Dis 2 - CoB"}, {guid = "b200c7", name = "TnH vs Dis 3 - HnA"}, }, }, ["1_5"] = { guid = "cfeba5", name = "Take and Hold vs Disruption", cards = { {guid = "2db2a2", name = "TnH vs Dis 1 - SE"}, {guid = "366693", name = "TnH vs Dis 2 - CoB"}, {guid = "b200c7", name = "TnH vs Dis 3 - HnA"}, }, }, ["5_3"] = { guid = "1e6711", name = "Take and Hold vs Purge the Foe", cards = { {guid = "f2c13b", name = "TnH vs PtF 1 - SE"}, {guid = "af501d", name = "TnH vs PtF 2 - SnD"}, {guid = "812e71", name = "TnH vs PtF 3 - HnA"}, }, }, ["3_5"] = { guid = "1e6711", name = "Take and Hold vs Purge the Foe", cards = { {guid = "f2c13b", name = "TnH vs PtF 1 - SE"}, {guid = "af501d", name = "TnH vs PtF 2 - SnD"}, {guid = "812e71", name = "TnH vs PtF 3 - HnA"}, }, }, ["5_4"] = { guid = "109a6b", name = "Take and Hold vs Reconnaissance", cards = { {guid = "c26edc", name = "TnH vs Rec 1 - TP"}, {guid = "0538a9", name = "TnH vs Rec 2 - DoW"}, {guid = "bc8eb5", name = "TnH vs Rec 3 - SnD"}, }, }, ["4_5"] = { guid = "109a6b", name = "Take and Hold vs Reconnaissance", cards = { {guid = "c26edc", name = "TnH vs Rec 1 - TP"}, {guid = "0538a9", name = "TnH vs Rec 2 - DoW"}, {guid = "bc8eb5", name = "TnH vs Rec 3 - SnD"}, }, }, ["2_4"] = { guid = "32e34a", name = "Priority Assets vs Reconnaissance", cards = { {guid = "8fc0f1", name = "PA vs Rec 1 - CoB"}, {guid = "cb843a", name = "PA vs Rec 2 - TP"}, {guid = "0d4f36", name = "PA vs Rec 3 - SE"}, }, }, ["4_2"] = { guid = "32e34a", name = "Priority Assets vs Reconnaissance", cards = { {guid = "8fc0f1", name = "PA vs Rec 1 - CoB"}, {guid = "cb843a", name = "PA vs Rec 2 - TP"}, {guid = "0d4f36", name = "PA vs Rec 3 - SE"}, }, }, ["2_2"] = { guid = "7b5ba7", name = "Priority Assets vs Priority Assets", cards = { {guid = "104223", name = "PA vs PA 1 - SE"}, {guid = "5fa95f", name = "PA vs PA 2 - CoB"}, {guid = "71a63a", name = "PA vs PA 3 - TP"}, }, }, ["3_3"] = { guid = "3ebbd6", name = "Purge the Foe vs Purge the Foe", cards = { {guid = "5fdb47", name = "PtF vs PtF 1 - SnD"}, {guid = "cb907f", name = "PtF vs PtF 2 - TP"}, {guid = "3998a5", name = "PtF vs PtF 3 - SE"}, }, }, ["3_1"] = { guid = "9ac38f", name = "Purge the Foe vs Disruption", cards = { {guid = "3eb69e", name = "PtF vs Dis 1 - SnD"}, {guid = "6277f1", name = "PtF vs Dis 2 - TP"}, {guid = "dbf11e", name = "PtF vs Dis 3 - SE"}, }, }, ["1_3"] = { guid = "9ac38f", name = "Purge the Foe vs Disruption", cards = { {guid = "3eb69e", name = "PtF vs Dis 1 - SnD"}, {guid = "6277f1", name = "PtF vs Dis 2 - TP"}, {guid = "dbf11e", name = "PtF vs Dis 3 - SE"}, }, }, ["3_2"] = { guid = "dc8738", name = "Purge the Foe vs Priority Assets", cards = { {guid = "653d63", name = "PtF vs PA 1 - DoW"}, {guid = "bc3814", name = "PtF vs PA 2 - SnD"}, {guid = "80af80", name = "PtF vs PA 3 - HnA"}, }, }, ["2_3"] = { guid = "dc8738", name = "Purge the Foe vs Priority Assets", cards = { {guid = "653d63", name = "PtF vs PA 1 - DoW"}, {guid = "bc3814", name = "PtF vs PA 2 - SnD"}, {guid = "80af80", name = "PtF vs PA 3 - HnA"}, }, }, ["3_4"] = { guid = "a22c33", name = "Purge the Foe vs Reconnaissance", cards = { {guid = "78d12d", name = "PtF vs Recon 1 - HnA"}, {guid = "29061a", name = "PtF vs Recon 2 - DoW"}, {guid = "33a96e", name = "PtF vs Recon 3 - CoB"}, }, }, ["4_3"] = { guid = "a22c33", name = "Purge the Foe vs Reconnaissance", cards = { {guid = "78d12d", name = "PtF vs Recon 1 - HnA"}, {guid = "29061a", name = "PtF vs Recon 2 - DoW"}, {guid = "33a96e", name = "PtF vs Recon 3 - CoB"}, }, }, ["1_1"] = { guid = "4409ba", name = "Disruption vs Disruption", cards = { {guid = "3fc11c", name = "Dis vs Dis 1 - CoB"}, {guid = "796c92", name = "Dis vs Dis 2 - TP"}, {guid = "bdfb2a", name = "Dis vs Dis 3 - SE"}, }, }, ["4_4"] = { guid = "793bc0", name = "Reconnaissance vs Reconnaissance", cards = { {guid = "262746", name = "Rec vs Rec 1 - SE"}, {guid = "e4117b", name = "Rec vs Rec 2 - CoB"}, {guid = "229fed", name = "Rec vs Rec 3 - TP"}, }, }, ["1_4"] = { guid = "2c7cd1", name = "Disruption vs Reconnaissance", cards = { {guid = "f1a6b5", name = "Dis vs Rec 1 - TP"}, {guid = "b3addc", name = "Dis vs Rec 2 - DoW"}, {guid = "002262", name = "Dis vs Rec 3 - SnD"}, }, }, ["4_1"] = { guid = "2c7cd1", name = "Disruption vs Reconnaissance", cards = { {guid = "f1a6b5", name = "Dis vs Rec 1 - TP"}, {guid = "b3addc", name = "Dis vs Rec 2 - DoW"}, {guid = "002262", name = "Dis vs Rec 3 - SnD"}, }, }, ["1_2"] = { guid = "f1e22b", name = "Disruption vs Priority Assets", cards = { {guid = "f9769a", name = "Dis vs PA 1 - SE"}, {guid = "f83312", name = "Dis vs PA 2 - TP"}, {guid = "4a79c4", name = "Dis vs PA 3 - SnD"}, }, }, ["2_1"] = { guid = "f1e22b", name = "Disruption vs Priority Assets", cards = { {guid = "f9769a", name = "Dis vs PA 1 - SE"}, {guid = "f83312", name = "Dis vs PA 2 - TP"}, {guid = "4a79c4", name = "Dis vs PA 3 - SnD"}, }, }, } randomDeploymentDecks = { { guid = "6e0d78", name = "Take and Hold vs Take and Hold", cards = { {guid = "b3537f", name = "TnH vs TnH 1 - TP"}, {guid = "f7026e", name = "TnH vs TnH 2 - DoW"}, {guid = "32ec37", name = "TnH vs TnH 3 - SnD"}, }, }, { guid = "eae80b", name = "Take and Hold vs Priority Assets", cards = { {guid = "936fc5", name = "TnH vs PA 1 - CoB"}, {guid = "9a0779", name = "TnH vs PA 2 - HnA"}, {guid = "0574f5", name = "TnH vs PA 3 - DoW"}, }, }, { guid = "cfeba5", name = "Take and Hold vs Disruption", cards = { {guid = "2db2a2", name = "TnH vs Dis 1 - SE"}, {guid = "366693", name = "TnH vs Dis 2 - CoB"}, {guid = "b200c7", name = "TnH vs Dis 3 - HnA"}, }, }, { guid = "1e6711", name = "Take and Hold vs Purge the Foe", cards = { {guid = "f2c13b", name = "TnH vs PtF 1 - SE"}, {guid = "af501d", name = "TnH vs PtF 2 - SnD"}, {guid = "812e71", name = "TnH vs PtF 3 - HnA"}, }, }, { guid = "109a6b", name = "Take and Hold vs Reconnaissance", cards = { {guid = "c26edc", name = "TnH vs Rec 1 - TP"}, {guid = "0538a9", name = "TnH vs Rec 2 - DoW"}, {guid = "bc8eb5", name = "TnH vs Rec 3 - SnD"}, }, }, { guid = "32e34a", name = "Priority Assets vs Reconnaissance", cards = { {guid = "8fc0f1", name = "PA vs Rec 1 - CoB"}, {guid = "cb843a", name = "PA vs Rec 2 - TP"}, {guid = "0d4f36", name = "PA vs Rec 3 - SE"}, }, }, { guid = "7b5ba7", name = "Priority Assets vs Priority Assets", cards = { {guid = "104223", name = "PA vs PA 1 - SE"}, {guid = "5fa95f", name = "PA vs PA 2 - CoB"}, {guid = "71a63a", name = "PA vs PA 3 - TP"}, }, }, { guid = "3ebbd6", name = "Purge the Foe vs Purge the Foe", cards = { {guid = "5fdb47", name = "PtF vs PtF 1 - SnD"}, {guid = "cb907f", name = "PtF vs PtF 2 - TP"}, {guid = "3998a5", name = "PtF vs PtF 3 - SE"}, }, }, { guid = "9ac38f", name = "Purge the Foe vs Disruption", cards = { {guid = "3eb69e", name = "PtF vs Dis 1 - SnD"}, {guid = "6277f1", name = "PtF vs Dis 2 - TP"}, {guid = "dbf11e", name = "PtF vs Dis 3 - SE"}, }, }, { guid = "dc8738", name = "Purge the Foe vs Priority Assets", cards = { {guid = "653d63", name = "PtF vs PA 1 - DoW"}, {guid = "bc3814", name = "PtF vs PA 2 - SnD"}, {guid = "80af80", name = "PtF vs PA 3 - HnA"}, }, }, { guid = "a22c33", name = "Purge the Foe vs Reconnaissance", cards = { {guid = "78d12d", name = "PtF vs Recon 1 - HnA"}, {guid = "29061a", name = "PtF vs Recon 2 - DoW"}, {guid = "33a96e", name = "PtF vs Recon 3 - CoB"}, }, }, { guid = "4409ba", name = "Disruption vs Disruption", cards = { {guid = "3fc11c", name = "Dis vs Dis 1 - CoB"}, {guid = "796c92", name = "Dis vs Dis 2 - TP"}, {guid = "bdfb2a", name = "Dis vs Dis 3 - SE"}, }, }, { guid = "793bc0", name = "Reconnaissance vs Reconnaissance", cards = { {guid = "262746", name = "Rec vs Rec 1 - SE"}, {guid = "e4117b", name = "Rec vs Rec 2 - CoB"}, {guid = "229fed", name = "Rec vs Rec 3 - TP"}, }, }, { guid = "2c7cd1", name = "Disruption vs Reconnaissance", cards = { {guid = "f1a6b5", name = "Dis vs Rec 1 - TP"}, {guid = "b3addc", name = "Dis vs Rec 2 - DoW"}, {guid = "002262", name = "Dis vs Rec 3 - SnD"}, }, }, { guid = "f1e22b", name = "Disruption vs Priority Assets", cards = { {guid = "f9769a", name = "Dis vs PA 1 - SE"}, {guid = "f83312", name = "Dis vs PA 2 - TP"}, {guid = "4a79c4", name = "Dis vs PA 3 - SnD"}, }, }, } deploymentCardSourceDeckByGuid = {} deploymentCardSourceDeckByName = {} layoutArtCardTag = "layout_art_card" loadedLayoutArtCardGuid = nil loadedLayoutArtTargetPosition = {65, 0.97, 0.00} loadedLayoutArtTargetScale = {0.97, 1.00, 1.03} layoutArtDeckScale = {2.00, 1.00, 2.10} deploymentLockTargetPosition = {60.75, 1.02, 0.00} -- Map (deployment) cards should always settle face up at y=180. Layout-art helper -- cards keep their own rotation (they have their own orientation handling). -- Absolute (not a +180 toggle) so a re-press is idempotent. deploymentMapCardRotation = {0, 180, 0} deploymentLockButtonWorldX = 36.65 deploymentLockButtonWorldY = 1.00 deploymentLockButtonOffsetX = -1.60 deploymentLockButtonWorldZ = {[1] = 3.00, [2] = 0.10, [3] = -2.80} deploymentRandomPickButtonWorldX = 31.50 deploymentRandomPickButtonWorldY = 1.00 deploymentRandomPickButtonWorldZ = generateMissionWorldZ function writeGenerateMissionButton() local center = self.positionToLocal({generateMissionWorldX + setupMidRowXOffset(), generateMissionWorldY, generateMissionWorldZ}) local missionReady = gameMode == "game" and packSelected ~= nil local generateLabel = gameModeUsesMatrixProps() and "Generate Missions & Layouts" or "Generate Missions" createButtonWithBorder({ label = generateLabel, click_function = missionReady and "generateMission" or "none", function_owner = self, position = {center.x, center.y, center.z}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = 4500, font_size = 350, color = {0.191999942, 0.701, 0.167999923}, font_color = {0, 0, 0}, tooltip = "Deal each player's primary mission for the current dispositions", }, {0, 0, 0, 1}) writeDeploymentRandomPickButton() end function writeDeploymentRandomPickButton() local btn = self.positionToLocal({deploymentRandomPickButtonWorldX + setupMidRowXOffset(), deploymentRandomPickButtonWorldY, deploymentRandomPickButtonWorldZ}) createButtonWithBorder({ label = "Random Single Layout", click_function = "randomMission", function_owner = self, position = {btn.x, btn.y, btn.z}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = 4500, font_size = 340, color = {1, 0.86, 0.05}, font_color = {0, 0, 0}, tooltip = "Randomly choose one of the deployment cards", }, {0, 0, 0, 1}) end -- ******************** -- SET MISSIONS -- ******************** -- Commits the generated primary missions to their in-game slots -- the same -- transform onMapCardLoaded runs when a map's "Load Map" button is pressed. -- Shown in every mode EXCEPT the standard 1v1/Solo game: there a map card is -- always loaded, and that load already performs this move. The matrix-free modes -- (Combat Patrol, Colosseum, Boarding Action) and Onslaught get a manual commit. -- Pinned to the requested world position in every mode it appears in (including -- Onslaught) -- no mid-row offset. setMissionsButtonWorldX = 20.00 setMissionsButtonWorldY = 1.00 setMissionsButtonWorldZ = 0.00 -- Set true the moment Generate (or Random Single Layout) deals a mission set, and -- reset on a fresh mode confirm. The button stays hidden until this is true, so it -- only appears once missions have been generated at least once this setup. missionsGenerated = false -- 1v1/Solo is the only standard-matrix Strike Force game; Onslaught is the other -- matrix game but is identified by its size. Everything else is matrix-free. function gameModeShowsSetMissions() return combatPatrolMode or matrixPropsDisabled or selectedSizeIsOnslaught() end -- True once Generate has dealt each player a primary mission. Requires BOTH -- players to have at least one primary card in their slots (getObjectsInMissionSlot -- matches the post-Generate display position), so a partial/failed deal that left -- only one side's card on the board does not enable the commit. function missionSetGenerated() for _, player in ipairs({"Red", "Blue"}) do local _, cards = placedPrimaryCardEntries(player) if #cards == 0 then return false end end return true end function writeSetMissionsButton() if not gameModeShowsSetMissions() then return end -- Hidden until Generate has dealt the missions for the first time this setup. if not missionsGenerated then return end local center = self.positionToLocal({setMissionsButtonWorldX, setMissionsButtonWorldY, setMissionsButtonWorldZ}) createButtonWithBorder({ label = "SET MISSIONS", click_function = "setMissions", function_owner = self, position = {center.x, center.y, center.z}, rotation = {0, 0, 0}, height = dispositionButtonHeight, width = 4500, font_size = 350, color = {1, 0.92, 0.16}, font_color = {0, 0, 0}, tooltip = "Move the generated primary missions onto the mission board", }, {0, 0, 0, 1}) end -- Click handler. Captures a restore point first (exactly like a map load) so the -- existing Back to Selection flow brings the primaries, the disposition/Generate -- controls, and this button all back. Setting restorePointAvailable also makes -- writeDispositionMenu early-return, so this button disappears once pressed. function setMissions() if inGame or gameMode ~= "game" then return end if not gameModeShowsSetMissions() then return end if restorePointAvailable then return end if missionDealInProgress then broadcastToAll("Missions are still being dealt -- try again in a moment.", {1, 0.75, 0}) return end if not missionSetGenerated() then broadcastToAll("Generate the missions first, then press SET MISSIONS.", {1, 0.75, 0}) return end -- Snapshot the pre-set state (primaries still at their display position), then -- slide them to the in-game slots. Mirrors onMapCardLoaded's primary move. captureRestorePoint() movePrimaryMissionCardsToIngameSlots() writeMenus() broadcastToAll("Missions set. Press Back to Selection to undo.", {0.3, 1.0, 0.3}) end function writeDeploymentLockButtons() for _, slot in ipairs(missionSlots) do if slot.role == "deployment" then local btnZ = deploymentLockButtonWorldZ[slot.index] or slot.position[3] local btn = self.positionToLocal({deploymentLockButtonWorldX, deploymentLockButtonWorldY, btnZ}) local targetIndex = 4 - slot.index self.createButton({ label = "Lock-in", click_function = "lockInDeployment" .. targetIndex, function_owner = self, position = {btn.x, btn.y, btn.z}, rotation = {0, 0, 0}, height = 130, width = 1050, font_size = 95, color = {1, 0.86, 0.05}, font_color = {0, 0, 0}, tooltip = "Move this deployment card to the mission board", }) end end end function isMissionCardObject(obj) return obj and (obj.tag == "Card" or obj.tag == "Deck") end -- A card may be returned into either a Deck (helper/primary sources) or a Bag -- (the matchup map sources). Both expose getObjects()/takeObject by GUID, so any -- by-GUID lookup that scans containers must consider both. function isCardContainer(obj) return obj and (obj.tag == "Deck" or obj.tag == "Bag") and obj.getObjects ~= nil end function addMissionSlotObject(objectsByGuid, obj) if isMissionCardObject(obj) then objectsByGuid[obj.getGUID()] = obj end end function missionSlotPositionMatches(slot, pos, target) return target and math.abs(pos.x - target[1]) < 1.6 and math.abs(pos.z - target[3]) < 1.2 and pos.y > 0.5 and pos.y < 3.0 end function getObjectsInMissionSlot(slot, zone) local objectsByGuid = {} if zone then for _, obj in ipairs(zone.getObjects()) do addMissionSlotObject(objectsByGuid, obj) end end for _, obj in ipairs(getAllObjects()) do if isMissionCardObject(obj) then local pos = obj.getPosition() if missionSlotPositionMatches(slot, pos, slot.position) or missionSlotPositionMatches(slot, pos, slot.displayPosition) then addMissionSlotObject(objectsByGuid, obj) end end end local objects = {} for _, obj in pairs(objectsByGuid) do table.insert(objects, obj) end return objects end function getDeploymentMissionSlot(index) for _, slot in ipairs(missionSlots) do if slot.role == "deployment" and slot.index == index then return slot end end return nil end function getDeploymentCardInSlot(slot, deploymentDeck) local candidates = getObjectsInMissionSlot(slot, nil) for _, obj in ipairs(candidates) do if obj ~= deploymentDeck then return obj end end return nil end function rememberDeploymentCardSource(card, deckGuid) if not card or not deckGuid then return end local guid = card.getGUID and card.getGUID() or nil if guid then deploymentCardSourceDeckByGuid[guid] = deckGuid end local name = card.getName and card.getName() or nil if deckGuid ~= layoutArtDeck_GUID and name and name ~= "" then deploymentCardSourceDeckByName[normalizeMissionName(name)] = deckGuid end end function cacheDeploymentDeckContents() for _, deckInfo in ipairs(randomDeploymentDecks) do for _, entry in ipairs(deckInfo.cards or {}) do if entry.guid then deploymentCardSourceDeckByGuid[entry.guid] = deckInfo.guid end local name = entry.nickname or entry.name if name and name ~= "" then deploymentCardSourceDeckByName[normalizeMissionName(name)] = deckInfo.guid end end local deck = getObjectFromGUID(deckInfo.guid) if deck and deck.getObjects then for _, entry in ipairs(deck.getObjects()) do if entry.guid then deploymentCardSourceDeckByGuid[entry.guid] = deckInfo.guid end local name = entry.nickname or entry.name if name and name ~= "" then deploymentCardSourceDeckByName[normalizeMissionName(name)] = deckInfo.guid end end end end local layoutDeck = getObjectFromGUID(layoutArtDeck_GUID) if layoutDeck and layoutDeck.getObjects then for _, entry in ipairs(layoutDeck.getObjects()) do if entry.guid then deploymentCardSourceDeckByGuid[entry.guid] = layoutArtDeck_GUID end end end end function getDeploymentCardSourceDeck(card) cacheDeploymentDeckContents() if not card then return nil end local guid = card.getGUID and card.getGUID() or nil local deckGuid = guid and deploymentCardSourceDeckByGuid[guid] or nil if card.hasTag and card.hasTag(layoutArtCardTag) then deckGuid = layoutArtDeck_GUID end if not deckGuid and card.getName then local name = card.getName() deckGuid = deploymentCardSourceDeckByName[normalizeMissionName(name)] end return deckGuid and getObjectFromGUID(deckGuid) or nil end function returnDeploymentCardToSourceDeck(card) if not card then return nil end local sourceDeck = getDeploymentCardSourceDeck(card) if sourceDeck then card.locked = false return sourceDeck.putObject(card) or sourceDeck end -- No known source: deployment/layout cards now come exclusively from the -- matchup matrix decks and the layout-art deck (fb4b5d), both tracked in -- deploymentCardSourceDeckBy*. The old shared deployment deck is gone, so a -- card with no recorded source simply stays where it is. return nil end function returnDeploymentCardsFromSlot(slot) for _, obj in ipairs(getObjectsInMissionSlot(slot, nil)) do returnDeploymentCardToSourceDeck(obj) end end function selectedDeploymentCardGuidSet(choices) local selected = {} -- pairs (not ipairs): Random Mission passes a sparse table that only fills the -- middle slot ({[2] = choice}), which ipairs would skip entirely. for _, choice in pairs(choices or {}) do if choice.guid then selected[choice.guid] = true end end return selected end -- Return a generated map card to its source bag. We putObject into the live bag -- (located by GUID) rather than setPosition to a recorded "home": a Bag keeps its -- GUID and accepts putObject wherever it sits on the table, so the card always -- ends up INSIDE its bag no matter where the bags have been moved. The old -- setPosition-to-home only dropped the card next to a bag, and missed entirely -- once the bag's recorded coordinate went stale. returnCounts is no longer needed -- but is kept so the caller's loop is unchanged. function returnDeploymentCardHome(card, returnCounts) if not card then return end card.tooltip = true card.interactable = true returnDeploymentCardToSourceDeck(card) end function clearDeploymentSlotForChoices(slot, selectedGuids, returnCounts) for _, obj in ipairs(getObjectsInMissionSlot(slot, nil)) do local guid = obj.getGUID and obj.getGUID() or nil if guid and selectedGuids[guid] then obj.locked = false obj.tooltip = true obj.interactable = true elseif guid and deploymentCardSourceDeckByGuid[guid] then returnDeploymentCardHome(obj, returnCounts) else returnDeploymentCardToSourceDeck(obj) end end end function clearDeploymentSlotsForChoices(choices, layoutChoices) local selectedGuids = selectedDeploymentCardGuidSet(choices) for guid in pairs(selectedDeploymentCardGuidSet(layoutChoices)) do selectedGuids[guid] = true end local returnCounts = {} for _, slot in ipairs(missionSlots) do if slot.role == "deployment" or slot.role == "layout" then clearDeploymentSlotForChoices(slot, selectedGuids, returnCounts) end end clearDeploymentSlotForChoices({position = deploymentLockTargetPosition}, selectedGuids, returnCounts) end function returnLayoutCardsToDeck(exceptCard) for _, slot in ipairs(missionSlots) do if slot.role == "layout" then for _, obj in ipairs(getObjectsInMissionSlot(slot, nil)) do if obj ~= exceptCard then returnDeploymentCardToSourceDeck(obj) end end end end end function returnLoadedLayoutArtCardToDeck() if not loadedLayoutArtCardGuid then return end local card = getObjectFromGUID(loadedLayoutArtCardGuid) loadedLayoutArtCardGuid = nil if card then card.locked = false card.setScale(layoutArtDeckScale) returnDeploymentCardToSourceDeck(card) end end function placeLoadedLayoutArtCard(mapName) local card = findLooseLayoutArtCard(mapName) returnLayoutCardsToDeck(card) if not card then broadcastToAll("Could not find paired layout art for loaded map: " .. tostring(mapName), "Yellow") return end loadedLayoutArtCardGuid = card.getGUID() card.locked = false card.setScale(loadedLayoutArtTargetScale) card.setPosition(loadedLayoutArtTargetPosition) card.tooltip = true card.interactable = true card.setLock(true) end function returnCardsInDeploymentSlotsToDeck(includeLockedIn) for _, slot in ipairs(missionSlots) do if slot.role == "deployment" or slot.role == "layout" then returnDeploymentCardsFromSlot(slot) end end if includeLockedIn then returnDeploymentCardsFromSlot({position = deploymentLockTargetPosition}) end end function movePrimaryCardToIngameSlot(card, slot, smooth) local target = {slot.position[1], slot.position[2], slot.position[3]} card.locked = false card.setScale({1.0, 1.0, 1.0}) -- Load Map wipes the mat on the next frame, so that path must place cards -- directly on the safe board. Start Game can keep the visible smooth move. if smooth == false then card.setPosition(target) card.locked = true return end card.setPositionSmooth(target, false, true) local function finishMove() if card then card.locked = true end end local function moveFinished() if not card then return true end local pos = card.getPosition() return math.abs(pos.x - target[1]) < 0.2 and math.abs(pos.z - target[3]) < 0.2 end local function forceFinishMove() if card then card.setPosition(target) card.locked = true end end Wait.condition(finishMove, moveFinished, 5, forceFinishMove) end function movePrimaryMissionCardsToIngameSlots(smooth) for _, slot in ipairs(missionSlots) do if slot.role == "primary" then for _, card in ipairs(getObjectsInMissionSlot(slot, nil)) do movePrimaryCardToIngameSlot(card, slot, smooth) end end end end -- Name of the primary mission card sitting in a player's primary slots, read from -- the actual card dealt off that player's primary deck (Red = 25c0d8, Blue = e64441). -- getObjectsInMissionSlot matches both the display and in-game positions, so this is -- reliable whether the card has finished its startGame slide to the board or not. function getPrimaryMissionNameForPlayer(player) for _, slot in ipairs(missionSlots) do if slot.role == "primary" and slot.player == player then for _, obj in ipairs(getObjectsInMissionSlot(slot, nil)) do if obj.tag == "Card" then local n = obj.getName() if n and n ~= "" then return n end end end end end return "" end -- Push the static-per-game scoring overlay header info to Global once the game -- starts (disposition + primary per player, shared deployment zone). These change -- a lot during deployment, so we only snapshot them here at startGame. function pushScoringContext() local deployName = (selectedDeployment() and selectedDeployment().name) or "" Global.call("setScoringContext", { redDisposition = dispositionValues[redDispositionSelected] or "", blueDisposition = dispositionValues[blueDispositionSelected] or "", redPrimary = getPrimaryMissionNameForPlayer("Red"), bluePrimary = getPrimaryMissionNameForPlayer("Blue"), deployZone = deployName, }) end function moveDeploymentCardToLockTarget(cardToMove) if not cardToMove then return end local lockedDeploymentSlot = {position = deploymentLockTargetPosition} for _, obj in ipairs(getObjectsInMissionSlot(lockedDeploymentSlot, nil)) do if obj ~= cardToMove then returnDeploymentCardToSourceDeck(obj) end end cardToMove.locked = false cardToMove.setPositionSmooth(deploymentLockTargetPosition, false, true) Wait.frames(function() if cardToMove then cardToMove.locked = true end end, 30) end function lockInDeploymentSlot(index) local slot = getDeploymentMissionSlot(index) if not slot then return end local cardToMove = getDeploymentCardInSlot(slot, nil) if not cardToMove then broadcastToAll("No deployment card found above this Lock-in button.", "Yellow") return end moveDeploymentCardToLockTarget(cardToMove) end -- Random Mission: run the same flow as Generate Mission, but place a single -- randomly-chosen deployment card instead of all three. Shares the cooldown and -- in-progress guards, so spamming it across different matchups stays resilient. function randomMission() runMissionGeneration("random") end function lockInDeployment1() lockInDeploymentSlot(1) end function lockInDeployment2() lockInDeploymentSlot(2) end function lockInDeployment3() lockInDeploymentSlot(3) end -- lower-cased, trimmed copy for tolerant name comparisons. Curly single quotes -- (U+2018/U+2019) are folded to a straight ' so "Destroyer's Wrath" matches -- regardless of which apostrophe the card or the table uses. function normalizeMissionName(s) s = tostring(s or "") s = s:gsub("\226\128\152", "'"):gsub("\226\128\153", "'") return (s:lower():gsub("^%s+", ""):gsub("%s+$", "")) end mapCreatorDisplaySuffixes = {"Cra5hNatural", "BTTF", "Battlemaster - BTTF Ruins", "Battlemaster - Desert", "Zim", "T5S2", "LCT-Pack-1"} -- Map cards carry creator credit at the end of their visible nickname. Strip -- only a recognized creator suffix when matching shared layout art or reading -- the deployment-zone suffix. function mapLogicalName(name) name = tostring(name or ""):gsub("%s+$", "") local normalized = normalizeMissionName(name) for _, creator in ipairs(mapCreatorDisplaySuffixes) do local suffix = " - " .. creator local normalizedSuffix = suffix:lower() if normalized:sub(-#normalizedSuffix) == normalizedSuffix then return name:sub(1, #name - #suffix):gsub("%s+$", "") end end return name end function mapDeploymentZoneName(name) return mapLogicalName(name):match("^.*%-%s*(.-)%s*$") end -- "Death Trap 2" -> "Death Trap"; a card with no trailing 1/2 is unchanged. function missionBaseName(nick) return (tostring(nick or ""):gsub("%s+[12]%s*$", "")) end -- Resolve a mission's two card slots inside a deck, by card nickname. -- Returns { {guid, name, source}, {guid, name, source} } for slot 1 and 2. -- source is "mission" (real card found, placed by its own 1/2 suffix), "blank" -- ("Blank Primary 1/2" padding an empty slot), or "missing" (no card at all). -- extraEntries (optional): {guid=, nickname=} for cards already sitting in this -- player's slots. They are searched BEFORE the deck so a card that is already -- placed wins over its deck copy -- that lets a re-press reposition the existing -- card instead of recalling it to the (far-away) deck and drawing it again. function resolvePrimaryCardGuids(deck, missionName, extraEntries) local found = {} -- found[1], found[2] = {guid, name} local blank = {} -- blank[1], blank[2] = {guid, name} local target = normalizeMissionName(missionName) local entries = {} if extraEntries then for _, e in ipairs(extraEntries) do table.insert(entries, e) end end if deck and deck.getObjects then for _, e in ipairs(deck.getObjects()) do table.insert(entries, e) end end for _, entry in ipairs(entries) do local nick = entry.nickname or entry.name or "" local low = normalizeMissionName(nick) local num = tonumber(string.match(nick, "(%d)%s*$")) if string.find(low, "^blank primary") then if (num == 1 or num == 2) and not blank[num] then blank[num] = {guid = entry.guid, name = nick} end elseif target ~= "" and normalizeMissionName(missionBaseName(nick)) == target then local idx = (num == 2) and 2 or 1 if not found[idx] then found[idx] = {guid = entry.guid, name = nick} elseif not found[3 - idx] then found[3 - idx] = {guid = entry.guid, name = nick} end end end local result = {} for i = 1, 2 do if found[i] then result[i] = {guid = found[i].guid, name = found[i].name, source = "mission"} elseif blank[i] then result[i] = {guid = blank[i].guid, name = blank[i].name, source = "blank"} else result[i] = {guid = nil, name = nil, source = "missing"} end end return result end function resolveDeckCardByName(deck, cardName) local target = normalizeMissionName(cardName) if deck and deck.getObjects and target ~= "" then for _, entry in ipairs(deck.getObjects()) do local nick = entry.nickname or entry.name or "" if normalizeMissionName(missionBaseName(nick)) == target then return {guid = entry.guid, name = nick, source = "card"} end end end return {guid = nil, name = cardName, source = "missing"} end -- One-line, chat-friendly summary of what a mission resolved to. -- Look up the configured matchup for the current dispositions. -- Key convention: "_" (see global.ttslua missionMatchups). function getCurrentMatchup() local matchups = Global.getTable("missionMatchups") or {} local redIdx = normalizeDispositionIndex(redDispositionSelected) local blueIdx = normalizeDispositionIndex(blueDispositionSelected) local key = redIdx .. "_" .. blueIdx return matchups[key], key end function getCurrentDeploymentChoices(key) return deploymentMatrixDecks[key] end function copyRandomDeploymentDecks() local decks = {} for _, deckInfo in ipairs(randomDeploymentDecks) do table.insert(decks, deckInfo) end return decks end -- Map-card nicknames use the number before " - " as their logical layout slot. -- Multiple creators can provide variants of the same slot; generation chooses -- one variant per slot so the displayed choices remain layouts 1, 2 and 3. function deploymentCardLayoutIndex(card) local name = card and (card.nickname or card.name) or "" return tonumber(string.match(name, "%s(%d+)%s*%-")) end function getCardNameByGuid(guid, fallback) local loose = guid and getObjectFromGUID(guid) or nil if loose and loose.getName then local name = loose.getName() if name and name ~= "" then return name end end for _, candidate in ipairs(getAllObjects()) do if isCardContainer(candidate) then for _, entry in ipairs(candidate.getObjects()) do if entry.guid == guid then return entry.nickname or entry.name or fallback end end end end return fallback end function findLooseLayoutArtCard(cardName) local target = normalizeMissionName(mapLogicalName(cardName)) for _, obj in ipairs(getAllObjects()) do if obj.tag == "Card" and obj.hasTag and obj.hasTag(layoutArtCardTag) and normalizeMissionName(obj.getName()) == target then return obj end end return nil end function resolveLayoutArtChoice(mapChoice) if not mapChoice or not mapChoice.name then return nil end local target = normalizeMissionName(mapLogicalName(mapChoice.name)) local layoutDeck = getObjectFromGUID(layoutArtDeck_GUID) if layoutDeck and layoutDeck.getObjects then for _, entry in ipairs(layoutDeck.getObjects()) do local name = entry.nickname or entry.name or "" if normalizeMissionName(name) == target then deploymentCardSourceDeckByGuid[entry.guid] = layoutArtDeck_GUID return {deck = layoutDeck, deckGuid = layoutArtDeck_GUID, guid = entry.guid, name = name} end end end local loose = findLooseLayoutArtCard(mapChoice.name) if loose then local guid = loose.getGUID() deploymentCardSourceDeckByGuid[guid] = layoutArtDeck_GUID return {deck = layoutDeck, deckGuid = layoutArtDeck_GUID, guid = guid, name = loose.getName()} end return nil end function resolveLayoutArtChoices(mapChoices) local choices = {} local missing = {} for index, mapChoice in pairs(mapChoices or {}) do local choice = resolveLayoutArtChoice(mapChoice) if choice then choices[index] = choice else table.insert(missing, mapChoice.name or "?") end end if #missing > 0 then broadcastToAll("Missing matching layout art: " .. table.concat(missing, ", "), "Yellow") end return choices end -- Narrow a layout's variant list to the map filter's wishes, using the baked -- MAP_INDEX (creator/eligible) and the Global "activeMapCreators" set written by -- the map-filter overlay. With no active filter, every eligible map can roll. With -- one or more active filters, only those creators are eligible; this keeps repeat -- Generate clicks from silently mixing in other creators while cards are moving -- back to their source bags. function filterDeploymentVariants(variants) local index = Global.getTable("MAP_INDEX") or {} local active = Global.getTable("activeMapCreators") or {} local anyActive = next(active) ~= nil local eligible = {} for _, card in ipairs(variants) do local meta = index[card.guid] if not meta or meta.eligible ~= false then table.insert(eligible, card) end end if not anyActive then return eligible end local preferred = {} for _, card in ipairs(eligible) do local meta = index[card.guid] if meta and active[meta.creator] then table.insert(preferred, card) end end return preferred end function addPlacedDeploymentCardsForDeck(cards, deckGuid) if not deckGuid then return end local seen = {} for _, card in ipairs(cards) do if card.guid then seen[card.guid] = true end end local slots = {} for _, slot in ipairs(missionSlots) do if slot.role == "deployment" then table.insert(slots, slot) end end table.insert(slots, {position = deploymentLockTargetPosition}) for _, slot in ipairs(slots) do for _, obj in ipairs(getObjectsInMissionSlot(slot, nil)) do local guid = obj.getGUID and obj.getGUID() or nil if guid and not seen[guid] then local sourceDeck = getDeploymentCardSourceDeck(obj) if sourceDeck and sourceDeck.getGUID and sourceDeck.getGUID() == deckGuid then local name = obj.getName and obj.getName() or "" table.insert(cards, {guid = guid, nickname = name, name = name}) seen[guid] = true end end end end end function chooseDeploymentCardsFromDeckInfo(deckInfo, count) cacheDeploymentDeckContents() local choices = {} local deck = deckInfo and getObjectFromGUID(deckInfo.guid) or nil local cards = deck and deck.getObjects and deck.getObjects() or {} -- The save/manifest are now the source of truth for map-card inventory. The -- hand-written card lists remain as fallback metadata for unusual load states, -- but normal generation reads the live source bag so imported static cards do -- not also need to be duplicated into startMenu tables. if #cards == 0 and deckInfo and deckInfo.cards then cards = deckInfo.cards end addPlacedDeploymentCardsForDeck(cards, deckInfo and deckInfo.guid) local cardsByLayout = {} local cardsWithoutLayout = {} for _, card in ipairs(cards) do local layoutIndex = deploymentCardLayoutIndex(card) if layoutIndex then cardsByLayout[layoutIndex] = cardsByLayout[layoutIndex] or {} table.insert(cardsByLayout[layoutIndex], card) else table.insert(cardsWithoutLayout, card) end end local selectedCards = {} for layoutIndex = 1, count do local variants = filterDeploymentVariants(cardsByLayout[layoutIndex] or {}) if #variants > 0 then table.insert(selectedCards, variants[math.random(#variants)]) else local fallbackVariants = filterDeploymentVariants(cardsWithoutLayout) if #fallbackVariants > 0 then local picked = fallbackVariants[math.random(#fallbackVariants)] for idx, card in ipairs(cardsWithoutLayout) do if card.guid == picked.guid then table.remove(cardsWithoutLayout, idx) break end end table.insert(selectedCards, picked) end end end for _, card in ipairs(selectedCards) do local name = getCardNameByGuid(card.guid, card.nickname or card.name or deckInfo.name) deploymentCardSourceDeckByGuid[card.guid] = deckInfo.guid if name and name ~= "" then deploymentCardSourceDeckByName[normalizeMissionName(name)] = deckInfo.guid end table.insert(choices, { deck = deck, deckGuid = deckInfo.guid, deckName = deckInfo.name, guid = card.guid, name = name, }) end return choices end function chooseDeploymentCardsForMatchup(key, count) local deckInfo = getCurrentDeploymentChoices(key) if deckInfo then return chooseDeploymentCardsFromDeckInfo(deckInfo, count), false end broadcastToAll("Missing dispotition data, using placeholder missions", "Yellow") return chooseRandomDeploymentCards(count), true end function chooseRandomDeploymentCards(count) cacheDeploymentDeckContents() local choices = {} local availableCards = {} for _, deckInfo in ipairs(randomDeploymentDecks) do -- Collapse creator variants first so fallback selection weights logical -- maps equally instead of favoring decks that have more creators. for _, choice in ipairs(chooseDeploymentCardsFromDeckInfo(deckInfo, 3)) do table.insert(availableCards, choice) end end while #choices < count and #availableCards > 0 do local cardIndex = math.random(#availableCards) table.insert(choices, table.remove(availableCards, cardIndex)) end return choices end -- Frames over which a card's scale is smoothly interpolated to its target, so it -- grows/shrinks instead of popping. Set to 0/1 for an instant snap. primaryResizeFrames = 12 -- Smoothly lerp a card's scale to `target` over `frames`. Every TTS call is -- wrapped in pcall so a card destroyed mid-animation (e.g. recalled into a deck) -- just stops the animation instead of throwing. function animateCardScale(card, target, frames) frames = frames or primaryResizeFrames if frames < 1 then frames = 1 end local startV if not pcall(function() startV = card.getScale() end) or not startV then pcall(function() card.setScale(target) end) return end local sx, sy, sz = startV.x, startV.y, startV.z local step = 0 local function tick() step = step + 1 local t = step / frames local applied = pcall(function() if t >= 1 then card.setScale(target) else card.setScale({sx + (target[1] - sx) * t, sy + (target[2] - sy) * t, sz + (target[3] - sz) * t}) end end) if applied and t < 1 then Wait.frames(tick, 1) end end Wait.frames(tick, 1) end function finishPlacedMissionCard(card, placement) if not card then return end if placement.sourceDeckGuid then rememberDeploymentCardSource(card, placement.sourceDeckGuid) end if placement.role == "layout" and card.addTag then card.addTag(layoutArtCardTag) end if placement.flip == false and card.is_face_down then card.flip() end if placement.rotation then card.setRotationSmooth(placement.rotation, false, true) end if placement.scale then animateCardScale(card, placement.scale) end card.tooltip = true card.interactable = true Wait.frames(function() if card then card.locked = placement.lock ~= false end end, 1) end function moveExistingMissionCard(card, placement) card.locked = false card.setPositionSmooth(placement.position, false, true) -- scale is animated inside finishPlacedMissionCard finishPlacedMissionCard(card, placement) end function releaseGenerateMissionCooldown() Wait.time(function() generateMissionCooldownActive = false end, generateMissionCooldownSeconds) end -- True only while cards are actively being placed (the multi-frame deal). Random -- Pick is blocked during this window so it can't grab a half-dealt set of slots, -- keep one card, and then have the deal place the rest behind its back. missionDealInProgress = false -- Single completion path for a deal. Clears the in-progress flag immediately so -- Random Mission is usable again, and starts the (longer) press cooldown. Safe to -- call from any error handler or from the normal end of the deal. function finishMissionDeal() missionDealInProgress = false releaseGenerateMissionCooldown() -- Refresh the menu so the SET MISSIONS button appears now that a set is dealt. writeMenus() end -- Locate a card by GUID whether it is loose in the world or has been merged -- back into a deck. Returns (looseCard, containerDeck): -- looseCard ~= nil -> the card is loose; move it directly. -- containerDeck ~= nil -> the card lives inside that deck; take it by GUID. -- Both nil means the GUID is gone entirely. This is what keeps Generate Mission -- working after Random Mission: returned cards that TTS recombines into a new deck -- (with a fresh deck GUID) become invisible to getObjectFromGUID, so we have to -- scan deck contents to find them again. function findDeploymentCard(guid) if not guid then return nil, nil end local obj = getObjectFromGUID(guid) if obj then return obj, nil end for _, candidate in ipairs(getAllObjects()) do if isCardContainer(candidate) then for _, entry in ipairs(candidate.getObjects()) do if entry.guid == guid then return nil, candidate end end end end return nil, nil end -- Place a single mission card described by a placement. guid == nil means -- "draw the top card from p.deck" (used for primary slots with no exact card). function placeMissionCardFromGuid(p) local loose, container = nil, nil if p.guid then loose, container = findDeploymentCard(p.guid) end -- Already in the world (loose card, or a tiny deck TTS collapsed into its -- last card): just move it. if loose then moveExistingMissionCard(loose, p) return end if not container and p.deck and p.deck.tag == "Card" then moveExistingMissionCard(p.deck, p) return end -- Otherwise take it out of whichever deck holds it: the deck we found it in -- (preferred, survives recombination) or the originally chosen deck. local sourceDeck = container or p.deck if sourceDeck and sourceDeck.takeObject then local params = { position = p.position, flip = p.flip ~= false, smooth = true, callback_function = function(card) finishPlacedMissionCard(card, p) end, } if p.guid then params.guid = p.guid end if p.rotation then params.rotation = p.rotation end pcall(function() sourceDeck.takeObject(params) end) end end -- Deal one card per frame so repeated takeObject calls on the same deck don't -- collide within a single frame. Each placement is wrapped in pcall so a single -- failure can never strand the cooldown: we always advance to the next card and -- the base case always releases the cooldown. function dealMissionCards(placements, i) if i > #placements then finishMissionDeal() return end local ok, err = pcall(function() placeMissionCardFromGuid(placements[i]) end) if not ok then print("dealMissionCards placement " .. tostring(i) .. " failed: " .. tostring(err)) end Wait.frames(function() dealMissionCards(placements, i + 1) end, 1) end generateMissionCooldownActive = false generateMissionCooldownSeconds = 0.6 primaryMissionRecallWaitFrames = 20 -- Reduce a matchup's deployment choices to a single randomly-chosen card placed -- in the middle slot. The sparse {[2] = choice} table is read by index in the -- placement loop and by pairs() in selectedDeploymentCardGuidSet. function pickRandomMissionCard(choices) if not choices or #choices == 0 then return choices or {} end return {[2] = choices[math.random(#choices)]} end -- Cards already sitting in one player's primary slots. Returns two lists: -- resolver entries ({guid, nickname}) and the live card objects. Read BEFORE any -- recall so a re-press can keep cards that are still wanted. function placedPrimaryCardEntries(player) local entries, cards, seen = {}, {}, {} for _, slot in ipairs(missionSlots) do if slot.role == "primary" and slot.player == player then for _, obj in ipairs(getObjectsInMissionSlot(slot, nil)) do if obj.tag == "Card" then local g = obj.getGUID() if not seen[g] then seen[g] = true table.insert(entries, {guid = g, nickname = obj.getName()}) table.insert(cards, obj) end end end end end return entries, cards end -- Recall only the placed cards we are NOT keeping this run (e.g. the matchup -- changed). Wanted cards are left in place and repositioned by the deal, which -- avoids the slow deck round-trip that caused the press-twice toggle. function recallUnwantedPrimaryCards(cards, wanted, deck) if not deck then return end for _, card in ipairs(cards) do if not wanted[card.getGUID()] then card.locked = false deck.putObject(card) end end end -- Shared core for both mission buttons. deploymentMode: -- "all" -> place all 3 matchup deployment cards (Generate Mission). -- "random" -> place exactly one randomly-chosen matchup card (Random Mission). -- Primary mission resolution, cleanup, cooldown and all resiliency are identical -- in both modes. -- ===================== Legacy Battlemaster dynamic-maps provider ===================== -- Kept as dormant scaffolding for the old in-TTS dynamic card path. The visible -- map filter now uses static LCT map cards only, so runMissionGeneration no longer -- branches here. BATTLEMASTER_SPAWNER_GUID = "b4d10a" battlemasterArchetypeByDisposition = { ["Disruption"] = "disruption", ["Priority Assets"] = "priority-assets", ["Purge the Foe"] = "purge-the-foe", ["Reconnaissance"] = "reconnaissance", ["Take and Hold"] = "take-and-hold", } function battlemasterArchetypeForDisposition(index) local label = dispositionValues[normalizeDispositionIndex(index)] or "" return battlemasterArchetypeByDisposition[label] end -- Destroy every generated Battlemaster card (deployment + layout slots AND the -- lock target) regardless of where it sits; they have no source deck to return to. function destroyGeneratedBattlemasterCards() local removed = 0 for _, obj in ipairs(getAllObjects()) do if obj.hasTag and obj.hasTag("battlemaster_generated") then obj.destruct() removed = removed + 1 end end return removed end function battlemasterDeploymentSlotPositions() local positions = {} for _, slot in ipairs(missionSlots) do if slot.role == "deployment" and slot.index ~= nil then local p = slot.position positions[slot.index] = {x = p[1], y = p[2], z = p[3]} end end return positions end function runBattlemasterMissionGeneration(deploymentMode) if gameMode ~= "game" or not packSelected then return end if generateMissionCooldownActive then return end local archetypeA = battlemasterArchetypeForDisposition(redDispositionSelected) local archetypeB = battlemasterArchetypeForDisposition(blueDispositionSelected) if archetypeA == nil or archetypeB == nil then broadcastToAll("Battlemaster: could not map dispositions to force archetypes.", "Yellow") return end local spawner = getObjectFromGUID(BATTLEMASTER_SPAWNER_GUID) if spawner == nil then broadcastToAll("Battlemaster spawner object not found.", "Red") return end generateMissionCooldownActive = true missionDealInProgress = true -- Ownership: the spawner only ASSEMBLES (fetch + reconstruct + return card JSON). -- We do NOT mutate the table here. If assembly fails the table is left untouched -- (clean rollback); all clearing/spawning happens in onBattlemasterCardsReady once -- the card JSON is in hand. broadcastToAll("Battlemaster: generating maps for " .. tostring(dispositionValues[normalizeDispositionIndex(redDispositionSelected)]) .. " vs " .. tostring(dispositionValues[normalizeDispositionIndex(blueDispositionSelected)]) .. "...", {0.4, 0.8, 1}) spawner.call("requestMatchupCards", { archetypeA = archetypeA, archetypeB = archetypeB, mode = deploymentMode, slotPositions = battlemasterDeploymentSlotPositions(), rotationY = (deploymentMapCardRotation and deploymentMapCardRotation[2]) or 180, callbackGuid = self.getGUID(), callbackName = "onBattlemasterCardsReady", }) end -- Spawner callback (exactly-once). startMenu owns all table mutation here: on failure -- nothing changed (rollback); on success we clear the old set and spawn the new cards. function onBattlemasterCardsReady(result) if type(result) ~= "table" or not result.ok then finishMissionDeal() local err = type(result) == "table" and result.error or "unknown error" broadcastToAll("Battlemaster generation failed: " .. tostring(err) .. " (table unchanged).", {1, 0.4, 0.4}) return end -- Success: now it is safe to clear the previous set and place the new cards. destroyGeneratedBattlemasterCards() returnCardsInDeploymentSlotsToDeck(true) local placed = 0 for _, cardJson in pairs(result.cards or {}) do spawnObjectJSON({ json = cardJson, callback_function = function(obj) if obj then if obj.addTag then obj.addTag("battlemaster_generated") end if obj.setVar then obj.setVar("battlemasterGenerated", true) end end end, }) placed = placed + 1 end finishMissionDeal() broadcastToAll("Battlemaster: placed " .. tostring(placed) .. " map card(s). Press Load Map on a card to spawn its terrain.", {0.4, 1, 0.7}) end function runMissionGeneration(deploymentMode) if gameMode ~= "game" or not packSelected then return end if generateMissionCooldownActive then return end generateMissionCooldownActive = true missionDealInProgress = true missionsGenerated = true local setupOk, setupErr = pcall(function() local placeholder = Global.getVar("PLACEHOLDER_MISSION") or "Blank Primary" local matchup, key = getCurrentMatchup() local redMission = (matchup and matchup.red) or placeholder local blueMission = (matchup and matchup.blue) or placeholder local redDeck = getObjectFromGUID(Global.getVar("redPrimaryDeck_GUID")) local blueDeck = getObjectFromGUID(Global.getVar("bluePrimaryDeck_GUID")) -- 1. Resolve each player's primary cards, counting cards already in the slots -- (placed entries win over the deck copy), then recall only the cards we are -- not keeping. Cards we keep stay put and get repositioned by the deal. local redEntries, redCards = placedPrimaryCardEntries("Red") local blueEntries, blueCards = placedPrimaryCardEntries("Blue") local redGuids = resolvePrimaryCardGuids(redDeck, redMission, redEntries) local blueGuids = resolvePrimaryCardGuids(blueDeck, blueMission, blueEntries) local wanted = {} for _, g in ipairs(redGuids) do if g.guid then wanted[g.guid] = true end end for _, g in ipairs(blueGuids) do if g.guid then wanted[g.guid] = true end end recallUnwantedPrimaryCards(redCards, wanted, redDeck) recallUnwantedPrimaryCards(blueCards, wanted, blueDeck) local deploymentChoices = chooseDeploymentCardsForMatchup(key, 3) if deploymentMode == "random" then deploymentChoices = pickRandomMissionCard(deploymentChoices) end local layoutChoices = resolveLayoutArtChoices(deploymentChoices) clearDeploymentSlotsForChoices(deploymentChoices, layoutChoices) Wait.frames(function() local ok, err = pcall(function() -- 2. Build the ordered placement list. local primaryPlacements = {} local deploymentPlacements = {} for _, slot in ipairs(missionSlots) do if slot.role == "primary" then local guids = (slot.player == "Red") and redGuids or blueGuids local deck = (slot.player == "Red") and redDeck or blueDeck local info = guids[slot.index] local order = slot.index * 2 if slot.player == "Red" then order = order - 1 end -- No scale set: primary cards inherit the deck's 2.5 display -- scale, which is already the intended preview size, so they are -- not resized at generation (only on game start, to 1.0). table.insert(primaryPlacements, {order = order, deck = deck, guid = info and info.guid or nil, position = slot.displayPosition or slot.position}) elseif slot.role == "deployment" and gameModeUsesMatrixProps() then -- The matrix-free modes skip the matchup deployment maps: -- Combat Patrol fills these slots from its own map bag (see -- dealCombatPatrolMaps), and the per-size singles modes have no -- map bag at all -- otherwise a Take-and-Hold disposition would -- stack a matrix map under the dealt card / on a blank slot. local info = deploymentChoices[slot.index] if info and info.guid then table.insert(deploymentPlacements, {role = "deployment", deck = info.deck, guid = info.guid, position = slot.position, rotation = deploymentMapCardRotation, sourceDeckGuid = info.deckGuid, flip = false, lock = true}) end elseif slot.role == "layout" and gameModeUsesMatrixProps() then -- Matrix-free modes generate missions only -- no layout art from -- deck fb4b5d, so skip placing any layout slot. local info = layoutChoices[slot.index] if info and info.guid then table.insert(deploymentPlacements, {role = "layout", deck = info.deck, guid = info.guid, position = slot.position, sourceDeckGuid = info.deckGuid, flip = true, lock = true}) end end end table.sort(primaryPlacements, function(a, b) return a.order < b.order end) local placements = {} for _, placement in ipairs(primaryPlacements) do placement.order = nil table.insert(placements, placement) end for _, placement in ipairs(deploymentPlacements) do table.insert(placements, placement) end dealMissionCards(placements, 1) end) if not ok then print("Generate Mission failed: " .. tostring(err)) finishMissionDeal() end end, primaryMissionRecallWaitFrames) end) if not setupOk then print("Generate Mission setup failed: " .. tostring(setupErr)) finishMissionDeal() end end function generateMission() runMissionGeneration("all") -- Combat Patrol additionally deals 3 random map cards from its own bag into -- the deployment slots. runMissionGeneration is untouched: in this mode it -- resolves no matchup maps (the deployment slots stay empty for it), so the -- two deals never contend for the same slots. if combatPatrolMode then dealCombatPatrolMaps() end end -- Return every already-dealt CP map card to the bag so a re-press reshuffles -- cleanly instead of stacking. Cards carry combatPatrolMapCardTag while loose. function returnCombatPatrolMapsToBag(bag) for _, obj in ipairs(getAllObjects()) do if obj.hasTag and obj.hasTag(combatPatrolMapCardTag) then pcall(function() obj.setLock(false) bag.putObject(obj) end) end end end combatPatrolDealInProgress = false function dealCombatPatrolMaps() if combatPatrolDealInProgress then return end local bag = getObjectFromGUID(COMBAT_PATROL_BAG_GUID) if not bag or not bag.takeObject then broadcastToAll("Combat Patrol map bag not found.", {1, 0.4, 0.4}) return end combatPatrolDealInProgress = true returnCombatPatrolMapsToBag(bag) -- Let the returns settle so the bag holds the full set again, then shuffle -- and deal 3 cards into the deployment slots. Wait.frames(function() local ok, err = pcall(function() if bag.shuffle then bag.shuffle() end dealCombatPatrolMapCard(bag, combatPatrolMapSlotPositions, {1, 2, 3}, 1) end) if not ok then print("dealCombatPatrolMaps failed: " .. tostring(err)) combatPatrolDealInProgress = false end end, 30) end -- One card per frame (mirrors dealMissionCards) so repeated takeObject calls on -- the bag don't collide within a single frame. function dealCombatPatrolMapCard(bag, positions, order, i) if i > #order then combatPatrolDealInProgress = false return end local pos = positions[order[i]] if pos then pcall(function() bag.takeObject({ position = pos, rotation = combatPatrolMapCardRotation, smooth = true, callback_function = function(card) if not card then return end card.addTag(combatPatrolMapCardTag) card.tooltip = true card.interactable = true Wait.frames(function() if card then card.setLock(true) end end, 1) end, }) end) end Wait.frames(function() dealCombatPatrolMapCard(bag, positions, order, i + 1) end, 1) end function drawDeployZone(zone) local drawDataZone = zone.draw for i, drawData in ipairs(drawDataZone) do if drawData.type == "arrow" then drawArrow(drawData) end if drawData.type == "line" then drawLine(drawData) end if drawData.type == "stepped" then drawSteps(drawData) end if drawData.type == "quarter" then drawQuarter(drawData) end if drawData.type == "diagonal" then drawDiagonal(drawData) end if drawData.type == "rectangle" then drawRectangle(drawData) end if drawData.type == "corner" then drawCorner(drawData) end if drawData.type == "triangle" then drawTriangle(drawData) end if drawData.type == "circle" then drawCircle(drawData, "deployZone") end if drawData.type == "circleInZone" then drawCircleInZone(drawData, "deployZone") end if drawData.type == "cornerRectangle" then drawCornerRectangle(drawData) end if drawData.type == "circleDeployment" then drawCircleDeployment(drawData) end if drawData.type == "cornerPolyline" then drawCornerPolyline(drawData) end end setDeployHeight() end function setDeployHeight() for _, guid in ipairs(deploymentOverlayGUIDs) do local found = getObjectFromGUID(guid) if found then local pos = found.getPosition() found.setPosition({pos[1], defaultDeployHeight + deployOffset, pos[3]}) end end end function drawRectangle(drawData) local mat = getObjectFromGUID(mat_GUID) local linePosL = {x = 0, y = deployLineYPos, z = 0} local lineRotL = {x = 0, y = 90, z = 0} local lineScaleL = {x = 5, y = deployLineHeight, z = 0.02} local linePosS1 = {x = 0, y = deployLineYPos, z = 0} local lineRotS1 = {x = 0, y = 0, z = 0} local lineScaleS1 = {x = 5, y = deployLineHeight, z = 0.02} local linePosS2 = {x = 0, y = deployLineYPos, z = 0} local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "z" or drawData.position == "-z" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end if drawData.wide ~= 0 then mapBase = drawData.wide * 2 end lineScaleL.x = drawData.wide * 2 linePosL.x = drawData.fromCenter lineScaleS1.x = (mapHeight/2)-drawData.fromCenter linePosS1.x = drawData.fromCenter + ((mapHeight/2) - drawData.fromCenter)/2 linePosS1.z = drawData.wide linePosS2.x = linePosS1.x linePosS2.z = -linePosS1.z if drawData.position == "x" then -- default values end if drawData.position == "-x" then linePosL.x = - linePosL.x linePosS2.x = -linePosS2.x linePosS1.x = -linePosS1.x end if drawData.position == "z" then linePosL.z = linePosL.x linePosL.x = 0 lineRotL.y = 0 local tmp = linePosS1.z linePosS1.z = linePosS1.x linePosS1.x = tmp lineRotS1.y = 90 linePosS2.z = linePosS1.z linePosS2.x = -tmp end if drawData.position == "-z" then linePosL.z = -linePosL.x linePosL.x = 0 lineRotL.y = 0 local tmp = linePosS1.z linePosS1.z = -linePosS1.x linePosS1.x = -tmp lineRotS1.y = 90 linePosS2.z = linePosS1.z linePosS2.x = tmp end spawnLine(linePosL, lineRotL, lineScaleL, drawData.color, "deployZone") -- orizz spawnLine(linePosS1, lineRotS1, lineScaleS1, drawData.color, "deployZone") -- vert1 spawnLine(linePosS2, lineRotS1, lineScaleS1, drawData.color, "deployZone") -- vert2 end function drawCorner(drawData) local mat = getObjectFromGUID(mat_GUID) local linePosL = {x = 0, y = deployLineYPos, z = 0} local lineRotL = {x = 0, y = 0, z = 0} local lineScaleL = {x = 5, y = deployLineHeight, z = 0.02} local linePosS1 = {x = 0, y = deployLineYPos, z = 0} local lineRotS1 = {x = 0, y = 90, z = 0} local lineScaleS1 = {x = 5, y = deployLineHeight, z = 0.02} local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "x" or drawData.position == "-x" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end lineScaleL.x = (mapHeight/2) + drawData.wide linePosL.x = (mapHeight/4)-(drawData.wide/2) linePosL.z = drawData.fromCenter lineScaleS1.x = (mapBase/2)-drawData.fromCenter linePosS1.z = drawData.fromCenter + lineScaleS1.x/2 linePosS1.x = -drawData.wide if drawData.position == "z" then -- default values end if drawData.position == "-z" then linePosL.x = - linePosL.x linePosL.z = - linePosL.z linePosS1.z = -linePosS1.z linePosS1.x = -linePosS1.x end if drawData.position == "x" then -- not valid, to do linePosL.z = linePosL.x linePosL.x = 0 lineRotL.y = 0 local tmp = linePosS1.z linePosS1.z = linePosS1.x linePosS1.x = tmp lineRotS1.y = 90 end if drawData.position == "-x" then-- not valid, to do linePosL.z = -linePosL.x linePosL.x = 0 lineRotL.y = 0 local tmp = linePosS1.z linePosS1.z = -linePosS1.x linePosS1.x = -tmp lineRotS1.y = 90 end spawnLine(linePosL, lineRotL, lineScaleL, drawData.color, "deployZone") -- long spawnLine(linePosS1, lineRotS1, lineScaleS1, drawData.color, "deployZone") -- short end function drawDiagonal(drawData) local mat = getObjectFromGUID(mat_GUID) local linePos = {x = 0, y = deployLineYPos, z = 0} local lineRot = {x = 0, y = 0, z = 0} local lineScale = {x = 5, y = deployLineHeight, z = 0.02} local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "x" or drawData.position == "-x" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end local mainDiagonal = 0 local edgeLoss = 0 local triBase = 0 -- triangle with hypotenuse as the needed deploy line local edgeAngle = 0 -- angle of the line from the given map edge local edgeAngleRad = 0 local halfTriBase = 0 -- triangle with hypotenuse that is half the line lenght local halfTriHeight = 0 -- triangle with hypotenuse that is half the line lenght mainDiagonal = math.sqrt(math.pow(mapBase, 2) + math.pow(mapHeight, 2)) edgeAngleRad = math.atan(mapBase / mapHeight) edgeAngle = math.deg(edgeAngleRad) edgeLoss = drawData.fromCenter / math.cos(edgeAngleRad) triBase = mapBase-edgeLoss local ratio = triBase / mapBase lineScale.x = mainDiagonal * ratio lineRot.y = edgeAngle halfTriBase = lineScale.x * math.sin(edgeAngleRad) / 2 halfTriHeight = lineScale.x * math.cos(edgeAngleRad) / 2 linePos.x = (mapHeight/2) - (lineScale.x/2) * math.cos(edgeAngleRad) linePos.z = (mapBase/2) - (lineScale.x/2) * math.sin(edgeAngleRad) if drawData.position == "z" then -- upper right -- default values end if drawData.position == "-z" then linePos.x = - linePos.x linePos.z = - linePos.z end spawnLine(linePos, lineRot, lineScale, drawData.color, "deployZone") end function drawTriangle(drawData) local mat = getObjectFromGUID(mat_GUID) local linePos = {x = 0, y = deployLineYPos, z = 0} local lineRot = {x = 0, y = 0, z = 0} local lineScale = {x = 5, y = deployLineHeight, z = 0.02} local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "z" or drawData.position == "-z" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end local triBase = 0 -- triangle with hypotenuse as the needed deploy line local triHeight = 0 -- triangle with hypotenuse as the needed deploy line local edgeAngle = 0 -- angle of the line from the given map edge local edgeAngleRad = 0 triBase = mapBase triHeight = mapHeight/2 edgeAngleRad = math.atan(triBase/triHeight ) if drawData.position == "z" or drawData.position == "-z" then edgeAngleRad = -edgeAngleRad end edgeAngle = math.deg(edgeAngleRad) lineScale.x = math.sqrt(triBase^2+triHeight^2) lineRot.y = edgeAngle if drawData.position == "x" then linePos.x=mapHeight/4 linePos.z=0 end if drawData.position == "-x" then linePos.x=-mapHeight/4 linePos.z=0 end if drawData.position == "z" then lineRot.y= lineRot.y-90 linePos.x=0 linePos.z=mapHeight/4 end if drawData.position == "-z" then lineRot.y= lineRot.y+90 linePos.x=0 linePos.z=-mapHeight/4 end spawnLine(linePos, lineRot, lineScale, drawData.color, "deployZone") end function drawQuarter(drawData) local mat = getObjectFromGUID(mat_GUID) local linePos = {x = 0, y = deployLineYPos, z = 0} local lineRot = {x = 0, y = 90, z = 0} local lineScale = {x = 5, y = deployLineHeight, z = 0.02} local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "x" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end lineScale.x = (mapBase/2)-drawData.fromCenter linePos.z = (lineScale.x/2) + drawData.fromCenter if drawData.position == "xz" then -- default values end if drawData.position == "x-z" or drawData.position == "-x-z" then linePos.z = -linePos.z end spawnLine(linePos, lineRot, lineScale, drawData.color, "deployZone") -- short line lineScale.x = (mapHeight/2)-drawData.fromCenter linePos.z = 0 linePos.x = (lineScale.x/2) + drawData.fromCenter lineRot.y = 0 if drawData.position == "-xz" or drawData.position == "-x-z" then linePos.x = -linePos.x end spawnLine(linePos, lineRot, lineScale, drawData.color, "deployZone") -- long line end function drawSegment(x1, z1, x2, z2, color, type) local dx = x2 - x1 local dz = z2 - z1 local length = math.sqrt(dx * dx + dz * dz) if length <= 0 then return end -- Yaw is negated vs a plain atan2(dz,dx): TTS's Y rotation is the opposite -- handedness to our (x,z) math, so without this diagonals come out mirrored -- (slope flipped) while axis-aligned segments look fine either way. local yaw if math.abs(dx) < 1e-6 then yaw = dz >= 0 and -90 or 90 else yaw = -math.deg(math.atan(dz / dx)) if dx < 0 then yaw = yaw + 180 end end spawnLine( {x = (x1 + x2) / 2, y = deployLineYPos, z = (z1 + z2) / 2}, {x = 0, y = yaw, z = 0}, {x = length, y = deployLineHeight, z = 0.02}, color, type ) end function drawCornerPolyline(drawData, tag) local mat = getObjectFromGUID(mat_GUID) if not mat or not drawData.points then return end tag = tag or "deployZone" local mapWidth = mat.getScale().z * sizeMulti local mapHeight = mat.getScale().x * sizeMulti local halfWidth = mapWidth / 2 local halfHeight = mapHeight / 2 local pos = drawData.position or "bottomLeft" local function worldPoint(point) local fromLeft = math.min(point[1] or 0, mapWidth) local fromBottom = math.min(point[2] or 0, mapHeight) if pos == "topRight" then return { x = halfHeight - fromBottom, z = -halfWidth + fromLeft, } end return { x = -halfHeight + fromBottom, z = halfWidth - fromLeft, } end for i = 1, #drawData.points - 1 do local a = worldPoint(drawData.points[i]) local b = worldPoint(drawData.points[i + 1]) drawSegment(a.x, a.z, b.x, b.z, drawData.color, tag) end end function drawCircle(drawData, type) local originalCircle=getObjectFromGUID(Global.getVar("centerCircle_GUID")) local circleObj = originalCircle.clone({ position = {0, -5, 0}}) if circleObj then circleObj.setLock(true) circleObj.interactable = false circleObj.setScale({drawData.fromCenter, deployLineHeight, drawData.fromCenter}) circleObj.setPosition({0, deployLineYPos, 0}) circleObj.setColorTint(getDeploymentOverlayColor(drawData.color)) circleObj.setGMNotes(type) circleObj.setName("") local blockComp = circleObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = circleObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end trackOverlayObject(circleObj, type) end end function drawCircleInZone(drawData, type) local mat = getObjectFromGUID(mat_GUID) local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "z" or drawData.position == "-z" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end local posX=0 local posZ=0 if drawData.position == "x" then posX=((mapHeight/2-drawData.deployFromCenter)/2)+drawData.deployFromCenter end if drawData.position == "-x" then posX=-((mapHeight/2-drawData.deployFromCenter)/2)+drawData.deployFromCenter end if drawData.position == "z" then posZ=((mapHeight/2-drawData.deployFromCenter)/2)+drawData.deployFromCenter end if drawData.position == "-z" then posZ=-((mapHeight/2-drawData.deployFromCenter)/2)+drawData.deployFromCenter end drawCircleNotCentered(drawData, type, posX,posZ) end function drawCircleNotCentered(drawData, type, centerX, centerZ) local originalCircle=getObjectFromGUID(Global.getVar("centerCircle_GUID")) local circleObj = originalCircle.clone({ position = {0, -5, 0}}) if circleObj then circleObj.setLock(true) circleObj.interactable = false circleObj.setScale({drawData.fromCenter, deployLineHeight, drawData.fromCenter}) circleObj.setPosition({centerX, deployLineYPos, centerZ}) circleObj.setColorTint(getDeploymentOverlayColor(drawData.color)) circleObj.setGMNotes(type) circleObj.setName("") local blockComp = circleObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = circleObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end trackOverlayObject(circleObj, type) end end function drawCornerRectangle(drawData) local mat = getObjectFromGUID(mat_GUID) local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge -- Calculate rectangle dimensions to extend from center boundary to map edge local rectWidth = 0 local rectHeight = 0 local centerX = 0 local centerZ = 0 if drawData.position == "xz" then -- upper right (Blue) rectWidth = (mapHeight/2) - drawData.fromCenter rectHeight = (mapBase/2) - drawData.fromCenter centerX = drawData.fromCenter + (rectWidth/2) centerZ = drawData.fromCenter + (rectHeight/2) elseif drawData.position == "x-z" then -- lower right (Black) rectWidth = (mapHeight/2) - drawData.fromCenter rectHeight = (mapBase/2) - drawData.fromCenter centerX = drawData.fromCenter + (rectWidth/2) centerZ = -(drawData.fromCenter + (rectHeight/2)) elseif drawData.position == "-xz" then -- upper left (Yellow) rectWidth = (mapHeight/2) - drawData.fromCenter rectHeight = (mapBase/2) - drawData.fromCenter centerX = -(drawData.fromCenter + (rectWidth/2)) centerZ = drawData.fromCenter + (rectHeight/2) elseif drawData.position == "-x-z" then -- lower left (Red) rectWidth = (mapHeight/2) - drawData.fromCenter rectHeight = (mapBase/2) - drawData.fromCenter centerX = -(drawData.fromCenter + (rectWidth/2)) centerZ = -(drawData.fromCenter + (rectHeight/2)) end -- Draw the four sides of the rectangle -- Top edge spawnLine({centerX, deployLineYPos, centerZ + rectHeight/2}, {0, 0, 0}, {rectWidth, deployLineHeight, 0.02}, drawData.color, "deployZone") -- Bottom edge spawnLine({centerX, deployLineYPos, centerZ - rectHeight/2}, {0, 0, 0}, {rectWidth, deployLineHeight, 0.02}, drawData.color, "deployZone") -- Left edge spawnLine({centerX - rectWidth/2, deployLineYPos, centerZ}, {0, 90, 0}, {rectHeight, deployLineHeight, 0.02}, drawData.color, "deployZone") -- Right edge spawnLine({centerX + rectWidth/2, deployLineYPos, centerZ}, {0, 90, 0}, {rectHeight, deployLineHeight, 0.02}, drawData.color, "deployZone") end function drawCircleDeployment(drawData) local mat = getObjectFromGUID(mat_GUID) local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge -- Calculate circle position based on position and fromCenter distance local centerX = 0 local centerZ = 0 if drawData.position == "z" then -- top center centerX = 0 centerZ = drawData.fromCenter elseif drawData.position == "-z" then -- bottom center centerX = 0 centerZ = -drawData.fromCenter elseif drawData.position == "-x" then -- left side centerX = -drawData.fromCenter centerZ = -(drawData.fromCenterZ or 0) elseif drawData.position == "x" then -- right side centerX = drawData.fromCenter centerZ = -(drawData.fromCenterZ or 0) end -- Draw the circle using the existing circle drawing function local originalCircle = getObjectFromGUID(Global.getVar("centerCircle_GUID")) local circleObj = originalCircle.clone({ position = {0, -5, 0}}) if circleObj then circleObj.setLock(true) circleObj.interactable = false circleObj.setScale({drawData.radius, deployLineHeight, drawData.radius}) circleObj.setPosition({centerX, deployLineYPos, centerZ}) circleObj.setColorTint(getDeploymentOverlayColor(drawData.color)) circleObj.setGMNotes("deployZone") circleObj.setName("") local blockComp = circleObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = circleObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end trackOverlayObject(circleObj, "deployZone") end end function drawSteps(drawData) local mat = getObjectFromGUID(mat_GUID) local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "z" or drawData.position == "-z" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end length = mapBase / #drawData.steps zPos = (mapBase / 2) - (length / 2) for index, step in pairs(drawData.steps) do local xPos = 0 if step.fromCenter then xPos = step.fromCenter else xPos = (mapHeight / 2) - step.fromSide end drawLineCommon(xPos, zPos, length, false, drawData.position, drawData.color) zPos = zPos - (length / 2) if index < #drawData.steps then -- Also draw a line connecting this step and the next. nextStepFromCenter = 0 if step.fromCenter then nextStepFromCenter = drawData.steps[index + 1].fromCenter else nextStepFromCenter = (mapHeight / 2) - drawData.steps[index + 1].fromSide end connectorLength = nextStepFromCenter - xPos connectorXPos = xPos + (connectorLength / 2) connectorLength = math.abs(connectorLength) drawLineCommon(connectorXPos, zPos, connectorLength, true, drawData.position, drawData.color) end zPos = zPos - (length / 2) end end function drawLine(drawData) local mat = getObjectFromGUID(mat_GUID) local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "z" or drawData.position == "-z" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end if drawData.fromSide then if drawData.fromSide ~= 0 then drawData.fromCenter = (mapHeight / 2) - drawData.fromSide end end drawLineCommon(drawData.fromCenter, 0, mapBase, false, drawData.position, drawData.color) end function drawLineCommon(xPos, zPos, length, rotated, position, color) local linePos = {x = xPos, y = deployLineYPos, z = zPos} local lineRot = {x = 0, y = 90, z = 0} if rotated then lineRot.y = 0 end local lineScale = {x = length, y = deployLineHeight, z = 0.02} if position == "x" then -- default values end if position == "-x" then linePos.x = -linePos.x end if position == "z" then lineRot.y = 90 - lineRot.y local oldZ = linePos.z linePos.z = linePos.x linePos.x = oldZ end if position == "-z" then lineRot.y = 90 - lineRot.y local oldZ = linePos.z linePos.z = -linePos.x linePos.x = oldZ end spawnLine(linePos, lineRot, lineScale, color, "deployZone") end function drawArrow(drawData) local mat = getObjectFromGUID(mat_GUID) local linePos = {x = 0, y = deployLineYPos, z = 0} local lineRot = {x = 0, y = 0, z = 0} local lineScale = {x = 5, y = deployLineHeight, z = 0.02} local mapBase = mat.getScale().z * sizeMulti --short edge local mapHeight = mat.getScale().x * sizeMulti --long edge if drawData.position == "z" or drawData.position == "-z" then mapBase = mat.getScale().x * sizeMulti mapHeight = mat.getScale().z * sizeMulti end if drawData.wide ~= 0 then mapBase = drawData.wide *2 end local triBase = (mapBase / 2) -- triangle with hypotenuse as the needed deploy line local triHeight = (mapHeight / 2) - drawData.fromCenter - drawData.fromEdge -- triangle with hypotenuse as the needed deploy line local edgeAngle = 0 -- angle of the line from the given map edge local edgeAngleRad = 0 local halfTriBase = 0 -- triangle with hypotenuse that is half the line lenght local halfTriHeight = 0 -- triangle with hypotenuse that is half the line lenght lineScale.x = math.sqrt(math.pow(triBase, 2) + math.pow(triHeight, 2)) edgeAngleRad = math.atan(triBase / triHeight) edgeAngle = math.deg(edgeAngleRad) lineRot.y = edgeAngle halfTriBase = (lineScale.x / 2) * math.sin(edgeAngleRad) halfTriHeight = (lineScale.x / 2 ) * math.cos(edgeAngleRad) linePos.x = triHeight - halfTriHeight + drawData.fromCenter linePos.z = -1 * (triBase - halfTriBase) if drawData.position == "x" then --default values end if drawData.position == "-x" then linePos.z = -linePos.z linePos.x = -linePos.x end if drawData.position == "z" then local tmp = linePos.z linePos.z = linePos.x linePos.x = -tmp lineRot.y = 90 + lineRot.y end if drawData.position == "-z" then local tmp = linePos.z linePos.z = -linePos.x linePos.x = tmp lineRot.y = 90 + lineRot.y end spawnLine(linePos, lineRot, lineScale, drawData.color, "deployZone") if drawData.position == "x" or drawData.position == "-x" then linePos.z = -linePos.z lineRot.y = -lineRot.y end if drawData.position == "z" or drawData.position == "-z" then linePos.x = -linePos.x lineRot.y = -lineRot.y end spawnLine(linePos, lineRot, lineScale, drawData.color, "deployZone") end function getDeploymentOverlayColor(color) if color == "Teal" then return {r = 0.35, g = 0.65, b = 1} end return color end function getOverlayGuidStore(type) if type == "deployZone9" then return nineInchOverlayGUIDs end if type == "territory" then return territoryOverlayGUIDs end return deploymentOverlayGUIDs end function trackOverlayObject(obj, type) if not obj then return end table.insert(getOverlayGuidStore(type), obj.getGUID()) end function destroyTrackedOverlayObjects(store) for _, guid in ipairs(store) do local obj = getObjectFromGUID(guid) if obj then obj.destroy() end end end -- Re-apply the non-interactive overlay state spawnLine sets at spawn time. -- Runtime collider/interactable tweaks don't reliably survive a TTS save, so -- a line restored from an autosave/rewind can come back as a physical snag -- ("models get stuck on it"). Re-hardening on adoption kills that symptom. function hardenOverlayObject(obj) if not obj then return end obj.setLock(true) obj.interactable = false obj.use_gravity = false local blockComp = obj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = obj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end end -- Spawned overlay objects (deploy zones, 9" lines, the territory divider) are -- saved into every autosave/rewind snapshot, but their tracking lists are not -- (see onSave). After a reload the lists are empty while the objects still -- exist, so the toggles can no longer find/destroy them. Re-adopt every overlay -- by its GMNotes tag so the toggles regain control of orphaned objects. function rebuildOverlayTracking() deploymentOverlayGUIDs = {} nineInchOverlayGUIDs = {} territoryOverlayGUIDs = {} for _, obj in ipairs(getAllObjects()) do local note = obj.getGMNotes() if note == "deployZone" then table.insert(deploymentOverlayGUIDs, obj.getGUID()) hardenOverlayObject(obj) elseif note == "deployZone9" then table.insert(nineInchOverlayGUIDs, obj.getGUID()) hardenOverlayObject(obj) elseif note == "territory" then table.insert(territoryOverlayGUIDs, obj.getGUID()) hardenOverlayObject(obj) end end end -- Bring the territory toggle (7e7711) back in line with what's actually on the -- table after a reload: "showing" (2) when a divider exists, "hidden" (1) -- otherwise. Mirrors syncIngameDeploymentButton for the deployment toggle. -- Deferred a frame so the button object is guaranteed live when called from -- Global onLoad (object load order isn't guaranteed). function syncTerritoryLineButton() Global.setVar("interactiveButtonTerritoryLine", #territoryOverlayGUIDs > 0 and 2 or 1) Wait.frames(function() local tlButton = getObjectFromGUID("7e7711") if tlButton then tlButton.call("syncTerritoryLineButtonImage") end end, 1) end function spawnLine(linePos, lineRot, lineScale, color, type) local lineObj = spawnObject({ type = "BlockSquare", position = linePos, rotation = lineRot, scale = {x = lineScale.x, y = lineScale.y * 0.5625, z = lineScale.z}, }) if lineObj then lineObj.setLock(true) lineObj.interactable = false lineObj.use_gravity = false lineObj.setGMNotes(type) lineObj.setColorTint(getDeploymentOverlayColor(color)) local blockComp = lineObj.getComponent("BoxCollider") if blockComp then blockComp.set("enabled", false) end local meshComp = lineObj.getComponent("MeshCollider") if meshComp then meshComp.set("enabled", false) end trackOverlayObject(lineObj, type) end end function destroyDeployZones() destroyTrackedOverlayObjects(deploymentOverlayGUIDs) destroyTrackedOverlayObjects(nineInchOverlayGUIDs) deploymentOverlayGUIDs = {} nineInchOverlayGUIDs = {} end function placeDeploy() destroyDeployZones() local data = DeployZonesData[deploySelected] drawDeployZone(data) writeMenus() syncIngameDeploymentButton() end -- A deployment overlay was just (re)drawn, so keep the in-game toggle (8aba11) -- in sync: "showing" (2) when a real zone is on the table, "hidden" (1) for "No -- Deployment Zone". Without this the toggle keeps its stale value and the -- player's first press is wasted re-syncing instead of actually hiding the zone. function syncIngameDeploymentButton() Global.setVar("interactiveButtonDeploymentZones", selectedDeploymentSpawnsOverlay() and 2 or 1) local dzButton = getObjectFromGUID("8aba11") if dzButton then dzButton.call("syncDeploymentZoneButtonImage") end end function deployUp() deployUpDown(true) end function deployDown() deployUpDown(false) end function deployUpDown(upDown) deployOffset = 0 objectivesOffset = 0 deploymentPregamePlaced = true if DeployZonesData[deploySelected].objectivesID then destroyAllObjectives() end local add=1 if not upDown then add=-1 end deploySelected = deploySelected + add if deploySelected > #DeployZonesData then deploySelected = 1 end if deploySelected < 1 then deploySelected = #DeployZonesData end writeMenus() placeDeploy() end -- The Battlemaster map mat is a Custom_Model whose pivot sits at this world Y, -- well below the terrain (y~1) and distinct from the persistent table surface mat -- (mat_GUID, at ~-9.52). Both the dynamic spawner's battlemat and the imported -- static cards place it here, so position is the universal signal. BM_MAT_PIVOT_Y = -10.13 BM_MAT_PIVOT_TOLERANCE = 0.25 -- Automatic mat randomization is keyed by the baked MAP_INDEX. That lets us -- identify a just-loaded static map while it is still inside a source bag, -- without reading card tags at runtime. function loadedMapCreator(params) local guid = params and params.guid if not guid then return nil end local meta = (Global.getTable("MAP_INDEX") or {})[guid] return type(meta) == "table" and meta.creator or nil end function mapCreatorIsBattlemasterBttfRuins(params) return loadedMapCreator(params) == "battlemaster_bttf_ruins" end function mapCreatorIsBattlemasterBttf(params) return loadedMapCreator(params) == "battlemaster_bttf" end function mapCreatorIsBattlemasterDesert(params) return loadedMapCreator(params) == "battlemaster_armageddon_desert" end -- Find the just-loaded map's own mat(s): the spawner-tagged battlemat, or any -- custom-mesh object parked at the battlemat pivot Y. Excludes the persistent -- table surface mat (mat_GUID) so we never re-skin the board itself. Note: -- getName() returns an object's nickname (empty here), so we identify the mat by -- position + the presence of a custom mesh, not by type name. function findBattlemasterMats() local found = {} for _, obj in ipairs(getAllObjects()) do if obj.getGUID() ~= mat_GUID and math.abs(obj.getPosition().y - BM_MAT_PIVOT_Y) < BM_MAT_PIVOT_TOLERANCE then local ok, custom = pcall(function() return obj.getCustomObject() end) if ok and type(custom) == "table" and type(custom.mesh) == "string" and custom.mesh ~= "" then table.insert(found, obj) end end end return found end -- Per-mat memory of the image a mat shipped with, so "Default" in the Mat -- Randomizer can restore it. Keyed by GUID; populated the first time either the -- auto-randomizer or the picker overrides a given mat (before the override), so -- "original" always means the map's own image, never a previous override. matOriginalDiffuse = {} function captureMatOriginal(mat) local guid = mat.getGUID() if matOriginalDiffuse[guid] ~= nil then return end local ok, custom = pcall(function() return mat.getCustomObject() end) if ok and type(custom) == "table" then matOriginalDiffuse[guid] = custom.diffuse or "" end end -- Swap a mat's diffuse image and reload so the change renders (TTS requires the -- reload for custom models). Same primitive the mat URL tool uses. function applyMatDiffuse(mat, url) local wasLocked = mat.getLock() local wasInteractable = mat.interactable local customInfo = mat.getCustomObject() customInfo.diffuse = url mat.setCustomObject(customInfo) restoreMatFlags(mat.reload(), wasLocked, wasInteractable) end -- After selected Battlemaster static maps load, swap their own mat image for a -- random URL baked from that theme's mat CSV. The mat spawns a few frames after -- Load Map (post-wipe), so we defer past terrain spawn and retry in case a heavy -- theme spawns late. Same diffuse-swap + reload pattern the mat URL tool uses on -- the table surface. -- Temporary diagnostic toggle: prints to the host's chat so we can see whether -- detection/find/apply each fire. Set false once the feature is confirmed. BM_MAT_DEBUG = true function bmMatDebug(msg) if BM_MAT_DEBUG and Global.getVar("DEBUG") == true then printToAll("[BM MAT] " .. msg, {0.4, 0.8, 1}) end end -- Frames to wait before the first look: past the card's board wipe (~frame 4) so -- we never re-skin the OUTGOING map's mat, which is about to be deleted. BM_MAT_FLOOR_FRAMES = 10 -- How often to re-check, and the total frame budget, for a slow-spawning theme. BM_MAT_POLL_FRAMES = 3 BM_MAT_MAX_FRAMES = 90 function makeBattlemasterMatNonInteractive(mat) if not mat then return end mat.setLock(true) mat.interactable = false mat.tooltip = false end function enforceBattlemasterMatsNonInteractive() local budget = BM_MAT_MAX_FRAMES local function poll() local mats = findBattlemasterMats() if #mats > 0 then for _, mat in ipairs(mats) do makeBattlemasterMatNonInteractive(mat) end return end budget = budget - BM_MAT_POLL_FRAMES if budget > 0 then Wait.frames(poll, BM_MAT_POLL_FRAMES) end end Wait.frames(poll, BM_MAT_FLOOR_FRAMES) end function reskinMapMatFromPool(urls, label, enabled) if not enabled then return end if type(urls) ~= "table" or #urls == 0 then bmMatDebug(label .. " empty -- not baked into this build.") return end local url = urls[math.random(#urls)] -- Poll for the freshly spawned mat and apply the instant it appears: fast, and -- early enough that the map's original mat image often hasn't finished loading, -- so the swap reads as smooth rather than a visible re-texture. local budget = BM_MAT_MAX_FRAMES local function poll() local mats = findBattlemasterMats() if #mats > 0 then bmMatDebug("applying " .. url .. " to " .. #mats .. " mat(s)") for _, mat in ipairs(mats) do makeBattlemasterMatNonInteractive(mat) captureMatOriginal(mat) applyMatDiffuse(mat, url) end return end budget = budget - BM_MAT_POLL_FRAMES if budget > 0 then Wait.frames(poll, BM_MAT_POLL_FRAMES) else bmMatDebug("gave up: no mat found within budget.") end end Wait.frames(poll, BM_MAT_FLOOR_FRAMES) end function reskinBattlemasterMatFromPool(urls, label) reskinMapMatFromPool(urls, label, BM_MAT_RANDOMIZER_ENABLED) end function reskinBattlemasterBttfRuinsMat() reskinBattlemasterMatFromPool(BTTF_RUINS_MAT_URLS, "BTTF_RUINS_MAT_URLS") end function reskinBattlemasterDesertMat() reskinBattlemasterMatFromPool(DESERT_MAT_URLS, "DESERT_MAT_URLS") end function reskinLctPack1Mat() reskinMapMatFromPool(LCT_PACK_1_MAT_URLS, "LCT_PACK_1_MAT_URLS", LCT_MAT_RANDOMIZER_ENABLED) end -- ===================== Mat Randomizer manual picker ===================== -- Lets the host pick a specific city mat for the loaded map's mat. Value 1 is -- "Default" (restore the map's original image); 2..N are the named mats from -- all_mats.csv. The control's UI now lives as XML on the Map Creator Filter object -- (mapFilter.ttslua, GUID 369707): its buttons forward here via obj.call, and we -- push the current selection label back into that XML after every cycle. The state -- and the diffuse-swap logic stay here because they share matOriginalDiffuse and -- the baked CITY_MAT_* tables with the auto-reskin-on-load path above. matRandomizerSelected = 1 -- The filter object that hosts the Mat Randomizer XML square; we push the value -- label into its whenever the selection changes. MAT_RANDO_PANEL_GUID = "369707" function matRandomizerOptionCount() return (type(CITY_MAT_NAMES) == "table" and #CITY_MAT_NAMES or 0) + 1 end function matRandomizerLabel() if matRandomizerSelected <= 1 then return "Default" end return CITY_MAT_NAMES[matRandomizerSelected - 1] or "Default" end function matRandomizerCycle(step) local count = matRandomizerOptionCount() matRandomizerSelected = matRandomizerSelected + step if matRandomizerSelected > count then matRandomizerSelected = 1 end if matRandomizerSelected < 1 then matRandomizerSelected = count end pushMatRandoLabel() end -- Mirror the current selection label into the XML square hosted on the filter -- object. Safe to call before that panel exists (no-op until it does). function pushMatRandoLabel() local panel = getObjectFromGUID(MAT_RANDO_PANEL_GUID) if panel then panel.UI.setValue("matRandoValue", matRandomizerLabel()) end end function matRandomizerUp() matRandomizerCycle(1) end function matRandomizerDown() matRandomizerCycle(-1) end -- One-click "Default": reset the selection to 1 (the map's own image), repaint the -- picker label, and apply immediately. Driven by the Default button in the XML square. function matRandomizerApplyDefault() matRandomizerSelected = 1 pushMatRandoLabel() matRandomizerApply() end -- One-click "Random": pick a random named city mat (index 2..N, skipping Default), -- repaint the picker label so it shows which one landed, and apply. Falls back to -- Default if nothing is baked. Driven by the Random button in the XML square. function matRandomizerRandom() local count = matRandomizerOptionCount() if count <= 1 then matRandomizerSelected = 1 else matRandomizerSelected = math.random(2, count) end pushMatRandoLabel() matRandomizerApply() end -- Apply targets: the loaded map's mat(s); if none, the table surface mat so the -- picker still does something sensible without a map loaded. function matRandomizerTargets() local mats = findBattlemasterMats() if #mats == 0 then local surface = getObjectFromGUID(mat_GUID) if surface then mats = {surface} end end return mats end function matRandomizerApply() local mats = matRandomizerTargets() if #mats == 0 then broadcastToAll("Mat Randomizer: no mat to change.", "Yellow") return end local isDefault = matRandomizerSelected <= 1 local url = (not isDefault) and CITY_MAT_URLS[matRandomizerSelected - 1] or nil for _, mat in ipairs(mats) do if isDefault then local orig = matOriginalDiffuse[mat.getGUID()] if orig ~= nil then applyMatDiffuse(mat, orig) end else captureMatOriginal(mat) applyMatDiffuse(mat, url) end end broadcastToAll("Mat Randomizer: applied " .. matRandomizerLabel(), {0.4, 0.8, 1}) end -- Called (via obj.call) by a map card the instant its "Load Map" button is -- pressed -- BEFORE the card wipes itself. params = {name=, guid=}. We announce -- the map and auto-select the deployment zone encoded before the optional creator -- credit, e.g. "... - Tipping Point - Team Belgium". function onMapCardLoaded(params) local name = (params and params.name) or "?" -- Provider tag = a transient card from the dynamic spawner (no paired static -- art). This drives the branch below. It is NOT the same as "Battlemaster -- creator": the imported static Battlemaster deck cards carry no provider but -- DO have paired layout art, so they must take the else branch. local isBattlemaster = type(params) == "table" and params.provider == "battlemaster" -- Snapshot the pre-load selection state BEFORE anything mutates or the card -- wipes the board, so "Back to Selection" can return here. captureRestorePoint() -- DEBUG tag tool: remember which map is loaded so backToSelectionDebug can -- key its recorded obj_* tags by this card's GUID. debugCurrentMapGuid = params and params.guid or nil debugCurrentMapName = name if isBattlemaster then -- Battlemaster cards are transient and have no paired static layout art. -- Loading one keeps just that map: drop the other generated choice cards. -- (Skip placeLoadedLayoutArtCard, which would warn about missing art.) local loadedGuid = params and params.guid or nil for _, obj in ipairs(getAllObjects()) do if obj.hasTag and obj.hasTag("battlemaster_generated") and obj.getGUID() ~= loadedGuid then obj.destruct() end end else -- Move the paired layout art to the mission board before the map wipe. The -- target sits outside the wipe area, so it survives without MapExclude. placeLoadedLayoutArtCard(name) end -- Re-skin selected static maps from creator-specific mat pools. local loadedCreator = loadedMapCreator(params) local lctMatRandomizerActive = loadedCreator == "lct1" and LCT_MAT_RANDOMIZER_ENABLED if BM_MAT_RANDOMIZER_ENABLED or lctMatRandomizerActive then bmMatDebug("onMapCardLoaded guid=" .. tostring(params and params.guid) .. " provider=" .. tostring(params and params.provider) .. " creator=" .. tostring(loadedCreator)) end if isBattlemaster then enforceBattlemasterMatsNonInteractive() end if lctMatRandomizerActive then reskinLctPack1Mat() elseif BM_MAT_RANDOMIZER_ENABLED then if loadedCreator == "battlemaster_bttf_ruins" or loadedCreator == "battlemaster_bttf" then reskinBattlemasterBttfRuinsMat() elseif loadedCreator == "battlemaster_armageddon_desert" then reskinBattlemasterDesertMat() end end -- Move primary missions off the mat before the map card starts its next-frame wipe. movePrimaryMissionCardsToIngameSlots(false) broadcastToAll("Loading map: " .. name, {0.3, 1.0, 0.3}) -- banner + chat log -- Keep the standalone CLEAR MAT button available throughout setup. writeMenus() -- The v2 wipe deliberately keeps the loaded card alive so its 0.5s terrain -- spawn can run; once that's done the card has no further purpose and just -- clutters the mat. Remove it after the terrain settles. It was snapshotted -- above, so "Back to Selection" still brings it back. Done before the -- zone-suffix early-return below so it applies to every card uniformly. if params and params.guid then local cardGuid = params.guid loadedMapCardDestroyTimerId = Wait.time(function() loadedMapCardDestroyTimerId = nil local card = getObjectFromGUID(cardGuid) if card then card.destruct() end end, loadedMapCardDestroyDelay) end local zoneName = mapDeploymentZoneName(name) if not zoneName or zoneName == "" then return end -- The card's wipe spawns ~1 frame after the press and the terrain at 0.5s; -- drawing the deploy lines now would just get wiped with everything else. -- Defer past both so the zone survives. This is a single one-shot timer. Wait.time(function() selectDeployZoneByName(zoneName) end, 0.7) end -- Comfortably past the card's 0.5s terrain spawn; destroying it earlier would -- kill that pending timer and the terrain would never appear. loadedMapCardDestroyDelay = 1.0 -- Id of the pending auto-destruct so backToSelection can cancel it (fast undo). loadedMapCardDestroyTimerId = nil -- Select+draw the deployment zone whose name matches, mirroring deployUpDown. function selectDeployZoneByName(zoneName) for i, zone in ipairs(DeployZonesData) do if zone.name == zoneName then deployOffset = 0 objectivesOffset = 0 deploymentPregamePlaced = true if DeployZonesData[deploySelected] and DeployZonesData[deploySelected].objectivesID then destroyAllObjectives() end deploySelected = i writeMenus() placeDeploy() return true end end broadcastToAll('Map "' .. zoneName .. '" has no matching deployment zone in the current size mode.', "Yellow") return false end -- END deploy zones manager -- ****************** -- OBJECTIVES MANAGER -- ****************** -- 9" secondary deployment lines toggle button (teal 59decf) local nineInchColor = {0x59/255, 0xDE/255, 0xCF/255} nineFromDeployBtn = { index = 1, label = '9" from DZ', tooltip = 'Toggle 9" lines offset from current deployment lines', click_function = "toggleNineFromDeployment", function_owner = self, position = { -menuX, 5, menuZ + (secondRowOffset * 3) }, rotation = {0, 0, 0}, height = 450, width = 1600, font_size = 200, color = nineInchColor, font_color = {0, 0, 0} } defaultObjectivesHeight=1.0 -- PROTOTYPE: place objective markers on top of any object carrying an objective tag. -- Each tag maps to a marker template GUID and the state that template should be set to. -- e6b8fb: state 1 = home objective (both sides; only the rotation differs) -- 3c9b57: state 1 = central / triangle objective -- 7fd7df: state 1 = neutral (expansion) objective objectiveSpawnY = 1.03 -- fallback height (table surface) used only if the downward raycast hits nothing objectiveMinSurfaceY = 1.04 -- hard floor for the detected surface; markers never seat below this Y even if the raycast finds something lower (e.g. under a center ruin on some Izar maps) objectiveTopOffset = 0.1 -- spawn lift above the detected surface (before the final seat below) objectiveSeatClearance = 0.0 -- gap left under a seated marker; 0 = touching, bump up to avoid z-fighting objectiveDebug = false -- build-wide debug flag (true on --test builds); set by initGlobalRefs once Global is ready -- rotY (optional) overrides only the Y rotation of the placed marker; the -- model's existing X/Z tilt is preserved. Omit it to keep the template default. -- Tag aliases (new naming convention; old names kept for backwards compatibility): -- obj_center == obj_central (single central marker) -- obj_center1 == obj_center2 == obj_triangle (triangle-group; 2 of them collapse to one) objectiveMarkerByTag = { obj_central = {guid = "3c9b57", state = 1}, obj_center = {guid = "3c9b57", state = 1}, obj_triangle = {guid = "3c9b57", state = 1}, obj_center1 = {guid = "3c9b57", state = 1}, obj_center2 = {guid = "3c9b57", state = 1}, obj_neutral = {guid = "7fd7df", state = 1}, obj_home_blue = {guid = "e6b8fb", state = 1, rotY = 270}, obj_home_red = {guid = "e6b8fb", state = 1, rotY = 270}, } -- Triangle-group tags: handled together for the 2 -> 1 central-marker collapse below. objectiveTriangleTags = { obj_triangle = true, obj_center1 = true, obj_center2 = true, } -- Resolution priority when an object carries more than one obj_ tag: the first tag -- in this list wins and the rest are ignored. Highest priority first: -- obj_triangle / obj_center1 / obj_center2 (tied) -- > obj_central / obj_center (tied) -- > obj_neutral -- > obj_home_red / obj_home_blue (tied) objectiveTagOrder = { "obj_triangle", "obj_center1", "obj_center2", "obj_central", "obj_center", "obj_neutral", "obj_home_red", "obj_home_blue", } function placeObjectives() destroyAllObjectives() -- Resolve each candidate object to its first-matching objective tag. local triangleTargets = {} local placements = {} -- {target = obj, tag = "..."} for _, target in ipairs(getAllObjects()) do if target.getGMNotes() ~= "objective" then for _, tag in ipairs(objectiveTagOrder) do if target.hasTag(tag) then if objectiveTriangleTags[tag] then table.insert(triangleTargets, target) else table.insert(placements, {target = target, tag = tag}) end break -- highest-priority tag wins; one marker per object end end end end local placed = 0 -- Edge case: exactly two obj_triangle objects collapse to a single central -- marker placed at the midpoint between where the two markers would have gone. -- Any other count (0, 1, 3+) falls back to one marker per object. if #triangleTargets == 2 then local p1 = objectiveMarkerPosition(triangleTargets[1]) local p2 = objectiveMarkerPosition(triangleTargets[2]) local mx, mz = (p1.x + p2.x) / 2, (p1.z + p2.z) / 2 local mid = {x = mx, y = objectiveSurfaceY(mx, mz), z = mz} local m = objectiveMarkerByTag.obj_triangle if objectiveDebug then print(string.format("[objectives] 2x obj_triangle -> single central marker at midpoint (%.2f, %.2f, %.2f)", mid.x, mid.y, mid.z)) end if spawnObjectiveMarkerAt(m.guid, m.state, mid, m.rotY) then placed = placed + 1 end else for _, target in ipairs(triangleTargets) do table.insert(placements, {target = target, tag = "obj_triangle"}) end end for _, pl in ipairs(placements) do local marker = objectiveMarkerByTag[pl.tag] if spawnObjectiveMarkerAt(marker.guid, marker.state, objectiveMarkerPosition(pl.target), marker.rotY) then placed = placed + 1 end end broadcastToAll(placed .. " objective(s) placed", {0.25, 1, 0.25}) end -- Casts a ray straight down through (x, z) and returns the height just above the -- highest surface there (terrain, mat, etc.), so markers sit on top of terrain of -- any height instead of a fixed Y that buries them under taller pieces. Other -- objective markers are ignored so a marker never stacks on one already placed. -- Falls back to the table height if the ray hits nothing. function objectiveSurfaceY(x, z) local hits = Physics.cast({ origin = {x = x, y = 50, z = z}, direction = {x = 0, y = -1, z = 0}, type = 1, max_distance = 100, }) local surfaceY = nil for _, hit in ipairs(hits) do local obj = hit.hit_object if not (obj and obj.getGMNotes() == "objective") then if surfaceY == nil or hit.point.y > surfaceY then surfaceY = hit.point.y end end end local surface = surfaceY or objectiveSpawnY if surface < objectiveMinSurfaceY then surface = objectiveMinSurfaceY end return surface + objectiveTopOffset end -- Position a marker should sit at for a given target: horizontal center of the -- target's bounds, lifted onto whatever surface is actually there at that point. function objectiveMarkerPosition(target) local b = target.getBounds() return { x = b.center.x, y = objectiveSurfaceY(b.center.x, b.center.z), z = b.center.z, } end -- Clones the template at pos, sets it to the requested state, then locks/tags -- the resulting object. setState swaps in a different object (with its own -- GMNotes and lock), so the final transform/lock/tag happen AFTER it settles. function spawnObjectiveMarkerAt(templateGUID, state, pos, rotY) local template = getObjectFromGUID(templateGUID) if not template then broadcastToAll("Objective template (" .. templateGUID .. ") not found.", {1, 0.25, 0.25}) return false end local copy = template.clone({position = pos}) if not copy then broadcastToAll("Failed to clone objective template (" .. templateGUID .. ").", {1, 0.25, 0.25}) return false end -- Lock the clone right away. The template is unlocked, so an unlocked clone -- would fall through the table during the frame waits below; worse, setState -- destroys-and-respawns the object, and if it's mid-fall the new object can be -- lost before we reposition it (this is why state-changed markers went missing). copy.setLock(true) Wait.frames(function() local marker = copy if state and marker.getStateId() ~= state then -- setState returns a NEW object in the target state (with its own lock -- and GMNotes); from here on operate only on that returned reference. marker = marker.setState(state) end Wait.frames(function() if marker == nil then broadcastToAll("Objective marker (template " .. templateGUID .. ") was lost during setState.", {1, 0.25, 0.25}) return end -- Instant setPosition (not smooth): a smooth glide is async and setLock -- would freeze the token mid-flight. This guarantees it lands at pos. if rotY then local r = marker.getRotation() marker.setRotation({r.x, rotY, r.z}) end marker.setPosition(pos) -- Seat it on the surface instead of floating: measure the marker's world -- bounds and drop it by the gap between its underside and the detected -- surface height. Deterministic -- no physics settling, no unlock/relock. local b = marker.getBounds() if b then local surfaceY = pos.y - objectiveTopOffset local underside = b.center.y - b.size.y / 2 local seatY = pos.y + (surfaceY + objectiveSeatClearance - underside) marker.setPosition({x = pos.x, y = seatY, z = pos.z}) end marker.setLock(true) marker.setGMNotes("objective") -- Pass-through but still mouse-pickable. The collider does double duty in -- TTS: physics blocking AND cursor raycasts (hover tooltip / right-click -- state menu). Disabling it kills both, so instead make it a *trigger* -- -- triggers don't block other objects yet are still hit by the mouse ray. marker.use_gravity = false local boxCol = marker.getComponent("BoxCollider") if boxCol then boxCol.set("isTrigger", true) end local meshCol = marker.getComponent("MeshCollider") if meshCol then meshCol.set("convex", true) -- Unity only allows convex mesh colliders to be triggers meshCol.set("isTrigger", true) end if objectiveDebug then local p = marker.getPosition() print(string.format("[objectives] placed %s (template %s state %s) final pos (%.2f, %.2f, %.2f)", marker.getGUID(), templateGUID, tostring(state), p.x, p.y, p.z)) end end, 2) end, 2) return true end function destroyAllObjectives() for i, obj in ipairs(getAllObjects()) do if obj.getGMNotes() == "objective" then obj.destroy() end end end -- END objectives manager -- *************** -- MISSION MANAGER -- *************** -- Why is there a mission manager here, but there's also a file "missionManager.ttslua"? They do indeed -- have similar roles; the code here handles the parts that are single-instance, while the other file -- handles the parts that are per-player. So, for example, when "Play Leviathan" is clicked, the code -- here fetches the deployment/primary/mission cards, while the per-player mission managers fetch the -- secondary cards. packSelected = nil missionPackData = nil selectPariahNexusBtn = { label = "Play\nPariah Nexus", click_function = "selectPariahNexus", function_owner = self, position = {2, 5, -16.5}, rotation = {0, 0, 0}, height = 700, width = 1500, font_size = 200, color = {1, 1, 1}, font_color = {0, 0, 0}, } function selectPariahNexus() selectMissionPack(MISSION_PACK_PARIAH_NEXUS) end function selectMissionPack(packIndex) packSelected = packIndex local redSecondaryDeckGuid = Global.getVar("edition11RedSecondaryDeck_GUID") local blueSecondaryDeckGuid = Global.getVar("edition11BlueSecondaryDeck_GUID") local dealFixedSecondaries = true local redFixedChoiceCardGuids = {"e9363d", "acca01", "8c62d6", "7fe9e4"} local blueFixedChoiceCardGuids = {"f68853", "7d0dc0", "27bca6", "774b53"} local redTacticalCardGuidToKeep = "838a9b" local blueTacticalCardGuidToKeep = "a847c1" Global.call("setMissionPack", { pack = packSelected, redSecondaryDeck_GUID = redSecondaryDeckGuid, blueSecondaryDeck_GUID = blueSecondaryDeckGuid, }) Global.call("useEdition11SecondaryDecks") redMissionManager = getObjectFromGUID(Global.getVar("redMissionManager_GUID")) redMissionManager.call("packSelected", {pack = packSelected, dealFixedSecondaries = dealFixedSecondaries, fixedChoiceCardGuids = redFixedChoiceCardGuids, tacticalCardGuidToKeep = redTacticalCardGuidToKeep}) blueMissionManager = getObjectFromGUID(Global.getVar("blueMissionManager_GUID")) blueMissionManager.call("packSelected", {pack = packSelected, dealFixedSecondaries = dealFixedSecondaries, fixedChoiceCardGuids = blueFixedChoiceCardGuids, tacticalCardGuidToKeep = blueTacticalCardGuidToKeep}) missionPackData = Global.getTable("missionPackData")[packSelected] primaryDeck = getObjectFromGUID(missionPackData.primaryDeck_GUID) destroyLegacyPrimaryDeck() -- Combat Patrol runs on a blank mat -- skip the matrix props/logos here too, -- so selecting a pack in that mode can't re-place them. if gameModeUsesMatrixProps() then placeGameModeObjects() end writeMenus() end -- END MISSION MANAGER -- ************ -- GAME MANAGER -- ************ simulation = false -- is used to test in single player redFirstLbl = "R e d p l a y e r f i r s t\n(click to toggle)" blueFirstLbl = "B l u e p l a y e r f i r s t\n(click to toggle)" inGame = false gameTurnCounter = nil armyMover = "" first = "Red" currentTurn = "Red" cpEveryTurn = true secondaryForceStartPressesRemaining = 3 phases = {"Command Phase", "Movement Phase", "Shooting Phase", "Charge Phase", "Fight Phase"} currentPhase = 1 startBtn = { index = 1, label = "S T A R T G A M E", click_function = "startGame", function_owner = self, position = {0, 5, 2.5}, rotation = {0, 0, 0}, height = 750, width = 5000, font_size = 500, color = {0, 0.6, 0}, font_color = {1, 1, 1} } startBtnDisabled = { index = 1, label = "S T A R T G A M E", click_function = "startGameDisabled", function_owner = self, position = {0, 5, 2.5}, rotation = {0, 0, 0}, height = 750, width = 5000, font_size = 500, color = {0.55, 0.55, 0.55}, font_color = {0.85, 0.85, 0.85} } firstPlayerBtn = { index = 1, label = redFirstLbl, click_function = "togglePlyr", function_owner = self, position = {0, 5, 4.5}, rotation = {0, 0, 0}, height = 750, width = 5000, font_size = 300, color = {1, 0, 0}, font_color = {1, 1, 1} } nextPhaseLbl = "Pass\nTurn" nextPhaseBtn = { index = 1, label = nextPhaseLbl, tooltip = "Left click - Next phase\nRight click - Skip to next turn", click_function = "nextPhase", function_owner = self, position = {0, 5, 3.5}, rotation = {0, 0, 0}, height = 1500, width = 5000, font_size = 600, color = {0.90, 0.30, 0.30}, font_color = {1, 1, 1} } position = {menuX, 5, menuZ} notePadTxt = "" MAP_FILTER_GUID = "369707" function shouldShowMapSetupOverlay() return gameMode == "game" and not inGame and missionSetupControlsVisible == true end function syncMapSetupOverlay() local mapFilter = getObjectFromGUID(MAP_FILTER_GUID) if not mapFilter then return end if shouldShowMapSetupOverlay() then mapFilter.call("showFilterMenu") else mapFilter.call("hideFilterMenu") end end function writeMenus() self.clearButtons() if not sizeConfirmed then writeSizeMenu() writeDeployMenu() else if inGame == false then writeStartMenu() writeDeployMenu() else writeIngameMenu() end end writeClearMatButton() writeBackToSelectionButton() writeBackToSelectionDebugButton() syncMapSetupOverlay() end menuButtonBorderThickness = 70 -- Creates a button with a border by drawing a slightly larger backing button behind it -- (TTS buttons have no outline property). borderColor defaults to dark grey. function createButtonWithBorder(params, borderColor) local border = {} for k, v in pairs(params) do border[k] = v end border.label = "" border.font_size = 1 border.height = (params.height or 0) + menuButtonBorderThickness border.width = (params.width or 0) + menuButtonBorderThickness local resolvedBorderColor = borderColor or {0.1, 0.1, 0.1, 1} border.color = resolvedBorderColor border.hover_color = resolvedBorderColor border.press_color = resolvedBorderColor self.createButton(border) -- Raise the real button slightly in y so it renders on top of the border. local top = {} for k, v in pairs(params) do top[k] = v end top.position = {params.position[1], params.position[2] + 0.01, params.position[3]} self.createButton(top) end function writeStartMenu() if gameMode ~= "game" then return end createButtonWithBorder(withSetupXOffset(startBtn)) createButtonWithBorder(withSetupXOffset(firstPlayerBtn)) end function writeDeployPickerMenu() if not deploySelected or not DeployZonesData[deploySelected] then deploySelected = #DeployZonesData end deployBtn.label = DeployZonesData[deploySelected].name createButtonWithBorder(withSetupXOffset(deployBtn)) createButtonWithBorder(withSetupXOffset(deployUpBtn)) createButtonWithBorder(withSetupXOffset(deployDownBtn)) end function writeDeployMenu() writeDeployPickerMenu() -- Table-texture swatches moved to the Table Theme row of the Map Mat Picker -- overlay (mapFilter.ttslua, GUID 369707); no physical buttons here anymore. writeDispositionMenu() -- self.createButton(nineFromDeployBtn) if gameMode == "game" then if not packSelected then selectPariahNexus() end end end function writeIngameMenu() destroyNineInchDeployLines() nextPhaseBtn.color = currentTurn == "Blue" and {0.30, 0.58, 0.90} or {0.90, 0.30, 0.30} nextPhaseBtn.font_color = {1, 1, 1} nextPhaseBtn.label = phases[currentPhase].."\nNext Phase" if currentPhase == #phases then nextPhaseBtn.label = phases[currentPhase].."\nPass Turn" end -- In Onslaught the phase button rides the same top-row offset as the Start -- button it replaces, so it appears in the relocated top area too. createButtonWithBorder(withSetupXOffset(nextPhaseBtn)) -- Disposition pickers are setup-only: once the game starts they are removed -- entirely (not relocated). The chosen dispositions live on in the scoring -- overlay via pushScoringContext, so no on-table label is needed here. end function getMissionManagerForColor(color) local guidVar = (color == "Red") and "redMissionManager_GUID" or "blueMissionManager_GUID" return getObjectFromGUID(Global.getVar(guidVar)) end function selectedPlayersForSecondarySetup() local colors = {} if Player.Red and Player.Red.seated then table.insert(colors, "Red") end if Player.Blue and Player.Blue.seated then table.insert(colors, "Blue") end return colors end function prepareSecondariesForStart(forceStart) local colors = selectedPlayersForSecondarySetup() local summaries = {} if not forceStart then for _, color in ipairs(colors) do local manager = getMissionManagerForColor(color) if manager and not manager.call("validateSecondaries") then return false end end end Global.setVar("LOCKED_IN_SECONDARIES", 0) for _, color in ipairs(colors) do local manager = getMissionManagerForColor(color) if manager then local summaryJson = manager.call("lockInSecondaries", {force = forceStart == true}) if not summaryJson then return false end local summary = JSON.decode(summaryJson) table.insert(summaries, summary) end end return summaries end function resetSecondaryForceStartCountdown() secondaryForceStartPressesRemaining = 3 end function secondaryForceStartMessage() if secondaryForceStartPressesRemaining == 1 then return "Click Start Button 1 more time to Force Start the game." end return "Click Start Button " .. secondaryForceStartPressesRemaining .. " more times to Force Start the game." end function formatSecondarySummary(summary) local cardText = "" if summary.cards and #summary.cards > 0 then cardText = " (" .. table.concat(summary.cards, ", ") .. ")" end return summary.color .. " chose " .. summary.mode .. " Secondary Objectives" .. cardText .. "." end function announceSecondarySelections(summaries) for _, summary in ipairs(summaries) do local message = formatSecondarySummary(summary) broadcastToAll(message, summary.color) end end function startGame(obj, player, alt) if alt then simulation = true end if singlesMode ~= "singles" then -- Count the number of players (if needed later) local playerList = Player.getPlayers() local numberOfPlayers = 0 for i, p in ipairs(playerList) do numberOfPlayers = numberOfPlayers + 1 end if not Player["Red"].seated or not Player["Blue"].seated then broadcastToAll("Must have both players seated", {1, 0, 0}) if not simulation then return end end end local selectedDeployZone = DeployZonesData and DeployZonesData[deploySelected] if not selectedDeployZone or selectedDeployZone.name == "No Deployment Zone" then broadcastToAll("Cannot start: select a deployment zone first.", {1, 0, 0}) if not simulation then return end end local forceSecondaryStart = secondaryForceStartPressesRemaining <= 0 local secondarySummaries = prepareSecondariesForStart(forceSecondaryStart) if not secondarySummaries then broadcastToAll("Cannot start: one or more players need to select Tactical or exactly two Fixed secondaries.", {1, 0.75, 0}) broadcastToAll(secondaryForceStartMessage(), {1, 0.75, 0}) secondaryForceStartPressesRemaining = secondaryForceStartPressesRemaining - 1 return end resetSecondaryForceStartCountdown() returnCardsInDeploymentSlotsToDeck(false) -- Transient Battlemaster cards have no source deck and may be locked in, so the -- return path above skips them; destroy them explicitly (scans the whole table, -- including the lock target) so none survive into the game. destroyGeneratedBattlemasterCards() movePrimaryMissionCardsToIngameSlots() pushScoringContext() Global.call("recordPlayers") inGame = true hideDeploymentZonesForGameStart() showObjectivesForGameStart() -- The map-creator filter only applies during setup; drop its panel now. local mapFilter = getObjectFromGUID(MAP_FILTER_GUID) if mapFilter then mapFilter.call("hideFilterMenu") end local scoresheet = getObjectFromGUID(Global.getVar("scoresheet_GUID")) scoresheet.call("startGame") startCustomTurns() broadcastToAll(phases[currentPhase], "Yellow") announceSecondarySelections(secondarySummaries) resetActivationTokens() Notes.setNotes("") writeMenus() updateGameStateHUD() simulation = false end function startGameDisabled() broadcastToAll("Choose game mode first", {1, 0.75, 0}) end -- Flip every Activation token back to "not acted". Called automatically at game -- start and on each phase change; the manual button lives on spawnGameTools. function resetActivationTokens() for _, obj in ipairs(getAllObjects()) do if obj.getVar("BCBtype") == "ActivationToken" then obj.call("resetAlredyActed") end end end function newRoundStarted() updateGameStateHUD() end local function counterValue(counter) if not counter then return 0 end local ok, value = pcall(function() return counter.call("getValue") end) if ok and value ~= nil then return tonumber(value) or 0 end if counter.Counter then return counter.Counter.getValue() end return 0 end local function counterIncrement(counter) if not counter then return 0 end local ok, result = pcall(function() return counter.call("increment") end) if ok and result ~= nil then return tonumber(result) or counterValue(counter) end if counter.Counter then counter.Counter.increment() return counter.Counter.getValue() end return 0 end function updateCpHUD() Global.call("setHudAttribute", {id = "CPWatch1", attr = "text", value = "CP: " .. counterValue(blueCpCounter)}) Global.call("setHudAttribute", {id = "CPWatch2", attr = "text", value = "CP: " .. counterValue(redCpCounter)}) end -- "Round R, Turn T" stamp shared by the turn-pass and CP-change chat -- messages. Uses the round counter (ee92cf) and the active player's turn counter. function getTurnStateLabel() local roundObj = getObjectFromGUID("ee92cf") local round = counterValue(roundObj) local turnCounter = (currentTurn == "Red") and redTurnCounter or blueTurnCounter local turnNum = counterValue(turnCounter) return "Round " .. round .. ", " .. currentTurn .. " Turn " .. turnNum end function gameNotStartedGuard() if inGame then return false end broadcastToAll("Game hasn't started!", {1, 0.5, 0}) return true end local function getHudPlayerColor(player) if type(player) == "string" then return player end if type(player) == "table" and player.color then return player.color end return nil end function hudPassTurn(player) if gameNotStartedGuard() then return end local playerColor = getHudPlayerColor(player) passTurn(self, playerColor or currentTurn, false) end local function hudCanControlTurn(player, targetTurn) if gameNotStartedGuard() then return end local playerColor = getHudPlayerColor(player) if singlesMode ~= "singles" and playerColor and playerColor ~= targetTurn then return false end if currentTurn ~= targetTurn then return end return true end local function hudAdvanceIfActive(player, targetTurn) if not hudCanControlTurn(player, targetTurn) then return end nextPhase(self, currentTurn, false) end function hudTurnWatch1(player) hudAdvanceIfActive(player, "Blue") end function hudTurnWatch2(player) hudAdvanceIfActive(player, "Red") end local _phaseIds = { "Ph1", "Ph2", "Ph3", "Ph4", "Ph5" } local _phaseToId = { [1]="Ph1", [2]="Ph2", [3]="Ph3", [4]="Ph4", [5]="Ph5" } function updatePhaseHUD() for _, id in ipairs(_phaseIds) do Global.call("setHudAttribute", {id = id.."1", attr = "textColor", value = "#FFFFFF30"}) Global.call("setHudAttribute", {id = id.."2", attr = "textColor", value = "#FFFFFF30"}) end local active = _phaseToId[currentPhase] if active then local activeRow = (currentTurn == "Blue") and "1" or "2" Global.call("setHudAttribute", {id = active..activeRow, attr = "textColor", value = "White"}) end end local function hudJumpToPhaseForTurn(player, targetTurn, idx) if not hudCanControlTurn(player, targetTurn) then return end jumpToPhase(idx) end function hudPhaseCmdBlue(player) hudJumpToPhaseForTurn(player, "Blue", 1) end function hudPhaseMovBlue(player) hudJumpToPhaseForTurn(player, "Blue", 2) end function hudPhaseShtBlue(player) hudJumpToPhaseForTurn(player, "Blue", 3) end function hudPhaseChrBlue(player) hudJumpToPhaseForTurn(player, "Blue", 4) end function hudPhaseFgtBlue(player) hudJumpToPhaseForTurn(player, "Blue", 5) end function hudPhaseCmdRed(player) hudJumpToPhaseForTurn(player, "Red", 1) end function hudPhaseMovRed(player) hudJumpToPhaseForTurn(player, "Red", 2) end function hudPhaseShtRed(player) hudJumpToPhaseForTurn(player, "Red", 3) end function hudPhaseChrRed(player) hudJumpToPhaseForTurn(player, "Red", 4) end function hudPhaseFgtRed(player) hudJumpToPhaseForTurn(player, "Red", 5) end function jumpToPhase(idx) if gameNotStartedGuard() then return end currentPhase = idx broadcastToAll(phases[currentPhase], "Yellow") writeMenus() updateGameStateHUD() end function updateGameStateHUD() local roundObj = getObjectFromGUID("ee92cf") local round = counterValue(roundObj) Global.call("setHudValue", {id = "RoundWatch", value = tostring(round)}) updatePhaseHUD() if inGame then local blueActive = currentTurn ~= "Red" Global.call("setHudAttribute", {id = "ChsStpPanel1", attr = "color", value = blueActive and "#2E86FFF8" or "#0B255C28"}) Global.call("setHudAttribute", {id = "ChsStpPanel2", attr = "color", value = blueActive and "#4A101028" or "#FF3737F8"}) Global.call("setHudAttribute", {id = "ChsStpPanel1", attr = "outline", value = blueActive and "#FFFFFF" or "#5B8FDECC"}) Global.call("setHudAttribute", {id = "ChsStpPanel1", attr = "outlineSize", value = blueActive and "6" or "1"}) Global.call("setHudAttribute", {id = "ChsStpPanel2", attr = "outline", value = blueActive and "#D46060CC" or "#FFFFFF"}) Global.call("setHudAttribute", {id = "ChsStpPanel2", attr = "outlineSize", value = blueActive and "1" or "6"}) else Global.call("setHudAttribute", {id = "ChsStpPanel1", attr = "color", value = "#1A5FC8E6"}) Global.call("setHudAttribute", {id = "ChsStpPanel2", attr = "color", value = "#B52020E6"}) Global.call("setHudAttribute", {id = "ChsStpPanel1", attr = "outline", value = "#5B8FDECC"}) Global.call("setHudAttribute", {id = "ChsStpPanel1", attr = "outlineSize", value = "2"}) Global.call("setHudAttribute", {id = "ChsStpPanel2", attr = "outline", value = "#D46060CC"}) Global.call("setHudAttribute", {id = "ChsStpPanel2", attr = "outlineSize", value = "2"}) end end function nextPhase(obj, player_color_click, alt_click) resetActivationTokens() if alt_click then passTurn(obj, player_color_click, alt_click) return end currentPhase = currentPhase + 1 if currentPhase > #phases then currentPhase = 1 passTurn(obj, player_color_click, alt_click) end broadcastToAll(phases[currentPhase], "Yellow") writeMenus() updateGameStateHUD() end function passTurn(obj, player_color_click, alt_click) if(singlesMode != "singles") then if player_color_click ~= currentTurn then if simulation then broadcastToAll("INTRUDER", "Pink") else return end end end currentPhase = 1 local cpMessage if currentTurn == "Red" then currentTurn = "Blue" blueTurnCounter.call("increaseSelf") counterIncrement(blueCpCounter) if cpEveryTurn then counterIncrement(redCpCounter) cpMessage = "Both players gained a CP" else cpMessage = "Blue player gained a CP" end else currentTurn = "Red" redTurnCounter.call("increaseSelf") counterIncrement(redCpCounter) if cpEveryTurn then counterIncrement(blueCpCounter) cpMessage = "Both players gained a CP" else cpMessage = "Red player gained a CP" end end -- Re-baseline the CP counters so their value pollers treat this automatic -- turn-pass gain as the new baseline and don't also announce it as a manual -- CP change (see cpCounter.ttslua). redCpCounter.call("markBaseline") blueCpCounter.call("markBaseline") updateCpHUD() updateGameStateHUD() -- QoL: the chess clock follows the turn. If it was running for the player -- who just finished, switch it to the new active player; paused or -- never-started clocks are left alone (see SyncClockToTurn in chessClock). local chessClock = getObjectFromGUID(Global.getVar("chessClock_GUID") or "") if chessClock then chessClock.call("SyncClockToTurn", {color = currentTurn}) end printToAll(getTurnStateLabel() .. " - " .. cpMessage, "Yellow") broadcastToAll("It's " .. currentTurn .. " Player's turn", currentTurn) writeMenus() end function startCustomTurns() if first == "Red" then currentTurn = "Blue" -- it has to be the opposite else currentTurn = "Red" end passTurn(self, currentTurn, false) end function togglePlyr() self.clearButtons() if first == "Red" then firstPlayerBtn.label = blueFirstLbl firstPlayerBtn.color = {0, 0, 1} first = "Blue" else firstPlayerBtn.label = redFirstLbl firstPlayerBtn.color = {1, 0, 0} first = "Red" end writeMenus() end function hideDeploymentZonesForGameStart() Global.setVar("interactiveButtonDeploymentZones", 1) destroyDeployZones() local button = getObjectFromGUID("8aba11") if button then button.call("syncDeploymentZoneButtonImage") end end function showObjectivesForGameStart() local button = getObjectFromGUID("15d652") if button then button.call("turnObjectivesOn") else broadcastToAll("Could not turn objectives on: Show Objectives button not found.", {1, 0.25, 0.25}) end end function showHideIngameDeployment() local state = Global.getVar("interactiveButtonDeploymentZones") if state == 1 then -- place Global.setVar("interactiveButtonDeploymentZones", 2) if selectedDeploymentSpawnsOverlay() then drawDeployZone(DeployZonesData[deploySelected]) end else -- recall Global.setVar("interactiveButtonDeploymentZones", 1) if selectedDeploymentSpawnsOverlay() then destroyDeployZones() end end end -- Territory divider line (toggle + helpers) -- The territory divider is the single centre line that splits the battlefield -- into each player's half. Its orientation is derived from the currently -- selected deployment zone so it always matches the chosen mission. territoryLineColor = "Yellow" function showHideTerritoryLine() local state = Global.getVar("interactiveButtonTerritoryLine") if state == 1 then -- place Global.setVar("interactiveButtonTerritoryLine", 2) drawTerritoryLine() else -- recall Global.setVar("interactiveButtonTerritoryLine", 1) destroyTerritoryLine() end end function destroyTerritoryLine() destroyTrackedOverlayObjects(territoryOverlayGUIDs) territoryOverlayGUIDs = {} end -- Returns the first non-circle draw spec of the selected deployment, which -- carries the type/position the divider orientation is derived from. function territoryReferenceEntry(zone) if not zone or not zone.draw then return nil end if zone.draw.type == "none" then return nil end for _, d in ipairs(zone.draw) do if d.type ~= "circle" and d.type ~= "circleInZone" and d.type ~= "circleDeployment" then return d end end return zone.draw[1] end -- Distance of a stepped DZ's boundary from the table centre, for one step. -- `halfEdge` is the half-length of the edge the `fromSide` value is measured from. function stepBoundaryFromCentre(step, halfEdge) if step.fromCenter then return step.fromCenter end return halfEdge - (step.fromSide or 0) end -- Length of a centre-crossing line at angle `thetaDeg` (0 = +X, 90 = +Z), -- clipped to the battlefield rectangle with half-extents hx (X) and hz (Z). function territoryChordLength(thetaDeg, hx, hz) local r = math.rad(thetaDeg) local c = math.abs(math.cos(r)) local s = math.abs(math.sin(r)) local tx = (c > 1e-6) and (hx / c) or math.huge local tz = (s > 1e-6) and (hz / s) or math.huge return 2 * math.min(tx, tz) end -- The territory divider is the line through the battlefield centre joining the -- two perimeter points equidistant from each deployment zone. Its orientation -- is derived from the selected DZ; the length is the chord clipped to the table. function drawTerritoryLine() local mat = getObjectFromGUID(mat_GUID) if not mat then return end -- If the selected zone declares its divider explicitly, draw those specs -- through the same cornerPolyline path (tagged "territory") and skip the -- type/position derivation below. Used by the Combat Patrol zones, whose -- cornerPolyline shapes the derivation logic can't infer an angle from. local selected = selectedDeployment() if selected and selected.territory then for _, spec in ipairs(selected.territory) do drawCornerPolyline(spec, "territory") end for _, guid in ipairs(territoryOverlayGUIDs) do local found = getObjectFromGUID(guid) if found then local p = found.getPosition() found.setPosition({p[1], defaultDeployHeight + deployOffset, p[3]}) end end return end local longEdge = mat.getScale().x * sizeMulti -- along X local shortEdge = mat.getScale().z * sizeMulti -- along Z local hx = longEdge / 2 local hz = shortEdge / 2 local entry = territoryReferenceEntry(selectedDeployment()) local pos = entry and entry.position or "x" local t = entry and entry.type or "line" local theta -- rot.y in degrees: 0 = along X, 90 = along Z if t == "triangle" then -- Crucible of Battle: parallel to the DZ edges, through the centre. -- For 60x44 this exits the long edges 15" from centre. theta = math.deg(math.atan(shortEdge / (longEdge / 2))) elseif t == "quarter" then -- Search and Destroy: the main corner-to-corner diagonal. theta = math.deg(math.atan(shortEdge / longEdge)) elseif t == "stepped" and entry.steps and entry.steps[2] then -- Sweeping Engagement / Tipping Point: join the edge midpoints between -- the two staggered DZ boundaries. -- signZ/signX carry the table's rot.y handedness so the slight tilt -- leans the correct way (same handedness as the Search and Destroy fix). if pos == "z" or pos == "-z" then local b1 = stepBoundaryFromCentre(entry.steps[1], hz) local b2 = stepBoundaryFromCentre(entry.steps[2], hz) local m = (b1 - b2) / 2 local signZ = (pos == "z") and -1 or 1 theta = math.deg(math.atan((signZ * m) / hx)) else local b1 = stepBoundaryFromCentre(entry.steps[1], hx) local b2 = stepBoundaryFromCentre(entry.steps[2], hx) local m = (b1 - b2) / 2 local signX = (pos == "x") and -1 or 1 if math.abs(m) < 1e-6 then theta = 90 else theta = math.deg(math.atan(hz / (signX * m))) end end elseif pos == "z" or pos == "-z" then -- Dawn of War style: divider runs along X at z = 0. theta = 0 else -- Hammer and Anvil style (and default): divider runs along Z at x = 0. theta = 90 end local length = territoryChordLength(theta, hx, hz) spawnLine({0, deployLineYPos, 0}, {0, theta, 0}, {x = length, y = deployLineHeight, z = 0.02}, territoryLineColor, "territory") -- Match the lift applied to deployment overlays so it sits at table height. for _, guid in ipairs(territoryOverlayGUIDs) do local found = getObjectFromGUID(guid) if found then local p = found.getPosition() found.setPosition({p[1], defaultDeployHeight + deployOffset, p[3]}) end end end -- 9" secondary deployment lines (toggle + helpers) function destroyNineInchDeployLines() destroyTrackedOverlayObjects(nineInchOverlayGUIDs) nineInchOverlayGUIDs = {} end function toggleNineFromDeployment() -- Remove if present, else add at 9" if #nineInchOverlayGUIDs > 0 then destroyNineInchDeployLines() return end addNineInchDeployLines(9) end function addNineInchDeployLines(distance) for _, guid in ipairs(deploymentOverlayGUIDs) do local obj = getObjectFromGUID(guid) if obj then local sc = obj.getScale() -- Only process thin straight lines, skip circles and other shapes if sc.z and sc.z <= 0.05 then local rot = obj.getRotation() local pos = obj.getPosition() local yawRad = math.rad(rot.y) -- normal vector in XZ plane (perpendicular to line axis) local nx = math.cos(yawRad + math.rad(90)) local nz = math.sin(yawRad + math.rad(90)) -- point towards table center local cx = -pos.x local cz = -pos.z local dot = (nx * cx) + (nz * cz) if dot < 0 then nx = -nx nz = -nz end local newPos = { x = pos.x + nx * distance, y = pos.y, z = pos.z + nz * distance } local newRot = { x = rot.x, y = rot.y, z = rot.z } local newScale = { x = sc.x, y = sc.y, z = sc.z } spawnLine(newPos, newRot, newScale, nineInchColor, "deployZone9") end end end end function recallAll() destroyAllObjectives() destroyDeployZones() end function none() end LuaScriptState]{"svblueDispositionSelected":1,"svCombatPatrolMode":false,"svcurrentPhase":1,"svcurrentTurn":"Red","svDebugMapTagData":[],"svdeploySelected":1,"svgameMode":"none","svinGame":false,"svmapSizeSelected":3,"svMatrixPropsDisabled":false,"svMatTextureSelected":1,"svMissionSetupControlsVisible":false,"svredDispositionSelected":3,"svsizeConfirmed":false}XmlUI50GUIDf4ee71Name Custom_ModelTransformposX9V@posY?posZrotXrotYp@rotZscaleX?scaleY?scaleZ?Nickname StopwatchDescriptionGMNotesAltLookAngle&xyzColorDiffuse&rGZ?gFY?b@?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomMeshNMeshURLihttps://steamusercontent-a.akamaihd.net/ugc/937194385976803995/735CE8D675A462D3674D9488B6500CBF255EF60E/DiffuseURLihttps://steamusercontent-a.akamaihd.net/ugc/937194506253244821/1D89A4E1CE4B2A381BF18A269C9C6D5CD675671C/NormalURLColliderURLihttps://steamusercontent-a.akamaihd.net/ugc/937194506248553405/A3204CC0B60259F71CA38DBC0AAD1434BFAB6952/ConvexMaterialIndexTypeIndexCustomShaderSpecularColor&r?g?b?SpecularIntensity?SpecularSharpnessu@FresnelStrength?CastShadowsLuaScriptZ-- FTC-GUID: f4ee71 --[[ Gizmo's Chess Clock/Timer by Gizmo https://steamcommunity.com/sharedfiles/filedetails/?id=1541793481 Keybind Addon by MoonkeyMod https://steamcommunity.com/sharedfiles/filedetails/?id=2310083136 Color patch by Nyss AKA Liyarin https://steamcommunity.com/id/Liyarin/myworkshopfiles/?appid=286160 Host/Payed by Cratos https://steamcommunity.com/id/GodCratos/myworkshopfiles?appid=286160 such Liyarin on Fiverr, he do good work. ]] -- Liyarin's Vars pColors = { pColor1 = "Blue", pColor2 = "Red" } -- HUD overlay colours (hex rgba). Richer tones vs flat named colours. -- pColors above is kept for setColorTint() which only accepts TTS named colours. uiPanelColors = { pColor1 = "#1A5FC8E6", -- rich navy-blue, 90% opaque pColor2 = "#B52020E6" -- deep crimson, 90% opaque } -- Outline styles for active (running) vs passive (waiting) clock panels. UI_ACTIVE_OUTLINE = "#FFFFFF" UI_ACTIVE_OUTLINE_SIZE = "6" UI_P1_PASSIVE_OUTLINE = "#5B8FDECC" UI_P2_PASSIVE_OUTLINE = "#D46060CC" UI_PASSIVE_OUTLINE_SIZE = "2" -- Clock pill states: active (running) vs paused. CLOCK_PILL_ACTIVE_OUTLINE = "#FFFFFF" CLOCK_PILL_ACTIVE_OUTLINE_SIZE = "4" CLOCK_PILL_ACTIVE_BG = "rgba(0,0,0,0)" CLOCK_PILL_ACTIVE_TEXT = "White" CLOCK_PILL_PASSIVE_OUTLINE = "rgba(0,0,0,0)" CLOCK_PILL_PASSIVE_SIZE = "0" CLOCK_PILL_PASSIVE_BG = "#1A1A1ACC" CLOCK_PILL_PASSIVE_TEXT = "#FFFFFF40" -- Original Vars clockWaitId = nil Colons = true holding = false holdTime = 0 holding_1= false hold_1Time = 0 holding_2= false hold_2Time = 0 unColoredColor = {105/255,101/255,98/255} holdColor_1 ="" holdColor_2 ="" holdColor="" holdPlayer_1="" holdPlayer_2="" data = { start_1 = 5400 ,start_2 = 5400, seconds_1= 5400 ,seconds_2 = 5400, bonus_1 = 0 ,bonus_2 = 0, delay_1 = 0 ,delay_2 = 0, selDigit = 6 ,delayed = 0, state = 11 ,storedState = 11, ended_1 = true ,ended_2 = true, DoTurns = false ,color_1="", color_2="" } HOLD = 1 RESOLUTION = 0.25 tmpDigits = {0,0,0,0,0,0,0,0,0,0} digitNames = {"Hr1_1","Mn1_10","Mn1_01","Sc1_10","Sc1_01","Hr2_1","Mn2_10","Mn2_01","Sc2_10","Sc2_01"} states = {"increment_1","increment_2","decrement_1","decrement_2","adjustTime","setTime","setBonus","setDelay","doDelay_1","doDelay_2","Paused"} -- 1 2 3 4 5 6 7 8 9 10 11 function onSave() self.script_state = JSON.encode({ data = data, colors = pColors }) -- return self.script_state end function onDestroy() onSave() if clockWaitId then Wait.stop(clockWaitId) clockWaitId = nil end end function onLoad(json) if json~="" and json~=nil then local decoded = JSON.decode(json) data = decoded.data pColors = decoded.colors else data.seconds_1 = data.start_1 data.seconds_2 = data.start_2 end Wait.condition(function() Wait.frames(function() changeButtonColors() end, 10) end, function() for _, obj in pairs(getAllObjects()) do if not obj.resting then return false end end return true end, 10, changeButtonColors) DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) ensureChessStopwatchUI() refreshHudHighlight() -- Re-apply once Global.UI is guaranteed live (see note in refreshHudHighlight): -- the immediate call can lose the load race against the overlay coming up. -- The onClick wiring is re-pushed too, for the same reason. Wait.time(function() ensureChessStopwatchUI() refreshHudHighlight() end, 1) -- Game Keys (Options -> Game Keys). Shared controls: the target side is -- derived from the clock state, not from who pressed, so a single binding -- serves every player (spectators included) - see PassClock/PauseClock. addHotkey("Pass Chess Clock", function(playerColor, _, _) PassClock(playerColor) end) addHotkey("Pause Chess Clock", function(playerColor, _, _) PauseClock(playerColor) end) clockWaitId = Wait.time(Tick, RESOLUTION, -1) end function Tick() self.call(states[data.state],{}) if holding==true then if holdColor == "Grey" or (data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black") then return end holdTime = holdTime+RESOLUTION end if holding_1==true then hold_1Time = hold_1Time+RESOLUTION end if holding_2==true then hold_2Time = hold_2Time+RESOLUTION end end --Allows script buuton to call Gizmo's click functions -- Legacy numpad keys, kept for muscle memory but re-pointed at the shared -- state-derived controls (the primary bindings are the "Pass/Pause Chess Clock" -- Game Keys registered in onLoad). The old per-seat semantics - 9 Blue-only, -- 7 Red-only, each starting the presser's OWN clock while broadcasting "Time -- passed" - were misleading: pressed at the end of your turn they paused your -- clock instead of passing it. function onScriptingButtonDown(index, color) if index == 9 or index == 7 then PassClock(color) elseif index == 8 then PauseClock(color) end end -- Gizmo's click functions function Click_1(player, _, _) if hold_1Time 9 then tmpDigits[data.selDigit]=0 end else if tmpDigits[data.selDigit] > 5 then tmpDigits[data.selDigit]=0 end end self.UI.show(digitNames[data.selDigit]) self.UI.setAttribute(digitNames[data.selDigit] , "image", tmpDigits[data.selDigit]) elseif data.state==7 or data.state==8 then tmpDigits[data.selDigit] = tmpDigits[data.selDigit]+1 if data.selDigit==5 or data.selDigit==10 then if tmpDigits[data.selDigit] > 9 then tmpDigits[data.selDigit]=0 end else if tmpDigits[data.selDigit] > 5 then tmpDigits[data.selDigit]=0 end end self.UI.show(digitNames[data.selDigit]) self.UI.setAttribute(digitNames[data.selDigit] , "image", tmpDigits[data.selDigit]) end end holding_1=false hold_1Time=0 end function Click_2(player, _, _) if hold_2Time10 then if data.state==6 then tmpDigits[1]=tmpDigits[6] tmpDigits[2]=tmpDigits[7] tmpDigits[3]=tmpDigits[8] tmpDigits[4]=tmpDigits[9] tmpDigits[5]=tmpDigits[10] tmpToStartSec() DisplayClock(1, data.start_1) end data.selDigit=1 end end elseif data.state==7 then self.UI.show(digitNames[data.selDigit]) if data.selDigit==5 then bon = tmpToSecs() data.bonus_1 = bon[1] data.bonus_2 = bon[2] secsToTmp(data.delay_1, data.delay_2) data.state = 8 data.selDigit = 9 HideColons() HideDigits() DisplayClock(2, data.delay_2) self.UI.setAttribute("Hr2_1" , "image", "d") else data.selDigit = data.selDigit+1 if data.selDigit>10 then tmpDigits[4]=tmpDigits[9] tmpDigits[5]=tmpDigits[10] bon = tmpToSecs() DisplayClock(1, bon[1]) self.UI.setAttribute("Hr1_1" , "image", "b") data.selDigit=4 end end elseif data.state==8 then self.UI.show(digitNames[data.selDigit]) if data.selDigit==5 then del = tmpToSecs() data.delay_1 = del[1] data.delay_2 = del[2] data.state = 11 data.selDigit = 6 data.seconds_1 = data.start_1 data.seconds_2 = data.start_2 DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) ShowDigits() else data.selDigit = data.selDigit+1 if data.selDigit>10 then tmpDigits[4]=tmpDigits[9] tmpDigits[5]=tmpDigits[10] del = tmpToSecs() DisplayClock(1, del[1]) self.UI.setAttribute("Hr1_1" , "image", "d") data.selDigit=4 end end end end holding=false holdTime=0 end function Hold_C(player, _, _) if player.color == "Grey" then return end holding=true holdColor = player.color end function Hold_1(player, _, _) if player.color == "Grey" then return end holdColor_1 = player.color holdPlayer_1 = player.steam_name holding_1=true end function Hold_2(player, _, _) if player.color == "Grey" then return end holdColor_2 = player.color holdPlayer_2 = player.steam_name holding_2=true end function Pause() data.storedState=data.state data.state=11 if data.DoTurns==true then self.setColorTint(unColoredColor) Turns.enable=false end end function Resume() data.state=data.storedState data.storedState=11 end function SetTurn() if data.DoTurns==true then -- Turns.type=2 -- Turns.pass_turns=false -- Turns.skip_empty_hands=false -- Turns.disable_interactations=true if data.state==1 or data.state==3 or data.state==9 then self.setColorTint(stringColorToRGB(data.color_1)) -- Turns.order={data.color_1} -- Turns.turn_color=data.color_1 elseif data.state==2 or data.state==4 or data.state==10 then self.setColorTint(stringColorToRGB(data.color_2)) -- Turns.order={data.color_2} -- Turns.turn_color=data.color_2 end -- Turns.enable=true end end function HideDigits() for i=1,10,1 do self.UI.hide(digitNames[i]) end end function ShowDigits() for i=1,10,1 do self.UI.show(digitNames[i]) end end function HideColons() self.UI.hide("Cn1") self.UI.hide("Cn2") self.UI.hide("Cn3") self.UI.hide("Cn4") end function ShowColons() self.UI.show("Cn1") self.UI.show("Cn2") self.UI.show("Cn3") self.UI.show("Cn4") end function Paused() TurnCheck() if data.storedState~=9 and data.storedState~=10 then DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) ShowDigits() end if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end data.state=6 secsToTmp(data.start_1, data.start_2) DisplayClock(1, data.start_1) DisplayClock(2, data.start_2) elseif Colons==true then if data.storedState==11 then HideColons() elseif data.storedState==1 or data.storedState==3 then self.UI.hide("Cn1") self.UI.hide("Cn2") elseif data.storedState==9 then self.UI.hide("Cn1") self.UI.hide("Cn2") HideDigits() self.UI.show(digitNames[4]) self.UI.show(digitNames[5]) DisplayClock(1, data.delayed) elseif data.storedState==2 or data.storedState==4 then self.UI.hide("Cn3") self.UI.hide("Cn4") elseif data.storedState==10 then self.UI.hide("Cn3") self.UI.hide("Cn4") HideDigits() self.UI.show(digitNames[9]) self.UI.show(digitNames[10]) DisplayClock(2, data.delayed) end Colons = false return end ShowColons() Colons = true end function DisplayClock(which, value) secs = value hours = secs/3600 secs = secs%3600 minutes = secs/60 secs = secs%60 self.UI.setAttribute("Hr"..which.."_1" , "image", math.floor(hours%10)) self.UI.setAttribute("Mn"..which.."_10", "image", math.floor(minutes/10)) self.UI.setAttribute("Mn"..which.."_01", "image", math.floor(minutes%10)) self.UI.setAttribute("Sc"..which.."_10", "image", math.floor(secs/10)) self.UI.setAttribute("Sc"..which.."_01", "image", math.floor(secs%10)) DisplayUIClock(which) end function increment_1() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end if data.DoTurns==true then self.setColorTint(unColoredColor) Turns.enable=false end data.storedState=data.state data.state=5 secsToTmp(data.seconds_1, data.seconds_2) DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) elseif holdTime==0 then ShowColons() DisplayClock(1, data.seconds_1) data.seconds_1=data.seconds_1+RESOLUTION end end function increment_2() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end if data.DoTurns==true then self.setColorTint(unColoredColor) Turns.enable=false end data.storedState=data.state data.state=5 secsToTmp(data.seconds_1, data.seconds_2) DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) elseif holdTime==0 then ShowColons() DisplayClock(2, data.seconds_2) data.seconds_2=data.seconds_2+RESOLUTION end end function decrement_1() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end if data.DoTurns==true then self.setColorTint(unColoredColor) Turns.enable=false end data.storedState=data.state data.state=5 secsToTmp(data.seconds_1, data.seconds_2) DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) elseif holdTime==0 then ShowColons() DisplayClock(1, data.seconds_1) data.seconds_1 = data.seconds_1-RESOLUTION if data.seconds_1 < 0 then Ding() data.ended_1=true data.state=11 data.storedState=data.state end end end function decrement_2() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end if data.DoTurns==true then self.setColorTint(unColoredColor) Turns.enable=false end data.storedState=data.state data.state=5 secsToTmp(data.seconds_1, data.seconds_2) DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) elseif holdTime==0 then ShowColons() DisplayClock(2, data.seconds_2) data.seconds_2 = data.seconds_2-RESOLUTION if data.seconds_2 < 0 then Ding() data.ended_2=true data.state=11 data.storedState=data.state end end end function adjustTime() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end data.state=11 data.storedState=11 data.selDigit=6 data.seconds_1 = data.start_1 data.seconds_2 = data.start_2 DisplayClock(1, data.start_1) DisplayClock(2, data.start_2) else ShowColons() for i=1,10,1 do if i~=data.selDigit then self.UI.show(digitNames[i]) end end if Colons==true then self.UI.hide(digitNames[data.selDigit]) Colons = false else self.UI.show(digitNames[data.selDigit]) Colons = true end end end function setTime() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end data.state=11 data.storedState=11 data.selDigit=6 tmpToStartSec() data.seconds_1 = data.start_1 data.seconds_2 = data.start_2 DisplayClock(1, data.start_1) DisplayClock(2, data.start_2) else ShowColons() for i=1,10,1 do if i~=data.selDigit then self.UI.show(digitNames[i]) end end if Colons==true then self.UI.hide(digitNames[data.selDigit]) HideColons() Colons = false else self.UI.show(digitNames[data.selDigit]) ShowColons() Colons = true end end end function setBonus() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end data.state=11 data.storedState=11 data.selDigit=6 bon = tmpToSecs() data.bonus_1 = bon[1] data.bonus_2 = bon[2] data.seconds_1 = data.start_1 data.seconds_2 = data.start_2 DisplayClock(1, data.start_1) DisplayClock(2, data.start_2) else count=2 if data.selDigit>5 then self.UI.show("Hr2_1") burp={9,10} else self.UI.show("Hr1_1") burp={4,5,9,10} count=4 end HideColons() for i=1,count,1 do if burp[i]~=data.selDigit then self.UI.show(digitNames[burp[i]]) end end if Colons==true then self.UI.hide(digitNames[data.selDigit]) Colons = false else self.UI.show(digitNames[data.selDigit]) Colons = true end end end function setDelay() if holdTime==HOLD then if data.DoTurns == true and data.color_1 ~= holdColor and data.color_2 ~= holdColor and holdColor ~= "Black" then return end data.state=11 data.storedState=11 data.selDigit=6 del = tmpToSecs() data.delay_1 = del[1] data.delay_2 = del[2] data.seconds_1 = data.start_1 data.seconds_2 = data.start_2 DisplayClock(1, data.start_1) DisplayClock(2, data.start_2) else count=2 if data.selDigit>5 then self.UI.show("Hr2_1") burp={9,10} else self.UI.show("Hr1_1") burp={4,5,9,10} count=4 end HideColons() for i=1,2,1 do if burp[i]~=data.selDigit then self.UI.show(digitNames[burp[i]]) end end if Colons==true then self.UI.hide(digitNames[data.selDigit]) Colons = false else self.UI.show(digitNames[data.selDigit]) Colons = true end end end function doDelay_1() if data.delayed==0 then ShowColons() ShowDigits() DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) if data.start_1==0 then data.state = 1 else data.state = 3 end else HideColons() HideDigits() self.UI.show(digitNames[4]) self.UI.show(digitNames[5]) DisplayClock(1, data.delayed) data.delayed=data.delayed-RESOLUTION end end function doDelay_2() if data.delayed==0 then ShowColons() ShowDigits() DisplayClock(1, data.seconds_1) DisplayClock(2, data.seconds_2) if data.start_2==0 then data.state = 2 else data.state = 4 end else HideColons() HideDigits() self.UI.show(digitNames[9]) self.UI.show(digitNames[10]) DisplayClock(2, data.delayed) data.delayed=data.delayed-RESOLUTION end end function secsToTmp(secs_1, secs_2) secs = secs_1 hours = secs/3600 secs = secs%3600 minutes = secs/60 secs = secs%60 tmpDigits[1] = math.floor(hours%10) tmpDigits[2] = math.floor(minutes/10) tmpDigits[3] = math.floor(minutes%10) tmpDigits[4] = math.floor(secs/10) tmpDigits[5] = math.floor(secs%10) secs = secs_2 hours = secs/3600 secs = secs%3600 minutes = secs/60 secs = secs%60 tmpDigits[6] = math.floor(hours%10) tmpDigits[7] = math.floor(minutes/10) tmpDigits[8] = math.floor(minutes%10) tmpDigits[9] = math.floor(secs/10) tmpDigits[10]= math.floor(secs%10) end function tmpToStartSec() data.start_1=(tmpDigits[1]*3600)+(tmpDigits[2]*600)+(tmpDigits[3]*60)+(tmpDigits[4]*10)+tmpDigits[5] data.start_2=(tmpDigits[6]*3600)+(tmpDigits[7]*600)+(tmpDigits[8]*60)+(tmpDigits[9]*10)+tmpDigits[10] end function tmpToSecs() return {(tmpDigits[1]*3600)+(tmpDigits[2]*600)+(tmpDigits[3]*60)+(tmpDigits[4]*10)+tmpDigits[5], (tmpDigits[6]*3600)+(tmpDigits[7]*600)+(tmpDigits[8]*60)+(tmpDigits[9]*10)+tmpDigits[10]} end function Ding() Wait.time(Dong, 0.025, 50) end function Dong() pos = self.getPosition() pos.y = pos.y+0.125 self.setPosition(pos) self.setVelocity({0,-200,0}) end function TurnCheck() if hold_1Time==HOLD then if data.DoTurns==false then data.color_1=holdColor_1 if data.color_2~="" then broadcastToAll("Color-Mode Enabled.", {0,1,0}) data.DoTurns=true else broadcastToAll("Waiting For Player 2...", {1,1,1}) end broadcastToAll("Set Player 1 to "..holdPlayer_1.." ("..holdColor_1..")", {0.5,0.5,1}) elseif holdColor_1==data.color_1 or holdColor_1==data.color_2 or holdColor_1=="Black" then broadcastToAll("Color-Mode Disabled.", {1,0,0}) data.DoTurns=false data.color_1="" data.color_2="" self.setColorTint(unColoredColor) end end if hold_2Time==HOLD then if data.DoTurns==false then data.color_2=holdColor_2 if data.color_1~="" then broadcastToAll("Color-Mode Enabled.", {0,1,0}) data.DoTurns=true else broadcastToAll("Waiting For Player 1...", {1,1,1}) end broadcastToAll("Set Player 2 to "..holdPlayer_2.." ("..holdColor_2..")", {0.5,0.5,1}) elseif holdColor_2==data.color_1 or holdColor_2==data.color_2 or holdColor_2=="Black" then broadcastToAll("Color-Mode Disabled.", {1,0,0}) data.DoTurns=false data.color_1="" data.color_2="" self.setColorTint(unColoredColor) end end end -- Liyarin's color functions function ensureChessStopwatchUI() local guid = self.getGUID() Global.call("setHudAttribute", {id = "ChsStpBtn1", attr = "onClick", value = guid .. "/Click_1"}) Global.call("setHudAttribute", {id = "ChsStpBtn2", attr = "onClick", value = guid .. "/Click_2"}) Global.call("setHudAttribute", {id = "ChsStpMinus1", attr = "onClick", value = guid .. "/SubTime1"}) Global.call("setHudAttribute", {id = "ChsStpPlus1", attr = "onClick", value = guid .. "/AddTime1"}) Global.call("setHudAttribute", {id = "ChsStpMinus2", attr = "onClick", value = guid .. "/SubTime2"}) Global.call("setHudAttribute", {id = "ChsStpPlus2", attr = "onClick", value = guid .. "/AddTime2"}) Global.call("setHudAttribute", {id = "ChsStpPlay1", attr = "onClick", value = guid .. "/PlayClock1"}) Global.call("setHudAttribute", {id = "ChsStpPlay2", attr = "onClick", value = guid .. "/PlayClock2"}) Global.call("setHudAttribute", {id = "ChsStpPause1", attr = "onClick", value = guid .. "/PauseClock"}) Global.call("setHudAttribute", {id = "ChsStpPause2", attr = "onClick", value = guid .. "/PauseClock"}) end -- ===== Shared clock controls (HUD play/pause buttons, Game Keys, numpad keys) ===== -- Design: the affected side is derived from the CLOCK STATE, never from who -- pressed - so one binding serves both players and spectators alike. The real -- presser is only used for the chat announcement. Gizmo's Click_1/Click_2 keep -- their seat/colour-mode guards for direct pill clicks and the physical clock, -- so these controls drive them through a synthetic "Black" actor, which those -- guards explicitly exempt. local CLOCK_ACTOR = {color = "Black"} -- Accepts a Player instance (XML onClick) or a colour string (hotkeys). -- NOTE: XML-callback Player instances are MoonSharp userdata, not Lua tables, -- so duck-type the fields instead of checking type(player) == "table". local function presserName(player) if type(player) == "string" then local seated = Player[player] if seated and seated.steam_name then return seated.steam_name end return player end if player ~= nil then local ok, name = pcall(function() return player.steam_name end) if ok and name and name ~= "" then return name end local okColor, color = pcall(function() return player.color end) if okColor and color then return tostring(color) end end return "?" end -- 1 = Blue running, 2 = Red running, nil = neither (paused/editing). local function clockSideRunning(state) if state == 1 or state == 3 or state == 9 then return 1 end if state == 2 or state == 4 or state == 10 then return 2 end return nil end -- States 5-8 are the physical clock's digit-editing modes (set time/bonus/delay); -- remote controls must not fire mid-edit because Click_1/Click_2 double as -- digit +/- buttons there. local function clockEditing() return data.state >= 5 and data.state <= 8 end local function startClockSide(side, byName) if side == 1 then Click_1(CLOCK_ACTOR) broadcastToAll("Clock passed to Blue by " .. byName, {0.4, 0.6, 1}) else Click_2(CLOCK_ACTOR) broadcastToAll("Clock passed to Red by " .. byName, {1, 0.4, 0.4}) end end -- Pass = stop whichever clock is running and start the other one. When paused, -- falls back to whoever ran LAST (storedState), so a pass from pause still -- hands the time to the opponent and starts it. function PassClock(player) if clockEditing() then broadcastToAll("Clock is in time-setup mode - finish editing first.", {1, 0.6, 0}) return end local side = clockSideRunning(data.state) or clockSideRunning(data.storedState) if not side then broadcastToAll("No clock has been started yet - use a play button first.", {1, 0.6, 0}) return end startClockSide(side == 1 and 2 or 1, presserName(player)) end -- Pause toggle: pauses the running clock; pressed again while paused it -- resumes whoever was running (Pause() stashes that side in storedState, and -- Resume() restores it exactly - no Click_N, so no time bonus is re-granted). -- Before any clock has ever run (start of game) storedState is also the paused -- state, so there is nothing to resume and the press is a no-op. function PauseClock(player) if clockEditing() then return end local running = clockSideRunning(data.state) if running then Pause() refreshHudHighlight() broadcastToAll("Clock paused by " .. presserName(player), {0.8, 0.8, 0.8}) return end local side = clockSideRunning(data.storedState) if not side then return end Resume() SetTurn() refreshHudHighlight() local sideName = (side == 1) and "Blue" or "Red" broadcastToAll("Clock resumed by " .. presserName(player) .. " - " .. sideName .. "'s time is running", (side == 1) and {0.4, 0.6, 1} or {1, 0.4, 0.4}) end -- HUD play buttons: explicitly start one side's clock. Doubles as resume after -- a pause and as a pass when the other side was running. No-op if that side is -- already running. local function playClockSide(side, player) if clockEditing() then broadcastToAll("Clock is in time-setup mode - finish editing first.", {1, 0.6, 0}) return end if clockSideRunning(data.state) == side then return end local byName = presserName(player) if side == 1 then Click_1(CLOCK_ACTOR) broadcastToAll("Blue clock started by " .. byName, {0.4, 0.6, 1}) else Click_2(CLOCK_ACTOR) broadcastToAll("Red clock started by " .. byName, {1, 0.4, 0.4}) end end function PlayClock1(player) playClockSide(1, player) end function PlayClock2(player) playClockSide(2, player) end -- Called by startMenu.passTurn after the active player flips: if the clock was -- running for the previous player, switch it to the new active player. A -- paused, editing, or never-started clock is left alone, so turn passes made -- before the game clock is started never start it. function SyncClockToTurn(params) local color = params and params.color local side = (color == "Blue" and 1) or (color == "Red" and 2) or nil if not side then return end if clockEditing() then return end local running = clockSideRunning(data.state) if not running or running == side then return end if side == 1 then Click_1(CLOCK_ACTOR) else Click_2(CLOCK_ACTOR) end -- printToAll, not broadcast: passTurn already pops the turn banner. printToAll("Chess clock follows the turn - now running for " .. color .. ".", (side == 1) and {0.4, 0.6, 1} or {1, 0.4, 0.4}) end -- Changes the color of the UI buttons based on the set colors function changeButtonColors() -- Stopwatch Buttons self.UI.setAttribute("pColor1", "color", pColors.pColor1) self.UI.setAttribute("pColor2", "color", pColors.pColor2) --UI Buttons ensureChessStopwatchUI() end -- Changes the button of one side based on the player that clicked it function changeColors(clickPlayer, clickBtn, buttonID) if buttonID == "pColor1" or buttonID == "pColor2" then pColors[buttonID] = clickPlayer.color changeButtonColors() end end -- +/- 5-minute adjustments from the HUD buttons function AddTime1() data.seconds_1 = data.seconds_1 + 300; DisplayClock(1, data.seconds_1) end function SubTime1() data.seconds_1 = math.max(0, data.seconds_1 - 300); DisplayClock(1, data.seconds_1) end function AddTime2() data.seconds_2 = data.seconds_2 + 300; DisplayClock(2, data.seconds_2) end function SubTime2() data.seconds_2 = math.max(0, data.seconds_2 - 300); DisplayClock(2, data.seconds_2) end -- Calculates numbers for UI clock function DisplayUIClock(which) -- which 1,2 local clockString = string.format("%02d:%02d:%02d", math.floor(hours), math.floor(minutes), math.floor(secs)) Global.call("setHudAttribute", {id = "ChsStpWatch"..which, attr = "text", value = clockString}) end -- Style one clock pill (which = 1 Blue / 2 Red) as active (running) or passive. function applyClockPill(which, active) local panel = "ClockSubPanel"..which local watch = "ChsStpWatch"..which if active then Global.call("setHudAttribute", {id = panel, attr = "outline", value = CLOCK_PILL_ACTIVE_OUTLINE}) Global.call("setHudAttribute", {id = panel, attr = "outlineSize", value = CLOCK_PILL_ACTIVE_OUTLINE_SIZE}) Global.call("setHudAttribute", {id = panel, attr = "color", value = CLOCK_PILL_ACTIVE_BG}) Global.call("setHudAttribute", {id = watch, attr = "color", value = CLOCK_PILL_ACTIVE_TEXT}) else Global.call("setHudAttribute", {id = panel, attr = "outline", value = CLOCK_PILL_PASSIVE_OUTLINE}) Global.call("setHudAttribute", {id = panel, attr = "outlineSize", value = CLOCK_PILL_PASSIVE_SIZE}) Global.call("setHudAttribute", {id = panel, attr = "color", value = CLOCK_PILL_PASSIVE_BG}) Global.call("setHudAttribute", {id = watch, attr = "color", value = CLOCK_PILL_PASSIVE_TEXT}) end end -- Re-apply the active/passive clock pill styling from the persisted clock state. -- Click_1/Click_2 set this live, but the styling lives only in the non-persistent -- Global overlay, so on reload it resets to the passive XML default while -- data.state keeps the clock running. Blue (player 1) runs in states 1/3/9, Red -- (player 2) in 2/4/10; any other state is paused/editing (both passive). function refreshHudHighlight() local s = data.state local blueRunning = (s == 1 or s == 3 or s == 9) local redRunning = (s == 2 or s == 4 or s == 10) applyClockPill(1, blueRunning) applyClockPill(2, redRunning) end LuaScriptState&{"colors":{"pColor1":"Blue","pColor2":"Red"},"data":{"bonus_1":0,"bonus_2":0,"color_1":"","color_2":"","delay_1":0,"delay_2":0,"delayed":0,"DoTurns":false,"ended_1":false,"ended_2":false,"seconds_1":5400,"seconds_2":5400,"selDigit":6,"start_1":5400,"start_2":5400,"state":11,"storedState":11}}XmlUIz ', creatorButtonId(c.id), colorFor(c.id), c.display) end -- Category bulk-toggles share the creator grid's geometry (same cell width, -- column count and spacing) so the two buttons line up over the columns below. local catCells = {} for _, cat in ipairs(CATEGORY_BUTTONS) do catCells[#catCells + 1] = string.format( '', categoryButtonId(cat.id), categoryColorFor(cat.id), cat.label) end local categoryRow = string.format( '\n%s\n', FILTER_CELL_W, FILTER_CELL_H, FILTER_SPACING, FILTER_SPACING, FILTER_COLUMNS, FILTER_CELL_H + FILTER_SPACING, table.concat(catCells, "\n")) -- Thin rule between the category controls and the creator list. local separator = '' local numRows = math.ceil(#list / FILTER_COLUMNS) local gridHeight = numRows * (FILTER_CELL_H + FILTER_SPACING) + FILTER_SPACING -- Content width = exactly the grid's width (n cells + the gaps between them) plus -- the 14+14 padding, so the full-width rule separators span exactly the button -- columns instead of overhanging past the last column. local width = FILTER_COLUMNS * FILTER_CELL_W + (FILTER_COLUMNS - 1) * FILTER_SPACING + 28 -- title + header rule + category row + separator + creator grid + padding/spacing slack. local height = 70 + 4 + (FILTER_CELL_H + FILTER_SPACING) + 4 + gridHeight + 60 local xml = string.format( '\n' .. 'Map Filters\n' .. '%s\n%s\n%s\n' .. '\n%s\n\n' .. '', UI_VERTICAL_LIFT, FILTER_SCALE, FILTER_SCALE, width, height, separator, categoryRow, separator, FILTER_CELL_W, FILTER_CELL_H, FILTER_SPACING, FILTER_SPACING, FILTER_COLUMNS, gridHeight, table.concat(cells, "\n")) -- The filter and the Mat Randomizer square are two siblings in one UI string: -- an object has a single XmlUI, so they must be set together (a second setXml -- would wipe the first). The square reuses the filter's width/height/scale so it -- matches exactly, shares the filter's lift so both sit at the same readable -- height, and is offset sideways (X) to sit beside the filter. self.UI.setXml(xml .. buildMatRandoPanel(width, height)) end -- ===================== Mat Randomizer square ===================== -- A sibling panel, same size as the filter, hosting the Mat Randomizer controls as -- XML. The buttons forward to startMenu (738804), which owns the selection state and -- the diffuse-swap logic (it shares matOriginalDiffuse + the baked CITY_MAT_* tables -- with its auto-reskin-on-load path, so that logic stays there). startMenu pushes the -- current label back into after each cycle. -- Horizontal gap (UI units) between the filter and the square beside it. The -- square's center is offset by the filter's rendered width (width * FILTER_SCALE) -- plus this gap so the panels don't overlap. Flip the sign in buildMatRandoPanel -- to move the square to the filter's other side. MAT_RANDO_GAP = 40 START_MENU_GUID = "738804" function startMenuObj() return getObjectFromGUID(START_MENU_GUID) end function shouldShowFromStartMenu() local sm = startMenuObj() if not sm then return false end local ok, visible = pcall(function() return sm.call("shouldShowMapSetupOverlay") end) return ok and visible == true end -- Current selection label, read from startMenu so the panel paints correctly on -- every (re)build. Falls back to "Default" if the menu isn't ready yet. function currentMatRandoLabel() local sm = startMenuObj() if sm then local ok, label = pcall(function() return sm.call("matRandomizerLabel") end) if ok and type(label) == "string" then return label end end return "Default" end -- Picker geometry. Width matches the filter so the two boxes line up. The panel -- keeps the filter's full height (so the two boxes are the same size) but the -- controls only fill the top; the empty space below the "Table Theme" heading is -- reserved for the table-theme controls being added next. -- Vertical sizes trimmed ~10% (from 70/100/90/50/8) to keep the panel compact. MAT_RANDO_TITLE_H = 63 -- "Map Mat Picker" title and the "Table Theme" heading MAT_RANDO_ROW_H = 90 -- picker row (arrows + value) MAT_RANDO_BTN_H = 81 -- shared Apply / Random / Default row MAT_RANDO_ARROW_W = 160 -- fixed width of each arrow button MAT_RANDO_RULE_H = 4 -- thin rule (matches the filter's separator) MAT_RANDO_PAD = 14 MAT_RANDO_SPACING = 7 -- Table Theme swatches. These mirror startMenu's `matTextures` table (same order): -- the button labels are the texture names and the button colors approximate each -- texture's RGB so the swatch reads as that theme. `colorOn` is a brightened version -- used to highlight the active swatch. Clicking one forwards to startMenu's -- setMatTexture, which owns the swap (it holds matTextures + the selected index). -- The list order here MUST stay in sync with matTextures over there. TABLE_THEMES = { {label = "Green", color = "#004F08", colorOn = "#22C235"}, {label = "Blue", color = "#004F70", colorOn = "#27A3D6"}, {label = "Grey", color = "#616657", colorOn = "#AEB4A0"}, {label = "Purple", color = "#5C4269", colorOn = "#A077B8"}, {label = "Red Wood", color = "#5A2A1A", colorOn = "#B0563A"}, {label = "Grey Stone", color = "#5B5B57", colorOn = "#ACACA4"}, {label = "Space", color = "#1A1F3A", colorOn = "#4A5699"}, {label = "Wood", color = "#6B4A2A", colorOn = "#C28A4A"}, {label = "Graffiti", color = "#6A2A5A", colorOn = "#C24FA8"}, {label = "Green 2", color = "#2A5A1A", colorOn = "#5AC23A"}, {label = "Stripes", color = "#4A4A52", colorOn = "#9A9AA8"}, {label = "Yellow", color = "#7A6810", colorOn = "#E0C32A"}, } -- How many swatches per row in the theme grid. With many themes a single row makes -- the labels unreadable, so they wrap across rows (mirrors the creator grid below). TABLE_THEME_COLUMNS = 4 TABLE_THEME_CELL_H = 70 -- Currently-selected theme index, read from startMenu so the panel can mark the -- active swatch. 0 if the menu isn't ready (nothing marked). function currentTableThemeIndex() local sm = startMenuObj() if sm then local ok, idx = pcall(function() return sm.call("matTextureSelectedIndex") end) if ok and type(idx) == "number" then return idx end end return 0 end function buildMatRandoPanel(width, height) -- Same lift as the filter (so both sit at the readable height) and offset -- sideways by the filter's rendered width plus the gap, so they sit beside each -- other without overlapping. position is in unscaled UI units; rendered width is -- width * FILTER_SCALE, so the center-to-center offset must clear that. local offsetX = math.floor(width * FILTER_SCALE) + MAT_RANDO_GAP -- Children take their preferred heights (childForceExpandHeight=false) and pack -- to the top; whatever height is left over sits empty below the Table Theme row. -- Table Theme swatches: one button per theme, wrapped into a fixed-column grid so -- the labels stay readable as the list grows (a single row squeezes them too thin). -- The active theme is painted in its brighter `colorOn` so you can see the selection. local selectedTheme = currentTableThemeIndex() local themeCells = {} for i, theme in ipairs(TABLE_THEMES) do local color = (i == selectedTheme) and theme.colorOn or theme.color themeCells[#themeCells + 1] = string.format( '', i, color, theme.label) end -- Cell width = the inner content width (panel width minus the L/R padding) split -- across the columns, with the inter-cell spacing removed. Height reserved is the -- number of wrapped rows times (cell + spacing). local themeSpacing = 10 local themeInnerW = width - 2 * MAT_RANDO_PAD local themeCellW = math.floor( (themeInnerW - (TABLE_THEME_COLUMNS - 1) * themeSpacing) / TABLE_THEME_COLUMNS) local themeRows = math.ceil(#TABLE_THEMES / TABLE_THEME_COLUMNS) local themeGridH = themeRows * (TABLE_THEME_CELL_H + themeSpacing) local themeRow = string.format( '\n%s\n', themeCellW, TABLE_THEME_CELL_H, themeSpacing, themeSpacing, TABLE_THEME_COLUMNS, themeGridH, table.concat(themeCells, "\n")) return string.format( '\n' .. 'Map Mat Picker\n' .. '\n' .. '\n' .. '\n' .. '%s\n' .. '\n' .. '\n' -- Apply / Random / Default share one row: childForceExpandWidth makes the -- three buttons split the inner width evenly with the spacing between them. .. '\n' .. '\n' .. '\n' .. '\n' .. '\n' .. '\n' .. 'Table Theme\n' .. '\n' .. '%s\n' .. '', offsetX, UI_VERTICAL_LIFT, FILTER_SCALE, FILTER_SCALE, width, height, MAT_RANDO_PAD, MAT_RANDO_PAD, MAT_RANDO_PAD, MAT_RANDO_PAD, MAT_RANDO_SPACING, MAT_RANDO_TITLE_H, MAT_RANDO_RULE_H, MAT_RANDO_ROW_H, MAT_RANDO_ARROW_W, currentMatRandoLabel(), MAT_RANDO_ARROW_W, MAT_RANDO_BTN_H, MAT_RANDO_RULE_H, MAT_RANDO_TITLE_H, MAT_RANDO_RULE_H, themeRow) end -- Thin forwarders: TTS routes an XML button's onClick to its own object's script, -- so these live here and hand off to startMenu's untouched picker logic. function onMatRandoPrev(player, value, id) local sm = startMenuObj() if sm then sm.call("matRandomizerDown") end end function onMatRandoNext(player, value, id) local sm = startMenuObj() if sm then sm.call("matRandomizerUp") end end function onMatRandoApply(player, value, id) local sm = startMenuObj() if sm then sm.call("matRandomizerApply") end end -- Reset the picker to Default and apply in one click (restores the map's own image). function onMatRandoDefault(player, value, id) local sm = startMenuObj() if sm then sm.call("matRandomizerApplyDefault") end end -- Pick a random city mat, apply it, and update the picker label to match. function onMatRandoRandom(player, value, id) local sm = startMenuObj() if sm then sm.call("matRandomizerRandom") end end -- Table Theme swatch: button id is "theme", forwarding to startMenu's -- setMatTexture (which owns the swatch list + swap). Rebuild afterwards so the -- check mark moves to the newly selected swatch. function onTableThemeSwatch(player, value, id) local sm = startMenuObj() if not sm then return end sm.call("setMatTexture" .. string.match(id, "%d+")) buildFilterUI() end function onCreatorToggle(player, value, id) local creator = string.gsub(id, "^crt_", "") if activeCreators[creator] then activeCreators[creator] = nil else activeCreators[creator] = true end self.UI.setAttribute(id, "color", colorFor(creator)) local mapType = creatorTypeOf(creator) if mapType then self.UI.setAttribute(categoryButtonId(mapType), "color", categoryColorFor(mapType)) end publishActiveCreators() end -- Map type of a single creator id (used to refresh the category button when one -- of its creators is toggled individually). function creatorTypeOf(creatorId) for _, c in ipairs(creatorList()) do if c.id == creatorId then return c.type end end end -- Bulk-toggle every creator in a category. If the category is fully active, -- turn all of them off; otherwise turn all of them on. function onCategoryToggle(player, value, id) local mapType = string.gsub(id, "^cat_", "") local turnOn = not categoryActive(mapType) for _, creatorId in ipairs(creatorsOfType(mapType)) do activeCreators[creatorId] = turnOn or nil end publishActiveCreators() -- Many creator buttons changed at once; rebuild to repaint them all. buildFilterUI() end -- Shallow copy so external readers can't mutate our state. function copyActive() local copy = {} for k, v in pairs(activeCreators) do copy[k] = v end return copy end function publishActiveCreators() Global.setTable(ACTIVE_GLOBAL_KEY, copyActive()) end function getActiveCreators() return copyActive() end -- Called by startMenu.startGame: remove the floating panel for the rest of the game. function hideFilterMenu() menuHidden = true self.UI.setXml("") end -- Bring the panel back (e.g. if a pre-game flow ever needs it again). function showFilterMenu() menuHidden = false buildFilterUI() end LuaScriptState{"activeCreators":{"battlemaster_bttf":true,"battlemaster_bttf_ruins":true,"cr5sh":true,"lct1":true,"t5s2":true},"menuHidden":true}XmlUI197GUIDfdf6e7NameBagTransformposX(%posYposZ+rotX Jۚ>rotY`ProtZ@?scaleX?scaleY?scaleZ?NicknameCombat Patrol MapsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?gu?bLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScriptLuaScriptStateXmlUIContainedObjects] 0GUID9200feName CardCustomTransformposXBposY@ 4?posZI$rotX@Nס>rotYf@rotZDscaleX?scaleY?scaleZ?NicknameCombat Patrol 1DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardID1SidewaysCardCustomDeckC20948FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/16954500003545467278/43F2C0C37244CA34EF4CFA3739D60B1AF640217D/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/16954500003545467278/43F2C0C37244CA34EF4CFA3739D60B1AF640217D/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScript@ -- TO CHANGE THE SIZE OF THE AREA FOR THE MAP LOADING/DELETING, READ THIS -- Change the zoneScale size to match a zone you create in game (as reference) -- Be sure the zone extends below the table too ------------------------------------------------------------- zoneScale = {x=44.04, y=73, z=30.15} --30x44 Incursion and Combat Patrol Size ------------------------------------------------------------- function onLoad() self.createButton({ label = "Load Map", font_size = 115, color = {0, 1, 0,}, position = {-0.96, 0.5, 0}, rotation = {0, 90, 0}, scale = {1.3, 1, .7}, width = 570, height = 160, click_function = "loadMap", function_owner = self, }) self.createButton({ label = "Clear Map", font_size = 100, position = {1, 0.5, 0}, rotation = {0, 90, 0}, scale = {.6, .6, .4}, width = 530, height = 130, color = {1, 0, 0}, click_function = "clearMap", function_owner = self, }) end string.split = function(s, delimiter) local result = { } local from = 1 local delim_from, delim_to = string.find( s, delimiter, from ) while delim_from do table.insert( result, string.sub( s, from , delim_from-1 ) ) from = delim_to + 1 delim_from, delim_to = string.find( s, delimiter, from ) end table.insert( result, string.sub( s, from ) ) return result end -- Wipe everything inside the zone except the mats/build tables and any -- MapExclude-tagged object. v2 defers detection a couple frames so the zone -- is populated first. onCleared (optional) runs only AFTER the board is -- verified clear and the zone removed, so a follow-up spawn can never race -- the wipe and leave a previously loaded map behind. function wipeMapZone(zone, onCleared) -- @@MAP_ZONES_V2@@ deferred detection Wait.frames(function() local keep = {["28865a"]=true, ["4ee1f2"]=true, ["6012bf"]=true, ["948ce5"]=true, ["e7ca6e"]=true} for _, obj in ipairs(zone.getObjects()) do if obj ~= self and not keep[obj.getGUID()] and obj.getGMNotes() ~= "MapExclude" then obj.destruct() end end zone.destruct() if onCleared then Wait.frames(onCleared, 2) end end, 2) end -- Clear Map reuses the wipe with no follow-up spawn. function scriptzoneCallback(zone) wipeMapZone(zone, nil) end function spawnMapTerrain() for _, objectJSON in ipairs(objectJSONs) do spawnObjectJSON({ json = objectJSON }) end end function loadMap(objectClicked, clickerColor, altClickUsed) do local _m = getObjectFromGUID("738804"); if _m then _m.call("onMapCardLoaded", {name = self.getName(), guid = self.getGUID()}) end end -- Spawn the scripting zone; its callback wipes the board and, once it is -- 100% clear, spawns this card's terrain. Gating the spawn on the wipe -- (instead of a fixed timer running alongside it) guarantees nothing from -- a previously loaded map survives next to the new one. spawnObject({ position = {x=0, y=26, z=0}, scale = zoneScale, type = 'ScriptingTrigger', callback_function = function(zone) wipeMapZone(zone, spawnMapTerrain) end, callback_owner = self, }) end function clearMap(objectClicked, clickerColor, altClickUsed) spawnObject({ position = {x=0, y=26, z=0}, scale = zoneScale, type = 'ScriptingTrigger', callback_function = scriptzoneCallback, callback_owner = self, }) end objectJSONs = { [[{ "GUID": "7f414f", "Name": "Custom_Model", "Transform": { "posX": 0.0, "posY": -10.13, "posZ": 0.0, "rotX": 0.0, "rotY": 0.0, "rotZ": 0.0, "scaleX": 1.2206, "scaleY": 1.06, "scaleZ": 0.835 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": true, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/879750610978796176/4A5A65543B98BCFBF57E910D06EC984208223D38/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1043093746445993785/76099AB45F8127F2172E1B9BEBDE4AF712C3B160/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "function onload() \r\n self.interactable = false\r\n self.gizmo_selectable = false\r\nend", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "f63f0b", "Name": "Custom_Model", "Transform": { "posX": -9.394501, "posY": 0.980394065, "posZ": 12.9603748, "rotX": 4.60334741E-05, "rotY": 180.0328, "rotZ": -0.000135370123, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_home_red" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/16883380972164515698/5BA49DFD4787ECD0CDB246E4A4A6CC9AB0CD5291/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "3a6057", "Name": "Custom_Model", "Transform": { "posX": -20.2218552, "posY": 1.02045524, "posZ": 7.700258, "rotX": 0.233742252, "rotY": 269.9447, "rotZ": 0.0620989129, "scaleX": 0.5459259, "scaleY": 0.89, "scaleZ": 0.8900023 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1689399871832891845/3F6D3FC61E2D12329A75B0193BDE1D5289D232DE/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1689399871832891695/7D30E9BF25563CB0319C9D88A3F22093B3A3F557/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1689399871832891845/3F6D3FC61E2D12329A75B0193BDE1D5289D232DE/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 0, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "21fd1b", "Name": "Custom_Assetbundle", "Transform": { "posX": -2.33078361, "posY": -0.00414852472, "posZ": -1.29570043, "rotX": 0.06440603, "rotY": 90.01353, "rotZ": 359.767883, "scaleX": 1.00374949, "scaleY": 1.12359607, "scaleZ": 1.83174849 }, "Nickname": "Barricades | [CC99CC]Obstacle[-] [00BFFF]Defence Line, Light Cover, Heavy Cover, Defensible, Unstable Position, Difficult Ground[-]", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/1663475995186911202/037A7198418E12C210B004CBBFA317A77D1B0974/", "AssetbundleSecondaryURL": "https://steamusercontent-a.akamaihd.net/ugc/1479948333016900141/C644B80C46B7019B83ACDC07B04312784194518B/", "MaterialIndex": 0, "TypeIndex": 1, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "ed2cb0", "Name": "Custom_Model", "Transform": { "posX": -20.23522, "posY": 1.01973915, "posZ": 12.4050646, "rotX": 0.000122083162, "rotY": 270.310028, "rotZ": 4.82876058E-05, "scaleX": 0.625000358, "scaleY": 0.625000358, "scaleZ": 0.625000358 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863413633/A06C0CB118961517025223D49C8F00401D4A9E54/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "Convex": true, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "d41781", "Name": "Custom_Model", "Transform": { "posX": -2.09229255, "posY": -1.24508716E-07, "posZ": 0.080636166, "rotX": 1.83850113E-06, "rotY": -1.02452841E-05, "rotZ": -3.43048E-06, "scaleX": 1.00000024, "scaleY": 1.0, "scaleZ": 1.00000024 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863413633/A06C0CB118961517025223D49C8F00401D4A9E54/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "Convex": true, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "6c4768", "Name": "Custom_Assetbundle", "Transform": { "posX": -0.0457633659, "posY": 0.00827856548, "posZ": -1.14333844, "rotX": 359.967163, "rotY": 270.0066, "rotZ": 0.2215826, "scaleX": 1.59999919, "scaleY": 1.59999907, "scaleZ": 1.59999931 }, "Nickname": "Barricades | [CC99CC]Obstacle[-] [00BFFF]Defence Line, Light Cover, Heavy Cover, Defensible, Unstable Position, Difficult Ground[-]", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/1663475995186909916/C9885DE06B572F7136D4F9A42C4B28990D352E7F/", "AssetbundleSecondaryURL": "https://steamusercontent-a.akamaihd.net/ugc/1479948333016900141/C644B80C46B7019B83ACDC07B04312784194518B/", "MaterialIndex": 0, "TypeIndex": 1, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "793fb0", "Name": "Custom_Model", "Transform": { "posX": -0.153702438, "posY": 2.0187106E-07, "posZ": -2.192452, "rotX": 9.919264E-07, "rotY": 4.99447218E-14, "rotZ": 5.769827E-06, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863413633/A06C0CB118961517025223D49C8F00401D4A9E54/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "Convex": true, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "dc1e53", "Name": "Custom_Assetbundle", "Transform": { "posX": -1.07550859, "posY": 0.008957845, "posZ": 0.0131313251, "rotX": 1.04387367, "rotY": 269.9985, "rotZ": 359.597015, "scaleX": 1.59999943, "scaleY": 1.59999931, "scaleZ": 1.59999919 }, "Nickname": "Barricades | [CC99CC]Obstacle[-] [00BFFF]Defence Line, Light Cover, Heavy Cover, Defensible, Unstable Position, Difficult Ground[-]", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/1663475995186909916/C9885DE06B572F7136D4F9A42C4B28990D352E7F/", "AssetbundleSecondaryURL": "https://steamusercontent-a.akamaihd.net/ugc/1479948333016900141/C644B80C46B7019B83ACDC07B04312784194518B/", "MaterialIndex": 0, "TypeIndex": 1, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "77dd2d", "Name": "Custom_Model", "Transform": { "posX": -17.0, "posY": 0.980448246, "posZ": -3.00000334, "rotX": 0.00139817293, "rotY": 90.0287247, "rotZ": 0.000954454765, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "6e50e7", "Name": "Custom_Model", "Transform": { "posX": -13.0768766, "posY": 3.019753, "posZ": 11.4131451, "rotX": -0.000142110555, "rotY": 0.0125626884, "rotZ": 0.000164382509, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "1": { "GUID": "d52827", "Name": "Custom_Model", "Transform": { "posX": -4.584063, "posY": 3.01975822, "posZ": 6.36626625, "rotX": 1.58231592E-06, "rotY": 359.986267, "rotZ": -4.86936733E-06, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } }, "ChildObjects": [ { "GUID": "769c52", "Name": "Custom_Model", "Transform": { "posX": 0.8701764, "posY": 1.04042065, "posZ": 0.0434796773, "rotX": 1.095526E-06, "rotY": 179.995178, "rotZ": 180.0, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.9218439 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/17078008105170289585/DBCFFA451CFABB738C938ECD4947BDF4409DA906/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/12978352037778725442/FB91F6D2CD8F12D234C19C336EF33E775AE24145/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/17078008105170289585/DBCFFA451CFABB738C938ECD4947BDF4409DA906/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "ce7ba3", "Name": "Custom_Model", "Transform": { "posX": -16.0036221, "posY": 1.01984739, "posZ": -6.212808, "rotX": 0.000960221863, "rotY": 2.03504515E-05, "rotZ": -0.001459, "scaleX": 0.7000004, "scaleY": 0.7000002, "scaleZ": 0.7000004 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 0.9999998, "g": 0.992165864, "b": 0.9999998 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1030707799276647654/A666ED668EBE4CE92239C849AC33FA6268201EE7/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1030707799276647728/C98DEAEA6FB0F4758F16BB9698ED67BDACB3D040/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1030707799276647819/6756DB1DA28553218412AE6F325080297EC3E911/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "be2971", "Name": "Custom_Model", "Transform": { "posX": -8.98901, "posY": 1.021889, "posZ": 0.0502125621, "rotX": -7.48259936E-07, "rotY": 176.888931, "rotZ": 1.96449628E-06, "scaleX": 0.8587723, "scaleY": 0.952459, "scaleZ": 0.8587723 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1680393048673863857/8C7565F9EFCBD1517A93857B38D18382C6667AC8/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1680393048673863969/983F4F94D4269C0DC3AAF94DC51B10447BEED569/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1680393048673863857/8C7565F9EFCBD1517A93857B38D18382C6667AC8/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 0, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "f6f4a1", "Name": "Custom_Model", "Transform": { "posX": -5.0, "posY": 0.9803881, "posZ": -9.0, "rotX": 6.70459E-09, "rotY": 180.009827, "rotZ": -6.60295356E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_neutral" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "00ad1e", "Name": "Custom_Model", "Transform": { "posX": -11.0, "posY": 0.980388045, "posZ": 2.99999952, "rotX": -7.938674E-07, "rotY": 90.0, "rotZ": 1.85129224E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "45a0f4", "Name": "Custom_Model", "Transform": { "posX": -8.0830965, "posY": 3.01975584, "posZ": -11.2374039, "rotX": -1.64800422E-05, "rotY": 90.12108, "rotZ": 0.000142353019, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.877410948 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "acc80b", "Name": "Custom_Model", "Transform": { "posX": -8.0830965, "posY": 3.01975584, "posZ": -11.2374039, "rotX": -1.64800422E-05, "rotY": 90.12108, "rotZ": 0.000142353019, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.877410948 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "774b61", "Name": "Custom_Model", "Transform": { "posX": 0.481254965, "posY": 1.040521, "posZ": 1.40920544, "rotX": -4.386846E-06, "rotY": 89.99933, "rotZ": 180.0, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.9218439 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/15820265501726824465/97938A4784E70D0E340C930F86FB8DFB74332F3E/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/12978352037778725442/FB91F6D2CD8F12D234C19C336EF33E775AE24145/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/15820265501726824465/97938A4784E70D0E340C930F86FB8DFB74332F3E/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] } } }]], [[{ "GUID": "f021aa", "Name": "Custom_Model", "Transform": { "posX": -2.0, "posY": 0.980388045, "posZ": -7.15255737E-07, "rotX": 2.20518217E-07, "rotY": 90.00002, "rotZ": -1.96742022E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "c8b410", "Name": "Custom_Model", "Transform": { "posX": -1.00000012, "posY": 1.02418673, "posZ": -4.00000048, "rotX": 4.467511E-07, "rotY": 180.0, "rotZ": 7.938878E-08, "scaleX": 0.4345803, "scaleY": 0.442857623, "scaleZ": 0.436077565 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://pastebin.com/raw/ZZD26Nfe", "DiffuseURL": "http://orig03.deviantart.net/392f/f/2017/093/0/4/fence_by_itchydani3l-db4l4hj.png", "NormalURL": "", "ColliderURL": "https://pastebin.com/raw/EBqMvFMv", "Convex": true, "MaterialIndex": 2, "TypeIndex": 1, "CustomShader": { "SpecularColor": { "r": 0.7372549, "g": 0.7372549, "b": 0.7372549 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "6a4172", "Name": "Custom_Model", "Transform": { "posX": 2.0, "posY": 1.000776, "posZ": 7.15255737E-07, "rotX": 2.20518388E-07, "rotY": 270.0, "rotZ": -1.96742022E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "2d08dd", "Name": "Custom_Model", "Transform": { "posX": 1.00000012, "posY": 1.04457474, "posZ": 4.00000048, "rotX": 4.467511E-07, "rotY": 1.00179122E-05, "rotZ": 7.938878E-08, "scaleX": 0.4345803, "scaleY": 0.442857623, "scaleZ": 0.436077565 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://pastebin.com/raw/ZZD26Nfe", "DiffuseURL": "http://orig03.deviantart.net/392f/f/2017/093/0/4/fence_by_itchydani3l-db4l4hj.png", "NormalURL": "", "ColliderURL": "https://pastebin.com/raw/EBqMvFMv", "Convex": true, "MaterialIndex": 2, "TypeIndex": 1, "CustomShader": { "SpecularColor": { "r": 0.7372549, "g": 0.7372549, "b": 0.7372549 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "655541", "Name": "Custom_Model", "Transform": { "posX": 5.0, "posY": 1.00077617, "posZ": 9.0, "rotX": 6.70459E-09, "rotY": 0.009818169, "rotZ": -6.60295356E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_neutral" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "253e2c", "Name": "Custom_Model", "Transform": { "posX": 8.0830965, "posY": 3.040144, "posZ": 11.2374039, "rotX": -1.64800367E-05, "rotY": 270.1211, "rotZ": 0.000142353034, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.877410948 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "faae18", "Name": "Custom_Model", "Transform": { "posX": 8.0830965, "posY": 3.040144, "posZ": 11.2374039, "rotX": -1.64800367E-05, "rotY": 270.1211, "rotZ": 0.000142353034, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.877410948 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/17059421103808202296/4FDB086C331C928BA20C68A4BC23EA948516C770/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "774b61", "Name": "Custom_Model", "Transform": { "posX": 0.481254965, "posY": 1.040521, "posZ": 1.40920544, "rotX": -4.386846E-06, "rotY": 89.99933, "rotZ": 180.0, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.9218439 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/15820265501726824465/97938A4784E70D0E340C930F86FB8DFB74332F3E/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/12978352037778725442/FB91F6D2CD8F12D234C19C336EF33E775AE24145/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/15820265501726824465/97938A4784E70D0E340C930F86FB8DFB74332F3E/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] } } }]], [[{ "GUID": "07b209", "Name": "Custom_Model", "Transform": { "posX": 11.0, "posY": 1.000776, "posZ": -2.99999952, "rotX": -7.93867571E-07, "rotY": 270.0, "rotZ": 1.85129252E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "955226", "Name": "Custom_Model", "Transform": { "posX": 8.98901, "posY": 1.0422771, "posZ": -0.0502125621, "rotX": -7.482599E-07, "rotY": 356.888916, "rotZ": 1.96449628E-06, "scaleX": 0.8587723, "scaleY": 0.952459, "scaleZ": 0.8587723 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1680393048673863857/8C7565F9EFCBD1517A93857B38D18382C6667AC8/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1680393048673863969/983F4F94D4269C0DC3AAF94DC51B10447BEED569/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1680393048673863857/8C7565F9EFCBD1517A93857B38D18382C6667AC8/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 0, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "d3c7cb", "Name": "Custom_Model", "Transform": { "posX": 9.394501, "posY": 1.00078213, "posZ": -12.9603748, "rotX": 4.60334668E-05, "rotY": 0.0327949449, "rotZ": -0.000135370123, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_home_blue" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/16883380972164515698/5BA49DFD4787ECD0CDB246E4A4A6CC9AB0CD5291/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "e74ed8", "Name": "Custom_Model", "Transform": { "posX": 13.0768766, "posY": 3.040141, "posZ": -11.4131451, "rotX": -0.000142110555, "rotY": 180.012558, "rotZ": 0.000164382582, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "1": { "GUID": "d52827", "Name": "Custom_Model", "Transform": { "posX": -4.584063, "posY": 3.01975822, "posZ": 6.36626625, "rotX": 1.58231592E-06, "rotY": 359.986267, "rotZ": -4.86936733E-06, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/16617421087887886765/AB589628DDCF4E903EE58098E1D2ADFB31F083EA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/15619035763489937620/C34044813F76223EDF41FBC1E3878E6266F080E6/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } }, "ChildObjects": [ { "GUID": "769c52", "Name": "Custom_Model", "Transform": { "posX": 0.8701764, "posY": 1.04042065, "posZ": 0.0434796773, "rotX": 1.095526E-06, "rotY": 179.995178, "rotZ": 180.0, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 0.9218439 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/17078008105170289585/DBCFFA451CFABB738C938ECD4947BDF4409DA906/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/12978352037778725442/FB91F6D2CD8F12D234C19C336EF33E775AE24145/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/17078008105170289585/DBCFFA451CFABB738C938ECD4947BDF4409DA906/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "1f6815", "Name": "Custom_Model", "Transform": { "posX": 17.0, "posY": 1.00083637, "posZ": 3.00000334, "rotX": 0.00139817339, "rotY": 270.028717, "rotZ": 0.000954455056, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/14124440623579246927/18AF1EADFAF61F900DC8E27E134D3CE955F09F10/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "e56800", "Name": "Custom_Model", "Transform": { "posX": 16.0036221, "posY": 1.04023552, "posZ": 6.212808, "rotX": 0.000960221863, "rotY": 180.000015, "rotZ": -0.001459, "scaleX": 0.7000004, "scaleY": 0.7000002, "scaleZ": 0.7000004 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 0.9999998, "g": 0.992165864, "b": 0.9999998 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1030707799276647654/A666ED668EBE4CE92239C849AC33FA6268201EE7/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1030707799276647728/C98DEAEA6FB0F4758F16BB9698ED67BDACB3D040/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1030707799276647819/6756DB1DA28553218412AE6F325080297EC3E911/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "9f6db2", "Name": "Custom_Model", "Transform": { "posX": 20.2218552, "posY": 1.04084325, "posZ": -7.700258, "rotX": 0.233742356, "rotY": 89.94471, "rotZ": 0.0620989278, "scaleX": 0.5459259, "scaleY": 0.89, "scaleZ": 0.8900023 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1689399871832891845/3F6D3FC61E2D12329A75B0193BDE1D5289D232DE/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1689399871832891695/7D30E9BF25563CB0319C9D88A3F22093B3A3F557/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1689399871832891845/3F6D3FC61E2D12329A75B0193BDE1D5289D232DE/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 0, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "21fd1b", "Name": "Custom_Assetbundle", "Transform": { "posX": -2.33078361, "posY": -0.00414852472, "posZ": -1.29570043, "rotX": 0.06440603, "rotY": 90.01353, "rotZ": 359.767883, "scaleX": 1.00374949, "scaleY": 1.12359607, "scaleZ": 1.83174849 }, "Nickname": "Barricades | [CC99CC]Obstacle[-] [00BFFF]Defence Line, Light Cover, Heavy Cover, Defensible, Unstable Position, Difficult Ground[-]", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/1663475995186911202/037A7198418E12C210B004CBBFA317A77D1B0974/", "AssetbundleSecondaryURL": "https://steamusercontent-a.akamaihd.net/ugc/1479948333016900141/C644B80C46B7019B83ACDC07B04312784194518B/", "MaterialIndex": 0, "TypeIndex": 1, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "11b589", "Name": "Custom_Model", "Transform": { "posX": 20.23522, "posY": 1.04012728, "posZ": -12.4050646, "rotX": 0.000122083191, "rotY": 90.31003, "rotZ": 4.82876167E-05, "scaleX": 0.625000358, "scaleY": 0.625000358, "scaleZ": 0.625000358 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863413633/A06C0CB118961517025223D49C8F00401D4A9E54/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "Convex": true, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "d41781", "Name": "Custom_Model", "Transform": { "posX": -2.09229255, "posY": -1.24508716E-07, "posZ": 0.080636166, "rotX": 1.83850113E-06, "rotY": -1.02452841E-05, "rotZ": -3.43048E-06, "scaleX": 1.00000024, "scaleY": 1.0, "scaleZ": 1.00000024 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863413633/A06C0CB118961517025223D49C8F00401D4A9E54/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "Convex": true, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "6c4768", "Name": "Custom_Assetbundle", "Transform": { "posX": -0.0457633659, "posY": 0.00827856548, "posZ": -1.14333844, "rotX": 359.967163, "rotY": 270.0066, "rotZ": 0.2215826, "scaleX": 1.59999919, "scaleY": 1.59999907, "scaleZ": 1.59999931 }, "Nickname": "Barricades | [CC99CC]Obstacle[-] [00BFFF]Defence Line, Light Cover, Heavy Cover, Defensible, Unstable Position, Difficult Ground[-]", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/1663475995186909916/C9885DE06B572F7136D4F9A42C4B28990D352E7F/", "AssetbundleSecondaryURL": "https://steamusercontent-a.akamaihd.net/ugc/1479948333016900141/C644B80C46B7019B83ACDC07B04312784194518B/", "MaterialIndex": 0, "TypeIndex": 1, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "793fb0", "Name": "Custom_Model", "Transform": { "posX": -0.153702438, "posY": 2.0187106E-07, "posZ": -2.192452, "rotX": 9.919264E-07, "rotY": 4.99447218E-14, "rotZ": 5.769827E-06, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863413633/A06C0CB118961517025223D49C8F00401D4A9E54/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043863414920/9EED599658C77372CC6C10C0B4603AA69634DD99/", "Convex": true, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "dc1e53", "Name": "Custom_Assetbundle", "Transform": { "posX": -1.07550859, "posY": 0.008957845, "posZ": 0.0131313251, "rotX": 1.04387367, "rotY": 269.9985, "rotZ": 359.597015, "scaleX": 1.59999943, "scaleY": 1.59999931, "scaleZ": 1.59999919 }, "Nickname": "Barricades | [CC99CC]Obstacle[-] [00BFFF]Defence Line, Light Cover, Heavy Cover, Defensible, Unstable Position, Difficult Ground[-]", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/1663475995186909916/C9885DE06B572F7136D4F9A42C4B28990D352E7F/", "AssetbundleSecondaryURL": "https://steamusercontent-a.akamaihd.net/ugc/1479948333016900141/C644B80C46B7019B83ACDC07B04312784194518B/", "MaterialIndex": 0, "TypeIndex": 1, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } ] }]], } LuaScriptStateXmlUI1JGUIDc2f5b5Name CardCustomTransformposXs"posY 4?posZ$rotXrq>rotY-f@rotZ>scaleX?scaleY?scaleZ?NicknameCombat Patrol 2DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardID2SidewaysCardCustomDeckC20968FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/12547535006516769936/0C45FE1ADB85ED27FF691A0C2168C2E0D75CA627/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/12547535006516769936/0C45FE1ADB85ED27FF691A0C2168C2E0D75CA627/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScript"G -- TO CHANGE THE SIZE OF THE AREA FOR THE MAP LOADING/DELETING, READ THIS -- Change the zoneScale size to match a zone you create in game (as reference) -- Be sure the zone extends below the table too ------------------------------------------------------------- zoneScale = {x=44.04, y=73, z=30.15} --30x44 Incursion and Combat Patrol Size ------------------------------------------------------------- function onLoad() self.createButton({ label = "Load Map", font_size = 115, color = {0, 1, 0,}, position = {-0.96, 0.5, 0}, rotation = {0, 90, 0}, scale = {1.3, 1, .7}, width = 570, height = 160, click_function = "loadMap", function_owner = self, }) self.createButton({ label = "Clear Map", font_size = 100, position = {1, 0.5, 0}, rotation = {0, 90, 0}, scale = {.6, .6, .4}, width = 530, height = 130, color = {1, 0, 0}, click_function = "clearMap", function_owner = self, }) end string.split = function(s, delimiter) local result = { } local from = 1 local delim_from, delim_to = string.find( s, delimiter, from ) while delim_from do table.insert( result, string.sub( s, from , delim_from-1 ) ) from = delim_to + 1 delim_from, delim_to = string.find( s, delimiter, from ) end table.insert( result, string.sub( s, from ) ) return result end -- Wipe everything inside the zone except the mats/build tables and any -- MapExclude-tagged object. v2 defers detection a couple frames so the zone -- is populated first. onCleared (optional) runs only AFTER the board is -- verified clear and the zone removed, so a follow-up spawn can never race -- the wipe and leave a previously loaded map behind. function wipeMapZone(zone, onCleared) -- @@MAP_ZONES_V2@@ deferred detection Wait.frames(function() local keep = {["28865a"]=true, ["4ee1f2"]=true, ["6012bf"]=true, ["948ce5"]=true, ["e7ca6e"]=true} for _, obj in ipairs(zone.getObjects()) do if obj ~= self and not keep[obj.getGUID()] and obj.getGMNotes() ~= "MapExclude" then obj.destruct() end end zone.destruct() if onCleared then Wait.frames(onCleared, 2) end end, 2) end -- Clear Map reuses the wipe with no follow-up spawn. function scriptzoneCallback(zone) wipeMapZone(zone, nil) end function spawnMapTerrain() for _, objectJSON in ipairs(objectJSONs) do spawnObjectJSON({ json = objectJSON }) end end function loadMap(objectClicked, clickerColor, altClickUsed) do local _m = getObjectFromGUID("738804"); if _m then _m.call("onMapCardLoaded", {name = self.getName(), guid = self.getGUID()}) end end -- Spawn the scripting zone; its callback wipes the board and, once it is -- 100% clear, spawns this card's terrain. Gating the spawn on the wipe -- (instead of a fixed timer running alongside it) guarantees nothing from -- a previously loaded map survives next to the new one. spawnObject({ position = {x=0, y=26, z=0}, scale = zoneScale, type = 'ScriptingTrigger', callback_function = function(zone) wipeMapZone(zone, spawnMapTerrain) end, callback_owner = self, }) end function clearMap(objectClicked, clickerColor, altClickUsed) spawnObject({ position = {x=0, y=26, z=0}, scale = zoneScale, type = 'ScriptingTrigger', callback_function = scriptzoneCallback, callback_owner = self, }) end objectJSONs = { [[{ "GUID": "7f414f", "Name": "Custom_Model", "Transform": { "posX": 0.0, "posY": -10.13, "posZ": 0.0, "rotX": 0.0, "rotY": 0.0, "rotZ": 0.0, "scaleX": 1.2206, "scaleY": 1.06, "scaleZ": 0.835 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": true, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/879750610978796176/4A5A65543B98BCFBF57E910D06EC984208223D38/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2031721158054060806/78D7A0E8246FB2CE6EAA8B7379A6073E6DD4C1CE/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "function onload() \r\n self.interactable = false\r\n self.gizmo_selectable = false\r\nend", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "6cdcca", "Name": "Custom_Model", "Transform": { "posX": -17.1250076, "posY": 1.03772187, "posZ": 1.08790553, "rotX": 0.138719887, "rotY": 224.398148, "rotZ": 359.97702, "scaleX": 1.3499999, "scaleY": 1.3499999, "scaleZ": 1.3499999 }, "Nickname": "Light terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723987/4DEF9C3A9C4EAB226A9354C0CA67CADAFD894324/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "de5a74", "Name": "Custom_Model", "Transform": { "posX": -0.6993279, "posY": -0.00803379249, "posZ": 0.7549688, "rotX": 359.8716, "rotY": 45.0458031, "rotZ": 0.231682017, "scaleX": 0.125581414, "scaleY": 0.166016653, "scaleZ": 0.265116364 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "b995ca", "Name": "Custom_Model", "Transform": { "posX": -0.708459854, "posY": -0.0152157545, "posZ": -0.6941243, "rotX": 359.916626, "rotY": 314.935181, "rotZ": 0.120598637, "scaleX": 0.125581443, "scaleY": 0.166016638, "scaleZ": 0.2651163 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "c388f9", "Name": "Custom_Assetbundle", "Transform": { "posX": -17.9701862, "posY": 0.980388045, "posZ": 1.929429, "rotX": -1.22327046E-08, "rotY": 90.02205, "rotZ": -2.5600555E-09, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_home_blue" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/17340806108804505934/4B46C3DBA9709342C6038E6C339E9183773F7F4F/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "340546", "Name": "Custom_Assetbundle", "Transform": { "posX": -11.5, "posY": 0.980388045, "posZ": 12.5, "rotX": 2.293289E-08, "rotY": 0.000740667, "rotZ": -7.012662E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/18109999757297310215/5DB2EDC94302F2260A40CDE398054AB73C583B2D/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "b98682", "Name": "Custom_Assetbundle", "Transform": { "posX": -16.0, "posY": 0.980388045, "posZ": 6.0, "rotX": -1.86295264E-08, "rotY": 359.991516, "rotZ": -3.02449124E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/12322865955445680032/0919B948C303AE084AD0661B3EDE36FCCBF28FCF/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "0ea0ce", "Name": "Custom_Assetbundle", "Transform": { "posX": -18.5000019, "posY": 0.9803881, "posZ": 4.499999, "rotX": 1.80546316E-08, "rotY": 359.97583, "rotZ": -1.05980192E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16918139172926584908/8C6BC64D270CD20FEEA73D40FBD80633CC72A532/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "18c38c", "Name": "Custom_Model", "Transform": { "posX": -11.8554344, "posY": 1.115844, "posZ": -8.566619, "rotX": 359.963959, "rotY": 90.0, "rotZ": 0.0724505261, "scaleX": 0.921821237, "scaleY": 0.6, "scaleZ": 0.923663855 }, "Nickname": "Dense (4\")", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729945/FC008F35C5755026179642E71E87EEF63A0D2823/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "7a38dd", "Name": "Custom_Model", "Transform": { "posX": 0.255953729, "posY": 2.715735, "posZ": -2.27760625, "rotX": 359.907959, "rotY": 269.9945, "rotZ": 359.986664, "scaleX": -0.38975206, "scaleY": 1.12169731, "scaleZ": -0.104761004 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "d10809", "Name": "Custom_Model", "Transform": { "posX": -2.88788462, "posY": 2.718147, "posZ": 0.101681225, "rotX": 0.0171858575, "rotY": 0.0259741712, "rotZ": 359.9281, "scaleX": -0.5033702, "scaleY": 1.12169719, "scaleZ": 0.120867729 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "14cdbf", "Name": "Custom_Model", "Transform": { "posX": -13.7811441, "posY": 4.0, "posZ": -6.28127861, "rotX": 359.991364, "rotY": 179.947037, "rotZ": -0.00492575066, "scaleX": 0.23, "scaleY": 0.01, "scaleZ": 0.3 }, "Nickname": "Ruins (3\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163474432/1B246E80C5E91E97FF1E7D13775D45BFF03DC72B/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "Convex": true, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "PhysicsMaterial": { "StaticFriction": 0.4, "DynamicFriction": 0.4, "Bounciness": 0.0, "FrictionCombine": 0, "BounceCombine": 0 }, "Rigidbody": { "Mass": 0.0, "Drag": 0.1, "AngularDrag": 0.1, "UseGravity": true } }]], [[{ "GUID": "676491", "Name": "Custom_Model", "Transform": { "posX": -10.8417635, "posY": 1.11449158, "posZ": 6.667982, "rotX": 359.983063, "rotY": 89.99988, "rotZ": 0.0720515, "scaleX": 0.564739764, "scaleY": 0.6, "scaleZ": 0.8753913 }, "Nickname": "Dense (4\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729945/FC008F35C5755026179642E71E87EEF63A0D2823/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "7a38dd", "Name": "Custom_Model", "Transform": { "posX": 0.255953729, "posY": 2.715735, "posZ": -2.27760625, "rotX": 359.907959, "rotY": 269.9945, "rotZ": 359.986664, "scaleX": -0.38975206, "scaleY": 1.12169731, "scaleZ": -0.104761004 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "d10809", "Name": "Custom_Model", "Transform": { "posX": -2.88788462, "posY": 2.718147, "posZ": 0.101681225, "rotX": 0.0171858575, "rotY": 0.0259741712, "rotZ": 359.9281, "scaleX": -0.5033702, "scaleY": 1.12169719, "scaleZ": 0.120867729 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "86be52", "Name": "Custom_Model", "Transform": { "posX": -11.6591825, "posY": 1.03772509, "posZ": 1.04619634, "rotX": 0.139426738, "rotY": 315.000031, "rotZ": 359.976654, "scaleX": 1.3499999, "scaleY": 1.3499999, "scaleZ": 1.3499999 }, "Nickname": "Light terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723987/4DEF9C3A9C4EAB226A9354C0CA67CADAFD894324/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "de5a74", "Name": "Custom_Model", "Transform": { "posX": -0.6993279, "posY": -0.00803379249, "posZ": 0.7549688, "rotX": 359.8716, "rotY": 45.0458031, "rotZ": 0.231682017, "scaleX": 0.125581414, "scaleY": 0.166016653, "scaleZ": 0.265116364 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "b995ca", "Name": "Custom_Model", "Transform": { "posX": -0.708459854, "posY": -0.0152157545, "posZ": -0.6941243, "rotX": 359.916626, "rotY": 314.935181, "rotZ": 0.120598637, "scaleX": 0.125581443, "scaleY": 0.166016638, "scaleZ": 0.2651163 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "187913", "Name": "Custom_Model", "Transform": { "posX": -12.4371042, "posY": 4.0, "posZ": 7.873564, "rotX": -0.000277350016, "rotY": 90.0010452, "rotZ": -0.00200599339, "scaleX": 0.158396229, "scaleY": 0.01, "scaleZ": 0.185047016 }, "Nickname": "Ruins (3\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163474432/1B246E80C5E91E97FF1E7D13775D45BFF03DC72B/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "Convex": true, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "PhysicsMaterial": { "StaticFriction": 0.4, "DynamicFriction": 0.4, "Bounciness": 0.0, "FrictionCombine": 0, "BounceCombine": 0 }, "Rigidbody": { "Mass": 0.0, "Drag": 0.1, "AngularDrag": 0.1, "UseGravity": true } }]], [[{ "GUID": "93ae6a", "Name": "Custom_Assetbundle", "Transform": { "posX": -7.0, "posY": 0.980388045, "posZ": -2.0, "rotX": -1.16394619E-06, "rotY": 90.02927, "rotZ": 5.44969936E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/15633617285222415204/5306AA649D2877AFEF7FEBDFCE3052F6E9977E98/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "ea2098", "Name": "Custom_Assetbundle", "Transform": { "posX": -14.9999981, "posY": 0.980388, "posZ": -1.00000036, "rotX": -4.06588256E-08, "rotY": 359.982819, "rotZ": -7.698049E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16079218023307560393/A11A66EA4B73E650059F86573FCA12C91492E2B0/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "adaa49", "Name": "Custom_Model", "Transform": { "posX": -6.00660324, "posY": 1.01975811, "posZ": -4.899242, "rotX": 3.9188044E-06, "rotY": 269.98645, "rotZ": -5.016485E-07, "scaleX": 0.27, "scaleY": 0.3569356, "scaleZ": 0.57 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "f3de8c", "Name": "Custom_Assetbundle", "Transform": { "posX": -0.999999762, "posY": 0.980388045, "posZ": 8.0, "rotX": 6.088942E-07, "rotY": 269.991119, "rotZ": 7.98032147E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_neutral" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/12322865955445680032/0919B948C303AE084AD0661B3EDE36FCCBF28FCF/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "0ea0ce", "Name": "Custom_Assetbundle", "Transform": { "posX": -18.5000019, "posY": 0.9803881, "posZ": 4.499999, "rotX": 1.80546316E-08, "rotY": 359.97583, "rotZ": -1.05980192E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16918139172926584908/8C6BC64D270CD20FEEA73D40FBD80633CC72A532/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "42af8c", "Name": "Custom_Assetbundle", "Transform": { "posX": -5.0, "posY": 1.000776, "posZ": 2.0, "rotX": 4.88790768E-08, "rotY": 1.00179122E-05, "rotZ": -1.30628052E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/15633617285222415204/5306AA649D2877AFEF7FEBDFCE3052F6E9977E98/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "ea2098", "Name": "Custom_Assetbundle", "Transform": { "posX": -14.9999981, "posY": 0.980388, "posZ": -1.00000036, "rotX": -4.06588256E-08, "rotY": 359.982819, "rotZ": -7.698049E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16079218023307560393/A11A66EA4B73E650059F86573FCA12C91492E2B0/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "3deeec", "Name": "Custom_Model", "Transform": { "posX": -3.03017259, "posY": 1.344676, "posZ": 11.4405489, "rotX": 8.029641E-05, "rotY": 90.0021, "rotZ": 0.0545158461, "scaleX": 2.199999, "scaleY": 2.199999, "scaleZ": 2.199999 }, "Nickname": "Dense", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729945/FC008F35C5755026179642E71E87EEF63A0D2823/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "efe8e6", "Name": "Custom_Model", "Transform": { "posX": -1.92398238, "posY": 1.043503, "posZ": 3.076157, "rotX": 0.0451543853, "rotY": 179.991486, "rotZ": 3.74965362E-06, "scaleX": 0.237096831, "scaleY": 0.8129034, "scaleZ": 0.210000038 }, "Nickname": "Dense", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 0.485355079, "g": 0.8763066, "b": 0.285481781 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://drive.google.com/uc?export=download&id=0BwpXdiEgZ5GzMllIZVI1MEcwU3M", "DiffuseURL": "https://i.imgur.com/vkjnwMR.jpg", "NormalURL": "http://i.imgur.com/OMj4RFQ.jpg", "ColliderURL": "https://drive.google.com/uc?export=download&id=0BwpXdiEgZ5GzeGNBY2oxR1NweTQ", "Convex": false, "MaterialIndex": 2, "TypeIndex": 1, "CustomShader": { "SpecularColor": { "r": 0.7372549, "g": 0.7372549, "b": 0.7372549 }, "SpecularIntensity": 0.0, "SpecularSharpness": 7.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "5a5652", "Name": "Custom_Assetbundle", "Transform": { "posX": 5.0, "posY": 0.980388045, "posZ": -2.0, "rotX": 4.88790768E-08, "rotY": 180.0, "rotZ": -1.30628052E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/15633617285222415204/5306AA649D2877AFEF7FEBDFCE3052F6E9977E98/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "ea2098", "Name": "Custom_Assetbundle", "Transform": { "posX": -14.9999981, "posY": 0.980388, "posZ": -1.00000036, "rotX": -4.06588256E-08, "rotY": 359.982819, "rotZ": -7.698049E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16079218023307560393/A11A66EA4B73E650059F86573FCA12C91492E2B0/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "0111a9", "Name": "Custom_Model", "Transform": { "posX": 1.92398238, "posY": 1.02311492, "posZ": -3.076157, "rotX": 0.0451543853, "rotY": 359.991455, "rotZ": 3.7496543E-06, "scaleX": 0.237096831, "scaleY": 0.8129034, "scaleZ": 0.210000038 }, "Nickname": "Dense", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 0.485355079, "g": 0.8763066, "b": 0.285481781 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://drive.google.com/uc?export=download&id=0BwpXdiEgZ5GzMllIZVI1MEcwU3M", "DiffuseURL": "https://i.imgur.com/vkjnwMR.jpg", "NormalURL": "http://i.imgur.com/OMj4RFQ.jpg", "ColliderURL": "https://drive.google.com/uc?export=download&id=0BwpXdiEgZ5GzeGNBY2oxR1NweTQ", "Convex": false, "MaterialIndex": 2, "TypeIndex": 1, "CustomShader": { "SpecularColor": { "r": 0.7372549, "g": 0.7372549, "b": 0.7372549 }, "SpecularIntensity": 0.0, "SpecularSharpness": 7.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "943f04", "Name": "Custom_Assetbundle", "Transform": { "posX": 0.999999762, "posY": 1.000776, "posZ": -8.0, "rotX": 6.088941E-07, "rotY": 89.99111, "rotZ": 7.980322E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_neutral" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/12322865955445680032/0919B948C303AE084AD0661B3EDE36FCCBF28FCF/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "0ea0ce", "Name": "Custom_Assetbundle", "Transform": { "posX": -18.5000019, "posY": 0.9803881, "posZ": 4.499999, "rotX": 1.80546316E-08, "rotY": 359.97583, "rotZ": -1.05980192E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16918139172926584908/8C6BC64D270CD20FEEA73D40FBD80633CC72A532/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "15b4ce", "Name": "Custom_Model", "Transform": { "posX": 3.03017259, "posY": 1.36506414, "posZ": -11.4405489, "rotX": 8.029475E-05, "rotY": 270.0021, "rotZ": 0.05451584, "scaleX": 2.199999, "scaleY": 2.199999, "scaleZ": 2.199999 }, "Nickname": "Dense", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729945/FC008F35C5755026179642E71E87EEF63A0D2823/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "e9c2ce", "Name": "Custom_Assetbundle", "Transform": { "posX": 7.0, "posY": 1.000776, "posZ": 2.0, "rotX": -1.16394642E-06, "rotY": 270.029266, "rotZ": 5.44969936E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/15633617285222415204/5306AA649D2877AFEF7FEBDFCE3052F6E9977E98/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "ea2098", "Name": "Custom_Assetbundle", "Transform": { "posX": -14.9999981, "posY": 0.980388, "posZ": -1.00000036, "rotX": -4.06588256E-08, "rotY": 359.982819, "rotZ": -7.698049E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16079218023307560393/A11A66EA4B73E650059F86573FCA12C91492E2B0/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "8f7597", "Name": "Custom_Model", "Transform": { "posX": 6.00660324, "posY": 1.04014611, "posZ": 4.899242, "rotX": 3.9188044E-06, "rotY": 89.98644, "rotZ": -5.016485E-07, "scaleX": 0.27, "scaleY": 0.3569356, "scaleZ": 0.57 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "665804", "Name": "Custom_Assetbundle", "Transform": { "posX": 16.0, "posY": 1.000776, "posZ": -6.0, "rotX": -1.86295281E-08, "rotY": 179.991531, "rotZ": -3.02449124E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/12322865955445680032/0919B948C303AE084AD0661B3EDE36FCCBF28FCF/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "0ea0ce", "Name": "Custom_Assetbundle", "Transform": { "posX": -18.5000019, "posY": 0.9803881, "posZ": 4.499999, "rotX": 1.80546316E-08, "rotY": 359.97583, "rotZ": -1.05980192E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": false, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/16918139172926584908/8C6BC64D270CD20FEEA73D40FBD80633CC72A532/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "eab1af", "Name": "Custom_Model", "Transform": { "posX": 10.8417635, "posY": 1.13487959, "posZ": -6.667982, "rotX": 359.983063, "rotY": 269.999878, "rotZ": 0.0720515, "scaleX": 0.564739764, "scaleY": 0.6, "scaleZ": 0.8753913 }, "Nickname": "Dense (4\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729945/FC008F35C5755026179642E71E87EEF63A0D2823/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "7a38dd", "Name": "Custom_Model", "Transform": { "posX": 0.255953729, "posY": 2.715735, "posZ": -2.27760625, "rotX": 359.907959, "rotY": 269.9945, "rotZ": 359.986664, "scaleX": -0.38975206, "scaleY": 1.12169731, "scaleZ": -0.104761004 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "d10809", "Name": "Custom_Model", "Transform": { "posX": -2.88788462, "posY": 2.718147, "posZ": 0.101681225, "rotX": 0.0171858575, "rotY": 0.0259741712, "rotZ": 359.9281, "scaleX": -0.5033702, "scaleY": 1.12169719, "scaleZ": 0.120867729 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "5eced3", "Name": "Custom_Assetbundle", "Transform": { "posX": 17.9701862, "posY": 1.000776, "posZ": -1.929429, "rotX": -1.22327046E-08, "rotY": 270.022034, "rotZ": -2.56005639E-09, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_home_blue" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/17340806108804505934/4B46C3DBA9709342C6038E6C339E9183773F7F4F/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "States": { "2": { "GUID": "340546", "Name": "Custom_Assetbundle", "Transform": { "posX": -11.5, "posY": 0.980388045, "posZ": 12.5, "rotX": 2.293289E-08, "rotY": 0.000740667, "rotZ": -7.012662E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomAssetbundle": { "AssetbundleURL": "https://steamusercontent-a.akamaihd.net/ugc/18109999757297310215/5DB2EDC94302F2260A40CDE398054AB73C583B2D/", "AssetbundleSecondaryURL": "", "MaterialIndex": 0, "TypeIndex": 0, "LoopingEffectIndex": 0 }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" } } }]], [[{ "GUID": "a91f06", "Name": "Custom_Model", "Transform": { "posX": 11.6591825, "posY": 1.0581131, "posZ": -1.04619634, "rotX": 0.139426783, "rotY": 135.000046, "rotZ": 359.976654, "scaleX": 1.3499999, "scaleY": 1.3499999, "scaleZ": 1.3499999 }, "Nickname": "Light terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723987/4DEF9C3A9C4EAB226A9354C0CA67CADAFD894324/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "de5a74", "Name": "Custom_Model", "Transform": { "posX": -0.6993279, "posY": -0.00803379249, "posZ": 0.7549688, "rotX": 359.8716, "rotY": 45.0458031, "rotZ": 0.231682017, "scaleX": 0.125581414, "scaleY": 0.166016653, "scaleZ": 0.265116364 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "b995ca", "Name": "Custom_Model", "Transform": { "posX": -0.708459854, "posY": -0.0152157545, "posZ": -0.6941243, "rotX": 359.916626, "rotY": 314.935181, "rotZ": 0.120598637, "scaleX": 0.125581443, "scaleY": 0.166016638, "scaleZ": 0.2651163 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "d7db02", "Name": "Custom_Model", "Transform": { "posX": 12.4371042, "posY": 4.020388, "posZ": -7.873564, "rotX": -0.000277350016, "rotY": 270.001038, "rotZ": -0.00200599339, "scaleX": 0.158396229, "scaleY": 0.01, "scaleZ": 0.185047016 }, "Nickname": "Ruins (3\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163474432/1B246E80C5E91E97FF1E7D13775D45BFF03DC72B/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "Convex": true, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "PhysicsMaterial": { "StaticFriction": 0.4, "DynamicFriction": 0.4, "Bounciness": 0.0, "FrictionCombine": 0, "BounceCombine": 0 }, "Rigidbody": { "Mass": 0.0, "Drag": 0.1, "AngularDrag": 0.1, "UseGravity": true } }]], [[{ "GUID": "b3f3ac", "Name": "Custom_Model", "Transform": { "posX": 11.8554344, "posY": 1.13623214, "posZ": 8.566619, "rotX": 359.963959, "rotY": 270.0, "rotZ": 0.0724505261, "scaleX": 0.921821237, "scaleY": 0.6, "scaleZ": 0.923663855 }, "Nickname": "Dense (4\")", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729945/FC008F35C5755026179642E71E87EEF63A0D2823/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839729745/F66029CA4C7183BADB1CDC493C7C3132A54E133F/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "7a38dd", "Name": "Custom_Model", "Transform": { "posX": 0.255953729, "posY": 2.715735, "posZ": -2.27760625, "rotX": 359.907959, "rotY": 269.9945, "rotZ": 359.986664, "scaleX": -0.38975206, "scaleY": 1.12169731, "scaleZ": -0.104761004 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "d10809", "Name": "Custom_Model", "Transform": { "posX": -2.88788462, "posY": 2.718147, "posZ": 0.101681225, "rotX": 0.0171858575, "rotY": 0.0259741712, "rotZ": 359.9281, "scaleX": -0.5033702, "scaleY": 1.12169719, "scaleZ": 0.120867729 }, "Nickname": "Ruins (7\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84845043866421234/32849BD15F6528B97FE1BB2DABD218D982425F13/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163347052/BE213D729763EB03C94EA11BFCD2DCE666E5D3B4/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "" } ] }]], [[{ "GUID": "0a1050", "Name": "Custom_Model", "Transform": { "posX": 13.7811441, "posY": 4.020388, "posZ": 6.28127861, "rotX": 359.991364, "rotY": 359.947021, "rotZ": -0.004925752, "scaleX": 0.23, "scaleY": 0.01, "scaleZ": 0.3 }, "Nickname": "Ruins (3\")", "Description": "Models may only be placed on the bottom level of this terrain feature.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2436887475163474432/1B246E80C5E91E97FF1E7D13775D45BFF03DC72B/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1175950344509459105/BE06F19C326EAFF247136F6B74AFE53430C6AE74/", "Convex": true, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"\n", "LuaScriptState": "", "XmlUI": "", "PhysicsMaterial": { "StaticFriction": 0.4, "DynamicFriction": 0.4, "Bounciness": 0.0, "FrictionCombine": 0, "BounceCombine": 0 }, "Rigidbody": { "Mass": 0.0, "Drag": 0.1, "AngularDrag": 0.1, "UseGravity": true } }]], [[{ "GUID": "74de67", "Name": "Custom_Model", "Transform": { "posX": 17.1250076, "posY": 1.05811, "posZ": -1.08790553, "rotX": 0.138719931, "rotY": 44.39814, "rotZ": 359.97702, "scaleX": 1.3499999, "scaleY": 1.3499999, "scaleZ": 1.3499999 }, "Nickname": "Light terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723987/4DEF9C3A9C4EAB226A9354C0CA67CADAFD894324/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159839723789/6B309DCCA2AC1A33B0D5C151218A0AC5F492F986/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "", "ChildObjects": [ { "GUID": "de5a74", "Name": "Custom_Model", "Transform": { "posX": -0.6993279, "posY": -0.00803379249, "posZ": 0.7549688, "rotX": 359.8716, "rotY": 45.0458031, "rotZ": 0.231682017, "scaleX": 0.125581414, "scaleY": 0.166016653, "scaleZ": 0.265116364 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" }, { "GUID": "b995ca", "Name": "Custom_Model", "Transform": { "posX": -0.708459854, "posY": -0.0152157545, "posZ": -0.6941243, "rotX": 359.916626, "rotY": 314.935181, "rotZ": 0.120598637, "scaleX": 0.125581443, "scaleY": 0.166016638, "scaleZ": 0.2651163 }, "Nickname": "Light terrain", "Description": ".", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121667931/E6148D823502BFA6EEA6DCB11DDD37EC75E4CB69/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660534/97BB6D4726EF04DBFCFCF2FC5D4AD379391331FE/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/928183493121660957/9998DF5C83CDA2D44887FBFE93772978E8BE6322/", "Convex": false, "MaterialIndex": 1, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "\nBCBtype = \"terrain\"", "LuaScriptState": "", "XmlUI": "" } ] }]], } LuaScriptStateXmlUI2GUID7ed2beName CardCustomTransformposX@j@posY 4?posZFj$rotX@osrotY@8f@rotZf>scaleX?scaleY?scaleZ?NicknameCombat Patrol 3DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCardID$3SidewaysCardCustomDeckC20978FaceURLkhttps://steamusercontent-a.akamaihd.net/ugc/11618025985094664442/E10CA7D86576A481266B694B1CDB1B05B05E63C8/BackURLkhttps://steamusercontent-a.akamaihd.net/ugc/11618025985094664442/E10CA7D86576A481266B694B1CDB1B05B05E63C8/NumWidthNumHeightBackIsHiddenUniqueBackTypeLuaScriptΣ -- TO CHANGE THE SIZE OF THE AREA FOR THE MAP LOADING/DELETING, READ THIS -- Change the zoneScale size to match a zone you create in game (as reference) -- Be sure the zone extends below the table too ------------------------------------------------------------- zoneScale = {x=44.04, y=73, z=30.15} --30x44 Incursion and Combat Patrol Size ------------------------------------------------------------- function onLoad() self.createButton({ label = "Load Map", font_size = 115, color = {0, 1, 0,}, position = {-0.96, 0.5, 0}, rotation = {0, 90, 0}, scale = {1.3, 1, .7}, width = 570, height = 160, click_function = "loadMap", function_owner = self, }) self.createButton({ label = "Clear Map", font_size = 100, position = {1, 0.5, 0}, rotation = {0, 90, 0}, scale = {.6, .6, .4}, width = 530, height = 130, color = {1, 0, 0}, click_function = "clearMap", function_owner = self, }) end string.split = function(s, delimiter) local result = { } local from = 1 local delim_from, delim_to = string.find( s, delimiter, from ) while delim_from do table.insert( result, string.sub( s, from , delim_from-1 ) ) from = delim_to + 1 delim_from, delim_to = string.find( s, delimiter, from ) end table.insert( result, string.sub( s, from ) ) return result end -- Wipe everything inside the zone except the mats/build tables and any -- MapExclude-tagged object. v2 defers detection a couple frames so the zone -- is populated first. onCleared (optional) runs only AFTER the board is -- verified clear and the zone removed, so a follow-up spawn can never race -- the wipe and leave a previously loaded map behind. function wipeMapZone(zone, onCleared) -- @@MAP_ZONES_V2@@ deferred detection Wait.frames(function() local keep = {["28865a"]=true, ["4ee1f2"]=true, ["6012bf"]=true, ["948ce5"]=true, ["e7ca6e"]=true} for _, obj in ipairs(zone.getObjects()) do if obj ~= self and not keep[obj.getGUID()] and obj.getGMNotes() ~= "MapExclude" then obj.destruct() end end zone.destruct() if onCleared then Wait.frames(onCleared, 2) end end, 2) end -- Clear Map reuses the wipe with no follow-up spawn. function scriptzoneCallback(zone) wipeMapZone(zone, nil) end function spawnMapTerrain() for _, objectJSON in ipairs(objectJSONs) do spawnObjectJSON({ json = objectJSON }) end end function loadMap(objectClicked, clickerColor, altClickUsed) do local _m = getObjectFromGUID("738804"); if _m then _m.call("onMapCardLoaded", {name = self.getName(), guid = self.getGUID()}) end end -- Spawn the scripting zone; its callback wipes the board and, once it is -- 100% clear, spawns this card's terrain. Gating the spawn on the wipe -- (instead of a fixed timer running alongside it) guarantees nothing from -- a previously loaded map survives next to the new one. spawnObject({ position = {x=0, y=26, z=0}, scale = zoneScale, type = 'ScriptingTrigger', callback_function = function(zone) wipeMapZone(zone, spawnMapTerrain) end, callback_owner = self, }) end function clearMap(objectClicked, clickerColor, altClickUsed) spawnObject({ position = {x=0, y=26, z=0}, scale = zoneScale, type = 'ScriptingTrigger', callback_function = scriptzoneCallback, callback_owner = self, }) end objectJSONs = { [[{ "GUID": "7f414f", "Name": "Custom_Model", "Transform": { "posX": 0.0, "posY": -10.13, "posZ": 0.0, "rotX": 0.0, "rotY": 0.0, "rotZ": 0.0, "scaleX": 1.2206, "scaleY": 1.06, "scaleZ": 0.835 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": true, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/879750610978796176/4A5A65543B98BCFBF57E910D06EC984208223D38/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2031721158053390855/E5D77D94FE11FAA8D3FC56B14F2FC8DD0F10B9F1/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "function onload() \r\n self.interactable = false\r\n self.gizmo_selectable = false\r\nend", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "d7c2d9", "Name": "Custom_Model", "Transform": { "posX": -12.1863508, "posY": 0.9803869, "posZ": 0.8824916, "rotX": -6.442058E-06, "rotY": 269.999939, "rotZ": 4.01314865E-05, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_home_red" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/16883380972164515698/5BA49DFD4787ECD0CDB246E4A4A6CC9AB0CD5291/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "e63224", "Name": "Custom_Model", "Transform": { "posX": -18.4457932, "posY": 1.04251361, "posZ": 10.1127453, "rotX": 1.09800076E-05, "rotY": 270.0464, "rotZ": -1.08751145E-07, "scaleX": 1.20000017, "scaleY": 1.20000017, "scaleZ": 1.20000017 }, "Nickname": "Light Terrain", "Description": "Small Ruin\r\n\r\nThis is Heavy terrain.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775162026/5E6C274426C009B895182A7944CAC3E296ABDBBD/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766140894/BCEC2085E59227D674538E55D67F03138376CC07/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775162026/5E6C274426C009B895182A7944CAC3E296ABDBBD/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "449d76", "Name": "Custom_Model", "Transform": { "posX": -13.3282042, "posY": 1.03507018, "posZ": 2.94646764, "rotX": 359.852539, "rotY": 180.040741, "rotZ": 359.7887, "scaleX": 0.825000167, "scaleY": 0.825000167, "scaleZ": 0.825000167 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": false, "Autoraise": false, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766645067/0EDE2D4898D49434532F6D194C773C2F1FB6DD27/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766123637/FB4A8A1B9F98A459FA4BDC771A52AFCA29EEFBB5/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766645067/0EDE2D4898D49434532F6D194C773C2F1FB6DD27/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "d9c558", "Name": "Custom_Model", "Transform": { "posX": -13.1220818, "posY": 1.01036358, "posZ": 10.6135979, "rotX": 359.866547, "rotY": 89.9969559, "rotZ": 0.0671334863, "scaleX": 0.950000346, "scaleY": 0.950000346, "scaleZ": 0.950000346 }, "Nickname": "Light Terrain", "Description": "Small Ruin\r\n\r\nThis is Heavy terrain.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": false, "Autoraise": false, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775151239/06876BAEAAFE383A2DF723558E06AE7E6092B9D8/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766138251/51F963B9934AD14355A5715AD2F5DA7025252135/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775151239/06876BAEAAFE383A2DF723558E06AE7E6092B9D8/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "a575c2", "Name": "Custom_Model", "Transform": { "posX": -13.000001, "posY": 0.980388165, "posZ": -2.000002, "rotX": -2.09601311E-07, "rotY": 90.0097351, "rotZ": 2.02516816E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "722919", "Name": "Custom_Model", "Transform": { "posX": -12.0001907, "posY": 1.04604506, "posZ": -4.99999952, "rotX": 0.0131915072, "rotY": 0.008296971, "rotZ": 359.7837, "scaleX": 0.82, "scaleY": 1.02122056, "scaleZ": 0.656685233 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1852681510007517842/A49F5DB08B4B726084948D1FD79B7D625F24EE07/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1829033806572958162/ADFC131F71CC6446077F60F87C980A9B9DCF7C12/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1852681510007517842/A49F5DB08B4B726084948D1FD79B7D625F24EE07/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "af9dc6", "Name": "Custom_Model", "Transform": { "posX": -5.66450644, "posY": 1.026427, "posZ": -10.6148987, "rotX": 359.817017, "rotY": 89.96828, "rotZ": 359.873718, "scaleX": 3.19999743, "scaleY": 3.19999743, "scaleZ": 3.19999743 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": false, "Autoraise": false, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766643380/EB0B11D6CD15B2DBCAD9158E20A0359572A20EFD/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766117723/5825DFC62ED64E0F61C26BB3EBFAC48F00AD94E3/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766643380/EB0B11D6CD15B2DBCAD9158E20A0359572A20EFD/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "dccfbb", "Name": "Custom_Model", "Transform": { "posX": -8.0, "posY": 0.980388045, "posZ": -8.0, "rotX": -1.4941206E-07, "rotY": 90.0, "rotZ": 1.36558768E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_neutral" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "7949b1", "Name": "Custom_Model", "Transform": { "posX": -7.0, "posY": 0.980388045, "posZ": 13.0, "rotX": -4.857354E-07, "rotY": 89.992775, "rotZ": -3.85332335E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "04e0f5", "Name": "Custom_Model", "Transform": { "posX": -6.0, "posY": 1.02516747, "posZ": 10.0, "rotX": 0.00948795, "rotY": 359.972931, "rotZ": 0.56011796, "scaleX": 1.29999983, "scaleY": 1.29999983, "scaleZ": 1.29999983 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1667979695693153187/E31E470165C09F7C2654571A3D6E4E1C565C021C/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1667979695693150828/2980A3EF49E8388CC94194A4814BB9978465AAAA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1667979695693153187/E31E470165C09F7C2654571A3D6E4E1C565C021C/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "c9c367", "Name": "Custom_Model", "Transform": { "posX": -6.0, "posY": 0.980388045, "posZ": 0.0, "rotX": 3.76194464E-07, "rotY": 359.988861, "rotZ": 4.25505071E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "d8d3c6", "Name": "Custom_Model", "Transform": { "posX": -3.115093, "posY": 1.01975775, "posZ": 1.87474918, "rotX": -8.537641E-06, "rotY": 179.995361, "rotZ": 2.348223E-06, "scaleX": 0.3750004, "scaleY": 0.3750004, "scaleZ": 0.3750004 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159838957919/DDA310304A58AAAF0E505CB4D44080AB94F8CC91/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159838953952/2A932F8D3A67A9722469600A28B18A9BD4A42D42/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159838953659/5B8B2E4400C32F5A149082F9747B9DF757C61D0C/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "47d781", "Name": "Custom_Model", "Transform": { "posX": 6.0, "posY": 1.000776, "posZ": 0.0, "rotX": 3.76194464E-07, "rotY": 179.988922, "rotZ": 4.25505071E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "c6b79a", "Name": "Custom_Model", "Transform": { "posX": 3.115093, "posY": 1.04014587, "posZ": -1.87474918, "rotX": -8.537641E-06, "rotY": -0.00463451073, "rotZ": 2.348223E-06, "scaleX": 0.3750004, "scaleY": 0.3750004, "scaleZ": 0.3750004 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159838957919/DDA310304A58AAAF0E505CB4D44080AB94F8CC91/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159838953952/2A932F8D3A67A9722469600A28B18A9BD4A42D42/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/84846159838953659/5B8B2E4400C32F5A149082F9747B9DF757C61D0C/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 1, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "e97aff", "Name": "Custom_Model", "Transform": { "posX": 8.0, "posY": 1.000776, "posZ": 8.0, "rotX": -1.4941206E-07, "rotY": 270.0, "rotZ": 1.36558768E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_neutral" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/10974228888976556321/8D4C54E1528D6A86C4AA4A8B098A18E3E8A55459/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "e9de2f", "Name": "Custom_Model", "Transform": { "posX": 5.66450644, "posY": 1.04681516, "posZ": 10.6148987, "rotX": 359.817017, "rotY": 269.968262, "rotZ": 359.873718, "scaleX": 3.19999743, "scaleY": 3.19999743, "scaleZ": 3.19999743 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": false, "Autoraise": false, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766643380/EB0B11D6CD15B2DBCAD9158E20A0359572A20EFD/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766117723/5825DFC62ED64E0F61C26BB3EBFAC48F00AD94E3/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766643380/EB0B11D6CD15B2DBCAD9158E20A0359572A20EFD/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "f957e1", "Name": "Custom_Model", "Transform": { "posX": 7.0, "posY": 1.000776, "posZ": -13.0, "rotX": -4.857354E-07, "rotY": 269.992767, "rotZ": -3.853326E-08, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "38b343", "Name": "Custom_Model", "Transform": { "posX": 6.0, "posY": 1.04555559, "posZ": -10.0, "rotX": 0.009487953, "rotY": 179.9729, "rotZ": 0.5601178, "scaleX": 1.29999983, "scaleY": 1.29999983, "scaleZ": 1.29999983 }, "Nickname": "Light Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1667979695693153187/E31E470165C09F7C2654571A3D6E4E1C565C021C/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1667979695693150828/2980A3EF49E8388CC94194A4814BB9978465AAAA/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1667979695693153187/E31E470165C09F7C2654571A3D6E4E1C565C021C/", "Convex": true, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "da92fb", "Name": "Custom_Model", "Transform": { "posX": 13.000001, "posY": 1.00077629, "posZ": 2.000002, "rotX": -2.09601225E-07, "rotY": 270.009735, "rotZ": 2.02516816E-07, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/9866166411894382071/909D41C918D31C529CA086BD872C30BB55D27124/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "ff2efa", "Name": "Custom_Model", "Transform": { "posX": 12.0001907, "posY": 1.06643319, "posZ": 4.99999952, "rotX": 0.0131915072, "rotY": 180.0083, "rotZ": 359.7837, "scaleX": 0.82, "scaleY": 1.02122056, "scaleZ": 0.656685233 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/1852681510007517842/A49F5DB08B4B726084948D1FD79B7D625F24EE07/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/1829033806572958162/ADFC131F71CC6446077F60F87C980A9B9DCF7C12/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/1852681510007517842/A49F5DB08B4B726084948D1FD79B7D625F24EE07/", "Convex": false, "MaterialIndex": 3, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "8578b1", "Name": "Custom_Model", "Transform": { "posX": 12.1863508, "posY": 1.000775, "posZ": -0.8824916, "rotX": -6.442061E-06, "rotY": 89.99994, "rotZ": 4.01314937E-05, "scaleX": 1.0, "scaleY": 1.0, "scaleZ": 1.0 }, "Nickname": "", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "Tags": [ "obj_home_blue" ], "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/16883380972164515698/5BA49DFD4787ECD0CDB246E4A4A6CC9AB0CD5291/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/13255643833716385777/6A67FAC39DD7631AB460168AF75209601FA91CDB/", "NormalURL": "", "ColliderURL": "", "Convex": true, "MaterialIndex": 3, "TypeIndex": 0, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.0, "SpecularSharpness": 2.0, "FresnelStrength": 0.0 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "392271", "Name": "Custom_Model", "Transform": { "posX": 13.3282042, "posY": 1.05545831, "posZ": -2.94646764, "rotX": 359.852539, "rotY": 0.040745277, "rotZ": 359.7887, "scaleX": 0.825000167, "scaleY": 0.825000167, "scaleZ": 0.825000167 }, "Nickname": "Dense Terrain", "Description": "", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": false, "Autoraise": false, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766645067/0EDE2D4898D49434532F6D194C773C2F1FB6DD27/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766123637/FB4A8A1B9F98A459FA4BDC771A52AFCA29EEFBB5/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766645067/0EDE2D4898D49434532F6D194C773C2F1FB6DD27/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "6e9a3a", "Name": "Custom_Model", "Transform": { "posX": 13.1220818, "posY": 1.03075171, "posZ": -10.6135979, "rotX": 359.866547, "rotY": 269.996948, "rotZ": 0.06713348, "scaleX": 0.950000346, "scaleY": 0.950000346, "scaleZ": 0.950000346 }, "Nickname": "Light Terrain", "Description": "Small Ruin\r\n\r\nThis is Heavy terrain.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": false, "Snap": false, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": false, "Autoraise": false, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775151239/06876BAEAAFE383A2DF723558E06AE7E6092B9D8/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766138251/51F963B9934AD14355A5715AD2F5DA7025252135/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775151239/06876BAEAAFE383A2DF723558E06AE7E6092B9D8/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], [[{ "GUID": "f343af", "Name": "Custom_Model", "Transform": { "posX": 18.4457932, "posY": 1.06290174, "posZ": -10.1127453, "rotX": 1.09800048E-05, "rotY": 90.04639, "rotZ": -1.08751969E-07, "scaleX": 1.20000017, "scaleY": 1.20000017, "scaleZ": 1.20000017 }, "Nickname": "Light Terrain", "Description": "Small Ruin\r\n\r\nThis is Heavy terrain.", "GMNotes": "", "AltLookAngle": { "x": 0.0, "y": 0.0, "z": 0.0 }, "ColorDiffuse": { "r": 1.0, "g": 1.0, "b": 1.0 }, "LayoutGroupSortIndex": 0, "Value": 0, "Locked": true, "Grid": true, "Snap": true, "IgnoreFoW": false, "MeasureMovement": false, "DragSelectable": true, "Autoraise": true, "Sticky": true, "Tooltip": true, "GridProjection": false, "HideWhenFaceDown": false, "Hands": false, "CustomMesh": { "MeshURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775162026/5E6C274426C009B895182A7944CAC3E296ABDBBD/", "DiffuseURL": "https://steamusercontent-a.akamaihd.net/ugc/2494522727766140894/BCEC2085E59227D674538E55D67F03138376CC07/", "NormalURL": "", "ColliderURL": "https://steamusercontent-a.akamaihd.net/ugc/2494523090775162026/5E6C274426C009B895182A7944CAC3E296ABDBBD/", "Convex": false, "MaterialIndex": 0, "TypeIndex": 4, "CustomShader": { "SpecularColor": { "r": 1.0, "g": 1.0, "b": 1.0 }, "SpecularIntensity": 0.1, "SpecularSharpness": 7.0, "FresnelStrength": 0.4 }, "CastShadows": true }, "LuaScript": "", "LuaScriptState": "", "XmlUI": "" }]], } LuaScriptStateXmlUI198GUID3f26faNameBagTransformposXIC@posY?posZ@EC@rotXQ>rotY@n"rotZ@ <scaleX?scaleY?scaleZ?NicknameMission & Utility Tokens RedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?bLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScript--[[ Ultimate memory bag by bercianor ]]---------------------------------------- -- Version: 1.2.2 -- URL: -- Fork of "Utility memory bag 2.7.0" by Directsun at -- Boxes thanks to "(Tool) - Tragic Game Boxes" by Tragic --[[ ChangeLog: v1.0.0: Relative position - Added posibility of save Absolute position or Relative to Bag position of the objects (each object) - When bag rotate and the object is saved as relative position, its position rotates according to bag - When you rotate an object and apply that movement to rest, all objects rotates its position relative to the moved object v1.1.0: Reordening - Buttons little relocation - Code reorder - Set pan variables for the buttons v1.1.1: Selecction button position - A new better calc for the position of the selection buttons, but it needs to be improved v1.2.0: Now with boxes! - Show content when container flipped (only objects inside bag) v1.2.1: Final version? - Shows all saved abjects v1.2.2: Fix selection button location ]]------------------------------------------------------------------------------ --[[ Events ]]------------------------------------------------------------------ function onload(saved_data) GlobalMemoryGroups:onLoad(self.getGUID()) AllMemoryBagsInScene:add(self.getGUID()) btn_x_offset = 0 btn_y_offset = 0.3 btn_z_offset = 0 text_y_offset = 0 --Set up information for if there is no saved data memoryList = {} memoryGroupName:set(nil) position_type = "relative" if saved_data ~= "" then --Set up information off of loaded_data local loaded_data = JSON.decode(saved_data) memoryList = loaded_data.ml or memoryList memoryGroupName:set(loaded_data.groupName or nil) position_type = loaded_data.pos_type or position_type end moveList = {} moveGuid = nil fresh = true if next(memoryList) ~= nil then fresh = false end if self.getRotation().z == 180 then showContent() elseif fresh then createSetupButton() else createMemoryActionButtons() end end function updateSave() saved_data = JSON.encode({ ["ml"]=memoryList, ["groupName"]=memoryGroupName:get(), ["pos_type"]=position_type }) self.script_state = saved_data end --Shows content (grouped) on flip function onObjectRotate(object, spin, flip, player_color, old_spin, old_flip) if object == self and flip ~= old_flip then if flip == 180 then object.clearButtons() object.clearInputs() showContent() elseif flip == 0 then object.UI.setXml("") if fresh then createSetupButton() else createMemoryActionButtons() end end end end function showContent() local bagObjList = self.getObjects() local content = {} for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then if content[obj.getName()] ~= nil then content[obj.getName()] = content[obj.getName()] + 1 else content[obj.getName()] = 1 end else for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then if content[bagObj.name] ~= nil then content[bagObj.name] = content[bagObj.name] + 1 else content[bagObj.name] = 1 end break end end end end local sb = {} for name, amount in pairs(content) do table.insert(sb, string.format("- %dx %s\n", amount, name)) end createBackText(table.concat(sb)) end --[[ Events ]]------------------------------------------------------------------ --[[ UI Creations ]]------------------------------------------------------------ --Make setup button function createSetupButton() self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={0,btn_y_offset,2+btn_z_offset}, rotation={0,0,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --Creates Setting Up buttons function createSetupActionButtons(move) self.createButton({ label="Cancel", click_function="buttonClick_cancel", function_owner=self, position={0,btn_y_offset,2+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Submit", click_function="buttonClick_submit", function_owner=self, position={0,btn_y_offset,2.8+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) if move == false then self.createButton({ label="Add", click_function="buttonClick_add", function_owner=self, position={0,btn_y_offset,3.6+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.25,1,0.25} }) self.createButton({ label="Selection", click_function="editDragSelection", function_owner=self, position={0,btn_y_offset,-2-btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) groupNameInput:create(memoryGroupName:get()) if fresh == false then self.createButton({ label="Set New", click_function="buttonClick_setNew", function_owner=self, position={0,btn_y_offset,4.4+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) self.createButton({ label="Remove", click_function="buttonClick_remove", function_owner=self, position={0,btn_y_offset,5.2+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,0.25,0.25} }) end end self.createButton({ label="Reset", click_function="buttonClick_reset", function_owner=self, position={2+btn_x_offset,btn_y_offset,0}, rotation={0,90,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) if position_type == "relative" then self.createButton({ label="Rel. Pos.", click_function="buttonClick_posType", function_owner=self, position={2.8+btn_x_offset,btn_y_offset,0}, rotation={0,90,0}, height=350, width=1000, font_size=250, color={0,0,0}, font_color={1,1,1} }) else self.createButton({ label="Abs. Pos.", click_function="buttonClick_posType", function_owner=self, position={2.8+btn_x_offset,btn_y_offset,0}, rotation={0,90,0}, height=350, width=1000, font_size=250, color={0,0,0}, font_color={1,1,1} }) end end --Creates selection buttons on objects function createButtonsOnAllObjects(move) buttonIndexMap = {} local howManyButtons = 0 local objsToHaveButtons = {} if move == true then for guid in pairs(memoryListBackup) do table.insert(objsToHaveButtons, getObjectFromGUID(guid)) end else objsToHaveButtons = getAllObjects() end for _, obj in ipairs(objsToHaveButtons) do if obj ~= self then --On a normal bag, the button positions aren't the same size as the bag. --TODO: Needs a better calc for the globalScaleFactor local globalScaleFactor = 1.25/(1+math.log(self.getScale().x,6)) local deltaPos = findOffsetDistance(self.getPosition(), obj.getPosition(), obj) deltaPos = Vector(deltaPos.x,deltaPos.y,deltaPos.z):rotateOver('y', 180-self.getRotation().y) local objPos = {} objPos.x = deltaPos.x * globalScaleFactor objPos.y = deltaPos.y * globalScaleFactor objPos.z = -deltaPos.z * globalScaleFactor --Workaround for custom PDFs if obj.Book then objPos.y = objPos.y + 0.5 end --Offset rotation of bag local rot = self.getRotation() rot.y = 180-rot.y --Create function local funcName = "selectButton_" .. howManyButtons local func = function() buttonClick_selection(obj, move) end local color = {0.75,0.25,0.25,0.6} local colorMove = {0,0,1,0.6} if move == true then color = colorMove end self.setVar(funcName, func) self.createButton({ click_function=funcName, function_owner=self, position=objPos, rotation=rot, height=1000, width=1000, color=color, }) buttonIndexMap[obj.getGUID()] = howManyButtons howManyButtons = howManyButtons + 1 end end end --Creates recall and place buttons function createMemoryActionButtons() self.createButton({ label="Place", click_function="buttonClick_place", function_owner=self, position={0,btn_y_offset,2+btn_z_offset}, rotation={0,0,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Recall", click_function="buttonClick_recall", function_owner=self, position={0,btn_y_offset,2.8+btn_z_offset}, rotation={0,0,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end function createBackText(text) self.UI.setXmlTable({ { tag="Panel", attributes={ height = (self.getBoundsNormalized().size.z*67.5/self.getScale().z), width = (self.getBoundsNormalized().size.x*67.5/self.getScale().x), position = "0 0 "..text_y_offset*100, rotation = "180 0 0", rectAlignment = "MiddleCenter" }, children={ tag = "Text", attributes = { text = text, id = "content", color = "#000000FF", fontStyle = "Bold", alignment ="UpperLeft", resizeTextForBestFit= true, resizeTextMinSize = 10, resizeTextMaxSize = 40, horizontalOverflow = "Wrap", -- Wrap or Overflow? verticalOverflow = "Truncate" -- Truncate or Overflow? } } } }) end --[[ UI Creations ]]------------------------------------------------------------ --[[ Button Triggers ]]--------------------------------------------------------- --Triggered by Transpose button function buttonClick_transpose() moveGuid = nil broadcastToAll("Select one object and move it- all objects will move relative to the new location", {0.75, 0.75, 1}) memoryListBackup = duplicateTable(memoryList) memoryList = {} moveList = {} self.clearButtons() self.clearInputs() createButtonsOnAllObjects(true) createSetupActionButtons(true) end --Triggered by setup button, function buttonClick_setup() memoryListBackup = duplicateTable(memoryList) memoryList = {} self.clearButtons() self.clearInputs() createButtonsOnAllObjects(false) createSetupActionButtons(false) end --Checks or unchecks buttons function buttonClick_selection(obj, move) local index = buttonIndexMap[obj.getGUID()] local colorMove = {0,0,1,0.6} local color = {0,1,0,0.6} previousGuid = selectedGuid selectedGuid = obj.getGUID() theList = memoryList if move == true then theList = moveList if previousGuid ~= nil and previousGuid ~= selectedGuid then local prevObj = getObjectFromGUID(previousGuid) prevObj.highlightOff() self.editButton({index=previousIndex, color=colorMove}) theList[previousGuid] = nil end previousIndex = index end if theList[selectedGuid] == nil then self.editButton({index=index, color=color}) --Adding pos/rot to memory table local pos = {} local rot = {} if position_type == "relative" then pos = findOffsetDistance(self.getPosition(), obj.getPosition()) rot = findOffsetDistance(self.getRotation(), obj.getRotation()) else pos = obj.getPosition() rot = obj.getRotation() end --I need to add it like this or it won't save due to indexing issue theList[obj.getGUID()] = { bag_rotation=self.getRotation(), pos_type=position_type, pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock(), tint=obj.getColorTint() } obj.highlightOn({0,1,0}) else color = {0.75,0.25,0.25,0.6} if move == true then color = colorMove end self.editButton({index=index, color=color}) theList[obj.getGUID()] = nil obj.highlightOff() end end function editDragSelection(bagObj, player, remove) local selectedObjs = Player[player].getSelectedObjects() if not remove then for _, obj in ipairs(selectedObjs) do local index = buttonIndexMap[obj.getGUID()] --Ignore if already in the memory list, or does not have a button if index and not memoryList[obj.getGUID()] then self.editButton({index=index, color={0,1,0,0.6}}) --Adding pos/rot to memory table local pos = {} local rot = {} if position_type == "relative" then pos = findOffsetDistance(self.getPosition(), obj.getPosition()) rot = findOffsetDistance(self.getRotation(), obj.getRotation()) else pos = obj.getPosition() rot = obj.getRotation() end --I need to add it like this or it won't save due to indexing issue memoryList[obj.getGUID()] = { bag_rotation=self.getRotation(), pos_type=position_type, pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock(), tint=obj.getColorTint() } obj.highlightOn({0,1,0}) end end else for _, obj in ipairs(selectedObjs) do local index = buttonIndexMap[obj.getGUID()] if index and memoryList[obj.getGUID()] then color = {0.75,0.25,0.25,0.6} self.editButton({index=index, color=color}) memoryList[obj.getGUID()] = nil obj.highlightOff() end end end end function updateMemoryWithMoves() memoryList = memoryListBackup --get the first transposed object's coordinates local obj = getObjectFromGUID(moveGuid) local refObjPos = {} local refObjRot = {} if memoryList[moveGuid].pos_type == "relative" then refObjPos.x = self.getPosition().x + memoryList[moveGuid].pos.x refObjPos.y = self.getPosition().y + memoryList[moveGuid].pos.y refObjPos.z = self.getPosition().z + memoryList[moveGuid].pos.z refObjRot.x = self.getRotation().x + memoryList[moveGuid].rot.x refObjRot.y = self.getRotation().y + memoryList[moveGuid].rot.y refObjRot.z = self.getRotation().z + memoryList[moveGuid].rot.z else refObjPos.x = memoryList[moveGuid].pos.x refObjPos.y = memoryList[moveGuid].pos.y refObjPos.z = memoryList[moveGuid].pos.z refObjRot.x = memoryList[moveGuid].rot.x refObjRot.y = memoryList[moveGuid].rot.y refObjRot.z = memoryList[moveGuid].rot.z end local deltaPos = findOffsetDistance(obj.getPosition(), refObjPos) local deltaRot = findOffsetDistance(obj.getRotation(), refObjRot) for guid, entry in pairs(memoryList) do local pos = {} local rot = {} pos.x = entry.pos.x - deltaPos.x pos.y = entry.pos.y - deltaPos.y pos.z = entry.pos.z - deltaPos.z rot.x = entry.rot.x - deltaRot.x rot.y = entry.rot.y - deltaRot.y rot.z = entry.rot.z - deltaRot.z if entry.pos_type == "relative" then pos = obj.positionToWorld(obj.positionToLocal(Vector(self.getPosition().x + pos.x, self.getPosition().y + pos.y, self.getPosition().z + pos.z)):rotateOver('y', -deltaRot.y)) pos = findOffsetDistance(self.getPosition(), pos) else pos = obj.positionToWorld(obj.positionToLocal(Vector(pos.x,pos.y,pos.z)):rotateOver('y', -deltaRot.y)) end memoryList[guid].pos.x = pos.x memoryList[guid].pos.y = pos.y memoryList[guid].pos.z = pos.z memoryList[guid].rot.x = rot.x memoryList[guid].rot.y = rot.y memoryList[guid].rot.z = rot.z end moveList = {} end --Cancels selection process function buttonClick_cancel() memoryList = memoryListBackup moveList = {} self.clearButtons() self.clearInputs() if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end removeAllHighlights() broadcastToAll("Selection Canceled", {1,1,1}) moveGuid = nil end --Saves selections function buttonClick_submit() fresh = false if next(moveList) ~= nil then for guid in pairs(moveList) do moveGuid = guid end if memoryListBackup[moveGuid] == nil then broadcastToAll("Item selected for moving is not already in memory", {1, 0.25, 0.25}) else broadcastToAll("Moving all items in memory relative to new objects position!", {0.75, 0.75, 1}) self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(moveList) do moveGuid = guid count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end updateMemoryWithMoves() updateSave() buttonClick_place() end elseif next(memoryList) == nil and moveGuid == nil then memoryList = memoryListBackup broadcastToAll("No selections made.", {0.75, 0.25, 0.25}) end combineMemoryFromBagsWithin() groupNameInput:setGroupNameToInputField() self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() moveGuid = nil end function buttonClick_add() fresh = false combineTables(memoryList, memoryListBackup) broadcastToAll("Adding internal bags and selections to existing memory", {0.25, 0.75, 0.25}) combineMemoryFromBagsWithin() self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() end function buttonClick_remove() broadcastToAll("Removing Selected Entries From Memory", {1.0, 0.25, 0.25}) self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 memoryListBackup[guid] = nil local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Removed", {1,1,1}) memoryList = memoryListBackup updateSave() end function buttonClick_setNew() broadcastToAll("Setting new position relative to items in memory", {0.75, 0.75, 1}) self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for _, obj in ipairs(getAllObjects()) do guid = obj.guid if memoryListBackup[guid] ~= nil then count = count + 1 memoryListBackup[guid].bag_rotation = self.getRotation() memoryListBackup[guid].pos_type = position_type if position_type == "relative" then memoryListBackup[guid].pos = findOffsetDistance(self.getPosition(), obj.getPosition()) memoryListBackup[guid].rot = findOffsetDistance(self.getRotation(), obj.getRotation()) else memoryListBackup[guid].pos = obj.getPosition() memoryListBackup[guid].rot = obj.getRotation() end memoryListBackup[guid].lock = obj.getLock() memoryListBackup[guid].tint = obj.getColorTint() end end broadcastToAll(count.." Objects Saved", {1,1,1}) memoryList = memoryListBackup updateSave() end --Resets bag to starting status function buttonClick_reset() fresh = true memoryList = {} memoryGroupName:set(nil) self.clearButtons() self.clearInputs() createSetupButton() removeAllHighlights() broadcastToAll("Tool Reset", {1,1,1}) updateSave() end --Changes between Relative Position and Absolute Position function buttonClick_posType() if position_type == "relative" then for _, obj in ipairs(self.getButtons()) do if obj["label"] == "Rel. Pos." then self.editButton({index=obj.index, label="Abs. Pos."}) break end end position_type="absolute" else for _, obj in ipairs(self.getButtons()) do if obj.label == "Abs. Pos." then self.editButton({index=obj.index, label="Rel. Pos."}) break end end position_type="relative" end end --Sends objects from bag/table to their saved position/rotation function buttonClick_place() if anyOtherBagsInMyGroupArePlaced() then recallOtherBagsInMyGroup() Wait.frames(_placeObjects, CONFIG.MEMORY_GROUP.FRAME_DELAY_BEFORE_PLACING_OBJECTS) else _placeObjects() end end function _placeObjects() local bagObjList = self.getObjects() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) local pos = {} local rot = {} if entry.pos_type == "relative" then local rotated_pos = Vector(entry.pos.x,entry.pos.y,entry.pos.z):rotateOver('y', findOffsetDistance(entry.bag_rotation, self.getRotation()).y) pos.x = self.getPosition().x + rotated_pos.x pos.y = self.getPosition().y + rotated_pos.y pos.z = self.getPosition().z + rotated_pos.z rot.x = self.getRotation().x + entry.rot.x rot.y = self.getRotation().y + entry.rot.y rot.z = self.getRotation().z + entry.rot.z else pos.x = entry.pos.x pos.y = entry.pos.y pos.z = entry.pos.z rot.x = entry.rot.x rot.y = entry.rot.y rot.z = entry.rot.z end if obj ~= nil then --If obj is out on the table, move it to the saved pos/rot obj.setPositionSmooth(pos) obj.setRotationSmooth(rot) obj.setLock(entry.lock) obj.setColorTint(entry.tint) else --If obj is inside of the bag for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then local item = self.takeObject({ guid=guid, position=pos, rotation=rot, smooth=false }) item.setLock(entry.lock) item.setColorTint(entry.tint) break end end end end broadcastToAll("Objects Placed", {1,1,1}) end --Recalls objects to bag from table function buttonClick_recall() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then self.putObject(obj) end end broadcastToAll("Objects Recalled", {1,1,1}) end --[[ Button Triggers ]]--------------------------------------------------------- --[[ This object provides access to a variable stored on the "Global script". The variable holds the GUIDs for every Ultimate Memory Bag in the scene. Example: {'805ebd', '35cc21', 'fc8886', 'f50264', '5f5f63'} --]] AllMemoryBagsInScene = { NAME_OF_GLOBAL_VARIABLE = "_UltimateMemoryBag_AllMemoryBagsInScene" } function AllMemoryBagsInScene:add(guid) local guids = Global.getTable(self.NAME_OF_GLOBAL_VARIABLE) or {} table.insert(guids, guid) Global.setTable(self.NAME_OF_GLOBAL_VARIABLE, guids) end function AllMemoryBagsInScene:getGuidList() return Global.getTable(self.NAME_OF_GLOBAL_VARIABLE) or {} end --[[ Memory Bag Groups ]]------------------------------------------------------- --[[ Ultimate Memory Bags may be added to a named group, called a "memory group". You can add a bag to a group through the bag's UI: "Setup" > "Group Name". Only one bag from a group may have it's contents placed on the table at a time. When "Place" is clicked on a bag, the other bags in it's memory group are recalled. By default a memory bag is not in any group. It's memory group is "nil". --]] CONFIG = { MEMORY_GROUP = { -- This determines how many frames to wait before actually placing objects onto the table when the "Place" button is clicked. -- This gives the other bags time to recall their objects. -- The delay ONLY occurs if other bags have objects out. FRAME_DELAY_BEFORE_PLACING_OBJECTS = 30, }, } memoryGroupName = {memoryBag=self} function memoryGroupName:get() return self._name end function memoryGroupName:set(newName) GlobalMemoryGroups:unregisterBagInGroup(self:get(), self.memoryBag.getGUID()) GlobalMemoryGroups:registerBagInGroup(newName, self.memoryBag.getGUID()) if newName == "" then self._name = nil else self._name = newName end end -- Click the "Recall" button on all other bags in my memory group. function recallOtherBagsInMyGroup() for _,bag in ipairs(getOtherBagsInMyGroup()) do bag.call('buttonClick_recall') end end -- Return "true" if another bag in my memory group has any objects out on the table. function anyOtherBagsInMyGroupArePlaced() for _,bag in ipairs(getOtherBagsInMyGroup()) do local state = bag.call('areAnyOfMyObjectsPlaced') if state then return true end end return false end -- Return "true" if at least one object from this memory bag is out on the table. function areAnyOfMyObjectsPlaced() for guid,_ in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then return true end end return false end function getOtherBagsInMyGroup() local bags = {} for bagGuid,_ in pairs(GlobalMemoryGroups:getGroup(memoryGroupName:get())) do if bagGuid ~= self.getGUID() then bag = getObjectFromGUID(bagGuid) -- "bag" is nill if it has been deleted since the last time onLoad() was called. if bag ~= nil then table.insert(bags, bag) end end end return bags end --[[ This object provides access to a variable stored on the "Global script". The variable holds the names & guids of all memory bag groups. The global variable is a table and holds data like this: { 'My First Group Name' = { '805ebd' = {}, '35cc21' = {}, 'fc8886' = {}, }, 'My Second Group Name' = { 'f50264' = {}, '5f5f63' = {}, }, } --]] GlobalMemoryGroups = { NAME_OF_GLOBAL_VARIABLE = '_GlobalUltimateMemoryBagGroups', } -- Call me inside this script's "onLoad()" method! function GlobalMemoryGroups:onLoad(myGuid) -- Create and initialize the global variable if it doesn't already exist: if self:_getGroups() == nil then self:_setGroups({}) end end -- Return the GUIDs of all bags in the "groupName". The return value is a dictionary that maps [GUID -> empty table]. function GlobalMemoryGroups:getGroup(groupName) guids = self:_getGroups()[groupName] or {} return guids end -- Registers a bag in a memory group. Creates a new group if one doesn't exist. function GlobalMemoryGroups:registerBagInGroup(groupName, bagGuid) if groupName == nil or groupName == "" then return end self:_tryCreateNewGroup(groupName) local groups = self:_getGroups() groups[groupName][bagGuid] = {} self:_setGroups(groups) end -- Removes this bag from the memory group. function GlobalMemoryGroups:unregisterBagInGroup(groupName, bagGuid) local groups = self:_getGroups() local group = groups[groupName] if group ~= nil then group[bagGuid] = nil self:_setGroups(groups) end end -- Return the global variable, which is a table holding all memory group names & guids. function GlobalMemoryGroups:_getGroups() return Global.getTable(self.NAME_OF_GLOBAL_VARIABLE) end -- Override the global variable (i.e. the entire table). function GlobalMemoryGroups:_setGroups(newTable) Global.setTable(self.NAME_OF_GLOBAL_VARIABLE, newTable) end -- Add a new memory group named "groupName" to the global variable, if one doesn't already exist. function GlobalMemoryGroups:_tryCreateNewGroup(groupName) local groups = self:_getGroups() if groups[groupName] == nil then groups[groupName] = {} self:_setGroups(groups) end end -- This object controls the "Group Name" input text field that is part of the bag's ingame UI. groupNameInput = { greyedOutText = "Group Name", widthPerCharacter = 100, padding = 4, memoryBag=self, } function groupNameInput:create(optionalStartingValue) local effectiveText = optionalStartingValue or self.greyedOutText local width = self:computeWidth(effectiveText) self.memoryBag.createInput({ label=self.greyedOutText, value=optionalStartingValue or nil, alignment=3, -- Center aligned input_function="groupNameInput_onCharacterTyped", function_owner=self.memoryBag, position={-2.1-btn_x_offset,btn_y_offset,0}, rotation={0,270,0}, width=width, height=350, font_size=250, color={0,0,0}, font_color={1,1,1}, }) end function groupNameInput:computeWidth(text) return (string.len(text) + self.padding) * self.widthPerCharacter end function groupNameInput:updatedWidth(text) self.memoryBag.editInput({ index=0, width=self:computeWidth(text) }) end function groupNameInput:onCharacterTyped(text, stillEditing) if stillEditing then self:updatedWidth(text) else if text == "" then self:updatedWidth(self.greyedOutText) end end end function groupNameInput_onCharacterTyped(memoryBag, playerColor, text, stillEditing) groupNameInput:onCharacterTyped(text, stillEditing) end function groupNameInput:setGroupNameToInputField() local inputFields = self.memoryBag.getInputs() if inputFields ~= nil then -- Get input field 0, which corresponds to the groupNameInput. -- Unfortunately "self.getInputs()" doesn't return the inputs in a guaranteed order. local nameField = nil for _,field in ipairs(inputFields) do if field.index == 0 then nameField = field end end memoryGroupName:set(nameField.value) end end --[[ Memory Bag Groups ]]------------------------------------------------------- --[[ Utility functions ]]------------------------------------------------------- function combineTables(first_table, second_table) for k,v in pairs(second_table) do first_table[k] = v end end function combineMemoryFromBagsWithin() local bagObjList = self.getObjects() for _, bagObj in ipairs(bagObjList) do local data = bagObj.lua_script_state if data ~= nil then local j = JSON.decode(data) if j ~= nil and j.ml ~= nil then for guid, entry in pairs(j.ml) do memoryList[guid] = entry end end end end end --Find delta (difference) between 2 x/y/z coordinates function findOffsetDistance(p1, p2, obj) local xOffset = 0 local yOffset = 0 local zOffset = 0 if obj ~= nil then local bounds = obj.getBounds() yOffset = (bounds.size.y - bounds.offset.y) end local deltaPos = {} deltaPos.x = (p2.x-p1.x) deltaPos.y = (p2.y-p1.y) + yOffset deltaPos.z = (p2.z-p1.z) return deltaPos end --Coroutine delay, in seconds function wait(time) local start = os.time() repeat coroutine.yield(0) until os.time() > start + time end --Duplicates a table (needed to prevent it making reference to the same objects) function duplicateTable(oldTable) local newTable = {} for k, v in pairs(oldTable) do newTable[k] = v end return newTable end --Moves scripted highlight from all objects function removeAllHighlights() for _, obj in ipairs(getAllObjects()) do obj.highlightOff() end end --Round number (num) to the Nth decimal (dec) function round(num, dec) local mult = 10^(dec or 0) return math.floor(num * mult + 0.5) / mult end --Converts to string any object function table_print(tt, indent, done) done = done or {} indent = indent or 0 if type(tt) == 'table' then local sb = {} for key, value in pairs (tt) do table.insert(sb, string.rep (" ", indent)) -- indent it if type (value) == "table" and not done [value] then done [value] = true table.insert(sb, key .. " = {\n"); table.insert(sb, table_print(value, indent + 2, done)) table.insert(sb, string.rep (" ", indent)) -- indent it table.insert(sb, "}\n"); elseif "number" == type(key) then table.insert(sb, string.format("\"%s\"\n", tostring(value))) else table.insert(sb, string.format("%s = \"%s\"\n", tostring (key), tostring(value))) end end return table.concat(sb) else return tt .. "\n" end end function toStr(o) if "nil" == type(o) then return tostring(nil) elseif "table" == type(o) then return table_print(o,2) elseif "string" == type(o) then return o else return tostring(o) end end --[[ Utility functions ]]------------------------------------------------------- LuaScriptState{"ml":{"10530d":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-31.8516,"y":0.2563,"z":19.1313},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1e34a5":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-25.4747,"y":0.2563,"z":16.8344},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"228a2c":{"bag_rotation":{"x":1.44068667395914E-06,"y":359.991363525391,"z":-7.47160697756044E-07},"lock":false,"pos":{"x":-30.298,"y":0.2563,"z":5.3274},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"2820bd":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-28.6631,"y":0.2563,"z":19.1313},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"28a687":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-25.4738,"y":0.2563,"z":12.2603},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"2f0a6f":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-27.1139,"y":0.2563,"z":9.9506},"pos_type":"relative","rot":{"x":0,"y":-359.9744,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"38b165":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-31.8922,"y":0.2563,"z":5.3274},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"43644f":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-31.7786,"y":0.2563,"z":16.8348},"pos_type":"relative","rot":{"x":0,"y":-359.9573,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"5a6fad":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-28.6622,"y":0.2563,"z":12.2603},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6e8010":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-27.068,"y":0.2563,"z":12.2603},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"72702d":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-27.0683,"y":0.2563,"z":16.8344},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"76e3b0":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-27.1227,"y":0.2563,"z":7.6566},"pos_type":"relative","rot":{"x":0,"y":-359.9585,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7e5f14":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-30.2973,"y":0.2563,"z":14.5324},"pos_type":"relative","rot":{"x":0,"y":-359.9744,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"8806c6":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-28.6619,"y":0.2563,"z":16.8344},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"91de16":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-27.1077,"y":0.2563,"z":14.5324},"pos_type":"relative","rot":{"x":0,"y":-359.959,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"9815e3":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-28.7081,"y":0.2563,"z":9.9506},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a14627":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-28.7169,"y":0.2563,"z":7.6566},"pos_type":"relative","rot":{"x":0,"y":-359.9583,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a5b4e0":{"bag_rotation":{"x":1.44068667395914E-06,"y":359.991363525391,"z":-7.47160697756044E-07},"lock":false,"pos":{"x":-28.7038,"y":0.2563,"z":5.3274},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"ae9e78":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-25.5197,"y":0.2563,"z":9.9506},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"af9bd9":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-30.2555,"y":0.2563,"z":16.8344},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"b797d6":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-27.0688,"y":0.2563,"z":19.1313},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"bcc2fa":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-25.5129,"y":0.2563,"z":14.5324},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c569df":{"bag_rotation":{"x":1.49406355376414E-06,"y":359.991363525391,"z":-5.33721276951837E-07},"lock":false,"pos":{"x":-25.4745,"y":0.2563,"z":19.1313},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c5da0a":{"bag_rotation":{"x":1.3873096804673E-06,"y":359.991363525391,"z":-9.60600118560251E-07},"lock":false,"pos":{"x":-27.1096,"y":0.2563,"z":5.3274},"pos_type":"relative","rot":{"x":0,"y":-359.9732,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c718fa":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-25.5285,"y":0.2563,"z":7.6566},"pos_type":"relative","rot":{"x":0,"y":-359.96,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"da928e":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-30.2574,"y":0.2563,"z":19.1313},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e36fc7":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-30.3111,"y":0.2563,"z":7.6566},"pos_type":"relative","rot":{"x":0,"y":-359.9742,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"f062a8":{"bag_rotation":{"x":1.54744066094281E-06,"y":359.991363525391,"z":-3.20281912991049E-07},"lock":false,"pos":{"x":-28.7025,"y":0.2563,"z":14.5324},"pos_type":"relative","rot":{"x":0,"y":-359.9743,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"fdd769":{"bag_rotation":{"x":-1.24854132081964E-05,"y":359.991363525391,"z":1.36079606818384E-05},"lock":false,"pos":{"x":-25.5153,"y":0.2563,"z":5.3275},"pos_type":"relative","rot":{"x":0,"y":-359.9614,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}}},"pos_type":"relative"}XmlUIContainedObjects20 GUIDfdd769NameCustom_Model_Infinite_BagTransformposX1@posYx?posZE@rotX">rotY®?rotZYɓscaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDcc0623Name Custom_TokenTransformposX@rz@posYfy?posZ rotXMv@rotY_?rotZv@scaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11015760531339961670/14CCDC1610D7B735F12A6B2481F37253147E7ED2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects{0sGUID67e977Name Custom_TokenTransformposX3K?posYposZ@[ K?rotXFj=rotY$>rotZܥlscaleXt'?scaleY@$@scaleZ`+?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11015760531339961670/14CCDC1610D7B735F12A6B2481F37253147E7ED2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI1 GUIDc5da0aNameCustom_Model_Infinite_BagTransformposX/@posYw?posZE@rotXn>rotY똒?rotZscaleX*?scaleY?scaleZ*?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDf41e86Name Custom_TokenTransformposX Vu@posY (n?posZbrotX`v@rotYY?rotZxv@scaleX?scaleY?scaleZ?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9633452287099972292/3E3D05BD43C3614171D5B04A7F840BD1EABC5C08/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsv0nGUIDa96ff7Name Custom_TokenTransformposX K?posY`w=posZ K?rotX`f6rotYP$>rotZ 9scaleXB)?scaleY@$@scaleZ`B)?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9633452287099972292/3E3D05BD43C3614171D5B04A7F840BD1EABC5C08/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI2 GUIDa5b4e0NameCustom_Model_Infinite_BagTransformposX,@posYw?posZE@rotXԟ>rotYP?rotZ_scaleX?scaleY?scaleZ??NicknameVanguard OperationDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects}0uGUID9a6e83Name Custom_TokenTransformposX`O@posY x?posZ`믿rotXsrotYv@rotZ`Z?scaleX?scaleY?scaleZ?NicknameVanguard OperationDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14133185665765790575/AFD9923A3E7EB16C6CDAEADA46627956D4E05044/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects}0uGUIDd6e655Name Custom_TokenTransformposXposYposZrotX onrotY W>rotZ yscaleX`0?scaleY#@scaleZ"?NicknameVanguard OperationDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14133185665765790575/AFD9923A3E7EB16C6CDAEADA46627956D4E05044/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI3 GUID228a2cNameCustom_Model_Infinite_BagTransformposX)@posY@w?posZE@rotXd~>rotYP?rotZscaleX?scaleY?scaleZ?Nickname Vital LinkDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUIDb2795fName Custom_TokenTransformposXfB@posY{?posZv|?rotX@v@rotY@$v@rotZ@~v@scaleX?scaleY?scaleZ?Nickname Vital LinkDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13316841862099947121/B4A1B6952BE61F9BE9181DAB8CCE9CCE1C4C1D81/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsu0mGUID77f73cName Custom_TokenTransformposX=posY@>posZ rotXao=rotYt>rotZ ܥ|scaleX#?scaleY@$@scaleZ@.?Nickname Vital LinkDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13316841862099947121/B4A1B6952BE61F9BE9181DAB8CCE9CCE1C4C1D81/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI4{ GUID38b165NameCustom_Model_Infinite_BagTransformposXFm&@posYw?posZE@rotXټ>rotYP?rotZ`scaleX@?scaleY?scaleZ??Nickname SabotageDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectss0kGUIDb20417Name Custom_TokenTransformposXb@posYzx?posZ?rotX v@rotYv@rotZv@scaleX?scaleY?scaleZ?Nickname SabotageDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13345490819359870575/518551630D7C3C729B6E61D222F954B6FC62D289/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectss0kGUIDa50e30Name Custom_TokenTransformposXposYposZrotX`[rotYc>rotZܥ\=scaleX`"?scaleY #@scaleZ"?Nickname SabotageDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13345490819359870575/518551630D7C3C729B6E61D222F954B6FC62D289/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI5 GUIDe36fc7NameCustom_Model_Infinite_BagTransformposX̖)@posY`w?posZ&G@rotX`>rotYP?rotZ 觾scaleX8?scaleY?scaleZ6?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsw0oGUIDa35171Name Custom_TokenTransformposX @posY jy?posZG2rotX C~v@rotY~wv@rotZ@~v@scaleX?scaleY?scaleZ?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12903101805084442889/DAF461EA99AA3533BE61030FE4F69D8CD3FE0E45/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsw0oGUID80825cName Custom_TokenTransformposXposYposZrotX&=rotY@S>rotZ ?;scaleX@ "?scaleY@$@scaleZ@ "?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12903101805084442889/DAF461EA99AA3533BE61030FE4F69D8CD3FE0E45/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI6p GUIDa14627NameCustom_Model_Infinite_BagTransformposX,@posYw?posZ&G@rotXb>rotY?rotZ*&scaleX@?scaleY?scaleZ@?NicknameDecoyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID4c2bbaName Custom_TokenTransformposX*@posY`x?posZrotXv@rotYDJ?rotZv@scaleX?scaleY?scaleZ?NicknameDecoyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9396773628506016304/1072CBD8A5DCD191E4EF1AED010A112E3897F02D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectso0gGUID93731bName Custom_TokenTransformposXZ<posYposZ; =rotX31rotY`p>rotZ ܥlscaleX`"?scaleY$@scaleZ`"?NicknameDecoyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9396773628506016304/1072CBD8A5DCD191E4EF1AED010A112E3897F02D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI7 GUID76e3b0NameCustom_Model_Infinite_BagTransformposXB/@posY`w?posZ&G@rotX>rotY|Ӡ?rotZ*۪scaleX@?scaleY?scaleZ@?NicknameTrapDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID917f6aName Custom_TokenTransformposX[@posYx?posZ0rotX:?rotY@?rotZ`lh?scaleX?scaleY?scaleZ?NicknameTrapDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16996015813023681293/E5618C890547E9BB2026240B22DD534DC7338B09/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID965f71Name Custom_TokenTransformposX=posYً5=posZj>rotX%=rotYૌ>rotZ ܥ<scaleX"?scaleY@$@scaleZ"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16996015813023681293/E5618C890547E9BB2026240B22DD534DC7338B09/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI8 GUIDc718faNameCustom_Model_Infinite_BagTransformposX1@posYw?posZ&G@rotXSz>rotY?rotZscaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUID4f6460Name Custom_TokenTransformposX@posYWx?posZXrotX v@rotY?rotZL~v@scaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9567059880902514641/4C6EEBE9AE3955823BBED6C754D1424E884BA37E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsz0rGUID56a843Name Custom_TokenTransformposXK?posYԽposZ@Z K?rotXX6=rotY>>rotZ ;scaleXt'?scaleY@$@scaleZ+?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9567059880902514641/4C6EEBE9AE3955823BBED6C754D1424E884BA37E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI9 GUIDae9e78NameCustom_Model_Infinite_BagTransformposX1@posY w?posZNLH@rotX` >rotYy?rotZgscaleXB?scaleY?scaleZA?NicknameExtract IntelligenceDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0wGUID691117Name Custom_TokenTransformposX1@posYx?posZ rotXG?rotY~v@rotZ@UscaleX?scaleY?scaleZ?NicknameExtract IntelligenceDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13137433287317181357/DD2A59C039A48A080BC2E65ABEA181FD8EDD0D8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0wGUID2d77daName Custom_TokenTransformposXojposYuch=posZ`krotX`=rotY@>rotZ;scaleX"?scaleY#@scaleZ"?NicknameExtract IntelligenceDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13137433287317181357/DD2A59C039A48A080BC2E65ABEA181FD8EDD0D8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI10 GUID2f0a6fNameCustom_Model_Infinite_BagTransformposX/@posY@w?posZNLH@rotXK>rotY^?rotZ`좾scaleX@?scaleY?scaleZ??Nickname TriangulateDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID4da066Name Custom_TokenTransformposX@v@posYx?posZWHrotXv@rotY/?rotZ@v@scaleX?scaleY?scaleZ?Nickname TriangulateDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15167827123933348806/9DBA093351A9089AB5DF389E43D137D63CE47610/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDfe2fb3Name Custom_TokenTransformposX`aO=posY@posZ`j>rotXhQ=rotYԾrotZ ܥLscaleX"?scaleY$@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15167827123933348806/9DBA093351A9089AB5DF389E43D137D63CE47610/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI11 GUID9815e3NameCustom_Model_Infinite_BagTransformposX,@posY@w?posZNLH@rotXW>rotYy?rotZ@:"scaleX@?scaleY?scaleZ@?Nickname SurveilledDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID05ad59Name Custom_TokenTransformposX @posY y?posZ`rotXyv@rotY c3?rotZ`'?scaleX?scaleY?scaleZ?Nickname SurveilledDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14221116480909066130/7E68ABA31FCFC51F1870D10423667C3012585698/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsu0mGUIDfc81eaName Custom_TokenTransformposXposYposZrotXN=rotY`m$>rotZ YscaleX`"?scaleY #@scaleZ`"?Nickname SurveilledDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14221116480909066130/7E68ABA31FCFC51F1870D10423667C3012585698/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI12 GUID28a687NameCustom_Model_Infinite_BagTransformposX1@posYw?posZsI@rotX߼>rotYy?rotZ3scaleX@?scaleY?scaleZ@?Nickname CondemnedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDf20b0aName Custom_TokenTransformposXzj@posYXy?posZ rotXJ?rotY`?rotZ`v@scaleX?scaleY?scaleZ?Nickname CondemnedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10136191893480835129/D937B0190529BB65AB07DFAB220043EBCD8D12C4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDcf215aName Custom_TokenTransformposX`_sposY@5=posZj>rotX%/=rotYt>rotZ5;scaleX`"?scaleY@$@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10136191893480835129/D937B0190529BB65AB07DFAB220043EBCD8D12C4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI13 GUID6e8010NameCustom_Model_Infinite_BagTransformposX 0@posY@w?posZsI@rotX0U>rotYy?rotZҌscaleX??scaleY?scaleZ??NicknameConsecrated ObjectiveDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUID36d1acName Custom_TokenTransformposX`;@posYy?posZrotX`9v@rotY`e>?rotZv@scaleX?scaleY?scaleZ?NicknameConsecrated ObjectiveDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11344075926540136158/441ECF2486875364E3D840985BDD329EB7EDD3F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID3325daName Custom_TokenTransformposX`HG=posY`posZ@jrotXvN=rotY T>rotZ ܥl=scaleX@"?scaleY$@scaleZ"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11344075926540136158/441ECF2486875364E3D840985BDD329EB7EDD3F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI14 GUID5a6fadNameCustom_Model_Infinite_BagTransformposX ,@posY w?posZsI@rotX>rotYy?rotZ6scaleX@?scaleY?scaleZ@?NicknameConsecration UnitDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects|0tGUIDc600cbName Custom_TokenTransformposXA'@posYy?posZrotX;v@rotY mR?rotZ`v@scaleX?scaleY?scaleZ?NicknameConsecration UnitDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11707490194700849577/D74BEAF91432478B415EAA1390A3AF454F3E856D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID8c4dfaName Custom_TokenTransformposXposYposZrotX`F$1=rotY rotZscaleX`"?scaleY@$@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11707490194700849577/D74BEAF91432478B415EAA1390A3AF454F3E856D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI15 GUIDbcc2faNameCustom_Model_Infinite_BagTransformposX 1@posYv?posZ ǖJ@rotX >rotYy?rotZscaleX?scaleY?scaleZ?NicknameTempting TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUID6b86eeName Custom_TokenTransformposX@posYy?posZy rotXVrotY@}ƛ?rotZzd?scaleX?scaleY?scaleZ?NicknameTempting TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11334881371396603720/20836020B64ABEFED081ACE3E23D463CE2C773C8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID4f4b96Name Custom_TokenTransformposXKposY=posZjrotX -rotYȾrotZd;scaleX`.?scaleY#@scaleZ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11334881371396603720/20836020B64ABEFED081ACE3E23D463CE2C773C8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI16u GUID91de16NameCustom_Model_Infinite_BagTransformposX/@posY@w?posZ ǖJ@rotX`>rotY?rotZWscaleX?scaleY?scaleZ?NicknameBeaconDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUID82c491Name Custom_TokenTransformposX@posYx?posZrotX9rotY@v@rotZJnscaleX?scaleY?scaleZ?NicknameBeaconDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12613944897978262544/582B1C3BF6339216ED74C79F8C7ABC7957408361/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID878918Name Custom_TokenTransformposXXposY@posZ@rotX=rotY >rotZ9p^=scaleX6 ?scaleY@$@scaleZ#?NicknameBeaconDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12613944897978262544/582B1C3BF6339216ED74C79F8C7ABC7957408361/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI17 GUIDf062a8NameCustom_Model_Infinite_BagTransformposXf,@posYw?posZ ǖJ@rotXӻ>rotYy?rotZscaleX|?scaleY?scaleZ|?NicknamePlunderDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID67e50eName Custom_TokenTransformposX@posYu?posZMrotX~v@rotYrr?rotZ}v@scaleX?scaleY?scaleZ?NicknamePlunderDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17102515563881940173/1C96C93721FC2A2047C3647627D60A83BB1F609F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDb7223bName Custom_TokenTransformposX`1=posY`(hPposZQjrotX-rotY>rotZ ܥ<scaleX`!?scaleY#@scaleZ`!?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17102515563881940173/1C96C93721FC2A2047C3647627D60A83BB1F609F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI18 GUID7e5f14NameCustom_Model_Infinite_BagTransformposX@ݝ)@posYw?posZ ǖJ@rotX >rotY^?rotZ scaleX?scaleY?scaleZ?NicknameCleanseDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID251b08Name Custom_TokenTransformposX@posYt?posZrotX v@rotY ?rotZ|v@scaleX?scaleY?scaleZ?NicknameCleanseDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15743033929547957664/0F0B18C9892DB6C8DE934AD6494AA13ABEC59D06/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDf0e64fName Custom_TokenTransformposX pposY!posZ@curotX7=rotY@to>rotZ ܥ<scaleX`o'?scaleY@$@scaleZ`?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15743033929547957664/0F0B18C9892DB6C8DE934AD6494AA13ABEC59D06/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI19 GUID1e34a5NameCustom_Model_Infinite_BagTransformposX1@posYv?posZ oK@rotX@Xz>rotYy?rotZscaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDa443a0Name Custom_TokenTransformposX@H@posY y?posZ`H"rotXv@rotY?rotZdZ?scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDc36fc6Name Custom_TokenTransformposX`muposY+}9=posZ3h>rotXf=rotY;>rotZQ;scaleX@#?scaleY#@scaleZ7 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI20 GUID72702dNameCustom_Model_Infinite_BagTransformposX` 0@posYw?posZ oK@rotX@>rotYy?rotZbscaleX4?scaleY?scaleZZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDcf3933Name Custom_TokenTransformposX@posY`x?posZ'rotX}v@rotYp?rotZ@e?scaleX?scaleY?scaleZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDb2490dName Custom_TokenTransformposX3s>posYposZa>rotX@!=rotY@H(rotZUkscaleX;)?scaleY#@scaleZ@?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI21 GUID8806c6NameCustom_Model_Infinite_BagTransformposX0,@posYv?posZ oK@rotX稵>rotYy?rotZ` scaleX?scaleY?scaleZg?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID1bce95Name Custom_TokenTransformposXn{@posY`x?posZ*8rotXlnrotY r?rotZ`*s8?scaleX?scaleY?scaleZ?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID08e2e1Name Custom_TokenTransformposXnposY~@=posZ==rotX KCrotYȾrotZ=;scaleXb%?scaleY@$@scaleZU?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI22 GUIDaf9bd9NameCustom_Model_Infinite_BagTransformposX D)@posYv?posZ oK@rotX>rotYy?rotZ9㤾scaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUIDc21ac8Name Custom_TokenTransformposX@posYkx?posZnrotX?rotY?rotZ`'tscaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0~GUIDc2a871Name Custom_TokenTransformposX`6_=posY`D=posZGhrotX]=rotY@.d>rotZm;scaleX@#?scaleY@$@scaleZ8 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI23 GUID43644fNameCustom_Model_Infinite_BagTransformposX`p&@posYw?posZ@|K@rotX@J>rotY[h?rotZ@ˎscaleX?scaleY?scaleZ?NicknameMarked Terrain DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDc43efbName Custom_TokenTransformposXp@posYR{?posZ@' rotX~v@rotY)?rotZ`?scaleX?scaleY?scaleZ?Nickname Advanced DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17375442705273788649/C9825BC79C6A1D6C175D482A471EB8111A07E538/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectst0lGUIDf7a604Name Custom_TokenTransformposX 5KposYposZ`5KrotX =rotY T>rotZ ܥL=scaleX'?scaleY$@scaleZ-+?Nickname Advanced DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17375442705273788649/C9825BC79C6A1D6C175D482A471EB8111A07E538/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI24 GUID10530dNameCustom_Model_Infinite_BagTransformposX &@posYv?posZpL@rotX>rotYP?rotZ@GscaleX@?scaleY?scaleZ@?NicknameSpotterDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID2caf7bName Custom_TokenTransformposXe@posY`y?posZ rotX@h?rotY`ϳ?rotZv@scaleX?scaleY?scaleZ?NicknameSpotterDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11169413540459603032/BE84EEBB7E8931CBF43D87D72A14169784FF46D6/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID3e6a8cName Custom_TokenTransformposXposYposZrotX?rotYa~>rotZ  scaleX`"?scaleY#@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11169413540459603032/BE84EEBB7E8931CBF43D87D72A14169784FF46D6/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI25 GUIDda928eNameCustom_Model_Infinite_BagTransformposX K)@posYv?posZpL@rotX@~>rotYP?rotZscaleX@?scaleY?scaleZ@?Nickname EmpoweredDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDb0d692Name Custom_TokenTransformposX @posYx?posZ@E rotX v@rotYv@rotZ@u ?scaleX?scaleY?scaleZ?Nickname EmpoweredDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11098337360439051439/FB35717A49E281348F8FA1382DB5C638D966A84E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID4ba37fName Custom_TokenTransformposXjposY@N=posZ@:=rotXp$rotYؾrotZ`BW;scaleX`"?scaleY #@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11098337360439051439/FB35717A49E281348F8FA1382DB5C638D966A84E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI26 GUID2820bdNameCustom_Model_Infinite_BagTransformposX ,@posY`v?posZpL@rotX>rotYy?rotZ@scaleX@?scaleY?scaleZ??NicknameCursedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUIDb20417Name Custom_TokenTransformposXB@posYx?posZ@7rotXD>v?rotYv@rotZv@scaleX?scaleY?scaleZ?NicknameCursedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12018982883938420699/E955D6C4F074C48DE6E972CC265B77E1411C346C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDd7973dName Custom_TokenTransformposX˽posY@>posZ@j>rotX?=rotY>rotZ {;scaleX"?scaleY@$@scaleZ"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12018982883938420699/E955D6C4F074C48DE6E972CC265B77E1411C346C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI27 GUIDb797d6NameCustom_Model_Infinite_BagTransformposXm 0@posYv?posZpL@rotX ʼ>rotYy?rotZh~scaleX@?scaleY?scaleZ@?NicknameBattle ShockedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID71ef18Name Custom_TokenTransformposX{@posYx?posZ %rotX=IrotY`O?rotZ覆?scaleX?scaleY?scaleZ?NicknameBattle ShockedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17571342072913910979/F18567A81AA610FBF1584CFB77A5AEDB1E0088BE/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDd973aeName Custom_TokenTransformposXj>posY@ڼposZirotXOwrotYؾrotZ ܥ\scaleX`"?scaleY#@scaleZ "?NicknameBattle ShockedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17571342072913910979/F18567A81AA610FBF1584CFB77A5AEDB1E0088BE/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI28 GUIDc569dfNameCustom_Model_Infinite_BagTransformposX1@posYv?posZpL@rotXϸ>rotYP?rotZ/scaleX?scaleY?scaleZ?Nickname Marked Unit DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsw0oGUIDb04b86Name Custom_TokenTransformposXb@posY`y?posZXrotXv@rotY>]?rotZv@scaleX?scaleY?scaleZ?Nickname Marked Unit DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18206224845466279907/0CDF522A890516DC85363FF7E2DF015E9A4CA951/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID1b0734Name Custom_TokenTransformposX!posY@posZ#<rotXuD=rotY@_>rotZ ܥ\scaleX!?scaleY@$@scaleZ!?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18206224845466279907/0CDF522A890516DC85363FF7E2DF015E9A4CA951/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI199GUID58d30bNameBagTransformposX jCposY?posZLCrotX@BrotYf@rotZD>scaleX?scaleY?scaleZ?NicknameMission & Utility Tokens RedDescription GMNotesAltLookAngle&xyzColorDiffuse&r?g?bLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexBagOrderLuaScript--[[ Ultimate memory bag by bercianor ]]---------------------------------------- -- Version: 1.2.2 -- URL: -- Fork of "Utility memory bag 2.7.0" by Directsun at -- Boxes thanks to "(Tool) - Tragic Game Boxes" by Tragic --[[ ChangeLog: v1.0.0: Relative position - Added posibility of save Absolute position or Relative to Bag position of the objects (each object) - When bag rotate and the object is saved as relative position, its position rotates according to bag - When you rotate an object and apply that movement to rest, all objects rotates its position relative to the moved object v1.1.0: Reordening - Buttons little relocation - Code reorder - Set pan variables for the buttons v1.1.1: Selecction button position - A new better calc for the position of the selection buttons, but it needs to be improved v1.2.0: Now with boxes! - Show content when container flipped (only objects inside bag) v1.2.1: Final version? - Shows all saved abjects v1.2.2: Fix selection button location ]]------------------------------------------------------------------------------ --[[ Events ]]------------------------------------------------------------------ function onload(saved_data) GlobalMemoryGroups:onLoad(self.getGUID()) AllMemoryBagsInScene:add(self.getGUID()) btn_x_offset = 0 btn_y_offset = 0.3 btn_z_offset = 0 text_y_offset = 0 --Set up information for if there is no saved data memoryList = {} memoryGroupName:set(nil) position_type = "relative" if saved_data ~= "" then --Set up information off of loaded_data local loaded_data = JSON.decode(saved_data) memoryList = loaded_data.ml or memoryList memoryGroupName:set(loaded_data.groupName or nil) position_type = loaded_data.pos_type or position_type end moveList = {} moveGuid = nil fresh = true if next(memoryList) ~= nil then fresh = false end if self.getRotation().z == 180 then showContent() elseif fresh then createSetupButton() else createMemoryActionButtons() end end function updateSave() saved_data = JSON.encode({ ["ml"]=memoryList, ["groupName"]=memoryGroupName:get(), ["pos_type"]=position_type }) self.script_state = saved_data end --Shows content (grouped) on flip function onObjectRotate(object, spin, flip, player_color, old_spin, old_flip) if object == self and flip ~= old_flip then if flip == 180 then object.clearButtons() object.clearInputs() showContent() elseif flip == 0 then object.UI.setXml("") if fresh then createSetupButton() else createMemoryActionButtons() end end end end function showContent() local bagObjList = self.getObjects() local content = {} for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then if content[obj.getName()] ~= nil then content[obj.getName()] = content[obj.getName()] + 1 else content[obj.getName()] = 1 end else for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then if content[bagObj.name] ~= nil then content[bagObj.name] = content[bagObj.name] + 1 else content[bagObj.name] = 1 end break end end end end local sb = {} for name, amount in pairs(content) do table.insert(sb, string.format("- %dx %s\n", amount, name)) end createBackText(table.concat(sb)) end --[[ Events ]]------------------------------------------------------------------ --[[ UI Creations ]]------------------------------------------------------------ --Make setup button function createSetupButton() self.createButton({ label="Setup", click_function="buttonClick_setup", function_owner=self, position={0,btn_y_offset,2+btn_z_offset}, rotation={0,0,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end --Creates Setting Up buttons function createSetupActionButtons(move) self.createButton({ label="Cancel", click_function="buttonClick_cancel", function_owner=self, position={0,btn_y_offset,2+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Submit", click_function="buttonClick_submit", function_owner=self, position={0,btn_y_offset,2.8+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) if move == false then self.createButton({ label="Add", click_function="buttonClick_add", function_owner=self, position={0,btn_y_offset,3.6+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.25,1,0.25} }) self.createButton({ label="Selection", click_function="editDragSelection", function_owner=self, position={0,btn_y_offset,-2-btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,1,1} }) groupNameInput:create(memoryGroupName:get()) if fresh == false then self.createButton({ label="Set New", click_function="buttonClick_setNew", function_owner=self, position={0,btn_y_offset,4.4+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={0.75,0.75,1} }) self.createButton({ label="Remove", click_function="buttonClick_remove", function_owner=self, position={0,btn_y_offset,5.2+btn_z_offset}, rotation={0,0,0}, height=350, width=1100, font_size=250, color={0,0,0}, font_color={1,0.25,0.25} }) end end self.createButton({ label="Reset", click_function="buttonClick_reset", function_owner=self, position={2+btn_x_offset,btn_y_offset,0}, rotation={0,90,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) if position_type == "relative" then self.createButton({ label="Rel. Pos.", click_function="buttonClick_posType", function_owner=self, position={2.8+btn_x_offset,btn_y_offset,0}, rotation={0,90,0}, height=350, width=1000, font_size=250, color={0,0,0}, font_color={1,1,1} }) else self.createButton({ label="Abs. Pos.", click_function="buttonClick_posType", function_owner=self, position={2.8+btn_x_offset,btn_y_offset,0}, rotation={0,90,0}, height=350, width=1000, font_size=250, color={0,0,0}, font_color={1,1,1} }) end end --Creates selection buttons on objects function createButtonsOnAllObjects(move) buttonIndexMap = {} local howManyButtons = 0 local objsToHaveButtons = {} if move == true then for guid in pairs(memoryListBackup) do table.insert(objsToHaveButtons, getObjectFromGUID(guid)) end else objsToHaveButtons = getAllObjects() end for _, obj in ipairs(objsToHaveButtons) do if obj ~= self then --On a normal bag, the button positions aren't the same size as the bag. --TODO: Needs a better calc for the globalScaleFactor local globalScaleFactor = 1.25/(1+math.log(self.getScale().x,6)) local deltaPos = findOffsetDistance(self.getPosition(), obj.getPosition(), obj) deltaPos = Vector(deltaPos.x,deltaPos.y,deltaPos.z):rotateOver('y', 180-self.getRotation().y) local objPos = {} objPos.x = deltaPos.x * globalScaleFactor objPos.y = deltaPos.y * globalScaleFactor objPos.z = -deltaPos.z * globalScaleFactor --Workaround for custom PDFs if obj.Book then objPos.y = objPos.y + 0.5 end --Offset rotation of bag local rot = self.getRotation() rot.y = 180-rot.y --Create function local funcName = "selectButton_" .. howManyButtons local func = function() buttonClick_selection(obj, move) end local color = {0.75,0.25,0.25,0.6} local colorMove = {0,0,1,0.6} if move == true then color = colorMove end self.setVar(funcName, func) self.createButton({ click_function=funcName, function_owner=self, position=objPos, rotation=rot, height=1000, width=1000, color=color, }) buttonIndexMap[obj.getGUID()] = howManyButtons howManyButtons = howManyButtons + 1 end end end --Creates recall and place buttons function createMemoryActionButtons() self.createButton({ label="Place", click_function="buttonClick_place", function_owner=self, position={0,btn_y_offset,2+btn_z_offset}, rotation={0,0,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) self.createButton({ label="Recall", click_function="buttonClick_recall", function_owner=self, position={0,btn_y_offset,2.8+btn_z_offset}, rotation={0,0,0}, height=350, width=800, font_size=250, color={0,0,0}, font_color={1,1,1} }) end function createBackText(text) self.UI.setXmlTable({ { tag="Panel", attributes={ height = (self.getBoundsNormalized().size.z*67.5/self.getScale().z), width = (self.getBoundsNormalized().size.x*67.5/self.getScale().x), position = "0 0 "..text_y_offset*100, rotation = "180 0 0", rectAlignment = "MiddleCenter" }, children={ tag = "Text", attributes = { text = text, id = "content", color = "#000000FF", fontStyle = "Bold", alignment ="UpperLeft", resizeTextForBestFit= true, resizeTextMinSize = 10, resizeTextMaxSize = 40, horizontalOverflow = "Wrap", -- Wrap or Overflow? verticalOverflow = "Truncate" -- Truncate or Overflow? } } } }) end --[[ UI Creations ]]------------------------------------------------------------ --[[ Button Triggers ]]--------------------------------------------------------- --Triggered by Transpose button function buttonClick_transpose() moveGuid = nil broadcastToAll("Select one object and move it- all objects will move relative to the new location", {0.75, 0.75, 1}) memoryListBackup = duplicateTable(memoryList) memoryList = {} moveList = {} self.clearButtons() self.clearInputs() createButtonsOnAllObjects(true) createSetupActionButtons(true) end --Triggered by setup button, function buttonClick_setup() memoryListBackup = duplicateTable(memoryList) memoryList = {} self.clearButtons() self.clearInputs() createButtonsOnAllObjects(false) createSetupActionButtons(false) end --Checks or unchecks buttons function buttonClick_selection(obj, move) local index = buttonIndexMap[obj.getGUID()] local colorMove = {0,0,1,0.6} local color = {0,1,0,0.6} previousGuid = selectedGuid selectedGuid = obj.getGUID() theList = memoryList if move == true then theList = moveList if previousGuid ~= nil and previousGuid ~= selectedGuid then local prevObj = getObjectFromGUID(previousGuid) prevObj.highlightOff() self.editButton({index=previousIndex, color=colorMove}) theList[previousGuid] = nil end previousIndex = index end if theList[selectedGuid] == nil then self.editButton({index=index, color=color}) --Adding pos/rot to memory table local pos = {} local rot = {} if position_type == "relative" then pos = findOffsetDistance(self.getPosition(), obj.getPosition()) rot = findOffsetDistance(self.getRotation(), obj.getRotation()) else pos = obj.getPosition() rot = obj.getRotation() end --I need to add it like this or it won't save due to indexing issue theList[obj.getGUID()] = { bag_rotation=self.getRotation(), pos_type=position_type, pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock(), tint=obj.getColorTint() } obj.highlightOn({0,1,0}) else color = {0.75,0.25,0.25,0.6} if move == true then color = colorMove end self.editButton({index=index, color=color}) theList[obj.getGUID()] = nil obj.highlightOff() end end function editDragSelection(bagObj, player, remove) local selectedObjs = Player[player].getSelectedObjects() if not remove then for _, obj in ipairs(selectedObjs) do local index = buttonIndexMap[obj.getGUID()] --Ignore if already in the memory list, or does not have a button if index and not memoryList[obj.getGUID()] then self.editButton({index=index, color={0,1,0,0.6}}) --Adding pos/rot to memory table local pos = {} local rot = {} if position_type == "relative" then pos = findOffsetDistance(self.getPosition(), obj.getPosition()) rot = findOffsetDistance(self.getRotation(), obj.getRotation()) else pos = obj.getPosition() rot = obj.getRotation() end --I need to add it like this or it won't save due to indexing issue memoryList[obj.getGUID()] = { bag_rotation=self.getRotation(), pos_type=position_type, pos={x=round(pos.x,4), y=round(pos.y,4), z=round(pos.z,4)}, rot={x=round(rot.x,4), y=round(rot.y,4), z=round(rot.z,4)}, lock=obj.getLock(), tint=obj.getColorTint() } obj.highlightOn({0,1,0}) end end else for _, obj in ipairs(selectedObjs) do local index = buttonIndexMap[obj.getGUID()] if index and memoryList[obj.getGUID()] then color = {0.75,0.25,0.25,0.6} self.editButton({index=index, color=color}) memoryList[obj.getGUID()] = nil obj.highlightOff() end end end end function updateMemoryWithMoves() memoryList = memoryListBackup --get the first transposed object's coordinates local obj = getObjectFromGUID(moveGuid) local refObjPos = {} local refObjRot = {} if memoryList[moveGuid].pos_type == "relative" then refObjPos.x = self.getPosition().x + memoryList[moveGuid].pos.x refObjPos.y = self.getPosition().y + memoryList[moveGuid].pos.y refObjPos.z = self.getPosition().z + memoryList[moveGuid].pos.z refObjRot.x = self.getRotation().x + memoryList[moveGuid].rot.x refObjRot.y = self.getRotation().y + memoryList[moveGuid].rot.y refObjRot.z = self.getRotation().z + memoryList[moveGuid].rot.z else refObjPos.x = memoryList[moveGuid].pos.x refObjPos.y = memoryList[moveGuid].pos.y refObjPos.z = memoryList[moveGuid].pos.z refObjRot.x = memoryList[moveGuid].rot.x refObjRot.y = memoryList[moveGuid].rot.y refObjRot.z = memoryList[moveGuid].rot.z end local deltaPos = findOffsetDistance(obj.getPosition(), refObjPos) local deltaRot = findOffsetDistance(obj.getRotation(), refObjRot) for guid, entry in pairs(memoryList) do local pos = {} local rot = {} pos.x = entry.pos.x - deltaPos.x pos.y = entry.pos.y - deltaPos.y pos.z = entry.pos.z - deltaPos.z rot.x = entry.rot.x - deltaRot.x rot.y = entry.rot.y - deltaRot.y rot.z = entry.rot.z - deltaRot.z if entry.pos_type == "relative" then pos = obj.positionToWorld(obj.positionToLocal(Vector(self.getPosition().x + pos.x, self.getPosition().y + pos.y, self.getPosition().z + pos.z)):rotateOver('y', -deltaRot.y)) pos = findOffsetDistance(self.getPosition(), pos) else pos = obj.positionToWorld(obj.positionToLocal(Vector(pos.x,pos.y,pos.z)):rotateOver('y', -deltaRot.y)) end memoryList[guid].pos.x = pos.x memoryList[guid].pos.y = pos.y memoryList[guid].pos.z = pos.z memoryList[guid].rot.x = rot.x memoryList[guid].rot.y = rot.y memoryList[guid].rot.z = rot.z end moveList = {} end --Cancels selection process function buttonClick_cancel() memoryList = memoryListBackup moveList = {} self.clearButtons() self.clearInputs() if next(memoryList) == nil then createSetupButton() else createMemoryActionButtons() end removeAllHighlights() broadcastToAll("Selection Canceled", {1,1,1}) moveGuid = nil end --Saves selections function buttonClick_submit() fresh = false if next(moveList) ~= nil then for guid in pairs(moveList) do moveGuid = guid end if memoryListBackup[moveGuid] == nil then broadcastToAll("Item selected for moving is not already in memory", {1, 0.25, 0.25}) else broadcastToAll("Moving all items in memory relative to new objects position!", {0.75, 0.75, 1}) self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(moveList) do moveGuid = guid count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end updateMemoryWithMoves() updateSave() buttonClick_place() end elseif next(memoryList) == nil and moveGuid == nil then memoryList = memoryListBackup broadcastToAll("No selections made.", {0.75, 0.25, 0.25}) end combineMemoryFromBagsWithin() groupNameInput:setGroupNameToInputField() self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() moveGuid = nil end function buttonClick_add() fresh = false combineTables(memoryList, memoryListBackup) broadcastToAll("Adding internal bags and selections to existing memory", {0.25, 0.75, 0.25}) combineMemoryFromBagsWithin() self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Saved", {1,1,1}) updateSave() end function buttonClick_remove() broadcastToAll("Removing Selected Entries From Memory", {1.0, 0.25, 0.25}) self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for guid in pairs(memoryList) do count = count + 1 memoryListBackup[guid] = nil local obj = getObjectFromGUID(guid) if obj ~= nil then obj.highlightOff() end end broadcastToAll(count.." Objects Removed", {1,1,1}) memoryList = memoryListBackup updateSave() end function buttonClick_setNew() broadcastToAll("Setting new position relative to items in memory", {0.75, 0.75, 1}) self.clearButtons() self.clearInputs() createMemoryActionButtons() local count = 0 for _, obj in ipairs(getAllObjects()) do guid = obj.guid if memoryListBackup[guid] ~= nil then count = count + 1 memoryListBackup[guid].bag_rotation = self.getRotation() memoryListBackup[guid].pos_type = position_type if position_type == "relative" then memoryListBackup[guid].pos = findOffsetDistance(self.getPosition(), obj.getPosition()) memoryListBackup[guid].rot = findOffsetDistance(self.getRotation(), obj.getRotation()) else memoryListBackup[guid].pos = obj.getPosition() memoryListBackup[guid].rot = obj.getRotation() end memoryListBackup[guid].lock = obj.getLock() memoryListBackup[guid].tint = obj.getColorTint() end end broadcastToAll(count.." Objects Saved", {1,1,1}) memoryList = memoryListBackup updateSave() end --Resets bag to starting status function buttonClick_reset() fresh = true memoryList = {} memoryGroupName:set(nil) self.clearButtons() self.clearInputs() createSetupButton() removeAllHighlights() broadcastToAll("Tool Reset", {1,1,1}) updateSave() end --Changes between Relative Position and Absolute Position function buttonClick_posType() if position_type == "relative" then for _, obj in ipairs(self.getButtons()) do if obj["label"] == "Rel. Pos." then self.editButton({index=obj.index, label="Abs. Pos."}) break end end position_type="absolute" else for _, obj in ipairs(self.getButtons()) do if obj.label == "Abs. Pos." then self.editButton({index=obj.index, label="Rel. Pos."}) break end end position_type="relative" end end --Sends objects from bag/table to their saved position/rotation function buttonClick_place() if anyOtherBagsInMyGroupArePlaced() then recallOtherBagsInMyGroup() Wait.frames(_placeObjects, CONFIG.MEMORY_GROUP.FRAME_DELAY_BEFORE_PLACING_OBJECTS) else _placeObjects() end end function _placeObjects() local bagObjList = self.getObjects() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) local pos = {} local rot = {} if entry.pos_type == "relative" then local rotated_pos = Vector(entry.pos.x,entry.pos.y,entry.pos.z):rotateOver('y', findOffsetDistance(entry.bag_rotation, self.getRotation()).y) pos.x = self.getPosition().x + rotated_pos.x pos.y = self.getPosition().y + rotated_pos.y pos.z = self.getPosition().z + rotated_pos.z rot.x = self.getRotation().x + entry.rot.x rot.y = self.getRotation().y + entry.rot.y rot.z = self.getRotation().z + entry.rot.z else pos.x = entry.pos.x pos.y = entry.pos.y pos.z = entry.pos.z rot.x = entry.rot.x rot.y = entry.rot.y rot.z = entry.rot.z end if obj ~= nil then --If obj is out on the table, move it to the saved pos/rot obj.setPositionSmooth(pos) obj.setRotationSmooth(rot) obj.setLock(entry.lock) obj.setColorTint(entry.tint) else --If obj is inside of the bag for _, bagObj in ipairs(bagObjList) do if bagObj.guid == guid then local item = self.takeObject({ guid=guid, position=pos, rotation=rot, smooth=false }) item.setLock(entry.lock) item.setColorTint(entry.tint) break end end end end broadcastToAll("Objects Placed", {1,1,1}) end --Recalls objects to bag from table function buttonClick_recall() for guid, entry in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then self.putObject(obj) end end broadcastToAll("Objects Recalled", {1,1,1}) end --[[ Button Triggers ]]--------------------------------------------------------- --[[ This object provides access to a variable stored on the "Global script". The variable holds the GUIDs for every Ultimate Memory Bag in the scene. Example: {'805ebd', '35cc21', 'fc8886', 'f50264', '5f5f63'} --]] AllMemoryBagsInScene = { NAME_OF_GLOBAL_VARIABLE = "_UltimateMemoryBag_AllMemoryBagsInScene" } function AllMemoryBagsInScene:add(guid) local guids = Global.getTable(self.NAME_OF_GLOBAL_VARIABLE) or {} table.insert(guids, guid) Global.setTable(self.NAME_OF_GLOBAL_VARIABLE, guids) end function AllMemoryBagsInScene:getGuidList() return Global.getTable(self.NAME_OF_GLOBAL_VARIABLE) or {} end --[[ Memory Bag Groups ]]------------------------------------------------------- --[[ Ultimate Memory Bags may be added to a named group, called a "memory group". You can add a bag to a group through the bag's UI: "Setup" > "Group Name". Only one bag from a group may have it's contents placed on the table at a time. When "Place" is clicked on a bag, the other bags in it's memory group are recalled. By default a memory bag is not in any group. It's memory group is "nil". --]] CONFIG = { MEMORY_GROUP = { -- This determines how many frames to wait before actually placing objects onto the table when the "Place" button is clicked. -- This gives the other bags time to recall their objects. -- The delay ONLY occurs if other bags have objects out. FRAME_DELAY_BEFORE_PLACING_OBJECTS = 30, }, } memoryGroupName = {memoryBag=self} function memoryGroupName:get() return self._name end function memoryGroupName:set(newName) GlobalMemoryGroups:unregisterBagInGroup(self:get(), self.memoryBag.getGUID()) GlobalMemoryGroups:registerBagInGroup(newName, self.memoryBag.getGUID()) if newName == "" then self._name = nil else self._name = newName end end -- Click the "Recall" button on all other bags in my memory group. function recallOtherBagsInMyGroup() for _,bag in ipairs(getOtherBagsInMyGroup()) do bag.call('buttonClick_recall') end end -- Return "true" if another bag in my memory group has any objects out on the table. function anyOtherBagsInMyGroupArePlaced() for _,bag in ipairs(getOtherBagsInMyGroup()) do local state = bag.call('areAnyOfMyObjectsPlaced') if state then return true end end return false end -- Return "true" if at least one object from this memory bag is out on the table. function areAnyOfMyObjectsPlaced() for guid,_ in pairs(memoryList) do local obj = getObjectFromGUID(guid) if obj ~= nil then return true end end return false end function getOtherBagsInMyGroup() local bags = {} for bagGuid,_ in pairs(GlobalMemoryGroups:getGroup(memoryGroupName:get())) do if bagGuid ~= self.getGUID() then bag = getObjectFromGUID(bagGuid) -- "bag" is nill if it has been deleted since the last time onLoad() was called. if bag ~= nil then table.insert(bags, bag) end end end return bags end --[[ This object provides access to a variable stored on the "Global script". The variable holds the names & guids of all memory bag groups. The global variable is a table and holds data like this: { 'My First Group Name' = { '805ebd' = {}, '35cc21' = {}, 'fc8886' = {}, }, 'My Second Group Name' = { 'f50264' = {}, '5f5f63' = {}, }, } --]] GlobalMemoryGroups = { NAME_OF_GLOBAL_VARIABLE = '_GlobalUltimateMemoryBagGroups', } -- Call me inside this script's "onLoad()" method! function GlobalMemoryGroups:onLoad(myGuid) -- Create and initialize the global variable if it doesn't already exist: if self:_getGroups() == nil then self:_setGroups({}) end end -- Return the GUIDs of all bags in the "groupName". The return value is a dictionary that maps [GUID -> empty table]. function GlobalMemoryGroups:getGroup(groupName) guids = self:_getGroups()[groupName] or {} return guids end -- Registers a bag in a memory group. Creates a new group if one doesn't exist. function GlobalMemoryGroups:registerBagInGroup(groupName, bagGuid) if groupName == nil or groupName == "" then return end self:_tryCreateNewGroup(groupName) local groups = self:_getGroups() groups[groupName][bagGuid] = {} self:_setGroups(groups) end -- Removes this bag from the memory group. function GlobalMemoryGroups:unregisterBagInGroup(groupName, bagGuid) local groups = self:_getGroups() local group = groups[groupName] if group ~= nil then group[bagGuid] = nil self:_setGroups(groups) end end -- Return the global variable, which is a table holding all memory group names & guids. function GlobalMemoryGroups:_getGroups() return Global.getTable(self.NAME_OF_GLOBAL_VARIABLE) end -- Override the global variable (i.e. the entire table). function GlobalMemoryGroups:_setGroups(newTable) Global.setTable(self.NAME_OF_GLOBAL_VARIABLE, newTable) end -- Add a new memory group named "groupName" to the global variable, if one doesn't already exist. function GlobalMemoryGroups:_tryCreateNewGroup(groupName) local groups = self:_getGroups() if groups[groupName] == nil then groups[groupName] = {} self:_setGroups(groups) end end -- This object controls the "Group Name" input text field that is part of the bag's ingame UI. groupNameInput = { greyedOutText = "Group Name", widthPerCharacter = 100, padding = 4, memoryBag=self, } function groupNameInput:create(optionalStartingValue) local effectiveText = optionalStartingValue or self.greyedOutText local width = self:computeWidth(effectiveText) self.memoryBag.createInput({ label=self.greyedOutText, value=optionalStartingValue or nil, alignment=3, -- Center aligned input_function="groupNameInput_onCharacterTyped", function_owner=self.memoryBag, position={-2.1-btn_x_offset,btn_y_offset,0}, rotation={0,270,0}, width=width, height=350, font_size=250, color={0,0,0}, font_color={1,1,1}, }) end function groupNameInput:computeWidth(text) return (string.len(text) + self.padding) * self.widthPerCharacter end function groupNameInput:updatedWidth(text) self.memoryBag.editInput({ index=0, width=self:computeWidth(text) }) end function groupNameInput:onCharacterTyped(text, stillEditing) if stillEditing then self:updatedWidth(text) else if text == "" then self:updatedWidth(self.greyedOutText) end end end function groupNameInput_onCharacterTyped(memoryBag, playerColor, text, stillEditing) groupNameInput:onCharacterTyped(text, stillEditing) end function groupNameInput:setGroupNameToInputField() local inputFields = self.memoryBag.getInputs() if inputFields ~= nil then -- Get input field 0, which corresponds to the groupNameInput. -- Unfortunately "self.getInputs()" doesn't return the inputs in a guaranteed order. local nameField = nil for _,field in ipairs(inputFields) do if field.index == 0 then nameField = field end end memoryGroupName:set(nameField.value) end end --[[ Memory Bag Groups ]]------------------------------------------------------- --[[ Utility functions ]]------------------------------------------------------- function combineTables(first_table, second_table) for k,v in pairs(second_table) do first_table[k] = v end end function combineMemoryFromBagsWithin() local bagObjList = self.getObjects() for _, bagObj in ipairs(bagObjList) do local data = bagObj.lua_script_state if data ~= nil then local j = JSON.decode(data) if j ~= nil and j.ml ~= nil then for guid, entry in pairs(j.ml) do memoryList[guid] = entry end end end end end --Find delta (difference) between 2 x/y/z coordinates function findOffsetDistance(p1, p2, obj) local xOffset = 0 local yOffset = 0 local zOffset = 0 if obj ~= nil then local bounds = obj.getBounds() yOffset = (bounds.size.y - bounds.offset.y) end local deltaPos = {} deltaPos.x = (p2.x-p1.x) deltaPos.y = (p2.y-p1.y) + yOffset deltaPos.z = (p2.z-p1.z) return deltaPos end --Coroutine delay, in seconds function wait(time) local start = os.time() repeat coroutine.yield(0) until os.time() > start + time end --Duplicates a table (needed to prevent it making reference to the same objects) function duplicateTable(oldTable) local newTable = {} for k, v in pairs(oldTable) do newTable[k] = v end return newTable end --Moves scripted highlight from all objects function removeAllHighlights() for _, obj in ipairs(getAllObjects()) do obj.highlightOff() end end --Round number (num) to the Nth decimal (dec) function round(num, dec) local mult = 10^(dec or 0) return math.floor(num * mult + 0.5) / mult end --Converts to string any object function table_print(tt, indent, done) done = done or {} indent = indent or 0 if type(tt) == 'table' then local sb = {} for key, value in pairs (tt) do table.insert(sb, string.rep (" ", indent)) -- indent it if type (value) == "table" and not done [value] then done [value] = true table.insert(sb, key .. " = {\n"); table.insert(sb, table_print(value, indent + 2, done)) table.insert(sb, string.rep (" ", indent)) -- indent it table.insert(sb, "}\n"); elseif "number" == type(key) then table.insert(sb, string.format("\"%s\"\n", tostring(value))) else table.insert(sb, string.format("%s = \"%s\"\n", tostring (key), tostring(value))) end end return table.concat(sb) else return tt .. "\n" end end function toStr(o) if "nil" == type(o) then return tostring(nil) elseif "table" == type(o) then return table_print(o,2) elseif "string" == type(o) then return o else return tostring(o) end end --[[ Utility functions ]]------------------------------------------------------- LuaScriptState{"ml":{"042b2a":{"bag_rotation":{"x":-2.37455356000282E-06,"y":179.998001098633,"z":2.46835384132282E-07},"lock":false,"pos":{"x":22.7226,"y":0.2563,"z":-12.3897},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"07f5de":{"bag_rotation":{"x":-2.58799786934105E-06,"y":179.998001098633,"z":2.00148349449591E-07},"lock":false,"pos":{"x":21.1284,"y":0.2563,"z":-12.3897},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"09cca7":{"bag_rotation":{"x":-1.30733974401664E-06,"y":179.998001098633,"z":6.00538783146476E-08},"lock":false,"pos":{"x":22.7641,"y":0.2563,"z":-5.4568},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"0b7d4c":{"bag_rotation":{"x":-2.48127594204561E-06,"y":179.998016357422,"z":2.06816451964187E-07},"lock":false,"pos":{"x":27.5062,"y":0.2563,"z":-19.2607},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"0f1270":{"bag_rotation":{"x":-2.26783186008106E-06,"y":179.998001098633,"z":2.46833451456041E-07},"lock":false,"pos":{"x":22.7229,"y":0.2563,"z":-16.9638},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1a66f6":{"bag_rotation":{"x":-2.26783163270738E-06,"y":179.998016357422,"z":2.66843784402226E-07},"lock":false,"pos":{"x":24.3165,"y":0.2563,"z":-16.9638},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"1fde4b":{"bag_rotation":{"x":-1.57413728629763E-06,"y":179.998001098633,"z":4.53594850569061E-07},"lock":false,"pos":{"x":25.9657,"y":0.2563,"z":-7.786},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"351f55":{"bag_rotation":{"x":-1.46741160733654E-06,"y":179.998001098633,"z":6.80376558648277E-07},"lock":false,"pos":{"x":22.7773,"y":0.2563,"z":-7.786},"pos_type":"relative","rot":{"x":0,"y":-0.0179,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"42d4eb":{"bag_rotation":{"x":-1.30733974401664E-06,"y":179.998001098633,"z":6.00538783146476E-08},"lock":false,"pos":{"x":21.1699,"y":0.2563,"z":-5.4569},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"584f9d":{"bag_rotation":{"x":-2.26783140533371E-06,"y":179.998001098633,"z":2.66843812823936E-07},"lock":false,"pos":{"x":21.1674,"y":0.2563,"z":-14.6618},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"60f702":{"bag_rotation":{"x":-2.48127571467194E-06,"y":179.998001098633,"z":2.33496990631465E-07},"lock":false,"pos":{"x":24.3571,"y":0.2563,"z":-14.6618},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"670d96":{"bag_rotation":{"x":-2.16110947803827E-06,"y":179.998001098633,"z":2.80182149481334E-07},"lock":false,"pos":{"x":21.1293,"y":0.2563,"z":-16.9638},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"6aa064":{"bag_rotation":{"x":-2.16110970541195E-06,"y":179.998016357422,"z":2.66841880147695E-07},"lock":false,"pos":{"x":25.9101,"y":0.2563,"z":-16.9638},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"78391f":{"bag_rotation":{"x":-2.48127571467194E-06,"y":179.998001098633,"z":2.33496990631465E-07},"lock":false,"pos":{"x":22.7623,"y":0.2563,"z":-14.6618},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"7d74f9":{"bag_rotation":{"x":-1.30733974401664E-06,"y":179.998001098633,"z":6.00538783146476E-08},"lock":false,"pos":{"x":25.9525,"y":0.2563,"z":-5.4568},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"99393e":{"bag_rotation":{"x":-2.5879974145937E-06,"y":179.998001098633,"z":2.20158710817486E-07},"lock":false,"pos":{"x":21.1743,"y":0.2563,"z":-10.08},"pos_type":"relative","rot":{"x":0,"y":-0.0179,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"a6d4e4":{"bag_rotation":{"x":-2.10774055631191E-06,"y":179.998001098633,"z":7.40418556688383E-07},"lock":false,"pos":{"x":22.7685,"y":0.2563,"z":-10.08},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"b7e49f":{"bag_rotation":{"x":-2.48127571467194E-06,"y":179.998001098633,"z":2.33496990631465E-07},"lock":false,"pos":{"x":25.9519,"y":0.2563,"z":-14.6618},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"bc6ef1":{"bag_rotation":{"x":-2.48127594204561E-06,"y":179.998016357422,"z":2.06816451964187E-07},"lock":false,"pos":{"x":22.7234,"y":0.2563,"z":-19.2607},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c1d652":{"bag_rotation":{"x":-1.30733974401664E-06,"y":179.998001098633,"z":6.00538783146476E-08},"lock":false,"pos":{"x":27.5467,"y":0.2563,"z":-5.4568},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"c8a9d6":{"bag_rotation":{"x":-1.30733974401664E-06,"y":179.998001098633,"z":6.00538783146476E-08},"lock":false,"pos":{"x":24.3583,"y":0.2563,"z":-5.4568},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"cd778f":{"bag_rotation":{"x":-2.48127594204561E-06,"y":179.998016357422,"z":2.06816451964187E-07},"lock":false,"pos":{"x":25.9119,"y":0.2563,"z":-19.2607},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"ce0224":{"bag_rotation":{"x":-2.48127594204561E-06,"y":179.998016357422,"z":2.06816451964187E-07},"lock":false,"pos":{"x":24.3177,"y":0.2563,"z":-19.2607},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"d3411d":{"bag_rotation":{"x":-1.52077257098426E-06,"y":179.998001098633,"z":6.73707404530433E-07},"lock":false,"pos":{"x":24.3715,"y":0.2563,"z":-7.786},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e28aea":{"bag_rotation":{"x":-2.37455356000282E-06,"y":179.998001098633,"z":2.46835384132282E-07},"lock":false,"pos":{"x":24.3168,"y":0.2563,"z":-12.3897},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e3addd":{"bag_rotation":{"x":-2.26783186008106E-06,"y":179.998016357422,"z":2.53503515068587E-07},"lock":false,"pos":{"x":27.4332,"y":0.2563,"z":-16.9642},"pos_type":"relative","rot":{"x":0,"y":-0.018,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"e74646":{"bag_rotation":{"x":-1.57413376200566E-06,"y":179.998001098633,"z":6.53698009500658E-07},"lock":false,"pos":{"x":21.1831,"y":0.2563,"z":-7.786},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"f28137":{"bag_rotation":{"x":-1.57413376200566E-06,"y":179.998001098633,"z":6.53698009500658E-07},"lock":false,"pos":{"x":24.3627,"y":0.2563,"z":-10.08},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}},"f67296":{"bag_rotation":{"x":-2.48127594204561E-06,"y":179.998016357422,"z":2.00146359929931E-07},"lock":false,"pos":{"x":21.1291,"y":0.2563,"z":-19.2607},"pos_type":"relative","rot":{"x":0,"y":-0.0181,"z":0},"tint":{"a":0,"b":1,"g":1,"r":1}}},"pos_type":"relative"}XmlUIContainedObjects20 GUID09cca7NameCustom_Model_Infinite_BagTransformposX@8g0posY }?posZ (FrotXrotY`\f@rotZ$>scaleX*?scaleY?scaleZ*?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDf41e86Name Custom_TokenTransformposX Vu@posY (n?posZbrotX`v@rotYY?rotZxv@scaleX?scaleY?scaleZ?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9633452287099972292/3E3D05BD43C3614171D5B04A7F840BD1EABC5C08/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsv0nGUIDa96ff7Name Custom_TokenTransformposX K?posY`w=posZ K?rotX`f6rotYP$>rotZ 9scaleXB)?scaleY@$@scaleZ`B)?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9633452287099972292/3E3D05BD43C3614171D5B04A7F840BD1EABC5C08/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI1 GUID42d4ebNameCustom_Model_Infinite_BagTransformposXU1posY}?posZ`+FrotX@"rotY[f@rotZو>scaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects{0sGUIDcc0623Name Custom_TokenTransformposX@rz@posYfy?posZ rotXMv@rotY_?rotZv@scaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11015760531339961670/14CCDC1610D7B735F12A6B2481F37253147E7ED2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects{0sGUID67e977Name Custom_TokenTransformposX3K?posYposZ@[ K?rotXFj=rotY$>rotZܥlscaleXt'?scaleY@$@scaleZ`+?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11015760531339961670/14CCDC1610D7B735F12A6B2481F37253147E7ED2/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI2 GUIDc8a9d6NameCustom_Model_Infinite_BagTransformposX5-posY }?posZ (FrotX8rotY \f@rotZ` >scaleX?scaleY?scaleZ??NicknameVanguard OperationDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects}0uGUID9a6e83Name Custom_TokenTransformposX`O@posY x?posZ`믿rotXsrotYv@rotZ`Z?scaleX?scaleY?scaleZ?NicknameVanguard OperationDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14133185665765790575/AFD9923A3E7EB16C6CDAEADA46627956D4E05044/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects}0uGUIDd6e655Name Custom_TokenTransformposXposYposZrotX onrotY W>rotZ yscaleX`0?scaleY#@scaleZ"?NicknameVanguard OperationDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14133185665765790575/AFD9923A3E7EB16C6CDAEADA46627956D4E05044/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI3 GUID7d74f9NameCustom_Model_Infinite_BagTransformposXm*posY|?posZ (FrotX>rotY\f@rotZF>scaleX?scaleY?scaleZ?Nickname Vital LinkDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUIDb2795fName Custom_TokenTransformposXfB@posY{?posZv|?rotX@v@rotY@$v@rotZ@~v@scaleX?scaleY?scaleZ?Nickname Vital LinkDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13316841862099947121/B4A1B6952BE61F9BE9181DAB8CCE9CCE1C4C1D81/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsu0mGUID77f73cName Custom_TokenTransformposX=posY@>posZ rotXao=rotYt>rotZ ܥ|scaleX#?scaleY@$@scaleZ@.?Nickname Vital LinkDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13316841862099947121/B4A1B6952BE61F9BE9181DAB8CCE9CCE1C4C1D81/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI4{ GUIDc1d652NameCustom_Model_Infinite_BagTransformposX='posY }?posZ (FrotXIٺrotY[f@rotZ6x>scaleX@?scaleY?scaleZ??Nickname SabotageDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectss0kGUIDb20417Name Custom_TokenTransformposXb@posYzx?posZ?rotX v@rotYv@rotZv@scaleX?scaleY?scaleZ?Nickname SabotageDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13345490819359870575/518551630D7C3C729B6E61D222F954B6FC62D289/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectss0kGUIDa50e30Name Custom_TokenTransformposXposYposZrotX`[rotYc>rotZܥ\=scaleX`"?scaleY #@scaleZ"?Nickname SabotageDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13345490819359870575/518551630D7C3C729B6E61D222F954B6FC62D289/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI5 GUID1fde4bNameCustom_Model_Infinite_BagTransformposX8g*posY@}?posZ`K1GrotX@GrotY[f@rotZ@z>scaleX8?scaleY?scaleZ6?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsw0oGUIDa35171Name Custom_TokenTransformposX @posY jy?posZG2rotX C~v@rotY~wv@rotZ@~v@scaleX?scaleY?scaleZ?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12903101805084442889/DAF461EA99AA3533BE61030FE4F69D8CD3FE0E45/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsw0oGUID80825cName Custom_TokenTransformposXposYposZrotX&=rotY@S>rotZ ?;scaleX@ "?scaleY@$@scaleZ@ "?Nickname Sensor SweepDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12903101805084442889/DAF461EA99AA3533BE61030FE4F69D8CD3FE0E45/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI6p GUIDd3411dNameCustom_Model_Infinite_BagTransformposXs-posY}?posZ`K1GrotX`vrotY@\f@rotZ`Sߧ>scaleX@?scaleY?scaleZ@?NicknameDecoyDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID4c2bbaName Custom_TokenTransformposX*@posY`x?posZrotXv@rotYDJ?rotZv@scaleX?scaleY?scaleZ?NicknameDecoyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9396773628506016304/1072CBD8A5DCD191E4EF1AED010A112E3897F02D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectso0gGUID93731bName Custom_TokenTransformposXZ<posYposZ; =rotX31rotY`p>rotZ ܥlscaleX`"?scaleY$@scaleZ`"?NicknameDecoyDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9396773628506016304/1072CBD8A5DCD191E4EF1AED010A112E3897F02D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI7 GUID351f55NameCustom_Model_Infinite_BagTransformposX@c0posY@}?posZ`K1GrotXrotY@^f@rotZS>scaleX@?scaleY?scaleZ@?NicknameTrapDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectso0gGUID917f6aName Custom_TokenTransformposX[@posYx?posZ0rotX:?rotY@?rotZ`lh?scaleX?scaleY?scaleZ?NicknameTrapDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16996015813023681293/E5618C890547E9BB2026240B22DD534DC7338B09/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID965f71Name Custom_TokenTransformposX=posYً5=posZj>rotX%=rotYૌ>rotZ ܥ<scaleX"?scaleY@$@scaleZ"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/16996015813023681293/E5618C890547E9BB2026240B22DD534DC7338B09/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI8 GUIDe74646NameCustom_Model_Infinite_BagTransformposX1posY}?posZ`K1GrotXcwrotY\f@rotZf>scaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUID4f6460Name Custom_TokenTransformposX@posYWx?posZXrotX v@rotY?rotZL~v@scaleX?scaleY?scaleZ?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9567059880902514641/4C6EEBE9AE3955823BBED6C754D1424E884BA37E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsz0rGUID56a843Name Custom_TokenTransformposXK?posYԽposZ@Z K?rotXX6=rotY>>rotZ ;scaleXt'?scaleY@$@scaleZ+?NicknameOperation MarkerDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9567059880902514641/4C6EEBE9AE3955823BBED6C754D1424E884BA37E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI9 GUIDf28137NameCustom_Model_Infinite_BagTransformposX-posY}?posZ VHrotX@rotY[f@rotZ@ݠ>scaleX@?scaleY?scaleZ@?Nickname SurveilledDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID05ad59Name Custom_TokenTransformposX @posY y?posZ`rotXyv@rotY c3?rotZ`'?scaleX?scaleY?scaleZ?Nickname SurveilledDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14221116480909066130/7E68ABA31FCFC51F1870D10423667C3012585698/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsu0mGUIDfc81eaName Custom_TokenTransformposXposYposZrotXN=rotY`m$>rotZ YscaleX`"?scaleY #@scaleZ`"?Nickname SurveilledDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/14221116480909066130/7E68ABA31FCFC51F1870D10423667C3012585698/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI10 GUIDa6d4e4NameCustom_Model_Infinite_BagTransformposXf0posY}?posZ VHrotX`[,rotY[f@rotZ)_>scaleX@?scaleY?scaleZ??Nickname TriangulateDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUID4da066Name Custom_TokenTransformposX@v@posYx?posZWHrotXv@rotY/?rotZ@v@scaleX?scaleY?scaleZ?Nickname TriangulateDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15167827123933348806/9DBA093351A9089AB5DF389E43D137D63CE47610/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDfe2fb3Name Custom_TokenTransformposX`aO=posY@posZ`j>rotXhQ=rotYԾrotZ ܥLscaleX"?scaleY$@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15167827123933348806/9DBA093351A9089AB5DF389E43D137D63CE47610/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI11 GUID99393eNameCustom_Model_Infinite_BagTransformposX51posY}?posZ VHrotXrotY \f@rotZ>scaleXB?scaleY?scaleZA?NicknameExtract IntelligenceDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0wGUID691117Name Custom_TokenTransformposX1@posYx?posZ rotXG?rotY~v@rotZ@UscaleX?scaleY?scaleZ?NicknameExtract IntelligenceDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13137433287317181357/DD2A59C039A48A080BC2E65ABEA181FD8EDD0D8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0wGUID2d77daName Custom_TokenTransformposXojposYuch=posZ`krotX`=rotY@>rotZ;scaleX"?scaleY#@scaleZ"?NicknameExtract IntelligenceDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13137433287317181357/DD2A59C039A48A080BC2E65ABEA181FD8EDD0D8E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI12 GUIDe28aeaNameCustom_Model_Infinite_BagTransformposXu-posY}?posZ`~IrotX/rotY[f@rotZ`>scaleX@?scaleY?scaleZ@?NicknameConsecration UnitDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects|0tGUIDc600cbName Custom_TokenTransformposXA'@posYy?posZrotX;v@rotY mR?rotZ`v@scaleX?scaleY?scaleZ?NicknameConsecration UnitDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11707490194700849577/D74BEAF91432478B415EAA1390A3AF454F3E856D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID8c4dfaName Custom_TokenTransformposXposYposZrotX`F$1=rotY rotZscaleX`"?scaleY@$@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11707490194700849577/D74BEAF91432478B415EAA1390A3AF454F3E856D/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI13 GUID042b2aNameCustom_Model_Infinite_BagTransformposX q0posY}?posZ`~IrotX`6̺rotY\f@rotZfߓ>scaleX??scaleY?scaleZ??NicknameConsecrated ObjectiveDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjects0xGUID36d1acName Custom_TokenTransformposX`;@posYy?posZrotX`9v@rotY`e>?rotZv@scaleX?scaleY?scaleZ?NicknameConsecrated ObjectiveDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11344075926540136158/441ECF2486875364E3D840985BDD329EB7EDD3F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID3325daName Custom_TokenTransformposX`HG=posY`posZ@jrotXvN=rotY T>rotZ ܥl=scaleX@"?scaleY$@scaleZ"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11344075926540136158/441ECF2486875364E3D840985BDD329EB7EDD3F8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI14 GUID07f5deNameCustom_Model_Infinite_BagTransformposX 2posY}?posZ`~IrotX2rotY[f@rotZ>scaleX@?scaleY?scaleZ@?Nickname CondemnedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDf20b0aName Custom_TokenTransformposXzj@posYXy?posZ rotXJ?rotY`?rotZ`v@scaleX?scaleY?scaleZ?Nickname CondemnedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10136191893480835129/D937B0190529BB65AB07DFAB220043EBCD8D12C4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDcf215aName Custom_TokenTransformposX`_sposY@5=posZj>rotX%/=rotYt>rotZ5;scaleX`"?scaleY@$@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/10136191893480835129/D937B0190529BB65AB07DFAB220043EBCD8D12C4/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI15 GUID584f9dNameCustom_Model_Infinite_BagTransformposX1posY}?posZeJrotX˹rotY[f@rotZ@>scaleX?scaleY?scaleZ?NicknameTempting TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsz0rGUID6b86eeName Custom_TokenTransformposX@posYy?posZy rotXVrotY@}ƛ?rotZzd?scaleX?scaleY?scaleZ?NicknameTempting TargetDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11334881371396603720/20836020B64ABEFED081ACE3E23D463CE2C773C8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID4f4b96Name Custom_TokenTransformposXKposY=posZjrotX -rotYȾrotZd;scaleX`.?scaleY#@scaleZ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11334881371396603720/20836020B64ABEFED081ACE3E23D463CE2C773C8/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI16u GUID78391fNameCustom_Model_Infinite_BagTransformposX`g0posY~?posZeJrotXɸrotY[f@rotZQ9>scaleX?scaleY?scaleZ?NicknameBeaconDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUID82c491Name Custom_TokenTransformposX@posYx?posZrotX9rotY@v@rotZJnscaleX?scaleY?scaleZ?NicknameBeaconDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12613944897978262544/582B1C3BF6339216ED74C79F8C7ABC7957408361/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsq0iGUID878918Name Custom_TokenTransformposXXposY@posZ@rotX=rotY >rotZ9p^=scaleX6 ?scaleY@$@scaleZ#?NicknameBeaconDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12613944897978262544/582B1C3BF6339216ED74C79F8C7ABC7957408361/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI17 GUID60f702NameCustom_Model_Infinite_BagTransformposX@Ӟ-posY}?posZeJrotX.rotY[f@rotZh>scaleX|?scaleY?scaleZ|?NicknamePlunderDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID67e50eName Custom_TokenTransformposX@posYu?posZMrotX~v@rotYrr?rotZ}v@scaleX?scaleY?scaleZ?NicknamePlunderDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17102515563881940173/1C96C93721FC2A2047C3647627D60A83BB1F609F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDb7223bName Custom_TokenTransformposX`1=posY`(hPposZQjrotX-rotY>rotZ ܥ<scaleX`!?scaleY#@scaleZ`!?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17102515563881940173/1C96C93721FC2A2047C3647627D60A83BB1F609F/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI18 GUIDb7e49fNameCustom_Model_Infinite_BagTransformposXIn*posY}?posZeJrotX`.᷾rotY\f@rotZ>scaleX?scaleY?scaleZ?NicknameCleanseDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID251b08Name Custom_TokenTransformposX@posYt?posZrotX v@rotY ?rotZ|v@scaleX?scaleY?scaleZ?NicknameCleanseDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15743033929547957664/0F0B18C9892DB6C8DE934AD6494AA13ABEC59D06/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDf0e64fName Custom_TokenTransformposX pposY!posZ@curotX7=rotY@to>rotZ ܥ<scaleX`o'?scaleY@$@scaleZ`?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15743033929547957664/0F0B18C9892DB6C8DE934AD6494AA13ABEC59D06/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI19 GUID670d96NameCustom_Model_Infinite_BagTransformposX 2posY}?posZ KrotXvrotY[f@rotZ6J>scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDa443a0Name Custom_TokenTransformposX@H@posY y?posZ`H"rotXv@rotY?rotZdZ?scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDc36fc6Name Custom_TokenTransformposX`muposY+}9=posZ3h>rotXf=rotY;>rotZQ;scaleX@#?scaleY#@scaleZ7 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI20 GUID0f1270NameCustom_Model_Infinite_BagTransformposXq0posY ~?posZ KrotX`sTrotY@Zf@rotZMd>scaleX4?scaleY?scaleZZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDcf3933Name Custom_TokenTransformposX@posY`x?posZ'rotX}v@rotYp?rotZ@e?scaleX?scaleY?scaleZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDb2490dName Custom_TokenTransformposX3s>posYposZa>rotX@!=rotY@H(rotZUkscaleX;)?scaleY#@scaleZ@?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI21 GUID1a66f6NameCustom_Model_Infinite_BagTransformposX@-posY}?posZ@ KrotX̾rotY@\f@rotZ g>scaleX?scaleY?scaleZg?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID1bce95Name Custom_TokenTransformposXn{@posY`x?posZ*8rotXlnrotY r?rotZ`*s8?scaleX?scaleY?scaleZ?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID08e2e1Name Custom_TokenTransformposXnposY~@=posZ==rotX KCrotYȾrotZ=;scaleXb%?scaleY@$@scaleZU?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI22 GUID6aa064NameCustom_Model_Infinite_BagTransformposX*posY}?posZ@ KrotX M츾rotY \f@rotZGF>scaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUIDc21ac8Name Custom_TokenTransformposX@posYkx?posZnrotX?rotY?rotZ`'tscaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0~GUIDc2a871Name Custom_TokenTransformposX`6_=posY`D=posZGhrotX]=rotY@.d>rotZm;scaleX@#?scaleY@$@scaleZ8 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI23 GUIDe3adddNameCustom_Model_Infinite_BagTransformposX w'posY~?posZ`KrotX V;rotY[f@rotZ>scaleX?scaleY?scaleZ?NicknameMarked Terrain DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDc43efbName Custom_TokenTransformposXp@posYR{?posZ@' rotX~v@rotY)?rotZ`?scaleX?scaleY?scaleZ?Nickname Advanced DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17375442705273788649/C9825BC79C6A1D6C175D482A471EB8111A07E538/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectst0lGUIDf7a604Name Custom_TokenTransformposX 5KposYposZ`5KrotX =rotY T>rotZ ܥL=scaleX'?scaleY$@scaleZ-+?Nickname Advanced DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17375442705273788649/C9825BC79C6A1D6C175D482A471EB8111A07E538/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI24 GUIDf67296NameCustom_Model_Infinite_BagTransformposX 2posY@~?posZ LrotXOrotY[f@rotZpĖ>scaleX?scaleY?scaleZ?Nickname Marked Unit DescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsw0oGUIDb04b86Name Custom_TokenTransformposXb@posY`y?posZXrotXv@rotY>]?rotZv@scaleX?scaleY?scaleZ?Nickname Marked Unit DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18206224845466279907/0CDF522A890516DC85363FF7E2DF015E9A4CA951/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID1b0734Name Custom_TokenTransformposX!posY@posZ#<rotXuD=rotY@_>rotZ ܥ\scaleX!?scaleY@$@scaleZ!?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/18206224845466279907/0CDF522A890516DC85363FF7E2DF015E9A4CA951/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI25 GUIDbc6ef1NameCustom_Model_Infinite_BagTransformposXq0posY~?posZ LrotX@+rotY[f@rotZ;f>scaleX@?scaleY?scaleZ@?NicknameBattle ShockedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsy0qGUID71ef18Name Custom_TokenTransformposX{@posYx?posZ %rotX=IrotY`O?rotZ覆?scaleX?scaleY?scaleZ?NicknameBattle ShockedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17571342072913910979/F18567A81AA610FBF1584CFB77A5AEDB1E0088BE/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjectsy0qGUIDd973aeName Custom_TokenTransformposXj>posY@ڼposZirotXOwrotYؾrotZ ܥ\scaleX`"?scaleY#@scaleZ "?NicknameBattle ShockedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17571342072913910979/F18567A81AA610FBF1584CFB77A5AEDB1E0088BE/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI26 GUIDce0224NameCustom_Model_Infinite_BagTransformposX-posY}?posZ LrotX@rotY \f@rotZU>scaleX@?scaleY?scaleZ??NicknameCursedDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsq0iGUIDb20417Name Custom_TokenTransformposXB@posYx?posZ@7rotXD>v?rotYv@rotZv@scaleX?scaleY?scaleZ?NicknameCursedDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12018982883938420699/E955D6C4F074C48DE6E972CC265B77E1411C346C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDd7973dName Custom_TokenTransformposX˽posY@>posZ@j>rotX?=rotY>rotZ {;scaleX"?scaleY@$@scaleZ"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12018982883938420699/E955D6C4F074C48DE6E972CC265B77E1411C346C/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI27 GUID0b7d4cNameCustom_Model_Infinite_BagTransformposX{R'posY}?posZ LrotX@ʷrotY[f@rotZ C>scaleX@?scaleY?scaleZ@?NicknameSpotterDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsr0jGUID2caf7bName Custom_TokenTransformposXe@posY`y?posZ rotX@h?rotY`ϳ?rotZv@scaleX?scaleY?scaleZ?NicknameSpotterDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11169413540459603032/BE84EEBB7E8931CBF43D87D72A14169784FF46D6/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID3e6a8cName Custom_TokenTransformposXposYposZrotX?rotYa~>rotZ  scaleX`"?scaleY#@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11169413540459603032/BE84EEBB7E8931CBF43D87D72A14169784FF46D6/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI28 GUIDcd778fNameCustom_Model_Infinite_BagTransformposXĂ*posY~?posZ LrotX "rotY[f@rotZN>scaleX@?scaleY?scaleZ@?Nickname EmpoweredDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDb0d692Name Custom_TokenTransformposX @posYx?posZ@E rotX v@rotYv@rotZ@u ?scaleX?scaleY?scaleZ?Nickname EmpoweredDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11098337360439051439/FB35717A49E281348F8FA1382DB5C638D966A84E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID4ba37fName Custom_TokenTransformposXjposY@N=posZ@:=rotXp$rotYؾrotZ`BW;scaleX`"?scaleY #@scaleZ`"?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/11098337360439051439/FB35717A49E281348F8FA1382DB5C638D966A84E/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI200GUIDe89f4bName Custom_TileTransformposX 'Q@posY?posZ Q@rotXrotYp@rotZ scaleX`gf@scaleY?scaleZ`gf@NicknameBhttps://steamcommunity.com/sharedfiles/filedetails/?id=3744155300DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImagemImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15224246869634929367/9A2B7454113624DE4C4F86AA1FD8427EFD83C0FA/ImageSecondaryURLkhttps://steamusercontent-a.akamaihd.net/ugc/15224246869634929367/9A2B7454113624DE4C4F86AA1FD8427EFD83C0FA/ImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUI201GUID4fcd1fName Custom_TileTransformposX 'Q@posY?posZ`6K@rotX@rotYp@rotZ@scaleX`gf@scaleY?scaleZ`gf@NicknameBhttps://steamcommunity.com/sharedfiles/filedetails/?id=3753014527DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImagemImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12292719357507654116/F1068204D1C646096EFA42D27DCB322E4DE9AFF7/ImageSecondaryURLkhttps://steamusercontent-a.akamaihd.net/ugc/12292719357507654116/F1068204D1C646096EFA42D27DCB322E4DE9AFF7/ImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUI202GUID546244Name Custom_TileTransformposXAT@posY`?posZ`6K@rotX/;rotYp@rotZ`UʺscaleX@scaleY?scaleZ@Nicknamediscord.gg/tts40kDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImagemImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17185686893335760195/B7C2AE850A8D50BBF04219C955DE09E544DD4196/ImageSecondaryURLkhttps://steamusercontent-a.akamaihd.net/ugc/17185686893335760195/B7C2AE850A8D50BBF04219C955DE09E544DD4196/ImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUI203GUID60dd5cName Custom_TileTransformposXAT@posY@?posZ Q@rotX rotY`p@rotZscaleX`gf@scaleY?scaleZ`gf@NicknameBhttps://steamcommunity.com/sharedfiles/filedetails/?id=3728735953DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImagemImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/15285661747835084008/40AC63D5267832D92BAAC45DA4EA3F90D83C3789/ImageSecondaryURLkhttps://steamusercontent-a.akamaihd.net/ugc/15285661747835084008/40AC63D5267832D92BAAC45DA4EA3F90D83C3789/ImageScalar?WidthScaleCustomTile8TypeThickness?StackableStretchLuaScriptLuaScriptStateXmlUI204GUID7dbaa0Name Custom_TileTransformposXAT@posY@?posZrotY@&?rotZ@:scaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUIDc21ac8Name Custom_TokenTransformposX@posYkx?posZnrotX?rotY?rotZ`'tscaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0~GUIDc2a871Name Custom_TokenTransformposX`6_=posY`D=posZGhrotX]=rotY@.d>rotZm;scaleX@#?scaleY@$@scaleZ8 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI208 GUID404fe0NameCustom_Model_Infinite_BagTransformposXA@posYbf?posZŘ?@rotX A>rotY&?rotZ@/scaleX?scaleY?scaleZg?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID1bce95Name Custom_TokenTransformposXn{@posY`x?posZ*8rotXlnrotY r?rotZ`*s8?scaleX?scaleY?scaleZ?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID08e2e1Name Custom_TokenTransformposXnposY~@=posZ==rotX KCrotYȾrotZ=;scaleXb%?scaleY@$@scaleZU?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI209 GUIDadae2aNameCustom_Model_Infinite_BagTransformposXA@posY@cf?posZ@=@rotX >rotY ;?rotZʠscaleX4?scaleY?scaleZZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDcf3933Name Custom_TokenTransformposX@posY`x?posZ'rotX}v@rotYp?rotZ@e?scaleX?scaleY?scaleZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDb2490dName Custom_TokenTransformposX3s>posYposZa>rotX@!=rotY@H(rotZUkscaleX;)?scaleY#@scaleZ@?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI210 GUIDc54686NameCustom_Model_Infinite_BagTransformposXA@posYcf?posZJ:@rotXϹ>rotY*ξrotZ`$scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDa443a0Name Custom_TokenTransformposX@H@posY y?posZ`H"rotXv@rotY?rotZdZ?scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDc36fc6Name Custom_TokenTransformposX`muposY+}9=posZ3h>rotXf=rotY;>rotZQ;scaleX@#?scaleY#@scaleZ7 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI211 GUID389ff7NameCustom_Model_Infinite_BagTransformposXAposYhf?posZ=rotX:rotY`f@rotZڕ>scaleX4?scaleY?scaleZZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectst0lGUIDcf3933Name Custom_TokenTransformposX@posY`x?posZ'rotX}v@rotYp?rotZ@e?scaleX?scaleY?scaleZ?Nickname Reroll 1sDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDb2490dName Custom_TokenTransformposX3s>posYposZa>rotX@!=rotY@H(rotZUkscaleX;)?scaleY#@scaleZ@?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12979792550920251901/1881C353EA92DF7CAF2E9C8E40ACAAC762FDA793/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI212 GUID450445NameCustom_Model_Infinite_BagTransformposX*AposYgf?posZH:<rotẌ́rotY@lf@rotZ`~>scaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUIDc21ac8Name Custom_TokenTransformposX@posYkx?posZnrotX?rotY?rotZ`'tscaleX?scaleY?scaleZ?Nickname +1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0~GUIDc2a871Name Custom_TokenTransformposX`6_=posY`D=posZGhrotX]=rotY@.d>rotZm;scaleX@#?scaleY@$@scaleZ8 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLjhttps://steamusercontent-a.akamaihd.net/ugc/9746593040717650684/7FE00753618D71FFC5B080BE72B4FC4F58864775/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI213 GUIDd1c6ddNameCustom_Model_Infinite_BagTransformposX@AposYgf?posZ9:rotX@ѻrotYf@rotZ>scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsv0nGUIDa443a0Name Custom_TokenTransformposX@H@posY y?posZ`H"rotXv@rotY?rotZdZ?scaleX?scaleY?scaleZ?Nickname -1 to RollsDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUIDc36fc6Name Custom_TokenTransformposX`muposY+}9=posZ3h>rotXf=rotY;>rotZQ;scaleX@#?scaleY#@scaleZ7 ?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/17451409936415375207/4D32671AF1656DCE57D7BEF71FC8E12077561406/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI214 GUIDaf7071NameCustom_Model_Infinite_BagTransformposX@AposYgf?posZ೧?rotX@蹾rotYf@rotZ.>scaleX?scaleY?scaleZg?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse1r?g?b?aLayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexCustomMeshMeshURLjhttps://steamusercontent-a.akamaihd.net/ugc/1666858152071990826/9AD455F2CBAEC01B2CBCDDB8B6DC4CE48D14B545/DiffuseURLNormalURLColliderURLConvexMaterialIndexTypeIndexCastShadowsLuaScriptLuaScriptStateXmlUIContainedObjectsu0mGUID1bce95Name Custom_TokenTransformposXn{@posY`x?posZ*8rotXlnrotY r?rotZ`*s8?scaleX?scaleY?scaleZ?Nickname Reroll AllDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUIChildObjects0GUID08e2e1Name Custom_TokenTransformposXnposY~@=posZ==rotX KCrotYȾrotZ=;scaleXb%?scaleY@$@scaleZU?Nickname[b][i]Battle-shocked[/i][/b]DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/13799366436359510943/F9E14014F56C22AAB19FB4E4108A10912EE862AF/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels9@StandUpStackableLuaScriptLuaScriptStateXmlUI215{GUID176bc4Name Infinite_BagTransformposX +=@posY`A?posZ-T?rotX@ʶrotY`\f@rotZ@7>scaleX?scaleY?scaleZ?Nicknamed6DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDeaee5cNameDie_6TransformposX<posYM @posZ{ ;rotX`tTU@rotYd\@rotZ%]r@scaleX?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexLuaScriptLuaScriptStateXmlUIRotationValues0BValue1Rotation&xVyz1BValue2Rotation&xyz2BValue3Rotation&xyzV3BValue4Rotation&xyzV@4BValue5Rotation&xyzf5BValue6Rotation&xV@yz216{GUID714214Name Infinite_BagTransformposX=posYA?posZ*?@rotX rotY`\f@rotZ>scaleX?scaleY?scaleZ?Nicknamed6DescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexMeshIndexLuaScriptLuaScriptStateXmlUIContainedObjects0GUIDeaee5dNameDie_6TransformposX<posYM @posZ{ ;rotX`tTU@rotYd\@rotZ%]r@scaleX?scaleY?scaleZ?NicknameDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsMaterialIndexLuaScriptLuaScriptStateXmlUIRotationValues0BValue1Rotation&xVyz1BValue2Rotation&xyz2BValue3Rotation&xyzV3BValue4Rotation&xyzV@4BValue5Rotation&xyzf5BValue6Rotation&xV@yz217GUID47587bName Custom_PDFTransformposXY@posY@"@posZ8@rotXF@rotY@p@rotZ>scaleX@scaleY?scaleZ@NicknameWarhammer Open FAQDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomPDFPDFUrlkhttps://steamusercontent-a.akamaihd.net/ugc/14862495171195424048/E810A1439732E8AAF64EEF476FEC27697C115AA0/PDFPasswordPDFPagePDFPageOffsetLuaScriptbookmarks = {} inputText = "" showBookmarks = false function onLoad(save_state) if save_state ~= "" then saved_data = JSON.decode(save_state) if saved_data.bookmarks then bookmarks = saved_data.bookmarks end end createBookmarks() showBookmarks = true end function checkLoaded() if not self.loading_custom and not self.spawning then return true end return false end function onSave() local save_state = JSON.encode({bookmarks = bookmarks}) self.script_state = save_state end function createToggle() self.createButton({ click_function = "toggleState", function_owner = self, label = ">", position = {1.6, 0, -1.9}, font_size = 60, width = 100, height = 100 }) end function toggleState() if showBookmarks then removeBookmarks() self.editButton({index = 0, label = ">"}) else createInputs() createBookmarks() self.editButton({index = 0, label = "<"}) end showBookmarks = not showBookmarks end function removeBookmarks() buttons = self.getButtons() inputs = self.getInputs() for i = #buttons - 1, 1, -1 do self.removeButton(i) end for i = #inputs -1, 0, -1 do self.removeInput(i) end end function createInputs() -- Bookmark name box self.createInput({ input_function = "updateText", function_owner = self, label = "Bookmark Name", position = {2.1, 0, -1.5}, width = 500, height = 100, font_size = 60, alignment = 2 }) -- Add bookmark button self.createButton({ click_function = "addBookmark", function_owner = self, label = "Add Bookmark", position = {3.2, 0, -1.5}, font_size = 60, width = 500, height = 100 }) end function updateText(obj, player_clicker_color, input_value, selected) inputText = input_value end function clearBookmarks() local buttons = self.getButtons() local numBookmarks = #buttons - 1 -- iterate if numBookmarks == 0 then return end for i = 1, numBookmarks do self.removeButton(i) end end function createBookmarks() if #bookmarks == 0 then return end local vOffset = 0.3 for i=1, #bookmarks do local vertPos = -1 + (vOffset * (i - 1)) self.createButton({ click_function = "clickBookmark"..i, function_owner = self, label = bookmarks[i].name, position = {2.1, 0, vertPos}, font_size = 60, width = 500, height = 100 }) end end function addBookmark(obj, player_clicker_color, alt_click) if #bookmarks >= 10 then print("Max bookmarks reached") return end if inputText == "" then print("Enter a bookmark name prior to saving.") else table.insert(bookmarks, {name = inputText, page = self.Book.getPage()}) self.editInput({index = 0, value = ""}) inputText = "" clearBookmarks() createBookmarks() end end function clickBookmark1(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[1].page) end function clickBookmark2(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[2].page) end function clickBookmark3(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[3].page) end function clickBookmark4(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[4].page) end function clickBookmark5(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[5].page) end function clickBookmark6(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[6].page) end function clickBookmark7(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[7].page) end function clickBookmark8(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[8].page) end function clickBookmark9(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[9].page) end function clickBookmark10(obj, player_clicker_color, alt_click) self.Book.setPage(bookmarks[10].page) end function removeBookmark1(obj, player_clicker_color, alt_click) table.remove(bookmarks, 1) clearBookmarks() createBookmarks() end function removeBookmark2(obj, player_clicker_color, alt_click) table.remove(bookmarks, 2) clearBookmarks() createBookmarks() end function removeBookmark3(obj, player_clicker_color, alt_click) table.remove(bookmarks, 3) clearBookmarks() createBookmarks() end function removeBookmark4(obj, player_clicker_color, alt_click) table.remove(bookmarks, 4) clearBookmarks() createBookmarks() end function removeBookmark5(obj, player_clicker_color, alt_click) table.remove(bookmarks, 5) clearBookmarks() createBookmarks() end function removeBookmark6(obj, player_clicker_color, alt_click) table.remove(bookmarks, 6) clearBookmarks() createBookmarks() end function removeBookmark7(obj, player_clicker_color, alt_click) table.remove(bookmarks, 7) clearBookmarks() createBookmarks() end function removeBookmark8(obj, player_clicker_color, alt_click) table.remove(bookmarks, 8) clearBookmarks() createBookmarks() end function removeBookmark9(obj, player_clicker_color, alt_click) table.remove(bookmarks, 9) clearBookmarks() createBookmarks() end function removeBookmark10(obj, player_clicker_color, alt_click) table.remove(bookmarks, 10) clearBookmarks() createBookmarks() endLuaScriptState{"bookmarks":[]}XmlUI218GUID7e7711Name Custom_TokenTransformposX,F@posY?posZ:@rotX UrotY@Ap@rotZ^躾scaleX`O(?scaleY?scaleZ`O(?Nickname%Interactive Buttons - Territory LineDescriptionGMNotesAltLookAngle&xyzColorDiffuse&r?g?b?LayoutGroupSortIndexValueLockedGridSnapIgnoreFoWMeasureMovementDragSelectableAutoraiseStickyTooltipGridProjectionHideWhenFaceDownHandsCustomImageImageURLkhttps://steamusercontent-a.akamaihd.net/ugc/12932446331391677200/26670D21AC69A469974C90BAA06B0F7A94358459/ImageSecondaryURLImageScalar?WidthScaleCustomTokenKThickness?MergeDistancePixels.@StandUpStackableLuaScript0-- FTC-GUID: 7e7711 -- Shared variables deployLineHeight = 2.1 sizeMulti = 36 local noLineState = 'https://steamusercontent-a.akamaihd.net/ugc/12932446331391677200/26670D21AC69A469974C90BAA06B0F7A94358459/' local lineOnState = 'https://steamusercontent-a.akamaihd.net/ugc/13483922162153684809/EC47EBA4101B6D3127E3C94509BE9C01A426CC08/' function onLoad(saved_data) self.createButton({ click_function = "toggleTerritoryLine", function_owner = self, label = "", position = {0, 0.2, 0}, width = 2000, height = 2000, color = {0, 0, 0, 0} }) end function toggleTerritoryLine() local manager = getObjectFromGUID("738804") manager.call("showHideTerritoryLine") syncTerritoryLineButtonImage() end function syncTerritoryLineButtonImage() local state = Global.getVar("interactiveButtonTerritoryLine") local params = { image = state == 1 and noLineState or lineOnState, } self.setCustomObject(params) self.reload() end LuaScriptStateXmlUI