Add high scores to level select menu #151

This commit is contained in:
Cong
2025-02-08 15:26:06 +11:00
parent 5610b6c888
commit e6c3d522f7
4 changed files with 113 additions and 62 deletions

View File

@@ -173,10 +173,6 @@ static GameLoopResult HighScoresScreenUpdate(GameLoopData *data, LoopRunner *l)
{
LoopRunnerPush(l, DisplayAllTimeHighScores(hData.g, scores));
}
else
{
// TODO: terminate high scores
}
}
return UPDATE_RESULT_OK;
@@ -249,7 +245,7 @@ static int DisplayEntry(
return 1 + FontH();
}
static int DisplayPage(const int idxStart, const CArray *entries)
static void DisplayPage(const CArray *entries)
{
int x = 80;
int y = 5 + FontH();
@@ -269,7 +265,7 @@ static int DisplayPage(const int idxStart, const CArray *entries)
}
CA_FOREACH_END()
int idx = idxStart;
int idx = 0;
CA_FOREACH(const HighScoreEntry, e, *entries)
bool isHighlighted = false;
for (int i = 0; i < MAX_LOCAL_PLAYERS; i++)
@@ -290,15 +286,7 @@ static int DisplayPage(const int idxStart, const CArray *entries)
}
idx++;
CA_FOREACH_END()
return idx;
}
typedef struct
{
GraphicsDevice *g;
CArray scores; // of HighScoreEntry
int highlights[MAX_LOCAL_PLAYERS];
int scoreIdx;
} HighScoresData;
static void HighScoreTerminate(GameLoopData *data);
static GameLoopResult HighScoreUpdate(GameLoopData *data, LoopRunner *l);
static void HighScoreDraw(GameLoopData *data);
@@ -308,7 +296,6 @@ static GameLoopData *DisplayAllTimeHighScores(
HighScoresData *data;
CMALLOC(data, sizeof *data);
data->g = graphics;
data->scoreIdx = 0;
// Take a copy of the scores as the parent will get destroyed
CArrayInit(&data->scores, sizeof(HighScoreEntry));
CArrayCopy(&data->scores, scores);
@@ -319,18 +306,14 @@ static GameLoopData *DisplayAllTimeHighScores(
static void HighScoreTerminate(GameLoopData *data)
{
HighScoresData *hData = data->Data;
CArrayTerminate(&hData->scores);
// TODO: terminate scores entries
HighScoresDataTerminate(hData);
CFREE(hData);
}
static void HighScoreDraw(GameLoopData *data)
{
HighScoresData *hData = data->Data;
BlitClearBuf(hData->g);
hData->scoreIdx = DisplayPage(hData->scoreIdx, &hData->scores);
BlitUpdateFromBuf(hData->g, hData->g->screen);
HighScoresDraw(hData);
}
static GameLoopResult HighScoreUpdate(GameLoopData *data, LoopRunner *l)
{
@@ -506,9 +489,9 @@ static void LoadHighScores(void)
for (int i = 0; i < (int)node->u.object.len; i++)
{
yajl_array entriesNode = YAJL_GET_ARRAY(node->u.object.values[i]);
const char *entriesPath = node->u.object.keys[i];
if (!entriesNode)
{
const char *entriesPath = node->u.object.keys[i];
LOG(LM_MAIN, LL_ERROR, "Unexpected format for high scores %s\n",
entriesPath);
continue;
@@ -599,7 +582,7 @@ static void LoadHighScores(void)
// TODO: weapon usage
CArrayPushBack(entries, &entry);
}
if (hashmap_put(sHighScores, path, (any_t *)entries) != MAP_OK)
if (hashmap_put(sHighScores, entriesPath, (any_t *)entries) != MAP_OK)
{
LOG(LM_MAIN, LL_ERROR, "failed to load high scores (%s)", path);
continue;
@@ -610,4 +593,35 @@ bail:
yajl_tree_free(node);
}
// TODO: terminate high scores
HighScoresData HighScoresDataLoad(const Campaign *co, GraphicsDevice *g)
{
LoadHighScores();
HighScoresData hData;
memset(&hData, 0, sizeof hData);
hData.g = g;
CArray *scores = NULL;
if (hashmap_get(sHighScores, co->Entry.Path, (any_t *)&scores) !=
MAP_MISSING)
{
// copy scores
CArrayInit(&hData.scores, sizeof(HighScoreEntry));
CArrayCopy(&hData.scores, scores);
}
return hData;
}
void HighScoresDataTerminate(HighScoresData *hData)
{
CA_FOREACH(HighScoreEntry, entry, hData->scores)
CFREE(entry->Name);
// TODO: free weapon usages
CA_FOREACH_END()
CArrayTerminate(&hData->scores);
}
void HighScoresDraw(const HighScoresData *hData)
{
BlitClearBuf(&gGraphicsDevice);
DisplayPage(&hData->scores);
BlitUpdateFromBuf(&gGraphicsDevice, gGraphicsDevice.screen);
}

View File

@@ -1,29 +1,29 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013, 2017 Cong Xu
All rights reserved.
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013, 2017, 2025 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:
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.
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.
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
@@ -36,3 +36,13 @@
GameLoopData *HighScoresScreen(Campaign *co, GraphicsDevice *g);
void SaveHighScores(void);
typedef struct
{
GraphicsDevice *g;
CArray scores; // of HighScoreEntry
int highlights[MAX_LOCAL_PLAYERS];
} HighScoresData;
HighScoresData HighScoresDataLoad(const Campaign *co, GraphicsDevice *g);
void HighScoresDataTerminate(HighScoresData *hData);
void HighScoresDraw(const HighScoresData *hData);

View File

@@ -271,7 +271,7 @@ static void MoveIndexToNextEnabledSubmenu(menu_t *menu, const bool isDown)
for (;;)
{
const menu_t *currentSubmenu =
CArrayGet(&menu->u.normal.subMenus, menu->u.normal.index);
CArrayGet(&menu->u.normal.subMenus, menu->u.normal.index);
if (!currentSubmenu->isDisabled)
{
break;
@@ -1043,7 +1043,8 @@ void MenuProcessCmd(MenuSystem *ms, int cmd)
}
if (menu->type == MENU_TYPE_CUSTOM)
{
if (menu->u.customData.inputFunc(cmd, menu->u.customData.data))
if (menu->u.customData.inputFunc == NULL ||
menu->u.customData.inputFunc(cmd, menu->u.customData.data))
{
ms->current = menu->parentMenu;
goto bail;
@@ -1117,8 +1118,7 @@ menu_t *MenuProcessButtonCmd(MenuSystem *ms, menu_t *menu, int cmd)
case MENU_TYPE_NORMAL:
case MENU_TYPE_OPTIONS:
case MENU_TYPE_CUSTOM:
if (subMenu->u.normal.isSubmenusAlt ? Right(cmd)
: (Button1(cmd)))
if (subMenu->u.normal.isSubmenusAlt ? Right(cmd) : (Button1(cmd)))
{
return subMenu;
}

View File

@@ -46,6 +46,7 @@
#include "autosave.h"
#include "game_loop.h"
#include "hiscores.h"
#include "menu.h"
#include "prep.h"
@@ -59,12 +60,13 @@ typedef struct
{
MenuSystem ms;
const CampaignSave *save;
HighScoresData hData;
} LevelSelectionData;
static void LevelSelectionTerminate(GameLoopData *data);
static void LevelSelectionOnEnter(GameLoopData *data);
static GameLoopResult LevelSelectionUpdate(GameLoopData *data, LoopRunner *l);
static void LevelSelectionDraw(GameLoopData *data);
static void MenuCreateStart(MenuSystem *ms, const CampaignSave *save);
static void MenuCreateStart(LevelSelectionData *data);
GameLoopData *LevelSelection(GraphicsDevice *graphics)
{
LevelSelectionData *data;
@@ -73,7 +75,7 @@ GameLoopData *LevelSelection(GraphicsDevice *graphics)
MenuSystemInit(
&data->ms, &gEventHandlers, graphics, svec2i_zero(),
graphics->cachedConfig.Res);
MenuCreateStart(&data->ms, data->save);
MenuCreateStart(data);
return GameLoopDataNew(
data, LevelSelectionTerminate, LevelSelectionOnEnter, NULL, NULL,
LevelSelectionUpdate, LevelSelectionDraw);
@@ -87,27 +89,31 @@ static void MenuCreateLevelSelect(
menu_t *l = MenuCreateReturn(buf, i);
MenuAddSubmenu(levelSelect, l);
}
static void MenuCreateStart(MenuSystem *ms, const CampaignSave *save)
static void HighScoresDrawFunc(
const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
const struct vec2i size, const void *data);
static void MenuCreateStart(LevelSelectionData *data)
{
MenuSystem *ms = &data->ms;
ms->root = ms->current = MenuCreateNormal("", "", MENU_TYPE_NORMAL, 0);
menu_t *menuContinue = MenuCreateReturn("Continue", RETURN_CODE_CONTINUE);
// Note: mission can be -1
menuContinue->isDisabled =
save == NULL || save->NextMission <= 0 ||
save->NextMission == (int)gCampaign.Setting.Missions.size;
data->save == NULL || data->save->NextMission <= 0 ||
data->save->NextMission == (int)gCampaign.Setting.Missions.size;
MenuAddSubmenu(ms->root, menuContinue);
// Create level select menus
menu_t *levelSelect = MenuCreateNormal(
"Level select...", "Select Level", MENU_TYPE_NORMAL, 0);
levelSelect->u.normal.maxItems = 20;
if (save)
if (data->save)
{
CArray levels;
CArrayInitFillZero(
&levels, sizeof(bool), gCampaign.Setting.Missions.size);
CA_FOREACH(const int, missionIndex, save->MissionsCompleted)
CA_FOREACH(const int, missionIndex, data->save->MissionsCompleted)
if (*missionIndex >= (int)gCampaign.Setting.Missions.size)
{
continue;
@@ -115,20 +121,28 @@ static void MenuCreateStart(MenuSystem *ms, const CampaignSave *save)
MenuCreateLevelSelect(levelSelect, &gCampaign, *missionIndex);
CArraySet(&levels, *missionIndex, &gTrue);
CA_FOREACH_END()
if (save->NextMission < (int)gCampaign.Setting.Missions.size &&
!*(bool *)CArrayGet(&levels, save->NextMission))
if (data->save->NextMission < (int)gCampaign.Setting.Missions.size &&
!*(bool *)CArrayGet(&levels, data->save->NextMission))
{
MenuCreateLevelSelect(levelSelect, &gCampaign, save->NextMission);
MenuCreateLevelSelect(
levelSelect, &gCampaign, data->save->NextMission);
}
CArrayTerminate(&levels);
}
levelSelect->isDisabled =
save == NULL || save->MissionsCompleted.size == 0 || gCampaign.Setting.Missions.size == 1;
levelSelect->isDisabled = data->save == NULL ||
data->save->MissionsCompleted.size == 0 ||
gCampaign.Setting.Missions.size == 1;
MenuAddSubmenu(ms->root, levelSelect);
MenuAddSubmenu(
ms->root, MenuCreateReturn("Start campaign", RETURN_CODE_START));
data->hData = HighScoresDataLoad(&gCampaign, &gGraphicsDevice);
menu_t *highScoresMenu = MenuCreateCustom(
"High Scores", HighScoresDrawFunc, NULL, &data->hData);
highScoresMenu->isDisabled = data->hData.scores.size == 0;
MenuAddSubmenu(ms->root, highScoresMenu);
MenuAddExitType(ms, MENU_TYPE_RETURN);
}
static void LevelSelectionTerminate(GameLoopData *data)
@@ -136,6 +150,7 @@ static void LevelSelectionTerminate(GameLoopData *data)
LevelSelectionData *pData = data->Data;
MenuSystemTerminate(&pData->ms);
HighScoresDataTerminate(&pData->hData);
CFREE(data->Data);
}
static void LevelSelectionOnEnter(GameLoopData *data)
@@ -195,3 +210,15 @@ static void LevelSelectionDraw(GameLoopData *data)
MenuDraw(&pData->ms);
}
static void HighScoresDrawFunc(
const menu_t *menu, GraphicsDevice *g, const struct vec2i pos,
const struct vec2i size, const void *data)
{
UNUSED(menu);
UNUSED(g);
UNUSED(pos);
UNUSED(size);
const HighScoresData *hData = data;
HighScoresDraw(hData);
}