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 "twine/scene/gamestate.h"
24 #include "common/file.h"
25 #include "common/rect.h"
26 #include "common/str.h"
27 #include "common/system.h"
28 #include "common/textconsole.h"
29 #include "common/util.h"
30 #include "twine/audio/music.h"
31 #include "twine/audio/sound.h"
32 #include "twine/input.h"
33 #include "twine/menu/interface.h"
34 #include "twine/menu/menu.h"
35 #include "twine/menu/menuoptions.h"
36 #include "twine/renderer/redraw.h"
37 #include "twine/renderer/renderer.h"
38 #include "twine/renderer/screens.h"
39 #include "twine/resources/resources.h"
40 #include "twine/scene/actor.h"
41 #include "twine/scene/animations.h"
42 #include "twine/scene/collision.h"
43 #include "twine/scene/extra.h"
44 #include "twine/scene/grid.h"
45 #include "twine/scene/scene.h"
46 #include "twine/shared.h"
47 #include "twine/text.h"
48 #include "twine/twine.h"
49 
50 namespace TwinE {
51 
GameState(TwinEEngine * engine)52 GameState::GameState(TwinEEngine *engine) : _engine(engine) {
53 	clearGameFlags();
54 	Common::fill(&_inventoryFlags[0], &_inventoryFlags[NUM_INVENTORY_ITEMS], 0);
55 	Common::fill(&_holomapFlags[0], &_holomapFlags[NUM_LOCATIONS], 0);
56 	Common::fill(&_gameChoices[0], &_gameChoices[10], TextId::kNone);
57 }
58 
initEngineProjections()59 void GameState::initEngineProjections() {
60 	_engine->_renderer->setOrthoProjection(_engine->width() / 2 - 9, _engine->height() / 2, 512);
61 	_engine->_renderer->setBaseTranslation(0, 0, 0);
62 	_engine->_renderer->setBaseRotation(ANGLE_0, ANGLE_0, ANGLE_0);
63 	_engine->_renderer->setLightVector(_engine->_scene->_alphaLight, _engine->_scene->_betaLight, ANGLE_0);
64 }
65 
initGameStateVars()66 void GameState::initGameStateVars() {
67 	debug(2, "Init game state variables");
68 	_engine->_extra->resetExtras();
69 
70 	for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
71 		_engine->_redraw->overlayList[i].info0 = -1;
72 	}
73 
74 	for (int32 i = 0; i < ARRAYSIZE(_engine->_scene->_sceneFlags); i++) {
75 		_engine->_scene->_sceneFlags[i] = 0;
76 	}
77 
78 	clearGameFlags();
79 	Common::fill(&_inventoryFlags[0], &_inventoryFlags[NUM_INVENTORY_ITEMS], 0);
80 
81 	_engine->_scene->initSceneVars();
82 
83 	Common::fill(&_holomapFlags[0], &_holomapFlags[NUM_LOCATIONS], 0);
84 }
85 
initHeroVars()86 void GameState::initHeroVars() {
87 	_engine->_actor->resetActor(OWN_ACTOR_SCENE_INDEX); // reset Hero
88 
89 	_magicBallIdx = -1;
90 
91 	_inventoryNumLeafsBox = 2;
92 	_inventoryNumLeafs = 2;
93 	_inventoryNumKashes = 0;
94 	_inventoryNumKeys = 0;
95 	_inventoryMagicPoints = 0;
96 
97 	_usingSabre = false;
98 
99 	_engine->_scene->_sceneHero->_body = BodyType::btNormal;
100 	_engine->_scene->_sceneHero->setLife(kActorMaxLife);
101 	_engine->_scene->_sceneHero->_talkColor = COLOR_BRIGHT_BLUE;
102 }
103 
initEngineVars()104 void GameState::initEngineVars() {
105 	debug(2, "Init engine variables");
106 	_engine->_interface->resetClip();
107 
108 	_engine->_scene->_alphaLight = ANGLE_315;
109 	_engine->_scene->_betaLight = ANGLE_334;
110 	initEngineProjections();
111 	initGameStateVars();
112 	initHeroVars();
113 
114 	_engine->_scene->_newHeroPos.x = 16 * BRICK_SIZE;
115 	_engine->_scene->_newHeroPos.y = 24 * BRICK_HEIGHT;
116 	_engine->_scene->_newHeroPos.z = 16 * BRICK_SIZE;
117 
118 	_engine->_scene->_currentSceneIdx = SCENE_CEILING_GRID_FADE_1;
119 	_engine->_scene->_needChangeScene = LBA1SceneId::Citadel_Island_Prison;
120 	_engine->_quitGame = -1;
121 	_engine->_scene->_mecaPenguinIdx = -1;
122 	_engine->_menuOptions->canShowCredits = false;
123 
124 	_inventoryNumLeafs = 0;
125 	_inventoryNumLeafsBox = 2;
126 	_inventoryMagicPoints = 0;
127 	_inventoryNumKashes = 0;
128 	_inventoryNumKeys = 0;
129 	_inventoryNumGas = 0;
130 
131 	_engine->_actor->_cropBottomScreen = 0;
132 
133 	_magicLevelIdx = 0;
134 	_usingSabre = false;
135 
136 	_gameChapter = 0;
137 
138 	_engine->_scene->_sceneTextBank = TextBankId::Options_and_menus;
139 	_engine->_scene->_currentlyFollowedActor = OWN_ACTOR_SCENE_INDEX;
140 	_engine->_actor->_heroBehaviour = HeroBehaviourType::kNormal;
141 	_engine->_actor->_previousHeroAngle = 0;
142 	_engine->_actor->_previousHeroBehaviour = HeroBehaviourType::kNormal;
143 }
144 
145 // http://lbafileinfo.kazekr.net/index.php?title=LBA1:Savegame
loadGame(Common::SeekableReadStream * file)146 bool GameState::loadGame(Common::SeekableReadStream *file) {
147 	if (file == nullptr) {
148 		return false;
149 	}
150 
151 	debug(2, "Load game");
152 	const byte saveFileVersion = file->readByte();
153 	// 4 is dotemu enhanced version of lba1
154 	if (saveFileVersion != 3 && saveFileVersion != 4) {
155 		warning("Could not load savegame - wrong magic byte");
156 		return false;
157 	}
158 
159 	initEngineVars();
160 
161 	int playerNameIdx = 0;
162 	do {
163 		const byte c = file->readByte();
164 		_engine->_menuOptions->_saveGameName[playerNameIdx++] = c;
165 		if (c == '\0') {
166 			break;
167 		}
168 		if (playerNameIdx >= ARRAYSIZE(_engine->_menuOptions->_saveGameName)) {
169 			warning("Failed to load savegame. Invalid playername.");
170 			return false;
171 		}
172 	} while (true);
173 
174 	byte numGameFlags = file->readByte();
175 	if (numGameFlags != NUM_GAME_FLAGS) {
176 		warning("Failed to load gameflags. Expected %u, but got %u", NUM_GAME_FLAGS, numGameFlags);
177 		return false;
178 	}
179 	for (uint8 i = 0; i < numGameFlags; ++i) {
180 		setGameFlag(i, file->readByte());
181 	}
182 	_engine->_scene->_needChangeScene = file->readByte(); // scene index
183 	_gameChapter = file->readByte();
184 
185 	_engine->_actor->_heroBehaviour = (HeroBehaviourType)file->readByte();
186 	_engine->_actor->_previousHeroBehaviour = _engine->_actor->_heroBehaviour;
187 	_engine->_scene->_sceneHero->setLife(file->readByte());
188 	setKashes(file->readSint16LE());
189 	_magicLevelIdx = file->readByte();
190 	setMagicPoints(file->readByte());
191 	setLeafBoxes(file->readByte());
192 	_engine->_scene->_newHeroPos.x = file->readSint16LE();
193 	_engine->_scene->_newHeroPos.y = file->readSint16LE();
194 	_engine->_scene->_newHeroPos.z = file->readSint16LE();
195 	_engine->_scene->_sceneHero->_angle = ToAngle(file->readSint16LE());
196 	_engine->_actor->_previousHeroAngle = _engine->_scene->_sceneHero->_angle;
197 	_engine->_scene->_sceneHero->_body = (BodyType)file->readByte();
198 
199 	const byte numHolomapFlags = file->readByte(); // number of holomap locations
200 	if (numHolomapFlags != NUM_LOCATIONS) {
201 		warning("Failed to load holomapflags. Got %u, expected %i", numHolomapFlags, NUM_LOCATIONS);
202 		return false;
203 	}
204 	file->read(_holomapFlags, NUM_LOCATIONS);
205 
206 	setGas(file->readByte());
207 
208 	const byte numInventoryFlags = file->readByte(); // number of used inventory items, always 28
209 	if (numInventoryFlags != NUM_INVENTORY_ITEMS) {
210 		warning("Failed to load inventoryFlags. Got %u, expected %i", numInventoryFlags, NUM_INVENTORY_ITEMS);
211 		return false;
212 	}
213 	file->read(_inventoryFlags, NUM_INVENTORY_ITEMS);
214 
215 	setLeafs(file->readByte());
216 	_usingSabre = file->readByte();
217 
218 	if (saveFileVersion == 4) {
219 		// the time the game was played
220 		file->readUint32LE();
221 		file->readUint32LE();
222 	}
223 
224 	_engine->_scene->_currentSceneIdx = SCENE_CEILING_GRID_FADE_1;
225 	_engine->_scene->_heroPositionType = ScenePositionType::kReborn;
226 	return true;
227 }
228 
saveGame(Common::WriteStream * file)229 bool GameState::saveGame(Common::WriteStream *file) {
230 	debug(2, "Save game");
231 	if (_engine->_menuOptions->_saveGameName[0] == '\0') {
232 		Common::strlcpy(_engine->_menuOptions->_saveGameName, "TwinEngineSave", sizeof(_engine->_menuOptions->_saveGameName));
233 	}
234 
235 	int32 sceneIdx = _engine->_scene->_currentSceneIdx;
236 	if (sceneIdx == Polar_Island_end_scene || sceneIdx == Citadel_Island_end_sequence_1 || sceneIdx == Citadel_Island_end_sequence_2 || sceneIdx == Credits_List_Sequence) {
237 		/* inventoryMagicPoints = 0x50 */
238 		/* herobehaviour = 0 */
239 		/* newheropos.x = 0xffff */
240 		sceneIdx = Polar_Island_Final_Battle;
241 	}
242 
243 	file->writeByte(0x03);
244 	file->writeString(_engine->_menuOptions->_saveGameName);
245 	file->writeByte('\0');
246 	file->writeByte(NUM_GAME_FLAGS);
247 	for (uint8 i = 0; i < NUM_GAME_FLAGS; ++i) {
248 		file->writeByte(hasGameFlag(i));
249 	}
250 	file->writeByte(sceneIdx);
251 	file->writeByte(_gameChapter);
252 	file->writeByte((byte)_engine->_actor->_heroBehaviour);
253 	file->writeByte(_engine->_scene->_sceneHero->_life);
254 	file->writeSint16LE(_inventoryNumKashes);
255 	file->writeByte(_magicLevelIdx);
256 	file->writeByte(_inventoryMagicPoints);
257 	file->writeByte(_inventoryNumLeafsBox);
258 	// we don't save the whole scene state - so we have to make sure that the hero is
259 	// respawned at the start of the scene - and not at its current position
260 	file->writeSint16LE(_engine->_scene->_newHeroPos.x);
261 	file->writeSint16LE(_engine->_scene->_newHeroPos.y);
262 	file->writeSint16LE(_engine->_scene->_newHeroPos.z);
263 	file->writeSint16LE(FromAngle(_engine->_scene->_sceneHero->_angle));
264 	file->writeByte((uint8)_engine->_scene->_sceneHero->_body);
265 
266 	// number of holomap locations
267 	file->writeByte(NUM_LOCATIONS);
268 	file->write(_holomapFlags, NUM_LOCATIONS);
269 
270 	file->writeByte(_inventoryNumGas);
271 
272 	// number of inventory items
273 	file->writeByte(NUM_INVENTORY_ITEMS);
274 	file->write(_inventoryFlags, NUM_INVENTORY_ITEMS);
275 
276 	file->writeByte(_inventoryNumLeafs);
277 	file->writeByte(_usingSabre ? 1 : 0);
278 	file->writeByte(0);
279 
280 	return true;
281 }
282 
setGameFlag(uint8 index,uint8 value)283 void GameState::setGameFlag(uint8 index, uint8 value) {
284 	if (_gameStateFlags[index] == value) {
285 		return;
286 	}
287 	debug(2, "Set gameStateFlags[%u]=%u", index, value);
288 	_gameStateFlags[index] = value;
289 	if (!value) {
290 		return;
291 	}
292 
293 	if ((index == GAMEFLAG_VIDEO_BAFFE || index == GAMEFLAG_VIDEO_BAFFE2 || index == GAMEFLAG_VIDEO_BAFFE3 || index == GAMEFLAG_VIDEO_BAFFE5) &&
294 		_gameStateFlags[GAMEFLAG_VIDEO_BAFFE] != 0 && _gameStateFlags[GAMEFLAG_VIDEO_BAFFE2] != 0 && _gameStateFlags[GAMEFLAG_VIDEO_BAFFE3] != 0 && _gameStateFlags[GAMEFLAG_VIDEO_BAFFE5] != 0) {
295 		// all 4 slap videos
296 		_engine->unlockAchievement("LBA_ACH_012");
297 	} else if (index == GAMEFLAG_VIDEO_BATEAU2) {
298 		// second video of ferry trip
299 		_engine->unlockAchievement("LBA_ACH_010");
300 	} else if (index == (uint8)InventoryItems::kiUseSabre) {
301 		_engine->unlockAchievement("LBA_ACH_002");
302 	} else if (index == (uint8)InventoryItems::kBottleOfSyrup) {
303 		_engine->unlockAchievement("LBA_ACH_007");
304 	}
305 }
306 
processFoundItem(InventoryItems item)307 void GameState::processFoundItem(InventoryItems item) {
308 	ScopedEngineFreeze freeze(_engine);
309 	_engine->_grid->centerOnActor(_engine->_scene->_sceneHero);
310 
311 	_engine->exitSceneryView();
312 	// Hide hero in scene
313 	_engine->_scene->_sceneHero->_staticFlags.bIsHidden = 1;
314 	_engine->_redraw->redrawEngineActions(true);
315 	_engine->_scene->_sceneHero->_staticFlags.bIsHidden = 0;
316 
317 	_engine->saveFrontBuffer();
318 
319 	const int32 itemCameraX = _engine->_grid->_newCamera.x * BRICK_SIZE;
320 	const int32 itemCameraY = _engine->_grid->_newCamera.y * BRICK_HEIGHT;
321 	const int32 itemCameraZ = _engine->_grid->_newCamera.z * BRICK_SIZE;
322 
323 	BodyData &bodyData = _engine->_resources->_bodyData[_engine->_scene->_sceneHero->_entity];
324 	const int32 bodyX = _engine->_scene->_sceneHero->_pos.x - itemCameraX;
325 	const int32 bodyY = _engine->_scene->_sceneHero->_pos.y - itemCameraY;
326 	const int32 bodyZ = _engine->_scene->_sceneHero->_pos.z - itemCameraZ;
327 	Common::Rect modelRect;
328 	_engine->_renderer->renderIsoModel(bodyX, bodyY, bodyZ, ANGLE_0, ANGLE_45, ANGLE_0, bodyData, modelRect);
329 	_engine->_interface->setClip(modelRect);
330 
331 	const int32 itemX = (_engine->_scene->_sceneHero->_pos.x + BRICK_HEIGHT) / BRICK_SIZE;
332 	int32 itemY = _engine->_scene->_sceneHero->_pos.y / BRICK_HEIGHT;
333 	if (_engine->_scene->_sceneHero->brickShape() != ShapeType::kNone) {
334 		itemY++;
335 	}
336 	const int32 itemZ = (_engine->_scene->_sceneHero->_pos.z + BRICK_HEIGHT) / BRICK_SIZE;
337 
338 	_engine->_grid->drawOverModelActor(itemX, itemY, itemZ);
339 
340 	IVec3 &projPos = _engine->_renderer->projectPositionOnScreen(bodyX, bodyY, bodyZ);
341 	projPos.y -= 150;
342 
343 	const int32 boxTopLeftX = projPos.x - 65;
344 	const int32 boxTopLeftY = projPos.y - 65;
345 	const int32 boxBottomRightX = projPos.x + 65;
346 	const int32 boxBottomRightY = projPos.y + 65;
347 	const Common::Rect boxRect(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
348 	_engine->_sound->playSample(Samples::BigItemFound);
349 
350 	// process vox play
351 	_engine->_music->stopMusic();
352 	_engine->_text->initTextBank(TextBankId::Inventory_Intro_and_Holomap);
353 
354 	_engine->_interface->resetClip();
355 	_engine->_text->initItemFoundText(item);
356 	_engine->_text->initDialogueBox();
357 
358 	ProgressiveTextState textState = ProgressiveTextState::ContinueRunning;
359 
360 	_engine->_text->initVoxToPlayTextId((TextId)item);
361 
362 	const int32 bodyAnimIdx = _engine->_animations->getBodyAnimIndex(AnimationTypes::kFoundItem);
363 	const AnimData &currentAnimData = _engine->_resources->_animData[bodyAnimIdx];
364 
365 	AnimTimerDataStruct tmpAnimTimer = _engine->_scene->_sceneHero->_animTimerData;
366 
367 	_engine->_animations->stockAnimation(bodyData, &_engine->_scene->_sceneHero->_animTimerData);
368 
369 	uint currentAnimState = 0;
370 
371 	_engine->_redraw->_numOfRedrawBox = 0;
372 
373 	ScopedKeyMap uiKeyMap(_engine, uiKeyMapId);
374 	int16 itemAngle = ANGLE_0;
375 	for (;;) {
376 		FrameMarker frame(_engine, 66);
377 		_engine->_interface->resetClip();
378 		_engine->_redraw->_currNumOfRedrawBox = 0;
379 		_engine->_redraw->blitBackgroundAreas();
380 		_engine->_interface->drawTransparentBox(boxRect, 4);
381 
382 		_engine->_interface->setClip(boxRect);
383 
384 		itemAngle += ANGLE_2;
385 
386 		_engine->_renderer->renderInventoryItem(_engine->_renderer->_projPos.x, _engine->_renderer->_projPos.y, _engine->_resources->_inventoryTable[item], itemAngle, 10000);
387 
388 		_engine->_menu->drawRectBorders(boxRect);
389 		_engine->_redraw->addRedrawArea(boxRect);
390 		_engine->_interface->resetClip();
391 		initEngineProjections();
392 
393 		if (_engine->_animations->setModelAnimation(currentAnimState, currentAnimData, bodyData, &_engine->_scene->_sceneHero->_animTimerData)) {
394 			currentAnimState++; // keyframe
395 			if (currentAnimState >= currentAnimData.getNumKeyframes()) {
396 				currentAnimState = currentAnimData.getLoopFrame();
397 			}
398 		}
399 
400 		_engine->_renderer->renderIsoModel(bodyX, bodyY, bodyZ, ANGLE_0, ANGLE_45, ANGLE_0, bodyData, modelRect);
401 		_engine->_interface->setClip(modelRect);
402 		_engine->_grid->drawOverModelActor(itemX, itemY, itemZ);
403 		_engine->_redraw->addRedrawArea(modelRect);
404 
405 		if (textState == ProgressiveTextState::ContinueRunning) {
406 			_engine->_interface->resetClip();
407 			textState = _engine->_text->updateProgressiveText();
408 		} else {
409 			_engine->_text->fadeInRemainingChars();
410 		}
411 
412 		_engine->_redraw->flipRedrawAreas();
413 
414 		_engine->readKeys();
415 		if (_engine->_input->toggleAbortAction()) {
416 			_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
417 			break;
418 		}
419 
420 		if (_engine->_input->toggleActionIfActive(TwinEActionType::UINextPage)) {
421 			if (textState == ProgressiveTextState::End) {
422 				_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
423 				break;
424 			}
425 			if (textState == ProgressiveTextState::NextPage) {
426 				textState = ProgressiveTextState::ContinueRunning;
427 			}
428 		}
429 
430 		_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry);
431 
432 		_engine->_lbaTime++;
433 	}
434 
435 	while (_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry)) {
436 		FrameMarker frame(_engine);
437 		_engine->readKeys();
438 		if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
439 			break;
440 		}
441 	}
442 
443 	initEngineProjections();
444 	_engine->_text->initSceneTextBank();
445 	_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
446 
447 	_engine->_scene->_sceneHero->_animTimerData = tmpAnimTimer;
448 	_engine->_interface->resetClip();
449 }
450 
processGameChoices(TextId choiceIdx)451 void GameState::processGameChoices(TextId choiceIdx) {
452 	_engine->saveFrontBuffer();
453 
454 	_gameChoicesSettings.reset();
455 	_gameChoicesSettings.setTextBankId((TextBankId)((int)_engine->_scene->_sceneTextBank + (int)TextBankId::Citadel_Island));
456 
457 	// filled via script
458 	for (int32 i = 0; i < _numChoices; i++) {
459 		_gameChoicesSettings.addButton(_gameChoices[i], 0);
460 	}
461 
462 	_engine->_text->drawAskQuestion(choiceIdx);
463 
464 	_engine->_menu->processMenu(&_gameChoicesSettings, false);
465 	const int16 activeButton = _gameChoicesSettings.getActiveButton();
466 	_choiceAnswer = _gameChoices[activeButton];
467 
468 	// get right VOX entry index
469 	if (_engine->_text->initVoxToPlayTextId(_choiceAnswer)) {
470 		while (_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry)) {
471 			FrameMarker frame(_engine);
472 			if (_engine->shouldQuit()) {
473 				break;
474 			}
475 		}
476 		_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
477 
478 		_engine->_text->_hasHiddenVox = false;
479 		_engine->_text->_voxHiddenIndex = 0;
480 	}
481 }
482 
processGameoverAnimation()483 void GameState::processGameoverAnimation() {
484 	const int32 tmpLbaTime = _engine->_lbaTime;
485 
486 	_engine->exitSceneryView();
487 	// workaround to fix hero redraw after drowning
488 	_engine->_scene->_sceneHero->_staticFlags.bIsHidden = 1;
489 	_engine->_redraw->redrawEngineActions(true);
490 	_engine->_scene->_sceneHero->_staticFlags.bIsHidden = 0;
491 
492 	// TODO: inSceneryView
493 	_engine->setPalette(_engine->_screens->_paletteRGBA);
494 	_engine->saveFrontBuffer();
495 	BodyData gameOverPtr;
496 	if (!gameOverPtr.loadFromHQR(Resources::HQR_RESS_FILE, RESSHQR_GAMEOVERMDL, _engine->isLBA1())) {
497 		return;
498 	}
499 
500 	_engine->_sound->stopSamples();
501 	_engine->_music->stopMidiMusic(); // stop fade music
502 	_engine->_renderer->setCameraPosition(_engine->width() / 2, _engine->height() / 2, 128, 200, 200);
503 	int32 startLbaTime = _engine->_lbaTime;
504 	const Common::Rect &rect = _engine->centerOnScreen(_engine->width() / 2, _engine->height() / 2);
505 	_engine->_interface->setClip(rect);
506 
507 	Common::Rect dummy;
508 	while (!_engine->_input->toggleAbortAction() && (_engine->_lbaTime - startLbaTime) <= 500) {
509 		FrameMarker frame(_engine, 66);
510 		_engine->readKeys();
511 		if (_engine->shouldQuit()) {
512 			return;
513 		}
514 
515 		const int32 avg = _engine->_collision->getAverageValue(40000, 3200, 500, _engine->_lbaTime - startLbaTime);
516 		const int32 cdot = _engine->_screens->lerp(1, ANGLE_360, 100, (_engine->_lbaTime - startLbaTime) % 100);
517 
518 		_engine->blitWorkToFront(rect);
519 		_engine->_renderer->setCameraAngle(0, 0, 0, 0, -cdot, 0, avg);
520 		_engine->_renderer->renderIsoModel(0, 0, 0, ANGLE_0, ANGLE_0, ANGLE_0, gameOverPtr, dummy);
521 
522 		_engine->_lbaTime++;
523 	}
524 
525 	_engine->_sound->playSample(Samples::Explode);
526 	_engine->blitWorkToFront(rect);
527 	_engine->_renderer->setCameraAngle(0, 0, 0, 0, 0, 0, 3200);
528 	_engine->_renderer->renderIsoModel(0, 0, 0, ANGLE_0, ANGLE_0, ANGLE_0, gameOverPtr, dummy);
529 
530 	_engine->delaySkip(2000);
531 
532 	_engine->_interface->resetClip();
533 	_engine->restoreFrontBuffer();
534 	initEngineProjections();
535 
536 	_engine->_lbaTime = tmpLbaTime;
537 }
538 
giveUp()539 void GameState::giveUp() {
540 	_engine->_sound->stopSamples();
541 	// TODO: is an autosave desired on giving up? I don't think so. What did the original game do here?
542 	//_engine->autoSave();
543 	initGameStateVars();
544 	_engine->_scene->stopRunningGame();
545 }
546 
setGas(int16 value)547 int16 GameState::setGas(int16 value) {
548 	_inventoryNumGas = CLIP<int16>(value, 0, 100);
549 	return _inventoryNumGas;
550 }
551 
addGas(int16 value)552 void GameState::addGas(int16 value) {
553 	setGas(_inventoryNumGas + value);
554 }
555 
setKashes(int16 value)556 int16 GameState::setKashes(int16 value) {
557 	_inventoryNumKashes = CLIP<int16>(value, 0, 999);
558 	if (_engine->_gameState->_inventoryNumKashes >= 500) {
559 		_engine->unlockAchievement("LBA_ACH_011");
560 	}
561 	return _inventoryNumKashes;
562 }
563 
setKeys(int16 value)564 int16 GameState::setKeys(int16 value) {
565 	_inventoryNumKeys = MAX<int16>(0, value);
566 	return _inventoryNumKeys;
567 }
568 
addKeys(int16 val)569 void GameState::addKeys(int16 val) {
570 	setKeys(_inventoryNumKeys + val);
571 }
572 
addKashes(int16 val)573 void GameState::addKashes(int16 val) {
574 	setKashes(_inventoryNumKashes + val);
575 }
576 
setMagicPoints(int16 val)577 int16 GameState::setMagicPoints(int16 val) {
578 	_inventoryMagicPoints = val;
579 	if (_inventoryMagicPoints > _magicLevelIdx * 20) {
580 		_inventoryMagicPoints = _magicLevelIdx * 20;
581 	} else if (_inventoryMagicPoints < 0) {
582 		_inventoryMagicPoints = 0;
583 	}
584 	return _inventoryMagicPoints;
585 }
586 
setMaxMagicPoints()587 int16 GameState::setMaxMagicPoints() {
588 	_inventoryMagicPoints = _magicLevelIdx * 20;
589 	return _inventoryMagicPoints;
590 }
591 
addMagicPoints(int16 val)592 void GameState::addMagicPoints(int16 val) {
593 	setMagicPoints(_inventoryMagicPoints + val);
594 }
595 
setLeafs(int16 val)596 int16 GameState::setLeafs(int16 val) {
597 	_inventoryNumLeafs = val;
598 	if (_inventoryNumLeafs > _inventoryNumLeafsBox) {
599 		_inventoryNumLeafs = _inventoryNumLeafsBox;
600 	}
601 	return _inventoryNumLeafs;
602 }
603 
addLeafs(int16 val)604 void GameState::addLeafs(int16 val) {
605 	setLeafs(_inventoryNumLeafs + val);
606 }
607 
setLeafBoxes(int16 val)608 int16 GameState::setLeafBoxes(int16 val) {
609 	_inventoryNumLeafsBox = val;
610 	if (_inventoryNumLeafsBox > 10) {
611 		_inventoryNumLeafsBox = 10;
612 	}
613 	if (_inventoryNumLeafsBox == 5) {
614 		_engine->unlockAchievement("LBA_ACH_003");
615 	}
616 	return _inventoryNumLeafsBox;
617 }
618 
addLeafBoxes(int16 val)619 void GameState::addLeafBoxes(int16 val) {
620 	setLeafBoxes(_inventoryNumLeafsBox + val);
621 }
622 
clearGameFlags()623 void GameState::clearGameFlags() {
624 	debug(2, "Clear all gameStateFlags");
625 	Common::fill(&_gameStateFlags[0], &_gameStateFlags[NUM_GAME_FLAGS], 0);
626 }
627 
hasGameFlag(uint8 index) const628 uint8 GameState::hasGameFlag(uint8 index) const {
629 	debug(6, "Query gameStateFlags[%u]=%u", index, _gameStateFlags[index]);
630 	return _gameStateFlags[index];
631 }
632 
633 } // namespace TwinE
634