Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • STJr/SRB2
  • Sryder/SRB2
  • wolfy852/SRB2
  • Alpha2244/SRB2
  • Inuyasha/SRB2
  • yoshibot/SRB2
  • TehRealSalt/SRB2
  • PrisimaTF/SRB2
  • Hatninja/SRB2
  • SteelT/SRB2
  • james/SRB2
  • ShaderWraith/SRB2
  • SinnamonLat/SRB2
  • mazmazz_/SRB2
  • filpAM/SRB2
  • chaoloveicemdboy/SRB2
  • Whooa21/SRB2
  • Machturne/SRB2
  • Golden/SRB2
  • Tatsuru/SRB2
  • Snu/SRB2
  • Zwip-Zwap_Zapony/SRB2
  • fickleheart/SRB2
  • alphaRexJames/SRB2
  • JJK/SRB2
  • diskpoppy/SRB2
  • Hannu_Hanhi/SRB2
  • ZipperQR/SRB2
  • kays/SRB2
  • spherallic/SRB2
  • Zippy_Zolton/SRB2
  • namiishere/SRB2
  • Ors/SRB2
  • SMS_Alfredo/SRB2
  • sonic_edge/SRB2
  • lavla/SRB2
  • ashi/SRB2
  • X.organic/SRB2
  • Fafabis/SRB2
  • Meziu/SRB2
  • v-rob/SRB2
  • tertu/SRB2
  • bitten2up/SRB2
  • flarn2006/SRB2
  • Krabs/SRB2
  • clairebun/SRB2
  • Lactozilla/SRB2
  • thehackstack/SRB2
  • Spice/SRB2
  • win8linux/SRB2
  • JohnFrostFox/SRB2
  • talktoneon726/SRB2
  • Wane/SRB2
  • Lamibe/SRB2
  • spectrumuk2/srb-2
  • nerdyminer18/srb-2
  • 256nil/SRB2
  • ARJr/SRB2
  • Alam/SRB2
  • Zenya/srb-2-marathon-demos
  • Acelite/srb-2-archivedmodifications
  • MIDIMan/SRB2
  • Lach/SRB2
  • Frostiikin/bounce-tweaks
  • Jaden/SRB2
  • Tyron/SRB2
  • Astronight/SRB2
  • Mari0shi06/SRB2
  • aiire/SRB2
  • Galactice/SRB2
  • srb2-ports/srb2-dreamcast
  • sdasdas/SRB2
  • chreas/srb-2-vr
  • StarManiaKG/the-story-of-sinically-rocketing-and-botching-the-2nd
  • LoganAir/SRB2
  • NepDisk/srb-2
  • alufolie91/SRB2
  • Felicia.iso/SRB2
  • twi/SRB2
  • BarrelsOFun/SRB2
  • Speed2411/SRB2
  • Leather_Realms/SRB2
  • Ayemar/SRB2
  • Acelite/SRB2
  • VladDoc/SRB2
  • kaldrum/model-features
  • strawberryfox417/SRB2
  • Lugent/SRB2
  • Rem/SRB2
  • Refrag/SRB2
  • Henry_3230/srb-3230
  • TehPuertoRicanSpartan2/tprs-srb2
  • Leminn/srb-2-marathon-stuff
  • chromaticpipe2/SRB2
  • MiguelGustavo15/SRB2
  • Maru/srb-2-tests
  • SilicDev/SRB2
  • UnmatchedBracket/SRB2
  • HybridDog/SRB2
  • xordspar0/SRB2
  • jsjhbewfhh/SRB2
  • Fancy2209/SRB2
  • Lorsoen/SRB2
  • shindoukin/SRB2
  • GamerOfDays/SRB2
  • Craftyawesome/SRB2
  • tenshi-tensai-tennoji/SRB2
  • Scarfdudebalder/SRB2
  • luigi-budd/srb-2-fix-interplag-lockon
  • mskluesner/SRB2
  • johnpetersa19/SRB2
  • Pheazant/SRB2
  • chromaticpipe2/srb2classic
  • romoney5/SRB2
  • PAS/SRB2Classic
  • BlueStaggo/SRB2
  • Jisk/srb-2-beef-jerky
