bool GameStateCutscene::load(const std::string& filename) {
	CutsceneSettings cutscene_settings;
	FileParser infile;

	// @CLASS Cutscene|Description of cutscenes in cutscenes/
	if (!infile.open(filename, FileParser::MOD_FILE, FileParser::ERROR_NORMAL))
		return false;

	Utils::logInfo("GameStateCutscene: Loading cutscene '%s'", filename.c_str());

	// parse the cutscene file
	while (infile.next()) {

		if (infile.new_section) {
			if (infile.section == "scene") {
				scenes.push_back(new Scene(cutscene_settings, Scene::CUTSCENE_STATIC));
				scenes.back()->subscenes.push_back(0);
			}
			else if (infile.section == "vscroll") {
				// if the previous scene was also a vertical scroller, don't create a new scene
				// instead, the previous scene will be extended
				if (scenes.empty() || scenes.back()->cutscene_type != Scene::CUTSCENE_VSCROLL) {
					scenes.push_back(new Scene(cutscene_settings, Scene::CUTSCENE_VSCROLL));
				}
			}
		}

		if (infile.section.empty()) {
			if (infile.key == "caption_margins") {
				// @ATTR caption_margins|float, float : X margin, Y margin|Percentage-based margins for the caption text based on screen size
				cutscene_settings.caption_margins.x = Parse::toFloat(Parse::popFirstString(infile.val))/100.0f;
				cutscene_settings.caption_margins.y = Parse::toFloat(Parse::popFirstString(infile.val))/100.0f;
			}
			else if (infile.key == "caption_background") {
				// @ATTR caption_background|color, int : Color, Alpha|Color (RGBA) of the caption area background.
				cutscene_settings.caption_background = Parse::toRGBA(infile.val);
			}
			else if (infile.key == "vscroll_speed") {
				// @ATTR vscroll_speed|float|The speed at which elements will scroll in 'vscroll' scenes.
				cutscene_settings.vscroll_speed = Parse::toFloat(infile.val);
			}
			else if (infile.key == "menu_backgrounds") {
				// @ATTR menu_backgrounds|bool|This cutscene will use a random fullscreen background image, like the title screen does
				has_background = true;
			}
			else if (infile.key == "music") {
				// @ATTR music|filename|The music file that will play during this cutscene.
				music = infile.val;
				hasMusic = true;
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}
		}
		else if (infile.section == "scene") {
			SceneComponent sc = SceneComponent();

			if (infile.key == "caption") {
				// @ATTR scene.caption|string|A caption that will be shown.
				sc.type = infile.key;
				sc.s = msg->get(infile.val);
			}
			else if (infile.key == "image") {
				// @ATTR scene.image|filename, int : Filename, Scaling type|Filename of an image that will be shown. The scaling type is a value between 0-2, corresponding to: none, fit height, fit screen.
				sc.type = infile.key;
				sc.s = Parse::popFirstString(infile.val);
				sc.x = Parse::popFirstInt(infile.val);
				if (sc.x < Scene::CUTSCENE_SCALE_NONE || sc.x > Scene::CUTSCENE_SCALE_SCREEN) {
					infile.error("GameStateCutscene: '%d' is not a valid scaling type.", sc.x);
					sc.x = Scene::CUTSCENE_SCALE_NONE;
				}
			}
			else if (infile.key == "pause") {
				// @ATTR scene.pause|duration|Pause before next component in 'ms' or 's'. A value of '-1' may be used to pause indefinitely.
				sc.type = infile.key;
				std::string temp = Parse::popFirstString(infile.val);
				if (temp == "-1")
					sc.x = -1;
				else
					sc.x = Parse::toDuration(temp);
				// next subscene starts on the component after this pause
				scenes.back()->subscenes.push_back(scenes.back()->components.size() + 1);
			}
			else if (infile.key == "soundfx") {
				// @ATTR scene.soundfx|filename|Filename of a sound that will be played
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}

			if (sc.type != "")
				scenes.back()->components.push_back(sc);

		}
		else if (infile.section == "vscroll") {
			SceneComponent sc = SceneComponent();

			if (infile.key == "text") {
				// @ATTR vscroll.text|string|A single, non-wrapping line of text.
				sc.type = infile.key;
				sc.s = msg->get(infile.val);
			}
			else if (infile.key == "image") {
				// @ATTR vscroll.image|filename|Filename of an image that will be shown.
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else if (infile.key == "separator") {
				// @ATTR vscroll.separator|int|Places an invisible gap of a specified height between elements.
				sc.type = infile.key;
				sc.x = Parse::toInt(infile.val);
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}

			if (sc.type != "")
				scenes.back()->components.push_back(sc);

		}
		else {
			infile.error("GameStateCutscene: '%s' is not a valid section.", infile.section.c_str());
		}

	}

	infile.close();

	if (scenes.empty()) {
		Utils::logInfo("GameStateCutscene: No scenes defined in cutscene file %s", filename.c_str());
		return false;
	}
	else {
		if (scenes.back()->components.back().type == "pause") {
			scenes.back()->subscenes.pop_back();
		}

		scenes.front()->is_first_scene = true;
		scenes.back()->is_last_scene = true;
	}

	render_device->setBackgroundColor(Color(0,0,0,0));

	return true;
}
bool GameStateCutscene::load(const std::string& filename) {
	FileParser infile;

	// @CLASS Cutscene|Description of cutscenes in cutscenes/
	if (!infile.open(filename))
		return false;

	// parse the cutscene file
	while (infile.next()) {

		if (infile.new_section) {
			if (infile.section == "scene")
				scenes.push(new Scene(caption_margins, scale_graphics));
		}

		if (infile.section.empty()) {
			if (infile.key == "scale_gfx") {
				// @ATTR scale_gfx|bool|The graphics will be scaled to fit screen width
				scale_graphics = toBool(infile.val);
			}
			else if (infile.key == "caption_margins") {
				// @ATTR caption_margins|[x,y]|Percentage-based margins for the caption text based on screen size
				caption_margins.x = toFloat(infile.nextValue())/100.0f;
				caption_margins.y = toFloat(infile.val)/100.0f;
			}
			else if (infile.key == "menu_backgrounds") {
				// @ATTR menu_backgrounds|bool|This cutscene will use a random fullscreen background image, like the title screen does
				has_background = true;
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}
		}
		else if (infile.section == "scene") {
			SceneComponent sc = SceneComponent();

			if (infile.key == "caption") {
				// @ATTR scene.caption|string|A caption that will be shown.
				sc.type = infile.key;
				sc.s = msg->get(infile.val);
			}
			else if (infile.key == "image") {
				// @ATTR scene.image|string|Filename of an image that will be shown.
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else if (infile.key == "pause") {
				// @ATTR scene.pause|duration|Pause before next component in 'ms' or 's'.
				sc.type = infile.key;
				sc.x = parse_duration(infile.val);
			}
			else if (infile.key == "soundfx") {
				// @ATTR scene.soundfx|string|Filename of a sound that will be played
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}

			if (sc.type != "")
				scenes.back()->components.push(sc);

		}
		else {
			infile.error("GameStateCutscene: '%s' is not a valid section.", infile.section.c_str());
		}

	}

	infile.close();

	if (scenes.empty()) {
		logError("GameStateCutscene: No scenes defined in cutscene file %s", filename.c_str());
		return false;
	}

	return true;
}
bool GameStateCutscene::load(const std::string& filename) {
	CutsceneSettings settings;
	FileParser infile;

	// @CLASS Cutscene|Description of cutscenes in cutscenes/
	if (!infile.open(filename))
		return false;

	// parse the cutscene file
	while (infile.next()) {

		if (infile.new_section) {
			if (infile.section == "scene") {
				scenes.push(new Scene(settings, CUTSCENE_STATIC));
			}
			else if (infile.section == "vscroll") {
				// if the previous scene was also a vertical scroller, don't create a new scene
				// instead, the previous scene will be extended
				if (scenes.empty() || scenes.front()->cutscene_type != CUTSCENE_VSCROLL) {
					scenes.push(new Scene(settings, CUTSCENE_VSCROLL));
				}
			}
		}

		if (infile.section.empty()) {
			if (infile.key == "scale_gfx") {
				// @ATTR scale_gfx|bool|The graphics will be scaled to fit screen width
				settings.scale_graphics = toBool(infile.val);
			}
			else if (infile.key == "caption_margins") {
				// @ATTR caption_margins|float, float : X margin, Y margin|Percentage-based margins for the caption text based on screen size
				settings.caption_margins.x = toFloat(infile.nextValue())/100.0f;
				settings.caption_margins.y = toFloat(infile.val)/100.0f;
			}
			else if (infile.key == "vscroll_speed") {
				// @ATTR vscroll_speed|float|The speed at which elements will scroll in 'vscroll' scenes.
				settings.vscroll_speed = toFloat(infile.val);
			}
			else if (infile.key == "menu_backgrounds") {
				// @ATTR menu_backgrounds|bool|This cutscene will use a random fullscreen background image, like the title screen does
				has_background = true;
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}
		}
		else if (infile.section == "scene") {
			SceneComponent sc = SceneComponent();

			if (infile.key == "caption") {
				// @ATTR scene.caption|string|A caption that will be shown.
				sc.type = infile.key;
				sc.s = msg->get(infile.val);
			}
			else if (infile.key == "image") {
				// @ATTR scene.image|filename|Filename of an image that will be shown.
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else if (infile.key == "pause") {
				// @ATTR scene.pause|duration|Pause before next component in 'ms' or 's'.
				sc.type = infile.key;
				sc.x = parse_duration(infile.val);
			}
			else if (infile.key == "soundfx") {
				// @ATTR scene.soundfx|filename|Filename of a sound that will be played
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}

			if (sc.type != "")
				scenes.back()->components.push(sc);

		}
		else if (infile.section == "vscroll") {
			SceneComponent sc = SceneComponent();

			if (infile.key == "text") {
				// @ATTR vscroll.text|string|A single, non-wrapping line of text.
				sc.type = infile.key;
				sc.s = msg->get(infile.val);
			}
			else if (infile.key == "image") {
				// @ATTR vscroll.image|filename|Filename of an image that will be shown.
				sc.type = infile.key;
				sc.s = infile.val;
			}
			else if (infile.key == "separator") {
				// @ATTR vscroll.separator|int|Places an invisible gap of a specified height between elements.
				sc.type = infile.key;
				sc.x = toInt(infile.nextValue());
			}
			else {
				infile.error("GameStateCutscene: '%s' is not a valid key.", infile.key.c_str());
			}

			if (sc.type != "")
				scenes.back()->components.push(sc);

		}
		else {
			infile.error("GameStateCutscene: '%s' is not a valid section.", infile.section.c_str());
		}

	}

	infile.close();

	if (scenes.empty()) {
		logError("GameStateCutscene: No scenes defined in cutscene file %s", filename.c_str());
		return false;
	}

	return true;
}