diff --git a/[gamemodes]/[deathmatch]/deathmatch/client/hud.lua b/[gamemodes]/[deathmatch]/deathmatch/client/hud.lua index be363ede8..a9988c5ea 100644 --- a/[gamemodes]/[deathmatch]/deathmatch/client/hud.lua +++ b/[gamemodes]/[deathmatch]/deathmatch/client/hud.lua @@ -48,22 +48,13 @@ _hud.scoreDisplay.update = function() ) end --- respawn screen -_hud.respawnScreen = {} --- respawn counter (You will respawn in x seconds) -_hud.respawnScreen.respawnCounter = dxText:create("", 0.5, 0.5, true, "beckett", 4) -_hud.respawnScreen.respawnCounter:type("stroke", 6) -_hud.respawnScreen.respawnCounter:color(255, 225, 225) -_hud.respawnScreen.respawnCounter:visible(false) -_hud.respawnScreen.setVisible = function(_, visible) - _hud.respawnScreen.respawnCounter:visible(visible) -end -_hud.respawnScreen.startCountdown = function() - if _respawnTime > 0 then - startCountdown(_respawnTime) - else - _hud.respawnScreen.respawnCounter:text("Wasted") - end +-- wasted screen +_hud.wastedScreen = {} +_hud.wastedScreen.text = dxText:create("Wasted", 0.5, 0.5, true, "beckett", 4) +_hud.wastedScreen.text:type("border", 2) +_hud.wastedScreen.text:color(255, 0, 0) +_hud.wastedScreen.setVisible = function(_, visible) + _hud.wastedScreen.text:visible(visible) end -- end screen @@ -93,49 +84,22 @@ _hud.endScreen.update = function(_, winner, draw, aborted) playSound("client/audio/mission_accomplished.mp3") end end + +-- spectate screen +_hud.spectateScreen = {} +-- spectating info label +_hud.spectateScreen.infoLabel = dxText:create("You are currently spectating.\nUse left and right arrow to switch players.", 0, 0, false, "default-bold", 2) +_hud.spectateScreen.infoLabel:color(225, 225, 225, 225) +_hud.spectateScreen.infoLabel:boundingBox(0, 0.8, 1, 1, true) +_hud.spectateScreen.infoLabel:align("bottom", "center") +_hud.spectateScreen.infoLabel:type("border", 2) +_hud.spectateScreen.setVisible = function(_, visible) + _hud.spectateScreen.infoLabel:visible(visible) +end + -- hide all HUD elements by default _hud.loadingScreen:setVisible(false) _hud.scoreDisplay:setVisible(false) -_hud.respawnScreen:setVisible(false) +_hud.wastedScreen:setVisible(false) _hud.endScreen:setVisible(false) - --- TODO: clean this junk up -local function dxSetAlpha ( dx, a ) - local r,g,b = dx:color() - dx:color(r,g,b,a) -end - -local countdownCR -local function countdown(time) - for i=time,0,-1 do - _hud.respawnScreen.respawnCounter:text("Wasted\n"..i) - setTimer ( countdownCR, 1000, 1 ) - coroutine.yield() - end -end - -local function hideCountdown() - setTimer ( - function() - _hud.respawnScreen:setVisible(false) - end, - 600, 1 - ) - Animation.createAndPlay( - _hud.respawnScreen.respawnCounter, - {{ from = 225, to = 0, time = 400, fn = dxSetAlpha }} - ) - removeEventHandler ( "onClientPlayerSpawn", localPlayer, hideCountdown ) -end - -function startCountdown(time) - Animation.createAndPlay( - _hud.respawnScreen.respawnCounter, - {{ from = 0, to = 225, time = 600, fn = dxSetAlpha }} - ) - addEventHandler ( "onClientPlayerSpawn", localPlayer, hideCountdown ) - _hud.respawnScreen:setVisible(true) - time = math.floor(time/1000) - countdownCR = coroutine.wrap(countdown) - countdownCR(time) -end +_hud.spectateScreen:setVisible(false) \ No newline at end of file diff --git a/[gamemodes]/[deathmatch]/deathmatch/client/main.lua b/[gamemodes]/[deathmatch]/deathmatch/client/main.lua index b514199bc..cc36300c0 100644 --- a/[gamemodes]/[deathmatch]/deathmatch/client/main.lua +++ b/[gamemodes]/[deathmatch]/deathmatch/client/main.lua @@ -1,3 +1,5 @@ +local _wastedTimer, _respawnTimer + -- -- startGamemodeClient: initializes the gamemode client -- @@ -9,6 +11,8 @@ local function startGamemodeClient() exports.scoreboard:scoreboardSetSortBy("Rank") -- fade out camera fadeCamera(false, 0) + -- disable zone name HUD element + setPlayerHudComponentVisible("area_name", false) -- if a game is in progress, apply the loading camera matrix if getElementData(resourceRoot, "gameState") == GAME_IN_PROGRESS then setCameraMatrix(unpack(calculateLoadingCameraMatrix())) @@ -25,6 +29,8 @@ local function stopGamemodeClient() exports.scoreboard:scoreboardRemoveColumn("Rank") -- hide scoreboard exports.scoreboard:setScoreboardForced(false) + -- re-enable zone name HUD element + setPlayerHudComponentVisible("area_name", true) end addEventHandler("onClientResourceStop", resourceRoot, stopGamemodeClient) @@ -68,10 +74,15 @@ addEventHandler("onClientGamemodeMapStop", resourceRoot, stopGamemodeMap) -- startGamemodeRound: triggered when a round begins -- local function startGamemodeRound() - -- attach player wasted handler - addEventHandler("onClientPlayerWasted", localPlayer, _hud.respawnScreen.startCountdown) + -- attach player spawn and wasted handler + addEventHandler("onClientPlayerSpawn", localPlayer, localPlayerSpawn) + addEventHandler("onClientPlayerWasted", localPlayer, localPlayerWasted) -- attach element data change handler addEventHandler("onClientElementDataChange", root, elementDataChange) + -- stop spectating + if isSpectating() then + stopSpectating(true) + end -- hide end/loading screens and scoreboard _hud.loadingScreen:setVisible(false) _hud.endScreen:setVisible(false) @@ -87,30 +98,73 @@ addEventHandler("onClientGamemodeRoundStart", resourceRoot, startGamemodeRound) -- stopGamemodeRound: triggered when a round ends -- local function stopGamemodeRound(winner, draw, aborted) - -- remove player wasted handler and hide respawn screen if active - removeEventHandler("onClientPlayerWasted", localPlayer, _hud.respawnScreen.startCountdown) - _hud.respawnScreen.setVisible(false) + -- remove player spawn & wasted handler and hide respawn screen if active + removeEventHandler("onClientPlayerWasted", localPlayer, localPlayerWasted) + removeEventHandler("onClientPlayerSpawn", localPlayer, localPlayerSpawn) -- remove element data change handler removeEventHandler("onClientElementDataChange", root, elementDataChange) -- hide score display _hud.scoreDisplay:setVisible(false) + -- hide wasted screen and cancel the wasted and respawn timers + _hud.wastedScreen:setVisible(false) + if isTimer(_wastedTimer) then + killTimer(_wastedTimer) + end + if isElement(_respawnTimer) then + destroyElement(_respawnTimer) + end -- spectate the winner if winner and player ~= winner then - if winner then - setCameraTarget(winner) + startSpectating(winner) + end + -- exit spectate mode and go to black if round was aborted + if aborted then + if isSpectating() then + stopSpectating(true) end - toggleAllControls(true, true, false) + fadeCamera(false, 0) + else + -- begin fading out the screen + fadeCamera(false, ROUND_START_DELAY/1000) + -- show end screen and scoreboard + _hud.endScreen:update(winner, draw, aborted) + _hud.endScreen:setVisible(true) + exports.scoreboard:setScoreboardForced(true) end - -- begin fading out the screen - fadeCamera(false, CAMERA_LOAD_DELAY/1000) - -- show end screen and scoreboard - _hud.endScreen:update(winner, draw, aborted) - _hud.endScreen:setVisible(true) - exports.scoreboard:setScoreboardForced(true) end addEvent("onClientGamemodeRoundEnd", true) addEventHandler("onClientGamemodeRoundEnd", resourceRoot, stopGamemodeRound) +-- +-- localPlayerWasted: triggered when local player is killed +-- +function localPlayerWasted() + -- show the wasted screen + _hud.wastedScreen:setVisible(true) + + -- set timer to show the spectate screen + _wastedTimer = setTimer(startSpectating, WASTED_CAMERA_DURATION, 1) + + -- create a respawn timer is repawn is enabled + if _respawnTime > 0 then + _respawnTimer = exports.missionTimer:createMissionTimer(WASTED_CAMERA_DURATION + _respawnTime, true, "You will respawn in %s seconds", 0.5, 50, true, "default-bold", 1) + end +end + +-- +-- localPlayerSpawn: triggered when local player is spawned +-- +function localPlayerSpawn() + -- if we're spectating, stop spectating + if isSpectating() then + stopSpectating() + end + -- kill the respawn timer if it exists + if isElement(_respawnTimer) then + destroyElement(_respawnTimer) + end +end + -- -- elementDataChange: triggered when element data changes - used to track score changes -- diff --git a/[gamemodes]/[deathmatch]/deathmatch/client/spectate.lua b/[gamemodes]/[deathmatch]/deathmatch/client/spectate.lua new file mode 100644 index 000000000..008d72b16 --- /dev/null +++ b/[gamemodes]/[deathmatch]/deathmatch/client/spectate.lua @@ -0,0 +1,168 @@ +local _spectating = false +local _currentTarget +local _validTargets = {} + +-- +-- startSpectating([target]): starts spectating the targted player, or a random one if target == nil +-- +function startSpectating(target) + -- fade camera out, hide radar hud and score screen + fadeCamera(false, 0) + setPlayerHudComponentVisible("radar", false) + _hud.scoreDisplay:setVisible(false) + + -- hide wasted screen and destroy wasted timer + _hud.wastedScreen:setVisible(false) + if isElement(_wastedTimer) then + destroyElement(_wastedTimer) + end + + -- if target is nil, pick a random player + if not target then + target = _validTargets[math.random(1, #_validTargets)] + end + + -- if there still isn't a target, error out + if not target then + -- TODO: handle this more gracefully + error("no valid spectate target", 2) + end + + -- set camera target and disable controls + iprint(target) + setCameraTarget(target) + toggleAllControls(false, true, false) + + -- show spectate screen + _hud.spectateScreen:setVisible(true) + + -- bind left and right arrow keys to cycle spectate target + bindKey("left", "down", cycleSpectateTarget, true) + bindKey("right", "down", cycleSpectateTarget) + + -- fade camera in next frame + setTimer(fadeCamera, 50, 1, true, 1) + + _spectating = true + _currentTarget = nil +end + +-- +-- stopSpectating([fadeOut]): exits spectate mode. if fadeOut == true camera will not fade back in +-- +function stopSpectating(fadeOut) + -- fade camera out, restore radar hud and score screen + fadeCamera(false, 0) + setPlayerHudComponentVisible("radar", true) + _hud.scoreDisplay:setVisible(true) + + -- reset camera target and controls + setCameraTarget(localPlayer) + toggleAllControls(true, true, false) + + -- hide spectate screen + _hud.spectateScreen:setVisible(false) + + -- bind left and right arrow keys to cycle spectate target + unbindKey("left", "down", cycleSpectateTarget) + unbindKey("right", "down", cycleSpectateTarget) + + -- fade camera in next frame + if not fadeOut then + setTimer(fadeCamera, 50, 1, true, 1) + end + + _spectating = false + _currentTarget = nil +end + +-- +-- isSpectating(): returns true if local player is spectating, false otherwise +-- +function isSpectating() + return _spectating +end + +-- +-- setSpectateTarget(target): updates spectate target +-- +function setSpectateTarget(target) + if not _spectating then + error("local player is not spectating", 2) + end + + if target == _currentTarget then + return + end + + _currentTarget = target + setCameraTarget(target) +end + +-- +-- cycleSpectateTarget(): cycles to next or previous target while in spectate mode +-- +function cycleSpectateTarget(previous) + if not _spectating then + error("local player is not spectating", 2) + end + + local index = 1 + for i, validTarget in ipairs(_validTargets) do + if validTarget == _currentTarget then + index = i + break + end + end + + if previous then + index = index - 1 + if index < 1 then + index = #_validTargets + end + else + index = index + 1 + if index > #_validTargets then + index = 1 + end + end + + setSpectateTarget(_validTargets[index]) +end + +-- +-- functions to update target list on player spawn, death, and resource start +-- +local function addValidTarget(playerTeam) + if source == localPlayer then + return + end + + table.insert(_validTargets, source) +end +addEventHandler("onClientPlayerSpawn", root, addValidTarget) + +local function removeValidTarget() + if source == localPlayer then + return + end + + for i, target in ipairs(_validTargets) do + table.remove(_validTargets, i) + end +end +addEventHandler("onClientPlayerWasted", root, removeValidTarget) + +local function refreshValidTargets() + local players = getElementsByType("player", root) + + -- remove local player and dead players from list + for i, player in ipairs(players) do + if player == localPlayer or isPedDead(player) then + table.remove(players, i) + end + end + + _validTargets = players +end +addEventHandler("onClientResourceStart", resourceRoot, refreshValidTargets) \ No newline at end of file diff --git a/[gamemodes]/[deathmatch]/deathmatch/meta.xml b/[gamemodes]/[deathmatch]/deathmatch/meta.xml index fb1d35f7e..55ea4e3ff 100644 --- a/[gamemodes]/[deathmatch]/deathmatch/meta.xml +++ b/[gamemodes]/[deathmatch]/deathmatch/meta.xml @@ -1,5 +1,5 @@ - + @@ -25,6 +25,7 @@