2016-03-01 15:47:10 +00:00
//
// 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 "p_setup.h"
# include "p_lnspec.h"
# include "p_conversation.h"
2019-02-22 18:07:58 +00:00
# include "udmf.h"
2016-03-01 15:47:10 +00:00
# include "doomerrors.h"
# include "actor.h"
# include "a_pickups.h"
# include "w_wad.h"
2019-01-09 01:03:26 +00:00
# include "g_levellocals.h"
2019-02-22 18:07:58 +00:00
# include "maploader.h"
2016-03-01 15:47:10 +00:00
# define Zd 1
# define St 2
2019-02-14 04:26:15 +00:00
# define Gz 4
2016-03-01 15:47:10 +00:00
class USDFParser : public UDMFParserBase
{
2019-01-09 01:03:26 +00:00
FLevelLocals * Level ;
2016-03-01 15:47:10 +00:00
//===========================================================================
//
// Checks an actor type (different representation depending on namespace)
//
//===========================================================================
PClassActor * CheckActorType ( const char * key )
{
2017-02-08 17:11:23 +00:00
PClassActor * type = nullptr ;
2016-03-01 15:47:10 +00:00
if ( namespace_bits = = St )
{
2017-02-08 17:11:23 +00:00
type = GetStrifeType ( CheckInt ( key ) ) ;
2016-03-01 15:47:10 +00:00
}
2019-02-14 04:26:15 +00:00
else if ( namespace_bits & ( Zd | Gz ) )
2016-03-01 15:47:10 +00:00
{
PClassActor * cls = PClass : : FindActor ( CheckString ( key ) ) ;
2017-02-08 17:11:23 +00:00
if ( cls = = nullptr )
2016-03-01 15:47:10 +00:00
{
sc . ScriptMessage ( " Unknown actor class '%s' " , key ) ;
2017-02-08 17:11:23 +00:00
return nullptr ;
2016-03-01 15:47:10 +00:00
}
2017-02-08 17:11:23 +00:00
type = cls ;
2016-03-01 15:47:10 +00:00
}
2017-04-05 10:43:49 +00:00
return type ;
}
PClassActor * CheckInventoryActorType ( const char * key )
{
PClassActor * const type = CheckActorType ( key ) ;
2018-12-02 20:35:04 +00:00
return nullptr ! = type & & type - > IsDescendantOf ( NAME_Inventory ) ? type : nullptr ;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
2016-10-28 11:42:37 +00:00
// Parse a cost/require/exclude block
2016-03-01 15:47:10 +00:00
//
//===========================================================================
2016-10-28 11:42:37 +00:00
bool ParseCostRequireExclude ( FStrifeDialogueReply * response , FName type )
2016-03-01 15:47:10 +00:00
{
FStrifeDialogueItemCheck check ;
check . Item = NULL ;
check . Amount = - 1 ;
while ( ! sc . CheckToken ( ' } ' ) )
{
FName key = ParseKey ( ) ;
switch ( key )
{
case NAME_Item :
2017-04-05 10:43:49 +00:00
check . Item = CheckInventoryActorType ( key ) ;
2016-03-01 15:47:10 +00:00
break ;
case NAME_Amount :
check . Amount = CheckInt ( key ) ;
break ;
}
}
2016-10-28 11:42:37 +00:00
switch ( type )
{
case NAME_Cost : response - > ItemCheck . Push ( check ) ; break ;
case NAME_Require : response - > ItemCheckRequire . Push ( check ) ; break ;
case NAME_Exclude : response - > ItemCheckExclude . Push ( check ) ; break ;
}
2016-03-01 15:47:10 +00:00
return true ;
}
//===========================================================================
//
// Parse a choice block
//
//===========================================================================
bool ParseChoice ( FStrifeDialogueReply * * & replyptr )
{
FStrifeDialogueReply * reply = new FStrifeDialogueReply ;
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 ;
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 :
2019-02-14 04:26:15 +00:00
if ( namespace_bits ! = Gz | | sc . TokenType = = TK_IntConst )
reply - > NextNode = CheckInt ( key ) ;
else
reply - > NextNodeName = CheckString ( key ) ;
2016-03-01 15:47:10 +00:00
break ;
case NAME_Closedialog :
closeDialog = CheckBool ( key ) ;
break ;
case NAME_Special :
reply - > ActionSpecial = CheckInt ( key ) ;
2017-02-26 15:54:09 +00:00
if ( reply - > ActionSpecial < 0 )
2016-03-01 15:47:10 +00:00
reply - > ActionSpecial = 0 ;
break ;
2017-02-26 16:05:04 +00:00
case NAME_SpecialName :
2019-02-14 04:26:15 +00:00
if ( namespace_bits & ( Zd | Gz ) )
2017-02-26 16:05:04 +00:00
reply - > ActionSpecial = P_FindLineSpecial ( CheckString ( key ) ) ;
break ;
2016-03-01 15:47:10 +00:00
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 )
{
2016-10-29 15:05:59 +00:00
case NAME_Cost :
2016-10-28 11:42:37 +00:00
case NAME_Require :
case NAME_Exclude :
2016-10-29 15:05:59 +00:00
// Require and Exclude are exclusive to namespace ZDoom. [FishyClockwork]
2019-02-14 04:26:15 +00:00
if ( key = = NAME_Cost | | ( namespace_bits & ( Zd | Gz ) ) )
2016-10-29 13:52:29 +00:00
{
2016-10-29 15:05:59 +00:00
ParseCostRequireExclude ( reply , key ) ;
2016-10-29 13:52:29 +00:00
break ;
}
2016-10-29 15:05:59 +00:00
// Intentional fall-through
2016-03-01 15:47:10 +00:00
default :
sc . UnGet ( ) ;
Skip ( ) ;
}
}
}
// Todo: Finalize
if ( reply - > ItemCheck . Size ( ) > 0 )
{
2017-02-19 00:11:52 +00:00
reply - > PrintAmount = reply - > ItemCheck [ 0 ] . Amount ;
if ( reply - > PrintAmount < = 0 ) reply - > NeedsGold = false ;
2016-03-01 15:47:10 +00:00
}
2017-02-19 00:11:52 +00:00
reply - > Reply = ReplyString ;
reply - > QuickYes = QuickYes ;
2016-03-01 15:47:10 +00:00
if ( reply - > ItemCheck . Size ( ) > 0 & & reply - > ItemCheck [ 0 ] . Item ! = NULL )
{
2017-02-19 00:11:52 +00:00
reply - > QuickNo = QuickNo ;
2016-03-01 15:47:10 +00:00
}
else
{
2017-02-19 00:11:52 +00:00
reply - > QuickNo = " " ;
2016-03-01 15:47:10 +00:00
}
2017-02-19 00:11:52 +00:00
reply - > LogString = LogString ;
2019-02-14 04:26:15 +00:00
if ( reply - > NextNode < 0 ) // compatibility: handle negative numbers
{
reply - > CloseDialog = ! closeDialog ;
reply - > NextNode * = - 1 ;
}
else
{
reply - > CloseDialog = closeDialog ;
}
2016-03-01 15:47:10 +00:00
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 :
2017-04-05 10:43:49 +00:00
check . Item = CheckInventoryActorType ( key ) ;
2016-03-01 15:47:10 +00:00
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 ;
2019-01-09 01:03:26 +00:00
node - > ThisNodeNum = Level - > StrifeDialogues . Push ( node ) ;
2016-03-01 15:47:10 +00:00
node - > ItemCheckNode = - 1 ;
FString SpeakerName ;
FString Dialogue ;
2016-10-14 09:55:50 +00:00
FString Goodbye ;
2016-03-01 15:47:10 +00:00
while ( ! sc . CheckToken ( ' } ' ) )
{
bool block = false ;
FName key = ParseKey ( true , & block ) ;
if ( ! block )
{
switch ( key )
{
2019-02-14 04:26:15 +00:00
case NAME_Pagename :
if ( namespace_bits ! = Gz )
sc . ScriptMessage ( " 'PageName' keyword only supported in the GZDoom namespace! " ) ;
else
node - > ThisNodeName = CheckString ( key ) ;
break ;
2016-03-01 15:47:10 +00:00
case NAME_Name :
SpeakerName = CheckString ( key ) ;
break ;
case NAME_Panel :
2017-02-19 13:21:49 +00:00
node - > Backdrop = CheckString ( key ) ;
break ;
case NAME_Userstring :
2019-02-14 04:26:15 +00:00
if ( namespace_bits & ( Zd | Gz ) )
2017-02-19 13:21:49 +00:00
{
node - > UserData = CheckString ( key ) ;
}
2016-03-01 15:47:10 +00:00
break ;
case NAME_Voice :
{
const char * name = CheckString ( key ) ;
if ( name [ 0 ] ! = 0 )
{
FString soundname = " svox/ " ;
soundname + = name ;
node - > SpeakerVoice = FSoundID ( S_FindSound ( soundname ) ) ;
2019-02-14 04:26:15 +00:00
if ( node - > SpeakerVoice = = 0 & & ( namespace_bits & ( Zd | Gz ) ) )
2016-03-01 15:47:10 +00:00
{
node - > SpeakerVoice = FSoundID ( S_FindSound ( name ) ) ;
}
}
}
break ;
case NAME_Dialog :
Dialogue = CheckString ( key ) ;
break ;
case NAME_Drop :
node - > DropType = CheckActorType ( key ) ;
break ;
case NAME_Link :
2019-02-14 04:26:15 +00:00
if ( namespace_bits ! = Gz | | sc . TokenType = = TK_IntConst )
node - > ItemCheckNode = CheckInt ( key ) ;
else
node - > ItemCheckNodeName = CheckString ( key ) ;
2016-03-01 15:47:10 +00:00
break ;
2016-10-14 09:55:50 +00:00
case NAME_Goodbye :
2016-10-30 19:27:07 +00:00
// Custom goodbyes are exclusive to namespace ZDoom. [FishyClockwork]
2019-02-14 04:26:15 +00:00
if ( namespace_bits & ( Zd | Gz ) )
2016-10-30 19:27:07 +00:00
{
Goodbye = CheckString ( key ) ;
}
2016-10-14 09:55:50 +00:00
break ;
2016-03-01 15:47:10 +00:00
}
}
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 ( ) ;
}
}
}
2017-02-19 00:11:52 +00:00
node - > SpeakerName = SpeakerName ;
node - > Dialogue = Dialogue ;
node - > Goodbye = Goodbye ;
2016-03-01 15:47:10 +00:00
return true ;
}
//===========================================================================
//
// Parse a conversation block
//
//===========================================================================
bool ParseConversation ( )
{
2018-08-18 23:14:15 +00:00
PClassActor * type = nullptr ;
2016-03-01 15:47:10 +00:00
int dlgid = - 1 ;
2018-08-18 23:14:15 +00:00
FName clsid = NAME_None ;
2019-01-09 01:03:26 +00:00
unsigned int startpos = Level - > StrifeDialogues . Size ( ) ;
2016-03-01 15:47:10 +00:00
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 :
2019-02-14 04:26:15 +00:00
if ( namespace_bits & ( Zd | Gz ) )
2016-03-01 15:47:10 +00:00
{
dlgid = CheckInt ( key ) ;
}
break ;
2017-02-19 13:21:49 +00:00
case NAME_Class :
2019-02-14 04:26:15 +00:00
if ( namespace_bits & ( Zd | Gz ) )
2017-02-19 13:21:49 +00:00
{
clsid = CheckString ( key ) ;
}
break ;
2016-03-01 15:47:10 +00:00
}
}
else
{
switch ( key )
{
case NAME_Page :
if ( ! ParsePage ( ) ) return false ;
break ;
default :
sc . UnGet ( ) ;
Skip ( ) ;
}
}
}
if ( type = = NULL & & dlgid = = 0 )
{
sc . ScriptMessage ( " No valid actor type defined in conversation. " ) ;
return false ;
}
2019-01-09 01:03:26 +00:00
Level - > SetConversation ( dlgid , type , startpos ) ;
for ( ; startpos < Level - > StrifeDialogues . Size ( ) ; startpos + + )
2016-03-01 15:47:10 +00:00
{
2019-01-09 01:03:26 +00:00
Level - > StrifeDialogues [ startpos ] - > SpeakerType = type ;
Level - > StrifeDialogues [ startpos ] - > MenuClassName = clsid ;
2016-03-01 15:47:10 +00:00
}
return true ;
}
//===========================================================================
//
// Parse an USDF lump
//
//===========================================================================
public :
2019-02-22 18:07:58 +00:00
bool Parse ( MapLoader * loader , int lumpnum , FileReader & lump , int lumplen )
2016-03-01 15:47:10 +00:00
{
2019-02-22 18:07:58 +00:00
Level = loader - > Level ;
2018-11-10 10:56:18 +00:00
sc . OpenMem ( Wads . GetLumpFullName ( lumpnum ) , lump . Read ( lumplen ) ) ;
2016-03-01 15:47:10 +00:00
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 )
{
2019-02-14 04:26:15 +00:00
case NAME_GZDoom :
namespace_bits = Gz ;
break ;
2016-03-01 15:47:10 +00:00
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
{
2019-02-14 04:26:15 +00:00
sc . ScriptMessage ( " Dialog script does not define a namespace. \n " ) ;
2016-03-01 15:47:10 +00:00
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 ) ;
2019-02-22 18:07:58 +00:00
loader - > LoadScriptFile ( sc . String , true , 0 ) ;
2016-03-01 15:47:10 +00:00
sc . MustGetToken ( ' ; ' ) ;
}
else
{
Skip ( ) ;
}
}
2019-02-14 04:26:15 +00:00
if ( namespace_bits = = Gz ) // string page name linker
{
int numnodes = Level - > StrifeDialogues . Size ( ) ;
int usedstrings = false ;
TMap < FString , int > nameToIndex ;
for ( int i = 0 ; i < numnodes ; i + + )
{
FString key = Level - > StrifeDialogues [ i ] - > ThisNodeName ;
if ( key . IsNotEmpty ( ) )
{
key . ToLower ( ) ;
if ( nameToIndex . CheckKey ( key ) )
Printf ( " Warning! Duplicate page name '%s'! \n " , Level - > StrifeDialogues [ i ] - > ThisNodeName . GetChars ( ) ) ;
else
nameToIndex [ key ] = i ;
usedstrings = true ;
}
}
if ( usedstrings )
{
for ( int i = 0 ; i < numnodes ; i + + )
{
FString itemLinkKey = Level - > StrifeDialogues [ i ] - > ItemCheckNodeName ;
if ( itemLinkKey . IsNotEmpty ( ) )
{
itemLinkKey . ToLower ( ) ;
if ( nameToIndex . CheckKey ( itemLinkKey ) )
Level - > StrifeDialogues [ i ] - > ItemCheckNode = nameToIndex [ itemLinkKey ] + 1 ;
else
Printf ( " Warning! Reference to non-existent item-linked dialogue page name '%s' in page %i! \n " , Level - > StrifeDialogues [ i ] - > ItemCheckNodeName . GetChars ( ) , i ) ;
}
FStrifeDialogueReply * NodeCheck = Level - > 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 ;
}
}
}
}
2016-03-01 15:47:10 +00:00
return true ;
}
} ;
2019-02-22 18:07:58 +00:00
bool MapLoader : : ParseUSDF ( int lumpnum , FileReader & lump , int lumplen )
2016-03-01 15:47:10 +00:00
{
USDFParser parse ;
try
{
2019-02-22 18:07:58 +00:00
if ( ! parse . Parse ( this , lumpnum , lump , lumplen ) )
2016-03-01 15:47:10 +00:00
{
// clean up the incomplete dialogue structures here
return false ;
}
return true ;
}
catch ( CRecoverableError & err )
{
Printf ( " %s " , err . GetMessage ( ) ) ;
return false ;
}
}