diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49f783722ab429986fb45b317a2d00c36a4b5f94..4a61d1194b1103423e78d993329e1b2014a542ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 m_queue.c info.c p_ceilng.c + p_dialog.c p_enemy.c p_floor.c p_inter.c diff --git a/src/Sourcefile b/src/Sourcefile index 7beb98c9e313286506c55d359bb19b38b013bfb0..d9de708ce6baf10e10182c5e24c5d8f9da77b30f 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -32,6 +32,7 @@ m_random.c m_queue.c info.c p_ceilng.c +p_dialog.c p_enemy.c p_floor.c p_inter.c diff --git a/src/d_main.c b/src/d_main.c index a07f4a5127545cd69230d90bf285ddf8cedfe6fc..d1f7fdbcf264e07991a2e38e906a515265ab1622 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -48,6 +48,7 @@ #include "m_misc.h" #include "p_setup.h" #include "p_saveg.h" +#include "p_dialog.h" #include "r_main.h" #include "r_local.h" #include "s_sound.h" diff --git a/src/d_player.h b/src/d_player.h index 62383f53a0797d8536aee5c1524069062aaf4749..6fa6ab1ec09b4916e5a78d5ffe568440875845be 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -609,6 +609,9 @@ typedef struct player_s botmem_t botmem; boolean blocked; + boolean promptactive; + struct dialog_s *textprompt; + tic_t jointime; // Timer when player joins game to change skin/color tic_t quittime; // Time elapsed since user disconnected, zero if connected tic_t lastinputtime; // the last tic the player has made any input diff --git a/src/deh_soc.c b/src/deh_soc.c index 41eb28a90f91f77c3672b7b35ea919f9a0edf6ef..dbb57385d178d59c69a9fbc3a732d682fd921da0 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1887,6 +1887,34 @@ void readlevelheader(MYFILE *f, INT32 num) Z_Free(s); } +static boolean ParseCutscenePic(cutscene_pic_t *pic, UINT16 usi, const char *word, char *word2) +{ + if (fastcmp(word, "NAME")) + { + strlcpy(pic->name, word2, sizeof(pic->name)); + } + else if (fastcmp(word, "HIRES")) + { + pic->hires = (UINT8)(usi || word2[0] == 'T' || word2[0] == 'Y'); + } + else if (fastcmp(word, "DURATION")) + { + pic->duration = usi; + } + else if (fastcmp(word, "XCOORD")) + { + pic->xcoord = usi; + } + else if (fastcmp(word, "YCOORD")) + { + pic->ycoord = usi; + } + else + return false; + + return true; +} + static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum) { char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL); @@ -1969,7 +1997,6 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum) i = atoi(word2); usi = (UINT16)i; - if (fastcmp(word, "NUMBEROFPICS")) { cutscenes[num]->scene[scenenum].numpics = (UINT8)i; @@ -1977,40 +2004,21 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum) else if (fastncmp(word, "PIC", 3)) { picid = (UINT8)atoi(word + 3); - if (picid > 8 || picid == 0) + if (picid > MAX_CUTSCENE_PICS || picid == 0) { deh_warning("CutSceneScene %d: unknown word '%s'", num, word); continue; } --picid; - if (fastcmp(word+4, "NAME")) - { - strncpy(cutscenes[num]->scene[scenenum].picname[picid], word2, 8); - } - else if (fastcmp(word+4, "HIRES")) - { - cutscenes[num]->scene[scenenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); - } - else if (fastcmp(word+4, "DURATION")) - { - cutscenes[num]->scene[scenenum].picduration[picid] = usi; - } - else if (fastcmp(word+4, "XCOORD")) - { - cutscenes[num]->scene[scenenum].xcoord[picid] = usi; - } - else if (fastcmp(word+4, "YCOORD")) - { - cutscenes[num]->scene[scenenum].ycoord[picid] = usi; - } - else + cutscene_pic_t *pic = &cutscenes[num]->scene[scenenum].pics[picid]; + + if (!ParseCutscenePic(pic, usi, word+4, word2)) deh_warning("CutSceneScene %d: unknown word '%s'", num, word); } else if (fastcmp(word, "MUSIC")) { - strncpy(cutscenes[num]->scene[scenenum].musswitch, word2, 7); - cutscenes[num]->scene[scenenum].musswitch[6] = 0; + strlcpy(cutscenes[num]->scene[scenenum].musswitch, word2, sizeof(cutscenes[num]->scene[scenenum].musswitch)); } else if (fastcmp(word, "MUSICTRACK")) { @@ -2123,6 +2131,8 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) UINT16 usi; UINT8 picid; + textpage_t *page = &textprompts[num]->page[pagenum]; + do { if (myfgets(s, MAXLINELEN, f)) @@ -2153,8 +2163,8 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) if (!pagetext) { - Z_Free(textprompts[num]->page[pagenum].text); - textprompts[num]->page[pagenum].text = NULL; + Z_Free(page->text); + page->text = NULL; continue; } @@ -2179,11 +2189,9 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) - strlen(buffer) - 1, f)); // A text prompt overwriting another one... - Z_Free(textprompts[num]->page[pagenum].text); - - textprompts[num]->page[pagenum].text = Z_StrDup(buffer); + Z_Free(page->text); - Z_Free(buffer); + page->text = buffer; continue; } @@ -2202,38 +2210,35 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) // copypasta from readcutscenescene if (fastcmp(word, "NUMBEROFPICS")) { - textprompts[num]->page[pagenum].numpics = (UINT8)i; + page->numpics = (UINT8)i; } else if (fastcmp(word, "PICMODE")) { UINT8 picmode = 0; // PROMPT_PIC_PERSIST if (usi == 1 || word2[0] == 'L') picmode = PROMPT_PIC_LOOP; else if (usi == 2 || word2[0] == 'D' || word2[0] == 'H') picmode = PROMPT_PIC_DESTROY; - textprompts[num]->page[pagenum].picmode = picmode; + page->picmode = picmode; } else if (fastcmp(word, "PICTOLOOP")) - textprompts[num]->page[pagenum].pictoloop = (UINT8)i; + page->pictoloop = (UINT8)i; else if (fastcmp(word, "PICTOSTART")) - textprompts[num]->page[pagenum].pictostart = (UINT8)i; + page->pictostart = (UINT8)i; else if (fastcmp(word, "PICSMETAPAGE")) { - if (usi && usi <= textprompts[num]->numpages) + if (usi && usi > 0 && usi <= textprompts[num]->numpages) { UINT8 metapagenum = usi - 1; - textprompts[num]->page[pagenum].numpics = textprompts[num]->page[metapagenum].numpics; - textprompts[num]->page[pagenum].picmode = textprompts[num]->page[metapagenum].picmode; - textprompts[num]->page[pagenum].pictoloop = textprompts[num]->page[metapagenum].pictoloop; - textprompts[num]->page[pagenum].pictostart = textprompts[num]->page[metapagenum].pictostart; + textpage_t *metapage = &textprompts[num]->page[metapagenum]; - for (picid = 0; picid < MAX_PROMPT_PICS; picid++) - { - strncpy(textprompts[num]->page[pagenum].picname[picid], textprompts[num]->page[metapagenum].picname[picid], 8); - textprompts[num]->page[pagenum].pichires[picid] = textprompts[num]->page[metapagenum].pichires[picid]; - textprompts[num]->page[pagenum].picduration[picid] = textprompts[num]->page[metapagenum].picduration[picid]; - textprompts[num]->page[pagenum].xcoord[picid] = textprompts[num]->page[metapagenum].xcoord[picid]; - textprompts[num]->page[pagenum].ycoord[picid] = textprompts[num]->page[metapagenum].ycoord[picid]; - } + page->numpics = metapage->numpics; + page->picmode = metapage->picmode; + page->pictoloop = metapage->pictoloop; + page->pictostart = metapage->pictostart; + + memcpy(&page->pics, + &metapage->pics, + sizeof(cutscene_pic_t) * page->numpics); } } else if (fastncmp(word, "PIC", 3)) @@ -2244,43 +2249,33 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) deh_warning("textpromptscene %d: unknown word '%s'", num, word); continue; } + --picid; - if (fastcmp(word+4, "NAME")) - { - strncpy(textprompts[num]->page[pagenum].picname[picid], word2, 8); - } - else if (fastcmp(word+4, "HIRES")) +#if 0 + if (picid >= page->numpics) { - textprompts[num]->page[pagenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); - } - else if (fastcmp(word+4, "DURATION")) - { - textprompts[num]->page[pagenum].picduration[picid] = usi; - } - else if (fastcmp(word+4, "XCOORD")) - { - textprompts[num]->page[pagenum].xcoord[picid] = usi; - } - else if (fastcmp(word+4, "YCOORD")) - { - textprompts[num]->page[pagenum].ycoord[picid] = usi; + deh_warning("textpromptscene %d: invalid page %d of %d", picid+1, page->numpics); + continue; } - else +#endif + + cutscene_pic_t *pic = &page->pics[picid]; + + if (!ParseCutscenePic(pic, usi, word+4, word2)) deh_warning("textpromptscene %d: unknown word '%s'", num, word); } else if (fastcmp(word, "MUSIC")) { - strncpy(textprompts[num]->page[pagenum].musswitch, word2, 7); - textprompts[num]->page[pagenum].musswitch[6] = 0; + strlcpy(page->musswitch, word2, sizeof(page->musswitch)); } else if (fastcmp(word, "MUSICTRACK")) { - textprompts[num]->page[pagenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK; + page->musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK; } else if (fastcmp(word, "MUSICLOOP")) { - textprompts[num]->page[pagenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); + page->musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y'); } // end copypasta from readcutscenescene else if (fastcmp(word, "NAME")) @@ -2304,19 +2299,19 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) name[j] = ' '; } - strncpy(textprompts[num]->page[pagenum].name, name, 32); + strncpy(page->name, name, 32); } else - *textprompts[num]->page[pagenum].name = '\0'; + *page->name = '\0'; } else if (fastcmp(word, "ICON")) - strncpy(textprompts[num]->page[pagenum].iconname, word2, 8); + strncpy(page->iconname, word2, 8); else if (fastcmp(word, "ICONALIGN")) - textprompts[num]->page[pagenum].rightside = (i || word2[0] == 'R'); + page->rightside = (i || word2[0] == 'R'); else if (fastcmp(word, "ICONFLIP")) - textprompts[num]->page[pagenum].iconflip = (i || word2[0] == 'T' || word2[0] == 'Y'); + page->iconflip = (i || word2[0] == 'T' || word2[0] == 'Y'); else if (fastcmp(word, "LINES")) - textprompts[num]->page[pagenum].lines = usi; + page->lines = usi; else if (fastcmp(word, "BACKCOLOR")) { INT32 backcolor; @@ -2343,65 +2338,67 @@ static void readtextpromptpage(MYFILE *f, INT32 num, INT32 pagenum) else if (i >= 256 && i < 512) backcolor = i; // non-transparent palette index else if (i < 0) backcolor = INT32_MAX; // CONS_BACKCOLOR user-configured else backcolor = 1; // default gray - textprompts[num]->page[pagenum].backcolor = backcolor; + page->backcolor = backcolor; } else if (fastcmp(word, "ALIGN")) { UINT8 align = 0; // left if (usi == 1 || word2[0] == 'R') align = 1; else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2; - textprompts[num]->page[pagenum].align = align; + page->align = align; } else if (fastcmp(word, "VERTICALALIGN")) { UINT8 align = 0; // top if (usi == 1 || word2[0] == 'B') align = 1; else if (usi == 2 || word2[0] == 'C' || word2[0] == 'M') align = 2; - textprompts[num]->page[pagenum].verticalalign = align; + page->verticalalign = align; } else if (fastcmp(word, "TEXTSPEED")) - textprompts[num]->page[pagenum].textspeed = get_number(word2); + page->textspeed = get_number(word2); else if (fastcmp(word, "TEXTSFX")) - textprompts[num]->page[pagenum].textsfx = get_number(word2); + page->textsfx = get_number(word2); else if (fastcmp(word, "HIDEHUD")) { UINT8 hidehud = 0; if ((word2[0] == 'F' && (word2[1] == 'A' || !word2[1])) || word2[0] == 'N') hidehud = 0; // false else if (usi == 1 || word2[0] == 'T' || word2[0] == 'Y') hidehud = 1; // true (hide appropriate HUD elements) else if (usi == 2 || word2[0] == 'A' || (word2[0] == 'F' && word2[1] == 'O')) hidehud = 2; // force (hide all HUD elements) - textprompts[num]->page[pagenum].hidehud = hidehud; + page->hidehud = hidehud; } else if (fastcmp(word, "METAPAGE")) { - if (usi && usi <= textprompts[num]->numpages) + if (usi && usi > 0 && usi <= textprompts[num]->numpages) { UINT8 metapagenum = usi - 1; - strncpy(textprompts[num]->page[pagenum].name, textprompts[num]->page[metapagenum].name, 32); - strncpy(textprompts[num]->page[pagenum].iconname, textprompts[num]->page[metapagenum].iconname, 8); - textprompts[num]->page[pagenum].rightside = textprompts[num]->page[metapagenum].rightside; - textprompts[num]->page[pagenum].iconflip = textprompts[num]->page[metapagenum].iconflip; - textprompts[num]->page[pagenum].lines = textprompts[num]->page[metapagenum].lines; - textprompts[num]->page[pagenum].backcolor = textprompts[num]->page[metapagenum].backcolor; - textprompts[num]->page[pagenum].align = textprompts[num]->page[metapagenum].align; - textprompts[num]->page[pagenum].verticalalign = textprompts[num]->page[metapagenum].verticalalign; - textprompts[num]->page[pagenum].textspeed = textprompts[num]->page[metapagenum].textspeed; - textprompts[num]->page[pagenum].textsfx = textprompts[num]->page[metapagenum].textsfx; - textprompts[num]->page[pagenum].hidehud = textprompts[num]->page[metapagenum].hidehud; + textpage_t *metapage = &textprompts[num]->page[metapagenum]; + + strlcpy(page->name, metapage->name, sizeof(page->name)); + strlcpy(page->iconname, metapage->iconname, sizeof(page->iconname)); + page->rightside = metapage->rightside; + page->iconflip = metapage->iconflip; + page->lines = metapage->lines; + page->backcolor = metapage->backcolor; + page->align = metapage->align; + page->verticalalign = metapage->verticalalign; + page->textspeed = metapage->textspeed; + page->textsfx = metapage->textsfx; + page->hidehud = metapage->hidehud; // music: don't copy, else each page change may reset the music } } else if (fastcmp(word, "TAG")) - strncpy(textprompts[num]->page[pagenum].tag, word2, 33); + strncpy(page->tag, word2, 33); else if (fastcmp(word, "NEXTPROMPT")) - textprompts[num]->page[pagenum].nextprompt = usi; + page->nextprompt = usi; else if (fastcmp(word, "NEXTPAGE")) - textprompts[num]->page[pagenum].nextpage = usi; + page->nextpage = usi; else if (fastcmp(word, "NEXTTAG")) - strncpy(textprompts[num]->page[pagenum].nexttag, word2, 33); + strncpy(page->nexttag, word2, 33); else if (fastcmp(word, "TIMETONEXT")) - textprompts[num]->page[pagenum].timetonext = get_number(word2); + page->timetonext = get_number(word2); else deh_warning("PromptPage %d: unknown word '%s'", num, word); } diff --git a/src/doomstat.h b/src/doomstat.h index 6a2d6acf00f816804c1ec8830b69c9848deadcbe..64dd6056a6ca31eb2512ed9693582fdf0dba8613 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -162,15 +162,22 @@ extern tic_t countdowntimer; extern boolean countdowntimeup; extern boolean exitfadestarted; +typedef struct +{ + char name[64]; + UINT8 hires; + UINT16 xcoord; + UINT16 ycoord; + UINT16 duration; +} cutscene_pic_t; + +#define MAX_CUTSCENE_PICS 8 + typedef struct { UINT8 numpics; - char picname[8][8]; - UINT8 pichires[8]; char *text; - UINT16 xcoord[8]; - UINT16 ycoord[8]; - UINT16 picduration[8]; + cutscene_pic_t pics[MAX_CUTSCENE_PICS]; UINT8 musicloop; UINT16 textxpos; UINT16 textypos; @@ -203,17 +210,14 @@ extern cutscene_t *cutscenes[128]; #define PROMPT_PIC_LOOP 1 #define PROMPT_PIC_DESTROY 2 #define MAX_PROMPT_PICS 8 + typedef struct { UINT8 numpics; UINT8 picmode; // sequence mode after displaying last pic, 0 = persist, 1 = loop, 2 = destroy UINT8 pictoloop; // if picmode == loop, which pic to loop to? UINT8 pictostart; // initial pic number to show - char picname[MAX_PROMPT_PICS][8]; - UINT8 pichires[MAX_PROMPT_PICS]; - UINT16 xcoord[MAX_PROMPT_PICS]; // gfx - UINT16 ycoord[MAX_PROMPT_PICS]; // gfx - UINT16 picduration[MAX_PROMPT_PICS]; + cutscene_pic_t pics[MAX_PROMPT_PICS]; char musswitch[7]; UINT16 musswitchflags; @@ -221,7 +225,7 @@ typedef struct char tag[33]; // page tag char name[34]; // narrator name, extra char for color - char iconname[8]; // narrator icon lump + char iconname[9]; // narrator icon lump boolean rightside; // narrator side, false = left, true = right boolean iconflip; // narrator flip icon horizontally UINT8 hidehud; // hide hud, 0 = show all, 1 = hide depending on prompt position (top/bottom), 2 = hide all diff --git a/src/f_finale.c b/src/f_finale.c index cb64618535659f329bb357f65c2cec0bbe006b2d..ac6b796b110c7f0ba74dfec7105749815789cdea 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -37,6 +37,7 @@ #include "m_cond.h" #include "p_local.h" #include "p_setup.h" +#include "p_dialog.h" #include "st_stuff.h" // hud hiding #include "fastcmp.h" #include "console.h" @@ -55,7 +56,6 @@ static INT32 timetonext; // Delay between screen changes static INT32 continuetime; // Short delay when continuing static tic_t animtimer; // Used for some animation timings -static INT16 skullAnimCounter; // Prompts: Chevron animation static INT32 deplete; static tic_t stoptimer; @@ -202,111 +202,20 @@ static patch_t *endescp[5]; // escape pod + flame static INT32 sparkloffs[3][2]; // eggrock explosions/blackrock sparkles static INT32 sparklloop; -// -// PROMPT STATE -// -boolean promptactive = false; -static mobj_t *promptmo; -static INT16 promptpostexectag; -static boolean promptblockcontrols; -static char *promptpagetext = NULL; -static INT32 callpromptnum = INT32_MAX; -static INT32 callpagenum = INT32_MAX; -static INT32 callplayer = INT32_MAX; - -// -// CUTSCENE TEXT WRITING -// -static const char *cutscene_basetext = NULL; -static char cutscene_disptext[1024]; -static INT32 cutscene_baseptr = 0; -static INT32 cutscene_writeptr = 0; -static INT32 cutscene_textcount = 0; -static INT32 cutscene_textspeed = 0; -static UINT8 cutscene_boostspeed = 0; - // STJR Intro char stjrintro[9] = "STJRI000"; static huddrawlist_h luahuddrawlist_title; -// -// This alters the text string cutscene_disptext. -// Use the typical string drawing functions to display it. -// Returns 0 if \0 is reached (end of input) -// -static UINT8 F_WriteText(void) -{ - INT32 numtowrite = 1; - const char *c; - - if (cutscene_boostspeed) - { - // for custom cutscene speedup mode - numtowrite = 8; - } - else - { - // Don't draw any characters if the count was 1 or more when we started - if (--cutscene_textcount >= 0) - return 1; - - if (cutscene_textspeed < 7) - numtowrite = 8 - cutscene_textspeed; - } - - for (;numtowrite > 0;++cutscene_baseptr) - { - c = &cutscene_basetext[cutscene_baseptr]; - if (!c || !*c || *c=='#') - return 0; - - // \xA0 - \xAF = change text speed - if ((UINT8)*c >= 0xA0 && (UINT8)*c <= 0xAF) - { - cutscene_textspeed = (INT32)((UINT8)*c - 0xA0); - continue; - } - // \xB0 - \xD2 = delay character for up to one second (35 tics) - else if ((UINT8)*c >= 0xB0 && (UINT8)*c <= (0xB0+TICRATE-1)) - { - cutscene_textcount = (INT32)((UINT8)*c - 0xAF); - numtowrite = 0; - continue; - } - - cutscene_disptext[cutscene_writeptr++] = *c; - - // Ignore other control codes (color) - if ((UINT8)*c < 0x80) - --numtowrite; - } - // Reset textcount for next tic based on speed - // if it wasn't already set by a delay. - if (cutscene_textcount < 0) - { - cutscene_textcount = 0; - if (cutscene_textspeed > 7) - cutscene_textcount = cutscene_textspeed - 7; - } - return 1; -} - -static void F_NewCutscene(const char *basetext) -{ - cutscene_basetext = basetext; - memset(cutscene_disptext,0,sizeof(cutscene_disptext)); - cutscene_writeptr = cutscene_baseptr = 0; - cutscene_textspeed = 9; - cutscene_textcount = TICRATE/2; -} +static textwriter_t textwriter; // ============= // INTRO SCENE // ============= #define NUMINTROSCENES 17 -INT32 intro_scenenum = 0; -INT32 intro_curtime = 0; + +static INT32 intro_scenenum = 0; +static INT32 intro_curtime = 0; const char *introtext[NUMINTROSCENES]; @@ -505,10 +414,10 @@ void F_StartIntro(void) gameaction = ga_nothing; paused = false; CON_ToggleOff(); - F_NewCutscene(introtext[0]); + P_ResetTextWriter(&textwriter, introtext[0]); intro_scenenum = 0; - finalecount = animtimer = skullAnimCounter = stoptimer = 0; + finalecount = animtimer = stoptimer = 0; timetonext = introscenetime[intro_scenenum]; } @@ -835,7 +744,8 @@ void F_IntroDrawer(void) V_DrawRightAlignedString(BASEVIDWIDTH-4, BASEVIDHEIGHT-12, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), "\x86""Press ""\x82""ENTER""\x86"" to skip..."); } - V_DrawString(cx, cy, V_ALLOWLOWERCASE, cutscene_disptext); + if (textwriter.disptext) + V_DrawString(cx, cy, V_ALLOWLOWERCASE, textwriter.disptext); } // @@ -848,7 +758,7 @@ void F_IntroTicker(void) timetonext--; - F_WriteText(); + P_CutsceneWriteText(&textwriter); // check for skipping if (keypressed) @@ -937,7 +847,7 @@ void F_IntroTicker(void) return; } - F_NewCutscene(introtext[++intro_scenenum]); + P_ResetTextWriter(&textwriter, introtext[++intro_scenenum]); timetonext = introscenetime[intro_scenenum]; F_WipeStartScreen(); @@ -2489,7 +2399,7 @@ void F_StartTitleScreen(void) // IWAD dependent stuff. - animtimer = skullAnimCounter = 0; + animtimer = 0; demoDelayLeft = demoDelayTime; demoIdleLeft = demoIdleTime; @@ -3834,7 +3744,7 @@ boolean F_ContinueResponder(event_t *event) // CUSTOM CUTSCENES // ================== static INT32 scenenum, cutnum; -static INT32 picxpos, picypos, picnum, pictime, picmode, numpics, pictoloop; +static INT32 picxpos, picypos, picnum, pictime; static INT32 textxpos, textypos; static boolean cutsceneover = false; static boolean runningprecutscene = false, precutresetplayer = false, precutFLS = false; @@ -3870,32 +3780,38 @@ static void F_AdvanceToNextScene(void) timetonext = 0; stoptimer = 0; picnum = 0; - picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum]; - picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum]; - if (cutscenes[cutnum]->scene[scenenum].musswitch[0]) - S_ChangeMusicEx(cutscenes[cutnum]->scene[scenenum].musswitch, - cutscenes[cutnum]->scene[scenenum].musswitchflags, - cutscenes[cutnum]->scene[scenenum].musicloop, - cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0); + scene_t *scene = &cutscenes[cutnum]->scene[scenenum]; + + cutscene_pic_t *pic = &scene->pics[picnum]; + + picxpos = pic->xcoord; + picypos = pic->ycoord; + + if (scene->musswitch[0]) + S_ChangeMusicEx(scene->musswitch, + scene->musswitchflags, + scene->musicloop, + scene->musswitchposition, 0, 0); // Fade to the next - F_NewCutscene(cutscenes[cutnum]->scene[scenenum].text); + P_ResetTextWriter(&textwriter, scene->text); picnum = 0; - picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum]; - picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum]; - textxpos = cutscenes[cutnum]->scene[scenenum].textxpos; - textypos = cutscenes[cutnum]->scene[scenenum].textypos; + pic = &scene->pics[picnum]; + picxpos = pic->xcoord; + picypos = pic->ycoord; + textxpos = scene->textxpos; + textypos = scene->textypos; - animtimer = pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum]; + animtimer = pictime = pic->duration; if (rendermode != render_none) { F_CutsceneDrawer(); F_WipeEndScreen(); - F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true); + F_RunWipe(scene->fadeoutid, true); } } @@ -3935,7 +3851,7 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset paused = false; CON_ToggleOff(); - F_NewCutscene(cutscenes[cutscenenum]->scene[0].text); + P_ResetTextWriter(&textwriter, cutscenes[cutscenenum]->scene[0].text); cutsceneover = false; runningprecutscene = precutscene; @@ -3944,24 +3860,29 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset scenenum = picnum = 0; cutnum = cutscenenum; - picxpos = cutscenes[cutnum]->scene[0].xcoord[0]; - picypos = cutscenes[cutnum]->scene[0].ycoord[0]; - textxpos = cutscenes[cutnum]->scene[0].textxpos; - textypos = cutscenes[cutnum]->scene[0].textypos; - pictime = cutscenes[cutnum]->scene[0].picduration[0]; + scene_t *scene = &cutscenes[cutnum]->scene[scenenum]; + + cutscene_pic_t *pic = &scene->pics[picnum]; + + picxpos = pic->xcoord; + picypos = pic->ycoord; + textxpos = scene->textxpos; + textypos = scene->textypos; + + pictime = pic->duration; keypressed = false; finalecount = 0; timetonext = 0; - animtimer = cutscenes[cutnum]->scene[0].picduration[0]; // Picture duration + animtimer = pic->duration; // Picture duration stoptimer = 0; - if (cutscenes[cutnum]->scene[0].musswitch[0]) - S_ChangeMusicEx(cutscenes[cutnum]->scene[0].musswitch, - cutscenes[cutnum]->scene[0].musswitchflags, - cutscenes[cutnum]->scene[0].musicloop, - cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0); + if (scene->musswitch[0]) + S_ChangeMusicEx(scene->musswitch, + scene->musswitchflags, + scene->musicloop, + scene->musswitchposition, 0, 0); else S_StopMusic(); S_StopSounds(); @@ -3972,19 +3893,22 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset // void F_CutsceneDrawer(void) { - V_DrawFill(0,0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + + cutscene_pic_t *pic = &cutscenes[cutnum]->scene[scenenum].pics[picnum]; - if (cutscenes[cutnum]->scene[scenenum].picname[picnum][0] != '\0') + if (pic->name[0] != '\0') { - if (cutscenes[cutnum]->scene[scenenum].pichires[picnum]) + if (pic->hires) V_DrawSmallScaledPatch(picxpos, picypos, 0, - W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY)); + W_CachePatchLongName(pic->name, PU_PATCH_LOWPRIORITY)); else V_DrawScaledPatch(picxpos,picypos, 0, - W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY)); + W_CachePatchLongName(pic->name, PU_PATCH_LOWPRIORITY)); } - V_DrawString(textxpos, textypos, V_ALLOWLOWERCASE, cutscene_disptext); + if (textwriter.disptext) + V_DrawString(textxpos, textypos, V_ALLOWLOWERCASE, textwriter.disptext); } void F_CutsceneTicker(void) @@ -3998,7 +3922,7 @@ void F_CutsceneTicker(void) // advance animation finalecount++; - cutscene_boostspeed = 0; + textwriter.boostspeed = 0; for (i = 0; i < MAXPLAYERS; i++) { @@ -4008,7 +3932,7 @@ void F_CutsceneTicker(void) if (players[i].cmd.buttons & BT_SPIN) { keypressed = false; - cutscene_boostspeed = 1; + textwriter.boostspeed = 1; if (timetonext) timetonext = 2; } @@ -4019,12 +3943,17 @@ void F_CutsceneTicker(void) animtimer--; if (animtimer <= 0) { - if (picnum < 7 && cutscenes[cutnum]->scene[scenenum].picname[picnum+1][0] != '\0') + scene_t *scene = &cutscenes[cutnum]->scene[scenenum]; + + cutscene_pic_t *next_pic = NULL; + + if (picnum < MAX_CUTSCENE_PICS-1 && scene->pics[picnum+1].name[0] != '\0') { picnum++; - picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum]; - picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum]; - pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum]; + next_pic = &scene->pics[picnum]; + picxpos = next_pic->xcoord; + picypos = next_pic->ycoord; + pictime = next_pic->duration; animtimer = pictime; } else @@ -4037,7 +3966,7 @@ void F_CutsceneTicker(void) if (++stoptimer > 2 && timetonext == 1) F_AdvanceToNextScene(); - else if (!timetonext && !F_WriteText()) + else if (!timetonext && !P_CutsceneWriteText(&textwriter)) timetonext = 5*TICRATE + 1; } @@ -4049,631 +3978,6 @@ boolean F_CutsceneResponder(event_t *event) return false; } -// ================== -// TEXT PROMPTS -// ================== - -static void F_GetPageTextGeometry(UINT8 *pagelines, boolean *rightside, INT32 *boxh, INT32 *texth, INT32 *texty, INT32 *namey, INT32 *chevrony, INT32 *textx, INT32 *textr) -{ - // reuse: - // cutnum -> promptnum - // scenenum -> pagenum - lumpnum_t iconlump = W_CheckNumForName(textprompts[cutnum]->page[scenenum].iconname); - - *pagelines = textprompts[cutnum]->page[scenenum].lines ? textprompts[cutnum]->page[scenenum].lines : 4; - *rightside = (iconlump != LUMPERROR && textprompts[cutnum]->page[scenenum].rightside); - - // Vertical calculations - *boxh = *pagelines*2; - *texth = textprompts[cutnum]->page[scenenum].name[0] ? (*pagelines-1)*2 : *pagelines*2; // name takes up first line if it exists - *texty = BASEVIDHEIGHT - ((*texth * 4) + (*texth/2)*4); - *namey = BASEVIDHEIGHT - ((*boxh * 4) + (*boxh/2)*4); - *chevrony = BASEVIDHEIGHT - (((1*2) * 4) + ((1*2)/2)*4); // force on last line - - // Horizontal calculations - // Shift text to the right if we have a character icon on the left side - // Add 4 margin against icon - *textx = (iconlump != LUMPERROR && !*rightside) ? ((*boxh * 4) + (*boxh/2)*4) + 4 : 4; - *textr = *rightside ? BASEVIDWIDTH - (((*boxh * 4) + (*boxh/2)*4) + 4) : BASEVIDWIDTH-4; -} - -static fixed_t F_GetPromptHideHudBound(void) -{ - UINT8 pagelines; - boolean rightside; - INT32 boxh, texth, texty, namey, chevrony; - INT32 textx, textr; - - if (cutnum == INT32_MAX || scenenum == INT32_MAX || !textprompts[cutnum] || scenenum >= textprompts[cutnum]->numpages || - !textprompts[cutnum]->page[scenenum].hidehud || - (splitscreen && textprompts[cutnum]->page[scenenum].hidehud != 2)) // don't hide on splitscreen, unless hide all is forced - return 0; - else if (textprompts[cutnum]->page[scenenum].hidehud == 2) // hide all - return BASEVIDHEIGHT; - - F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr); - - // calc boxheight (see V_DrawPromptBack) - boxh *= vid.dup; - boxh = (boxh * 4) + (boxh/2)*5; // 4 lines of space plus gaps between and some leeway - - // return a coordinate to check - // if negative: don't show hud elements below this coordinate (visually) - // if positive: don't show hud elements above this coordinate (visually) - return 0 - boxh; // \todo: if prompt at top of screen (someday), make this return positive -} - -boolean F_GetPromptHideHudAll(void) -{ - if (cutnum == INT32_MAX || scenenum == INT32_MAX || !textprompts[cutnum] || scenenum >= textprompts[cutnum]->numpages || - !textprompts[cutnum]->page[scenenum].hidehud || - (splitscreen && textprompts[cutnum]->page[scenenum].hidehud != 2)) // don't hide on splitscreen, unless hide all is forced - return false; - else if (textprompts[cutnum]->page[scenenum].hidehud == 2) // hide all - return true; - else - return false; -} - -boolean F_GetPromptHideHud(fixed_t y) -{ - INT32 ybound; - boolean fromtop; - fixed_t ytest; - - if (!promptactive) - return false; - - ybound = F_GetPromptHideHudBound(); - fromtop = (ybound >= 0); - ytest = (fromtop ? ybound : BASEVIDHEIGHT + ybound); - - return (fromtop ? y < ytest : y >= ytest); // true means hide -} - -static void F_PreparePageText(char *pagetext) -{ - UINT8 pagelines; - boolean rightside; - INT32 boxh, texth, texty, namey, chevrony; - INT32 textx, textr; - - F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr); - - if (promptpagetext) - Z_Free(promptpagetext); - promptpagetext = (pagetext && pagetext[0]) ? V_WordWrap(textx, textr, 0, pagetext) : Z_StrDup(""); - - F_NewCutscene(promptpagetext); - cutscene_textspeed = textprompts[cutnum]->page[scenenum].textspeed ? textprompts[cutnum]->page[scenenum].textspeed : TICRATE/5; - cutscene_textcount = 0; // no delay in beginning - cutscene_boostspeed = 0; // don't print 8 characters to start - - // \todo update control hot strings on re-config - // and somehow don't reset cutscene text counters -} - -static void F_AdvanceToNextPage(void) -{ - INT32 nextprompt = textprompts[cutnum]->page[scenenum].nextprompt ? textprompts[cutnum]->page[scenenum].nextprompt - 1 : INT32_MAX, - nextpage = textprompts[cutnum]->page[scenenum].nextpage ? textprompts[cutnum]->page[scenenum].nextpage - 1 : INT32_MAX, - oldcutnum = cutnum; - - if (textprompts[cutnum]->page[scenenum].nexttag[0]) - F_GetPromptPageByNamedTag(textprompts[cutnum]->page[scenenum].nexttag, &nextprompt, &nextpage); - - // determine next prompt - if (nextprompt != INT32_MAX) - { - if (nextprompt <= MAX_PROMPTS && textprompts[nextprompt]) - cutnum = nextprompt; - else - cutnum = INT32_MAX; - } - - // determine next page - if (nextpage != INT32_MAX) - { - if (cutnum != INT32_MAX) - { - scenenum = nextpage; - if (scenenum >= MAX_PAGES || scenenum > textprompts[cutnum]->numpages-1) - scenenum = INT32_MAX; - } - } - else - { - if (cutnum != oldcutnum) - scenenum = 0; - else if (scenenum + 1 < MAX_PAGES && scenenum < textprompts[cutnum]->numpages-1) - scenenum++; - else - scenenum = INT32_MAX; - } - - // close the prompt if either num is invalid - if (cutnum == INT32_MAX || scenenum == INT32_MAX) - F_EndTextPrompt(false, false); - else - { - // on page mode, number of tics before allowing boost - // on timer mode, number of tics until page advances - timetonext = textprompts[cutnum]->page[scenenum].timetonext ? textprompts[cutnum]->page[scenenum].timetonext : TICRATE/10; - F_PreparePageText(textprompts[cutnum]->page[scenenum].text); - - // gfx - picnum = textprompts[cutnum]->page[scenenum].pictostart; - numpics = textprompts[cutnum]->page[scenenum].numpics; - picmode = textprompts[cutnum]->page[scenenum].picmode; - pictoloop = textprompts[cutnum]->page[scenenum].pictoloop > 0 ? textprompts[cutnum]->page[scenenum].pictoloop - 1 : 0; - picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum]; - picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum]; - animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum]; - - // music change - if (textprompts[cutnum]->page[scenenum].musswitch[0]) - S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch, - textprompts[cutnum]->page[scenenum].musswitchflags, - textprompts[cutnum]->page[scenenum].musicloop); - } -} - -void F_EndTextPrompt(boolean forceexec, boolean noexec) -{ - boolean promptwasactive = promptactive; - promptactive = false; - callpromptnum = callpagenum = callplayer = INT32_MAX; - - if (promptwasactive) - { - if (promptmo && promptmo->player && promptblockcontrols) - promptmo->reactiontime = TICRATE/4; // prevent jumping right away // \todo account freeze realtime for this) - // \todo reset frozen realtime? - } - - // \todo net safety, maybe loop all player thinkers? - if ((promptwasactive || forceexec) && !noexec && promptpostexectag) - { - if (tmthing) // edge case where starting an invalid prompt immediately on level load will make P_MapStart fail - P_LinedefExecute(promptpostexectag, promptmo, NULL); - else - { - P_MapStart(); - P_LinedefExecute(promptpostexectag, promptmo, NULL); - P_MapEnd(); - } - } -} - -void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime) -{ - INT32 i; - - // if splitscreen and we already have a prompt active, ignore. - // \todo Proper per-player splitscreen support (individual prompts) - if (promptactive && splitscreen && promptnum == callpromptnum && pagenum == callpagenum) - return; - - // \todo proper netgame support - if (netgame) - { - F_EndTextPrompt(true, false); // run the post-effects immediately - return; - } - - // We share vars, so no starting text prompts over cutscenes or title screens! - keypressed = false; - finalecount = 0; - timetonext = 0; - animtimer = 0; - stoptimer = 0; - skullAnimCounter = 0; - - // Set up state - promptmo = mo; - promptpostexectag = postexectag; - promptblockcontrols = blockcontrols; - (void)freezerealtime; // \todo freeze player->realtime, maybe this needs to cycle through player thinkers - - // Initialize current prompt and scene - callpromptnum = promptnum; - callpagenum = pagenum; - cutnum = (promptnum < MAX_PROMPTS && textprompts[promptnum]) ? promptnum : INT32_MAX; - scenenum = (cutnum != INT32_MAX && pagenum < MAX_PAGES && pagenum <= textprompts[cutnum]->numpages-1) ? pagenum : INT32_MAX; - promptactive = (cutnum != INT32_MAX && scenenum != INT32_MAX); - - if (promptactive) - { - // on page mode, number of tics before allowing boost - // on timer mode, number of tics until page advances - timetonext = textprompts[cutnum]->page[scenenum].timetonext ? textprompts[cutnum]->page[scenenum].timetonext : TICRATE/10; - F_PreparePageText(textprompts[cutnum]->page[scenenum].text); - - // gfx - picnum = textprompts[cutnum]->page[scenenum].pictostart; - numpics = textprompts[cutnum]->page[scenenum].numpics; - picmode = textprompts[cutnum]->page[scenenum].picmode; - pictoloop = textprompts[cutnum]->page[scenenum].pictoloop > 0 ? textprompts[cutnum]->page[scenenum].pictoloop - 1 : 0; - picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum]; - picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum]; - animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum]; - - // music change - if (textprompts[cutnum]->page[scenenum].musswitch[0]) - S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch, - textprompts[cutnum]->page[scenenum].musswitchflags, - textprompts[cutnum]->page[scenenum].musicloop); - - // get the calling player - if (promptblockcontrols && mo && mo->player) - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (players[i].mo == mo) - { - callplayer = i; - break; - } - } - } - } - else - F_EndTextPrompt(true, false); // run the post-effects immediately -} - -static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length) -{ - INT32 gcs = gcs_custom; - boolean suffixed = true; - - if (!tag || !tag[0] || !tutorialmode) - return false; - - if (!strncmp(tag, "TAM", 3)) // Movement - gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement); - else if (!strncmp(tag, "TAC", 3)) // Camera - { - // Check for gcl_movement so we can differentiate between FPS and Platform schemes. - gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement); - if (gcs == gcs_custom) // try again, maybe we'll get a match - gcs = G_GetControlScheme(gamecontrol, gcl_camera, num_gcl_camera); - if (gcs == gcs_fps && !cv_usemouse.value) - gcs = gcs_platform; // Platform (arrow) scheme is stand-in for no mouse - } - else if (!strncmp(tag, "TAD", 3)) // Movement and Camera - gcs = G_GetControlScheme(gamecontrol, gcl_movement_camera, num_gcl_movement_camera); - else if (!strncmp(tag, "TAJ", 3)) // Jump - gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump); - else if (!strncmp(tag, "TAS", 3)) // Spin - gcs = G_GetControlScheme(gamecontrol, gcl_spin, num_gcl_spin); - else if (!strncmp(tag, "TAA", 3)) // Char ability - gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump); - else if (!strncmp(tag, "TAW", 3)) // Shield ability - gcs = G_GetControlScheme(gamecontrol, gcl_jump_spin, num_gcl_jump_spin); - else - gcs = G_GetControlScheme(gamecontrol, gcl_tutorial_used, num_gcl_tutorial_used); - - switch (gcs) - { - case gcs_fps: - // strncat(tag, "FPS", length); - suffixed = false; - break; - - case gcs_platform: - strncat(tag, "PLATFORM", length); - break; - - default: - strncat(tag, "CUSTOM", length); - break; - } - - return suffixed; -} - -void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum) -{ - INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX; - INT32 tutorialpromptnum = (tutorialmode) ? TUTORIAL_PROMPT-1 : 0; - boolean suffixed = false, found = false; - char suffixedtag[33]; - - *promptnum = *pagenum = INT32_MAX; - - if (!tag || !tag[0]) - return; - - strncpy(suffixedtag, tag, 33); - suffixedtag[32] = 0; - - if (tutorialmode) - suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33); - - for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++) - { - if (!textprompts[*promptnum]) - continue; - - for (*pagenum = 0; *pagenum < textprompts[*promptnum]->numpages && *pagenum < MAX_PAGES; (*pagenum)++) - { - if (suffixed && fastcmp(suffixedtag, textprompts[*promptnum]->page[*pagenum].tag)) - { - // this goes first because fastcmp ends early if first string is shorter - found = true; - break; - } - else if (nosuffixpromptnum == INT32_MAX && nosuffixpagenum == INT32_MAX && fastcmp(tag, textprompts[*promptnum]->page[*pagenum].tag)) - { - if (suffixed) - { - nosuffixpromptnum = *promptnum; - nosuffixpagenum = *pagenum; - // continue searching for the suffixed tag - } - else - { - found = true; - break; - } - } - } - - if (found) - break; - } - - if (suffixed && !found && nosuffixpromptnum != INT32_MAX && nosuffixpagenum != INT32_MAX) - { - found = true; - *promptnum = nosuffixpromptnum; - *pagenum = nosuffixpagenum; - } - - if (!found) - CONS_Debug(DBG_GAMELOGIC, "Text prompt: Can't find a page with named tag %s or suffixed tag %s\n", tag, suffixedtag); -} - -void F_TextPromptDrawer(void) -{ - // reuse: - // cutnum -> promptnum - // scenenum -> pagenum - lumpnum_t iconlump; - UINT8 pagelines; - boolean rightside; - INT32 boxh, texth, texty, namey, chevrony; - INT32 textx, textr; - - // Data - patch_t *patch; - - if (!promptactive) - return; - - iconlump = W_CheckNumForName(textprompts[cutnum]->page[scenenum].iconname); - F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr); - - // Draw gfx first - if (picnum >= 0 && picnum < numpics && textprompts[cutnum]->page[scenenum].picname[picnum][0] != '\0') - { - if (textprompts[cutnum]->page[scenenum].pichires[picnum]) - V_DrawSmallScaledPatch(picxpos, picypos, 0, - W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY)); - else - V_DrawScaledPatch(picxpos,picypos, 0, - W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY)); - } - - // Draw background - V_DrawPromptBack(boxh, textprompts[cutnum]->page[scenenum].backcolor); - - // Draw narrator icon - if (iconlump != LUMPERROR) - { - INT32 iconx, icony, scale, scaledsize; - patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH_LOWPRIORITY); - - // scale and center - if (patch->width > patch->height) - { - scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width); - scaledsize = FixedMul(patch->height, scale); - iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS; - icony = ((namey-4) << FRACBITS) + FixedDiv(BASEVIDHEIGHT - namey + 4 - scaledsize, 2); // account for 4 margin - } - else if (patch->height > patch->width) - { - scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->height); - scaledsize = FixedMul(patch->width, scale); - iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS; - icony = namey << FRACBITS; - iconx += FixedDiv(FixedMul(patch->height, scale) - scaledsize, 2); - } - else - { - scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width); - iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS; - icony = namey << FRACBITS; - } - - if (textprompts[cutnum]->page[scenenum].iconflip) - iconx += FixedMul(patch->width, scale) << FRACBITS; - - V_DrawFixedPatch(iconx, icony, scale, (V_SNAPTOBOTTOM|(textprompts[cutnum]->page[scenenum].iconflip ? V_FLIP : 0)), patch, NULL); - W_UnlockCachedPatch(patch); - } - - // Draw text - V_DrawString(textx, texty, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), cutscene_disptext); - - // Draw name - // Don't use V_YELLOWMAP here so that the name color can be changed with control codes - if (textprompts[cutnum]->page[scenenum].name[0]) - V_DrawString(textx, namey, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), textprompts[cutnum]->page[scenenum].name); - - // Draw chevron - if (promptblockcontrols && !timetonext) - V_DrawString(textr-8, chevrony + (skullAnimCounter/5), (V_SNAPTOBOTTOM|V_YELLOWMAP), "\x1B"); // down arrow -} - -#define nocontrolallowed(j) {\ - players[j].powers[pw_nocontrol] = 1;\ - if (players[j].mo)\ - {\ - if (players[j].mo->state == states+S_PLAY_STND && players[j].mo->tics != -1)\ - players[j].mo->tics++;\ - else if (players[j].mo->state == states+S_PLAY_WAIT)\ - P_SetMobjState(players[j].mo, S_PLAY_STND);\ - }\ - } - -void F_TextPromptTicker(void) -{ - INT32 i; - - if (!promptactive || paused || P_AutoPause()) - return; - - // advance animation - finalecount++; - cutscene_boostspeed = 0; - - // for the chevron - if (--skullAnimCounter <= 0) - skullAnimCounter = 8; - - // button handling - if (textprompts[cutnum]->page[scenenum].timetonext) - { - if (promptblockcontrols) // same procedure as below, just without the button handling - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (netgame && i != serverplayer && !IsPlayerAdmin(i)) - continue; - else if (splitscreen) { - // Both players' controls are locked, - // But only consoleplayer can advance the prompt. - // \todo Proper per-player splitscreen support (individual prompts) - if (i == consoleplayer || i == secondarydisplayplayer) - nocontrolallowed(i) - } - else if (i == consoleplayer) - nocontrolallowed(i) - - if (!splitscreen) - break; - } - } - - if (timetonext >= 1) - timetonext--; - - if (!timetonext) - F_AdvanceToNextPage(); - - F_WriteText(); - } - else - { - if (promptblockcontrols) - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (netgame && i != serverplayer && !IsPlayerAdmin(i)) - continue; - else if (splitscreen) { - // Both players' controls are locked, - // But only the triggering player can advance the prompt. - if (i == consoleplayer || i == secondarydisplayplayer) - { - players[i].powers[pw_nocontrol] = 1; - - if (callplayer == consoleplayer || callplayer == secondarydisplayplayer) - { - if (i != callplayer) - continue; - } - else if (i != consoleplayer) - continue; - } - else - continue; - } - else if (i == consoleplayer) - nocontrolallowed(i) - else - continue; - - if ((players[i].cmd.buttons & BT_SPIN) || (players[i].cmd.buttons & BT_JUMP)) - { - if (timetonext > 1) - timetonext--; - else if (cutscene_baseptr) // don't set boost if we just reset the string - cutscene_boostspeed = 1; // only after a slight delay - - if (keypressed) - { - if (!splitscreen) - break; - else - continue; - } - - if (!timetonext) // is 0 when finished generating text - { - F_AdvanceToNextPage(); - if (promptactive) - S_StartSound(NULL, sfx_menu1); - } - keypressed = true; // prevent repeat events - } - else if (!(players[i].cmd.buttons & BT_SPIN) && !(players[i].cmd.buttons & BT_JUMP)) - keypressed = false; - - if (!splitscreen) - break; - } - } - - // generate letter-by-letter text - if (scenenum >= MAX_PAGES || - !textprompts[cutnum]->page[scenenum].text || - !textprompts[cutnum]->page[scenenum].text[0] || - !F_WriteText()) - timetonext = !promptblockcontrols; // never show the chevron if we can't toggle pages - } - - // gfx - if (picnum >= 0 && picnum < numpics) - { - if (animtimer <= 0) - { - boolean persistanimtimer = false; - - if (picnum < numpics-1 && textprompts[cutnum]->page[scenenum].picname[picnum+1][0] != '\0') - picnum++; - else if (picmode == PROMPT_PIC_LOOP) - picnum = pictoloop; - else if (picmode == PROMPT_PIC_DESTROY) - picnum = -1; - else // if (picmode == PROMPT_PIC_PERSIST) - persistanimtimer = true; - - if (!persistanimtimer && picnum >= 0) - { - picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum]; - picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum]; - pictime = textprompts[cutnum]->page[scenenum].picduration[picnum]; - animtimer = pictime; - } - } - else - animtimer--; - } -} - // ================ // WAITINGPLAYERS // ================ diff --git a/src/f_finale.h b/src/f_finale.h index cb71775d05fc109aa4c3468c19cfe381eff72635..57eb9ace86c115fffebcc4c85f1f587d1a290a2d 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -34,7 +34,6 @@ void F_IntroTicker(void); void F_TitleScreenTicker(boolean run); void F_CutsceneTicker(void); void F_TitleDemoTicker(void); -void F_TextPromptTicker(void); // Called by main loop. void F_GameEndDrawer(void); @@ -56,13 +55,6 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset void F_CutsceneDrawer(void); void F_EndCutScene(void); -void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime); -void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum); -void F_TextPromptDrawer(void); -void F_EndTextPrompt(boolean forceexec, boolean noexec); -boolean F_GetPromptHideHudAll(void); -boolean F_GetPromptHideHud(fixed_t y); - void F_StartGameEnd(void); void F_StartIntro(void); void F_StartTitleScreen(void); diff --git a/src/g_game.c b/src/g_game.c index 90ccf29c1854904a8dd06003ddbd40b99de00ca6..8e557aac3f5bd669b9cf5fbc9a4ed9c29c40c3e8 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -158,6 +158,8 @@ boolean exitfadestarted = false; cutscene_t *cutscenes[128]; textprompt_t *textprompts[MAX_PROMPTS]; +struct dialog_s *globaltextprompt = NULL; + INT16 nextmapoverride; UINT8 skipstats; INT16 nextgametype = -1; @@ -2422,7 +2424,6 @@ void G_Ticker(boolean run) F_TitleDemoTicker(); P_Ticker(run); // tic the game ST_Ticker(run); - F_TextPromptTicker(); AM_Ticker(); HU_Ticker(); diff --git a/src/g_game.h b/src/g_game.h index 80a815f02d00a572972412901c72877e8f80de49..52d14ffe7d2b984c04bc52edc3d6eae709e8217d 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -45,7 +45,7 @@ extern INT16 rw_maximums[NUM_WEAPONS]; extern INT32 pausedelay; extern boolean pausebreakkey; -extern boolean promptactive; +extern struct dialog_s *globaltextprompt; extern consvar_t cv_pauseifunfocused; diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c index f4251ef08a92f89a1f8898e2644c953723086f78..1fb02fe05e7054d707edc42067aa915811fb179c 100644 --- a/src/netcode/d_clisrv.c +++ b/src/netcode/d_clisrv.c @@ -32,6 +32,7 @@ #include "../p_saveg.h" #include "../z_zone.h" #include "../p_local.h" +#include "../p_dialog.h" #include "../m_misc.h" #include "../am_map.h" #include "../m_random.h" @@ -586,6 +587,21 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) if (gametyperules & GTR_TEAMFLAGS) P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you! + P_EndTextPrompt(&players[playernum], false, false); + + // Reassign the callplayer of the globaltextprompt if it is someone who just left + if (globaltextprompt && globaltextprompt->callplayer == &players[playernum]) + { + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !(players[i].spectator || players[i].quittime)) + { + globaltextprompt->callplayer = &players[i]; + break; + } + } + } + RedistributeSpecialStageSpheres(playernum); LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting diff --git a/src/p_dialog.c b/src/p_dialog.c new file mode 100644 index 0000000000000000000000000000000000000000..d57722502311a27639eb95a783bd5c25eb73506e --- /dev/null +++ b/src/p_dialog.c @@ -0,0 +1,977 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 2024 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file p_dialog.c +/// \brief Text prompt system + +#include "doomdef.h" +#include "doomstat.h" +#include "p_dialog.h" +#include "p_local.h" +#include "g_game.h" +#include "g_input.h" +#include "s_sound.h" +#include "v_video.h" +#include "w_wad.h" +#include "z_zone.h" +#include "fastcmp.h" + +static INT16 skullAnimCounter; // Prompts: Chevron animation + +static boolean IsSpeedControlChar(UINT8 chr) +{ + return chr >= 0xA0 && chr <= 0xAF; +} + +static boolean IsDelayControlChar(UINT8 chr) +{ + return chr >= 0xB0 && chr <= (0xB0+TICRATE-1); +} + +static void WriterTextBufferAlloc(textwriter_t *writer) +{ + if (!writer->disptext_size) + writer->disptext_size = 16; + + size_t oldsize = writer->disptext_size; + + while (((unsigned)writer->writeptr) + 1 >= writer->disptext_size) + writer->disptext_size *= 2; + + if (!writer->disptext) + writer->disptext = Z_Calloc(writer->disptext_size, PU_STATIC, NULL); + else if (oldsize != writer->disptext_size) + { + writer->disptext = Z_Realloc(writer->disptext, writer->disptext_size, PU_STATIC, NULL); + memset(&writer->disptext[writer->writeptr], 0x00, writer->disptext_size - writer->writeptr); + } +} + +// +// This alters the text string writer->disptext. +// Use the typical string drawing functions to display it. +// Returns 0 if \0 is reached (end of input) +// +UINT8 P_CutsceneWriteText(textwriter_t *writer) +{ + INT32 numtowrite = 1; + const char *c; + + if (writer->boostspeed) + { + // for custom cutscene speedup mode + numtowrite = 8; + } + else + { + // Don't draw any characters if the count was 1 or more when we started + if (--writer->textcount >= 0) + return 1; + + if (writer->textspeed < 7) + numtowrite = 8 - writer->textspeed; + } + + for (;numtowrite > 0;++writer->baseptr) + { + c = &writer->basetext[writer->baseptr]; + if (!c || !*c || *c=='#') + return 0; + + // \xA0 - \xAF = change text speed + if (IsSpeedControlChar((UINT8)*c)) + { + writer->textspeed = (INT32)((UINT8)*c - 0xA0); + continue; + } + // \xB0 - \xD2 = delay character for up to one second (35 tics) + else if (IsDelayControlChar((UINT8)*c)) + { + writer->textcount = (INT32)((UINT8)*c - 0xAF); + numtowrite = 0; + continue; + } + + WriterTextBufferAlloc(writer); + + writer->disptext[writer->writeptr++] = *c; + + // Ignore other control codes (color) + if ((UINT8)*c < 0x80) + --numtowrite; + } + // Reset textcount for next tic based on speed + // if it wasn't already set by a delay. + if (writer->textcount < 0) + { + writer->textcount = 0; + if (writer->textspeed > 7) + writer->textcount = writer->textspeed - 7; + } + return 1; +} + +static UINT8 P_DialogWriteText(dialog_t *dialog, textwriter_t *writer) +{ + INT32 numtowrite = 1; + const char *c; + + unsigned char lastchar = 0; + + (void)dialog; + + if (writer->boostspeed) + { + // for custom cutscene speedup mode + numtowrite = 8; + } + else + { + // Don't draw any characters if the count was 1 or more when we started + if (--writer->textcount >= 0) + return 2; + + if (writer->textspeed < 7) + numtowrite = 8 - writer->textspeed; + } + + for (;numtowrite > 0;++writer->baseptr) + { + c = &writer->basetext[writer->baseptr]; + if (!c || !*c) + return 0; + + lastchar = *c; + + // \xA0 - \xAF = change text speed + if (IsSpeedControlChar(lastchar)) + { + writer->textspeed = (INT32)(lastchar - 0xA0); + continue; + } + // \xB0 - \xD2 = delay character for up to one second (35 tics) + else if (IsDelayControlChar(lastchar)) + { + writer->textcount = (INT32)(lastchar - 0xAF); + numtowrite = 0; + continue; + } + + WriterTextBufferAlloc(writer); + + writer->disptext[writer->writeptr++] = lastchar; + + // Ignore other control codes (color) + if ((UINT8)lastchar < 0x80) + --numtowrite; + } + + // Reset textcount for next tic based on speed + // if it wasn't already set by a delay. + if (writer->textcount < 0) + { + writer->textcount = 0; + if (writer->textspeed > 7) + writer->textcount = writer->textspeed - 7; + } + + if (!lastchar || isspace(lastchar)) + return 2; + else + return 1; +} + +void P_ResetTextWriter(textwriter_t *writer, const char *basetext) +{ + writer->basetext = basetext; + if (writer->disptext && writer->disptext_size) + memset(writer->disptext,0,writer->disptext_size); + writer->writeptr = writer->baseptr = 0; + writer->textspeed = 9; + writer->textcount = TICRATE/2; +} + +// ================== +// TEXT PROMPTS +// ================== + +static void F_GetPageTextGeometry(dialog_t *dialog, UINT8 *pagelines, boolean *rightside, INT32 *boxh, INT32 *texth, INT32 *texty, INT32 *namey, INT32 *chevrony, INT32 *textx, INT32 *textr) +{ + lumpnum_t iconlump = W_CheckNumForName(dialog->page->iconname); + + *pagelines = dialog->page->lines ? dialog->page->lines : 4; + *rightside = (iconlump != LUMPERROR && dialog->page->rightside); + + // Vertical calculations + *boxh = *pagelines*2; + *texth = dialog->page->name[0] ? (*pagelines-1)*2 : *pagelines*2; // name takes up first line if it exists + *texty = BASEVIDHEIGHT - ((*texth * 4) + (*texth/2)*4); + *namey = BASEVIDHEIGHT - ((*boxh * 4) + (*boxh/2)*4); + *chevrony = BASEVIDHEIGHT - (((1*2) * 4) + ((1*2)/2)*4); // force on last line + + // Horizontal calculations + // Shift text to the right if we have a character icon on the left side + // Add 4 margin against icon + *textx = (iconlump != LUMPERROR && !*rightside) ? ((*boxh * 4) + (*boxh/2)*4) + 4 : 4; + *textr = *rightside ? BASEVIDWIDTH - (((*boxh * 4) + (*boxh/2)*4) + 4) : BASEVIDWIDTH-4; +} + +static fixed_t F_GetPromptHideHudBound(dialog_t *dialog) +{ + UINT8 pagelines; + boolean rightside; + INT32 boxh, texth, texty, namey, chevrony; + INT32 textx, textr; + + if (!dialog->prompt || !dialog->page || + !dialog->page->hidehud || + (splitscreen && dialog->page->hidehud != 2)) // don't hide on splitscreen, unless hide all is forced + return 0; + else if (dialog->page->hidehud == 2) // hide all + return BASEVIDHEIGHT; + + F_GetPageTextGeometry(dialog, &pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr); + + // calc boxheight (see V_DrawPromptBack) + boxh *= vid.dup; + boxh = (boxh * 4) + (boxh/2)*5; // 4 lines of space plus gaps between and some leeway + + // return a coordinate to check + // if negative: don't show hud elements below this coordinate (visually) + // if positive: don't show hud elements above this coordinate (visually) + return 0 - boxh; // \todo: if prompt at top of screen (someday), make this return positive +} + +boolean F_GetPromptHideHudAll(void) +{ + if (!players[displayplayer].promptactive) + return false; + + dialog_t *dialog = globaltextprompt ? globaltextprompt : players[displayplayer].textprompt; + if (!dialog) + return false; + + if (!dialog->prompt || !dialog->page || + !dialog->page->hidehud || + (splitscreen && dialog->page->hidehud != 2)) // don't hide on splitscreen, unless hide all is forced + return false; + else if (dialog->page->hidehud == 2) // hide all + return true; + else + return false; +} + +boolean F_GetPromptHideHud(fixed_t y) +{ + INT32 ybound; + boolean fromtop; + fixed_t ytest; + + if (!players[displayplayer].promptactive) + return false; + + dialog_t *dialog = globaltextprompt ? globaltextprompt : players[displayplayer].textprompt; + if (!dialog) + return false; + + ybound = F_GetPromptHideHudBound(dialog); + fromtop = (ybound >= 0); + ytest = (fromtop ? ybound : BASEVIDHEIGHT + ybound); + + return (fromtop ? y < ytest : y >= ytest); // true means hide +} + +void P_DialogSetText(dialog_t *dialog, char *pagetext, INT32 numchars) +{ + UINT8 pagelines; + boolean rightside; + INT32 boxh, texth, texty, namey, chevrony; + INT32 textx, textr; + + F_GetPageTextGeometry(dialog, &pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr); + + if (dialog->pagetext) + Z_Free(dialog->pagetext); + dialog->pagetext = (pagetext && pagetext[0]) ? V_WordWrap(textx, textr, 0, pagetext) : Z_StrDup(""); + + textwriter_t *writer = &dialog->writer; + + P_ResetTextWriter(writer, dialog->pagetext); + + writer->textspeed = dialog->page->textspeed ? dialog->page->textspeed : TICRATE/5; + writer->textcount = 0; // no delay in beginning + writer->boostspeed = 0; // don't print 8 characters to start + + if (numchars <= 0) + return; + + while (writer->writeptr < numchars) + { + const char *c = &writer->basetext[writer->baseptr]; + if (!c || !*c || *c=='#') + return; + + writer->baseptr++; + + char chr = *c; + + if (!IsSpeedControlChar((UINT8)chr) && !IsDelayControlChar((UINT8)chr)) + { + WriterTextBufferAlloc(writer); + + writer->disptext[writer->writeptr++] = chr; + } + } +} + +static void P_PreparePageText(dialog_t *dialog, char *pagetext) +{ + P_DialogSetText(dialog, pagetext, 0); + + // \todo update control hot strings on re-config + // and somehow don't reset cutscene text counters +} + +static void P_DialogStartPage(dialog_t *dialog) +{ + // on page mode, number of tics before allowing boost + // on timer mode, number of tics until page advances + dialog->timetonext = dialog->page->timetonext ? dialog->page->timetonext : TICRATE/10; + P_PreparePageText(dialog, dialog->page->text); + + // gfx + dialog->numpics = dialog->page->numpics; + dialog->picnum = dialog->page->pictostart; + dialog->pictoloop = dialog->page->pictoloop > 0 ? dialog->page->pictoloop - 1 : 0; + dialog->pictimer = dialog->page->pics[dialog->picnum].duration; + dialog->picmode = dialog->page->picmode; + + memcpy(dialog->pics, dialog->page->pics, sizeof(cutscene_pic_t) * dialog->page->numpics); + + // music change + if (dialog->page->musswitch[0]) + { + S_ChangeMusic(dialog->page->musswitch, + dialog->page->musswitchflags, + dialog->page->musicloop); + } +} + +static void P_AdvanceToNextPage(player_t *player, dialog_t *dialog) +{ + INT32 nextprompt = INT32_MAX, nextpage = INT32_MAX; + + if (dialog->page->nextprompt) + nextprompt = dialog->page->nextprompt - 1; + if (dialog->page->nextpage) + nextpage = dialog->page->nextpage - 1; + + textprompt_t *oldprompt = dialog->prompt; + + if (dialog->page->nexttag[0]) + P_GetPromptPageByNamedTag(dialog->page->nexttag, &nextprompt, &nextpage); + + // determine next prompt + if (nextprompt != INT32_MAX) + { + if (nextprompt >= 0 && nextprompt < MAX_PROMPTS && textprompts[nextprompt]) + { + dialog->promptnum = nextprompt; + dialog->prompt = textprompts[nextprompt]; + } + else + { + dialog->promptnum = INT32_MAX; + dialog->prompt = NULL; + } + } + + // determine next page + if (nextpage != INT32_MAX) + { + if (dialog->prompt != NULL) + { + if (nextpage >= MAX_PAGES || nextpage > dialog->prompt->numpages-1) + { + dialog->pagenum = INT32_MAX; + dialog->page = NULL; + } + else + { + dialog->pagenum = nextpage; + dialog->page = &dialog->prompt->page[nextpage]; + } + } + } + else if (dialog->prompt != NULL) + { + if (dialog->prompt != oldprompt) + { + dialog->pagenum = 0; + dialog->page = &dialog->prompt->page[0]; + } + else if (dialog->pagenum + 1 < MAX_PAGES && dialog->pagenum < dialog->prompt->numpages-1) + { + dialog->pagenum++; + dialog->page = &dialog->prompt->page[dialog->pagenum]; + } + else + { + dialog->pagenum = INT32_MAX; + dialog->page = NULL; + } + } + + // close the prompt if either num is invalid + if (dialog->prompt == NULL || dialog->page == NULL) + P_EndTextPrompt(player, false, false); + else + P_DialogStartPage(dialog); +} + +void P_FreeTextPrompt(dialog_t *dialog) +{ + if (dialog) + { + Z_Free(dialog->writer.disptext); + Z_Free(dialog); + } +} + +static void P_FreePlayerDialog(player_t *player) +{ + if (player->textprompt == globaltextprompt) + return; + + P_FreeTextPrompt(player->textprompt); + + player->textprompt = NULL; +} + +static INT16 P_DoEndDialog(player_t *player, dialog_t *dialog, boolean forceexec, boolean noexec) +{ + boolean promptwasactive = player->promptactive; + + INT16 postexectag = 0; + + player->promptactive = false; + player->textprompt = NULL; + + if (dialog) + { + postexectag = dialog->postexectag; + + if (promptwasactive) + { + if (dialog->blockcontrols) + player->mo->reactiontime = TICRATE/4; // prevent jumping right away // \todo account freeze realtime for this) + // \todo reset frozen realtime? + } + } + + if ((promptwasactive || forceexec) && !noexec) + return postexectag; + + return 0; +} + +static void P_EndGlobalTextPrompt(boolean forceexec, boolean noexec) +{ + if (!globaltextprompt) + return; + + INT16 postexectag = 0; + + player_t *callplayer = globaltextprompt->callplayer; + + if (callplayer) + { + if ((callplayer->promptactive || forceexec) && !noexec && globaltextprompt->postexectag) + postexectag = globaltextprompt->postexectag; + } + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + P_DoEndDialog(&players[i], globaltextprompt, false, true); + } + + P_FreeTextPrompt(globaltextprompt); + + globaltextprompt = NULL; + + if (postexectag) + P_LinedefExecute(postexectag, callplayer->mo, NULL); +} + +void P_EndTextPrompt(player_t *player, boolean forceexec, boolean noexec) +{ + if (globaltextprompt && player->textprompt == globaltextprompt) + { + P_EndGlobalTextPrompt(forceexec, noexec); + return; + } + + if (!player->textprompt) + return; + + INT16 postexectag = P_DoEndDialog(player, player->textprompt, forceexec, noexec); + + if (player->textprompt) + P_FreePlayerDialog(player); + + if (postexectag) + P_LinedefExecute(postexectag, player->mo, NULL); +} + +void P_EndAllTextPrompts(boolean forceexec, boolean noexec) +{ + if (globaltextprompt) + P_EndGlobalTextPrompt(forceexec, noexec); + else + { + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + P_EndTextPrompt(&players[i], forceexec, noexec); + } + } +} + +void P_StartTextPrompt(player_t *player, INT32 promptnum, INT32 pagenum, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime, boolean allplayers) +{ + INT32 i; + + dialog_t *dialog = NULL; + + if (allplayers) + { + P_EndAllTextPrompts(false, true); + + globaltextprompt = Z_Calloc(sizeof(dialog_t), PU_LEVEL, NULL); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + players[i].textprompt = globaltextprompt; + } + + dialog = globaltextprompt; + } + else + { + if (player->textprompt) + dialog = player->textprompt; + else + { + dialog = Z_Calloc(sizeof(dialog_t), PU_LEVEL, NULL); + player->textprompt = dialog; + } + } + + dialog->timetonext = 0; + dialog->pictimer = 0; + + skullAnimCounter = 0; + + // Set up state + dialog->postexectag = postexectag; + dialog->blockcontrols = blockcontrols; + (void)freezerealtime; // \todo freeze player->realtime, maybe this needs to cycle through player thinkers + + // Initialize current prompt and scene + dialog->callplayer = player; + dialog->promptnum = (promptnum < MAX_PROMPTS && textprompts[promptnum]) ? promptnum : INT32_MAX; + dialog->pagenum = (dialog->promptnum != INT32_MAX && pagenum < MAX_PAGES && pagenum <= textprompts[dialog->promptnum]->numpages-1) ? pagenum : INT32_MAX; + dialog->prompt = NULL; + dialog->page = NULL; + + boolean promptactive = dialog->promptnum != INT32_MAX && dialog->pagenum != INT32_MAX; + + if (promptactive) + { + if (allplayers) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + players[i].promptactive = true; + } + } + else + player->promptactive = true; + + dialog->prompt = textprompts[dialog->promptnum]; + dialog->page = &dialog->prompt->page[dialog->pagenum]; + + P_DialogStartPage(dialog); + } + else + { + // run the post-effects immediately + if (allplayers) + P_EndGlobalTextPrompt(true, false); + else + P_EndTextPrompt(player, true, false); + } +} + +static boolean P_GetTextPromptTutorialTag(char *tag, INT32 length) +{ + INT32 gcs = gcs_custom; + boolean suffixed = true; + + if (!tag || !tag[0] || !tutorialmode) + return false; + + if (!strncmp(tag, "TAM", 3)) // Movement + gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement); + else if (!strncmp(tag, "TAC", 3)) // Camera + { + // Check for gcl_movement so we can differentiate between FPS and Platform schemes. + gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement); + if (gcs == gcs_custom) // try again, maybe we'll get a match + gcs = G_GetControlScheme(gamecontrol, gcl_camera, num_gcl_camera); + if (gcs == gcs_fps && !cv_usemouse.value) + gcs = gcs_platform; // Platform (arrow) scheme is stand-in for no mouse + } + else if (!strncmp(tag, "TAD", 3)) // Movement and Camera + gcs = G_GetControlScheme(gamecontrol, gcl_movement_camera, num_gcl_movement_camera); + else if (!strncmp(tag, "TAJ", 3)) // Jump + gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump); + else if (!strncmp(tag, "TAS", 3)) // Spin + gcs = G_GetControlScheme(gamecontrol, gcl_spin, num_gcl_spin); + else if (!strncmp(tag, "TAA", 3)) // Char ability + gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump); + else if (!strncmp(tag, "TAW", 3)) // Shield ability + gcs = G_GetControlScheme(gamecontrol, gcl_jump_spin, num_gcl_jump_spin); + else + gcs = G_GetControlScheme(gamecontrol, gcl_tutorial_used, num_gcl_tutorial_used); + + switch (gcs) + { + case gcs_fps: + // strncat(tag, "FPS", length); + suffixed = false; + break; + + case gcs_platform: + strncat(tag, "PLATFORM", length); + break; + + default: + strncat(tag, "CUSTOM", length); + break; + } + + return suffixed; +} + +void P_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum) +{ + INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX; + INT32 tutorialpromptnum = (tutorialmode) ? TUTORIAL_PROMPT-1 : 0; + boolean suffixed = false, found = false; + char suffixedtag[33]; + + *promptnum = *pagenum = INT32_MAX; + + if (!tag || !tag[0]) + return; + + strncpy(suffixedtag, tag, 33); + suffixedtag[32] = 0; + + if (tutorialmode) + suffixed = P_GetTextPromptTutorialTag(suffixedtag, 33); + + for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++) + { + if (!textprompts[*promptnum]) + continue; + + for (*pagenum = 0; *pagenum < textprompts[*promptnum]->numpages && *pagenum < MAX_PAGES; (*pagenum)++) + { + if (suffixed && fastcmp(suffixedtag, textprompts[*promptnum]->page[*pagenum].tag)) + { + // this goes first because fastcmp ends early if first string is shorter + found = true; + break; + } + else if (nosuffixpromptnum == INT32_MAX && nosuffixpagenum == INT32_MAX && fastcmp(tag, textprompts[*promptnum]->page[*pagenum].tag)) + { + if (suffixed) + { + nosuffixpromptnum = *promptnum; + nosuffixpagenum = *pagenum; + // continue searching for the suffixed tag + } + else + { + found = true; + break; + } + } + } + + if (found) + break; + } + + if (suffixed && !found && nosuffixpromptnum != INT32_MAX && nosuffixpagenum != INT32_MAX) + { + found = true; + *promptnum = nosuffixpromptnum; + *pagenum = nosuffixpagenum; + } + + if (!found) + CONS_Debug(DBG_GAMELOGIC, "Text prompt: Can't find a page with named tag %s or suffixed tag %s\n", tag, suffixedtag); +} + +void F_TextPromptDrawer(void) +{ + if (!players[displayplayer].promptactive) + return; + + dialog_t *dialog = globaltextprompt ? globaltextprompt : players[displayplayer].textprompt; + if (!dialog) + return; + + lumpnum_t iconlump; + UINT8 pagelines; + boolean rightside; + INT32 boxh, texth, texty, namey, chevrony; + INT32 textx, textr; + + // Data + patch_t *patch; + + iconlump = W_CheckNumForName(dialog->page->iconname); + F_GetPageTextGeometry(dialog, &pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr); + + // Draw gfx first + if (dialog->picnum >= 0 && dialog->picnum < dialog->numpics && dialog->pics[dialog->picnum].name[0] != '\0') + { + cutscene_pic_t *pic = &dialog->pics[dialog->picnum]; + + if (pic->hires) + V_DrawSmallScaledPatch(pic->xcoord, pic->ycoord, 0, + W_CachePatchLongName(pic->name, PU_PATCH_LOWPRIORITY)); + else + V_DrawScaledPatch(pic->xcoord, pic->ycoord, 0, + W_CachePatchLongName(pic->name, PU_PATCH_LOWPRIORITY)); + } + + // Draw background + V_DrawPromptBack(boxh, dialog->page->backcolor); + + // Draw narrator icon + if (iconlump != LUMPERROR) + { + INT32 iconx, icony, scale, scaledsize; + patch = W_CachePatchName(dialog->page->iconname, PU_PATCH_LOWPRIORITY); + + // scale and center + if (patch->width > patch->height) + { + scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width); + scaledsize = FixedMul(patch->height, scale); + iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS; + icony = ((namey-4) << FRACBITS) + FixedDiv(BASEVIDHEIGHT - namey + 4 - scaledsize, 2); // account for 4 margin + } + else if (patch->height > patch->width) + { + scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->height); + scaledsize = FixedMul(patch->width, scale); + iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS; + icony = namey << FRACBITS; + iconx += FixedDiv(FixedMul(patch->height, scale) - scaledsize, 2); + } + else + { + scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width); + iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS; + icony = namey << FRACBITS; + } + + if (dialog->page->iconflip) + iconx += FixedMul(patch->width, scale) << FRACBITS; + + V_DrawFixedPatch(iconx, icony, scale, (V_SNAPTOBOTTOM|(dialog->page->iconflip ? V_FLIP : 0)), patch, NULL); + W_UnlockCachedPatch(patch); + } + + // Draw text + if (dialog->writer.disptext) + V_DrawString(textx, texty, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), dialog->writer.disptext); + + // Draw name + // Don't use V_YELLOWMAP here so that the name color can be changed with control codes + if (dialog->page->name[0]) + V_DrawString(textx, namey, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), dialog->page->name); + + if (globaltextprompt && (globaltextprompt->callplayer != &players[displayplayer])) + return; + + // Draw chevron + if (dialog->blockcontrols && !dialog->timetonext) + V_DrawString(textr-8, chevrony + (skullAnimCounter/5), (V_SNAPTOBOTTOM|V_YELLOWMAP), "\x1B"); // down arrow +} + +static void P_LockPlayerControls(player_t *player) +{ + player->powers[pw_nocontrol] = 1; + + if (player->mo && !P_MobjWasRemoved(player->mo)) + { + if (player->mo->state == &states[S_PLAY_STND] && player->mo->tics != -1) + player->mo->tics++; + else if (player->mo->state == &states[S_PLAY_WAIT]) + P_SetMobjState(player->mo, S_PLAY_STND); + } +} + +static void P_UpdatePromptGfx(dialog_t *dialog) +{ + if (dialog->picnum < 0 || dialog->picnum >= dialog->numpics) + return; + + if (dialog->pictimer <= 0) + { + boolean persistanimtimer = false; + + if (dialog->picnum < dialog->numpics-1 && dialog->pics[dialog->picnum+1].name[0] != '\0') + dialog->picnum++; + else if (dialog->picmode == PROMPT_PIC_LOOP) + dialog->picnum = dialog->pictoloop; + else if (dialog->picmode == PROMPT_PIC_DESTROY) + dialog->picnum = -1; + else // if (dialog->picmode == PROMPT_PIC_PERSIST) + persistanimtimer = true; + + if (!persistanimtimer && dialog->picnum >= 0) + dialog->pictimer = dialog->pics[dialog->picnum].duration; + } + else + dialog->pictimer--; +} + +void P_RunTextPrompt(player_t *player) +{ + if (!player->promptactive) + return; + + dialog_t *dialog = globaltextprompt ? globaltextprompt : player->textprompt; + if (!dialog) + { + player->promptactive = false; + return; + } + + textwriter_t *writer = &dialog->writer; + + // advance animation + writer->boostspeed = 0; + + // for the chevron + if (P_IsLocalPlayer(player) && --skullAnimCounter <= 0) + skullAnimCounter = 8; + + player_t *promptplayer = player; + + if (globaltextprompt) + { + promptplayer = globaltextprompt->callplayer; + + // Reassign the callplayer if they either quit, or became a spectator + if (promptplayer->spectator || promptplayer->quittime) + { + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !(players[i].spectator || players[i].quittime)) + { + promptplayer = globaltextprompt->callplayer = &players[i]; + break; + } + } + } + } + + // button handling + if (dialog->page->timetonext) + { + if (dialog->blockcontrols) // same procedure as below, just without the button handling + P_LockPlayerControls(player); + + if (player == promptplayer) + { + if (dialog->timetonext >= 1) + dialog->timetonext--; + + if (!dialog->timetonext) + { + P_AdvanceToNextPage(player, dialog); + return; + } + else + { + INT32 write = P_DialogWriteText(dialog, &dialog->writer); + if (write == 1 && dialog->page->textsfx) + S_StartSound(NULL, dialog->page->textsfx); + } + } + } + else + { + if (dialog->blockcontrols) + { + P_LockPlayerControls(player); + + if ((promptplayer->cmd.buttons & BT_SPIN) || (promptplayer->cmd.buttons & BT_JUMP)) + { + if (dialog->timetonext > 1) + dialog->timetonext--; + else if (writer->baseptr) // don't set boost if we just reset the string + writer->boostspeed = 1; // only after a slight delay + + if (!dialog->timetonext) // is 0 when finished generating text + { + P_AdvanceToNextPage(player, dialog); + + if (promptplayer->promptactive) + S_StartSound(NULL, sfx_menu1); + + return; + } + } + } + + // never show the chevron if we can't toggle pages + if (dialog->pagenum >= MAX_PAGES || !dialog->page->text || !dialog->page->text[0]) + dialog->timetonext = !dialog->blockcontrols; + + // generate letter-by-letter text + if (player == promptplayer) + { + INT32 write = P_DialogWriteText(dialog, &dialog->writer); + if (write) + { + if (write == 1 && dialog->page->textsfx) + S_StartSound(NULL, dialog->page->textsfx); + } + else + dialog->timetonext = !dialog->blockcontrols; + } + } + + if (player == promptplayer) + P_UpdatePromptGfx(dialog); +} diff --git a/src/p_dialog.h b/src/p_dialog.h new file mode 100644 index 0000000000000000000000000000000000000000..2b5af0afa9d0c54f36fc9a4c82482ee199130a7d --- /dev/null +++ b/src/p_dialog.h @@ -0,0 +1,73 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1993-1996 by id Software, Inc. +// Copyright (C) 1998-2000 by DooM Legacy Team. +// Copyright (C) 2024 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file p_dialog.h +/// \brief Text prompt system + +#ifndef __P_DIALOG__ +#define __P_DIALOG__ + +#include "doomtype.h" + +#include "d_player.h" + +// +// CUTSCENE TEXT WRITING +// +typedef struct +{ + const char *basetext; + char *disptext; + size_t disptext_size; + INT32 baseptr; + INT32 writeptr; + INT32 textcount; + INT32 textspeed; + UINT8 boostspeed; +} textwriter_t; + +UINT8 P_CutsceneWriteText(textwriter_t *writer); +void P_ResetTextWriter(textwriter_t *writer, const char *basetext); + +// +// PROMPT STATE +// +typedef struct dialog_s +{ + INT32 promptnum; + INT32 pagenum; + textprompt_t *prompt; + textpage_t *page; + INT32 timetonext; + textwriter_t writer; + INT16 postexectag; + boolean blockcontrols; + char *pagetext; + player_t *callplayer; + INT32 picnum; + INT32 pictoloop; + INT32 pictimer; + INT32 picmode; + INT32 numpics; + cutscene_pic_t pics[MAX_PROMPT_PICS]; +} dialog_t; + +void P_StartTextPrompt(player_t *player, INT32 promptnum, INT32 pagenum, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime, boolean allplayers); +void P_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum); +void P_EndTextPrompt(player_t *player, boolean forceexec, boolean noexec); +void P_EndAllTextPrompts(boolean forceexec, boolean noexec); +void P_RunTextPrompt(player_t *player); +void P_FreeTextPrompt(dialog_t *dialog); +void P_DialogSetText(dialog_t *dialog, char *pagetext, INT32 numchars); +boolean F_GetPromptHideHudAll(void); +boolean F_GetPromptHideHud(fixed_t y); +void F_TextPromptDrawer(void); + +#endif diff --git a/src/p_inter.c b/src/p_inter.c index d8765e7a2b4bde8358ba5188155a18c1c64a01df..e7585561c94ad2d68891583f6144ab6b2d30a44f 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -17,6 +17,7 @@ #include "g_game.h" #include "m_random.h" #include "p_local.h" +#include "p_dialog.h" #include "s_sound.h" #include "r_main.h" #include "st_stuff.h" @@ -1502,7 +1503,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) { if (special->health && !player->bot) { - F_StartTextPrompt(199, 0, toucher, 0, true, false); + P_StartTextPrompt(player, 199, 0, 0, true, false, false); special->health = 0; if (ultimatemode && player->continues < 99) player->continues++; diff --git a/src/p_mobj.c b/src/p_mobj.c index f16fef2f00312ef6dd7d8a0cc60bffb447377ccc..2396c4984c9d347f19eafa6f74c8865879f280f5 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -19,6 +19,7 @@ #include "hu_stuff.h" #include "p_local.h" #include "p_setup.h" +#include "p_dialog.h" #include "r_fps.h" #include "r_main.h" #include "r_skins.h" @@ -11850,6 +11851,13 @@ void P_SpawnPlayer(INT32 playernum) // Spawn with a pity shield if necessary. P_DoPityCheck(p); + + if (globaltextprompt && p->textprompt != globaltextprompt) + { + P_EndTextPrompt(p, false, true); + p->promptactive = true; + p->textprompt = globaltextprompt; + } } void P_AfterPlayerSpawn(INT32 playernum) diff --git a/src/p_saveg.c b/src/p_saveg.c index 41d7e3c80d1a02c0737c1c3eed032eaaee58cf67..8cbdc7796efeb5ae8e9b9aeb46f24d36a0b6d90f 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -21,6 +21,7 @@ #include "p_local.h" #include "p_setup.h" #include "p_saveg.h" +#include "p_dialog.h" #include "r_data.h" #include "r_fps.h" #include "r_textures.h" @@ -49,6 +50,7 @@ UINT8 *save_p; #define ARCHIVEBLOCK_SPECIALS 0x7F228378 #define ARCHIVEBLOCK_EMBLEMS 0x7F4A5445 #define ARCHIVEBLOCK_SECPORTALS 0x7FBE34C9 +#define ARCHIVEBLOCK_TEXTPROMPT 0x7F5B94D3 // Note: This cannot be bigger // than an UINT16 @@ -62,6 +64,9 @@ typedef enum DRONE = 0x80, } player_saveflags; +static void P_NetArchiveDialog(dialog_t *dialog); +static void P_NetUnArchiveDialog(dialog_t *dialog); + static inline void P_ArchivePlayer(void) { const player_t *player = &players[consoleplayer]; @@ -128,7 +133,6 @@ static void P_NetArchivePlayers(void) { INT32 i, j; UINT16 flags; -// size_t q; WRITEUINT32(save_p, ARCHIVEBLOCK_PLAYERS); @@ -349,6 +353,15 @@ static void P_NetArchivePlayers(void) WRITEFIXED(save_p, players[i].jumpfactor); WRITEFIXED(save_p, players[i].height); WRITEFIXED(save_p, players[i].spinheight); + + if (players[i].promptactive && players[i].textprompt) + { + WRITEUINT8(save_p, players[i].promptactive); + if (!globaltextprompt) + P_NetArchiveDialog(players[i].textprompt); + } + else + WRITEUINT8(save_p, 0); } } @@ -563,6 +576,16 @@ static void P_NetUnArchivePlayers(void) players[i].height = READFIXED(save_p); players[i].spinheight = READFIXED(save_p); + players[i].promptactive = (boolean)READUINT8(save_p); + + if (globaltextprompt) + players[i].textprompt = globaltextprompt; + else + { + players[i].textprompt = Z_Calloc(sizeof(dialog_t), PU_LEVEL, NULL); + P_NetUnArchiveDialog(players[i].textprompt); + } + players[i].viewheight = 41*players[i].height/48; // scale cannot be factored in at this point } } @@ -2480,7 +2503,10 @@ static void SaveFadeThinker(const thinker_t *th, const UINT8 type) { const fade_t *ht = (const void *)th; WRITEUINT8(save_p, type); - WRITEUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc)); + if (ht->dest_exc) + WRITEUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc)); + else + WRITEUINT32(save_p, 0xFFFFFFFF); WRITEUINT32(save_p, ht->sectornum); WRITEUINT32(save_p, ht->ffloornum); WRITEINT32(save_p, ht->alpha); @@ -3654,10 +3680,15 @@ static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker) static inline thinker_t* LoadFadeThinker(actionf_p1 thinker) { + UINT32 dest_exc; sector_t *ss; fade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->dest_exc = GetNetColormapFromList(READUINT32(save_p)); + dest_exc = READUINT32(save_p); + if (dest_exc != 0xFFFFFFFF) + ht->dest_exc = GetNetColormapFromList(dest_exc); + else + ht->dest_exc = NULL; ht->sectornum = READUINT32(save_p); ht->ffloornum = READUINT32(save_p); ht->alpha = READINT32(save_p); @@ -4898,6 +4929,99 @@ static inline void P_NetUnArchiveEmblems(void) } } +static void P_NetArchiveDialog(dialog_t *dialog) +{ + if (dialog == NULL) + I_Error("P_NetArchiveDialog: dialog == NULL"); + + WRITEINT32(save_p, dialog->promptnum); + WRITEINT32(save_p, dialog->pagenum); + WRITEINT32(save_p, dialog->timetonext); + WRITEINT16(save_p, dialog->postexectag); + WRITEUINT8(save_p, dialog->blockcontrols); + WRITEUINT8(save_p, (UINT8)(dialog->callplayer-players)); + WRITEINT32(save_p, dialog->picnum); + WRITEINT32(save_p, dialog->pictoloop); + WRITEINT32(save_p, dialog->pictimer); + WRITEINT32(save_p, dialog->writer.writeptr); + WRITEINT32(save_p, dialog->writer.textcount); + WRITEINT32(save_p, dialog->writer.textspeed); + WRITEINT32(save_p, dialog->writer.boostspeed); +} + +static void P_NetUnArchiveDialog(dialog_t *dialog) +{ + UINT8 playernum; + + INT32 numchars, textcount, textspeed, boostspeed; + + if (dialog == NULL) + I_Error("P_NetUnArchiveDialog: dialog == NULL"); + + dialog->promptnum = READINT32(save_p); + dialog->pagenum = READINT32(save_p); + dialog->timetonext = READINT32(save_p); + dialog->postexectag = READINT16(save_p); + dialog->blockcontrols = READUINT8(save_p); + playernum = READUINT8(save_p); + dialog->picnum = READINT32(save_p); + dialog->pictoloop = READINT32(save_p); + dialog->pictimer = READINT32(save_p); + numchars = READINT32(save_p); + textcount = READINT32(save_p); + textspeed = READINT32(save_p); + boostspeed = READINT32(save_p); + + if (dialog->promptnum < 0 || dialog->promptnum >= MAX_PROMPTS || !textprompts[dialog->promptnum]) + I_Error("Invalid text prompt %d from server", dialog->promptnum); + else if (dialog->pagenum < 0 || dialog->pagenum >= MAX_PAGES || dialog->pagenum >= textprompts[dialog->promptnum]->numpages) + I_Error("Invalid text prompt page %d from server", dialog->pagenum); + + if (playernum >= MAXPLAYERS) + I_Error("Invalid player number %u from server", playernum); + + dialog->prompt = textprompts[dialog->promptnum]; + dialog->page = &dialog->prompt->page[dialog->pagenum]; + dialog->callplayer = &players[playernum]; + + P_DialogSetText(dialog, dialog->page->text, numchars); + + dialog->writer.textcount = textcount; + dialog->writer.textspeed = textspeed; + dialog->writer.boostspeed = boostspeed; +} + +static void P_NetArchiveGlobalTextPrompt(void) +{ + WRITEUINT32(save_p, ARCHIVEBLOCK_TEXTPROMPT); + + if (!globaltextprompt) + { + WRITEUINT8(save_p, 0); + return; + } + + WRITEUINT8(save_p, 0xFF); + + P_NetArchiveDialog(globaltextprompt); +} + +static void P_NetUnArchiveGlobalTextPrompt(void) +{ + if (READUINT32(save_p) != ARCHIVEBLOCK_TEXTPROMPT) + I_Error("Bad $$$.sav at archive block GlobalTextPrompt"); + + P_FreeTextPrompt(globaltextprompt); + + globaltextprompt = NULL; + + if (READUINT8(save_p) == 0xFF) + { + globaltextprompt = Z_Calloc(sizeof(dialog_t), PU_LEVEL, NULL); + P_NetUnArchiveDialog(globaltextprompt); + } +} + static void P_NetArchiveSectorPortals(void) { WRITEUINT32(save_p, ARCHIVEBLOCK_SECPORTALS); @@ -5042,6 +5166,7 @@ void P_SaveNetGame(boolean resending) CV_SaveNetVars(&save_p); P_NetArchiveMisc(resending); P_NetArchiveEmblems(); + P_NetArchiveGlobalTextPrompt(); // Assign the mobjnumber for pointer tracking for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) @@ -5096,6 +5221,7 @@ boolean P_LoadNetGame(boolean reloading) if (!P_NetUnArchiveMisc(reloading)) return false; P_NetUnArchiveEmblems(); + P_NetUnArchiveGlobalTextPrompt(); P_NetUnArchivePlayers(); if (gamestate == GS_LEVEL) { diff --git a/src/p_setup.c b/src/p_setup.c index 1c0315847b1097d83bdb598d1e4d365c4c0f1a8d..62ca64e20fb9841ec3e028ad5ed9cf64103ca2f3 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -20,6 +20,7 @@ #include "p_setup.h" #include "p_spec.h" #include "p_saveg.h" +#include "p_dialog.h" #include "i_time.h" #include "i_sound.h" // for I_PlayCD().. @@ -5704,9 +5705,9 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[2] |= TMP_KEEPCONTROLS; if (lines[i].flags & ML_MIDPEG) lines[i].args[2] |= TMP_KEEPREALTIME; - /*if (lines[i].flags & ML_NOCLIMB) + if (lines[i].flags & ML_NOCLIMB) lines[i].args[2] |= TMP_ALLPLAYERS; - if (lines[i].flags & ML_MIDSOLID) + /*if (lines[i].flags & ML_MIDSOLID) lines[i].args[2] |= TMP_FREEZETHINKERS;*/ lines[i].args[3] = (lines[i].sidenum[1] != NO_SIDEDEF) ? sides[lines[i].sidenum[1]].textureoffset >> FRACBITS : tag; break; @@ -7857,8 +7858,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) levelfadecol = (ranspecialwipe) ? 0 : 31; - // Close text prompt before freeing the old level - F_EndTextPrompt(false, true); + // Close text prompt before freeing the level + P_EndAllTextPrompts(false, true); LUA_InvalidateLevel(); diff --git a/src/p_spec.c b/src/p_spec.c index 7bec07c92730128796558e508718f0449bfe798f..e8ca0a7dcdc35bc2b351bbd69d54bb374bc590e4 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -20,6 +20,7 @@ #include "g_game.h" #include "p_local.h" #include "p_setup.h" // levelflats for flat animation +#include "p_dialog.h" #include "r_data.h" #include "r_fps.h" #include "r_textures.h" @@ -3594,14 +3595,14 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) case 459: // Control Text Prompt // console player only - if (mo && mo->player && P_IsLocalPlayer(mo->player) && (!bot || bot != mo)) + if (mo && mo->player && (!bot || bot != mo)) { INT32 promptnum = max(0, line->args[0] - 1); INT32 pagenum = max(0, line->args[1] - 1); INT32 postexectag = abs(line->args[3]); boolean closetextprompt = (line->args[2] & TMP_CLOSE); - //boolean allplayers = (line->args[2] & TMP_ALLPLAYERS); + boolean allplayers = (line->args[2] & TMP_ALLPLAYERS); boolean runpostexec = (line->args[2] & TMP_RUNPOSTEXEC); boolean blockcontrols = !(line->args[2] & TMP_KEEPCONTROLS); boolean freezerealtime = !(line->args[2] & TMP_KEEPREALTIME); @@ -3609,12 +3610,12 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) boolean callbynamedtag = (line->args[2] & TMP_CALLBYNAME); if (closetextprompt) - F_EndTextPrompt(false, false); + P_EndTextPrompt(mo->player, false, false); else { if (callbynamedtag && line->stringargs[0] && line->stringargs[0][0]) - F_GetPromptPageByNamedTag(line->stringargs[0], &promptnum, &pagenum); - F_StartTextPrompt(promptnum, pagenum, mo, runpostexec ? postexectag : 0, blockcontrols, freezerealtime); + P_GetPromptPageByNamedTag(line->stringargs[0], &promptnum, &pagenum); + P_StartTextPrompt(mo->player, promptnum, pagenum, runpostexec ? postexectag : 0, blockcontrols, freezerealtime, allplayers); } } break; @@ -8420,6 +8421,7 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor d->docollision = docollision; d->doghostfade = doghostfade; d->exactalpha = exactalpha; + d->dest_exc = NULL; // find any existing thinkers and remove them, then replace with new data P_ResetFakeFloorFader(rover, d, false); diff --git a/src/p_spec.h b/src/p_spec.h index 3bbaba58b69858cfc0d8627bfb2971e8628e6b40..5f2f4c77e1310f74a9e688b604e45c71bb41bd80 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -420,7 +420,7 @@ typedef enum TMP_CALLBYNAME = 1<<2, TMP_KEEPCONTROLS = 1<<3, TMP_KEEPREALTIME = 1<<4, - //TMP_ALLPLAYERS = 1<<5, + TMP_ALLPLAYERS = 1<<5, //TMP_FREEZETHINKERS = 1<<6, } textmappromptflags_t; diff --git a/src/p_tick.c b/src/p_tick.c index 4ab388486db62be9d1fa36d9289ac2b043219e47..49ae93fca43943ebc4c76da55f302ab4762bc126 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -14,6 +14,7 @@ #include "doomstat.h" #include "g_game.h" #include "p_local.h" +#include "p_dialog.h" #include "z_zone.h" #include "s_sound.h" #include "st_stuff.h" @@ -788,7 +789,10 @@ void P_Ticker(boolean run) // Run any "after all the other thinkers" stuff for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { P_PlayerAfterThink(&players[i]); + P_RunTextPrompt(&players[i]); + } PS_START_TIMING(ps_lua_thinkframe_time); LUA_HookThinkFrame(); diff --git a/src/screen.c b/src/screen.c index ca59b251dce1f6f1371af433b010a18b4304e621..2c0d5df661ded99e7e12d006adb3f86ae9f5dcbd 100644 --- a/src/screen.c +++ b/src/screen.c @@ -509,7 +509,7 @@ void SCR_ClosedCaptions(void) if (gamestate == GS_LEVEL) { - if (promptactive) + if (players[displayplayer].promptactive) basey -= 42; else if (splitscreen) basey -= 8; diff --git a/src/st_stuff.c b/src/st_stuff.c index e33d61f36578d5eae7d27ddf5b81138f439d1232..1d4a3ca2c6007080f7dd6a1ac0556369ec5f5fb4 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -31,6 +31,7 @@ #include "m_misc.h" // moviemode #include "m_anigif.h" // cv_gif_downscale #include "p_setup.h" // NiGHTS grading +#include "p_dialog.h" //random index #include "m_random.h"