local buttonCount = 20 local cameraCount = 18 function onLoad(saved_data) self.createButton({ label = "", tooltip = "Display full overlay", click_function = "displayFull", function_owner = self, position = { 0.0, 0.1, -0.63 }, height = 70, width = 700, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) self.createButton({ label = "", tooltip = "Display only play area", click_function = "displayPlayArea", function_owner = self, position = { 0.0, 0.1, -0.39 }, height = 70, width = 700, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) self.createButton({ label = "", tooltip = "Close overlay", click_function = "closeOverlay", function_owner = self, position = { 0.0, 0.1, -0.16 }, height = 70, width = 700, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) self.createButton({ label = "", tooltip = "Modify a camera position", click_function = "beginSetCamera", function_owner = self, position = { 0.0, 0.1, 0.19 }, height = 70, width = 700, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) self.createButton({ label = "", tooltip = "Claim a color (you will switch to this color when clicking in the overlay)", click_function = "beginClaimColor", function_owner = self, position = { -0.22, 0.1, 0.42 }, height = 70, width = 475, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) self.createButton({ label = "", tooltip = "Reset all color claims", click_function = "resetClaimColors", function_owner = self, position = { 0.48, 0.1, 0.42 }, height = 70, width = 230, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) self.createButton({ label = "", tooltip = "Reset camera positions to default", click_function = "resetCameras", function_owner = self, position = { 0.0, 0.1, 0.78 }, height = 70, width = 700, scale = { x = 1, y = 1, z = 1 }, color = { 1, 0, 0, 0 } }) defaultCameraParams = { { position = { -1.626, -2.5, 0 }, pitch = 74, yaw = 90, distance = 17.844 }, -- 1. ActAgenda { position = { -27.822, -2.5, 0.424 }, pitch = 74, yaw = 90, distance = -1 }, -- 2. Map { position = { -31.592, -2.5, 26.392 }, pitch = 74, yaw = 180, distance = -1 }, -- 3. Green playmat { position = { -55.026, -2.5, 12.052 }, pitch = 74, yaw = 90, distance = -1 }, -- 4. White playmat { position = { -55.026, -2.5, -11.479 }, pitch = 74, yaw = 90, distance = -1 }, -- 5. Orange playmat { position = { -31.592, -2.5, -26.392 }, pitch = 74, yaw = 0, distance = -1 }, -- 6. Red playmat { position = { -3.029, 1.652, 24.296 }, pitch = 74, yaw = 90, distance = 16 }, -- 7. Victory / SetAside { position = { -2.936, 1.552, -26.757 }, pitch = 74, yaw = 90, distance = 16 }, -- 8. Guide { position = { -11.833, 1.491, -0.145 }, pitch = 74, yaw = 90, distance = 10 }, -- 9. Player count { position = { -48.352, 1.552, -0.055 }, pitch = 74, yaw = 90, distance = 10 }, -- 10. Bless/Curse { position = { 12.560, 1.912, 0.458 }, pitch = 74, yaw = 90, distance = 35 }, -- 11. Scenarios { position = { 57.835, 1.552, 75.385 }, pitch = 74, yaw = 90, distance = 22 }, -- 12. Player card panel { position = { 60.377, 1.552, 55.941 }, pitch = 74, yaw = 90, distance = 10 }, -- 13. Card search panel { position = { 27.482, 1.480, 71.057 }, pitch = 74, yaw = 90, distance = 35 }, -- 14. Player card area { position = { -19.481, 1.552, 70.880 }, pitch = 74, yaw = 90, distance = 22 }, -- 15. Deck builder { position = { -52.918, 1.478, 70.899 }, pitch = 74, yaw = 90, distance = 42 }, -- 16. Rules area { position = { 24.551, 2.222, -71.284 }, pitch = 60, yaw = 90, distance = 60 }, -- 17. Cycle area { position = { -59.077, 1.462, -85.472 }, pitch = 74, yaw = 90, distance = 27 } -- 18. Additions } fullButtonData = { { id = "1", width = "84", height = "33", offsetX = "1", offsetY = "2" }, -- Act/Agenda { id = "2", width = "78", height = "69", offsetX = "1", offsetY = "-62" }, -- Map { id = "3", width = "36", height = "70", offsetX = "-62", offsetY = "-66" }, -- Green { id = "4", width = "70", height = "36", offsetX = "-36", offsetY = "-126" }, -- White { id = "5", width = "70", height = "36", offsetX = "39", offsetY = "-126" }, -- Orange { id = "6", width = "36", height = "70", offsetX = "64", offsetY = "-66" }, -- Red { id = "7", width = "38", height = "38", offsetX = "-64", offsetY = "-3" }, -- Victory { id = "8", width = "40", height = "40", offsetX = "66", offsetY = "-3" }, -- Guide { id = "9", width = "56", height = "16", offsetX = "1", offsetY = "-20" }, -- Player count { id = "10", width = "36", height = "16", offsetX = "1", offsetY = "-102" }, -- Bless/Curse { id = "11", width = "168", height = "56", offsetX = "1", offsetY = "47" }, -- Scenarios { id = "12", width = "52", height = "53", offsetX = "-154", offsetY = "134" }, -- Player card panel { id = "13", width = "22", height = "22", offsetX = "-116", offsetY = "132" }, -- Search card panel { id = "14", width = "120", height = "75", offsetX = "-152", offsetY = "70" }, -- Player card display { id = "15", width = "40", height = "54", offsetX = "-150", offsetY = "-38" }, -- Deck builder { id = "16", width = "104", height = "84", offsetX = "-154", offsetY = "-114" }, -- Rules area { id = "17", width = "100", height = "170", offsetX = "152", offsetY = "72" }, -- Cycle area { id = "18", width = "56", height = "60", offsetX = "182", offsetY = "-124" }, -- Additions { id = "19", width = "20", height = "20", offsetX = "-8", offsetY = "150" }, -- Shrink { id = "20", width = "20", height = "20", offsetX = "12", offsetY = "150" } -- Close } playButtonData = { { id = "1", width = "80", height = "33", offsetX = "0", offsetY = "55" }, { id = "2", width = "78", height = "70", offsetX = "0", offsetY = "-8" }, { id = "3", width = "35", height = "66", offsetX = "-65", offsetY = "-10" }, { id = "4", width = "68", height = "32", offsetX = "-36", offsetY = "-71" }, { id = "5", width = "68", height = "32", offsetX = "36", offsetY = "-71" }, { id = "6", width = "35", height = "66", offsetX = "65", offsetY = "-10" }, { id = "7", width = "38", height = "38", offsetX = "-66", offsetY = "52" }, { id = "8", width = "38", height = "38", offsetX = "66", offsetY = "52" }, { id = "9", width = "50", height = "12", offsetX = "0", offsetY = "33" }, { id = "10", width = "32", height = "12", offsetX = "0", offsetY = "-48" }, { id = "19", width = "20", height = "20", offsetX = "-10", offsetY = "80" }, { id = "20", width = "20", height = "20", offsetX = "10", offsetY = "80" } } playermatData = { { guid = '383d8b', origin = { x = -25.00, y = 0, z = 26.20 }, scale = { x = 31.5, y = 5.10, z = 14.59 }, orientation = { x = 0, y = 0, z = 0 }, minX = -44.43, maxX = -17.44, minZ = 20.17, maxZ = 32.97, xOffset = -0.07, zOffset = 0.00, claims = { true, false, false, false } }, { guid = '8b081b', origin = { x = -54.42, y = 0, z = 20.96 }, scale = { x = 36.63, y = 5.10, z = 14.59 }, orientation = { x = 0, y = 270, z = 0 }, minX = -61.4, maxX = -48.6, minZ = -2.39, maxZ = 24.53, xOffset = 0.07, zOffset = 0.03, claims = { false, true, false, false } }, { guid = 'bd0ff4', origin = { x = -54.42, y = 0, z = -20.96 }, scale = { x = 36.63, y = 5.10, z = 14.59 }, orientation = { x = 0, y = 270, z = 0 }, minX = -61.4, maxX = -48.6, minZ = -24.53, maxZ = 2.39, xOffset = 0.07, zOffset = 0.02, claims = { false, false, true, false } }, { guid = '0840d5', origin = { x = -25.00, y = 0, z = -26.60 }, scale = { x = 31.5, y = 5.10, z = 14.59 }, orientation = { x = 0, y = 180, z = 0 }, minX = -44.43, maxX = -17.44, minZ = -32.97, maxZ = -20.17, xOffset = 0.07, zOffset = -0.06, claims = { false, false, false, true } } } editing = false claiming = false selectedEditButton = -1 editPos = { 0, 0, 0 } editPitch = 0 editYaw = 0 editDistance = 0 if saved_data ~= "" then local loaded_data = JSON.decode(saved_data) cameraParams = loaded_data.cameras fullVisibility = loaded_data.fullVis playVisibility = loaded_data.playVis local allclaims = loaded_data.claims for i = 1, 4 do playermatData[i].claims = allclaims[i] end resetOverlay() else cameraParams = { {}, {}, {}, {} } for cam = 1, 4 do cameraParams[cam] = {} for i = 1, cameraCount do cameraParams[cam][i] = {} cameraParams[cam][i].position = defaultCameraParams[i].position cameraParams[cam][i].pitch = defaultCameraParams[i].pitch cameraParams[cam][i].yaw = defaultCameraParams[i].yaw cameraParams[cam][i].distance = defaultCameraParams[i].distance end end fullVisibility = { false, false, false, false } playVisibility = { false, false, false, false } end end function onSave() local allclaims = {} for i = 1, 4 do table.insert(allclaims, playermatData[i].claims) end return JSON.encode({ cameras = cameraParams, fullVis = fullVisibility, playVis = playVisibility, claims = allclaims }) end function displayFull(object, color) local playerCount = getPlayerCount() local colors if playerCount == 0 then return elseif playerCount == 1 then colors = { 1, 2, 3, 4 } else colors = { getIndexForPlayerColor(color) } end for i, v in ipairs(colors) do if v > 0 then fullVisibility[v] = true playVisibility[v] = false end end resetOverlay() end function displayPlayArea(object, color) local playerCount = getPlayerCount() local colors if playerCount == 0 then return elseif playerCount == 1 then colors = { 1, 2, 3, 4 } else colors = { getIndexForPlayerColor(color) } end for _, v in ipairs(colors) do if v > 0 then fullVisibility[v] = false playVisibility[v] = true end end resetOverlay() end function resetCameras(object, color) local playerCount = getPlayerCount() local colors if playerCount == 0 then return elseif playerCount == 1 then colors = { 1, 2, 3, 4 } else colors = { getIndexForPlayerColor(color) } end for iv, v in ipairs(colors) do if v > 0 then for i = 1, cameraCount do cameraParams[v][i].position = defaultCameraParams[i].position cameraParams[v][i].pitch = defaultCameraParams[i].pitch cameraParams[v][i].yaw = defaultCameraParams[i].yaw cameraParams[v][i].distance = defaultCameraParams[i].distance end end end end function closeOverlay(object, color) local playerCount = getPlayerCount() local colors editing = false claiming = false if playerCount == 0 then return elseif playerCount == 1 then colors = { 1, 2, 3, 4 } else colors = { getIndexForPlayerColor(color) } end for _, v in ipairs(colors) do if v > 0 then fullVisibility[v] = false playVisibility[v] = false end end resetOverlay() end function resizeOverlay(object, color) local playerCount = getPlayerCount() local colors if playerCount == 0 then return elseif playerCount == 1 then colors = { 1, 2, 3, 4 } else colors = { getIndexForPlayerColor(color) } end for _, v in ipairs(colors) do if v > 0 then local full = fullVisibility[v] fullVisibility[v] = not full playVisibility[v] = full end end resetOverlay() end function resetOverlay() local guid = self.getGUID() local color local panel local existingXml = UI.getXml() local openingXml = '' -- try to only remove our panels for p = 1, 2 do i, j = string.find(existingXml, ' ]] for _, d in ipairs(data) do local buttonID = tonumber(d.id) if editing and buttonID < 19 then if selectedEditButton < 0 then color = "rgba(1,1,1,1)" elseif buttonID == selectedEditButton then color = "rgba(0,1,0,1)" else color = "rgba(1,0,0,1)" end elseif claiming and buttonID < 19 then if buttonID >= 3 and buttonID <= 6 then color = "rgba(1,1,1,1)" else color = "rgba(1,0,0,1)" end else color = "rgba(0,1,0,0)" end xml = xml .. [[ ]] end xml = xml .. [[ ]] end if string.len(playColors) > 0 then data = playButtonData xml = xml .. [[ ]] for _, d in ipairs(data) do local buttonID = tonumber(d.id) if editing and buttonID < 19 then if selectedEditButton < 0 then color = "rgba(1,1,1,1)" elseif buttonID == selectedEditButton then color = "rgba(0,1,0,1)" else color = "rgba(1,0,0,1)" end elseif claiming and buttonID < 19 then if buttonID >= 3 and buttonID <= 6 then color = "rgba(1,1,1,1)" else color = "rgba(1,0,0,1)" end else color = "rgba(0,1,0,0)" end xml = xml .. [[ ]] end xml = xml .. [[ ]] end local existingAssets = UI.getCustomAssets() local largeOverlay = nil local smallOverlay = nil for _, v in pairs(existingAssets) do for _, vv in pairs(v) do if vv == 'OverlayLarge' then largeOverlay = v end if vv == 'OverlaySmall' then smallOverlay = v end end end local largeURL = 'http://cloud-3.steamusercontent.com/ugc/2021591230441678995/7B413A821136969D8723687A2AD66773B3F8FEED/' local smallURL = 'http://cloud-3.steamusercontent.com/ugc/2021591230447630077/18C86248B9BDAF1DE01B67791439A39EE4F97B60/' if largeOverlay == nil then largeOverlay = { name = 'OverlayLarge', url = largeURL } table.insert(existingAssets, largeOverlay) else largeOverlay.url = largeURL end if smallOverlay == nil then smallOverlay = { name = 'OverlaySmall', url = smallURL } table.insert(existingAssets, smallOverlay) else smallOverlay.url = smallURL end UI.setXml(xml, existingAssets) end function buttonClicked(player, _, idValue) local buttonID = tonumber(idValue) if buttonID == 19 then resizeOverlay(nil, player.color) return elseif buttonID == 20 then closeOverlay(nil, player.color) return end if editing then if selectedEditButton < 0 then selectedEditButton = buttonID else if buttonID == selectedEditButton and editDistance > 0 then local playerCount = getPlayerCount() local colors if playerCount == 1 then colors = { 1, 2, 3, 4 } else colors = { getIndexForPlayerColor(player.color) } end for i, v in ipairs(colors) do cameraParams[v][selectedEditButton].position = editPos cameraParams[v][selectedEditButton].pitch = editPitch cameraParams[v][selectedEditButton].yaw = editYaw cameraParams[v][selectedEditButton].distance = editDistance end end editing = false selectedEditButton = -1 end resetOverlay() elseif claiming then if buttonID >= 3 and buttonID <= 6 then local colorID = buttonID - 2 local playerIndex = getIndexForPlayerColor(player.color) -- if we haven't claimed it, break all earlier claims if playermatData[playerIndex].claims[colorID] == false then for i = 1, 4 do if i ~= colorID then playermatData[i].claims[colorID] = false playermatData[colorID].claims[i] = false end end end for i = 1, 4 do if playermatData[playerIndex].claims[i] then playermatData[i].claims[colorID] = true playermatData[colorID].claims[i] = true end end fullVisibility[colorID] = fullVisibility[playerIndex] playVisibility[colorID] = playVisibility[playerIndex] end claiming = false resetOverlay() else loadCamera(player, _, idValue) end end function loadCamera(player, _, idValue) local index = tonumber(idValue) local playerColor = player.color local playerIndex = getIndexForPlayerColor(playerColor) -- only do map zooming if the camera hasn't been specially set by user if index == 2 and cameraParams[playerIndex][index].distance <= 0.0 then local mapObjects = Physics.cast({ origin = { x = -29.2, y = 0, z = 0.0 }, direction = { x = 0, y = 1, z = 0 }, type = 3, size = { x = 36, y = 5, z = 31.4 }, orientation = { x = 0, y = 90, z = 0 } }) local minX = 100 local maxX = -100 local minZ = 100 local maxZ = -100 for _, v in pairs(mapObjects) do local obj = v.hit_object if obj.type == 'Card' or obj.type == 'Infinite' then local bounds = obj.getBounds() local x1 = bounds['center'][1] - bounds['size'][1] / 2 local x2 = bounds['center'][1] + bounds['size'][1] / 2 local z1 = bounds['center'][3] - bounds['size'][3] / 2 local z2 = bounds['center'][3] + bounds['size'][3] / 2 minX = math.min(x1, minX) maxX = math.max(x2, maxX) minZ = math.min(z1, minZ) maxZ = math.max(z2, maxZ) end end if minX < 100 then local dx = maxX - minX local dz = (maxZ - minZ) / (1.6) -- screen ratio * 1.2 (for my macbook pro, no idea how to generalize this) local centerX = (minX + maxX) / 2 -- offset is to move it a bit up, so the cards don't block anything local centerZ = (minZ + maxZ) / 2 local scale = math.max(dx, dz) -- regression line from the following data points, seems linear -- rows 1 scale 4.5 d 12 -- rows 2 scale 11 d 16 -- rows 3 scale 14.5 d 19.6 -- rows 4 scale 19.6 d 25 -- rows 5 scale 23.25 d 28 -- rows 6 scale 30.8 d 34 -- modified by testing local d = 0.96 * scale + 5 player.lookAt({ position = { centerX, 0, centerZ }, pitch = 74, yaw = 90, distance = d }) else player.lookAt({ position = { -30.667, 0, 0 }, pitch = 74, yaw = 90, distance = 32 }) end elseif index >= 3 and index <= 6 then local newMatIndex = index - 2 -- mat index 1 - 4 local newMatColor = getPlayerColorForIndex(newMatIndex) if newMatColor ~= nil then local playerCount = getPlayerCount() if playerCount <= 1 or playermatData[playerIndex].claims[newMatIndex] then player.changeColor(newMatColor) end end if cameraParams[newMatIndex][index].distance <= 0.0 then local matObjects = Physics.cast({ origin = playermatData[newMatIndex].origin, direction = { x = 0, y = 1, z = 0 }, type = 3, size = playermatData[newMatIndex].scale, orientation = playermatData[newMatIndex].orientation }) local minX = playermatData[newMatIndex].minX local maxX = playermatData[newMatIndex].maxX local minZ = playermatData[newMatIndex].minZ local maxZ = playermatData[newMatIndex].maxZ for i, v in pairs(matObjects) do local obj = v.hit_object if obj.type == 'Card' or obj.type == 'Infinite' then local bounds = obj.getBounds() local x1 = bounds['center'][1] - bounds['size'][1] / 2 local x2 = bounds['center'][1] + bounds['size'][1] / 2 local z1 = bounds['center'][3] - bounds['size'][3] / 2 local z2 = bounds['center'][3] + bounds['size'][3] / 2 minX = math.min(x1, minX) maxX = math.max(x2, maxX) minZ = math.min(z1, minZ) maxZ = math.max(z2, maxZ) end end local dx, dz, centerX, centerZ, yaw -- White/Orange if index > 3 and index < 6 then dx = maxX - minX dz = (maxZ - minZ) / 1.6 -- screen ratio * 1.2 (for my macbook pro, no idea how to generalize this) yaw = 90 -- offset is to move it a bit up and right, so the cards/toolbar don't block anything centerX = (minX + maxX) / 2 - dx * playermatData[newMatIndex].xOffset centerZ = (minZ + maxZ) / 2 + dz * playermatData[newMatIndex].zOffset -- Green/Red else dx = (maxX - minX) / 1.6 dz = maxZ - minZ yaw = playermatData[newMatIndex].orientation.y + 180 centerX = (minX + maxX) / 2 + dx * playermatData[newMatIndex].zOffset centerZ = (minZ + maxZ) / 2 - dz * playermatData[newMatIndex].xOffset end local scale = math.max(dx, dz) local d = 0.64 * scale + 7 -- need to wait if the player color changed Wait.frames(function() player.lookAt({ position = { centerX, 0, centerZ }, pitch = 75.823, yaw = yaw, distance = d }) end, 2) else Wait.frames(function() player.lookAt(cameraParams[newMatIndex][index]) end, 2) end else player.lookAt(cameraParams[playerIndex][index]) end end function beginSetCamera(object, color) if getPlayerCount() == 0 then return elseif getIndexForPlayerColor(color) < 0 then return end editing = true resetOverlay() end function updateEditCamera(params) editPos = params[1] editPitch = params[2] editYaw = params[3] editDistance = params[4] end function beginClaimColor(object, color) if getPlayerCount() == 0 then return elseif getIndexForPlayerColor(color) < 0 then return end claiming = true resetOverlay() end function resetClaimColors(object, color) if getPlayerCount() == 0 then return elseif getIndexForPlayerColor(color) < 0 then return end for c1 = 1, 4 do for c2 = 1, 4 do if c1 == c2 then playermatData[c1].claims[c2] = true else playermatData[c1].claims[c2] = false end end end end function getPlayerCount() local playerCount = 0 local playerColors = {} for i = 1, 4 do local guid = playermatData[i].guid local mat = getObjectFromGUID(guid) local color = mat.getVar('playerColor') playerColors[i] = color end local playerList = getSeatedPlayers() for i, v in ipairs(playerList) do for ii, vv in ipairs(playerColors) do if v == vv then playerCount = playerCount + 1 end end end return playerCount end function getPlayerColorForIndex(index) if index < 0 or index > 4 then return nil end local guid = playermatData[index]['guid'] if guid ~= nil then local mat = getObjectFromGUID(guid) return mat.getVar("playerColor") end end function getIndexForPlayerColor(color) for i = 1, 4 do local mat = getObjectFromGUID(playermatData[i].guid) if mat ~= nil then if mat.getVar('playerColor') == color then return i end end end return -1 end