PLAYAREA = { position = { x=-25, y=4, z=0 }, rotation = { x=0, y=90, z=0 }, scale = { x=37, y=5, z=43 } } ASSEMBLY = { position = { x=68, y=2, z=36 }, rotation = { x=0, y=270, z=180 } } SET_ASIDE = { position = { x=-2.52, y=2, z=14.87 }, rotation = { x=0, y=270, z=180 } } EXPLORATION = { position = { x=-6.33, y=2, z=14.87 }, rotation = { x=0, y=270, z=180 } } START = { position = { x=-30.22, y=2, z=-0.03 }, rotation = { x=0, y=270, z=180 } } HEART = 1 STAR = 2 DIAMOND = 3 TRIANGLE = 4 CIRCLE = 5 SQUARE = 6 BACKTICK = 7 TILDE = 8 PARALLEL = 9 T = 10 HOURGLASS = 11 LOCATIONS = { { name="City of the Serpents", symbol=DIAMOND, connections={ PARALLEL, BACKTICK, TRIANGLE, T, SQUARE } }, { name="Bridge over N'kai", symbol=HEART, connections={ PARALLEL, BACKTICK, CIRCLE, TILDE, HOURGLASS } }, { name="Abandoned Site", symbol=SQUARE, connections={ PARALLEL, DIAMOND, TRIANGLE, TILDE, T } }, { name="Caverns of Yoth", symbol=BACKTICK, connections={ CIRCLE, HOURGLASS, HEART, TILDE, DIAMOND } }, { name="Hall of Heresy", symbol=TRIANGLE, connections={ CIRCLE, PARALLEL, T, SQUARE, DIAMOND } }, { name="Bright Canyon", symbol=CIRCLE, connections={ BACKTICK, TILDE, T, HEART, TRIANGLE } }, { name="Forked Path", symbol=T, connections={ CIRCLE, DIAMOND, HOURGLASS, SQUARE, TRIANGLE } }, { name="Crumbling Precipice", symbol=HOURGLASS, connections={ PARALLEL, TILDE, HEART, T, BACKTICK } }, { name="Broken Passage", symbol=TILDE, connections={ CIRCLE, BACKTICK, HOURGLASS, SQUARE, HEART } }, { name="Steps of Yoth", symbol=PARALLEL, connections={ HOURGLASS, SQUARE, TRIANGLE, DIAMOND, HEART } } } STEPS = "Steps of Yoth" PERILS = "Perils of Yoth" function onLoad() self.createButton({ label="Set Up", click_function="setup", function_owner=self, position={0,0.1,-0.6}, height=120, width=500, scale={x=1.75, y=1.75, z=1.75}, font_size=80 }) self.createButton({ label="Update\nConnections", click_function="updateConnections", function_owner=self, position={0,0.1,0.4}, height=240, width=500, scale={x=1.75, y=1.75, z=1.75}, font_size=80 }) playarea = getObjectFromGUID("721ba2") TOKEN_IMAGES = Global.getTable("tokenplayerone") makeIndexes() math.randomseed(os.time()) end function makeIndexes() nameMap = {} for i,v in ipairs(LOCATIONS) do nameMap[v.name] = v end end function getPlayAreaObjects() return Physics.cast({ origin = PLAYAREA.position, direction = { x=0, y=1, z=0 }, type = 3, size = PLAYAREA.scale, orientation = PLAYAREA.rotation }) end function setup(_obj, _color, _alt_click) local objs = getPlayAreaObjects() clearLines() -- make index of all cards in play area and remove all clues and doom local allCards = {} for i,v in ipairs(objs) do local obj = v.hit_object local name = obj.getName() if name ~= nil then if obj.tag == "Card" then allCards[name] = { card = obj, guid = obj.getGUID() } elseif obj.tag == "Deck" then local cards = obj.getObjects() for j,card in ipairs(cards) do if card.guid ~= nil and card.name ~= nil then allCards[card.name] = { deck = obj, guid = card.guid } end end elseif obj.tag == "Tile" then local props = obj.getCustomObject() if (props.image == TOKEN_IMAGES.clue and props.image_bottom == TOKEN_IMAGES.doom) or (props.image == TOKEN_IMAGES.doom and props.image_bottom == TOKEN_IMAGES.clue) then Wait.time(|| Global.destroyObject(obj), 1) end end end end -- find location cards local locCards = {} local notFound = {} for i,loc in ipairs(LOCATIONS) do local card = allCards[loc.name] if card == nil then table.insert(notFound, loc.name) else table.insert(locCards, card) end end if #notFound > 0 then printToColor("The following locations were not found: " .. table.concat(notFound, ", "), _color) printToColor("Place them in the central play area and try again", _color) return end -- Perils of Yoth local peril = allCards[PERILS] if peril ~= nil then if peril.card ~= nil then placeCard(nil, peril.card, ASSEMBLY) else placeCard(peril.deck, peril.guid, ASSEMBLY) if peril.deck.is_face_down and #peril.deck.getObjects() > 0 then peril.deck.shuffle() end end end -- reset clue spawn status and make a deck of all location cards in the -- assembly area local spawnedGuids = playarea.getVar("SPAWNED_LOCATION_GUIDS") for i,loc in ipairs(locCards) do spawnedGuids[loc.guid] = nil if loc.card ~= nil then placeCard(nil, loc.card, ASSEMBLY) elseif loc.deck ~= nil and #loc.deck.getObjects() > 0 then placeCard(loc.deck, loc.guid, ASSEMBLY) else -- all but one card was taken from deck, so it doesn't exist anymore Wait.time(|| placeCard(nil, loc.guid, ASSEMBLY), 1) end end playarea.setVar("SPAWNED_LOCATION_GUIDS", spawnedGuids) Wait.time(setup_2, 2) end function setup_2() local objs = Physics.cast({ origin = ASSEMBLY.position, direction = { x=0, y=1, z=0 }, type = 3, size = { x=1, y=1, z=1 }, orientation = ASSEMBLY.rotation }) local deck = nil for i,v in ipairs(objs) do if v.hit_object.tag == "Deck" then deck = v.hit_object end end local locMap = {} for i,card in ipairs(deck.getObjects()) do locMap[card.name] = card.guid end -- place Steps of Yoth and Perils of Yoth (if necessary) placeCard(deck, locMap[STEPS], EXPLORATION) if locMap[PERILS] ~= nil then placeCard(deck, locMap[PERILS], EXPLORATION) end -- randomly select starting location deck.shuffle() placeCard(deck, nil, START, true) -- assemble exploration deck for i=1,4 do placeCard(deck, nil, EXPLORATION) end Wait.time(setup_3, 1) -- move remaining cards to set aside area deck.setPosition(SET_ASIDE.position) deck.setName("Set Aside Locations") end function setup_3() local objs = Physics.cast({ origin = EXPLORATION.position, direction = { x=0, y=1, z=0 }, type = 3, size = { x=1, y=1, z=1 }, orientation = EXPLORATION.rotation }) local deck = nil for i,v in ipairs(objs) do if v.hit_object.tag == "Deck" then deck = v.hit_object end end deck.shuffle() deck.setName("Exploration Deck") end function placeCard(deck, card, location, faceup) if deck ~= nil then deck.takeObject({ guid = card, position = location.position, rotation = location.rotation, smooth = false, flip = faceup }) return end if type(card) == "string" then card = getObjectFromGUID(card) end card.setRotation(location.rotation) card.setPosition(location.position) if faceup then card.flip() end end function updateConnections(obj, color, alt_click) local objs = getPlayAreaObjects() local cardObjs = {} local connObjs = {} for i,v in ipairs(objs) do local obj = v.hit_object local guid = obj.getGUID() if guid ~= nil and obj.tag == "Card" and not obj.is_face_down then local name = obj.getName() local loc = nameMap[name] if loc ~= nil then cardObjs[name] = obj connObjs[loc.symbol] = obj end end end local lines = {} for name,obj in pairs(cardObjs) do for j,conn in ipairs(nameMap[obj.getName()].connections) do if connObjs[conn] ~= nil then drawConnection(obj, connObjs[conn], lines) end end end Global.setVectorLines(lines) end function clearLines() Global.setVectorLines({}) end function drawConnection(origin, target, lines) local originPos = origin.getPosition() local targetPos = target.getPosition() table.insert(lines, { points={ originPos, targetPos } }) if not connectsTo(target, origin) then -- draw one-way arrow local midpoint = originPos:lerp(targetPos, 0.5) local direction = (targetPos - originPos):normalize() local arrow1 = direction:copy():rotateOver('y', 45) local arrow2 = direction:copy():rotateOver('y', -45) table.insert(lines, { points={ midpoint-arrow1, midpoint, midpoint-arrow2 } }) end end function connectsTo(origin, target) local symbol = nameMap[target.getName()].symbol for i,conn in ipairs(nameMap[origin.getName()].connections) do if conn == symbol then return true end end return false end