1 /*
2
3 Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
4 and the "Aleph One" developers.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 This license is contained in the file "COPYING",
17 which is included with this source code; it is available online at
18 http://www.gnu.org/licenses/gpl.html
19
20 Support for XML scripts in map files
21 by Loren Petrich,
22 April 16, 2000
23
24 The reason for a separate object is that it will be necessary to execute certain commands
25 only on certain levels.
26
27 Oct 13, 2000 (Loren Petrich)
28 Converted the in-memory script data into Standard Template Library vectors
29
30 Nov 25, 2000 (Loren Petrich)
31 Added support for specifying movies for levels, as Jesse Simko had requested.
32 Also added end-of-game support.
33
34 Jul 31, 2002 (Loren Petrich):
35 Added images.cpp/h accessor for TEXT resources,
36 because it can support the M2-Win95 map format.
37 */
38
39 #include <vector>
40 #include <map>
41 #include <sstream>
42
43 #include "cseries.h"
44 #include "shell.h"
45 #include "game_wad.h"
46 #include "Music.h"
47 #include "XML_LevelScript.h"
48 #include "XML_ParseTreeRoot.h"
49 #include "InfoTree.h"
50 #include "Plugins.h"
51 #include "Random.h"
52 #include "images.h"
53 #include "lua_script.h"
54 #include "Logging.h"
55
56 #include "OGL_LoadScreen.h"
57
58 #include "AStream.h"
59 #include "map.h"
60
61 // The "command" is an instruction to process a file/resource in a certain sort of way
62 struct LevelScriptCommand
63 {
64 // Types of commands
65 enum {
66 MML,
67 Music,
68 Movie,
69 #ifdef HAVE_LUA
70 Lua,
71 #endif /* HAVE_LUA */
72 #ifdef HAVE_OPENGL
73 LoadScreen
74 #endif
75 };
76 int Type;
77
78 enum {
79 UnsetResource = -32768
80 };
81
82 // Where to read the command data from:
83
84 // This is a MacOS resource ID
85 short RsrcID;
86
RsrcPresentLevelScriptCommand87 bool RsrcPresent() {return RsrcID != UnsetResource;}
88
89 // This is a Unix-style filespec, with form
90 // <dirname>/<dirname>/<filename>
91 // with the root directory being the map file's directory
92 std::string FileSpec;
93
94 // Additional data:
95
96 // Relative size for a movie (negative means don't use)
97 float Size;
98
99 // Additional load screen information:
100 short T, L, B, R;
101 rgb_color Colors[2];
102 bool Stretch;
103 bool Scale;
104
LevelScriptCommandLevelScriptCommand105 LevelScriptCommand(): RsrcID(UnsetResource), Size(NONE), L(0), T(0), R(0), B(0), Stretch(true), Scale(true) {
106 memset(&Colors[0], 0, sizeof(rgb_color));
107 memset(&Colors[1], 0xff, sizeof(rgb_color));
108 }
109 };
110
111 struct LevelScriptHeader
112 {
113 // Special pseudo-levels: restoration of previous parameters, defaults for this map file,
114 // and the end of the game
115 enum {
116 Restore = -3,
117 Default = -2,
118 End = -1
119 };
120
121 std::vector<LevelScriptCommand> Commands;
122
123 // Thanx to the Standard Template Library,
124 // the copy constructor and the assignment operator will be automatically implemented
125
126 // Whether the music files are to be played in random order;
127 // it defaults to false (sequential order)
128 bool RandomOrder;
129
LevelScriptHeaderLevelScriptHeader130 LevelScriptHeader(): RandomOrder(false) {}
131 };
132
133 // Scripts for current map file
134 static std::map<int, LevelScriptHeader> LevelScripts;
135
136 // Current script for adding commands to and for running
137 static LevelScriptHeader *CurrScriptPtr = NULL;
138
139 // Map-file parent directory (where all map-related files are supposed to live)
140 static DirectorySpecifier MapParentDir;
141
142 #ifdef HAVE_LUA
143 // Same for Lua
144 static bool LuaFound = false;
145 #endif /* HAVE_LUA */
146
147 // Movie filespec and whether it points to a real file
148 static FileSpecifier MovieFile;
149 static bool MovieFileExists = false;
150 static float MovieSize = NONE;
151
152 // For selecting the end-of-game screens --
153 // what fake level index for them, and how many to display
154 // (resource numbers increasing in sequence)
155 // (defaults from interface.cpp)
156 short EndScreenIndex = 99;
157 short NumEndScreens = 1;
158
159
160 // The level-script parsers are separate from the main MML ones,
161 // because they operate on per-level data.
162
163 // Parse marathon_levels script
164 static void parse_levels_xml(InfoTree root);
165
166 // This is for searching for a script and running it -- works for pseudo-levels
167 static void GeneralRunScript(int LevelIndex);
168
169 // Similar generic function for movies
170 static void FindMovieInScript(int LevelIndex);
171
172 // Defined in images.cpp and
173 extern bool get_text_resource_from_scenario(int resource_number, LoadedResource& TextRsrc);
174
175 // Loads all those in resource 128 in a map file (or some appropriate equivalent)
LoadLevelScripts(FileSpecifier & MapFile)176 void LoadLevelScripts(FileSpecifier& MapFile)
177 {
178 // Because all the files are to live in the map file's parent directory
179 MapFile.ToDirectory(MapParentDir);
180
181 // Get rid of the previous level script
182 // ghs: unless it's the first time, in which case we would be clearing
183 // any external level scripts, so don't
184 static bool FirstTime = true;
185 if (FirstTime)
186 FirstTime = false;
187 else
188 LevelScripts.clear();
189
190 // Set these to their defaults before trying to change them
191 EndScreenIndex = 99;
192 NumEndScreens = 1;
193
194 // OpenedResourceFile OFile;
195 // if (!MapFile.Open(OFile)) return;
196
197 // The script is stored at a special resource ID;
198 // simply quit if it could not be found
199 LoadedResource ScriptRsrc;
200
201 // if (!OFile.Get('T','E','X','T',128,ScriptRsrc)) return;
202 if (!get_text_resource_from_scenario(128,ScriptRsrc)) return;
203
204 // Load the script
205 std::istringstream strm(std::string((char *)ScriptRsrc.GetPointer(), ScriptRsrc.GetLength()));
206 try {
207 InfoTree root = InfoTree::load_xml(strm).get_child("marathon_levels");
208 parse_levels_xml(root);
209 } catch (InfoTree::parse_error e) {
210 logError("Error parsing map script in %s: %s", MapFile.GetPath(), e.what());
211 } catch (InfoTree::path_error e) {
212 logError("Error parsing map script in %s: %s", MapFile.GetPath(), e.what());
213 } catch (InfoTree::data_error e) {
214 logError("Error parsing map script in %s: %s", MapFile.GetPath(), e.what());
215 } catch (InfoTree::unexpected_error e) {
216 logError("Error parsing map script in %s: %s", MapFile.GetPath(), e.what());
217 }
218 }
219
ResetLevelScript()220 void ResetLevelScript()
221 {
222 // For whatever previous music had been playing...
223 Music::instance()->FadeOut(MACHINE_TICKS_PER_SECOND/2);
224
225 // If no scripts were loaded or none of them had music specified,
226 // then don't play any music
227 if (!Music::instance()->HasClassicLevelMusic())
228 Music::instance()->ClearLevelMusic();
229
230 #ifdef HAVE_OPENGL
231 OGL_LoadScreen::instance()->Clear();
232 #endif
233
234 // reset values to engine defaults first
235 ResetAllMMLValues();
236 // then load the base stuff (from Scripts folder and whatnot)
237 LoadBaseMMLScripts();
238 Plugins::instance()->load_mml();
239 }
240
241
242 // Runs a script for some level
243 // runs level-specific MML...
RunLevelScript(int LevelIndex)244 void RunLevelScript(int LevelIndex)
245 {
246 // None found just yet...
247 #ifdef HAVE_LUA
248 LuaFound = false;
249 #endif /* HAVE_LUA */
250
251 ResetLevelScript();
252
253 GeneralRunScript(LevelScriptHeader::Default);
254 GeneralRunScript(LevelIndex);
255
256 Music::instance()->SeedLevelMusic();
257
258 }
259
260 std::vector<uint8> mmls_chunk;
261 std::vector<uint8> luas_chunk;
262
RunScriptChunks()263 void RunScriptChunks()
264 {
265 int offset = 2;
266 while (offset < mmls_chunk.size())
267 {
268 if (offset + 8 + LEVEL_NAME_LENGTH > mmls_chunk.size())
269 break;
270
271 AIStreamBE header(&mmls_chunk[offset], 8 + LEVEL_NAME_LENGTH);
272 offset += 8 + LEVEL_NAME_LENGTH;
273
274 uint32 flags;
275 char name[LEVEL_NAME_LENGTH];
276 uint32 length;
277 header >> flags;
278 header.read(name, LEVEL_NAME_LENGTH);
279 name[LEVEL_NAME_LENGTH - 1] = '\0';
280 header >> length;
281 if (offset + length > mmls_chunk.size())
282 break;
283
284 if (length)
285 {
286 ParseMMLFromData(reinterpret_cast<char *>(&mmls_chunk[offset]), length);
287 }
288
289 offset += length;
290 }
291
292 offset = 2;
293 while (offset < luas_chunk.size())
294 {
295 if (offset + 8 + LEVEL_NAME_LENGTH > luas_chunk.size())
296 break;
297
298 AIStreamBE header(&luas_chunk[offset], 8 + LEVEL_NAME_LENGTH);
299 offset += 8 + LEVEL_NAME_LENGTH;
300
301 uint32 flags;
302 char name[LEVEL_NAME_LENGTH];
303 uint32 length;
304 header >> flags;
305 header.read(name, LEVEL_NAME_LENGTH);
306 name[LEVEL_NAME_LENGTH - 1] = '\0';
307 header >> length;
308 if (offset + length > luas_chunk.size())
309 break;
310
311 LoadLuaScript(reinterpret_cast<char *>(&luas_chunk[offset]), length, _embedded_lua_script);
312 offset += length;
313 }
314 }
315
316 // Intended to be run at the end of a game
RunEndScript()317 void RunEndScript()
318 {
319 GeneralRunScript(LevelScriptHeader::Default);
320 GeneralRunScript(LevelScriptHeader::End);
321 }
322
323 // Intended for restoring old parameter values, because MML sets values at a variety
324 // of different places, and it may be easier to simply set stuff back to defaults
325 // by including those defaults in the script.
RunRestorationScript()326 void RunRestorationScript()
327 {
328 GeneralRunScript(LevelScriptHeader::Default);
329 GeneralRunScript(LevelScriptHeader::Restore);
330 }
331
332 // Search for level script and then run it
GeneralRunScript(int LevelIndex)333 void GeneralRunScript(int LevelIndex)
334 {
335 // Find the pointer to the current script
336 if (LevelScripts.find(LevelIndex) == LevelScripts.end()) return;
337 CurrScriptPtr = &(LevelScripts[LevelIndex]);
338
339 // Insures that this order is the last order set
340 Music::instance()->LevelMusicRandom(CurrScriptPtr->RandomOrder);
341
342 // OpenedResourceFile OFile;
343 // FileSpecifier& MapFile = get_map_file();
344 // if (!MapFile.Open(OFile)) return;
345
346 for (unsigned k=0; k<CurrScriptPtr->Commands.size(); k++)
347 {
348 LevelScriptCommand& Cmd = CurrScriptPtr->Commands[k];
349
350 // Data to parse
351 char *Data = NULL;
352 size_t DataLen = 0;
353
354 // First, try to load a resource (only for scripts)
355 LoadedResource ScriptRsrc;
356 switch(Cmd.Type)
357 {
358 case LevelScriptCommand::MML:
359 #ifdef HAVE_LUA
360 case LevelScriptCommand::Lua:
361 #endif /* HAVE_LUA */
362 // if (Cmd.RsrcPresent() && OFile.Get('T','E','X','T',Cmd.RsrcID,ScriptRsrc))
363 if (Cmd.RsrcPresent() && get_text_resource_from_scenario(Cmd.RsrcID,ScriptRsrc))
364 {
365 Data = (char *)ScriptRsrc.GetPointer();
366 DataLen = ScriptRsrc.GetLength();
367 }
368 }
369
370 switch(Cmd.Type)
371 {
372 case LevelScriptCommand::MML:
373 {
374 // Skip if not loaded
375 if (Data == NULL || DataLen <= 0) break;
376
377 // Set to the MML root parser
378 // char ObjName[256];
379 // sprintf(ObjName,"[Map Rsrc %hd for Level %d]",Cmd.RsrcID,LevelIndex);
380 ParseMMLFromData(Data, DataLen);
381 }
382 break;
383
384 #ifdef HAVE_LUA
385
386 case LevelScriptCommand::Lua:
387 {
388 // Skip if not loaded
389 if (Data == NULL || DataLen <= 0) break;
390
391 // Load and indicate whether loading was successful
392 if (LoadLuaScript(Data, DataLen, _embedded_lua_script))
393 LuaFound = true;
394 }
395 break;
396 #endif /* HAVE_LUA */
397
398 case LevelScriptCommand::Music:
399 {
400 FileSpecifier MusicFile;
401 if (MusicFile.SetNameWithPath(Cmd.FileSpec.c_str()))
402 Music::instance()->PushBackLevelMusic(MusicFile);
403 }
404 break;
405 #ifdef HAVE_OPENGL
406 case LevelScriptCommand::LoadScreen:
407 {
408 if (Cmd.FileSpec.size() > 0)
409 {
410 if (Cmd.L || Cmd.T || Cmd.R || Cmd.B)
411 {
412 OGL_LoadScreen::instance()->Set(Cmd.FileSpec.c_str(), Cmd.Stretch, Cmd.Scale, Cmd.L, Cmd.T, Cmd.R - Cmd.L, Cmd.B - Cmd.T);
413 OGL_LoadScreen::instance()->Colors()[0] = Cmd.Colors[0];
414 OGL_LoadScreen::instance()->Colors()[1] = Cmd.Colors[1];
415 }
416 else
417 OGL_LoadScreen::instance()->Set(Cmd.FileSpec, Cmd.Stretch, Cmd.Scale);
418 }
419 }
420 #endif
421 // The movie info is handled separately
422
423 }
424 }
425 }
426
427
428
429 // Search for level script and then run it
FindMovieInScript(int LevelIndex)430 void FindMovieInScript(int LevelIndex)
431 {
432 // Find the pointer to the current script
433 if (LevelScripts.find(LevelIndex) == LevelScripts.end()) return;
434 CurrScriptPtr = &(LevelScripts[LevelIndex]);
435
436 for (unsigned k=0; k<CurrScriptPtr->Commands.size(); k++)
437 {
438 LevelScriptCommand& Cmd = CurrScriptPtr->Commands[k];
439
440 switch(Cmd.Type)
441 {
442 case LevelScriptCommand::Movie:
443 {
444 MovieFileExists = MovieFile.SetNameWithPath(Cmd.FileSpec.c_str());
445
446 // Set the size only if there was a movie file here
447 if (MovieFileExists)
448 MovieSize = Cmd.Size;
449 }
450 break;
451 }
452 }
453 }
454
455
456 // Movie functions
457
458 // Finds the level movie and the end movie, to be used in show_movie()
459 // The first is for some level,
460 // while the second is for the end of a game
FindLevelMovie(short index)461 void FindLevelMovie(short index)
462 {
463 MovieFileExists = false;
464 MovieSize = NONE;
465 FindMovieInScript(LevelScriptHeader::Default);
466 FindMovieInScript(index);
467 }
468
469
GetLevelMovie(float & Size)470 FileSpecifier *GetLevelMovie(float& Size)
471 {
472 if (MovieFileExists)
473 {
474 // Set only if the movie-size value is positive
475 if (MovieSize >= 0) Size = MovieSize;
476 return &MovieFile;
477 }
478 else
479 return NULL;
480 }
481
SetMMLS(uint8 * data,size_t length)482 void SetMMLS(uint8* data, size_t length)
483 {
484 if (!length)
485 {
486 mmls_chunk.clear();
487 }
488 else
489 {
490 mmls_chunk.resize(length);
491 memcpy(&mmls_chunk[0], data, length);
492 }
493 }
494
GetMMLS(size_t & length)495 uint8* GetMMLS(size_t& length)
496 {
497 length = mmls_chunk.size();
498 return length ? &mmls_chunk[0] : 0;
499 }
500
SetLUAS(uint8 * data,size_t length)501 void SetLUAS(uint8* data, size_t length)
502 {
503 if (!length)
504 {
505 luas_chunk.clear();
506 }
507 else
508 {
509 luas_chunk.resize(length);
510 memcpy(&luas_chunk[0], data, length);
511 }
512 }
513
GetLUAS(size_t & length)514 uint8* GetLUAS(size_t& length)
515 {
516 length = luas_chunk.size();
517 return length ? &luas_chunk[0] : 0;
518 }
519
520
reset_mml_default_levels()521 void reset_mml_default_levels()
522 {
523 // no reset
524 }
525
parse_mml_default_levels(const InfoTree & root)526 void parse_mml_default_levels(const InfoTree& root)
527 {
528 LevelScriptHeader *ls_ptr = &(LevelScripts[LevelScriptHeader::Default]);
529
530 BOOST_FOREACH(InfoTree child, root.children_named("music"))
531 {
532 LevelScriptCommand cmd;
533 cmd.Type = LevelScriptCommand::Music;
534
535 if (!child.read_attr("file", cmd.FileSpec))
536 continue;
537
538 ls_ptr->Commands.push_back(cmd);
539 }
540
541 BOOST_FOREACH(InfoTree child, root.children_named("random_order"))
542 {
543 child.read_attr("on", ls_ptr->RandomOrder);
544 }
545
546 #ifdef HAVE_OPENGL
547 BOOST_FOREACH(InfoTree child, root.children_named("load_screen"))
548 {
549 LevelScriptCommand cmd;
550 cmd.Type = LevelScriptCommand::LoadScreen;
551
552 if (!child.read_attr("file", cmd.FileSpec))
553 continue;
554
555 // expand relative file spec now (we may be in scoped search path)
556 FileSpecifier f;
557 if (f.SetNameWithPath(cmd.FileSpec.c_str()))
558 {
559 std::string base, part;
560 f.SplitPath(base, part);
561 cmd.FileSpec = base + '/' + part;
562 }
563
564 child.read_attr("stretch", cmd.Stretch);
565 child.read_attr("scale", cmd.Scale);
566 child.read_attr("progress_top", cmd.T);
567 child.read_attr("progress_bottom", cmd.B);
568 child.read_attr("progress_left", cmd.L);
569 child.read_attr("progress_right", cmd.R);
570
571 BOOST_FOREACH(InfoTree color, child.children_named("color"))
572 {
573 int index = -1;
574 if (color.read_attr_bounded("index", index, 0, 1))
575 {
576 color.read_color(cmd.Colors[index]);
577 }
578 }
579
580 ls_ptr->Commands.push_back(cmd);
581 }
582 #endif
583 }
584
parse_level_commands(InfoTree root,int index)585 void parse_level_commands(InfoTree root, int index)
586 {
587 // Find or create command list for this level
588 LevelScriptHeader *ls_ptr = &(LevelScripts[index]);
589
590 BOOST_FOREACH(InfoTree child, root.children_named("mml"))
591 {
592 LevelScriptCommand cmd;
593 cmd.Type = LevelScriptCommand::MML;
594
595 if (!child.read_attr_bounded<int16>("resource", cmd.RsrcID, 0, SHRT_MAX))
596 continue;
597
598 ls_ptr->Commands.push_back(cmd);
599 }
600
601 #ifdef HAVE_LUA
602 BOOST_FOREACH(InfoTree child, root.children_named("lua"))
603 {
604 LevelScriptCommand cmd;
605 cmd.Type = LevelScriptCommand::Lua;
606
607 if (!child.read_attr_bounded<int16>("resource", cmd.RsrcID, 0, SHRT_MAX))
608 continue;
609
610 ls_ptr->Commands.push_back(cmd);
611 }
612 #endif
613
614 BOOST_FOREACH(InfoTree child, root.children_named("music"))
615 {
616 LevelScriptCommand cmd;
617 cmd.Type = LevelScriptCommand::Music;
618
619 if (!child.read_attr("file", cmd.FileSpec))
620 continue;
621
622 ls_ptr->Commands.push_back(cmd);
623 }
624
625 BOOST_FOREACH(InfoTree child, root.children_named("random_order"))
626 {
627 child.read_attr("on", ls_ptr->RandomOrder);
628 }
629
630 BOOST_FOREACH(InfoTree child, root.children_named("movie"))
631 {
632 LevelScriptCommand cmd;
633 cmd.Type = LevelScriptCommand::Movie;
634
635 if (!child.read_attr("file", cmd.FileSpec))
636 continue;
637
638 child.read_attr("size", cmd.Size);
639
640 ls_ptr->Commands.push_back(cmd);
641 }
642
643 #ifdef HAVE_OPENGL
644 BOOST_FOREACH(InfoTree child, root.children_named("load_screen"))
645 {
646 LevelScriptCommand cmd;
647 cmd.Type = LevelScriptCommand::LoadScreen;
648
649 if (!child.read_attr("file", cmd.FileSpec))
650 continue;
651
652 child.read_attr("stretch", cmd.Stretch);
653 child.read_attr("scale", cmd.Scale);
654 child.read_attr("progress_top", cmd.T);
655 child.read_attr("progress_bottom", cmd.B);
656 child.read_attr("progress_left", cmd.L);
657 child.read_attr("progress_right", cmd.R);
658
659 BOOST_FOREACH(InfoTree color, child.children_named("color"))
660 {
661 int16 index;
662 if (color.read_indexed("index", index, 2))
663 {
664 color.read_color(cmd.Colors[index]);
665 }
666 }
667
668 ls_ptr->Commands.push_back(cmd);
669 }
670 #endif
671 }
672
parse_levels_xml(InfoTree root)673 void parse_levels_xml(InfoTree root)
674 {
675 BOOST_FOREACH(InfoTree lev, root.children_named("level"))
676 {
677 int16 index;
678 if (lev.read_indexed("index", index, SHRT_MAX+1))
679 {
680 parse_level_commands(lev, index);
681 }
682 }
683
684 BOOST_FOREACH(InfoTree child, root.children_named("end"))
685 {
686 parse_level_commands(child, LevelScriptHeader::End);
687 }
688 BOOST_FOREACH(InfoTree child, root.children_named("default"))
689 {
690 parse_level_commands(child, LevelScriptHeader::Default);
691 }
692 BOOST_FOREACH(InfoTree child, root.children_named("restore"))
693 {
694 parse_level_commands(child, LevelScriptHeader::Restore);
695 }
696
697 BOOST_FOREACH(InfoTree child, root.children_named("end_screens"))
698 {
699 child.read_attr("index", EndScreenIndex);
700 child.read_indexed("count", NumEndScreens, SHRT_MAX+1);
701 }
702 }
703