/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * Based on the Reverse Engineering work of Christophe Fontanel, * maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/) */ #include "dm/projexpl.h" #include "dm/dungeonman.h" #include "dm/timeline.h" #include "dm/group.h" #include "dm/objectman.h" #include "dm/movesens.h" #include "dm/sounds.h" namespace DM { ProjExpl::ProjExpl(DMEngine *vm) : _vm(vm) { _creatureDamageOutcome = 0; _secondaryDirToOrFromParty = 0; _lastCreatureAttackTime = -200; _createLauncherProjectile = false; _projectilePoisonAttack = 0; _projectileAttackType = 0; _lastPartyMovementTime = 0; } void ProjExpl::createProjectile(Thing thing, int16 mapX, int16 mapY, uint16 cell, Direction dir, byte kineticEnergy, byte attack, byte stepEnergy) { Thing projectileThing = _vm->_dungeonMan->getUnusedThing(kDMThingTypeProjectile); if (projectileThing == Thing::_none) /* BUG0_16 If the game cannot create a projectile thing because it has run out of such things (60 maximum) then the object being thrown/shot/launched is orphaned. If the game has run out of projectile things it will try to remove a projectile from elsewhere in the dungeon, except in an area of 11x11 squares centered around the party (to make sure the player cannot actually see the thing disappear on screen) */ return; projectileThing = _vm->thingWithNewCell(projectileThing, cell); Projectile *projectilePtr = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing); projectilePtr->_slot = thing; projectilePtr->_kineticEnergy = MIN((int16)kineticEnergy, (int16)255); projectilePtr->_attack = attack; _vm->_dungeonMan->linkThingToList(projectileThing, Thing(0), mapX, mapY); /* Projectiles are added on the square and not 'moved' onto the square. In the case of a projectile launcher sensor, this means that the new projectile traverses the square in front of the launcher without any trouble: there is no impact if it is a wall, the projectile direction is not changed if it is a teleporter. Impacts with creatures and champions are still processed */ TimelineEvent newEvent; newEvent._mapTime = _vm->setMapAndTime(_vm->_dungeonMan->_currMapIndex, _vm->_gameTime + 1); if (_createLauncherProjectile) newEvent._type = kDMEventTypeMoveProjectile; /* Launcher projectiles can impact immediately */ else newEvent._type = kDMEventTypeMoveProjectileIgnoreImpacts; /* Projectiles created by champions or creatures ignore impacts on their first movement */ newEvent._priority = 0; newEvent._Bu._slot = projectileThing.toUint16(); newEvent._Cu._projectile.setMapX(mapX); newEvent._Cu._projectile.setMapY(mapY); newEvent._Cu._projectile.setStepEnergy(stepEnergy); newEvent._Cu._projectile.setDir(dir); projectilePtr->_eventIndex = _vm->_timeline->addEventGetEventIndex(&newEvent); } bool ProjExpl::hasProjectileImpactOccurred(int16 impactType, int16 mapXCombo, int16 mapYCombo, int16 cell, Thing projectileThing) { Projectile *projectileThingData = (Projectile *)_vm->_dungeonMan->getThingData(Thing(projectileThing)); bool removePotion = false; int16 potionPower = 0; _creatureDamageOutcome = kDMKillOutcomeNoCreaturesInGroup; Thing projectileAssociatedThing = projectileThingData->_slot; int16 projectileAssociatedThingType = projectileAssociatedThing.getType(); Potion *potion = nullptr; Thing explosionThing = Thing::_none; if (projectileAssociatedThingType == kDMThingTypePotion) { Group *projectileAssociatedGroup = (Group *)_vm->_dungeonMan->getThingData(projectileAssociatedThing); PotionType potionType = ((Potion *)projectileAssociatedGroup)->getType(); if ((potionType == kDMPotionTypeVen) || (potionType == kDMPotionTypeFulBomb)) { explosionThing = (potionType == kDMPotionTypeVen) ? Thing::_explPoisonCloud: Thing::_explFireBall; removePotion = true; potionPower = ((Potion *)projectileAssociatedGroup)->getPower(); potion = (Potion *)projectileAssociatedGroup; } } bool createExplosionOnImpact = (projectileAssociatedThingType == kDMThingTypeExplosion) && (projectileAssociatedThing != Thing::_explSlime) && (projectileAssociatedThing != Thing::_explPoisonBolt); Thing *curGroupSlot = nullptr; int16 projectileMapX; int16 projectileMapY; int16 projectileTargetMapX = mapXCombo; int16 projectileTargetMapY = mapYCombo; if (mapXCombo <= 255) { projectileMapX = mapXCombo; projectileMapY = mapYCombo; } else { projectileMapX = (mapXCombo >> 8) - 1; projectileMapY = (mapYCombo >> 8); projectileTargetMapX &= 0x00FF; projectileTargetMapY &= 0x00FF; } int16 championAttack = 0; int16 attack = 0; int16 championIndex = 0; switch (impactType) { case kDMElementTypeDoor: { byte curSquare = _vm->_dungeonMan->_currMapData[projectileTargetMapX][projectileTargetMapY]; int16 curDoorState = Square(curSquare).getDoorState(); Door *curDoor = (Door *)_vm->_dungeonMan->getSquareFirstThingData(projectileTargetMapX, projectileTargetMapY); if ((curDoorState != kDMDoorStateDestroyed) && (projectileAssociatedThing == Thing::_explOpenDoor)) { if (curDoor->hasButton()) _vm->_moveSens->addEvent(kDMEventTypeDoor, projectileTargetMapX, projectileTargetMapY, kDMCellNorthWest, kDMSensorEffectToggle, _vm->_gameTime + 1); break; } if ((curDoorState == kDMDoorStateDestroyed) || (curDoorState <= kDMDoorStateOneFourth)) return false; DoorInfo curDoorInfo = _vm->_dungeonMan->_currMapDoorInfo[curDoor->getType()]; if (getFlag(curDoorInfo._attributes, kDMMaskDoorInfoProjectilesCanPassThrough)) { if (projectileAssociatedThingType == kDMThingTypeExplosion) { if (projectileAssociatedThing.toUint16() >= Thing::_explHarmNonMaterial.toUint16()) return false; } else { int16 associatedThingIndex = _vm->_dungeonMan->getObjectInfoIndex(projectileAssociatedThing); uint16 associatedAllowedSlots = _vm->_dungeonMan->_objectInfos[associatedThingIndex].getAllowedSlots(); int16 iconIndex = _vm->_objectMan->getIconIndex(projectileAssociatedThing); if ((projectileThingData->_attack > _vm->getRandomNumber(128)) && getFlag(associatedAllowedSlots, kDMMaskPouchPassAndThroughDoors) && ( (projectileAssociatedThingType != kDMThingTypeJunk) || (iconIndex < kDMIconIndiceJunkIronKey) || (iconIndex > kDMIconIndiceJunkMasterKey) )) { return false; } } } attack = getProjectileImpactAttack(projectileThingData, projectileAssociatedThing) + 1; _vm->_groupMan->groupIsDoorDestoryedByAttack(projectileTargetMapX, projectileTargetMapY, attack + _vm->getRandomNumber(attack), false, 0); } break; case kDMElementTypeChampion: championIndex = _vm->_championMan->getIndexInCell(cell); if (championIndex < 0) return false; championAttack = attack = getProjectileImpactAttack(projectileThingData, projectileAssociatedThing); break; case kDMElementTypeCreature: { Group *curGroup = (Group *)_vm->_dungeonMan->getThingData(_vm->_groupMan->groupGetThing(projectileTargetMapX, projectileTargetMapY)); uint16 curCreatureIndex = _vm->_groupMan->getCreatureOrdinalInCell(curGroup, cell); if (!curCreatureIndex) return false; curCreatureIndex--; CreatureType curCreatureType = curGroup->_type; CreatureInfo *curCreatureInfo = &_vm->_dungeonMan->_creatureInfos[curCreatureType]; if ((projectileAssociatedThing == Thing::_explFireBall) && (curCreatureType == kDMCreatureTypeBlackFlame)) { uint16 *curCreatureHealth = &curGroup->_health[curCreatureIndex]; *curCreatureHealth = MIN(1000, *curCreatureHealth + getProjectileImpactAttack(projectileThingData, projectileAssociatedThing)); goto T0217044; } if (getFlag(curCreatureInfo->_attributes, kDMCreatureMaskNonMaterial) && (projectileAssociatedThing != Thing::_explHarmNonMaterial)) return false; attack = (uint16)((unsigned long)getProjectileImpactAttack(projectileThingData, projectileAssociatedThing) << 6) / curCreatureInfo->_defense; if (attack) { int16 outcome = _vm->_groupMan->groupGetDamageCreatureOutcome(curGroup, curCreatureIndex, projectileTargetMapX, projectileTargetMapY, attack + _vm->_groupMan->groupGetResistanceAdjustedPoisonAttack(curCreatureType, _projectilePoisonAttack), true); if (outcome != kDMKillOutcomeNoCreaturesInGroup) _vm->_groupMan->processEvents29to41(projectileTargetMapX, projectileTargetMapY, kDMEventTypeCreateReactionHitByProjectile, 0); _creatureDamageOutcome = outcome; if (!createExplosionOnImpact && (outcome == kDMKillOutcomeNoCreaturesInGroup) && (projectileAssociatedThingType == kDMThingTypeWeapon) && getFlag(curCreatureInfo->_attributes, kDMCreatureMaskKeepThrownSharpWeapon)) { Weapon *weapon = (Weapon *)_vm->_dungeonMan->getThingData(projectileAssociatedThing); WeaponType weaponType = weapon->getType(); if ((weaponType == kDMWeaponDagger) || (weaponType == kDMWeaponArrow) || (weaponType == kDMWeaponSlayer) || (weaponType == kDMWeaponPoisonDart) || (weaponType == kDMWeaponThrowingStar)) curGroupSlot = &curGroup->_slot; } } } break; } if (championAttack && _projectilePoisonAttack && _vm->getRandomNumber(2) && _vm->_championMan->addPendingDamageAndWounds_getDamage(championIndex, attack, kDMWoundHead | kDMWoundTorso, _projectileAttackType)) _vm->_championMan->championPoison(championIndex, _projectilePoisonAttack); if (createExplosionOnImpact || removePotion) { uint16 explosionAttack; if (removePotion) { projectileAssociatedThing = explosionThing; explosionAttack = potionPower; } else { explosionAttack = projectileThingData->_kineticEnergy; } if ((projectileAssociatedThing == Thing::_explLightningBolt) && !(explosionAttack >>= 1)) goto T0217044; createExplosion(projectileAssociatedThing, explosionAttack, mapXCombo, mapYCombo, (projectileAssociatedThing == Thing::_explPoisonCloud) ? (uint16)kDMCreatureTypeSingleCenteredCreature : cell); } else { uint16 soundIndex; if ((projectileAssociatedThing).getType() == kDMThingTypeWeapon) soundIndex = kDMSoundIndexMetallicThud; else if (projectileAssociatedThing == Thing::_explPoisonBolt) soundIndex = kDMSoundIndexSpell; else soundIndex = kDMSoundIndexWoodenThudAttackTrolinAntmanStoneGolem; _vm->_sound->requestPlay(soundIndex, projectileMapX, projectileMapY, kDMSoundModePlayIfPrioritized); } T0217044: if (removePotion) { potion->_nextThing = Thing::_none; projectileThingData->_slot = explosionThing; } _vm->_dungeonMan->unlinkThingFromList(projectileThing, Thing(0), projectileMapX, projectileMapY); projectileDelete(projectileThing, curGroupSlot, projectileMapX, projectileMapY); return true; } uint16 ProjExpl::getProjectileImpactAttack(Projectile *projectile, Thing thing) { _projectilePoisonAttack = 0; _projectileAttackType = kDMAttackTypeBlunt; uint16 kineticEnergy = projectile->_kineticEnergy; ThingType thingType = thing.getType(); uint16 attack; if (thingType != kDMThingTypeExplosion) { if (thingType == kDMThingTypeWeapon) { WeaponInfo *weaponInfo = _vm->_dungeonMan->getWeaponInfo(thing); attack = weaponInfo->_kineticEnergy; _projectileAttackType = kDMAttackTypeBlunt; } else attack = _vm->getRandomNumber(4); attack += _vm->_dungeonMan->getObjectWeight(thing) >> 1; } else if (thing == Thing::_explSlime) { attack = _vm->getRandomNumber(16); _projectilePoisonAttack = attack + 10; attack += _vm->getRandomNumber(32); } else { if (thing.toUint16() >= Thing::_explHarmNonMaterial.toUint16()) { _projectileAttackType = kDMAttackTypeMagic; if (thing == Thing::_explPoisonBolt) { _projectilePoisonAttack = kineticEnergy; return 1; } return 0; } _projectileAttackType = kDMAttackTypeFire; attack = _vm->getRandomNumber(16) + _vm->getRandomNumber(16) + 10; if (thing == Thing::_explLightningBolt) { _projectileAttackType = kDMAttackTypeLightning; attack *= 5; } } attack = ((attack + kineticEnergy) >> 4) + 1; attack += _vm->getRandomNumber((attack >> 1) + 1) + _vm->getRandomNumber(4); attack = MAX(attack >> 1, attack - (32 - (projectile->_attack >> 3))); return attack; } void ProjExpl::createExplosion(Thing explThing, uint16 attack, uint16 mapXCombo, uint16 mapYCombo, uint16 cell) { Thing unusedThing = _vm->_dungeonMan->getUnusedThing(kDMThingTypeExplosion); if (unusedThing == Thing::_none) return; Explosion *explosion = &((Explosion *)_vm->_dungeonMan->_thingData[kDMThingTypeExplosion])[(unusedThing).getIndex()]; int16 projectileTargetMapX; int16 projectileTargetMapY; uint16 projectileMapX = mapXCombo; uint16 projectileMapY = mapYCombo; if (mapXCombo <= 255) { projectileTargetMapX = mapXCombo; projectileTargetMapY = mapYCombo; } else { projectileTargetMapX = mapXCombo & 0x00FF; projectileTargetMapY = mapYCombo & 0x00FF; projectileMapX >>= 8; projectileMapX--; projectileMapY >>= 8; } if (cell == kDMCreatureTypeSingleCenteredCreature) explosion->setCentered(true); else { explosion->setCentered(false); unusedThing = _vm->thingWithNewCell(unusedThing, cell); } explosion->setType(explThing.toUint16() - Thing::_firstExplosion.toUint16()); explosion->setAttack(attack); if (explThing.toUint16() < Thing::_explHarmNonMaterial.toUint16()) { uint16 soundIndex = (attack > 80) ? kDMSoundIndexStrongExplosion : kDMSoundIndexWeakExplosion; _vm->_sound->requestPlay(soundIndex, projectileMapX, projectileMapY, kDMSoundModePlayIfPrioritized); } else if (explThing != Thing::_explSmoke) _vm->_sound->requestPlay(kDMSoundIndexSpell, projectileMapX, projectileMapY, kDMSoundModePlayIfPrioritized); _vm->_dungeonMan->linkThingToList(unusedThing, Thing(0), projectileMapX, projectileMapY); TimelineEvent newEvent; newEvent._mapTime = _vm->setMapAndTime(_vm->_dungeonMan->_currMapIndex, _vm->_gameTime + ((explThing == Thing::_explRebirthStep1) ? 5 : 1)); newEvent._type = kDMEventTypeExplosion; newEvent._priority = 0; newEvent._Cu._slot = unusedThing.toUint16(); newEvent._Bu._location._mapX = projectileMapX; newEvent._Bu._location._mapY = projectileMapY; _vm->_timeline->addEventGetEventIndex(&newEvent); if ((explThing == Thing::_explLightningBolt) || (explThing == Thing::_explFireBall)) { projectileMapX = projectileTargetMapX; projectileMapY = projectileTargetMapY; attack = (attack >> 1) + 1; attack += _vm->getRandomNumber(attack) + 1; if ((explThing == Thing::_explFireBall) || (attack >>= 1)) { if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (projectileMapX == _vm->_dungeonMan->_partyMapX) && (projectileMapY == _vm->_dungeonMan->_partyMapY)) { int16 wounds = kDMWoundReadHand | kDMWoundActionHand | kDMWoundHead | kDMWoundTorso | kDMWoundLegs | kDMWoundFeet; _vm->_championMan->getDamagedChampionCount(attack, wounds, kDMAttackTypeFire); } else { unusedThing = _vm->_groupMan->groupGetThing(projectileMapX, projectileMapY); if (unusedThing != Thing::_endOfList) { Group *creatureGroup = (Group *)_vm->_dungeonMan->getThingData(unusedThing); CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[creatureGroup->_type]; int16 creatureFireResistance = creatureInfo->getFireResistance(); if (creatureFireResistance != kDMImmuneToFire) { if (getFlag(creatureInfo->_attributes, kDMCreatureMaskNonMaterial)) attack >>= 2; if ((attack -= _vm->getRandomNumber((creatureFireResistance << 1) + 1)) > 0) _creatureDamageOutcome = _vm->_groupMan->getDamageAllCreaturesOutcome(creatureGroup, projectileMapX, projectileMapY, attack, true); } } } } } } int16 ProjExpl::projectileGetImpactCount(int16 impactType, int16 mapX, int16 mapY, int16 cell) { int16 impactCount = 0; _creatureDamageOutcome = kDMKillOutcomeNoCreaturesInGroup; for (Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY); curThing != Thing::_endOfList; ) { if (((curThing).getType() == kDMThingTypeProjectile) && ((curThing).getCell() == cell) && hasProjectileImpactOccurred(impactType, mapX, mapY, cell, curThing)) { projectileDeleteEvent(curThing); impactCount++; if ((impactType == kDMElementTypeCreature) && (_creatureDamageOutcome == kDMKillOutcomeAllCreaturesInGroup)) break; curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY); } else curThing = _vm->_dungeonMan->getNextThing(curThing); } return impactCount; } void ProjExpl::projectileDeleteEvent(Thing thing) { Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(thing); _vm->_timeline->deleteEvent(projectile->_eventIndex); } void ProjExpl::projectileDelete(Thing projectileThing, Thing *groupSlot, int16 mapX, int16 mapY) { Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing); Thing projectileSlotThing = projectile->_slot; if (projectileSlotThing.getType() != kDMThingTypeExplosion) { if (groupSlot != NULL) { Thing previousThing = *groupSlot; if (previousThing == Thing::_endOfList) { Thing *genericThing = (Thing *)_vm->_dungeonMan->getThingData(projectileSlotThing); *genericThing = Thing::_endOfList; *groupSlot = projectileSlotThing; } else _vm->_dungeonMan->linkThingToList(projectileSlotThing, previousThing, kDMMapXNotOnASquare, 0); } else _vm->_moveSens->getMoveResult(Thing((projectileSlotThing).getTypeAndIndex() | getFlag(projectileThing.toUint16(), 0xC)), -2, 0, mapX, mapY); } projectile->_nextThing = Thing::_none; } void ProjExpl::processEvents48To49(TimelineEvent *event) { int16 sourceMapX = -1; int16 sourceMapY = -1; TimelineEvent firstEvent = *event; TimelineEvent *curEvent = &firstEvent; Thing projectileThingNewCell = Thing(curEvent->_Bu._slot); Thing projectileThing = projectileThingNewCell; Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing); int16 destinationMapX = curEvent->_Cu._projectile.getMapX(); int16 destinationMapY = curEvent->_Cu._projectile.getMapY(); if (curEvent->_type == kDMEventTypeMoveProjectileIgnoreImpacts) curEvent->_type = kDMEventTypeMoveProjectile; else { uint16 projectileCurCell = projectileThingNewCell.getCell(); if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (destinationMapX == _vm->_dungeonMan->_partyMapX) && (destinationMapY == _vm->_dungeonMan->_partyMapY) && hasProjectileImpactOccurred(kDMElementTypeChampion, destinationMapX, destinationMapY, projectileCurCell, projectileThingNewCell)) return; if ((_vm->_groupMan->groupGetThing(destinationMapX, destinationMapY) != Thing::_endOfList) && hasProjectileImpactOccurred(kDMElementTypeCreature, destinationMapX, destinationMapY, projectileCurCell, projectileThing)) return; uint16 stepEnergy = curEvent->_Cu._projectile.getStepEnergy(); if (projectile->_kineticEnergy <= stepEnergy) { _vm->_dungeonMan->unlinkThingFromList(projectileThingNewCell = projectileThing, Thing(0), destinationMapX, destinationMapY); projectileDelete(projectileThingNewCell, NULL, destinationMapX, destinationMapY); return; } projectile->_kineticEnergy -= stepEnergy; if (projectile->_attack < stepEnergy) projectile->_attack = 0; else projectile->_attack -= stepEnergy; } uint16 projectileDirection = curEvent->_Cu._projectile.getDir(); projectileThingNewCell = Thing(curEvent->_Bu._slot); uint16 projectileNewCell = projectileThingNewCell.getCell(); bool projectileMovesToOtherSquare = (projectileDirection == projectileNewCell) || (_vm->turnDirRight(projectileDirection) == projectileNewCell); if (projectileMovesToOtherSquare) { sourceMapX = destinationMapX; sourceMapY = destinationMapY; destinationMapX += _vm->_dirIntoStepCountEast[projectileDirection], destinationMapY += _vm->_dirIntoStepCountNorth[projectileDirection]; Square destSquare = _vm->_dungeonMan->getSquare(destinationMapX, destinationMapY); ElementType destSquareType = destSquare.getType(); if ((destSquareType == kDMElementTypeWall) || ((destSquareType == kDMElementTypeFakeWall) && !getFlag(destSquare.toByte(), (kDMSquareMaskFakeWallImaginary | kDMSquareMaskFakeWallOpen))) || ((destSquareType == kDMElementTypeStairs) && (Square(_vm->_dungeonMan->_currMapData[sourceMapX][sourceMapY]).getType() == kDMElementTypeStairs))) { if (hasProjectileImpactOccurred(destSquare.getType(), sourceMapX, sourceMapY, projectileNewCell, projectileThingNewCell)) { return; } } } if ((projectileDirection & 0x0001) == (projectileNewCell & 0x0001)) projectileNewCell--; else projectileNewCell++; projectileThingNewCell = _vm->thingWithNewCell(projectileThingNewCell, projectileNewCell &= 0x0003); if (projectileMovesToOtherSquare) { _vm->_moveSens->getMoveResult(projectileThingNewCell, sourceMapX, sourceMapY, destinationMapX, destinationMapY); curEvent->_Cu._projectile.setMapX(_vm->_moveSens->_moveResultMapX); curEvent->_Cu._projectile.setMapY(_vm->_moveSens->_moveResultMapY); curEvent->_Cu._projectile.setDir((Direction)_vm->_moveSens->_moveResultDir); projectileThingNewCell = _vm->thingWithNewCell(projectileThingNewCell, _vm->_moveSens->_moveResultCell); _vm->setMap(curEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex); } else { if ((Square(_vm->_dungeonMan->getSquare(destinationMapX, destinationMapY)).getType() == kDMElementTypeDoor) && hasProjectileImpactOccurred(kDMElementTypeDoor, destinationMapX, destinationMapY, projectileNewCell, projectileThing)) return; _vm->_dungeonMan->unlinkThingFromList(projectileThingNewCell, Thing(0), destinationMapX, destinationMapY); _vm->_dungeonMan->linkThingToList(projectileThingNewCell, Thing(0), destinationMapX, destinationMapY); } // This code is from CSB20. The projectiles move at the same speed on all maps instead of moving slower on maps other than the party map */ curEvent->_mapTime++; curEvent->_Bu._slot = projectileThingNewCell.toUint16(); projectile->_eventIndex = _vm->_timeline->addEventGetEventIndex(curEvent); } void ProjExpl::processEvent25(TimelineEvent *event) { uint16 mapX = event->_Bu._location._mapX; uint16 mapY = event->_Bu._location._mapY; Explosion *explosion = &((Explosion *)_vm->_dungeonMan->_thingData[kDMThingTypeExplosion])[Thing((event->_Cu._slot)).getIndex()]; int16 curSquareType = Square(_vm->_dungeonMan->_currMapData[mapX][mapY]).getType(); bool explosionOnPartySquare = (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY); Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY); Group *group = nullptr; CreatureInfo *creatureInfo = nullptr; CreatureType creatureType; if (groupThing != Thing::_endOfList) { group = (Group *)_vm->_dungeonMan->getThingData(groupThing); creatureType = group->_type; creatureInfo = &_vm->_dungeonMan->_creatureInfos[creatureType]; } Thing explosionThing = Thing(Thing::_firstExplosion.toUint16() + explosion->getType()); int16 attack; if (explosionThing == Thing::_explPoisonCloud) attack = MAX(1, MIN(explosion->getAttack() >> 5, 4) + _vm->getRandomNumber(2)); /* Value between 1 and 5 */ else { attack = (explosion->getAttack() >> 1) + 1; attack += _vm->getRandomNumber(attack) + 1; } bool AddEventFl = false; switch (explosionThing.toUint16()) { case 0xFF82: if (!(attack >>= 1)) break; case 0xFF80: if (curSquareType == kDMElementTypeDoor) _vm->_groupMan->groupIsDoorDestoryedByAttack(mapX, mapY, attack, true, 0); break; case 0xFF83: if ((groupThing != Thing::_endOfList) && getFlag(creatureInfo->_attributes, kDMCreatureMaskNonMaterial)) { if ((creatureType == kDMCreatureTypeMaterializerZytaz) && (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex)) { int16 nonMaterialAdditionalAttack = attack >> 3; attack -= nonMaterialAdditionalAttack; nonMaterialAdditionalAttack <<= 1; nonMaterialAdditionalAttack++; int16 creatureCount = group->getCount(); do { if (getFlag(_vm->_groupMan->_activeGroups[group->getActiveGroupIndex()]._aspect[creatureCount], kDMAspectMaskActiveGroupIsAttacking)) /* Materializer / Zytaz can only be damaged while they are attacking */ _vm->_groupMan->groupGetDamageCreatureOutcome(group, creatureCount, mapX, mapY, attack + _vm->getRandomNumber(nonMaterialAdditionalAttack) + _vm->getRandomNumber(4), true); } while (--creatureCount >= 0); } else _vm->_groupMan->getDamageAllCreaturesOutcome(group, mapX, mapY, attack, true); } break; case 0xFFE4: explosion->setType(explosion->getType() + 1); _vm->_sound->requestPlay(kDMSoundIndexStrongExplosion, mapX, mapY, kDMSoundModePlayIfPrioritized); AddEventFl = true; break; case 0xFFA8: if (explosion->getAttack() > 55) { explosion->setAttack(explosion->getAttack() - 40); AddEventFl = true; } break; case 0xFF87: if (explosionOnPartySquare) _vm->_championMan->getDamagedChampionCount(attack, kDMWoundNone, kDMAttackTypeNormal); else if ((groupThing != Thing::_endOfList) && (attack = _vm->_groupMan->groupGetResistanceAdjustedPoisonAttack(creatureType, attack)) && (_vm->_groupMan->getDamageAllCreaturesOutcome(group, mapX, mapY, attack, true) != kDMKillOutcomeAllCreaturesInGroup) && (attack > 2)) { _vm->_groupMan->processEvents29to41(mapX, mapY, kDMEventTypeCreateReactionDangerOnSquare, 0); } if (explosion->getAttack() >= 6) { explosion->setAttack(explosion->getAttack() - 3); AddEventFl = true; } break; } if (AddEventFl) { TimelineEvent newEvent; newEvent = *event; newEvent._mapTime++; _vm->_timeline->addEventGetEventIndex(&newEvent); } else { _vm->_dungeonMan->unlinkThingFromList(Thing(event->_Cu._slot), Thing(0), mapX, mapY); explosion->setNextThing(Thing::_none); } } }