diff --git a/specs/usdf.txt b/specs/usdf.txt new file mode 100644 index 000000000..fc955f02d --- /dev/null +++ b/specs/usdf.txt @@ -0,0 +1,158 @@ +=============================================================================== +Universal Strife Dialog Format Specification v2.0 - 08/20/10 + +Written by Braden "Blzut3" Obrzut - admin@maniacsvault.net + +Defined with input from: + +CodeImp +Gez +Graf Zahl +Quasar +et al. + + Copyright (c) 2010 Braden Obrzut. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + +The grammar and syntax is similar to that of UDMF. A compliant UDMF parser +should be applyable to the USDF. However, it will need to be capable of +handling sub-blocks. Unknown sub-blocks should be skipped. + +======================================= +II. Implementation Semantics +======================================= + +------------------------------------ +II.A : Storage and Retrieval of Data +------------------------------------ + +This is the same as in UDMF. + +----------------------------------- +II.B : Storage Within Archive Files +----------------------------------- + +There are two options for the USDF lump placement. This can either be a part +of the UDMF lump list or standalone. If used stand alone the lump name +DIALOGXY is used corresponding with MAPXY. For UDMF the lump shall be called +"DIALOGUE". + +-------------------------------- +II.C : Implementation Dependence +-------------------------------- + +USDF also implements the namespace statement. This has all the same +requirements as UDMF. + +======================================= +III. Standardized Fields +======================================= + +The following are required for all USDF complient implementations. Like UDMF, +any unknown field/function should be ignored and not treated as an error. + +NOTE: "mobj" refers to Strife's conversationIDs and not doom editor numbers or + Hexen's spawnids. A valid mobj value is any positive integer greater + than or equal to 1. + +--------------------- +III.A : Conversations +--------------------- + +Conversations are groups of pages that can be assigned to a particular object. +Implementors should preserve the IDs to allow for dynamic reassignment through +scripting although this is not a requirement. + +conversation // Starts a dialog. +{ + actor = ; // mobj for this conversation's actor. If previously + // used, this will override the previous conversation. + + page // Starts a new page. Pages are automatically numbered starting at 0. + { + name = ; // Name that goes in the upper left hand corner + panel = ; // Name of lump to render as the background. + voice = ; // Narration sound lump. + dialog = ; // Dialog of the page. + drop = ; // mobj for the object to drop if the actor is + // killed. + link = ; // Page to jump to if all ifitem conditions are + // satisified. + + // jumps to the specified page if the player has the specified amount + // or more of item in their inventory. This can be repeated as many + // times as the author wants, all conditions must be met for the + // jump to occur. + ifitem + { + item = ; // mobj of item to check. + amount = ; // amount required to be in inventory. + } + + // Choices shall be automatically numbered. + choice + { + text = ; // Name of the choice. + + // The amount of an item needed to successfully pick this option. + // This can be repeated, but only the first will be shown (provided + // diaplaycost is true). All costs must be satisfied for success. + cost + { + item = ; // Item that is required for this option. + amount = ; // Minimum amount of the item needed. + } + + displaycost = ; // Weather the cost should be + // displayed with the option. + // If no cost is specified this should + // be ignored. + yesmessage = ; // Text to add to console when choice + // is accepted. + nomessage = ; // Text to add to console when choice + // is denied. + + log = ; // LOG entry to use on success. + giveitem = ; // Gives the specified item upon + // success. + // The following are the same as the special for linedefs in UDMF. + // They are executed on success. + special = ; + arg0 = ; + arg1 = ; + arg2 = ; + arg3 = ; + arg4 = ; + + nextpage = ; // Sets the next page. + closedialog = ; // Should the dialog be closed upon + // selecting this choice? + // Default: false + } + } +} + +------------------------------- +III.B : Including Other Dialogs +------------------------------- + +Unlike the original Strife dialog format. The lump "SCRIPT00" should not be +included automatically. Instead the user must specify this behavior by using +the include function, which takes the name of a lump to include. Include only +needs to be available in the global scope and for compatibility reasons, must +include the result of the script and not act like a preprocessor statement. + +include = ; + +=============================================================================== +EOF +=============================================================================== diff --git a/specs/usdf_zdoom.txt b/specs/usdf_zdoom.txt new file mode 100644 index 000000000..8fd8fec40 --- /dev/null +++ b/specs/usdf_zdoom.txt @@ -0,0 +1,56 @@ +=============================================================================== +Universal Strife Dialog Format ZDoom extensions v1.0 - 14.08.2010 + + Copyright (c) 2010 Christoph Oelckers. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + + No changes. + +======================================= +II. Implementation Semantics +======================================= + +No changes. + +======================================= +III. Standardized Fields +======================================= + +ZDoom implements the base specification as described with one important change: +To take advantage of named actor classes any field specifying an actor type +by a conversationID takes a class name instead. +This means that ZDoom dialogues are not forward-compatible with the 'Strife' +namespace. Other ports should be aware of this. +ZDoom-format dialogues need to start with the line: + +namespace = "ZDoom"; + + +--------------------- +III.A : Conversations +--------------------- + +This block only lists the newly added fields. Currently ZDoom only adds one +field to the specification: + +conversation // Starts a dialog. +{ + id = ; // assigns an ID to a dialogue. IDs are used to dynamically assign + // dialogues to actors. For 'Strife' namespace or binary dialogues + // the standard conversation ID ('actor' property) is used instead + // for this purpose but since 'ZDoom' namespace requires the actor + // to be a class name it needs a separate field for this. +} + +=============================================================================== +EOF +=============================================================================== diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9fb4cab09..5fadf0b2d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -703,6 +703,7 @@ add_executable( zdoom WIN32 p_tick.cpp p_trace.cpp p_udmf.cpp + p_usdf.cpp p_user.cpp p_writemap.cpp p_xlat.cpp diff --git a/src/actionspecials.h b/src/actionspecials.h index 7f855147e..c735c7a9d 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -77,7 +77,7 @@ DEFINE_SPECIAL(Teleport_EndGame, 75, 0, 0, 0) DEFINE_SPECIAL(TeleportOther, 76, 3, 3, 3) DEFINE_SPECIAL(TeleportGroup, 77, 5, 5, 5) DEFINE_SPECIAL(TeleportInSector, 78, 4, 5, 5) - +DEFINE_SPECIAL(Thing_SetConversation, 79, 2, 2, 2) DEFINE_SPECIAL(ACS_Execute, 80, 1, 5, 5) DEFINE_SPECIAL(ACS_Suspend, 81, 2, 2, 2) DEFINE_SPECIAL(ACS_Terminate, 82, 2, 2, 2) diff --git a/src/actor.h b/src/actor.h index 9ae11696b..84aab5fed 100644 --- a/src/actor.h +++ b/src/actor.h @@ -892,8 +892,9 @@ public: FState *MeleeState; FState *MissileState; - // [RH] The dialogue to show when this actor is "used." - FStrifeDialogueNode *Conversation; + + int ConversationRoot; // THe root of the current dialogue + FStrifeDialogueNode *Conversation; // [RH] The dialogue to show when this actor is "used." // [RH] Decal(s) this weapon/projectile generates on impact. FDecalBase *DecalGenerator; diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp index dd32cdb55..9c06b6714 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -90,6 +90,23 @@ char *copystring (const char *s) return b; } +//============================================================================ +// +// ncopystring +// +// If the string has no content, returns NULL. Otherwise, returns a copy. +// +//============================================================================ + +char *ncopystring (const char *string) +{ + if (string == NULL || string[0] == 0) + { + return NULL; + } + return copystring (string); +} + //========================================================================== // // ReplaceString diff --git a/src/cmdlib.h b/src/cmdlib.h index fe41204ec..f9d7fae70 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -36,6 +36,7 @@ int ParseNum (const char *str); bool IsNum (const char *str); // [RH] added char *copystring(const char *s); +char *ncopystring(const char *s); void ReplaceString (char **ptr, const char *str); bool CheckWildcards (const char *pattern, const char *text); diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index cdac25902..0d443bb69 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -317,6 +317,7 @@ PClass *PClass::CreateDerivedClass (FName name, unsigned int size) info->DamageFactors = NULL; info->PainChances = NULL; info->ColorSets = NULL; + info->ConversationID = -1; m_RuntimeActors.Push (type); } return type; @@ -411,6 +412,7 @@ void PClass::InitializeActorInfo () info->DamageFactors = NULL; info->PainChances = NULL; info->ColorSets = NULL; + info->ConversationID = -1; m_RuntimeActors.Push (this); } diff --git a/src/farchive.h b/src/farchive.h index 9af7d25ba..533b3920e 100644 --- a/src/farchive.h +++ b/src/farchive.h @@ -290,6 +290,10 @@ template<> inline FArchive &operator<< (FArchive &arc, FFont* &font) return SerializeFFontPtr (arc, font); } +struct FStrifeDialogueNode; +template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node); + + template inline FArchive &operator<< (FArchive &arc, TArray &self) diff --git a/src/g_shared/a_action.cpp b/src/g_shared/a_action.cpp index 3697bcba8..2243d48a4 100644 --- a/src/g_shared/a_action.cpp +++ b/src/g_shared/a_action.cpp @@ -68,7 +68,7 @@ void A_Unblock(AActor *self, bool drop) self->flags &= ~MF_SOLID; - // If the self has a conversation that sets an item to drop, drop that. + // If the actor has a conversation that sets an item to drop, drop that. if (self->Conversation != NULL && self->Conversation->DropType != NULL) { P_DropItem (self, self->Conversation->DropType, -1, 256); diff --git a/src/info.h b/src/info.h index 9480f5f59..d7d939c0b 100644 --- a/src/info.h +++ b/src/info.h @@ -203,6 +203,7 @@ struct FActorInfo BYTE GameFilter; BYTE SpawnID; SWORD DoomEdNum; + int ConversationID; FStateLabels *StateList; DmgFactors *DamageFactors; PainChanceList *PainChances; diff --git a/src/namedef.h b/src/namedef.h index 94e0c97bd..b91781b86 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -443,3 +443,23 @@ xx(blockprojectiles) xx(blockuse) xx(Renderstyle) + +// USDF keywords +xx(Amount) +xx(Text) +xx(Displaycost) +xx(Yesmessage) +xx(Nomessage) +xx(Log) +xx(Giveitem) +xx(Nextpage) +xx(Closedialog) +xx(Cost) +xx(Page) +xx(Count) +xx(Name) +xx(Panel) +xx(Dialog) +xx(Ifitem) +xx(Choice) +xx(Link) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 8e32ee8ab..37739a43d 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -59,6 +59,8 @@ #include "doomstat.h" #include "c_console.h" #include "sbar.h" +#include "farchive.h" +#include "p_lnspec.h" // The conversations as they exist inside a SCRIPTxx lump. struct Response @@ -103,19 +105,20 @@ void GiveSpawner (player_t *player, const PClass *type); TArray StrifeDialogues; -// There were 344 types in Strife, and Strife conversations refer -// to their index in the mobjinfo table. This table indexes all -// the Strife actor types in the order Strife had them and is -// initialized as part of the actor's setup in infodefaults.cpp. -const PClass *StrifeTypes[1001]; +typedef TMap FStrifeTypeMap; // maps conversation IDs to actor classes +typedef TMap FDialogueIDMap; // maps dialogue IDs to dialogue array index (for ACS) +typedef TMap FDialogueMap; // maps actor class names to dialogue array index + +static FStrifeTypeMap StrifeTypes; +static FDialogueIDMap DialogueRoots; +static FDialogueMap ClassRoots; static menu_t ConversationMenu; static TArray ConversationItems; static int ConversationPauseTic; static bool ShowGold; -static void LoadScriptFile (const char *name); -static void LoadScriptFile(FileReader *lump, int numnodes); +static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type); static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeakerType); static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeakerType); static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses); @@ -139,13 +142,42 @@ static FBrokenLines *DialogueLines; // //============================================================================ -static const PClass *GetStrifeType (int typenum) +void SetStrifeType(int convid, const PClass *Class) { - if (typenum > 0 && typenum < 1001) + StrifeTypes[convid] = Class; +} + +void SetConversation(int convid, const PClass *Class, int dlgindex) +{ + if (convid != -1) { - return StrifeTypes[typenum]; + DialogueRoots[convid] = dlgindex; } - return NULL; + if (Class != NULL) + { + ClassRoots[Class->TypeName] = dlgindex; + } +} + +const PClass *GetStrifeType (int typenum) +{ + const PClass **ptype = StrifeTypes.CheckKey(typenum); + if (ptype == NULL) return NULL; + else return *ptype; +} + +int GetConversation(int conv_id) +{ + int *pindex = DialogueRoots.CheckKey(conv_id); + if (pindex == NULL) return -1; + else return *pindex; +} + +int GetConversation(FName classname) +{ + int *pindex = ClassRoots.CheckKey(classname); + if (pindex == NULL) return -1; + else return *pindex; } //============================================================================ @@ -158,11 +190,11 @@ static const PClass *GetStrifeType (int typenum) void P_LoadStrifeConversations (MapData *map, const char *mapname) { + P_FreeStrifeConversations (); if (map->Size(ML_CONVERSATION) > 0) { - LoadScriptFile ("SCRIPT00"); map->Seek(ML_CONVERSATION); - LoadScriptFile (map->file, map->Size(ML_CONVERSATION)); + LoadScriptFile (map->lumpnum, map->file, map->Size(ML_CONVERSATION), false, 0); } else { @@ -170,10 +202,13 @@ void P_LoadStrifeConversations (MapData *map, const char *mapname) { return; } - char scriptname[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],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 }; - LoadScriptFile ("SCRIPT00"); - LoadScriptFile (scriptname); + if (!LoadScriptFile(scriptname_t, false, 2)) + { + LoadScriptFile (scriptname_b, false, 1); + } } } @@ -192,36 +227,13 @@ void P_FreeStrifeConversations () delete node; } - for (int i = 0; i < 344; ++i) - { - if (StrifeTypes[i] != NULL) - { - AActor * ac = GetDefaultByType (StrifeTypes[i]); - if (ac != NULL) ac->Conversation = NULL; - } - } + DialogueRoots.Clear(); + ClassRoots.Clear(); CurNode = NULL; PrevNode = NULL; } -//============================================================================ -// -// ncopystring -// -// If the string has no content, returns NULL. Otherwise, returns a copy. -// -//============================================================================ - -static char *ncopystring (const char *string) -{ - if (string == NULL || string[0] == 0) - { - return NULL; - } - return copystring (string); -} - //============================================================================ // // LoadScriptFile @@ -230,61 +242,89 @@ static char *ncopystring (const char *string) // //============================================================================ -static void LoadScriptFile (const char *name) +bool LoadScriptFile (const char *name, bool include, int type) { int lumpnum = Wads.CheckNumForName (name); FileReader *lump; if (lumpnum < 0) { - return; + return false; } lump = Wads.ReopenLumpNum (lumpnum); - LoadScriptFile(lump, Wads.LumpLength(lumpnum)); + bool res = LoadScriptFile(lumpnum, lump, Wads.LumpLength(lumpnum), include, type); delete lump; + return res; } -static void LoadScriptFile(FileReader *lump, int numnodes) +static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type) { int i; DWORD prevSpeakerType; FStrifeDialogueNode *node; + char buffer[4]; - if (!(gameinfo.flags & GI_SHAREWARE)) + lump->Read(buffer, 4); + lump->Seek(0, SEEK_SET); + + // 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)) { - // Strife scripts are always a multiple of 1516 bytes because each entry - // is exactly 1516 bytes long. - if (numnodes % 1516 != 0) - { - return; - } - numnodes /= 1516; - } - else - { - // And the teaser version has 1488-byte entries. - if (numnodes % 1488 != 0) - { - return; - } - numnodes /= 1488; + DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum)); + return false; } - prevSpeakerType = 0; - - for (i = 0; i < numnodes; ++i) + if (!isbinary) { + P_ParseUSDF(lumpnum, lump, numnodes); + } + else + { + if (!include) + { + LoadScriptFile("SCRIPT00", true, 1); + } if (!(gameinfo.flags & GI_SHAREWARE)) { - node = ReadRetailNode (lump, prevSpeakerType); + // Strife scripts are always a multiple of 1516 bytes because each entry + // is exactly 1516 bytes long. + if (numnodes % 1516 != 0) + { + DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum)); + return false; + } + numnodes /= 1516; } else { - node = ReadTeaserNode (lump, prevSpeakerType); + // And the teaser version has 1488-byte entries. + if (numnodes % 1488 != 0) + { + DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum)); + return false; + } + numnodes /= 1488; + } + + prevSpeakerType = 0; + + for (i = 0; i < numnodes; ++i) + { + if (!(gameinfo.flags & GI_SHAREWARE)) + { + node = ReadRetailNode (lump, prevSpeakerType); + } + else + { + node = ReadTeaserNode (lump, prevSpeakerType); + } + node->ThisNodeNum = StrifeDialogues.Push(node); } - node->ThisNodeNum = StrifeDialogues.Push(node); } + return true; } //============================================================================ @@ -316,12 +356,14 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker // actor, so newly spawned actors will use this conversation by default. type = GetStrifeType (speech.SpeakerType); node->SpeakerType = type; - if (prevSpeakerType != speech.SpeakerType) + + if (speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType) { if (type != NULL) { - GetDefaultByType (type)->Conversation = node; + ClassRoots[type->TypeName] = StrifeDialogues.Size(); } + DialogueRoots[speech.SpeakerType] = StrifeDialogues.Size(); prevSpeakerType = speech.SpeakerType; } @@ -345,9 +387,11 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker 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] = GetStrifeType (speech.ItemCheck[j]); + node->ItemCheck[j].Item = GetStrifeType (speech.ItemCheck[j]); + node->ItemCheck[j].Amount = -1; } node->ItemCheckNode = speech.Link; node->Children = NULL; @@ -385,12 +429,14 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker // actor, so newly spawned actors will use this conversation by default. type = GetStrifeType (speech.SpeakerType); node->SpeakerType = type; - if (prevSpeakerType != speech.SpeakerType) + + if (speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType) { if (type != NULL) { - GetDefaultByType (type)->Conversation = node; + ClassRoots[type->TypeName] = StrifeDialogues.Size(); } + DialogueRoots[speech.SpeakerType] = StrifeDialogues.Size(); prevSpeakerType = speech.SpeakerType; } @@ -419,9 +465,11 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker 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] = NULL; + node->ItemCheck[j].Item = NULL; + node->ItemCheck[j].Amount = -1; } node->ItemCheckNode = 0; node->Children = NULL; @@ -476,15 +524,18 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) // The message to record in the log for this reply. reply->LogNumber = rsp->Log; + reply->LogString = NULL; // 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) { - reply->ItemCheck[k] = GetStrifeType (rsp->Item[k]); - reply->ItemCheckAmount[k] = rsp->Count[k]; + reply->ItemCheck[k].Item = GetStrifeType (rsp->Item[k]); + reply->ItemCheck[k].Amount = rsp->Count[k]; } // ReplyLines is calculated when the menu is shown. It is just Reply @@ -517,7 +568,7 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) { reply->QuickYes = ncopystring (rsp->Yes); } - if (reply->ItemCheck[0] != 0) + if (reply->ItemCheck[0].Item != 0) { reply->QuickNo = ncopystring (rsp->No); } @@ -713,13 +764,20 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang } // Check if we should jump to another node - while (CurNode->ItemCheck[0] != NULL) + while (CurNode->ItemCheck.Size() > 0 && CurNode->ItemCheck[0].Item != NULL) { - if (CheckStrifeItem (pc->player, CurNode->ItemCheck[0]) && - CheckStrifeItem (pc->player, CurNode->ItemCheck[1]) && - CheckStrifeItem (pc->player, CurNode->ItemCheck[2])) + bool jump = true; + for (i = 0; i < (int)CurNode->ItemCheck.Size(); ++i) { - int root = FindNode (pc->player->ConversationNPC->GetDefault()->Conversation); + if(!CheckStrifeItem (pc->player, CurNode->ItemCheck[i].Item, CurNode->ItemCheck[i].Amount)) + { + jump = false; + break; + } + } + if (jump) + { + int root = pc->player->ConversationNPC->ConversationRoot; CurNode = StrifeDialogues[root + CurNode->ItemCheckNode - 1]; } else @@ -1063,9 +1121,9 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply npc = player->ConversationNPC; // Check if you have the requisite items for this choice - for (i = 0; i < 3; ++i) + for (i = 0; i < (int)reply->ItemCheck.Size(); ++i) { - if (!CheckStrifeItem(player, reply->ItemCheck[i], reply->ItemCheckAmount[i])) + if (!CheckStrifeItem(player, reply->ItemCheck[i].Item, reply->ItemCheck[i].Amount)) { // No, you don't. Say so and let the NPC animate negatively. if (reply->QuickNo && isconsole) @@ -1132,12 +1190,18 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply } } + if (reply->ActionSpecial != 0) + { + takestuff |= !!LineSpecials[reply->ActionSpecial](NULL, player->mo, false, + reply->Args[0], reply->Args[1], reply->Args[2], reply->Args[3], reply->Args[4]); + } + // Take away required items if the give was successful or none was needed. if (takestuff) { - for (i = 0; i < 3; ++i) + for (i = 0; i < (int)reply->ItemCheck.Size(); ++i) { - TakeStrifeItem (player, reply->ItemCheck[i], reply->ItemCheckAmount[i]); + TakeStrifeItem (player, reply->ItemCheck[i].Item, reply->ItemCheck[i].Amount); } replyText = reply->QuickYes; } @@ -1147,7 +1211,11 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply } // Update the quest log, if needed. - if (reply->LogNumber != 0) + if (reply->LogString != NULL) + { + player->SetLogText(reply->LogString); + } + else if (reply->LogNumber != 0) { player->SetLogNumber(reply->LogNumber); } @@ -1162,7 +1230,7 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply // will show the new node right away without terminating the dialogue. if (reply->NextNode != 0) { - int rootnode = FindNode (npc->GetDefault()->Conversation); + int rootnode = npc->ConversationRoot; if (reply->NextNode < 0) { npc->Conversation = StrifeDialogues[rootnode - reply->NextNode - 1]; @@ -1316,3 +1384,26 @@ static void TerminalResponse (const char *str) } } } + + +template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node) +{ + DWORD convnum; + if (arc.IsStoring()) + { + arc.WriteCount (node == NULL? ~0u : node->ThisNodeNum); + } + else + { + convnum = arc.ReadCount(); + if (convnum >= StrifeDialogues.Size()) + { + node = NULL; + } + else + { + node = StrifeDialogues[convnum]; + } + } + return arc; +} diff --git a/src/p_conversation.h b/src/p_conversation.h index 13658e0f0..61c0ef8f4 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -1,22 +1,24 @@ #ifndef P_CONVERSATION_H #define P_CONVERSATION_H 1 -// TODO: Generalize the conversation system to something NWN-like that -// users can edit as simple text files. Particularly useful would be -// the ability to call ACS functions to implement AppearsWhen properties -// and ACS scripts to implement ActionTaken properties. -// TODO: Make this work in demos. +#include struct FStrifeDialogueReply; class FTexture; struct FBrokenLines; +struct FStrifeDialogueItemCheck +{ + const PClass *Item; + int Amount; +}; + // FStrifeDialogueNode holds text an NPC says to the player struct FStrifeDialogueNode { ~FStrifeDialogueNode (); const PClass *DropType; - const PClass *ItemCheck[3]; + TArray ItemCheck; int ThisNodeNum; // location of this node in StrifeDialogues int ItemCheckNode; // index into StrifeDialogues @@ -36,12 +38,14 @@ struct FStrifeDialogueReply FStrifeDialogueReply *Next; const PClass *GiveType; - const PClass *ItemCheck[3]; - int ItemCheckAmount[3]; + int ActionSpecial; + int Args[5]; + TArray ItemCheck; char *Reply; char *QuickYes; int NextNode; // index into StrifeDialogues int LogNumber; + char *LogString; char *QuickNo; bool NeedsGold; @@ -50,14 +54,16 @@ struct FStrifeDialogueReply extern TArray StrifeDialogues; -// There were 344 types in Strife, and Strife conversations refer -// to their index in the mobjinfo table. This table indexes all -// the Strife actor types in the order Strife had them and is -// initialized as part of the actor's setup in infodefaults.cpp. -extern const PClass *StrifeTypes[1001]; - struct MapData; +void SetStrifeType(int convid, const PClass *Class); +void SetConversation(int convid, const PClass *Class, int dlgindex); +const PClass *GetStrifeType (int typenum); +int GetConversation(int conv_id); +int GetConversation(FName classname); + +bool LoadScriptFile (const char *name, bool include, int type = 0); + void P_LoadStrifeConversations (MapData *map, const char *mapname); void P_FreeStrifeConversations (); @@ -66,4 +72,8 @@ void P_ResumeConversation (); void P_ConversationCommand (int netcode, int player, BYTE **stream); +class FileReader; +bool P_ParseUSDF(int lumpnum, FileReader *lump, int lumplen); + + #endif diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 72bea5c49..a53356a8e 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -3006,6 +3006,36 @@ FUNC(LS_StartConversation) return false; } +FUNC(LS_Thing_SetConversation) +// Thing_SetConversation (tid, dlg_id) +{ + int dlg_index = -1; + FStrifeDialogueNode *node = NULL; + + if (arg1 != 0) + { + dlg_index = GetConversation(arg1); + if (dlg_index == -1) return false; + node = StrifeDialogues[dlg_index]; + } + + if (arg0 != 0) + { + FActorIterator iterator (arg0); + while ((it = iterator.Next()) != NULL) + { + it->ConversationRoot = dlg_index; + it->Conversation = node; + } + } + else if (it) + { + it->ConversationRoot = dlg_index; + it->Conversation = node; + } + return true; +} + lnSpecFunc LineSpecials[256] = { @@ -3088,7 +3118,7 @@ lnSpecFunc LineSpecials[256] = /* 76 */ LS_TeleportOther, /* 77 */ LS_TeleportGroup, /* 78 */ LS_TeleportInSector, - /* 79 */ LS_NOP, + /* 79 */ LS_Thing_SetConversation, /* 80 */ LS_ACS_Execute, /* 81 */ LS_ACS_Suspend, /* 82 */ LS_ACS_Terminate, diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index f52067ba2..7b7e25888 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -361,65 +361,24 @@ void AActor::Serialize (FArchive &arc) arc << foo; } - if (arc.IsStoring ()) + if (SaveVersion > 2500) // adjust after merging into trunk! { - int convnum = 0; - unsigned int i; - - if (Conversation != NULL) - { - for (i = 0; i < StrifeDialogues.Size(); ++i) - { - if (StrifeDialogues[i] == GetDefault()->Conversation) - { - break; - } - } - for (; i + convnum < StrifeDialogues.Size(); ++convnum) - { - if (StrifeDialogues[i + convnum] == Conversation) - { - break; - } - } - if (i + convnum < StrifeDialogues.Size()) - { - convnum++; - } - else - { - convnum = 0; - } - } - arc.WriteCount (convnum); + arc << ConversationRoot << Conversation; } - else + else // old code which uses relative indexing. { int convnum; - unsigned int i; convnum = arc.ReadCount(); - if (convnum == 0 || GetDefault()->Conversation == NULL) + if (convnum == 0) { Conversation = NULL; + ConversationRoot = -1; } else { - for (i = 0; i < StrifeDialogues.Size(); ++i) - { - if (StrifeDialogues[i] == GetDefault()->Conversation) - { - break; - } - } - if (i + convnum <= StrifeDialogues.Size()) - { - Conversation = StrifeDialogues[i + convnum - 1]; - } - else - { - Conversation = GetDefault()->Conversation; - } + // This cannot be restored anymore. + I_Error("Cannot load old savegames with active dialogues"); } } @@ -3482,6 +3441,7 @@ bool AActor::UpdateWaterLevel (fixed_t oldz, bool dosplash) return false; // we did the splash ourselves } + //========================================================================== // // P_SpawnMobj @@ -3508,6 +3468,17 @@ AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t actor = static_cast(const_cast(type)->CreateNew ()); + // Set default dialogue + actor->ConversationRoot = GetConversation(actor->GetClass()->TypeName); + if (actor->ConversationRoot != -1) + { + actor->Conversation = StrifeDialogues[actor->ConversationRoot]; + } + else + { + actor->Conversation = NULL; + } + actor->x = actor->PrevX = ix; actor->y = actor->PrevY = iy; actor->z = actor->PrevZ = iz; diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index c057c253c..ef7f991ff 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -35,7 +35,6 @@ #include "r_data.h" #include "p_setup.h" -#include "sc_man.h" #include "p_lnspec.h" #include "templates.h" #include "i_system.h" @@ -43,6 +42,7 @@ #include "r_sky.h" #include "g_level.h" #include "v_palette.h" +#include "p_udmf.h" //=========================================================================== // @@ -124,6 +124,152 @@ extern TArray linemap; #define CHECK_N(f) if (!(namespace_bits&(f))) break; +//=========================================================================== +// +// Common parsing routines +// +//=========================================================================== + +//=========================================================================== +// +// Skip a key or block +// +//=========================================================================== + +void UDMFParserBase::Skip() +{ + if (developer) sc.ScriptMessage("Ignoring unknown key \"%s\".", sc.String); + if(sc.CheckToken('{')) + { + int level = 1; + while(sc.GetToken()) + { + if (sc.TokenType == '}') + { + level--; + if(level == 0) + { + sc.UnGet(); + break; + } + } + else if (sc.TokenType == '{') + { + level++; + } + } + } + else + { + sc.MustGetToken('='); + do + { + sc.MustGetAnyToken(); + } + while(sc.TokenType != ';'); + } +} + +//=========================================================================== +// +// Parses a 'key = value' line of the map +// +//=========================================================================== + +FName UDMFParserBase::ParseKey(bool checkblock, bool *isblock) +{ + sc.MustGetString(); + FName key = sc.String; + if (checkblock) + { + if (sc.CheckToken('{')) + { + if (isblock) *isblock = true; + return key; + } + else if (isblock) *isblock = false; + } + sc.MustGetToken('='); + + sc.Number = 0; + sc.Float = 0; + sc.MustGetAnyToken(); + + if (sc.TokenType == '+' || sc.TokenType == '-') + { + bool neg = (sc.TokenType == '-'); + sc.MustGetAnyToken(); + if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) + { + sc.ScriptMessage("Numeric constant expected"); + } + if (neg) + { + sc.Number = -sc.Number; + sc.Float = -sc.Float; + } + } + if (sc.TokenType == TK_StringConst) + { + parsedString = sc.String; + } + int savedtoken = sc.TokenType; + sc.MustGetToken(';'); + sc.TokenType = savedtoken; + return key; +} + +//=========================================================================== +// +// Syntax checks +// +//=========================================================================== + +int UDMFParserBase::CheckInt(const char *key) +{ + if (sc.TokenType != TK_IntConst) + { + sc.ScriptMessage("Integer value expected for key '%s'", key); + } + return sc.Number; +} + +double UDMFParserBase::CheckFloat(const char *key) +{ + if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) + { + sc.ScriptMessage("Floating point value expected for key '%s'", key); + } + return sc.Float; +} + +fixed_t UDMFParserBase::CheckFixed(const char *key) +{ + return FLOAT2FIXED(CheckFloat(key)); +} + +angle_t UDMFParserBase::CheckAngle(const char *key) +{ + return angle_t(CheckFloat(key) * ANGLE_90 / 90.); +} + +bool UDMFParserBase::CheckBool(const char *key) +{ + if (sc.TokenType == TK_True) return true; + if (sc.TokenType == TK_False) return false; + sc.ScriptMessage("Boolean value expected for key '%s'", key); + return false; +} + +const char *UDMFParserBase::CheckString(const char *key) +{ + if (sc.TokenType != TK_StringConst) + { + sc.ScriptMessage("String value expected for key '%s'", key); + } + return parsedString; +} + //=========================================================================== // // Storage of UDMF user properties @@ -233,15 +379,11 @@ fixed_t GetUDMFFixed(int type, int index, const char *key) // //=========================================================================== -struct UDMFParser +class UDMFParser : public UDMFParserBase { - FScanner sc; - FName namespc; - int namespace_bits; bool isTranslated; bool isExtended; bool floordrop; - FString parsedString; TArray ParsedLines; TArray ParsedSides; @@ -251,113 +393,13 @@ struct UDMFParser FDynamicColormap *fogMap, *normMap; +public: UDMFParser() { linemap.Clear(); fogMap = normMap = NULL; } - //=========================================================================== - // - // Parses a 'key = value' line of the map - // - //=========================================================================== - - FName ParseKey() - { - sc.MustGetString(); - FName key = sc.String; - sc.MustGetToken('='); - - sc.Number = 0; - sc.Float = 0; - sc.MustGetAnyToken(); - - if (sc.TokenType == '+' || sc.TokenType == '-') - { - bool neg = (sc.TokenType == '-'); - sc.MustGetAnyToken(); - if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) - { - sc.ScriptMessage("Numeric constant expected"); - } - if (neg) - { - sc.Number = -sc.Number; - sc.Float = -sc.Float; - } - } - if (sc.TokenType == TK_StringConst) - { - parsedString = sc.String; - } - int savedtoken = sc.TokenType; - sc.MustGetToken(';'); - sc.TokenType = savedtoken; - return key; - } - - //=========================================================================== - // - // Syntax checks - // - //=========================================================================== - - int CheckInt(const char *key) - { - if (sc.TokenType != TK_IntConst) - { - sc.ScriptMessage("Integer value expected for key '%s'", key); - } - return sc.Number; - } - - double CheckFloat(const char *key) - { - if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) - { - sc.ScriptMessage("Floating point value expected for key '%s'", key); - } - return sc.Float; - } - - fixed_t CheckFixed(const char *key) - { - return FLOAT2FIXED(CheckFloat(key)); - } - - angle_t CheckAngle(const char *key) - { - return angle_t(CheckFloat(key) * ANGLE_90 / 90.); - } - - bool CheckBool(const char *key) - { - if (sc.TokenType == TK_True) return true; - if (sc.TokenType == TK_False) return false; - sc.ScriptMessage("Boolean value expected for key '%s'", key); - return false; - } - - const char *CheckString(const char *key) - { - if (sc.TokenType != TK_StringConst) - { - sc.ScriptMessage("String value expected for key '%s'", key); - } - return parsedString; - } - - template - void Flag(T &value, int mask, const char *key) - { - if (CheckBool(key)) - value |= mask; - else - value &= ~mask; - } - - void AddUserKey(FName key, int kind, int index) { FUDMFKeys &keyarray = UDMFKeys[kind][index]; @@ -1457,6 +1499,10 @@ struct UDMFParser ParseVertex(&vt); ParsedVertices.Push(vt); } + else + { + Skip(); + } } // Create the real vertices diff --git a/src/p_udmf.h b/src/p_udmf.h new file mode 100644 index 000000000..0fc2f5564 --- /dev/null +++ b/src/p_udmf.h @@ -0,0 +1,38 @@ +#ifndef __P_UDMF_H +#define __P_UDMF_H + +#include "sc_man.h" +#include "m_fixed.h" +#include "tables.h" + +class UDMFParserBase +{ +protected: + FScanner sc; + FName namespc; + int namespace_bits; + FString parsedString; + + void Skip(); + FName ParseKey(bool checkblock = false, bool *isblock = NULL); + int CheckInt(const char *key); + double CheckFloat(const char *key); + fixed_t CheckFixed(const char *key); + angle_t CheckAngle(const char *key); + bool CheckBool(const char *key); + const char *CheckString(const char *key); + + template + void Flag(T &value, int mask, const char *key) + { + if (CheckBool(key)) + value |= mask; + else + value &= ~mask; + } + +}; + +#define BLOCK_ID (ENamedName)-1 + +#endif \ No newline at end of file diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp new file mode 100644 index 000000000..21bd8eeb3 --- /dev/null +++ b/src/p_usdf.cpp @@ -0,0 +1,505 @@ +// +// p_usdf.cpp +// +// USDF dialogue parser +// +//--------------------------------------------------------------------------- +// Copyright (c) 2010 +// Braden "Blzut3" Obrzut +// 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: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 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 "r_data.h" +#include "p_setup.h" +#include "p_lnspec.h" +#include "templates.h" +#include "i_system.h" +#include "p_conversation.h" +#include "p_udmf.h" +#include "doomerrors.h" + +#define Zd 1 +#define St 2 + +class USDFParser : public UDMFParserBase +{ + //=========================================================================== + // + // Checks an actor type (different representation depending on manespace) + // + //=========================================================================== + + const PClass *CheckActorType(const char *key) + { + if (namespace_bits == St) + { + return GetStrifeType(CheckInt(key)); + } + else if (namespace_bits == Zd) + { + const PClass *cls = PClass::FindClass(CheckString(key)); + if (cls == NULL) + { + sc.ScriptMessage("Unknown actor class '%s'", key); + return NULL; + } + if (!cls->IsDescendantOf(RUNTIME_CLASS(AActor))) + { + sc.ScriptMessage("'%s' is not an actor type", key); + return NULL; + } + } + return NULL; + } + + //=========================================================================== + // + // Parse a cost block + // + //=========================================================================== + + bool ParseCost(FStrifeDialogueReply *response) + { + FStrifeDialogueItemCheck check; + check.Item = NULL; + check.Amount = -1; + + while (!sc.CheckToken('}')) + { + FName key = ParseKey(); + switch(key) + { + case NAME_Item: + check.Item = CheckActorType(key); + break; + + case NAME_Amount: + check.Amount = CheckInt(key); + break; + } + } + + response->ItemCheck.Push(check); + return true; + } + + //=========================================================================== + // + // Parse a choice block + // + //=========================================================================== + + bool ParseChoice(FStrifeDialogueReply **&replyptr) + { + FStrifeDialogueReply *reply = new FStrifeDialogueReply; + memset(reply, 0, sizeof(*reply)); + + reply->Next = *replyptr; + *replyptr = reply; + replyptr = &reply->Next; + + FString ReplyString; + FString QuickYes; + FString QuickNo; + FString LogString; + bool closeDialog = false; + + + reply->NeedsGold = false; + while (!sc.CheckToken('}')) + { + bool block = false; + int costs = 0; + FName key = ParseKey(true, &block); + if (!block) + { + switch(key) + { + case NAME_Text: + ReplyString = CheckString(key); + break; + + case NAME_Displaycost: + reply->NeedsGold = CheckBool(key); + break; + + case NAME_Yesmessage: + QuickYes = CheckString(key); + //if (!QuickYes.Compare("_")) QuickYes = ""; + break; + + case NAME_Nomessage: + QuickNo = CheckString(key); + break; + + case NAME_Log: + if (namespace_bits == St) + { + const char *s = CheckString(key); + if(strlen(s) < 4 || strnicmp(s, "LOG", 3) != 0) + { + sc.ScriptMessage("Log must be in the format of LOG# to compile, ignoring."); + } + else + { + reply->LogNumber = atoi(s + 3); + } + } + else + { + LogString = CheckString(key); + } + break; + + case NAME_Giveitem: + reply->GiveType = CheckActorType(key); + break; + + case NAME_Nextpage: + reply->NextNode = CheckInt(key); + break; + + case NAME_Closedialog: + closeDialog = CheckBool(key); + break; + + case NAME_Special: + reply->ActionSpecial = CheckInt(key); + if (reply->ActionSpecial < 0 || reply->ActionSpecial > 255) + reply->ActionSpecial = 0; + break; + + case NAME_Arg0: + case NAME_Arg1: + case NAME_Arg2: + case NAME_Arg3: + case NAME_Arg4: + reply->Args[int(key)-int(NAME_Arg0)] = CheckInt(key); + break; + + + } + } + else + { + switch(key) + { + case NAME_Cost: + ParseCost(reply); + break; + + default: + sc.UnGet(); + Skip(); + } + } + } + // Todo: Finalize + if (reply->ItemCheck.Size() > 0) + { + if (reply->ItemCheck[0].Amount <= 0) reply->NeedsGold = false; + if (reply->NeedsGold) ReplyString.AppendFormat(" for %u", reply->ItemCheck[0].Amount); + } + + reply->Reply = ncopystring(ReplyString); + reply->QuickYes = ncopystring(QuickYes); + if (reply->ItemCheck.Size() > 0 && reply->ItemCheck[0].Item != NULL) + { + reply->QuickNo = ncopystring(QuickNo); + } + else + { + reply->QuickNo = NULL; + } + reply->LogString = ncopystring(LogString); + if(!closeDialog) reply->NextNode *= -1; + return true; + } + + //=========================================================================== + // + // Parse an ifitem block + // + //=========================================================================== + + bool ParseIfItem(FStrifeDialogueNode *node) + { + FStrifeDialogueItemCheck check; + check.Item = NULL; + check.Amount = -1; + + while (!sc.CheckToken('}')) + { + FName key = ParseKey(); + switch(key) + { + case NAME_Item: + check.Item = CheckActorType(key); + break; + + case NAME_Count: + // Not yet implemented in the engine. Todo later + check.Amount = CheckInt(key); + break; + } + } + + node->ItemCheck.Push(check); + return true; + } + + //=========================================================================== + // + // Parse a page block + // + //=========================================================================== + + bool ParsePage() + { + FStrifeDialogueNode *node = new FStrifeDialogueNode; + FStrifeDialogueReply **replyptr = &node->Children; + memset(node, 0, sizeof(*node)); + //node->ItemCheckCount[0] = node->ItemCheckCount[1] = node->ItemCheckCount[2] = -1; + + node->ThisNodeNum = StrifeDialogues.Push(node); + + FString SpeakerName; + FString Dialogue; + + while (!sc.CheckToken('}')) + { + bool block = false; + FName key = ParseKey(true, &block); + if (!block) + { + switch(key) + { + case NAME_Name: + SpeakerName = CheckString(key); + break; + + case NAME_Panel: + node->Backdrop = TexMan.CheckForTexture (CheckString(key), FTexture::TEX_MiscPatch); + break; + + case NAME_Voice: + { + FString soundname = (namespace_bits == St? "svox/" : ""); + const char * name = CheckString(key); + if (name[0] != 0) + { + soundname += name; + node->SpeakerVoice = FSoundID(S_FindSound(soundname)); + } + } + break; + + case NAME_Dialog: + Dialogue = CheckString(key); + break; + + case NAME_Drop: + node->DropType = CheckActorType(key); + break; + + case NAME_Link: + node->ItemCheckNode = CheckInt(key); + break; + + + } + } + else + { + switch(key) + { + case NAME_Ifitem: + if (!ParseIfItem(node)) return false; + break; + + case NAME_Choice: + if (!ParseChoice(replyptr)) return false; + break; + + default: + sc.UnGet(); + Skip(); + } + } + } + node->SpeakerName = ncopystring(SpeakerName); + node->Dialogue = ncopystring(Dialogue); + return true; + } + + + //=========================================================================== + // + // Parse a conversation block + // + //=========================================================================== + + bool ParseConversation() + { + const PClass *type = NULL; + int dlgid = -1; + unsigned int startpos = StrifeDialogues.Size(); + + while (!sc.CheckToken('}')) + { + bool block = false; + FName key = ParseKey(true, &block); + if (!block) + { + switch(key) + { + case NAME_Actor: + type = CheckActorType(key); + if (namespace_bits == St) + { + dlgid = CheckInt(key); + } + break; + + case NAME_Id: + if (namespace_bits == Zd) + { + dlgid = CheckInt(key); + } + break; + } + } + else + { + switch(key) + { + case NAME_Page: + if (!ParsePage()) return false; + break; + + default: + sc.UnGet(); + Skip(); + } + } + } + if (type == NULL) + { + sc.ScriptMessage("No valid actor type defined in conversation."); + return false; + } + SetConversation(dlgid, type, startpos); + for(;startpos < StrifeDialogues.Size(); startpos++) + { + StrifeDialogues[startpos]->SpeakerType = type; + } + return true; + } + + //=========================================================================== + // + // Parse an USDF lump + // + //=========================================================================== + +public: + bool Parse(int lumpnum, FileReader *lump, int lumplen) + { + char *buffer = new char[lumplen]; + lump->Read(buffer, lumplen); + sc.OpenMem(Wads.GetLumpFullName(lumpnum), buffer, lumplen); + delete [] buffer; + sc.SetCMode(true); + // Namespace must be the first field because everything else depends on it. + if (sc.CheckString("namespace")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_StringConst); + namespc = sc.String; + switch(namespc) + { + case NAME_ZDoom: + namespace_bits = Zd; + break; + case NAME_Strife: + namespace_bits = St; + break; + default: + sc.ScriptMessage("Unknown namespace %s. Ignoring dialogue lump.\n", sc.String); + return false; + } + sc.MustGetToken(';'); + } + else + { + sc.ScriptMessage("Map does not define a namespace.\n"); + return false; + } + + while (sc.GetString()) + { + if (sc.Compare("conversation")) + { + sc.MustGetToken('{'); + if (!ParseConversation()) return false; + } + else if (sc.Compare("include")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_StringConst); + LoadScriptFile(sc.String, true); + sc.MustGetToken(';'); + } + else + { + Skip(); + } + } + return true; + } +}; + + + +bool P_ParseUSDF(int lumpnum, FileReader *lump, int lumplen) +{ + USDFParser parse; + + try + { + if (!parse.Parse(lumpnum, lump, lumplen)) + { + // clean up the incomplete dialogue structures here + return false; + } + return true; + } + catch(CRecoverableError &err) + { + Printf("%s", err.GetMessage()); + return false; + } +} diff --git a/src/p_user.cpp b/src/p_user.cpp index 65909b3d3..cf80d3819 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -376,17 +376,17 @@ void player_t::SetLogNumber (int num) data[length]=0; SetLogText (data); delete[] data; - - // Print log text to console - AddToConsole(-1, TEXTCOLOR_GOLD); - AddToConsole(-1, LogText); - AddToConsole(-1, "\n"); } } void player_t::SetLogText (const char *text) { LogText = text; + + // Print log text to console + AddToConsole(-1, TEXTCOLOR_GOLD); + AddToConsole(-1, LogText); + AddToConsole(-1, "\n"); } int player_t::GetSpawnClass() diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index ffe287035..1e8f51f97 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -255,13 +255,11 @@ DEFINE_INFO_PROPERTY(conversationid, IiI, Actor) if ((gameinfo.flags & (GI_SHAREWARE|GI_TEASER2)) == (GI_SHAREWARE|GI_TEASER2)) convid=id2; - if (convid==-1) return; } - if (convid<0 || convid>1000) - { - I_Error ("ConversationID must be in the range [0,1000]"); - } - else StrifeTypes[convid] = info->Class; + + bag.Info->ConversationID = convid; + if (convid <= 0) return; // 0 is not usable because the dialogue scripts use it as 'no object'. + SetStrifeType(convid, info->Class); } //========================================================================== diff --git a/wadsrc/static/actors/strife/strifestuff.txt b/wadsrc/static/actors/strife/strifestuff.txt index d8ecd9bbb..a80685c35 100644 --- a/wadsrc/static/actors/strife/strifestuff.txt +++ b/wadsrc/static/actors/strife/strifestuff.txt @@ -1662,7 +1662,6 @@ ACTOR TargetPractice 208 ACTOR ForceFieldGuard 25 native { Game Strife - ConversationID 0 Health 10 Radius 2 Height 1 diff --git a/zdoom.vcproj b/zdoom.vcproj index 7885a5174..21247821f 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -900,6 +900,10 @@ RelativePath=".\src\p_udmf.cpp" > + +