1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "glk/adrift/scare.h"
24 #include "glk/adrift/scprotos.h"
25 #include "glk/jumps.h"
26
27 namespace Glk {
28 namespace Adrift {
29
30 /*
31 * Module notes:
32 *
33 * o Adds new "types" to jAsea's property descriptor: 'M' for multiline
34 * strings, 'Z', 'F'/'T', and 'E' for defaulted integers, booleans, and
35 * strings, 'i', 'b', and 's' for ignored integers, booleans, and strings,
36 * and '{...}' and '|...|' for "special" descriptions and version fixups
37 * that can't be described as things stand.
38 *
39 * o Adds new 'G' expression test, to check Global boolean.
40 *
41 * o The stack "adjustment" stuff is a bit of a bother.
42 */
43
44 /* Assorted definitions and constants. */
45 static const sc_char NUL = '\0';
46 enum {
47 PARSE_TEMP_LENGTH = 256,
48 PARSE_MAX_DEPTH = 32
49 };
50
51 /* Multiline separator sequences for the various versions supported. */
52 enum { SEPARATOR_SIZE = 3 };
53 static const sc_byte V400_SEPARATOR[SEPARATOR_SIZE] = {0xbd, 0xd0, 0x00};
54 static const sc_byte V390_SEPARATOR[SEPARATOR_SIZE] = {0x2a, 0x2a, 0x00};
55 static const sc_byte V380_SEPARATOR[SEPARATOR_SIZE] = {0x2a, 0x2a, 0x00};
56
57
58 /*
59 * Tables of properties descriptors. These strings define the structure of
60 * a TAF file. Field keys are:
61 *
62 * $,#,B,M - string, integer, boolean, and multiline properties
63 * E,F,T,Z - string, integer, and boolean properties not in the TAF;
64 * set to "", FALSE, TRUE and zero on parsing (version < 4)
65 * i,s,b - string, integer, and boolean in the TAF, but not stored
66 * [num] - arrays of property, fixed size to num
67 * V - variable sized array of property, size in input file
68 * W - like V, but size - 1 in input file (version < 4)
69 * <class> - class of property, separate parse target (recurse)
70 * ?[!]expr: - conditional property based on expr
71 * G[!]expr: - conditional property based on expr using globals
72 * |...| - fixup specials for versions < 4
73 * {special} - because some things just defy description
74 */
75 struct sc_parse_schema_t {
76 const sc_char *const class_name;
77 const sc_char *const descriptor;
78 };
79
80 /* Version 4.0 TAF file properties descriptor table. */
81 static const sc_parse_schema_t V400_PARSE_SCHEMA[] = {
82 {
83 "_GAME_",
84 "<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
85 " V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
86 " V<VARIABLE>Variables V<ALR>ALRs BCustomFont ?BCustomFont:$FontNameSize"
87 " $CompileDate"
88 },
89 {
90 "HEADER",
91 "MStartupText #StartRoom MWinText"
92 },
93 {
94 "GLOBAL",
95 "$GameName $GameAuthor $DontUnderstand #Perspective BShowExits #WaitTurns"
96 " BDispFirstRoom BBattleSystem #MaxScore $PlayerName BPromptName $PlayerDesc"
97 " #Task ?!#Task=0:$AltDesc #Position #ParentObject #PlayerGender"
98 " #MaxSize #MaxWt ?GBattleSystem:<BATTLE>Battle BEightPointCompass bNoDebug"
99 " BNoScoreNotify BNoMap bNoAutoComplete bNoControlPanel bNoMouse BSound"
100 " BGraphics <RESOURCE>IntroRes <RESOURCE>WinRes BStatusBox $StatusBoxText"
101 " iUnk1 iUnk2 BEmbedded"
102 },
103 {
104 "BATTLE",
105 "iStaminaLo iStaminaHi iStrengthLo iStrengthHi iAccuracyLo iAccuracyHi"
106 " iDefenseLo iDefenseHi iAgilityLo iAgilityHi iRecovery"
107 },
108 {
109 "ROOM",
110 "$Short $Long ?GEightPointCompass:[12]<ROOM_EXIT>Exits"
111 " ?!GEightPointCompass:[8]<ROOM_EXIT>Exits <RESOURCE>Res V<ROOM_ALT>Alts"
112 " ?!GNoMap:bHideOnMap"
113 },
114 {
115 "ROOM_EXIT",
116 "{V400_ROOM_EXIT:#Dest_#Var1_#Var2_#Var3}"
117 },
118 {
119 "ROOM_ALT",
120 "$M1 #Type <RESOURCE>Res1 $M2 #Var2 <RESOURCE>Res2 #HideObjects $Changed"
121 " #Var3 #DisplayRoom"
122 },
123 {
124 "RESOURCE",
125 "?GSound:$SoundFile,#SoundLen,ZSoundOffset"
126 " ?GGraphics:$GraphicFile,#GraphicLen,ZGraphicOffset {V400_RESOURCE}"
127 },
128 {
129 "OBJECT",
130 "$Prefix $Short V$Alias BStatic $Description #InitialPosition #Task"
131 " BTaskNotDone $AltDesc ?BStatic:<ROOM_LIST1>Where BContainer BSurface"
132 " #Capacity ?!BStatic:BWearable,#SizeWeight,#Parent"
133 " ?BStatic:{OBJECT:#Parent} #Openable ?#Openable=5:#Key ?#Openable=6:#Key"
134 " ?#Openable=7:#Key #SitLie ?!BStatic:BEdible BReadable ?BReadable:$ReadText"
135 " ?!BStatic:BWeapon #CurrentState ?!#CurrentState=0:$States,BStateListed"
136 " BListFlag <RESOURCE>Res1 <RESOURCE>Res2 ?GBattleSystem:<OBJ_BATTLE>Battle"
137 " $InRoomDesc #OnlyWhenNotMoved"
138 },
139 {
140 "OBJ_BATTLE",
141 "iProtectionValue iHitValue iMethod iAccuracy"
142 },
143 {
144 "ROOM_LIST1",
145 "#Type {ROOM_LIST1}"
146 },
147 {
148 "TASK",
149 "V$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
150 " #ShowRoomDesc BRepeatable BReversible V$ReverseCommand <ROOM_LIST0>Where"
151 " $Question ?$Question:$Hint1,$Hint2 V<TASK_RESTR>Restrictions"
152 " V<TASK_ACTION>Actions $RestrMask <RESOURCE>Res"
153 },
154 {
155 "TASK_RESTR",
156 "#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2 ?#Type=2:#Var1,#Var2"
157 " ?#Type=3:#Var1,#Var2,#Var3 ?#Type=4:#Var1,#Var2,#Var3,$Var4 $FailMessage"
158 },
159 {
160 "TASK_ACTION",
161 "#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2,#Var3"
162 " ?#Type=2:#Var1,#Var2 ?#Type=3:#Var1,#Var2,#Var3,$Expr,#Var5"
163 " ?#Type=4:#Var1 ?#Type=5:#Var1,#Var2 ?#Type=6:#Var1,#Var2,#Var3"
164 " ?#Type=7:iVar1,iVar2,iVar3"
165 },
166 {
167 "ROOM_LIST0",
168 "#Type {ROOM_LIST0}"
169 },
170 {
171 "EVENT",
172 "$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
173 " ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
174 " $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
175 " BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
176 " #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
177 " #TaskAffected [5]<RESOURCE>Res"
178 },
179 {
180 "NPC",
181 "$Name $Prefix V$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
182 " V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
183 " $InRoomText #Gender [4]<RESOURCE>Res ?GBattleSystem:<NPC_BATTLE>Battle"
184 },
185 {
186 "NPC_BATTLE",
187 "iAttitude iStaminaLo iStaminaHi iStrengthLo iStrengthHi iAccuracyLo"
188 " iAccuracyHi iDefenseLo iDefenseHi iAgilityLo iAgilityHi iSpeed"
189 " iKilledTask iRecovery iStaminaTask"
190 },
191 {
192 "TOPIC",
193 "$Subject $Reply #Task $AltReply"
194 },
195 {
196 "WALK",
197 "#NumStops BLoop #StartTask #CharTask #MeetObject #ObjectTask #StoppingTask"
198 " #MeetChar $ChangedDesc {WALK:#Rooms_#Times}"
199 },
200 {
201 "ROOM_GROUP",
202 "$Name {ROOM_GROUP:[]BList}"
203 },
204 {
205 "SYNONYM",
206 "$Replacement $Original"
207 },
208 {
209 "VARIABLE",
210 "$Name #Type $Value"
211 },
212 {
213 "ALR",
214 "$Original $Replacement"
215 },
216 {NULL, NULL}
217 };
218
219 /* Version 3.9 TAF file properties descriptor table. */
220 static const sc_parse_schema_t V390_PARSE_SCHEMA[] = {
221 {
222 "_GAME_",
223 "<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
224 " V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
225 " V<VARIABLE>Variables V<ALR>ALRs BCustomFont ?BCustomFont:$FontNameSize"
226 " $CompileDate sPassword"
227 },
228 {
229 "HEADER",
230 "MStartupText #StartRoom MWinText"
231 },
232 {
233 "GLOBAL",
234 "$GameName $GameAuthor $DontUnderstand #Perspective BShowExits #WaitTurns"
235 " BDispFirstRoom BBattleSystem #MaxScore $PlayerName BPromptName $PlayerDesc"
236 " #Task ?!#Task=0:$AltDesc #Position #ParentObject #PlayerGender"
237 " #MaxSize #MaxWt ?GBattleSystem:<BATTLE>Battle BEightPointCompass bNoDebug"
238 " BNoScoreNotify BNoMap bNoAutoComplete bNoControlPanel bNoMouse"
239 " BSound BGraphics <RESOURCE>IntroRes <RESOURCE>WinRes FStatusBox"
240 " EStatusBoxText iUnk1 iUnk2 FEmbedded"
241 },
242 {
243 "BATTLE",
244 "iStamina iStrength iDefense"
245 },
246 {
247 "ROOM",
248 "$Short $Long $LastDesc ?GEightPointCompass:[12]<ROOM_EXIT>Exits"
249 " ?!GEightPointCompass:[8]<ROOM_EXIT>Exits $AddDesc1 #Task1 $AddDesc2 #Task2"
250 " #Obj $AltDesc #TypeHideObjects <RESOURCE>Res <RESOURCE>LastRes"
251 " <RESOURCE>Task1Res <RESOURCE>Task2Res <RESOURCE>AltRes"
252 " ?!GNoMap:bHideOnMap |V390_ROOM:_Alts_|"
253 },
254 {
255 "ROOM_EXIT",
256 "{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}"
257 },
258 {
259 "RESOURCE",
260 "?GSound:$SoundFile,ZSoundLen,ZSoundOffset"
261 " ?GGraphics:$GraphicFile,ZGraphicLen,ZGraphicOffset"
262 },
263 {
264 "OBJECT",
265 "$Prefix $Short"
266 " [1]$Alias BStatic $Description #InitialPosition #Task BTaskNotDone"
267 " $AltDesc ?BStatic:<ROOM_LIST1>Where BContainer BSurface #Capacity"
268 " ?!BStatic:BWearable,#SizeWeight,#Parent ?BStatic:{OBJECT:#Parent}"
269 " #Openable |V390_OBJECT:_Openable_,Key| #SitLie ?!BStatic:BEdible BReadable"
270 " ?BReadable:$ReadText ?!BStatic:BWeapon ZCurrentState FListFlag"
271 " <RESOURCE>Res1 <RESOURCE>Res2 ?GBattleSystem:<OBJ_BATTLE>Battle"
272 " EInRoomDesc ZOnlyWhenNotMoved"
273 },
274 {
275 "OBJ_BATTLE",
276 "iProtectionValue iHitValue iMethod"
277 },
278 {
279 "ROOM_LIST1",
280 "#Type {ROOM_LIST1}"
281 },
282 {
283 "TASK",
284 "W$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
285 " #ShowRoomDesc BRepeatable BReversible W$ReverseCommand <ROOM_LIST0>Where"
286 " $Question ?$Question:$Hint1,$Hint2 V<TASK_RESTR>Restrictions"
287 " V<TASK_ACTION>Actions |V390_TASK:$RestrMask| <RESOURCE>Res"
288 },
289 {
290 "TASK_RESTR",
291 "#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2 ?#Type=2:#Var1,#Var2"
292 " ?#Type=3:#Var1,#Var2,#Var3 ?#Type=4:#Var1,#Var2,#Var3,EVar4"
293 ",|V390_TASK_RESTR:Var1>0?#Var1++| $FailMessage"
294 },
295 {
296 "TASK_ACTION",
297 "#Type |V390_TASK_ACTION:Type>4?#Type++| ?#Type=0:#Var1,#Var2,#Var3"
298 " ?#Type=1:#Var1,#Var2,#Var3 ?#Type=2:#Var1,#Var2"
299 " ?#Type=3:#Var1,#Var2,#Var3,|V390_TASK_ACTION:$Expr_#Var5|"
300 " ?#Type=4:#Var1 ?#Type=6:#Var1,ZVar2,ZVar3 ?#Type=7:iVar1,iVar2,iVar3"
301 },
302 {
303 "ROOM_LIST0",
304 "#Type {ROOM_LIST0}"
305 },
306 {
307 "EVENT",
308 "$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
309 " ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
310 " $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
311 " BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
312 " #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
313 " #TaskAffected [5]<RESOURCE>Res"
314 },
315 {
316 "NPC",
317 "$Name $Prefix [1]$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
318 " V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
319 " $InRoomText #Gender [4]<RESOURCE>Res ?GBattleSystem:<NPC_BATTLE>Battle"
320 },
321 {
322 "NPC_BATTLE",
323 "iAttitude iStamina iStrength iDefense iSpeed iKilledTask"
324 },
325 {
326 "TOPIC",
327 "$Subject $Reply #Task $AltReply"
328 },
329 {
330 "WALK",
331 "#NumStops BLoop #StartTask #CharTask #MeetObject #ObjectTask #StoppingTask"
332 " ZMeetChar $ChangedDesc {WALK:#Rooms_#Times}"
333 },
334 {
335 "ROOM_GROUP",
336 "$Name {ROOM_GROUP:[]BList}"
337 },
338 {
339 "SYNONYM",
340 "$Replacement $Original"
341 },
342 {
343 "VARIABLE",
344 "$Name ZType $Value"
345 },
346 {
347 "ALR",
348 "$Original $Replacement"
349 },
350 {NULL, NULL}
351 };
352
353 /* Version 3.8 TAF file properties descriptor table. */
354 static const sc_parse_schema_t V380_PARSE_SCHEMA[] = {
355 {
356 "_GAME_",
357 "<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
358 " V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
359 " FCustomFont $CompileDate sPassword |V380_GLOBAL:_MaxScore_|"
360 " |V380_OBJECT:_InitialPositions_|"
361 },
362 {
363 "HEADER",
364 "MStartupText #StartRoom MWinText"
365 },
366 {
367 "GLOBAL",
368 "$GameName $GameAuthor #MaxCarried |V380_MaxSize_MaxWt_| $DontUnderstand"
369 " #Perspective BShowExits #WaitTurns FDispFirstRoom FBattleSystem"
370 " EPlayerName FPromptName EPlayerDesc ZTask ZPosition ZParentObject"
371 " ZPlayerGender FEightPointCompass TNoScoreNotify FSound FGraphics"
372 " FStatusBox EStatusBoxText FEmbedded"
373 },
374 {
375 "ROOM",
376 "$Short $Long $LastDesc [8]<ROOM_EXIT>Exits $AddDesc1 #Task1 $AddDesc2"
377 " #Task2 #Obj $AltDesc #TypeHideObjects |V380_ROOM:_Alts_|"
378 },
379 {
380 "ROOM_EXIT",
381 "{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}"
382 },
383 {
384 "OBJECT",
385 "$Prefix $Short [1]$Alias BStatic $Description #InitialPosition #Task"
386 " BTaskNotDone $AltDesc ?BStatic:<ROOM_LIST1>Where #SurfaceContainer"
387 " FSurface ?#SurfaceContainer=2:TSurface FContainer"
388 " ?#SurfaceContainer=1:TContainer #Capacity |V380_OBJECT:#Capacity*10+2|"
389 " ?!BStatic:BWearable,#SizeWeight,#Parent ?BStatic:{OBJECT:#Parent}"
390 " #Openable |V380_OBJECT:_Openable_,Key| #SitLie ?!BStatic:BEdible BReadable"
391 " ?BReadable:$ReadText ?!BStatic:BWeapon ZCurrentState FListFlag"
392 " EInRoomDesc ZOnlyWhenNotMoved"
393 },
394 {
395 "ROOM_LIST1",
396 "#Type {ROOM_LIST1}"
397 },
398 {
399 "TASK",
400 "W$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
401 " #ShowRoomDesc BRepeatable #Score BSingleScore [6]<TASK_MOVE>Movements"
402 " BReversible W$ReverseCommand #WearObj1 #WearObj2 #HoldObj1 #HoldObj2"
403 " #HoldObj3 #Obj1 #Task BTaskNotDone $TaskMsg $HoldMsg $WearMsg $CompanyMsg"
404 " BNotInSameRoom #NPC $Obj1Msg #Obj1Room <ROOM_LIST0>Where BKillsPlayer"
405 " BHoldingSameRoom $Question ?$Question:$Hint1,$Hint2 #Obj2"
406 " ?!#Obj2=0:#Obj2Var1,#Obj2Var2,$Obj2Msg BWinGame |V380_TASK:_Actions_|"
407 " |V380_TASK:_Restrictions_|"
408 },
409 {
410 "TASK_MOVE",
411 "#Var1 #Var2 #Var3"
412 },
413 {
414 "ROOM_LIST0",
415 "#Type {ROOM_LIST0}"
416 },
417 {
418 "EVENT",
419 "$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
420 " ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
421 " $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
422 " BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
423 " #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
424 " #TaskAffected"
425 },
426 {
427 "NPC",
428 "$Name $Prefix [1]$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
429 " V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
430 " $InRoomText ZGender"
431 },
432 {
433 "TOPIC",
434 "$Subject $Reply #Task $AltReply"
435 },
436 {
437 "WALK",
438 "#NumStops BLoop #StartTask #CharTask #MeetObject"
439 " ?!#MeetObject=0:|V380_WALK:_MeetObject_| #ObjectTask ZMeetChar"
440 " {WALK:#Rooms_#Times} ZStoppingTask EChangedDesc"
441 },
442 {
443 "ROOM_GROUP",
444 "$Name {ROOM_GROUP:[]BList}"
445 },
446 {
447 "SYNONYM",
448 "$Replacement $Original"
449 },
450 {NULL, NULL}
451 };
452
453
454 /*
455 * parse_select_schema()
456 *
457 * Select one of the parse schemata based on a TAF file.
458 */
parse_select_schema(sc_tafref_t taf)459 static const sc_parse_schema_t *parse_select_schema(sc_tafref_t taf) {
460 /* Switch based on the TAF file version. */
461 switch (taf_get_version(taf)) {
462 case TAF_VERSION_400:
463 return V400_PARSE_SCHEMA;
464 case TAF_VERSION_390:
465 return V390_PARSE_SCHEMA;
466 case TAF_VERSION_380:
467 return V380_PARSE_SCHEMA;
468 default:
469 sc_fatal("parse_select_schema: invalid TAF file version\n");
470 return NULL;
471 }
472 }
473
474
475 /* The uncompressed TAF file from which we get all our data. */
476 static sc_tafref_t parse_taf = NULL;
477 static sc_int parse_tafline = 0;
478
479 /* The parse schema selected for this TAF file. */
480 static sc_parse_schema_t const *parse_schema = NULL;
481
482 /* Properties bundle and trace flag, set before parsing. */
483 static sc_prop_setref_t parse_bundle = NULL;
484 static sc_bool parse_trace = FALSE;
485
486 /*
487 * Stack of property keys. The stack is filled by parsing, and written
488 * to the property store on parse terminals.
489 */
490 static sc_vartype_t parse_vt_key[PARSE_MAX_DEPTH];
491 static sc_char parse_format[PARSE_MAX_DEPTH];
492 static sc_int parse_depth = 0;
493
494
495 /*
496 * parse_push_key()
497 * parse_pop_key()
498 *
499 * Push a key of the given type onto the property key stack, and pop a key
500 * off on unwind.
501 */
parse_push_key(sc_vartype_t vt_key,sc_char type)502 static void parse_push_key(sc_vartype_t vt_key, sc_char type) {
503 if (parse_depth == PARSE_MAX_DEPTH)
504 sc_fatal("parse_push_key: stack overrun\n");
505
506 /* Push the key, and its associated type. */
507 parse_vt_key[parse_depth] = vt_key;
508 parse_format[parse_depth] = type;
509 parse_depth++;
510 }
511
parse_pop_key(void)512 static void parse_pop_key(void) {
513 /* Check the stack has something to pop, then pop it. */
514 if (parse_depth == 0)
515 sc_fatal("parse_pop_key: stack underrun\n");
516 parse_depth--;
517 }
518
519
520 /*
521 * parse_retrieve_stack()
522 *
523 * This is ugly. The parse produces indexes before the things that they
524 * index. An expedient fix is to switch i-s keys before storing a property
525 * value
526 */
parse_retrieve_stack(sc_char format[],sc_vartype_t vt_key[],sc_int * depth)527 static void parse_retrieve_stack(sc_char format[], sc_vartype_t vt_key[], sc_int *depth) {
528 sc_int index_;
529
530 /* Switch index-string key pairs. */
531 for (index_ = 0; index_ < parse_depth; index_++) {
532 if (index_ < parse_depth - 1
533 && parse_format[index_] == PROP_KEY_INTEGER
534 && parse_format[index_ + 1] == PROP_KEY_STRING) {
535 /* Swap format and key elements. */
536 format[index_] = parse_format[index_ + 1];
537 format[index_ + 1] = parse_format[index_];
538 vt_key[index_] = parse_vt_key[index_ + 1];
539 vt_key[index_ + 1] = parse_vt_key[index_];
540
541 index_++;
542 } else {
543 /* Simple copy of format and key elements. */
544 format[index_] = parse_format[index_];
545 vt_key[index_] = parse_vt_key[index_];
546 }
547 }
548
549 /* Return the parse depth. */
550 *depth = parse_depth;
551 }
552
553
554 /*
555 * parse_stack_backtrace()
556 *
557 * Dump the parse stack. Used for diagnostics on finding what we think may
558 * be a bad game.
559 */
parse_stack_backtrace(void)560 static void parse_stack_backtrace(void) {
561 sc_vartype_t vt_key[PARSE_MAX_DEPTH];
562 sc_char format[PARSE_MAX_DEPTH];
563 sc_int depth, index_;
564
565 parse_retrieve_stack(format, vt_key, &depth);
566
567 sc_error("parse_stack_backtrace: version %s schema parsed to depth %ld\n",
568 (parse_schema == V400_PARSE_SCHEMA) ? "4.00" :
569 (parse_schema == V390_PARSE_SCHEMA) ? "3.90" :
570 (parse_schema == V380_PARSE_SCHEMA) ? "3.80" : "[Invalid]",
571 depth);
572
573 sc_error("parse_stack_backtrace: parse stack backtrace follows...\n");
574 for (index_ = 0; index_ < depth; index_++) {
575 sc_char type;
576
577 type = format[index_];
578 if (type == PROP_KEY_INTEGER)
579 sc_error("%2ld - [%c] %ld\n", index_, type, vt_key[index_].integer);
580 else if (type == PROP_KEY_STRING)
581 sc_error("%2ld - [%c] \"%s\"\n", index_, type, vt_key[index_].string);
582 else
583 sc_error("%2ld - [%c] %p\n", index_, type, vt_key[index_].voidp);
584 }
585 }
586
587
588 /*
589 * parse_put_property()
590 * parse_get_property()
591 *
592 * Write or read a property based on the keys amassed so far.
593 */
parse_put_property(sc_vartype_t vt_value,sc_char type)594 static void parse_put_property(sc_vartype_t vt_value, sc_char type) {
595 sc_vartype_t vt_key[PARSE_MAX_DEPTH];
596 sc_char format[PARSE_MAX_DEPTH + 4];
597 sc_int depth;
598
599 /* Retrieve the adjusted stack. */
600 parse_retrieve_stack(format + 3, vt_key, &depth);
601
602 /* Complete the format for the property put. */
603 format[0] = type;
604 format[1] = '-';
605 format[2] = '>';
606 format[depth + 3] = NUL;
607
608 /* Store the property under the stacked keys. */
609 assert(parse_bundle);
610 prop_put(parse_bundle, format, vt_value, vt_key);
611 }
612
parse_get_property(sc_vartype_t * vt_rvalue,sc_char type)613 static sc_bool parse_get_property(sc_vartype_t *vt_rvalue, sc_char type) {
614 sc_vartype_t vt_key[PARSE_MAX_DEPTH];
615 sc_char format[PARSE_MAX_DEPTH + 4];
616 sc_int depth;
617 sc_bool status;
618
619 /* Retrieve the adjusted stack. */
620 parse_retrieve_stack(format + 3, vt_key, &depth);
621
622 /* Complete the format for the property put. */
623 format[0] = type;
624 format[1] = '<';
625 format[2] = '-';
626 format[depth + 3] = NUL;
627
628 /* Retrieve the property using the stacked keys. */
629 assert(parse_bundle);
630 status = prop_get(parse_bundle, format, vt_rvalue, vt_key);
631
632 return status;
633 }
634
635
636 /*
637 * parse_get_child_count()
638 *
639 * Convenience form of parse_get_property(), retrieve an integer property
640 * indicating the child count of the effectively stacked node, or zero if
641 * no such node exists.
642 */
parse_get_child_count(void)643 static sc_int parse_get_child_count(void) {
644 sc_vartype_t vt_rvalue;
645
646 if (!parse_get_property(&vt_rvalue, PROP_INTEGER))
647 vt_rvalue.integer = 0;
648
649 return vt_rvalue.integer;
650 }
651
652
653 /*
654 * parse_get_integer_property()
655 * parse_get_boolean_property()
656 * parse_get_string_property()
657 *
658 * Convenience forms of parse_get_property(), retrieve directly, and report
659 * a fatal error if the property does not exist.
660 */
parse_get_integer_property(void)661 static sc_int parse_get_integer_property(void) {
662 sc_vartype_t vt_rvalue;
663
664 if (!parse_get_property(&vt_rvalue, PROP_INTEGER))
665 sc_fatal("parse_get_integer_property: missing property\n");
666
667 return vt_rvalue.integer;
668 }
669
parse_get_boolean_property(void)670 static sc_bool parse_get_boolean_property(void) {
671 sc_vartype_t vt_rvalue;
672
673 if (!parse_get_property(&vt_rvalue, PROP_BOOLEAN))
674 sc_fatal("parse_get_boolean_property: missing property\n");
675
676 return vt_rvalue.boolean;
677 }
678
parse_get_string_property(void)679 static const sc_char *parse_get_string_property(void) {
680 sc_vartype_t vt_rvalue;
681
682 if (!parse_get_property(&vt_rvalue, PROP_STRING))
683 sc_fatal("parse_get_string_property: missing property\n");
684
685 return vt_rvalue.string;
686 }
687
688
689 /* Pushback line, and pushback requested flag. */
690 static const sc_char *parse_pushback_line = NULL;
691 static sc_bool parse_use_pushback = FALSE;
692
693 /*
694 * parse_get_taf_string()
695 * parse_get_taf_integer()
696 * parse_get_taf_boolean()
697 * parse_taf_pushback()
698 *
699 * Wrapper round obtaining the next TAF file line, with variants to convert
700 * the line content into an integer or boolean, and a function for effective
701 * TAF line pushback.
702 */
parse_get_taf_string(CONTEXT)703 static const sc_char *parse_get_taf_string(CONTEXT) {
704 const sc_char *line;
705
706 /* If pushback requested, use that instead of reading. */
707 if (parse_use_pushback) {
708 /* Use the pushback line, and clear the request. */
709 assert(parse_pushback_line);
710 line = parse_pushback_line;
711 parse_use_pushback = FALSE;
712 } else {
713 /* Get the next line, and complain if absent. */
714 line = taf_next_line(parse_taf);
715 if (!line) {
716 sc_error("parse_get_taf_string:"
717 " out of TAF data at line %ld\n", parse_tafline);
718 parse_stack_backtrace();
719 LONG_JUMP0;
720 }
721
722 /* Note this line for possible pushback. */
723 parse_pushback_line = line;
724 }
725
726 /* Print out the line we're parsing if tracing. */
727 if (parse_trace)
728 sc_trace("Parse: read in line %ld : %s\n", parse_tafline, line);
729
730 parse_tafline++;
731 return line;
732 }
733
parse_get_taf_integer(CONTEXT)734 static sc_int parse_get_taf_integer(CONTEXT) {
735 const sc_char *line;
736 sc_int integer;
737
738 /* Get line, and scan for a single integer; return it. */
739 R0FUNC0(parse_get_taf_string, line);
740 if (sscanf(line, "%ld", &integer) != 1) {
741 sc_error("parse_get_taf_integer:"
742 " invalid integer at line %ld\n", parse_tafline - 1);
743 parse_stack_backtrace();
744 LONG_JUMP0;
745 }
746
747 return integer;
748 }
749
parse_get_taf_boolean(CONTEXT)750 static sc_bool parse_get_taf_boolean(CONTEXT) {
751 const sc_char *line;
752 sc_uint boolean;
753
754 /*
755 * Get line, and scan for a single integer; check it's a valid-looking flag,
756 * and return it.
757 */
758 R0FUNC0(parse_get_taf_string, line);
759 if (sscanf(line, "%lu", &boolean) != 1) {
760 sc_error("parse_get_taf_boolean:"
761 " invalid boolean at line %ld\n", parse_tafline - 1);
762 parse_stack_backtrace();
763 LONG_JUMP0;
764 }
765 if (boolean != 0 && boolean != 1) {
766 sc_error("parse_get_taf_boolean:"
767 " warning: suspect boolean at line %ld\n", parse_tafline - 1);
768 }
769
770 return boolean != 0;
771 }
772
parse_taf_pushback(void)773 static void parse_taf_pushback(void) {
774 if (parse_use_pushback || !parse_pushback_line)
775 sc_fatal("parse_taf_pushback: too much pushback requested\n");
776
777 /* Set pushback request, and decrement line counter. */
778 parse_use_pushback = TRUE;
779 parse_tafline--;
780
781 /* Note pushback for tracing purposes. */
782 if (parse_trace)
783 sc_trace("Parse: push back at line %ld\n", parse_tafline);
784 }
785
786
787 /* Enumerations of parse types found in the parse schema. */
788 enum {
789 PARSE_INTEGER = '#',
790 PARSE_DEFAULT_ZERO = 'Z',
791 PARSE_BOOLEAN = 'B',
792 PARSE_DEFAULT_FALSE = 'F',
793 PARSE_DEFAULT_TRUE = 'T',
794 PARSE_STRING = '$',
795 PARSE_DEFAULT_EMPTY = 'E',
796 PARSE_MULTILINE = 'M',
797 PARSE_VECTOR = 'V',
798 PARSE_VECTOR_ALTERNATE = 'W',
799 PARSE_ARRAY = '[',
800 PARSE_EXPRESSION = '?',
801 PARSE_EXPRESSION_NOT = '!',
802 PARSE_GLOBAL_EXPRESSION = 'G',
803 PARSE_CLASS = '<',
804 PARSE_FIXUP = '|',
805 PARSE_SPECIAL = '{',
806 PARSE_IGNORE_INTEGER = 'i',
807 PARSE_IGNORE_BOOLEAN = 'b',
808 PARSE_IGNORE_STRING = 's'
809 };
810
811 /* Forward declarations of parse functions for recursion. */
812 static void parse_element(CONTEXT, const sc_char *element);
813 static void parse_class(CONTEXT, const sc_char *class_);
814 static void parse_descriptor(CONTEXT, const sc_char *descriptor);
815
816 /*
817 * parse_array()
818 *
819 * Parse a descriptor [] array.
820 */
parse_array(CONTEXT,const sc_char * array)821 static void parse_array(CONTEXT, const sc_char *array) {
822 sc_int count, index_;
823 sc_char element[PARSE_TEMP_LENGTH];
824
825 if (parse_trace)
826 sc_trace("Parse: entering array %s\n", array);
827
828 /* Find the count of elements in the array, and the element itself. */
829 if (sscanf(array, "[%ld]%[^ ]", &count, element) != 2)
830 sc_fatal("parse_array: bad array, %s\n", array);
831
832 /* Parse the element for array count iterations, each a key. */
833 for (index_ = 0; index_ < count; index_++) {
834 sc_vartype_t vt_key;
835
836 vt_key.integer = index_;
837 parse_push_key(vt_key, PROP_KEY_INTEGER);
838
839 CALL1(parse_element, element);
840
841 parse_pop_key();
842 }
843
844 if (parse_trace)
845 sc_trace("Parse: leaving array %s\n", array);
846 }
847
848
849 /*
850 * parse_vector_common()
851 * parse_vector()
852 * parse_vector_alternate()
853 *
854 * Parse a variable-length vector of properties.
855 */
parse_vector_common(CONTEXT,const sc_char * vector,sc_int count)856 static void parse_vector_common(CONTEXT, const sc_char *vector, sc_int count) {
857 sc_int index_;
858
859 /* Parse the vector property count times, pushing a key on each. */
860 for (index_ = 0; index_ < count; index_++) {
861 sc_vartype_t vt_key;
862
863 vt_key.integer = index_;
864 parse_push_key(vt_key, PROP_KEY_INTEGER);
865
866 CALL1(parse_element, vector + 1);
867
868 parse_pop_key();
869 }
870 }
871
parse_vector(CONTEXT,const sc_char * vector)872 static void parse_vector(CONTEXT, const sc_char *vector) {
873 sc_int count;
874
875 if (parse_trace)
876 sc_trace("Parse: entering vector %s\n", vector);
877
878 /* Find the count of elements in the vector, and parse. */
879 FUNC0(parse_get_taf_integer, count);
880 CALL2(parse_vector_common, vector, count);
881
882 if (parse_trace)
883 sc_trace("Parse: leaving vector %s\n", vector);
884 }
885
parse_vector_alternate(CONTEXT,const sc_char * vector)886 static void parse_vector_alternate(CONTEXT, const sc_char *vector) {
887 sc_int count1;
888
889 if (parse_trace)
890 sc_trace("Parse: entering alternate vector %s\n", vector);
891
892 /* Element count, this is a vector described by size - 1. */
893 FUNC0(parse_get_taf_integer, count1);
894 CALL2(parse_vector_common, vector, count1 + 1);
895
896 if (parse_trace)
897 sc_trace("Parse: leaving alternate vector %s\n", vector);
898 }
899
900
901 /*
902 * parse_test_expression()
903 * parse_expression()
904 *
905 * Parse a conditional field definition, with runtime test.
906 */
parse_test_expression(const sc_char * test_expression)907 static sc_bool parse_test_expression(const sc_char *test_expression) {
908 sc_vartype_t vt_key;
909 sc_char plhs[PARSE_TEMP_LENGTH];
910 sc_int rhs;
911 sc_bool retval = FALSE;
912
913 /* Identify the type of expression to evaluate. */
914 switch (test_expression[0]) {
915 case PARSE_BOOLEAN:
916 /* Read boolean property and return its value. */
917 vt_key.string = test_expression + 1;
918 parse_push_key(vt_key, PROP_KEY_STRING);
919 retval = parse_get_boolean_property();
920 parse_pop_key();
921 break;
922
923 case PARSE_INTEGER:
924 /* Get the left and right sides of = comparison. */
925 if (sscanf(test_expression, "#%[^=]=%ld", plhs, &rhs) != 2) {
926 sc_fatal("parse_test_expression: bad = compare, %s\n",
927 test_expression + 1);
928 }
929
930 /* Read integer property and return comparison. */
931 vt_key.string = plhs;
932 parse_push_key(vt_key, PROP_KEY_STRING);
933 retval = (parse_get_integer_property() == rhs);
934 parse_pop_key();
935 break;
936
937 case PARSE_STRING:
938 /* Read property and return TRUE if not an empty string. */
939 vt_key.string = test_expression + 1;
940 parse_push_key(vt_key, PROP_KEY_STRING);
941 retval = !sc_strempty(parse_get_string_property());
942 parse_pop_key();
943 break;
944
945 case PARSE_GLOBAL_EXPRESSION: {
946 sc_vartype_t vt_gkey[2];
947
948 /* Read the given Global boolean property and return it. */
949 vt_gkey[0].string = "Globals";
950 vt_gkey[1].string = test_expression + 1;
951 retval = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
952 break;
953 }
954
955 default:
956 sc_fatal("parse_test_expression:"
957 " bad expression, %s\n", test_expression + 1);
958 }
959
960 if (parse_trace)
961 sc_trace("Parse: expression is %s\n", retval ? "true" : "false");
962
963 return retval;
964 }
965
parse_expression(CONTEXT,const sc_char * expression)966 static void parse_expression(CONTEXT, const sc_char *expression) {
967 sc_char test_expression[PARSE_TEMP_LENGTH];
968 sc_bool is_present;
969
970 if (parse_trace)
971 sc_trace("Parse: entering expression %s\n", expression);
972
973 /* Isolate the test part of the expression. */
974 if (sscanf(expression, "?%[^:]", test_expression) != 1)
975 sc_fatal("parse_expression: bad expression, %s\n", expression);
976
977 /* Handle the remainder of the expression only if test passes. */
978 is_present = (test_expression[0] == PARSE_EXPRESSION_NOT)
979 ? !parse_test_expression(test_expression + 1)
980 : parse_test_expression(test_expression);
981 if (is_present) {
982 sc_int next;
983
984 /*
985 * Following the ':' may be a single element, or a comma-separated list.
986 */
987 for (next = strlen(test_expression) + 2; expression[next] != NUL;) {
988 sc_char element[PARSE_TEMP_LENGTH];
989
990 /* Get the next individual element to parse. */
991 if (sscanf(expression + next, "%[^,]", element) != 1)
992 sc_fatal("parse_expression: bad list, %s\n", expression + next);
993
994 /* Parse this isolated element. */
995 CALL1(parse_element, element);
996
997 /* Advance to the start of the next element. */
998 next += strlen(element);
999 next += strspn(expression + next, ",");
1000 }
1001 }
1002
1003 if (parse_trace)
1004 sc_trace("Parse: leaving expression %s\n", expression);
1005 }
1006
1007
1008 /*
1009 * parse_read_multiline()
1010 *
1011 * Helper for parse_terminal(), reads in a multiline string. The return
1012 * string is malloc'ed, and the caller needs to handle that.
1013 */
parse_read_multiline(CONTEXT)1014 static sc_char *parse_read_multiline(CONTEXT) {
1015 const sc_byte *separator = NULL;
1016 const sc_char *line;
1017 sc_char *multiline;
1018
1019 /* Select the appropriate multiline separator. */
1020 switch (taf_get_version(parse_taf)) {
1021 case TAF_VERSION_400:
1022 separator = V400_SEPARATOR;
1023 break;
1024 case TAF_VERSION_390:
1025 separator = V390_SEPARATOR;
1026 break;
1027 case TAF_VERSION_380:
1028 separator = V380_SEPARATOR;
1029 break;
1030 default:
1031 sc_fatal("parse_read_multiline: invalid TAF file version\n");
1032 break;
1033 }
1034
1035 /* Take a simple copy of the first line. */
1036 R0FUNC0(parse_get_taf_string, line);
1037 multiline = (sc_char *)sc_malloc(strlen(line) + 1);
1038 strcpy(multiline, line);
1039
1040 /* Now concatenate until separator found. */
1041 R0FUNC0(parse_get_taf_string, line);
1042 while (memcmp(line, separator, SEPARATOR_SIZE) != 0) {
1043 multiline = (sc_char *)sc_realloc(multiline,
1044 strlen(multiline) + strlen(line) + 2);
1045 strcat(multiline, "\n");
1046 strcat(multiline, line);
1047 R0FUNC0(parse_get_taf_string, line);
1048 }
1049
1050 return multiline;
1051 }
1052
1053
1054 /*
1055 * parse_terminal()
1056 *
1057 * Common handler for string, integer, boolean, and multiline parse terminals.
1058 */
parse_terminal(CONTEXT,const sc_char * terminal)1059 static void parse_terminal(CONTEXT, const sc_char *terminal) {
1060 sc_vartype_t vt_key, vt_value;
1061
1062 if (parse_trace)
1063 sc_trace("Parse: entering terminal %s\n", terminal);
1064
1065 /* Push the key string. */
1066 vt_key.string = terminal + 1;
1067 parse_push_key(vt_key, PROP_KEY_STRING);
1068
1069 /* Retrieve, or invent, then store the value. */
1070 switch (terminal[0]) {
1071 case PARSE_INTEGER:
1072 FUNC0(parse_get_taf_integer, vt_value.integer);
1073 parse_put_property(vt_value, PROP_INTEGER);
1074 break;
1075 case PARSE_DEFAULT_ZERO:
1076 vt_value.integer = 0;
1077 parse_put_property(vt_value, PROP_INTEGER);
1078 break;
1079
1080 case PARSE_BOOLEAN:
1081 FUNC0(parse_get_taf_boolean, vt_value.boolean);
1082 parse_put_property(vt_value, PROP_BOOLEAN);
1083 break;
1084 case PARSE_DEFAULT_FALSE:
1085 case PARSE_DEFAULT_TRUE:
1086 vt_value.boolean = (terminal[0] == PARSE_DEFAULT_TRUE);
1087 parse_put_property(vt_value, PROP_BOOLEAN);
1088 break;
1089
1090 case PARSE_STRING:
1091 FUNC0(parse_get_taf_string, vt_value.string);
1092 parse_put_property(vt_value, PROP_STRING);
1093 break;
1094 case PARSE_DEFAULT_EMPTY:
1095 vt_value.string = "";
1096 parse_put_property(vt_value, PROP_STRING);
1097 break;
1098
1099 case PARSE_MULTILINE:
1100 /* Assign to and adopt mutable string rather than const string. */
1101 FUNC0(parse_read_multiline, vt_value.mutable_string);
1102 parse_put_property(vt_value, PROP_STRING);
1103
1104 assert(parse_bundle);
1105 prop_adopt(parse_bundle, vt_value.mutable_string);
1106 break;
1107
1108 case PARSE_IGNORE_INTEGER:
1109 CALL0(parse_get_taf_integer);
1110 break;
1111 case PARSE_IGNORE_BOOLEAN:
1112 CALL0(parse_get_taf_boolean);
1113 break;
1114 case PARSE_IGNORE_STRING:
1115 CALL0(parse_get_taf_string);
1116 break;
1117
1118 default:
1119 sc_fatal("parse_terminal: bad type, %c\n", terminal[0]);
1120 }
1121
1122 /* Pop terminal key. */
1123 parse_pop_key();
1124
1125 if (parse_trace)
1126 sc_trace("Parse: leaving terminal %s\n", terminal);
1127 }
1128
1129
1130 /*
1131 * Resources table. This table enables resource offsets to be calculated
1132 * for the various sound and graphic resources encountered on parsing
1133 * version 4.0 games. It's unused if the version is not 4.0.
1134 */
1135 struct sc_parse_resource_t {
1136 sc_char *name;
1137 sc_uint hash;
1138 sc_int length;
1139 sc_int offset;
1140 };
1141
1142 enum { RESOURCE_GROW_INCREMENT = 32 };
1143 static sc_int parse_resources_length = 0;
1144 static sc_int parse_resources_size = 0;
1145 static sc_parse_resource_t *parse_resources = NULL;
1146
1147
1148 /*
1149 * parse_clear_v400_resources_table()
1150 *
1151 * Free and clear down the version 4.0 resources table.
1152 */
parse_clear_v400_resources_table(void)1153 static void parse_clear_v400_resources_table(void) {
1154 /* Free allocated memory and return to initial values. */
1155 if (parse_resources) {
1156 sc_int index_;
1157
1158 for (index_ = 0; index_ < parse_resources_length; index_++)
1159 sc_free(parse_resources[index_].name);
1160
1161 sc_free(parse_resources);
1162 parse_resources = NULL;
1163 }
1164 parse_resources_length = 0;
1165 parse_resources_size = 0;
1166 }
1167
1168
1169 /*
1170 * parse_get_v400_resource_offset()
1171 *
1172 * Notes version 4.0 resource names encountered in the parse, and their
1173 * lengths, and builds up a list of resources with their data offsets. The
1174 * function assumes that resources are appended to the TAF file in the
1175 * order in which they are encountered when reading through the TAF file.
1176 *
1177 * A warning -- this function may return a new length. Resources that
1178 * have been seen once already have non-useful (though apparently non-zero)
1179 * lengths; this function needs to handle that. The caller needs to compare
1180 * length with real_length to see if that happened.
1181 */
parse_get_v400_resource_offset(const sc_char * name,sc_int length,sc_int * real_length)1182 static sc_int parse_get_v400_resource_offset(const sc_char *name,
1183 sc_int length, sc_int *real_length) {
1184 sc_char *clean_name;
1185 sc_uint hash;
1186 sc_int index_, offset;
1187
1188 /*
1189 * Take a copy of the name, and remove any trailing "##" looping sound
1190 * indicator flag. Who thinks this junk up?
1191 */
1192 clean_name = (sc_char *)sc_malloc(strlen(name) + 1);
1193 strcpy(clean_name, name);
1194 if (strcmp(clean_name + strlen(clean_name) - 2, "##") == 0)
1195 clean_name[strlen(clean_name) - 2] = NUL;
1196
1197 /*
1198 * Scan the current resources list for a matching name, and if the resource
1199 * is already known, return its offset. The hash check is an attempt to
1200 * improve the search times, relative to using only string comparisons --
1201 * the table's not fully hashed. If found, we need to also pass back the
1202 * corrected length.
1203 */
1204 offset = -1;
1205 hash = sc_hash(clean_name);
1206 for (index_ = 0; index_ < parse_resources_length; index_++) {
1207 if (parse_resources[index_].hash == hash
1208 && strcmp(parse_resources[index_].name, clean_name) == 0) {
1209 offset = parse_resources[index_].offset;
1210 break;
1211 }
1212 }
1213 if (offset != -1) {
1214 *real_length = parse_resources[index_].length;
1215 sc_free(clean_name);
1216 return offset;
1217 }
1218
1219 /* Resize the resources table if required. */
1220 if (parse_resources_length == parse_resources_size) {
1221 parse_resources_size += RESOURCE_GROW_INCREMENT;
1222 parse_resources = (sc_parse_resource_t *)sc_realloc(parse_resources,
1223 parse_resources_size *
1224 sizeof(parse_resources[0]));
1225 }
1226
1227 /*
1228 * Calculate the offset. For the first resource, it's zero; for others,
1229 * it's one after the prior entry's offset and length.
1230 */
1231 if (parse_resources_length == 0)
1232 offset = 0;
1233 else {
1234 offset = parse_resources[parse_resources_length - 1].offset
1235 + parse_resources[parse_resources_length - 1].length + 1;
1236 }
1237
1238 /* Add details to the table. */
1239 parse_resources[parse_resources_length].name = clean_name;
1240 parse_resources[parse_resources_length].hash = hash;
1241 parse_resources[parse_resources_length].offset = offset;
1242 parse_resources[parse_resources_length].length = length;
1243 parse_resources_length++;
1244
1245 *real_length = length;
1246 return offset;
1247 }
1248
1249
1250 /*
1251 * parse_handle_v400_resources()
1252 *
1253 * Extra special handling for version 4.0 resources; extracts details of
1254 * the resource just parsed, and adds an offset property for each defined.
1255 *
1256 * A warning -- Adrift seems to use -ve numbers as lengths for resources
1257 * already parsed, where TAF files include the resource. It's unclear
1258 * what the -ve values mean, so here we ignore them and work off the
1259 * resource file name given. This means we have to look for length not
1260 * equal to zero, not just lengths greater than zero.
1261 *
1262 * TODO Work out what this means. The -ve lengths look like a form of
1263 * 'resource number'; -(length+2) is tantalizingly close to the index into
1264 * our parse_resources table, but not always...
1265 */
parse_handle_v400_resources(sc_bool has_sound,sc_bool has_graphics)1266 static void parse_handle_v400_resources(sc_bool has_sound, sc_bool has_graphics) {
1267 sc_vartype_t vt_key, vt_value;
1268 const sc_char *file;
1269 sc_int length, offset;
1270
1271 /*
1272 * Retrieve the file and length for the sound just parsed. If there's a
1273 * file of non-zero length, rewrite its offset.
1274 */
1275 if (has_sound) {
1276 /* Retrieve the file and length information. */
1277 vt_key.string = "SoundFile";
1278 parse_push_key(vt_key, PROP_KEY_STRING);
1279 file = parse_get_string_property();
1280 parse_pop_key();
1281
1282 vt_key.string = "SoundLen";
1283 parse_push_key(vt_key, PROP_KEY_STRING);
1284 length = parse_get_integer_property();
1285 parse_pop_key();
1286
1287 /*
1288 * If defined and has a length, rewrite the offset, and also the length
1289 * in case changed.
1290 */
1291 if (!sc_strempty(file) && length != 0) {
1292 sc_int real_length;
1293
1294 offset = parse_get_v400_resource_offset(file, length, &real_length);
1295 vt_key.string = "SoundOffset";
1296 parse_push_key(vt_key, PROP_KEY_STRING);
1297
1298 vt_value.integer = offset;
1299 parse_put_property(vt_value, PROP_INTEGER);
1300
1301 parse_pop_key();
1302
1303 /* Rewrite length if changed. */
1304 if (real_length != length) {
1305 vt_key.string = "SoundLen";
1306 parse_push_key(vt_key, PROP_KEY_STRING);
1307
1308 vt_value.integer = real_length;
1309 parse_put_property(vt_value, PROP_INTEGER);
1310
1311 parse_pop_key();
1312 }
1313 }
1314 }
1315
1316 /* Now do the same thing for graphics. */
1317 if (has_graphics) {
1318 /* Retrieve the file and length information. */
1319 vt_key.string = "GraphicFile";
1320 parse_push_key(vt_key, PROP_KEY_STRING);
1321 file = parse_get_string_property();
1322 parse_pop_key();
1323
1324 vt_key.string = "GraphicLen";
1325 parse_push_key(vt_key, PROP_KEY_STRING);
1326 length = parse_get_integer_property();
1327 parse_pop_key();
1328
1329 /*
1330 * If defined and has a length, rewrite the offset, and also the length
1331 * in case changed.
1332 */
1333 if (!sc_strempty(file) && length != 0) {
1334 sc_int real_length;
1335
1336 offset = parse_get_v400_resource_offset(file, length, &real_length);
1337 vt_key.string = "GraphicOffset";
1338 parse_push_key(vt_key, PROP_KEY_STRING);
1339
1340 vt_value.integer = offset;
1341 parse_put_property(vt_value, PROP_INTEGER);
1342
1343 parse_pop_key();
1344
1345 /* Rewrite length if changed. */
1346 if (real_length != length) {
1347 vt_key.string = "GraphicLen";
1348 parse_push_key(vt_key, PROP_KEY_STRING);
1349
1350 vt_value.integer = real_length;
1351 parse_put_property(vt_value, PROP_INTEGER);
1352
1353 parse_pop_key();
1354 }
1355 }
1356 }
1357 }
1358
1359
1360 /*
1361 * parse_special()
1362 *
1363 * Handler for special items that can't be described accurately, and
1364 * therefore need careful treatment.
1365 */
parse_special(CONTEXT,const sc_char * special)1366 static void parse_special(CONTEXT, const sc_char *special) {
1367 if (parse_trace)
1368 sc_trace("Parse: entering special %s\n", special);
1369
1370 /* Special handling for version 4.0 resources. */
1371 if (strcmp(special, "{V400_RESOURCE}") == 0) {
1372 sc_vartype_t vt_key[2];
1373 sc_bool has_sound, has_graphics;
1374
1375 /* Get sound and graphics global flags. */
1376 vt_key[0].string = "Globals";
1377 vt_key[1].string = "Sound";
1378 has_sound = prop_get_boolean(parse_bundle, "B<-ss", vt_key);
1379
1380 vt_key[1].string = "Graphics";
1381 has_graphics = prop_get_boolean(parse_bundle, "B<-ss", vt_key);
1382
1383 /* Apply special handling to the resources. */
1384 parse_handle_v400_resources(has_sound, has_graphics);
1385 }
1386
1387 /* Parse a version 4.0 optional set of room exit information. */
1388 else if (strcmp(special, "{V400_ROOM_EXIT:#Dest_#Var1_#Var2_#Var3}") == 0) {
1389 sc_int flag;
1390
1391 /* Get next flag, and if true, pushback and parse. */
1392 FUNC0(parse_get_taf_integer, flag);
1393 if (flag != 0) {
1394 parse_taf_pushback();
1395 CALL1(parse_descriptor, "#Dest #Var1 #Var2 #Var3");
1396 }
1397 }
1398
1399 /* Parse version 3.9 and version 3.8 optional room exit information. */
1400 else if (strcmp(special,
1401 "{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}") == 0) {
1402 sc_int flag;
1403
1404 /* Get next flag, and if true, pushback and parse. */
1405 FUNC0(parse_get_taf_integer, flag);
1406 if (flag != 0) {
1407 parse_taf_pushback();
1408 CALL1(parse_descriptor, "#Dest #Var1 #Var2 ZVar3");
1409 }
1410 }
1411
1412 /* Parse room lists, with optional extra room. */
1413 else if (strcmp(special, "{ROOM_LIST0}") == 0
1414 || strcmp(special, "{ROOM_LIST1}") == 0) {
1415 sc_vartype_t vt_key, vt_value;
1416 sc_int room_count, num_rooms, type, index_;
1417
1418 /* Retrieve the room list type. */
1419 vt_key.string = "Type";
1420 parse_push_key(vt_key, PROP_KEY_STRING);
1421 type = parse_get_integer_property();
1422 parse_pop_key();
1423
1424 /* Write remaining room list depending on the type. */
1425 switch (type) {
1426 case ROOMLIST_NO_ROOMS:
1427 case ROOMLIST_ALL_ROOMS:
1428 case ROOMLIST_NPC_PART:
1429 break;
1430
1431 case ROOMLIST_ONE_ROOM:
1432 /* Store this room as the single list entry. */
1433 CALL1(parse_element, "#Room");
1434 break;
1435
1436 case ROOMLIST_SOME_ROOMS:
1437 /* Get count of rooms defined, add one if necessary. */
1438 vt_key.string = "Rooms";
1439 room_count = prop_get_child_count(parse_bundle, "I<-s", &vt_key);
1440
1441 if (strcmp(special, "{ROOM_LIST1}") == 0)
1442 num_rooms = room_count + 1;
1443 else
1444 num_rooms = room_count;
1445
1446 /* Store an array of rooms flags for each room. */
1447 vt_key.string = "Rooms";
1448 parse_push_key(vt_key, PROP_KEY_STRING);
1449 for (index_ = 0; index_ < num_rooms; index_++) {
1450 sc_bool this_room;
1451
1452 /* Get flag for this room. */
1453 FUNC0(parse_get_taf_boolean, this_room);
1454
1455 /* Store flag directly. */
1456 vt_key.integer = index_;
1457 parse_push_key(vt_key, PROP_KEY_INTEGER);
1458 vt_value.boolean = this_room;
1459 parse_put_property(vt_value, PROP_BOOLEAN);
1460 parse_pop_key();
1461 }
1462 parse_pop_key();
1463 break;
1464
1465 default:
1466 sc_fatal("parse_special: bad type, %ld\n", type);
1467 }
1468 }
1469
1470 /* Parse Parent number iff this object is an NPC part. */
1471 else if (strcmp(special, "{OBJECT:#Parent}") == 0) {
1472 sc_vartype_t vt_key;
1473 sc_int type;
1474
1475 /* Check object's Where room list Type for NPC part. */
1476 vt_key.string = "Where";
1477 parse_push_key(vt_key, PROP_KEY_STRING);
1478 vt_key.string = "Type";
1479 parse_push_key(vt_key, PROP_KEY_STRING);
1480 type = parse_get_integer_property();
1481 parse_pop_key();
1482 parse_pop_key();
1483
1484 /* Get Parent if the object is part of an NPC. */
1485 if (type == ROOMLIST_NPC_PART) {
1486 CALL1(parse_element, "#Parent");
1487 }
1488 }
1489
1490 /* Parse a list of rooms and times for a walk. */
1491 else if (strcmp(special, "{WALK:#Rooms_#Times}") == 0) {
1492 sc_vartype_t vt_key, vt_value;
1493 sc_int num_stops, index_;
1494
1495 /* Obtain the count of stops in this walk. */
1496 vt_key.string = "NumStops";
1497 parse_push_key(vt_key, PROP_KEY_STRING);
1498 num_stops = parse_get_integer_property();
1499 parse_pop_key();
1500
1501 /* Look for a room and time for each stop. */
1502 for (index_ = 0; index_ < num_stops; index_++) {
1503 sc_int room, time;
1504
1505 /* Parse and store Rooms[index_]. */
1506 vt_key.string = "Rooms";
1507 parse_push_key(vt_key, PROP_KEY_STRING);
1508 vt_key.integer = index_;
1509 parse_push_key(vt_key, PROP_KEY_INTEGER);
1510
1511 FUNC0(parse_get_taf_integer, room);
1512
1513 vt_value.integer = room;
1514 parse_put_property(vt_value, PROP_INTEGER);
1515 parse_pop_key();
1516 parse_pop_key();
1517
1518 /* Parse and store Times[index_]. */
1519 vt_key.string = "Times";
1520 parse_push_key(vt_key, PROP_KEY_STRING);
1521 vt_key.integer = index_;
1522 parse_push_key(vt_key, PROP_KEY_INTEGER);
1523
1524 FUNC0(parse_get_taf_integer, time);
1525
1526 vt_value.integer = time;
1527 parse_put_property(vt_value, PROP_INTEGER);
1528 parse_pop_key();
1529 parse_pop_key();
1530 }
1531 }
1532
1533 /* Parse a room group variable size boolean list. */
1534 else if (strcmp(special, "{ROOM_GROUP:[]BList}") == 0) {
1535 sc_vartype_t vt_key, vt_value;
1536 sc_int num_rooms, index_, l2index_;
1537 sc_bool in_group;
1538
1539 /* Get the count of rooms defined. */
1540 vt_key.string = "Rooms";
1541 num_rooms = prop_get_integer(parse_bundle, "I<-s", &vt_key);
1542
1543 /* Read a boolean for each room. */
1544 l2index_ = 0;
1545 for (index_ = 0; index_ < num_rooms; index_++) {
1546 FUNC0(parse_get_taf_boolean, in_group);
1547
1548 /* Store raw flag as List[index_]. */
1549 vt_key.string = "List";
1550 parse_push_key(vt_key, PROP_KEY_STRING);
1551 vt_key.integer = index_;
1552 parse_push_key(vt_key, PROP_KEY_INTEGER);
1553 vt_value.boolean = in_group;
1554 parse_put_property(vt_value, PROP_BOOLEAN);
1555
1556 parse_pop_key();
1557 parse_pop_key();
1558
1559 /* Store in-group index'es as List2[0..n]. */
1560 if (in_group) {
1561 vt_key.string = "List2";
1562 parse_push_key(vt_key, PROP_KEY_STRING);
1563 vt_key.integer = l2index_;
1564 parse_push_key(vt_key, PROP_KEY_INTEGER);
1565 vt_value.integer = index_;
1566 parse_put_property(vt_value, PROP_INTEGER);
1567
1568 parse_pop_key();
1569 parse_pop_key();
1570
1571 l2index_++;
1572 }
1573 }
1574 }
1575
1576 /* Error if no special handler available. */
1577 else {
1578 sc_fatal("parse_special: no handler for \"%s\"\n", special);
1579 }
1580
1581 if (parse_trace)
1582 sc_trace("Parse: leaving special %s\n", special);
1583 }
1584
1585
1586 /*
1587 * parse_fixup_v390_v380_room_alt()
1588 *
1589 * Helper for parse_fixup_v390_v380_room_alts(). Handles creation of
1590 * version 4.0 room alts for version 3.9 and version 3.8 games.
1591 */
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)1592 static void parse_fixup_v390_v380_room_alt(const sc_char *m1, sc_int type,
1593 const sc_char *resource1, const sc_char *m2, sc_int var2, const sc_char *resource2,
1594 sc_int hide_objects, const sc_char *changed, sc_int var3, sc_int display_room) {
1595 sc_vartype_t vt_key, vt_value, vt_gkey[2];
1596 sc_bool has_sound, has_graphics;
1597 sc_int alt_count;
1598 const sc_char *soundfile1, *graphicfile1;
1599 const sc_char *soundfile2, *graphicfile2;
1600
1601 /*
1602 * Initialize resource files to empty, for cases where no resource is copied
1603 * over from the main room (NULL resource1/2).
1604 */
1605 soundfile1 = "";
1606 graphicfile1 = "";
1607 soundfile2 = "";
1608 graphicfile2 = "";
1609
1610 /* Get sound and graphics flags, always FALSE for version 3.8. */
1611 vt_gkey[0].string = "Globals";
1612 vt_gkey[1].string = "Sound";
1613 has_sound = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
1614
1615 vt_gkey[1].string = "Graphics";
1616 has_graphics = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
1617
1618 /* Get a count of alts so far defined for the room. */
1619 vt_key.string = "Alts";
1620 parse_push_key(vt_key, PROP_KEY_STRING);
1621 alt_count = parse_get_child_count();
1622 parse_pop_key();
1623
1624 /*
1625 * Lookup any resource details now, and save them. Because this is not
1626 * version 4.0, we can ignore lengths, and set them to zero when needed.
1627 */
1628 if (has_sound || has_graphics) {
1629 if (resource1) {
1630 vt_key.string = resource1;
1631 parse_push_key(vt_key, PROP_KEY_STRING);
1632 if (has_sound) {
1633 vt_key.string = "SoundFile";
1634 parse_push_key(vt_key, PROP_KEY_STRING);
1635 soundfile1 = parse_get_string_property();
1636 parse_pop_key();
1637 }
1638 if (has_graphics) {
1639 vt_key.string = "GraphicFile";
1640 parse_push_key(vt_key, PROP_KEY_STRING);
1641 graphicfile1 = parse_get_string_property();
1642 parse_pop_key();
1643 }
1644 parse_pop_key();
1645 }
1646
1647 if (resource2) {
1648 vt_key.string = resource2;
1649 parse_push_key(vt_key, PROP_KEY_STRING);
1650 if (has_sound) {
1651 vt_key.string = "SoundFile";
1652 parse_push_key(vt_key, PROP_KEY_STRING);
1653 soundfile2 = parse_get_string_property();
1654 parse_pop_key();
1655 }
1656 if (has_graphics) {
1657 vt_key.string = "GraphicFile";
1658 parse_push_key(vt_key, PROP_KEY_STRING);
1659 graphicfile2 = parse_get_string_property();
1660 parse_pop_key();
1661 }
1662 parse_pop_key();
1663 }
1664 }
1665
1666 /*
1667 * Create a room alt to match data passed in. Start with the Alts string
1668 * and the index to the alt being written. To correctly emulate the parse,
1669 * we also have to reverse the "Alts" and the index, as parse_put_property()
1670 * will swap them. Madness.
1671 */
1672 vt_key.integer = alt_count;
1673 parse_push_key(vt_key, PROP_KEY_INTEGER);
1674 vt_key.string = "Alts";
1675 parse_push_key(vt_key, PROP_KEY_STRING);
1676
1677 /* Write M1 and Type. */
1678 vt_key.string = "M1";
1679 parse_push_key(vt_key, PROP_KEY_STRING);
1680 vt_value.string = m1;
1681 parse_put_property(vt_value, PROP_STRING);
1682 parse_pop_key();
1683 vt_key.string = "Type";
1684 parse_push_key(vt_key, PROP_KEY_STRING);
1685 vt_value.integer = type;
1686 parse_put_property(vt_value, PROP_INTEGER);
1687 parse_pop_key();
1688
1689 /* If resources, add these as retrieved above. */
1690 if (has_sound || has_graphics) {
1691 vt_key.string = "Res1";
1692 parse_push_key(vt_key, PROP_KEY_STRING);
1693 if (has_sound) {
1694 vt_key.string = "SoundFile";
1695 parse_push_key(vt_key, PROP_KEY_STRING);
1696 vt_value.string = soundfile1;
1697 parse_put_property(vt_value, PROP_STRING);
1698 parse_pop_key();
1699 vt_key.string = "SoundLen";
1700 parse_push_key(vt_key, PROP_KEY_STRING);
1701 vt_value.integer = 0;
1702 parse_put_property(vt_value, PROP_INTEGER);
1703 parse_pop_key();
1704 }
1705 if (has_graphics) {
1706 vt_key.string = "GraphicFile";
1707 parse_push_key(vt_key, PROP_KEY_STRING);
1708 vt_value.string = graphicfile1;
1709 parse_put_property(vt_value, PROP_STRING);
1710 parse_pop_key();
1711 vt_key.string = "GraphicLen";
1712 parse_push_key(vt_key, PROP_KEY_STRING);
1713 vt_value.integer = 0;
1714 parse_put_property(vt_value, PROP_INTEGER);
1715 parse_pop_key();
1716 }
1717 parse_pop_key();
1718 }
1719
1720 /* Write M2 and Var2. */
1721 vt_key.string = "M2";
1722 parse_push_key(vt_key, PROP_KEY_STRING);
1723 vt_value.string = m2;
1724 parse_put_property(vt_value, PROP_STRING);
1725 parse_pop_key();
1726 vt_key.string = "Var2";
1727 parse_push_key(vt_key, PROP_KEY_STRING);
1728 vt_value.integer = var2;
1729 parse_put_property(vt_value, PROP_INTEGER);
1730 parse_pop_key();
1731
1732 /* If resources, again add these as retrieved above. */
1733 if (has_sound || has_graphics) {
1734 vt_key.string = "Res2";
1735 parse_push_key(vt_key, PROP_KEY_STRING);
1736 if (has_sound) {
1737 vt_key.string = "SoundFile";
1738 parse_push_key(vt_key, PROP_KEY_STRING);
1739 vt_value.string = soundfile2;
1740 parse_put_property(vt_value, PROP_STRING);
1741 parse_pop_key();
1742 vt_key.string = "SoundLen";
1743 parse_push_key(vt_key, PROP_KEY_STRING);
1744 vt_value.integer = 0;
1745 parse_put_property(vt_value, PROP_INTEGER);
1746 parse_pop_key();
1747 }
1748 if (has_graphics) {
1749 vt_key.string = "GraphicFile";
1750 parse_push_key(vt_key, PROP_KEY_STRING);
1751 vt_value.string = graphicfile2;
1752 parse_put_property(vt_value, PROP_STRING);
1753 parse_pop_key();
1754 vt_key.string = "GraphicLen";
1755 parse_push_key(vt_key, PROP_KEY_STRING);
1756 vt_value.integer = 0;
1757 parse_put_property(vt_value, PROP_INTEGER);
1758 parse_pop_key();
1759 }
1760 parse_pop_key();
1761 }
1762
1763 /* Finish off with the last four alt properties. */
1764 vt_key.string = "HideObjects";
1765 parse_push_key(vt_key, PROP_KEY_STRING);
1766 vt_value.integer = hide_objects;
1767 parse_put_property(vt_value, PROP_INTEGER);
1768 parse_pop_key();
1769 vt_key.string = "Changed";
1770 parse_push_key(vt_key, PROP_KEY_STRING);
1771 vt_value.string = changed;
1772 parse_put_property(vt_value, PROP_STRING);
1773 parse_pop_key();
1774 vt_key.string = "Var3";
1775 parse_push_key(vt_key, PROP_KEY_STRING);
1776 vt_value.integer = var3;
1777 parse_put_property(vt_value, PROP_INTEGER);
1778 parse_pop_key();
1779 vt_key.string = "DisplayRoom";
1780 parse_push_key(vt_key, PROP_KEY_STRING);
1781 vt_value.integer = display_room;
1782 parse_put_property(vt_value, PROP_INTEGER);
1783 parse_pop_key();
1784
1785 parse_pop_key();
1786 parse_pop_key();
1787 }
1788
1789
1790 /* Multiplier for combination AltDesc Type and HideObject values. */
1791 enum { V390_V380_ALT_TYPEHIDE_MULT = 10 };
1792
1793 /*
1794 * parse_fixup_v390_v380_room_alts()
1795 *
1796 * Common helper function for parse_fixup_v390() and parse_fixup_v380(),
1797 * converts version 3.9 and version 3.8 fixed room description alts into
1798 * an equivalent array of version 4.0 style room alts.
1799 */
parse_fixup_v390_v380_room_alts(void)1800 static void parse_fixup_v390_v380_room_alts(void) {
1801 sc_vartype_t vt_key;
1802 const sc_char *m1, *m2, *changed;
1803 sc_int type, var2, hide_objects, var3, display_room;
1804
1805 /* Room alt invariants. */
1806 m2 = ""; /* No else text */
1807 changed = ""; /* No changed room name */
1808
1809 /*
1810 * Create a room alt to override all others, controlled by an object
1811 * condition and with optional object hiding.
1812 */
1813 type = 2; /* Object condition */
1814 display_room = 0; /* Override all others */
1815
1816 vt_key.string = "Obj";
1817 parse_push_key(vt_key, PROP_KEY_STRING);
1818 var3 = parse_get_integer_property();
1819 parse_pop_key();
1820
1821 if (var3 > 0) {
1822 sc_int typehideobjects;
1823
1824 vt_key.string = "AltDesc";
1825 parse_push_key(vt_key, PROP_KEY_STRING);
1826 m1 = parse_get_string_property();
1827 parse_pop_key();
1828
1829 vt_key.string = "TypeHideObjects";
1830 parse_push_key(vt_key, PROP_KEY_STRING);
1831 typehideobjects = parse_get_integer_property();
1832 parse_pop_key();
1833
1834 var2 = typehideobjects / V390_V380_ALT_TYPEHIDE_MULT;
1835 hide_objects = typehideobjects % V390_V380_ALT_TYPEHIDE_MULT;
1836
1837 parse_fixup_v390_v380_room_alt(m1, type, "AltRes",
1838 m2, var2, NULL,
1839 hide_objects, changed, var3,
1840 display_room);
1841 }
1842
1843 /*
1844 * If a second task alternate description is defined, create a room alt to
1845 * add after the main description, one that stops printing once done.
1846 */
1847 type = 0; /* Task condition */
1848 display_room = 1; /* Print after main and stop */
1849
1850 vt_key.string = "Task2";
1851 parse_push_key(vt_key, PROP_KEY_STRING);
1852 var2 = parse_get_integer_property();
1853 parse_pop_key();
1854
1855 if (var2 > 0) {
1856 vt_key.string = "AddDesc2";
1857 parse_push_key(vt_key, PROP_KEY_STRING);
1858 m1 = parse_get_string_property();
1859 parse_pop_key();
1860
1861 var3 = 0;
1862 hide_objects = 0;
1863
1864 parse_fixup_v390_v380_room_alt(m1, type, "Task2Res",
1865 m2, var2, NULL,
1866 hide_objects, changed, var3,
1867 display_room);
1868 }
1869
1870 /* Do the same for any first task additional description. */
1871 type = 0; /* Task condition */
1872 display_room = 1; /* Print after main and stop */
1873
1874 vt_key.string = "Task1";
1875 parse_push_key(vt_key, PROP_KEY_STRING);
1876 var2 = parse_get_integer_property();
1877 parse_pop_key();
1878
1879 if (var2 > 0) {
1880 vt_key.string = "AddDesc1";
1881 parse_push_key(vt_key, PROP_KEY_STRING);
1882 m1 = parse_get_string_property();
1883 parse_pop_key();
1884
1885 var3 = 0;
1886 hide_objects = 0;
1887
1888 parse_fixup_v390_v380_room_alt(m1, type, "Task1Res",
1889 m2, var2, NULL,
1890 hide_objects, changed, var3,
1891 display_room);
1892 }
1893
1894 /*
1895 * If still printing at this point, we need a catch-all room alt that will
1896 * print. So create one with an always true condition.
1897 */
1898 type = 0; /* Task condition */
1899 display_room = 2; /* Lowest priority output */
1900
1901 vt_key.string = "LastDesc";
1902 parse_push_key(vt_key, PROP_KEY_STRING);
1903 m1 = parse_get_string_property();
1904 parse_pop_key();
1905
1906 if (!sc_strempty(m1)) {
1907 var2 = 0; /* No task - always TRUE */
1908 var3 = 0;
1909 hide_objects = 0;
1910
1911 parse_fixup_v390_v380_room_alt(m1, type, "LastRes",
1912 m2, var2, NULL,
1913 hide_objects, changed, var3,
1914 display_room);
1915 }
1916 }
1917
1918
1919 /*
1920 * parse_fixup_v390()
1921 *
1922 * Handler for fixup special items to help with conversions from TAF version
1923 * 3.9 format into version 4.0.
1924 */
parse_fixup_v390(CONTEXT,const sc_char * fixup)1925 static void parse_fixup_v390(CONTEXT, const sc_char *fixup) {
1926 if (parse_trace)
1927 sc_trace("Parse: entering version 3.9 fixup %s\n", fixup);
1928
1929 /* Fixup a version 3.9 task action by incrementing Type > 4. */
1930 if (strcmp(fixup, "|V390_TASK_ACTION:Type>4?#Type++|") == 0) {
1931 sc_vartype_t vt_key, vt_value;
1932 sc_int type;
1933
1934 /* Retrieve Type, and if > 4, increment. */
1935 vt_key.string = "Type";
1936 parse_push_key(vt_key, PROP_KEY_STRING);
1937 type = parse_get_integer_property();
1938
1939 if (type > 4) {
1940 vt_value.integer = type + 1;
1941 parse_put_property(vt_value, PROP_INTEGER);
1942 }
1943
1944 parse_pop_key();
1945 }
1946
1947 /* Handle either Expr or Var5 for version 3.9 task actions. */
1948 else if (strcmp(fixup, "|V390_TASK_ACTION:$Expr_#Var5|") == 0) {
1949 sc_vartype_t vt_key;
1950 sc_int var2;
1951
1952 /* Either Expr or Var5, depending on Var2. */
1953 vt_key.string = "Var2";
1954 parse_push_key(vt_key, PROP_KEY_STRING);
1955 var2 = parse_get_integer_property();
1956 parse_pop_key();
1957
1958 if (var2 == 5) {
1959 CALL1(parse_descriptor, "$Expr ZVar5");
1960 } else {
1961 CALL1(parse_descriptor, "EExpr #Var5");
1962 }
1963 }
1964
1965 /*
1966 * Exchange openable values 5 and 6, and write -1 key for openable objects.
1967 */
1968 else if (strcmp(fixup, "|V390_OBJECT:_Openable_,Key|") == 0) {
1969 sc_vartype_t vt_key, vt_value;
1970 sc_int openable;
1971
1972 /* Retrieve Openable, and if 5 or 6, exchange. */
1973 vt_key.string = "Openable";
1974 parse_push_key(vt_key, PROP_KEY_STRING);
1975 openable = parse_get_integer_property();
1976
1977 if (openable == 5 || openable == 6) {
1978 vt_value.integer = (openable == 5) ? 6 : 5;
1979 parse_put_property(vt_value, PROP_INTEGER);
1980 }
1981
1982 parse_pop_key();
1983
1984 /* For openable objects, store a Key of -1. */
1985 if (openable == 5 || openable == 6) {
1986 vt_key.string = "Key";
1987 parse_push_key(vt_key, PROP_KEY_STRING);
1988 vt_value.integer = -1;
1989 parse_put_property(vt_value, PROP_INTEGER);
1990 parse_pop_key();
1991 }
1992 }
1993
1994 /* Create a RestrMask that 'and's all the restrictions together. */
1995 else if (strcmp(fixup, "|V390_TASK:$RestrMask|") == 0) {
1996 sc_vartype_t vt_key, vt_value;
1997 sc_int restriction_count;
1998
1999 /* Get a count of restrictions. */
2000 vt_key.string = "Restrictions";
2001 parse_push_key(vt_key, PROP_KEY_STRING);
2002 restriction_count = parse_get_child_count();
2003 parse_pop_key();
2004
2005 /* Allocate and fill a new mask for these restrictions. */
2006 if (restriction_count > 0) {
2007 sc_char *restrmask;
2008 sc_int index_;
2009
2010 restrmask = (sc_char *)sc_malloc(2 * restriction_count);
2011 strcpy(restrmask, "#");
2012 for (index_ = 1; index_ < restriction_count; index_++)
2013 strcat(restrmask, "A#");
2014
2015 vt_key.string = "RestrMask";
2016 parse_push_key(vt_key, PROP_KEY_STRING);
2017 vt_value.string = restrmask;
2018 parse_put_property(vt_value, PROP_STRING);
2019 parse_pop_key();
2020
2021 prop_adopt(parse_bundle, restrmask);
2022 }
2023 }
2024
2025 /*
2026 * Increment var1 for variable restrictions to compensate for there being no
2027 * referenced text comparison (no string variables).
2028 */
2029 else if (strcmp(fixup, "|V390_TASK_RESTR:Var1>0?#Var1++|") == 0) {
2030 sc_vartype_t vt_key, vt_value;
2031 sc_int var1;
2032
2033 /* Retrieve Var1, and if greater than zero, increment. */
2034 vt_key.string = "Var1";
2035 parse_push_key(vt_key, PROP_KEY_STRING);
2036 var1 = parse_get_integer_property();
2037
2038 if (var1 > 0) {
2039 vt_value.integer = var1 + 1;
2040 parse_put_property(vt_value, PROP_INTEGER);
2041 }
2042
2043 parse_pop_key();
2044 }
2045
2046 /* Convert version 3.9 fixed alts into a version 4.0 array. */
2047 else if (strcmp(fixup, "|V390_ROOM:_Alts_|") == 0) {
2048 parse_fixup_v390_v380_room_alts();
2049 }
2050
2051 /* Error if no fixup special handler available. */
2052 else {
2053 sc_fatal("parse_fixup_v390: no handler for \"%s\"\n", fixup);
2054 }
2055
2056 if (parse_trace)
2057 sc_trace("Parse: leaving version 3.9 fixup %s\n", fixup);
2058 }
2059
2060
2061 /*
2062 * Object surface and container masks for version 3.8 object fixup, container
2063 * capacity conversion factor and default object sizing, and the count of
2064 * task movements in a version 3.8 task.
2065 */
2066 enum { V380_OBJ_IS_SURFACE = 2, V380_OBJ_IS_CONTAINER = 1 };
2067 enum { V380_OBJ_CAPACITY_MULT = 10, V380_OBJ_DEFAULT_SIZE = 2 };
2068 enum { V380_TASK_MOVEMENTS = 6 };
2069
2070 /*
2071 * parse_fixup_v380_action()
2072 *
2073 * Helper for parse_fixup_v380(), adds a task action.
2074 */
parse_fixup_v380_action(sc_int type,sc_int var_count,sc_int var1,sc_int var2,sc_int var3)2075 static void parse_fixup_v380_action(sc_int type, sc_int var_count,
2076 sc_int var1, sc_int var2, sc_int var3) {
2077 sc_vartype_t vt_key, vt_value;
2078 sc_int action_count;
2079
2080 /* Get a count of actions so far defined for the task. */
2081 vt_key.string = "Actions";
2082 parse_push_key(vt_key, PROP_KEY_STRING);
2083 action_count = parse_get_child_count();
2084 parse_pop_key();
2085
2086 /* Write actions key, reversed to emulate parse actions. */
2087 vt_key.integer = action_count;
2088 parse_push_key(vt_key, PROP_KEY_INTEGER);
2089 vt_key.string = "Actions";
2090 parse_push_key(vt_key, PROP_KEY_STRING);
2091
2092 /* Write new action according to the given arguments. */
2093 vt_key.string = "Type";
2094 parse_push_key(vt_key, PROP_KEY_STRING);
2095 vt_value.integer = type;
2096 parse_put_property(vt_value, PROP_INTEGER);
2097 parse_pop_key();
2098
2099 vt_key.string = "Var1";
2100 parse_push_key(vt_key, PROP_KEY_STRING);
2101 vt_value.integer = var1;
2102 parse_put_property(vt_value, PROP_INTEGER);
2103 parse_pop_key();
2104
2105 if (var_count > 1) {
2106 vt_key.string = "Var2";
2107 parse_push_key(vt_key, PROP_KEY_STRING);
2108 vt_value.integer = var2;
2109 parse_put_property(vt_value, PROP_INTEGER);
2110 parse_pop_key();
2111 }
2112
2113 if (var_count > 2) {
2114 vt_key.string = "Var3";
2115 parse_push_key(vt_key, PROP_KEY_STRING);
2116 vt_value.integer = var3;
2117 parse_put_property(vt_value, PROP_INTEGER);
2118 parse_pop_key();
2119 }
2120
2121 parse_pop_key();
2122 parse_pop_key();
2123 }
2124
2125
2126 /*
2127 * parse_fixup_v380_movement()
2128 *
2129 * Helper for parse_fixup_v380(), converts a task movement into an action.
2130 */
parse_fixup_v380_movement(sc_int mvar1,sc_int mvar2,sc_int mvar3)2131 static void parse_fixup_v380_movement(sc_int mvar1, sc_int mvar2, sc_int mvar3) {
2132 sc_int var1;
2133
2134 /* If nothing was selected to move, ignore the call. */
2135 if (mvar1 == 0)
2136 return;
2137
2138 /*
2139 * Accept only player moves into rooms. Other combinations, such as move
2140 * player to worn by player, are unlikely. And move player to same room as
2141 * player isn't useful.
2142 */
2143 if (mvar1 == 1) {
2144 if (mvar3 == 0 && mvar2 >= 2)
2145 parse_fixup_v380_action(1, 3, 0, 0, mvar2 - 2);
2146 return;
2147 }
2148
2149 /*
2150 * Convert movement var1 into action var1. Var1 is the dynamic object + 3,
2151 * or 2 for referenced object, or 0 for all held.
2152 */
2153 switch (mvar1) {
2154 case 2:
2155 var1 = 2;
2156 break; /* Referenced obj */
2157 case 3:
2158 var1 = 0;
2159 break; /* All held */
2160 default:
2161 var1 = mvar1 - 1;
2162 break; /* Dynamic obj */
2163 }
2164
2165 /* Dissect the rest of the movement. */
2166 switch (mvar3) {
2167 case 0: /* To room */
2168 /*
2169 * Convert movement var2 into action var2 and var3. Var2 is 0 for move
2170 * to room, 6 for move to player room. Var3 is 0 for hidden, otherwise
2171 * the room number plus one.
2172 */
2173 if (mvar2 == 0) /* Hidden */
2174 parse_fixup_v380_action(0, 3, var1, 0, 0);
2175 else if (mvar2 == 1) /* Player room */
2176 parse_fixup_v380_action(0, 3, var1, 6, 0);
2177 else /* Specified room */
2178 parse_fixup_v380_action(0, 3, var1, 0, mvar2 - 1);
2179 break;
2180
2181 case 1: /* To inside */
2182 case 2: /* To onto */
2183 /*
2184 * Convert movement var2 and var3 into action var3 and var2, a simple
2185 * conversion, but check that var2 is not 'not selected' first.
2186 */
2187 if (mvar2 > 0)
2188 parse_fixup_v380_action(0, 3, var1, mvar3 + 1, mvar2 - 1);
2189 break;
2190
2191 case 3: /* To held by */
2192 case 4: /* To worn by */
2193 /*
2194 * Convert movement var2 and var3 into action var3 and var2, in this
2195 * case a simple conversion, since version 4.0 task actions are close
2196 * here.
2197 */
2198 parse_fixup_v380_action(0, 3, var1, mvar3 + 1, mvar2);
2199 break;
2200
2201 default:
2202 sc_fatal("parse_fixup_v380_movement: invalid mvar3, %ld\n", mvar3);
2203 }
2204 }
2205
2206
2207 /*
2208 * parse_fixup_v380_restr()
2209 *
2210 * Helper for parse_fixup_v380(), adds a task restriction.
2211 */
parse_fixup_v380_restr(sc_int type,sc_int var_count,sc_int var1,sc_int var2,sc_int var3,const sc_char * failmessage)2212 static void parse_fixup_v380_restr(sc_int type, sc_int var_count,
2213 sc_int var1, sc_int var2, sc_int var3, const sc_char *failmessage) {
2214 sc_vartype_t vt_key, vt_value;
2215 sc_int restriction_count;
2216
2217 /* Get a count of restrictions so far defined for the task. */
2218 vt_key.string = "Restrictions";
2219 parse_push_key(vt_key, PROP_KEY_STRING);
2220 restriction_count = parse_get_child_count();
2221 parse_pop_key();
2222
2223 /* Write restrictions key, reversed to emulate parse actions. */
2224 vt_key.integer = restriction_count;
2225 parse_push_key(vt_key, PROP_KEY_INTEGER);
2226 vt_key.string = "Restrictions";
2227 parse_push_key(vt_key, PROP_KEY_STRING);
2228
2229 /* Write new restriction according to the given arguments. */
2230 vt_key.string = "Type";
2231 parse_push_key(vt_key, PROP_KEY_STRING);
2232 vt_value.integer = type;
2233 parse_put_property(vt_value, PROP_INTEGER);
2234 parse_pop_key();
2235
2236 vt_key.string = "Var1";
2237 parse_push_key(vt_key, PROP_KEY_STRING);
2238 vt_value.integer = var1;
2239 parse_put_property(vt_value, PROP_INTEGER);
2240 parse_pop_key();
2241
2242 if (var_count > 1) {
2243 vt_key.string = "Var2";
2244 parse_push_key(vt_key, PROP_KEY_STRING);
2245 vt_value.integer = var2;
2246 parse_put_property(vt_value, PROP_INTEGER);
2247 parse_pop_key();
2248 }
2249
2250 if (var_count > 2) {
2251 vt_key.string = "Var3";
2252 parse_push_key(vt_key, PROP_KEY_STRING);
2253 vt_value.integer = var3;
2254 parse_put_property(vt_value, PROP_INTEGER);
2255 parse_pop_key();
2256 }
2257
2258 vt_key.string = "FailMessage";
2259 parse_push_key(vt_key, PROP_KEY_STRING);
2260 vt_value.string = failmessage;
2261 parse_put_property(vt_value, PROP_STRING);
2262 parse_pop_key();
2263
2264 parse_pop_key();
2265 parse_pop_key();
2266 }
2267
2268
2269 /*
2270 * parse_fixup_v380_obj_restr()
2271 * parse_fixup_v380_task_restr()
2272 * parse_fixup_v380_wear_restr()
2273 * parse_fixup_v380_npc_restr()
2274 * parse_fixup_v380_objroom_restr()
2275 * parse_fixup_v380_objstate_restr()
2276 *
2277 * Helper handlers for parse_fixup_v380(); create task restrictions.
2278 */
parse_fixup_v380_obj_restr(sc_bool holding,sc_int holdobj,const sc_char * failmessage)2279 static void parse_fixup_v380_obj_restr(sc_bool holding, sc_int holdobj, const sc_char *failmessage) {
2280 /* Ignore if no object selected. */
2281 if (holdobj > 0) {
2282 sc_int var1, var2;
2283
2284 /*
2285 * Create version 4.0 task restriction to check for either the
2286 * referenced object or a dynamic object being either held or in the
2287 * same room (visible to player).
2288 */
2289 var1 = (holdobj == 1) ? 2 : holdobj + 1;
2290 var2 = holding ? 1 : 3;
2291 parse_fixup_v380_restr(0, 3, var1, var2, 0, failmessage);
2292 }
2293 }
2294
parse_fixup_v380_task_restr(sc_bool tasknotdone,sc_int task,const sc_char * failmessage)2295 static void parse_fixup_v380_task_restr(sc_bool tasknotdone, sc_int task, const sc_char *failmessage) {
2296 /* Ignore if no task selected. */
2297 if (task > 0) {
2298 sc_int var2;
2299
2300 /* Create version 4.0 restriction to check task state. */
2301 var2 = tasknotdone ? 1 : 0;
2302 parse_fixup_v380_restr(2, 2, task, var2, 0, failmessage);
2303 }
2304 }
2305
parse_fixup_v380_wear_restr(sc_int wearobj,const sc_char * failmessage)2306 static void parse_fixup_v380_wear_restr(sc_int wearobj, const sc_char *failmessage) {
2307 /* Ignore if no object selected. */
2308 if (wearobj > 0) {
2309 sc_vartype_t vt_key[3];
2310 sc_int object_count, object, dynamic, obj_index;
2311
2312 /*
2313 * Create version 4.0 restrictions for something or nothing worn by
2314 * player.
2315 */
2316 if (wearobj == 1) {
2317 parse_fixup_v380_restr(0, 3, 1, 2, 0, failmessage);
2318 return;
2319 } else if (wearobj == 2) {
2320 parse_fixup_v380_restr(0, 3, 0, 2, 0, failmessage);
2321 return;
2322 }
2323
2324 /* Get the count of objects defined. */
2325 vt_key[0].string = "Objects";
2326 object_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
2327
2328 /* Convert wearobj from worn index to object index. */
2329 wearobj -= 2;
2330 for (object = 0; object < object_count && wearobj > 0; object++) {
2331 sc_bool bstatic, wearable;
2332
2333 vt_key[1].integer = object;
2334 vt_key[2].string = "Static";
2335 bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2336 if (!bstatic) {
2337 vt_key[2].string = "Wearable";
2338 wearable = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2339 if (wearable)
2340 wearobj--;
2341 }
2342 }
2343 obj_index = object - 1;
2344
2345 /* Now convert wearobj from object index to dynamic index. */
2346 dynamic = 0;
2347 for (object = 0; object <= obj_index; object++) {
2348 sc_bool bstatic;
2349
2350 vt_key[1].integer = object;
2351 vt_key[2].string = "Static";
2352 bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2353 if (!bstatic)
2354 dynamic++;
2355 }
2356 dynamic--;
2357
2358 /* Create version 4.0 restriction for object worn by player. */
2359 parse_fixup_v380_restr(0, 3, dynamic + 3, 2, 0, failmessage);
2360 }
2361 }
2362
parse_fixup_v380_npc_restr(sc_bool notinsameroom,sc_int npc,const sc_char * failmessage)2363 static void parse_fixup_v380_npc_restr(sc_bool notinsameroom, sc_int npc,
2364 const sc_char *failmessage) {
2365 /* Ignore if no NPC selected. */
2366 if (npc > 0) {
2367 sc_int var2;
2368
2369 if (npc == 1) {
2370 /* Create restriction to look for alone, or not. */
2371 var2 = notinsameroom ? 3 : 2;
2372 parse_fixup_v380_restr(3, 3, 0, var2, 0, failmessage);
2373 return;
2374 }
2375
2376 /* Create restriction to look for company. */
2377 var2 = notinsameroom ? 1 : 0;
2378 parse_fixup_v380_restr(3, 3, 0, var2, npc, failmessage);
2379 }
2380 }
2381
parse_fixup_v380_objroom_restr(sc_int obj,sc_int objroom,const sc_char * failmessage)2382 static void parse_fixup_v380_objroom_restr(sc_int obj, sc_int objroom, const sc_char *failmessage) {
2383 /* Ignore if no object selected. */
2384 if (obj > 0) {
2385 /* Create version 4.0 restriction to check object in room. */
2386 parse_fixup_v380_restr(0, 3, obj + 1, 0, objroom, failmessage);
2387 }
2388 }
2389
parse_fixup_v380_objstate_restr(sc_int obj,sc_int ivar1,sc_int ivar2,const sc_char * failmessage)2390 static void parse_fixup_v380_objstate_restr(sc_int obj, sc_int ivar1, sc_int ivar2,
2391 const sc_char *failmessage) {
2392 sc_vartype_t vt_key[3];
2393 sc_int object, dynamic, var2, var3;
2394
2395 /* Initialize variables to avoid gcc warnings. */
2396 var2 = -1;
2397 var3 = -1;
2398
2399 /* Ignore restrictions with no "type". */
2400 if (ivar1 == 0)
2401 return;
2402
2403 /* Look for opened/closed restrictions, convert and return. */
2404 if (ivar1 == 3 || ivar1 == 4) {
2405 sc_int stateful;
2406
2407 /* Convert obj from object to openable (stateful) index. */
2408 stateful = 0;
2409 for (object = 0; object <= obj - 1; object++) {
2410 sc_int openable;
2411
2412 vt_key[0].string = "Objects";
2413 vt_key[1].integer = object;
2414 vt_key[2].string = "Openable";
2415 openable = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2416 if (openable > 0)
2417 stateful++;
2418 }
2419 stateful--;
2420
2421 /*
2422 * Create a version 4.0 restriction that checks that an object's state
2423 * is open (var2 = 0) or closed (var2 = 1).
2424 */
2425 var2 = (ivar1 == 3) ? 0 : 1;
2426 parse_fixup_v380_restr(1, 2, stateful + 1, var2, 0, failmessage);
2427 return;
2428 }
2429
2430 /* Convert obj from object to dynamic index. */
2431 dynamic = 0;
2432 for (object = 0; object <= obj - 1; object++) {
2433 sc_bool bstatic;
2434
2435 vt_key[0].string = "Objects";
2436 vt_key[1].integer = object;
2437 vt_key[2].string = "Static";
2438 bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2439 if (!bstatic)
2440 dynamic++;
2441 }
2442 dynamic--;
2443
2444 /* Create version 4.0 object location restrictions for the rest. */
2445 switch (ivar1) {
2446 case 1:
2447 var2 = 4;
2448 var3 = ivar2;
2449 break; /* Inside */
2450 case 2:
2451 var2 = 5;
2452 var3 = ivar2;
2453 break; /* On */
2454 case 5:
2455 var2 = 1;
2456 var3 = ivar2 + 1;
2457 break; /* Held by */
2458 case 6:
2459 var2 = 2;
2460 var3 = ivar2 + 1;
2461 break; /* Worn by */
2462 default:
2463 sc_fatal("parse_fixup_v380_objstate_restr: invalid ivar1, %ld\n", ivar1);
2464 }
2465 parse_fixup_v380_restr(0, 3, dynamic + 3, var2, var3, failmessage);
2466 }
2467
2468
2469 /*
2470 * parse_fixup_v380()
2471 *
2472 * Handler for fixup special items to help with conversions from TAF version
2473 * 3.8 format into version 4.0.
2474 */
parse_fixup_v380(const sc_char * fixup)2475 static void parse_fixup_v380(const sc_char *fixup) {
2476 if (parse_trace)
2477 sc_trace("Parse: entering version 3.8 fixup %s\n", fixup);
2478
2479 /* Convert container capacity attributes to version 4.0 values. */
2480 if (strcmp(fixup, "|V380_OBJECT:#Capacity*10+2|") == 0) {
2481 sc_vartype_t vt_key, vt_value;
2482 sc_int surfacecontainer;
2483
2484 /* Get the object surface and container attributes. */
2485 vt_key.string = "SurfaceContainer";
2486 parse_push_key(vt_key, PROP_KEY_STRING);
2487 surfacecontainer = parse_get_integer_property();
2488 parse_pop_key();
2489
2490 /* Convert capacity from version 3.8 format to version 4.0. */
2491 if (surfacecontainer == V380_OBJ_IS_CONTAINER) {
2492 sc_int capacity;
2493
2494 vt_key.string = "Capacity";
2495 parse_push_key(vt_key, PROP_KEY_STRING);
2496 capacity = parse_get_integer_property();
2497
2498 capacity = capacity * V380_OBJ_CAPACITY_MULT + V380_OBJ_DEFAULT_SIZE;
2499
2500 vt_value.integer = capacity;
2501 parse_put_property(vt_value, PROP_INTEGER);
2502 parse_pop_key();
2503 }
2504 }
2505
2506 /*
2507 * Exchange openable values 5 and 6, watch for a possible 1 from a 3.8 game
2508 * (interpret as 0), and write -1 key for openable objects.
2509 */
2510 else if (strcmp(fixup, "|V380_OBJECT:_Openable_,Key|") == 0) {
2511 sc_vartype_t vt_key, vt_value;
2512 sc_int openable;
2513
2514 /* Retrieve Openable, and if 5 or 6, exchange. */
2515 vt_key.string = "Openable";
2516 parse_push_key(vt_key, PROP_KEY_STRING);
2517 openable = parse_get_integer_property();
2518
2519 if (openable == 5 || openable == 6) {
2520 vt_value.integer = (openable == 5) ? 6 : 5;
2521 parse_put_property(vt_value, PROP_INTEGER);
2522 }
2523
2524 /* If the odd value of 1, rewrite as zero. */
2525 else if (openable == 1) {
2526 vt_value.integer = 0;
2527 parse_put_property(vt_value, PROP_INTEGER);
2528 }
2529
2530 parse_pop_key();
2531
2532 /* For openable objects, store a Key of -1. */
2533 if (openable == 5 || openable == 6) {
2534 vt_key.string = "Key";
2535 parse_push_key(vt_key, PROP_KEY_STRING);
2536 vt_value.integer = -1;
2537 parse_put_property(vt_value, PROP_INTEGER);
2538 parse_pop_key();
2539 }
2540 }
2541
2542 /* Create version 4.0 task actions from a version 3.8 task. */
2543 else if (strcmp(fixup, "|V380_TASK:_Actions_|") == 0) {
2544 sc_vartype_t vt_key;
2545 sc_int score;
2546 sc_bool killsplayer, wingame;
2547 sc_int movement;
2548
2549 /* Retrieve the score change for the task. */
2550 vt_key.string = "Score";
2551 parse_push_key(vt_key, PROP_KEY_STRING);
2552 score = parse_get_integer_property();
2553 parse_pop_key();
2554
2555 /* Create any appropriate score change action. */
2556 if (score != 0)
2557 parse_fixup_v380_action(4, 1, score, 0, 0);
2558
2559 /* Get player death and game winning flags. */
2560 vt_key.string = "KillsPlayer";
2561 parse_push_key(vt_key, PROP_KEY_STRING);
2562 killsplayer = parse_get_boolean_property();
2563 parse_pop_key();
2564 vt_key.string = "WinGame";
2565 parse_push_key(vt_key, PROP_KEY_STRING);
2566 wingame = parse_get_boolean_property();
2567 parse_pop_key();
2568
2569 /* Create any appropriate game ending actions. */
2570 if (killsplayer)
2571 parse_fixup_v380_action(6, 1, 2, 0, 0);
2572 if (wingame)
2573 parse_fixup_v380_action(6, 1, 0, 0, 0);
2574
2575 /* Handle each defined movement for the task. */
2576 for (movement = 0; movement < V380_TASK_MOVEMENTS; movement++) {
2577 sc_int mvar1, mvar2, mvar3;
2578
2579 vt_key.integer = movement;
2580 parse_push_key(vt_key, PROP_KEY_INTEGER);
2581 vt_key.string = "Movements";
2582 parse_push_key(vt_key, PROP_KEY_STRING);
2583
2584 /* Retrieve the movement parameters. */
2585 vt_key.string = "Var1";
2586 parse_push_key(vt_key, PROP_KEY_STRING);
2587 mvar1 = parse_get_integer_property();
2588 parse_pop_key();
2589 vt_key.string = "Var2";
2590 parse_push_key(vt_key, PROP_KEY_STRING);
2591 mvar2 = parse_get_integer_property();
2592 parse_pop_key();
2593 vt_key.string = "Var3";
2594 parse_push_key(vt_key, PROP_KEY_STRING);
2595 mvar3 = parse_get_integer_property();
2596 parse_pop_key();
2597
2598 parse_pop_key();
2599 parse_pop_key();
2600
2601 /* Create the corresponding task action. */
2602 parse_fixup_v380_movement(mvar1, mvar2, mvar3);
2603 }
2604 }
2605
2606 /* Create version 4.0 task restrictions from a version 3.8 task. */
2607 else if (strcmp(fixup, "|V380_TASK:_Restrictions_|") == 0) {
2608 sc_vartype_t vt_key, vt_value;
2609 sc_bool holding, tasknotdone, notinsameroom;
2610 sc_int holdobj1, holdobj2, holdobj3, task;
2611 sc_int wearobj1, wearobj2, npc, obj1, obj1room, obj2;
2612 const sc_char *holdmsg, *taskmsg, *wearmsg, *companymsg;
2613 const sc_char *obj1msg;
2614 sc_int restriction_count;
2615
2616 /* Create restrictions for objects not held or absent. */
2617 vt_key.string = "HoldingSameRoom";
2618 parse_push_key(vt_key, PROP_KEY_STRING);
2619 holding = parse_get_boolean_property();
2620 parse_pop_key();
2621
2622 vt_key.string = "HoldObj1";
2623 parse_push_key(vt_key, PROP_KEY_STRING);
2624 holdobj1 = parse_get_integer_property();
2625 parse_pop_key();
2626
2627 vt_key.string = "HoldObj2";
2628 parse_push_key(vt_key, PROP_KEY_STRING);
2629 holdobj2 = parse_get_integer_property();
2630 parse_pop_key();
2631
2632 vt_key.string = "HoldObj3";
2633 parse_push_key(vt_key, PROP_KEY_STRING);
2634 holdobj3 = parse_get_integer_property();
2635 parse_pop_key();
2636
2637 vt_key.string = "HoldMsg";
2638 parse_push_key(vt_key, PROP_KEY_STRING);
2639 holdmsg = parse_get_string_property();
2640 parse_pop_key();
2641
2642 parse_fixup_v380_obj_restr(holding, holdobj1, holdmsg);
2643 parse_fixup_v380_obj_restr(holding, holdobj2, holdmsg);
2644 parse_fixup_v380_obj_restr(holding, holdobj3, holdmsg);
2645
2646 /* Create any task state restriction. */
2647 vt_key.string = "TaskNotDone";
2648 parse_push_key(vt_key, PROP_KEY_STRING);
2649 tasknotdone = parse_get_boolean_property();
2650 parse_pop_key();
2651
2652 vt_key.string = "Task";
2653 parse_push_key(vt_key, PROP_KEY_STRING);
2654 task = parse_get_integer_property();
2655 parse_pop_key();
2656
2657 vt_key.string = "TaskMsg";
2658 parse_push_key(vt_key, PROP_KEY_STRING);
2659 taskmsg = parse_get_string_property();
2660 parse_pop_key();
2661
2662 parse_fixup_v380_task_restr(tasknotdone, task, taskmsg);
2663
2664 /* Create any object not worn restrictions. */
2665 vt_key.string = "WearObj1";
2666 parse_push_key(vt_key, PROP_KEY_STRING);
2667 wearobj1 = parse_get_integer_property();
2668 parse_pop_key();
2669
2670 vt_key.string = "WearObj2";
2671 parse_push_key(vt_key, PROP_KEY_STRING);
2672 wearobj2 = parse_get_integer_property();
2673 parse_pop_key();
2674
2675 vt_key.string = "WearMsg";
2676 parse_push_key(vt_key, PROP_KEY_STRING);
2677 wearmsg = parse_get_string_property();
2678 parse_pop_key();
2679
2680 parse_fixup_v380_wear_restr(wearobj1, wearmsg);
2681 parse_fixup_v380_wear_restr(wearobj2, wearmsg);
2682
2683 /* Check for presence/absence of NPCs restriction. */
2684 vt_key.string = "NotInSameRoom";
2685 parse_push_key(vt_key, PROP_KEY_STRING);
2686 notinsameroom = parse_get_boolean_property();
2687 parse_pop_key();
2688
2689 vt_key.string = "NPC";
2690 parse_push_key(vt_key, PROP_KEY_STRING);
2691 npc = parse_get_integer_property();
2692 parse_pop_key();
2693
2694 vt_key.string = "CompanyMsg";
2695 parse_push_key(vt_key, PROP_KEY_STRING);
2696 companymsg = parse_get_string_property();
2697 parse_pop_key();
2698
2699 parse_fixup_v380_npc_restr(notinsameroom, npc, companymsg);
2700
2701 /* Create any object location restriction. */
2702 vt_key.string = "Obj1";
2703 parse_push_key(vt_key, PROP_KEY_STRING);
2704 obj1 = parse_get_integer_property();
2705 parse_pop_key();
2706
2707 vt_key.string = "Obj1Room";
2708 parse_push_key(vt_key, PROP_KEY_STRING);
2709 obj1room = parse_get_integer_property();
2710 parse_pop_key();
2711
2712 vt_key.string = "Obj1Msg";
2713 parse_push_key(vt_key, PROP_KEY_STRING);
2714 obj1msg = parse_get_string_property();
2715 parse_pop_key();
2716
2717 parse_fixup_v380_objroom_restr(obj1, obj1room, obj1msg);
2718
2719 /* And finally, any object state restriction. */
2720 vt_key.string = "Obj2";
2721 parse_push_key(vt_key, PROP_KEY_STRING);
2722 obj2 = parse_get_integer_property();
2723 parse_pop_key();
2724
2725 if (obj2 > 0) {
2726 sc_int var1, var2;
2727 const sc_char *obj2msg;
2728
2729 vt_key.string = "Obj2Var1";
2730 parse_push_key(vt_key, PROP_KEY_STRING);
2731 var1 = parse_get_integer_property();
2732 parse_pop_key();
2733
2734 vt_key.string = "Obj2Var2";
2735 parse_push_key(vt_key, PROP_KEY_STRING);
2736 var2 = parse_get_integer_property();
2737 parse_pop_key();
2738
2739 vt_key.string = "Obj2Msg";
2740 parse_push_key(vt_key, PROP_KEY_STRING);
2741 obj2msg = parse_get_string_property();
2742 parse_pop_key();
2743
2744 parse_fixup_v380_objstate_restr(obj2, var1, var2, obj2msg);
2745 }
2746
2747 /* Get a count of restrictions created. */
2748 vt_key.string = "Restrictions";
2749 parse_push_key(vt_key, PROP_KEY_STRING);
2750 restriction_count = parse_get_child_count();
2751 parse_pop_key();
2752
2753 /* Allocate and fill a new mask for these restrictions. */
2754 if (restriction_count > 0) {
2755 sc_char *restrmask;
2756 sc_int index_;
2757
2758 restrmask = (sc_char *)sc_malloc(2 * restriction_count);
2759 strcpy(restrmask, "#");
2760 for (index_ = 1; index_ < restriction_count; index_++)
2761 strcat(restrmask, "A#");
2762
2763 vt_key.string = "RestrMask";
2764 parse_push_key(vt_key, PROP_KEY_STRING);
2765 vt_value.string = restrmask;
2766 parse_put_property(vt_value, PROP_STRING);
2767 parse_pop_key();
2768
2769 prop_adopt(parse_bundle, restrmask);
2770 }
2771 }
2772
2773 /*
2774 * Adjust dynamic object initial positions and parents (where contained
2775 * or on surfaces) into version 4.0 range.
2776 */
2777 else if (strcmp(fixup, "|V380_OBJECT:_InitialPositions_|") == 0) {
2778 sc_vartype_t vt_key[3];
2779 sc_int object_count, object, *object_type;
2780
2781 /* Get a count of objects. */
2782 vt_key[0].string = "Objects";
2783 object_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
2784
2785 /* Build an array of object container/surface types. */
2786 object_type = (sc_int *)sc_malloc(object_count * sizeof(*object_type));
2787 for (object = 0; object < object_count; object++) {
2788 vt_key[1].integer = object;
2789 vt_key[2].string = "SurfaceContainer";
2790 object_type[object] = prop_get_integer(parse_bundle,
2791 "I<-sis", vt_key);
2792 }
2793
2794 /* Adjust each object's initial position if necessary. */
2795 for (object = 0; object < object_count; object++) {
2796 sc_vartype_t vt_value;
2797 sc_bool is_static;
2798 sc_int initialposition;
2799
2800 /* Ignore static objects; we only want dynamic ones. */
2801 vt_key[1].integer = object;
2802 vt_key[2].string = "Static";
2803 is_static = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2804 if (is_static)
2805 continue;
2806
2807 /* If initial position is above on/in, increment. */
2808 vt_key[1].integer = object;
2809 vt_key[2].string = "InitialPosition";
2810 initialposition = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2811 if (initialposition > 2) {
2812 vt_value.integer = initialposition + 1;
2813 prop_put(parse_bundle, "I->sis", vt_value, vt_key);
2814 }
2815
2816 /*
2817 * If initial position is on or in, decide which, depending on the
2818 * type of the parent. From this, expand initial position into a
2819 * version 4.0 value.
2820 */
2821 if (initialposition == 2) {
2822 sc_int count, parent, index_;
2823
2824 /* Get parent container/surface index. */
2825 vt_key[1].integer = object;
2826 vt_key[2].string = "Parent";
2827 count = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2828
2829 /* Convert container/surface index. */
2830 for (parent = 0; parent < object_count && count >= 0; parent++) {
2831 if (object_type[parent] == V380_OBJ_IS_CONTAINER
2832 || object_type[parent] == V380_OBJ_IS_SURFACE)
2833 count--;
2834 }
2835 parent--;
2836
2837 /* If parent is a surface, adjust position. */
2838 if (object_type[parent] == V380_OBJ_IS_SURFACE) {
2839 vt_key[2].string = "InitialPosition";
2840 vt_value.integer = initialposition + 1;
2841 prop_put(parse_bundle, "I->sis", vt_value, vt_key);
2842 }
2843
2844 /*
2845 * For both, adjust parent to be an object index for that type
2846 * of object only.
2847 */
2848 count = 0;
2849 for (index_ = 0; index_ < parent; index_++) {
2850 if (object_type[index_] == object_type[parent])
2851 count++;
2852 }
2853 vt_key[2].string = "Parent";
2854 vt_value.integer = count;
2855 prop_put(parse_bundle, "I->sis", vt_value, vt_key);
2856 }
2857 }
2858
2859 /* Done with temporary array. */
2860 sc_free(object_type);
2861 }
2862
2863 /* Convert carry limit into version 4.0-like size and weight limits. */
2864 else if (strcmp(fixup, "|V380_MaxSize_MaxWt_|") == 0) {
2865 sc_vartype_t vt_key, vt_value;
2866 sc_int maxcarried;
2867
2868 vt_key.string = "MaxCarried";
2869 parse_push_key(vt_key, PROP_KEY_STRING);
2870 maxcarried = parse_get_integer_property();
2871 parse_pop_key();
2872
2873 vt_value.integer = maxcarried * V380_OBJ_CAPACITY_MULT
2874 + V380_OBJ_DEFAULT_SIZE;
2875
2876 vt_key.string = "MaxSize";
2877 parse_push_key(vt_key, PROP_KEY_STRING);
2878 parse_put_property(vt_value, PROP_INTEGER);
2879 parse_pop_key();
2880
2881 vt_key.string = "MaxWt";
2882 parse_push_key(vt_key, PROP_KEY_STRING);
2883 parse_put_property(vt_value, PROP_INTEGER);
2884 parse_pop_key();
2885 }
2886
2887 /* Add up positive scoring tasks to arrive at max score. */
2888 else if (strcmp(fixup, "|V380_GLOBAL:_MaxScore_|") == 0) {
2889 sc_vartype_t vt_key[3], vt_value;
2890 sc_int task_count, maxscore, task;
2891
2892 /* Get a count of tasks. */
2893 vt_key[0].string = "Tasks";
2894 task_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
2895
2896 /* Sum positive scoring tasks. */
2897 maxscore = 0;
2898 for (task = 0; task < task_count; task++) {
2899 sc_int score;
2900
2901 vt_key[1].integer = task;
2902 vt_key[2].string = "Score";
2903 score = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2904 if (score > 0)
2905 maxscore += score;
2906 }
2907
2908 /* Write MaxScore global property. */
2909 vt_key[0].string = "Globals";
2910 vt_key[1].string = "MaxScore";
2911 vt_value.integer = maxscore;
2912 prop_put(parse_bundle, "I->ss", vt_value, vt_key);
2913 }
2914
2915 /* Convert walk meetobject from dynamic index to object. */
2916 else if (strcmp(fixup, "|V380_WALK:_MeetObject_|") == 0) {
2917 sc_vartype_t vt_key, vt_value, vt_gkey[3];
2918 sc_int meetobject, count, object_count, object;
2919
2920 vt_key.string = "MeetObject";
2921 parse_push_key(vt_key, PROP_KEY_STRING);
2922 meetobject = parse_get_integer_property();
2923
2924 /* Get a count of objects. */
2925 vt_gkey[0].string = "Objects";
2926 object_count = prop_get_child_count(parse_bundle, "I<-s", vt_gkey);
2927
2928 /* Convert dynamic index to object, and rewrite. */
2929 count = meetobject - 1;
2930 for (object = 0; object < object_count && count >= 0; object++) {
2931 sc_bool bstatic;
2932
2933 vt_gkey[1].integer = object;
2934 vt_gkey[2].string = "Static";
2935 bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_gkey);
2936 if (!bstatic)
2937 count--;
2938 }
2939 object--;
2940
2941 vt_value.integer = object;
2942 parse_put_property(vt_value, PROP_INTEGER);
2943 parse_pop_key();
2944 }
2945
2946 /* Convert version 3.8 room data into a version 4.0 alts array. */
2947 else if (strcmp(fixup, "|V380_ROOM:_Alts_|") == 0) {
2948 parse_fixup_v390_v380_room_alts();
2949 }
2950
2951 /* Error if no fixup special handler available. */
2952 else {
2953 sc_fatal("parse_fixup_v380: no handler for \"%s\"\n", fixup);
2954 }
2955
2956 if (parse_trace)
2957 sc_trace("Parse: leaving version 3.8 fixup %s\n", fixup);
2958 }
2959
2960
2961 /*
2962 * parse_fixup()
2963 *
2964 * Handler for fixup special items to help with conversions from TAF version
2965 * 3.9 and version 3.8 formats into version 4.0.
2966 */
parse_fixup(CONTEXT,const sc_char * fixup)2967 static void parse_fixup(CONTEXT, const sc_char *fixup) {
2968 /*
2969 * Pick a fixup handler specific to the TAF version. This helps keep
2970 * fixup code separate, rather than glommed into one large function.
2971 */
2972 switch (taf_get_version(parse_taf)) {
2973 case TAF_VERSION_400:
2974 sc_fatal("parse_fixup: unexpected call\n");
2975 break;
2976 case TAF_VERSION_390:
2977 CALL1(parse_fixup_v390, fixup);
2978 break;
2979 case TAF_VERSION_380:
2980 parse_fixup_v380(fixup);
2981 break;
2982 default:
2983 sc_fatal("parse_fixup: invalid TAF file version\n");
2984 break;
2985 }
2986 }
2987
2988
2989 /*
2990 * parse_element()
2991 *
2992 * Parse a class descriptor element.
2993 */
parse_element(CONTEXT,const sc_char * element)2994 static void parse_element(CONTEXT, const sc_char *element) {
2995 if (parse_trace)
2996 sc_trace("Parse: entering element %s\n", element);
2997
2998 /* Determine the element type from the first character. */
2999 switch (element[0]) {
3000 case PARSE_ARRAY:
3001 CALL1(parse_array, element);
3002 break;
3003 case PARSE_VECTOR:
3004 CALL1(parse_vector, element);
3005 break;
3006 case PARSE_VECTOR_ALTERNATE:
3007 CALL1(parse_vector_alternate, element);
3008 break;
3009 case PARSE_CLASS:
3010 CALL1(parse_class, element);
3011 break;
3012 case PARSE_EXPRESSION:
3013 CALL1(parse_expression, element);
3014 break;
3015 case PARSE_SPECIAL:
3016 CALL1(parse_special, element);
3017 break;
3018 case PARSE_FIXUP:
3019 CALL1(parse_fixup, element);
3020 break;
3021
3022 case PARSE_INTEGER:
3023 case PARSE_DEFAULT_ZERO:
3024 case PARSE_BOOLEAN:
3025 case PARSE_DEFAULT_TRUE:
3026 case PARSE_DEFAULT_FALSE:
3027 case PARSE_STRING:
3028 case PARSE_DEFAULT_EMPTY:
3029 case PARSE_IGNORE_INTEGER:
3030 case PARSE_IGNORE_BOOLEAN:
3031 case PARSE_IGNORE_STRING:
3032 case PARSE_MULTILINE:
3033 CALL1(parse_terminal, element);
3034 break;
3035 default:
3036 sc_fatal("parse_element: bad type, %c\n", element[0]);
3037 }
3038
3039 if (parse_trace)
3040 sc_trace("Parse: leaving element %s\n", element);
3041 }
3042
3043
3044 /*
3045 * parse_descriptor()
3046 *
3047 * Parse a class's properties descriptor list.
3048 */
parse_descriptor(CONTEXT,const sc_char * descriptor)3049 static void parse_descriptor(CONTEXT, const sc_char *descriptor) {
3050 sc_int next;
3051
3052 /* Find and parse each element in the descriptor. */
3053 for (next = 0; descriptor[next] != NUL;) {
3054 sc_char element[PARSE_TEMP_LENGTH];
3055
3056 /* Isolate the next descriptor element. */
3057 if (sscanf(descriptor + next, "%[^ ]", element) != 1)
3058 sc_fatal("parse_element: no element, %s\n", descriptor + next);
3059
3060 /* Parse this isolated element. */
3061 CALL1(parse_element, element);
3062
3063 /* Advance over the element and any trailing whitespace. */
3064 next += strlen(element);
3065 next += strspn(descriptor + next, " ");
3066 }
3067 }
3068
3069
3070 /*
3071 * parse_class()
3072 *
3073 * Parse a class of properties.
3074 */
parse_class(CONTEXT,const sc_char * class_)3075 static void parse_class(CONTEXT, const sc_char *class_) {
3076 sc_char class_name[PARSE_TEMP_LENGTH];
3077 sc_int index_;
3078 sc_vartype_t vt_key;
3079
3080 /* Isolate the class name. */
3081 if (sscanf(class_, "<%[^>]", class_name) != 1)
3082 sc_fatal("parse_class: error in class, %s\n", class_);
3083 if (parse_trace)
3084 sc_trace("Parse: entering class %s\n", class_name);
3085
3086 /* Find the class in the parse schema, and fail if not found. */
3087 for (index_ = 0; parse_schema[index_].class_name; index_++) {
3088 if (strcmp(parse_schema[index_].class_name, class_name) == 0)
3089 break;
3090 }
3091 if (!parse_schema[index_].class_name)
3092 sc_fatal("parse_class: class not described, %s\n", class_name);
3093
3094 /*
3095 * Unless we are at the top level of the parse schema, push the class tag
3096 * as a key. The top level is "_GAME_", index_ 0, and isn't part of key
3097 * formation.
3098 */
3099 if (index_ > 0) {
3100 vt_key.string = class_ + strlen(class_name) + 2;
3101 parse_push_key(vt_key, PROP_KEY_STRING);
3102 }
3103
3104 /* Parse each element in the descriptor. */
3105 CALL1(parse_descriptor, parse_schema[index_].descriptor);
3106
3107 /* Pop a key if the class tag was pushed above. */
3108 if (index_ > 0)
3109 parse_pop_key();
3110
3111 if (parse_trace)
3112 sc_trace("Parse: leaving class %s\n", class_name);
3113 }
3114
3115
3116 /*
3117 * parse_add_walkalerts()
3118 *
3119 * Add a list of all NPC walks started by each task. This is post-processing
3120 * that occurs after the TAF file has been successfully parsed.
3121 */
parse_add_walkalerts(sc_prop_setref_t bundle)3122 static void parse_add_walkalerts(sc_prop_setref_t bundle) {
3123 sc_vartype_t vt_key[5];
3124 sc_int npcs_count, npc;
3125
3126 /* Get the count of NPCs. */
3127 vt_key[0].string = "NPCs";
3128 npcs_count = prop_get_child_count(bundle, "I<-s", vt_key);
3129
3130 /* Set up each NPC. */
3131 for (npc = 0; npc < npcs_count; npc++) {
3132 sc_int walk_count, walk;
3133
3134 /* Get NPC walk details. */
3135 vt_key[1].integer = npc;
3136 vt_key[2].string = "Walks";
3137 walk_count = prop_get_child_count(bundle, "I<-sis", vt_key);
3138
3139 for (walk = 0; walk < walk_count; walk++) {
3140 sc_int starttask;
3141
3142 /* Get start task of walk. */
3143 vt_key[3].integer = walk;
3144 vt_key[4].string = "StartTask";
3145 starttask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
3146 if (starttask >= 0) {
3147 sc_vartype_t vt_key2[4], vt_value;
3148 sc_int count;
3149
3150 /* Count existing walkalerts for the task. */
3151 vt_key2[0].string = "Tasks";
3152 vt_key2[1].integer = starttask;
3153 vt_key2[2].string = "NPCWalkAlert";
3154 count = prop_get_child_count(bundle, "I<-sis", vt_key2);
3155
3156 /* Add two more -- NPC and walk. */
3157 vt_key2[3].integer = count;
3158 vt_value.integer = npc;
3159 prop_put(bundle, "I->sisi", vt_value, vt_key2);
3160 vt_key2[3].integer = count + 1;
3161 vt_value.integer = walk;
3162 prop_put(bundle, "I->sisi", vt_value, vt_key2);
3163 }
3164 }
3165 }
3166 }
3167
3168
3169 /*
3170 * parse_add_movetimes()
3171 *
3172 * Add a list of move times to all NPC walks. This is post-processing that
3173 * occurs after the TAF file has been successfully parsed.
3174 */
parse_add_movetimes(sc_prop_setref_t bundle)3175 static void parse_add_movetimes(sc_prop_setref_t bundle) {
3176 sc_vartype_t vt_key[6];
3177 sc_int npcs_count, npc;
3178
3179 /* Get the count of NPCs. */
3180 vt_key[0].string = "NPCs";
3181 npcs_count = prop_get_child_count(bundle, "I<-s", vt_key);
3182
3183 /* Set up each NPC. */
3184 for (npc = 0; npc < npcs_count; npc++) {
3185 sc_int walk_count, walk;
3186
3187 /* Get NPC walk details. */
3188 vt_key[1].integer = npc;
3189 vt_key[2].string = "Walks";
3190 walk_count = prop_get_child_count(bundle, "I<-sis", vt_key);
3191
3192 for (walk = 0; walk < walk_count; walk++) {
3193 sc_int waittimes;
3194 sc_int *movetimes, index_;
3195 sc_vartype_t vt_value;
3196
3197 vt_key[3].integer = walk;
3198 vt_key[4].string = "Times";
3199 waittimes = prop_get_child_count(bundle, "I<-sisis", vt_key);
3200
3201 movetimes = (sc_int *)sc_malloc((waittimes + 1) * sizeof(*movetimes));
3202 memset(movetimes, 0, (waittimes + 1) * sizeof(*movetimes));
3203 for (index_ = waittimes - 1; index_ >= 0; index_--) {
3204 vt_key[4].string = "Times";
3205 vt_key[5].integer = index_;
3206 movetimes[index_] = prop_get_integer(bundle, "I<-sisisi", vt_key)
3207 + movetimes[index_ + 1];
3208 }
3209 movetimes[waittimes] = -2;
3210
3211 for (index_ = 0; index_ <= waittimes; index_++) {
3212 vt_key[4].string = "MoveTimes";
3213 vt_key[5].integer = index_;
3214 vt_value.integer = movetimes[index_];
3215 prop_put(bundle, "I->sisisi", vt_value, vt_key);
3216 }
3217 sc_free(movetimes);
3218 }
3219 }
3220 }
3221
3222
3223 /*
3224 * parse_add_alrs_index()
3225 *
3226 * Sort ALRs by original string length and store an indexer property, so
3227 * that ALR replacements look at longer strings before shorter ones.
3228 */
parse_add_alrs_index(sc_prop_setref_t bundle)3229 static void parse_add_alrs_index(sc_prop_setref_t bundle) {
3230 sc_vartype_t vt_key[3];
3231 sc_int alr_count, index_, alr;
3232 sc_int *alr_lengths, longest, shortest, length;
3233
3234 /* Count ALRs, and set invariant part of properties key. */
3235 vt_key[0].string = "ALRs";
3236 alr_count = prop_get_child_count(bundle, "I<-s", vt_key);
3237
3238 /*
3239 * Set up an array of the lengths of ALR original strings, and while at it,
3240 * get the shortest and longest defined.
3241 */
3242 alr_lengths = (sc_int *)sc_malloc(alr_count * sizeof(*alr_lengths));
3243 shortest = INTEGER_MAX;
3244 longest = 0;
3245 for (index_ = 0; index_ < alr_count; index_++) {
3246 const sc_char *original;
3247
3248 vt_key[1].integer = index_;
3249 vt_key[2].string = "Original";
3250 original = prop_get_string(bundle, "S<-sis", vt_key);
3251 length = strlen(original);
3252
3253 alr_lengths[index_] = length;
3254 shortest = (length < shortest) ? length : shortest;
3255 longest = (length > longest) ? length : longest;
3256 }
3257
3258 /*
3259 * Now write a set of secondary properties that define the order of handling
3260 * for ALRs. Our friend qsort() can't help here as it doesn't define the
3261 * final ordering of equal members, and we need here to retain file ordering
3262 * for ALR originals of the same length.
3263 */
3264 vt_key[0].string = "ALRs2";
3265 alr = 0;
3266 for (length = longest; length >= shortest; length--) {
3267 /* Find and add each ALR of this length. */
3268 for (index_ = 0; index_ < alr_count; index_++) {
3269 if (alr_lengths[index_] == length) {
3270 sc_vartype_t vt_value;
3271
3272 vt_key[1].integer = alr++;
3273 vt_key[2].string = "ALRIndex";
3274 vt_value.integer = index_;
3275 prop_put(bundle, "I->sis", vt_value, vt_key);
3276 }
3277 }
3278 }
3279 assert(alr == alr_count);
3280
3281 /* Done with ALR lengths array. */
3282 sc_free(alr_lengths);
3283 }
3284
3285
3286 /*
3287 * parse_add_resources_offset()
3288 *
3289 * Add the resources offset to the properties as an extra game property
3290 * for version 4.0 games. For version 3.9 and version 3.8 games, write
3291 * zero; only version 4.0 games can embed their resources into the TAF file.
3292 */
parse_add_resources_offset(sc_prop_setref_t bundle,sc_tafref_t taf)3293 static void parse_add_resources_offset(sc_prop_setref_t bundle, sc_tafref_t taf) {
3294 sc_vartype_t vt_key[2], vt_value;
3295 sc_bool embedded;
3296 sc_int offset;
3297
3298 /*
3299 * Get the resources offset from the TAF, or default to zero. The resources
3300 * offset is one byte after the end of game data.
3301 */
3302 vt_key[0].string = "Globals";
3303 vt_key[1].string = "Embedded";
3304 embedded = prop_get_boolean(bundle, "B<-ss", vt_key);
3305 offset = embedded ? taf_get_game_data_length(taf) + 1 : 0;
3306
3307 /* Add this offset to the properties. */
3308 vt_key[0].string = "ResourceOffset";
3309 vt_value.integer = offset;
3310 prop_put(bundle, "I->s", vt_value, vt_key);
3311 }
3312
3313
3314 /*
3315 * parse_add_version()
3316 *
3317 * Add the TAF version to the properties, both integer and character forms
3318 * for convenience.
3319 */
parse_add_version(sc_prop_setref_t bundle,sc_tafref_t taf)3320 static void parse_add_version(sc_prop_setref_t bundle, sc_tafref_t taf) {
3321 sc_vartype_t vt_key, vt_value;
3322
3323 /* Add the version integer to the properties. */
3324 vt_key.string = "Version";
3325 vt_value.integer = taf_get_version(taf);
3326 prop_put(bundle, "I->s", vt_value, &vt_key);
3327
3328 /* Add the version string to the properties. */
3329 switch (taf_get_version(taf)) {
3330 case TAF_VERSION_400:
3331 vt_value.string = "4.00";
3332 break;
3333 case TAF_VERSION_390:
3334 vt_value.string = "3.90";
3335 break;
3336 case TAF_VERSION_380:
3337 vt_value.string = "3.80";
3338 break;
3339 default:
3340 sc_error("parse_add_version_string: invalid TAF file version\n");
3341 vt_value.string = "[Unknown version]";
3342 break;
3343 }
3344 vt_key.string = "VersionString";
3345 prop_put(bundle, "S->s", vt_value, &vt_key);
3346 }
3347
3348
3349 /*
3350 * parse_game()
3351 *
3352 * Parse a game into a set properties. Return TRUE on success, FALSE if
3353 * it encountered an error reading the TAF file.
3354 */
parse_game(sc_tafref_t taf,sc_prop_setref_t bundle)3355 sc_bool parse_game(sc_tafref_t taf, sc_prop_setref_t bundle) {
3356 assert(taf && bundle);
3357 Context context;
3358
3359 /* Store the TAF to read from, and the bundle to store into. */
3360 parse_taf = taf;
3361 parse_bundle = bundle;
3362 parse_schema = parse_select_schema(parse_taf);
3363 parse_depth = 0;
3364
3365 // Try parsing a complete game
3366 taf_first_line(parse_taf);
3367 parse_tafline = 0;
3368 parse_class(context, "<_GAME_>");
3369
3370 if (context._break) {
3371 // Error with one of the TAF file lines
3372 parse_clear_v400_resources_table();
3373 parse_taf = NULL;
3374 parse_bundle = NULL;
3375 parse_schema = NULL;
3376 parse_depth = 0;
3377 return FALSE;
3378 }
3379
3380 /* Free the accumulated version 4.0 resources details. */
3381 parse_clear_v400_resources_table();
3382
3383 /* See if we reached the end of the TAF. */
3384 if (taf_more_lines(parse_taf))
3385 sc_error("parse_game: unexpected trailing data\n");
3386
3387 /* Append post-processing walkalerts and move times. */
3388 parse_add_walkalerts(parse_bundle);
3389 parse_add_movetimes(parse_bundle);
3390
3391 /* Append sorted ALR list and resources offset. */
3392 parse_add_alrs_index(parse_bundle);
3393 parse_add_resources_offset(parse_bundle, parse_taf);
3394
3395 /* Add a note of the TAF file version. */
3396 parse_add_version(parse_bundle, parse_taf);
3397
3398 /* Trim excess allocations from properties. */
3399 prop_solidify(parse_bundle);
3400
3401 /* Return successfully. */
3402 parse_taf = NULL;
3403 parse_bundle = NULL;
3404 parse_schema = NULL;
3405 parse_depth = 0;
3406 return TRUE;
3407 }
3408
3409
3410 /*
3411 * parse_debug_trace()
3412 *
3413 * Set parse tracing on/off.
3414 */
parse_debug_trace(sc_bool flag)3415 void parse_debug_trace(sc_bool flag) {
3416 parse_trace = flag;
3417 }
3418
3419 } // End of namespace Adrift
3420 } // End of namespace Glk
3421