Refactor pause menu #284

This commit is contained in:
Cong
2024-03-12 22:57:32 +11:00
parent 577bcc0f80
commit f37179019b
8 changed files with 310 additions and 169 deletions

2
.gitignore vendored
View File

@@ -26,7 +26,6 @@ install_manifest_Runtime.txt
__pycache__/
.cmake/
compile_commands.json
CMakeSettings.json
# Generated files
src/cdogs/sys_config.h
@@ -62,6 +61,7 @@ Win32/
*.tmp
*.VC.*
.vs/
CMakeSettings.json
VSInheritEnvironments.txt
# XCode

View File

@@ -34,6 +34,7 @@ set(CDOGS_SDL_SOURCES
menu_utils.c
namegen.c
password.c
pause_menu.c
player_select_menus.c
prep.c
prep_equip.c
@@ -57,6 +58,7 @@ set(CDOGS_SDL_HEADERS
menu_utils.h
namegen.h
password.h
pause_menu.h
player_select_menus.h
prep.h
prep_equip.h

View File

@@ -1,7 +1,7 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2017, 2019-2020 Cong Xu
Copyright (c) 2013-2017, 2019-2020, 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -120,16 +120,12 @@ static void DrawSharedRadar(GraphicsDevice *device, bool showExit)
static void DrawPlayerAreas(HUD *hud, const int numViews);
static void DrawDeathmatchScores(HUD *hud);
static void DrawStateMessage(
HUD *hud, const input_device_e pausingDevice,
const bool controllerUnplugged);
static void DrawMissionState(HUD *hud);
static void DrawHUDMessage(HUD *hud);
static void DrawKeycards(HUD *hud);
static void DrawMissionTime(HUD *hud);
static void DrawObjectiveCounts(HUD *hud);
void HUDDraw(
HUD *hud, const input_device_e pausingDevice,
const bool controllerUnplugged, const int numViews)
void HUDDraw(HUD *hud, const int numViews, const bool paused)
{
if (ConfigGetBool(&gConfig, "Graphics.ShowHUD"))
{
@@ -153,7 +149,10 @@ void HUDDraw(
}
}
DrawStateMessage(hud, pausingDevice, controllerUnplugged);
if (!paused)
{
DrawMissionState(hud);
}
}
static void DrawPlayerAreas(HUD *hud, const int numViews)
@@ -282,79 +281,6 @@ static void DrawDeathmatchScores(HUD *hud)
CA_FOREACH_END()
}
static void DrawMissionState(HUD *hud);
static void DrawStateMessage(
HUD *hud, const input_device_e pausingDevice,
const bool controllerUnplugged)
{
if (controllerUnplugged || pausingDevice != INPUT_DEVICE_UNSET)
{
// Draw a background overlay
color_t overlay = colorBlack;
overlay.a = 128;
DrawRectangle(
hud->device, svec2i_zero(), hud->device->cachedConfig.Res, overlay,
true);
}
if (controllerUnplugged)
{
struct vec2i pos = svec2i_scale_divide(
svec2i_subtract(
gGraphicsDevice.cachedConfig.Res,
FontStrSize(ARROW_LEFT
"Paused" ARROW_RIGHT
"\nFoobar\nPlease reconnect controller")),
2);
const int x = pos.x;
FontStr(ARROW_LEFT "Paused" ARROW_RIGHT, pos);
pos.y += FontH();
pos = FontStr("Press ", pos);
char buf[256];
color_t c = colorWhite;
InputGetButtonNameColor(pausingDevice, 0, CMD_ESC, buf, &c);
pos = FontStrMask(buf, pos, c);
FontStr(" to quit", pos);
pos.x = x;
pos.y += FontH();
FontStr("Please reconnect controller", pos);
}
else if (pausingDevice != INPUT_DEVICE_UNSET)
{
struct vec2i pos = svec2i_scale_divide(
svec2i_subtract(
gGraphicsDevice.cachedConfig.Res,
FontStrSize("Foo\nPress foo or bar to unpause\nBaz")),
2);
const int x = pos.x;
FontStr(ARROW_LEFT "Paused" ARROW_RIGHT, pos);
pos.y += FontH();
pos = FontStr("Press ", pos);
char buf[256];
color_t c = colorWhite;
InputGetButtonNameColor(pausingDevice, 0, CMD_ESC, buf, &c);
pos = FontStrMask(buf, pos, c);
FontStr(" again to quit", pos);
pos.x = x;
pos.y += FontH();
pos = FontStr("Press ", pos);
c = colorWhite;
InputGetButtonNameColor(pausingDevice, 0, CMD_BUTTON1, buf, &c);
pos = FontStrMask(buf, pos, c);
pos = FontStr(" or ", pos);
c = colorWhite;
InputGetButtonNameColor(pausingDevice, 0, CMD_BUTTON2, buf, &c);
pos = FontStrMask(buf, pos, c);
FontStr(" to unpause", pos);
}
else
{
DrawMissionState(hud);
}
}
static void DrawMissionState(HUD *hud)
{
char s[50];

View File

@@ -77,6 +77,4 @@ HUDDrawData HUDGetDrawData(void);
void HUDUpdate(HUD *hud, const int ms);
void HUDDraw(
HUD *hud, const input_device_e pausingDevice,
const bool controllerUnplugged, const int numViews);
void HUDDraw(HUD *hud, const int numViews, const bool paused);

View File

@@ -86,8 +86,8 @@ static void PlayerSpecialCommands(TActor *actor, const int cmd)
}
}
else if (
!Button2(actor->lastCmd) && Button2(cmd) &&
!actor->specialCmdDir && !actor->CanPickupSpecial &&
!Button2(actor->lastCmd) && Button2(cmd) && !actor->specialCmdDir &&
!actor->CanPickupSpecial &&
!(ConfigGetEnum(&gConfig, "Game.SwitchMoveStyle") ==
SWITCHMOVE_SLIDE &&
CMD_HAS_DIRECTION(cmd)))
@@ -216,6 +216,8 @@ static void RunGameOnEnter(GameLoopData *data)
}
}
PauseMenuInit(&rData->pm, &gEventHandlers, &gGraphicsDevice);
rData->m->state = MISSION_STATE_WAITING;
rData->m->isDone = false;
rData->m->DoneCounter = 0;
@@ -264,6 +266,7 @@ static void RunGameOnExit(GameLoopData *data)
CA_FOREACH_END()
CArrayTerminate(&rData->ammoSpawners);
CameraTerminate(&rData->Camera);
PauseMenuTerminate(&rData->pm);
// Draw background
GrafxRedrawBackground(&gGraphicsDevice, rData->Camera.lastPosition);
@@ -311,17 +314,14 @@ static void RunGameInput(GameLoopData *data)
}
memset(rData->cmds, 0, sizeof rData->cmds);
int cmdAll = 0;
int idx = 0;
input_device_e pausingDevice = INPUT_DEVICE_UNSET;
input_device_e firstPausingDevice = INPUT_DEVICE_UNSET;
if (GetNumPlayers(PLAYER_ANY, false, true) == 0)
{
// If no players, allow default keyboard to control camera
rData->cmds[0] = GetKeyboardCmd(&gEventHandlers.keyboard, 0, false);
firstPausingDevice = INPUT_DEVICE_KEYBOARD;
}
else
{
int idx = 0;
for (int i = 0; i < (int)gPlayerDatas.size; i++, idx++)
{
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
@@ -330,87 +330,29 @@ static void RunGameInput(GameLoopData *data)
idx--;
continue;
}
if (firstPausingDevice == INPUT_DEVICE_UNSET)
{
firstPausingDevice = p->inputDevice;
}
rData->cmds[idx] = GetGameCmd(&gEventHandlers, p);
cmdAll |= rData->cmds[idx];
// Only allow the first player to escape
// Use keypress otherwise the player will quit immediately
if (idx == 0 && (rData->cmds[idx] & CMD_ESC) &&
!(rData->lastCmds[idx] & CMD_ESC))
{
pausingDevice = p->inputDevice;
}
}
}
if (KeyIsPressed(&gEventHandlers.keyboard, SDL_SCANCODE_ESCAPE))
{
pausingDevice = INPUT_DEVICE_KEYBOARD;
}
// Check if any controllers are unplugged
rData->controllerUnplugged = false;
CA_FOREACH(const PlayerData, p, gPlayerDatas)
if (p->inputDevice == INPUT_DEVICE_UNSET && p->IsLocal)
{
rData->controllerUnplugged = true;
break;
}
CA_FOREACH_END()
// If in Superhot(tm) Mode, don't update unless there was an input in this
// or the last frame
data->SkipNextFrame = data->SuperhotMode && !lastCmdAll;
// Check if:
// - escape was pressed, or
// - window lost focus
// - controller unplugged
// If the game is paused, unpause if a button is released
// If the game was not paused, enter pause mode
// If the game was paused and escape was pressed, exit the game
if (rData->pausingDevice != INPUT_DEVICE_UNSET && AnyButton(lastCmdAll) &&
!AnyButton(cmdAll))
// Update and check if we want to quit
if (PauseMenuUpdate(&rData->pm, rData->cmds, rData->lastCmds))
{
rData->pausingDevice = INPUT_DEVICE_UNSET;
}
else if (rData->controllerUnplugged || gEventHandlers.HasLostFocus)
{
// Pause the game
rData->pausingDevice = firstPausingDevice;
rData->isMap = false;
SoundPlay(&gSoundDevice, StrSound("menu_error"));
}
else if (pausingDevice != INPUT_DEVICE_UNSET)
{
if (rData->pausingDevice != INPUT_DEVICE_UNSET)
{
// Already paused; exit
GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
e.u.MissionEnd.IsQuit = true;
GameEventsEnqueue(&gGameEvents, e);
// Need to unpause to process the quit
rData->pausingDevice = INPUT_DEVICE_UNSET;
rData->controllerUnplugged = false;
// Don't skip exiting the game
data->SkipNextFrame = false;
SoundPlay(&gSoundDevice, StrSound("menu_back"));
}
else
{
// Pause the game
rData->pausingDevice = pausingDevice;
rData->isMap = false;
SoundPlay(&gSoundDevice, StrSound("menu_back"));
}
// Need to unpause to process the quit
data->SkipNextFrame = true;
}
const bool paused = rData->pausingDevice != INPUT_DEVICE_UNSET ||
rData->controllerUnplugged;
if (!paused)
// Don't show map if pause menu is shown
if (PauseMenuIsShown(&rData->pm))
{
rData->isMap = false;
}
if (!PauseMenuIsShown(&rData->pm))
{
// Check if automap key is pressed by any player
// Toggle
@@ -464,8 +406,7 @@ static GameLoopResult RunGameUpdate(GameLoopData *data, LoopRunner *l)
// If we're not hosting a net game,
// don't update if the game has paused or has automap shown
// Important: don't consider paused if we are trying to quit
const bool paused = rData->pausingDevice != INPUT_DEVICE_UNSET ||
rData->controllerUnplugged || rData->isMap;
const bool paused = PauseMenuIsShown(&rData->pm) || rData->isMap;
if (!gCampaign.IsClient && !ConfigGetBool(&gConfig, "StartServer") &&
paused && !gEventHandlers.HasQuit)
{
@@ -700,9 +641,8 @@ static void RunGameDraw(GameLoopData *data)
// Draw HUD layer
BlitClearBuf(&gGraphicsDevice);
CameraDrawMode(&rData->Camera);
HUDDraw(
&rData->Camera.HUD, rData->pausingDevice, rData->controllerUnplugged,
rData->Camera.NumViews);
HUDDraw(&rData->Camera.HUD, rData->Camera.NumViews, PauseMenuIsShown(&rData->pm));
PauseMenuDraw(&rData->pm);
// Draw automap if enabled
if (rData->isMap)
{

View File

@@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2015, 2017, 2020-2021 Cong Xu
Copyright (c) 2015, 2017, 2020-2021, 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -58,6 +58,7 @@
#include <cdogs/powerup.h>
#include "game_loop.h"
#include "pause_menu.h"
GameLoopData *RunGame(Campaign *co, struct MissionOptions *m, Map *map);
@@ -67,9 +68,7 @@ typedef struct
struct MissionOptions *m;
Map *map;
Camera Camera;
// TODO: turn the following into a screen system?
input_device_e pausingDevice; // INPUT_DEVICE_UNSET if not paused
bool controllerUnplugged;
PauseMenu pm;
bool isMap;
int cmds[MAX_LOCAL_PLAYERS];
int lastCmds[MAX_LOCAL_PLAYERS];

226
src/pause_menu.c Normal file
View File

@@ -0,0 +1,226 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "pause_menu.h"
#include <cdogs/draw/drawtools.h>
#include <cdogs/font.h>
void PauseMenuInit(PauseMenu *pm, EventHandlers *handlers, GraphicsDevice *g)
{
memset(pm, 0, sizeof *pm);
MenuSystemInit(&pm->ms, handlers, g, svec2i_zero(), g->cachedConfig.Res);
// TODO: create pause menu
pm->handlers = handlers;
pm->g = g;
pm->pausingDevice = INPUT_DEVICE_UNSET;
}
void PauseMenuTerminate(PauseMenu *pm)
{
MenuSystemTerminate(&pm->ms);
}
bool PauseMenuUpdate(
PauseMenu *pm, const int cmds[MAX_LOCAL_PLAYERS],
const int lastCmds[MAX_LOCAL_PLAYERS])
{
int cmdAll = 0;
int lastCmdAll = 0;
for (int i = 0; i < MAX_LOCAL_PLAYERS; i++)
{
lastCmdAll |= lastCmds[i];
}
input_device_e pausingDevice = INPUT_DEVICE_UNSET;
input_device_e firstPausingDevice = INPUT_DEVICE_UNSET;
if (GetNumPlayers(PLAYER_ANY, false, true) == 0)
{
// If no players, allow default keyboard
firstPausingDevice = INPUT_DEVICE_KEYBOARD;
cmdAll |= cmds[0];
}
else
{
int idx = 0;
for (int i = 0; i < (int)gPlayerDatas.size; i++, idx++)
{
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
if (!p->IsLocal)
{
idx--;
continue;
}
cmdAll |= cmds[idx];
if (firstPausingDevice == INPUT_DEVICE_UNSET)
{
firstPausingDevice = p->inputDevice;
}
// Only allow the first player to escape
// Use keypress otherwise the player will quit immediately
if (idx == 0 && (cmds[idx] & CMD_ESC) &&
!(lastCmds[idx] & CMD_ESC))
{
pausingDevice = p->inputDevice;
}
}
}
if (KeyIsPressed(&gEventHandlers.keyboard, SDL_SCANCODE_ESCAPE))
{
pausingDevice = INPUT_DEVICE_KEYBOARD;
}
// Check if any controllers are unplugged
pm->controllerUnplugged = false;
CA_FOREACH(const PlayerData, p, gPlayerDatas)
if (p->inputDevice == INPUT_DEVICE_UNSET && p->IsLocal)
{
pm->controllerUnplugged = true;
break;
}
CA_FOREACH_END()
// Check if:
// - escape was pressed, or
// - window lost focus
// - controller unplugged
// If the game is paused, update the pause menu
// If the game was not paused, enter pause mode
// If the game was paused and escape was pressed, exit the game
if (pm->pausingDevice != INPUT_DEVICE_UNSET && AnyButton(lastCmdAll) &&
!AnyButton(cmdAll))
{
// Unpause on any button press
// TODO: don't do this, update menu system instead
pm->pausingDevice = INPUT_DEVICE_UNSET;
}
else if (pm->controllerUnplugged || gEventHandlers.HasLostFocus)
{
// Pause the game
pm->pausingDevice = firstPausingDevice;
SoundPlay(&gSoundDevice, StrSound("menu_error"));
}
else if (pausingDevice != INPUT_DEVICE_UNSET)
{
if (pm->pausingDevice != INPUT_DEVICE_UNSET)
{
// Already paused; exit
// TODO: don't exit, use pause menu
GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
e.u.MissionEnd.IsQuit = true;
GameEventsEnqueue(&gGameEvents, e);
// Need to unpause to process the quit
pm->pausingDevice = INPUT_DEVICE_UNSET;
pm->controllerUnplugged = false;
SoundPlay(&gSoundDevice, StrSound("menu_back"));
// Return true to say we want to quit
return true;
}
else
{
// Pause the game
pm->pausingDevice = pausingDevice;
SoundPlay(&gSoundDevice, StrSound("menu_back"));
}
}
return false;
}
void PauseMenuDraw(const PauseMenu *pm)
{
if (!PauseMenuIsShown(pm))
{
return;
}
// Draw a background overlay
color_t overlay = colorBlack;
overlay.a = 128;
DrawRectangle(
pm->g, svec2i_zero(), pm->g->cachedConfig.Res, overlay, true);
if (pm->controllerUnplugged)
{
struct vec2i pos = svec2i_scale_divide(
svec2i_subtract(
pm->g->cachedConfig.Res,
FontStrSize(ARROW_LEFT
"Paused" ARROW_RIGHT
"\nFoobar\nPlease reconnect controller")),
2);
const int x = pos.x;
FontStr(ARROW_LEFT "Paused" ARROW_RIGHT, pos);
pos.y += FontH();
pos = FontStr("Press ", pos);
char buf[256];
color_t c = colorWhite;
InputGetButtonNameColor(pm->pausingDevice, 0, CMD_ESC, buf, &c);
pos = FontStrMask(buf, pos, c);
FontStr(" to quit", pos);
pos.x = x;
pos.y += FontH();
FontStr("Please reconnect controller", pos);
}
else if (pm->pausingDevice != INPUT_DEVICE_UNSET)
{
struct vec2i pos = svec2i_scale_divide(
svec2i_subtract(
gGraphicsDevice.cachedConfig.Res,
FontStrSize("Foo\nPress foo or bar to unpause\nBaz")),
2);
const int x = pos.x;
FontStr(ARROW_LEFT "Paused" ARROW_RIGHT, pos);
pos.y += FontH();
pos = FontStr("Press ", pos);
char buf[256];
color_t c = colorWhite;
InputGetButtonNameColor(pm->pausingDevice, 0, CMD_ESC, buf, &c);
pos = FontStrMask(buf, pos, c);
FontStr(" again to quit", pos);
pos.x = x;
pos.y += FontH();
pos = FontStr("Press ", pos);
c = colorWhite;
InputGetButtonNameColor(pm->pausingDevice, 0, CMD_BUTTON1, buf, &c);
pos = FontStrMask(buf, pos, c);
pos = FontStr(" or ", pos);
c = colorWhite;
InputGetButtonNameColor(pm->pausingDevice, 0, CMD_BUTTON2, buf, &c);
pos = FontStrMask(buf, pos, c);
FontStr(" to unpause", pos);
}
}
bool PauseMenuIsShown(const PauseMenu *pm)
{
return pm->pausingDevice != INPUT_DEVICE_UNSET || pm->controllerUnplugged;
}

50
src/pause_menu.h Normal file
View File

@@ -0,0 +1,50 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2024 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "menu.h"
typedef struct
{
MenuSystem ms;
EventHandlers *handlers;
GraphicsDevice *g;
input_device_e pausingDevice; // INPUT_DEVICE_UNSET if not paused
bool controllerUnplugged;
} PauseMenu;
void PauseMenuInit(PauseMenu *pm, EventHandlers *handlers, GraphicsDevice *g);
void PauseMenuTerminate(PauseMenu *pm);
// Returns whether to quit
bool PauseMenuUpdate(
PauseMenu *pm, const int cmds[MAX_LOCAL_PLAYERS],
const int lastCmds[MAX_LOCAL_PLAYERS]);
void PauseMenuDraw(const PauseMenu *pm);
bool PauseMenuIsShown(const PauseMenu *pm);