Skip to content

Commit

Permalink
Load noah scrolls #712
Browse files Browse the repository at this point in the history
  • Loading branch information
cxong committed Oct 12, 2024
1 parent b48ab7c commit 615a555
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 31 deletions.
6 changes: 3 additions & 3 deletions data/.wolf3d/N3Ddata.cdogscpn/pickups.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@
"Effects": [{
"Type": "Menu",
"Menu": {
"Text": "What is 1+1?",
"Text": "This pickup is a template only; the first Item is the correct effects, the second the wrong effects.",
"Items": [{
"Text": "2",
"Text": "correct",
"Effects": [{
"Type": "Score",
"Score": 1000
Expand All @@ -119,7 +119,7 @@
"Sound": "bonus"
}]
}, {
"Text": "11",
"Text": "wrong",
"Effects": [{
"Type": "Health",
"Health": 4
Expand Down
35 changes: 20 additions & 15 deletions src/cdogs/actors.c
Original file line number Diff line number Diff line change
Expand Up @@ -1064,22 +1064,27 @@ int CommandActor(TActor *actor, int cmd, int ticks)
if (Button1(cmd) && !Button1(actor->lastCmd))
{
// Apply effects of pickup menu item and reset
const PickupMenuItem *m = CArrayGet(
&actor->pickupMenu.effect->u.Menu.Items,
actor->pickupMenu.index);
bool canPickup = false;
const char *sound = "menu_enter";
CA_FOREACH(const PickupEffect, pe, m->Effects)
CASSERT(pe->Type != PICKUP_MENU, "can't have nested menu effects");
canPickup = PickupApplyEffect(
actor, actor->pickupMenu.pickup, pe, true, &sound);
CA_FOREACH_END()
if (canPickup && sound != NULL)
if (actor->pickupMenu.index <
actor->pickupMenu.effect->u.Menu.Items.size)
{
e = GameEventNew(GAME_EVENT_SOUND_AT);
strcpy(e.u.SoundAt.Sound, sound);
e.u.SoundAt.Pos = Vec2ToNet(actor->thing.Pos);
GameEventsEnqueue(&gGameEvents, e);
const PickupMenuItem *m = CArrayGet(
&actor->pickupMenu.effect->u.Menu.Items,
actor->pickupMenu.index);
bool canPickup = false;
const char *sound = "menu_enter";
CA_FOREACH(const PickupEffect, pe, m->Effects)
CASSERT(
pe->Type != PICKUP_MENU, "can't have nested menu effects");
canPickup = PickupApplyEffect(
actor, actor->pickupMenu.pickup, pe, true, &sound);
CA_FOREACH_END()
if (canPickup && sound != NULL)
{
e = GameEventNew(GAME_EVENT_SOUND_AT);
strcpy(e.u.SoundAt.Sound, sound);
e.u.SoundAt.Pos = Vec2ToNet(actor->thing.Pos);
GameEventsEnqueue(&gGameEvents, e);
}
}
actor->pickupMenu.pickup = NULL;
actor->pickupMenu.effect = NULL;
Expand Down
11 changes: 10 additions & 1 deletion src/cdogs/cwolfmap/common.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once
#include "wad/wad.h"
#include <stddef.h>
#include <stdint.h>
#include "wad/wad.h"

typedef enum
{
Expand Down Expand Up @@ -30,3 +30,12 @@ typedef struct
char *data;
wad_t *wad;
} CWAudio;

typedef struct
{
char *question;
char **answers;
int nAnswers;
// Assumes one correct answer
int correctIdx;
} CWN3DQuiz;
38 changes: 38 additions & 0 deletions src/cdogs/cwolfmap/cwolfmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,39 @@ int CWLoad(CWolfMap *map, const char *path, const int spearMission)
map->levels[i].description =
CWLevelN3DLoadDescription(languageBuf, i);
}

for (int q = 1;; q++)
{
char *question = CWLevelN3DLoadQuizQuestion(languageBuf, q);
if (question == NULL)
{
break;
}
map->nQuizzes++;
map->quizzes =
realloc(map->quizzes, map->nQuizzes * sizeof(CWN3DQuiz));
CWN3DQuiz *quiz = &map->quizzes[map->nQuizzes - 1];
memset(quiz, 0, sizeof *quiz);
quiz->question = question;
for (char a = 'A';; a++)
{
bool correct = false;
char *answer =
CWLevelN3DLoadQuizAnswer(languageBuf, q, a, &correct);
if (answer == NULL)
{
break;
}
quiz->nAnswers++;
quiz->answers =
realloc(quiz->answers, quiz->nAnswers * sizeof(char *));
quiz->answers[quiz->nAnswers - 1] = answer;
if (correct)
{
quiz->correctIdx = quiz->nAnswers - 1;
}
}
}
free(languageBuf);
}

Expand Down Expand Up @@ -370,6 +403,7 @@ void CWCopy(CWolfMap *dst, const CWolfMap *src)
dst->vswap.sounds = malloc(vswapSoundsLen);
memcpy(dst->vswap.sounds, src->vswap.sounds, vswapSoundsLen);
// TODO: copy wad
// TODO: copy quizzes
}

void CWFree(CWolfMap *map)
Expand All @@ -381,6 +415,10 @@ void CWFree(CWolfMap *map)
LevelsFree(map);
CWAudioFree(&map->audio);
CWVSwapFree(&map->vswap);
for (int i = 0; i < map->nQuizzes; i++)
{
CWN3DQuizFree(&map->quizzes[i]);
}
memset(map, 0, sizeof *map);
}
static void LevelFree(CWLevel *level);
Expand Down
2 changes: 2 additions & 0 deletions src/cdogs/cwolfmap/cwolfmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ typedef struct
CWAudio audio;
CWVSwap vswap;
CWMapType type;
CWN3DQuiz *quizzes;
int nQuizzes;
} CWolfMap;

CWMapType CWGetType(
Expand Down
49 changes: 41 additions & 8 deletions src/cdogs/cwolfmap/n3d.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ char *CWN3DLoadLanguageEnu(const char *path)
return buf;
}

static char *LoadLanguageEnuString(const char *buf, const char *key)
static char *LoadLanguageEnuString(
const char *buf, const char *key, char **comment)
{
const char *start = buf;
char linebuf[1024];
Expand All @@ -51,7 +52,15 @@ static char *LoadLanguageEnuString(const char *buf, const char *key)
if (strcmp(linebuf, key) == 0)
{
char *value = equals + strlen(" = \"");
char *endQuote = linebuf + len - strlen("\";");
char *endQuote = strstr(value, "\";");
if (comment)
{
const char *commentStart = strstr(endQuote, "// ");
if (commentStart)
{
*comment = strdup(commentStart + strlen("// "));
}
}
*endQuote = '\0';
// Unescape newlines in the string
char *dst = value;
Expand Down Expand Up @@ -87,17 +96,41 @@ char *CWLevelN3DLoadDescription(const char *buf, const int level)
switch (level)
{
case 0:
return LoadLanguageEnuString(buf, "NOAH_BRIEF_01");
return LoadLanguageEnuString(buf, "NOAH_BRIEF_01", NULL);
case 3:
return LoadLanguageEnuString(buf, "NOAH_BRIEF_02");
return LoadLanguageEnuString(buf, "NOAH_BRIEF_02", NULL);
case 7:
return LoadLanguageEnuString(buf, "NOAH_BRIEF_03");
return LoadLanguageEnuString(buf, "NOAH_BRIEF_03", NULL);
case 12:
return LoadLanguageEnuString(buf, "NOAH_BRIEF_04");
return LoadLanguageEnuString(buf, "NOAH_BRIEF_04", NULL);
case 17:
return LoadLanguageEnuString(buf, "NOAH_BRIEF_05");
return LoadLanguageEnuString(buf, "NOAH_BRIEF_05", NULL);
case 23:
return LoadLanguageEnuString(buf, "NOAH_BRIEF_06");
return LoadLanguageEnuString(buf, "NOAH_BRIEF_06", NULL);
}
return NULL;
}

char *CWLevelN3DLoadQuizQuestion(const char *buf, const int quiz)
{
char key[256];
sprintf(key, "NOAH_QUIZ_Q%02d", quiz);
return LoadLanguageEnuString(buf, key, NULL);
}
char *CWLevelN3DLoadQuizAnswer(
const char *buf, const int quiz, const char answer, bool *correct)
{
char key[256];
sprintf(key, "NOAH_QUIZ_A%02d%c", quiz, answer);
char *comment = NULL;
char *result = LoadLanguageEnuString(buf, key, &comment);
*correct = comment && strcmp(comment, "Correct") == 0;
free(comment);
return result;
}

void CWN3DQuizFree(CWN3DQuiz *quiz)
{
free(quiz->question);
free(quiz->answers);
}
12 changes: 12 additions & 0 deletions src/cdogs/cwolfmap/n3d.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
#pragma once

#include <stdbool.h>

#include "common.h"

// Load the noah3d.pak/language.enu file to char buffer, containing briefs and
// quizzes
char *CWN3DLoadLanguageEnu(const char *path);

char *CWLevelN3DLoadDescription(const char *buf, const int level);

// Load a quiz question (quiz starts from #1)
char *CWLevelN3DLoadQuizQuestion(const char *buf, const int quiz);
// Load a quiz answer (quiz starts from #1, answer starts from 'A')
char *CWLevelN3DLoadQuizAnswer(
const char *buf, const int quiz, const char answer, bool *correct);

void CWN3DQuizFree(CWN3DQuiz *quiz);
58 changes: 57 additions & 1 deletion src/cdogs/map_wolf.c
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ int MapWolfScan(
}

static void LoadSounds(const SoundDevice *s, const CWolfMap *map);
static void LoadN3DScrolls(const CWolfMap *map);
static void LoadMission(
CampaignSetting *c, const map_t tileClasses, CWolfMap *map,
const int spearMission, const int missionIndex, const int numMissions);
Expand Down Expand Up @@ -783,6 +784,12 @@ int MapWolfLoad(

CharacterStoreCopy(&c->characters, &cs, &gPlayerTemplates.CustomClasses);

// Special case for N3D: generate scrolls with unique questions/answers
if (map->type == CWMAPTYPE_N3D && map->nQuizzes > 0)
{
LoadN3DScrolls(map);
}

for (int i = 0; i < map->nLevels; i++)
{
LoadMission(c, tileClasses, map, spearMission, i, numMissions);
Expand Down Expand Up @@ -937,6 +944,54 @@ static void AddRandomSound(
}
}

static void LoadN3DScrolls(const CWolfMap *map)
{
// Copy the effects from the "scroll" pickup
const PickupClass *scroll = StrPickupClass("scroll");
const PickupEffect *menuEffect = CArrayGet(&scroll->Effects, 0);
const CArray *correctEffects =
&((const PickupMenuItem *)CArrayGet(&menuEffect->u.Menu.Items, 0))
->Effects;
const CArray *wrongEffects =
&((const PickupMenuItem *)CArrayGet(&menuEffect->u.Menu.Items, 1))
->Effects;
for (int i = 0; i < map->nQuizzes; i++)
{
PickupClass c;
PickupClassInit(&c);
char buf[256];
sprintf(buf, "scroll%d", i);
CSTRDUP(c.Name, buf);
CPicCopyPic(&c.Pic, &scroll->Pic);
CSTRDUP(c.Sound, scroll->Sound);

PickupEffect e;
memset(&e, 0, sizeof e);
e.Type = PICKUP_MENU;
const CWN3DQuiz *quiz = &map->quizzes[i];
CSTRDUP(e.u.Menu.Text, quiz->question);

CArrayInit(&e.u.Menu.Items, sizeof(PickupMenuItem));
for (int j = 0; j < quiz->nAnswers; j++)
{
PickupMenuItem m;
PickupMenuItemInit(&m);
CSTRDUP(m.Text, quiz->answers[j]);
const CArray *effects =
j == quiz->correctIdx ? correctEffects : wrongEffects;
CA_FOREACH(const PickupEffect, pe, *effects)
PickupEffect ec = PickupEffectCopy(pe);
CArrayPushBack(&m.Effects, &ec);
CA_FOREACH_END()
CArrayPushBack(&e.u.Menu.Items, &m);
}

CArrayPushBack(&c.Effects, &e);

CArrayPushBack(&gPickupClasses.CustomClasses, &c);
}
}

static void LoadTile(
MissionStatic *m, const uint16_t ch, const CWolfMap *map,
const struct vec2i v, const int missionIndex);
Expand Down Expand Up @@ -2285,8 +2340,9 @@ static void LoadEntity(
switch (map->type)
{
case CWMAPTYPE_N3D:
// TODO: Place a random scroll pickup, use shuffling
MissionStaticTryAddPickup(
&m->u.Static, StrPickupClass("scroll"), v);
&m->u.Static, StrPickupClass("scroll0"), v);
break;
default:
MissionStaticTryAddItem(
Expand Down
28 changes: 25 additions & 3 deletions src/cdogs/pickup_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,34 @@ int StrPickupClassId(const char *s)

#define VERSION 3

static void PickupClassInit(PickupClass *c)
void PickupClassInit(PickupClass *c)
{
memset(c, 0, sizeof *c);
CArrayInit(&c->Effects, sizeof(PickupEffect));
}
void PickupMenuItemInit(PickupMenuItem *m)
{
memset(m, 0, sizeof *m);
CArrayInit(&m->Effects, sizeof(PickupEffect));
}
PickupEffect PickupEffectCopy(const PickupEffect *e)
{
PickupEffect out;
memcpy(&out, e, sizeof out);
switch (e->Type)
{
case PICKUP_SOUND:
CSTRDUP(out.u.Sound, e->u.Sound);
break;
case PICKUP_MENU:
CASSERT(false, "copying pickup menu not implemented");
break;
default:
// Do nothing
break;
}
return out;
}
static void PickupEffectTerminate(PickupEffect *e);
static void PickupMenuItemTerminate(PickupMenuItem *m)
{
Expand Down Expand Up @@ -388,8 +411,7 @@ static PickupEffect LoadPickupEffect(json_t *node, const int version)
for (json_t *item = itemsNode->child; item; item = item->next)
{
PickupMenuItem m;
memset(&m, 0, sizeof m);
CArrayInit(&m.Effects, sizeof(PickupEffect));
PickupMenuItemInit(&m);
LoadStr(&m.Text, item, "Text");
json_t *effectsNode =
json_find_first_label(item, "Effects")->child;
Expand Down
4 changes: 4 additions & 0 deletions src/cdogs/pickup_class.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ PickupClass *KeyPickupClass(const char *style, const int i);
PickupClass *PickupClassGetById(PickupClasses *classes, const int id);
int StrPickupClassId(const char *s);

void PickupMenuItemInit(PickupMenuItem *m);
PickupEffect PickupEffectCopy(const PickupEffect *e);
void PickupClassInit(PickupClass *c);

void PickupClassesInit(
PickupClasses *classes, const char *filename, const AmmoClasses *ammo,
const WeaponClasses *guns);
Expand Down

0 comments on commit 615a555

Please sign in to comment.