117 results
Select Git revision
Show changes
......@@ -217,6 +217,7 @@ enum player_e
player_awayviewaiming,
player_spectator,
player_outofcoop,
player_promptactive,
player_bot,
player_botleader,
player_lastbuttons,
......@@ -366,6 +367,7 @@ static const char *const player_opt[] = {
"awayviewaiming",
"spectator",
"outofcoop",
"promptactive",
"bot",
"botleader",
"lastbuttons",
......@@ -810,6 +812,9 @@ static int player_get(lua_State *L)
case player_outofcoop:
lua_pushboolean(L, plr->outofcoop);
break;
case player_promptactive:
lua_pushboolean(L, plr->promptactive);
break;
case player_bot:
lua_pushinteger(L, plr->bot);
break;
......@@ -1332,6 +1337,9 @@ static int player_set(lua_State *L)
case player_outofcoop:
plr->outofcoop = lua_toboolean(L, 3);
break;
case player_promptactive:
// This is purely informative; you're not supposed to set this.
return NOSET;
case player_bot:
return NOSET;
case player_botleader:
......
......@@ -689,7 +689,7 @@ static inline MYFILE *LUA_GetFile(UINT16 wad, UINT16 lump, char **name)
lumpinfo_t *lump_p = &wadfiles[wad]->lumpinfo[lump];
len += 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
*name = malloc(len+1);
sprintf(*name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
snprintf(*name, len+1, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
(*name)[len] = '\0'; // annoying that index takes priority over dereference, but w/e
}
......
......@@ -3298,7 +3298,7 @@ boolean M_Responder(event_t *ev)
if (ch == -1)
return false;
else if (ch == gamecontrol[GC_SYSTEMMENU][0] || ch == gamecontrol[GC_SYSTEMMENU][1]) // allow remappable ESC key
else if (G_IsGameControl(ch, GC_SYSTEMMENU)) // allow remappable ESC key
ch = KEY_ESCAPE;
// F-Keys
......
......@@ -1642,9 +1642,9 @@ boolean M_ScreenshotResponder(event_t *ev)
if (ch >= KEY_MOUSE1 && menuactive) // If it's not a keyboard key, then don't allow it in the menus!
return false;
if (ch == KEY_F8 || ch == gamecontrol[GC_SCREENSHOT][0] || ch == gamecontrol[GC_SCREENSHOT][1]) // remappable F8
if (ch == KEY_F8 || G_IsGameControl(ch, GC_SCREENSHOT)) // remappable F8
M_ScreenShot();
else if (ch == KEY_F9 || ch == gamecontrol[GC_RECORDGIF][0] || ch == gamecontrol[GC_RECORDGIF][1]) // remappable F9
else if (ch == KEY_F9 || G_IsGameControl(ch, GC_RECORDGIF)) // remappable F9
((moviemode) ? M_StopMovie : M_StartMovie)();
else
return false;
......@@ -2272,3 +2272,25 @@ int M_RoundUp(double number)
return (int)number;
}
boolean M_StringToNumber(const char *str, int *out)
{
char *string_end_pos = NULL;
#ifndef AVOID_ERRNO
errno = 0;
#endif
int result = strtol(str, &string_end_pos, 10);
if (string_end_pos == str || *string_end_pos != '\0')
return false;
#ifndef AVOID_ERRNO
if (errno == ERANGE)
return false;
#endif
*out = result;
return true;
}
......@@ -115,6 +115,8 @@ FUNCMATH UINT8 M_CountBits(UINT32 num, UINT8 size);
// Rounds off floating numbers and checks for 0 - 255 bounds
int M_RoundUp(double number);
boolean M_StringToNumber(const char *str, int *out);
#include "w_wad.h"
extern char configfile[MAX_WADPATH];
......
......@@ -10,6 +10,7 @@
/// \brief Tokenizer
#include "m_tokenizer.h"
#include "m_writebuffer.h"
#include "z_zone.h"
tokenizer_t *Tokenizer_Open(const char *inputString, unsigned numTokens)
......@@ -21,6 +22,8 @@ tokenizer_t *Tokenizer_Open(const char *inputString, unsigned numTokens)
tokenizer->endPos = 0;
tokenizer->inputLength = 0;
tokenizer->inComment = 0;
tokenizer->inString = 0;
tokenizer->line = 1;
tokenizer->get = Tokenizer_Read;
if (numTokens < 1)
......@@ -53,7 +56,18 @@ void Tokenizer_Close(tokenizer_t *tokenizer)
Z_Free(tokenizer);
}
static void Tokenizer_DetectComment(tokenizer_t *tokenizer, UINT32 *pos)
static boolean DetectLineBreak(tokenizer_t *tokenizer, size_t pos)
{
if (tokenizer->input[pos] == '\n')
{
tokenizer->line++;
return true;
}
return false;
}
static void DetectComment(tokenizer_t *tokenizer, UINT32 *pos)
{
if (tokenizer->inComment)
return;
......@@ -72,7 +86,7 @@ static void Tokenizer_DetectComment(tokenizer_t *tokenizer, UINT32 *pos)
tokenizer->inComment = 2;
}
static void Tokenizer_ReadTokenString(tokenizer_t *tokenizer, UINT32 i)
static void ReadTokenString(tokenizer_t *tokenizer, UINT32 i)
{
UINT32 tokenLength = tokenizer->endPos - tokenizer->startPos;
if (tokenLength + 1 > tokenizer->capacity[i])
......@@ -94,8 +108,48 @@ const char *Tokenizer_Read(tokenizer_t *tokenizer, UINT32 i)
tokenizer->startPos = tokenizer->endPos;
// If in a string, return the entire string within quotes, except without the quotes.
if (tokenizer->inString == 1)
{
writebuffer_t buf;
M_BufferInit(&buf);
while (tokenizer->input[tokenizer->endPos] != '"' && tokenizer->endPos < tokenizer->inputLength)
{
DetectLineBreak(tokenizer, tokenizer->endPos);
M_BufferWrite(&buf, tokenizer->input[tokenizer->endPos]);
tokenizer->endPos++;
// Escape any quotation marks
if (tokenizer->input[tokenizer->endPos] == '\\' && tokenizer->input[tokenizer->endPos+1] == '"')
{
M_BufferWrite(&buf, '"');
tokenizer->endPos += 2;
}
}
M_BufferWrite(&buf, '\0');
tokenizer->token[i] = (char*)buf.data;
tokenizer->inString = 2;
return tokenizer->token[i];
}
// If just ended a string, return only a quotation mark.
else if (tokenizer->inString == 2)
{
tokenizer->endPos = tokenizer->startPos + 1;
tokenizer->token[i][0] = tokenizer->input[tokenizer->startPos];
tokenizer->token[i][1] = '\0';
tokenizer->inString = 0;
return tokenizer->token[i];
}
// Try to detect comments now, in case we're pointing right at one
Tokenizer_DetectComment(tokenizer, &tokenizer->startPos);
DetectComment(tokenizer, &tokenizer->startPos);
// Find the first non-whitespace char, or else the end of the string trying
while ((tokenizer->input[tokenizer->startPos] == ' '
......@@ -106,8 +160,10 @@ const char *Tokenizer_Read(tokenizer_t *tokenizer, UINT32 i)
|| tokenizer->inComment != 0)
&& tokenizer->startPos < tokenizer->inputLength)
{
boolean inLineBreak = DetectLineBreak(tokenizer, tokenizer->startPos);
// Try to detect comment endings now
if (tokenizer->inComment == 1 && tokenizer->input[tokenizer->startPos] == '\n')
if (tokenizer->inComment == 1 && inLineBreak)
tokenizer->inComment = 0; // End of line for a single-line comment
else if (tokenizer->inComment == 2
&& tokenizer->startPos < tokenizer->inputLength - 1
......@@ -120,15 +176,16 @@ const char *Tokenizer_Read(tokenizer_t *tokenizer, UINT32 i)
}
tokenizer->startPos++;
Tokenizer_DetectComment(tokenizer, &tokenizer->startPos);
DetectComment(tokenizer, &tokenizer->startPos);
}
// If the end of the string is reached, no token is to be read
if (tokenizer->startPos == tokenizer->inputLength) {
if (tokenizer->startPos == tokenizer->inputLength)
{
tokenizer->endPos = tokenizer->inputLength;
return NULL;
}
// Else, if it's one of these three symbols, capture only this one character
// Else, if it's one of these symbols, capture only this one character
else if (tokenizer->input[tokenizer->startPos] == ','
|| tokenizer->input[tokenizer->startPos] == '{'
|| tokenizer->input[tokenizer->startPos] == '}'
......@@ -136,22 +193,15 @@ const char *Tokenizer_Read(tokenizer_t *tokenizer, UINT32 i)
|| tokenizer->input[tokenizer->startPos] == ']'
|| tokenizer->input[tokenizer->startPos] == '='
|| tokenizer->input[tokenizer->startPos] == ':'
|| tokenizer->input[tokenizer->startPos] == '%')
|| tokenizer->input[tokenizer->startPos] == ';'
|| tokenizer->input[tokenizer->startPos] == '%'
|| tokenizer->input[tokenizer->startPos] == '"')
{
tokenizer->endPos = tokenizer->startPos + 1;
tokenizer->token[i][0] = tokenizer->input[tokenizer->startPos];
tokenizer->token[i][1] = '\0';
return tokenizer->token[i];
}
// Return entire string within quotes, except without the quotes.
else if (tokenizer->input[tokenizer->startPos] == '"')
{
tokenizer->endPos = ++tokenizer->startPos;
while (tokenizer->input[tokenizer->endPos] != '"' && tokenizer->endPos < tokenizer->inputLength)
tokenizer->endPos++;
Tokenizer_ReadTokenString(tokenizer, i);
tokenizer->endPos++;
if (tokenizer->input[tokenizer->startPos] == '"')
tokenizer->inString = 1;
return tokenizer->token[i];
}
......@@ -169,15 +219,17 @@ const char *Tokenizer_Read(tokenizer_t *tokenizer, UINT32 i)
&& tokenizer->input[tokenizer->endPos] != '='
&& tokenizer->input[tokenizer->endPos] != ':'
&& tokenizer->input[tokenizer->endPos] != '%'
&& tokenizer->input[tokenizer->endPos] != ';'
&& tokenizer->inComment == 0)
&& tokenizer->endPos < tokenizer->inputLength)
{
tokenizer->endPos++;
// Try to detect comment starts now; if it's in a comment, we don't want it in this token
Tokenizer_DetectComment(tokenizer, &tokenizer->endPos);
DetectComment(tokenizer, &tokenizer->endPos);
}
Tokenizer_ReadTokenString(tokenizer, i);
ReadTokenString(tokenizer, i);
return tokenizer->token[i];
}
......@@ -189,7 +241,7 @@ const char *Tokenizer_SRB2Read(tokenizer_t *tokenizer, UINT32 i)
tokenizer->startPos = tokenizer->endPos;
// Try to detect comments now, in case we're pointing right at one
Tokenizer_DetectComment(tokenizer, &tokenizer->startPos);
DetectComment(tokenizer, &tokenizer->startPos);
// Find the first non-whitespace char, or else the end of the string trying
while ((tokenizer->input[tokenizer->startPos] == ' '
......@@ -201,8 +253,10 @@ const char *Tokenizer_SRB2Read(tokenizer_t *tokenizer, UINT32 i)
|| tokenizer->inComment != 0)
&& tokenizer->startPos < tokenizer->inputLength)
{
boolean inLineBreak = DetectLineBreak(tokenizer, tokenizer->startPos);
// Try to detect comment endings now
if (tokenizer->inComment == 1 && tokenizer->input[tokenizer->startPos] == '\n')
if (tokenizer->inComment == 1 && inLineBreak)
tokenizer->inComment = 0; // End of line for a single-line comment
else if (tokenizer->inComment == 2
&& tokenizer->startPos < tokenizer->inputLength - 1
......@@ -215,7 +269,7 @@ const char *Tokenizer_SRB2Read(tokenizer_t *tokenizer, UINT32 i)
}
tokenizer->startPos++;
Tokenizer_DetectComment(tokenizer, &tokenizer->startPos);
DetectComment(tokenizer, &tokenizer->startPos);
}
// If the end of the string is reached, no token is to be read
......@@ -238,9 +292,12 @@ const char *Tokenizer_SRB2Read(tokenizer_t *tokenizer, UINT32 i)
{
tokenizer->endPos = ++tokenizer->startPos;
while (tokenizer->input[tokenizer->endPos] != '"' && tokenizer->endPos < tokenizer->inputLength)
{
DetectLineBreak(tokenizer, tokenizer->endPos);
tokenizer->endPos++;
}
Tokenizer_ReadTokenString(tokenizer, i);
ReadTokenString(tokenizer, i);
tokenizer->endPos++;
return tokenizer->token[i];
}
......@@ -260,10 +317,10 @@ const char *Tokenizer_SRB2Read(tokenizer_t *tokenizer, UINT32 i)
{
tokenizer->endPos++;
// Try to detect comment starts now; if it's in a comment, we don't want it in this token
Tokenizer_DetectComment(tokenizer, &tokenizer->endPos);
DetectComment(tokenizer, &tokenizer->endPos);
}
Tokenizer_ReadTokenString(tokenizer, i);
ReadTokenString(tokenizer, i);
return tokenizer->token[i];
}
......
......@@ -24,6 +24,8 @@ typedef struct Tokenizer
UINT32 endPos;
UINT32 inputLength;
UINT8 inComment; // 0 = not in comment, 1 = // Single-line, 2 = /* Multi-line */
UINT8 inString;
int line;
const char *(*get)(struct Tokenizer*, UINT32);
} tokenizer_t;
......
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// 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 m_writebuffer.c
/// \brief Basic resizeable buffer
#include "m_writebuffer.h"
#include "z_zone.h"
void M_BufferInit(writebuffer_t *buffer)
{
buffer->data = NULL;
buffer->pos = 0;
buffer->capacity = 16;
}
void M_BufferFree(writebuffer_t *buffer)
{
Z_Free(buffer->data);
M_BufferInit(buffer);
}
void M_BufferWrite(writebuffer_t *buffer, UINT8 chr)
{
if (!buffer->data || buffer->pos >= buffer->capacity)
{
buffer->capacity *= 2;
buffer->data = Z_Realloc(buffer->data, buffer->capacity, PU_STATIC, NULL);
}
buffer->data[buffer->pos] = chr;
buffer->pos++;
}
void M_BufferMemWrite(writebuffer_t *buffer, UINT8 *mem, size_t count)
{
if (!count)
return;
size_t oldpos = buffer->pos;
size_t oldcapacity = buffer->capacity;
buffer->pos += count;
while (buffer->pos >= buffer->capacity)
buffer->capacity *= 2;
if (!buffer->data || buffer->capacity != oldcapacity)
buffer->data = Z_Realloc(buffer->data, buffer->capacity, PU_STATIC, NULL);
memcpy(&buffer->data[oldpos], mem, count);
}
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// 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 m_writebuffer.h
/// \brief Basic resizeable buffer
#ifndef __M_TEXTBUFFER__
#define __M_TEXTBUFFER__
#include "doomtype.h"
typedef struct
{
UINT8 *data;
size_t pos;
size_t capacity;
} writebuffer_t;
void M_BufferInit(writebuffer_t *buffer);
void M_BufferFree(writebuffer_t *buffer);
void M_BufferWrite(writebuffer_t *buffer, UINT8 chr);
void M_BufferMemWrite(writebuffer_t *buffer, UINT8 *mem, size_t count);
#endif
......@@ -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,8 @@ 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);
RedistributeSpecialStageSpheres(playernum);
LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting
......
......@@ -26,6 +26,7 @@
#include "../r_skins.h"
#include "../p_local.h"
#include "../p_setup.h"
#include "../p_dialog.h"
#include "../s_sound.h"
#include "../i_sound.h"
#include "../m_misc.h"
......@@ -77,6 +78,8 @@ static void Got_RandomSeed(UINT8 **cp, INT32 playernum);
static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum);
static void Got_Teamchange(UINT8 **cp, INT32 playernum);
static void Got_Clearscores(UINT8 **cp, INT32 playernum);
static void Got_TextPromptChoice(UINT8 **cp, INT32 playernum);
static void Got_TextPromptConfirm(UINT8 **cp, INT32 playernum);
static void PointLimit_OnChange(void);
static void TimeLimit_OnChange(void);
......@@ -431,7 +434,9 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
"SUICIDE",
"LUACMD",
"LUAVAR",
"LUAFILE"
"LUAFILE",
"DIALOGCHOICE",
"DIALOGCONFIRM"
};
// =========================================================================
......@@ -468,6 +473,8 @@ void D_RegisterServerCommands(void)
RegisterNetXCmd(XD_RUNSOC, Got_RunSOCcmd);
RegisterNetXCmd(XD_LUACMD, Got_Luacmd);
RegisterNetXCmd(XD_LUAFILE, Got_LuaFile);
RegisterNetXCmd(XD_DIALOGCHOICE, Got_TextPromptChoice);
RegisterNetXCmd(XD_DIALOGCONFIRM, Got_TextPromptConfirm);
// Remote Administration
COM_AddCommand("password", Command_Changepassword_f, COM_LUA);
......@@ -2840,6 +2847,56 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
P_CheckSurvivors();
}
void D_SendTextPromptChoice(INT32 choice, UINT8 localplayer)
{
UINT8 buf[sizeof(INT32)];
UINT8 *buf_p = buf;
WRITEINT32(buf_p, choice);
if (localplayer == 1)
SendNetXCmd2(XD_DIALOGCHOICE, buf, sizeof(buf));
else
SendNetXCmd(XD_DIALOGCHOICE, buf, sizeof(buf));
}
void D_SendTextPromptConfirm(INT32 choice, UINT8 localplayer)
{
UINT8 buf[sizeof(INT32)];
UINT8 *buf_p = buf;
WRITEINT32(buf_p, choice);
if (localplayer == 1)
SendNetXCmd2(XD_DIALOGCONFIRM, buf, sizeof(buf));
else
SendNetXCmd(XD_DIALOGCONFIRM, buf, sizeof(buf));
}
static void Got_TextPromptChoice(UINT8 **cp, INT32 playernum)
{
INT32 choice = READINT32(*cp);
if (!P_SetCurrentDialogChoice(&players[playernum], choice))
{
CONS_Alert(CONS_WARNING, M_GetText("Illegal text prompt choice command received from %s\n"), player_names[playernum]);
if (server)
SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
}
}
static void Got_TextPromptConfirm(UINT8 **cp, INT32 playernum)
{
INT32 choice = READINT32(*cp);
if (!P_SelectDialogChoice(&players[playernum], choice))
{
CONS_Alert(CONS_WARNING, M_GetText("Illegal text prompt confirmation command received from %s\n"), player_names[playernum]);
if (server)
SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
}
}
//
// Attempts to make password system a little sane without
// rewriting the entire goddamn XD_file system
......
......@@ -124,29 +124,31 @@ extern consvar_t cv_freedemocamera;
typedef enum
{
XD_NAMEANDCOLOR = 1,
XD_WEAPONPREF, // 2
XD_KICK, // 3
XD_NETVAR, // 4
XD_SAY, // 5
XD_MAP, // 6
XD_EXITLEVEL, // 7
XD_ADDFILE, // 8
XD_ADDFOLDER, // 9
XD_PAUSE, // 10
XD_ADDPLAYER, // 11
XD_TEAMCHANGE, // 12
XD_CLEARSCORES, // 13
XD_VERIFIED, // 14
XD_RANDOMSEED, // 15
XD_RUNSOC, // 16
XD_REQADDFILE, // 17
XD_REQADDFOLDER,// 18
XD_SETMOTD, // 19
XD_SUICIDE, // 20
XD_DEMOTED, // 21
XD_LUACMD, // 22
XD_LUAVAR, // 23
XD_LUAFILE, // 24
XD_WEAPONPREF, // 2
XD_KICK, // 3
XD_NETVAR, // 4
XD_SAY, // 5
XD_MAP, // 6
XD_EXITLEVEL, // 7
XD_ADDFILE, // 8
XD_ADDFOLDER, // 9
XD_PAUSE, // 10
XD_ADDPLAYER, // 11
XD_TEAMCHANGE, // 12
XD_CLEARSCORES, // 13
XD_VERIFIED, // 14
XD_RANDOMSEED, // 15
XD_RUNSOC, // 16
XD_REQADDFILE, // 17
XD_REQADDFOLDER, // 18
XD_SETMOTD, // 19
XD_SUICIDE, // 20
XD_DEMOTED, // 21
XD_LUACMD, // 22
XD_LUAVAR, // 23
XD_LUAFILE, // 24
XD_DIALOGCHOICE, // 25
XD_DIALOGCONFIRM, // 25
MAXNETXCMD
} netxcmd_t;
......@@ -198,6 +200,8 @@ void D_RegisterClientCommands(void);
void CleanupPlayerName(INT32 playernum, const char *newname);
boolean EnsurePlayerNameIsGood(char *name, INT32 playernum);
void D_SendPlayerConfig(void);
void D_SendTextPromptChoice(INT32 choice, UINT8 localplayer);
void D_SendTextPromptConfirm(INT32 choice, UINT8 localplayer);
void Command_ExitGame_f(void);
void Command_Retry_f(void);
void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
......
// 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_dialogscript.h"
#include "p_local.h"
#include "g_game.h"
#include "g_input.h"
#include "s_sound.h"
#include "v_video.h"
#include "r_textures.h"
#include "m_writebuffer.h"
#include "hu_stuff.h"
#include "w_wad.h"
#include "z_zone.h"
#include "fastcmp.h"
#include <errno.h>
static INT16 chevronAnimCounter;
void P_ResetTextWriter(textwriter_t *writer, const char *basetext, size_t basetextlength)
{
writer->basetext = basetext;
writer->basetextlength = basetextlength;
writer->writeptr = writer->baseptr = 0;
writer->textspeed = 9;
writer->textcount = TICRATE/2;
if (writer->disptext && writer->disptextsize)
memset(writer->disptext, 0, writer->disptextsize);
}
// ==================
// TEXT PROMPTS
// ==================
static void P_LockPlayerControls(player_t *player);
static void P_UpdatePromptGfx(dialog_t *dialog);
static void P_PlayDialogSound(player_t *player, sfxenum_t sound);
static textprompt_t *P_GetTextPromptByID(INT32 promptnum)
{
if (promptnum < 0 || promptnum >= MAX_PROMPTS || !textprompts[promptnum])
return NULL;
return textprompts[promptnum];
}
INT32 P_GetTextPromptByName(const char *name)
{
for (INT32 i = 0; i < MAX_PROMPTS; i++)
{
if (textprompts[i] && textprompts[i]->name && strcmp(name, textprompts[i]->name) == 0)
return i;
}
return -1;
}
INT32 P_GetPromptPageByName(textprompt_t *prompt, const char *name)
{
for (INT32 i = 0; i < prompt->numpages; i++)
{
const char *pagename = prompt->page[i].pagename;
if (pagename && strcmp(name, pagename) == 0)
return i;
}
return -1;
}
void P_InitTextPromptPage(textpage_t *page)
{
page->lines = 4; // default line count to four
page->textspeed = TICRATE/5; // default text speed to 1/5th of a second
page->backcolor = 1; // default to gray
page->hidehud = 1; // hide appropriate HUD elements
}
void P_FreeTextPrompt(textprompt_t *prompt)
{
if (!prompt)
return;
for (INT32 i = 0; i < prompt->numpages; i++)
{
textpage_t *page = &prompt->page[i];
for (INT32 j = 0; j < page->numchoices; j++)
{
promptchoice_t *choice = &page->choices[j];
Z_Free(choice->text);
Z_Free(choice->nextpromptname);
Z_Free(choice->nextpagename);
}
Z_Free(page->choices);
Z_Free(page->pics);
Z_Free(page->pagename);
Z_Free(page->nextpromptname);
Z_Free(page->nextpagename);
Z_Free(page->name);
Z_Free(page->text);
}
Z_Free(prompt->name);
Z_Free(prompt);
}
dialog_t *P_GetPlayerDialog(player_t *player)
{
return globaltextprompt ? globaltextprompt : player->textprompt;
}
typedef struct
{
UINT8 pagelines;
boolean rightside;
INT32 boxh;
INT32 texth;
INT32 texty;
INT32 namey;
INT32 chevrony;
INT32 textx;
INT32 textr;
INT32 choicesx;
INT32 choicesy;
struct {
INT32 x, y, w, h;
} choicesbox;
} dialog_geometry_t;
static void GetPageTextGeometry(dialog_t *dialog, dialog_geometry_t *geo)
{
boolean has_icon = dialog->iconlump != LUMPERROR;
INT32 pagelines = dialog->page->lines;
boolean rightside = has_icon && dialog->page->rightside;
// Vertical calculations
INT32 boxh = pagelines*2;
INT32 texth = dialog->speaker[0] ? (pagelines-1)*2 : pagelines*2; // name takes up first line if it exists
INT32 namey = BASEVIDHEIGHT - ((boxh * 4) + (boxh/2)*4);
geo->texty = BASEVIDHEIGHT - ((texth * 4) + (texth/2)*4);
geo->namey = namey;
geo->chevrony = BASEVIDHEIGHT - (((1*2) * 4) + ((1*2)/2)*4); // force on last line
geo->pagelines = pagelines;
geo->rightside = rightside;
geo->boxh = boxh;
geo->texth = texth;
// Horizontal calculations
// Shift text to the right if we have a character icon on the left side
// Add 4 margin against icon
geo->textx = (has_icon && !rightside) ? ((boxh * 4) + (boxh/2)*4) + 4 : 4;
geo->textr = rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4) + 4) : BASEVIDWIDTH-4;
// Calculate choices box
if (dialog->numchoices)
{
const int choices_box_spacing = 4;
if (dialog->longestchoice == -1)
{
for (INT32 i = 0; i < dialog->numchoices; i++)
{
INT32 width = V_StringWidth(dialog->page->choices[i].text, 0);
if (width > dialog->longestchoice)
dialog->longestchoice = width;
}
}
INT32 longestchoice = dialog->longestchoice + 16;
INT32 choices_height = dialog->numchoices * 10;
INT32 choices_x, choices_y, choices_h, choices_w = longestchoice + (choices_box_spacing * 2);
if (dialog->page->choicesleftside)
choices_x = 16;
else
choices_x = (BASEVIDWIDTH - 8) - choices_w;
choices_h = choices_height + (choices_box_spacing * 2);
choices_y = namey - 8 - choices_h;
geo->choicesbox.x = choices_x;
geo->choicesbox.w = choices_w;
geo->choicesbox.y = choices_y;
geo->choicesbox.h = choices_h;
geo->choicesx = choices_x + choices_box_spacing;
geo->choicesy = choices_y + choices_box_spacing;
}
else
{
geo->choicesbox.x = 0;
geo->choicesbox.y = 0;
geo->choicesbox.w = 0;
geo->choicesbox.w = 0;
geo->choicesx = 0;
geo->choicesy = 0;
}
}
static fixed_t F_GetPromptHideHudBound(dialog_t *dialog)
{
dialog_geometry_t geo;
INT32 boxh;
if (!dialog->prompt || !dialog->page || !dialog->page->hidehud)
return 0;
else if (dialog->page->hidehud == 2) // hide all
return BASEVIDHEIGHT;
GetPageTextGeometry(dialog, &geo);
// calc boxheight (see V_DrawPromptBack)
boxh = geo.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 (!stplyr || !stplyr->promptactive)
return false;
dialog_t *dialog = P_GetPlayerDialog(stplyr);
if (!dialog)
return false;
if (!dialog->prompt || !dialog->page || !dialog->page->hidehud)
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 (!stplyr || !stplyr->promptactive)
return false;
dialog_t *dialog = P_GetPlayerDialog(stplyr);
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_SetMetaPage(textpage_t *page, textpage_t *metapage)
{
Z_Free(page->name);
if (metapage->name)
page->name = Z_StrDup(metapage->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
}
void P_SetPicsMetaPage(textpage_t *page, textpage_t *metapage)
{
page->numpics = metapage->numpics;
page->picmode = metapage->picmode;
page->pictoloop = metapage->pictoloop;
page->pictostart = metapage->pictostart;
page->pics = Z_Realloc(page->pics, sizeof(cutscene_pic_t) * page->numpics, PU_STATIC, NULL);
memcpy(page->pics, metapage->pics, sizeof(cutscene_pic_t) * page->numpics);
}
void P_SetDialogSpeaker(dialog_t *dialog, const char *speaker)
{
if (!speaker || !strlen(speaker))
{
dialog->speaker[0] = '\0';
return;
}
strlcpy(dialog->speaker, speaker, sizeof(dialog->speaker));
}
void P_SetDialogIcon(dialog_t *dialog, const char *icon)
{
dialog->iconlump = LUMPERROR;
if (!icon || !strlen(icon))
{
dialog->icon[0] = '\0';
return;
}
strlcpy(dialog->icon, icon, sizeof(dialog->icon));
lumpnum_t iconlump = W_CheckNumForLongName(icon);
if (iconlump != LUMPERROR && W_IsValidPatchNum(iconlump))
dialog->iconlump = iconlump;
}
static void P_DialogGetDispText(char *disptext, const char *text, size_t length)
{
for (size_t i = 0; i < length; i++)
{
const char *c = &text[i];
if (*c == '\0')
{
*disptext = *c;
return;
}
const UINT8 *code = (const UINT8*)c;
if (*code == TP_OP_CONTROL)
{
i += P_DialogSkipOpcode(code + 1);
continue;
}
*disptext = *c;
disptext++;
}
}
enum
{
DIALOG_WRITETEXT_DONE,
DIALOG_WRITETEXT_TALK,
DIALOG_WRITETEXT_SILENT
};
static UINT8 P_DialogWriteText(dialog_t *dialog, textwriter_t *writer)
{
const UINT8 *baseptr = (const UINT8*)writer->basetext;
if (!baseptr || writer->baseptr >= writer->basetextlength)
return DIALOG_WRITETEXT_DONE;
UINT8 lastchar = '\0';
writer->numtowrite = 1;
const int max_speed = 8;
if (writer->boostspeed && !writer->paused)
{
// When the player is holding jump or spin
writer->numtowrite = max_speed;
}
else
{
// Don't draw any characters if the count was 1 or more when we started
if (--writer->textcount >= 0)
return DIALOG_WRITETEXT_SILENT;
writer->paused = false;
if (writer->textspeed >= 0 && writer->textspeed < max_speed-1)
writer->numtowrite = max_speed - writer->textspeed;
}
while (writer->numtowrite > 0)
{
const UINT8 *c = &baseptr[writer->baseptr];
if (!*c)
return DIALOG_WRITETEXT_DONE;
const UINT8 *code = (const UINT8*)c;
if (*code == TP_OP_CONTROL)
{
code = P_DialogRunOpcode(code + 1, dialog, writer);
writer->baseptr = code - baseptr;
lastchar = '\0';
continue;
}
lastchar = *c;
writer->writeptr++;
writer->baseptr++;
if (writer->numtowrite <= 0)
break;
// Ignore non-printable characters
// (that is, decrement numtowrite if this character is printable.)
if (lastchar < 0x80 && writer->textspeed >= 0)
--writer->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 > max_speed-1)
writer->textcount = writer->textspeed - max_speed-1;
}
if (lastchar == '\0' || isspace(lastchar))
return DIALOG_WRITETEXT_SILENT;
else
return DIALOG_WRITETEXT_TALK;
}
static void P_DialogSetDisplayText(dialog_t *dialog)
{
P_DialogGetDispText(dialog->disptext, dialog->pagetext, dialog->pagetextlength);
dialog_geometry_t geo;
GetPageTextGeometry(dialog, &geo);
V_WordWrapInPlace(geo.textx, geo.textr, 0, dialog->disptext);
}
static char *P_ProcessPageText(dialog_t *dialog, UINT8 *pagetext, size_t textlength, size_t *outlength)
{
writebuffer_t buf;
M_BufferInit(&buf);
UINT8 *text = pagetext;
UINT8 *end = pagetext + textlength;
while (text < end)
{
if (!P_DialogPreprocessOpcode(dialog, &text, &buf))
{
M_BufferWrite(&buf, *text);
text++;
}
}
*outlength = buf.pos;
return (char*)buf.data;
}
void P_SetDialogText(dialog_t *dialog, char *pagetext, size_t textlength)
{
Z_Free(dialog->pagetext);
if (pagetext && pagetext[0])
dialog->pagetext = P_ProcessPageText(dialog, (UINT8 *)pagetext, textlength, &dialog->pagetextlength);
else
{
dialog->pagetextlength = 0;
dialog->pagetext = Z_StrDup("");
}
Z_Free(dialog->disptext);
dialog->disptextsize = textlength + 1;
dialog->disptext = Z_Calloc(dialog->disptextsize, PU_STATIC, NULL);
textwriter_t *writer = &dialog->writer;
P_ResetTextWriter(writer, dialog->pagetext, dialog->pagetextlength);
INT32 textspeed = dialog->page->textspeed;
if (textspeed >= -1)
writer->textspeed = textspeed;
else
writer->textspeed = TICRATE/5;
writer->textcount = 0; // no delay in beginning
writer->boostspeed = false; // don't print 8 characters to start
P_DialogSetDisplayText(dialog);
// Immediately type the first character
P_DialogWriteText(dialog, writer);
}
static void P_DialogStartPage(player_t *player, dialog_t *dialog)
{
dialog->paused = false;
dialog->gotonext = false;
dialog->jumpdown = 0;
dialog->spindown = 0;
// 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;
dialog->longestchoice = -1;
if (dialog->page->numchoices > 0)
{
INT32 startchoice = dialog->page->startchoice ? dialog->page->startchoice - 1 : 0;
INT32 nochoice = dialog->page->nochoice ? dialog->page->nochoice - 1 : 0;
dialog->numchoices = dialog->page->numchoices;
if (startchoice >= 0 && startchoice < dialog->numchoices)
dialog->curchoice = startchoice;
if (nochoice >= 0 && nochoice < dialog->numchoices)
dialog->nochoice = nochoice;
}
else
{
dialog->numchoices = 0;
dialog->curchoice = 0;
dialog->nochoice = -1;
}
dialog->showchoices = false;
dialog->selectedchoice = false;
// gfx
dialog->numpics = dialog->page->numpics;
dialog->pics = dialog->page->pics;
if (dialog->pics)
{
dialog->picnum = dialog->page->pictostart;
dialog->pictoloop = dialog->page->pictoloop > 0 ? dialog->page->pictoloop - 1 : 0;
dialog->pictimer = dialog->pics[dialog->picnum].duration;
dialog->picmode = dialog->page->picmode;
}
else
{
dialog->picnum = 0;
dialog->pictoloop = 0;
dialog->pictimer = 0;
dialog->picmode = 0;
}
// Set up narrator
P_SetDialogSpeaker(dialog, dialog->page->name);
P_SetDialogIcon(dialog, dialog->page->iconname);
dialog->iconflip = dialog->page->iconflip;
// Prepare page text
P_SetDialogText(dialog, dialog->page->text, dialog->page->textlength);
// music change
if (P_IsLocalPlayer(player) || globaltextprompt)
{
if (dialog->page->musswitch[0])
{
S_ChangeMusic(dialog->page->musswitch,
dialog->page->musswitchflags,
dialog->page->musicloop);
}
else if (dialog->page->restoremusic)
{
S_ChangeMusic(mapheaderinfo[gamemap-1]->musname,
mapheaderinfo[gamemap-1]->mustrack,
true);
}
}
chevronAnimCounter = 0;
}
static boolean P_LoadNextPageAndPrompt(player_t *player, dialog_t *dialog, INT32 nextprompt, INT32 nextpage)
{
textprompt_t *oldprompt = dialog->prompt;
// 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);
return false;
}
P_DialogStartPage(player, dialog);
return true;
}
static void P_GetNextPromptAndPage(textprompt_t *dialog, char *nextpromptname, char *nextpagename, char *nexttag, INT32 nextprompt, INT32 nextpage, INT32 *foundprompt, INT32 *foundpage)
{
INT32 prompt = INT32_MAX, page = INT32_MAX;
if (nextpromptname && nextpromptname[0])
prompt = P_GetTextPromptByName(nextpromptname);
else if (nextprompt)
prompt = nextprompt - 1;
textprompt_t *nextdialog = P_GetTextPromptByID(prompt);
if (!nextdialog)
nextdialog = dialog;
if (nextpagename && nextpagename[0])
{
if (nextdialog)
page = P_GetPromptPageByName(nextdialog, nextpagename);
}
else if (nextpage)
page = nextpage - 1;
if (nexttag && nexttag[0])
P_GetPromptPageByNamedTag(nexttag, &prompt, &page);
*foundprompt = prompt;
*foundpage = page;
}
static void P_PageGetNextPromptAndPage(textprompt_t *prompt, textpage_t *page, INT32 *nextprompt, INT32 *nextpage)
{
P_GetNextPromptAndPage(prompt,
page->nextpromptname, page->nextpagename, page->nexttag,
page->nextprompt, page->nextpage,
nextprompt, nextpage);
}
static void P_ChoiceGetNextPromptAndPage(textprompt_t *prompt, promptchoice_t *choice, INT32 *nextprompt, INT32 *nextpage)
{
P_GetNextPromptAndPage(prompt,
choice->nextpromptname, choice->nextpagename, choice->nexttag,
choice->nextprompt, choice->nextpage,
nextprompt, nextpage);
}
static boolean P_AdvanceToNextPage(player_t *player, dialog_t *dialog)
{
textprompt_t *curprompt = dialog->prompt;
textpage_t *curpage = dialog->page;
if (curpage->exectag)
P_LinedefExecute(curpage->exectag, player->mo, NULL);
if (!player->promptactive || dialog->prompt != curprompt || dialog->page != curpage)
return false;
if (curpage->endprompt)
{
P_EndTextPrompt(player, false, false);
return false;
}
INT32 nextprompt = INT32_MAX, nextpage = INT32_MAX;
P_PageGetNextPromptAndPage(dialog->prompt, curpage, &nextprompt, &nextpage);
return P_LoadNextPageAndPrompt(player, dialog, nextprompt, nextpage);
}
static boolean P_ExecuteChoice(player_t *player, dialog_t *dialog, promptchoice_t *choice)
{
textprompt_t *curprompt = dialog->prompt;
textpage_t *curpage = dialog->page;
if (choice->exectag)
P_LinedefExecute(choice->exectag, player->mo, NULL);
if (!player->promptactive || dialog->prompt != curprompt || dialog->page != curpage)
return false;
if (choice->endprompt)
{
P_EndTextPrompt(player, false, false);
return false;
}
INT32 nextprompt = INT32_MAX, nextpage = INT32_MAX;
P_ChoiceGetNextPromptAndPage(curprompt, choice, &nextprompt, &nextpage);
return P_LoadNextPageAndPrompt(player, dialog, nextprompt, nextpage);
}
void P_FreeDialog(dialog_t *dialog)
{
if (dialog)
{
Z_Free(dialog->writer.disptext);
Z_Free(dialog->pagetext);
Z_Free(dialog->disptext);
Z_Free(dialog);
}
}
static void P_FreePlayerDialog(player_t *player)
{
if (player->textprompt == globaltextprompt)
return;
P_FreeDialog(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
if (dialog->jumpdown)
player->pflags |= PF_JUMPDOWN;
if (dialog->spindown)
player->pflags |= PF_SPINDOWN;
}
}
}
if ((promptwasactive || forceexec) && !noexec)
return postexectag;
return 0;
}
static void P_EndGlobalTextPrompt(boolean forceexec, boolean noexec)
{
if (!globaltextprompt)
return;
INT16 postexectag = 0;
player_t *player = globaltextprompt->player;
if (player)
{
if ((player->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_FreeDialog(globaltextprompt);
globaltextprompt = NULL;
if (postexectag)
P_LinedefExecute(postexectag, player->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;
// 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->player = 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(player, 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 P_RunDialog(player_t *player)
{
if (!player || !player->promptactive)
return;
dialog_t *dialog = P_GetPlayerDialog(player);
if (!dialog)
{
player->promptactive = false;
return;
}
player_t *promptplayer = dialog->player;
if (!promptplayer)
return;
// Don't update if the player is a spectator, or quit the game
if (promptplayer->spectator || promptplayer->quittime)
return;
// for the chevron
if (player == &players[displayplayer])
{
if (--chevronAnimCounter <= 0)
chevronAnimCounter = 8;
}
// Block player controls
if (dialog->blockcontrols)
P_LockPlayerControls(player);
// Stop here if this player isn't who started this dialog
if (player != promptplayer)
return;
textwriter_t *writer = &dialog->writer;
writer->boostspeed = false;
// button handling
if (dialog->page->timetonext)
{
// same procedure as below, just without the button handling
if (dialog->timetonext >= 1)
dialog->timetonext--;
if (!dialog->timetonext)
{
P_AdvanceToNextPage(player, dialog);
return;
}
else if (!dialog->paused)
{
INT32 write = P_DialogWriteText(dialog, writer);
if (write == DIALOG_WRITETEXT_TALK)
{
if (dialog->page->textsfx)
P_PlayDialogSound(player, dialog->page->textsfx);
}
}
}
else
{
if (dialog->blockcontrols)
{
boolean gotonext = dialog->gotonext;
// Handle choices
if (dialog->showchoices)
{
if (dialog->selectedchoice)
{
dialog->selectedchoice = false;
P_PlayDialogSound(player, sfx_menu1);
P_ExecuteChoice(player, dialog, &dialog->page->choices[dialog->curchoice]);
return;
}
}
else
{
if (player->cmd.buttons & BT_JUMP)
{
if (dialog->jumpdown < TICRATE)
dialog->jumpdown++;
}
else
dialog->jumpdown = 0;
if (player->cmd.buttons & BT_SPIN)
{
if (dialog->spindown < TICRATE)
dialog->spindown++;
}
else
dialog->spindown = 0;
}
if (dialog->jumpdown || dialog->spindown)
{
if (dialog->timetonext > 1)
dialog->timetonext--;
else if (writer->baseptr) // don't set boost if we just reset the string
writer->boostspeed = true; // only after a slight delay
if (dialog->paused)
{
if (dialog->jumpdown == 1 || dialog->spindown == 1)
{
dialog->paused = false;
dialog->timetonext = TICRATE/10;
writer->boostspeed = false;
}
}
else if (!dialog->timetonext && !dialog->showchoices) // timetonext is 0 when finished generating text
gotonext = true;
}
if (gotonext)
{
if (P_AdvanceToNextPage(player, dialog))
P_PlayDialogSound(player, sfx_menu1);
return;
}
}
// never show the chevron if we can't toggle pages
if (!dialog->page->text || !dialog->page->text[0])
dialog->timetonext = !dialog->blockcontrols;
// generate letter-by-letter text
if (!dialog->paused)
{
INT32 write = P_DialogWriteText(dialog, writer);
if (write != DIALOG_WRITETEXT_DONE)
{
if (write == DIALOG_WRITETEXT_TALK && dialog->page->textsfx)
P_PlayDialogSound(player, dialog->page->textsfx);
}
else
{
if (dialog->blockcontrols && !dialog->showchoices && dialog->numchoices)
dialog->showchoices = true;
dialog->timetonext = !dialog->blockcontrols;
}
}
}
P_UpdatePromptGfx(dialog);
}
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->pics || 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--;
}
static void P_PlayDialogSound(player_t *player, sfxenum_t sound)
{
if (P_IsLocalPlayer(player) || &players[displayplayer] == player)
S_StartSound(NULL, sound);
}
boolean P_SetCurrentDialogChoice(player_t *player, INT32 choice)
{
if (!player->promptactive)
return false;
dialog_t *dialog = P_GetPlayerDialog(player);
if (!dialog || choice < 0 || choice >= dialog->numchoices)
return false;
dialog->curchoice = choice;
P_PlayDialogSound(player, sfx_menu1);
return true;
}
boolean P_SelectDialogChoice(player_t *player, INT32 choice)
{
if (!player->promptactive)
return false;
dialog_t *dialog = P_GetPlayerDialog(player);
if (!dialog || choice < 0 || choice >= dialog->numchoices)
return false;
dialog->curchoice = choice;
dialog->selectedchoice = true;
return true;
}
static void F_DrawDialogText(INT32 x, INT32 y, INT32 option, const char *string, size_t numchars, boolean do_line_breaks, const UINT8 *highlight)
{
INT32 w, c, cx = x, cy = y, dup, scrwidth, left = 0;
const char *ch = string;
INT32 charflags = option & V_CHARCOLORMASK;
const UINT8 *colormap = NULL;
const INT32 spacewidth = 4;
INT32 lowercase = (option & V_ALLOWLOWERCASE);
option &= ~V_FLIP; // which is also shared with V_ALLOWLOWERCASE...
if (option & V_NOSCALESTART)
{
dup = vid.dup;
scrwidth = vid.width;
}
else
{
dup = 1;
scrwidth = vid.width/vid.dup;
left = (scrwidth - BASEVIDWIDTH)/2;
scrwidth -= left;
}
if (option & V_NOSCALEPATCH)
scrwidth *= vid.dup;
for (size_t i = 0; i < numchars; ch++, i++)
{
if (!*ch)
break;
if (*ch & 0x80) //color parsing -x 2.16.09
{
// manually set flags override color codes
charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
continue;
}
if (*ch == '\n')
{
if (do_line_breaks)
{
cx = x;
cy += 12*dup;
}
continue;
}
c = *ch;
if (!lowercase)
c = toupper(c);
c -= HU_FONTSTART;
// character does not exist or is a space
if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
{
cx += spacewidth * dup;
continue;
}
w = hu_font[c]->width * dup;
if (cx > scrwidth)
continue;
if (cx+left + w < 0) //left boundary check
{
cx += w;
continue;
}
colormap = V_GetStringColormap(charflags);
if (!colormap)
colormap = highlight;
V_DrawFixedPatch(cx<<FRACBITS, cy<<FRACBITS, FRACUNIT, option, hu_font[c], colormap);
cx += w;
}
}
static void F_DrawDialogIcon(dialog_t *dialog, dialog_geometry_t *geo, INT32 flags)
{
INT32 iconx, icony, scale, scaledsize;
INT32 width, height;
INT32 boxh = geo->boxh;
INT32 namey = geo->namey;
boolean rightside = geo->rightside;
patch_t *patch = NULL;
if (dialog->iconlump != LUMPERROR)
{
patch = W_CachePatchLongName(dialog->icon, PU_PATCH_LOWPRIORITY);
if (!patch)
return;
width = patch->width;
height = patch->height;
}
else
return;
// scale and center
if (width > height)
{
scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, width);
scaledsize = FixedMul(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 (height > width)
{
scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, height);
scaledsize = FixedMul(width, scale);
iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
icony = namey << FRACBITS;
iconx += FixedDiv(FixedMul(height, scale) - scaledsize, 2);
}
else
{
scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, width);
iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
icony = namey << FRACBITS;
}
if (dialog->iconflip)
{
iconx += FixedMul(width, scale) << FRACBITS;
flags |= V_FLIP;
}
V_DrawFixedPatch(iconx, icony, scale, flags, patch, NULL);
}
void F_TextPromptDrawer(void)
{
if (!stplyr || !stplyr->promptactive)
return;
dialog_t *dialog = P_GetPlayerDialog(stplyr);
if (!dialog)
return;
dialog_geometry_t geo;
INT32 draw_flags = V_PERPLAYER;
INT32 snap_flags = V_SNAPTOBOTTOM;
// Data
patch_t *patch;
GetPageTextGeometry(dialog, &geo);
INT32 boxh = geo.boxh, texty = geo.texty, namey = geo.namey, chevrony = geo.chevrony;
INT32 textx = geo.textx, textr = geo.textr;
INT32 choicesx = geo.choicesx, choicesy = geo.choicesy;
// Draw gfx first
if (dialog->pics && dialog->picnum >= 0 && dialog->picnum < dialog->numpics && dialog->pics[dialog->picnum].name[0] != '\0')
{
cutscene_pic_t *pic = &dialog->pics[dialog->picnum];
patch = W_CachePatchLongName(pic->name, PU_PATCH_LOWPRIORITY);
if (pic->hires)
V_DrawSmallScaledPatch(pic->xcoord, pic->ycoord, draw_flags, patch);
else
V_DrawScaledPatch(pic->xcoord, pic->ycoord, draw_flags, patch);
}
// Draw background
V_DrawPromptBack(boxh, dialog->page->backcolor, draw_flags|snap_flags);
// Draw narrator icon
F_DrawDialogIcon(dialog, &geo, draw_flags | snap_flags);
// Draw text
if (dialog->disptext)
F_DrawDialogText(textx, texty, (draw_flags|snap_flags|V_ALLOWLOWERCASE), dialog->disptext, dialog->writer.writeptr, true, NULL);
// Draw name
if (dialog->speaker[0])
F_DrawDialogText(textx, namey, (draw_flags|snap_flags|V_ALLOWLOWERCASE|V_YELLOWMAP), dialog->speaker, strlen(dialog->speaker), false, NULL);
// Draw choices
if (dialog->showchoices)
{
INT32 choices_flags = draw_flags | snap_flags;
if (dialog->page->choicesleftside)
choices_flags |= V_SNAPTOLEFT;
else
choices_flags |= V_SNAPTORIGHT;
V_DrawPromptRect(geo.choicesbox.x, geo.choicesbox.y, geo.choicesbox.w, geo.choicesbox.h, dialog->page->backcolor, choices_flags);
textx = choicesx + 12;
for (INT32 i = 0; i < dialog->numchoices; i++)
{
const char *text = dialog->page->choices[i].text;
INT32 flags = choices_flags | V_ALLOWLOWERCASE;
INT32 highlight_flags = 0;
boolean selected = dialog->curchoice == i;
if (selected)
highlight_flags |= V_YELLOWMAP;
F_DrawDialogText(textx, choicesy, flags, text, strlen(text), false, V_GetStringColormap(highlight_flags & V_CHARCOLORMASK));
if (selected)
V_DrawString(choicesx + (chevronAnimCounter/5), choicesy, choices_flags|V_YELLOWMAP, "\x1D");
choicesy += 10;
}
}
if (globaltextprompt && (globaltextprompt->player != &players[displayplayer]))
return;
// Draw chevron
if (dialog->blockcontrols && (!dialog->timetonext || dialog->paused) && !dialog->showchoices)
V_DrawString(textr-8, chevrony + (chevronAnimCounter/5), (draw_flags|snap_flags|V_YELLOWMAP), "\x1B"); // down arrow
}
// 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 "doomstat.h"
#include "d_player.h"
#include "d_event.h"
//
// CUTSCENE TEXT WRITING
//
typedef struct
{
const char *basetext;
size_t basetextlength;
char *disptext;
size_t disptextsize;
UINT32 baseptr;
UINT32 writeptr;
INT32 textcount;
INT32 textspeed;
INT32 numtowrite;
boolean boostspeed;
boolean paused;
} textwriter_t;
UINT8 P_CutsceneWriteText(textwriter_t *writer);
void P_ResetTextWriter(textwriter_t *writer, const char *basetext, size_t basetextlength);
//
// PROMPT STATE
//
typedef struct dialog_s
{
INT32 promptnum;
INT32 pagenum;
player_t *player; // The player that started this conversation
textwriter_t writer;
char *pagetext;
size_t pagetextlength;
boolean blockcontrols;
boolean gotonext;
boolean paused;
INT32 timetonext;
INT16 postexectag;
tic_t jumpdown;
tic_t spindown;
INT32 picnum;
INT32 pictoloop;
INT32 pictimer;
INT32 picmode;
INT32 numpics;
cutscene_pic_t *pics;
char speaker[256];
char icon[256];
boolean iconflip;
boolean showchoices;
INT32 curchoice;
INT32 numchoices;
INT32 nochoice;
boolean selectedchoice;
// For internal use.
textprompt_t *prompt;
textpage_t *page;
INT32 longestchoice;
lumpnum_t iconlump;
char *disptext;
size_t disptextsize;
} dialog_t;
void P_StartTextPrompt(player_t *player, INT32 promptnum, INT32 pagenum, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime, boolean allplayers);
void P_EndTextPrompt(player_t *player, boolean forceexec, boolean noexec);
void P_EndAllTextPrompts(boolean forceexec, boolean noexec);
void P_RunDialog(player_t *player);
void P_FreeDialog(dialog_t *dialog);
void P_SetDialogText(dialog_t *dialog, char *pagetext, size_t textlength);
void P_SetDialogSpeaker(dialog_t *dialog, const char *speaker);
void P_SetDialogIcon(dialog_t *dialog, const char *icon);
boolean P_SetCurrentDialogChoice(player_t *player, INT32 choice);
boolean P_SelectDialogChoice(player_t *player, INT32 choice);
boolean F_GetPromptHideHudAll(void);
boolean F_GetPromptHideHud(fixed_t y);
void F_TextPromptDrawer(void);
INT32 P_GetTextPromptByName(const char *name);
INT32 P_GetPromptPageByName(textprompt_t *prompt, const char *name);
void P_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum);
void P_SetMetaPage(textpage_t *page, textpage_t *metapage);
void P_SetPicsMetaPage(textpage_t *page, textpage_t *metapage);
void P_InitTextPromptPage(textpage_t *page);
void P_FreeTextPrompt(textprompt_t *prompt);
dialog_t *P_GetPlayerDialog(player_t *player);
#endif
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// 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_dialogscript.c
/// \brief Text prompt script interpreter
#include "p_dialogscript.h"
#include "d_player.h" // player_t
#include "r_skins.h" // skins
#include "g_game.h" // player_names
#include "m_misc.h"
#include "m_writebuffer.h"
#include "usdf.h"
#include "z_zone.h"
#include "w_wad.h"
static boolean GetCommand(char **command_start, char **command_end, char *start)
{
while (isspace(*start))
{
if (*start == '\0')
return false;
start++;
}
char *end = start;
while (*end != '}')
{
if (*end == '\0')
return false;
if (isspace(*end))
break;
end++;
}
*command_start = start;
*command_end = end;
return true;
}
static boolean MatchCommand(const char *match, size_t matchlen, char *tkn, size_t tknlen, char **input)
{
if (tknlen != matchlen)
return false;
char *cmp = tkn;
char *cmpEnd = cmp + tknlen;
while (cmp < cmpEnd)
{
if (toupper(*cmp) != *match)
return false;
cmp++;
match++;
}
while (isspace(*cmpEnd))
cmpEnd++;
*input = cmpEnd;
return true;
}
static char *GetCommandParam(char **input)
{
char *start = *input;
if (*start == '}')
return NULL;
char *end = start;
while (*end != '}')
{
if (*end == '\0')
return NULL;
end++;
}
size_t tknlen = end - start;
if (!tknlen)
return NULL;
while (isspace(start[tknlen - 1]))
{
tknlen--;
if (!tknlen)
return NULL;
}
char *result = Z_Malloc(tknlen + 1, PU_STATIC, NULL);
memcpy(result, start, tknlen);
result[tknlen] = '\0';
*input = end;
return result;
}
static void GetCommandEnd(char **input)
{
char *start = *input;
if (*start == '}')
return;
char *end = start;
while (*end != '}')
{
if (*end == '\0')
return;
end++;
}
*input = end;
}
#define WRITE_CHAR(writechr) M_BufferWrite(bufptr, (UINT8)(writechr))
#define WRITE_OP(writeop) { \
WRITE_CHAR(0xFF); \
WRITE_CHAR((writeop)); \
}
#define WRITE_NUM(num) { \
int n = num; \
WRITE_CHAR((n >> 24) & 0xFF); \
WRITE_CHAR((n >> 16) & 0xFF); \
WRITE_CHAR((n >> 8) & 0xFF); \
WRITE_CHAR((n) & 0xFF); \
}
#define WRITE_STRING(str) { \
size_t maxlen = strlen(str); \
if (maxlen > 255) \
maxlen = 255; \
WRITE_CHAR(maxlen); \
M_BufferMemWrite(bufptr, (UINT8*)str, maxlen); \
}
#define EXPECTED_NUMBER(which) USDFParseError(tokenizer_line, CONS_WARNING, "Expected integer for command '%s'; got '%s' instead", which, param)
#define EXPECTED_PARAM(which) USDFParseError(tokenizer_line, CONS_WARNING, "Expected parameter for command '%s'", which)
#define IS_COMMAND(match) MatchCommand(match, sizeof(match) - 1, command_start, cmd_len, input)
static boolean CheckIfNonScriptCommand(char *command_start, size_t cmd_len, char **input, writebuffer_t *bufptr, int tokenizer_line);
void P_ParseDialogScriptCommand(char **input, writebuffer_t *bufptr, int tokenizer_line)
{
char *command_start = NULL;
char *command_end = NULL;
if (!GetCommand(&command_start, &command_end, *input))
{
GetCommandEnd(input);
return;
}
size_t cmd_len = command_end - command_start;
if (IS_COMMAND("DELAY"))
{
int delay_frames = 12;
char *param = GetCommandParam(input);
if (param)
{
if (!M_StringToNumber(param, &delay_frames))
EXPECTED_NUMBER("DELAY");
Z_Free(param);
}
if (delay_frames > 0)
{
WRITE_OP(TP_OP_DELAY);
WRITE_NUM(delay_frames);
}
}
else if (IS_COMMAND("PAUSE"))
{
int pause_frames = 12;
char *param = GetCommandParam(input);
if (param)
{
if (!M_StringToNumber(param, &pause_frames))
EXPECTED_NUMBER("PAUSE");
Z_Free(param);
}
if (pause_frames > 0)
{
WRITE_OP(TP_OP_PAUSE);
WRITE_NUM(pause_frames);
}
}
else if (IS_COMMAND("SPEED"))
{
char *param = GetCommandParam(input);
if (param)
{
int speed = 1;
if (M_StringToNumber(param, &speed))
{
if (speed < 0)
speed = 0;
speed--;
}
else
EXPECTED_NUMBER("SPEED");
Z_Free(param);
WRITE_OP(TP_OP_SPEED);
WRITE_NUM(speed);
}
else
EXPECTED_PARAM("SPEED");
}
else if (IS_COMMAND("NAME"))
{
char *param = GetCommandParam(input);
if (param)
{
WRITE_OP(TP_OP_NAME);
WRITE_STRING(param);
Z_Free(param);
}
else
EXPECTED_PARAM("NAME");
}
else if (IS_COMMAND("ICON"))
{
char *param = GetCommandParam(input);
if (param)
{
WRITE_OP(TP_OP_ICON);
WRITE_STRING(param);
Z_Free(param);
}
else
EXPECTED_PARAM("ICON");
}
else if (IS_COMMAND("CHARNAME"))
{
WRITE_OP(TP_OP_CHARNAME);
}
else if (IS_COMMAND("SUPERNAME"))
{
WRITE_OP(TP_OP_SUPERNAME);
}
else if (IS_COMMAND("PLAYERNAME"))
{
WRITE_OP(TP_OP_PLAYERNAME);
}
else if (IS_COMMAND("NEXTPAGE"))
{
WRITE_OP(TP_OP_NEXTPAGE);
}
else if (IS_COMMAND("WAIT"))
{
WRITE_OP(TP_OP_WAIT);
}
else if (!CheckIfNonScriptCommand(command_start, cmd_len, input, bufptr, tokenizer_line))
USDFParseError(tokenizer_line, CONS_WARNING, "Unknown command %.*s", (int)cmd_len, command_start);
GetCommandEnd(input);
}
void P_ParseDialogNonScriptCommand(char **input, writebuffer_t *bufptr, int tokenizer_line)
{
char *command_start = NULL;
char *command_end = NULL;
if (!GetCommand(&command_start, &command_end, *input))
{
GetCommandEnd(input);
return;
}
size_t cmd_len = command_end - command_start;
if (!CheckIfNonScriptCommand(command_start, cmd_len, input, bufptr, tokenizer_line))
USDFParseError(tokenizer_line, CONS_WARNING, "Unknown command %.*s", (int)cmd_len, command_start);
GetCommandEnd(input);
}
static int ParsePromptTextColor(const char *color)
{
const char *text_colors[] = {
"white",
"magenta",
"yellow",
"green",
"blue",
"red",
"gray",
"orange",
"sky",
"purple",
"aqua",
"peridot",
"azure",
"brown",
"rosy",
"invert"
};
for (size_t i = 0; i < sizeof(text_colors) / sizeof(text_colors[0]); i++)
{
if (stricmp(color, text_colors[i]) == 0)
return (int)(i + 128);
}
return -1;
}
static boolean CheckIfNonScriptCommand(char *command_start, size_t cmd_len, char **input, writebuffer_t *bufptr, int tokenizer_line)
{
if (IS_COMMAND("COLOR"))
{
char *param = GetCommandParam(input);
if (param)
{
int color = ParsePromptTextColor(param);
if (color == -1)
USDFParseError(tokenizer_line, CONS_WARNING, "Unknown text color '%s'", param);
else
WRITE_CHAR((UINT8)color);
Z_Free(param);
}
else
WRITE_CHAR(128);
}
else
return false;
return true;
}
#undef IS_COMMAND
boolean P_WriteDialogScriptForCutsceneTextCode(UINT8 chr, writebuffer_t *bufptr)
{
if (chr >= 0xA0 && chr <= 0xAF)
{
WRITE_OP(TP_OP_SPEED);
WRITE_NUM(chr - 0xA0);
return true;
}
else if (chr >= 0xB0 && chr <= (0xB0+TICRATE-1))
{
WRITE_OP(TP_OP_DELAY);
WRITE_NUM(chr - 0xAF);
return true;
}
return false;
}
#undef EXPECTED_NUMBER
#undef EXPECTED_PARAM
#undef WRITE_CHAR
#undef WRITE_OP
#undef WRITE_NUM
#define READ_NUM(where) { \
int temp = 0; \
temp |= (*code) << 24; code++; \
temp |= (*code) << 16; code++; \
temp |= (*code) << 8; code++; \
temp |= (*code); code++; \
where = temp; \
}
static char *ReadString(const UINT8 **code)
{
const UINT8 *c = *code;
UINT8 sz = *c++;
if (!sz)
return NULL;
char *str = Z_Malloc(sz + 1, PU_STATIC, NULL);
char *str_p = str;
while (sz--)
*str_p++ = *c++;
*str_p = '\0';
*code = c;
return str;
}
const UINT8 *P_DialogRunOpcode(const UINT8 *code, dialog_t *dialog, textwriter_t *writer)
{
int num = 0;
switch (*code++)
{
case TP_OP_SPEED:
READ_NUM(num);
writer->textspeed = num;
break;
case TP_OP_DELAY:
READ_NUM(num);
writer->textcount = num;
writer->numtowrite = 0;
writer->boostspeed = false;
break;
case TP_OP_PAUSE:
READ_NUM(num);
writer->textcount = num;
writer->numtowrite = 0;
writer->boostspeed = false;
writer->paused = true;
break;
case TP_OP_NEXTPAGE:
dialog->gotonext = true;
break;
case TP_OP_WAIT:
dialog->paused = true;
writer->boostspeed = false;
writer->numtowrite = 0;
break;
case TP_OP_NAME: {
char *speaker = ReadString(&code);
if (speaker)
{
P_SetDialogSpeaker(dialog, speaker);
Z_Free(speaker);
}
break;
}
case TP_OP_ICON: {
char *icon = ReadString(&code);
if (icon)
{
P_SetDialogIcon(dialog, icon);
Z_Free(icon);
}
break;
case TP_OP_CHARNAME:
case TP_OP_SUPERNAME:
case TP_OP_PLAYERNAME:
// Ignore
break;
}
}
return code;
}
boolean P_DialogPreprocessOpcode(dialog_t *dialog, UINT8 **cptr, writebuffer_t *buf)
{
UINT8 *code = *cptr;
if (*code++ != TP_OP_CONTROL)
return false;
player_t *player = dialog->player;
switch (*code++)
{
case TP_OP_CHARNAME: {
char charname[SKINNAMESIZE+1];
strlcpy(charname, skins[player->skin]->realname, sizeof(charname));
M_BufferMemWrite(buf, (UINT8 *)charname, strlen(charname));
break;
}
case TP_OP_SUPERNAME: {
char supername[SKINNAMESIZE+7];
strlcpy(supername, skins[player->skin]->supername, sizeof(supername));
M_BufferMemWrite(buf, (UINT8 *)supername, strlen(supername));
break;
}
case TP_OP_PLAYERNAME: {
char playername[MAXPLAYERNAME+1];
if (netgame)
strlcpy(playername, player_names[player-players], sizeof(playername));
else
strlcpy(playername, skins[player->skin]->realname, sizeof(playername));
M_BufferMemWrite(buf, (UINT8 *)playername, strlen(playername));
break;
}
default:
return false;
}
*cptr = code;
return true;
}
int P_DialogSkipOpcode(const UINT8 *code)
{
const UINT8 *baseptr = code;
switch (*code++)
{
case TP_OP_SPEED:
case TP_OP_DELAY:
case TP_OP_PAUSE:
code += 4;
break;
case TP_OP_NAME:
case TP_OP_ICON: {
UINT8 n = *code++;
code += n;
break;
case TP_OP_NEXTPAGE:
case TP_OP_WAIT:
case TP_OP_CHARNAME:
case TP_OP_SUPERNAME:
case TP_OP_PLAYERNAME:
// Nothing else to read
break;
}
}
return code - baseptr;
}
#undef READ_NUM
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// 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_dialogscript.h
/// \brief Text prompt script interpreter
#ifndef __P_DIALOGSCRIPT__
#define __P_DIALOGSCRIPT__
#include "doomtype.h"
#include "p_dialog.h"
#include "m_writebuffer.h"
enum
{
TP_OP_SPEED,
TP_OP_DELAY,
TP_OP_PAUSE,
TP_OP_NEXTPAGE,
TP_OP_WAIT,
TP_OP_NAME,
TP_OP_ICON,
TP_OP_CHARNAME,
TP_OP_SUPERNAME,
TP_OP_PLAYERNAME,
TP_OP_CONTROL = 0xFF
};
void P_ParseDialogScriptCommand(char **input, writebuffer_t *bufptr, int tokenizer_line);
void P_ParseDialogNonScriptCommand(char **input, writebuffer_t *bufptr, int tokenizer_line);
boolean P_WriteDialogScriptForCutsceneTextCode(UINT8 chr, writebuffer_t *bufptr);
const UINT8 *P_DialogRunOpcode(const UINT8 *code, dialog_t *dialog, textwriter_t *writer);
boolean P_DialogPreprocessOpcode(dialog_t *dialog, UINT8 **cptr, writebuffer_t *buf);
int P_DialogSkipOpcode(const UINT8 *code);
#endif
......@@ -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++;
......
......@@ -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)
......
......@@ -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 if (players[i].promptactive)
{
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
}
}
......@@ -2485,7 +2508,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);
......@@ -3661,10 +3687,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);
......@@ -4905,6 +4936,177 @@ 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);
WRITEINT32(save_p, dialog->numchoices);
if (dialog->numchoices)
{
WRITEINT32(save_p, dialog->curchoice);
WRITEINT32(save_p, dialog->nochoice);
}
WRITEUINT8(save_p, (UINT8)(dialog->player-players));
WRITEINT32(save_p, dialog->picnum);
WRITEINT32(save_p, dialog->pictoloop);
WRITEINT32(save_p, dialog->pictimer);
WRITEUINT32(save_p, dialog->writer.baseptr);
WRITEUINT32(save_p, dialog->writer.writeptr);
WRITEINT32(save_p, dialog->writer.textcount);
WRITEINT32(save_p, dialog->writer.textspeed);
WRITEUINT8(save_p, (UINT8)dialog->jumpdown);
WRITEUINT8(save_p, (UINT8)dialog->spindown);
WRITESTRINGN(save_p, dialog->speaker, sizeof(dialog->speaker) - 1);
WRITESTRINGN(save_p, dialog->icon, sizeof(dialog->icon) - 1);
UINT8 flags = 0;
if (dialog->gotonext)
flags |= 1<<0;
if (dialog->paused)
flags |= 1<<1;
if (dialog->showchoices)
flags |= 1<<2;
if (dialog->selectedchoice)
flags |= 1<<3;
if (dialog->iconflip)
flags |= 1<<4;
if (dialog->writer.boostspeed)
flags |= 1<<5;
if (dialog->writer.paused)
flags |= 1<<6;
WRITEUINT8(save_p, flags);
}
static void P_NetUnArchiveDialog(dialog_t *dialog)
{
UINT8 playernum;
UINT32 baseptr, writeptr;
INT32 textcount, textspeed;
tic_t jumpdown, spindown;
char speaker[256], icon[256];
boolean iconflip, boostspeed, writerpaused;
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);
dialog->numchoices = READINT32(save_p);
dialog->longestchoice = -1;
if (dialog->numchoices)
{
dialog->curchoice = READINT32(save_p);
dialog->nochoice = READINT32(save_p);
}
else
{
dialog->curchoice = 0;
dialog->nochoice = -1;
}
playernum = READUINT8(save_p);
dialog->picnum = READINT32(save_p);
dialog->pictoloop = READINT32(save_p);
dialog->pictimer = READINT32(save_p);
baseptr = READUINT32(save_p);
writeptr = READUINT32(save_p);
textcount = READINT32(save_p);
textspeed = READINT32(save_p);
jumpdown = (tic_t)READUINT8(save_p);
spindown = (tic_t)READUINT8(save_p);
READSTRINGN(save_p, speaker, sizeof(speaker) - 1);
READSTRINGN(save_p, icon, sizeof(icon) - 1);
UINT8 flags = READUINT8(save_p);
dialog->gotonext = (flags & (1<<0)) ? true : false;
dialog->paused = (flags & (1<<1)) ? true : false;
dialog->showchoices = (dialog->numchoices && (flags & (1<<2))) ? true : false;
dialog->selectedchoice = (dialog->numchoices && (flags & (1<<3))) ? true : false;
iconflip = (flags & (1<<4)) ? true : false;
boostspeed = (flags & (1<<5)) ? true : false;
writerpaused = (flags & (1<<6)) ? true : false;
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->player = &players[playernum];
if (dialog->numchoices)
{
if (dialog->numchoices < 0 || dialog->numchoices > dialog->page->numchoices)
I_Error("Invalid text prompt numchoices %d from server", dialog->numchoices);
if (dialog->curchoice < 0 || dialog->curchoice > dialog->page->numchoices)
I_Error("Invalid text prompt curchoice %d from server", dialog->curchoice);
if (dialog->nochoice != -1 && (dialog->nochoice < 0 || dialog->nochoice > dialog->page->numchoices))
I_Error("Invalid text prompt nochoice %d from server", dialog->nochoice);
}
P_SetDialogText(dialog, dialog->page->text, dialog->page->textlength);
strlcpy(dialog->speaker, speaker, sizeof(dialog->speaker));
P_SetDialogIcon(dialog, icon);
dialog->iconflip = iconflip;
dialog->jumpdown = jumpdown;
dialog->spindown = spindown;
dialog->writer.baseptr = baseptr;
dialog->writer.writeptr = writeptr;
dialog->writer.textcount = textcount;
dialog->writer.textspeed = textspeed;
dialog->writer.boostspeed = boostspeed;
dialog->writer.paused = writerpaused;
}
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_FreeDialog(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);
......@@ -5049,6 +5251,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)
......@@ -5103,6 +5306,7 @@ boolean P_LoadNetGame(boolean reloading)
if (!P_NetUnArchiveMisc(reloading))
return false;
P_NetUnArchiveEmblems();
P_NetUnArchiveGlobalTextPrompt();
P_NetUnArchivePlayers();
if (gamestate == GS_LEVEL)
{
......
......@@ -20,13 +20,16 @@
#include "p_setup.h"
#include "p_spec.h"
#include "p_saveg.h"
#include "p_polyobj.h"
#include "p_dialog.h"
#include "i_time.h"
#include "i_sound.h" // for I_PlayCD()..
#include "i_sound.h"
#include "i_video.h" // for I_FinishUpdate()..
#include "r_sky.h"
#include "i_system.h"
#include "r_main.h"
#include "r_data.h"
#include "r_things.h" // for R_AddSpriteDefs
#include "r_textures.h"
......@@ -52,13 +55,11 @@
#include "dehacked.h" // for map headers
#include "deh_tables.h" // FREE_SKINCOLORS
#include "r_main.h"
#include "usdf.h"
#include "m_cond.h" // for emblems
#include "m_argv.h"
#include "p_polyobj.h"
#include "v_video.h"
#include "filesrch.h" // refreshdirmenu
......@@ -5705,9 +5706,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;
......@@ -7858,8 +7859,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();
......@@ -8305,6 +8306,9 @@ static boolean P_LoadAddon(UINT16 numlumps)
if (!mapsadded)
CONS_Printf(M_GetText("No maps added\n"));
// Parse all DIALOGUE lumps
P_LoadDialogueLumps(wadnum);
R_LoadSpriteInfoLumps(wadnum, numlumps);
#ifdef HWRENDER
......