mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-14 16:41:07 +00:00
- moved all dialogue loading code into the map loader.
This commit is contained in:
parent
e06aa28620
commit
8c06a00ee6
9 changed files with 585 additions and 523 deletions
|
@ -952,7 +952,6 @@ set (PCH_SOURCES
|
||||||
p_states.cpp
|
p_states.cpp
|
||||||
p_things.cpp
|
p_things.cpp
|
||||||
p_tick.cpp
|
p_tick.cpp
|
||||||
p_usdf.cpp
|
|
||||||
p_user.cpp
|
p_user.cpp
|
||||||
r_utility.cpp
|
r_utility.cpp
|
||||||
r_sky.cpp
|
r_sky.cpp
|
||||||
|
@ -1075,6 +1074,8 @@ set (PCH_SOURCES
|
||||||
maploader/slopes.cpp
|
maploader/slopes.cpp
|
||||||
maploader/glnodes.cpp
|
maploader/glnodes.cpp
|
||||||
maploader/udmf.cpp
|
maploader/udmf.cpp
|
||||||
|
maploader/usdf.cpp
|
||||||
|
maploader/strifedialogue.cpp
|
||||||
maploader/polyobjects.cpp
|
maploader/polyobjects.cpp
|
||||||
maploader/renderinfo.cpp
|
maploader/renderinfo.cpp
|
||||||
maploader/compatibility.cpp
|
maploader/compatibility.cpp
|
||||||
|
|
|
@ -3016,7 +3016,7 @@ void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position)
|
||||||
LoadMapinfoACSLump();
|
LoadMapinfoACSLump();
|
||||||
|
|
||||||
|
|
||||||
P_LoadStrifeConversations(Level, map, lumpname);
|
LoadStrifeConversations(map, lumpname);
|
||||||
|
|
||||||
FMissingTextureTracker missingtex;
|
FMissingTextureTracker missingtex;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
#include "g_levellocals.h"
|
#include "g_levellocals.h"
|
||||||
|
|
||||||
class FileReader;
|
class FileReader;
|
||||||
|
struct FStrifeDialogueNode;
|
||||||
|
struct FStrifeDialogueReply;
|
||||||
|
struct Response;
|
||||||
|
|
||||||
struct EDMapthing
|
struct EDMapthing
|
||||||
{
|
{
|
||||||
|
@ -98,6 +101,7 @@ struct MapData;
|
||||||
class MapLoader
|
class MapLoader
|
||||||
{
|
{
|
||||||
friend class UDMFParser;
|
friend class UDMFParser;
|
||||||
|
friend class USDFParser;
|
||||||
void *level; // this is to hide the global variable and produce an error for referencing it.
|
void *level; // this is to hide the global variable and produce an error for referencing it.
|
||||||
public:
|
public:
|
||||||
FLevelLocals *Level;
|
FLevelLocals *Level;
|
||||||
|
@ -176,6 +180,16 @@ private:
|
||||||
void ReportUnpairedMinisegs();
|
void ReportUnpairedMinisegs();
|
||||||
void CalcIndices();
|
void CalcIndices();
|
||||||
|
|
||||||
|
// Strife dialogue
|
||||||
|
void LoadStrifeConversations (MapData *map, const char *mapname);
|
||||||
|
bool LoadScriptFile (const char *name, bool include, int type);
|
||||||
|
bool LoadScriptFile(const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type);
|
||||||
|
FStrifeDialogueNode *ReadRetailNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType);
|
||||||
|
FStrifeDialogueNode *ReadTeaserNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType);
|
||||||
|
void ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses);
|
||||||
|
|
||||||
|
bool ParseUSDF(int lumpnum, FileReader &lump, int lumplen);
|
||||||
|
|
||||||
// Specials
|
// Specials
|
||||||
void SpawnSpecials();
|
void SpawnSpecials();
|
||||||
void InitSectorSpecial(sector_t *sector, int special);
|
void InitSectorSpecial(sector_t *sector, int special);
|
||||||
|
|
560
src/maploader/strifedialogue.cpp
Normal file
560
src/maploader/strifedialogue.cpp
Normal file
|
@ -0,0 +1,560 @@
|
||||||
|
/*
|
||||||
|
** strifedialogue.cpp
|
||||||
|
** loads Strife style conversation dialogs
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2004-2008 Randy Heit
|
||||||
|
** Copyright 2006-2019 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "actor.h"
|
||||||
|
#include "p_conversation.h"
|
||||||
|
#include "w_wad.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "v_text.h"
|
||||||
|
#include "gi.h"
|
||||||
|
#include "a_keys.h"
|
||||||
|
#include "p_enemy.h"
|
||||||
|
#include "gstrings.h"
|
||||||
|
#include "p_setup.h"
|
||||||
|
#include "d_net.h"
|
||||||
|
#include "d_event.h"
|
||||||
|
#include "doomstat.h"
|
||||||
|
#include "c_console.h"
|
||||||
|
#include "g_levellocals.h"
|
||||||
|
#include "maploader.h"
|
||||||
|
|
||||||
|
// The conversations as they exist inside a SCRIPTxx lump.
|
||||||
|
struct Response
|
||||||
|
{
|
||||||
|
int32_t GiveType;
|
||||||
|
int32_t Item[3];
|
||||||
|
int32_t Count[3];
|
||||||
|
char Reply[32];
|
||||||
|
char Yes[80];
|
||||||
|
int32_t Link;
|
||||||
|
uint32_t Log;
|
||||||
|
char No[80];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Speech
|
||||||
|
{
|
||||||
|
uint32_t SpeakerType;
|
||||||
|
int32_t DropType;
|
||||||
|
int32_t ItemCheck[3];
|
||||||
|
int32_t Link;
|
||||||
|
char Name[16];
|
||||||
|
char Sound[8];
|
||||||
|
char Backdrop[8];
|
||||||
|
char Dialogue[320];
|
||||||
|
Response Responses[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Teaser version of the game uses an older version of the structure
|
||||||
|
struct TeaserSpeech
|
||||||
|
{
|
||||||
|
uint32_t SpeakerType;
|
||||||
|
int32_t DropType;
|
||||||
|
uint32_t VoiceNumber;
|
||||||
|
char Name[16];
|
||||||
|
char Dialogue[320];
|
||||||
|
Response Responses[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// P_LoadStrifeConversations
|
||||||
|
//
|
||||||
|
// Loads the SCRIPT00 and SCRIPTxx files for a corresponding map.
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
void MapLoader::LoadStrifeConversations (MapData *map, const char *mapname)
|
||||||
|
{
|
||||||
|
if (map->Size(ML_CONVERSATION) > 0)
|
||||||
|
{
|
||||||
|
LoadScriptFile (nullptr, map->lumpnum, map->Reader(ML_CONVERSATION), map->Size(ML_CONVERSATION), false, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (strnicmp (mapname, "MAP", 3) == 0)
|
||||||
|
{
|
||||||
|
char scriptname_b[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],0 };
|
||||||
|
char scriptname_t[9] = { 'D','I','A','L','O','G',mapname[3],mapname[4],0 };
|
||||||
|
|
||||||
|
if ( LoadScriptFile(scriptname_t, false, 2)
|
||||||
|
|| LoadScriptFile(scriptname_b, false, 1))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameinfo.Dialogue.IsNotEmpty())
|
||||||
|
{
|
||||||
|
if (LoadScriptFile(gameinfo.Dialogue, false, 0))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadScriptFile("SCRIPT00", false, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// LoadScriptFile
|
||||||
|
//
|
||||||
|
// Loads a SCRIPTxx file and converts it into a more useful internal format.
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
bool MapLoader::LoadScriptFile (const char *name, bool include, int type)
|
||||||
|
{
|
||||||
|
int lumpnum = Wads.CheckNumForName (name);
|
||||||
|
const bool found = lumpnum >= 0
|
||||||
|
|| (lumpnum = Wads.CheckNumForFullName (name)) >= 0;
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
if (type == 0)
|
||||||
|
{
|
||||||
|
Printf(TEXTCOLOR_RED "Could not find dialog file %s\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FileReader lump = Wads.ReopenLumpReader (lumpnum);
|
||||||
|
|
||||||
|
auto fn = Wads.GetLumpFile(lumpnum);
|
||||||
|
auto wadname = Wads.GetWadName(fn);
|
||||||
|
if (stricmp(wadname, "STRIFE0.WAD") && stricmp(wadname, "STRIFE1.WAD") && stricmp(wadname, "SVE.WAD")) name = nullptr; // Only localize IWAD content.
|
||||||
|
if (name && !stricmp(name, "SCRIPT00")) name = nullptr; // This only contains random string references which already use the string table.
|
||||||
|
|
||||||
|
bool res = LoadScriptFile(name, lumpnum, lump, Wads.LumpLength(lumpnum), include, type);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MapLoader::LoadScriptFile(const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
uint32_t prevSpeakerType;
|
||||||
|
FStrifeDialogueNode *node;
|
||||||
|
char buffer[4];
|
||||||
|
|
||||||
|
lump.Read(buffer, 4);
|
||||||
|
lump.Seek(-4, FileReader::SeekCur);
|
||||||
|
|
||||||
|
// The binary format is so primitive that this check is enough to detect it.
|
||||||
|
bool isbinary = (buffer[0] == 0 || buffer[1] == 0 || buffer[2] == 0 || buffer[3] == 0);
|
||||||
|
|
||||||
|
if ((type == 1 && !isbinary) || (type == 2 && isbinary))
|
||||||
|
{
|
||||||
|
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isbinary)
|
||||||
|
{
|
||||||
|
ParseUSDF(lumpnum, lump, numnodes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!include)
|
||||||
|
{
|
||||||
|
LoadScriptFile("SCRIPT00", true, 1);
|
||||||
|
}
|
||||||
|
if (!(gameinfo.flags & GI_SHAREWARE))
|
||||||
|
{
|
||||||
|
// Strife scripts are always a multiple of 1516 bytes because each entry
|
||||||
|
// is exactly 1516 bytes long.
|
||||||
|
if (numnodes % 1516 != 0)
|
||||||
|
{
|
||||||
|
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
numnodes /= 1516;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// And the teaser version has 1488-byte entries.
|
||||||
|
if (numnodes % 1488 != 0)
|
||||||
|
{
|
||||||
|
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
numnodes /= 1488;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevSpeakerType = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < numnodes; ++i)
|
||||||
|
{
|
||||||
|
if (!(gameinfo.flags & GI_SHAREWARE))
|
||||||
|
{
|
||||||
|
node = ReadRetailNode (name, lump, prevSpeakerType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node = ReadTeaserNode (name, lump, prevSpeakerType);
|
||||||
|
}
|
||||||
|
node->ThisNodeNum = Level->StrifeDialogues.Push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// ReadRetailNode
|
||||||
|
//
|
||||||
|
// Converts a single dialogue node from the Retail version of Strife.
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
static FString TokenFromString(const char *speech)
|
||||||
|
{
|
||||||
|
FString token = speech;
|
||||||
|
token.ToUpper();
|
||||||
|
token.ReplaceChars(".,-+!?'", ' ');
|
||||||
|
token.Substitute(" ", "");
|
||||||
|
token.Truncate(5);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
FStrifeDialogueNode *MapLoader::ReadRetailNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType)
|
||||||
|
{
|
||||||
|
FStrifeDialogueNode *node;
|
||||||
|
Speech speech;
|
||||||
|
char fullsound[16];
|
||||||
|
PClassActor *type;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
node = new FStrifeDialogueNode;
|
||||||
|
|
||||||
|
auto pos = lump.Tell();
|
||||||
|
lump.Read (&speech, sizeof(speech));
|
||||||
|
|
||||||
|
// Byte swap all the ints in the original data
|
||||||
|
speech.SpeakerType = LittleLong(speech.SpeakerType);
|
||||||
|
speech.DropType = LittleLong(speech.DropType);
|
||||||
|
speech.Link = LittleLong(speech.Link);
|
||||||
|
|
||||||
|
// Assign the first instance of a conversation as the default for its
|
||||||
|
// actor, so newly spawned actors will use this conversation by default.
|
||||||
|
type = GetStrifeType (speech.SpeakerType);
|
||||||
|
node->SpeakerType = type;
|
||||||
|
|
||||||
|
if ((signed)(speech.SpeakerType) >= 0 && prevSpeakerType != speech.SpeakerType)
|
||||||
|
{
|
||||||
|
if (type != NULL)
|
||||||
|
{
|
||||||
|
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
|
||||||
|
}
|
||||||
|
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
|
||||||
|
prevSpeakerType = speech.SpeakerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the rest of the data to our own internal format.
|
||||||
|
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
FStringf label("$TXT_DLG_%s_d%d_%s", name, int(pos), TokenFromString(speech.Dialogue).GetChars());
|
||||||
|
node->Dialogue = GStrings.exists(label.GetChars()+1)? label : FString(speech.Dialogue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->Dialogue = speech.Dialogue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The speaker's portrait, if any.
|
||||||
|
speech.Dialogue[0] = 0; //speech.Backdrop[8] = 0;
|
||||||
|
node->Backdrop = speech.Backdrop;
|
||||||
|
|
||||||
|
// The speaker's voice for this node, if any.
|
||||||
|
speech.Backdrop[0] = 0; //speech.Sound[8] = 0;
|
||||||
|
mysnprintf (fullsound, countof(fullsound), "svox/%s", speech.Sound);
|
||||||
|
node->SpeakerVoice = fullsound;
|
||||||
|
|
||||||
|
// The speaker's name, if any.
|
||||||
|
speech.Sound[0] = 0; //speech.Name[16] = 0;
|
||||||
|
if (name && speech.Name[0])
|
||||||
|
{
|
||||||
|
FString label = speech.Name;
|
||||||
|
label.ReplaceChars(' ', '_');
|
||||||
|
label.ReplaceChars('\'', '_');
|
||||||
|
node->SpeakerName.Format("$TXT_SPEAKER_%s", label.GetChars());
|
||||||
|
if (!GStrings.exists(node->SpeakerName.GetChars() + 1)) node->SpeakerName = speech.Name;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->SpeakerName = speech.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The item the speaker should drop when killed.
|
||||||
|
node->DropType = GetStrifeType(speech.DropType);
|
||||||
|
|
||||||
|
// Items you need to have to make the speaker use a different node.
|
||||||
|
node->ItemCheck.Resize(3);
|
||||||
|
for (j = 0; j < 3; ++j)
|
||||||
|
{
|
||||||
|
auto inv = GetStrifeType(speech.ItemCheck[j]);
|
||||||
|
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
|
||||||
|
node->ItemCheck[j].Item = inv;
|
||||||
|
node->ItemCheck[j].Amount = -1;
|
||||||
|
}
|
||||||
|
node->ItemCheckNode = speech.Link;
|
||||||
|
node->Children = NULL;
|
||||||
|
|
||||||
|
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// ReadTeaserNode
|
||||||
|
//
|
||||||
|
// Converts a single dialogue node from the Teaser version of Strife.
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
FStrifeDialogueNode *MapLoader::ReadTeaserNode (const char *name, FileReader &lump, uint32_t &prevSpeakerType)
|
||||||
|
{
|
||||||
|
FStrifeDialogueNode *node;
|
||||||
|
TeaserSpeech speech;
|
||||||
|
char fullsound[16];
|
||||||
|
PClassActor *type;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
node = new FStrifeDialogueNode;
|
||||||
|
|
||||||
|
auto pos = lump.Tell() * 1516 / 1488;
|
||||||
|
lump.Read (&speech, sizeof(speech));
|
||||||
|
|
||||||
|
// Byte swap all the ints in the original data
|
||||||
|
speech.SpeakerType = LittleLong(speech.SpeakerType);
|
||||||
|
speech.DropType = LittleLong(speech.DropType);
|
||||||
|
|
||||||
|
// Assign the first instance of a conversation as the default for its
|
||||||
|
// actor, so newly spawned actors will use this conversation by default.
|
||||||
|
type = GetStrifeType(speech.SpeakerType);
|
||||||
|
node->SpeakerType = type;
|
||||||
|
|
||||||
|
if ((signed)speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType)
|
||||||
|
{
|
||||||
|
if (type != NULL)
|
||||||
|
{
|
||||||
|
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
|
||||||
|
}
|
||||||
|
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
|
||||||
|
prevSpeakerType = speech.SpeakerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the rest of the data to our own internal format.
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
FStringf label("$TXT_DLG_%s_d%d_%s", name, pos, TokenFromString(speech.Dialogue).GetChars());
|
||||||
|
node->Dialogue = GStrings.exists(label.GetChars() + 1)? label : FString(speech.Dialogue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->Dialogue = speech.Dialogue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Teaser version doesn't have portraits.
|
||||||
|
node->Backdrop = "";
|
||||||
|
|
||||||
|
// The speaker's voice for this node, if any.
|
||||||
|
if (speech.VoiceNumber != 0)
|
||||||
|
{
|
||||||
|
mysnprintf (fullsound, countof(fullsound), "svox/voc%u", speech.VoiceNumber);
|
||||||
|
node->SpeakerVoice = fullsound;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->SpeakerVoice = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The speaker's name, if any.
|
||||||
|
speech.Dialogue[0] = 0; //speech.Name[16] = 0;
|
||||||
|
if (name && speech.Name[0])
|
||||||
|
{
|
||||||
|
FString label = speech.Name;
|
||||||
|
label.ReplaceChars(' ', '_');
|
||||||
|
label.ReplaceChars('\'', '_');
|
||||||
|
node->SpeakerName.Format("$TXT_SPEAKER_%s", label.GetChars());
|
||||||
|
if (!GStrings.exists(node->SpeakerName.GetChars() + 1)) node->SpeakerName = speech.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->SpeakerName = speech.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The item the speaker should drop when killed.
|
||||||
|
node->DropType = GetStrifeType (speech.DropType);
|
||||||
|
|
||||||
|
// Items you need to have to make the speaker use a different node.
|
||||||
|
node->ItemCheck.Resize(3);
|
||||||
|
for (j = 0; j < 3; ++j)
|
||||||
|
{
|
||||||
|
node->ItemCheck[j].Item = NULL;
|
||||||
|
node->ItemCheck[j].Amount = -1;
|
||||||
|
}
|
||||||
|
node->ItemCheckNode = 0;
|
||||||
|
node->Children = NULL;
|
||||||
|
|
||||||
|
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//
|
||||||
|
// ParseReplies
|
||||||
|
//
|
||||||
|
// Convert PC responses. Rather than being stored inside the main node, they
|
||||||
|
// hang off it as a singly-linked list, so no space is wasted on replies that
|
||||||
|
// don't even matter.
|
||||||
|
//
|
||||||
|
//============================================================================
|
||||||
|
|
||||||
|
void MapLoader::ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses)
|
||||||
|
{
|
||||||
|
FStrifeDialogueReply *reply;
|
||||||
|
int j, k;
|
||||||
|
|
||||||
|
// Byte swap first.
|
||||||
|
for (j = 0; j < 5; ++j)
|
||||||
|
{
|
||||||
|
responses[j].GiveType = LittleLong(responses[j].GiveType);
|
||||||
|
responses[j].Link = LittleLong(responses[j].Link);
|
||||||
|
responses[j].Log = LittleLong(responses[j].Log);
|
||||||
|
for (k = 0; k < 3; ++k)
|
||||||
|
{
|
||||||
|
responses[j].Item[k] = LittleLong(responses[j].Item[k]);
|
||||||
|
responses[j].Count[k] = LittleLong(responses[j].Count[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < 5; ++j)
|
||||||
|
{
|
||||||
|
Response *rsp = &responses[j];
|
||||||
|
|
||||||
|
// If the reply has no text and goes nowhere, then it doesn't
|
||||||
|
// need to be remembered.
|
||||||
|
if (rsp->Reply[0] == 0 && rsp->Link == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reply = new FStrifeDialogueReply;
|
||||||
|
|
||||||
|
// The next node to use when this reply is chosen.
|
||||||
|
reply->NextNode = rsp->Link;
|
||||||
|
if (reply->NextNode < 0)
|
||||||
|
{
|
||||||
|
reply->NextNode *= -1;
|
||||||
|
reply->CloseDialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The message to record in the log for this reply.
|
||||||
|
reply->LogNumber = rsp->Log;
|
||||||
|
reply->LogString = "";
|
||||||
|
|
||||||
|
// The item to receive when this reply is used.
|
||||||
|
reply->GiveType = GetStrifeType (rsp->GiveType);
|
||||||
|
reply->ActionSpecial = 0;
|
||||||
|
|
||||||
|
// Do you need anything special for this reply to succeed?
|
||||||
|
reply->ItemCheck.Resize(3);
|
||||||
|
for (k = 0; k < 3; ++k)
|
||||||
|
{
|
||||||
|
auto inv = GetStrifeType(rsp->Item[k]);
|
||||||
|
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
|
||||||
|
reply->ItemCheck[k].Item = inv;
|
||||||
|
reply->ItemCheck[k].Amount = rsp->Count[k];
|
||||||
|
}
|
||||||
|
reply->PrintAmount = reply->ItemCheck[0].Amount;
|
||||||
|
reply->ItemCheckRequire.Clear();
|
||||||
|
reply->ItemCheckExclude.Clear();
|
||||||
|
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
FStringf label("$TXT_RPLY%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Reply).GetChars());
|
||||||
|
reply->Reply = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->Reply);
|
||||||
|
|
||||||
|
reply->Reply = label;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reply->Reply = rsp->Reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If the first item check has a positive amount required, then
|
||||||
|
// add that to the reply string. Otherwise, use the reply as-is.
|
||||||
|
reply->NeedsGold = (rsp->Count[0] > 0);
|
||||||
|
|
||||||
|
// QuickYes messages are shown when you meet the item checks.
|
||||||
|
// QuickNo messages are shown when you don't.
|
||||||
|
// Note that empty nodes contain a '_' in retail Strife, a '.' in the teasers and an empty string in SVE.
|
||||||
|
if (((rsp->Yes[0] == '_' || rsp->Yes[0] == '.') && rsp->Yes[1] == 0) || rsp->Yes[0] == 0)
|
||||||
|
{
|
||||||
|
reply->QuickYes = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (name)
|
||||||
|
{
|
||||||
|
FStringf label("$TXT_RYES%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Yes).GetChars());
|
||||||
|
reply->QuickYes = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->Yes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reply->QuickYes = rsp->Yes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reply->ItemCheck[0].Item != 0)
|
||||||
|
{
|
||||||
|
FStringf label("$TXT_RNO%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->No).GetChars());
|
||||||
|
reply->QuickNo = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->No);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reply->QuickNo = "";
|
||||||
|
}
|
||||||
|
reply->Next = *replyptr;
|
||||||
|
*replyptr = reply;
|
||||||
|
replyptr = &reply->Next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
#include "gi.h"
|
#include "gi.h"
|
||||||
#include "r_sky.h"
|
#include "r_sky.h"
|
||||||
#include "g_level.h"
|
#include "g_level.h"
|
||||||
#include "p_udmf.h"
|
#include "udmf.h"
|
||||||
#include "r_state.h"
|
#include "r_state.h"
|
||||||
#include "w_wad.h"
|
#include "w_wad.h"
|
||||||
#include "p_tags.h"
|
#include "p_tags.h"
|
||||||
|
|
|
@ -35,12 +35,13 @@
|
||||||
#include "p_setup.h"
|
#include "p_setup.h"
|
||||||
#include "p_lnspec.h"
|
#include "p_lnspec.h"
|
||||||
#include "p_conversation.h"
|
#include "p_conversation.h"
|
||||||
#include "p_udmf.h"
|
#include "udmf.h"
|
||||||
#include "doomerrors.h"
|
#include "doomerrors.h"
|
||||||
#include "actor.h"
|
#include "actor.h"
|
||||||
#include "a_pickups.h"
|
#include "a_pickups.h"
|
||||||
#include "w_wad.h"
|
#include "w_wad.h"
|
||||||
#include "g_levellocals.h"
|
#include "g_levellocals.h"
|
||||||
|
#include "maploader.h"
|
||||||
|
|
||||||
#define Zd 1
|
#define Zd 1
|
||||||
#define St 2
|
#define St 2
|
||||||
|
@ -496,9 +497,9 @@ class USDFParser : public UDMFParserBase
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool Parse(FLevelLocals *l, int lumpnum, FileReader &lump, int lumplen)
|
bool Parse(MapLoader *loader,int lumpnum, FileReader &lump, int lumplen)
|
||||||
{
|
{
|
||||||
Level = l;
|
Level = loader->Level;
|
||||||
sc.OpenMem(Wads.GetLumpFullName(lumpnum), lump.Read(lumplen));
|
sc.OpenMem(Wads.GetLumpFullName(lumpnum), lump.Read(lumplen));
|
||||||
sc.SetCMode(true);
|
sc.SetCMode(true);
|
||||||
// Namespace must be the first field because everything else depends on it.
|
// Namespace must be the first field because everything else depends on it.
|
||||||
|
@ -541,7 +542,7 @@ public:
|
||||||
{
|
{
|
||||||
sc.MustGetToken('=');
|
sc.MustGetToken('=');
|
||||||
sc.MustGetToken(TK_StringConst);
|
sc.MustGetToken(TK_StringConst);
|
||||||
LoadScriptFile(Level, sc.String, true);
|
loader->LoadScriptFile(sc.String, true, 0);
|
||||||
sc.MustGetToken(';');
|
sc.MustGetToken(';');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -607,13 +608,13 @@ public:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool P_ParseUSDF(FLevelLocals *l, int lumpnum, FileReader &lump, int lumplen)
|
bool MapLoader::ParseUSDF(int lumpnum, FileReader &lump, int lumplen)
|
||||||
{
|
{
|
||||||
USDFParser parse;
|
USDFParser parse;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!parse.Parse(l, lumpnum, lump, lumplen))
|
if (!parse.Parse(this, lumpnum, lump, lumplen))
|
||||||
{
|
{
|
||||||
// clean up the incomplete dialogue structures here
|
// clean up the incomplete dialogue structures here
|
||||||
return false;
|
return false;
|
|
@ -58,43 +58,6 @@
|
||||||
#include "v_video.h"
|
#include "v_video.h"
|
||||||
#include "actorinlines.h"
|
#include "actorinlines.h"
|
||||||
|
|
||||||
// The conversations as they exist inside a SCRIPTxx lump.
|
|
||||||
struct Response
|
|
||||||
{
|
|
||||||
int32_t GiveType;
|
|
||||||
int32_t Item[3];
|
|
||||||
int32_t Count[3];
|
|
||||||
char Reply[32];
|
|
||||||
char Yes[80];
|
|
||||||
int32_t Link;
|
|
||||||
uint32_t Log;
|
|
||||||
char No[80];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Speech
|
|
||||||
{
|
|
||||||
uint32_t SpeakerType;
|
|
||||||
int32_t DropType;
|
|
||||||
int32_t ItemCheck[3];
|
|
||||||
int32_t Link;
|
|
||||||
char Name[16];
|
|
||||||
char Sound[8];
|
|
||||||
char Backdrop[8];
|
|
||||||
char Dialogue[320];
|
|
||||||
Response Responses[5];
|
|
||||||
};
|
|
||||||
|
|
||||||
// The Teaser version of the game uses an older version of the structure
|
|
||||||
struct TeaserSpeech
|
|
||||||
{
|
|
||||||
uint32_t SpeakerType;
|
|
||||||
int32_t DropType;
|
|
||||||
uint32_t VoiceNumber;
|
|
||||||
char Name[16];
|
|
||||||
char Dialogue[320];
|
|
||||||
Response Responses[5];
|
|
||||||
};
|
|
||||||
|
|
||||||
static FRandom pr_randomspeech("RandomSpeech");
|
static FRandom pr_randomspeech("RandomSpeech");
|
||||||
|
|
||||||
static int ConversationMenuY;
|
static int ConversationMenuY;
|
||||||
|
@ -103,10 +66,6 @@ static int ConversationMenuY;
|
||||||
static FStrifeDialogueNode *PrevNode;
|
static FStrifeDialogueNode *PrevNode;
|
||||||
static int StaticLastReply;
|
static int StaticLastReply;
|
||||||
|
|
||||||
static bool LoadScriptFile(FLevelLocals *Level, const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type);
|
|
||||||
static FStrifeDialogueNode *ReadRetailNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType);
|
|
||||||
static FStrifeDialogueNode *ReadTeaserNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType);
|
|
||||||
static void ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses);
|
|
||||||
static bool DrawConversationMenu ();
|
static bool DrawConversationMenu ();
|
||||||
static void PickConversationReply (int replyindex);
|
static void PickConversationReply (int replyindex);
|
||||||
static void TerminalResponse (const char *str);
|
static void TerminalResponse (const char *str);
|
||||||
|
@ -153,475 +112,6 @@ int FLevelLocals::GetConversation(FName classname)
|
||||||
else return *pindex;
|
else return *pindex;
|
||||||
}
|
}
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// P_LoadStrifeConversations
|
|
||||||
//
|
|
||||||
// Loads the SCRIPT00 and SCRIPTxx files for a corresponding map.
|
|
||||||
//
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
void P_LoadStrifeConversations (FLevelLocals *Level, MapData *map, const char *mapname)
|
|
||||||
{
|
|
||||||
if (map->Size(ML_CONVERSATION) > 0)
|
|
||||||
{
|
|
||||||
LoadScriptFile (Level, nullptr, map->lumpnum, map->Reader(ML_CONVERSATION), map->Size(ML_CONVERSATION), false, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (strnicmp (mapname, "MAP", 3) == 0)
|
|
||||||
{
|
|
||||||
char scriptname_b[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],0 };
|
|
||||||
char scriptname_t[9] = { 'D','I','A','L','O','G',mapname[3],mapname[4],0 };
|
|
||||||
|
|
||||||
if ( LoadScriptFile(Level, scriptname_t, false, 2)
|
|
||||||
|| LoadScriptFile(Level, scriptname_b, false, 1))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameinfo.Dialogue.IsNotEmpty())
|
|
||||||
{
|
|
||||||
if (LoadScriptFile(Level, gameinfo.Dialogue, false, 0))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadScriptFile(Level, "SCRIPT00", false, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// LoadScriptFile
|
|
||||||
//
|
|
||||||
// Loads a SCRIPTxx file and converts it into a more useful internal format.
|
|
||||||
//
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
bool LoadScriptFile (FLevelLocals *Level, const char *name, bool include, int type)
|
|
||||||
{
|
|
||||||
int lumpnum = Wads.CheckNumForName (name);
|
|
||||||
const bool found = lumpnum >= 0
|
|
||||||
|| (lumpnum = Wads.CheckNumForFullName (name)) >= 0;
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
if (type == 0)
|
|
||||||
{
|
|
||||||
Printf(TEXTCOLOR_RED "Could not find dialog file %s\n", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
FileReader lump = Wads.ReopenLumpReader (lumpnum);
|
|
||||||
|
|
||||||
auto fn = Wads.GetLumpFile(lumpnum);
|
|
||||||
auto wadname = Wads.GetWadName(fn);
|
|
||||||
if (stricmp(wadname, "STRIFE0.WAD") && stricmp(wadname, "STRIFE1.WAD") && stricmp(wadname, "SVE.WAD")) name = nullptr; // Only localize IWAD content.
|
|
||||||
if (name && !stricmp(name, "SCRIPT00")) name = nullptr; // This only contains random string references which already use the string table.
|
|
||||||
|
|
||||||
bool res = LoadScriptFile(Level, name, lumpnum, lump, Wads.LumpLength(lumpnum), include, type);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool LoadScriptFile(FLevelLocals *Level, const char *name, int lumpnum, FileReader &lump, int numnodes, bool include, int type)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
uint32_t prevSpeakerType;
|
|
||||||
FStrifeDialogueNode *node;
|
|
||||||
char buffer[4];
|
|
||||||
|
|
||||||
lump.Read(buffer, 4);
|
|
||||||
lump.Seek(-4, FileReader::SeekCur);
|
|
||||||
|
|
||||||
// The binary format is so primitive that this check is enough to detect it.
|
|
||||||
bool isbinary = (buffer[0] == 0 || buffer[1] == 0 || buffer[2] == 0 || buffer[3] == 0);
|
|
||||||
|
|
||||||
if ((type == 1 && !isbinary) || (type == 2 && isbinary))
|
|
||||||
{
|
|
||||||
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isbinary)
|
|
||||||
{
|
|
||||||
P_ParseUSDF(Level, lumpnum, lump, numnodes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!include)
|
|
||||||
{
|
|
||||||
LoadScriptFile(Level, "SCRIPT00", true, 1);
|
|
||||||
}
|
|
||||||
if (!(gameinfo.flags & GI_SHAREWARE))
|
|
||||||
{
|
|
||||||
// Strife scripts are always a multiple of 1516 bytes because each entry
|
|
||||||
// is exactly 1516 bytes long.
|
|
||||||
if (numnodes % 1516 != 0)
|
|
||||||
{
|
|
||||||
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
numnodes /= 1516;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// And the teaser version has 1488-byte entries.
|
|
||||||
if (numnodes % 1488 != 0)
|
|
||||||
{
|
|
||||||
DPrintf(DMSG_ERROR, "Incorrect data format for conversation script in %s.\n", Wads.GetLumpFullName(lumpnum));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
numnodes /= 1488;
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSpeakerType = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < numnodes; ++i)
|
|
||||||
{
|
|
||||||
if (!(gameinfo.flags & GI_SHAREWARE))
|
|
||||||
{
|
|
||||||
node = ReadRetailNode (Level, name, lump, prevSpeakerType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node = ReadTeaserNode (Level, name, lump, prevSpeakerType);
|
|
||||||
}
|
|
||||||
node->ThisNodeNum = Level->StrifeDialogues.Push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// ReadRetailNode
|
|
||||||
//
|
|
||||||
// Converts a single dialogue node from the Retail version of Strife.
|
|
||||||
//
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
static FString TokenFromString(const char *speech)
|
|
||||||
{
|
|
||||||
FString token = speech;
|
|
||||||
token.ToUpper();
|
|
||||||
token.ReplaceChars(".,-+!?'", ' ');
|
|
||||||
token.Substitute(" ", "");
|
|
||||||
token.Truncate(5);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FStrifeDialogueNode *ReadRetailNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType)
|
|
||||||
{
|
|
||||||
FStrifeDialogueNode *node;
|
|
||||||
Speech speech;
|
|
||||||
char fullsound[16];
|
|
||||||
PClassActor *type;
|
|
||||||
int j;
|
|
||||||
|
|
||||||
node = new FStrifeDialogueNode;
|
|
||||||
|
|
||||||
auto pos = lump.Tell();
|
|
||||||
lump.Read (&speech, sizeof(speech));
|
|
||||||
|
|
||||||
// Byte swap all the ints in the original data
|
|
||||||
speech.SpeakerType = LittleLong(speech.SpeakerType);
|
|
||||||
speech.DropType = LittleLong(speech.DropType);
|
|
||||||
speech.Link = LittleLong(speech.Link);
|
|
||||||
|
|
||||||
// Assign the first instance of a conversation as the default for its
|
|
||||||
// actor, so newly spawned actors will use this conversation by default.
|
|
||||||
type = GetStrifeType (speech.SpeakerType);
|
|
||||||
node->SpeakerType = type;
|
|
||||||
|
|
||||||
if ((signed)(speech.SpeakerType) >= 0 && prevSpeakerType != speech.SpeakerType)
|
|
||||||
{
|
|
||||||
if (type != NULL)
|
|
||||||
{
|
|
||||||
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
|
|
||||||
}
|
|
||||||
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
|
|
||||||
prevSpeakerType = speech.SpeakerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the rest of the data to our own internal format.
|
|
||||||
|
|
||||||
if (name)
|
|
||||||
{
|
|
||||||
FStringf label("$TXT_DLG_%s_d%d_%s", name, int(pos), TokenFromString(speech.Dialogue).GetChars());
|
|
||||||
node->Dialogue = GStrings.exists(label.GetChars()+1)? label : FString(speech.Dialogue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node->Dialogue = speech.Dialogue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The speaker's portrait, if any.
|
|
||||||
speech.Dialogue[0] = 0; //speech.Backdrop[8] = 0;
|
|
||||||
node->Backdrop = speech.Backdrop;
|
|
||||||
|
|
||||||
// The speaker's voice for this node, if any.
|
|
||||||
speech.Backdrop[0] = 0; //speech.Sound[8] = 0;
|
|
||||||
mysnprintf (fullsound, countof(fullsound), "svox/%s", speech.Sound);
|
|
||||||
node->SpeakerVoice = fullsound;
|
|
||||||
|
|
||||||
// The speaker's name, if any.
|
|
||||||
speech.Sound[0] = 0; //speech.Name[16] = 0;
|
|
||||||
if (name && speech.Name[0])
|
|
||||||
{
|
|
||||||
FString label = speech.Name;
|
|
||||||
label.ReplaceChars(' ', '_');
|
|
||||||
label.ReplaceChars('\'', '_');
|
|
||||||
node->SpeakerName.Format("$TXT_SPEAKER_%s", label.GetChars());
|
|
||||||
if (!GStrings.exists(node->SpeakerName.GetChars() + 1)) node->SpeakerName = speech.Name;
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node->SpeakerName = speech.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item the speaker should drop when killed.
|
|
||||||
node->DropType = GetStrifeType(speech.DropType);
|
|
||||||
|
|
||||||
// Items you need to have to make the speaker use a different node.
|
|
||||||
node->ItemCheck.Resize(3);
|
|
||||||
for (j = 0; j < 3; ++j)
|
|
||||||
{
|
|
||||||
auto inv = GetStrifeType(speech.ItemCheck[j]);
|
|
||||||
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
|
|
||||||
node->ItemCheck[j].Item = inv;
|
|
||||||
node->ItemCheck[j].Amount = -1;
|
|
||||||
}
|
|
||||||
node->ItemCheckNode = speech.Link;
|
|
||||||
node->Children = NULL;
|
|
||||||
|
|
||||||
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// ReadTeaserNode
|
|
||||||
//
|
|
||||||
// Converts a single dialogue node from the Teaser version of Strife.
|
|
||||||
//
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
static FStrifeDialogueNode *ReadTeaserNode (FLevelLocals *Level, const char *name, FileReader &lump, uint32_t &prevSpeakerType)
|
|
||||||
{
|
|
||||||
FStrifeDialogueNode *node;
|
|
||||||
TeaserSpeech speech;
|
|
||||||
char fullsound[16];
|
|
||||||
PClassActor *type;
|
|
||||||
int j;
|
|
||||||
|
|
||||||
node = new FStrifeDialogueNode;
|
|
||||||
|
|
||||||
auto pos = lump.Tell() * 1516 / 1488;
|
|
||||||
lump.Read (&speech, sizeof(speech));
|
|
||||||
|
|
||||||
// Byte swap all the ints in the original data
|
|
||||||
speech.SpeakerType = LittleLong(speech.SpeakerType);
|
|
||||||
speech.DropType = LittleLong(speech.DropType);
|
|
||||||
|
|
||||||
// Assign the first instance of a conversation as the default for its
|
|
||||||
// actor, so newly spawned actors will use this conversation by default.
|
|
||||||
type = GetStrifeType(speech.SpeakerType);
|
|
||||||
node->SpeakerType = type;
|
|
||||||
|
|
||||||
if ((signed)speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType)
|
|
||||||
{
|
|
||||||
if (type != NULL)
|
|
||||||
{
|
|
||||||
Level->ClassRoots[type->TypeName] = Level->StrifeDialogues.Size();
|
|
||||||
}
|
|
||||||
Level->DialogueRoots[speech.SpeakerType] = Level->StrifeDialogues.Size();
|
|
||||||
prevSpeakerType = speech.SpeakerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the rest of the data to our own internal format.
|
|
||||||
if (name)
|
|
||||||
{
|
|
||||||
FStringf label("$TXT_DLG_%s_d%d_%s", name, pos, TokenFromString(speech.Dialogue).GetChars());
|
|
||||||
node->Dialogue = GStrings.exists(label.GetChars() + 1)? label : FString(speech.Dialogue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node->Dialogue = speech.Dialogue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Teaser version doesn't have portraits.
|
|
||||||
node->Backdrop = "";
|
|
||||||
|
|
||||||
// The speaker's voice for this node, if any.
|
|
||||||
if (speech.VoiceNumber != 0)
|
|
||||||
{
|
|
||||||
mysnprintf (fullsound, countof(fullsound), "svox/voc%u", speech.VoiceNumber);
|
|
||||||
node->SpeakerVoice = fullsound;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node->SpeakerVoice = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The speaker's name, if any.
|
|
||||||
speech.Dialogue[0] = 0; //speech.Name[16] = 0;
|
|
||||||
if (name && speech.Name[0])
|
|
||||||
{
|
|
||||||
FString label = speech.Name;
|
|
||||||
label.ReplaceChars(' ', '_');
|
|
||||||
label.ReplaceChars('\'', '_');
|
|
||||||
node->SpeakerName.Format("$TXT_SPEAKER_%s", label.GetChars());
|
|
||||||
if (!GStrings.exists(node->SpeakerName.GetChars() + 1)) node->SpeakerName = speech.Name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node->SpeakerName = speech.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The item the speaker should drop when killed.
|
|
||||||
node->DropType = GetStrifeType (speech.DropType);
|
|
||||||
|
|
||||||
// Items you need to have to make the speaker use a different node.
|
|
||||||
node->ItemCheck.Resize(3);
|
|
||||||
for (j = 0; j < 3; ++j)
|
|
||||||
{
|
|
||||||
node->ItemCheck[j].Item = NULL;
|
|
||||||
node->ItemCheck[j].Amount = -1;
|
|
||||||
}
|
|
||||||
node->ItemCheckNode = 0;
|
|
||||||
node->Children = NULL;
|
|
||||||
|
|
||||||
ParseReplies (name, int(pos), &node->Children, &speech.Responses[0]);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// ParseReplies
|
|
||||||
//
|
|
||||||
// Convert PC responses. Rather than being stored inside the main node, they
|
|
||||||
// hang off it as a singly-linked list, so no space is wasted on replies that
|
|
||||||
// don't even matter.
|
|
||||||
//
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
static void ParseReplies (const char *name, int pos, FStrifeDialogueReply **replyptr, Response *responses)
|
|
||||||
{
|
|
||||||
FStrifeDialogueReply *reply;
|
|
||||||
int j, k;
|
|
||||||
|
|
||||||
// Byte swap first.
|
|
||||||
for (j = 0; j < 5; ++j)
|
|
||||||
{
|
|
||||||
responses[j].GiveType = LittleLong(responses[j].GiveType);
|
|
||||||
responses[j].Link = LittleLong(responses[j].Link);
|
|
||||||
responses[j].Log = LittleLong(responses[j].Log);
|
|
||||||
for (k = 0; k < 3; ++k)
|
|
||||||
{
|
|
||||||
responses[j].Item[k] = LittleLong(responses[j].Item[k]);
|
|
||||||
responses[j].Count[k] = LittleLong(responses[j].Count[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < 5; ++j)
|
|
||||||
{
|
|
||||||
Response *rsp = &responses[j];
|
|
||||||
|
|
||||||
// If the reply has no text and goes nowhere, then it doesn't
|
|
||||||
// need to be remembered.
|
|
||||||
if (rsp->Reply[0] == 0 && rsp->Link == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
reply = new FStrifeDialogueReply;
|
|
||||||
|
|
||||||
// The next node to use when this reply is chosen.
|
|
||||||
reply->NextNode = rsp->Link;
|
|
||||||
if (reply->NextNode < 0)
|
|
||||||
{
|
|
||||||
reply->NextNode *= -1;
|
|
||||||
reply->CloseDialog = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The message to record in the log for this reply.
|
|
||||||
reply->LogNumber = rsp->Log;
|
|
||||||
reply->LogString = "";
|
|
||||||
|
|
||||||
// The item to receive when this reply is used.
|
|
||||||
reply->GiveType = GetStrifeType (rsp->GiveType);
|
|
||||||
reply->ActionSpecial = 0;
|
|
||||||
|
|
||||||
// Do you need anything special for this reply to succeed?
|
|
||||||
reply->ItemCheck.Resize(3);
|
|
||||||
for (k = 0; k < 3; ++k)
|
|
||||||
{
|
|
||||||
auto inv = GetStrifeType(rsp->Item[k]);
|
|
||||||
if (!inv->IsDescendantOf(NAME_Inventory)) inv = nullptr;
|
|
||||||
reply->ItemCheck[k].Item = inv;
|
|
||||||
reply->ItemCheck[k].Amount = rsp->Count[k];
|
|
||||||
}
|
|
||||||
reply->PrintAmount = reply->ItemCheck[0].Amount;
|
|
||||||
reply->ItemCheckRequire.Clear();
|
|
||||||
reply->ItemCheckExclude.Clear();
|
|
||||||
|
|
||||||
if (name)
|
|
||||||
{
|
|
||||||
FStringf label("$TXT_RPLY%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Reply).GetChars());
|
|
||||||
reply->Reply = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->Reply);
|
|
||||||
|
|
||||||
reply->Reply = label;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reply->Reply = rsp->Reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// If the first item check has a positive amount required, then
|
|
||||||
// add that to the reply string. Otherwise, use the reply as-is.
|
|
||||||
reply->NeedsGold = (rsp->Count[0] > 0);
|
|
||||||
|
|
||||||
// QuickYes messages are shown when you meet the item checks.
|
|
||||||
// QuickNo messages are shown when you don't.
|
|
||||||
// Note that empty nodes contain a '_' in retail Strife, a '.' in the teasers and an empty string in SVE.
|
|
||||||
if (((rsp->Yes[0] == '_' || rsp->Yes[0] == '.') && rsp->Yes[1] == 0) || rsp->Yes[0] == 0)
|
|
||||||
{
|
|
||||||
reply->QuickYes = "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (name)
|
|
||||||
{
|
|
||||||
FStringf label("$TXT_RYES%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->Yes).GetChars());
|
|
||||||
reply->QuickYes = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->Yes);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reply->QuickYes = rsp->Yes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (reply->ItemCheck[0].Item != 0)
|
|
||||||
{
|
|
||||||
FStringf label("$TXT_RNO%d_%s_d%d_%s", j, name, pos, TokenFromString(rsp->No).GetChars());
|
|
||||||
reply->QuickNo = GStrings.exists(label.GetChars() + 1)? label : FString(rsp->No);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reply->QuickNo = "";
|
|
||||||
}
|
|
||||||
reply->Next = *replyptr;
|
|
||||||
*replyptr = reply;
|
|
||||||
replyptr = &reply->Next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//============================================================================
|
//============================================================================
|
||||||
//
|
//
|
||||||
// FStrifeDialogueNode :: ~FStrifeDialogueNode
|
// FStrifeDialogueNode :: ~FStrifeDialogueNode
|
||||||
|
|
|
@ -65,9 +65,6 @@ struct MapData;
|
||||||
|
|
||||||
PClassActor *GetStrifeType (int typenum);
|
PClassActor *GetStrifeType (int typenum);
|
||||||
|
|
||||||
bool LoadScriptFile (FLevelLocals *Level, const char *name, bool include, int type = 0);
|
|
||||||
|
|
||||||
void P_LoadStrifeConversations (FLevelLocals *Level, MapData *map, const char *mapname);
|
|
||||||
void P_FreeStrifeConversations ();
|
void P_FreeStrifeConversations ();
|
||||||
|
|
||||||
void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveangle);
|
void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveangle);
|
||||||
|
@ -76,7 +73,6 @@ void P_ResumeConversation ();
|
||||||
void P_ConversationCommand (int netcode, int player, uint8_t **stream);
|
void P_ConversationCommand (int netcode, int player, uint8_t **stream);
|
||||||
|
|
||||||
class FileReader;
|
class FileReader;
|
||||||
bool P_ParseUSDF(FLevelLocals *Level, int lumpnum, FileReader &lump, int lumplen);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue