ah_sce_unpacked/unpacked/Custom_Token SCED Tour 0e5aa8.ttslua
2024-07-27 21:47:52 -04:00

615 lines
25 KiB
Plaintext

-- Bundled by luabundle {"version":"1.6.0"}
local __bundle_require, __bundle_loaded, __bundle_register, __bundle_modules = (function(superRequire)
local loadingPlaceholder = {[{}] = true}
local register
local modules = {}
local require
local loaded = {}
register = function(name, body)
if not modules[name] then
modules[name] = body
end
end
require = function(name)
local loadedModule = loaded[name]
if loadedModule then
if loadedModule == loadingPlaceholder then
return nil
end
else
if not modules[name] then
if not superRequire then
local identifier = type(name) == 'string' and '\"' .. name .. '\"' or tostring(name)
error('Tried to require ' .. identifier .. ', but no such module has been registered')
else
return superRequire(name)
end
end
loaded[name] = loadingPlaceholder
loadedModule = modules[name](require, loaded, register, modules)
loaded[name] = loadedModule
end
return loadedModule
end
return require, loaded, register, modules
end)(nil)
__bundle_register("core/tour/TourStarter", function(require, _LOADED, __bundle_register, __bundle_modules)
local tourManager = require("core/tour/TourManager")
function onLoad()
self.createButton({
click_function = "startTour",
function_owner = self,
position = { 1.27, 0.05, 0.035},
width = 500,
height = 20,
color = { 0, 0, 0, 0 },
-- TTS has a minium height for buttons, have to scale the Z-axis down to get the right size
scale = { 1, 1, 0.82 },
tooltip = "Start the Tour",
})
self.createButton({
click_function = "deleteStarter",
function_owner = self,
position = { 1.27, 0.05, 0.309},
width = 500,
height = 20,
color = { 0, 0, 0, 0 },
-- TTS has a minium height for buttons, have to scale the Z-axis down to get the right size
scale = { 1, 1, 0.82 },
tooltip = "Delete this Panel",
})
end
function startTour(_, playerColor, _)
tourManager.startTour(playerColor)
end
function deleteStarter(_, _, _)
self.destruct()
end
end)
__bundle_register("core/tour/TourManager", function(require, _LOADED, __bundle_register, __bundle_modules)
do
require("core/tour/TourScript")
require("core/tour/TourCard")
local TourManager = {}
local internal = {}
local guidReferenceApi = require("core/GUIDReferenceApi")
-- Base IDs for various tour card UI elements. Actual IDs will have _[playerColor] appended
local CARD_ID = "tourCard"
local LEFT_NARRATOR_ID = "tourNarratorImageLeft"
local RIGHT_NARRATOR_ID = "tourNarratorImageRight"
local BUBBLE_ID = "tourSpeechBubble"
local TEXT_ID = "tourText"
local NEXT_BUTTON_ID = "tourNext"
local STOP_BUTTON_ID = "tourStop"
-- Table centerpoint for the camera hook object. Camera handling is a bit erratic so it doesn't
-- always land right where you think it's going to, but it's close
local HOOK_CAMERA_HOME = {
x = -30.2,
y = 60,
z = 0,
}
-- Default (0) position for the camera, as defined in the mod. If we don't recreate this position
-- EXACTLY when exiting the tour then camera controls get weird
local DEFAULT_CAMERA_POS = {
position = { x = -22.26, y = -2.5, z = 5.26 },
pitch = 64.34,
yaw = 90,
distance = 104
}
-- Global XML coordinates where we can present a card
local SCREEN_POSITIONS = {
center = "0 0 0",
north = "0 300 0",
east = "600 0 0",
west = "-600 0 0",
south = "0 -300 0",
-- Northwest is only used by the Mandy card, move it a little right than standard so it's
-- closer to the importer
northwest = "-500 300 0",
northeast = "600 300 0",
southwest = "-600 -300 0",
-- Used by the Diana and Wini cards referencing the bottom-right global controls, moved a little
-- closer to them
southeast = "730 -365 0"
}
-- Tracks the current state of the tours. Keyed by player color to keep each player's tour
-- separate, will hold the camera hook and current card.
local tourState = {}
-- Kicks off the tour by initializing the card and camera hook. A callback on the hook creation
-- will then show the first card.
---@param playerColor string Player color to start the tour for
TourManager.startTour = function(playerColor)
tourState[playerColor] = {
currentCardIndex = 1
}
-- Camera gets really screwy when we finalize if we don't start settled in ThirdPerson at the
-- default position before attaching to the hook. Unfortunately there are no callbacks for when
-- the movement is done, but the delay seems to handle it
Player[playerColor].setCameraMode("ThirdPerson")
Player[playerColor].lookAt(DEFAULT_CAMERA_POS)
-- Initial camera rotation is painfully slow. White and Orange players are likely oriented
-- correctly, but need a longer start delay for Green and Red
local delay = 0.5
if playerColor ~= "White" and playerColor ~= "Orange" then
delay = 2
broadcastToColor("Starting the tour, please wait...", playerColor)
end
Wait.time(function()
internal.createTourCard(playerColor)
-- XML update to add the new card takes a few frames to load, wait for it to finish then create the hook
Wait.condition(function() internal.createCameraHook(playerColor) end, function() return not Global.UI.loading end)
end, delay)
end
-- Shows the next card in the tour script. This method is exposed (rather than being part of
-- internal) because the XMLUI callbacks expect the method to be on the object directly.
---@param player tts__Player object to show the next card for, provided by XMLUI callback
function nextCard(player)
internal.hideCard(player.color)
Wait.time(function()
tourState[player.color].currentCardIndex = tourState[player.color].currentCardIndex + 1
if tourState[player.color].currentCardIndex > #TOUR_SCRIPT then
internal.finalizeTour(player.color)
else
internal.showCurrentCard(player.color)
end
end, 0.3)
end
-- Ends the tour and cleans up the camera. This method is exposed (rather than being part of
-- internal) because the XMLUI callbacks expect the method to be on the object directly.
---@param player tts__Player object to end the tour for, provided by XMLUI callback
function stopTour(player)
internal.hideCard(player.color)
Wait.time(function()
internal.finalizeTour(player.color)
end, 0.3)
end
-- Updates the card UI for the script at the current index, moves the camera to the proper
-- position, and shows the card.
---@param playerColor string Player color to show the current card for
internal.showCurrentCard = function(playerColor)
internal.updateCardDisplay(playerColor)
local delay = 0
local cardIndex = tourState[playerColor].currentCardIndex
local hook = getObjectFromGUID(tourState[playerColor].cameraHookGuid)
if not TOUR_SCRIPT[cardIndex].skipCentering then
hook.setPositionSmooth(HOOK_CAMERA_HOME, false, false)
delay = delay + 0.5
end
local lookPos
local objReferenceData = TOUR_SCRIPT[cardIndex].objReferenceData
if objReferenceData ~= nil then
local lookAtObj = guidReferenceApi.getObjectByOwnerAndType(objReferenceData.owner, objReferenceData.type)
lookPos = lookAtObj.getPosition()
lookPos.y = TOUR_SCRIPT[cardIndex].distanceFromObj or 0
-- Since camera isn't directly above the hook, changing the Y affects the visual position of
-- whatever object we're trying to look at. This is an approximation, but close enough to
-- keep the object more centered
lookPos.x = lookPos.x - lookPos.y / 2
elseif TOUR_SCRIPT[cardIndex].showPos ~= nil then
lookPos = TOUR_SCRIPT[cardIndex].showPos
end
if lookPos ~= nil then
Wait.time(function()
hook.setPositionSmooth(lookPos, false, false)
end, delay)
delay = delay + 0.5
end
Wait.time(function() Global.UI.show(internal.getUiId(CARD_ID, playerColor)) end, delay)
end
-- Hides the current card being shown to a player. This can be in preparation for showing the
-- next card, or ending the tour.
---@param playerColor string Player color to hide the current card for
internal.hideCard = function(playerColor)
Global.UI.hide(internal.getUiId(CARD_ID, playerColor))
end
-- Cleans up all the various resources associated with the tour, and (hopefully) resets the
-- camera to the default position. Camera handling is erratic, the final card in the script
-- should include instructions for the player to fix it.
---@param playerColor string Player color to clean up
internal.finalizeTour = function(playerColor)
local cameraHook = getObjectFromGUID(tourState[playerColor].cameraHookGuid)
cameraHook.destruct()
Player[playerColor].setCameraMode("ThirdPerson")
tourState[playerColor] = nil
Wait.frames(function()
Player[playerColor].lookAt(DEFAULT_CAMERA_POS)
end, 3)
end
-- Updates the card UI to show the appropriate card configuration.
---@param playerColor string Player color to update card for
internal.updateCardDisplay = function(playerColor)
local index = tourState[playerColor].currentCardIndex
Global.UI.setAttribute(internal.getUiId(LEFT_NARRATOR_ID, playerColor), "image", "Inv-" .. TOUR_SCRIPT[index].narrator)
Global.UI.setAttribute(internal.getUiId(RIGHT_NARRATOR_ID, playerColor), "image", "Inv-" .. TOUR_SCRIPT[index].narrator)
Global.UI.setAttribute(internal.getUiId(TEXT_ID, playerColor), "text", "\"" .. TOUR_SCRIPT[index].text .. "\"")
local cardPos = TOUR_SCRIPT[index].position or "north"
Global.UI.setAttribute(internal.getUiId(CARD_ID, playerColor), "position", SCREEN_POSITIONS[cardPos])
Global.UI.setAttribute(internal.getUiId(NEXT_BUTTON_ID, playerColor), "active", index < #TOUR_SCRIPT)
-- Adjust images so the narrator is on the left or right, as defined by the card
if TOUR_SCRIPT[index].speakerSide == "right" then
Global.UI.setAttribute(internal.getUiId(LEFT_NARRATOR_ID, playerColor), "active", false)
Global.UI.setAttribute(internal.getUiId(RIGHT_NARRATOR_ID, playerColor), "active", true)
Global.UI.setAttribute(internal.getUiId(BUBBLE_ID, playerColor), "rotation", "0 180 0")
Global.UI.setAttribute(internal.getUiId(TEXT_ID, playerColor), "offsetXY", "-15 -15")
Global.UI.setAttribute(internal.getUiId(NEXT_BUTTON_ID, playerColor), "offsetXY", "-35 -45")
Global.UI.setAttribute(internal.getUiId(STOP_BUTTON_ID, playerColor), "offsetXY", "5 -45")
else
Global.UI.setAttribute(internal.getUiId(LEFT_NARRATOR_ID, playerColor), "active", true)
Global.UI.setAttribute(internal.getUiId(RIGHT_NARRATOR_ID, playerColor), "active", false)
Global.UI.setAttribute(internal.getUiId(BUBBLE_ID, playerColor), "rotation", "0 0 0")
Global.UI.setAttribute(internal.getUiId(TEXT_ID, playerColor), "offsetXY", "15 -15")
Global.UI.setAttribute(internal.getUiId(NEXT_BUTTON_ID, playerColor), "offsetXY", "-5 -45")
Global.UI.setAttribute(internal.getUiId(STOP_BUTTON_ID, playerColor), "offsetXY", "35 -45")
end
end
-- Creates a small, transparent object which the camera will be attached to in order to move the
-- user's view around the table. This should be called only at the beginning of the tour. Once
-- creation is complete the user's camera will be attached to the hook and the first card will be
-- shown.
---@param playerColor string Player color to create the hook for
internal.createCameraHook = function(playerColor)
local hookData = {
Name = "BlockSquare",
Transform = {
posX = HOOK_CAMERA_HOME.x,
posY = HOOK_CAMERA_HOME.y,
posZ = HOOK_CAMERA_HOME.z,
rotX = 0,
rotY = 270,
rotZ = 0,
scaleX = 0.1,
scaleY = 0.1,
scaleZ = 0.1,
},
ColorDiffuse = {
r = 0,
g = 0,
b = 0,
a = 0,
},
Locked = true,
GMNotes = playerColor
}
spawnObjectData({ data = hookData, callback_function = internal.onHookCreated })
end
-- Callback for creation of the camera hook object. Will attach the camera and show the current
-- (presumably first) card.
---@param hook tts__Object Created object
internal.onHookCreated = function(hook)
local playerColor = hook.getGMNotes()
tourState[playerColor].cameraHookGuid = hook.getGUID()
Player[playerColor].attachCameraToObject({
object = hook,
offset = { x = -20, y = 30, z = 0 }
})
internal.showCurrentCard(playerColor)
end
-- Creates an XMLUI entry in Global for a player-specific tour card. Dynamically creating this
-- is somewhat complex, but ensures we can properly handle any player color.
---@param playerColor string Player color to create the card for
internal.createTourCard = function(playerColor)
-- Make sure the card doesn't exist before we create a new one
if Global.UI.getAttributes(internal.getUiId(CARD_ID, playerColor)) ~= nil then return end
tourCardTemplate.attributes.id = internal.getUiId(CARD_ID, playerColor)
tourCardTemplate.children[1].attributes.id = internal.getUiId(LEFT_NARRATOR_ID, playerColor)
tourCardTemplate.children[2].attributes.id = internal.getUiId(RIGHT_NARRATOR_ID, playerColor)
tourCardTemplate.children[3].attributes.id = internal.getUiId(BUBBLE_ID, playerColor)
tourCardTemplate.children[4].attributes.id = internal.getUiId(TEXT_ID, playerColor)
tourCardTemplate.children[5].attributes.id = internal.getUiId(NEXT_BUTTON_ID, playerColor)
tourCardTemplate.children[5].attributes.onClick = self.getGUID() .. "/nextCard"
tourCardTemplate.children[6].attributes.id = internal.getUiId(STOP_BUTTON_ID, playerColor)
tourCardTemplate.children[6].attributes.onClick = self.getGUID() .. "/stopTour"
internal.setDeepVisibility(tourCardTemplate, playerColor)
local globalXml = Global.UI.getXmlTable()
table.insert(globalXml, tourCardTemplate)
Global.UI.setXmlTable(globalXml)
end
-- Panels don't cause their children to inherit their visibility value, so this recurses down the
-- XML table to set all children to the same visibility.
---@param xmlUi table Lua table describing the XML
---@param playerColor string String color of the player to make this visible for
internal.setDeepVisibility = function(xmlUi, playerColor)
xmlUi.attributes.visibility = "" .. playerColor
if xmlUi.children ~= nil then
for _, child in ipairs(xmlUi.children) do
internal.setDeepVisibility(child, playerColor)
end
end
end
internal.getUiId = function(baseId, playerColor)
return baseId .. "_" .. playerColor
end
return TourManager
end
end)
__bundle_register("core/GUIDReferenceApi", function(require, _LOADED, __bundle_register, __bundle_modules)
do
local GUIDReferenceApi = {}
local function getGuidHandler()
return getObjectFromGUID("123456")
end
-- Returns the matching object
---@param owner string Parent object for this search
---@param type string Type of object to search for
---@return any: Object reference to the matching object
GUIDReferenceApi.getObjectByOwnerAndType = function(owner, type)
return getGuidHandler().call("getObjectByOwnerAndType", { owner = owner, type = type })
end
-- Returns all matching objects as a table with references
---@param type string Type of object to search for
---@return table: List of object references to matching objects
GUIDReferenceApi.getObjectsByType = function(type)
return getGuidHandler().call("getObjectsByType", type)
end
-- Returns all matching objects as a table with references
---@param owner string Parent object for this search
---@return table: List of object references to matching objects
GUIDReferenceApi.getObjectsByOwner = function(owner)
return getGuidHandler().call("getObjectsByOwner", owner)
end
-- Sends new information to the reference handler to edit the main index
---@param owner string Parent of the object
---@param type string Type of the object
---@param guid string GUID of the object
GUIDReferenceApi.editIndex = function(owner, type, guid)
return getGuidHandler().call("editIndex", {
owner = owner,
type = type,
guid = guid
})
end
-- Returns the owner of an object or the object it's located on
---@param object tts__GameObject Object for this search
---@return string: Parent of the object or object it's located on
GUIDReferenceApi.getOwnerOfObject = function(object)
return getGuidHandler().call("getOwnerOfObject", object)
end
return GUIDReferenceApi
end
end)
__bundle_register("core/tour/TourCard", function(require, _LOADED, __bundle_register, __bundle_modules)
-- Table definition for the tour card layout. This is functionally XMLUI in Lua form, but using
-- this for dynamic creation ensures we can handle any player color without needing 10
-- near-duplicate definitions in Global.xml
tourCardTemplate = {
tag = "Panel",
attributes = {
id = "tourCard",
height = 215,
width = 330,
rotation = "0 0 0",
position = "0 300 30",
showAnimation = "FadeIn",
hideAnimation = "FadeOut",
active = false
},
children = {
{
tag = "Image",
attributes = {
id = "tourNarratorImageLeft",
height = 120,
width = 80,
rectAlignment = "UpperLeft",
offsetXY = "-80 0"
-- Image will be set when the card is updated
}
},
{
tag = "Image",
attributes = {
id = "tourNarratorImageRight",
active = false,
height = 125,
width = 80,
rectAlignment = "UpperRight",
offsetXY = "80 0"
-- Image will be set when the card is updated
}
},
{
tag = "Image",
attributes = {
id = "tourSpeechBubble",
color = "#F5F5DC",
height = 215,
width = 330,
rectAlignment = "MiddleCenter",
image = "SpeechBubble"
}
},
{
tag = "Text",
attributes = {
id = "tourText",
-- Everything on this is double-sized and scaled down to keep the text sharps
height = 370,
width = 520,
scale = "0.5 0.5 1",
rectAlignment = "UpperCenter",
offsetXY = "15 -15",
resizeTextForBestFit = true,
resizeTextMinSize = 20,
resizeTextMaxSize = 32,
color = "#050505",
alignment = "UpperLeft",
horizontalOverflow = "wrap"
}
},
{
tag = "Image",
attributes = {
id = "tourNext",
height = 45,
width = 45,
rectAlignment = "LowerRight",
offsetXY = "-5 -45",
image = "NextArrow"
}
},
{
tag = "Image",
attributes = {
id = "tourStop",
height = 45,
width = 45,
rectAlignment = "LowerLeft",
offsetXY = "35 -45",
image = "Exit"
}
}
}
}
end)
__bundle_register("core/tour/TourScript", function(require, _LOADED, __bundle_register, __bundle_modules)
-- Script for the SCED tour. Documentation and definitions to come.
TOUR_SCRIPT = {
{
narrator = "Roland",
text = "Despite my best efforts, looks like you found us. You may live to regret that. As long as you're here though we might as well show you around.\n\nUse the arrow to move forward, and if the horrors get to be too much you can quit whenever you like. Ready to get started?",
position = "center"
},
{
narrator = "Darrell",
text = "Cameras can be tricky things. Best you leave handling it to the professionals during the tour. Don't try to move the camera until the tour is complete.\n\nOnce we're done, remember you can use the 'p' key to switch back to third-person mode, and the spacebar to reset the position.",
position = "center",
speakerSide = "right"
},
{
narrator = "Daisy",
text = "If you're new to the game, the library here has everything you'll need. A little research can go a long way, and looking into old newspapers for the weird and unusual can yield some surprisingly helpful information.\n\nI put a few right there that might prove enlightening.",
objReferenceData = { owner = "Mythos", type = "RulesReference" },
distanceFromObj = 20,
position = "west",
speakerSide = "right"
},
{
narrator = "Mandy",
text = "To survive what's coming you'll need a deck. If it's safely hidden away on ArkhamDB you can load it here, and even find the newest version after an upgrade without changing the ID.\n\nNo need to publish all your decks, use 'Private' and you can see it. Just make sure to select 'Make your decks public' in ArkhamDB.",
objReferenceData = { owner = "Mythos", type = "DeckImporter" },
distanceFromObj = -5,
position = "northwest",
skipCentering = true
},
{
narrator = "Daniela",
text = "I prefer the hands-on approach to building things, if you do too you can build a deck yourself.\n\nAll the cards you could ever need are here, laid out like a disassembled engine. Place the cards on the table, copy them for your deck, and you'll be ready for anything.",
objReferenceData = { owner = "Mythos", type = "PlayerCardPanel" },
distanceFromObj = -7,
position = "south",
speakerSide = "right"
},
{
narrator = "Finn",
text = "Ready to face the unknown? We've smuggled shocking revelations and devious enemies from all over the world. Download the campaign you want to play, then Place it on the table to see the scenarios.\n\nJust remember - if it turns out to be too much for you, I was never here.",
objReferenceData = { owner = "Mythos", type = "CampaignThePathToCarcosa" },
distanceFromObj = 20,
position = "northwest",
skipCentering = true
},
{
narrator = "Diana",
text = "These symbols on the bottom right are a repository of arcane knowledge, containing all the official content to download plus some deviously creative works from fans. One should beware those who seem too fond of the darkness, but you cannot deny the quality of their efforts.\n\nDon't see anything here? Only promoted players can access these.",
position = "southeast"
},
{
narrator = "Winifred",
text = "No good aviator would fly a plane she didn't know and hadn't tweaked a bit herself. The gear icon contains settings to customize your play experience, from alternate ways to track your clues to a variety of helpers to streamline the game.\n\nEverything here is optional, but who doesn't want to go as fast as they can? Just remember that all settings affect all players, so strap in and trust your pilot!",
position = "southeast"
},
{
narrator = "Amina",
text = "This is the Mythos area. Encounter cards, acts, and agenda will all be placed here while the large map below is where you will be exploring - be sure to set the number of investigators!\n\nYou can count doom on the agenda by clicking the large counter, and the smaller will automatically count doom tokens on the table. The chaos bag is in that book over on the right, and you can add or remove tokens from it whenever you need.",
showPos = { x = -2.85, y = 0, z = 0.55 },
position = "north",
speakerSide = "right"
},
{
narrator = "Gloria",
text = "The evils that lurk in this world are out there, creeping ever closer. When they find you, this will easily draw a card from the encounter deck. The deck will even reshuffle itself when needed, for the enemies we face are unending.",
showPos = { x = -35, y = -20, z = 28 },
position = "west",
skipCentering = true
},
{
narrator = "Jacqueline",
text = "When the ire of fate finds you and the chaos looms, this large button will draw a chaos token. Click it again to return the token to the bag.\n\nWhether a vision of the future or a curse from the opponents we face, if you need additional tokens a right-click will draw more. I wish you luck, but have a vision of red tentacles reaching for you...",
showPos = { x = -35, y = -20, z = 4.25 },
position = "north",
skipCentering = true,
speakerSide = "right"
},
{
narrator = "Kohaku",
text = "Folklorists, immersed in the rich narratives of blessings and curses, explore the essence of human beliefs. You can use this tool to control the amount of bless and curse tokens in the chaos bag. It will also display the amount in the bag (+ sealed on cards) for you. Remember to remove bless / curse tokens with this after resolving them.",
objReferenceData = { owner = "Mythos", type = "BlessCurseManager" },
position = "center",
skipCentering = true,
speakerSide = "left"
},
{
narrator = "Preston",
text = "I can afford to buy what I need, but for those less well-off we've provided an endless pool of tokens to track your game. Simply drag one out of the pools here.\n\nResources are my favorite of course, but damage and horror are as inevitable as taxes. I leave those to my bookkeeper though. Those tokens can work like counters, use the number keys to change the value.",
objReferenceData = { owner = "Mythos", type = "ResourceTokenBag" },
position = "north",
skipCentering = true,
speakerSide = "right"
},
{
narrator = "Norman",
text = "That's the end of the tour, but there's much more to discover if you look in the right places. Some cards have helpers on the right-click menu, and every new version adds new content and functions.\n\nDon't be afraid to explore, and best of luck out there! We'll all need it...",
position = "center",
speakerSide = "right"
}
}
end)
__bundle_register("__root", function(require, _LOADED, __bundle_register, __bundle_modules)
require("core/tour/TourStarter")
end)
return __bundle_require("__root")