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 #if defined(ENABLE_EOB) || defined(ENABLE_LOL)
24 
25 #include "kyra/engine/kyra_rpg.h"
26 #include "kyra/sound/sound.h"
27 
28 #include "common/system.h"
29 
30 namespace Kyra {
31 
KyraRpgEngine(OSystem * system,const GameFlags & flags)32 KyraRpgEngine::KyraRpgEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags), _numFlyingObjects(_flags.gameID == GI_LOL ? 8 : 10) {
33 	_txt = 0;
34 	_mouseClick = 0;
35 	_preserveEvents = _buttonListChanged = false;
36 
37 	_sceneXoffset = 0;
38 	_sceneShpDim = 5;
39 
40 	_activeButtons = 0;
41 
42 	_currentLevel = 0;
43 
44 	_vcnBlocks = 0;
45 	_vcfBlocks = 0;
46 	_vcnTransitionMask = _vcnMaskTbl = 0;
47 	_vcnShift = 0;
48 	_vcnColTable = 0;
49 	_vcnShiftVal = 0;
50 	_vcnBpp = flags.useHiColorMode ? 2 : 1;
51 	_vcnSrcBitsPerPixel = (flags.platform == Common::kPlatformAmiga) ? 5 : (_vcnBpp == 2 ? 8 : 4);
52 	_vcnDrawLine = 0;
53 
54 	_vmpPtr = 0;
55 	_blockBrightness = _wllVcnOffset = _wllVcnOffset2 = _wllVcnRmdOffset = 0;
56 	_blockDrawingBuffer = 0;
57 	_sceneWindowBuffer = 0;
58 	_monsterShapes = _monsterPalettes = 0;
59 
60 	_doorShapes = 0;
61 
62 	_levelDecorationProperties = 0;
63 	_levelDecorationData = 0;
64 	_levelDecorationShapes = 0;
65 	_decorationCount = 0;
66 	_mappedDecorationsCount = 0;
67 	memset(_visibleBlockIndex, 0, sizeof(_visibleBlockIndex));
68 
69 	_lvlShapeTop = _lvlShapeBottom = _lvlShapeLeftRight = 0;
70 	_levelBlockProperties = 0;
71 	_hasTempDataFlags = 0;
72 
73 	_wllVmpMap = _specialWallTypes = _wllWallFlags = 0;
74 	_wllShapeMap = 0;
75 
76 	_sceneDrawVarDown = _sceneDrawVarRight = _sceneDrawVarLeft = _wllProcessFlag = 0;
77 
78 	_currentBlock = 0;
79 	_currentDirection = 0;
80 	_compassDirection = -1;
81 	_updateFlags = _clickedSpecialFlag = 0;
82 	_sceneDefaultUpdate = 0;
83 	_sceneUpdateRequired = false;
84 
85 	_flyingObjectsPtr = 0;
86 	_flyingObjectStructSize = sizeof(EoBFlyingObject);
87 
88 	_clickedShapeXOffs = _clickedShapeYOffs = 0;
89 
90 	_dscShapeX = 0;
91 	_dscTileIndex = 0;
92 	_dscDoorScaleOffs = 0;
93 	_dscDim1 = 0;
94 	_dscDim2 = 0;
95 	_dscBlockMap = 0;
96 	_dscBlockIndex = 0;
97 	_dscShapeIndex = 0;
98 	_dscDimMap = 0;
99 	_dscDoorShpIndex = 0;
100 	_dscDoorY2 = 0;
101 	_dscDoorFrameY1 = 0;
102 	_dscDoorFrameY2 = 0;
103 	_dscDoorFrameIndex1 = 0;
104 	_dscDoorFrameIndex2 = 0;
105 
106 	_shpDmX1 = _shpDmX2 = 0;
107 
108 	memset(_openDoorState, 0, sizeof(_openDoorState));
109 	memset(_dialogueButtonString, 0, 3 * sizeof(const char *));
110 	_dialogueButtonPosX = 0;
111 	_dialogueButtonPosY = 0;
112 	_dialogueNumButtons = _dialogueButtonYoffs = _dialogueHighlightedButton = 0;
113 	_currentControlMode = 0;
114 	_specialSceneFlag = 0;
115 	_updateCharNum = -1;
116 	_activeVoiceFileTotalTime = 0;
117 	_updatePortraitSpeechAnimDuration = _resetPortraitAfterSpeechAnim = _needSceneRestore = 0;
118 	_fadeText = false;
119 
120 	memset(_lvlTempData, 0, sizeof(_lvlTempData));
121 
122 	_dialogueField = false;
123 	_dialogueFieldAmiga = true;
124 
125 	_environmentSfx = _environmentSfxVol = _envSfxDistThreshold = 0;
126 	_monsterStepCounter = _monsterStepMode = 0;
127 }
128 
~KyraRpgEngine()129 KyraRpgEngine::~KyraRpgEngine() {
130 	delete[] _wllVmpMap;
131 	delete[] _wllShapeMap;
132 	delete[] _specialWallTypes;
133 	delete[] _wllWallFlags;
134 
135 	delete[] _vmpPtr;
136 	delete[] _vcnColTable;
137 	delete[] _vcnBlocks;
138 	delete[] _vcfBlocks;
139 	delete[] _vcnTransitionMask;
140 	delete[] _vcnShift;
141 	delete[] _blockDrawingBuffer;
142 	delete[] _sceneWindowBuffer;
143 	delete _vcnDrawLine;
144 
145 	delete[] _lvlShapeTop;
146 	delete[] _lvlShapeBottom;
147 	delete[] _lvlShapeLeftRight;
148 
149 	delete[] _doorShapes;
150 
151 	delete[] _levelDecorationShapes;
152 	delete[] _levelDecorationData;
153 	delete[] _levelDecorationProperties;
154 	delete[] _levelBlockProperties;
155 }
156 
init()157 Common::Error KyraRpgEngine::init() {
158 	gui_resetButtonList();
159 
160 	_levelDecorationProperties = new LevelDecorationProperty[100];
161 	memset(_levelDecorationProperties, 0, 100 * sizeof(LevelDecorationProperty));
162 	_levelDecorationShapes = new uint8*[400];
163 	memset(_levelDecorationShapes, 0, 400 * sizeof(uint8 *));
164 	_levelBlockProperties = new LevelBlockProperty[1025];
165 	memset(_levelBlockProperties, 0, 1025 * sizeof(LevelBlockProperty));
166 
167 	_wllVmpMap = new uint8[256];
168 	memset(_wllVmpMap, 0, 256);
169 	_wllShapeMap = new int8[256];
170 	memset(_wllShapeMap, 0, 256);
171 	_specialWallTypes = new uint8[256];
172 	memset(_specialWallTypes, 0, 256);
173 	_wllWallFlags = new uint8[256];
174 	memset(_wllWallFlags, 0, 256);
175 
176 	_blockDrawingBuffer = new uint16[1320];
177 	memset(_blockDrawingBuffer, 0, 1320 * sizeof(uint16));
178 	int windowBufferSize = _flags.useHiColorMode ? 42240 : 21120;
179 	_sceneWindowBuffer = new uint8[windowBufferSize];
180 	memset(_sceneWindowBuffer, 0, windowBufferSize);
181 
182 	_lvlShapeTop = new int16[18];
183 	memset(_lvlShapeTop, 0, 18 * sizeof(int16));
184 	_lvlShapeBottom = new int16[18];
185 	memset(_lvlShapeBottom, 0, 18 * sizeof(int16));
186 	_lvlShapeLeftRight = new int16[36];
187 	memset(_lvlShapeLeftRight, 0, 36 * sizeof(int16));
188 
189 	_vcnColTable = new uint8[128];
190 	for (int i = 0; i < 128; i++)
191 		_vcnColTable[i] = i & 0x0F;
192 
193 	if (_vcnBpp == 2)
194 		_vcnDrawLine = new VcnLineDrawingMethods(new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_fw_hiCol), new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_bw_hiCol),
195 			new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_fw_trans_hiCol), new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_bw_trans_hiCol));
196 	else if (_flags.platform == Common::kPlatformAmiga)
197 		_vcnDrawLine = new VcnLineDrawingMethods(new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_fw_Amiga), new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_bw_Amiga),
198 			new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_fw_trans_Amiga), new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_bw_trans_Amiga));
199 	else
200 		_vcnDrawLine = new VcnLineDrawingMethods(new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_fw_4bit), new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_bw_4bit),
201 			new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_fw_trans_4bit), new VcnDrawProc(this, &KyraRpgEngine::vcnDraw_bw_trans_4bit));
202 
203 	_doorShapes = new uint8*[6];
204 	memset(_doorShapes, 0, 6 * sizeof(uint8 *));
205 
206 	initStaticResource();
207 
208 	_envSfxDistThreshold = ((_flags.gameID == GI_EOB2 && _sound->getSfxType() == Sound::kTowns) || _sound->getSfxType() == Sound::kAdLib || _sound->getSfxType() == Sound::kPCSpkr) ? 15 : (_sound->getSfxType() == Sound::kAmiga ? 4 : 3);
209 
210 	_dialogueButtonLabelColor1 = guiSettings()->buttons.labelColor1;
211 	_dialogueButtonLabelColor2 = guiSettings()->buttons.labelColor2;
212 	_dialogueButtonWidth = guiSettings()->buttons.width;
213 
214 	return Common::kNoError;
215 }
216 
posWithinRect(int posX,int posY,int x1,int y1,int x2,int y2)217 bool KyraRpgEngine::posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2) {
218 	if (posX < x1 || posX > x2 || posY < y1 || posY > y2)
219 		return false;
220 	return true;
221 }
222 
drawDialogueButtons()223 void KyraRpgEngine::drawDialogueButtons() {
224 	int cp = screen()->setCurPage(0);
225 	Screen::FontId of = screen()->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : ((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : Screen::FID_6_FNT));
226 
227 	for (int i = 0; i < _dialogueNumButtons; i++) {
228 		int x = _dialogueButtonPosX[i];
229 		if (_flags.lang == Common::JA_JPN && _flags.use16ColorMode) {
230 			gui_drawBox(x, ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1, 74, 10, 0xEE, 0xCC, -1);
231 			screen()->printText(_dialogueButtonString[i], (x + 37 - (screen()->getTextWidth(_dialogueButtonString[i])) / 2) & ~3,
232 			                    ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) + 2) & ~7, _dialogueHighlightedButton == i ? 0xC1 : 0xE1, 0);
233 		} else {
234 			int sjisYOffset = (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? 1 : ((_flags.lang == Common::JA_JPN && (_dialogueButtonString[i][0] & 0x80)) ? 2 : 0);
235 			screen()->set16bitShadingLevel(4);
236 			gui_drawBox(x, (_dialogueButtonYoffs + _dialogueButtonPosY[i]), _dialogueButtonWidth, guiSettings()->buttons.height, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill);
237 			screen()->set16bitShadingLevel(0);
238 			screen()->printText(_dialogueButtonString[i], x + (_dialogueButtonWidth >> 1) - (screen()->getTextWidth(_dialogueButtonString[i])) / 2,
239 			                    (_dialogueButtonYoffs + _dialogueButtonPosY[i]) + 2 - sjisYOffset, _dialogueHighlightedButton == i ? _dialogueButtonLabelColor1 : _dialogueButtonLabelColor2, 0);
240 		}
241 	}
242 	screen()->setFont(of);
243 	screen()->setCurPage(cp);
244 }
245 
processDialogue()246 uint16 KyraRpgEngine::processDialogue() {
247 	int df = _dialogueHighlightedButton;
248 	int res = 0;
249 
250 	for (int i = 0; i < _dialogueNumButtons; i++) {
251 		int x = _dialogueButtonPosX[i];
252 		int y = ((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1 : (_dialogueButtonYoffs + _dialogueButtonPosY[i]));
253 		Common::Point p = getMousePos();
254 		if (posWithinRect(p.x, p.y, x, y, x + _dialogueButtonWidth, y + guiSettings()->buttons.height)) {
255 			_dialogueHighlightedButton = i;
256 			break;
257 		}
258 	}
259 
260 	if (_dialogueNumButtons == 0) {
261 		int e = checkInput(0, false) & 0xFF;
262 		removeInputTop();
263 
264 		if (e) {
265 			gui_notifyButtonListChanged();
266 
267 			if (e == _keyMap[Common::KEYCODE_SPACE] || e == _keyMap[Common::KEYCODE_RETURN]) {
268 				snd_stopSpeech(true);
269 			}
270 		}
271 
272 		if (snd_updateCharacterSpeech() != 2) {
273 			res = 1;
274 			if (!shouldQuit()) {
275 				removeInputTop();
276 				gui_notifyButtonListChanged();
277 			}
278 		}
279 	} else {
280 		int e = checkInput(0, false, 0) & 0xFF;
281 		removeInputTop();
282 		if (e)
283 			gui_notifyButtonListChanged();
284 
285 		if ((_flags.gameID == GI_LOL && (e == 200 || e == 202)) || (_flags.gameID != GI_LOL && (e == 199 || e == 201))) {
286 			for (int i = 0; i < _dialogueNumButtons; i++) {
287 				int x = _dialogueButtonPosX[i];
288 				int y = (gameFlags().use16ColorMode ? ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1 : (_dialogueButtonYoffs + _dialogueButtonPosY[i]));
289 				Common::Point p = getMousePos();
290 				if (posWithinRect(p.x, p.y, x, y, x + _dialogueButtonWidth, y + guiSettings()->buttons.height)) {
291 					_dialogueHighlightedButton = i;
292 					res = _dialogueHighlightedButton + 1;
293 					break;
294 				}
295 			}
296 		} else if (e == _keyMap[Common::KEYCODE_SPACE] || e == _keyMap[Common::KEYCODE_RETURN]) {
297 			snd_stopSpeech(true);
298 			res = _dialogueHighlightedButton + 1;
299 		} else if (e == _keyMap[Common::KEYCODE_LEFT] || e == _keyMap[Common::KEYCODE_DOWN]) {
300 			if (_dialogueNumButtons > 1 && _dialogueHighlightedButton > 0)
301 				_dialogueHighlightedButton--;
302 		} else if (e == _keyMap[Common::KEYCODE_RIGHT] || e == _keyMap[Common::KEYCODE_UP]) {
303 			if (_dialogueNumButtons > 1 && _dialogueHighlightedButton < (_dialogueNumButtons - 1))
304 				_dialogueHighlightedButton++;
305 		}
306 	}
307 
308 	if (df != _dialogueHighlightedButton)
309 		drawDialogueButtons();
310 
311 	screen()->updateScreen();
312 
313 	if (res == 0)
314 		return 0;
315 
316 	stopPortraitSpeechAnim();
317 
318 	if (game() == GI_LOL) {
319 		if (!textEnabled() && _currentControlMode) {
320 			screen()->setScreenDim(5);
321 			const ScreenDim *d = screen()->getScreenDim(5);
322 			screen()->fillRect(d->sx, d->sy + d->h - 9, d->sx + d->w - 1, d->sy + d->h - 1, d->unkA);
323 		} else {
324 			const ScreenDim *d = screen()->_curDim;
325 			if (gameFlags().use16ColorMode)
326 				screen()->fillRect(d->sx, d->sy, d->sx + d->w - 3, d->sy + d->h - 2, d->unkA);
327 			else
328 				screen()->fillRect(d->sx, d->sy, d->sx + d->w - 2, d->sy + d->h - 1, d->unkA);
329 			txt()->clearDim(4);
330 			txt()->resetDimTextPositions(4);
331 		}
332 	}
333 
334 	return res;
335 }
336 
delayUntil(uint32 time,bool,bool doUpdate,bool isMainLoop)337 void KyraRpgEngine::delayUntil(uint32 time, bool, bool doUpdate, bool isMainLoop) {
338 	uint32 curTime = _system->getMillis();
339 	if (time > curTime)
340 		delay(time - curTime, doUpdate, isMainLoop);
341 }
342 
rollDice(int times,int pips,int inc)343 int KyraRpgEngine::rollDice(int times, int pips, int inc) {
344 	if (times <= 0 || pips <= 0)
345 		return inc;
346 
347 	int res = 0;
348 	while (times--)
349 		res += _rnd.getRandomNumberRng(1, pips);
350 
351 	return res + inc;
352 }
353 
snd_processEnvironmentalSoundEffect(int soundId,int block)354 bool KyraRpgEngine::snd_processEnvironmentalSoundEffect(int soundId, int block) {
355 	if (!_sound->sfxEnabled() || shouldQuit())
356 		return false;
357 
358 	if (_environmentSfx)
359 		snd_playSoundEffect(_environmentSfx, _environmentSfxVol);
360 
361 	int dist = 0;
362 	if (block) {
363 		dist = getBlockDistance(_currentBlock, block);
364 		if (dist > _envSfxDistThreshold) {
365 			_environmentSfx = 0;
366 			return false;
367 		}
368 	}
369 
370 	_environmentSfx = soundId;
371 
372 	if (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns)
373 		_environmentSfxVol = dist ? (16 - dist) * 8 - 1 : 127;
374 	else if (_flags.platform == Common::kPlatformAmiga)
375 		_environmentSfxVol = dist ? (soundId != 13 ? dist : (dist >= 4) ? 4 : dist) : 1;
376 	else
377 		_environmentSfxVol = (15 - ((block || (_flags.gameID == GI_LOL && dist < 2)) ? dist : 0)) << 4;
378 
379 	return true;
380 }
381 
updateEnvironmentalSfx(int soundId)382 void KyraRpgEngine::updateEnvironmentalSfx(int soundId) {
383 	snd_processEnvironmentalSoundEffect(soundId, _currentBlock);
384 }
385 
386 } // End of namespace Kyra
387 
388 #endif // ENABLE_EOB || ENABLE_LOL
389