- merge USDF branch into trunk.

- add USDF spexs.


SVN r2561 (trunk)
This commit is contained in:
Christoph Oelckers 2010-08-20 12:20:51 +00:00
parent 6f82db47b8
commit b452bec0ee
23 changed files with 1227 additions and 274 deletions

158
specs/usdf.txt Normal file
View File

@ -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 = <integer>; // 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 = <string>; // Name that goes in the upper left hand corner
panel = <string>; // Name of lump to render as the background.
voice = <string>; // Narration sound lump.
dialog = <string>; // Dialog of the page.
drop = <integer>; // mobj for the object to drop if the actor is
// killed.
link = <integer>; // 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 = <integer>; // mobj of item to check.
amount = <integer>; // amount required to be in inventory.
}
// Choices shall be automatically numbered.
choice
{
text = <string>; // 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 = <integer>; // Item that is required for this option.
amount = <integer>; // Minimum amount of the item needed.
}
displaycost = <bool>; // Weather the cost should be
// displayed with the option.
// If no cost is specified this should
// be ignored.
yesmessage = <string>; // Text to add to console when choice
// is accepted.
nomessage = <string>; // Text to add to console when choice
// is denied.
log = <string>; // LOG entry to use on success.
giveitem = <integer>; // Gives the specified item upon
// success.
// The following are the same as the special for linedefs in UDMF.
// They are executed on success.
special = <integer>;
arg0 = <integer>;
arg1 = <integer>;
arg2 = <integer>;
arg3 = <integer>;
arg4 = <integer>;
nextpage = <integer>; // Sets the next page.
closedialog = <bool>; // 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 = <string>;
===============================================================================
EOF
===============================================================================

56
specs/usdf_zdoom.txt Normal file
View File

@ -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 = <int>; // 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
===============================================================================

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -290,6 +290,10 @@ template<> inline FArchive &operator<< <FFont> (FArchive &arc, FFont* &font)
return SerializeFFontPtr (arc, font);
}
struct FStrifeDialogueNode;
template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node);
template<class T,class TT>
inline FArchive &operator<< (FArchive &arc, TArray<T,TT> &self)

View File

@ -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);

View File

@ -203,6 +203,7 @@ struct FActorInfo
BYTE GameFilter;
BYTE SpawnID;
SWORD DoomEdNum;
int ConversationID;
FStateLabels *StateList;
DmgFactors *DamageFactors;
PainChanceList *PainChances;

View File

@ -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)

View File

@ -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<FStrifeDialogueNode *> 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<int, const PClass *> FStrifeTypeMap; // maps conversation IDs to actor classes
typedef TMap<int, int> FDialogueIDMap; // maps dialogue IDs to dialogue array index (for ACS)
typedef TMap<FName, int> FDialogueMap; // maps actor class names to dialogue array index
static FStrifeTypeMap StrifeTypes;
static FDialogueIDMap DialogueRoots;
static FDialogueMap ClassRoots;
static menu_t ConversationMenu;
static TArray<menuitem_t> 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)
{
return StrifeTypes[typenum];
StrifeTypes[convid] = Class;
}
return NULL;
void SetConversation(int convid, const PClass *Class, int dlgindex)
{
if (convid != -1)
{
DialogueRoots[convid] = dlgindex;
}
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,34 +242,59 @@ 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];
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))
{
DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum));
return false;
}
if (!isbinary)
{
P_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)
{
return;
DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum));
return false;
}
numnodes /= 1516;
}
@ -266,7 +303,8 @@ static void LoadScriptFile(FileReader *lump, int numnodes)
// And the teaser version has 1488-byte entries.
if (numnodes % 1488 != 0)
{
return;
DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum));
return false;
}
numnodes /= 1488;
}
@ -286,6 +324,8 @@ static void LoadScriptFile(FileReader *lump, int numnodes)
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;
}

View File

@ -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 <tarray.h>
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<FStrifeDialogueItemCheck> 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<FStrifeDialogueItemCheck> 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<FStrifeDialogueNode *> 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

View File

@ -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,

View File

@ -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;
arc << ConversationRoot << Conversation;
}
}
for (; i + convnum < StrifeDialogues.Size(); ++convnum)
{
if (StrifeDialogues[i + convnum] == Conversation)
{
break;
}
}
if (i + convnum < StrifeDialogues.Size())
{
convnum++;
}
else
{
convnum = 0;
}
}
arc.WriteCount (convnum);
}
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<AActor *>(const_cast<PClass *>(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;

View File

@ -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<int> 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<line_t> ParsedLines;
TArray<side_t> 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<typename T>
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

38
src/p_udmf.h Normal file
View File

@ -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<typename T>
void Flag(T &value, int mask, const char *key)
{
if (CheckBool(key))
value |= mask;
else
value &= ~mask;
}
};
#define BLOCK_ID (ENamedName)-1
#endif

505
src/p_usdf.cpp Normal file
View File

@ -0,0 +1,505 @@
//
// p_usdf.cpp
//
// USDF dialogue parser
//
//---------------------------------------------------------------------------
// Copyright (c) 2010
// Braden "Blzut3" Obrzut <admin@maniacsvault.net>
// 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 <organization> 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 <COPYRIGHT HOLDER> 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;
}
}

View File

@ -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()

View File

@ -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);
}
//==========================================================================

View File

@ -1662,7 +1662,6 @@ ACTOR TargetPractice 208
ACTOR ForceFieldGuard 25 native
{
Game Strife
ConversationID 0
Health 10
Radius 2
Height 1

View File

@ -900,6 +900,10 @@
RelativePath=".\src\p_udmf.cpp"
>
</File>
<File
RelativePath=".\src\p_usdf.cpp"
>
</File>
<File
RelativePath=".\src\p_user.cpp"
>