scummvm/engines/glk/adrift/sctafpar.cpp
2022-10-23 22:46:19 +02:00

3423 lines
95 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/adrift/scare.h"
#include "glk/adrift/scprotos.h"
#include "glk/jumps.h"
namespace Glk {
namespace Adrift {
/*
* Module notes:
*
* o Adds new "types" to jAsea's property descriptor: 'M' for multiline
* strings, 'Z', 'F'/'T', and 'E' for defaulted integers, booleans, and
* strings, 'i', 'b', and 's' for ignored integers, booleans, and strings,
* and '{...}' and '|...|' for "special" descriptions and version fixups
* that can't be described as things stand.
*
* o Adds new 'G' expression test, to check Global boolean.
*
* o The stack "adjustment" stuff is a bit of a bother.
*/
/* Assorted definitions and constants. */
static const sc_char NUL = '\0';
enum {
PARSE_TEMP_LENGTH = 256,
PARSE_MAX_DEPTH = 32
};
/* Multiline separator sequences for the various versions supported. */
enum { SEPARATOR_SIZE = 3 };
static const sc_byte V400_SEPARATOR[SEPARATOR_SIZE] = {0xbd, 0xd0, 0x00};
static const sc_byte V390_SEPARATOR[SEPARATOR_SIZE] = {0x2a, 0x2a, 0x00};
static const sc_byte V380_SEPARATOR[SEPARATOR_SIZE] = {0x2a, 0x2a, 0x00};
/*
* Tables of properties descriptors. These strings define the structure of
* a TAF file. Field keys are:
*
* $,#,B,M - string, integer, boolean, and multiline properties
* E,F,T,Z - string, integer, and boolean properties not in the TAF;
* set to "", FALSE, TRUE and zero on parsing (version < 4)
* i,s,b - string, integer, and boolean in the TAF, but not stored
* [num] - arrays of property, fixed size to num
* V - variable sized array of property, size in input file
* W - like V, but size - 1 in input file (version < 4)
* <class> - class of property, separate parse target (recurse)
* ?[!]expr: - conditional property based on expr
* G[!]expr: - conditional property based on expr using globals
* |...| - fixup specials for versions < 4
* {special} - because some things just defy description
*/
struct sc_parse_schema_t {
const sc_char *const class_name;
const sc_char *const descriptor;
};
/* Version 4.0 TAF file properties descriptor table. */
static const sc_parse_schema_t V400_PARSE_SCHEMA[] = {
{
"_GAME_",
"<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
" V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
" V<VARIABLE>Variables V<ALR>ALRs BCustomFont ?BCustomFont:$FontNameSize"
" $CompileDate"
},
{
"HEADER",
"MStartupText #StartRoom MWinText"
},
{
"GLOBAL",
"$GameName $GameAuthor $DontUnderstand #Perspective BShowExits #WaitTurns"
" BDispFirstRoom BBattleSystem #MaxScore $PlayerName BPromptName $PlayerDesc"
" #Task ?!#Task=0:$AltDesc #Position #ParentObject #PlayerGender"
" #MaxSize #MaxWt ?GBattleSystem:<BATTLE>Battle BEightPointCompass bNoDebug"
" BNoScoreNotify BNoMap bNoAutoComplete bNoControlPanel bNoMouse BSound"
" BGraphics <RESOURCE>IntroRes <RESOURCE>WinRes BStatusBox $StatusBoxText"
" iUnk1 iUnk2 BEmbedded"
},
{
"BATTLE",
"iStaminaLo iStaminaHi iStrengthLo iStrengthHi iAccuracyLo iAccuracyHi"
" iDefenseLo iDefenseHi iAgilityLo iAgilityHi iRecovery"
},
{
"ROOM",
"$Short $Long ?GEightPointCompass:[12]<ROOM_EXIT>Exits"
" ?!GEightPointCompass:[8]<ROOM_EXIT>Exits <RESOURCE>Res V<ROOM_ALT>Alts"
" ?!GNoMap:bHideOnMap"
},
{
"ROOM_EXIT",
"{V400_ROOM_EXIT:#Dest_#Var1_#Var2_#Var3}"
},
{
"ROOM_ALT",
"$M1 #Type <RESOURCE>Res1 $M2 #Var2 <RESOURCE>Res2 #HideObjects $Changed"
" #Var3 #DisplayRoom"
},
{
"RESOURCE",
"?GSound:$SoundFile,#SoundLen,ZSoundOffset"
" ?GGraphics:$GraphicFile,#GraphicLen,ZGraphicOffset {V400_RESOURCE}"
},
{
"OBJECT",
"$Prefix $Short V$Alias BStatic $Description #InitialPosition #Task"
" BTaskNotDone $AltDesc ?BStatic:<ROOM_LIST1>Where BContainer BSurface"
" #Capacity ?!BStatic:BWearable,#SizeWeight,#Parent"
" ?BStatic:{OBJECT:#Parent} #Openable ?#Openable=5:#Key ?#Openable=6:#Key"
" ?#Openable=7:#Key #SitLie ?!BStatic:BEdible BReadable ?BReadable:$ReadText"
" ?!BStatic:BWeapon #CurrentState ?!#CurrentState=0:$States,BStateListed"
" BListFlag <RESOURCE>Res1 <RESOURCE>Res2 ?GBattleSystem:<OBJ_BATTLE>Battle"
" $InRoomDesc #OnlyWhenNotMoved"
},
{
"OBJ_BATTLE",
"iProtectionValue iHitValue iMethod iAccuracy"
},
{
"ROOM_LIST1",
"#Type {ROOM_LIST1}"
},
{
"TASK",
"V$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
" #ShowRoomDesc BRepeatable BReversible V$ReverseCommand <ROOM_LIST0>Where"
" $Question ?$Question:$Hint1,$Hint2 V<TASK_RESTR>Restrictions"
" V<TASK_ACTION>Actions $RestrMask <RESOURCE>Res"
},
{
"TASK_RESTR",
"#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2 ?#Type=2:#Var1,#Var2"
" ?#Type=3:#Var1,#Var2,#Var3 ?#Type=4:#Var1,#Var2,#Var3,$Var4 $FailMessage"
},
{
"TASK_ACTION",
"#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2,#Var3"
" ?#Type=2:#Var1,#Var2 ?#Type=3:#Var1,#Var2,#Var3,$Expr,#Var5"
" ?#Type=4:#Var1 ?#Type=5:#Var1,#Var2 ?#Type=6:#Var1,#Var2,#Var3"
" ?#Type=7:iVar1,iVar2,iVar3"
},
{
"ROOM_LIST0",
"#Type {ROOM_LIST0}"
},
{
"EVENT",
"$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
" ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
" $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
" BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
" #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
" #TaskAffected [5]<RESOURCE>Res"
},
{
"NPC",
"$Name $Prefix V$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
" V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
" $InRoomText #Gender [4]<RESOURCE>Res ?GBattleSystem:<NPC_BATTLE>Battle"
},
{
"NPC_BATTLE",
"iAttitude iStaminaLo iStaminaHi iStrengthLo iStrengthHi iAccuracyLo"
" iAccuracyHi iDefenseLo iDefenseHi iAgilityLo iAgilityHi iSpeed"
" iKilledTask iRecovery iStaminaTask"
},
{
"TOPIC",
"$Subject $Reply #Task $AltReply"
},
{
"WALK",
"#NumStops BLoop #StartTask #CharTask #MeetObject #ObjectTask #StoppingTask"
" #MeetChar $ChangedDesc {WALK:#Rooms_#Times}"
},
{
"ROOM_GROUP",
"$Name {ROOM_GROUP:[]BList}"
},
{
"SYNONYM",
"$Replacement $Original"
},
{
"VARIABLE",
"$Name #Type $Value"
},
{
"ALR",
"$Original $Replacement"
},
{nullptr, nullptr}
};
/* Version 3.9 TAF file properties descriptor table. */
static const sc_parse_schema_t V390_PARSE_SCHEMA[] = {
{
"_GAME_",
"<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
" V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
" V<VARIABLE>Variables V<ALR>ALRs BCustomFont ?BCustomFont:$FontNameSize"
" $CompileDate sPassword"
},
{
"HEADER",
"MStartupText #StartRoom MWinText"
},
{
"GLOBAL",
"$GameName $GameAuthor $DontUnderstand #Perspective BShowExits #WaitTurns"
" BDispFirstRoom BBattleSystem #MaxScore $PlayerName BPromptName $PlayerDesc"
" #Task ?!#Task=0:$AltDesc #Position #ParentObject #PlayerGender"
" #MaxSize #MaxWt ?GBattleSystem:<BATTLE>Battle BEightPointCompass bNoDebug"
" BNoScoreNotify BNoMap bNoAutoComplete bNoControlPanel bNoMouse"
" BSound BGraphics <RESOURCE>IntroRes <RESOURCE>WinRes FStatusBox"
" EStatusBoxText iUnk1 iUnk2 FEmbedded"
},
{
"BATTLE",
"iStamina iStrength iDefense"
},
{
"ROOM",
"$Short $Long $LastDesc ?GEightPointCompass:[12]<ROOM_EXIT>Exits"
" ?!GEightPointCompass:[8]<ROOM_EXIT>Exits $AddDesc1 #Task1 $AddDesc2 #Task2"
" #Obj $AltDesc #TypeHideObjects <RESOURCE>Res <RESOURCE>LastRes"
" <RESOURCE>Task1Res <RESOURCE>Task2Res <RESOURCE>AltRes"
" ?!GNoMap:bHideOnMap |V390_ROOM:_Alts_|"
},
{
"ROOM_EXIT",
"{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}"
},
{
"RESOURCE",
"?GSound:$SoundFile,ZSoundLen,ZSoundOffset"
" ?GGraphics:$GraphicFile,ZGraphicLen,ZGraphicOffset"
},
{
"OBJECT",
"$Prefix $Short"
" [1]$Alias BStatic $Description #InitialPosition #Task BTaskNotDone"
" $AltDesc ?BStatic:<ROOM_LIST1>Where BContainer BSurface #Capacity"
" ?!BStatic:BWearable,#SizeWeight,#Parent ?BStatic:{OBJECT:#Parent}"
" #Openable |V390_OBJECT:_Openable_,Key| #SitLie ?!BStatic:BEdible BReadable"
" ?BReadable:$ReadText ?!BStatic:BWeapon ZCurrentState FListFlag"
" <RESOURCE>Res1 <RESOURCE>Res2 ?GBattleSystem:<OBJ_BATTLE>Battle"
" EInRoomDesc ZOnlyWhenNotMoved"
},
{
"OBJ_BATTLE",
"iProtectionValue iHitValue iMethod"
},
{
"ROOM_LIST1",
"#Type {ROOM_LIST1}"
},
{
"TASK",
"W$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
" #ShowRoomDesc BRepeatable BReversible W$ReverseCommand <ROOM_LIST0>Where"
" $Question ?$Question:$Hint1,$Hint2 V<TASK_RESTR>Restrictions"
" V<TASK_ACTION>Actions |V390_TASK:$RestrMask| <RESOURCE>Res"
},
{
"TASK_RESTR",
"#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2 ?#Type=2:#Var1,#Var2"
" ?#Type=3:#Var1,#Var2,#Var3 ?#Type=4:#Var1,#Var2,#Var3,EVar4"
",|V390_TASK_RESTR:Var1>0?#Var1++| $FailMessage"
},
{
"TASK_ACTION",
"#Type |V390_TASK_ACTION:Type>4?#Type++| ?#Type=0:#Var1,#Var2,#Var3"
" ?#Type=1:#Var1,#Var2,#Var3 ?#Type=2:#Var1,#Var2"
" ?#Type=3:#Var1,#Var2,#Var3,|V390_TASK_ACTION:$Expr_#Var5|"
" ?#Type=4:#Var1 ?#Type=6:#Var1,ZVar2,ZVar3 ?#Type=7:iVar1,iVar2,iVar3"
},
{
"ROOM_LIST0",
"#Type {ROOM_LIST0}"
},
{
"EVENT",
"$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
" ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
" $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
" BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
" #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
" #TaskAffected [5]<RESOURCE>Res"
},
{
"NPC",
"$Name $Prefix [1]$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
" V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
" $InRoomText #Gender [4]<RESOURCE>Res ?GBattleSystem:<NPC_BATTLE>Battle"
},
{
"NPC_BATTLE",
"iAttitude iStamina iStrength iDefense iSpeed iKilledTask"
},
{
"TOPIC",
"$Subject $Reply #Task $AltReply"
},
{
"WALK",
"#NumStops BLoop #StartTask #CharTask #MeetObject #ObjectTask #StoppingTask"
" ZMeetChar $ChangedDesc {WALK:#Rooms_#Times}"
},
{
"ROOM_GROUP",
"$Name {ROOM_GROUP:[]BList}"
},
{
"SYNONYM",
"$Replacement $Original"
},
{
"VARIABLE",
"$Name ZType $Value"
},
{
"ALR",
"$Original $Replacement"
},
{nullptr, nullptr}
};
/* Version 3.8 TAF file properties descriptor table. */
static const sc_parse_schema_t V380_PARSE_SCHEMA[] = {
{
"_GAME_",
"<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
" V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
" FCustomFont $CompileDate sPassword |V380_GLOBAL:_MaxScore_|"
" |V380_OBJECT:_InitialPositions_|"
},
{
"HEADER",
"MStartupText #StartRoom MWinText"
},
{
"GLOBAL",
"$GameName $GameAuthor #MaxCarried |V380_MaxSize_MaxWt_| $DontUnderstand"
" #Perspective BShowExits #WaitTurns FDispFirstRoom FBattleSystem"
" EPlayerName FPromptName EPlayerDesc ZTask ZPosition ZParentObject"
" ZPlayerGender FEightPointCompass TNoScoreNotify FSound FGraphics"
" FStatusBox EStatusBoxText FEmbedded"
},
{
"ROOM",
"$Short $Long $LastDesc [8]<ROOM_EXIT>Exits $AddDesc1 #Task1 $AddDesc2"
" #Task2 #Obj $AltDesc #TypeHideObjects |V380_ROOM:_Alts_|"
},
{
"ROOM_EXIT",
"{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}"
},
{
"OBJECT",
"$Prefix $Short [1]$Alias BStatic $Description #InitialPosition #Task"
" BTaskNotDone $AltDesc ?BStatic:<ROOM_LIST1>Where #SurfaceContainer"
" FSurface ?#SurfaceContainer=2:TSurface FContainer"
" ?#SurfaceContainer=1:TContainer #Capacity |V380_OBJECT:#Capacity*10+2|"
" ?!BStatic:BWearable,#SizeWeight,#Parent ?BStatic:{OBJECT:#Parent}"
" #Openable |V380_OBJECT:_Openable_,Key| #SitLie ?!BStatic:BEdible BReadable"
" ?BReadable:$ReadText ?!BStatic:BWeapon ZCurrentState FListFlag"
" EInRoomDesc ZOnlyWhenNotMoved"
},
{
"ROOM_LIST1",
"#Type {ROOM_LIST1}"
},
{
"TASK",
"W$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
" #ShowRoomDesc BRepeatable #Score BSingleScore [6]<TASK_MOVE>Movements"
" BReversible W$ReverseCommand #WearObj1 #WearObj2 #HoldObj1 #HoldObj2"
" #HoldObj3 #Obj1 #Task BTaskNotDone $TaskMsg $HoldMsg $WearMsg $CompanyMsg"
" BNotInSameRoom #NPC $Obj1Msg #Obj1Room <ROOM_LIST0>Where BKillsPlayer"
" BHoldingSameRoom $Question ?$Question:$Hint1,$Hint2 #Obj2"
" ?!#Obj2=0:#Obj2Var1,#Obj2Var2,$Obj2Msg BWinGame |V380_TASK:_Actions_|"
" |V380_TASK:_Restrictions_|"
},
{
"TASK_MOVE",
"#Var1 #Var2 #Var3"
},
{
"ROOM_LIST0",
"#Type {ROOM_LIST0}"
},
{
"EVENT",
"$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
" ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
" $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
" BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
" #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
" #TaskAffected"
},
{
"NPC",
"$Name $Prefix [1]$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
" V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
" $InRoomText ZGender"
},
{
"TOPIC",
"$Subject $Reply #Task $AltReply"
},
{
"WALK",
"#NumStops BLoop #StartTask #CharTask #MeetObject"
" ?!#MeetObject=0:|V380_WALK:_MeetObject_| #ObjectTask ZMeetChar"
" {WALK:#Rooms_#Times} ZStoppingTask EChangedDesc"
},
{
"ROOM_GROUP",
"$Name {ROOM_GROUP:[]BList}"
},
{
"SYNONYM",
"$Replacement $Original"
},
{nullptr, nullptr}
};
/*
* parse_select_schema()
*
* Select one of the parse schemata based on a TAF file.
*/
static const sc_parse_schema_t *parse_select_schema(sc_tafref_t taf) {
/* Switch based on the TAF file version. */
switch (taf_get_version(taf)) {
case TAF_VERSION_400:
return V400_PARSE_SCHEMA;
case TAF_VERSION_390:
return V390_PARSE_SCHEMA;
case TAF_VERSION_380:
return V380_PARSE_SCHEMA;
default:
sc_fatal("parse_select_schema: invalid TAF file version\n");
return nullptr;
}
}
/* The uncompressed TAF file from which we get all our data. */
static sc_tafref_t parse_taf = nullptr;
static sc_int parse_tafline = 0;
/* The parse schema selected for this TAF file. */
static sc_parse_schema_t const *parse_schema = nullptr;
/* Properties bundle and trace flag, set before parsing. */
static sc_prop_setref_t parse_bundle = nullptr;
static sc_bool parse_trace = FALSE;
/*
* Stack of property keys. The stack is filled by parsing, and written
* to the property store on parse terminals.
*/
static sc_vartype_t parse_vt_key[PARSE_MAX_DEPTH];
static sc_char parse_format[PARSE_MAX_DEPTH];
static sc_int parse_depth = 0;
/*
* parse_push_key()
* parse_pop_key()
*
* Push a key of the given type onto the property key stack, and pop a key
* off on unwind.
*/
static void parse_push_key(sc_vartype_t vt_key, sc_char type) {
if (parse_depth == PARSE_MAX_DEPTH)
sc_fatal("parse_push_key: stack overrun\n");
/* Push the key, and its associated type. */
memcpy(parse_vt_key + parse_depth, &vt_key, sizeof(vt_key));
parse_format[parse_depth] = type;
parse_depth++;
}
static void parse_pop_key(void) {
/* Check the stack has something to pop, then pop it. */
if (parse_depth == 0)
sc_fatal("parse_pop_key: stack underrun\n");
parse_depth--;
}
/*
* parse_retrieve_stack()
*
* This is ugly. The parse produces indexes before the things that they
* index. An expedient fix is to switch i-s keys before storing a property
* value
*/
static void parse_retrieve_stack(sc_char format[], sc_vartype_t vt_key[], sc_int *depth) {
sc_int index_;
/* Switch index-string key pairs. */
for (index_ = 0; index_ < parse_depth; index_++) {
if (index_ < parse_depth - 1
&& parse_format[index_] == PROP_KEY_INTEGER
&& parse_format[index_ + 1] == PROP_KEY_STRING) {
/* Swap format and key elements. */
format[index_] = parse_format[index_ + 1];
format[index_ + 1] = parse_format[index_];
vt_key[index_] = parse_vt_key[index_ + 1];
vt_key[index_ + 1] = parse_vt_key[index_];
index_++;
} else {
/* Simple copy of format and key elements. */
format[index_] = parse_format[index_];
vt_key[index_] = parse_vt_key[index_];
}
}
/* Return the parse depth. */
*depth = parse_depth;
}
/*
* parse_stack_backtrace()
*
* Dump the parse stack. Used for diagnostics on finding what we think may
* be a bad game.
*/
static void parse_stack_backtrace(void) {
sc_vartype_t vt_key[PARSE_MAX_DEPTH];
sc_char format[PARSE_MAX_DEPTH];
sc_int depth, index_;
parse_retrieve_stack(format, vt_key, &depth);
sc_error("parse_stack_backtrace: version %s schema parsed to depth %ld\n",
(parse_schema == V400_PARSE_SCHEMA) ? "4.00" :
(parse_schema == V390_PARSE_SCHEMA) ? "3.90" :
(parse_schema == V380_PARSE_SCHEMA) ? "3.80" : "[Invalid]",
depth);
sc_error("parse_stack_backtrace: parse stack backtrace follows...\n");
for (index_ = 0; index_ < depth; index_++) {
sc_char type;
type = format[index_];
if (type == PROP_KEY_INTEGER)
sc_error("%2ld - [%c] %ld\n", index_, type, vt_key[index_].integer);
else if (type == PROP_KEY_STRING)
sc_error("%2ld - [%c] \"%s\"\n", index_, type, vt_key[index_].string);
else
sc_error("%2ld - [%c] %p\n", index_, type, vt_key[index_].voidp);
}
}
/*
* parse_put_property()
* parse_get_property()
*
* Write or read a property based on the keys amassed so far.
*/
static void parse_put_property(sc_vartype_t vt_value, sc_char type) {
sc_vartype_t vt_key[PARSE_MAX_DEPTH];
sc_char format[PARSE_MAX_DEPTH + 4];
sc_int depth;
/* Retrieve the adjusted stack. */
parse_retrieve_stack(format + 3, vt_key, &depth);
/* Complete the format for the property put. */
format[0] = type;
format[1] = '-';
format[2] = '>';
format[depth + 3] = NUL;
/* Store the property under the stacked keys. */
assert(parse_bundle);
prop_put(parse_bundle, format, vt_value, vt_key);
}
static sc_bool parse_get_property(sc_vartype_t *vt_rvalue, sc_char type) {
sc_vartype_t vt_key[PARSE_MAX_DEPTH];
sc_char format[PARSE_MAX_DEPTH + 4];
sc_int depth;
sc_bool status;
/* Retrieve the adjusted stack. */
parse_retrieve_stack(format + 3, vt_key, &depth);
/* Complete the format for the property put. */
format[0] = type;
format[1] = '<';
format[2] = '-';
format[depth + 3] = NUL;
/* Retrieve the property using the stacked keys. */
assert(parse_bundle);
status = prop_get(parse_bundle, format, vt_rvalue, vt_key);
return status;
}
/*
* parse_get_child_count()
*
* Convenience form of parse_get_property(), retrieve an integer property
* indicating the child count of the effectively stacked node, or zero if
* no such node exists.
*/
static sc_int parse_get_child_count(void) {
sc_vartype_t vt_rvalue;
if (!parse_get_property(&vt_rvalue, PROP_INTEGER))
vt_rvalue.integer = 0;
return vt_rvalue.integer;
}
/*
* parse_get_integer_property()
* parse_get_boolean_property()
* parse_get_string_property()
*
* Convenience forms of parse_get_property(), retrieve directly, and report
* a fatal error if the property does not exist.
*/
static sc_int parse_get_integer_property(void) {
sc_vartype_t vt_rvalue;
if (!parse_get_property(&vt_rvalue, PROP_INTEGER))
sc_fatal("parse_get_integer_property: missing property\n");
return vt_rvalue.integer;
}
static sc_bool parse_get_boolean_property(void) {
sc_vartype_t vt_rvalue;
if (!parse_get_property(&vt_rvalue, PROP_BOOLEAN))
sc_fatal("parse_get_boolean_property: missing property\n");
return vt_rvalue.boolean;
}
static const sc_char *parse_get_string_property(void) {
sc_vartype_t vt_rvalue;
if (!parse_get_property(&vt_rvalue, PROP_STRING))
sc_fatal("parse_get_string_property: missing property\n");
return vt_rvalue.string;
}
/* Pushback line, and pushback requested flag. */
static const sc_char *parse_pushback_line = nullptr;
static sc_bool parse_use_pushback = FALSE;
/*
* parse_get_taf_string()
* parse_get_taf_integer()
* parse_get_taf_boolean()
* parse_taf_pushback()
*
* Wrapper round obtaining the next TAF file line, with variants to convert
* the line content into an integer or boolean, and a function for effective
* TAF line pushback.
*/
static const sc_char *parse_get_taf_string(CONTEXT) {
const sc_char *line;
/* If pushback requested, use that instead of reading. */
if (parse_use_pushback) {
/* Use the pushback line, and clear the request. */
assert(parse_pushback_line);
line = parse_pushback_line;
parse_use_pushback = FALSE;
} else {
/* Get the next line, and complain if absent. */
line = taf_next_line(parse_taf);
if (!line) {
sc_error("parse_get_taf_string:"
" out of TAF data at line %ld\n", parse_tafline);
parse_stack_backtrace();
LONG_JUMP0;
}
/* Note this line for possible pushback. */
parse_pushback_line = line;
}
/* Print out the line we're parsing if tracing. */
if (parse_trace)
sc_trace("Parse: read in line %ld : %s\n", parse_tafline, line);
parse_tafline++;
return line;
}
static sc_int parse_get_taf_integer(CONTEXT) {
const sc_char *line;
sc_int integer;
/* Get line, and scan for a single integer; return it. */
R0FUNC0(parse_get_taf_string, line);
if (sscanf(line, "%ld", &integer) != 1) {
sc_error("parse_get_taf_integer:"
" invalid integer at line %ld\n", parse_tafline - 1);
parse_stack_backtrace();
LONG_JUMP0;
}
return integer;
}
static sc_bool parse_get_taf_boolean(CONTEXT) {
const sc_char *line;
sc_uint boolean;
/*
* Get line, and scan for a single integer; check it's a valid-looking flag,
* and return it.
*/
R0FUNC0(parse_get_taf_string, line);
if (sscanf(line, "%lu", &boolean) != 1) {
sc_error("parse_get_taf_boolean:"
" invalid boolean at line %ld\n", parse_tafline - 1);
parse_stack_backtrace();
LONG_JUMP0;
}
if (boolean != 0 && boolean != 1) {
sc_error("parse_get_taf_boolean:"
" warning: suspect boolean at line %ld\n", parse_tafline - 1);
}
return boolean != 0;
}
static void parse_taf_pushback(void) {
if (parse_use_pushback || !parse_pushback_line)
sc_fatal("parse_taf_pushback: too much pushback requested\n");
/* Set pushback request, and decrement line counter. */
parse_use_pushback = TRUE;
parse_tafline--;
/* Note pushback for tracing purposes. */
if (parse_trace)
sc_trace("Parse: push back at line %ld\n", parse_tafline);
}
/* Enumerations of parse types found in the parse schema. */
enum {
PARSE_INTEGER = '#',
PARSE_DEFAULT_ZERO = 'Z',
PARSE_BOOLEAN = 'B',
PARSE_DEFAULT_FALSE = 'F',
PARSE_DEFAULT_TRUE = 'T',
PARSE_STRING = '$',
PARSE_DEFAULT_EMPTY = 'E',
PARSE_MULTILINE = 'M',
PARSE_VECTOR = 'V',
PARSE_VECTOR_ALTERNATE = 'W',
PARSE_ARRAY = '[',
PARSE_EXPRESSION = '?',
PARSE_EXPRESSION_NOT = '!',
PARSE_GLOBAL_EXPRESSION = 'G',
PARSE_CLASS = '<',
PARSE_FIXUP = '|',
PARSE_SPECIAL = '{',
PARSE_IGNORE_INTEGER = 'i',
PARSE_IGNORE_BOOLEAN = 'b',
PARSE_IGNORE_STRING = 's'
};
/* Forward declarations of parse functions for recursion. */
static void parse_element(CONTEXT, const sc_char *element);
static void parse_class(CONTEXT, const sc_char *class_);
static void parse_descriptor(CONTEXT, const sc_char *descriptor);
/*
* parse_array()
*
* Parse a descriptor [] array.
*/
static void parse_array(CONTEXT, const sc_char *array) {
sc_int count, index_;
sc_char element[PARSE_TEMP_LENGTH];
if (parse_trace)
sc_trace("Parse: entering array %s\n", array);
/* Find the count of elements in the array, and the element itself. */
if (sscanf(array, "[%ld]%[^ ]", &count, element) != 2)
sc_fatal("parse_array: bad array, %s\n", array);
/* Parse the element for array count iterations, each a key. */
for (index_ = 0; index_ < count; index_++) {
sc_vartype_t vt_key;
vt_key.integer = index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
CALL1(parse_element, element);
parse_pop_key();
}
if (parse_trace)
sc_trace("Parse: leaving array %s\n", array);
}
/*
* parse_vector_common()
* parse_vector()
* parse_vector_alternate()
*
* Parse a variable-length vector of properties.
*/
static void parse_vector_common(CONTEXT, const sc_char *vector, sc_int count) {
sc_int index_;
/* Parse the vector property count times, pushing a key on each. */
for (index_ = 0; index_ < count; index_++) {
sc_vartype_t vt_key;
vt_key.integer = index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
CALL1(parse_element, vector + 1);
parse_pop_key();
}
}
static void parse_vector(CONTEXT, const sc_char *vector) {
sc_int count;
if (parse_trace)
sc_trace("Parse: entering vector %s\n", vector);
/* Find the count of elements in the vector, and parse. */
FUNC0(parse_get_taf_integer, count);
CALL2(parse_vector_common, vector, count);
if (parse_trace)
sc_trace("Parse: leaving vector %s\n", vector);
}
static void parse_vector_alternate(CONTEXT, const sc_char *vector) {
sc_int count1;
if (parse_trace)
sc_trace("Parse: entering alternate vector %s\n", vector);
/* Element count, this is a vector described by size - 1. */
FUNC0(parse_get_taf_integer, count1);
CALL2(parse_vector_common, vector, count1 + 1);
if (parse_trace)
sc_trace("Parse: leaving alternate vector %s\n", vector);
}
/*
* parse_test_expression()
* parse_expression()
*
* Parse a conditional field definition, with runtime test.
*/
static sc_bool parse_test_expression(const sc_char *test_expression) {
sc_vartype_t vt_key;
sc_char plhs[PARSE_TEMP_LENGTH];
sc_int rhs;
sc_bool retval = FALSE;
/* Identify the type of expression to evaluate. */
switch (test_expression[0]) {
case PARSE_BOOLEAN:
/* Read boolean property and return its value. */
vt_key.string = test_expression + 1;
parse_push_key(vt_key, PROP_KEY_STRING);
retval = parse_get_boolean_property();
parse_pop_key();
break;
case PARSE_INTEGER:
/* Get the left and right sides of = comparison. */
if (sscanf(test_expression, "#%[^=]=%ld", plhs, &rhs) != 2) {
sc_fatal("parse_test_expression: bad = compare, %s\n",
test_expression + 1);
}
/* Read integer property and return comparison. */
vt_key.string = plhs;
parse_push_key(vt_key, PROP_KEY_STRING);
retval = (parse_get_integer_property() == rhs);
parse_pop_key();
break;
case PARSE_STRING:
/* Read property and return TRUE if not an empty string. */
vt_key.string = test_expression + 1;
parse_push_key(vt_key, PROP_KEY_STRING);
retval = !sc_strempty(parse_get_string_property());
parse_pop_key();
break;
case PARSE_GLOBAL_EXPRESSION: {
sc_vartype_t vt_gkey[2];
/* Read the given Global boolean property and return it. */
vt_gkey[0].string = "Globals";
vt_gkey[1].string = test_expression + 1;
retval = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
break;
}
default:
sc_fatal("parse_test_expression:"
" bad expression, %s\n", test_expression + 1);
}
if (parse_trace)
sc_trace("Parse: expression is %s\n", retval ? "true" : "false");
return retval;
}
static void parse_expression(CONTEXT, const sc_char *expression) {
sc_char test_expression[PARSE_TEMP_LENGTH];
sc_bool is_present;
if (parse_trace)
sc_trace("Parse: entering expression %s\n", expression);
/* Isolate the test part of the expression. */
if (sscanf(expression, "?%[^:]", test_expression) != 1)
sc_fatal("parse_expression: bad expression, %s\n", expression);
/* Handle the remainder of the expression only if test passes. */
is_present = (test_expression[0] == PARSE_EXPRESSION_NOT)
? !parse_test_expression(test_expression + 1)
: parse_test_expression(test_expression);
if (is_present) {
sc_int next;
/*
* Following the ':' may be a single element, or a comma-separated list.
*/
for (next = strlen(test_expression) + 2; expression[next] != NUL;) {
sc_char element[PARSE_TEMP_LENGTH];
/* Get the next individual element to parse. */
if (sscanf(expression + next, "%[^,]", element) != 1)
sc_fatal("parse_expression: bad list, %s\n", expression + next);
/* Parse this isolated element. */
CALL1(parse_element, element);
/* Advance to the start of the next element. */
next += strlen(element);
next += strspn(expression + next, ",");
}
}
if (parse_trace)
sc_trace("Parse: leaving expression %s\n", expression);
}
/*
* parse_read_multiline()
*
* Helper for parse_terminal(), reads in a multiline string. The return
* string is malloc'ed, and the caller needs to handle that.
*/
static sc_char *parse_read_multiline(CONTEXT) {
const sc_byte *separator = nullptr;
const sc_char *line;
sc_char *multiline;
/* Select the appropriate multiline separator. */
switch (taf_get_version(parse_taf)) {
case TAF_VERSION_400:
separator = V400_SEPARATOR;
break;
case TAF_VERSION_390:
separator = V390_SEPARATOR;
break;
case TAF_VERSION_380:
separator = V380_SEPARATOR;
break;
default:
sc_fatal("parse_read_multiline: invalid TAF file version\n");
break;
}
/* Take a simple copy of the first line. */
R0FUNC0(parse_get_taf_string, line);
size_t ln = strlen(line) + 1;
multiline = (sc_char *)sc_malloc(ln);
Common::strcpy_s(multiline, ln, line);
/* Now concatenate until separator found. */
R0FUNC0(parse_get_taf_string, line);
while (memcmp(line, separator, SEPARATOR_SIZE) != 0) {
ln = strlen(multiline) + strlen(line) + 2;
multiline = (sc_char *)sc_realloc(multiline, ln);
Common::strcat_s(multiline, ln, "\n");
Common::strcat_s(multiline, ln, line);
R0FUNC0(parse_get_taf_string, line);
}
return multiline;
}
/*
* parse_terminal()
*
* Common handler for string, integer, boolean, and multiline parse terminals.
*/
static void parse_terminal(CONTEXT, const sc_char *terminal) {
sc_vartype_t vt_key, vt_value;
if (parse_trace)
sc_trace("Parse: entering terminal %s\n", terminal);
/* Push the key string. */
vt_key.string = terminal + 1;
parse_push_key(vt_key, PROP_KEY_STRING);
/* Retrieve, or invent, then store the value. */
switch (terminal[0]) {
case PARSE_INTEGER:
FUNC0(parse_get_taf_integer, vt_value.integer);
parse_put_property(vt_value, PROP_INTEGER);
break;
case PARSE_DEFAULT_ZERO:
vt_value.integer = 0;
parse_put_property(vt_value, PROP_INTEGER);
break;
case PARSE_BOOLEAN:
FUNC0(parse_get_taf_boolean, vt_value.boolean);
parse_put_property(vt_value, PROP_BOOLEAN);
break;
case PARSE_DEFAULT_FALSE:
case PARSE_DEFAULT_TRUE:
vt_value.boolean = (terminal[0] == PARSE_DEFAULT_TRUE);
parse_put_property(vt_value, PROP_BOOLEAN);
break;
case PARSE_STRING:
FUNC0(parse_get_taf_string, vt_value.string);
parse_put_property(vt_value, PROP_STRING);
break;
case PARSE_DEFAULT_EMPTY:
vt_value.string = "";
parse_put_property(vt_value, PROP_STRING);
break;
case PARSE_MULTILINE:
/* Assign to and adopt mutable string rather than const string. */
FUNC0(parse_read_multiline, vt_value.mutable_string);
parse_put_property(vt_value, PROP_STRING);
assert(parse_bundle);
prop_adopt(parse_bundle, vt_value.mutable_string);
break;
case PARSE_IGNORE_INTEGER:
CALL0(parse_get_taf_integer);
break;
case PARSE_IGNORE_BOOLEAN:
CALL0(parse_get_taf_boolean);
break;
case PARSE_IGNORE_STRING:
CALL0(parse_get_taf_string);
break;
default:
sc_fatal("parse_terminal: bad type, %c\n", terminal[0]);
}
/* Pop terminal key. */
parse_pop_key();
if (parse_trace)
sc_trace("Parse: leaving terminal %s\n", terminal);
}
/*
* Resources table. This table enables resource offsets to be calculated
* for the various sound and graphic resources encountered on parsing
* version 4.0 games. It's unused if the version is not 4.0.
*/
struct sc_parse_resource_t {
sc_char *name;
sc_uint hash;
sc_int length;
sc_int offset;
};
enum { RESOURCE_GROW_INCREMENT = 32 };
static sc_int parse_resources_length = 0;
static sc_int parse_resources_size = 0;
static sc_parse_resource_t *parse_resources = nullptr;
/*
* parse_clear_v400_resources_table()
*
* Free and clear down the version 4.0 resources table.
*/
static void parse_clear_v400_resources_table(void) {
/* Free allocated memory and return to initial values. */
if (parse_resources) {
sc_int index_;
for (index_ = 0; index_ < parse_resources_length; index_++)
sc_free(parse_resources[index_].name);
sc_free(parse_resources);
parse_resources = nullptr;
}
parse_resources_length = 0;
parse_resources_size = 0;
}
/*
* parse_get_v400_resource_offset()
*
* Notes version 4.0 resource names encountered in the parse, and their
* lengths, and builds up a list of resources with their data offsets. The
* function assumes that resources are appended to the TAF file in the
* order in which they are encountered when reading through the TAF file.
*
* A warning -- this function may return a new length. Resources that
* have been seen once already have non-useful (though apparently non-zero)
* lengths; this function needs to handle that. The caller needs to compare
* length with real_length to see if that happened.
*/
static sc_int parse_get_v400_resource_offset(const sc_char *name,
sc_int length, sc_int *real_length) {
sc_char *clean_name;
sc_uint hash;
sc_int index_, offset;
/*
* Take a copy of the name, and remove any trailing "##" looping sound
* indicator flag. Who thinks this junk up?
*/
size_t ln = strlen(name) + 1;
clean_name = (sc_char *)sc_malloc(ln);
Common::strcpy_s(clean_name, ln, name);
if (strcmp(clean_name + strlen(clean_name) - 2, "##") == 0)
clean_name[strlen(clean_name) - 2] = NUL;
/*
* Scan the current resources list for a matching name, and if the resource
* is already known, return its offset. The hash check is an attempt to
* improve the search times, relative to using only string comparisons --
* the table's not fully hashed. If found, we need to also pass back the
* corrected length.
*/
offset = -1;
hash = sc_hash(clean_name);
for (index_ = 0; index_ < parse_resources_length; index_++) {
if (parse_resources[index_].hash == hash
&& strcmp(parse_resources[index_].name, clean_name) == 0) {
offset = parse_resources[index_].offset;
break;
}
}
if (offset != -1) {
*real_length = parse_resources[index_].length;
sc_free(clean_name);
return offset;
}
/* Resize the resources table if required. */
if (parse_resources_length == parse_resources_size) {
parse_resources_size += RESOURCE_GROW_INCREMENT;
parse_resources = (sc_parse_resource_t *)sc_realloc(parse_resources,
parse_resources_size *
sizeof(parse_resources[0]));
}
/*
* Calculate the offset. For the first resource, it's zero; for others,
* it's one after the prior entry's offset and length.
*/
if (parse_resources_length == 0)
offset = 0;
else {
offset = parse_resources[parse_resources_length - 1].offset
+ parse_resources[parse_resources_length - 1].length + 1;
}
/* Add details to the table. */
parse_resources[parse_resources_length].name = clean_name;
parse_resources[parse_resources_length].hash = hash;
parse_resources[parse_resources_length].offset = offset;
parse_resources[parse_resources_length].length = length;
parse_resources_length++;
*real_length = length;
return offset;
}
/*
* parse_handle_v400_resources()
*
* Extra special handling for version 4.0 resources; extracts details of
* the resource just parsed, and adds an offset property for each defined.
*
* A warning -- Adrift seems to use -ve numbers as lengths for resources
* already parsed, where TAF files include the resource. It's unclear
* what the -ve values mean, so here we ignore them and work off the
* resource file name given. This means we have to look for length not
* equal to zero, not just lengths greater than zero.
*
* TODO Work out what this means. The -ve lengths look like a form of
* 'resource number'; -(length+2) is tantalizingly close to the index into
* our parse_resources table, but not always...
*/
static void parse_handle_v400_resources(sc_bool has_sound, sc_bool has_graphics) {
sc_vartype_t vt_key, vt_value;
const sc_char *file;
sc_int length, offset;
/*
* Retrieve the file and length for the sound just parsed. If there's a
* file of non-zero length, rewrite its offset.
*/
if (has_sound) {
/* Retrieve the file and length information. */
vt_key.string = "SoundFile";
parse_push_key(vt_key, PROP_KEY_STRING);
file = parse_get_string_property();
parse_pop_key();
vt_key.string = "SoundLen";
parse_push_key(vt_key, PROP_KEY_STRING);
length = parse_get_integer_property();
parse_pop_key();
/*
* If defined and has a length, rewrite the offset, and also the length
* in case changed.
*/
if (!sc_strempty(file) && length != 0) {
sc_int real_length;
offset = parse_get_v400_resource_offset(file, length, &real_length);
vt_key.string = "SoundOffset";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = offset;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
/* Rewrite length if changed. */
if (real_length != length) {
vt_key.string = "SoundLen";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = real_length;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
}
}
/* Now do the same thing for graphics. */
if (has_graphics) {
/* Retrieve the file and length information. */
vt_key.string = "GraphicFile";
parse_push_key(vt_key, PROP_KEY_STRING);
file = parse_get_string_property();
parse_pop_key();
vt_key.string = "GraphicLen";
parse_push_key(vt_key, PROP_KEY_STRING);
length = parse_get_integer_property();
parse_pop_key();
/*
* If defined and has a length, rewrite the offset, and also the length
* in case changed.
*/
if (!sc_strempty(file) && length != 0) {
sc_int real_length;
offset = parse_get_v400_resource_offset(file, length, &real_length);
vt_key.string = "GraphicOffset";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = offset;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
/* Rewrite length if changed. */
if (real_length != length) {
vt_key.string = "GraphicLen";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = real_length;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
}
}
}
/*
* parse_special()
*
* Handler for special items that can't be described accurately, and
* therefore need careful treatment.
*/
static void parse_special(CONTEXT, const sc_char *special) {
if (parse_trace)
sc_trace("Parse: entering special %s\n", special);
/* Special handling for version 4.0 resources. */
if (strcmp(special, "{V400_RESOURCE}") == 0) {
sc_vartype_t vt_key[2];
sc_bool has_sound, has_graphics;
/* Get sound and graphics global flags. */
vt_key[0].string = "Globals";
vt_key[1].string = "Sound";
has_sound = prop_get_boolean(parse_bundle, "B<-ss", vt_key);
vt_key[1].string = "Graphics";
has_graphics = prop_get_boolean(parse_bundle, "B<-ss", vt_key);
/* Apply special handling to the resources. */
parse_handle_v400_resources(has_sound, has_graphics);
}
/* Parse a version 4.0 optional set of room exit information. */
else if (strcmp(special, "{V400_ROOM_EXIT:#Dest_#Var1_#Var2_#Var3}") == 0) {
sc_int flag;
/* Get next flag, and if true, pushback and parse. */
FUNC0(parse_get_taf_integer, flag);
if (flag != 0) {
parse_taf_pushback();
CALL1(parse_descriptor, "#Dest #Var1 #Var2 #Var3");
}
}
/* Parse version 3.9 and version 3.8 optional room exit information. */
else if (strcmp(special,
"{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}") == 0) {
sc_int flag;
/* Get next flag, and if true, pushback and parse. */
FUNC0(parse_get_taf_integer, flag);
if (flag != 0) {
parse_taf_pushback();
CALL1(parse_descriptor, "#Dest #Var1 #Var2 ZVar3");
}
}
/* Parse room lists, with optional extra room. */
else if (strcmp(special, "{ROOM_LIST0}") == 0
|| strcmp(special, "{ROOM_LIST1}") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int room_count, num_rooms, type, index_;
/* Retrieve the room list type. */
vt_key.string = "Type";
parse_push_key(vt_key, PROP_KEY_STRING);
type = parse_get_integer_property();
parse_pop_key();
/* Write remaining room list depending on the type. */
switch (type) {
case ROOMLIST_NO_ROOMS:
case ROOMLIST_ALL_ROOMS:
case ROOMLIST_NPC_PART:
break;
case ROOMLIST_ONE_ROOM:
/* Store this room as the single list entry. */
CALL1(parse_element, "#Room");
break;
case ROOMLIST_SOME_ROOMS:
/* Get count of rooms defined, add one if necessary. */
vt_key.string = "Rooms";
room_count = prop_get_child_count(parse_bundle, "I<-s", &vt_key);
if (strcmp(special, "{ROOM_LIST1}") == 0)
num_rooms = room_count + 1;
else
num_rooms = room_count;
/* Store an array of rooms flags for each room. */
vt_key.string = "Rooms";
parse_push_key(vt_key, PROP_KEY_STRING);
for (index_ = 0; index_ < num_rooms; index_++) {
sc_bool this_room;
/* Get flag for this room. */
FUNC0(parse_get_taf_boolean, this_room);
/* Store flag directly. */
vt_key.integer = index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_value.boolean = this_room;
parse_put_property(vt_value, PROP_BOOLEAN);
parse_pop_key();
}
parse_pop_key();
break;
default:
sc_fatal("parse_special: bad type, %ld\n", type);
}
}
/* Parse Parent number iff this object is an NPC part. */
else if (strcmp(special, "{OBJECT:#Parent}") == 0) {
sc_vartype_t vt_key;
sc_int type;
/* Check object's Where room list Type for NPC part. */
vt_key.string = "Where";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_key.string = "Type";
parse_push_key(vt_key, PROP_KEY_STRING);
type = parse_get_integer_property();
parse_pop_key();
parse_pop_key();
/* Get Parent if the object is part of an NPC. */
if (type == ROOMLIST_NPC_PART) {
CALL1(parse_element, "#Parent");
}
}
/* Parse a list of rooms and times for a walk. */
else if (strcmp(special, "{WALK:#Rooms_#Times}") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int num_stops, index_;
/* Obtain the count of stops in this walk. */
vt_key.string = "NumStops";
parse_push_key(vt_key, PROP_KEY_STRING);
num_stops = parse_get_integer_property();
parse_pop_key();
/* Look for a room and time for each stop. */
for (index_ = 0; index_ < num_stops; index_++) {
sc_int room, time;
/* Parse and store Rooms[index_]. */
vt_key.string = "Rooms";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_key.integer = index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
FUNC0(parse_get_taf_integer, room);
vt_value.integer = room;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
parse_pop_key();
/* Parse and store Times[index_]. */
vt_key.string = "Times";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_key.integer = index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
FUNC0(parse_get_taf_integer, time);
vt_value.integer = time;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
parse_pop_key();
}
}
/* Parse a room group variable size boolean list. */
else if (strcmp(special, "{ROOM_GROUP:[]BList}") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int num_rooms, index_, l2index_;
sc_bool in_group;
/* Get the count of rooms defined. */
vt_key.string = "Rooms";
num_rooms = prop_get_integer(parse_bundle, "I<-s", &vt_key);
/* Read a boolean for each room. */
l2index_ = 0;
for (index_ = 0; index_ < num_rooms; index_++) {
FUNC0(parse_get_taf_boolean, in_group);
/* Store raw flag as List[index_]. */
vt_key.string = "List";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_key.integer = index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_value.boolean = in_group;
parse_put_property(vt_value, PROP_BOOLEAN);
parse_pop_key();
parse_pop_key();
/* Store in-group index'es as List2[0..n]. */
if (in_group) {
vt_key.string = "List2";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_key.integer = l2index_;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_value.integer = index_;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
parse_pop_key();
l2index_++;
}
}
}
/* Error if no special handler available. */
else {
sc_fatal("parse_special: no handler for \"%s\"\n", special);
}
if (parse_trace)
sc_trace("Parse: leaving special %s\n", special);
}
/*
* parse_fixup_v390_v380_room_alt()
*
* Helper for parse_fixup_v390_v380_room_alts(). Handles creation of
* version 4.0 room alts for version 3.9 and version 3.8 games.
*/
static void parse_fixup_v390_v380_room_alt(const sc_char *m1, sc_int type,
const sc_char *resource1, const sc_char *m2, sc_int var2, const sc_char *resource2,
sc_int hide_objects, const sc_char *changed, sc_int var3, sc_int display_room) {
sc_vartype_t vt_key, vt_value, vt_gkey[2];
sc_bool has_sound, has_graphics;
sc_int alt_count;
const sc_char *soundfile1, *graphicfile1;
const sc_char *soundfile2, *graphicfile2;
/*
* Initialize resource files to empty, for cases where no resource is copied
* over from the main room (NULL resource1/2).
*/
soundfile1 = "";
graphicfile1 = "";
soundfile2 = "";
graphicfile2 = "";
/* Get sound and graphics flags, always FALSE for version 3.8. */
vt_gkey[0].string = "Globals";
vt_gkey[1].string = "Sound";
has_sound = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
vt_gkey[1].string = "Graphics";
has_graphics = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
/* Get a count of alts so far defined for the room. */
vt_key.string = "Alts";
parse_push_key(vt_key, PROP_KEY_STRING);
alt_count = parse_get_child_count();
parse_pop_key();
/*
* Lookup any resource details now, and save them. Because this is not
* version 4.0, we can ignore lengths, and set them to zero when needed.
*/
if (has_sound || has_graphics) {
if (resource1) {
vt_key.string = resource1;
parse_push_key(vt_key, PROP_KEY_STRING);
if (has_sound) {
vt_key.string = "SoundFile";
parse_push_key(vt_key, PROP_KEY_STRING);
soundfile1 = parse_get_string_property();
parse_pop_key();
}
if (has_graphics) {
vt_key.string = "GraphicFile";
parse_push_key(vt_key, PROP_KEY_STRING);
graphicfile1 = parse_get_string_property();
parse_pop_key();
}
parse_pop_key();
}
if (resource2) {
vt_key.string = resource2;
parse_push_key(vt_key, PROP_KEY_STRING);
if (has_sound) {
vt_key.string = "SoundFile";
parse_push_key(vt_key, PROP_KEY_STRING);
soundfile2 = parse_get_string_property();
parse_pop_key();
}
if (has_graphics) {
vt_key.string = "GraphicFile";
parse_push_key(vt_key, PROP_KEY_STRING);
graphicfile2 = parse_get_string_property();
parse_pop_key();
}
parse_pop_key();
}
}
/*
* Create a room alt to match data passed in. Start with the Alts string
* and the index to the alt being written. To correctly emulate the parse,
* we also have to reverse the "Alts" and the index, as parse_put_property()
* will swap them. Madness.
*/
vt_key.integer = alt_count;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_key.string = "Alts";
parse_push_key(vt_key, PROP_KEY_STRING);
/* Write M1 and Type. */
vt_key.string = "M1";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = m1;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "Type";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = type;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
/* If resources, add these as retrieved above. */
if (has_sound || has_graphics) {
vt_key.string = "Res1";
parse_push_key(vt_key, PROP_KEY_STRING);
if (has_sound) {
vt_key.string = "SoundFile";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = soundfile1;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "SoundLen";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = 0;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
if (has_graphics) {
vt_key.string = "GraphicFile";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = graphicfile1;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "GraphicLen";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = 0;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
parse_pop_key();
}
/* Write M2 and Var2. */
vt_key.string = "M2";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = m2;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "Var2";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var2;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
/* If resources, again add these as retrieved above. */
if (has_sound || has_graphics) {
vt_key.string = "Res2";
parse_push_key(vt_key, PROP_KEY_STRING);
if (has_sound) {
vt_key.string = "SoundFile";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = soundfile2;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "SoundLen";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = 0;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
if (has_graphics) {
vt_key.string = "GraphicFile";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = graphicfile2;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "GraphicLen";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = 0;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
parse_pop_key();
}
/* Finish off with the last four alt properties. */
vt_key.string = "HideObjects";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = hide_objects;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
vt_key.string = "Changed";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = changed;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
vt_key.string = "Var3";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var3;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
vt_key.string = "DisplayRoom";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = display_room;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
parse_pop_key();
parse_pop_key();
}
/* Multiplier for combination AltDesc Type and HideObject values. */
enum { V390_V380_ALT_TYPEHIDE_MULT = 10 };
/*
* parse_fixup_v390_v380_room_alts()
*
* Common helper function for parse_fixup_v390() and parse_fixup_v380(),
* converts version 3.9 and version 3.8 fixed room description alts into
* an equivalent array of version 4.0 style room alts.
*/
static void parse_fixup_v390_v380_room_alts(void) {
sc_vartype_t vt_key;
const sc_char *m1, *m2, *changed;
sc_int type, var2, hide_objects, var3, display_room;
/* Room alt invariants. */
m2 = ""; /* No else text */
changed = ""; /* No changed room name */
/*
* Create a room alt to override all others, controlled by an object
* condition and with optional object hiding.
*/
type = 2; /* Object condition */
display_room = 0; /* Override all others */
vt_key.string = "Obj";
parse_push_key(vt_key, PROP_KEY_STRING);
var3 = parse_get_integer_property();
parse_pop_key();
if (var3 > 0) {
sc_int typehideobjects;
vt_key.string = "AltDesc";
parse_push_key(vt_key, PROP_KEY_STRING);
m1 = parse_get_string_property();
parse_pop_key();
vt_key.string = "TypeHideObjects";
parse_push_key(vt_key, PROP_KEY_STRING);
typehideobjects = parse_get_integer_property();
parse_pop_key();
var2 = typehideobjects / V390_V380_ALT_TYPEHIDE_MULT;
hide_objects = typehideobjects % V390_V380_ALT_TYPEHIDE_MULT;
parse_fixup_v390_v380_room_alt(m1, type, "AltRes",
m2, var2, nullptr,
hide_objects, changed, var3,
display_room);
}
/*
* If a second task alternate description is defined, create a room alt to
* add after the main description, one that stops printing once done.
*/
type = 0; /* Task condition */
display_room = 1; /* Print after main and stop */
vt_key.string = "Task2";
parse_push_key(vt_key, PROP_KEY_STRING);
var2 = parse_get_integer_property();
parse_pop_key();
if (var2 > 0) {
vt_key.string = "AddDesc2";
parse_push_key(vt_key, PROP_KEY_STRING);
m1 = parse_get_string_property();
parse_pop_key();
var3 = 0;
hide_objects = 0;
parse_fixup_v390_v380_room_alt(m1, type, "Task2Res",
m2, var2, nullptr,
hide_objects, changed, var3,
display_room);
}
/* Do the same for any first task additional description. */
type = 0; /* Task condition */
display_room = 1; /* Print after main and stop */
vt_key.string = "Task1";
parse_push_key(vt_key, PROP_KEY_STRING);
var2 = parse_get_integer_property();
parse_pop_key();
if (var2 > 0) {
vt_key.string = "AddDesc1";
parse_push_key(vt_key, PROP_KEY_STRING);
m1 = parse_get_string_property();
parse_pop_key();
var3 = 0;
hide_objects = 0;
parse_fixup_v390_v380_room_alt(m1, type, "Task1Res",
m2, var2, nullptr,
hide_objects, changed, var3,
display_room);
}
/*
* If still printing at this point, we need a catch-all room alt that will
* print. So create one with an always true condition.
*/
type = 0; /* Task condition */
display_room = 2; /* Lowest priority output */
vt_key.string = "LastDesc";
parse_push_key(vt_key, PROP_KEY_STRING);
m1 = parse_get_string_property();
parse_pop_key();
if (!sc_strempty(m1)) {
var2 = 0; /* No task - always TRUE */
var3 = 0;
hide_objects = 0;
parse_fixup_v390_v380_room_alt(m1, type, "LastRes",
m2, var2, nullptr,
hide_objects, changed, var3,
display_room);
}
}
/*
* parse_fixup_v390()
*
* Handler for fixup special items to help with conversions from TAF version
* 3.9 format into version 4.0.
*/
static void parse_fixup_v390(CONTEXT, const sc_char *fixup) {
if (parse_trace)
sc_trace("Parse: entering version 3.9 fixup %s\n", fixup);
/* Fixup a version 3.9 task action by incrementing Type > 4. */
if (strcmp(fixup, "|V390_TASK_ACTION:Type>4?#Type++|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int type;
/* Retrieve Type, and if > 4, increment. */
vt_key.string = "Type";
parse_push_key(vt_key, PROP_KEY_STRING);
type = parse_get_integer_property();
if (type > 4) {
vt_value.integer = type + 1;
parse_put_property(vt_value, PROP_INTEGER);
}
parse_pop_key();
}
/* Handle either Expr or Var5 for version 3.9 task actions. */
else if (strcmp(fixup, "|V390_TASK_ACTION:$Expr_#Var5|") == 0) {
sc_vartype_t vt_key;
sc_int var2;
/* Either Expr or Var5, depending on Var2. */
vt_key.string = "Var2";
parse_push_key(vt_key, PROP_KEY_STRING);
var2 = parse_get_integer_property();
parse_pop_key();
if (var2 == 5) {
CALL1(parse_descriptor, "$Expr ZVar5");
} else {
CALL1(parse_descriptor, "EExpr #Var5");
}
}
/*
* Exchange openable values 5 and 6, and write -1 key for openable objects.
*/
else if (strcmp(fixup, "|V390_OBJECT:_Openable_,Key|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int openable;
/* Retrieve Openable, and if 5 or 6, exchange. */
vt_key.string = "Openable";
parse_push_key(vt_key, PROP_KEY_STRING);
openable = parse_get_integer_property();
if (openable == 5 || openable == 6) {
vt_value.integer = (openable == 5) ? 6 : 5;
parse_put_property(vt_value, PROP_INTEGER);
}
parse_pop_key();
/* For openable objects, store a Key of -1. */
if (openable == 5 || openable == 6) {
vt_key.string = "Key";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = -1;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
}
/* Create a RestrMask that 'and's all the restrictions together. */
else if (strcmp(fixup, "|V390_TASK:$RestrMask|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int restriction_count;
/* Get a count of restrictions. */
vt_key.string = "Restrictions";
parse_push_key(vt_key, PROP_KEY_STRING);
restriction_count = parse_get_child_count();
parse_pop_key();
/* Allocate and fill a new mask for these restrictions. */
if (restriction_count > 0) {
sc_char *restrmask;
sc_int index_;
size_t ln = 2 * restriction_count;
restrmask = (sc_char *)sc_malloc(ln);
Common::strcpy_s(restrmask, ln, "#");
for (index_ = 1; index_ < restriction_count; index_++)
Common::strcat_s(restrmask, ln, "A#");
vt_key.string = "RestrMask";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = restrmask;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
prop_adopt(parse_bundle, restrmask);
}
}
/*
* Increment var1 for variable restrictions to compensate for there being no
* referenced text comparison (no string variables).
*/
else if (strcmp(fixup, "|V390_TASK_RESTR:Var1>0?#Var1++|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int var1;
/* Retrieve Var1, and if greater than zero, increment. */
vt_key.string = "Var1";
parse_push_key(vt_key, PROP_KEY_STRING);
var1 = parse_get_integer_property();
if (var1 > 0) {
vt_value.integer = var1 + 1;
parse_put_property(vt_value, PROP_INTEGER);
}
parse_pop_key();
}
/* Convert version 3.9 fixed alts into a version 4.0 array. */
else if (strcmp(fixup, "|V390_ROOM:_Alts_|") == 0) {
parse_fixup_v390_v380_room_alts();
}
/* Error if no fixup special handler available. */
else {
sc_fatal("parse_fixup_v390: no handler for \"%s\"\n", fixup);
}
if (parse_trace)
sc_trace("Parse: leaving version 3.9 fixup %s\n", fixup);
}
/*
* Object surface and container masks for version 3.8 object fixup, container
* capacity conversion factor and default object sizing, and the count of
* task movements in a version 3.8 task.
*/
enum { V380_OBJ_IS_SURFACE = 2, V380_OBJ_IS_CONTAINER = 1 };
enum { V380_OBJ_CAPACITY_MULT = 10, V380_OBJ_DEFAULT_SIZE = 2 };
enum { V380_TASK_MOVEMENTS = 6 };
/*
* parse_fixup_v380_action()
*
* Helper for parse_fixup_v380(), adds a task action.
*/
static void parse_fixup_v380_action(sc_int type, sc_int var_count,
sc_int var1, sc_int var2, sc_int var3) {
sc_vartype_t vt_key, vt_value;
sc_int action_count;
/* Get a count of actions so far defined for the task. */
vt_key.string = "Actions";
parse_push_key(vt_key, PROP_KEY_STRING);
action_count = parse_get_child_count();
parse_pop_key();
/* Write actions key, reversed to emulate parse actions. */
vt_key.integer = action_count;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_key.string = "Actions";
parse_push_key(vt_key, PROP_KEY_STRING);
/* Write new action according to the given arguments. */
vt_key.string = "Type";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = type;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
vt_key.string = "Var1";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var1;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
if (var_count > 1) {
vt_key.string = "Var2";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var2;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
if (var_count > 2) {
vt_key.string = "Var3";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var3;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
parse_pop_key();
parse_pop_key();
}
/*
* parse_fixup_v380_movement()
*
* Helper for parse_fixup_v380(), converts a task movement into an action.
*/
static void parse_fixup_v380_movement(sc_int mvar1, sc_int mvar2, sc_int mvar3) {
sc_int var1;
/* If nothing was selected to move, ignore the call. */
if (mvar1 == 0)
return;
/*
* Accept only player moves into rooms. Other combinations, such as move
* player to worn by player, are unlikely. And move player to same room as
* player isn't useful.
*/
if (mvar1 == 1) {
if (mvar3 == 0 && mvar2 >= 2)
parse_fixup_v380_action(1, 3, 0, 0, mvar2 - 2);
return;
}
/*
* Convert movement var1 into action var1. Var1 is the dynamic object + 3,
* or 2 for referenced object, or 0 for all held.
*/
switch (mvar1) {
case 2:
var1 = 2;
break; /* Referenced obj */
case 3:
var1 = 0;
break; /* All held */
default:
var1 = mvar1 - 1;
break; /* Dynamic obj */
}
/* Dissect the rest of the movement. */
switch (mvar3) {
case 0: /* To room */
/*
* Convert movement var2 into action var2 and var3. Var2 is 0 for move
* to room, 6 for move to player room. Var3 is 0 for hidden, otherwise
* the room number plus one.
*/
if (mvar2 == 0) /* Hidden */
parse_fixup_v380_action(0, 3, var1, 0, 0);
else if (mvar2 == 1) /* Player room */
parse_fixup_v380_action(0, 3, var1, 6, 0);
else /* Specified room */
parse_fixup_v380_action(0, 3, var1, 0, mvar2 - 1);
break;
case 1: /* To inside */
case 2: /* To onto */
/*
* Convert movement var2 and var3 into action var3 and var2, a simple
* conversion, but check that var2 is not 'not selected' first.
*/
if (mvar2 > 0)
parse_fixup_v380_action(0, 3, var1, mvar3 + 1, mvar2 - 1);
break;
case 3: /* To held by */
case 4: /* To worn by */
/*
* Convert movement var2 and var3 into action var3 and var2, in this
* case a simple conversion, since version 4.0 task actions are close
* here.
*/
parse_fixup_v380_action(0, 3, var1, mvar3 + 1, mvar2);
break;
default:
sc_fatal("parse_fixup_v380_movement: invalid mvar3, %ld\n", mvar3);
}
}
/*
* parse_fixup_v380_restr()
*
* Helper for parse_fixup_v380(), adds a task restriction.
*/
static void parse_fixup_v380_restr(sc_int type, sc_int var_count,
sc_int var1, sc_int var2, sc_int var3, const sc_char *failmessage) {
sc_vartype_t vt_key, vt_value;
sc_int restriction_count;
/* Get a count of restrictions so far defined for the task. */
vt_key.string = "Restrictions";
parse_push_key(vt_key, PROP_KEY_STRING);
restriction_count = parse_get_child_count();
parse_pop_key();
/* Write restrictions key, reversed to emulate parse actions. */
vt_key.integer = restriction_count;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_key.string = "Restrictions";
parse_push_key(vt_key, PROP_KEY_STRING);
/* Write new restriction according to the given arguments. */
vt_key.string = "Type";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = type;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
vt_key.string = "Var1";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var1;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
if (var_count > 1) {
vt_key.string = "Var2";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var2;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
if (var_count > 2) {
vt_key.string = "Var3";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = var3;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
vt_key.string = "FailMessage";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = failmessage;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
parse_pop_key();
parse_pop_key();
}
/*
* parse_fixup_v380_obj_restr()
* parse_fixup_v380_task_restr()
* parse_fixup_v380_wear_restr()
* parse_fixup_v380_npc_restr()
* parse_fixup_v380_objroom_restr()
* parse_fixup_v380_objstate_restr()
*
* Helper handlers for parse_fixup_v380(); create task restrictions.
*/
static void parse_fixup_v380_obj_restr(sc_bool holding, sc_int holdobj, const sc_char *failmessage) {
/* Ignore if no object selected. */
if (holdobj > 0) {
sc_int var1, var2;
/*
* Create version 4.0 task restriction to check for either the
* referenced object or a dynamic object being either held or in the
* same room (visible to player).
*/
var1 = (holdobj == 1) ? 2 : holdobj + 1;
var2 = holding ? 1 : 3;
parse_fixup_v380_restr(0, 3, var1, var2, 0, failmessage);
}
}
static void parse_fixup_v380_task_restr(sc_bool tasknotdone, sc_int task, const sc_char *failmessage) {
/* Ignore if no task selected. */
if (task > 0) {
sc_int var2;
/* Create version 4.0 restriction to check task state. */
var2 = tasknotdone ? 1 : 0;
parse_fixup_v380_restr(2, 2, task, var2, 0, failmessage);
}
}
static void parse_fixup_v380_wear_restr(sc_int wearobj, const sc_char *failmessage) {
/* Ignore if no object selected. */
if (wearobj > 0) {
sc_vartype_t vt_key[3];
sc_int object_count, object, dynamic, obj_index;
/*
* Create version 4.0 restrictions for something or nothing worn by
* player.
*/
if (wearobj == 1) {
parse_fixup_v380_restr(0, 3, 1, 2, 0, failmessage);
return;
} else if (wearobj == 2) {
parse_fixup_v380_restr(0, 3, 0, 2, 0, failmessage);
return;
}
/* Get the count of objects defined. */
vt_key[0].string = "Objects";
object_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
/* Convert wearobj from worn index to object index. */
wearobj -= 2;
for (object = 0; object < object_count && wearobj > 0; object++) {
sc_bool bstatic, wearable;
vt_key[1].integer = object;
vt_key[2].string = "Static";
bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
if (!bstatic) {
vt_key[2].string = "Wearable";
wearable = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
if (wearable)
wearobj--;
}
}
obj_index = object - 1;
/* Now convert wearobj from object index to dynamic index. */
dynamic = 0;
for (object = 0; object <= obj_index; object++) {
sc_bool bstatic;
vt_key[1].integer = object;
vt_key[2].string = "Static";
bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
if (!bstatic)
dynamic++;
}
dynamic--;
/* Create version 4.0 restriction for object worn by player. */
parse_fixup_v380_restr(0, 3, dynamic + 3, 2, 0, failmessage);
}
}
static void parse_fixup_v380_npc_restr(sc_bool notinsameroom, sc_int npc,
const sc_char *failmessage) {
/* Ignore if no NPC selected. */
if (npc > 0) {
sc_int var2;
if (npc == 1) {
/* Create restriction to look for alone, or not. */
var2 = notinsameroom ? 3 : 2;
parse_fixup_v380_restr(3, 3, 0, var2, 0, failmessage);
return;
}
/* Create restriction to look for company. */
var2 = notinsameroom ? 1 : 0;
parse_fixup_v380_restr(3, 3, 0, var2, npc, failmessage);
}
}
static void parse_fixup_v380_objroom_restr(sc_int obj, sc_int objroom, const sc_char *failmessage) {
/* Ignore if no object selected. */
if (obj > 0) {
/* Create version 4.0 restriction to check object in room. */
parse_fixup_v380_restr(0, 3, obj + 1, 0, objroom, failmessage);
}
}
static void parse_fixup_v380_objstate_restr(sc_int obj, sc_int ivar1, sc_int ivar2,
const sc_char *failmessage) {
sc_vartype_t vt_key[3];
sc_int object, dynamic, var2, var3;
/* Initialize variables to avoid gcc warnings. */
var2 = -1;
var3 = -1;
/* Ignore restrictions with no "type". */
if (ivar1 == 0)
return;
/* Look for opened/closed restrictions, convert and return. */
if (ivar1 == 3 || ivar1 == 4) {
sc_int stateful;
/* Convert obj from object to openable (stateful) index. */
stateful = 0;
for (object = 0; object <= obj - 1; object++) {
sc_int openable;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Openable";
openable = prop_get_integer(parse_bundle, "I<-sis", vt_key);
if (openable > 0)
stateful++;
}
stateful--;
/*
* Create a version 4.0 restriction that checks that an object's state
* is open (var2 = 0) or closed (var2 = 1).
*/
var2 = (ivar1 == 3) ? 0 : 1;
parse_fixup_v380_restr(1, 2, stateful + 1, var2, 0, failmessage);
return;
}
/* Convert obj from object to dynamic index. */
dynamic = 0;
for (object = 0; object <= obj - 1; object++) {
sc_bool bstatic;
vt_key[0].string = "Objects";
vt_key[1].integer = object;
vt_key[2].string = "Static";
bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
if (!bstatic)
dynamic++;
}
dynamic--;
/* Create version 4.0 object location restrictions for the rest. */
switch (ivar1) {
case 1:
var2 = 4;
var3 = ivar2;
break; /* Inside */
case 2:
var2 = 5;
var3 = ivar2;
break; /* On */
case 5:
var2 = 1;
var3 = ivar2 + 1;
break; /* Held by */
case 6:
var2 = 2;
var3 = ivar2 + 1;
break; /* Worn by */
default:
sc_fatal("parse_fixup_v380_objstate_restr: invalid ivar1, %ld\n", ivar1);
}
parse_fixup_v380_restr(0, 3, dynamic + 3, var2, var3, failmessage);
}
/*
* parse_fixup_v380()
*
* Handler for fixup special items to help with conversions from TAF version
* 3.8 format into version 4.0.
*/
static void parse_fixup_v380(const sc_char *fixup) {
if (parse_trace)
sc_trace("Parse: entering version 3.8 fixup %s\n", fixup);
/* Convert container capacity attributes to version 4.0 values. */
if (strcmp(fixup, "|V380_OBJECT:#Capacity*10+2|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int surfacecontainer;
/* Get the object surface and container attributes. */
vt_key.string = "SurfaceContainer";
parse_push_key(vt_key, PROP_KEY_STRING);
surfacecontainer = parse_get_integer_property();
parse_pop_key();
/* Convert capacity from version 3.8 format to version 4.0. */
if (surfacecontainer == V380_OBJ_IS_CONTAINER) {
sc_int capacity;
vt_key.string = "Capacity";
parse_push_key(vt_key, PROP_KEY_STRING);
capacity = parse_get_integer_property();
capacity = capacity * V380_OBJ_CAPACITY_MULT + V380_OBJ_DEFAULT_SIZE;
vt_value.integer = capacity;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
}
/*
* Exchange openable values 5 and 6, watch for a possible 1 from a 3.8 game
* (interpret as 0), and write -1 key for openable objects.
*/
else if (strcmp(fixup, "|V380_OBJECT:_Openable_,Key|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int openable;
/* Retrieve Openable, and if 5 or 6, exchange. */
vt_key.string = "Openable";
parse_push_key(vt_key, PROP_KEY_STRING);
openable = parse_get_integer_property();
if (openable == 5 || openable == 6) {
vt_value.integer = (openable == 5) ? 6 : 5;
parse_put_property(vt_value, PROP_INTEGER);
}
/* If the odd value of 1, rewrite as zero. */
else if (openable == 1) {
vt_value.integer = 0;
parse_put_property(vt_value, PROP_INTEGER);
}
parse_pop_key();
/* For openable objects, store a Key of -1. */
if (openable == 5 || openable == 6) {
vt_key.string = "Key";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.integer = -1;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
}
/* Create version 4.0 task actions from a version 3.8 task. */
else if (strcmp(fixup, "|V380_TASK:_Actions_|") == 0) {
sc_vartype_t vt_key;
sc_int score;
sc_bool killsplayer, wingame;
sc_int movement;
/* Retrieve the score change for the task. */
vt_key.string = "Score";
parse_push_key(vt_key, PROP_KEY_STRING);
score = parse_get_integer_property();
parse_pop_key();
/* Create any appropriate score change action. */
if (score != 0)
parse_fixup_v380_action(4, 1, score, 0, 0);
/* Get player death and game winning flags. */
vt_key.string = "KillsPlayer";
parse_push_key(vt_key, PROP_KEY_STRING);
killsplayer = parse_get_boolean_property();
parse_pop_key();
vt_key.string = "WinGame";
parse_push_key(vt_key, PROP_KEY_STRING);
wingame = parse_get_boolean_property();
parse_pop_key();
/* Create any appropriate game ending actions. */
if (killsplayer)
parse_fixup_v380_action(6, 1, 2, 0, 0);
if (wingame)
parse_fixup_v380_action(6, 1, 0, 0, 0);
/* Handle each defined movement for the task. */
for (movement = 0; movement < V380_TASK_MOVEMENTS; movement++) {
sc_int mvar1, mvar2, mvar3;
vt_key.integer = movement;
parse_push_key(vt_key, PROP_KEY_INTEGER);
vt_key.string = "Movements";
parse_push_key(vt_key, PROP_KEY_STRING);
/* Retrieve the movement parameters. */
vt_key.string = "Var1";
parse_push_key(vt_key, PROP_KEY_STRING);
mvar1 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "Var2";
parse_push_key(vt_key, PROP_KEY_STRING);
mvar2 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "Var3";
parse_push_key(vt_key, PROP_KEY_STRING);
mvar3 = parse_get_integer_property();
parse_pop_key();
parse_pop_key();
parse_pop_key();
/* Create the corresponding task action. */
parse_fixup_v380_movement(mvar1, mvar2, mvar3);
}
}
/* Create version 4.0 task restrictions from a version 3.8 task. */
else if (strcmp(fixup, "|V380_TASK:_Restrictions_|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_bool holding, tasknotdone, notinsameroom;
sc_int holdobj1, holdobj2, holdobj3, task;
sc_int wearobj1, wearobj2, npc, obj1, obj1room, obj2;
const sc_char *holdmsg, *taskmsg, *wearmsg, *companymsg;
const sc_char *obj1msg;
sc_int restriction_count;
/* Create restrictions for objects not held or absent. */
vt_key.string = "HoldingSameRoom";
parse_push_key(vt_key, PROP_KEY_STRING);
holding = parse_get_boolean_property();
parse_pop_key();
vt_key.string = "HoldObj1";
parse_push_key(vt_key, PROP_KEY_STRING);
holdobj1 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "HoldObj2";
parse_push_key(vt_key, PROP_KEY_STRING);
holdobj2 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "HoldObj3";
parse_push_key(vt_key, PROP_KEY_STRING);
holdobj3 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "HoldMsg";
parse_push_key(vt_key, PROP_KEY_STRING);
holdmsg = parse_get_string_property();
parse_pop_key();
parse_fixup_v380_obj_restr(holding, holdobj1, holdmsg);
parse_fixup_v380_obj_restr(holding, holdobj2, holdmsg);
parse_fixup_v380_obj_restr(holding, holdobj3, holdmsg);
/* Create any task state restriction. */
vt_key.string = "TaskNotDone";
parse_push_key(vt_key, PROP_KEY_STRING);
tasknotdone = parse_get_boolean_property();
parse_pop_key();
vt_key.string = "Task";
parse_push_key(vt_key, PROP_KEY_STRING);
task = parse_get_integer_property();
parse_pop_key();
vt_key.string = "TaskMsg";
parse_push_key(vt_key, PROP_KEY_STRING);
taskmsg = parse_get_string_property();
parse_pop_key();
parse_fixup_v380_task_restr(tasknotdone, task, taskmsg);
/* Create any object not worn restrictions. */
vt_key.string = "WearObj1";
parse_push_key(vt_key, PROP_KEY_STRING);
wearobj1 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "WearObj2";
parse_push_key(vt_key, PROP_KEY_STRING);
wearobj2 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "WearMsg";
parse_push_key(vt_key, PROP_KEY_STRING);
wearmsg = parse_get_string_property();
parse_pop_key();
parse_fixup_v380_wear_restr(wearobj1, wearmsg);
parse_fixup_v380_wear_restr(wearobj2, wearmsg);
/* Check for presence/absence of NPCs restriction. */
vt_key.string = "NotInSameRoom";
parse_push_key(vt_key, PROP_KEY_STRING);
notinsameroom = parse_get_boolean_property();
parse_pop_key();
vt_key.string = "NPC";
parse_push_key(vt_key, PROP_KEY_STRING);
npc = parse_get_integer_property();
parse_pop_key();
vt_key.string = "CompanyMsg";
parse_push_key(vt_key, PROP_KEY_STRING);
companymsg = parse_get_string_property();
parse_pop_key();
parse_fixup_v380_npc_restr(notinsameroom, npc, companymsg);
/* Create any object location restriction. */
vt_key.string = "Obj1";
parse_push_key(vt_key, PROP_KEY_STRING);
obj1 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "Obj1Room";
parse_push_key(vt_key, PROP_KEY_STRING);
obj1room = parse_get_integer_property();
parse_pop_key();
vt_key.string = "Obj1Msg";
parse_push_key(vt_key, PROP_KEY_STRING);
obj1msg = parse_get_string_property();
parse_pop_key();
parse_fixup_v380_objroom_restr(obj1, obj1room, obj1msg);
/* And finally, any object state restriction. */
vt_key.string = "Obj2";
parse_push_key(vt_key, PROP_KEY_STRING);
obj2 = parse_get_integer_property();
parse_pop_key();
if (obj2 > 0) {
sc_int var1, var2;
const sc_char *obj2msg;
vt_key.string = "Obj2Var1";
parse_push_key(vt_key, PROP_KEY_STRING);
var1 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "Obj2Var2";
parse_push_key(vt_key, PROP_KEY_STRING);
var2 = parse_get_integer_property();
parse_pop_key();
vt_key.string = "Obj2Msg";
parse_push_key(vt_key, PROP_KEY_STRING);
obj2msg = parse_get_string_property();
parse_pop_key();
parse_fixup_v380_objstate_restr(obj2, var1, var2, obj2msg);
}
/* Get a count of restrictions created. */
vt_key.string = "Restrictions";
parse_push_key(vt_key, PROP_KEY_STRING);
restriction_count = parse_get_child_count();
parse_pop_key();
/* Allocate and fill a new mask for these restrictions. */
if (restriction_count > 0) {
sc_char *restrmask;
sc_int index_;
size_t ln = 2 * restriction_count;
restrmask = (sc_char *)sc_malloc(ln);
Common::strcpy_s(restrmask, ln, "#");
for (index_ = 1; index_ < restriction_count; index_++)
Common::strcat_s(restrmask, ln, "A#");
vt_key.string = "RestrMask";
parse_push_key(vt_key, PROP_KEY_STRING);
vt_value.string = restrmask;
parse_put_property(vt_value, PROP_STRING);
parse_pop_key();
prop_adopt(parse_bundle, restrmask);
}
}
/*
* Adjust dynamic object initial positions and parents (where contained
* or on surfaces) into version 4.0 range.
*/
else if (strcmp(fixup, "|V380_OBJECT:_InitialPositions_|") == 0) {
sc_vartype_t vt_key[3];
sc_int object_count, object, *object_type;
/* Get a count of objects. */
vt_key[0].string = "Objects";
object_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
/* Build an array of object container/surface types. */
object_type = (sc_int *)sc_malloc(object_count * sizeof(*object_type));
for (object = 0; object < object_count; object++) {
vt_key[1].integer = object;
vt_key[2].string = "SurfaceContainer";
object_type[object] = prop_get_integer(parse_bundle,
"I<-sis", vt_key);
}
/* Adjust each object's initial position if necessary. */
for (object = 0; object < object_count; object++) {
sc_vartype_t vt_value;
sc_bool is_static;
sc_int initialposition;
/* Ignore static objects; we only want dynamic ones. */
vt_key[1].integer = object;
vt_key[2].string = "Static";
is_static = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
if (is_static)
continue;
/* If initial position is above on/in, increment. */
vt_key[1].integer = object;
vt_key[2].string = "InitialPosition";
initialposition = prop_get_integer(parse_bundle, "I<-sis", vt_key);
if (initialposition > 2) {
vt_value.integer = initialposition + 1;
prop_put(parse_bundle, "I->sis", vt_value, vt_key);
}
/*
* If initial position is on or in, decide which, depending on the
* type of the parent. From this, expand initial position into a
* version 4.0 value.
*/
if (initialposition == 2) {
sc_int count, parent, index_;
/* Get parent container/surface index. */
vt_key[1].integer = object;
vt_key[2].string = "Parent";
count = prop_get_integer(parse_bundle, "I<-sis", vt_key);
/* Convert container/surface index. */
for (parent = 0; parent < object_count && count >= 0; parent++) {
if (object_type[parent] == V380_OBJ_IS_CONTAINER
|| object_type[parent] == V380_OBJ_IS_SURFACE)
count--;
}
parent--;
/* If parent is a surface, adjust position. */
if (object_type[parent] == V380_OBJ_IS_SURFACE) {
vt_key[2].string = "InitialPosition";
vt_value.integer = initialposition + 1;
prop_put(parse_bundle, "I->sis", vt_value, vt_key);
}
/*
* For both, adjust parent to be an object index for that type
* of object only.
*/
count = 0;
for (index_ = 0; index_ < parent; index_++) {
if (object_type[index_] == object_type[parent])
count++;
}
vt_key[2].string = "Parent";
vt_value.integer = count;
prop_put(parse_bundle, "I->sis", vt_value, vt_key);
}
}
/* Done with temporary array. */
sc_free(object_type);
}
/* Convert carry limit into version 4.0-like size and weight limits. */
else if (strcmp(fixup, "|V380_MaxSize_MaxWt_|") == 0) {
sc_vartype_t vt_key, vt_value;
sc_int maxcarried;
vt_key.string = "MaxCarried";
parse_push_key(vt_key, PROP_KEY_STRING);
maxcarried = parse_get_integer_property();
parse_pop_key();
vt_value.integer = maxcarried * V380_OBJ_CAPACITY_MULT
+ V380_OBJ_DEFAULT_SIZE;
vt_key.string = "MaxSize";
parse_push_key(vt_key, PROP_KEY_STRING);
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
vt_key.string = "MaxWt";
parse_push_key(vt_key, PROP_KEY_STRING);
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
/* Add up positive scoring tasks to arrive at max score. */
else if (strcmp(fixup, "|V380_GLOBAL:_MaxScore_|") == 0) {
sc_vartype_t vt_key[3], vt_value;
sc_int task_count, maxscore, task;
/* Get a count of tasks. */
vt_key[0].string = "Tasks";
task_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
/* Sum positive scoring tasks. */
maxscore = 0;
for (task = 0; task < task_count; task++) {
sc_int score;
vt_key[1].integer = task;
vt_key[2].string = "Score";
score = prop_get_integer(parse_bundle, "I<-sis", vt_key);
if (score > 0)
maxscore += score;
}
/* Write MaxScore global property. */
vt_key[0].string = "Globals";
vt_key[1].string = "MaxScore";
vt_value.integer = maxscore;
prop_put(parse_bundle, "I->ss", vt_value, vt_key);
}
/* Convert walk meetobject from dynamic index to object. */
else if (strcmp(fixup, "|V380_WALK:_MeetObject_|") == 0) {
sc_vartype_t vt_key, vt_value, vt_gkey[3];
sc_int meetobject, count, object_count, object;
vt_key.string = "MeetObject";
parse_push_key(vt_key, PROP_KEY_STRING);
meetobject = parse_get_integer_property();
/* Get a count of objects. */
vt_gkey[0].string = "Objects";
object_count = prop_get_child_count(parse_bundle, "I<-s", vt_gkey);
/* Convert dynamic index to object, and rewrite. */
count = meetobject - 1;
for (object = 0; object < object_count && count >= 0; object++) {
sc_bool bstatic;
vt_gkey[1].integer = object;
vt_gkey[2].string = "Static";
bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_gkey);
if (!bstatic)
count--;
}
object--;
vt_value.integer = object;
parse_put_property(vt_value, PROP_INTEGER);
parse_pop_key();
}
/* Convert version 3.8 room data into a version 4.0 alts array. */
else if (strcmp(fixup, "|V380_ROOM:_Alts_|") == 0) {
parse_fixup_v390_v380_room_alts();
}
/* Error if no fixup special handler available. */
else {
sc_fatal("parse_fixup_v380: no handler for \"%s\"\n", fixup);
}
if (parse_trace)
sc_trace("Parse: leaving version 3.8 fixup %s\n", fixup);
}
/*
* parse_fixup()
*
* Handler for fixup special items to help with conversions from TAF version
* 3.9 and version 3.8 formats into version 4.0.
*/
static void parse_fixup(CONTEXT, const sc_char *fixup) {
/*
* Pick a fixup handler specific to the TAF version. This helps keep
* fixup code separate, rather than glommed into one large function.
*/
switch (taf_get_version(parse_taf)) {
case TAF_VERSION_400:
sc_fatal("parse_fixup: unexpected call\n");
break;
case TAF_VERSION_390:
CALL1(parse_fixup_v390, fixup);
break;
case TAF_VERSION_380:
parse_fixup_v380(fixup);
break;
default:
sc_fatal("parse_fixup: invalid TAF file version\n");
break;
}
}
/*
* parse_element()
*
* Parse a class descriptor element.
*/
static void parse_element(CONTEXT, const sc_char *element) {
if (parse_trace)
sc_trace("Parse: entering element %s\n", element);
/* Determine the element type from the first character. */
switch (element[0]) {
case PARSE_ARRAY:
CALL1(parse_array, element);
break;
case PARSE_VECTOR:
CALL1(parse_vector, element);
break;
case PARSE_VECTOR_ALTERNATE:
CALL1(parse_vector_alternate, element);
break;
case PARSE_CLASS:
CALL1(parse_class, element);
break;
case PARSE_EXPRESSION:
CALL1(parse_expression, element);
break;
case PARSE_SPECIAL:
CALL1(parse_special, element);
break;
case PARSE_FIXUP:
CALL1(parse_fixup, element);
break;
case PARSE_INTEGER:
case PARSE_DEFAULT_ZERO:
case PARSE_BOOLEAN:
case PARSE_DEFAULT_TRUE:
case PARSE_DEFAULT_FALSE:
case PARSE_STRING:
case PARSE_DEFAULT_EMPTY:
case PARSE_IGNORE_INTEGER:
case PARSE_IGNORE_BOOLEAN:
case PARSE_IGNORE_STRING:
case PARSE_MULTILINE:
CALL1(parse_terminal, element);
break;
default:
sc_fatal("parse_element: bad type, %c\n", element[0]);
}
if (parse_trace)
sc_trace("Parse: leaving element %s\n", element);
}
/*
* parse_descriptor()
*
* Parse a class's properties descriptor list.
*/
static void parse_descriptor(CONTEXT, const sc_char *descriptor) {
sc_int next;
/* Find and parse each element in the descriptor. */
for (next = 0; descriptor[next] != NUL;) {
sc_char element[PARSE_TEMP_LENGTH];
/* Isolate the next descriptor element. */
if (sscanf(descriptor + next, "%[^ ]", element) != 1)
sc_fatal("parse_element: no element, %s\n", descriptor + next);
/* Parse this isolated element. */
CALL1(parse_element, element);
/* Advance over the element and any trailing whitespace. */
next += strlen(element);
next += strspn(descriptor + next, " ");
}
}
/*
* parse_class()
*
* Parse a class of properties.
*/
static void parse_class(CONTEXT, const sc_char *class_) {
sc_char class_name[PARSE_TEMP_LENGTH];
sc_int index_;
sc_vartype_t vt_key;
/* Isolate the class name. */
if (sscanf(class_, "<%[^>]", class_name) != 1)
sc_fatal("parse_class: error in class, %s\n", class_);
if (parse_trace)
sc_trace("Parse: entering class %s\n", class_name);
/* Find the class in the parse schema, and fail if not found. */
for (index_ = 0; parse_schema[index_].class_name; index_++) {
if (strcmp(parse_schema[index_].class_name, class_name) == 0)
break;
}
if (!parse_schema[index_].class_name)
sc_fatal("parse_class: class not described, %s\n", class_name);
/*
* Unless we are at the top level of the parse schema, push the class tag
* as a key. The top level is "_GAME_", index_ 0, and isn't part of key
* formation.
*/
if (index_ > 0) {
vt_key.string = class_ + strlen(class_name) + 2;
parse_push_key(vt_key, PROP_KEY_STRING);
}
/* Parse each element in the descriptor. */
CALL1(parse_descriptor, parse_schema[index_].descriptor);
/* Pop a key if the class tag was pushed above. */
if (index_ > 0)
parse_pop_key();
if (parse_trace)
sc_trace("Parse: leaving class %s\n", class_name);
}
/*
* parse_add_walkalerts()
*
* Add a list of all NPC walks started by each task. This is post-processing
* that occurs after the TAF file has been successfully parsed.
*/
static void parse_add_walkalerts(sc_prop_setref_t bundle) {
sc_vartype_t vt_key[5];
sc_int npcs_count, npc;
/* Get the count of NPCs. */
vt_key[0].string = "NPCs";
npcs_count = prop_get_child_count(bundle, "I<-s", vt_key);
/* Set up each NPC. */
for (npc = 0; npc < npcs_count; npc++) {
sc_int walk_count, walk;
/* Get NPC walk details. */
vt_key[1].integer = npc;
vt_key[2].string = "Walks";
walk_count = prop_get_child_count(bundle, "I<-sis", vt_key);
for (walk = 0; walk < walk_count; walk++) {
sc_int starttask;
/* Get start task of walk. */
vt_key[3].integer = walk;
vt_key[4].string = "StartTask";
starttask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
if (starttask >= 0) {
sc_vartype_t vt_key2[4], vt_value;
sc_int count;
/* Count existing walkalerts for the task. */
vt_key2[0].string = "Tasks";
vt_key2[1].integer = starttask;
vt_key2[2].string = "NPCWalkAlert";
count = prop_get_child_count(bundle, "I<-sis", vt_key2);
/* Add two more -- NPC and walk. */
vt_key2[3].integer = count;
vt_value.integer = npc;
prop_put(bundle, "I->sisi", vt_value, vt_key2);
vt_key2[3].integer = count + 1;
vt_value.integer = walk;
prop_put(bundle, "I->sisi", vt_value, vt_key2);
}
}
}
}
/*
* parse_add_movetimes()
*
* Add a list of move times to all NPC walks. This is post-processing that
* occurs after the TAF file has been successfully parsed.
*/
static void parse_add_movetimes(sc_prop_setref_t bundle) {
sc_vartype_t vt_key[6];
sc_int npcs_count, npc;
/* Get the count of NPCs. */
vt_key[0].string = "NPCs";
npcs_count = prop_get_child_count(bundle, "I<-s", vt_key);
/* Set up each NPC. */
for (npc = 0; npc < npcs_count; npc++) {
sc_int walk_count, walk;
/* Get NPC walk details. */
vt_key[1].integer = npc;
vt_key[2].string = "Walks";
walk_count = prop_get_child_count(bundle, "I<-sis", vt_key);
for (walk = 0; walk < walk_count; walk++) {
sc_int waittimes;
sc_int *movetimes, index_;
sc_vartype_t vt_value;
vt_key[3].integer = walk;
vt_key[4].string = "Times";
waittimes = prop_get_child_count(bundle, "I<-sisis", vt_key);
movetimes = (sc_int *)sc_malloc((waittimes + 1) * sizeof(*movetimes));
memset(movetimes, 0, (waittimes + 1) * sizeof(*movetimes));
for (index_ = waittimes - 1; index_ >= 0; index_--) {
vt_key[4].string = "Times";
vt_key[5].integer = index_;
movetimes[index_] = prop_get_integer(bundle, "I<-sisisi", vt_key)
+ movetimes[index_ + 1];
}
movetimes[waittimes] = -2;
for (index_ = 0; index_ <= waittimes; index_++) {
vt_key[4].string = "MoveTimes";
vt_key[5].integer = index_;
vt_value.integer = movetimes[index_];
prop_put(bundle, "I->sisisi", vt_value, vt_key);
}
sc_free(movetimes);
}
}
}
/*
* parse_add_alrs_index()
*
* Sort ALRs by original string length and store an indexer property, so
* that ALR replacements look at longer strings before shorter ones.
*/
static void parse_add_alrs_index(sc_prop_setref_t bundle) {
sc_vartype_t vt_key[3];
sc_int alr_count, index_, alr;
sc_int *alr_lengths, longest, shortest, length;
/* Count ALRs, and set invariant part of properties key. */
vt_key[0].string = "ALRs";
alr_count = prop_get_child_count(bundle, "I<-s", vt_key);
/*
* Set up an array of the lengths of ALR original strings, and while at it,
* get the shortest and longest defined.
*/
alr_lengths = (sc_int *)sc_malloc(alr_count * sizeof(*alr_lengths));
shortest = INTEGER_MAX;
longest = 0;
for (index_ = 0; index_ < alr_count; index_++) {
const sc_char *original;
vt_key[1].integer = index_;
vt_key[2].string = "Original";
original = prop_get_string(bundle, "S<-sis", vt_key);
length = strlen(original);
alr_lengths[index_] = length;
shortest = (length < shortest) ? length : shortest;
longest = (length > longest) ? length : longest;
}
/*
* Now write a set of secondary properties that define the order of handling
* for ALRs. Our friend qsort() can't help here as it doesn't define the
* final ordering of equal members, and we need here to retain file ordering
* for ALR originals of the same length.
*/
vt_key[0].string = "ALRs2";
alr = 0;
for (length = longest; length >= shortest; length--) {
/* Find and add each ALR of this length. */
for (index_ = 0; index_ < alr_count; index_++) {
if (alr_lengths[index_] == length) {
sc_vartype_t vt_value;
vt_key[1].integer = alr++;
vt_key[2].string = "ALRIndex";
vt_value.integer = index_;
prop_put(bundle, "I->sis", vt_value, vt_key);
}
}
}
assert(alr == alr_count);
/* Done with ALR lengths array. */
sc_free(alr_lengths);
}
/*
* parse_add_resources_offset()
*
* Add the resources offset to the properties as an extra game property
* for version 4.0 games. For version 3.9 and version 3.8 games, write
* zero; only version 4.0 games can embed their resources into the TAF file.
*/
static void parse_add_resources_offset(sc_prop_setref_t bundle, sc_tafref_t taf) {
sc_vartype_t vt_key[2], vt_value;
sc_bool embedded;
sc_int offset;
/*
* Get the resources offset from the TAF, or default to zero. The resources
* offset is one byte after the end of game data.
*/
vt_key[0].string = "Globals";
vt_key[1].string = "Embedded";
embedded = prop_get_boolean(bundle, "B<-ss", vt_key);
offset = embedded ? taf_get_game_data_length(taf) + 1 : 0;
/* Add this offset to the properties. */
vt_key[0].string = "ResourceOffset";
vt_value.integer = offset;
prop_put(bundle, "I->s", vt_value, vt_key);
}
/*
* parse_add_version()
*
* Add the TAF version to the properties, both integer and character forms
* for convenience.
*/
static void parse_add_version(sc_prop_setref_t bundle, sc_tafref_t taf) {
sc_vartype_t vt_key, vt_value;
/* Add the version integer to the properties. */
vt_key.string = "Version";
vt_value.integer = taf_get_version(taf);
prop_put(bundle, "I->s", vt_value, &vt_key);
/* Add the version string to the properties. */
switch (taf_get_version(taf)) {
case TAF_VERSION_400:
vt_value.string = "4.00";
break;
case TAF_VERSION_390:
vt_value.string = "3.90";
break;
case TAF_VERSION_380:
vt_value.string = "3.80";
break;
default:
sc_error("parse_add_version_string: invalid TAF file version\n");
vt_value.string = "[Unknown version]";
break;
}
vt_key.string = "VersionString";
prop_put(bundle, "S->s", vt_value, &vt_key);
}
/*
* parse_game()
*
* Parse a game into a set properties. Return TRUE on success, FALSE if
* it encountered an error reading the TAF file.
*/
sc_bool parse_game(sc_tafref_t taf, sc_prop_setref_t bundle) {
assert(taf && bundle);
Context context;
/* Store the TAF to read from, and the bundle to store into. */
parse_taf = taf;
parse_bundle = bundle;
parse_schema = parse_select_schema(parse_taf);
parse_depth = 0;
// Try parsing a complete game
taf_first_line(parse_taf);
parse_tafline = 0;
parse_class(context, "<_GAME_>");
if (context._break) {
// Error with one of the TAF file lines
parse_clear_v400_resources_table();
parse_taf = nullptr;
parse_bundle = nullptr;
parse_schema = nullptr;
parse_depth = 0;
return FALSE;
}
/* Free the accumulated version 4.0 resources details. */
parse_clear_v400_resources_table();
/* See if we reached the end of the TAF. */
if (taf_more_lines(parse_taf))
sc_error("parse_game: unexpected trailing data\n");
/* Append post-processing walkalerts and move times. */
parse_add_walkalerts(parse_bundle);
parse_add_movetimes(parse_bundle);
/* Append sorted ALR list and resources offset. */
parse_add_alrs_index(parse_bundle);
parse_add_resources_offset(parse_bundle, parse_taf);
/* Add a note of the TAF file version. */
parse_add_version(parse_bundle, parse_taf);
/* Trim excess allocations from properties. */
prop_solidify(parse_bundle);
/* Return successfully. */
parse_taf = nullptr;
parse_bundle = nullptr;
parse_schema = nullptr;
parse_depth = 0;
return TRUE;
}
/*
* parse_debug_trace()
*
* Set parse tracing on/off.
*/
void parse_debug_trace(sc_bool flag) {
parse_trace = flag;
}
} // End of namespace Adrift
} // End of namespace Glk