diff --git a/specs/usdf_gzdoom.txt b/specs/usdf_gzdoom.txt new file mode 100644 index 000000000..6c71d3b9c --- /dev/null +++ b/specs/usdf_gzdoom.txt @@ -0,0 +1,63 @@ +=============================================================================== +GZDoom Strife Dialog Format v1.0 +based on ZDoom Strife Dialog Format ZDoom v1.1 - 23.08.2010 + + Copyright (c) 2019 Rachael Alexanderson + uses ZDoom Strife Dialog Format ZDoom v1.1 as a template, + original document 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. Changes to ZSDF spec +======================================= + + GZDoom Strife Dialogue format implements the ZSDF base specification as described with + the improvement of being able to name pages using strings. + + GZDoom-format dialogues need to start with this line: + + namespace = "GZDoom"; + + +--------------------- +III.A : Conversations +--------------------- + + This block only lists the newly added fields. Currently GZDoom only modifies the following + fields and adds the "pagename" field to the specification: + + conversation // Starts a dialog. + { + page + { + pagename = ; // names the current page, for linking using links or responses + link = ; // if int, uses the old system of linking page by number + // if string, will parse item links to a named page + + choice + { + next = ; // same as link above, can either go to a name or id + } + } + } + +=============================================================================== +EOF +=============================================================================== diff --git a/src/namedef.h b/src/namedef.h index e87cc0399..9ba71ab74 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -563,6 +563,7 @@ xx(Monsterpush) xx(ZDoom) xx(ZDoomTranslated) xx(Vavoom) +xx(GZDoom) xx(Xpanningfloor) xx(Ypanningfloor) @@ -731,6 +732,7 @@ xx(Require) xx(Exclude) xx(Userstring) xx(Sky) +xx(Pagename) // Special menus xx(Mainmenu) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 02fef0a4f..22ac48b3d 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -569,6 +569,11 @@ static void ParseReplies (const char *name, int pos, FStrifeDialogueReply **repl // 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; @@ -1124,14 +1129,13 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply if (reply->NextNode != 0) { int rootnode = npc->ConversationRoot; - const bool isNegative = reply->NextNode < 0; - const unsigned next = (unsigned)(rootnode + (isNegative ? -1 : 1) * reply->NextNode - 1); + const unsigned next = (unsigned)(rootnode + reply->NextNode - 1); if (next < StrifeDialogues.Size()) { npc->Conversation = StrifeDialogues[next]; - if (isNegative) + if (!(reply->CloseDialog)) { if (gameaction != ga_slideshow) { diff --git a/src/p_conversation.h b/src/p_conversation.h index 2a516e06e..ad1465841 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -24,6 +24,8 @@ struct FStrifeDialogueNode TArray ItemCheck; int ThisNodeNum = 0; // location of this node in StrifeDialogues int ItemCheckNode = 0; // index into StrifeDialogues + FString ThisNodeName = nullptr; + FString ItemCheckNodeName = nullptr; PClassActor *SpeakerType = nullptr; FString SpeakerName; @@ -54,7 +56,9 @@ struct FStrifeDialogueReply FString LogString; int NextNode = 0; // index into StrifeDialogues int LogNumber = 0; + FString NextNodeName = nullptr; bool NeedsGold = false; + bool CloseDialog = true; }; extern TArray StrifeDialogues; diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp index 9774371e5..bbd64fdd4 100644 --- a/src/p_usdf.cpp +++ b/src/p_usdf.cpp @@ -46,6 +46,7 @@ #define Zd 1 #define St 2 +#define Gz 4 class USDFParser : public UDMFParserBase { @@ -62,7 +63,7 @@ class USDFParser : public UDMFParserBase { type = GetStrifeType(CheckInt(key)); } - else if (namespace_bits == Zd) + else if (namespace_bits & ( Zd | Gz )) { PClassActor *cls = PClass::FindActor(CheckString(key)); if (cls == nullptr) @@ -188,7 +189,10 @@ class USDFParser : public UDMFParserBase break; case NAME_Nextpage: - reply->NextNode = CheckInt(key); + if (namespace_bits != Gz || sc.TokenType == TK_IntConst) + reply->NextNode = CheckInt(key); + else + reply->NextNodeName = CheckString(key); break; case NAME_Closedialog: @@ -202,7 +206,7 @@ class USDFParser : public UDMFParserBase break; case NAME_SpecialName: - if (namespace_bits == Zd) + if (namespace_bits & ( Zd | Gz )) reply->ActionSpecial = P_FindLineSpecial(CheckString(key)); break; @@ -225,7 +229,7 @@ class USDFParser : public UDMFParserBase case NAME_Require: case NAME_Exclude: // Require and Exclude are exclusive to namespace ZDoom. [FishyClockwork] - if (key == NAME_Cost || namespace_bits == Zd) + if (key == NAME_Cost || (namespace_bits & ( Zd | Gz ))) { ParseCostRequireExclude(reply, key); break; @@ -256,7 +260,15 @@ class USDFParser : public UDMFParserBase reply->QuickNo = ""; } reply->LogString = LogString; - if(!closeDialog) reply->NextNode *= -1; + if (reply->NextNode < 0) // compatibility: handle negative numbers + { + reply->CloseDialog = !closeDialog; + reply->NextNode *= -1; + } + else + { + reply->CloseDialog = closeDialog; + } return true; } @@ -318,6 +330,13 @@ class USDFParser : public UDMFParserBase { switch(key) { + case NAME_Pagename: + if (namespace_bits != Gz) + sc.ScriptMessage("'PageName' keyword only supported in the GZDoom namespace!"); + else + node->ThisNodeName = CheckString(key); + break; + case NAME_Name: SpeakerName = CheckString(key); break; @@ -327,7 +346,7 @@ class USDFParser : public UDMFParserBase break; case NAME_Userstring: - if (namespace_bits == Zd) + if (namespace_bits & ( Zd | Gz )) { node->UserData = CheckString(key); } @@ -341,7 +360,7 @@ class USDFParser : public UDMFParserBase FString soundname = "svox/"; soundname += name; node->SpeakerVoice = FSoundID(S_FindSound(soundname)); - if (node->SpeakerVoice == 0 && namespace_bits == Zd) + if (node->SpeakerVoice == 0 && (namespace_bits & ( Zd | Gz ))) { node->SpeakerVoice = FSoundID(S_FindSound(name)); } @@ -358,12 +377,15 @@ class USDFParser : public UDMFParserBase break; case NAME_Link: - node->ItemCheckNode = CheckInt(key); + if (namespace_bits != Gz || sc.TokenType == TK_IntConst) + node->ItemCheckNode = CheckInt(key); + else + node->ItemCheckNodeName = CheckString(key); break; case NAME_Goodbye: // Custom goodbyes are exclusive to namespace ZDoom. [FishyClockwork] - if (namespace_bits == Zd) + if (namespace_bits & ( Zd | Gz )) { Goodbye = CheckString(key); } @@ -425,14 +447,14 @@ class USDFParser : public UDMFParserBase break; case NAME_Id: - if (namespace_bits == Zd) + if (namespace_bits & ( Zd | Gz )) { dlgid = CheckInt(key); } break; case NAME_Class: - if (namespace_bits == Zd) + if (namespace_bits & ( Zd | Gz )) { clsid = CheckString(key); } @@ -486,6 +508,9 @@ public: namespc = sc.String; switch(namespc) { + case NAME_GZDoom: + namespace_bits = Gz; + break; case NAME_ZDoom: namespace_bits = Zd; break; @@ -500,7 +525,7 @@ public: } else { - sc.ScriptMessage("Map does not define a namespace.\n"); + sc.ScriptMessage("Dialog script does not define a namespace.\n"); return false; } @@ -523,6 +548,58 @@ public: Skip(); } } + + if (namespace_bits == Gz) // string page name linker + { + int numnodes = StrifeDialogues.Size(); + int usedstrings = false; + + TMap nameToIndex; + for (int i = 0; i < numnodes; i++) + { + FString key = StrifeDialogues[i]->ThisNodeName; + if (key.IsNotEmpty()) + { + key.ToLower(); + if (nameToIndex.CheckKey(key)) + Printf("Warning! Duplicate page name '%s'!\n", StrifeDialogues[i]->ThisNodeName.GetChars()); + else + nameToIndex[key] = i; + usedstrings = true; + } + } + if (usedstrings) + { + for (int i = 0; i < numnodes; i++) + { + FString itemLinkKey = StrifeDialogues[i]->ItemCheckNodeName; + if (itemLinkKey.IsNotEmpty()) + { + itemLinkKey.ToLower(); + if (nameToIndex.CheckKey(itemLinkKey)) + StrifeDialogues[i]->ItemCheckNode = nameToIndex[itemLinkKey] + 1; + else + Printf("Warning! Reference to non-existent item-linked dialogue page name '%s' in page %i!\n", StrifeDialogues[i]->ItemCheckNodeName.GetChars(), i); + } + + FStrifeDialogueReply *NodeCheck = StrifeDialogues[i]->Children; + while (NodeCheck) + { + if (NodeCheck->NextNodeName.IsNotEmpty()) + { + FString key = NodeCheck->NextNodeName; + key.ToLower(); + if (nameToIndex.CheckKey(key)) + NodeCheck->NextNode = nameToIndex[key] + 1; + else + Printf("Warning! Reference to non-existent reply-linked dialogue page name '%s' in page %i!\n", NodeCheck->NextNodeName.GetChars(), i); + } + NodeCheck = NodeCheck->Next; + } + } + } + + } return true; } };