diff --git a/src/doom/statdump.c b/src/doom/statdump.c index 99886910..5bacad67 100644 --- a/src/doom/statdump.c +++ b/src/doom/statdump.c @@ -1,356 +1,356 @@ - /* - - Copyright(C) 2005-2014 Simon Howard - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - -- - - Functions for presenting the information captured from the statistics - buffer to a file. - - */ - -#include -#include -#include - -#include "d_player.h" -#include "d_mode.h" -#include "m_argv.h" - -#include "statdump.h" - -/* Par times for E1M1-E1M9. */ -static const int doom1_par_times[] = -{ - 30, 75, 120, 90, 165, 180, 180, 30, 165, -}; - -/* Par times for MAP01-MAP09. */ -static const int doom2_par_times[] = -{ - 30, 90, 120, 120, 90, 150, 120, 120, 270, -}; - -/* Player colors. */ -static const char *player_colors[] = -{ - "Green", "Indigo", "Brown", "Red" -}; - -// Array of end-of-level statistics that have been captured. - -#define MAX_CAPTURES 32 -static wbstartstruct_t captured_stats[MAX_CAPTURES]; -static int num_captured_stats = 0; - -static GameMission_t discovered_gamemission = none; - -/* Try to work out whether this is a Doom 1 or Doom 2 game, by looking - * at the episode and map, and the par times. This is used to decide - * how to format the level name. Unfortunately, in some cases it is - * impossible to determine whether this is Doom 1 or Doom 2. */ - -static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats) -{ - int partime; - int level; - int i; - - if (discovered_gamemission != none) - { - return; - } - - for (i=0; i 0) - { - discovered_gamemission = doom; - return; - } - - /* This is episode 1. If this is level 10 or higher, - it must be Doom 2. */ - - if (level >= 9) - { - discovered_gamemission = doom2; - return; - } - - /* Try to work out if this is Doom 1 or Doom 2 by looking - at the par time. */ - - partime = stats[i].partime; - - if (partime == doom1_par_times[level] * TICRATE - && partime != doom2_par_times[level] * TICRATE) - { - discovered_gamemission = doom; - return; - } - - if (partime != doom1_par_times[level] * TICRATE - && partime == doom2_par_times[level] * TICRATE) - { - discovered_gamemission = doom2; - return; - } - } -} - -/* Returns the number of players active in the given stats buffer. */ - -static int GetNumPlayers(wbstartstruct_t *stats) -{ - int i; - int num_players = 0; - - for (i=0; iplyr[i].in) - { - ++num_players; - } - } - - return num_players; -} - -static void PrintBanner(FILE *stream) -{ - fprintf(stream, "===========================================\n"); -} - -static void PrintPercentage(FILE *stream, int amount, int total) -{ - if (total == 0) - { - fprintf(stream, "0"); - } - else - { - fprintf(stream, "%i / %i", amount, total); - - // statdump.exe is a 16-bit program, so very occasionally an - // integer overflow can occur when doing this calculation with - // a large value. Therefore, cast to short to give the same - // output. - - fprintf(stream, " (%i%%)", (short) (amount * 100) / total); - } -} - -/* Display statistics for a single player. */ - -static void PrintPlayerStats(FILE *stream, wbstartstruct_t *stats, - int player_num) -{ - wbplayerstruct_t *player = &stats->plyr[player_num]; - - fprintf(stream, "Player %i (%s):\n", player_num + 1, - player_colors[player_num]); - - /* Kills percentage */ - - fprintf(stream, "\tKills: "); - PrintPercentage(stream, player->skills, stats->maxkills); - fprintf(stream, "\n"); - - /* Items percentage */ - - fprintf(stream, "\tItems: "); - PrintPercentage(stream, player->sitems, stats->maxitems); - fprintf(stream, "\n"); - - /* Secrets percentage */ - - fprintf(stream, "\tSecrets: "); - PrintPercentage(stream, player->ssecret, stats->maxsecret); - fprintf(stream, "\n"); -} - -/* Frags table for multiplayer games. */ - -static void PrintFragsTable(FILE *stream, wbstartstruct_t *stats) -{ - int x, y; - - fprintf(stream, "Frags:\n"); - - /* Print header */ - - fprintf(stream, "\t\t"); - - for (x=0; xplyr[x].in) - { - continue; - } - - fprintf(stream, "%s\t", player_colors[x]); - } - - fprintf(stream, "\n"); - - fprintf(stream, "\t\t-------------------------------- VICTIMS\n"); - - /* Print table */ - - for (y=0; yplyr[y].in) - { - continue; - } - - fprintf(stream, "\t%s\t|", player_colors[y]); - - for (x=0; xplyr[x].in) - { - continue; - } - - fprintf(stream, "%i\t", stats->plyr[y].frags[x]); - } - - fprintf(stream, "\n"); - } - - fprintf(stream, "\t\t|\n"); - fprintf(stream, "\t KILLERS\n"); -} - -/* Displays the level name: MAPxy or ExMy, depending on game mode. */ - -static void PrintLevelName(FILE *stream, int episode, int level) -{ - PrintBanner(stream); - - switch (discovered_gamemission) - { - - case doom: - fprintf(stream, "E%iM%i\n", episode + 1, level + 1); - break; - case doom2: - fprintf(stream, "MAP%02i\n", level + 1); - break; - default: - case none: - fprintf(stream, "E%iM%i / MAP%02i\n", - episode + 1, level + 1, level + 1); - break; - } - - PrintBanner(stream); -} - -/* Print details of a statistics buffer to the given file. */ - -static void PrintStats(FILE *stream, wbstartstruct_t *stats) -{ - int leveltime, partime; - int i; - - PrintLevelName(stream, stats->epsd, stats->last); - fprintf(stream, "\n"); - - leveltime = stats->plyr[0].stime / TICRATE; - partime = stats->partime / TICRATE; - fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60); - fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60); - fprintf(stream, "\n"); - - for (i=0; iplyr[i].in) - { - PrintPlayerStats(stream, stats, i); - } - } - - if (GetNumPlayers(stats) >= 2) - { - PrintFragsTable(stream, stats); - } - - fprintf(stream, "\n"); -} - -void StatCopy(wbstartstruct_t *stats) -{ - if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES) - { - memcpy(&captured_stats[num_captured_stats], stats, - sizeof(wbstartstruct_t)); - ++num_captured_stats; - } -} - -void StatDump(void) -{ - FILE *dumpfile; - int i; - - //! - // @category compat - // @arg - // - // Dump statistics information to the specified file on the levels - // that were played. The output from this option matches the output - // from statdump.exe (see ctrlapi.zip in the /idgames archive). - // - - i = M_CheckParmWithArgs("-statdump", 1); - - if (i > 0) - { - printf("Statistics captured for %i level(s)\n", num_captured_stats); - - // We actually know what the real gamemission is, but this has - // to match the output from statdump.exe. - - DiscoverGamemode(captured_stats, num_captured_stats); - - // Allow "-" as output file, for stdout. - - if (strcmp(myargv[i + 1], "-") != 0) - { - dumpfile = fopen(myargv[i + 1], "w"); - } - else - { - dumpfile = NULL; - } - - for (i = 0; i < num_captured_stats; ++i) - { - PrintStats(dumpfile, &captured_stats[i]); - } - - if (dumpfile != NULL) - { - fclose(dumpfile); - } - } -} - + /* + + Copyright(C) 2005-2014 Simon Howard + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + -- + + Functions for presenting the information captured from the statistics + buffer to a file. + + */ + +#include +#include +#include + +#include "d_player.h" +#include "d_mode.h" +#include "m_argv.h" + +#include "statdump.h" + +/* Par times for E1M1-E1M9. */ +static const int doom1_par_times[] = +{ + 30, 75, 120, 90, 165, 180, 180, 30, 165, +}; + +/* Par times for MAP01-MAP09. */ +static const int doom2_par_times[] = +{ + 30, 90, 120, 120, 90, 150, 120, 120, 270, +}; + +/* Player colors. */ +static const char *player_colors[] = +{ + "Green", "Indigo", "Brown", "Red" +}; + +// Array of end-of-level statistics that have been captured. + +#define MAX_CAPTURES 32 +static wbstartstruct_t captured_stats[MAX_CAPTURES]; +static int num_captured_stats = 0; + +static GameMission_t discovered_gamemission = none; + +/* Try to work out whether this is a Doom 1 or Doom 2 game, by looking + * at the episode and map, and the par times. This is used to decide + * how to format the level name. Unfortunately, in some cases it is + * impossible to determine whether this is Doom 1 or Doom 2. */ + +static void DiscoverGamemode(wbstartstruct_t *stats, int num_stats) +{ + int partime; + int level; + int i; + + if (discovered_gamemission != none) + { + return; + } + + for (i=0; i 0) + { + discovered_gamemission = doom; + return; + } + + /* This is episode 1. If this is level 10 or higher, + it must be Doom 2. */ + + if (level >= 9) + { + discovered_gamemission = doom2; + return; + } + + /* Try to work out if this is Doom 1 or Doom 2 by looking + at the par time. */ + + partime = stats[i].partime; + + if (partime == doom1_par_times[level] * TICRATE + && partime != doom2_par_times[level] * TICRATE) + { + discovered_gamemission = doom; + return; + } + + if (partime != doom1_par_times[level] * TICRATE + && partime == doom2_par_times[level] * TICRATE) + { + discovered_gamemission = doom2; + return; + } + } +} + +/* Returns the number of players active in the given stats buffer. */ + +static int GetNumPlayers(wbstartstruct_t *stats) +{ + int i; + int num_players = 0; + + for (i=0; iplyr[i].in) + { + ++num_players; + } + } + + return num_players; +} + +static void PrintBanner(FILE *stream) +{ + fprintf(stream, "===========================================\n"); +} + +static void PrintPercentage(FILE *stream, int amount, int total) +{ + if (total == 0) + { + fprintf(stream, "0"); + } + else + { + fprintf(stream, "%i / %i", amount, total); + + // statdump.exe is a 16-bit program, so very occasionally an + // integer overflow can occur when doing this calculation with + // a large value. Therefore, cast to short to give the same + // output. + + fprintf(stream, " (%i%%)", (short) (amount * 100) / total); + } +} + +/* Display statistics for a single player. */ + +static void PrintPlayerStats(FILE *stream, wbstartstruct_t *stats, + int player_num) +{ + wbplayerstruct_t *player = &stats->plyr[player_num]; + + fprintf(stream, "Player %i (%s):\n", player_num + 1, + player_colors[player_num]); + + /* Kills percentage */ + + fprintf(stream, "\tKills: "); + PrintPercentage(stream, player->skills, stats->maxkills); + fprintf(stream, "\n"); + + /* Items percentage */ + + fprintf(stream, "\tItems: "); + PrintPercentage(stream, player->sitems, stats->maxitems); + fprintf(stream, "\n"); + + /* Secrets percentage */ + + fprintf(stream, "\tSecrets: "); + PrintPercentage(stream, player->ssecret, stats->maxsecret); + fprintf(stream, "\n"); +} + +/* Frags table for multiplayer games. */ + +static void PrintFragsTable(FILE *stream, wbstartstruct_t *stats) +{ + int x, y; + + fprintf(stream, "Frags:\n"); + + /* Print header */ + + fprintf(stream, "\t\t"); + + for (x=0; xplyr[x].in) + { + continue; + } + + fprintf(stream, "%s\t", player_colors[x]); + } + + fprintf(stream, "\n"); + + fprintf(stream, "\t\t-------------------------------- VICTIMS\n"); + + /* Print table */ + + for (y=0; yplyr[y].in) + { + continue; + } + + fprintf(stream, "\t%s\t|", player_colors[y]); + + for (x=0; xplyr[x].in) + { + continue; + } + + fprintf(stream, "%i\t", stats->plyr[y].frags[x]); + } + + fprintf(stream, "\n"); + } + + fprintf(stream, "\t\t|\n"); + fprintf(stream, "\t KILLERS\n"); +} + +/* Displays the level name: MAPxy or ExMy, depending on game mode. */ + +static void PrintLevelName(FILE *stream, int episode, int level) +{ + PrintBanner(stream); + + switch (discovered_gamemission) + { + + case doom: + fprintf(stream, "E%iM%i\n", episode + 1, level + 1); + break; + case doom2: + fprintf(stream, "MAP%02i\n", level + 1); + break; + default: + case none: + fprintf(stream, "E%iM%i / MAP%02i\n", + episode + 1, level + 1, level + 1); + break; + } + + PrintBanner(stream); +} + +/* Print details of a statistics buffer to the given file. */ + +static void PrintStats(FILE *stream, wbstartstruct_t *stats) +{ + int leveltime, partime; + int i; + + PrintLevelName(stream, stats->epsd, stats->last); + fprintf(stream, "\n"); + + leveltime = stats->plyr[0].stime / TICRATE; + partime = stats->partime / TICRATE; + fprintf(stream, "Time: %i:%02i", leveltime / 60, leveltime % 60); + fprintf(stream, " (par: %i:%02i)\n", partime / 60, partime % 60); + fprintf(stream, "\n"); + + for (i=0; iplyr[i].in) + { + PrintPlayerStats(stream, stats, i); + } + } + + if (GetNumPlayers(stats) >= 2) + { + PrintFragsTable(stream, stats); + } + + fprintf(stream, "\n"); +} + +void StatCopy(wbstartstruct_t *stats) +{ + if (M_ParmExists("-statdump") && num_captured_stats < MAX_CAPTURES) + { + memcpy(&captured_stats[num_captured_stats], stats, + sizeof(wbstartstruct_t)); + ++num_captured_stats; + } +} + +void StatDump(void) +{ + FILE *dumpfile; + int i; + + //! + // @category compat + // @arg + // + // Dump statistics information to the specified file on the levels + // that were played. The output from this option matches the output + // from statdump.exe (see ctrlapi.zip in the /idgames archive). + // + + i = M_CheckParmWithArgs("-statdump", 1); + + if (i > 0) + { + printf("Statistics captured for %i level(s)\n", num_captured_stats); + + // We actually know what the real gamemission is, but this has + // to match the output from statdump.exe. + + DiscoverGamemode(captured_stats, num_captured_stats); + + // Allow "-" as output file, for stdout. + + if (strcmp(myargv[i + 1], "-") != 0) + { + dumpfile = fopen(myargv[i + 1], "w"); + } + else + { + dumpfile = NULL; + } + + for (i = 0; i < num_captured_stats; ++i) + { + PrintStats(dumpfile, &captured_stats[i]); + } + + if (dumpfile != NULL) + { + fclose(dumpfile); + } + } +} + diff --git a/src/doom/statdump.h b/src/doom/statdump.h index bddf9a2b..48db2ad2 100644 --- a/src/doom/statdump.h +++ b/src/doom/statdump.h @@ -1,23 +1,23 @@ - /* - - Copyright(C) 2005-2014 Simon Howard - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - */ - -#ifndef DOOM_STATDUMP_H -#define DOOM_STATDUMP_H - -void StatCopy(wbstartstruct_t *stats); -void StatDump(void); - -#endif /* #ifndef DOOM_STATDUMP_H */ + /* + + Copyright(C) 2005-2014 Simon Howard + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + */ + +#ifndef DOOM_STATDUMP_H +#define DOOM_STATDUMP_H + +void StatCopy(wbstartstruct_t *stats); +void StatDump(void); + +#endif /* #ifndef DOOM_STATDUMP_H */ diff --git a/src/strife/m_saves.c b/src/strife/m_saves.c index e4d64e59..8702a95f 100644 --- a/src/strife/m_saves.c +++ b/src/strife/m_saves.c @@ -1,528 +1,528 @@ -// -// Copyright(C) 1993-1996 Id Software, Inc. -// Copyright(C) 2010 James Haley, Samuel Villarreal -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// DESCRIPTION: -// -// [STRIFE] New Module -// -// Strife Hub Saving Code -// - -// For GNU C and POSIX targets, dirent.h should be available. Otherwise, for -// Visual C++, we need to include the win_opendir module. -#if defined(_MSC_VER) -#include -#elif defined(__GNUC__) || defined(POSIX) -#include -#else -#error Need an include for dirent.h! -#endif -#include -#include -#include - -#include "z_zone.h" -#include "i_system.h" -#include "d_player.h" -#include "deh_str.h" -#include "doomstat.h" -#include "m_misc.h" -#include "m_saves.h" -#include "p_dialog.h" - -// -// File Paths -// -// Strife maintains multiple file paths related to savegames. -// -char *savepath; // The actual path of the selected saveslot -char *savepathtemp; // The path of the temporary saveslot (strfsav6.ssg) -char *loadpath; // Path used while loading the game - -char character_name[CHARACTER_NAME_LEN]; // Name of "character" for saveslot - -// -// ClearTmp -// -// Clear the temporary save directory -// -void ClearTmp(void) -{ - DIR *sp2dir = NULL; - struct dirent *f = NULL; - - if(savepathtemp == NULL) - I_Error("you fucked up savedir man!"); - - if(!(sp2dir = opendir(savepathtemp))) - I_Error("ClearTmp: Couldn't open dir %s", savepathtemp); - - while((f = readdir(sp2dir))) - { - char *filepath = NULL; - - // haleyjd: skip "." and ".." without assuming they're the - // first two entries like the original code did. - if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) - continue; - - // haleyjd: use M_SafeFilePath, not sprintf - filepath = M_SafeFilePath(savepathtemp, f->d_name); - remove(filepath); - - Z_Free(filepath); - } - - closedir(sp2dir); -} - -// -// ClearSlot -// -// Clear a single save slot folder -// -void ClearSlot(void) -{ - DIR *spdir = NULL; - struct dirent *f = NULL; - - if(savepath == NULL) - I_Error("userdir is fucked up man!"); - - if(!(spdir = opendir(savepath))) - I_Error("ClearSlot: Couldn't open dir %s", savepath); - - while((f = readdir(spdir))) - { - char *filepath = NULL; - - if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) - continue; - - // haleyjd: use M_SafeFilePath, not sprintf - filepath = M_SafeFilePath(savepath, f->d_name); - remove(filepath); - - Z_Free(filepath); - } - - closedir(spdir); -} - -// -// FromCurr -// -// Copying files from savepathtemp to savepath -// -void FromCurr(void) -{ - DIR *sp2dir = NULL; - struct dirent *f = NULL; - - if(!(sp2dir = opendir(savepathtemp))) - I_Error("FromCurr: Couldn't open dir %s", savepathtemp); - - while((f = readdir(sp2dir))) - { - byte *filebuffer = NULL; - int filelen = 0; - char *srcfilename = NULL; - char *dstfilename = NULL; - - // haleyjd: skip "." and ".." without assuming they're the - // first two entries like the original code did. - if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) - continue; - - // haleyjd: use M_SafeFilePath, NOT sprintf. - srcfilename = M_SafeFilePath(savepathtemp, f->d_name); - dstfilename = M_SafeFilePath(savepath, f->d_name); - - filelen = M_ReadFile(srcfilename, &filebuffer); - M_WriteFile(dstfilename, filebuffer, filelen); - - Z_Free(filebuffer); - Z_Free(srcfilename); - Z_Free(dstfilename); - } - - closedir(sp2dir); -} - -// -// ToCurr -// -// Copying files from savepath to savepathtemp -// -void ToCurr(void) -{ - DIR *spdir = NULL; - struct dirent *f = NULL; - - ClearTmp(); - - // BUG: Rogue copypasta'd this error message, which is why we don't know - // the real original name of this function. - if(!(spdir = opendir(savepath))) - I_Error("ClearSlot: Couldn't open dir %s", savepath); - - while((f = readdir(spdir))) - { - byte *filebuffer = NULL; - int filelen = 0; - char *srcfilename = NULL; - char *dstfilename = NULL; - - if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) - continue; - - // haleyjd: use M_SafeFilePath, NOT sprintf. - srcfilename = M_SafeFilePath(savepath, f->d_name); - dstfilename = M_SafeFilePath(savepathtemp, f->d_name); - - filelen = M_ReadFile(srcfilename, &filebuffer); - M_WriteFile(dstfilename, filebuffer, filelen); - - Z_Free(filebuffer); - Z_Free(srcfilename); - Z_Free(dstfilename); - } - - closedir(spdir); -} - -// -// M_SaveMoveMapToHere -// -// Moves a map to the "HERE" save. -// -void M_SaveMoveMapToHere(void) -{ - char *mapsave = NULL; - char *heresave = NULL; - char tmpnum[33]; - - // haleyjd: no itoa available... - M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap); - - // haleyjd: use M_SafeFilePath, not sprintf - mapsave = M_SafeFilePath(savepath, tmpnum); - heresave = M_SafeFilePath(savepath, "here"); - - // haleyjd: use M_FileExists, not access - if(M_FileExists(mapsave)) - { - remove(heresave); - rename(mapsave, heresave); - } - - Z_Free(mapsave); - Z_Free(heresave); -} - -// -// M_SaveMoveHereToMap -// -// Moves the "HERE" save to a map. -// -void M_SaveMoveHereToMap(void) -{ - char *mapsave = NULL; - char *heresave = NULL; - char tmpnum[33]; - - // haleyjd: no itoa available... - M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap); - - mapsave = M_SafeFilePath(savepathtemp, tmpnum); - heresave = M_SafeFilePath(savepathtemp, "here"); - - if(M_FileExists(heresave)) - { - remove(mapsave); - rename(heresave, mapsave); - } - - Z_Free(mapsave); - Z_Free(heresave); -} - -// -// M_SaveMisObj -// -// Writes the mission objective into the MIS_OBJ file. -// -boolean M_SaveMisObj(const char *path) -{ - boolean result; - char *destpath = NULL; - - // haleyjd 20110210: use M_SafeFilePath, not sprintf - destpath = M_SafeFilePath(path, "mis_obj"); - result = M_WriteFile(destpath, mission_objective, OBJECTIVE_LEN); - - Z_Free(destpath); - return result; -} - -// -// M_ReadMisObj -// -// Reads the mission objective from the MIS_OBJ file. -// -void M_ReadMisObj(void) -{ - FILE *f = NULL; - char *srcpath = NULL; - - // haleyjd: use M_SafeFilePath, not sprintf - srcpath = M_SafeFilePath(savepathtemp, "mis_obj"); - - if((f = fopen(srcpath, "rb"))) - { - fread(mission_objective, 1, OBJECTIVE_LEN, f); - fclose(f); - } - - Z_Free(srcpath); -} - -//============================================================================= -// -// Original Routines -// -// haleyjd - None of the below code is derived from Strife itself, but has been -// adapted or created in order to provide secure, portable filepath handling -// for the purposes of savegame support. This is partially needed to allow for -// differences in Choco due to it being multiplatform. The rest exists because -// I cannot stand programming in an impoverished ANSI C environment that -// calls sprintf on fixed-size buffers. :P -// - -// -// M_Calloc -// -// haleyjd 20110210 - original routine -// Because Choco doesn't have Z_Calloc O_o -// -void *M_Calloc(size_t n1, size_t n2) -{ - return (n1 *= n2) ? memset(Z_Malloc(n1, PU_STATIC, NULL), 0, n1) : NULL; -} - -// -// M_StringAlloc -// -// haleyjd: This routine takes any number of strings and a number of extra -// characters, calculates their combined length, and calls Z_Alloca to create -// a temporary buffer of that size. This is extremely useful for allocation of -// file paths, and is used extensively in d_main.c. The pointer returned is -// to a temporary Z_Alloca buffer, which lives until the next main loop -// iteration, so don't cache it. Note that this idiom is not possible with the -// normal non-standard alloca function, which allocates stack space. -// -// [STRIFE] - haleyjd 20110210 -// This routine is taken from the Eternity Engine and adapted to do without -// Z_Alloca. I need secure string concatenation for filepath handling. The -// only difference from use in EE is that the pointer returned in *str must -// be manually freed. -// -int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...) -{ - va_list args; - size_t len = extra; - - if(numstrs < 1) - I_Error("M_StringAlloc: invalid input\n"); - - len += strlen(str1); - - --numstrs; - - if(numstrs != 0) - { - va_start(args, str1); - - while(numstrs != 0) - { - const char *argstr = va_arg(args, const char *); - - len += strlen(argstr); - - --numstrs; - } - - va_end(args); - } - - ++len; - - *str = (char *)(M_Calloc(1, len)); - - return len; -} - -// -// M_NormalizeSlashes -// -// Remove trailing slashes, translate backslashes to slashes -// The string to normalize is passed and returned in str -// -// killough 11/98: rewritten -// -// [STRIFE] - haleyjd 20110210: Borrowed from Eternity and adapted to respect -// the DIR_SEPARATOR define used by Choco Doom. This routine originated in -// BOOM. -// -void M_NormalizeSlashes(char *str) -{ - char *p; - - // Convert all slashes/backslashes to DIR_SEPARATOR - for(p = str; *p; p++) - { - if((*p == '/' || *p == '\\') && *p != DIR_SEPARATOR) - *p = DIR_SEPARATOR; - } - - // Remove trailing slashes - while(p > str && *--p == DIR_SEPARATOR) - *p = 0; - - // Collapse multiple slashes - for(p = str; (*str++ = *p); ) - if(*p++ == DIR_SEPARATOR) - while(*p == DIR_SEPARATOR) - p++; -} - -// -// M_SafeFilePath -// -// haleyjd 20110210 - original routine. -// This routine performs safe, portable concatenation of a base file path -// with another path component or file name. The returned string is Z_Malloc'd -// and should be freed when it has exhausted its usefulness. -// -char *M_SafeFilePath(const char *basepath, const char *newcomponent) -{ - int newstrlen = 0; - char *newstr = NULL; - - if (!strcmp(basepath, "")) - { - basepath = "."; - } - - // Always throw in a slash. M_NormalizeSlashes will remove it in the case - // that either basepath or newcomponent includes a redundant slash at the - // end or beginning respectively. - newstrlen = M_StringAlloc(&newstr, 3, 1, basepath, "/", newcomponent); - M_snprintf(newstr, newstrlen, "%s/%s", basepath, newcomponent); - M_NormalizeSlashes(newstr); - - return newstr; -} - -// -// M_CreateSaveDirs -// -// haleyjd 20110210: Vanilla Strife went tits-up if it didn't have the full set -// of save folders which were created externally by the installer. fraggle says -// that's no good for Choco purposes, and I agree, so this routine will create -// the full set of folders under the configured savegamedir. -// -void M_CreateSaveDirs(const char *savedir) -{ - int i; - - for(i = 0; i < 7; i++) - { - char *compositedir; - - // compose the full path by concatenating with savedir - compositedir = M_SafeFilePath(savedir, M_MakeStrifeSaveDir(i, "")); - - M_MakeDirectory(compositedir); - - Z_Free(compositedir); - } -} - -// -// M_MakeStrifeSaveDir -// -// haleyjd 20110211: Convenience routine -// -char *M_MakeStrifeSaveDir(int slotnum, const char *extra) -{ - static char tmpbuffer[32]; - - M_snprintf(tmpbuffer, sizeof(tmpbuffer), - "strfsav%d.ssg%s", slotnum, extra); - - return tmpbuffer; -} - -// -// M_GetFilePath -// -// haleyjd: STRIFE-FIXME: Temporary? -// Code borrowed from Eternity, and modified to return separator char -// -char M_GetFilePath(const char *fn, char *dest, size_t len) -{ - boolean found_slash = false; - char *p; - char sepchar = '\0'; - - memset(dest, 0, len); - - p = dest + len - 1; - - M_StringCopy(dest, fn, len); - - while(p >= dest) - { - if(*p == '/' || *p == '\\') - { - sepchar = *p; - found_slash = true; // mark that the path ended with a slash - *p = '\0'; - break; - } - *p = '\0'; - p--; - } - - // haleyjd: in the case that no slash was ever found, yet the - // path string is empty, we are dealing with a file local to the - // working directory. The proper path to return for such a string is - // not "", but ".", since the format strings add a slash now. When - // the string is empty but a slash WAS found, we really do want to - // return the empty string, since the path is relative to the root. - if(!found_slash && *dest == '\0') - *dest = '.'; - - // if a separator is not found, default to forward, because Windows - // supports that too. - if(sepchar == '\0') - sepchar = '/'; - - return sepchar; -} - -// EOF - - +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2010 James Haley, Samuel Villarreal +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// [STRIFE] New Module +// +// Strife Hub Saving Code +// + +// For GNU C and POSIX targets, dirent.h should be available. Otherwise, for +// Visual C++, we need to include the win_opendir module. +#if defined(_MSC_VER) +#include +#elif defined(__GNUC__) || defined(POSIX) +#include +#else +#error Need an include for dirent.h! +#endif +#include +#include +#include + +#include "z_zone.h" +#include "i_system.h" +#include "d_player.h" +#include "deh_str.h" +#include "doomstat.h" +#include "m_misc.h" +#include "m_saves.h" +#include "p_dialog.h" + +// +// File Paths +// +// Strife maintains multiple file paths related to savegames. +// +char *savepath; // The actual path of the selected saveslot +char *savepathtemp; // The path of the temporary saveslot (strfsav6.ssg) +char *loadpath; // Path used while loading the game + +char character_name[CHARACTER_NAME_LEN]; // Name of "character" for saveslot + +// +// ClearTmp +// +// Clear the temporary save directory +// +void ClearTmp(void) +{ + DIR *sp2dir = NULL; + struct dirent *f = NULL; + + if(savepathtemp == NULL) + I_Error("you fucked up savedir man!"); + + if(!(sp2dir = opendir(savepathtemp))) + I_Error("ClearTmp: Couldn't open dir %s", savepathtemp); + + while((f = readdir(sp2dir))) + { + char *filepath = NULL; + + // haleyjd: skip "." and ".." without assuming they're the + // first two entries like the original code did. + if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) + continue; + + // haleyjd: use M_SafeFilePath, not sprintf + filepath = M_SafeFilePath(savepathtemp, f->d_name); + remove(filepath); + + Z_Free(filepath); + } + + closedir(sp2dir); +} + +// +// ClearSlot +// +// Clear a single save slot folder +// +void ClearSlot(void) +{ + DIR *spdir = NULL; + struct dirent *f = NULL; + + if(savepath == NULL) + I_Error("userdir is fucked up man!"); + + if(!(spdir = opendir(savepath))) + I_Error("ClearSlot: Couldn't open dir %s", savepath); + + while((f = readdir(spdir))) + { + char *filepath = NULL; + + if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) + continue; + + // haleyjd: use M_SafeFilePath, not sprintf + filepath = M_SafeFilePath(savepath, f->d_name); + remove(filepath); + + Z_Free(filepath); + } + + closedir(spdir); +} + +// +// FromCurr +// +// Copying files from savepathtemp to savepath +// +void FromCurr(void) +{ + DIR *sp2dir = NULL; + struct dirent *f = NULL; + + if(!(sp2dir = opendir(savepathtemp))) + I_Error("FromCurr: Couldn't open dir %s", savepathtemp); + + while((f = readdir(sp2dir))) + { + byte *filebuffer = NULL; + int filelen = 0; + char *srcfilename = NULL; + char *dstfilename = NULL; + + // haleyjd: skip "." and ".." without assuming they're the + // first two entries like the original code did. + if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) + continue; + + // haleyjd: use M_SafeFilePath, NOT sprintf. + srcfilename = M_SafeFilePath(savepathtemp, f->d_name); + dstfilename = M_SafeFilePath(savepath, f->d_name); + + filelen = M_ReadFile(srcfilename, &filebuffer); + M_WriteFile(dstfilename, filebuffer, filelen); + + Z_Free(filebuffer); + Z_Free(srcfilename); + Z_Free(dstfilename); + } + + closedir(sp2dir); +} + +// +// ToCurr +// +// Copying files from savepath to savepathtemp +// +void ToCurr(void) +{ + DIR *spdir = NULL; + struct dirent *f = NULL; + + ClearTmp(); + + // BUG: Rogue copypasta'd this error message, which is why we don't know + // the real original name of this function. + if(!(spdir = opendir(savepath))) + I_Error("ClearSlot: Couldn't open dir %s", savepath); + + while((f = readdir(spdir))) + { + byte *filebuffer = NULL; + int filelen = 0; + char *srcfilename = NULL; + char *dstfilename = NULL; + + if(!strcmp(f->d_name, ".") || !strcmp(f->d_name, "..")) + continue; + + // haleyjd: use M_SafeFilePath, NOT sprintf. + srcfilename = M_SafeFilePath(savepath, f->d_name); + dstfilename = M_SafeFilePath(savepathtemp, f->d_name); + + filelen = M_ReadFile(srcfilename, &filebuffer); + M_WriteFile(dstfilename, filebuffer, filelen); + + Z_Free(filebuffer); + Z_Free(srcfilename); + Z_Free(dstfilename); + } + + closedir(spdir); +} + +// +// M_SaveMoveMapToHere +// +// Moves a map to the "HERE" save. +// +void M_SaveMoveMapToHere(void) +{ + char *mapsave = NULL; + char *heresave = NULL; + char tmpnum[33]; + + // haleyjd: no itoa available... + M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap); + + // haleyjd: use M_SafeFilePath, not sprintf + mapsave = M_SafeFilePath(savepath, tmpnum); + heresave = M_SafeFilePath(savepath, "here"); + + // haleyjd: use M_FileExists, not access + if(M_FileExists(mapsave)) + { + remove(heresave); + rename(mapsave, heresave); + } + + Z_Free(mapsave); + Z_Free(heresave); +} + +// +// M_SaveMoveHereToMap +// +// Moves the "HERE" save to a map. +// +void M_SaveMoveHereToMap(void) +{ + char *mapsave = NULL; + char *heresave = NULL; + char tmpnum[33]; + + // haleyjd: no itoa available... + M_snprintf(tmpnum, sizeof(tmpnum), "%d", gamemap); + + mapsave = M_SafeFilePath(savepathtemp, tmpnum); + heresave = M_SafeFilePath(savepathtemp, "here"); + + if(M_FileExists(heresave)) + { + remove(mapsave); + rename(heresave, mapsave); + } + + Z_Free(mapsave); + Z_Free(heresave); +} + +// +// M_SaveMisObj +// +// Writes the mission objective into the MIS_OBJ file. +// +boolean M_SaveMisObj(const char *path) +{ + boolean result; + char *destpath = NULL; + + // haleyjd 20110210: use M_SafeFilePath, not sprintf + destpath = M_SafeFilePath(path, "mis_obj"); + result = M_WriteFile(destpath, mission_objective, OBJECTIVE_LEN); + + Z_Free(destpath); + return result; +} + +// +// M_ReadMisObj +// +// Reads the mission objective from the MIS_OBJ file. +// +void M_ReadMisObj(void) +{ + FILE *f = NULL; + char *srcpath = NULL; + + // haleyjd: use M_SafeFilePath, not sprintf + srcpath = M_SafeFilePath(savepathtemp, "mis_obj"); + + if((f = fopen(srcpath, "rb"))) + { + fread(mission_objective, 1, OBJECTIVE_LEN, f); + fclose(f); + } + + Z_Free(srcpath); +} + +//============================================================================= +// +// Original Routines +// +// haleyjd - None of the below code is derived from Strife itself, but has been +// adapted or created in order to provide secure, portable filepath handling +// for the purposes of savegame support. This is partially needed to allow for +// differences in Choco due to it being multiplatform. The rest exists because +// I cannot stand programming in an impoverished ANSI C environment that +// calls sprintf on fixed-size buffers. :P +// + +// +// M_Calloc +// +// haleyjd 20110210 - original routine +// Because Choco doesn't have Z_Calloc O_o +// +void *M_Calloc(size_t n1, size_t n2) +{ + return (n1 *= n2) ? memset(Z_Malloc(n1, PU_STATIC, NULL), 0, n1) : NULL; +} + +// +// M_StringAlloc +// +// haleyjd: This routine takes any number of strings and a number of extra +// characters, calculates their combined length, and calls Z_Alloca to create +// a temporary buffer of that size. This is extremely useful for allocation of +// file paths, and is used extensively in d_main.c. The pointer returned is +// to a temporary Z_Alloca buffer, which lives until the next main loop +// iteration, so don't cache it. Note that this idiom is not possible with the +// normal non-standard alloca function, which allocates stack space. +// +// [STRIFE] - haleyjd 20110210 +// This routine is taken from the Eternity Engine and adapted to do without +// Z_Alloca. I need secure string concatenation for filepath handling. The +// only difference from use in EE is that the pointer returned in *str must +// be manually freed. +// +int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...) +{ + va_list args; + size_t len = extra; + + if(numstrs < 1) + I_Error("M_StringAlloc: invalid input\n"); + + len += strlen(str1); + + --numstrs; + + if(numstrs != 0) + { + va_start(args, str1); + + while(numstrs != 0) + { + const char *argstr = va_arg(args, const char *); + + len += strlen(argstr); + + --numstrs; + } + + va_end(args); + } + + ++len; + + *str = (char *)(M_Calloc(1, len)); + + return len; +} + +// +// M_NormalizeSlashes +// +// Remove trailing slashes, translate backslashes to slashes +// The string to normalize is passed and returned in str +// +// killough 11/98: rewritten +// +// [STRIFE] - haleyjd 20110210: Borrowed from Eternity and adapted to respect +// the DIR_SEPARATOR define used by Choco Doom. This routine originated in +// BOOM. +// +void M_NormalizeSlashes(char *str) +{ + char *p; + + // Convert all slashes/backslashes to DIR_SEPARATOR + for(p = str; *p; p++) + { + if((*p == '/' || *p == '\\') && *p != DIR_SEPARATOR) + *p = DIR_SEPARATOR; + } + + // Remove trailing slashes + while(p > str && *--p == DIR_SEPARATOR) + *p = 0; + + // Collapse multiple slashes + for(p = str; (*str++ = *p); ) + if(*p++ == DIR_SEPARATOR) + while(*p == DIR_SEPARATOR) + p++; +} + +// +// M_SafeFilePath +// +// haleyjd 20110210 - original routine. +// This routine performs safe, portable concatenation of a base file path +// with another path component or file name. The returned string is Z_Malloc'd +// and should be freed when it has exhausted its usefulness. +// +char *M_SafeFilePath(const char *basepath, const char *newcomponent) +{ + int newstrlen = 0; + char *newstr = NULL; + + if (!strcmp(basepath, "")) + { + basepath = "."; + } + + // Always throw in a slash. M_NormalizeSlashes will remove it in the case + // that either basepath or newcomponent includes a redundant slash at the + // end or beginning respectively. + newstrlen = M_StringAlloc(&newstr, 3, 1, basepath, "/", newcomponent); + M_snprintf(newstr, newstrlen, "%s/%s", basepath, newcomponent); + M_NormalizeSlashes(newstr); + + return newstr; +} + +// +// M_CreateSaveDirs +// +// haleyjd 20110210: Vanilla Strife went tits-up if it didn't have the full set +// of save folders which were created externally by the installer. fraggle says +// that's no good for Choco purposes, and I agree, so this routine will create +// the full set of folders under the configured savegamedir. +// +void M_CreateSaveDirs(const char *savedir) +{ + int i; + + for(i = 0; i < 7; i++) + { + char *compositedir; + + // compose the full path by concatenating with savedir + compositedir = M_SafeFilePath(savedir, M_MakeStrifeSaveDir(i, "")); + + M_MakeDirectory(compositedir); + + Z_Free(compositedir); + } +} + +// +// M_MakeStrifeSaveDir +// +// haleyjd 20110211: Convenience routine +// +char *M_MakeStrifeSaveDir(int slotnum, const char *extra) +{ + static char tmpbuffer[32]; + + M_snprintf(tmpbuffer, sizeof(tmpbuffer), + "strfsav%d.ssg%s", slotnum, extra); + + return tmpbuffer; +} + +// +// M_GetFilePath +// +// haleyjd: STRIFE-FIXME: Temporary? +// Code borrowed from Eternity, and modified to return separator char +// +char M_GetFilePath(const char *fn, char *dest, size_t len) +{ + boolean found_slash = false; + char *p; + char sepchar = '\0'; + + memset(dest, 0, len); + + p = dest + len - 1; + + M_StringCopy(dest, fn, len); + + while(p >= dest) + { + if(*p == '/' || *p == '\\') + { + sepchar = *p; + found_slash = true; // mark that the path ended with a slash + *p = '\0'; + break; + } + *p = '\0'; + p--; + } + + // haleyjd: in the case that no slash was ever found, yet the + // path string is empty, we are dealing with a file local to the + // working directory. The proper path to return for such a string is + // not "", but ".", since the format strings add a slash now. When + // the string is empty but a slash WAS found, we really do want to + // return the empty string, since the path is relative to the root. + if(!found_slash && *dest == '\0') + *dest = '.'; + + // if a separator is not found, default to forward, because Windows + // supports that too. + if(sepchar == '\0') + sepchar = '/'; + + return sepchar; +} + +// EOF + + diff --git a/src/strife/m_saves.h b/src/strife/m_saves.h index 0a65b241..8b6bcede 100644 --- a/src/strife/m_saves.h +++ b/src/strife/m_saves.h @@ -1,56 +1,56 @@ -// -// Copyright(C) 1993-1996 Id Software, Inc. -// Copyright(C) 2010 James Haley, Samuel Villareal -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// DESCRIPTION: -// -// [STRIFE] New Module -// -// Strife Hub Saving Code -// - -#ifndef M_SAVES_H__ -#define M_SAVES_H__ - -#define CHARACTER_NAME_LEN 32 - -extern char *savepath; -extern char *savepathtemp; -extern char *loadpath; -extern char character_name[CHARACTER_NAME_LEN]; - -// Strife Savegame Functions -void ClearTmp(void); -void ClearSlot(void); -void FromCurr(void); -void ToCurr(void); -void M_SaveMoveMapToHere(void); -void M_SaveMoveHereToMap(void); - -boolean M_SaveMisObj(const char *path); -void M_ReadMisObj(void); - -// Custom Utilities for Filepath Handling -void *M_Calloc(size_t n1, size_t n2); -void M_NormalizeSlashes(char *str); -int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...); -char *M_SafeFilePath(const char *basepath, const char *newcomponent); -char M_GetFilePath(const char *fn, char *dest, size_t len); -char *M_MakeStrifeSaveDir(int slotnum, const char *extra); -void M_CreateSaveDirs(const char *savedir); - -#endif - -// EOF - - +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2010 James Haley, Samuel Villareal +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// [STRIFE] New Module +// +// Strife Hub Saving Code +// + +#ifndef M_SAVES_H__ +#define M_SAVES_H__ + +#define CHARACTER_NAME_LEN 32 + +extern char *savepath; +extern char *savepathtemp; +extern char *loadpath; +extern char character_name[CHARACTER_NAME_LEN]; + +// Strife Savegame Functions +void ClearTmp(void); +void ClearSlot(void); +void FromCurr(void); +void ToCurr(void); +void M_SaveMoveMapToHere(void); +void M_SaveMoveHereToMap(void); + +boolean M_SaveMisObj(const char *path); +void M_ReadMisObj(void); + +// Custom Utilities for Filepath Handling +void *M_Calloc(size_t n1, size_t n2); +void M_NormalizeSlashes(char *str); +int M_StringAlloc(char **str, int numstrs, size_t extra, const char *str1, ...); +char *M_SafeFilePath(const char *basepath, const char *newcomponent); +char M_GetFilePath(const char *fn, char *dest, size_t len); +char *M_MakeStrifeSaveDir(int slotnum, const char *extra); +void M_CreateSaveDirs(const char *savedir); + +#endif + +// EOF + + diff --git a/src/strife/p_dialog.c b/src/strife/p_dialog.c index a18fec7d..eb31301b 100644 --- a/src/strife/p_dialog.c +++ b/src/strife/p_dialog.c @@ -1,1413 +1,1413 @@ -// -// Copyright(C) 1993-1996 Id Software, Inc. -// Copyright(C) 2010 James Haley, Samuel Villarreal -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// DESCRIPTION: -// -// [STRIFE] New Module -// -// Dialog Engine for Strife -// - -#include - -#include "z_zone.h" -#include "w_wad.h" -#include "deh_str.h" -#include "d_main.h" -#include "d_mode.h" -#include "d_player.h" -#include "doomstat.h" -#include "m_random.h" -#include "m_menu.h" -#include "m_misc.h" -#include "r_main.h" -#include "v_video.h" -#include "p_local.h" -#include "sounds.h" -#include "p_dialog.h" -#include "s_sound.h" -#include "p_local.h" -#include "p_inter.h" - -// -// Defines and Macros -// - -// haleyjd: size of the original Strife mapdialog_t structure. -#define ORIG_MAPDIALOG_SIZE 0x5EC - -#define DIALOG_INT(field, ptr) \ - field = ((int)ptr[0] | \ - ((int)ptr[1] << 8) | \ - ((int)ptr[2] << 16) | \ - ((int)ptr[3] << 24)); \ - ptr += 4; - -#define DIALOG_STR(field, ptr, len) \ - memcpy(field, ptr, len); \ - ptr += len; - -// -// Globals -// - -// This can be toggled at runtime to determine if the full dialog messages -// are subtitled on screen or not. Defaults to off. -int dialogshowtext = false; - -// The global mission objective buffer. This gets written to and read from file, -// and is set by dialogs and line actions. -char mission_objective[OBJECTIVE_LEN]; - -// -// Static Globals -// - -// True if SCRIPT00 is loaded. -static boolean script0loaded; - -// Number of dialogs defined in the current level's script. -static int numleveldialogs; - -// The actual level dialogs. This didn't exist in Strife, but is new to account -// for structure alignment/packing concerns, given that Chocolate Doom is -// multiplatform. -static mapdialog_t *leveldialogs; - -// The actual script00 dialogs. As above. -static mapdialog_t *script0dialogs; - -// Number of dialogs defined in the SCRIPT00 lump. -static int numscript0dialogs; - -// The player engaged in dialog. This is always player 1, though, since Rogue -// never completed the ability to use dialog outside of single-player mode. -static player_t *dialogplayer; - -// The object to which the player is speaking. -static mobj_t *dialogtalker; - -// The talker's current angle -static angle_t dialogtalkerangle; - -// The currently active mapdialog object. -static mapdialog_t *currentdialog; - -// Text at the end of the choices -static char dialoglastmsgbuffer[48]; - -// Item to display to player when picked up or recieved -static char pickupstring[46]; - -// Health based on gameskill given by the front's medic -static const int healthamounts[] = { -100 , -75, -50, -50, -100 }; - -//============================================================================= -// -// Dialog State Sets -// -// These are used to animate certain actors in response to what happens in -// their dialog sequences. -// - -typedef struct dialogstateset_s -{ - mobjtype_t type; // the type of object - statenum_t greet; // greeting state, for start of dialog - statenum_t yes; // "yes" state, for an affirmative response - statenum_t no; // "no" state, when you don't have the right items -} dialogstateset_t; - -static dialogstateset_t dialogstatesets[] = -{ - { MT_PLAYER, S_NULL, S_NULL, S_NULL }, - { MT_SHOPKEEPER_W, S_MRGT_00, S_MRYS_00, S_MRNO_00 }, - { MT_SHOPKEEPER_B, S_MRGT_00, S_MRYS_00, S_MRNO_00 }, - { MT_SHOPKEEPER_A, S_MRGT_00, S_MRYS_00, S_MRNO_00 }, - { MT_SHOPKEEPER_M, S_MRGT_00, S_MRYS_00, S_MRNO_00 } -}; - -// Rogue stored this in a static global rather than making it a define... -static int numdialogstatesets = arrlen(dialogstatesets); - -// Current dialog talker state -static dialogstateset_t *dialogtalkerstates; - -//============================================================================= -// -// Random Messages -// -// Rogue hard-coded these so they wouldn't have to repeat them several times -// in the SCRIPT00 lump, apparently. -// - -#define MAXRNDMESSAGES 10 - -typedef struct rndmessage_s -{ - const char *type_name; - int nummessages; - char *messages[MAXRNDMESSAGES]; -} rndmessage_t; - -static rndmessage_t rndMessages[] = -{ - // Peasants - { - "PEASANT", - 10, - { - "PLEASE DON'T HURT ME.", - - "IF YOU'RE LOOKING TO HURT ME, I'M \n" - "NOT REALLY WORTH THE EFFORT.", - - "I DON'T KNOW ANYTHING.", - - "GO AWAY OR I'LL CALL THE GUARDS!", - - "I WISH SOMETIMES THAT ALL THESE \n" - "REBELS WOULD JUST LEARN THEIR \n" - "PLACE AND STOP THIS NONSENSE.", - - "JUST LEAVE ME ALONE, OK?", - - "I'M NOT SURE, BUT SOMETIMES I THINK \n" - "THAT I KNOW SOME OF THE ACOLYTES.", - - "THE ORDER'S GOT EVERYTHING AROUND HERE PRETTY WELL LOCKED UP TIGHT.", - - "THERE'S NO WAY THAT THIS IS JUST A \n" - "SECURITY FORCE.", - - "I'VE HEARD THAT THE ORDER IS REALLY \n" - "NERVOUS ABOUT THE FRONT'S \n" - "ACTIONS AROUND HERE." - } - }, - // Rebel - { - "REBEL", - 10, - { - "THERE'S NO WAY THE ORDER WILL \n" - "STAND AGAINST US.", - - "WE'RE ALMOST READY TO STRIKE. \n" - "MACIL'S PLANS ARE FALLING IN PLACE.", - - "WE'RE ALL BEHIND YOU, DON'T WORRY.", - - "DON'T GET TOO CLOSE TO ANY OF THOSE BIG ROBOTS. THEY'LL MELT YOU DOWN \n" - "FOR SCRAP!", - - "THE DAY OF OUR GLORY WILL SOON \n" - "COME, AND THOSE WHO OPPOSE US WILL \n" - "BE CRUSHED!", - - "DON'T GET TOO COMFORTABLE. WE'VE \n" - "STILL GOT OUR WORK CUT OUT FOR US.", - - "MACIL SAYS THAT YOU'RE THE NEW \n" - "HOPE. BEAR THAT IN MIND.", - - "ONCE WE'VE TAKEN THESE CHARLATANS DOWN, WE'LL BE ABLE TO REBUILD THIS " - "WORLD AS IT SHOULD BE.", - - "REMEMBER THAT YOU AREN'T FIGHTING \n" - "JUST FOR YOURSELF, BUT FOR \n" - "EVERYONE HERE AND OUTSIDE.", - - "AS LONG AS ONE OF US STILL STANDS, \n" - "WE WILL WIN." - } - }, - // Acolyte - { - "AGUARD", - 10, - { - "MOVE ALONG, PEASANT.", - - "FOLLOW THE TRUE FAITH, ONLY THEN \n" - "WILL YOU BEGIN TO UNDERSTAND.", - - "ONLY THROUGH DEATH CAN ONE BE \n" - "TRULY REBORN.", - - "I'M NOT INTERESTED IN YOUR USELESS \n" - "DRIVEL.", - - "IF I HAD WANTED TO TALK TO YOU I \n" - "WOULD HAVE TOLD YOU SO.", - - "GO AND ANNOY SOMEONE ELSE!", - - "KEEP MOVING!", - - "IF THE ALARM GOES OFF, JUST STAY OUT OF OUR WAY!", - - "THE ORDER WILL CLEANSE THE WORLD \n" - "AND USHER IT INTO THE NEW ERA.", - - "PROBLEM? NO, I THOUGHT NOT.", - } - }, - // Beggar - { - "BEGGAR", - 10, - { - "ALMS FOR THE POOR?", - - "WHAT ARE YOU LOOKING AT, SURFACER?", - - "YOU WOULDN'T HAVE ANY EXTRA FOOD, WOULD YOU?", - - "YOU SURFACE PEOPLE WILL NEVER \n" - " " - " UNDERSTAND US.", - - "HA, THE GUARDS CAN'T FIND US. THOSE \n" - "IDIOTS DON'T EVEN KNOW WE EXIST.", - - "ONE DAY EVERYONE BUT THOSE WHO SERVE THE ORDER WILL BE FORCED TO " - " JOIN US.", - - "STARE NOW, BUT YOU KNOW THAT THIS WILL BE YOUR OWN FACE ONE DAY.", - - // Note: "NOTHING THING" is an authentic typo - "THERE'S NOTHING THING MORE \n" - "ANNOYING THAN A SURFACER WITH AN ATTITUDE!", - - "THE ORDER WILL MAKE SHORT WORK OF YOUR PATHETIC FRONT.", - - "WATCH YOURSELF SURFACER. WE KNOW OUR ENEMIES!" - } - }, - // Templar - { - "PGUARD", - 10, - { - "WE ARE THE HANDS OF FATE. TO EARN \n" - "OUR WRATH IS TO FIND OBLIVION!", - - "THE ORDER WILL CLEANSE THE WORLD \n" - "OF THE WEAK AND CORRUPT!", - - "OBEY THE WILL OF THE MASTERS!", - - "LONG LIFE TO THE BROTHERS OF THE \n" - "ORDER!", - - "FREE WILL IS AN ILLUSION THAT BINDS \n" - "THE WEAK MINDED.", - - "POWER IS THE PATH TO GLORY. TO \n" - "FOLLOW THE ORDER IS TO WALK THAT \n" - "PATH!", - - "TAKE YOUR PLACE AMONG THE \n" - "RIGHTEOUS, JOIN US!", - - "THE ORDER PROTECTS ITS OWN.", - - "ACOLYTES? THEY HAVE YET TO SEE THE FULL GLORY OF THE ORDER.", - - "IF THERE IS ANY HONOR INSIDE THAT \n" - "PATHETIC SHELL OF A BODY, \n" - "YOU'LL ENTER INTO THE ARMS OF THE \n" - "ORDER." - } - } -}; - -// And again, this could have been a define, but was a variable. -static int numrndmessages = arrlen(rndMessages); - -//============================================================================= -// -// Dialog Menu Structure -// -// The Strife dialog system is actually just a serious abuse of the DOOM menu -// engine. Hence why it doesn't work in multiplayer games or during demo -// recording. -// - -#define NUMDIALOGMENUITEMS 6 - -static void P_DialogDrawer(void); - -static menuitem_t dialogmenuitems[] = -{ - { 1, "", P_DialogDoChoice, '1' }, // These items are loaded dynamically - { 1, "", P_DialogDoChoice, '2' }, - { 1, "", P_DialogDoChoice, '3' }, - { 1, "", P_DialogDoChoice, '4' }, - { 1, "", P_DialogDoChoice, '5' }, - { 1, "", P_DialogDoChoice, '6' } // Item 6 is always the dismissal item -}; - -static menu_t dialogmenu = -{ - NUMDIALOGMENUITEMS, - NULL, - dialogmenuitems, - P_DialogDrawer, - 42, - 75, - 0 -}; - -// Lump number of the dialog background picture, if any. -static int dialogbgpiclumpnum; - -// Name of current speaking character. -static char *dialogname; - -// Current dialog text. -static const char *dialogtext; - -//============================================================================= -// -// Routines -// - -// -// P_ParseDialogLump -// -// haleyjd 09/02/10: This is an original function added to parse out the -// dialogs from the dialog lump rather than reading them raw from the lump -// pointer. This avoids problems with structure packing. -// -static void P_ParseDialogLump(byte *lump, mapdialog_t **dialogs, - int numdialogs, int tag) -{ - int i; - byte *rover = lump; - - *dialogs = Z_Malloc(numdialogs * sizeof(mapdialog_t), tag, NULL); - - for(i = 0; i < numdialogs; i++) - { - int j; - mapdialog_t *curdialog = &((*dialogs)[i]); - - DIALOG_INT(curdialog->speakerid, rover); - DIALOG_INT(curdialog->dropitem, rover); - DIALOG_INT(curdialog->checkitem[0], rover); - DIALOG_INT(curdialog->checkitem[1], rover); - DIALOG_INT(curdialog->checkitem[2], rover); - DIALOG_INT(curdialog->jumptoconv, rover); - DIALOG_STR(curdialog->name, rover, MDLG_NAMELEN); - DIALOG_STR(curdialog->voice, rover, MDLG_LUMPLEN); - DIALOG_STR(curdialog->backpic, rover, MDLG_LUMPLEN); - DIALOG_STR(curdialog->text, rover, MDLG_TEXTLEN); - - // copy choices - for(j = 0; j < 5; j++) - { - mapdlgchoice_t *curchoice = &(curdialog->choices[j]); - DIALOG_INT(curchoice->giveitem, rover); - DIALOG_INT(curchoice->needitems[0], rover); - DIALOG_INT(curchoice->needitems[1], rover); - DIALOG_INT(curchoice->needitems[2], rover); - DIALOG_INT(curchoice->needamounts[0], rover); - DIALOG_INT(curchoice->needamounts[1], rover); - DIALOG_INT(curchoice->needamounts[2], rover); - DIALOG_STR(curchoice->text, rover, MDLG_CHOICELEN); - DIALOG_STR(curchoice->textok, rover, MDLG_MSGLEN); - DIALOG_INT(curchoice->next, rover); - DIALOG_INT(curchoice->objective, rover); - DIALOG_STR(curchoice->textno, rover, MDLG_MSGLEN); - } - } -} - -// -// P_DialogLoad -// -// [STRIFE] New function -// haleyjd 09/02/10: Loads the dialog script for the current map. Also loads -// SCRIPT00 if it has not yet been loaded. -// -void P_DialogLoad(void) -{ - char lumpname[9]; - int lumpnum; - - // load the SCRIPTxy lump corresponding to MAPxy, if it exists. - DEH_snprintf(lumpname, sizeof(lumpname), "script%02d", gamemap); - if((lumpnum = W_CheckNumForName(lumpname)) == -1) - numleveldialogs = 0; - else - { - byte *leveldialogptr = W_CacheLumpNum(lumpnum, PU_STATIC); - numleveldialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE; - P_ParseDialogLump(leveldialogptr, &leveldialogs, numleveldialogs, - PU_LEVEL); - Z_Free(leveldialogptr); // haleyjd: free the original lump - } - - // also load SCRIPT00 if it has not been loaded yet - if(!script0loaded) - { - byte *script0ptr; - - script0loaded = true; - // BUG: Rogue should have used W_GetNumForName here... - lumpnum = W_CheckNumForName(DEH_String("script00")); - script0ptr = W_CacheLumpNum(lumpnum, PU_STATIC); - numscript0dialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE; - P_ParseDialogLump(script0ptr, &script0dialogs, numscript0dialogs, - PU_STATIC); - Z_Free(script0ptr); // haleyjd: free the original lump - } -} - -// -// P_PlayerHasItem -// -// [STRIFE] New function -// haleyjd 09/02/10: Checks for inventory items, quest flags, etc. for dialogs. -// Returns the amount possessed, or 0 if none. -// -int P_PlayerHasItem(player_t *player, mobjtype_t type) -{ - int i; - - if(type > 0) - { - // check keys - if(type >= MT_KEY_BASE && type < MT_INV_SHADOWARMOR) - return (player->cards[type - MT_KEY_BASE]); - - // check sigil pieces - if(type >= MT_SIGIL_A && type <= MT_SIGIL_E) - return (type - MT_SIGIL_A <= player->sigiltype); - - // check quest tokens - if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31) - return (player->questflags & (1 << (type - MT_TOKEN_QUEST1))); - - // check inventory - for(i = 0; i < 32; i++) - { - if(type == player->inventory[i].type) - return player->inventory[i].amount; - } - } - return 0; -} - -// -// P_DialogFind -// -// [STRIFE] New function -// haleyjd 09/03/10: Looks for a dialog definition matching the given -// Script ID # for an mobj. -// -mapdialog_t *P_DialogFind(mobjtype_t type, int jumptoconv) -{ - int i; - - // check the map-specific dialogs first - for(i = 0; i < numleveldialogs; i++) - { - if(type == leveldialogs[i].speakerid) - { - if(jumptoconv <= 1) - return &leveldialogs[i]; - else - --jumptoconv; - } - } - - // check SCRIPT00 dialogs next - for(i = 0; i < numscript0dialogs; i++) - { - if(type == script0dialogs[i].speakerid) - return &script0dialogs[i]; - } - - // the default dialog is script 0 in the SCRIPT00 lump. - return &script0dialogs[0]; -} - -// -// P_DialogGetStates -// -// [STRIFE] New function -// haleyjd 09/03/10: Find the set of special dialog states (greetings, yes, no) -// for a particular thing type. -// -static dialogstateset_t *P_DialogGetStates(mobjtype_t type) -{ - int i; - - // look for a match by type - for(i = 0; i < numdialogstatesets; i++) - { - if(type == dialogstatesets[i].type) - return &dialogstatesets[i]; - } - - // return the default 0 record if no match. - return &dialogstatesets[0]; -} - -// -// P_DialogGetMsg -// -// [STRIFE] New function -// haleyjd 09/03/10: Redirects dialog messages when the script indicates that -// the actor should use a random message stored in the executable instead. -// -static const char *P_DialogGetMsg(const char *message) -{ - // if the message starts with "RANDOM"... - if(!strncasecmp(message, DEH_String("RANDOM"), 6)) - { - int i; - const char *nameloc = message + 7; - - // look for a match in rndMessages for the string starting - // 7 chars after "RANDOM_" - for(i = 0; i < numrndmessages; i++) - { - if(!strncasecmp(nameloc, rndMessages[i].type_name, 4)) - { - // found a match, so return a random message - int rnd = M_Random(); - int nummessages = rndMessages[i].nummessages; - return DEH_String(rndMessages[i].messages[rnd % nummessages]); - } - } - } - - // otherwise, just return the message passed in. - return message; -} - -// -// P_GiveInventoryItem -// -// [STRIFE] New function -// haleyjd 09/03/10: Give an inventory item to the player, if possible. -// villsa 09/09/10: Fleshed out routine -// -boolean P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type) -{ - int curinv = 0; - int i; - boolean ok = false; - mobjtype_t item = 0; - inventory_t* invtail; - - // repaint the status bar due to inventory changing - player->st_update = true; - - while(1) - { - // inventory is full - if(curinv > player->numinventory) - return true; - - item = player->inventory[curinv].type; - if(type < item) - { - if(curinv != MAXINVENTORYSLOTS) - { - // villsa - sort inventory item if needed - invtail = &player->inventory[player->numinventory - 1]; - if(player->numinventory >= (curinv + 1)) - { - for(i = player->numinventory; i >= (curinv + 1); --i) - { - invtail[1].sprite = invtail[0].sprite; - invtail[1].type = invtail[0].type; - invtail[1].amount = invtail[0].amount; - - invtail--; - } - } - - // villsa - add inventory item - player->inventory[curinv].amount = 1; - player->inventory[curinv].sprite = sprnum; - player->inventory[curinv].type = type; - - // sort cursor if needed - if(player->numinventory) - { - if(curinv <= player->inventorycursor) - player->inventorycursor++; - } - - player->numinventory++; - - return true; - } - - return false; - } - - if(type == item) - break; - - curinv++; - } - - // check amount of inventory item by using the mass from mobjinfo - if(player->inventory[curinv].amount < mobjinfo[item].mass) - { - player->inventory[curinv].amount++; - ok = true; - } - else - ok = false; - - return ok; -} - -// -// P_GiveItemToPlayer -// -// [STRIFE] New function -// haleyjd 09/03/10: Sorts out how to give something to the player. -// Not strictly just for inventory items. -// villsa 09/09/10: Fleshed out function -// -boolean P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type) -{ - int i = 0; - line_t junk; - int sound = sfx_itemup; // haleyjd 09/21/10: different sounds for items - - // set quest if mf_givequest flag is set - if(mobjinfo[type].flags & MF_GIVEQUEST) - player->questflags |= 1 << (mobjinfo[type].speed - 1); - - // check for keys - if(type >= MT_KEY_BASE && type <= MT_NEWKEY5) - { - P_GiveCard(player, type - MT_KEY_BASE); - return true; - } - - // check for quest tokens - if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31) - { - if(mobjinfo[type].name) - { - M_StringCopy(pickupstring, DEH_String(mobjinfo[type].name), 39); - player->message = pickupstring; - } - player->questflags |= 1 << (type - MT_TOKEN_QUEST1); - - if(player == &players[consoleplayer]) - S_StartSound(NULL, sound); - return true; - } - - // haleyjd 09/22/10: Refactored to give sprites higher priority than - // mobjtypes and to implement missing logic. - switch(sprnum) - { - case SPR_HELT: // This is given only by the "DONNYTRUMP" cheat (aka Midas) - P_GiveInventoryItem(player, SPR_HELT, MT_TOKEN_TOUGHNESS); - P_GiveInventoryItem(player, SPR_GUNT, MT_TOKEN_ACCURACY); - - // [STRIFE] Bizarre... - for(i = 0; i < 5 * player->accuracy + 300; i++) - P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); - break; - - case SPR_ARM1: // Armor 1 - if(!P_GiveArmor(player, -2)) - P_GiveInventoryItem(player, sprnum, type); - break; - - case SPR_ARM2: // Armor 2 - if(!P_GiveArmor(player, -1)) - P_GiveInventoryItem(player, sprnum, type); - break; - - case SPR_COIN: // 1 Gold - P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); - break; - - case SPR_CRED: // 10 Gold - for(i = 0; i < 10; i++) - P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); - break; - - case SPR_SACK: // 25 gold - for(i = 0; i < 25; i++) - P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); - break; - - case SPR_CHST: // 50 gold - for(i = 0; i < 50; i++) - P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); - break; // haleyjd 20141215: missing break, caused Rowan to not take ring from you. - - case SPR_BBOX: // Box of Bullets - if(!P_GiveAmmo(player, am_bullets, 5)) - return false; - break; - - case SPR_BLIT: // Bullet Clip - if(!P_GiveAmmo(player, am_bullets, 1)) - return false; - break; - - case SPR_PMAP: // Map powerup - if(!P_GivePower(player, pw_allmap)) - return false; - sound = sfx_yeah; // bluh-doop! - break; - - case SPR_COMM: // Communicator - if(!P_GivePower(player, pw_communicator)) - return false; - sound = sfx_yeah; // bluh-doop! - break; - - case SPR_MSSL: // Mini-missile - if(!P_GiveAmmo(player, am_missiles, 1)) - return false; - break; - - case SPR_ROKT: // Crate of missiles - if(!P_GiveAmmo(player, am_missiles, 5)) - return false; - break; - - case SPR_BRY1: // Battery cell - if(!P_GiveAmmo(player, am_cell, 1)) - return false; - break; - - case SPR_CPAC: // Cell pack - if(!P_GiveAmmo(player, am_cell, 5)) - return false; - break; - - case SPR_PQRL: // Poison bolts - if(!P_GiveAmmo(player, am_poisonbolts, 5)) - return false; - break; - - case SPR_XQRL: // Electric bolts - if(!P_GiveAmmo(player, am_elecbolts, 5)) - return false; - break; - - case SPR_GRN1: // HE Grenades - if(!P_GiveAmmo(player, am_hegrenades, 1)) - return false; - break; - - case SPR_GRN2: // WP Grenades - if(!P_GiveAmmo(player, am_wpgrenades, 1)) - return false; - break; - - case SPR_BKPK: // Backpack (aka Ammo Satchel) - if(!player->backpack) - { - for(i = 0; i < NUMAMMO; i++) - player->maxammo[i] *= 2; - - player->backpack = true; - } - for(i = 0; i < NUMAMMO; i++) - P_GiveAmmo(player, i, 1); - break; - - case SPR_RIFL: // Assault Rifle - if(player->weaponowned[wp_rifle]) - return false; - - if(!P_GiveWeapon(player, wp_rifle, false)) - return false; - - sound = sfx_wpnup; // SHK-CHK! - break; - - case SPR_FLAM: // Flamethrower - if(player->weaponowned[wp_flame]) - return false; - - if(!P_GiveWeapon(player, wp_flame, false)) - return false; - - sound = sfx_wpnup; // SHK-CHK! - break; - - case SPR_MMSL: // Mini-missile Launcher - if(player->weaponowned[wp_missile]) - return false; - - if(!P_GiveWeapon(player, wp_missile, false)) - return false; - - sound = sfx_wpnup; // SHK-CHK! - break; - - case SPR_TRPD: // Mauler - if(player->weaponowned[wp_mauler]) - return false; - - if(!P_GiveWeapon(player, wp_mauler, false)) - return false; - - sound = sfx_wpnup; // SHK-CHK! - break; - - case SPR_CBOW: // Here's a crossbow. Just aim straight, and *SPLAT!* - if(player->weaponowned[wp_elecbow]) - return false; - - if(!P_GiveWeapon(player, wp_elecbow, false)) - return false; - - sound = sfx_wpnup; // SHK-CHK! - break; - - case SPR_TOKN: // Miscellaneous items - These are determined by thingtype. - switch(type) - { - case MT_KEY_HAND: // Severed hand - P_GiveCard(player, key_SeveredHand); - break; - - case MT_MONY_300: // 300 Gold (this is the only way to get it, in fact) - for(i = 0; i < 300; i++) - P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); - break; - - case MT_TOKEN_AMMO: // Ammo token - you get this from the Weapons Trainer - if(player->ammo[am_bullets] >= 50) - return false; - - player->ammo[am_bullets] = 50; - break; - - case MT_TOKEN_HEALTH: // Health token - from the Front's doctor - if(!P_GiveBody(player, healthamounts[gameskill])) - return false; - break; - - case MT_TOKEN_ALARM: // Alarm token - particularly from the Oracle. - P_NoiseAlert(player->mo, player->mo); - A_AlertSpectreC(dialogtalker); // BUG: assumes in a dialog o_O - break; - - case MT_TOKEN_DOOR1: // Door special 1 - junk.tag = 222; - EV_DoDoor(&junk, vld_open); - break; - - case MT_TOKEN_PRISON_PASS: // Door special 1 - Prison pass - junk.tag = 223; - EV_DoDoor(&junk, vld_open); - if(gamemap == 2) // If on Tarnhill, give Prison pass object - P_GiveInventoryItem(player, sprnum, type); - break; - - case MT_TOKEN_SHOPCLOSE: // Door special 3 - "Shop close" - unused? - junk.tag = 222; - EV_DoDoor(&junk, vld_close); - break; - - case MT_TOKEN_DOOR3: // Door special 4 (or 3? :P ) - junk.tag = 224; - EV_DoDoor(&junk, vld_close); - break; - - case MT_TOKEN_STAMINA: // Stamina upgrade - if(player->stamina >= 100) - return false; - - player->stamina += 10; - P_GiveBody(player, 200); // full healing - break; - - case MT_TOKEN_NEW_ACCURACY: // Accuracy upgrade - if(player->accuracy >= 100) - return false; - - player->accuracy += 10; - break; - - case MT_SLIDESHOW: // Slideshow (start a finale) - gameaction = ga_victory; - if(gamemap == 10) - P_GiveItemToPlayer(player, SPR_TOKN, MT_TOKEN_QUEST17); - break; - - default: // The default is to just give it as an inventory item. - P_GiveInventoryItem(player, sprnum, type); - break; - } - break; - - default: // The ultimate default: Give it as an inventory item. - if(!P_GiveInventoryItem(player, sprnum, type)) - return false; - break; - } - - // Play sound. - if(player == &players[consoleplayer]) - S_StartSound(NULL, sound); - - return true; -} - -// -// P_TakeDialogItem -// -// [STRIFE] New function -// haleyjd 09/03/10: Removes needed items from the player's inventory. -// -static void P_TakeDialogItem(player_t *player, int type, int amount) -{ - int i; - - if(amount <= 0) - return; - - for(i = 0; i < player->numinventory; i++) - { - // find a matching item - if(type != player->inventory[i].type) - continue; - - // if there is none left... - if((player->inventory[i].amount -= amount) < 1) - { - // ...shift everything above it down - int j; - - // BUG: They should have stopped at j < numinventory. This - // seems to implicitly assume that numinventory is always at - // least one less than the max # of slots, otherwise it - // pulls in data from the following player_t fields: - // st_update, numinventory, inventorycursor, accuracy, stamina - for(j = i + 1; j <= player->numinventory; j++) - { - inventory_t *item1 = &(player->inventory[j - 1]); - inventory_t *item2 = &(player->inventory[j]); - - *item1 = *item2; - } - - // blank the topmost slot - // BUG: This will overwrite the aforementioned fields if - // numinventory is equal to the number of slots! - // STRIFE-TODO: Overflow emulation? - player->inventory[player->numinventory].type = NUMMOBJTYPES; - player->inventory[player->numinventory].sprite = -1; - player->numinventory--; - - // update cursor position - if(player->inventorycursor >= player->numinventory) - { - if(player->inventorycursor) - player->inventorycursor--; - } - } // end if - - return; // done! - - } // end for -} - -// -// P_DialogDrawer -// -// This function is set as the drawer callback for the dialog menu. -// -static void P_DialogDrawer(void) -{ - angle_t angle; - int y; - int i; - int height; - int finaly; - char choicetext[64]; - char choicetext2[64]; - - // Run down bonuscount faster than usual so that flashes from being given - // items are less obvious. - if(dialogplayer->bonuscount) - { - dialogplayer->bonuscount -= 3; - if(dialogplayer->bonuscount < 0) - dialogplayer->bonuscount = 0; - } - - angle = R_PointToAngle2(dialogplayer->mo->x, - dialogplayer->mo->y, - dialogtalker->x, - dialogtalker->y); - angle -= dialogplayer->mo->angle; - - // Dismiss the dialog if the player is out of alignment, or the thing he was - // talking to is now engaged in battle. - if ((angle > ANG45 && angle < (ANG270+ANG45)) - || (dialogtalker->flags & MF_NODIALOG) != 0) - { - P_DialogDoChoice(dialogmenu.numitems - 1); - } - - dialogtalker->reactiontime = 2; - - // draw background - if(dialogbgpiclumpnum != -1) - { - patch_t *patch = W_CacheLumpNum(dialogbgpiclumpnum, PU_CACHE); - V_DrawPatchDirect(0, 0, patch); - } - - // if there's a valid background pic, delay drawing the rest of the menu - // for a while; otherwise, it will appear immediately - if(dialogbgpiclumpnum == -1 || menupausetime <= gametic) - { - if(menuindialog) - { - // time to pause the game? - if(menupausetime + 3 < gametic) - menupause = true; - } - - // draw character name - M_WriteText(12, 18, dialogname); - y = 28; - - // show text (optional for dialogs with voices) - if(dialogshowtext || currentdialog->voice[0] == '\0') - y = M_WriteText(20, 28, dialogtext); - - height = 20 * dialogmenu.numitems; - - finaly = 175 - height; // preferred height - if(y > finaly) - finaly = 199 - height; // height it will bump down to if necessary. - - // draw divider - M_WriteText(42, finaly - 6, DEH_String("______________________________")); - - dialogmenu.y = finaly + 6; - y = 0; - - // draw the menu items - for(i = 0; i < dialogmenu.numitems - 1; i++) - { - DEH_snprintf(choicetext, sizeof(choicetext), - "%d) %s", i + 1, currentdialog->choices[i].text); - - // alternate text for items that need money - if(currentdialog->choices[i].needamounts[0] > 0) - { - // haleyjd 20120401: necessary to avoid undefined behavior: - M_StringCopy(choicetext2, choicetext, sizeof(choicetext2)); - DEH_snprintf(choicetext, sizeof(choicetext), - "%s for %d", choicetext2, - currentdialog->choices[i].needamounts[0]); - } - - M_WriteText(dialogmenu.x, dialogmenu.y + 3 + y, choicetext); - y += 19; - } - - // draw the final item for dismissing the dialog - M_WriteText(dialogmenu.x, 19 * i + dialogmenu.y + 3, dialoglastmsgbuffer); - } -} - -// -// P_DialogDoChoice -// -// [STRIFE] New function -// haleyjd 09/05/10: Handles making a choice in a dialog. Installed as the -// callback for all items in the dialogmenu structure. -// -void P_DialogDoChoice(int choice) -{ - int i = 0, nextdialog = 0; - boolean candochoice = true; - char *message = NULL; - mapdlgchoice_t *currentchoice; - - if(choice == -1) - choice = dialogmenu.numitems - 1; - - currentchoice = &(currentdialog->choices[choice]); - - I_StartVoice(NULL); // STRIFE-TODO: verify (should stop previous voice I believe) - - // villsa 09/08/10: converted into for loop - for(i = 0; i < MDLG_MAXITEMS; i++) - { - if(P_PlayerHasItem(dialogplayer, currentchoice->needitems[i]) < - currentchoice->needamounts[i]) - { - candochoice = false; // nope, missing something - } - } - - if(choice != dialogmenu.numitems - 1 && candochoice) - { - int item; - - message = currentchoice->textok; - if(dialogtalkerstates->yes) - P_SetMobjState(dialogtalker, dialogtalkerstates->yes); - - item = currentchoice->giveitem; - if(item < 0 || - P_GiveItemToPlayer(dialogplayer, - states[mobjinfo[item].spawnstate].sprite, - item)) - { - // if successful, take needed items - int count = 0; - // villsa 09/08/10: converted into for loop - for(count = 0; count < MDLG_MAXITEMS; count++) - { - P_TakeDialogItem(dialogplayer, - currentchoice->needitems[count], - currentchoice->needamounts[count]); - } - } - else - message = DEH_String("You seem to have enough!"); - - // store next dialog into the talking actor - nextdialog = currentchoice->next; - if(nextdialog != 0) - dialogtalker->miscdata = (byte)(abs(nextdialog)); - } - else - { - // not successful - message = currentchoice->textno; - if(dialogtalkerstates->no) - P_SetMobjState(dialogtalker, dialogtalkerstates->no); - } - - if(choice != dialogmenu.numitems - 1) - { - int objective; - char *objlump; - - if((objective = currentchoice->objective)) - { - DEH_snprintf(mission_objective, OBJECTIVE_LEN, "log%i", objective); - objlump = W_CacheLumpName(mission_objective, PU_CACHE); - M_StringCopy(mission_objective, objlump, OBJECTIVE_LEN); - } - // haleyjd 20130301: v1.31 hack: if first char of message is a period, - // clear the player's message. Is this actually used anywhere? - if(gameversion == exe_strife_1_31 && message[0] == '.') - message = NULL; - dialogplayer->message = message; - } - - dialogtalker->angle = dialogtalkerangle; - dialogplayer->st_update = true; - M_ClearMenus(0); - - if(nextdialog >= 0 || gameaction == ga_victory) // Macil hack - menuindialog = false; - else - P_DialogStart(dialogplayer); -} - -// -// P_DialogStartP1 -// -// [STRIFE] New function -// haleyjd 09/13/10: This is a hack used by the finale system. -// -void P_DialogStartP1(void) -{ - P_DialogStart(&players[0]); -} - -// -// P_DialogStart -// -// villsa [STRIFE] New function -// -void P_DialogStart(player_t *player) -{ - int i = 0; - int pic; - int rnd = 0; - char* byetext; - int jumptoconv; - - if(menuactive || netgame) - return; - - // are we facing towards our NPC? - P_AimLineAttack(player->mo, player->mo->angle, (128*FRACUNIT)); - if(!linetarget) - { - P_AimLineAttack(player->mo, player->mo->angle + (ANG90/16), (128*FRACUNIT)); - if(!linetarget) - P_AimLineAttack(player->mo, player->mo->angle - (ANG90/16), (128*FRACUNIT)); - } - - if(!linetarget) - return; - - // already in combat, can't talk to it - if(linetarget->flags & MF_NODIALOG) - return; - - // set pointer to the character talking - dialogtalker = linetarget; - - // play a sound - if(player == &players[consoleplayer]) - S_StartSound(0, sfx_radio); - - linetarget->target = player->mo; // target the player - dialogtalker->reactiontime = 2; // set reactiontime - dialogtalkerangle = dialogtalker->angle; // remember original angle - - // face talker towards player - A_FaceTarget(dialogtalker); - - // face towards NPC's direction - player->mo->angle = R_PointToAngle2(player->mo->x, - player->mo->y, - dialogtalker->x, - dialogtalker->y); - // set pointer to player talking - dialogplayer = player; - - // haleyjd 09/08/10: get any stored dialog state from this object - jumptoconv = linetarget->miscdata; - - // check item requirements - while(1) - { - int i = 0; - currentdialog = P_DialogFind(linetarget->type, jumptoconv); - - // dialog's jumptoconv equal to 0? There's nothing to jump to. - if(currentdialog->jumptoconv == 0) - break; - - // villsa 09/08/10: converted into for loop - for(i = 0; i < MDLG_MAXITEMS; i++) - { - // if the item is non-zero, the player must have at least one in his - // or her inventory - if(currentdialog->checkitem[i] != 0 && - P_PlayerHasItem(dialogplayer, currentdialog->checkitem[i]) < 1) - break; - } - - if(i < MDLG_MAXITEMS) // didn't find them all? this is our dialog! - break; - - jumptoconv = currentdialog->jumptoconv; - } - - M_DialogDimMsg(20, 28, currentdialog->text, false); - dialogtext = P_DialogGetMsg(currentdialog->text); - - // get states - dialogtalkerstates = P_DialogGetStates(linetarget->type); - - // have talker greet the player - if(dialogtalkerstates->greet) - P_SetMobjState(dialogtalker, dialogtalkerstates->greet); - - // get talker's name - if(currentdialog->name[0]) - dialogname = currentdialog->name; - else - { - // use a fallback: - if(mobjinfo[linetarget->type].name) - dialogname = DEH_String(mobjinfo[linetarget->type].name); // mobjtype name - else - dialogname = DEH_String("Person"); // default name - like Joe in Doom 3 :P - } - - // setup number of choices to choose from - for(i = 0; i < MDLG_MAXCHOICES; i++) - { - if(!currentdialog->choices[i].giveitem) - break; - } - - // set number of choices to menu - dialogmenu.numitems = i + 1; - - rnd = M_Random() % 3; - - // setup dialog menu - M_StartControlPanel(); - menupause = false; - menuindialog = true; - menupausetime = gametic + 17; - currentMenu = &dialogmenu; - - if(i >= dialogmenu.lastOn) - itemOn = dialogmenu.lastOn; - else - itemOn = 0; - - // get backdrop - pic = W_CheckNumForName(currentdialog->backpic); - dialogbgpiclumpnum = pic; - if(pic != -1) - V_DrawPatchDirect(0, 0, W_CacheLumpNum(pic, PU_CACHE)); - - // get voice - I_StartVoice(currentdialog->voice); - - // get bye text - switch(rnd) - { - case 2: - byetext = DEH_String("BYE!"); - break; - case 1: - byetext = DEH_String("Thanks, Bye!"); - break; - default: - case 0: - byetext = DEH_String("See you later!"); - break; - } - - DEH_snprintf(dialoglastmsgbuffer, sizeof(dialoglastmsgbuffer), - "%d) %s", i + 1, byetext); -} - -// EOF - - +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2010 James Haley, Samuel Villarreal +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// [STRIFE] New Module +// +// Dialog Engine for Strife +// + +#include + +#include "z_zone.h" +#include "w_wad.h" +#include "deh_str.h" +#include "d_main.h" +#include "d_mode.h" +#include "d_player.h" +#include "doomstat.h" +#include "m_random.h" +#include "m_menu.h" +#include "m_misc.h" +#include "r_main.h" +#include "v_video.h" +#include "p_local.h" +#include "sounds.h" +#include "p_dialog.h" +#include "s_sound.h" +#include "p_local.h" +#include "p_inter.h" + +// +// Defines and Macros +// + +// haleyjd: size of the original Strife mapdialog_t structure. +#define ORIG_MAPDIALOG_SIZE 0x5EC + +#define DIALOG_INT(field, ptr) \ + field = ((int)ptr[0] | \ + ((int)ptr[1] << 8) | \ + ((int)ptr[2] << 16) | \ + ((int)ptr[3] << 24)); \ + ptr += 4; + +#define DIALOG_STR(field, ptr, len) \ + memcpy(field, ptr, len); \ + ptr += len; + +// +// Globals +// + +// This can be toggled at runtime to determine if the full dialog messages +// are subtitled on screen or not. Defaults to off. +int dialogshowtext = false; + +// The global mission objective buffer. This gets written to and read from file, +// and is set by dialogs and line actions. +char mission_objective[OBJECTIVE_LEN]; + +// +// Static Globals +// + +// True if SCRIPT00 is loaded. +static boolean script0loaded; + +// Number of dialogs defined in the current level's script. +static int numleveldialogs; + +// The actual level dialogs. This didn't exist in Strife, but is new to account +// for structure alignment/packing concerns, given that Chocolate Doom is +// multiplatform. +static mapdialog_t *leveldialogs; + +// The actual script00 dialogs. As above. +static mapdialog_t *script0dialogs; + +// Number of dialogs defined in the SCRIPT00 lump. +static int numscript0dialogs; + +// The player engaged in dialog. This is always player 1, though, since Rogue +// never completed the ability to use dialog outside of single-player mode. +static player_t *dialogplayer; + +// The object to which the player is speaking. +static mobj_t *dialogtalker; + +// The talker's current angle +static angle_t dialogtalkerangle; + +// The currently active mapdialog object. +static mapdialog_t *currentdialog; + +// Text at the end of the choices +static char dialoglastmsgbuffer[48]; + +// Item to display to player when picked up or recieved +static char pickupstring[46]; + +// Health based on gameskill given by the front's medic +static const int healthamounts[] = { -100 , -75, -50, -50, -100 }; + +//============================================================================= +// +// Dialog State Sets +// +// These are used to animate certain actors in response to what happens in +// their dialog sequences. +// + +typedef struct dialogstateset_s +{ + mobjtype_t type; // the type of object + statenum_t greet; // greeting state, for start of dialog + statenum_t yes; // "yes" state, for an affirmative response + statenum_t no; // "no" state, when you don't have the right items +} dialogstateset_t; + +static dialogstateset_t dialogstatesets[] = +{ + { MT_PLAYER, S_NULL, S_NULL, S_NULL }, + { MT_SHOPKEEPER_W, S_MRGT_00, S_MRYS_00, S_MRNO_00 }, + { MT_SHOPKEEPER_B, S_MRGT_00, S_MRYS_00, S_MRNO_00 }, + { MT_SHOPKEEPER_A, S_MRGT_00, S_MRYS_00, S_MRNO_00 }, + { MT_SHOPKEEPER_M, S_MRGT_00, S_MRYS_00, S_MRNO_00 } +}; + +// Rogue stored this in a static global rather than making it a define... +static int numdialogstatesets = arrlen(dialogstatesets); + +// Current dialog talker state +static dialogstateset_t *dialogtalkerstates; + +//============================================================================= +// +// Random Messages +// +// Rogue hard-coded these so they wouldn't have to repeat them several times +// in the SCRIPT00 lump, apparently. +// + +#define MAXRNDMESSAGES 10 + +typedef struct rndmessage_s +{ + const char *type_name; + int nummessages; + char *messages[MAXRNDMESSAGES]; +} rndmessage_t; + +static rndmessage_t rndMessages[] = +{ + // Peasants + { + "PEASANT", + 10, + { + "PLEASE DON'T HURT ME.", + + "IF YOU'RE LOOKING TO HURT ME, I'M \n" + "NOT REALLY WORTH THE EFFORT.", + + "I DON'T KNOW ANYTHING.", + + "GO AWAY OR I'LL CALL THE GUARDS!", + + "I WISH SOMETIMES THAT ALL THESE \n" + "REBELS WOULD JUST LEARN THEIR \n" + "PLACE AND STOP THIS NONSENSE.", + + "JUST LEAVE ME ALONE, OK?", + + "I'M NOT SURE, BUT SOMETIMES I THINK \n" + "THAT I KNOW SOME OF THE ACOLYTES.", + + "THE ORDER'S GOT EVERYTHING AROUND HERE PRETTY WELL LOCKED UP TIGHT.", + + "THERE'S NO WAY THAT THIS IS JUST A \n" + "SECURITY FORCE.", + + "I'VE HEARD THAT THE ORDER IS REALLY \n" + "NERVOUS ABOUT THE FRONT'S \n" + "ACTIONS AROUND HERE." + } + }, + // Rebel + { + "REBEL", + 10, + { + "THERE'S NO WAY THE ORDER WILL \n" + "STAND AGAINST US.", + + "WE'RE ALMOST READY TO STRIKE. \n" + "MACIL'S PLANS ARE FALLING IN PLACE.", + + "WE'RE ALL BEHIND YOU, DON'T WORRY.", + + "DON'T GET TOO CLOSE TO ANY OF THOSE BIG ROBOTS. THEY'LL MELT YOU DOWN \n" + "FOR SCRAP!", + + "THE DAY OF OUR GLORY WILL SOON \n" + "COME, AND THOSE WHO OPPOSE US WILL \n" + "BE CRUSHED!", + + "DON'T GET TOO COMFORTABLE. WE'VE \n" + "STILL GOT OUR WORK CUT OUT FOR US.", + + "MACIL SAYS THAT YOU'RE THE NEW \n" + "HOPE. BEAR THAT IN MIND.", + + "ONCE WE'VE TAKEN THESE CHARLATANS DOWN, WE'LL BE ABLE TO REBUILD THIS " + "WORLD AS IT SHOULD BE.", + + "REMEMBER THAT YOU AREN'T FIGHTING \n" + "JUST FOR YOURSELF, BUT FOR \n" + "EVERYONE HERE AND OUTSIDE.", + + "AS LONG AS ONE OF US STILL STANDS, \n" + "WE WILL WIN." + } + }, + // Acolyte + { + "AGUARD", + 10, + { + "MOVE ALONG, PEASANT.", + + "FOLLOW THE TRUE FAITH, ONLY THEN \n" + "WILL YOU BEGIN TO UNDERSTAND.", + + "ONLY THROUGH DEATH CAN ONE BE \n" + "TRULY REBORN.", + + "I'M NOT INTERESTED IN YOUR USELESS \n" + "DRIVEL.", + + "IF I HAD WANTED TO TALK TO YOU I \n" + "WOULD HAVE TOLD YOU SO.", + + "GO AND ANNOY SOMEONE ELSE!", + + "KEEP MOVING!", + + "IF THE ALARM GOES OFF, JUST STAY OUT OF OUR WAY!", + + "THE ORDER WILL CLEANSE THE WORLD \n" + "AND USHER IT INTO THE NEW ERA.", + + "PROBLEM? NO, I THOUGHT NOT.", + } + }, + // Beggar + { + "BEGGAR", + 10, + { + "ALMS FOR THE POOR?", + + "WHAT ARE YOU LOOKING AT, SURFACER?", + + "YOU WOULDN'T HAVE ANY EXTRA FOOD, WOULD YOU?", + + "YOU SURFACE PEOPLE WILL NEVER \n" + " " + " UNDERSTAND US.", + + "HA, THE GUARDS CAN'T FIND US. THOSE \n" + "IDIOTS DON'T EVEN KNOW WE EXIST.", + + "ONE DAY EVERYONE BUT THOSE WHO SERVE THE ORDER WILL BE FORCED TO " + " JOIN US.", + + "STARE NOW, BUT YOU KNOW THAT THIS WILL BE YOUR OWN FACE ONE DAY.", + + // Note: "NOTHING THING" is an authentic typo + "THERE'S NOTHING THING MORE \n" + "ANNOYING THAN A SURFACER WITH AN ATTITUDE!", + + "THE ORDER WILL MAKE SHORT WORK OF YOUR PATHETIC FRONT.", + + "WATCH YOURSELF SURFACER. WE KNOW OUR ENEMIES!" + } + }, + // Templar + { + "PGUARD", + 10, + { + "WE ARE THE HANDS OF FATE. TO EARN \n" + "OUR WRATH IS TO FIND OBLIVION!", + + "THE ORDER WILL CLEANSE THE WORLD \n" + "OF THE WEAK AND CORRUPT!", + + "OBEY THE WILL OF THE MASTERS!", + + "LONG LIFE TO THE BROTHERS OF THE \n" + "ORDER!", + + "FREE WILL IS AN ILLUSION THAT BINDS \n" + "THE WEAK MINDED.", + + "POWER IS THE PATH TO GLORY. TO \n" + "FOLLOW THE ORDER IS TO WALK THAT \n" + "PATH!", + + "TAKE YOUR PLACE AMONG THE \n" + "RIGHTEOUS, JOIN US!", + + "THE ORDER PROTECTS ITS OWN.", + + "ACOLYTES? THEY HAVE YET TO SEE THE FULL GLORY OF THE ORDER.", + + "IF THERE IS ANY HONOR INSIDE THAT \n" + "PATHETIC SHELL OF A BODY, \n" + "YOU'LL ENTER INTO THE ARMS OF THE \n" + "ORDER." + } + } +}; + +// And again, this could have been a define, but was a variable. +static int numrndmessages = arrlen(rndMessages); + +//============================================================================= +// +// Dialog Menu Structure +// +// The Strife dialog system is actually just a serious abuse of the DOOM menu +// engine. Hence why it doesn't work in multiplayer games or during demo +// recording. +// + +#define NUMDIALOGMENUITEMS 6 + +static void P_DialogDrawer(void); + +static menuitem_t dialogmenuitems[] = +{ + { 1, "", P_DialogDoChoice, '1' }, // These items are loaded dynamically + { 1, "", P_DialogDoChoice, '2' }, + { 1, "", P_DialogDoChoice, '3' }, + { 1, "", P_DialogDoChoice, '4' }, + { 1, "", P_DialogDoChoice, '5' }, + { 1, "", P_DialogDoChoice, '6' } // Item 6 is always the dismissal item +}; + +static menu_t dialogmenu = +{ + NUMDIALOGMENUITEMS, + NULL, + dialogmenuitems, + P_DialogDrawer, + 42, + 75, + 0 +}; + +// Lump number of the dialog background picture, if any. +static int dialogbgpiclumpnum; + +// Name of current speaking character. +static char *dialogname; + +// Current dialog text. +static const char *dialogtext; + +//============================================================================= +// +// Routines +// + +// +// P_ParseDialogLump +// +// haleyjd 09/02/10: This is an original function added to parse out the +// dialogs from the dialog lump rather than reading them raw from the lump +// pointer. This avoids problems with structure packing. +// +static void P_ParseDialogLump(byte *lump, mapdialog_t **dialogs, + int numdialogs, int tag) +{ + int i; + byte *rover = lump; + + *dialogs = Z_Malloc(numdialogs * sizeof(mapdialog_t), tag, NULL); + + for(i = 0; i < numdialogs; i++) + { + int j; + mapdialog_t *curdialog = &((*dialogs)[i]); + + DIALOG_INT(curdialog->speakerid, rover); + DIALOG_INT(curdialog->dropitem, rover); + DIALOG_INT(curdialog->checkitem[0], rover); + DIALOG_INT(curdialog->checkitem[1], rover); + DIALOG_INT(curdialog->checkitem[2], rover); + DIALOG_INT(curdialog->jumptoconv, rover); + DIALOG_STR(curdialog->name, rover, MDLG_NAMELEN); + DIALOG_STR(curdialog->voice, rover, MDLG_LUMPLEN); + DIALOG_STR(curdialog->backpic, rover, MDLG_LUMPLEN); + DIALOG_STR(curdialog->text, rover, MDLG_TEXTLEN); + + // copy choices + for(j = 0; j < 5; j++) + { + mapdlgchoice_t *curchoice = &(curdialog->choices[j]); + DIALOG_INT(curchoice->giveitem, rover); + DIALOG_INT(curchoice->needitems[0], rover); + DIALOG_INT(curchoice->needitems[1], rover); + DIALOG_INT(curchoice->needitems[2], rover); + DIALOG_INT(curchoice->needamounts[0], rover); + DIALOG_INT(curchoice->needamounts[1], rover); + DIALOG_INT(curchoice->needamounts[2], rover); + DIALOG_STR(curchoice->text, rover, MDLG_CHOICELEN); + DIALOG_STR(curchoice->textok, rover, MDLG_MSGLEN); + DIALOG_INT(curchoice->next, rover); + DIALOG_INT(curchoice->objective, rover); + DIALOG_STR(curchoice->textno, rover, MDLG_MSGLEN); + } + } +} + +// +// P_DialogLoad +// +// [STRIFE] New function +// haleyjd 09/02/10: Loads the dialog script for the current map. Also loads +// SCRIPT00 if it has not yet been loaded. +// +void P_DialogLoad(void) +{ + char lumpname[9]; + int lumpnum; + + // load the SCRIPTxy lump corresponding to MAPxy, if it exists. + DEH_snprintf(lumpname, sizeof(lumpname), "script%02d", gamemap); + if((lumpnum = W_CheckNumForName(lumpname)) == -1) + numleveldialogs = 0; + else + { + byte *leveldialogptr = W_CacheLumpNum(lumpnum, PU_STATIC); + numleveldialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE; + P_ParseDialogLump(leveldialogptr, &leveldialogs, numleveldialogs, + PU_LEVEL); + Z_Free(leveldialogptr); // haleyjd: free the original lump + } + + // also load SCRIPT00 if it has not been loaded yet + if(!script0loaded) + { + byte *script0ptr; + + script0loaded = true; + // BUG: Rogue should have used W_GetNumForName here... + lumpnum = W_CheckNumForName(DEH_String("script00")); + script0ptr = W_CacheLumpNum(lumpnum, PU_STATIC); + numscript0dialogs = W_LumpLength(lumpnum) / ORIG_MAPDIALOG_SIZE; + P_ParseDialogLump(script0ptr, &script0dialogs, numscript0dialogs, + PU_STATIC); + Z_Free(script0ptr); // haleyjd: free the original lump + } +} + +// +// P_PlayerHasItem +// +// [STRIFE] New function +// haleyjd 09/02/10: Checks for inventory items, quest flags, etc. for dialogs. +// Returns the amount possessed, or 0 if none. +// +int P_PlayerHasItem(player_t *player, mobjtype_t type) +{ + int i; + + if(type > 0) + { + // check keys + if(type >= MT_KEY_BASE && type < MT_INV_SHADOWARMOR) + return (player->cards[type - MT_KEY_BASE]); + + // check sigil pieces + if(type >= MT_SIGIL_A && type <= MT_SIGIL_E) + return (type - MT_SIGIL_A <= player->sigiltype); + + // check quest tokens + if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31) + return (player->questflags & (1 << (type - MT_TOKEN_QUEST1))); + + // check inventory + for(i = 0; i < 32; i++) + { + if(type == player->inventory[i].type) + return player->inventory[i].amount; + } + } + return 0; +} + +// +// P_DialogFind +// +// [STRIFE] New function +// haleyjd 09/03/10: Looks for a dialog definition matching the given +// Script ID # for an mobj. +// +mapdialog_t *P_DialogFind(mobjtype_t type, int jumptoconv) +{ + int i; + + // check the map-specific dialogs first + for(i = 0; i < numleveldialogs; i++) + { + if(type == leveldialogs[i].speakerid) + { + if(jumptoconv <= 1) + return &leveldialogs[i]; + else + --jumptoconv; + } + } + + // check SCRIPT00 dialogs next + for(i = 0; i < numscript0dialogs; i++) + { + if(type == script0dialogs[i].speakerid) + return &script0dialogs[i]; + } + + // the default dialog is script 0 in the SCRIPT00 lump. + return &script0dialogs[0]; +} + +// +// P_DialogGetStates +// +// [STRIFE] New function +// haleyjd 09/03/10: Find the set of special dialog states (greetings, yes, no) +// for a particular thing type. +// +static dialogstateset_t *P_DialogGetStates(mobjtype_t type) +{ + int i; + + // look for a match by type + for(i = 0; i < numdialogstatesets; i++) + { + if(type == dialogstatesets[i].type) + return &dialogstatesets[i]; + } + + // return the default 0 record if no match. + return &dialogstatesets[0]; +} + +// +// P_DialogGetMsg +// +// [STRIFE] New function +// haleyjd 09/03/10: Redirects dialog messages when the script indicates that +// the actor should use a random message stored in the executable instead. +// +static const char *P_DialogGetMsg(const char *message) +{ + // if the message starts with "RANDOM"... + if(!strncasecmp(message, DEH_String("RANDOM"), 6)) + { + int i; + const char *nameloc = message + 7; + + // look for a match in rndMessages for the string starting + // 7 chars after "RANDOM_" + for(i = 0; i < numrndmessages; i++) + { + if(!strncasecmp(nameloc, rndMessages[i].type_name, 4)) + { + // found a match, so return a random message + int rnd = M_Random(); + int nummessages = rndMessages[i].nummessages; + return DEH_String(rndMessages[i].messages[rnd % nummessages]); + } + } + } + + // otherwise, just return the message passed in. + return message; +} + +// +// P_GiveInventoryItem +// +// [STRIFE] New function +// haleyjd 09/03/10: Give an inventory item to the player, if possible. +// villsa 09/09/10: Fleshed out routine +// +boolean P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type) +{ + int curinv = 0; + int i; + boolean ok = false; + mobjtype_t item = 0; + inventory_t* invtail; + + // repaint the status bar due to inventory changing + player->st_update = true; + + while(1) + { + // inventory is full + if(curinv > player->numinventory) + return true; + + item = player->inventory[curinv].type; + if(type < item) + { + if(curinv != MAXINVENTORYSLOTS) + { + // villsa - sort inventory item if needed + invtail = &player->inventory[player->numinventory - 1]; + if(player->numinventory >= (curinv + 1)) + { + for(i = player->numinventory; i >= (curinv + 1); --i) + { + invtail[1].sprite = invtail[0].sprite; + invtail[1].type = invtail[0].type; + invtail[1].amount = invtail[0].amount; + + invtail--; + } + } + + // villsa - add inventory item + player->inventory[curinv].amount = 1; + player->inventory[curinv].sprite = sprnum; + player->inventory[curinv].type = type; + + // sort cursor if needed + if(player->numinventory) + { + if(curinv <= player->inventorycursor) + player->inventorycursor++; + } + + player->numinventory++; + + return true; + } + + return false; + } + + if(type == item) + break; + + curinv++; + } + + // check amount of inventory item by using the mass from mobjinfo + if(player->inventory[curinv].amount < mobjinfo[item].mass) + { + player->inventory[curinv].amount++; + ok = true; + } + else + ok = false; + + return ok; +} + +// +// P_GiveItemToPlayer +// +// [STRIFE] New function +// haleyjd 09/03/10: Sorts out how to give something to the player. +// Not strictly just for inventory items. +// villsa 09/09/10: Fleshed out function +// +boolean P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type) +{ + int i = 0; + line_t junk; + int sound = sfx_itemup; // haleyjd 09/21/10: different sounds for items + + // set quest if mf_givequest flag is set + if(mobjinfo[type].flags & MF_GIVEQUEST) + player->questflags |= 1 << (mobjinfo[type].speed - 1); + + // check for keys + if(type >= MT_KEY_BASE && type <= MT_NEWKEY5) + { + P_GiveCard(player, type - MT_KEY_BASE); + return true; + } + + // check for quest tokens + if(type >= MT_TOKEN_QUEST1 && type <= MT_TOKEN_QUEST31) + { + if(mobjinfo[type].name) + { + M_StringCopy(pickupstring, DEH_String(mobjinfo[type].name), 39); + player->message = pickupstring; + } + player->questflags |= 1 << (type - MT_TOKEN_QUEST1); + + if(player == &players[consoleplayer]) + S_StartSound(NULL, sound); + return true; + } + + // haleyjd 09/22/10: Refactored to give sprites higher priority than + // mobjtypes and to implement missing logic. + switch(sprnum) + { + case SPR_HELT: // This is given only by the "DONNYTRUMP" cheat (aka Midas) + P_GiveInventoryItem(player, SPR_HELT, MT_TOKEN_TOUGHNESS); + P_GiveInventoryItem(player, SPR_GUNT, MT_TOKEN_ACCURACY); + + // [STRIFE] Bizarre... + for(i = 0; i < 5 * player->accuracy + 300; i++) + P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); + break; + + case SPR_ARM1: // Armor 1 + if(!P_GiveArmor(player, -2)) + P_GiveInventoryItem(player, sprnum, type); + break; + + case SPR_ARM2: // Armor 2 + if(!P_GiveArmor(player, -1)) + P_GiveInventoryItem(player, sprnum, type); + break; + + case SPR_COIN: // 1 Gold + P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); + break; + + case SPR_CRED: // 10 Gold + for(i = 0; i < 10; i++) + P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); + break; + + case SPR_SACK: // 25 gold + for(i = 0; i < 25; i++) + P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); + break; + + case SPR_CHST: // 50 gold + for(i = 0; i < 50; i++) + P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); + break; // haleyjd 20141215: missing break, caused Rowan to not take ring from you. + + case SPR_BBOX: // Box of Bullets + if(!P_GiveAmmo(player, am_bullets, 5)) + return false; + break; + + case SPR_BLIT: // Bullet Clip + if(!P_GiveAmmo(player, am_bullets, 1)) + return false; + break; + + case SPR_PMAP: // Map powerup + if(!P_GivePower(player, pw_allmap)) + return false; + sound = sfx_yeah; // bluh-doop! + break; + + case SPR_COMM: // Communicator + if(!P_GivePower(player, pw_communicator)) + return false; + sound = sfx_yeah; // bluh-doop! + break; + + case SPR_MSSL: // Mini-missile + if(!P_GiveAmmo(player, am_missiles, 1)) + return false; + break; + + case SPR_ROKT: // Crate of missiles + if(!P_GiveAmmo(player, am_missiles, 5)) + return false; + break; + + case SPR_BRY1: // Battery cell + if(!P_GiveAmmo(player, am_cell, 1)) + return false; + break; + + case SPR_CPAC: // Cell pack + if(!P_GiveAmmo(player, am_cell, 5)) + return false; + break; + + case SPR_PQRL: // Poison bolts + if(!P_GiveAmmo(player, am_poisonbolts, 5)) + return false; + break; + + case SPR_XQRL: // Electric bolts + if(!P_GiveAmmo(player, am_elecbolts, 5)) + return false; + break; + + case SPR_GRN1: // HE Grenades + if(!P_GiveAmmo(player, am_hegrenades, 1)) + return false; + break; + + case SPR_GRN2: // WP Grenades + if(!P_GiveAmmo(player, am_wpgrenades, 1)) + return false; + break; + + case SPR_BKPK: // Backpack (aka Ammo Satchel) + if(!player->backpack) + { + for(i = 0; i < NUMAMMO; i++) + player->maxammo[i] *= 2; + + player->backpack = true; + } + for(i = 0; i < NUMAMMO; i++) + P_GiveAmmo(player, i, 1); + break; + + case SPR_RIFL: // Assault Rifle + if(player->weaponowned[wp_rifle]) + return false; + + if(!P_GiveWeapon(player, wp_rifle, false)) + return false; + + sound = sfx_wpnup; // SHK-CHK! + break; + + case SPR_FLAM: // Flamethrower + if(player->weaponowned[wp_flame]) + return false; + + if(!P_GiveWeapon(player, wp_flame, false)) + return false; + + sound = sfx_wpnup; // SHK-CHK! + break; + + case SPR_MMSL: // Mini-missile Launcher + if(player->weaponowned[wp_missile]) + return false; + + if(!P_GiveWeapon(player, wp_missile, false)) + return false; + + sound = sfx_wpnup; // SHK-CHK! + break; + + case SPR_TRPD: // Mauler + if(player->weaponowned[wp_mauler]) + return false; + + if(!P_GiveWeapon(player, wp_mauler, false)) + return false; + + sound = sfx_wpnup; // SHK-CHK! + break; + + case SPR_CBOW: // Here's a crossbow. Just aim straight, and *SPLAT!* + if(player->weaponowned[wp_elecbow]) + return false; + + if(!P_GiveWeapon(player, wp_elecbow, false)) + return false; + + sound = sfx_wpnup; // SHK-CHK! + break; + + case SPR_TOKN: // Miscellaneous items - These are determined by thingtype. + switch(type) + { + case MT_KEY_HAND: // Severed hand + P_GiveCard(player, key_SeveredHand); + break; + + case MT_MONY_300: // 300 Gold (this is the only way to get it, in fact) + for(i = 0; i < 300; i++) + P_GiveInventoryItem(player, SPR_COIN, MT_MONY_1); + break; + + case MT_TOKEN_AMMO: // Ammo token - you get this from the Weapons Trainer + if(player->ammo[am_bullets] >= 50) + return false; + + player->ammo[am_bullets] = 50; + break; + + case MT_TOKEN_HEALTH: // Health token - from the Front's doctor + if(!P_GiveBody(player, healthamounts[gameskill])) + return false; + break; + + case MT_TOKEN_ALARM: // Alarm token - particularly from the Oracle. + P_NoiseAlert(player->mo, player->mo); + A_AlertSpectreC(dialogtalker); // BUG: assumes in a dialog o_O + break; + + case MT_TOKEN_DOOR1: // Door special 1 + junk.tag = 222; + EV_DoDoor(&junk, vld_open); + break; + + case MT_TOKEN_PRISON_PASS: // Door special 1 - Prison pass + junk.tag = 223; + EV_DoDoor(&junk, vld_open); + if(gamemap == 2) // If on Tarnhill, give Prison pass object + P_GiveInventoryItem(player, sprnum, type); + break; + + case MT_TOKEN_SHOPCLOSE: // Door special 3 - "Shop close" - unused? + junk.tag = 222; + EV_DoDoor(&junk, vld_close); + break; + + case MT_TOKEN_DOOR3: // Door special 4 (or 3? :P ) + junk.tag = 224; + EV_DoDoor(&junk, vld_close); + break; + + case MT_TOKEN_STAMINA: // Stamina upgrade + if(player->stamina >= 100) + return false; + + player->stamina += 10; + P_GiveBody(player, 200); // full healing + break; + + case MT_TOKEN_NEW_ACCURACY: // Accuracy upgrade + if(player->accuracy >= 100) + return false; + + player->accuracy += 10; + break; + + case MT_SLIDESHOW: // Slideshow (start a finale) + gameaction = ga_victory; + if(gamemap == 10) + P_GiveItemToPlayer(player, SPR_TOKN, MT_TOKEN_QUEST17); + break; + + default: // The default is to just give it as an inventory item. + P_GiveInventoryItem(player, sprnum, type); + break; + } + break; + + default: // The ultimate default: Give it as an inventory item. + if(!P_GiveInventoryItem(player, sprnum, type)) + return false; + break; + } + + // Play sound. + if(player == &players[consoleplayer]) + S_StartSound(NULL, sound); + + return true; +} + +// +// P_TakeDialogItem +// +// [STRIFE] New function +// haleyjd 09/03/10: Removes needed items from the player's inventory. +// +static void P_TakeDialogItem(player_t *player, int type, int amount) +{ + int i; + + if(amount <= 0) + return; + + for(i = 0; i < player->numinventory; i++) + { + // find a matching item + if(type != player->inventory[i].type) + continue; + + // if there is none left... + if((player->inventory[i].amount -= amount) < 1) + { + // ...shift everything above it down + int j; + + // BUG: They should have stopped at j < numinventory. This + // seems to implicitly assume that numinventory is always at + // least one less than the max # of slots, otherwise it + // pulls in data from the following player_t fields: + // st_update, numinventory, inventorycursor, accuracy, stamina + for(j = i + 1; j <= player->numinventory; j++) + { + inventory_t *item1 = &(player->inventory[j - 1]); + inventory_t *item2 = &(player->inventory[j]); + + *item1 = *item2; + } + + // blank the topmost slot + // BUG: This will overwrite the aforementioned fields if + // numinventory is equal to the number of slots! + // STRIFE-TODO: Overflow emulation? + player->inventory[player->numinventory].type = NUMMOBJTYPES; + player->inventory[player->numinventory].sprite = -1; + player->numinventory--; + + // update cursor position + if(player->inventorycursor >= player->numinventory) + { + if(player->inventorycursor) + player->inventorycursor--; + } + } // end if + + return; // done! + + } // end for +} + +// +// P_DialogDrawer +// +// This function is set as the drawer callback for the dialog menu. +// +static void P_DialogDrawer(void) +{ + angle_t angle; + int y; + int i; + int height; + int finaly; + char choicetext[64]; + char choicetext2[64]; + + // Run down bonuscount faster than usual so that flashes from being given + // items are less obvious. + if(dialogplayer->bonuscount) + { + dialogplayer->bonuscount -= 3; + if(dialogplayer->bonuscount < 0) + dialogplayer->bonuscount = 0; + } + + angle = R_PointToAngle2(dialogplayer->mo->x, + dialogplayer->mo->y, + dialogtalker->x, + dialogtalker->y); + angle -= dialogplayer->mo->angle; + + // Dismiss the dialog if the player is out of alignment, or the thing he was + // talking to is now engaged in battle. + if ((angle > ANG45 && angle < (ANG270+ANG45)) + || (dialogtalker->flags & MF_NODIALOG) != 0) + { + P_DialogDoChoice(dialogmenu.numitems - 1); + } + + dialogtalker->reactiontime = 2; + + // draw background + if(dialogbgpiclumpnum != -1) + { + patch_t *patch = W_CacheLumpNum(dialogbgpiclumpnum, PU_CACHE); + V_DrawPatchDirect(0, 0, patch); + } + + // if there's a valid background pic, delay drawing the rest of the menu + // for a while; otherwise, it will appear immediately + if(dialogbgpiclumpnum == -1 || menupausetime <= gametic) + { + if(menuindialog) + { + // time to pause the game? + if(menupausetime + 3 < gametic) + menupause = true; + } + + // draw character name + M_WriteText(12, 18, dialogname); + y = 28; + + // show text (optional for dialogs with voices) + if(dialogshowtext || currentdialog->voice[0] == '\0') + y = M_WriteText(20, 28, dialogtext); + + height = 20 * dialogmenu.numitems; + + finaly = 175 - height; // preferred height + if(y > finaly) + finaly = 199 - height; // height it will bump down to if necessary. + + // draw divider + M_WriteText(42, finaly - 6, DEH_String("______________________________")); + + dialogmenu.y = finaly + 6; + y = 0; + + // draw the menu items + for(i = 0; i < dialogmenu.numitems - 1; i++) + { + DEH_snprintf(choicetext, sizeof(choicetext), + "%d) %s", i + 1, currentdialog->choices[i].text); + + // alternate text for items that need money + if(currentdialog->choices[i].needamounts[0] > 0) + { + // haleyjd 20120401: necessary to avoid undefined behavior: + M_StringCopy(choicetext2, choicetext, sizeof(choicetext2)); + DEH_snprintf(choicetext, sizeof(choicetext), + "%s for %d", choicetext2, + currentdialog->choices[i].needamounts[0]); + } + + M_WriteText(dialogmenu.x, dialogmenu.y + 3 + y, choicetext); + y += 19; + } + + // draw the final item for dismissing the dialog + M_WriteText(dialogmenu.x, 19 * i + dialogmenu.y + 3, dialoglastmsgbuffer); + } +} + +// +// P_DialogDoChoice +// +// [STRIFE] New function +// haleyjd 09/05/10: Handles making a choice in a dialog. Installed as the +// callback for all items in the dialogmenu structure. +// +void P_DialogDoChoice(int choice) +{ + int i = 0, nextdialog = 0; + boolean candochoice = true; + char *message = NULL; + mapdlgchoice_t *currentchoice; + + if(choice == -1) + choice = dialogmenu.numitems - 1; + + currentchoice = &(currentdialog->choices[choice]); + + I_StartVoice(NULL); // STRIFE-TODO: verify (should stop previous voice I believe) + + // villsa 09/08/10: converted into for loop + for(i = 0; i < MDLG_MAXITEMS; i++) + { + if(P_PlayerHasItem(dialogplayer, currentchoice->needitems[i]) < + currentchoice->needamounts[i]) + { + candochoice = false; // nope, missing something + } + } + + if(choice != dialogmenu.numitems - 1 && candochoice) + { + int item; + + message = currentchoice->textok; + if(dialogtalkerstates->yes) + P_SetMobjState(dialogtalker, dialogtalkerstates->yes); + + item = currentchoice->giveitem; + if(item < 0 || + P_GiveItemToPlayer(dialogplayer, + states[mobjinfo[item].spawnstate].sprite, + item)) + { + // if successful, take needed items + int count = 0; + // villsa 09/08/10: converted into for loop + for(count = 0; count < MDLG_MAXITEMS; count++) + { + P_TakeDialogItem(dialogplayer, + currentchoice->needitems[count], + currentchoice->needamounts[count]); + } + } + else + message = DEH_String("You seem to have enough!"); + + // store next dialog into the talking actor + nextdialog = currentchoice->next; + if(nextdialog != 0) + dialogtalker->miscdata = (byte)(abs(nextdialog)); + } + else + { + // not successful + message = currentchoice->textno; + if(dialogtalkerstates->no) + P_SetMobjState(dialogtalker, dialogtalkerstates->no); + } + + if(choice != dialogmenu.numitems - 1) + { + int objective; + char *objlump; + + if((objective = currentchoice->objective)) + { + DEH_snprintf(mission_objective, OBJECTIVE_LEN, "log%i", objective); + objlump = W_CacheLumpName(mission_objective, PU_CACHE); + M_StringCopy(mission_objective, objlump, OBJECTIVE_LEN); + } + // haleyjd 20130301: v1.31 hack: if first char of message is a period, + // clear the player's message. Is this actually used anywhere? + if(gameversion == exe_strife_1_31 && message[0] == '.') + message = NULL; + dialogplayer->message = message; + } + + dialogtalker->angle = dialogtalkerangle; + dialogplayer->st_update = true; + M_ClearMenus(0); + + if(nextdialog >= 0 || gameaction == ga_victory) // Macil hack + menuindialog = false; + else + P_DialogStart(dialogplayer); +} + +// +// P_DialogStartP1 +// +// [STRIFE] New function +// haleyjd 09/13/10: This is a hack used by the finale system. +// +void P_DialogStartP1(void) +{ + P_DialogStart(&players[0]); +} + +// +// P_DialogStart +// +// villsa [STRIFE] New function +// +void P_DialogStart(player_t *player) +{ + int i = 0; + int pic; + int rnd = 0; + char* byetext; + int jumptoconv; + + if(menuactive || netgame) + return; + + // are we facing towards our NPC? + P_AimLineAttack(player->mo, player->mo->angle, (128*FRACUNIT)); + if(!linetarget) + { + P_AimLineAttack(player->mo, player->mo->angle + (ANG90/16), (128*FRACUNIT)); + if(!linetarget) + P_AimLineAttack(player->mo, player->mo->angle - (ANG90/16), (128*FRACUNIT)); + } + + if(!linetarget) + return; + + // already in combat, can't talk to it + if(linetarget->flags & MF_NODIALOG) + return; + + // set pointer to the character talking + dialogtalker = linetarget; + + // play a sound + if(player == &players[consoleplayer]) + S_StartSound(0, sfx_radio); + + linetarget->target = player->mo; // target the player + dialogtalker->reactiontime = 2; // set reactiontime + dialogtalkerangle = dialogtalker->angle; // remember original angle + + // face talker towards player + A_FaceTarget(dialogtalker); + + // face towards NPC's direction + player->mo->angle = R_PointToAngle2(player->mo->x, + player->mo->y, + dialogtalker->x, + dialogtalker->y); + // set pointer to player talking + dialogplayer = player; + + // haleyjd 09/08/10: get any stored dialog state from this object + jumptoconv = linetarget->miscdata; + + // check item requirements + while(1) + { + int i = 0; + currentdialog = P_DialogFind(linetarget->type, jumptoconv); + + // dialog's jumptoconv equal to 0? There's nothing to jump to. + if(currentdialog->jumptoconv == 0) + break; + + // villsa 09/08/10: converted into for loop + for(i = 0; i < MDLG_MAXITEMS; i++) + { + // if the item is non-zero, the player must have at least one in his + // or her inventory + if(currentdialog->checkitem[i] != 0 && + P_PlayerHasItem(dialogplayer, currentdialog->checkitem[i]) < 1) + break; + } + + if(i < MDLG_MAXITEMS) // didn't find them all? this is our dialog! + break; + + jumptoconv = currentdialog->jumptoconv; + } + + M_DialogDimMsg(20, 28, currentdialog->text, false); + dialogtext = P_DialogGetMsg(currentdialog->text); + + // get states + dialogtalkerstates = P_DialogGetStates(linetarget->type); + + // have talker greet the player + if(dialogtalkerstates->greet) + P_SetMobjState(dialogtalker, dialogtalkerstates->greet); + + // get talker's name + if(currentdialog->name[0]) + dialogname = currentdialog->name; + else + { + // use a fallback: + if(mobjinfo[linetarget->type].name) + dialogname = DEH_String(mobjinfo[linetarget->type].name); // mobjtype name + else + dialogname = DEH_String("Person"); // default name - like Joe in Doom 3 :P + } + + // setup number of choices to choose from + for(i = 0; i < MDLG_MAXCHOICES; i++) + { + if(!currentdialog->choices[i].giveitem) + break; + } + + // set number of choices to menu + dialogmenu.numitems = i + 1; + + rnd = M_Random() % 3; + + // setup dialog menu + M_StartControlPanel(); + menupause = false; + menuindialog = true; + menupausetime = gametic + 17; + currentMenu = &dialogmenu; + + if(i >= dialogmenu.lastOn) + itemOn = dialogmenu.lastOn; + else + itemOn = 0; + + // get backdrop + pic = W_CheckNumForName(currentdialog->backpic); + dialogbgpiclumpnum = pic; + if(pic != -1) + V_DrawPatchDirect(0, 0, W_CacheLumpNum(pic, PU_CACHE)); + + // get voice + I_StartVoice(currentdialog->voice); + + // get bye text + switch(rnd) + { + case 2: + byetext = DEH_String("BYE!"); + break; + case 1: + byetext = DEH_String("Thanks, Bye!"); + break; + default: + case 0: + byetext = DEH_String("See you later!"); + break; + } + + DEH_snprintf(dialoglastmsgbuffer, sizeof(dialoglastmsgbuffer), + "%d) %s", i + 1, byetext); +} + +// EOF + + diff --git a/src/strife/p_dialog.h b/src/strife/p_dialog.h index 0cf4c9b4..08bef81e 100644 --- a/src/strife/p_dialog.h +++ b/src/strife/p_dialog.h @@ -1,102 +1,102 @@ -// -// Copyright(C) 1993-1996 Id Software, Inc. -// Copyright(C) 1996 Rogue Entertainment / Velocity, Inc. -// Copyright(C) 2010 James Haley, Samuel Villareal -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// DESCRIPTION: -// -// [STRIFE] New Module -// -// Dialog Engine for Strife -// - -#ifndef P_DIALOG_H__ -#define P_DIALOG_H__ - -#define OBJECTIVE_LEN 300 - -#define MAXINVENTORYSLOTS 30 - -#define MDLG_CHOICELEN 32 -#define MDLG_MSGLEN 80 -#define MDLG_NAMELEN 16 -#define MDLG_LUMPLEN 8 -#define MDLG_TEXTLEN 320 -#define MDLG_MAXCHOICES 5 -#define MDLG_MAXITEMS 3 - -extern char mission_objective[OBJECTIVE_LEN]; - -extern int dialogshowtext; - -// villsa - convenient macro for giving objective logs to player -#define GiveObjective(x, minlumpnum) \ -do { \ - int obj_ln = W_CheckNumForName(DEH_String(x)); \ - if(obj_ln > minlumpnum) \ - M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \ - OBJECTIVE_LEN);\ -} while(0) - -// haleyjd - voice and objective in one -#define GiveVoiceObjective(voice, log, minlumpnum) \ -do { \ - int obj_ln = W_CheckNumForName(DEH_String(log)); \ - I_StartVoice(DEH_String(voice)); \ - if(obj_ln > minlumpnum) \ - M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \ - OBJECTIVE_LEN);\ -} while(0) - -typedef struct mapdlgchoice_s -{ - int giveitem; // item given when successful - int needitems[MDLG_MAXITEMS]; // item needed for success - int needamounts[MDLG_MAXITEMS]; // amount of items needed - char text[MDLG_CHOICELEN]; // normal text - char textok[MDLG_MSGLEN]; // message given on success - int next; // next dialog? - int objective; // ??? - char textno[MDLG_MSGLEN]; // message given on failure -} mapdlgchoice_t; - -typedef struct mapdialog_s -{ - int speakerid; // script ID# for mobjtype that will use this dialog - int dropitem; // item to drop if that thingtype is killed - int checkitem[MDLG_MAXITEMS]; // item(s) needed to see this dialog - int jumptoconv; // conversation to jump to when... ? - char name[MDLG_NAMELEN]; // name of speaker - char voice[MDLG_LUMPLEN]; // voice file to play - char backpic[MDLG_LUMPLEN]; // backdrop pic for character, if any - char text[MDLG_TEXTLEN]; // main message text - - // options that this dialog gives the player - mapdlgchoice_t choices[MDLG_MAXCHOICES]; -} mapdialog_t; - -void P_DialogLoad(void); -void P_DialogStart(player_t *player); -void P_DialogDoChoice(int choice); -boolean P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type); -boolean P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type); -boolean P_UseInventoryItem(player_t* player, int item); -void P_DialogStartP1(void); -mapdialog_t* P_DialogFind(mobjtype_t type, int jumptoconv); -int P_PlayerHasItem(player_t *player, mobjtype_t type); - -#endif - -// EOF - - +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 1996 Rogue Entertainment / Velocity, Inc. +// Copyright(C) 2010 James Haley, Samuel Villareal +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// +// [STRIFE] New Module +// +// Dialog Engine for Strife +// + +#ifndef P_DIALOG_H__ +#define P_DIALOG_H__ + +#define OBJECTIVE_LEN 300 + +#define MAXINVENTORYSLOTS 30 + +#define MDLG_CHOICELEN 32 +#define MDLG_MSGLEN 80 +#define MDLG_NAMELEN 16 +#define MDLG_LUMPLEN 8 +#define MDLG_TEXTLEN 320 +#define MDLG_MAXCHOICES 5 +#define MDLG_MAXITEMS 3 + +extern char mission_objective[OBJECTIVE_LEN]; + +extern int dialogshowtext; + +// villsa - convenient macro for giving objective logs to player +#define GiveObjective(x, minlumpnum) \ +do { \ + int obj_ln = W_CheckNumForName(DEH_String(x)); \ + if(obj_ln > minlumpnum) \ + M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \ + OBJECTIVE_LEN);\ +} while(0) + +// haleyjd - voice and objective in one +#define GiveVoiceObjective(voice, log, minlumpnum) \ +do { \ + int obj_ln = W_CheckNumForName(DEH_String(log)); \ + I_StartVoice(DEH_String(voice)); \ + if(obj_ln > minlumpnum) \ + M_StringCopy(mission_objective, W_CacheLumpNum(obj_ln, PU_CACHE), \ + OBJECTIVE_LEN);\ +} while(0) + +typedef struct mapdlgchoice_s +{ + int giveitem; // item given when successful + int needitems[MDLG_MAXITEMS]; // item needed for success + int needamounts[MDLG_MAXITEMS]; // amount of items needed + char text[MDLG_CHOICELEN]; // normal text + char textok[MDLG_MSGLEN]; // message given on success + int next; // next dialog? + int objective; // ??? + char textno[MDLG_MSGLEN]; // message given on failure +} mapdlgchoice_t; + +typedef struct mapdialog_s +{ + int speakerid; // script ID# for mobjtype that will use this dialog + int dropitem; // item to drop if that thingtype is killed + int checkitem[MDLG_MAXITEMS]; // item(s) needed to see this dialog + int jumptoconv; // conversation to jump to when... ? + char name[MDLG_NAMELEN]; // name of speaker + char voice[MDLG_LUMPLEN]; // voice file to play + char backpic[MDLG_LUMPLEN]; // backdrop pic for character, if any + char text[MDLG_TEXTLEN]; // main message text + + // options that this dialog gives the player + mapdlgchoice_t choices[MDLG_MAXCHOICES]; +} mapdialog_t; + +void P_DialogLoad(void); +void P_DialogStart(player_t *player); +void P_DialogDoChoice(int choice); +boolean P_GiveItemToPlayer(player_t *player, int sprnum, mobjtype_t type); +boolean P_GiveInventoryItem(player_t *player, int sprnum, mobjtype_t type); +boolean P_UseInventoryItem(player_t* player, int item); +void P_DialogStartP1(void); +mapdialog_t* P_DialogFind(mobjtype_t type, int jumptoconv); +int P_PlayerHasItem(player_t *player, mobjtype_t type); + +#endif + +// EOF + +