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