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