do require("core/tour/TourScript") require("core/tour/TourCard") local TourManager = { } local internal = { } -- Base IDs for various tour card UI elements. Actual IDs will have _[playerColor] appended local CARD_ID = "tourCard" local NARRATOR_ID = "tourNarratorImage" 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 CAMERA_HOME = { x = -30.2, y = 60, z = 0, } -- Trackers for the current state. local tourState = { } local cameraHookGuid = { } local currentCardIndex = { } -- 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 Player color to start the tour for TourManager.startTour = function(playerColor) log(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 2 sec seems to handle it Player[playerColor].setCameraMode("ThirdPerson") Player[playerColor].lookAt({position={-22.265,-2.5,5.2575},pitch=64.343,yaw=90.333,distance=104.7}) Wait.time(function() internal.createTourCard(playerColor) -- XML update 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, 2) 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 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 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 Player color to show the current card for internal.showCurrentCard = function(playerColor) internal.updateCardDisplay(playerColor) local hook = getObjectFromGUID(tourState[playerColor].cameraHookGuid) hook.setPositionSmooth(CAMERA_HOME, false, false) local delay = 0.5 local cardIndex = tourState[playerColor].currentCardIndex if TOUR_SCRIPT[cardIndex].showObj ~= nil then Wait.time(function() local lookAtObj = getObjectFromGUID(TOUR_SCRIPT[cardIndex].showObj) hook.setPositionSmooth(lookAtObj.getPosition(), 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 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 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() -- This resets to the default camera position. If we don't place the camera exactly at the -- default, camera controls get weird Player[playerColor].lookAt({position={-22.265,-2.5,5.2575},pitch=64.343,yaw=90.333,distance=104.7}) end, 3) end -- Updates the card UI to show the appropriate narrator and text. -- @param playerColor Player color to update card for internal.updateCardDisplay = function(playerColor) local index = tourState[playerColor].currentCardIndex Global.UI.setAttribute(internal.getUiId(NARRATOR_ID, playerColor), "image", TOUR_SCRIPT[index].narrator) Global.UI.setAttribute(internal.getUiId(TEXT_ID, playerColor), "text", TOUR_SCRIPT[index].text) 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 Player color to create the hook for internal.createCameraHook = function(playerColor) log("Creating") log(playerColor) local hookData = { Name = "BlockSquare", Transform = { posX = CAMERA_HOME.x, posY = CAMERA_HOME.y, posZ = CAMERA_HOME.z, rotX = 0, rotY = 270.0, 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 Created object internal.onHookCreated = function(hook) local playerColor = hook.getGMNotes() log("Hook created") log(playerColor) 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 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 CARD_ID = CARD_ID .. "_" .. playerColor NARRATOR_ID = NARRATOR_ID .. "_" .. playerColor TEXT_ID = TEXT_ID .. "_" .. playerColor NEXT_BUTTON_ID = NEXT_BUTTON_ID .. "_" .. playerColor STOP_BUTTON_ID = STOP_BUTTON_ID .. "_" .. playerColor tourCardTemplate.attributes.id = internal.getUiId(CARD_ID, playerColor) tourCardTemplate.attributes.visibility = playerColor tourCardTemplate.children[1].attributes.id = internal.getUiId(NARRATOR_ID, playerColor) tourCardTemplate.children[2].children[1].attributes.id = internal.getUiId(TEXT_ID, playerColor) tourCardTemplate.children[3].attributes.id = internal.getUiId(NEXT_BUTTON_ID, playerColor) tourCardTemplate.children[4].attributes.id = internal.getUiId(STOP_BUTTON_ID, playerColor) tourCardTemplate.children[3].attributes.onClick = self.getGUID().."/nextCard" tourCardTemplate.children[4].attributes.onClick = self.getGUID().."/stopTour" local globalXml = Global.UI.getXmlTable() table.insert(globalXml, tourCardTemplate) Global.UI.setXmlTable(globalXml) end internal.getUiId = function(baseId, playerColor) return baseId .. "_" .. playerColor end return TourManager end