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 #ifdef ENABLE_EOB
24 
25 #include "kyra/engine/kyra_rpg.h"
26 #include "kyra/resource/resource.h"
27 #include "kyra/sound/sound_intern.h"
28 #include "kyra/sound/sound_pc_v1.h"
29 #include "kyra/script/script_eob.h"
30 #include "kyra/engine/timer.h"
31 #include "kyra/gui/debugger.h"
32 
33 #include "common/config-manager.h"
34 #include "common/debug-channels.h"
35 #include "common/translation.h"
36 
37 #include "gui/error.h"
38 
39 #include "backends/keymapper/action.h"
40 #include "backends/keymapper/keymapper.h"
41 #include "backends/keymapper/standard-actions.h"
42 
43 namespace Kyra {
44 
45 const char *const EoBCoreEngine::kKeymapName = "eob";
46 
EoBCoreEngine(OSystem * system,const GameFlags & flags)47 EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) : KyraRpgEngine(system, flags), _numLargeItemShapes(flags.gameID == GI_EOB1 ? 14 : 11),
48 	_numSmallItemShapes(flags.gameID == GI_EOB1 ? 23 : 26),	_numThrownItemShapes(flags.gameID == GI_EOB1 ? 12 : 9),
49 	_numItemIconShapes(flags.gameID == GI_EOB1 ? 89 : 112),	_teleporterWallId(flags.gameID == GI_EOB1 ? 52 : 44) {
50 
51 	_screen = 0;
52 	_gui = 0;
53 
54 	_playFinale = false;
55 	_runFlag = true;
56 	_configMouse = _config2431 = true;
57 	_mouseSpeed = _padSpeed = 1;
58 	_inputMode = 0;
59 	_loading = false;
60 
61 	_enableHiResDithering = false;
62 
63 	_tickLength = (_flags.platform == Common::kPlatformSegaCD) ? 38 : 55;
64 	_envAudioTimer = 0;
65 	_flashShapeTimer = 0;
66 	_flashShapeTimerIntv0 = (_flags.platform == Common::kPlatformSegaCD ? 2 * _tickLength : 0);
67 	_flashShapeTimerIntv1 = _tickLength * (_flags.platform == Common::kPlatformSegaCD ? 2 : 8);
68 	_flashShapeTimerIntv2 = _tickLength * 8;
69 	_drawSceneTimer = 0;
70 	_vcnFilePattern = "%s.VCN";
71 	_vmpFilePattern = "%s.VMP";
72 
73 	_largeItemShapes = _smallItemShapes = _thrownItemShapes = _spellShapes = _firebeamShapes = 0;
74 	_itemIconShapes = _blueItemIconShapes = _xtraItemIconShapes = _wallOfForceShapes = _teleporterShapes = _sparkShapes = _compassShapes = 0;
75 	_redSplatShape = _greenSplatShape = _deadCharShape = _disabledCharGrid = _swapShape = 0;
76 	_blackBoxSmallGrid = _weaponSlotGrid = _blackBoxWideGrid = _lightningColumnShape = 0;
77 
78 	memset(_redSplatBG, 0, sizeof(_redSplatBG));
79 	memset(_largeItemShapesScl, 0, sizeof(_largeItemShapesScl));
80 	memset(_smallItemShapesScl, 0, sizeof(_smallItemShapesScl));
81 	memset(_thrownItemShapesScl, 0, sizeof(_thrownItemShapesScl));
82 
83 	_monsterAcHitChanceTable1 = _monsterAcHitChanceTable2 = 0;
84 
85 	_monsterDustStrings = 0;
86 	_enemyMageSpellList = 0;
87 	_enemyMageSfx = 0;
88 	_beholderSpellList = 0;
89 	_beholderSfx = 0;
90 
91 	_faceShapes = 0;
92 	_characters = 0;
93 	_items = 0;
94 	_itemTypes = 0;
95 	_itemNames = 0;
96 	_itemNamesStatic = 0;
97 	_itemInHand = -1;
98 	_numItems = _numItemNames = _numItemNamesStatic = 0;
99 
100 	_castScrollSlot = 0;
101 	_currentSub = 0;
102 
103 	_itemsOverlay = 0;
104 
105 	_partyEffectFlags = 0;
106 	_lastUsedItem = 0;
107 
108 	_levelDecorationRects = 0;
109 	_doorSwitches = 0;
110 	_monsterProps = 0;
111 	_monsterDecorations = 0;
112 	_monsterFlashOverlay = _monsterStoneOverlay = 0;
113 	_monsters = 0;
114 	_dstMonsterIndex = 0;
115 	_preventMonsterFlash = false;
116 	_sceneShakeCountdown = 0;
117 
118 	_teleporterPulse = 0;
119 
120 	_dscShapeCoords = 0;
121 	_dscItemPosIndex = 0;
122 	_dscItemShpX = 0;
123 	_dscItemScaleIndex = 0;
124 	_dscItemTileIndex = 0;
125 	_dscItemShapeMap = 0;
126 	_dscDoorScaleOffs = 0;
127 	_dscDoorScaleMult1 = 0;
128 	_dscDoorScaleMult2 = 0;
129 	_dscDoorScaleMult3 = 0;
130 	_dscDoorY1 = 0;
131 	_dscDoorXE = 0;
132 
133 	_shapeShakeOffsetX = _shapeShakeOffsetY = 0;
134 	_greenFadingTable = _blueFadingTable = _lightBlueFadingTable = _blackFadingTable = _greyFadingTable = 0;
135 
136 	_menuDefs = 0;
137 
138 	_exchangeCharacterId = -1;
139 	_charExchangeSwap = 0;
140 	_configHpBarGraphs = true;
141 	_configMouseBtSwap = false;
142 
143 	memset(_dialogueLastBitmap, 0, 13);
144 	_npcSequenceSub = 0;
145 	_moveCounter = 0;
146 	_partyResting = false;
147 
148 	_flyingObjects = 0;
149 
150 	_inf = 0;
151 	_stepCounter = 0;
152 	_stepsUntilScriptCall = 0;
153 	_scriptTimersMode = 3;
154 	_currentDirection = 0;
155 
156 	_openBookSpellLevel = 0;
157 	_openBookSpellSelectedItem = 0;
158 	_openBookSpellListOffset = 0;
159 	_openBookChar = _openBookCharBackup = _openBookCasterLevel = 0;
160 	_openBookType = _openBookTypeBackup = 0;
161 	_openBookSpellList = 0;
162 	_openBookAvailableSpells = 0;
163 	_activeSpellCharId = 0;
164 	_activeSpellCharacterPos = 0;
165 	_activeSpell = 0;
166 	_characterSpellTarget = 0;
167 	_returnAfterSpellCallback = false;
168 	_spells = 0;
169 	_spellAnimBuffer = 0;
170 	_clericSpellOffset = 0;
171 	_restPartyElapsedTime = _disableElapsedTime = 0;
172 	_allowSkip = false;
173 	_allowImport = false;
174 
175 	_wallsOfForce = 0;
176 
177 	_rrCount = 0;
178 	memset(_rrNames, 0, 10 * sizeof(const char *));
179 	memset(_rrId, 0, 10 * sizeof(int8));
180 
181 	_mainMenuStrings = _levelGainStrings = _monsterSpecAttStrings = _characterGuiStringsHp = 0;
182 	_characterGuiStringsWp = _characterGuiStringsWr = _characterGuiStringsSt = 0;
183 	_characterGuiStringsIn = _characterStatusStrings7 = _characterStatusStrings8 = 0;
184 	_characterStatusStrings9 = _characterStatusStrings12 = _characterStatusStrings13 = 0;
185 	_classModifierFlags = _saveThrowLevelIndex = _saveThrowModDiv = _saveThrowModExt = 0;
186 	_wandTypes = _drawObjPosIndex = _flightObjFlipIndex = _expObjectTblIndex = 0;
187 	_expObjectShpStart = _expObjectTlMode = _expObjectAnimTbl1 = _expObjectAnimTbl2 = _expObjectAnimTbl3 = 0;
188 	_monsterStepTable0 = _monsterStepTable1 = _monsterStepTable2 = _monsterStepTable3 = 0;
189 	_projectileWeaponAmmoTypes = _flightObjShpMap = _flightObjSclIndex = 0;
190 	_monsterCloseAttPosTable1 = _monsterCloseAttPosTable2 = _monsterCloseAttChkTable1 = 0;
191 	_monsterCloseAttChkTable2 = _monsterCloseAttDstTable1 = _monsterCloseAttDstTable2 = 0;
192 	_monsterProximityTable = _findBlockMonstersTable = _wallOfForceDsY = _wallOfForceDsNumW = 0;
193 	_wallOfForceDsNumH = _wallOfForceShpId = _wllFlagPreset = _teleporterShapeCoords = 0;
194 	_monsterCloseAttUnkTable = _monsterFrmOffsTable1 = _monsterFrmOffsTable2 = 0;
195 	_monsterDirChangeTable = _portalSeq = 0;
196 	_wallOfForceDsX = 0;
197 	_expObjectAnimTbl1Size = _expObjectAnimTbl2Size = _expObjectAnimTbl3Size = 0;
198 	_wllFlagPresetSize = _scriptTimersCount = _buttonList1Size = _buttonList2Size = 0;
199 	_buttonList3Size = _buttonList4Size = _buttonList5Size = _buttonList6Size = 0;
200 	_buttonList7Size = _buttonList8Size = 0;
201 	_inventorySlotsY = _mnDef = 0;
202 	_invFont1 = _invFont2 = _conFont = Screen::FID_6_FNT;
203 	_invFont3 = Screen::FID_8_FNT;
204 	_transferStringsScummVM = 0;
205 	_buttonDefs = 0;
206 	_npcPreset = 0;
207 	_npcPresetNames = 0;
208 	_chargenStatStrings = _chargenRaceSexStrings = _chargenClassStrings = 0;
209 	_chargenAlignmentStrings = _pryDoorStrings = _warningStrings = _ripItemStrings = 0;
210 	_cursedString = _enchantedString = _magicObjectStrings = _magicObjectString5 = 0;
211 	_patternSuffix = _patternGrFix1 = _patternGrFix2 = _validateArmorString = 0;
212 	_validateCursedString = _validateNoDropString = _potionStrings = _wandStrings = 0;
213 	_itemMisuseStrings = _suffixStringsRings = _suffixStringsPotions = 0;
214 	_suffixStringsWands = _takenStrings = _potionEffectStrings = _yesNoStrings = 0;
215 	_npcMaxStrings = _okStrings = _npcJoinStrings = _cancelStrings = 0;
216 	_abortStrings = _saveLoadStrings = _mnWord = _mnPrompt = _bookNumbers = 0;
217 	_mageSpellList = _clericSpellList = _spellNames = _magicStrings1 = 0;
218 	_magicStrings2 = _magicStrings3 = _magicStrings4 = _magicStrings6 = 0;
219 	_magicStrings7 = _magicStrings8 = _magicStrings9 = _saveNamePatterns = 0;
220 	_spellAnimBuffer = 0;
221 	_sparkEffectDefSteps = _sparkEffectDefSubSteps = _sparkEffectDefShift = 0;
222 	_sparkEffectDefAdd = _sparkEffectDefX = _sparkEffectDefY = _sparkEffectOfShift = 0;
223 	_sparkEffectOfX = _sparkEffectOfY = _magicFlightObjectProperties = 0;
224 	_turnUndeadEffect = _burningHandsDest = _coneOfColdGfxTbl = 0;
225 	_sparkEffectOfFlags1 = _sparkEffectOfFlags2 = 0;
226 	_coneOfColdDest1 = _coneOfColdDest2 = _coneOfColdDest3 = _coneOfColdDest4 = 0;
227 	_coneOfColdGfxTblSize = 0;
228 	_menuButtonDefs = 0;
229 	_updateCharNum = 0;
230 	_menuStringsMain = _menuStringsSaveLoad = _menuStringsOnOff = _menuStringsSpells = 0;
231 	_menuStringsRest = _menuStringsDrop = _menuStringsExit = _menuStringsStarve = 0;
232 	_menuStringsScribe = _menuStringsDrop2 = _menuStringsHead = _menuStringsPoison = 0;
233 	_menuStringsMgc = _menuStringsPrefs = _menuStringsRest2 = _menuStringsRest3 = 0;
234 	_menuStringsRest4 = _menuStringsDefeat = _menuStringsTransfer = _menuStringsSpec = 0;
235 	_menuStringsSpellNo = _menuYesNoStrings = _2431Strings = _textInputCharacterLines = _textInputSelectStrings = 0;
236 	_errorSlotEmptyString = _errorSlotNoNameString = _menuOkString = 0;
237 	_spellLevelsMage = _spellLevelsCleric = _numSpellsCleric = _numSpellsWisAdj = _numSpellsPal = _numSpellsMage = 0;
238 	_mnNumWord = _numSpells = _mageSpellListSize = _spellLevelsMageSize = _spellLevelsClericSize = _textInputCharacterLinesSize = 0;
239 	_inventorySlotsX = _slotValidationFlags = _encodeMonsterShpTable = 0;
240 	_cgaMappingDefault = _cgaMappingAlt = _cgaMappingInv = _cgaLevelMappingIndex = _cgaMappingItemsL = _cgaMappingItemsS = _cgaMappingThrown = _cgaMappingIcons = _cgaMappingDeco = 0;
241 	_amigaLevelSoundList1 = _amigaLevelSoundList2 = 0;
242 	_dcrShpDataPos = 0;
243 	_amigaSoundMap = 0;
244 	_amigaCurSoundFile = -1;
245 	_prefMenuPlatformOffset = 0;
246 	_lastVIntTick = _lastSecTick = _totalPlaySecs = _totalEnemiesKilled = _totalSteps = 0;
247 	_levelMaps = 0;
248 	_closeSpellbookAfterUse = false;
249 	_wndBackgrnd = 0;
250 
251 	memset(_cgaMappingLevel, 0, sizeof(_cgaMappingLevel));
252 	memset(_expRequirementTables, 0, sizeof(_expRequirementTables));
253 	memset(_saveThrowTables, 0, sizeof(_saveThrowTables));
254 	memset(_doorType, 0, sizeof(_doorType));
255 	memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch));
256 	memset(_scriptTimers, 0, sizeof(_scriptTimers));
257 	memset(_monsterBlockPosArray, 0, sizeof(_monsterBlockPosArray));
258 	memset(_foundMonstersArray, 0, sizeof(_foundMonstersArray));
259 
260 #define DWM0 _dscWallMapping.push_back(0)
261 #define DWM(x) _dscWallMapping.push_back(&_sceneDrawVar##x)
262 	DWM0;       DWM0;       DWM(Down);  DWM(Right);
263 	DWM(Down);  DWM(Right); DWM(Down);  DWM0;
264 	DWM(Down);  DWM(Left);  DWM(Down);  DWM(Left);
265 	DWM0;       DWM0;       DWM(Down);  DWM(Right);
266 	DWM(Down);  DWM(Right); DWM(Down);  DWM0;
267 	DWM(Down);  DWM(Left);  DWM(Down);  DWM(Left);
268 	DWM(Down);  DWM(Right); DWM(Down);  DWM0;
269 	DWM(Down);  DWM(Left);  DWM0;       DWM(Right);
270 	DWM(Down);  DWM0;       DWM0;       DWM(Left);
271 #undef DWM
272 #undef DWM0
273 }
274 
~EoBCoreEngine()275 EoBCoreEngine::~EoBCoreEngine() {
276 	releaseItemsAndDecorationsShapes();
277 	releaseTempData();
278 
279 	if (_faceShapes) {
280 		for (int i = 0; i < 44; i++) {
281 			if (_characters) {
282 				for (int ii = 0; ii < 6; ii++) {
283 					if (_characters[ii].faceShape == _faceShapes[i])
284 						_characters[ii].faceShape = 0;
285 				}
286 			}
287 			delete[] _faceShapes[i];
288 			_faceShapes[i] = 0;
289 		}
290 		delete[] _faceShapes;
291 	}
292 
293 	if (_characters) {
294 		for (int i = 0; i < 6; i++) {
295 			delete[] _characters[i].faceShape;
296 			delete[] _characters[i].nameShape;
297 		}
298 	}
299 
300 	delete[] _characters;
301 	delete[] _items;
302 	delete[] _itemTypes;
303 
304 	releaseShpArr(_itemNames, 130);
305 	delete[] _flyingObjects;
306 
307 	delete[] _monsterFlashOverlay;
308 	delete[] _monsterStoneOverlay;
309 	delete[] _monsters;
310 
311 	if (_monsterDecorations) {
312 		releaseMonsterShapes(0, 36);
313 		delete[] _monsterShapes;
314 		delete[] _monsterDecorations;
315 
316 		for (int i = 0; i < 24; i++)
317 			delete[] _monsterPalettes[i];
318 		delete[] _monsterPalettes;
319 	}
320 
321 	delete[] _monsterProps;
322 
323 	if (_doorSwitches) {
324 		releaseDoorShapes();
325 		delete[] _doorSwitches;
326 	}
327 
328 	releaseDecorations();
329 	delete[] _levelDecorationRects;
330 	_dscWallMapping.clear();
331 
332 	delete[] _greenFadingTable;
333 	delete[] _blueFadingTable;
334 	delete[] _lightBlueFadingTable;
335 	delete[] _blackFadingTable;
336 	delete[] _greyFadingTable;
337 
338 	delete[] _spells;
339 	delete[] _spellAnimBuffer;
340 	delete[] _wallsOfForce;
341 	delete[] _buttonDefs;
342 
343 	for (int i = 0; i < 6; i++)
344 		delete[] _redSplatBG[i];
345 
346 	delete _gui;
347 	_gui = 0;
348 	delete _screen;
349 	_screen = 0;
350 
351 	delete[] _menuDefs;
352 	_menuDefs = 0;
353 
354 	delete[] _amigaSoundMap;
355 	_amigaSoundMap = 0;
356 
357 	delete _inf;
358 	_inf = 0;
359 	delete _timer;
360 	_timer = 0;
361 	delete _txt;
362 	_txt = 0;
363 }
364 
initKeymaps(const Common::String & gameId)365 Common::KeymapArray EoBCoreEngine::initKeymaps(const Common::String &gameId) {
366 	Common::Keymap *const keyMap = new Common::Keymap(Common::Keymap::kKeymapTypeGame, kKeymapName, "Eye of the Beholder");
367 
368 	addKeymapAction(keyMap, Common::kStandardActionLeftClick, _("Interact via Left Click"), &Common::Action::setLeftClickEvent, "MOUSE_LEFT", "JOY_A");
369 	addKeymapAction(keyMap, Common::kStandardActionRightClick, _("Interact via Right Click"), &Common::Action::setRightClickEvent, "MOUSE_RIGHT", "JOY_B");
370 	addKeymapAction(keyMap, "MVF", _("Move Forward"), Common::KeyState(Common::KEYCODE_UP), "UP", "JOY_UP");
371 	addKeymapAction(keyMap, "MVB", _("Move Back"), Common::KeyState(Common::KEYCODE_DOWN), "DOWN", "JOY_DOWN");
372 	addKeymapAction(keyMap, "MVL", _("Move Left"), Common::KeyState(Common::KEYCODE_LEFT), "LEFT", "JOY_LEFT_TRIGGER");
373 	addKeymapAction(keyMap, "MVR", _("Move Right"), Common::KeyState(Common::KEYCODE_RIGHT), "RIGHT", "JOY_RIGHT_TRIGGER");
374 	addKeymapAction(keyMap, "TL", _("Turn Left"), Common::KeyState(Common::KEYCODE_HOME), "HOME", "JOY_LEFT");
375 	addKeymapAction(keyMap, "TR", _("Turn Right"), Common::KeyState(Common::KEYCODE_PAGEUP), "PAGEUP", "JOY_RIGHT");
376 	addKeymapAction(keyMap, "INV", _("Open/Close Inventory"), Common::KeyState(Common::KEYCODE_i, 'i'), "i", "JOY_X");
377 	addKeymapAction(keyMap, "SCE", _("Switch Inventory/Character screen"), Common::KeyState(Common::KEYCODE_p, 'p'), "p", "JOY_Y");
378 	addKeymapAction(keyMap, "CMP", _("Camp"), Common::KeyState(Common::KEYCODE_c, 'c'), "c", "");
379 	addKeymapAction(keyMap, "CSP", _("Cast Spell"), Common::KeyState(Common::KEYCODE_SPACE, ' '), "SPACE", "JOY_LEFT_SHOULDER");
380 	// TODO: Spell cursor, but this needs more thought, since different
381 	// game versions use different keycodes.
382 	addKeymapAction(keyMap, "SL1", _("Spell Level 1"), Common::KeyState(Common::KEYCODE_1, '1'), "1", "");
383 	addKeymapAction(keyMap, "SL2", _("Spell Level 2"), Common::KeyState(Common::KEYCODE_2, '2'), "2", "");
384 	addKeymapAction(keyMap, "SL3", _("Spell Level 3"), Common::KeyState(Common::KEYCODE_3, '3'), "3", "");
385 	addKeymapAction(keyMap, "SL4", _("Spell Level 4"), Common::KeyState(Common::KEYCODE_4, '4'), "4", "");
386 	addKeymapAction(keyMap, "SL5", _("Spell Level 5"), Common::KeyState(Common::KEYCODE_5, '5'), "5", "");
387 	if (gameId == "eob2")
388 		addKeymapAction(keyMap, "SL6", _("Spell Level 6"), Common::KeyState(Common::KEYCODE_6, '6'), "6", "");
389 
390 	return Common::Keymap::arrayOf(keyMap);
391 }
392 
init()393 Common::Error EoBCoreEngine::init() {
394 	if (ConfMan.hasKey("render_mode"))
395 		_configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode"));
396 
397 	_enableHiResDithering = (_configRenderMode == Common::kRenderEGA && _flags.useHiRes);
398 
399 	_screen = new Screen_EoB(this, _system);
400 	assert(_screen);
401 	_screen->setResolution();
402 
403 	_res = new Resource(this);
404 	assert(_res);
405 	_res->reset();
406 
407 	_staticres = new StaticResource(this);
408 	assert(_staticres);
409 	if (!_staticres->init())
410 		error("_staticres->init() failed");
411 
412 	// We start the respective sound driver even if "No Music" has been selected, because we
413 	// don't have a null driver class (and don't really need one). We just disable the sound here.
414 	MidiDriver::DeviceHandle dev = 0;
415 	switch (_flags.platform) {
416 	case Common::kPlatformDOS: {
417 		int flags = MDT_ADLIB | MDT_PCSPK;
418 		dev = MidiDriver::detectDevice(_flags.gameID == GI_EOB1 ? flags | MDT_PCJR : flags);
419 		MusicType type = MidiDriver::getMusicType(dev);
420 		_sound = new SoundPC_v1(this, _mixer, type == MT_ADLIB ? Sound::kAdLib : type == MT_PCSPK ? Sound::kPCSpkr : Sound::kPCjr);
421 		} break;
422 	case Common::kPlatformFMTowns:
423 		dev = MidiDriver::detectDevice(MDT_TOWNS);
424 		_sound = new SoundTowns_Darkmoon(this, _mixer);
425 		break;
426 	case Common::kPlatformPC98:
427 		if (_flags.gameID == GI_EOB1) {
428 			dev = MidiDriver::detectDevice(MDT_PC98);
429 			_sound = new SoundPC98_EoB(this, _mixer);
430 		} else {
431 			dev = MidiDriver::detectDevice(MDT_PC98 | MDT_MIDI);
432 			/**/
433 		}
434 		break;
435 	case Common::kPlatformAmiga:
436 		dev = MidiDriver::detectDevice(MDT_AMIGA);
437 		_sound = new SoundAmiga_EoB(this, _mixer);
438 		break;
439 	case Common::kPlatformSegaCD:
440 		dev = MidiDriver::detectDevice(MDT_SEGACD);
441 		_sound = new SoundSegaCD_EoB(this, _mixer);
442 		break;
443 	default:
444 		// Dummy error message. Unsupported platforms don't have detection entries.
445 		error("Unsupported platform '%d'", _flags.platform);
446 	}
447 
448 	assert(_sound);
449 	_sound->init();
450 
451 	if (_flags.platform == Common::kPlatformPC98)
452 		_sound->loadSfxFile("EFECT.OBJ");
453 
454 	// Setup volume settings (and read in all ConfigManager settings)
455 	_configNullSound = (MidiDriver::getMusicType(dev) == MT_NULL);
456 	syncSoundSettings();
457 
458 	if (!_screen->init())
459 		error("screen()->init() failed");
460 
461 	if (ConfMan.hasKey("save_slot")) {
462 		_gameToLoad = ConfMan.getInt("save_slot");
463 		if (!saveFileLoadable(_gameToLoad))
464 			_gameToLoad = -1;
465 	}
466 
467 	setupKeyMap();
468 
469 	if (_flags.platform != Common::kPlatformSegaCD) {
470 		_gui = new GUI_EoB(this);
471 		assert(_gui);
472 		_txt = new TextDisplayer_rpg(this, _screen);
473 		assert(_txt);
474 	}
475 
476 	_inf = new EoBInfProcessor(this, _screen);
477 	assert(_inf);
478 	setDebugger(new Debugger_EoB(this));
479 
480 	loadFonts();
481 
482 	Common::Error err = KyraRpgEngine::init();
483 	if (err.getCode() != Common::kNoError)
484 		return err;
485 
486 	initButtonData();
487 	initMenus();
488 	initStaticResource();
489 	initSpells();
490 
491 	_timer = new TimerManager(this, _system);
492 	assert(_timer);
493 	setupTimers();
494 
495 	_wllVmpMap[1] = 1;
496 	_wllVmpMap[2] = 2;
497 	memset(&_wllVmpMap[3], 3, 20);
498 	_wllVmpMap[23] = 4;
499 	_wllVmpMap[24] = 5;
500 
501 	memcpy(_wllWallFlags, _wllFlagPreset, _wllFlagPresetSize);
502 
503 	memset(&_specialWallTypes[3], 1, 5);
504 	memset(&_specialWallTypes[13], 1, 5);
505 	_specialWallTypes[8] = _specialWallTypes[18] = 6;
506 
507 	memset(&_wllShapeMap[3], -1, 5);
508 	memset(&_wllShapeMap[13], -1, 5);
509 
510 	_wllVcnOffset = (_flags.platform == Common::kPlatformFMTowns) ? 0 : 16;
511 	int bpp = (_flags.platform == Common::kPlatformFMTowns) ? 2 : 1;
512 
513 	_greenFadingTable = new uint8[256 * bpp];
514 	_blueFadingTable = new uint8[256 * bpp];
515 	_lightBlueFadingTable = new uint8[256 * bpp];
516 	_blackFadingTable = new uint8[256 * bpp];
517 	_greyFadingTable = new uint8[256 * bpp];
518 
519 	_monsters = new EoBMonsterInPlay[30];
520 	memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay));
521 
522 	_characters = new EoBCharacter[6];
523 	memset(_characters, 0, sizeof(EoBCharacter) * 6);
524 
525 	_items = new EoBItem[600];
526 	memset(_items, 0, sizeof(EoBItem) * 600);
527 
528 	_itemNames = new char*[130];
529 	for (int i = 0; i < 130; i++) {
530 		_itemNames[i] = new char[35];
531 		memset(_itemNames[i], 0, 35);
532 	}
533 
534 	_flyingObjects = new EoBFlyingObject[_numFlyingObjects];
535 	_flyingObjectsPtr = _flyingObjects;
536 	memset(_flyingObjects, 0, _numFlyingObjects * sizeof(EoBFlyingObject));
537 
538 	int bufferSize = _flags.useHiColorMode ? 8192 : 4096;
539 	_spellAnimBuffer = new uint8[bufferSize];
540 	memset(_spellAnimBuffer, 0, bufferSize);
541 
542 	_wallsOfForce = new WallOfForce[5];
543 	memset(_wallsOfForce, 0, 5 * sizeof(WallOfForce));
544 
545 	memset(_doorType, 0, sizeof(_doorType));
546 	memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch));
547 
548 	_monsterShapes = new uint8*[36];
549 	memset(_monsterShapes, 0, 36 * sizeof(uint8 *));
550 	_monsterDecorations = new SpriteDecoration[36];
551 	memset(_monsterDecorations, 0, 36 * sizeof(SpriteDecoration));
552 	_monsterPalettes = new uint8*[24];
553 	for (int i = 0; i < 24; i++)
554 		_monsterPalettes[i] = new uint8[16];
555 
556 	_doorSwitches = new SpriteDecoration[6];
557 	memset(_doorSwitches, 0, 6 * sizeof(SpriteDecoration));
558 
559 	_monsterFlashOverlay = new uint8[16];
560 	_monsterStoneOverlay = new uint8[16];
561 	memset(_monsterFlashOverlay, (_configRenderMode == Common::kRenderCGA) ? 0xFF : guiSettings()->colors.guiColorWhite, 16 * sizeof(uint8));
562 	memset(_monsterStoneOverlay, (_flags.platform == Common::kPlatformAmiga) ? guiSettings()->colors.guiColorWhite : 0x0D, 16 * sizeof(uint8));
563 	_monsterFlashOverlay[0] = _monsterStoneOverlay[0] = 0;
564 
565 	return Common::kNoError;
566 }
567 
loadFonts()568 void EoBCoreEngine::loadFonts() {
569 	// Only the fonts that are based on game resource files are loaded here. ScummVM builtin fonts like the
570 	// FM-Towns ROM font or the PC-98 SJIS font get initialized in Screen::init() and Screen_EoB::init().
571 
572 	if (_flags.platform == Common::kPlatformAmiga) {
573 		if (_res->exists("EOBF6.FONT"))
574 			_screen->loadFont(Screen::FID_6_FNT, "EOBF6.FONT");
575 		else if (_res->exists("FONTS/EOBF6.FONT"))
576 			_screen->loadFont(Screen::FID_6_FNT, "FONTS/EOBF6.FONT");
577 		else
578 			AmigaDOSFont::errorDialog(0);
579 
580 		if (_res->exists("EOBF8.FONT"))
581 			_screen->loadFont(Screen::FID_8_FNT, "EOBF8.FONT");
582 		else if (_res->exists("FONTS/EOBF8.FONT"))
583 			_screen->loadFont(Screen::FID_8_FNT, "FONTS/EOBF8.FONT");
584 		else
585 			AmigaDOSFont::errorDialog(0);
586 
587 	} else if (_flags.platform != Common::kPlatformSegaCD) {
588 		_screen->loadFont(Screen::FID_6_FNT, "FONT6.FNT");
589 		_screen->loadFont(Screen::FID_8_FNT, "FONT8.FNT");
590 	}
591 
592 	if (_flags.platform == Common::kPlatformFMTowns) {
593 		_screen->loadFont(Screen::FID_SJIS_SMALL_FNT, "FONT.DMP");
594 	} else if (_flags.platform == Common::kPlatformPC98) {
595 		_screen->loadFont(Screen::FID_SJIS_SMALL_FNT, "FONT12.FNT");
596 		_invFont1 = Screen::FID_SJIS_SMALL_FNT;
597 		_conFont = _invFont3 = Screen::FID_SJIS_FNT;
598 	} else if (_flags.platform == Common::kPlatformSegaCD) {
599 		_screen->loadFont(Screen::FID_8_FNT, "FONTK12");
600 		_screen->setFontStyles(Screen::FID_8_FNT, Font::kStyleNone);
601 		_invFont1 = _invFont2 = _conFont = Screen::FID_8_FNT;
602 	}
603 }
604 
go()605 Common::Error EoBCoreEngine::go() {
606 	static_cast<Debugger_EoB *>(getDebugger())->initialize();
607 	_txt->removePageBreakFlag();
608 	_screen->setFont(_flags.platform == Common::kPlatformPC98 ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT);
609 	loadItemsAndDecorationsShapes();
610 
611 	_screen->setMouseCursor(0, 0, _itemIconShapes[0]);
612 
613 	// Import original save game files (especially the "Quick Start Party").
614 	// The SegaCD version has a "Default Party" main menu option instead.
615 	if (ConfMan.getBool("importOrigSaves")) {
616 		if (_flags.platform != Common::kPlatformSegaCD)
617 			importOriginalSaveFile(-1);
618 		ConfMan.setBool("importOrigSaves", false);
619 		ConfMan.flushToDisk();
620 	}
621 
622 	loadItemDefs();
623 	int action = 0;
624 
625 	for (bool repeatLoop = true; repeatLoop; repeatLoop ^= true) {
626 		action = 0;
627 
628 		if (_gameToLoad != -1) {
629 			startupLoad();
630 			if (loadGameState(_gameToLoad).getCode() != Common::kNoError)
631 				error("Couldn't load game slot %d on startup", _gameToLoad);
632 			_gameToLoad = -1;
633 		} else {
634 			_screen->showMouse();
635 			action = mainMenu();
636 		}
637 
638 		if (action == -1) {
639 			// load game
640 			startupLoad();
641 			repeatLoop = _gui->runLoadMenu(_flags.platform == Common::kPlatformSegaCD ? 80 : 72, _flags.platform == Common::kPlatformSegaCD ? 16 : 14, true);
642 			if (!repeatLoop)
643 				startupReset();
644 		} else if (action == -2 || action == -4) {
645 			// new game
646 			repeatLoop = startCharacterGeneration(action == -4);
647 			if (repeatLoop && !shouldQuit())
648 				startupNew();
649 			else
650 				startupReset();
651 		} else if (action == -3) {
652 			// transfer party
653 			repeatLoop = startPartyTransfer();
654 			if (repeatLoop && !shouldQuit())
655 				startupNew();
656 		}
657 	}
658 
659 	if (!shouldQuit() && action >= -4) {
660 		runLoop();
661 
662 		if (_playFinale) {
663 			// make final save for party transfer
664 			saveGameStateIntern(-1, 0, 0);
665 			_sound->selectAudioResourceSet(kMusicFinale);
666 			seq_playFinale();
667 		}
668 	}
669 
670 	return Common::kNoError;
671 }
672 
registerDefaultSettings()673 void EoBCoreEngine::registerDefaultSettings() {
674 	KyraEngine_v1::registerDefaultSettings();
675 	ConfMan.registerDefault("hpbargraphs", true);
676 	ConfMan.registerDefault("mousebtswap", false);
677 	ConfMan.registerDefault("importOrigSaves", true);
678 }
679 
readSettings()680 void EoBCoreEngine::readSettings() {
681 	_configHpBarGraphs = ConfMan.getBool("hpbargraphs");
682 	_configMouseBtSwap = ConfMan.getBool("mousebtswap");
683 	_configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1;
684 	_configMusic = (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformSegaCD) ? (ConfMan.getBool("music_mute") ? 0 : 1) : (_configSounds ? 1 : 0);
685 
686 	if (_sound) {
687 		_sound->enableMusic(_configNullSound ? false : _configMusic);
688 		_sound->enableSFX(_configNullSound ? false : _configSounds);
689 	}
690 }
691 
writeSettings()692 void EoBCoreEngine::writeSettings() {
693 	ConfMan.setBool("hpbargraphs", _configHpBarGraphs);
694 	ConfMan.setBool("mousebtswap", _configMouseBtSwap);
695 	ConfMan.setBool("sfx_mute", _configSounds == 0);
696 	if (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformSegaCD)
697 		ConfMan.setBool("music_mute", _configMusic == 0);
698 
699 	if (_sound) {
700 		if (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformSegaCD) {
701 			if (!_configMusic)
702 				snd_playSong(0);
703 		} else if (!_configSounds) {
704 			_sound->haltTrack();
705 		}
706 		_sound->enableMusic(_configNullSound ? false : _configMusic);
707 		_sound->enableSFX(_configNullSound ? false : _configSounds);
708 	}
709 
710 	ConfMan.flushToDisk();
711 }
712 
startupNew()713 void EoBCoreEngine::startupNew() {
714 	gui_setPlayFieldButtons();
715 	_screen->_curPage = 0;
716 	gui_drawPlayField(false);
717 	_screen->_curPage = 0;
718 	gui_drawAllCharPortraitsWithStats();
719 	drawScene(1);
720 	_updateFlags = 0;
721 	_updateCharNum = 0;
722 }
723 
runLoop()724 void EoBCoreEngine::runLoop() {
725 	_envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength);
726 	_flashShapeTimer = 0;
727 	_drawSceneTimer = _system->getMillis();
728 	_screen->setFont(_conFont);
729 	_screen->setScreenDim(7);
730 
731 	_runFlag = true;
732 
733 	while (!shouldQuit() && _runFlag) {
734 		checkPartyStatus(true);
735 		checkInput(_activeButtons, true, 0);
736 		removeInputTop();
737 
738 		if (!_runFlag)
739 			break;
740 
741 		_timer->update();
742 		updateScriptTimers();
743 		updateWallOfForceTimers();
744 
745 		if (_sceneUpdateRequired && !_sceneShakeCountdown)
746 			drawScene(1);
747 
748 		updateAnimTimers();
749 
750 		uint32 curTime = _system->getMillis();
751 		if (_envAudioTimer < curTime && !(_flags.gameID == GI_EOB1 && (_flags.platform == Common::kPlatformSegaCD || _flags.platform == Common::kPlatformAmiga || _currentLevel == 0 || _currentLevel > 3))) {
752 			_envAudioTimer = curTime + (rollDice(1, 10, 3) * 18 * _tickLength);
753 			snd_processEnvironmentalSoundEffect(_flags.gameID == GI_EOB1 ? 30 : (rollDice(1, 2, -1) ? 27 : 28), _currentBlock + rollDice(1, 12, -1));
754 		}
755 
756 		snd_updateLevelScore();
757 		snd_updateEnvironmentalSfx(0);
758 		turnUndeadAuto();
759 	}
760 }
761 
checkPartyStatus(bool handleDeath)762 bool EoBCoreEngine::checkPartyStatus(bool handleDeath) {
763 	int numChars = 0;
764 	for (int i = 0; i < 6; i++)
765 		numChars += testCharacter(i, 13);
766 
767 	if (numChars)
768 		return false;
769 
770 	if (!handleDeath)
771 		return true;
772 
773 	gui_drawAllCharPortraitsWithStats();
774 
775 	if (checkPartyStatusExtra()) {
776 		Screen::FontId of = _screen->setFont(_flags.use16ColorMode ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT);
777 		gui_updateControls();
778 		int x = 0;
779 		int y = 0;
780 		if (_flags.platform == Common::kPlatformSegaCD) {
781 			startupLoad();
782 			x = 80;
783 			y = 16;
784 		}
785 		if (_gui->runLoadMenu(x, y)) {
786 			_screen->setFont(of);
787 			return true;
788 		}
789 	}
790 
791 	if (_flags.platform == Common::kPlatformSegaCD)
792 		_screen->sega_fadeToBlack(1);
793 
794 	if (!shouldQuit())
795 		quitGame();
796 
797 	return false;
798 }
799 
updateAnimTimers()800 void EoBCoreEngine::updateAnimTimers() {
801 	uint32 curTime = _system->getMillis();
802 	if (_lastSecTick + 1000 <= curTime) {
803 		_lastSecTick = curTime;
804 		_totalPlaySecs++;
805 	}
806 
807 	if (_lastVIntTick + 16 <= curTime) {
808 		_lastVIntTick = curTime;
809 		gui_updateAnimations();
810 	}
811 }
812 
loadItemsAndDecorationsShapes()813 void EoBCoreEngine::loadItemsAndDecorationsShapes() {
814 	releaseItemsAndDecorationsShapes();
815 	int div = (_flags.gameID == GI_EOB1) ? 3 : 8;
816 	int mul = (_flags.gameID == GI_EOB1) ? 64 : 24;
817 
818 	_largeItemShapes = new const uint8*[_numLargeItemShapes];
819 	_screen->loadShapeSetBitmap("ITEML1", 5, 3);
820 	for (int i = 0; i < _numLargeItemShapes; i++)
821 		_largeItemShapes[i] = _screen->encodeShape((i / div) << 3, (i % div) * mul, 8, 24, false, _cgaMappingItemsL);
822 
823 	if (_flags.gameID == GI_EOB1) {
824 		for (int c = 0; c < 3; ++c) {
825 			_largeItemShapesScl[c] = new const uint8*[_numLargeItemShapes];
826 			for (int i = 0; i < _numLargeItemShapes; i++)
827 				_largeItemShapesScl[c][i] = _screen->encodeShape((i / div) << 3, (i % div) * mul + 24 + (c << 4), 6 - 2 * c, 16 - ((c >> 1) << 3), false, _cgaMappingItemsL);
828 		}
829 	}
830 
831 	_smallItemShapes = new const uint8*[_numSmallItemShapes];
832 	_screen->loadShapeSetBitmap("ITEMS1", 5, 3);
833 	for (int i = 0; i < _numSmallItemShapes; i++)
834 		_smallItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingItemsS);
835 
836 	if (_flags.gameID == GI_EOB1) {
837 		for (int c = 0; c < 3; ++c) {
838 			_smallItemShapesScl[c] = new const uint8*[_numSmallItemShapes];
839 			for (int i = 0; i < _numSmallItemShapes; i++)
840 				_smallItemShapesScl[c][i] = _screen->encodeShape((i / div) << 2, (i % div) * mul + 24 + (c << 4), 3 - c, 16 - ((c >> 1) << 3), false, _cgaMappingItemsS);
841 		}
842 	}
843 
844 	_thrownItemShapes = new const uint8*[_numThrownItemShapes];
845 	if (_flags.gameID == GI_EOB2)
846 		_spellShapes = new const uint8*[4];
847 	_firebeamShapes = new const uint8*[3];
848 
849 	_screen->loadShapeSetBitmap("THROWN", 5, 3);
850 	for (int i = 0; i < _numThrownItemShapes; i++)
851 		_thrownItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingThrown);
852 
853 	if (_flags.gameID == GI_EOB1) {
854 		for (int c = 0; c < 3; ++c) {
855 			_thrownItemShapesScl[c] = new const uint8*[_numThrownItemShapes];
856 			for (int i = 0; i < _numThrownItemShapes; i++)
857 				_thrownItemShapesScl[c][i] = _screen->encodeShape((i / div) << 2, (i % div) * mul + 24 + (c << 4), 3 - c, 16 - ((c >> 1) << 3), false, _cgaMappingThrown);
858 		}
859 	} else {
860 		for (int i = 0; i < 4; i++)
861 			_spellShapes[i] = _screen->encodeShape(8, i << 5, 6, 32, false, _cgaMappingThrown);
862 	}
863 
864 	_firebeamShapes[0] = _screen->encodeShape(16, 0, 4, 24, false, _cgaMappingThrown);
865 	_firebeamShapes[1] = _screen->encodeShape(16, 24, 4, 24, false, _cgaMappingThrown);
866 	_firebeamShapes[2] = _screen->encodeShape(16, 48, 3, 24, false, _cgaMappingThrown);
867 	_redSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 144 : 72, 5, 24, false, _cgaMappingThrown);
868 	_greenSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 168 : 96, 5, 16, false, _cgaMappingThrown);
869 
870 	_itemIconShapes = new const uint8*[_numItemIconShapes];
871 	_screen->loadShapeSetBitmap("ITEMICN", 5, 3);
872 	for (int i = 0; i < _numItemIconShapes; i++)
873 		_itemIconShapes[i] = _screen->encodeShape((i % 0x14) << 1, (i / 0x14) << 4, 2, 0x10, false, _cgaMappingIcons);
874 
875 	if (_flags.platform == Common::kPlatformAmiga) {
876 		const uint8 offsY = (_flags.gameID == GI_EOB1) ? 80 : 96;
877 		_blueItemIconShapes = new const uint8*[_numItemIconShapes];
878 		for (int i = 0; i < _numItemIconShapes; i++) {
879 			int bx = (i % 0x14) << 1;
880 			int by = (i / 0x14) << 4;
881 			_blueItemIconShapes[i] = _screen->getPagePixel(2, (bx << 3) + 8, by + offsY + 8) ? _screen->encodeShape(bx, by + offsY, 2, 0x10, false, 0) : _screen->encodeShape(bx, by, 2, 0x10, false, 0);
882 		}
883 	}
884 
885 	_teleporterShapes = new const uint8*[6];
886 	_sparkShapes = new const uint8*[4];
887 	_compassShapes = new const uint8*[12];
888 	if (_flags.gameID == GI_EOB2)
889 		_wallOfForceShapes = new const uint8*[6];
890 
891 	_screen->loadShapeSetBitmap("DECORATE", 5, 3);
892 	if (_flags.gameID == GI_EOB2) {
893 		_lightningColumnShape = _screen->encodeShape(18, 88, 4, 64);
894 		for (int i = 0; i < 6; i++)
895 			_wallOfForceShapes[i] = _screen->encodeShape(_wallOfForceShapeDefs[(i << 2)], _wallOfForceShapeDefs[(i << 2) + 1], _wallOfForceShapeDefs[(i << 2) + 2], _wallOfForceShapeDefs[(i << 2) + 3]);
896 	}
897 
898 	for (int i = 0; i < 6; i++)
899 		_teleporterShapes[i] = _screen->encodeShape(_teleporterShapeDefs[(i << 2)], _teleporterShapeDefs[(i << 2) + 1], _teleporterShapeDefs[(i << 2) + 2], _teleporterShapeDefs[(i << 2) + 3], false, _cgaMappingDefault);
900 
901 	_sparkShapes[0] = _screen->encodeShape(29, 0, 2, 16, false, _cgaMappingDeco);
902 	_sparkShapes[1] = _screen->encodeShape(31, 0, 2, 16, false, _cgaMappingDeco);
903 	_sparkShapes[2] = _screen->encodeShape(33, 0, 2, 16, false, _cgaMappingDeco);
904 	_sparkShapes[3] = 0;
905 	_deadCharShape = _screen->encodeShape(0, 88, 4, 32, false, _cgaMappingDeco);
906 	_disabledCharGrid = _screen->encodeShape(4, 88, 4, 32, false, _cgaMappingDeco);
907 	_blackBoxSmallGrid = _screen->encodeShape(9, 88, 2, 8, false, _cgaMappingDeco);
908 	_weaponSlotGrid = _screen->encodeShape(8, 88, 4, 16, false, _cgaMappingDeco);
909 	_blackBoxWideGrid = _screen->encodeShape(8, 104, 4, 8, false, _cgaMappingDeco);
910 
911 	static const uint8 dHeight[] = { 17, 10, 10 };
912 	static const uint8 dY[] = { 120, 137, 147 };
913 
914 	for (int y = 0; y < 3; y++) {
915 		for (int x = 0; x < 4; x++)
916 			_compassShapes[(y << 2) + x] = _screen->encodeShape(x * 3, dY[y], 3, dHeight[y], false, _cgaMappingDeco);
917 	}
918 }
919 
releaseItemsAndDecorationsShapes()920 void EoBCoreEngine::releaseItemsAndDecorationsShapes() {
921 	if (_flags.platform != Common::kPlatformFMTowns || _flags.gameID != GI_EOB2) {
922 		releaseShpArr(_largeItemShapes, _numLargeItemShapes);
923 		releaseShpArr(_smallItemShapes, _numSmallItemShapes);
924 		releaseShpArr(_thrownItemShapes, _numThrownItemShapes);
925 		releaseShpArr(_spellShapes, 4);
926 		releaseShpArr(_itemIconShapes, _numItemIconShapes);
927 		releaseShpArr(_blueItemIconShapes, _numItemIconShapes);
928 		releaseShpArr(_xtraItemIconShapes, 3);
929 		releaseShpArr(_sparkShapes, 4);
930 		releaseShpArr(_wallOfForceShapes, 6);
931 		releaseShpArr(_compassShapes, 12);
932 		releaseShpArr(_firebeamShapes, 3);
933 
934 		// SegaCD uses the spark shapes for drawing the teleporters. We copy only the pointers, not the whole shapes.
935 		if (_flags.platform != Common::kPlatformSegaCD) {
936 			releaseShpArr(_teleporterShapes, 6);
937 		}
938 
939 		delete[] _redSplatShape;
940 		delete[] _greenSplatShape;
941 		delete[] _swapShape;
942 		delete[] _deadCharShape;
943 		delete[] _disabledCharGrid;
944 		delete[] _blackBoxSmallGrid;
945 		delete[] _weaponSlotGrid;
946 		delete[] _blackBoxWideGrid;
947 		delete[] _lightningColumnShape;
948 	}
949 
950 	delete[] _largeItemShapes;
951 	delete[] _smallItemShapes;
952 	delete[] _thrownItemShapes;
953 	delete[] _spellShapes;
954 	delete[] _itemIconShapes;
955 	delete[] _blueItemIconShapes;
956 	delete[] _xtraItemIconShapes;
957 	delete[] _sparkShapes;
958 	delete[] _wallOfForceShapes;
959 	delete[] _teleporterShapes;
960 	delete[] _compassShapes;
961 	delete[] _firebeamShapes;
962 
963 	for (int i = 0; i < 3; ++i) {
964 		releaseShpArr(_largeItemShapesScl[i], _numLargeItemShapes);
965 		releaseShpArr(_smallItemShapesScl[i], _numSmallItemShapes);
966 		releaseShpArr(_thrownItemShapesScl[i], _numThrownItemShapes);
967 		delete[] _largeItemShapesScl[i];
968 		delete[] _smallItemShapesScl[i];
969 		delete[] _thrownItemShapesScl[i];
970 	}
971 }
972 
setHandItem(Item itemIndex)973 void EoBCoreEngine::setHandItem(Item itemIndex) {
974 	if (itemIndex == -1) {
975 		if (_flags.platform == Common::kPlatformFMTowns)
976 			_screen->setMouseCursor(8, 8, _itemIconShapes[37], 0);
977 		return;
978 	}
979 
980 	if (_screen->curDimIndex() == 7 && itemIndex) {
981 		printFullItemName(itemIndex);
982 		_txt->printMessage(_takenStrings[0]);
983 	}
984 
985 	_itemInHand = itemIndex;
986 	int icon = _items[_itemInHand].icon;
987 	const uint8 *shp = _itemIconShapes[icon];
988 	const uint8 *ovl = 0;
989 	bool applyBluePal = ((_partyEffectFlags & 2) && (_items[_itemInHand].flags & 0x80)) ? true : false;
990 
991 	if (_xtraItemIconShapes) {
992 		bool applyBluePalC = applyBluePal;
993 		applyBluePal = false;
994 		if (_items[_itemInHand].nameUnid == 23)
995 			shp = _xtraItemIconShapes[0];
996 		else if (_items[_itemInHand].nameUnid == 97)
997 			shp = _xtraItemIconShapes[1];
998 		else if (_items[_itemInHand].nameId == 39)
999 			shp = _xtraItemIconShapes[2];
1000 		else
1001 			applyBluePal = applyBluePalC;
1002 	}
1003 
1004 	if (icon && applyBluePal) {
1005 		if (_blueItemIconShapes)
1006 			shp = _blueItemIconShapes[icon];
1007 		else
1008 			ovl = _flags.gameID == GI_EOB1 ? ((_configRenderMode == Common::kRenderCGA) ? _itemsOverlayCGA : &_itemsOverlay[icon << 4]) : _screen->generateShapeOverlay(shp, _lightBlueFadingTable);
1009 	}
1010 
1011 	int mouseOffs = itemIndex ? 8 : 0;
1012 	_screen->setMouseCursor(mouseOffs, mouseOffs, shp, ovl);
1013 
1014 	if (_flags.useHiColorMode) {
1015 		_screen->setFadeTable(_greyFadingTable);
1016 		_screen->setShapeFadingLevel(0);
1017 	}
1018 }
1019 
getDexterityArmorClassModifier(int dexterity)1020 int EoBCoreEngine::getDexterityArmorClassModifier(int dexterity) {
1021 	static const int8 mod[] = { 5, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -4, -4, -5, -5, -5, -6, -6 };
1022 	return mod[dexterity];
1023 }
1024 
generateCharacterHitpointsByLevel(int charIndex,int levelIndex)1025 int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex) {
1026 	EoBCharacter *c = &_characters[charIndex];
1027 	int m = getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur);
1028 
1029 	int h = 0;
1030 
1031 	for (int i = 0; i < 3; i++) {
1032 		if (!(levelIndex & (1 << i)))
1033 			continue;
1034 
1035 		int d = getCharacterClassType(c->cClass, i);
1036 
1037 		if (c->level[i] <= _hpIncrPerLevel[6 + i])
1038 			h += rollDice(1, (d >= 0) ? _hpIncrPerLevel[d] : 0);
1039 		else
1040 			h += _hpIncrPerLevel[12 + i];
1041 
1042 		h += m;
1043 	}
1044 
1045 	h /= _numLevelsPerClass[c->cClass];
1046 
1047 	if (h < 1)
1048 		h = 1;
1049 
1050 	return h;
1051 }
1052 
getClassAndConstHitpointsModifier(int cclass,int constitution)1053 int EoBCoreEngine::getClassAndConstHitpointsModifier(int cclass, int constitution) {
1054 	int res = _hpConstModifiers[constitution];
1055 	// This also applies to EOB1 despite being coded differently there
1056 	if (res <= 2 || (_classModifierFlags[cclass] & 0x31))
1057 		return res;
1058 
1059 	return 2;
1060 }
1061 
getCharacterClassType(int cclass,int levelIndex)1062 int EoBCoreEngine::getCharacterClassType(int cclass, int levelIndex) {
1063 	return _characterClassType[cclass * 3 + levelIndex];
1064 }
1065 
getModifiedHpLimits(int hpModifier,int constModifier,int level,bool mode)1066 int EoBCoreEngine::getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode) {
1067 	int s = _hpIncrPerLevel[6 + hpModifier] > level ? level : _hpIncrPerLevel[6 + hpModifier];
1068 	int res = s;
1069 
1070 	if (!mode)
1071 		res *= (hpModifier >= 0 ? _hpIncrPerLevel[hpModifier] : 0);
1072 
1073 	if (level > s) {
1074 		s = level - s;
1075 		res += (s * _hpIncrPerLevel[12 + hpModifier]);
1076 	}
1077 
1078 	if (!mode || (constModifier > 0))
1079 		res += (level * constModifier);
1080 
1081 	return res;
1082 }
1083 
getCharStrength(int str,int strExt,bool twoDigitsPadding)1084 Common::String EoBCoreEngine::getCharStrength(int str, int strExt, bool twoDigitsPadding) {
1085 	if (strExt) {
1086 		if (strExt == 100)
1087 			strExt = 0;
1088 		_strenghtStr = Common::String::format(twoDigitsPadding ? "%02d/%02d" : "%d/%02d", str, strExt);
1089 	} else {
1090 		_strenghtStr = Common::String::format(twoDigitsPadding ? "%02d" : "%d", str);
1091 	}
1092 
1093 	return _strenghtStr;
1094 }
1095 
testCharacter(int16 index,int flags)1096 int EoBCoreEngine::testCharacter(int16 index, int flags) {
1097 	if (index == -1)
1098 		return 0;
1099 
1100 	EoBCharacter *c = &_characters[index];
1101 	int res = 1;
1102 
1103 	if (flags & 1)
1104 		res &= (c->flags & 1);
1105 
1106 	if (flags & 2)
1107 		res &= ((c->hitPointsCur <= -10) || (c->flags & 8)) ? 0 : 1;
1108 
1109 	if (flags & 4)
1110 		res &= ((c->hitPointsCur <= 0) || (c->flags & 8)) ? 0 : 1;
1111 
1112 	if (flags & 8)
1113 		res &= (c->flags & 12) ? 0 : 1;
1114 
1115 	if (flags & 0x20)
1116 		res &= (c->flags & 4) ? 0 : 1;
1117 
1118 	if (flags & 0x10)
1119 		res &= (c->flags & 2) ? 0 : 1;
1120 
1121 	if (flags & 0x40)
1122 		res &= (c->food <= 0) ? 0 : 1;
1123 
1124 	return res;
1125 }
1126 
getNextValidCharIndex(int curCharIndex,int searchStep)1127 int EoBCoreEngine::getNextValidCharIndex(int curCharIndex, int searchStep) {
1128 	do {
1129 		curCharIndex += searchStep;
1130 		if (curCharIndex < 0)
1131 			curCharIndex = 5;
1132 		if (curCharIndex > 5)
1133 			curCharIndex = 0;
1134 	} while (!testCharacter(curCharIndex, 1));
1135 
1136 	return curCharIndex;
1137 }
1138 
recalcArmorClass(int index)1139 void EoBCoreEngine::recalcArmorClass(int index) {
1140 	EoBCharacter *c = &_characters[index];
1141 	int acm = getDexterityArmorClassModifier(c->dexterityCur);
1142 	c->armorClass = 10 + acm;
1143 
1144 	static uint8 slot[] = { 17, 0, 1, 18 };
1145 	for (int i = 0; i < 4; i++) {
1146 		int itm = c->inventory[slot[i]];
1147 		if (!itm)
1148 			continue;
1149 
1150 		if (i == 2) {
1151 			if (!validateWeaponSlotItem(index, 1))
1152 				continue;
1153 		}
1154 
1155 		int tp = _items[itm].type;
1156 
1157 		if (!(_itemTypes[tp].allowedClasses & _classModifierFlags[c->cClass]) || (_itemTypes[tp].extraProperties & 0x7F) || (i >= 1 && i <= 2 && tp != 27 && !(_flags.gameID == GI_EOB2 && tp == 57)))
1158 			continue;
1159 
1160 		c->armorClass += _itemTypes[tp].armorClass;
1161 		c->armorClass -= _items[itm].value;
1162 	}
1163 
1164 	if (!_items[c->inventory[17]].value) {
1165 		int8 m1 = 0;
1166 		int8 m2 = 0;
1167 
1168 		if (c->inventory[25]) {
1169 			if (!(_itemTypes[_items[c->inventory[25]].type].extraProperties & 0x7F))
1170 				m1 = _items[c->inventory[25]].value;
1171 		}
1172 
1173 		if (c->inventory[26]) {
1174 			if (!(_itemTypes[_items[c->inventory[26]].type].extraProperties & 0x7F))
1175 				m2 = _items[c->inventory[26]].value;
1176 		}
1177 
1178 		c->armorClass -= MAX(m1, m2);
1179 	}
1180 
1181 	if (c->effectsRemainder[0] > 0) {
1182 		if (c->armorClass <= (acm + 6))
1183 			c->effectsRemainder[0] = 0;
1184 		else
1185 			c->armorClass = (acm + 6);
1186 	}
1187 
1188 	// shield
1189 	if ((c->effectFlags & 8) && (c->armorClass > 4))
1190 		c->armorClass = 4;
1191 
1192 	// magical vestment
1193 	if (c->effectFlags & 0x4000) {
1194 		int8 m1 = 5;
1195 
1196 		if (getClericPaladinLevel(index) > 5)
1197 			m1 += ((getClericPaladinLevel(index) - 5) / 3);
1198 
1199 		if (c->armorClass > m1)
1200 			c->armorClass = m1;
1201 	}
1202 
1203 	if (c->armorClass < -10)
1204 		c->armorClass = -10;
1205 }
1206 
validateWeaponSlotItem(int index,int slot)1207 int EoBCoreEngine::validateWeaponSlotItem(int index, int slot) {
1208 	EoBCharacter *c = &_characters[index];
1209 	int itm1 = c->inventory[0];
1210 	int r = itemUsableByCharacter(index, itm1);
1211 	int tp1 = _items[itm1].type;
1212 
1213 	if (!slot)
1214 		return (!itm1 || r) ? 1 : 0;
1215 
1216 	int itm2 = c->inventory[1];
1217 	r = itemUsableByCharacter(index, itm2);
1218 	int tp2 = _items[itm2].type;
1219 
1220 	if (itm1 && _itemTypes[tp1].requiredHands == 2)
1221 		return 0;
1222 
1223 	if (!itm2)
1224 		return 1;
1225 
1226 	int f = (_itemTypes[tp2].extraProperties & 0x7F);
1227 	if (f <= 0 || f > 3)
1228 		return r;
1229 
1230 	if (_itemTypes[tp2].requiredHands)
1231 		return 0;
1232 
1233 	return r;
1234 }
1235 
getClericPaladinLevel(int index)1236 int EoBCoreEngine::getClericPaladinLevel(int index) {
1237 	if (_castScrollSlot)
1238 		return 9;
1239 
1240 	if (index == -1)
1241 		return (_currentLevel < 7) ? 5 : 9;
1242 
1243 	int l = getCharacterLevelIndex(2, _characters[index].cClass);
1244 	if (l > -1)
1245 		return _characters[index].level[l];
1246 
1247 	l = getCharacterLevelIndex(4, _characters[index].cClass);
1248 	if (l > -1) {
1249 		if (_characters[index].level[l] > 8)
1250 			return _characters[index].level[l] - 8;
1251 	}
1252 
1253 	return 1;
1254 }
1255 
getMageLevel(int index)1256 int EoBCoreEngine::getMageLevel(int index) {
1257 	if (_castScrollSlot)
1258 		return 9;
1259 
1260 	if (index == -1)
1261 		return (_currentLevel < 7) ? 5 : 9;
1262 
1263 	int l = getCharacterLevelIndex(1, _characters[index].cClass);
1264 	return (l > -1) ? _characters[index].level[l] : 1;
1265 }
1266 
getCharacterLevelIndex(int type,int cClass)1267 int EoBCoreEngine::getCharacterLevelIndex(int type, int cClass) {
1268 	if (getCharacterClassType(cClass, 0) == type)
1269 		return 0;
1270 
1271 	if (getCharacterClassType(cClass, 1) == type)
1272 		return 1;
1273 
1274 	if (getCharacterClassType(cClass, 2) == type)
1275 		return 2;
1276 
1277 	return -1;
1278 }
1279 
countCharactersWithSpecificItems(int16 itemType,int16 itemValue)1280 int EoBCoreEngine::countCharactersWithSpecificItems(int16 itemType, int16 itemValue) {
1281 	int res = 0;
1282 	for (int i = 0; i < 6; i++) {
1283 		if (!testCharacter(i, 1))
1284 			continue;
1285 		if (checkInventoryForItem(i, itemType, itemValue) != -1)
1286 			res++;
1287 	}
1288 	return res;
1289 }
1290 
checkInventoryForItem(int character,int16 itemType,int16 itemValue)1291 int EoBCoreEngine::checkInventoryForItem(int character, int16 itemType, int16 itemValue) {
1292 	if (character < 0)
1293 		return -1;
1294 
1295 	for (int i = 0; i < 27; i++) {
1296 		uint16 inv = _characters[character].inventory[i];
1297 		if (!inv)
1298 			continue;
1299 		if (_items[inv].type != itemType && itemType != -1)
1300 			continue;
1301 		if (_items[inv].value == itemValue || itemValue == -1)
1302 			return i;
1303 	}
1304 	return -1;
1305 }
1306 
modifyCharacterHitpoints(int character,int16 points)1307 void EoBCoreEngine::modifyCharacterHitpoints(int character, int16 points) {
1308 	if (!testCharacter(character, 3))
1309 		return;
1310 
1311 	EoBCharacter *c = &_characters[character];
1312 	c->hitPointsCur += points;
1313 	if (c->hitPointsCur > c->hitPointsMax)
1314 		c->hitPointsCur = c->hitPointsMax;
1315 
1316 	gui_drawHitpoints(character);
1317 	gui_drawCharPortraitWithStats(character);
1318 }
1319 
neutralizePoison(int character)1320 void EoBCoreEngine::neutralizePoison(int character) {
1321 	_characters[character].flags &= ~2;
1322 	_characters[character].effectFlags &= ~0x2000;
1323 	deleteCharEventTimer(character, -34);
1324 	gui_drawCharPortraitWithStats(character);
1325 }
1326 
npcSequence(int npcIndex)1327 void EoBCoreEngine::npcSequence(int npcIndex) {
1328 	if (_flags.platform != Common::kPlatformSegaCD) {
1329 		_screen->loadShapeSetBitmap("OUTTAKE", 5, 3);
1330 		_screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 6, Screen::CR_NO_P_CHECK);
1331 
1332 		drawNpcScene(npcIndex);
1333 
1334 		Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT");
1335 		if (s) {
1336 			_screen->loadFileDataToPage(s, 5, 32000);
1337 		} else {
1338 			s = _res->createReadStream("TEXT.CPS");
1339 			if (s->readSint32BE() + 12 == s->size())
1340 				_screen->loadSpecialAmigaCPS("TEXT.CPS", 5, false);
1341 			else
1342 				_screen->loadBitmap("TEXT.CPS", 5, 5, 0, true);
1343 		}
1344 		delete s;
1345 
1346 		gui_drawBox(0, 121, 320, 79, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill);
1347 		_txt->setupField(9, true);
1348 		_txt->resetPageBreakString();
1349 	}
1350 
1351 	runNpcDialogue(npcIndex);
1352 
1353 	_txt->removePageBreakFlag();
1354 	gui_restorePlayField();
1355 }
1356 
initNpc(int npcIndex)1357 void EoBCoreEngine::initNpc(int npcIndex) {
1358 	EoBCharacter *c = _characters;
1359 	int i = 0;
1360 	for (; i < 6; i++) {
1361 		if (!(_characters[i].flags & 1)) {
1362 			c = &_characters[i];
1363 			break;
1364 		}
1365 	}
1366 
1367 	delete[] c->faceShape;
1368 	memcpy(c, &_npcPreset[npcIndex], sizeof(EoBCharacter));
1369 	Common::strlcpy(c->name, _npcPresetNames[npcIndex], 21);
1370 	recalcArmorClass(i);
1371 	makeFaceShapes(i);
1372 	makeNameShapes(i);
1373 
1374 	for (i = 0; i < 25; i++) {
1375 		if (!c->inventory[i])
1376 			continue;
1377 		c->inventory[i] = duplicateItem(c->inventory[i]);
1378 	}
1379 }
1380 
npcJoinDialogue(int npcIndex,int queryJoinTextId,int confirmJoinTextId,int noJoinTextId)1381 int EoBCoreEngine::npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId) {
1382 	gui_drawDialogueBox();
1383 	int r = runDialogue(queryJoinTextId, _flags.platform == Common::kPlatformSegaCD ? 3 : 2, _flags.platform == Common::kPlatformSegaCD ? 3 : -1, _yesNoStrings[0], _yesNoStrings[1], _flags.platform == Common::kPlatformSegaCD ? _yesNoStrings[2] : 0) - 1;
1384 	if (r == 0) {
1385 		if (confirmJoinTextId == -1) {
1386 			Common::String tmp = Common::String::format(_npcJoinStrings[0], _npcPresetNames[npcIndex]);
1387 			_txt->printDialogueText(tmp.c_str(), true);
1388 		} else {
1389 			_txt->printDialogueText(confirmJoinTextId, _okStrings[0]);
1390 		}
1391 
1392 		if (prepareForNewPartyMember(33, npcIndex + 1))
1393 			initNpc(npcIndex);
1394 
1395 	} else if (r == 1 && noJoinTextId != -1) {
1396 		_txt->printDialogueText(noJoinTextId, _okStrings[0]);
1397 	}
1398 
1399 	return r ^ 1;
1400 }
1401 
prepareForNewPartyMember(int16 itemType,int16 itemValue)1402 int EoBCoreEngine::prepareForNewPartyMember(int16 itemType, int16 itemValue) {
1403 	int numChars = 0;
1404 	for (int i = 0; i < 6; i++)
1405 		numChars += (_characters[i].flags & 1);
1406 
1407 	if (numChars < 6) {
1408 		deletePartyItems(itemType, itemValue);
1409 	} else {
1410 		gui_drawDialogueBox();
1411 		_screen->set16bitShadingLevel(4);
1412 		_txt->printDialogueText(_npcMaxStrings[0]);
1413 		_screen->set16bitShadingLevel(0);
1414 
1415 		if (_flags.platform == Common::kPlatformSegaCD) {
1416 			resetSkipFlag();
1417 			_allowSkip = true;
1418 			while (!(shouldQuit() || skipFlag()))
1419 				delay(20);
1420 			_allowSkip = false;
1421 			resetSkipFlag();
1422 		}
1423 
1424 		int r = runDialogue(-1, 7, -1, _characters[0].name, _characters[1].name, _characters[2].name, _characters[3].name, _characters[4].name, _characters[5].name, _abortStrings[0]) - 1;
1425 
1426 		if (r == 6)
1427 			return 0;
1428 
1429 		deletePartyItems(itemType, itemValue);
1430 		removeCharacterFromParty(r);
1431 	}
1432 
1433 	return 1;
1434 }
1435 
dropCharacter(int charIndex)1436 void EoBCoreEngine::dropCharacter(int charIndex) {
1437 	if (!testCharacter(charIndex, 1))
1438 		return;
1439 
1440 	removeCharacterFromParty(charIndex);
1441 
1442 	if (charIndex < 5)
1443 		exchangeCharacters(charIndex, testCharacter(5, 1) ? 5 : 4);
1444 
1445 	gui_processCharPortraitClick(0);
1446 	gui_setPlayFieldButtons();
1447 	setupCharacterTimers();
1448 }
1449 
removeCharacterFromParty(int charIndex)1450 void EoBCoreEngine::removeCharacterFromParty(int charIndex) {
1451 	EoBCharacter *c = &_characters[charIndex];
1452 	c->flags = 0;
1453 
1454 	for (int i = 0; i < 27; i++) {
1455 		if (i == 16 || !c->inventory[i])
1456 			continue;
1457 
1458 		setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, c->inventory[i], _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]);
1459 		c->inventory[i] = 0;
1460 	}
1461 
1462 	while (c->inventory[16])
1463 		setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, getQueuedItem(&c->inventory[16], 0, -1), _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]);
1464 
1465 	c->inventory[16] = 0;
1466 
1467 	if (_updateCharNum == charIndex)
1468 		_updateCharNum = 0;
1469 
1470 	setupCharacterTimers();
1471 }
1472 
exchangeCharacters(int charIndex1,int charIndex2)1473 void EoBCoreEngine::exchangeCharacters(int charIndex1, int charIndex2) {
1474 	EoBCharacter temp;
1475 	memcpy(&temp, &_characters[charIndex1], sizeof(EoBCharacter));
1476 	memcpy(&_characters[charIndex1], &_characters[charIndex2], sizeof(EoBCharacter));
1477 	memcpy(&_characters[charIndex2], &temp, sizeof(EoBCharacter));
1478 }
1479 
increasePartyExperience(int16 points)1480 void EoBCoreEngine::increasePartyExperience(int16 points) {
1481 	int cnt = 0;
1482 	for (int i = 0; i < 6; i++) {
1483 		if (testCharacter(i, 3))
1484 			cnt++;
1485 	}
1486 
1487 	if (cnt <= 0)
1488 		return;
1489 
1490 	points /= cnt;
1491 
1492 	for (int i = 0; i < 6; i++) {
1493 		if (!testCharacter(i, 3))
1494 			continue;
1495 		increaseCharacterExperience(i, points);
1496 	}
1497 }
1498 
increaseCharacterExperience(int charIndex,int32 points)1499 void EoBCoreEngine::increaseCharacterExperience(int charIndex, int32 points) {
1500 	int cl = _characters[charIndex].cClass;
1501 	points /= _numLevelsPerClass[cl];
1502 
1503 	for (int i = 0; i < 3; i++) {
1504 		if (getCharacterClassType(cl, i) == -1)
1505 			continue;
1506 		_characters[charIndex].experience[i] += points;
1507 
1508 		uint32 er = getRequiredExperience(cl, i, _characters[charIndex].level[i] + 1);
1509 		if (er == 0xFFFFFFFF)
1510 			continue;
1511 
1512 		if (_characters[charIndex].experience[i] >= er)
1513 			increaseCharacterLevel(charIndex, i);
1514 	}
1515 }
1516 
getRequiredExperience(int cClass,int levelIndex,int level)1517 uint32 EoBCoreEngine::getRequiredExperience(int cClass, int levelIndex, int level) {
1518 	cClass = getCharacterClassType(cClass, levelIndex);
1519 	if (cClass == -1)
1520 		return 0xFFFFFFFF;
1521 
1522 	const uint32 *tbl = _expRequirementTables[cClass];
1523 	return tbl[level - 1];
1524 }
1525 
increaseCharacterLevel(int charIndex,int levelIndex)1526 void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) {
1527 	_characters[charIndex].level[levelIndex]++;
1528 	int hpInc = generateCharacterHitpointsByLevel(charIndex, 1 << levelIndex);
1529 	_characters[charIndex].hitPointsCur += hpInc;
1530 	_characters[charIndex].hitPointsMax += hpInc;
1531 
1532 	gui_drawCharPortraitWithStats(charIndex);
1533 	_txt->printMessage(_levelGainStrings[0], -1, _characters[charIndex].name);
1534 	snd_playSoundEffect(_flags.platform == Common::kPlatformSegaCD ? 0x1017 : 0x17);
1535 }
1536 
setWeaponSlotStatus(int charIndex,int mode,int slot)1537 void EoBCoreEngine::setWeaponSlotStatus(int charIndex, int mode, int slot) {
1538 	if (mode == 0 || mode == 2)
1539 		_characters[charIndex].disabledSlots ^= (1 << slot);
1540 	else if (mode != 1)
1541 		return;
1542 
1543 	_characters[charIndex].slotStatus[slot] = 0;
1544 	gui_drawCharPortraitWithStats(charIndex);
1545 }
1546 
setupDialogueButtons(int presetfirst,int numStr,va_list & args)1547 void EoBCoreEngine::setupDialogueButtons(int presetfirst, int numStr, va_list &args) {
1548 	_dialogueNumButtons = numStr;
1549 	_dialogueHighlightedButton = 0;
1550 
1551 	Screen::FontId of = _screen->setFont((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : _screen->_currentFont);
1552 
1553 	for (int i = 0; i < numStr; i++) {
1554 		const char *s = va_arg(args, const char*);
1555 		if (s)
1556 			_dialogueButtonString[i] = s;
1557 		else
1558 			_dialogueNumButtons = numStr = i;
1559 	}
1560 
1561 	const ScreenDim *dm = screen()->_curDim;
1562 	int yOffs = (_txt->lineCount() + 1) * _screen->getFontHeight() + dm->sy + 4;
1563 
1564 	_dialogueButtonPosX = &guiSettings()->buttons.posX[presetfirst];
1565 	_dialogueButtonPosY = &guiSettings()->buttons.posY[presetfirst];
1566 	_dialogueButtonXoffs = (_flags.platform == Common::kPlatformSegaCD) ? 8 : 0;
1567 	_dialogueButtonYoffs = (_flags.platform == Common::kPlatformSegaCD) ? 160 : yOffs;
1568 
1569 	drawDialogueButtons();
1570 
1571 	_screen->setFont(of);
1572 
1573 	if (!shouldQuit())
1574 		removeInputTop();
1575 }
1576 
initDialogueSequence()1577 void EoBCoreEngine::initDialogueSequence() {
1578 	_npcSequenceSub = -1;
1579 	_txt->setWaitButtonMode(0);
1580 	_dialogueField = true;
1581 	_dialogueLastBitmap[0] = 0;
1582 
1583 	_txt->resetPageBreakString();
1584 	gui_updateControls();
1585 	//_allowSkip = true;
1586 
1587 	// WORKAROUND for bug in the original code (all platforms). Sequence sound would be terminated prematurely.
1588 	if (_flags.gameID == GI_EOB2 && _currentLevel == 2 && _currentBlock == 654)
1589 		_sound->stopAllSoundEffects();
1590 	else
1591 		snd_stopSound();
1592 
1593 	Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT");
1594 	if (s) {
1595 		_screen->loadFileDataToPage(s, 5, 32000);
1596 	} else {
1597 		s = _res->createReadStream("TEXT.CPS");
1598 		if (s->readSint32BE() + 12 == s->size())
1599 			_screen->loadSpecialAmigaCPS("TEXT.CPS", 5, false);
1600 		else
1601 			_screen->loadBitmap("TEXT.CPS", 5, 5, 0, true);
1602 	}
1603 	delete s;
1604 
1605 	_txt->setupField(9, 0);
1606 }
1607 
restoreAfterDialogueSequence()1608 void EoBCoreEngine::restoreAfterDialogueSequence() {
1609 	_txt->allowPageBreak(false);
1610 	_dialogueField = _dialogueFieldAmiga = false;
1611 
1612 	_dialogueLastBitmap[0] = 0;
1613 
1614 	gui_restorePlayField();
1615 	//_allowSkip = false;
1616 	_screen->setScreenDim(7);
1617 
1618 	if (_flags.gameID == GI_EOB2)
1619 		snd_playSoundEffect(2);
1620 
1621 	_sceneUpdateRequired = true;
1622 }
1623 
drawSequenceBitmap(const char * file,int destRect,int x1,int y1,int flags)1624 void EoBCoreEngine::drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags) {
1625 	static const uint8 frameX[] = { 1, 0 };
1626 	static const uint8 frameY[] = { 8, 0 };
1627 	static const uint8 frameW[] = { 20, 40 };
1628 	static const uint8 frameH[] = { 96, 121 };
1629 
1630 	int page = ((flags & 2) || destRect) ? 0 : 6;
1631 	int amigaPalIndex = (x1 ? 1 : 0) + (y1 ? 2 : 0) + 1;
1632 
1633 	if (scumm_stricmp(_dialogueLastBitmap, file)) {
1634 		_screen->clearPage(2);
1635 		if (!destRect) {
1636 			if (!(flags & 1)) {
1637 				_screen->loadEoBBitmap("BORDER", 0, 3, 3, 2);
1638 				if (_flags.platform == Common::kPlatformAmiga)
1639 					_screen->copyRegion(0, 0, 0, 0, 320, 122, 2, 0, Screen::CR_NO_P_CHECK);
1640 				_screen->copyRegion(0, 0, 0, 0, 184, 121, 2, page, Screen::CR_NO_P_CHECK);
1641 			} else {
1642 				_screen->copyRegion(0, 0, 0, 0, 184, 121, 0, page, Screen::CR_NO_P_CHECK);
1643 			}
1644 
1645 			if (!page)
1646 				_screen->copyRegion(0, 0, 0, 0, 184, 121, 2, 6, Screen::CR_NO_P_CHECK);
1647 		}
1648 
1649 		_screen->loadEoBBitmap(file, 0, 3, 3, 2);
1650 		strcpy(_dialogueLastBitmap, file);
1651 	}
1652 
1653 	if (_flags.platform == Common::kPlatformAmiga) {
1654 		int cp = _screen->setCurPage(0);
1655 		if (!_dialogueFieldAmiga)
1656 			gui_drawDialogueBox();
1657 		_screen->drawClippedLine(0, 120, 319, 120, 9);
1658 		_screen->drawClippedLine(0, 121, 319, 121, guiSettings()->colors.fill);
1659 		_screen->setPagePixel(0, 319, 121, 9);
1660 		_screen->setCurPage(cp);
1661 		_screen->setDualPalettes(_screen->getPalette(amigaPalIndex), _screen->getPalette(7));
1662 		_dialogueFieldAmiga = true;
1663 	}
1664 
1665 	if (flags & 2)
1666 		_screen->crossFadeRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page);
1667 	else
1668 		_screen->copyRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page, Screen::CR_NO_P_CHECK);
1669 
1670 	if (page == 6)
1671 		_screen->copyRegion(0, 0, 0, 0, 184, (_flags.platform == Common::kPlatformAmiga) ? 110 : 121, 6, 0, Screen::CR_NO_P_CHECK);
1672 
1673 	_screen->updateScreen();
1674 }
1675 
runDialogue(int dialogueTextId,int numStr,int loopButtonId,...)1676 int EoBCoreEngine::runDialogue(int dialogueTextId, int numStr, int loopButtonId, ...) {
1677 	int res;
1678 	do {
1679 		res = 0;
1680 		if (dialogueTextId != -1)
1681 			txt()->printDialogueText(dialogueTextId, 0);
1682 
1683 		va_list args;
1684 		va_start(args, loopButtonId);
1685 		if (_flags.platform == Common::kPlatformSegaCD && numStr > 3)
1686 			setupDialogueButtons(numStr == 4 ? 14 : 5, numStr, args);
1687 		else if (numStr > 2)
1688 			setupDialogueButtons(2, numStr, args);
1689 		else
1690 			setupDialogueButtons(0, numStr, args);
1691 		va_end(args);
1692 
1693 		while (res == 0 && !shouldQuit())
1694 			res = processDialogue();
1695 	} while (res == loopButtonId && !shouldQuit());
1696 
1697 	if (_flags.platform != Common::kPlatformSegaCD)
1698 		gui_drawDialogueBox();
1699 
1700 	return res;
1701 }
1702 
restParty_displayWarning(const char * str)1703 void EoBCoreEngine::restParty_displayWarning(const char *str) {
1704 	int od = _screen->curDimIndex();
1705 	_screen->setScreenDim(7);
1706 	Screen::FontId of = _screen->setFont(_conFont);
1707 	_screen->setCurPage(0);
1708 
1709 	_txt->printMessage(Common::String::format(_flags.platform == Common::kPlatformSegaCD ? "%s" : "\r%s\r", str).c_str());
1710 
1711 	_screen->setFont(of);
1712 	_screen->setScreenDim(od);
1713 }
1714 
restParty_updateMonsters()1715 bool EoBCoreEngine::restParty_updateMonsters() {
1716 	bool sfxEnabled = _sound->sfxEnabled();
1717 	bool musicEnabled = _sound->musicEnabled();
1718 	_sound->enableSFX(false);
1719 	_sound->enableMusic(false);
1720 
1721 	for (int i = 0; i < 5; i++) {
1722 		_partyResting = true;
1723 
1724 		// The original SegaCD code does not update the monsters during resting. I presume to
1725 		// avoid graphical issues which I have (apparently successfully) tried to fix. At first
1726 		// I had disabled the monster updated just like the original, but it annoyed me, since
1727 		// it felt like a step backwards...
1728 		Screen::FontId of = _screen->setFont(_conFont);
1729 		int od = _screen->curDimIndex();
1730 		_screen->setScreenDim(7);
1731 		updateMonsters(0);
1732 		updateMonsters(1);
1733 		timerProcessFlyingObjects(0);
1734 		_screen->setScreenDim(od);
1735 		_screen->setFont(of);
1736 
1737 		_partyResting = false;
1738 
1739 		for (int ii = 0; ii < 30; ii++) {
1740 			if (_monsters[ii].mode == 8)
1741 				continue;
1742 			if (getBlockDistance(_currentBlock, _monsters[ii].block) >= 2)
1743 				continue;
1744 
1745 			restParty_displayWarning(_menuStringsRest4[0]);
1746 			_sound->enableSFX(sfxEnabled);
1747 			_sound->enableMusic(musicEnabled);
1748 			return true;
1749 		}
1750 	}
1751 
1752 	_sound->enableSFX(sfxEnabled);
1753 	_sound->enableMusic(musicEnabled);
1754 	return false;
1755 }
1756 
restParty_getCharacterWithLowestHp()1757 int EoBCoreEngine::restParty_getCharacterWithLowestHp() {
1758 	int lhp = 900;
1759 	int res = -1;
1760 
1761 	for (int i = 0; i < 6; i++) {
1762 		if (!testCharacter(i, 3))
1763 			continue;
1764 		if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax)
1765 			continue;
1766 		if (_characters[i].hitPointsCur < lhp) {
1767 			lhp = _characters[i].hitPointsCur;
1768 			res = i;
1769 		}
1770 	}
1771 
1772 	return res + 1;
1773 }
1774 
restParty_checkHealSpells(int charIndex)1775 bool EoBCoreEngine::restParty_checkHealSpells(int charIndex) {
1776 	static const uint8 eob1healSpells[] = { 2, 15, 20 };
1777 	static const uint8 eob2healSpells[] = { 3, 16, 20 };
1778 	const uint8 *spells = _flags.gameID == GI_EOB1 ? eob1healSpells : eob2healSpells;
1779 	const int8 *list = _characters[charIndex].clericSpells;
1780 
1781 	for (int i = 0; i < 80; i++) {
1782 		int s = list[i] < 0 ? -list[i] : list[i];
1783 		if (s == spells[0] || s == spells[1] || s == spells[2])
1784 			return true;
1785 	}
1786 
1787 	return false;
1788 }
1789 
restParty_checkSpellsToLearn()1790 bool EoBCoreEngine::restParty_checkSpellsToLearn() {
1791 	for (int i = 0; i < 6; i++) {
1792 		if (!testCharacter(i, 0x43))
1793 			continue;
1794 
1795 		if ((getCharacterLevelIndex(2, _characters[i].cClass) != -1 || getCharacterLevelIndex(4, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 30, -1) != -1)) {
1796 			for (int ii = 0; ii < 80; ii++) {
1797 				if (_characters[i].clericSpells[ii] < 0)
1798 					return true;
1799 			}
1800 		}
1801 
1802 		if ((getCharacterLevelIndex(1, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 29, -1) != -1)) {
1803 			for (int ii = 0; ii < 80; ii++) {
1804 				if (_characters[i].mageSpells[ii] < 0)
1805 					return true;
1806 			}
1807 		}
1808 	}
1809 
1810 	return false;
1811 }
1812 
restParty_extraAbortCondition()1813 bool EoBCoreEngine::restParty_extraAbortCondition() {
1814 	return false;
1815 }
1816 
delay(uint32 millis,bool,bool)1817 void EoBCoreEngine::delay(uint32 millis, bool, bool) {
1818 	while (millis && !shouldQuit() && !(_allowSkip && skipFlag())) {
1819 		updateInput();
1820 		uint32 step = MIN<uint32>(millis, (_tickLength / 5));
1821 		_system->delayMillis(step);
1822 		millis -= step;
1823 	}
1824 }
1825 
pauseEngineIntern(bool pause)1826 void EoBCoreEngine::pauseEngineIntern(bool pause) {
1827 	KyraEngine_v1::pauseEngineIntern(pause);
1828 	seq_segaPausePlayer(pause);
1829 }
1830 
displayParchment(int id)1831 void EoBCoreEngine::displayParchment(int id) {
1832 	_txt->setWaitButtonMode(1);
1833 	_txt->resetPageBreakString();
1834 	gui_updateControls();
1835 
1836 	if (id >= 0) {
1837 		// display text
1838 		Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT");
1839 		if (s) {
1840 			_screen->loadFileDataToPage(s, 5, 32000);
1841 		} else {
1842 			s = _res->createReadStream("TEXT.CPS");
1843 			if (s->readSint32BE() + 12 == s->size())
1844 				_screen->loadSpecialAmigaCPS("TEXT.CPS", 5, false);
1845 			else
1846 				_screen->loadBitmap("TEXT.CPS", 5, 5, 0, true);
1847 		}
1848 		delete s;
1849 		_screen->set16bitShadingLevel(4);
1850 		gui_drawBox(0, 0, 176, 175, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill);
1851 		_screen->set16bitShadingLevel(0);
1852 		_txt->setupField(12, 1);
1853 		if (_flags.gameID == GI_EOB2)
1854 			id++;
1855 		_txt->printDialogueText(id, _okStrings[0]);
1856 
1857 	} else {
1858 		// display bitmap
1859 		static const uint8 x[] = { 0, 20, 0 };
1860 		static const uint8 y[] = { 0, 0, 96 };
1861 		id = -id - 1;
1862 
1863 		if (_flags.platform == Common::kPlatformAmiga)
1864 			_txt->setupField(9, 0);
1865 
1866 		drawSequenceBitmap("MAP", 0, x[id], y[id], 0);
1867 
1868 		removeInputTop();
1869 		while (!shouldQuit()) {
1870 			delay(_tickLength);
1871 			if (checkInput(0, false, 0) & 0xFF)
1872 				break;
1873 			removeInputTop();
1874 		}
1875 		removeInputTop();
1876 	}
1877 
1878 	restoreAfterDialogueSequence();
1879 }
1880 
countResurrectionCandidates()1881 int EoBCoreEngine::countResurrectionCandidates() {
1882 	_rrCount = 0;
1883 	memset(_rrNames, 0, 10 * sizeof(const char *));
1884 
1885 	for (int i = 0; i < 6; i++) {
1886 		if (!testCharacter(i, 1))
1887 			continue;
1888 		if (_characters[i].hitPointsCur != -10)
1889 			continue;
1890 
1891 		_rrNames[_rrCount] = _characters[i].name;
1892 		_rrId[_rrCount++] = i;
1893 	}
1894 
1895 	for (int i = 0; i < 6; i++) {
1896 		if (!testCharacter(i, 1))
1897 			continue;
1898 
1899 		for (int ii = 0; ii < 27; ii++) {
1900 			uint16 inv = _characters[i].inventory[ii];
1901 			if (!inv)
1902 				continue;
1903 
1904 			if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[inv].type].extraProperties & 0x7F) != 8)) || (_flags.gameID == GI_EOB2 && _items[inv].type != 33))
1905 				continue;
1906 
1907 			_rrNames[_rrCount] = _npcPresetNames[_items[inv].value - 1];
1908 			_rrId[_rrCount++] = -_items[inv].value;
1909 		}
1910 	}
1911 
1912 	if (_itemInHand > 0) {
1913 		if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[_itemInHand].type].extraProperties & 0x7F) == 8)) || (_flags.gameID == GI_EOB2 && _items[_itemInHand].type == 33)) {
1914 			_rrNames[_rrCount] = _npcPresetNames[_items[_itemInHand].value - 1];
1915 			_rrId[_rrCount++] = -_items[_itemInHand].value;
1916 		}
1917 	}
1918 
1919 	return _rrCount;
1920 }
1921 
seq_portal()1922 void EoBCoreEngine::seq_portal() {
1923 	const uint8 **shapes = makePortalShapes();
1924 	assert(shapes);
1925 
1926 	_screen->copyRegion(24, 0, 24, 0, 144, 104, 2, 5, Screen::CR_NO_P_CHECK);
1927 	_screen->copyRegion(24, 0, 24, 0, 144, 104, 0, 2, Screen::CR_NO_P_CHECK);
1928 	_screen->drawShape(2, shapes[11], 28, 9, 0);
1929 	_screen->drawShape(2, shapes[1], 34, 28, 0);
1930 	_screen->drawShape(2, shapes[6], 120, 28, 0);
1931 	_screen->drawShape(2, shapes[0], 56, 27, 0);
1932 
1933 	if (_flags.platform == Common::kPlatformSegaCD) {
1934 		snd_playSoundEffect(19);
1935 		_screen->copyRegion(24, 0, 24, 0, 144, 104, 2, 0, Screen::CR_NO_P_CHECK);
1936 		_screen->updateScreen();
1937 	} else {
1938 		snd_playSoundEffect(33);
1939 		snd_playSoundEffect(19);
1940 		_screen->crossFadeRegion(24, 0, 24, 0, 144, 104, 2, 0);
1941 		delay(30 * _tickLength);
1942 	}
1943 
1944 	_screen->copyRegion(24, 0, 24, 0, 144, 104, 5, 2, Screen::CR_NO_P_CHECK);
1945 
1946 	for (const int8 *pos = _portalSeq; *pos > -1 && !shouldQuit();) {
1947 		int s = *pos++;
1948 		_screen->drawShape(0, shapes[11 + s], 28, 9, 0);
1949 		_screen->drawShape(0, shapes[1 + s], 34, 28, 0);
1950 		_screen->drawShape(0, shapes[6 + s], 120, 28, 0);
1951 
1952 		if (_flags.platform != Common::kPlatformSegaCD) {
1953 			if ((s == 1) && (pos >= _portalSeq + 3)) {
1954 				if (*(pos - 3) == 0) {
1955 					snd_playSoundEffect(24);
1956 					snd_playSoundEffect(86);
1957 				}
1958 			}
1959 		}
1960 
1961 		s = *pos++;
1962 		if (s == 0) {
1963 			_screen->drawShape(0, shapes[0], 56, 27, 0);
1964 		} else {
1965 			s--;
1966 			_screen->copyRegion((s % 5) << 6, s / 5 * 77, 56, 27, 64, 77, 2, 0, Screen::CR_NO_P_CHECK);
1967 		}
1968 
1969 		if (_flags.platform != Common::kPlatformSegaCD) {
1970 			if (s == 1)
1971 				snd_playSoundEffect(31);
1972 			else if (s == 3) {
1973 				if (*(pos - 2) == 3)
1974 					snd_playSoundEffect(90);
1975 			}
1976 		}
1977 
1978 		_screen->updateScreen();
1979 		delay(2 * _tickLength);
1980 	}
1981 
1982 	for (int i = 0; i < 16; i++)
1983 		delete[] shapes[i];
1984 	delete[] shapes;
1985 }
1986 
makePortalShapes()1987 const uint8 **EoBCoreEngine::makePortalShapes() {
1988 	const uint8 **shapes = new const uint8*[16];
1989 	_screen->loadShapeSetBitmap("PORTALA", 5, 3);
1990 
1991 	for (int i = 0; i < 5; i++) {
1992 		shapes[1 + i] = _screen->encodeShape(i * 3, 0, 3, 75, false, _cgaMappingDefault);
1993 		shapes[6 + i] = _screen->encodeShape(i * 3, 80, 3, 75, false, _cgaMappingDefault);
1994 		shapes[11 + i] = _screen->encodeShape(15, i * 18, 15, 18, false, _cgaMappingDefault);
1995 	}
1996 
1997 	shapes[0] = _screen->encodeShape(30, 0, 8, 77, false, _cgaMappingDefault);
1998 	_screen->loadEoBBitmap("PORTALB", _cgaMappingDefault, 5, 3, 2);
1999 
2000 	return shapes;
2001 }
2002 
checkPassword()2003 bool EoBCoreEngine::checkPassword() {
2004 	char answ[20];
2005 	Screen::FontId of = _screen->setFont(Screen::FID_8_FNT);
2006 	_screen->copyPage(0, Screen_EoB::kCheckPwBackupPage);
2007 
2008 	_screen->setScreenDim(13);
2009 	gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1);
2010 	gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill);
2011 	_screen->modifyScreenDim(13, _screen->_curDim->sx + 1, _screen->_curDim->sy + 2, _screen->_curDim->w - 2, _screen->_curDim->h - 16);
2012 
2013 	for (int i = 0; i < 3; i++) {
2014 		_screen->fillRect(_screen->_curDim->sx << 3, _screen->_curDim->sy, ((_screen->_curDim->sx + _screen->_curDim->w) << 3) - 1, (_screen->_curDim->sy + _screen->_curDim->h) - 1, guiSettings()->colors.fill);
2015 		int c = rollDice(1, _mnNumWord - 1, -1);
2016 		const uint8 *shp = (_mnDef[c << 2] < _numLargeItemShapes) ? _largeItemShapes[_mnDef[c << 2]] : (_mnDef[c << 2] < 15 ? 0 : _smallItemShapes[_mnDef[c << 2] - 15]);
2017 		assert(shp);
2018 		_screen->drawShape(0, shp, 100, 2, 13);
2019 		_screen->printShadedText(Common::String::format(_mnPrompt[0], _mnDef[(c << 2) + 1], _mnDef[(c << 2) + 2]).c_str(), (_screen->_curDim->sx + 1) << 3, _screen->_curDim->sy, guiSettings()->colors.guiColorWhite, guiSettings()->colors.fill, guiSettings()->colors.guiColorBlack);
2020 		memset(answ, 0, 20);
2021 		gui_drawBox(76, 100, 133, 14, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1);
2022 		gui_drawBox(77, 101, 131, 12, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1);
2023 		if (_gui->getTextInput(answ, 10, 103, 15, guiSettings()->colors.guiColorWhite, guiSettings()->colors.fill, guiSettings()->colors.guiColorDarkRed) < 0)
2024 			i = 3;
2025 		if (!scumm_stricmp(_mnWord[c], answ))
2026 			break;
2027 		else if (i == 2)
2028 			return false;
2029 	}
2030 
2031 	_screen->modifyScreenDim(13, _screen->_curDim->sx - 1, _screen->_curDim->sy - 2, _screen->_curDim->w + 2, _screen->_curDim->h + 16);
2032 	_screen->setFont(of);
2033 	_screen->copyPage(Screen_EoB::kCheckPwBackupPage, 0);
2034 	return true;
2035 }
2036 
convertAsciiToSjis(Common::String str)2037 Common::String EoBCoreEngine::convertAsciiToSjis(Common::String str) {
2038 	if (_flags.platform != Common::kPlatformFMTowns)
2039 		return str;
2040 
2041 	Common::String n;
2042 	const char *src = str.c_str();
2043 	int pos = 0;
2044 	for (uint32 i = 0; i < str.size(); ++i) {
2045 		if (src[i] & 0x80) {
2046 			n.insertChar(src[i++], pos++);
2047 			n.insertChar(src[i], pos++);
2048 		} else if (src[i] >= 32 && src[i] <= 64) {
2049 			n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2], pos++);
2050 			n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2 + 1], pos++);
2051 		} else if ((src[i] >= 97 && src[i] <= 122) || (src[i] >= 65 && src[i] <= 90)) {
2052 			char c = (src[i] >= 97) ? src[i] - 97 : src[i] - 65;
2053 			n.insertChar(_ascii2SjisTables2[0][c * 2], pos++);
2054 			n.insertChar(_ascii2SjisTables2[0][c * 2 + 1], pos++);
2055 		}
2056 	}
2057 
2058 	return n;
2059 }
2060 
useSlotWeapon(int charIndex,int slotIndex,Item item)2061 void EoBCoreEngine::useSlotWeapon(int charIndex, int slotIndex, Item item) {
2062 	EoBCharacter *c = &_characters[charIndex];
2063 	int tp = item ? _items[item].type : 0;
2064 
2065 	if (c->effectFlags & 0x40)
2066 		removeCharacterEffect(_flags.gameID == GI_EOB1 ? 8 : 10, charIndex, 1); // remove invisibility effect
2067 
2068 	int ep = _itemTypes[tp].extraProperties & 0x7F;
2069 	int8 inflict = 0;
2070 
2071 	if (ep == 1) {
2072 		inflict = closeDistanceAttack(charIndex, item);
2073 		if (!inflict)
2074 			inflict = -1;
2075 		snd_playSoundEffect(32);
2076 		playStrikeAnimation(inflict > 0 ? (_monsters[_dstMonsterIndex].pos == 4 ? 4 : _dscItemPosIndex[(_currentDirection << 2) | (_monsters[_dstMonsterIndex].pos & 3)]) : 4, item);
2077 	} else if (ep == 2) {
2078 		inflict = thrownAttack(charIndex, slotIndex, item);
2079 	} else if (ep == 3) {
2080 		inflict = projectileWeaponAttack(charIndex, item);
2081 		gui_drawCharPortraitWithStats(charIndex);
2082 	}
2083 
2084 	if (inflict > 0) {
2085 		if (_items[item].flags & 8) {
2086 			c->hitPointsCur += inflict;
2087 			gui_drawCharPortraitWithStats(charIndex);
2088 		}
2089 
2090 		if (_items[item].flags & 0x10)
2091 			c->inventory[slotIndex] = 0;
2092 
2093 		inflictMonsterDamage(&_monsters[_dstMonsterIndex], inflict, true);
2094 	}
2095 
2096 	c->disabledSlots ^= (1 << slotIndex);
2097 	c->slotStatus[slotIndex] = inflict;
2098 
2099 	gui_drawCharPortraitWithStats(charIndex);
2100 	setCharEventTimer(charIndex, 18, inflict >= -2 ? slotIndex + 2 : slotIndex, 1);
2101 }
2102 
closeDistanceAttack(int charIndex,Item item)2103 int EoBCoreEngine::closeDistanceAttack(int charIndex, Item item) {
2104 	if (charIndex > 1)
2105 		return -3;
2106 
2107 	uint16 d = calcNewBlockPosition(_currentBlock, _currentDirection);
2108 	int r = getClosestMonster(charIndex, d);
2109 
2110 	if (r == -1) {
2111 		uint8 w = _specialWallTypes[_levelBlockProperties[d].walls[_sceneDrawVarDown]];
2112 		if (w == 0xFF) {
2113 			if (_flags.gameID == GI_EOB1) {
2114 				_levelBlockProperties[d].walls[_sceneDrawVarDown]++;
2115 				_levelBlockProperties[d].walls[_sceneDrawVarDown ^ 2]++;
2116 
2117 			} else {
2118 				for (int i = 0; i < 4; i++) {
2119 					if (_specialWallTypes[_levelBlockProperties[d].walls[i]] == 0xFF)
2120 						_levelBlockProperties[d].walls[i]++;
2121 				}
2122 			}
2123 			_sceneUpdateRequired = true;
2124 
2125 		} else if ((_flags.gameID == GI_EOB1) || (_flags.gameID == GI_EOB2 && w != 8 && w != 9)) {
2126 			return -1;
2127 		}
2128 
2129 		return (_flags.gameID == GI_EOB2 && ((_itemTypes[_items[item].type].allowedClasses & 4) || !item)) ? -5 : -2;
2130 
2131 	} else {
2132 		if (_monsters[r].flags & 0x20) {
2133 			killMonster(&_monsters[r], 1);
2134 			_txt->printMessage(_monsterDustStrings[0]);
2135 			return -2;
2136 		}
2137 
2138 		if (!characterAttackHitTest(charIndex, r, item, 1))
2139 			return -1;
2140 
2141 		uint16 flg = 0x100;
2142 
2143 		if (isMagicEffectItem(item))
2144 			flg |= 1;
2145 
2146 		_dstMonsterIndex = r;
2147 		return calcMonsterDamage(&_monsters[r], charIndex, item, 1, flg, 5, 3);
2148 	}
2149 
2150 	return 0;
2151 }
2152 
thrownAttack(int charIndex,int slotIndex,Item item)2153 int EoBCoreEngine::thrownAttack(int charIndex, int slotIndex, Item item) {
2154 	int d = charIndex > 3 ? charIndex - 2 : charIndex;
2155 	if (!launchObject(charIndex, item, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + d], _currentDirection, _items[item].type))
2156 		return 0;
2157 
2158 	snd_playSoundEffect(11);
2159 	_characters[charIndex].inventory[slotIndex] = 0;
2160 	reloadWeaponSlot(charIndex, slotIndex, -1, 0);
2161 	_sceneUpdateRequired = true;
2162 	return 0;
2163 }
2164 
projectileWeaponAttack(int charIndex,Item item)2165 int EoBCoreEngine::projectileWeaponAttack(int charIndex, Item item) {
2166 	int tp = _items[item].type;
2167 
2168 	if (_flags.gameID == GI_EOB1)
2169 		assert(tp >= 7);
2170 
2171 	int t = _projectileWeaponAmmoTypes[_flags.gameID == GI_EOB1 ? tp - 7 : tp];
2172 	Item ammoItem = 0;
2173 
2174 	if (t == 16) {
2175 		if (_characters[charIndex].inventory[0] && _items[_characters[charIndex].inventory[0]].type == 16)
2176 			SWAP(ammoItem, _characters[charIndex].inventory[0]);
2177 		else if (_characters[charIndex].inventory[1] && _items[_characters[charIndex].inventory[1]].type == 16)
2178 			SWAP(ammoItem, _characters[charIndex].inventory[1]);
2179 		else if (_characters[charIndex].inventory[16])
2180 			ammoItem = getQueuedItem(&_characters[charIndex].inventory[16], 0, -1);
2181 
2182 	} else {
2183 		for (int i = 0; i < 27; i++) {
2184 			if (_items[_characters[charIndex].inventory[i]].type == t) {
2185 				SWAP(ammoItem, _characters[charIndex].inventory[i]);
2186 				if (i < 2)
2187 					gui_drawCharPortraitWithStats(charIndex);
2188 				break;
2189 			}
2190 		}
2191 	}
2192 
2193 	if (!ammoItem)
2194 		return -4;
2195 
2196 	int c = charIndex;
2197 	if (c > 3)
2198 		c -= 2;
2199 
2200 	if (launchObject(charIndex, ammoItem, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + c], _currentDirection, tp)) {
2201 		snd_playSoundEffect(tp == 7 ? 26 : 11);
2202 		_sceneUpdateRequired = true;
2203 	}
2204 
2205 	return 0;
2206 }
2207 
inflictMonsterDamage(EoBMonsterInPlay * m,int damage,bool giveExperience)2208 void EoBCoreEngine::inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience) {
2209 	m->hitPointsCur -= damage;
2210 	m->flags = (m->flags & 0xF7) | 1;
2211 
2212 	if (_monsterProps[m->type].capsFlags & 0x2000) {
2213 		explodeMonster(m);
2214 		checkSceneUpdateNeed(m->block);
2215 		m->hitPointsCur = 0;
2216 	} else {
2217 		if (checkSceneUpdateNeed(m->block)) {
2218 			m->flags |= 2;
2219 			if (_preventMonsterFlash)
2220 				return;
2221 			flashMonsterShape(m);
2222 		}
2223 	}
2224 
2225 	if (m->hitPointsCur <= 0) {
2226 		if (_flags.platform == Common::kPlatformSegaCD)
2227 			snd_playSoundEffect(0x1082);
2228 		killMonster(m, giveExperience);
2229 	} else if (getBlockDistance(m->block, _currentBlock) < 4) {
2230 		m->dest = _currentBlock;
2231 	}
2232 }
2233 
calcAndInflictMonsterDamage(EoBMonsterInPlay * m,int times,int pips,int offs,int flags,int savingThrowType,int savingThrowEffect)2234 void EoBCoreEngine::calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) {
2235 	int dmg = calcMonsterDamage(m, times, pips, offs, flags, savingThrowType, savingThrowEffect);
2236 	if (dmg > 0)
2237 		inflictMonsterDamage(m, dmg, flags & 0x800 ? true : false);
2238 }
2239 
calcAndInflictCharacterDamage(int charIndex,int times,int itemOrPips,int useStrModifierOrBase,int flags,int savingThrowType,int savingThrowEffect)2240 void EoBCoreEngine::calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) {
2241 	int dmg = calcCharacterDamage(charIndex, times, itemOrPips, useStrModifierOrBase, flags, savingThrowType, savingThrowEffect);
2242 	if (dmg)
2243 		inflictCharacterDamage(charIndex, dmg);
2244 }
2245 
calcCharacterDamage(int charIndex,int times,int itemOrPips,int useStrModifierOrBase,int flags,int savingThrowType,int savingThrowEffect)2246 int EoBCoreEngine::calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) {
2247 	int s = (flags & 0x100) ? calcDamageModifers(times, 0, itemOrPips, _items[itemOrPips].type, useStrModifierOrBase) : rollDice(times, itemOrPips, useStrModifierOrBase);
2248 	EoBCharacter *c = &_characters[charIndex];
2249 
2250 	if (savingThrowType != 5) {
2251 		if (trySavingThrow(c, _charClassModifier[c->cClass], c->level[0], savingThrowType, c->raceSex >> 1 /*fix bug in original code by adding a right shift*/))
2252 			s = savingThrowReduceDamage(savingThrowEffect, s);
2253 	}
2254 
2255 	if ((flags & 0x110) == 0x110) {
2256 		if (!calcDamageCheckItemType(_items[itemOrPips].type))
2257 			s = 1;
2258 	}
2259 
2260 	if (flags & 4) {
2261 		if (checkInventoryForRings(charIndex, 3))
2262 			s = 0;
2263 	}
2264 
2265 	if (flags & 0x400) {
2266 		if (c->effectFlags & 0x2000)
2267 			s = 0;
2268 		else
2269 			_txt->printMessage(_characterStatusStrings8[0], -1, c->name);
2270 	}
2271 
2272 	return s;
2273 }
2274 
inflictCharacterDamage(int charIndex,int damage)2275 void EoBCoreEngine::inflictCharacterDamage(int charIndex, int damage) {
2276 	EoBCharacter *c = &_characters[charIndex];
2277 	if (!testCharacter(charIndex, 3))
2278 		return;
2279 
2280 	if (c->effectsRemainder[3])
2281 		c->effectsRemainder[3] = (damage < c->effectsRemainder[3]) ? (c->effectsRemainder[3] - damage) : 0;
2282 
2283 	c->hitPointsCur -= damage;
2284 	c->damageTaken = damage;
2285 
2286 	if (c->hitPointsCur > -10) {
2287 		snd_playSoundEffect(21);
2288 		if (_flags.platform == Common::kPlatformSegaCD)
2289 			_sceneShakeCountdown = c->gfxUpdateCountdown = 32;
2290 	} else {
2291 		c->hitPointsCur = -10;
2292 		c->flags &= 1;
2293 		c->food = 0;
2294 		removeAllCharacterEffects(charIndex);
2295 		snd_playSoundEffect(_flags.platform == Common::kPlatformSegaCD ? 0x8001 + (c->raceSex & 1) : 22);
2296 	}
2297 
2298 	if (c->effectsRemainder[0]) {
2299 		c->effectsRemainder[0] = (damage < c->effectsRemainder[0]) ? (c->effectsRemainder[0] - damage) : 0;
2300 		if (!c->effectsRemainder[0])
2301 			removeCharacterEffect(1, charIndex, 1);
2302 	}
2303 
2304 	if (_currentControlMode)
2305 		gui_drawFaceShape(charIndex);
2306 	else
2307 		gui_drawCharPortraitWithStats(charIndex);
2308 
2309 	if (c->hitPointsCur <= 0 && _updateFlags == 1 && charIndex == _openBookChar) {
2310 		Button b;
2311 		clickedSpellbookAbort(&b);
2312 	}
2313 
2314 	setCharEventTimer(charIndex, 18, 6, 1);
2315 }
2316 
characterAttackHitTest(int charIndex,int monsterIndex,int item,int attackType)2317 bool EoBCoreEngine::characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType) {
2318 	if (charIndex < 0)
2319 		return true;
2320 
2321 	int p = item ? (_flags.gameID == GI_EOB1 ? _items[item].type : (_itemTypes[_items[item].type].extraProperties & 0x7F)) : 0;
2322 
2323 	if (_monsters[monsterIndex].flags & 0x20)
2324 		return true;// EOB 2 only ?
2325 
2326 	int t = _monsters[monsterIndex].type;
2327 	int d = (p < 1 || p > 3) ? 0 : _items[item].value;
2328 
2329 	if (_flags.gameID == GI_EOB2) {
2330 		if ((p > 0 && p < 4) || !item) {
2331 			if (((_monsterProps[t].immunityFlags & 0x200) && (d <= 0)) || ((_monsterProps[t].immunityFlags & 0x1000) && (d <= 1)))
2332 				return false;
2333 		}
2334 	}
2335 
2336 	d += (attackType ? getStrHitChanceModifier(charIndex) : getDexHitChanceModifier(charIndex));
2337 
2338 	int m = getMonsterAcHitChanceModifier(charIndex, _monsterProps[t].armorClass) - d;
2339 	int s = rollDice(1, 20);
2340 
2341 	_monsters[monsterIndex].flags |= 1;
2342 
2343 	if (_flags.gameID == GI_EOB1) {
2344 		if (_partyEffectFlags & 0x30)
2345 			s++;
2346 		if (_characters[charIndex].effectFlags & 0x40)
2347 			s++;
2348 	} else if ((_partyEffectFlags & 0x8400) || (_characters[charIndex].effectFlags & 0x1000)) {
2349 		s++;
2350 	}
2351 
2352 	s = CLIP(s, 1, 20);
2353 
2354 	return s >= m;
2355 }
2356 
monsterAttackHitTest(EoBMonsterInPlay * m,int charIndex)2357 bool EoBCoreEngine::monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex) {
2358 	int tp = m->type;
2359 	EoBMonsterProperty *p = &_monsterProps[tp];
2360 
2361 	int r = rollDice(1, 20);
2362 	if (r != 20) {
2363 		// Prot from evil
2364 		if (_characters[charIndex].effectFlags & 0x800)
2365 			r -= 2;
2366 		// blur
2367 		if (_characters[charIndex].effectFlags & 0x10)
2368 			r -= 2;
2369 		// prayer
2370 		if (_partyEffectFlags & 0x8000)
2371 			r--;
2372 	}
2373 
2374 	return ((r == 20) || (r >= (p->hitChance - _characters[charIndex].armorClass)));
2375 }
2376 
flyingObjectMonsterHit(EoBFlyingObject * fo,int monsterIndex)2377 bool EoBCoreEngine::flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex) {
2378 	if (fo->attackerId != -1) {
2379 		if (!characterAttackHitTest(fo->attackerId, monsterIndex, fo->item, 0))
2380 			return false;
2381 	}
2382 	calcAndInflictMonsterDamage(&_monsters[monsterIndex], fo->attackerId, fo->item, 0, (fo->attackerId == -1) ? 0x110 : 0x910, 5, 3);
2383 	return true;
2384 }
2385 
flyingObjectPartyHit(EoBFlyingObject * fo)2386 bool EoBCoreEngine::flyingObjectPartyHit(EoBFlyingObject *fo) {
2387 	int ps = _dscItemPosIndex[(_currentDirection << 2) + (_items[fo->item].pos & 3)];
2388 	bool res = false;
2389 
2390 	bool b = ((_currentDirection == fo->direction || _currentDirection == (fo->direction ^ 2)) && ps > 2);
2391 	int s = ps << 1;
2392 	if (ps > 2)
2393 		s += rollDice(1, 2, -1);
2394 
2395 	static const int8 charId[] = { 0, -1, 1, -1, 2, 4, 3, 5 };
2396 
2397 	for (int i = 0; i < 2; i++) {
2398 		int c = charId[s];
2399 		s ^= 1;
2400 		if (!testCharacter(c, 3))
2401 			continue;
2402 		calcAndInflictCharacterDamage(c, -1, fo->item, 0, 0x110, 5, 3);
2403 		res = true;
2404 		if (ps < 2 || b == 0)
2405 			break;
2406 	}
2407 
2408 	return res;
2409 }
2410 
monsterCloseAttack(EoBMonsterInPlay * m)2411 void EoBCoreEngine::monsterCloseAttack(EoBMonsterInPlay *m) {
2412 	int first = _monsterCloseAttDstTable1[(_currentDirection << 2) + m->dir] * 12;
2413 	int v = (m->pos == 4) ? rollDice(1, 2, -1) : _monsterCloseAttChkTable2[(m->dir << 2) + m->pos];
2414 	if (!v)
2415 		first += 6;
2416 
2417 	int last = first + 6;
2418 	for (int i = first; i < last; i++) {
2419 		int c = _monsterCloseAttDstTable2[i];
2420 		if (!testCharacter(c, 3))
2421 			continue;
2422 
2423 		// Character Invisibility
2424 		if ((_characters[c].effectFlags & 0x140) && (rollDice(1, 20) >= 5))
2425 			continue;
2426 
2427 		int dmg = 0;
2428 		for (int ii = 0; ii < _monsterProps[m->type].attacksPerRound; ii++) {
2429 			if (!monsterAttackHitTest(m, c))
2430 				continue;
2431 			dmg += rollDice(_monsterProps[m->type].dmgDc[ii].times, _monsterProps[m->type].dmgDc[ii].pips, _monsterProps[m->type].dmgDc[ii].base);
2432 			if (_characters[c].effectsRemainder[1]) {
2433 				if (--_characters[c].effectsRemainder[1])
2434 					dmg = 0;
2435 			}
2436 		}
2437 
2438 		if (dmg > 0) {
2439 			if ((_monsterProps[m->type].capsFlags & 0x80) && rollDice(1, 4, -1) != 3) {
2440 				int slot = rollDice(1, 27, -1);
2441 				for (int iii = 0; iii < 27; iii++) {
2442 					Item itm = _characters[c].inventory[slot];
2443 					if (!itm || !(_itemTypes[_items[itm].type].extraProperties & 0x80)) {
2444 						if (++slot == 27)
2445 							slot = 0;
2446 						continue;
2447 					}
2448 
2449 					_characters[c].inventory[slot] = 0;
2450 					_txt->printMessage(_ripItemStrings[(_characters[c].raceSex & 1) ^ 1], -1, _characters[c].name);
2451 					printFullItemName(itm);
2452 					_txt->printMessage(_ripItemStrings[2]);
2453 					break;
2454 				}
2455 				gui_drawCharPortraitWithStats(c);
2456 			}
2457 
2458 			inflictCharacterDamage(c, dmg);
2459 
2460 			if (_monsterProps[m->type].capsFlags & 0x10) {
2461 				statusAttack(c, 2, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 3 : 2], 0, 1, 8, 1);
2462 				_characters[c].effectFlags &= ~0x2000;
2463 			}
2464 
2465 			if (_monsterProps[m->type].capsFlags & 0x20)
2466 				statusAttack(c, 4, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 4 : 3], 2, 5, 9, 1);
2467 
2468 			if (_monsterProps[m->type].capsFlags & 0x8000)
2469 				statusAttack(c, 8, _monsterSpecAttStrings[4], 2, 0, 0, 1);
2470 
2471 		}
2472 
2473 		if (!(_monsterProps[m->type].capsFlags & 0x4000))
2474 			return;
2475 	}
2476 }
2477 
monsterSpellCast(EoBMonsterInPlay * m,int type)2478 void EoBCoreEngine::monsterSpellCast(EoBMonsterInPlay *m, int type) {
2479 	launchMagicObject(-1, type, m->block, m->pos, m->dir);
2480 	snd_processEnvironmentalSoundEffect(_spells[_magicFlightObjectProperties[type << 2]].sound, m->block);
2481 }
2482 
statusAttack(int charIndex,int attackStatusFlags,const char * attackStatusString,int savingThrowType,uint32 effectDuration,int restoreEvent,int noRefresh)2483 void EoBCoreEngine::statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh) {
2484 	EoBCharacter *c = &_characters[charIndex];
2485 	if ((c->flags & attackStatusFlags) && noRefresh)
2486 		return;
2487 	if (!testCharacter(charIndex, 3))
2488 		return;
2489 
2490 	if (savingThrowType != 5 && specialAttackSavingThrow(charIndex, savingThrowType))
2491 		return;
2492 
2493 	if (attackStatusFlags & 8) {
2494 		removeAllCharacterEffects(charIndex);
2495 		c->flags = (c->flags & 1) | 8;
2496 	} else {
2497 		c->flags |= attackStatusFlags;
2498 	}
2499 
2500 	if ((attackStatusFlags & 0x0C) && (_openBookChar == charIndex) && _updateFlags) {
2501 		Button b;
2502 		clickedSpellbookAbort(&b);
2503 	}
2504 
2505 	if (effectDuration)
2506 		setCharEventTimer(charIndex, effectDuration * 546, restoreEvent, 1);
2507 
2508 	gui_drawCharPortraitWithStats(charIndex);
2509 	_txt->printMessage(_characterStatusStrings13[0], -1, c->name, attackStatusString);
2510 }
2511 
calcMonsterDamage(EoBMonsterInPlay * m,int times,int pips,int offs,int flags,int savingThrowType,int savingThrowEffect)2512 int EoBCoreEngine::calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) {
2513 	int s = flags & 0x100 ? calcDamageModifers(times, m, pips, _items[pips].type, offs) : rollDice(times, pips, offs);
2514 	EoBMonsterProperty *p = &_monsterProps[m->type];
2515 
2516 	if (savingThrowType != 5) {
2517 		if (trySavingThrow(m, 0, p->level, savingThrowType, 6))
2518 			s = savingThrowReduceDamage(savingThrowEffect, s);
2519 	}
2520 
2521 	if ((flags & 0x110) == 0x110) {
2522 		if (!calcDamageCheckItemType(_items[pips].type))
2523 			s = 1;
2524 	}
2525 
2526 	if ((flags & 0x100) && (!(_itemTypes[_items[pips].type].allowedClasses & 4 /* bug in original code ??*/))
2527 	    && ((_flags.gameID == GI_EOB2 && (p->immunityFlags & 0x100)) || (_flags.gameID == GI_EOB1 && (p->capsFlags & 4))))
2528 		s >>= 1;
2529 
2530 	if (p->immunityFlags & 0x2000) {
2531 		if (flags & 0x100) {
2532 			if (_items[pips].value < 3)
2533 				s >>= 2;
2534 			if (_items[pips].value == 3)
2535 				s >>= 1;
2536 			if (s == 0)
2537 				s = _items[pips].value;
2538 
2539 		} else {
2540 			s >>= 1;
2541 		}
2542 	}
2543 
2544 	if (flags & 1) {
2545 		if (tryMonsterAttackEvasion(m))
2546 			s = 0;
2547 	}
2548 
2549 	if (_flags.gameID == GI_EOB1)
2550 		return s;
2551 
2552 	static const uint16 damageImmunityFlags[] = { 0x01, 0x10, 0x02, 0x20, 0x80, 0x400, 0x20, 0x800, 0x40, 0x80, 0x400, 0x40 };
2553 	for (int i = 0; i < 12; i += 2) {
2554 		if ((flags & damageImmunityFlags[i]) && (p->immunityFlags & damageImmunityFlags[i + 1]))
2555 			s = 0;
2556 	}
2557 
2558 	return s;
2559 }
2560 
calcDamageModifers(int charIndex,EoBMonsterInPlay * m,int item,int itemType,int useStrModifier)2561 int EoBCoreEngine::calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier) {
2562 	int s = (useStrModifier && (charIndex != -1)) ? getStrDamageModifier(charIndex) : 0;
2563 	if (item) {
2564 		EoBItemType *p = &_itemTypes[itemType];
2565 		int t = m ? m->type : 0;
2566 		s += ((m && (_monsterProps[t].capsFlags & 1)) ? rollDice(p->dmgNumDiceL, p->dmgNumPipsL, p->dmgIncS /* bug in original code ? */) :
2567 		      rollDice(p->dmgNumDiceS, p->dmgNumPipsS, p->dmgIncS));
2568 		s += _items[item].value;
2569 	} else {
2570 		s += rollDice(1, 2);
2571 	}
2572 
2573 	return (s < 0) ? 0 : s;
2574 }
2575 
trySavingThrow(void * target,int hpModifier,int level,int type,int race)2576 bool EoBCoreEngine::trySavingThrow(void *target, int hpModifier, int level, int type, int race) {
2577 	static const int8 constMod[] = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5 };
2578 
2579 	if (type == 5)
2580 		return false;
2581 
2582 	int s = getSaveThrowModifier(hpModifier, level, type);
2583 	if (((race == 3 || race == 5) && (type == 4 || type == 1 || type == 0)) || (race == 4 && (type == 4 || type == 1))) {
2584 		EoBCharacter *c = (EoBCharacter *)target;
2585 		s -= constMod[c->constitutionCur];
2586 	}
2587 
2588 	return rollDice(1, 20) >= s;
2589 }
2590 
specialAttackSavingThrow(int charIndex,int type)2591 bool EoBCoreEngine::specialAttackSavingThrow(int charIndex, int type) {
2592 	return trySavingThrow(&_characters[charIndex], _charClassModifier[_characters[charIndex].cClass], _characters[charIndex].level[0], type, _characters[charIndex].raceSex >> 1);
2593 }
2594 
getSaveThrowModifier(int hpModifier,int level,int type)2595 int EoBCoreEngine::getSaveThrowModifier(int hpModifier, int level, int type) {
2596 	const uint8 *tbl = _saveThrowTables[hpModifier];
2597 	if (_saveThrowLevelIndex[hpModifier] < level)
2598 		level = _saveThrowLevelIndex[hpModifier];
2599 	level /= _saveThrowModDiv[hpModifier];
2600 	level += (_saveThrowModExt[hpModifier] * type);
2601 
2602 	return tbl[level];
2603 }
2604 
calcDamageCheckItemType(int itemType)2605 bool EoBCoreEngine::calcDamageCheckItemType(int itemType) {
2606 	itemType = _itemTypes[itemType].extraProperties & 0x7F;
2607 	return (itemType == 2 || itemType == 3) ? true : false;
2608 }
2609 
savingThrowReduceDamage(int savingThrowEffect,int damage)2610 int EoBCoreEngine::savingThrowReduceDamage(int savingThrowEffect, int damage) {
2611 	if (savingThrowEffect == 3)
2612 		return 0;
2613 
2614 	if (savingThrowEffect == 0 || savingThrowEffect == 1)
2615 		return damage >> 1;
2616 
2617 	return damage;
2618 }
2619 
tryMonsterAttackEvasion(EoBMonsterInPlay * m)2620 bool EoBCoreEngine::tryMonsterAttackEvasion(EoBMonsterInPlay *m) {
2621 	return rollDice(1, 100) < _monsterProps[m->type].dmgModifierEvade ? true : false;
2622 }
2623 
getStrHitChanceModifier(int charIndex)2624 int EoBCoreEngine::getStrHitChanceModifier(int charIndex) {
2625 	static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 };
2626 	static const int8 strExtMod[] = { 1, 2, 2, 2, 3 };
2627 	static const int8 strMod[] = { -4, -3, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 3, 4, 4, 5, 6, 7 };
2628 
2629 	int r = strMod[_characters[charIndex].strengthCur - 1];
2630 	if (_characters[charIndex].strengthExtCur) {
2631 		for (int i = 0; i < 5; i++) {
2632 			if (_characters[charIndex].strengthExtCur >= strExtLimit[i])
2633 				r = strExtMod[i];
2634 		}
2635 	}
2636 
2637 	return r;
2638 }
2639 
getStrDamageModifier(int charIndex)2640 int EoBCoreEngine::getStrDamageModifier(int charIndex) {
2641 	static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 };
2642 	static const int8 strExtMod[] = { 3, 3, 4, 5, 6 };
2643 	static const int8 strMod[] = { -3, -2, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 7, 8, 9, 10, 11, 12, 14 };
2644 
2645 	int r = strMod[_characters[charIndex].strengthCur - 1];
2646 	if (_characters[charIndex].strengthExtCur) {
2647 		for (int i = 0; i < 5; i++) {
2648 			if (_characters[charIndex].strengthExtCur >= strExtLimit[i])
2649 				r = strExtMod[i];
2650 		}
2651 	}
2652 
2653 	return r;
2654 }
2655 
getDexHitChanceModifier(int charIndex)2656 int EoBCoreEngine::getDexHitChanceModifier(int charIndex) {
2657 	static const int8 dexMod[] = { -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 3, 4, 4, 4 };
2658 	return dexMod[_characters[charIndex].dexterityCur - 1];
2659 }
2660 
getMonsterAcHitChanceModifier(int charIndex,int monsterAc)2661 int EoBCoreEngine::getMonsterAcHitChanceModifier(int charIndex, int monsterAc) {
2662 	int l = _characters[charIndex].level[0] - 1;
2663 	int cm = _charClassModifier[_characters[charIndex].cClass];
2664 
2665 	return (20 - ((l / _monsterAcHitChanceTable1[cm]) * _monsterAcHitChanceTable2[cm])) - monsterAc;
2666 }
2667 
explodeMonster(EoBMonsterInPlay * m)2668 void EoBCoreEngine::explodeMonster(EoBMonsterInPlay *m) {
2669 	m->flags |= 2;
2670 	if (getBlockDistance(m->block, _currentBlock) < 2) {
2671 		explodeObject(0, _currentBlock, 2);
2672 		for (int i = 0; i < 6; i++)
2673 			calcAndInflictCharacterDamage(i, 6, 6, 0, 8, 1, 0);
2674 	} else {
2675 		explodeObject(0, m->block, 2);
2676 	}
2677 	m->flags &= ~2;
2678 }
2679 
addLevelMap(int level)2680 void EoBCoreEngine::addLevelMap(int level) {
2681 	assert(level);
2682 	_levelMaps |= (1 << (level - 1));
2683 }
2684 
hasLevelMap(int level) const2685 bool EoBCoreEngine::hasLevelMap(int level) const {
2686 	return _levelMaps & (1 << (level - 1));
2687 }
2688 
countMaps() const2689 uint32 EoBCoreEngine::countMaps() const {
2690 	uint32 res = 0;
2691 	for (int i = 1; i < 13; ++i) {
2692 		if (hasLevelMap(i))
2693 			res++;
2694 	}
2695 	return res;
2696 }
2697 
countArrows() const2698 uint32 EoBCoreEngine::countArrows() const {
2699 	uint32 res = 0;
2700 	for (int i = 0; i < 6; ++i)
2701 		res += countQueuedItems(_characters[i].inventory[16], -1, -1, 1, 1);
2702 	return res;
2703 }
2704 
snd_playSong(int track,bool loop)2705 void EoBCoreEngine::snd_playSong(int track, bool loop) {
2706 	if (_flags.platform == Common::kPlatformSegaCD && !loop)
2707 		track |= 0x80;
2708 	_sound->playTrack(track);
2709 }
2710 
snd_playLevelScore()2711 void EoBCoreEngine::snd_playLevelScore() {
2712 	if (_flags.platform == Common::kPlatformPC98) {
2713 		if (_flags.gameID == GI_EOB1)
2714 			snd_playSong(_currentLevel + 1);
2715 	} else if (_flags.platform == Common::kPlatformSegaCD) {
2716 		static const uint8 levelTracksSegaCD[13] = { 7, 7, 7, 7, 6, 6, 6, 4, 4, 4, 5, 5, 10 };
2717 		snd_playSong(levelTracksSegaCD[_currentLevel]);
2718 	}
2719 }
2720 
snd_playSoundEffect(int track,int volume)2721 void EoBCoreEngine::snd_playSoundEffect(int track, int volume) {
2722 	if ((track < 1) || (_flags.gameID == GI_EOB2 && track > 119) || shouldQuit())
2723 		return;
2724 
2725 	if (_flags.platform == Common::kPlatformSegaCD) {
2726 		if (volume == 0xFF)
2727 			volume = 0x0E;
2728 		if (track == 23 || track == 28)
2729 			track |= 0x1000;
2730 	}
2731 
2732 	_sound->playSoundEffect(track, volume);
2733 }
2734 
snd_stopSound()2735 void EoBCoreEngine::snd_stopSound() {
2736 	_sound->haltTrack();
2737 	_sound->stopAllSoundEffects();
2738 }
2739 
snd_fadeOut(int del)2740 void EoBCoreEngine::snd_fadeOut(int del) {
2741 	_sound->beginFadeOut(del);
2742 }
2743 
2744 } // End of namespace Kyra
2745 
2746 #endif // ENABLE_EOB
2747