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 /*
24 * Based on the Reverse Engineering work of Christophe Fontanel,
25 * maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
26 */
27 
28 #include "dm/timeline.h"
29 #include "dm/dungeonman.h"
30 #include "dm/champion.h"
31 #include "dm/inventory.h"
32 #include "dm/group.h"
33 #include "dm/projexpl.h"
34 #include "dm/movesens.h"
35 #include "dm/text.h"
36 #include "dm/eventman.h"
37 #include "dm/objectman.h"
38 #include "dm/sounds.h"
39 
40 
41 namespace DM {
42 
initConstants()43 void Timeline::initConstants() {
44 	static signed char actionDefense[44] = { // @ G0495_ac_Graphic560_ActionDefense
45 		0,   /* N */
46 		36,  /* BLOCK */
47 		0,   /* CHOP */
48 		0,   /* X */
49 		-4,  /* BLOW HORN */
50 		-10, /* FLIP */
51 		-10, /* PUNCH */
52 		-5,  /* KICK */
53 		4,   /* WAR CRY */
54 		-20, /* STAB */
55 		-15, /* CLIMB DOWN */
56 		-10, /* FREEZE LIFE */
57 		16,  /* HIT */
58 		5,   /* SWING */
59 		-15, /* STAB */
60 		-17, /* THRUST */
61 		-5,  /* JAB */
62 		29,  /* PARRY */
63 		10,  /* HACK */
64 		-10, /* BERZERK */
65 		-7,  /* FIREBALL */
66 		-7,  /* DISPELL */
67 		-7,  /* CONFUSE */
68 		-7,  /* LIGHTNING */
69 		-7,  /* DISRUPT */
70 		-5,  /* MELEE */
71 		-15, /* X */
72 		-9,  /* INVOKE */
73 		4,   /* SLASH */
74 		0,   /* CLEAVE */
75 		0,   /* BASH */
76 		5,   /* STUN */
77 		-15, /* SHOOT */
78 		-7,  /* SPELLSHIELD */
79 		-7,  /* FIRESHIELD */
80 		8,   /* FLUXCAGE */
81 		-20, /* HEAL */
82 		-5,  /* CALM */
83 		0,   /* LIGHT */
84 		-15, /* WINDOW */
85 		-7,  /* SPIT */
86 		-4,  /* BRANDISH */
87 		0,   /* THROW */
88 		8    /* FUSE */
89 	};
90 
91 	for (int i = 0; i < 44; i++)
92 		_actionDefense[i] = actionDefense[i];
93 }
94 
Timeline(DMEngine * vm)95 Timeline::Timeline(DMEngine *vm) : _vm(vm) {
96 	_eventMaxCount = 0;
97 	_events = nullptr;
98 	_eventCount = 0;
99 	_timeline = nullptr;
100 	_firstUnusedEventIndex = 0;
101 
102 	initConstants();
103 }
104 
~Timeline()105 Timeline::~Timeline() {
106 	delete[] _events;
107 	delete[] _timeline;
108 }
109 
initTimeline()110 void Timeline::initTimeline() {
111 	_events = new TimelineEvent[_eventMaxCount];
112 	_timeline = new uint16[_eventMaxCount];
113 	if (_vm->_gameMode != kDMModeLoadSavedGame) {
114 		for (int16 i = 0; i < _eventMaxCount; ++i)
115 			_events[i]._type = kDMEventTypeNone;
116 		_eventCount = 0;
117 		_firstUnusedEventIndex = 0;
118 	}
119 }
120 
deleteEvent(uint16 eventIndex)121 void Timeline::deleteEvent(uint16 eventIndex) {
122 	_events[eventIndex]._type = kDMEventTypeNone;
123 	if (eventIndex < _firstUnusedEventIndex)
124 		_firstUnusedEventIndex = eventIndex;
125 
126 	_eventCount--;
127 
128 	uint16 eventCount = _eventCount;
129 	if (eventCount == 0)
130 		return;
131 
132 	uint16 timelineIndex = getIndex(eventIndex);
133 	if (timelineIndex == eventCount)
134 		return;
135 
136 	_timeline[timelineIndex] = _timeline[eventCount];
137 	fixChronology(timelineIndex);
138 }
139 
fixChronology(uint16 timelineIndex)140 void Timeline::fixChronology(uint16 timelineIndex) {
141 	uint16 eventCount = _eventCount;
142 	if (eventCount == 1)
143 		return;
144 
145 	uint16 eventIndex = _timeline[timelineIndex];
146 	TimelineEvent *timelineEvent = &_events[eventIndex];
147 	bool chronologyFixed = false;
148 	while (timelineIndex > 0) { /* Check if the event should be moved earlier in the timeline */
149 		uint16 altTimelineIndex = (timelineIndex - 1) >> 1;
150 		if (isEventABeforeB(timelineEvent, &_events[_timeline[altTimelineIndex]])) {
151 			_timeline[timelineIndex] = _timeline[altTimelineIndex];
152 			timelineIndex = altTimelineIndex;
153 			chronologyFixed = true;
154 		} else
155 			break;
156 	}
157 	if (!chronologyFixed) {
158 		eventCount = ((eventCount - 1) - 1) >> 1;
159 		while (timelineIndex <= eventCount) { /* Check if the event should be moved later in the timeline */
160 			uint16 altTimelineIndex = (timelineIndex << 1) + 1;
161 			if (((altTimelineIndex + 1) < _eventCount) && (isEventABeforeB(&_events[_timeline[altTimelineIndex + 1]], &_events[_timeline[altTimelineIndex]])))
162 				altTimelineIndex++;
163 
164 			if (isEventABeforeB(&_events[_timeline[altTimelineIndex]], timelineEvent)) {
165 				_timeline[timelineIndex] = _timeline[altTimelineIndex];
166 				timelineIndex = altTimelineIndex;
167 			} else
168 				break;
169 		}
170 	}
171 
172 	_timeline[timelineIndex] = eventIndex;
173 }
174 
isEventABeforeB(TimelineEvent * eventA,TimelineEvent * eventB)175 bool Timeline::isEventABeforeB(TimelineEvent *eventA, TimelineEvent *eventB) {
176 	bool simultaneousFl = (_vm->filterTime(eventA->_mapTime) == _vm->filterTime(eventB->_mapTime));
177 
178 	return (_vm->filterTime(eventA->_mapTime) < _vm->filterTime(eventB->_mapTime)) ||
179 		(simultaneousFl && (eventA->getTypePriority() > eventB->getTypePriority())) ||
180 		(simultaneousFl && (eventA->getTypePriority() == eventB->getTypePriority()) && (eventA <= eventB));
181 }
182 
getIndex(uint16 eventIndex)183 uint16 Timeline::getIndex(uint16 eventIndex) {
184 	uint16 timelineIndex;
185 	uint16 *timelineEntry = _timeline;
186 
187 	for (timelineIndex = 0; timelineIndex < _eventMaxCount; timelineIndex++) {
188 		if (*timelineEntry++ == eventIndex)
189 			break;
190 	}
191 
192 	if (timelineIndex >= _eventMaxCount) /* BUG0_00 Useless code. The function is always called with event indices that are in the timeline */
193 		timelineIndex = 0; /* BUG0_01 Coding error without consequence. Wrong return value. If the specified event index is not found in the timeline the function returns 0 which is the same value that is returned if the event index is found in the first timeline entry. No consequence because this code is never executed */
194 
195 	return timelineIndex;
196 }
197 
addEventGetEventIndex(TimelineEvent * event)198 uint16 Timeline::addEventGetEventIndex(TimelineEvent *event) {
199 	if (_eventCount == _eventMaxCount)
200 		error("Too many events");
201 
202 	if ((event->_type >= kDMEventTypeCorridor) && (event->_type <= kDMEventTypeDoor)) {
203 		TimelineEvent *curEvent = _events;
204 		for (uint16 eventIndex = 0; eventIndex < _eventMaxCount; eventIndex++, curEvent++) {
205 			if ((curEvent->_type >= kDMEventTypeCorridor) && (curEvent->_type <= kDMEventTypeDoor)) {
206 				if ((event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY()) && ((curEvent->_type != kDMEventTypeWall) || (curEvent->_Cu.A._cell == event->_Cu.A._cell))) {
207 					curEvent->_Cu.A._effect = event->_Cu.A._effect;
208 					return eventIndex;
209 				}
210 				continue;
211 			} else if ((curEvent->_type == kDMEventTypeDoorAnimation) && (event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY())) {
212 				if (event->_Cu.A._effect == kDMSensorEffectToggle)
213 					event->_Cu.A._effect = 1 - curEvent->_Cu.A._effect;
214 
215 				deleteEvent(eventIndex);
216 				break;
217 			}
218 		}
219 	} else if (event->_type == kDMEventTypeDoorAnimation) {
220 		TimelineEvent *curEvent = _events;
221 		for (uint16 eventIndex = 0; eventIndex < _eventMaxCount; eventIndex++, curEvent++) {
222 			if ((event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY())) {
223 				if (curEvent->_type == kDMEventTypeDoor) {
224 					if (curEvent->_Cu.A._effect == kDMSensorEffectToggle)
225 						curEvent->_Cu.A._effect = 1 - event->_Cu.A._effect;
226 
227 					return eventIndex;
228 				}
229 				if (curEvent->_type == kDMEventTypeDoorAnimation) {
230 					curEvent->_Cu.A._effect = event->_Cu.A._effect;
231 					return eventIndex;
232 				}
233 			}
234 		}
235 	} else if (event->_type == kDMEventTypeDoorDestruction) {
236 		TimelineEvent *curEvent = _events;
237 		for (uint16 eventIndex = 0; eventIndex < _eventMaxCount; eventIndex++, curEvent++) {
238 			if ((event->getMapXY() == curEvent->getMapXY()) && (_vm->getMap(event->_mapTime) == _vm->getMap(curEvent->_mapTime))) {
239 				if ((curEvent->_type == kDMEventTypeDoorAnimation) || (curEvent->_type == kDMEventTypeDoor))
240 					deleteEvent(eventIndex);
241 			}
242 		}
243 	}
244 
245 	uint16 newEventIndex = _firstUnusedEventIndex;
246 	_events[newEventIndex] = *event; /* Copy the event data (Megamax C can assign structures) */
247 	do {
248 		if (_firstUnusedEventIndex == _eventMaxCount)
249 			break;
250 		_firstUnusedEventIndex++;
251 	} while ((_events[_firstUnusedEventIndex])._type != kDMEventTypeNone);
252 	_timeline[_eventCount] = newEventIndex;
253 	fixChronology(_eventCount++);
254 	return newEventIndex;
255 }
256 
processTimeline()257 void Timeline::processTimeline() {
258 	while (isFirstEventExpiered()) {
259 		TimelineEvent newEvent;
260 		TimelineEvent *curEvent = &newEvent;
261 		extractFirstEvent(curEvent);
262 		_vm->_dungeonMan->setCurrentMap(_vm->getMap(newEvent._mapTime));
263 		TimelineEventType curEventType = newEvent._type;
264 		if ((curEventType > (kDMEventTypeGroupReactionDangerOnSquare - 1)) && (curEventType < (kDMEventTypeUpdateBehavior3 + 1)))
265 			_vm->_groupMan->processEvents29to41(newEvent._Bu._location._mapX, newEvent._Bu._location._mapY, curEventType, newEvent._Cu._ticks);
266 		else {
267 			switch (curEventType) {
268 			case kDMEventTypeMoveProjectileIgnoreImpacts:
269 			case kDMEventTypeMoveProjectile:
270 				_vm->_projexpl->processEvents48To49(curEvent);
271 				break;
272 			case kDMEventTypeDoorAnimation:
273 				processEventDoorAnimation(curEvent);
274 				break;
275 			case kDMEventTypeExplosion:
276 				_vm->_projexpl->processEvent25(curEvent);
277 				break;
278 			case kDMEventTypeFakeWall:
279 				processEventSquareFakewall(curEvent);
280 				break;
281 			case kDMEventTypeDoorDestruction:
282 				processEventDoorDestruction(curEvent);
283 				break;
284 			case kDMEventTypeDoor:
285 				processEventSquareDoor(curEvent);
286 				break;
287 			case kDMEventTypePit:
288 				processEventSquarePit(curEvent);
289 				break;
290 			case kDMEventTypeTeleporter:
291 				processEventSquareTeleporter(curEvent);
292 				break;
293 			case kDMEventTypeWall:
294 				processEventSquareWall(curEvent);
295 				break;
296 			case kDMEventTypeCorridor:
297 				processEventSquareCorridor(curEvent);
298 				break;
299 			case kDMEventTypeMoveGroupSilent:
300 			case kDMEventTypeMoveGroupAudible:
301 				processEventsMoveGroup(curEvent);
302 				break;
303 			case kDMEventTypeEnableGroupGenerator:
304 				procesEventEnableGroupGenerator(curEvent);
305 				break;
306 			case kDMEventTypePlaySound:
307 				_vm->_sound->requestPlay(newEvent._Cu._soundIndex, newEvent._Bu._location._mapX, newEvent._Bu._location._mapY, kDMSoundModePlayIfPrioritized);
308 				break;
309 			case kDMEventTypeRemoveFluxcage:
310 				if (!_vm->_gameWon) {
311 					_vm->_dungeonMan->unlinkThingFromList(Thing(newEvent._Cu._slot), Thing(0), newEvent._Bu._location._mapX, newEvent._Bu._location._mapY);
312 					curEvent = (TimelineEvent *)_vm->_dungeonMan->getThingData(Thing(newEvent._Cu._slot));
313 					((Explosion *)curEvent)->setNextThing(_vm->_thingNone);
314 				}
315 				break;
316 			case kDMEventTypeEnableChampionAction:
317 				processEventEnableChampionAction(newEvent._priority);
318 				if (newEvent._Bu._slotOrdinal)
319 					processEventMoveWeaponFromQuiverToSlot(newEvent._priority, _vm->ordinalToIndex(newEvent._Bu._slotOrdinal));
320 
321 				_vm->_championMan->drawChampionState((ChampionIndex)newEvent._priority);
322 				break;
323 			case kDMEventTypeHideDamageReceived:
324 				processEventHideDamageReceived(newEvent._priority);
325 				break;
326 			case kDMEventTypeLight:
327 				_vm->_dungeonMan->setCurrentMap(_vm->_dungeonMan->_partyMapIndex);
328 				processEventLight(curEvent);
329 				_vm->_inventoryMan->setDungeonViewPalette();
330 				break;
331 			case kDMEventTypeInvisibility:
332 				_vm->_championMan->_party._event71Count_Invisibility--;
333 				break;
334 			case kDMEventTypeChampionShield:
335 				_vm->_championMan->_champions[newEvent._priority]._shieldDefense -= newEvent._Bu._defense;
336 				setFlag(_vm->_championMan->_champions[newEvent._priority]._attributes, kDMAttributeStatusBox);
337 				_vm->_championMan->drawChampionState((ChampionIndex)newEvent._priority);
338 				break;
339 			case kDMEventTypeThievesEye:
340 				_vm->_championMan->_party._event73Count_ThievesEye--;
341 				break;
342 			case kDMEventTypePartyShield:
343 				_vm->_championMan->_party._shieldDefense -= newEvent._Bu._defense;
344 				refreshAllChampionStatusBoxes();
345 				break;
346 			case kDMEventTypeSpellShield:
347 				_vm->_championMan->_party._spellShieldDefense -= newEvent._Bu._defense;
348 				refreshAllChampionStatusBoxes();
349 				break;
350 			case kDMEventTypeFireShield:
351 				_vm->_championMan->_party._fireShieldDefense -= newEvent._Bu._defense;
352 				refreshAllChampionStatusBoxes();
353 				break;
354 			case kDMEventTypePoisonChampion: {
355 				uint16 championIndex = newEvent._priority;
356 				_vm->_championMan->_champions[championIndex = newEvent._priority]._poisonEventCount--;
357 				_vm->_championMan->championPoison(championIndex, newEvent._Bu._attack);
358 				}
359 				break;
360 			case kDMEventTypeViAltarRebirth:
361 				processEventViAltarRebirth(curEvent);
362 				break;
363 			case kDMEventTypeFootprints:
364 				_vm->_championMan->_party._event79Count_Footprints--;
365 				break;
366 			default:
367 				break;
368 			}
369 		}
370 		_vm->_dungeonMan->setCurrentMap(_vm->_dungeonMan->_partyMapIndex);
371 	}
372 }
373 
isFirstEventExpiered()374 bool Timeline::isFirstEventExpiered() {
375 	return (_eventCount && (_vm->filterTime(_events[_timeline[0]]._mapTime) <= _vm->_gameTime));
376 }
377 
extractFirstEvent(TimelineEvent * event)378 void Timeline::extractFirstEvent(TimelineEvent *event) {
379 	uint16 eventIndex = _timeline[0];
380 
381 	*event = _events[eventIndex];
382 	deleteEvent(eventIndex);
383 }
384 
processEventDoorAnimation(TimelineEvent * event)385 void Timeline::processEventDoorAnimation(TimelineEvent *event) {
386 	uint16 mapX = event->_Bu._location._mapX;
387 	uint16 mapY = event->_Bu._location._mapY;
388 	Square *curSquare = (Square *)&_vm->_dungeonMan->_currMapData[mapX][mapY];
389 	DoorState doorState = (DoorState)(*curSquare).getDoorState();
390 	if (doorState == kDMDoorStateDestroyed)
391 		return;
392 
393 	event->_mapTime++;
394 	int16 sensorEffect = event->_Cu.A._effect;
395 	if (sensorEffect == kDMSensorEffectClear) {
396 		Door *curDoor = (Door *)_vm->_dungeonMan->getSquareFirstThingData(mapX, mapY);
397 		bool verticalDoorFl = curDoor->opensVertically();
398 		if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX)
399 		 && (mapY == _vm->_dungeonMan->_partyMapY) && (doorState != kDMDoorStateOpen)) {
400 			if (_vm->_championMan->_partyChampionCount > 0) {
401 				curSquare->setDoorState(kDMDoorStateOpen);
402 
403 				// Strangerke
404 				// Original bug fixed - A closing horizontal door wounds champions to the head instead of to the hands. Missing parenthesis in the condition cause all doors to wound the head in addition to the torso
405 				// See BUG0_78
406 				int16 wounds = kDMWoundTorso | (verticalDoorFl ? kDMWoundHead : kDMWoundReadHand | kDMWoundActionHand);
407 				if (_vm->_championMan->getDamagedChampionCount(5, wounds, kDMAttackTypeSelf))
408 					_vm->_sound->requestPlay(kDMSoundIndexPartyDamaged, mapX, mapY, kDMSoundModePlayIfPrioritized);
409 			}
410 			event->_mapTime++;
411 			addEventGetEventIndex(event);
412 			return;
413 		}
414 		Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY);
415 		uint16 creatureAttributes = _vm->_dungeonMan->getCreatureAttributes(groupThing);
416 		if ((groupThing != _vm->_thingEndOfList) && !getFlag(creatureAttributes, kDMCreatureMaskNonMaterial)) {
417 			if (doorState >= (verticalDoorFl ? CreatureInfo::getHeight(creatureAttributes) : 1)) { /* Creature height or 1 */
418 				if (_vm->_groupMan->getDamageAllCreaturesOutcome((Group *)_vm->_dungeonMan->getThingData(groupThing), mapX, mapY, 5, true) != kDMKillOutcomeAllCreaturesInGroup)
419 					_vm->_groupMan->processEvents29to41(mapX, mapY, kDMEventTypeCreateReactionDangerOnSquare, 0);
420 
421 				int16 nextState = doorState - 1;
422 				doorState = (doorState == kDMDoorStateOpen) ? kDMDoorStateOpen : (DoorState) nextState;
423 				curSquare->setDoorState(doorState);
424 				_vm->_sound->requestPlay(kDMSoundIndexWoodenThudAttackTrolinAntmanStoneGolem, mapX, mapY, kDMSoundModePlayIfPrioritized);
425 				event->_mapTime++;
426 				addEventGetEventIndex(event);
427 				return;
428 			}
429 		}
430 	}
431 	if ((sensorEffect == kDMSensorEffectSet) && (doorState == kDMDoorStateOpen))
432 		return;
433 
434 	if ((sensorEffect == kDMSensorEffectClear) && (doorState == kDMDoorStateClosed))
435 		return;
436 
437 	int16 nextDoorEffect = doorState + 1;
438 	int16 prevDoorEffect = doorState - 1;
439 	doorState = (DoorState) ((sensorEffect == kDMSensorEffectSet) ? prevDoorEffect : nextDoorEffect);
440 	curSquare->setDoorState(doorState);
441 	_vm->_sound->requestPlay(kDMSoundIndexDoorRattle, mapX, mapY, kDMSoundModePlayIfPrioritized);
442 
443 	if (sensorEffect == kDMSensorEffectSet) {
444 		if (doorState == kDMDoorStateOpen)
445 			return;
446 	} else if (doorState == kDMDoorStateClosed)
447 		return;
448 
449 	addEventGetEventIndex(event);
450 }
451 
processEventSquareFakewall(TimelineEvent * event)452 void Timeline::processEventSquareFakewall(TimelineEvent *event) {
453 	uint16 mapX = event->_Bu._location._mapX;
454 	uint16 mapY = event->_Bu._location._mapY;
455 	byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
456 	int16 effect = event->_Cu.A._effect;
457 	if (effect == kDMSensorEffectToggle)
458 		effect = getFlag(*curSquare, kDMSquareMaskFakeWallOpen) ? kDMSensorEffectClear : kDMSensorEffectSet;
459 
460 	if (effect == kDMSensorEffectClear) {
461 		if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY)) {
462 			event->_mapTime++;
463 			addEventGetEventIndex(event);
464 		} else {
465 			Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY);
466 			if ((groupThing != _vm->_thingEndOfList) && !getFlag(_vm->_dungeonMan->getCreatureAttributes(groupThing), kDMCreatureMaskNonMaterial)) {
467 				event->_mapTime++;
468 				addEventGetEventIndex(event);
469 			} else
470 				clearFlag(*curSquare, kDMSquareMaskFakeWallOpen);
471 		}
472 	} else
473 		setFlag(*curSquare, kDMSquareMaskFakeWallOpen);
474 }
475 
processEventDoorDestruction(TimelineEvent * event)476 void Timeline::processEventDoorDestruction(TimelineEvent *event) {
477 	Square *square = (Square *)&_vm->_dungeonMan->_currMapData[event->_Bu._location._mapX][event->_Bu._location._mapY];
478 	square->setDoorState(kDMDoorStateDestroyed);
479 }
480 
processEventSquareDoor(TimelineEvent * event)481 void Timeline::processEventSquareDoor(TimelineEvent *event) {
482 	int16 doorState = Square(_vm->_dungeonMan->_currMapData[event->_Bu._location._mapX][event->_Bu._location._mapY]).getDoorState();
483 	if (doorState == kDMDoorStateDestroyed)
484 		return;
485 
486 	if (event->_Cu.A._effect == kDMSensorEffectToggle)
487 		event->_Cu.A._effect = (doorState == kDMDoorStateOpen) ? kDMSensorEffectClear : kDMSensorEffectSet;
488 	else if (event->_Cu.A._effect == kDMSensorEffectSet) {
489 		if ((doorState == kDMDoorStateOpen) || (doorState == kDMDoorStateClosed))
490 			return;
491 	}
492 	event->_type = kDMEventTypeDoorAnimation;
493 	addEventGetEventIndex(event);
494 }
495 
processEventSquarePit(TimelineEvent * event)496 void Timeline::processEventSquarePit(TimelineEvent *event) {
497 	uint16 mapX = event->_Bu._location._mapX;
498 	uint16 mapY = event->_Bu._location._mapY;
499 
500 	byte *square = &_vm->_dungeonMan->_currMapData[mapX][mapY];
501 	if (event->_Cu.A._effect == kDMSensorEffectToggle)
502 		event->_Cu.A._effect = getFlag(*square, kDMSquareMaskPitOpen) ? kDMSensorEffectClear : kDMSensorEffectSet;
503 
504 	if (event->_Cu.A._effect == kDMSensorEffectSet) {
505 		setFlag(*square, kDMSquareMaskPitOpen);
506 		moveTeleporterOrPitSquareThings(mapX, mapY);
507 	} else
508 		clearFlag(*square, kDMSquareMaskPitOpen);
509 }
510 
moveTeleporterOrPitSquareThings(uint16 mapX,uint16 mapY)511 void Timeline::moveTeleporterOrPitSquareThings(uint16 mapX, uint16 mapY) {
512 	if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex)
513 	 && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY)) {
514 		_vm->_moveSens->getMoveResult(_vm->_thingParty, mapX, mapY, mapX, mapY);
515 		_vm->_championMan->drawChangedObjectIcons();
516 	}
517 
518 	Thing curThing = _vm->_groupMan->groupGetThing(mapX, mapY);
519 	if (curThing != _vm->_thingEndOfList)
520 		_vm->_moveSens->getMoveResult(curThing, mapX, mapY, mapX, mapY);
521 
522 	curThing = _vm->_dungeonMan->getSquareFirstObject(mapX, mapY);
523 	Thing nextThing = curThing;
524 	int16 thingsToMoveCount = 0;
525 	while (curThing != _vm->_thingEndOfList) {
526 		if (curThing.getType() > kDMThingTypeGroup)
527 			thingsToMoveCount++;
528 
529 		curThing = _vm->_dungeonMan->getNextThing(curThing);
530 	}
531 	curThing = nextThing;
532 	while ((curThing != _vm->_thingEndOfList) && thingsToMoveCount) {
533 		thingsToMoveCount--;
534 		nextThing = _vm->_dungeonMan->getNextThing(curThing);
535 		uint16 curThingType = curThing.getType();
536 		if (curThingType > kDMThingTypeGroup)
537 			_vm->_moveSens->getMoveResult(curThing, mapX, mapY, mapX, mapY);
538 
539 		if (curThingType == kDMThingTypeProjectile) {
540 			Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(curThing);
541 			TimelineEvent *newEvent;
542 			newEvent = &_events[projectile->_eventIndex];
543 			newEvent->_Cu._projectile.setMapX(_vm->_moveSens->_moveResultMapX);
544 			newEvent->_Cu._projectile.setMapY(_vm->_moveSens->_moveResultMapY);
545 			newEvent->_Cu._projectile.setDir((Direction)_vm->_moveSens->_moveResultDir);
546 			newEvent->_Bu._slot = _vm->thingWithNewCell(curThing, _vm->_moveSens->_moveResultCell).toUint16();
547 			_vm->setMap(newEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
548 		} else if (curThingType == kDMThingTypeExplosion) {
549 			TimelineEvent *newEvent = _events;
550 			for (uint16 i = 0; i < _eventMaxCount; newEvent++, i++) {
551 				if ((newEvent->_type == kDMEventTypeExplosion) && (newEvent->_Cu._slot == curThing.toUint16())) { /* BUG0_23 A Fluxcage explosion remains on a square forever. If you open a pit or teleporter on a square where there is a Fluxcage explosion, the Fluxcage explosion is moved but the associated event is not updated (because Fluxcage explosions do not use k25_TMEventTypeExplosion but rather k24_TMEventTypeRemoveFluxcage) causing the Fluxcage explosion to remain in the dungeon forever on its destination square. When the k24_TMEventTypeRemoveFluxcage expires the explosion thing is not removed, but it is marked as unused. Consequently, any objects placed on the Fluxcage square after it was moved but before it expires become orphans upon expiration. After expiration, any object placed on the fluxcage square is cloned when picked up */
552 					newEvent->_Bu._location._mapX = _vm->_moveSens->_moveResultMapX;
553 					newEvent->_Bu._location._mapY = _vm->_moveSens->_moveResultMapY;
554 					newEvent->_Cu._slot = _vm->thingWithNewCell(curThing, _vm->_moveSens->_moveResultCell).toUint16();
555 					_vm->setMap(newEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
556 				}
557 			}
558 		}
559 		curThing = nextThing;
560 	}
561 }
562 
processEventSquareTeleporter(TimelineEvent * event)563 void Timeline::processEventSquareTeleporter(TimelineEvent *event) {
564 	uint16 mapX = event->_Bu._location._mapX;
565 	uint16 mapY = event->_Bu._location._mapY;
566 
567 	byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
568 	if (event->_Cu.A._effect == kDMSensorEffectToggle)
569 		event->_Cu.A._effect = getFlag(*curSquare, kDMSquareMaskTeleporterOpen) ? kDMSensorEffectClear : kDMSensorEffectSet;
570 
571 	if (event->_Cu.A._effect == kDMSensorEffectSet) {
572 		setFlag(*curSquare, kDMSquareMaskTeleporterOpen);
573 		moveTeleporterOrPitSquareThings(mapX, mapY);
574 	} else
575 		clearFlag(*curSquare, kDMSquareMaskTeleporterOpen);
576 }
577 
processEventSquareWall(TimelineEvent * event)578 void Timeline::processEventSquareWall(TimelineEvent *event) {
579 	int16 mapX = event->_Bu._location._mapX;
580 	int16 mapY = event->_Bu._location._mapY;
581 	Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
582 	uint16 curCell = event->_Cu.A._cell;
583 	while (curThing != _vm->_thingEndOfList) {
584 		int16 curThingType = curThing.getType();
585 		if ((curThingType == kDMstringTypeText) && (curThing.getCell() == event->_Cu.A._cell)) {
586 			TextString *textString = (TextString *)_vm->_dungeonMan->getThingData(curThing);
587 			if (event->_Cu.A._effect == kDMSensorEffectToggle)
588 				textString->setVisible(!textString->isVisible());
589 			else
590 				textString->setVisible(event->_Cu.A._effect == kDMSensorEffectSet);
591 		} else if (curThingType == kDMThingTypeSensor) {
592 			Sensor *curThingSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
593 			uint16 curSensorType = curThingSensor->getType();
594 			uint16 curSensorData = curThingSensor->getData();
595 			if (curSensorType == kDMSensorWallCountdown) {
596 				if (curSensorData > 0) {
597 					if (event->_Cu.A._effect == kDMSensorEffectSet) {
598 						if (curSensorData < 511)
599 							curSensorData++;
600 					} else
601 						curSensorData--;
602 
603 					curThingSensor->setData(curSensorData);
604 					if (curThingSensor->getAttrEffectA() == kDMSensorEffectHold) {
605 						int16 triggerSetEffect = ((curSensorData == 0) != curThingSensor->getAttrRevertEffectA());
606 						_vm->_moveSens->triggerEffect(curThingSensor, triggerSetEffect ? kDMSensorEffectSet : kDMSensorEffectClear, mapX, mapY, curCell);
607 					} else if (curSensorData == 0)
608 						_vm->_moveSens->triggerEffect(curThingSensor, (SensorEffect)curThingSensor->getAttrEffectA(), mapX, mapY, curCell);
609 				}
610 			} else if (curSensorType == kDMSensorWallAndOrGate) {
611 				int16 bitMask = 1 << (event->_Cu.A._cell);
612 				if (event->_Cu.A._effect == kDMSensorEffectToggle) {
613 					if (getFlag(curSensorData, bitMask))
614 						clearFlag(curSensorData, bitMask);
615 					else
616 						setFlag(curSensorData, bitMask);
617 				} else if (event->_Cu.A._effect)
618 					clearFlag(curSensorData, bitMask);
619 				else
620 					setFlag(curSensorData, bitMask);
621 
622 				curThingSensor->setData(curSensorData);
623 				bool triggerSetEffect = (Sensor::getDataMask1(curSensorData) == Sensor::getDataMask2(curSensorData)) != curThingSensor->getAttrRevertEffectA();
624 				if (curThingSensor->getAttrEffectA() == kDMSensorEffectHold)
625 					_vm->_moveSens->triggerEffect(curThingSensor, triggerSetEffect ? kDMSensorEffectSet : kDMSensorEffectClear, mapX, mapY, curCell);
626 				else if (triggerSetEffect)
627 					_vm->_moveSens->triggerEffect(curThingSensor, (SensorEffect)curThingSensor->getAttrEffectA(), mapX, mapY, curCell);
628 			} else if ((((curSensorType >= kDMSensorWallSingleProjLauncherNewObj) && (curSensorType <= kDMSensorWallDoubleProjLauncherExplosion)) || (curSensorType == kDMSensorWallSingleProjLauncherSquareObj) || (curSensorType == kDMSensorWallDoubleProjLauncherSquareObj)) && (curThing.getCell() == event->_Cu.A._cell)) {
629 				triggerProjectileLauncher(curThingSensor, event);
630 				if (curThingSensor->getAttrOnlyOnce())
631 					curThingSensor->setTypeDisabled();
632 			} else if (curSensorType == kDMSensorWallEndGame) {
633 				_vm->delay(60 * curThingSensor->getAttrValue());
634 				_vm->_restartGameAllowed = false;
635 				_vm->_gameWon = true;
636 				_vm->endGame(true);
637 			}
638 		}
639 		curThing = _vm->_dungeonMan->getNextThing(curThing);
640 	}
641 	_vm->_moveSens->processRotationEffect();
642 }
643 
triggerProjectileLauncher(Sensor * sensor,TimelineEvent * event)644 void Timeline::triggerProjectileLauncher(Sensor *sensor, TimelineEvent *event) {
645 	int16 mapX = event->_Bu._location._mapX;
646 	int16 mapY = event->_Bu._location._mapY;
647 	uint16 cell = event->_Cu.A._cell;
648 	uint16 projectileCell = _vm->returnOppositeDir((Direction)cell);
649 	int16 sensorType = sensor->getType();
650 	int16 sensorData = sensor->getData();
651 	int16 kineticEnergy = sensor->getActionKineticEnergy();
652 	int16 stepEnergy = sensor->getActionStepEnergy();
653 	bool launchSingleProjectile = (sensorType == kDMSensorWallSingleProjLauncherNewObj) ||
654 		(sensorType == kDMSensorWallSingleProjLauncherExplosion) ||
655 		(sensorType == kDMSensorWallSingleProjLauncherSquareObj);
656 
657 	Thing firstProjectileAssociatedThing;
658 	Thing secondProjectileAssociatedThing;
659 	if ((sensorType == kDMSensorWallSingleProjLauncherExplosion) || (sensorType == kDMSensorWallDoubleProjLauncherExplosion))
660 		firstProjectileAssociatedThing = secondProjectileAssociatedThing = Thing(sensorData + _vm->_thingFirstExplosion.toUint16());
661 	else if ((sensorType == kDMSensorWallSingleProjLauncherSquareObj) || (sensorType == kDMSensorWallDoubleProjLauncherSquareObj)) {
662 		firstProjectileAssociatedThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
663 		while (firstProjectileAssociatedThing != _vm->_thingNone) { /* BUG0_19 The game crashes when an object launcher sensor is triggered. _vm->_none should be _vm->_endOfList. If there are no more objects on the square then this loop may return an undefined value, this can crash the game. In the original DM and CSB dungeons, the number of times that these sensors are triggered is always controlled to be equal to the number of available objects (with a countdown sensor or a number of once only sensors) */
664 			uint16 projectiveThingCell = firstProjectileAssociatedThing.getCell();
665 			if ((firstProjectileAssociatedThing.getType() > kDMThingTypeSensor) && ((projectiveThingCell == cell) || (projectiveThingCell == _vm->turnDirRight(cell))))
666 				break;
667 			firstProjectileAssociatedThing = _vm->_dungeonMan->getNextThing(firstProjectileAssociatedThing);
668 		}
669 		if (firstProjectileAssociatedThing == _vm->_thingNone) /* BUG0_19 The game crashes when an object launcher sensor is triggered. _vm->_none should be _vm->_endOfList */
670 			return;
671 
672 		_vm->_dungeonMan->unlinkThingFromList(firstProjectileAssociatedThing, Thing(0), mapX, mapY); /* The object is removed without triggering any sensor effects */
673 		if (!launchSingleProjectile) {
674 			secondProjectileAssociatedThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
675 			while (secondProjectileAssociatedThing != _vm->_thingNone) { /* BUG0_19 The game crashes when an object launcher sensor is triggered. _vm->_none should be _vm->_endOfList. If there are no more objects on the square then this loop may return an undefined value, this can crash the game */
676 				uint16 projectiveThingCell = secondProjectileAssociatedThing.getCell();
677 				if ((secondProjectileAssociatedThing.getType() > kDMThingTypeSensor) && ((projectiveThingCell == cell) || (projectiveThingCell == _vm->turnDirRight(cell))))
678 					break;
679 				secondProjectileAssociatedThing = _vm->_dungeonMan->getNextThing(secondProjectileAssociatedThing);
680 			}
681 			if (secondProjectileAssociatedThing == _vm->_thingNone) /* BUG0_19 The game crashes when an object launcher sensor is triggered. _vm->_none should be _vm->_endOfList */
682 				launchSingleProjectile = true;
683 			else
684 				_vm->_dungeonMan->unlinkThingFromList(secondProjectileAssociatedThing, _vm->_thingNone, mapX, mapY); /* The object is removed without triggering any sensor effects */
685 		}
686 	} else {
687 		firstProjectileAssociatedThing = _vm->_dungeonMan->getObjForProjectileLaucherOrObjGen(sensorData);
688 		if ((firstProjectileAssociatedThing) == _vm->_thingNone)
689 			return;
690 
691 		secondProjectileAssociatedThing = _vm->_dungeonMan->getObjForProjectileLaucherOrObjGen(sensorData);
692 		if (!launchSingleProjectile && (secondProjectileAssociatedThing == _vm->_thingNone))
693 			launchSingleProjectile = true;
694 	}
695 	if (launchSingleProjectile)
696 		projectileCell = _vm->normalizeModulo4(projectileCell + _vm->getRandomNumber(2));
697 
698 	/* BUG0_20 The game crashes if the launcher sensor is on a map boundary and shoots in a direction outside the map */
699 	mapX += _vm->_dirIntoStepCountEast[cell];
700 	mapY += _vm->_dirIntoStepCountNorth[cell];
701 	_vm->_projexpl->_createLauncherProjectile = true;
702 	_vm->_projexpl->createProjectile(firstProjectileAssociatedThing, mapX, mapY, projectileCell, (Direction)cell, kineticEnergy, 100, stepEnergy);
703 	if (!launchSingleProjectile)
704 		_vm->_projexpl->createProjectile(secondProjectileAssociatedThing, mapX, mapY, _vm->turnDirRight(projectileCell), (Direction)cell, kineticEnergy, 100, stepEnergy);
705 
706 	_vm->_projexpl->_createLauncherProjectile = false;
707 }
708 
processEventSquareCorridor(TimelineEvent * event)709 void Timeline::processEventSquareCorridor(TimelineEvent *event) {
710 	uint16 mapX = event->_Bu._location._mapX;
711 	uint16 mapY = event->_Bu._location._mapY;
712 	Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
713 	while (curThing != _vm->_thingEndOfList) {
714 		int16 curThingType = curThing.getType();
715 		if (curThingType == kDMstringTypeText) {
716 			TextString *textString = (TextString *)_vm->_dungeonMan->getThingData(curThing);
717 			bool textCurrentlyVisible = textString->isVisible();
718 			if (event->_Cu.A._effect == kDMSensorEffectToggle)
719 				textString->setVisible(!textCurrentlyVisible);
720 			else
721 				textString->setVisible((event->_Cu.A._effect == kDMSensorEffectSet));
722 
723 			if (!textCurrentlyVisible && textString->isVisible() && (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY)) {
724 				_vm->_dungeonMan->decodeText(_vm->_stringBuildBuffer, curThing, kDMTextTypeMessage);
725 				_vm->_textMan->printMessage(kDMColorWhite, _vm->_stringBuildBuffer);
726 			}
727 		} else if (curThingType == kDMThingTypeSensor) {
728 			Sensor *curSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
729 			if (curSensor->getType() == kDMSensorFloorGroupGenerator) {
730 				int16 creatureCount = curSensor->getAttrValue();
731 				if (getFlag(creatureCount, kDMMaskRandomizeGeneratedCreatureCount))
732 					creatureCount = _vm->getRandomNumber(getFlag(creatureCount, kDMMaskGeneratedCreatureCount));
733 				else
734 					creatureCount--;
735 
736 				uint16 healthMultiplier = curSensor->getActionHealthMultiplier();
737 				if (healthMultiplier == 0)
738 					healthMultiplier = _vm->_dungeonMan->_currMap->_difficulty;
739 
740 				_vm->_groupMan->groupGetGenerated((CreatureType)curSensor->getData(), healthMultiplier, creatureCount, (Direction)_vm->getRandomNumber(4), mapX, mapY);
741 				if (curSensor->getAttrAudibleA())
742 					_vm->_sound->requestPlay(kDMSoundIndexBuzz, mapX, mapY, kDMSoundModePlayIfPrioritized);
743 
744 				if (curSensor->getAttrOnlyOnce())
745 					curSensor->setTypeDisabled();
746 				else {
747 					uint16 actionTicks = curSensor->getActionTicks();
748 					if (actionTicks != 0) {
749 						curSensor->setTypeDisabled();
750 						if (actionTicks > 127)
751 							actionTicks = (actionTicks - 126) << 6;
752 
753 						TimelineEvent newEvent;
754 						newEvent._type = kDMEventTypeEnableGroupGenerator;
755 						newEvent._mapTime = _vm->setMapAndTime(_vm->_dungeonMan->_currMapIndex, _vm->_gameTime + actionTicks);
756 						newEvent._priority = 0;
757 						newEvent._Bu._location._mapX = mapX;
758 						newEvent._Bu._location._mapY = mapY;
759 						newEvent._Bu._location._mapY = mapY;
760 						addEventGetEventIndex(&newEvent);
761 					}
762 				}
763 			}
764 		}
765 		curThing = _vm->_dungeonMan->getNextThing(curThing);
766 	}
767 }
768 
processEventsMoveGroup(TimelineEvent * event)769 void Timeline::processEventsMoveGroup(TimelineEvent *event) {
770 	bool randomDirectionMoveRetried = false;
771 	uint16 mapX = event->_Bu._location._mapX;
772 	uint16 mapY = event->_Bu._location._mapY;
773 
774 T0252001:
775 	if (((_vm->_dungeonMan->_currMapIndex != _vm->_dungeonMan->_partyMapIndex) || (mapX != _vm->_dungeonMan->_partyMapX) || (mapY != _vm->_dungeonMan->_partyMapY)) && (_vm->_groupMan->groupGetThing(mapX, mapY) == _vm->_thingEndOfList)) { /* BUG0_24 Lord Chaos may teleport into one of the Black Flames and become invisible until the Black Flame is killed. In this case, _vm->_groupMan->f175_groupGetThing returns the Black Flame thing and the Lord Chaos thing is not moved into the dungeon until the Black Flame is killed */
776 		if (event->_type == kDMEventTypeMoveGroupAudible)
777 			_vm->_sound->requestPlay(kDMSoundIndexBuzz, mapX, mapY, kDMSoundModePlayIfPrioritized);
778 
779 		_vm->_moveSens->getMoveResult(Thing(event->_Cu._slot), kDMMapXNotOnASquare, 0, mapX, mapY);
780 	} else {
781 		if (!randomDirectionMoveRetried) {
782 			randomDirectionMoveRetried = true;
783 			Group *group = (Group *)_vm->_dungeonMan->getThingData(Thing(event->_Cu._slot));
784 			if ((group->_type == kDMCreatureTypeLordChaos) && !_vm->getRandomNumber(4)) {
785 				switch (_vm->getRandomNumber(4)) {
786 				case 0:
787 					mapX--;
788 					break;
789 				case 1:
790 					mapX++;
791 					break;
792 				case 2:
793 					mapY--;
794 					break;
795 				case 3:
796 					mapY++;
797 					break;
798 				default:
799 					break;
800 				}
801 				if (_vm->_groupMan->isSquareACorridorTeleporterPitOrDoor(mapX, mapY))
802 					goto T0252001;
803 			}
804 		}
805 		event->_mapTime += 5;
806 		addEventGetEventIndex(event);
807 	}
808 }
809 
procesEventEnableGroupGenerator(TimelineEvent * event)810 void Timeline::procesEventEnableGroupGenerator(TimelineEvent *event) {
811 	Thing curThing = _vm->_dungeonMan->getSquareFirstThing(event->_Bu._location._mapX, event->_Bu._location._mapY);
812 	while (curThing != _vm->_thingNone) {
813 		if ((curThing.getType()) == kDMThingTypeSensor) {
814 			Sensor *curSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
815 			if (curSensor->getType() == kDMSensorDisabled) {
816 				curSensor->setDatAndTypeWithOr(kDMSensorFloorGroupGenerator);
817 				return;
818 			}
819 		}
820 		curThing = _vm->_dungeonMan->getNextThing(curThing);
821 	}
822 }
823 
processEventEnableChampionAction(uint16 champIndex)824 void Timeline::processEventEnableChampionAction(uint16 champIndex) {
825 	Champion *curChampion = &_vm->_championMan->_champions[champIndex];
826 	curChampion->_enableActionEventIndex = -1;
827 	clearFlag(curChampion->_attributes, kDMAttributeDisableAction);
828 	if (curChampion->_actionIndex != kDMActionNone) {
829 		curChampion->_actionDefense -= _actionDefense[curChampion->_actionDefense];
830 	}
831 	if (curChampion->_currHealth) {
832 		if ((curChampion->_actionIndex == kDMActionShoot) && (curChampion->_slots[kDMSlotReadyHand] == _vm->_thingNone)) {
833 			int16 slotIndex = kDMSlotQuiverLine1_1;
834 			if (_vm->_championMan->isAmmunitionCompatibleWithWeapon(champIndex, kDMSlotActionHand, slotIndex))
835 				_vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, _vm->_championMan->getObjectRemovedFromSlot(champIndex, slotIndex), kDMSlotReadyHand);
836 			else {
837 				for (int16 quiverSlotIndex = 0; quiverSlotIndex < 3; quiverSlotIndex++) {
838 					slotIndex = quiverSlotIndex + kDMSlotQuiverLine2_1;
839 					if (_vm->_championMan->isAmmunitionCompatibleWithWeapon(champIndex, kDMSlotActionHand, slotIndex))
840 						_vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, _vm->_championMan->getObjectRemovedFromSlot(champIndex, slotIndex), kDMSlotReadyHand);
841 				}
842 			}
843 		}
844 		setFlag(curChampion->_attributes, kDMAttributeActionHand);
845 		_vm->_championMan->drawChampionState((ChampionIndex)champIndex);
846 	}
847 	curChampion->_actionIndex = kDMActionNone;
848 }
849 
processEventMoveWeaponFromQuiverToSlot(uint16 champIndex,uint16 slotIndex)850 void Timeline::processEventMoveWeaponFromQuiverToSlot(uint16 champIndex, uint16 slotIndex) {
851 	Champion *curChampion = &_vm->_championMan->_champions[champIndex];
852 	if (curChampion->_slots[slotIndex] != _vm->_thingNone)
853 		return;
854 
855 	if (hasWeaponMovedSlot(champIndex, curChampion, kDMSlotQuiverLine1_1, slotIndex))
856 		return;
857 
858 	for (uint16 srcSlotIndex = kDMSlotQuiverLine2_1; srcSlotIndex <= kDMSlotQuiverLine2_2; srcSlotIndex++) {
859 		if (hasWeaponMovedSlot(champIndex, curChampion, srcSlotIndex, slotIndex))
860 			break;
861 	}
862 }
863 
hasWeaponMovedSlot(int16 champIndex,Champion * champ,uint16 sourceSlotIndex,int16 destSlotIndex)864 bool Timeline::hasWeaponMovedSlot(int16 champIndex, Champion *champ, uint16 sourceSlotIndex, int16 destSlotIndex) {
865 	if (Thing(champ->_slots[sourceSlotIndex]).getType() == kDMThingTypeWeapon) {
866 		_vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, _vm->_championMan->getObjectRemovedFromSlot(champIndex, sourceSlotIndex),
867 			(ChampionSlot)destSlotIndex);
868 		return true;
869 	}
870 	return false;
871 }
872 
processEventHideDamageReceived(uint16 champIndex)873 void Timeline::processEventHideDamageReceived(uint16 champIndex) {
874 	InventoryMan &inventory = *_vm->_inventoryMan;
875 	Champion *curChampion = &_vm->_championMan->_champions[champIndex];
876 	curChampion->_hideDamageReceivedIndex = -1;
877 	if (!curChampion->_currHealth)
878 		return;
879 
880 	if (_vm->indexToOrdinal(champIndex) == inventory._inventoryChampionOrdinal) {
881 		_vm->_eventMan->showMouse();
882 		inventory.drawStatusBoxPortrait((ChampionIndex)champIndex);
883 		_vm->_eventMan->hideMouse();
884 	} else {
885 		setFlag(curChampion->_attributes, kDMAttributeNameTitle);
886 		_vm->_championMan->drawChampionState((ChampionIndex)champIndex);
887 	}
888 }
889 
processEventLight(TimelineEvent * event)890 void Timeline::processEventLight(TimelineEvent *event) {
891 	int16 lightPower = event->_Bu._lightPower;
892 	if (lightPower == 0)
893 		return;
894 
895 	bool negativeLightPower = (lightPower < 0);
896 	if (negativeLightPower)
897 		lightPower = -lightPower;
898 
899 	int16 weakerLightPower = lightPower - 1;
900 	int16 lightAmount = _vm->_championMan->_lightPowerToLightAmount[lightPower] - _vm->_championMan->_lightPowerToLightAmount[weakerLightPower];
901 	if (negativeLightPower) {
902 		lightAmount = -lightAmount;
903 		weakerLightPower = -weakerLightPower;
904 	}
905 	_vm->_championMan->_party._magicalLightAmount += lightAmount;
906 	if (weakerLightPower) {
907 		TimelineEvent newEvent;
908 		newEvent._type = kDMEventTypeLight;
909 		newEvent._Bu._lightPower = weakerLightPower;
910 		newEvent._mapTime = _vm->setMapAndTime(_vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 4);
911 		newEvent._priority = 0;
912 		addEventGetEventIndex(&newEvent);
913 	}
914 }
915 
refreshAllChampionStatusBoxes()916 void Timeline::refreshAllChampionStatusBoxes() {
917 	for (uint16 idx = kDMChampionFirst; idx < _vm->_championMan->_partyChampionCount; idx++)
918 		setFlag(_vm->_championMan->_champions[idx]._attributes, kDMAttributeStatusBox);
919 
920 	_vm->_championMan->drawAllChampionStates();
921 }
922 
processEventViAltarRebirth(TimelineEvent * event)923 void Timeline::processEventViAltarRebirth(TimelineEvent *event) {
924 	int16 mapX = event->_Bu._location._mapX;
925 	int16 mapY = event->_Bu._location._mapY;
926 	uint16 cell = event->_Cu.A._cell;
927 	uint16 championIndex = event->_priority;
928 	uint16 rebirthStep = event->_Cu.A._effect;
929 	switch (rebirthStep) { /* Rebirth is a 3 steps process (Step 2 -> Step 1 -> Step 0). Step is stored in the Effect value of the event */
930 	case 2:
931 		_vm->_projexpl->createExplosion(_vm->_thingExplRebirthStep1, 0, mapX, mapY, cell);
932 		event->_mapTime += 5;
933 T0255002:
934 		rebirthStep--;
935 		event->_Cu.A._effect = rebirthStep;
936 		addEventGetEventIndex(event);
937 		break;
938 	case 1: {
939 		Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
940 		while (curThing != _vm->_thingEndOfList) {
941 			if ((curThing.getCell() == cell) && (curThing.getType() == kDMThingTypeJunk)) {
942 				int16 iconIndex = _vm->_objectMan->getIconIndex(curThing);
943 				if (iconIndex == kDMIconIndiceJunkChampionBones) {
944 					Junk *junkData = (Junk *)_vm->_dungeonMan->getThingData(curThing);
945 					if (junkData->getChargeCount() == championIndex) {
946 						_vm->_dungeonMan->unlinkThingFromList(curThing, Thing(0), mapX, mapY); /* BUG0_25 When a champion dies, no bones object is created so it is not possible to bring the champion back to life at an altar of Vi. Each time a champion is brought back to life, the bones object is removed from the dungeon but it is not marked as unused and thus becomes an orphan. After a large number of champion deaths, all JUNK things are exhausted and the game cannot create any more. This also affects the creation of JUNK things dropped by some creatures when they die (Screamer, Rockpile, Magenta Worm, Pain Rat, Red Dragon) */
947 						junkData->setNextThing(_vm->_thingNone);
948 						event->_mapTime += 1;
949 						goto T0255002;
950 					}
951 				}
952 			}
953 			curThing = _vm->_dungeonMan->getNextThing(curThing);
954 		}
955 		}
956 		break;
957 	case 0:
958 		_vm->_championMan->viAltarRebirth(event->_priority);
959 		break;
960 	default:
961 		break;
962 	}
963 }
964 
saveEventsPart(Common::OutSaveFile * file)965 void Timeline::saveEventsPart(Common::OutSaveFile *file) {
966 	for (uint16 i = 0; i < _eventMaxCount; ++i) {
967 		TimelineEvent *event = &_events[i];
968 		file->writeSint32BE(event->_mapTime);
969 		file->writeByte(event->_type);
970 		file->writeByte(event->_priority);
971 		file->writeByte(event->_Bu._location._mapX); // writing bytes of the union I think should preserve the union's identity
972 		file->writeByte(event->_Bu._location._mapY);
973 		file->writeUint16BE(event->_Cu.A._cell); // writing bytes of the union I think should preserve the union's identity
974 		file->writeUint16BE(event->_Cu.A._effect);
975 	}
976 }
977 
saveTimelinePart(Common::OutSaveFile * file)978 void Timeline::saveTimelinePart(Common::OutSaveFile *file) {
979 	for (uint16 i = 0; i < _eventMaxCount; ++i)
980 		file->writeUint16BE(_timeline[i]);
981 }
982 
loadEventsPart(Common::InSaveFile * file)983 void Timeline::loadEventsPart(Common::InSaveFile *file) {
984 	for (uint16 i = 0; i < _eventMaxCount; ++i) {
985 		TimelineEvent *event = &_events[i];
986 		event->_mapTime = file->readSint32BE();
987 		event->_type = (TimelineEventType)file->readByte();
988 		event->_priority = file->readByte();
989 		event->_Bu._location._mapX = file->readByte();
990 		event->_Bu._location._mapY = file->readByte();
991 		event->_Cu.A._cell = file->readUint16BE();
992 		event->_Cu.A._effect = file->readUint16BE();
993 	}
994 }
995 
loadTimelinePart(Common::InSaveFile * file)996 void Timeline::loadTimelinePart(Common::InSaveFile *file) {
997 	for (uint16 i = 0; i < _eventMaxCount; ++i)
998 		_timeline[i] = file->readUint16BE();
999 }
1000 
1001 }
1002