mirror of
https://github.com/cxong/cdogs-sdl.git
synced 2025-07-23 07:23:01 +02:00
Disable passwords, add list of missions completed to autosave
This commit is contained in:
185
src/autosave.c
185
src/autosave.c
@@ -2,7 +2,7 @@
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
|
||||
Copyright (c) 2013-2016, 2019 Cong Xu
|
||||
Copyright (c) 2013-2016, 2019, 2021 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -43,29 +43,39 @@
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
#define VERSION 3
|
||||
|
||||
Autosave gAutosave;
|
||||
|
||||
|
||||
void MissionSaveInit(MissionSave *ms)
|
||||
void CampaignSaveInit(CampaignSave *ms)
|
||||
{
|
||||
memset(ms, 0, sizeof *ms);
|
||||
ms->IsValid = 1;
|
||||
ms->IsValid = true;
|
||||
CArrayInit(&ms->MissionsCompleted, sizeof(int));
|
||||
CArrayInit(&ms->Players, sizeof(PlayerSave));
|
||||
}
|
||||
|
||||
bool CampaignSaveIsValid(const CampaignSave *cs)
|
||||
{
|
||||
return cs != NULL && cs->IsValid && strlen(cs->Campaign.Path) > 0;
|
||||
}
|
||||
|
||||
|
||||
void AutosaveInit(Autosave *autosave)
|
||||
{
|
||||
memset(&autosave->LastMission.Campaign, 0, sizeof autosave->LastMission.Campaign);
|
||||
autosave->LastMission.Campaign.Mode = GAME_MODE_NORMAL;
|
||||
strcpy(autosave->LastMission.Password, "");
|
||||
CArrayInit(&autosave->Missions, sizeof(MissionSave));
|
||||
memset(autosave, 0, sizeof *autosave);
|
||||
autosave->LastCampaignIndex = -1;
|
||||
CArrayInit(&autosave->Campaigns, sizeof(CampaignSave));
|
||||
}
|
||||
void AutosaveTerminate(Autosave *autosave)
|
||||
{
|
||||
CA_FOREACH(MissionSave, m, autosave->Missions)
|
||||
CA_FOREACH(CampaignSave, m, autosave->Campaigns)
|
||||
CampaignEntryTerminate(&m->Campaign);
|
||||
CArrayTerminate(&m->MissionsCompleted);
|
||||
CArrayTerminate(&m->Players);
|
||||
CA_FOREACH_END()
|
||||
CArrayTerminate(&autosave->Missions);
|
||||
CArrayTerminate(&autosave->Campaigns);
|
||||
}
|
||||
|
||||
static void LoadCampaignNode(CampaignEntry *c, json_t *node)
|
||||
@@ -88,27 +98,40 @@ static void AddCampaignNode(CampaignEntry *c, json_t *root)
|
||||
json_insert_pair_into_object(root, "Campaign", subConfig);
|
||||
}
|
||||
|
||||
static void LoadMissionNode(MissionSave *m, json_t *node)
|
||||
static void LoadMissionNode(CampaignSave *m, json_t *node, const int version)
|
||||
{
|
||||
MissionSaveInit(m);
|
||||
CampaignSaveInit(m);
|
||||
LoadCampaignNode(&m->Campaign, json_find_first_label(node, "Campaign")->child);
|
||||
strcpy(m->Password, json_find_first_label(node, "Password")->child->text);
|
||||
LoadInt(&m->MissionsCompleted, node, "MissionsCompleted");
|
||||
LoadInt(&m->NextMission, node, "NextMission");
|
||||
if (version < 3)
|
||||
{
|
||||
int missionsCompleted = 0;
|
||||
LoadInt(&missionsCompleted, node, "MissionsCompleted");
|
||||
for (int i = 0; i < missionsCompleted; i++)
|
||||
{
|
||||
CArrayPushBack(&m->MissionsCompleted, &i);
|
||||
}
|
||||
m->NextMission = missionsCompleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadIntArray(&m->MissionsCompleted, node, "MissionsCompleted");
|
||||
}
|
||||
// Check that file exists
|
||||
char buf[CDOGS_PATH_MAX];
|
||||
GetDataFilePath(buf, m->Campaign.Path);
|
||||
m->IsValid = access(buf, F_OK | R_OK) != -1;
|
||||
}
|
||||
static json_t *CreateMissionNode(MissionSave *m)
|
||||
static json_t *CreateMissionNode(CampaignSave *m)
|
||||
{
|
||||
json_t *subConfig = json_new_object();
|
||||
AddCampaignNode(&m->Campaign, subConfig);
|
||||
json_insert_pair_into_object(subConfig, "Password", json_new_string(m->Password));
|
||||
AddIntPair(subConfig, "MissionsCompleted", m->MissionsCompleted);
|
||||
AddIntPair(subConfig, "NextMission", m->NextMission);
|
||||
AddIntArray(subConfig, "MissionsCompleted", &m->MissionsCompleted);
|
||||
return subConfig;
|
||||
}
|
||||
|
||||
static void LoadMissionNodes(Autosave *a, json_t *root, const char *nodeName)
|
||||
static void LoadMissionNodes(Autosave *a, json_t *root, const char *nodeName, const int version)
|
||||
{
|
||||
json_t *child;
|
||||
if (json_find_first_label(root, nodeName) == NULL)
|
||||
@@ -118,21 +141,23 @@ static void LoadMissionNodes(Autosave *a, json_t *root, const char *nodeName)
|
||||
child = json_find_first_label(root, nodeName)->child->child;
|
||||
while (child != NULL)
|
||||
{
|
||||
MissionSave m;
|
||||
LoadMissionNode(&m, child);
|
||||
AutosaveAddMission(a, &m);
|
||||
CampaignSave m;
|
||||
LoadMissionNode(&m, child, version);
|
||||
AutosaveAddCampaign(a, &m);
|
||||
child = child->next;
|
||||
}
|
||||
}
|
||||
static void AddMissionNodes(Autosave *a, json_t *root, const char *nodeName)
|
||||
{
|
||||
json_t *missions = json_new_array();
|
||||
CA_FOREACH(MissionSave, m, a->Missions)
|
||||
CA_FOREACH(CampaignSave, m, a->Campaigns)
|
||||
json_insert_child(missions, CreateMissionNode(m));
|
||||
CA_FOREACH_END()
|
||||
json_insert_pair_into_object(root, nodeName, missions);
|
||||
}
|
||||
|
||||
static CampaignSave *FindCampaign(Autosave *autosave, const char *path, int *missionIndex);
|
||||
|
||||
void AutosaveLoad(Autosave *autosave, const char *filename)
|
||||
{
|
||||
FILE *f = fopen(filename, "r");
|
||||
@@ -149,15 +174,27 @@ void AutosaveLoad(Autosave *autosave, const char *filename)
|
||||
printf("Error parsing autosave '%s'\n", filename);
|
||||
goto bail;
|
||||
}
|
||||
// Note: need to load missions before LastMission because the former
|
||||
// will overwrite the latter, since AutosaveAddMission also
|
||||
// writes to LastMission
|
||||
LoadMissionNodes(autosave, root, "Missions");
|
||||
if (json_find_first_label(root, "LastMission"))
|
||||
int version = 2;
|
||||
LoadInt(&version, root, "Version");
|
||||
LoadMissionNodes(autosave, root, "Missions", version);
|
||||
if (version < 3)
|
||||
{
|
||||
if (json_find_first_label(root, "LastMission"))
|
||||
{
|
||||
json_t *lastMission = json_find_first_label(root, "LastMission")->child;
|
||||
json_t *campaign = json_find_first_label(lastMission, "Campaign")->child;
|
||||
char *path = NULL;
|
||||
LoadStr(&path, campaign, "Path");
|
||||
if (path != NULL)
|
||||
{
|
||||
FindCampaign(autosave, path, &autosave->LastCampaignIndex);
|
||||
CFREE(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadMissionNode(
|
||||
&autosave->LastMission,
|
||||
json_find_first_label(root, "LastMission")->child);
|
||||
LoadInt(&autosave->LastCampaignIndex, root, "LastCampaignIndex");
|
||||
}
|
||||
|
||||
bail:
|
||||
@@ -181,9 +218,8 @@ void AutosaveSave(Autosave *autosave, const char *filename)
|
||||
}
|
||||
|
||||
root = json_new_object();
|
||||
json_insert_pair_into_object(root, "Version", json_new_number("2"));
|
||||
json_insert_pair_into_object(
|
||||
root, "LastMission", CreateMissionNode(&autosave->LastMission));
|
||||
AddIntPair(root, "Version", VERSION);
|
||||
AddIntPair(root, "LastCampaignIndex", autosave->LastCampaignIndex);
|
||||
AddMissionNodes(autosave, root, "Missions");
|
||||
|
||||
json_tree_to_string(root, &text);
|
||||
@@ -191,8 +227,8 @@ void AutosaveSave(Autosave *autosave, const char *filename)
|
||||
fputs(formatText, f);
|
||||
|
||||
// clean up
|
||||
free(formatText);
|
||||
free(text);
|
||||
CFREE(formatText);
|
||||
CFREE(text);
|
||||
json_free_value(&root);
|
||||
|
||||
fclose(f);
|
||||
@@ -207,7 +243,7 @@ void AutosaveSave(Autosave *autosave, const char *filename)
|
||||
#endif
|
||||
}
|
||||
|
||||
MissionSave *AutosaveFindMission(Autosave *autosave, const char *path)
|
||||
static CampaignSave *FindCampaign(Autosave *autosave, const char *path, int *missionIndex)
|
||||
{
|
||||
if (path == NULL || strlen(path) == 0)
|
||||
{
|
||||
@@ -218,49 +254,74 @@ MissionSave *AutosaveFindMission(Autosave *autosave, const char *path)
|
||||
// in this form
|
||||
char relPath[CDOGS_PATH_MAX] = "";
|
||||
RelPathFromCWD(relPath, path);
|
||||
CA_FOREACH(MissionSave, m, autosave->Missions)
|
||||
CA_FOREACH(CampaignSave, m, autosave->Campaigns)
|
||||
const char *campaignPath = m->Campaign.Path;
|
||||
if (campaignPath != NULL && strcmp(campaignPath, relPath) == 0)
|
||||
{
|
||||
if (missionIndex != NULL)
|
||||
{
|
||||
*missionIndex = _ca_index;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
CA_FOREACH_END()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void AutosaveAddMission(Autosave *autosave, MissionSave *mission)
|
||||
void AutosaveAddCampaign(Autosave *autosave, CampaignSave *cs)
|
||||
{
|
||||
MissionSave *existingMission = AutosaveFindMission(
|
||||
autosave, mission->Campaign.Path);
|
||||
if (existingMission != NULL)
|
||||
CampaignSave *existing = FindCampaign(
|
||||
autosave, cs->Campaign.Path, &autosave->LastCampaignIndex);
|
||||
if (existing != NULL)
|
||||
{
|
||||
CampaignEntryTerminate(&existingMission->Campaign);
|
||||
CampaignEntryTerminate(&existing->Campaign);
|
||||
}
|
||||
else
|
||||
{
|
||||
CArrayPushBack(&autosave->Missions, mission);
|
||||
existingMission =
|
||||
CArrayGet(&autosave->Missions, autosave->Missions.size - 1);
|
||||
memset(existingMission, 0, sizeof *existingMission);
|
||||
CArrayPushBack(&autosave->Campaigns, cs);
|
||||
autosave->LastCampaignIndex = autosave->Campaigns.size - 1;
|
||||
existing =
|
||||
CArrayGet(&autosave->Campaigns, autosave->LastCampaignIndex);
|
||||
CampaignSaveInit(existing);
|
||||
}
|
||||
const int maxMissionsCompleted =
|
||||
MAX(existingMission->MissionsCompleted, mission->MissionsCompleted);
|
||||
memcpy(existingMission, mission, sizeof *existingMission);
|
||||
existingMission->MissionsCompleted = maxMissionsCompleted;
|
||||
CampaignEntryCopy(&existingMission->Campaign, &mission->Campaign);
|
||||
CampaignEntryTerminate(&autosave->LastMission.Campaign);
|
||||
memcpy(&autosave->LastMission, mission, sizeof autosave->LastMission);
|
||||
CampaignEntryCopy(&autosave->LastMission.Campaign, &mission->Campaign);
|
||||
|
||||
existing->NextMission = cs->NextMission;
|
||||
// Update missions completed
|
||||
CA_FOREACH(const int, missionIndex, cs->MissionsCompleted)
|
||||
bool found = false;
|
||||
for (int i = 0; i < (int)existing->MissionsCompleted.size; i++)
|
||||
{
|
||||
if (*(int *)CArrayGet(&existing->MissionsCompleted, i) == *missionIndex)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
CArrayPushBack(&existing->MissionsCompleted, missionIndex);
|
||||
}
|
||||
CA_FOREACH_END()
|
||||
// Sort and remove duplicates
|
||||
qsort(
|
||||
existing->MissionsCompleted.data, existing->MissionsCompleted.size, existing->MissionsCompleted.elemSize,
|
||||
CompareIntsAsc);
|
||||
CArrayUnique(&existing->MissionsCompleted, IntsEqual);
|
||||
|
||||
CampaignEntryCopy(&existing->Campaign, &cs->Campaign);
|
||||
}
|
||||
|
||||
void AutosaveLoadMission(
|
||||
Autosave *autosave, MissionSave *mission, const char *path)
|
||||
const CampaignSave *AutosaveGetCampaign(
|
||||
Autosave *autosave, const char *path)
|
||||
{
|
||||
MissionSave *existingMission = AutosaveFindMission(autosave, path);
|
||||
MissionSaveInit(mission);
|
||||
if (existingMission != NULL)
|
||||
{
|
||||
memcpy(mission, existingMission, sizeof *mission);
|
||||
CampaignEntryCopy(&mission->Campaign, &existingMission->Campaign);
|
||||
}
|
||||
return FindCampaign(autosave, path, NULL);
|
||||
}
|
||||
|
||||
const CampaignSave *AutosaveGetLastCampaign(const Autosave *a)
|
||||
{
|
||||
if (a->LastCampaignIndex < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return CArrayGet(&a->Campaigns, a->LastCampaignIndex);
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
|
||||
Copyright (c) 2013-2015, Cong Xu
|
||||
Copyright (c) 2013-2015, 2021 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -31,25 +31,32 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cdogs/campaigns.h>
|
||||
#include <cdogs/player.h>
|
||||
#include <cdogs/sys_config.h>
|
||||
|
||||
#define PASSWORD_MAX 16
|
||||
#define AUTOSAVE_FILE "autosave.json"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *Guns[MAX_WEAPONS];
|
||||
CArray ammo; // of int
|
||||
} PlayerSave;
|
||||
typedef struct
|
||||
{
|
||||
CampaignEntry Campaign;
|
||||
char Password[PASSWORD_MAX + 1];
|
||||
int IsValid;
|
||||
int MissionsCompleted;
|
||||
} MissionSave;
|
||||
bool IsValid;
|
||||
int NextMission;
|
||||
CArray MissionsCompleted; // of int
|
||||
CArray Players; // of PlayerSave
|
||||
} CampaignSave;
|
||||
|
||||
void MissionSaveInit(MissionSave *ms);
|
||||
void CampaignSaveInit(CampaignSave *ms);
|
||||
bool CampaignSaveIsValid(const CampaignSave *cs);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MissionSave LastMission;
|
||||
CArray Missions; // of MissionSave
|
||||
int LastCampaignIndex;
|
||||
CArray Campaigns; // of CampaignSave
|
||||
} Autosave;
|
||||
|
||||
extern Autosave gAutosave;
|
||||
@@ -58,6 +65,7 @@ void AutosaveInit(Autosave *autosave);
|
||||
void AutosaveTerminate(Autosave *autosave);
|
||||
void AutosaveLoad(Autosave *autosave, const char *filename);
|
||||
void AutosaveSave(Autosave *autosave, const char *filename);
|
||||
void AutosaveAddMission(Autosave *autosave, MissionSave *mission);
|
||||
void AutosaveLoadMission(
|
||||
Autosave *autosave, MissionSave *mission, const char *path);
|
||||
void AutosaveAddCampaign(Autosave *autosave, CampaignSave *cs);
|
||||
const CampaignSave *AutosaveGetCampaign(
|
||||
Autosave *autosave, const char *path);
|
||||
const CampaignSave *AutosaveGetLastCampaign(const Autosave *a);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2013-2020 Cong Xu
|
||||
Copyright (c) 2013-2021 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -187,17 +187,6 @@ GameLoopData *ScreenMissionBriefing(const struct MissionOptions *m)
|
||||
mData->TitleOpts.Pad.y = y - 25;
|
||||
}
|
||||
|
||||
// Password
|
||||
if (m->index > 0)
|
||||
{
|
||||
sprintf(
|
||||
mData->Password, "Password: %s", gAutosave.LastMission.Password);
|
||||
mData->PasswordOpts = FontOptsNew();
|
||||
mData->PasswordOpts.HAlign = ALIGN_CENTER;
|
||||
mData->PasswordOpts.Area = gGraphicsDevice.cachedConfig.Res;
|
||||
mData->PasswordOpts.Pad.y = y - 15;
|
||||
}
|
||||
|
||||
// Description
|
||||
if (m->missionData->Description)
|
||||
{
|
||||
@@ -448,21 +437,15 @@ static void MissionSummaryOnEnter(GameLoopData *data)
|
||||
|
||||
MusicPlay(&gSoundDevice, MUSIC_BRIEFING, NULL, NULL);
|
||||
|
||||
if (mData->completed && IsPasswordAllowed(mData->c->Entry.Mode))
|
||||
if (mData->completed && CanLevelSelect(mData->c->Entry.Mode))
|
||||
{
|
||||
// Save password
|
||||
MissionSave ms;
|
||||
MissionSaveInit(&ms);
|
||||
CampaignSave ms;
|
||||
CampaignSaveInit(&ms);
|
||||
ms.Campaign = mData->c->Entry;
|
||||
// Don't make password for next level if there is none
|
||||
int passwordIndex = mData->m->index + 1;
|
||||
if (passwordIndex == mData->c->Entry.NumMissions)
|
||||
{
|
||||
passwordIndex--;
|
||||
}
|
||||
strcpy(ms.Password, MakePassword(passwordIndex, 0));
|
||||
ms.MissionsCompleted = mData->m->index + 1;
|
||||
AutosaveAddMission(&gAutosave, &ms);
|
||||
CArrayPushBack(&ms.MissionsCompleted, &mData->m->index);
|
||||
ms.NextMission = mData->m->NextMission;
|
||||
AutosaveAddCampaign(&gAutosave, &ms);
|
||||
AutosaveSave(&gAutosave, GetConfigFilePath(AUTOSAVE_FILE));
|
||||
}
|
||||
|
||||
@@ -706,19 +689,6 @@ static void MissionSummaryMenuDraw(
|
||||
const int w = gGraphicsDevice.cachedConfig.Res.x;
|
||||
const int h = gGraphicsDevice.cachedConfig.Res.y;
|
||||
|
||||
// Display password
|
||||
if (strlen(gAutosave.LastMission.Password) > 0)
|
||||
{
|
||||
char s[64];
|
||||
sprintf(s, "Last password: %s", gAutosave.LastMission.Password);
|
||||
FontOpts opts = FontOptsNew();
|
||||
opts.HAlign = ALIGN_CENTER;
|
||||
opts.VAlign = ALIGN_END;
|
||||
opts.Area = g->cachedConfig.Res;
|
||||
opts.Pad.y = opts.Area.y / 12;
|
||||
FontStrOpt(s, svec2i_zero(), opts);
|
||||
}
|
||||
|
||||
// Display objectives and bonuses
|
||||
struct vec2i pos = svec2i(w / 6, h / 2 + h / 10);
|
||||
int idx = 1;
|
||||
@@ -738,7 +708,7 @@ static void MissionSummaryMenuDraw(
|
||||
s, "Objective %d: %d of %d, %d required", idx, o->done, o->Count,
|
||||
o->Required);
|
||||
FontOpts opts = FontOptsNew();
|
||||
opts.Area = gGraphicsDevice.cachedConfig.Res;
|
||||
opts.Area = g->cachedConfig.Res;
|
||||
opts.Pad = pos;
|
||||
if (!ObjectiveIsRequired(o))
|
||||
{
|
||||
@@ -750,7 +720,7 @@ static void MissionSummaryMenuDraw(
|
||||
// Objective status text
|
||||
opts = FontOptsNew();
|
||||
opts.HAlign = ALIGN_END;
|
||||
opts.Area = gGraphicsDevice.cachedConfig.Res;
|
||||
opts.Area = g->cachedConfig.Res;
|
||||
opts.Pad = pos;
|
||||
if (!ObjectiveIsComplete(o))
|
||||
{
|
||||
|
@@ -46,7 +46,7 @@ void CampaignEntryInit(CampaignEntry *entry, const char *title, GameMode mode)
|
||||
CSTRDUP(entry->Info, title);
|
||||
entry->Mode = mode;
|
||||
}
|
||||
void CampaignEntryCopy(CampaignEntry *dst, CampaignEntry *src)
|
||||
void CampaignEntryCopy(CampaignEntry *dst, const CampaignEntry *src)
|
||||
{
|
||||
memcpy(dst, src, sizeof *dst);
|
||||
if (src->Filename)
|
||||
|
@@ -2,7 +2,7 @@
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
|
||||
Copyright (c) 2013-2015, 2020 Cong Xu
|
||||
Copyright (c) 2013-2015, 2020-2021 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -40,7 +40,7 @@ typedef struct
|
||||
} CampaignEntry;
|
||||
|
||||
void CampaignEntryInit(CampaignEntry *entry, const char *title, GameMode mode);
|
||||
void CampaignEntryCopy(CampaignEntry *dst, CampaignEntry *src);
|
||||
void CampaignEntryCopy(CampaignEntry *dst, const CampaignEntry *src);
|
||||
bool CampaignEntryTryLoad(
|
||||
CampaignEntry *entry, const char *path, GameMode mode);
|
||||
void CampaignEntryTerminate(CampaignEntry *entry);
|
||||
|
@@ -87,7 +87,7 @@ bool IsAutoMapEnabled(const GameMode mode)
|
||||
return mode != GAME_MODE_DOGFIGHT && mode != GAME_MODE_DEATHMATCH;
|
||||
}
|
||||
|
||||
bool IsPasswordAllowed(const GameMode mode)
|
||||
bool CanLevelSelect(const GameMode mode)
|
||||
{
|
||||
return mode == GAME_MODE_NORMAL;
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ bool IsGameOptionsNeeded(const GameMode mode);
|
||||
bool IsScoreNeeded(const GameMode mode);
|
||||
bool HasObjectives(const GameMode mode);
|
||||
bool IsAutoMapEnabled(const GameMode mode);
|
||||
bool IsPasswordAllowed(const GameMode mode);
|
||||
bool CanLevelSelect(const GameMode mode);
|
||||
bool IsMissionBriefingNeeded(const GameMode mode, const char *missionBriefing);
|
||||
bool AreKeysAllowed(const GameMode mode);
|
||||
bool AreHealthPickupsAllowed(const GameMode mode);
|
||||
|
@@ -77,7 +77,7 @@ struct SongDef *gGameSongs = NULL;
|
||||
struct SongDef *gMenuSongs = NULL;
|
||||
|
||||
|
||||
bool CampaignLoad(Campaign *co, CampaignEntry *entry)
|
||||
bool CampaignLoad(Campaign *co, const CampaignEntry *entry)
|
||||
{
|
||||
CASSERT(!co->IsLoaded, "loading campaign without unloading last one");
|
||||
// Note: use the mode already set by the menus
|
||||
|
@@ -22,7 +22,7 @@
|
||||
This file incorporates work covered by the following copyright and
|
||||
permission notice:
|
||||
|
||||
Copyright (c) 2013-2014, Cong Xu
|
||||
Copyright (c) 2013-2014, 2021 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
extern struct MissionOptions gMission;
|
||||
|
||||
bool CampaignLoad(Campaign *co, CampaignEntry *entry);
|
||||
bool CampaignLoad(Campaign *co, const CampaignEntry *entry);
|
||||
void CampaignUnload(Campaign *co);
|
||||
|
||||
void MissionOptionsInit(struct MissionOptions *mo);
|
||||
|
@@ -958,25 +958,6 @@ static void PlaceKey(
|
||||
CA_FOREACH_END()
|
||||
}
|
||||
}
|
||||
|
||||
static int CompareInts(const void *v1, const void *v2)
|
||||
{
|
||||
const int i1 = *(const int *)v1;
|
||||
const int i2 = *(const int *)v2;
|
||||
if (i1 > i2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (i1 < i2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static bool IntsEqual(const void *v1, const void *v2)
|
||||
{
|
||||
return CompareInts(v1, v2) == 0;
|
||||
}
|
||||
static bool AllTilesAroundUnwalkable(
|
||||
const MapBuilder *mb, const struct vec2i v)
|
||||
{
|
||||
@@ -1011,7 +992,7 @@ static void AddPillars(
|
||||
// Sort and remove duplicates
|
||||
qsort(
|
||||
allChildren.data, allChildren.size, allChildren.elemSize,
|
||||
CompareInts);
|
||||
CompareIntsDesc);
|
||||
CArrayUnique(&allChildren, IntsEqual);
|
||||
}
|
||||
CA_FOREACH_END()
|
||||
|
@@ -539,6 +539,40 @@ int Stricmp(const char *a, const char *b)
|
||||
return ca - cb;
|
||||
}
|
||||
|
||||
int CompareIntsAsc(const void *v1, const void *v2)
|
||||
{
|
||||
const int i1 = *(const int *)v1;
|
||||
const int i2 = *(const int *)v2;
|
||||
if (i1 < i2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (i1 > i2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int CompareIntsDesc(const void *v1, const void *v2)
|
||||
{
|
||||
const int i1 = *(const int *)v1;
|
||||
const int i2 = *(const int *)v2;
|
||||
if (i1 > i2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (i1 < i2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IntsEqual(const void *v1, const void *v2)
|
||||
{
|
||||
return *(const int *)v1 == *(const int *)v2;
|
||||
}
|
||||
|
||||
BodyPart StrBodyPart(const char *s)
|
||||
{
|
||||
S2T(BODY_PART_HEAD, "head");
|
||||
|
@@ -188,6 +188,9 @@ char *Div8Str(int i);
|
||||
void CamelToTitle(char *buf, const char *src);
|
||||
bool StrEndsWith(const char *str, const char *suffix);
|
||||
int Stricmp(const char *a, const char *b);
|
||||
int CompareIntsAsc(const void *v1, const void *v2);
|
||||
int CompareIntsDesc(const void *v1, const void *v2);
|
||||
bool IntsEqual(const void *v1, const void *v2);
|
||||
|
||||
// Helper macros for defining type/str conversion funcs
|
||||
#define T2S(_type, _str) \
|
||||
|
@@ -279,8 +279,8 @@ typedef struct
|
||||
// so we can enable it if LAN servers are found
|
||||
int MenuJoinIndex;
|
||||
} CheckLANServerData;
|
||||
static menu_t *MenuCreateContinue(const char *name, CampaignEntry *entry);
|
||||
static menu_t *MenuCreateQuickPlay(const char *name, CampaignEntry *entry);
|
||||
static menu_t *MenuCreateContinue(const char *name, const CampaignEntry *entry);
|
||||
static menu_t *MenuCreateQuickPlay(const char *name, const CampaignEntry *entry);
|
||||
static menu_t *MenuCreateCampaigns(
|
||||
const char *name, const char *title, CampaignList *list,
|
||||
const GameMode mode);
|
||||
@@ -292,8 +292,9 @@ static menu_t *MenuCreateStart(
|
||||
CustomCampaigns *campaigns)
|
||||
{
|
||||
menu_t *menu = MenuCreateNormal(name, "Start:", MENU_TYPE_NORMAL, 0);
|
||||
const CampaignSave *cs = AutosaveGetLastCampaign(&gAutosave);
|
||||
MenuAddSubmenu(
|
||||
menu, MenuCreateContinue("Continue", &gAutosave.LastMission.Campaign));
|
||||
menu, MenuCreateContinue("Continue", &cs->Campaign));
|
||||
const int menuContinueIndex = (int)menu->u.normal.subMenus.size - 1;
|
||||
MenuAddSubmenu(
|
||||
menu, MenuCreateCampaigns(
|
||||
@@ -317,9 +318,7 @@ static menu_t *MenuCreateStart(
|
||||
MenuAddSubmenu(menu, MenuCreateSeparator(""));
|
||||
MenuAddSubmenu(menu, MenuCreateBack("Back"));
|
||||
|
||||
if (strlen(gAutosave.LastMission.Password) == 0 ||
|
||||
!gAutosave.LastMission.IsValid ||
|
||||
strlen(gAutosave.LastMission.Campaign.Path) == 0)
|
||||
if (!CampaignSaveIsValid(cs))
|
||||
{
|
||||
MenuDisableSubmenu(menu, menuContinueIndex);
|
||||
}
|
||||
@@ -334,12 +333,12 @@ static menu_t *MenuCreateStart(
|
||||
typedef struct
|
||||
{
|
||||
GameMode GameMode;
|
||||
CampaignEntry *Entry;
|
||||
const CampaignEntry *Entry;
|
||||
} StartGameModeData;
|
||||
static void StartGameMode(menu_t *menu, void *data);
|
||||
|
||||
static menu_t *CreateStartGameMode(
|
||||
const char *name, GameMode mode, CampaignEntry *entry)
|
||||
const char *name, GameMode mode, const CampaignEntry *entry)
|
||||
{
|
||||
menu_t *menu = MenuCreate(name, MENU_TYPE_RETURN);
|
||||
menu->enterSound = MENU_SOUND_START;
|
||||
@@ -361,11 +360,11 @@ static void StartGameMode(menu_t *menu, void *data)
|
||||
printf("Error: cannot load campaign %s\n", mData->Entry->Info);
|
||||
}
|
||||
}
|
||||
static menu_t *MenuCreateContinue(const char *name, CampaignEntry *entry)
|
||||
static menu_t *MenuCreateContinue(const char *name, const CampaignEntry *entry)
|
||||
{
|
||||
return CreateStartGameMode(name, GAME_MODE_NORMAL, entry);
|
||||
}
|
||||
static menu_t *MenuCreateQuickPlay(const char *name, CampaignEntry *entry)
|
||||
static menu_t *MenuCreateQuickPlay(const char *name, const CampaignEntry *entry)
|
||||
{
|
||||
return CreateStartGameMode(name, GAME_MODE_QUICK_PLAY, entry);
|
||||
}
|
||||
@@ -429,17 +428,19 @@ static menu_t *MenuCreateCampaignItem(
|
||||
// - Green for new campaigns
|
||||
// - White (normal) for in-progress campaigns
|
||||
// - Grey for complete campaigns
|
||||
MissionSave m;
|
||||
AutosaveLoadMission(&gAutosave, &m, entry->Path);
|
||||
if (m.MissionsCompleted == entry->NumMissions)
|
||||
const CampaignSave *m = AutosaveGetCampaign(&gAutosave, entry->Path);
|
||||
if (m != NULL)
|
||||
{
|
||||
// Completed campaign
|
||||
menu->color = colorGray;
|
||||
}
|
||||
else if (m.MissionsCompleted > 0)
|
||||
{
|
||||
// Campaign in progress
|
||||
menu->color = colorYellow;
|
||||
if ((int)m->MissionsCompleted.size == entry->NumMissions)
|
||||
{
|
||||
// Completed campaign
|
||||
menu->color = colorGray;
|
||||
}
|
||||
else if (m->MissionsCompleted.size > 0)
|
||||
{
|
||||
// Campaign in progress
|
||||
menu->color = colorYellow;
|
||||
}
|
||||
}
|
||||
|
||||
return menu;
|
||||
|
367
src/password.c
367
src/password.c
@@ -1,28 +1,7 @@
|
||||
/*
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
Copyright (C) 1995 Ronny Wester
|
||||
Copyright (C) 2003 Jeremy Chin
|
||||
Copyright (C) 2003-2007 Lucas Martin-King
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
This file incorporates work covered by the following copyright and
|
||||
permission notice:
|
||||
|
||||
Copyright (c) 2013-2014, 2017 Cong Xu
|
||||
Copyright (c) 2013-2014, 2017, 2021 Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -73,300 +52,51 @@
|
||||
#define DONE "Done"
|
||||
|
||||
|
||||
const char *MakePassword(int mission, int isTwoPlayers)
|
||||
{
|
||||
static char s[PASSWORD_MAX + 1];
|
||||
int sum1, sum2, count;
|
||||
size_t i, x;
|
||||
static char *alphabet1 = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
static char *alphabet2 = "9876543210kjihgfedcbazyxwvutsrqponml";
|
||||
char *alphabet = isTwoPlayers ? alphabet2 : alphabet1;
|
||||
size_t base = strlen(alphabet);
|
||||
|
||||
sum1 = sum2 = 0;
|
||||
for (i = 0; i < strlen(gCampaign.Setting.Title); i++)
|
||||
{
|
||||
sum1 += gCampaign.Setting.Title[i];
|
||||
sum2 ^= gCampaign.Setting.Title[i];
|
||||
}
|
||||
|
||||
const int seed = ConfigGetInt(&gConfig, "Game.RandomSeed");
|
||||
x = ((sum2 << 23) | (mission << 16) | sum1) ^ (size_t)seed;
|
||||
count = 0;
|
||||
while (x > 0 && count < PASSWORD_MAX) {
|
||||
i = x % base;
|
||||
s[count++] = alphabet[i];
|
||||
x /= base;
|
||||
}
|
||||
s[count] = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
static int TestPassword(const char *password)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (int)gCampaign.Setting.Missions.size; i++)
|
||||
{
|
||||
// For legacy passwords, try both one and two players
|
||||
if (strcmp(password, MakePassword(i, 0)) == 0 ||
|
||||
strcmp(password, MakePassword(i, 1)) == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Give user an alpha-num entry screen and returns the decoded mission number.
|
||||
// If the password is invalid, the screen won't exit.
|
||||
// If the user cancels, 0 is returned.
|
||||
typedef struct
|
||||
{
|
||||
char Buffer[PASSWORD_MAX + 1];
|
||||
int Mission;
|
||||
int Selection;
|
||||
} EnterCodeScreenData;
|
||||
static void EnterCodeTerminate(GameLoopData *data);
|
||||
static void EnterCodeScreenOnExit(GameLoopData *data);
|
||||
static GameLoopResult EnterCodeScreenUpdate(GameLoopData *data, LoopRunner *l);
|
||||
static void EnterCodeScreenDraw(GameLoopData *data);
|
||||
static GameLoopData *EnterCodeScreen(const char *password)
|
||||
{
|
||||
EnterCodeScreenData *data;
|
||||
CCALLOC(data, sizeof *data);
|
||||
data->Selection = -1;
|
||||
strcpy(data->Buffer, password);
|
||||
|
||||
return GameLoopDataNew(
|
||||
data, EnterCodeTerminate, NULL, EnterCodeScreenOnExit,
|
||||
NULL, EnterCodeScreenUpdate, EnterCodeScreenDraw);
|
||||
}
|
||||
static void EnterCodeTerminate(GameLoopData *data)
|
||||
{
|
||||
EnterCodeScreenData *eData = data->Data;
|
||||
|
||||
CFREE(eData);
|
||||
}
|
||||
static void EnterCodeScreenOnExit(GameLoopData *data)
|
||||
{
|
||||
const EnterCodeScreenData *eData = data->Data;
|
||||
if (eData->Mission > 0)
|
||||
{
|
||||
gCampaign.MissionIndex = eData->Mission;
|
||||
MenuPlaySound(MENU_SOUND_ENTER);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuPlaySound(MENU_SOUND_SWITCH);
|
||||
}
|
||||
}
|
||||
#define PASSWORD_ENTRY_COLS 10
|
||||
#define PASSWORD_LETTERS "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
static bool PasswordEntry(EnterCodeScreenData *data, const int cmd);
|
||||
static GameLoopResult EnterCodeScreenUpdate(GameLoopData *data, LoopRunner *l)
|
||||
{
|
||||
EnterCodeScreenData *eData = data->Data;
|
||||
|
||||
// Check if anyone pressed escape
|
||||
int cmds[MAX_LOCAL_PLAYERS];
|
||||
memset(cmds, 0, sizeof cmds);
|
||||
GetPlayerCmds(&gEventHandlers, &cmds);
|
||||
if (EventIsEscape(&gEventHandlers, cmds, GetMenuCmd(&gEventHandlers)))
|
||||
{
|
||||
eData->Mission = 0;
|
||||
LoopRunnerPop(l);
|
||||
return UPDATE_RESULT_OK;
|
||||
}
|
||||
|
||||
const int cmd = GetMenuCmd(&gEventHandlers);
|
||||
// Returning 1 means we are still entering password
|
||||
if (PasswordEntry(eData, cmd))
|
||||
{
|
||||
return UPDATE_RESULT_DRAW;
|
||||
}
|
||||
|
||||
if (strlen(eData->Buffer) == 0)
|
||||
{
|
||||
// Nothing in the password buffer; exit
|
||||
eData->Mission = 0;
|
||||
LoopRunnerPop(l);
|
||||
return UPDATE_RESULT_OK;
|
||||
}
|
||||
|
||||
eData->Mission = TestPassword(eData->Buffer);
|
||||
if (eData->Mission == 0)
|
||||
{
|
||||
// Password doesn't work; play a bad sound
|
||||
MenuPlaySound(MENU_SOUND_ERROR);
|
||||
return UPDATE_RESULT_OK;
|
||||
}
|
||||
|
||||
// Password works; exit
|
||||
LoopRunnerPop(l);
|
||||
return UPDATE_RESULT_OK;
|
||||
}
|
||||
static bool PasswordEntry(EnterCodeScreenData *data, const int cmd)
|
||||
{
|
||||
if (data->Selection == -1)
|
||||
{
|
||||
data->Selection = (int)strlen(PASSWORD_LETTERS);
|
||||
}
|
||||
|
||||
if (cmd & CMD_BUTTON1)
|
||||
{
|
||||
if (data->Selection == (int)strlen(PASSWORD_LETTERS))
|
||||
{
|
||||
// Done
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen(data->Buffer) < PASSWORD_MAX)
|
||||
{
|
||||
// enter a letter
|
||||
data->Buffer[strlen(data->Buffer)] =
|
||||
PASSWORD_LETTERS[data->Selection];
|
||||
MenuPlaySound(MENU_SOUND_ENTER);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Too many letters entered
|
||||
MenuPlaySound(MENU_SOUND_ERROR);
|
||||
}
|
||||
}
|
||||
else if (cmd & CMD_BUTTON2)
|
||||
{
|
||||
if (strlen(data->Buffer) > 0)
|
||||
{
|
||||
// Delete a letter
|
||||
data->Buffer[strlen(data->Buffer) - 1] = 0;
|
||||
MenuPlaySound(MENU_SOUND_SWITCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No letters to delete
|
||||
MenuPlaySound(MENU_SOUND_ERROR);
|
||||
}
|
||||
}
|
||||
else if (cmd & CMD_LEFT)
|
||||
{
|
||||
if (data->Selection > 0)
|
||||
{
|
||||
data->Selection--;
|
||||
MenuPlaySound(MENU_SOUND_SWITCH);
|
||||
}
|
||||
}
|
||||
else if (cmd & CMD_RIGHT)
|
||||
{
|
||||
if (data->Selection < (int)strlen(PASSWORD_LETTERS))
|
||||
{
|
||||
data->Selection++;
|
||||
MenuPlaySound(MENU_SOUND_SWITCH);
|
||||
}
|
||||
}
|
||||
else if (cmd & CMD_UP)
|
||||
{
|
||||
if (data->Selection >= PASSWORD_ENTRY_COLS)
|
||||
{
|
||||
data->Selection -= PASSWORD_ENTRY_COLS;
|
||||
MenuPlaySound(MENU_SOUND_SWITCH);
|
||||
}
|
||||
}
|
||||
else if (cmd & CMD_DOWN)
|
||||
{
|
||||
if (data->Selection <=
|
||||
(int)strlen(PASSWORD_LETTERS) - PASSWORD_ENTRY_COLS)
|
||||
{
|
||||
data->Selection += PASSWORD_ENTRY_COLS;
|
||||
MenuPlaySound(MENU_SOUND_SWITCH);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static void EnterCodeScreenDraw(GameLoopData *data)
|
||||
{
|
||||
const EnterCodeScreenData *eData = data->Data;
|
||||
|
||||
BlitClearBuf(&gGraphicsDevice);
|
||||
|
||||
// Password display
|
||||
struct vec2i pos = svec2i(
|
||||
CenterX(FontStrW(eData->Buffer) + FontW('>') + FontW('<')),
|
||||
gGraphicsDevice.cachedConfig.Res.y / 4);
|
||||
pos = FontCh('>', pos);
|
||||
pos = FontStr(eData->Buffer, pos);
|
||||
FontCh('<', pos);
|
||||
|
||||
FontOpts opts = FontOptsNew();
|
||||
opts.HAlign = ALIGN_CENTER;
|
||||
opts.Area = gGraphicsDevice.cachedConfig.Res;
|
||||
opts.Pad.y = gGraphicsDevice.cachedConfig.Res.y / 12;
|
||||
FontStrOpt("Enter code", svec2i_zero(), opts);
|
||||
|
||||
// Draw password entry letters
|
||||
#define ENTRY_SPACING 12
|
||||
|
||||
const int x =
|
||||
CenterX(ENTRY_SPACING * (PASSWORD_ENTRY_COLS - 1) + FontW('a'));
|
||||
const int y = (int)CenterY(
|
||||
FontH() * ((strlen(PASSWORD_LETTERS) - 1) / PASSWORD_ENTRY_COLS));
|
||||
for (int i = 0; i < (int)strlen(PASSWORD_LETTERS) + 1; i++)
|
||||
{
|
||||
pos = svec2i(
|
||||
x + (i % PASSWORD_ENTRY_COLS) * ENTRY_SPACING,
|
||||
y + (i / PASSWORD_ENTRY_COLS) * FontH());
|
||||
color_t mask = (i == eData->Selection) ? colorRed : colorWhite;
|
||||
if (i < (int)strlen(PASSWORD_LETTERS))
|
||||
{
|
||||
FontChMask(PASSWORD_LETTERS[i], pos, mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
FontStrMask(DONE, pos, mask);
|
||||
}
|
||||
}
|
||||
|
||||
ShowControls();
|
||||
|
||||
BlitUpdateFromBuf(&gGraphicsDevice, gGraphicsDevice.screen);
|
||||
}
|
||||
|
||||
typedef enum
|
||||
{
|
||||
RETURN_CODE_CONTINUE = -1,
|
||||
RETURN_CODE_START = -2,
|
||||
RETURN_CODE_ENTER_CODE = -3
|
||||
} ReturnCode;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MenuSystem ms;
|
||||
MissionSave save;
|
||||
const CampaignSave *save;
|
||||
int mission;
|
||||
} PasswordData;
|
||||
static void PasswordTerminate(GameLoopData *data);
|
||||
static void PasswordOnEnter(GameLoopData *data);
|
||||
static GameLoopResult PasswordUpdate(GameLoopData *data, LoopRunner *l);
|
||||
static void PasswordDraw(GameLoopData *data);
|
||||
} 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 int mission, const MissionSave *save);
|
||||
GameLoopData *EnterPassword(GraphicsDevice *graphics)
|
||||
MenuSystem *ms, const int mission, const CampaignSave *save);
|
||||
GameLoopData *LevelSelection(GraphicsDevice *graphics)
|
||||
{
|
||||
PasswordData *data;
|
||||
CMALLOC(data, sizeof *data);
|
||||
AutosaveLoadMission(&gAutosave, &data->save, gCampaign.Entry.Path);
|
||||
LevelSelectionData *data;
|
||||
CCALLOC(data, sizeof *data);
|
||||
data->save = AutosaveGetCampaign(&gAutosave, gCampaign.Entry.Path);
|
||||
MenuSystemInit(
|
||||
&data->ms, &gEventHandlers, graphics, svec2i_zero(),
|
||||
graphics->cachedConfig.Res);
|
||||
data->mission = TestPassword(data->save.Password);
|
||||
MenuCreateStart(&data->ms, data->mission, &data->save);
|
||||
if (data->save)
|
||||
{
|
||||
data->mission = data->save->NextMission;
|
||||
}
|
||||
MenuCreateStart(&data->ms, data->mission, data->save);
|
||||
return GameLoopDataNew(
|
||||
data, PasswordTerminate, PasswordOnEnter, NULL,
|
||||
NULL, PasswordUpdate, PasswordDraw);
|
||||
data, LevelSelectionTerminate, LevelSelectionOnEnter, NULL,
|
||||
NULL, LevelSelectionUpdate, LevelSelectionDraw);
|
||||
}
|
||||
static void MenuCreateLevelSelect(menu_t *levelSelect, const Campaign *co, const int i)
|
||||
{
|
||||
const Mission *m = CArrayGet(&co->Setting.Missions, i);
|
||||
char buf[CDOGS_FILENAME_MAX];
|
||||
sprintf(buf, "%d: %s", i + 1, m->Title);
|
||||
menu_t *l = MenuCreateReturn(buf, i);
|
||||
MenuAddSubmenu(levelSelect, l);
|
||||
}
|
||||
static void MenuCreateStart(
|
||||
MenuSystem *ms, const int mission, const MissionSave *save)
|
||||
MenuSystem *ms, const int mission, const CampaignSave *save)
|
||||
{
|
||||
ms->root = ms->current = MenuCreateNormal("", "", MENU_TYPE_NORMAL, 0);
|
||||
|
||||
@@ -378,44 +108,42 @@ static void MenuCreateStart(
|
||||
// Create level select menus
|
||||
menu_t *levelSelect = MenuCreateNormal(
|
||||
"Level select...", "Select Level", MENU_TYPE_NORMAL, 0);
|
||||
for (int i = 0;
|
||||
i < MIN(save->MissionsCompleted + 1, (int)gCampaign.Setting.Missions.size);
|
||||
i++)
|
||||
if (save)
|
||||
{
|
||||
const Mission *m = CArrayGet(&gCampaign.Setting.Missions, i);
|
||||
char buf[CDOGS_FILENAME_MAX];
|
||||
sprintf(buf, "%d: %s", i + 1, m->Title);
|
||||
menu_t *l = MenuCreateReturn(buf, i);
|
||||
l->isDisabled = i > save->MissionsCompleted;
|
||||
MenuAddSubmenu(levelSelect, l);
|
||||
CA_FOREACH(const int, missionIndex, save->MissionsCompleted)
|
||||
if (*missionIndex >= (int)gCampaign.Setting.Missions.size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
MenuCreateLevelSelect(levelSelect, &gCampaign, *missionIndex);
|
||||
CA_FOREACH_END()
|
||||
MenuCreateLevelSelect(levelSelect, &gCampaign, save->NextMission);
|
||||
}
|
||||
levelSelect->isDisabled = save->MissionsCompleted == 0;
|
||||
levelSelect->isDisabled = save == NULL || save->MissionsCompleted.size == 0;
|
||||
MenuAddSubmenu(ms->root, levelSelect);
|
||||
|
||||
MenuAddSubmenu(ms->root, MenuCreateReturn("Start campaign", RETURN_CODE_START));
|
||||
|
||||
MenuAddSubmenu(ms->root, MenuCreateReturn("Enter code...", RETURN_CODE_ENTER_CODE));
|
||||
|
||||
MenuAddExitType(ms, MENU_TYPE_RETURN);
|
||||
}
|
||||
static void PasswordTerminate(GameLoopData *data)
|
||||
static void LevelSelectionTerminate(GameLoopData *data)
|
||||
{
|
||||
PasswordData *pData = data->Data;
|
||||
LevelSelectionData *pData = data->Data;
|
||||
|
||||
MenuSystemTerminate(&pData->ms);
|
||||
CFREE(data->Data);
|
||||
}
|
||||
static void PasswordOnEnter(GameLoopData *data)
|
||||
static void LevelSelectionOnEnter(GameLoopData *data)
|
||||
{
|
||||
PasswordData *pData = data->Data;
|
||||
LevelSelectionData *pData = data->Data;
|
||||
|
||||
// TODO: re-detect mission saves on enter
|
||||
MenuReset(&pData->ms);
|
||||
gCampaign.MissionIndex = 0;
|
||||
}
|
||||
static GameLoopResult PasswordUpdate(GameLoopData *data, LoopRunner *l)
|
||||
static GameLoopResult LevelSelectionUpdate(GameLoopData *data, LoopRunner *l)
|
||||
{
|
||||
PasswordData *pData = data->Data;
|
||||
LevelSelectionData *pData = data->Data;
|
||||
|
||||
const GameLoopResult result = MenuUpdate(&pData->ms);
|
||||
if (result == UPDATE_RESULT_OK)
|
||||
@@ -437,9 +165,6 @@ static GameLoopResult PasswordUpdate(GameLoopData *data, LoopRunner *l)
|
||||
case RETURN_CODE_START:
|
||||
LoopRunnerChange(l, GameOptions(gCampaign.Entry.Mode));
|
||||
break;
|
||||
case RETURN_CODE_ENTER_CODE:
|
||||
LoopRunnerPush(l, EnterCodeScreen(pData->save.Password));
|
||||
break;
|
||||
default:
|
||||
// Return code represents the mission to start on
|
||||
CASSERT(returnCode >= 0, "Invalid return code for password menu");
|
||||
@@ -451,9 +176,9 @@ static GameLoopResult PasswordUpdate(GameLoopData *data, LoopRunner *l)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static void PasswordDraw(GameLoopData *data)
|
||||
static void LevelSelectionDraw(GameLoopData *data)
|
||||
{
|
||||
const PasswordData *pData = data->Data;
|
||||
const LevelSelectionData *pData = data->Data;
|
||||
|
||||
MenuDraw(&pData->ms);
|
||||
}
|
||||
|
@@ -53,4 +53,4 @@
|
||||
#include "game_loop.h"
|
||||
|
||||
const char *MakePassword(int mission, int isTwoPlayers);
|
||||
GameLoopData *EnterPassword(GraphicsDevice *graphics);
|
||||
GameLoopData *LevelSelection(GraphicsDevice *graphics);
|
||||
|
@@ -448,9 +448,9 @@ static GameLoopResult PlayerSelectionUpdate(GameLoopData *data, LoopRunner *l)
|
||||
if (isDone && hasAtLeastOneInput)
|
||||
{
|
||||
pData->waitResult = EVENT_WAIT_OK;
|
||||
if (!gCampaign.IsClient && IsPasswordAllowed(gCampaign.Entry.Mode))
|
||||
if (!gCampaign.IsClient && CanLevelSelect(gCampaign.Entry.Mode))
|
||||
{
|
||||
LoopRunnerChange(l, EnterPassword(&gGraphicsDevice));
|
||||
LoopRunnerChange(l, LevelSelection(&gGraphicsDevice));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -61,22 +61,16 @@ PicManager gPicManager;
|
||||
|
||||
FEATURE(AutosaveInit, "Initialise autosave")
|
||||
SCENARIO("Initialise autosave")
|
||||
GIVEN("two autosaves")
|
||||
Autosave autosave1, autosave2;
|
||||
GIVEN("an autosave")
|
||||
Autosave autosave;
|
||||
|
||||
WHEN("I initialise both")
|
||||
AutosaveInit(&autosave1);
|
||||
AutosaveInit(&autosave2);
|
||||
WHEN("I initialise it")
|
||||
AutosaveInit(&autosave);
|
||||
|
||||
THEN("their campaign entries should equal each other")
|
||||
SHOULD_MEM_EQUAL(
|
||||
&autosave1.LastMission.Campaign,
|
||||
&autosave2.LastMission.Campaign,
|
||||
sizeof autosave1.LastMission.Campaign);
|
||||
AND("their passwords should equal each other")
|
||||
SHOULD_STR_EQUAL(
|
||||
autosave1.LastMission.Password,
|
||||
autosave2.LastMission.Password);
|
||||
THEN("the last campaign index should be -1")
|
||||
SHOULD_INT_EQUAL(autosave.LastCampaignIndex, -1);
|
||||
AND("the campaigns should be empty")
|
||||
SHOULD_INT_EQUAL(autosave.Campaigns.size, 0);
|
||||
SCENARIO_END
|
||||
FEATURE_END
|
||||
|
||||
@@ -85,116 +79,107 @@ FEATURE(save_and_load, "Save and load")
|
||||
GIVEN("an autosave with some values")
|
||||
Autosave autosave1;
|
||||
AutosaveInit(&autosave1);
|
||||
MissionSave mission1;
|
||||
memset(&mission1, 0, sizeof mission1);
|
||||
CSTRDUP(mission1.Campaign.Path, "mission.cdogscpn");
|
||||
strcpy(mission1.Password, "password");
|
||||
AutosaveAddMission(&autosave1, &mission1);
|
||||
CampaignSave cs1;
|
||||
CampaignSaveInit(&cs1);
|
||||
CSTRDUP(cs1.Campaign.Path, "campaign.cdogscpn");
|
||||
cs1.NextMission = 1;
|
||||
AutosaveAddCampaign(&autosave1, &cs1);
|
||||
AND("I save it to file")
|
||||
AutosaveSave(&autosave1, "tmp");
|
||||
|
||||
|
||||
WHEN("I initialise and load a second autosave from that file")
|
||||
Autosave autosave2;
|
||||
AutosaveInit(&autosave2);
|
||||
AutosaveLoad(&autosave2, "tmp");
|
||||
|
||||
THEN("their last mission paths should equal")
|
||||
SHOULD_STR_EQUAL(
|
||||
autosave2.LastMission.Campaign.Path,
|
||||
autosave1.LastMission.Campaign.Path);
|
||||
AND("their last mission passwords should equal")
|
||||
SHOULD_STR_EQUAL(
|
||||
autosave2.LastMission.Password,
|
||||
autosave1.LastMission.Password);
|
||||
AND("their mission paths should equal")
|
||||
MissionSave mission2;
|
||||
AutosaveLoadMission(&autosave2, &mission2, mission1.Campaign.Path);
|
||||
SHOULD_STR_EQUAL(
|
||||
mission2.Campaign.Path, mission1.Campaign.Path);
|
||||
AND("their mission passwords should equal")
|
||||
SHOULD_STR_EQUAL(mission2.Password, mission1.Password);
|
||||
CampaignSave *cs2 = CArrayGet(&autosave2.Campaigns, 0);
|
||||
|
||||
THEN("their mission paths should equal")
|
||||
SHOULD_STR_EQUAL(cs2->Campaign.Path, cs1.Campaign.Path);
|
||||
AND("their next missions should equal")
|
||||
SHOULD_STR_EQUAL(cs2->NextMission, cs1.NextMission);
|
||||
SCENARIO_END
|
||||
FEATURE_END
|
||||
|
||||
FEATURE(mission_autosaves, "Mission autosaves")
|
||||
SCENARIO("Load non-existing mission autosave")
|
||||
FEATURE(campaign_autosaves, "Campaign autosaves")
|
||||
SCENARIO("Load non-existing campaign autosave")
|
||||
GIVEN("an empty autosave")
|
||||
Autosave autosave;
|
||||
AutosaveInit(&autosave);
|
||||
|
||||
WHEN("I attempt to load a non-existing mission from it")
|
||||
MissionSave mission;
|
||||
AutosaveLoadMission(&autosave, &mission, "mission.cdogscpn");
|
||||
const CampaignSave *cs = AutosaveGetCampaign(&autosave, "mission.cdogscpn");
|
||||
|
||||
THEN("the mission should be empty")
|
||||
SHOULD_BE_TRUE(mission.Campaign.Path == NULL);
|
||||
AND("the password should be empty")
|
||||
SHOULD_STR_EQUAL(mission.Password, "");
|
||||
THEN("the result should be null")
|
||||
SHOULD_BE_TRUE(cs == NULL);
|
||||
SCENARIO_END
|
||||
|
||||
SCENARIO("Add new mission autosave")
|
||||
GIVEN("an autosave and a mission")
|
||||
SCENARIO("Add new campaign autosave")
|
||||
GIVEN("an autosave and a campaign")
|
||||
Autosave autosave;
|
||||
AutosaveInit(&autosave);
|
||||
MissionSave mission1;
|
||||
memset(&mission1, 0, sizeof mission1);
|
||||
CSTRDUP(mission1.Campaign.Path, "mission.cdogscpn");
|
||||
strcpy(mission1.Password, "password");
|
||||
CampaignSave cs1;
|
||||
CampaignSaveInit(&cs1);
|
||||
CSTRDUP(cs1.Campaign.Path, "campaign.cdogscpn");
|
||||
cs1.NextMission = 1;
|
||||
|
||||
WHEN("I add a new mission autosave to it")
|
||||
AutosaveAddMission(&autosave, &mission1);
|
||||
WHEN("I add a new campaign autosave to it")
|
||||
AutosaveAddCampaign(&autosave, &cs1);
|
||||
|
||||
THEN("I should be able to find the mission in the autosave")
|
||||
MissionSave mission2;
|
||||
AutosaveLoadMission(&autosave, &mission2, mission1.Campaign.Path);
|
||||
SHOULD_STR_EQUAL(mission2.Campaign.Path, mission1.Campaign.Path);
|
||||
SHOULD_STR_EQUAL(mission2.Password, mission1.Password);
|
||||
const CampaignSave *cs2 = AutosaveGetCampaign(&autosave, cs1.Campaign.Path);
|
||||
SHOULD_STR_EQUAL(cs2->Campaign.Path, cs1.Campaign.Path);
|
||||
SHOULD_INT_EQUAL(cs2->NextMission, cs1.NextMission);
|
||||
SCENARIO_END
|
||||
|
||||
SCENARIO("Add existing mission autosave")
|
||||
GIVEN("an autosave and a mission")
|
||||
SCENARIO("Add existing campaign autosave")
|
||||
GIVEN("an autosave and a campaign")
|
||||
Autosave autosave;
|
||||
AutosaveInit(&autosave);
|
||||
MissionSave mission1;
|
||||
memset(&mission1, 0, sizeof mission1);
|
||||
CSTRDUP(mission1.Campaign.Path, "mission.cdogscpn");
|
||||
strcpy(mission1.Password, "password");
|
||||
mission1.MissionsCompleted = 3;
|
||||
AND("I add the mission to the autosave")
|
||||
AutosaveAddMission(&autosave, &mission1);
|
||||
CampaignSave cs1;
|
||||
CampaignSaveInit(&cs1);
|
||||
CSTRDUP(cs1.Campaign.Path, "campaign.cdogscpn");
|
||||
cs1.NextMission = 1;
|
||||
int mission = 0;
|
||||
CArrayPushBack(&cs1.MissionsCompleted, &mission);
|
||||
AND("I add the campaign to the autosave")
|
||||
AutosaveAddCampaign(&autosave, &cs1);
|
||||
|
||||
WHEN("I add the same campaign but with different next mission")
|
||||
cs1.NextMission = 2;
|
||||
AND("and differeiont missions completed")
|
||||
CArrayClear(&cs1.MissionsCompleted);
|
||||
mission = 1;
|
||||
CArrayPushBack(&cs1.MissionsCompleted, &mission);
|
||||
AutosaveAddCampaign(&autosave, &cs1);
|
||||
|
||||
WHEN("I add the same mission but with new password")
|
||||
strcpy(mission1.Password, "new password");
|
||||
AND("and less missions completed")
|
||||
mission1.MissionsCompleted = 2;
|
||||
AutosaveAddMission(&autosave, &mission1);
|
||||
|
||||
THEN("I should be able to find the mission in the autosave")
|
||||
MissionSave mission2;
|
||||
AutosaveLoadMission(&autosave, &mission2, mission1.Campaign.Path);
|
||||
const CampaignSave *cs2 = AutosaveGetCampaign(&autosave, cs1.Campaign.Path);
|
||||
AND("with the new details")
|
||||
SHOULD_STR_EQUAL(mission2.Campaign.Path, mission1.Campaign.Path);
|
||||
SHOULD_STR_EQUAL(mission2.Password, mission1.Password);
|
||||
BUT("the greatest missions completed")
|
||||
SHOULD_INT_EQUAL(mission2.MissionsCompleted, 3);
|
||||
SHOULD_STR_EQUAL(cs2->Campaign.Path, cs1.Campaign.Path);
|
||||
BUT("the greatest next mission")
|
||||
SHOULD_INT_EQUAL(cs2->NextMission, 2);
|
||||
AND("the union of completed missions")
|
||||
SHOULD_INT_EQUAL(*(int *)CArrayGet(&cs2->MissionsCompleted, 0), 0);
|
||||
SHOULD_INT_EQUAL(*(int *)CArrayGet(&cs2->MissionsCompleted, 1), 1);
|
||||
SCENARIO_END
|
||||
|
||||
SCENARIO("Adding autosave updates last mission")
|
||||
GIVEN("an autosave and a mission")
|
||||
SCENARIO("Adding autosave updates last campaign")
|
||||
GIVEN("an autosave and a campaign")
|
||||
Autosave autosave;
|
||||
AutosaveInit(&autosave);
|
||||
MissionSave mission;
|
||||
memset(&mission, 0, sizeof mission);
|
||||
CSTRDUP(mission.Campaign.Path, "mission.cdogscpn");
|
||||
strcpy(mission.Password, "password");
|
||||
CampaignSave cs1;
|
||||
CampaignSaveInit(&cs1);
|
||||
CSTRDUP(cs1.Campaign.Path, "campaign.cdogscpn");
|
||||
cs1.NextMission = 1;
|
||||
|
||||
WHEN("I add a new mission autosave to it")
|
||||
AutosaveAddMission(&autosave, &mission);
|
||||
WHEN("I add a new campaign autosave to it")
|
||||
AutosaveAddCampaign(&autosave, &cs1);
|
||||
|
||||
THEN("the last mission will be the same as the new mission")
|
||||
THEN("the last campaign will be the same")
|
||||
const CampaignSave *cs2 = AutosaveGetLastCampaign(&autosave);
|
||||
SHOULD_STR_EQUAL(
|
||||
autosave.LastMission.Campaign.Path, mission.Campaign.Path);
|
||||
SHOULD_STR_EQUAL(autosave.LastMission.Password, mission.Password);
|
||||
cs2->Campaign.Path, cs1.Campaign.Path);
|
||||
SHOULD_INT_EQUAL(cs2->NextMission, cs1.NextMission);
|
||||
SCENARIO_END
|
||||
FEATURE_END
|
||||
|
||||
@@ -202,5 +187,5 @@ CBEHAVE_RUN(
|
||||
"Autosave features are:",
|
||||
TEST_FEATURE(AutosaveInit),
|
||||
TEST_FEATURE(save_and_load),
|
||||
TEST_FEATURE(mission_autosaves)
|
||||
TEST_FEATURE(campaign_autosaves)
|
||||
)
|
||||
|
Reference in New Issue
Block a user