diff --git a/source/games/blood/src/actor.cpp b/source/games/blood/src/actor.cpp index 18631a889..4e56ca8ea 100644 --- a/source/games/blood/src/actor.cpp +++ b/source/games/blood/src/actor.cpp @@ -4193,8 +4193,14 @@ static void actTouchFloor(DBloodActor* actor, int nSector) XSECTOR* pXSector = nullptr; if (pSector->extra > 0) pXSector = &xsector[pSector->extra]; - if (pXSector && (pSector->type == kSectorDamage || pXSector->damageType > 0)) - { + bool doDamage = (pXSector && (pSector->type == kSectorDamage || pXSector->damageType > 0)); + // don't allow damage for damage sectors if they are not enabled + #ifdef NOONE_EXTENSIONS + if (gModernMap && doDamage && pSector->type == kSectorDamage && !pXSector->state) + doDamage = false; + #endif + + if (doDamage) { DAMAGE_TYPE nDamageType; if (pSector->type == kSectorDamage) nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType, DAMAGE_TYPE_0, DAMAGE_TYPE_6); else nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType - 1, DAMAGE_TYPE_0, DAMAGE_TYPE_6); diff --git a/source/games/blood/src/ai.cpp b/source/games/blood/src/ai.cpp index b204e7959..c1e6b6bd2 100644 --- a/source/games/blood/src/ai.cpp +++ b/source/games/blood/src/ai.cpp @@ -893,13 +893,126 @@ int aiDamageSprite(DBloodActor* source, DBloodActor* actor, DAMAGE_TYPE nDmgType { spritetype *pSource = &source->s(); int nSource = pSource->index; - if (pSprite == pSource) - return 0; - if (pXSprite->target == -1 || (nSource != pXSprite->target && Chance(pSprite->type == pSource->type ? nDamage*pDudeInfo->changeTargetKin : nDamage*pDudeInfo->changeTarget))) + if (pSprite == pSource) return 0; + else if (pXSprite->target == -1 || (nSource != pXSprite->target && Chance(pSprite->type == pSource->type ? nDamage*pDudeInfo->changeTargetKin : nDamage*pDudeInfo->changeTarget))) { aiSetTarget(pXSprite, nSource); aiActivateDude(&bloodActors[pXSprite->reference]); } + + #ifdef NOONE_EXTENSIONS + if (gModernMap) { + + // for enemies in patrol mode + if (aiInPatrolState(pXSprite->aiState)) { + + aiPatrolStop(pSprite, pSource->index, pXSprite->dudeAmbush); + + PLAYER* pPlayer = getPlayerById(pSource->type); + if (!pPlayer) return nDamage; + if (powerupCheck(pPlayer, kPwUpShadowCloak)) pPlayer->pwUpTime[kPwUpShadowCloak] = 0; + if (readyForCrit(pSource, pSprite)) { + nDamage += aiDamageSprite(pSprite, pXSprite, pSource->index, nDmgType, nDamage * (10 - gGameOptions.nDifficulty)); + if (pXSprite->health > 0) { + int fullHp = (pXSprite->sysData2 > 0) ? ClipRange(pXSprite->sysData2 << 4, 1, 65535) : getDudeInfo(pSprite->type)->startHealth << 4; + if (((100 * pXSprite->health) / fullHp) <= 75) { + cumulDamage[pSprite->extra] += nDamage << 4; // to be sure any enemy will play the recoil animation + RecoilDude(pSprite, pXSprite); + } + } + + consoleSysMsg("Player #%d does the critical damage to patrol dude #%d!", pPlayer->nPlayer + 1, pSprite->index); + } + + return nDamage; + } + + if (pSprite->type == kDudeModernCustomBurning) { + + if (Chance(0x2000) && gDudeExtra[pSprite->extra].at0 < (int)gFrameClock) { + playGenDudeSound(pSprite, kGenDudeSndBurning); + gDudeExtra[pSprite->extra].at0 = (int)gFrameClock + 360; + } + + if (pXSprite->burnTime == 0) pXSprite->burnTime = 2400; + if (spriteIsUnderwater(pSprite, false)) { + pSprite->type = kDudeModernCustom; + pXSprite->burnTime = 0; + pXSprite->health = 1; // so it can be killed with flame weapons while underwater and if already was burning dude before. + aiGenDudeNewState(pSprite, &genDudeGotoW); + } + + return nDamage; + + } + + if (pSprite->type == kDudeModernCustom) { + + GENDUDEEXTRA* pExtra = genDudeExtra(pSprite); + if (nDmgType == DAMAGE_TYPE_1) { + + if (pXSprite->health > pDudeInfo->fleeHealth) return nDamage; + else if (pXSprite->txID <= 0 || getNextIncarnation(pXSprite) == NULL) { + removeDudeStuff(pSprite); + + if (pExtra->weaponType == kGenDudeWeaponKamikaze) + doExplosion(pSprite, pXSprite->data1 - kTrapExploder); + + if (spriteIsUnderwater(pSprite)) { + pXSprite->health = 0; + return nDamage; + } + + if (pXSprite->burnTime <= 0) + pXSprite->burnTime = 1200; + + if (pExtra->canBurn && pExtra->availDeaths[DAMAGE_TYPE_1] > 0) { + + aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1); + playGenDudeSound(pSprite, kGenDudeSndBurning); + pSprite->type = kDudeModernCustomBurning; + + if (pXSprite->data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation + pSprite->pal = 0; + + aiGenDudeNewState(pSprite, &genDudeBurnGoto); + actHealDude(pXSprite, dudeInfo[55].startHealth, dudeInfo[55].startHealth); + gDudeExtra[pSprite->extra].at0 = (int)gFrameClock + 360; + evKill(nSprite, 3, kCallbackFXFlameLick); + + } + + } else { + actKillDude(nSource, pSprite, DAMAGE_TYPE_0, 65535); + } + + } else if (canWalk(pSprite) && !inDodge(pXSprite->aiState) && !inRecoil(pXSprite->aiState)) { + + if (!dudeIsMelee(pXSprite)) { + if (inIdle(pXSprite->aiState) || Chance(getDodgeChance(pSprite))) { + if (!spriteIsUnderwater(pSprite)) { + if (!canDuck(pSprite) || !sub_5BDA8(pSprite, 14)) aiGenDudeNewState(pSprite, &genDudeDodgeShortL); + else aiGenDudeNewState(pSprite, &genDudeDodgeShortD); + + if (Chance(0x0200)) + playGenDudeSound(pSprite, kGenDudeSndGotHit); + + } else if (sub_5BDA8(pSprite, 13)) { + aiGenDudeNewState(pSprite, &genDudeDodgeShortW); + } + } + } else if (Chance(0x0200)) { + playGenDudeSound(pSprite, kGenDudeSndGotHit); + } + + } + + return nDamage; + + } + } + #endif + if (nDmgType == DAMAGE_TYPE_6) { DUDEEXTRA *pDudeExtra = &gDudeExtra[pSprite->extra]; @@ -985,79 +1098,6 @@ int aiDamageSprite(DBloodActor* source, DBloodActor* actor, DAMAGE_TYPE nDmgType evKill(nSprite, 3, kCallbackFXFlameLick); } break; -#ifdef NOONE_EXTENSIONS - case kDudeModernCustomBurning: - if (Chance(0x2000) && gDudeExtra[pSprite->extra].time < PlayClock) { - playGenDudeSound(pSprite, kGenDudeSndBurning); - gDudeExtra[pSprite->extra].time = PlayClock + 360; - } - if (pXSprite->burnTime == 0) pXSprite->burnTime = 2400; - if (spriteIsUnderwater(pSprite, false)) { - pSprite->type = kDudeModernCustom; - pXSprite->burnTime = 0; - pXSprite->health = 1; // so it can be killed with flame weapons while underwater and if already was burning dude before. - aiGenDudeNewState(pSprite, &genDudeGotoW); - } - break; - case kDudeModernCustom: { - GENDUDEEXTRA* pExtra = genDudeExtra(pSprite); - - if (nDmgType == DAMAGE_TYPE_1) { - if (pXSprite->health > (unsigned)pDudeInfo->fleeHealth) break; - else if (pXSprite->txID <= 0 || getNextIncarnation(pXSprite) == NULL) { - removeDudeStuff(pSprite); - - if (pExtra->weaponType == kGenDudeWeaponKamikaze) - doExplosion(pSprite, pXSprite->data1 - kTrapExploder); - - if (spriteIsUnderwater(pSprite, false)) { - pXSprite->health = 0; - break; - } - - if (pXSprite->burnTime <= 0) - pXSprite->burnTime = 1200; - - if (pExtra->canBurn && pExtra->availDeaths[DAMAGE_TYPE_1] > 0) { - - aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1); - playGenDudeSound(pSprite, kGenDudeSndBurning); - pSprite->type = kDudeModernCustomBurning; - - if (pXSprite->data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation - pSprite->pal = 0; - - aiGenDudeNewState(pSprite, &genDudeBurnGoto); - actHealDude(pXSprite, dudeInfo[55].startHealth, dudeInfo[55].startHealth); - gDudeExtra[pSprite->extra].time = PlayClock + 360; - evKill(nSprite, 3, kCallbackFXFlameLick); - - } - - } else { - actKillDude(nSource, pSprite, DAMAGE_TYPE_0, 65535); - } - } else if (canWalk(pSprite) && !inDodge(pXSprite->aiState) && !inRecoil(pXSprite->aiState)) { - if (!dudeIsMelee(pXSprite)) { - if (inIdle(pXSprite->aiState) || Chance(getDodgeChance(pSprite))) { - if (!spriteIsUnderwater(pSprite, false)) { - if (!canDuck(pSprite) || !dudeIsPlayingSeq(pSprite, 14)) aiGenDudeNewState(pSprite, &genDudeDodgeShortL); - else aiGenDudeNewState(pSprite, &genDudeDodgeShortD); - - if (Chance(0x0200)) - playGenDudeSound(pSprite, kGenDudeSndGotHit); - - } else if (dudeIsPlayingSeq(pSprite, 13)) { - aiGenDudeNewState(pSprite, &genDudeDodgeShortW); - } - } - } else if (Chance(0x0200)) { - playGenDudeSound(pSprite, kGenDudeSndGotHit); - } - } - break; - } -#endif case kDudeCultistBeast: if (pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth) { @@ -1515,17 +1555,25 @@ void aiInitSprite(spritetype *pSprite) DUDEEXTRA *pDudeExtra = &gDudeExtra[pSprite->extra]; pDudeExtra->recoil = 0; pDudeExtra->time = 0; + + #ifdef NOONE_EXTENSIONS + int stateTimer = -1, targetMarker = -1; + int targetX = 0, targetY = 0, targetZ = 0; + + // dude patrol init + if (gModernMap) { + + // must keep it in case of loading save + if (pXSprite->dudeFlag4 && spriRangeIsFine(pXSprite->target) && sprite[pXSprite->target].type == kMarkerPath) { + stateTimer = pXSprite->stateTimer; targetMarker = pXSprite->target; + targetX = pXSprite->targetX; targetY = pXSprite->targetY; + targetZ = pXSprite->targetZ; + } + + } + #endif switch (pSprite->type) { - #ifdef NOONE_EXTENSIONS - case kDudeModernCustom: - aiGenDudeInitSprite(pSprite, pXSprite); - genDudePrepare(pSprite, kGenDudePropertyAll); - return; - case kDudeModernCustomBurning: - aiGenDudeInitSprite(pSprite, pXSprite); - return; - #endif case kDudeCultistTommy: case kDudeCultistShotgun: case kDudeCultistTesla: @@ -1739,6 +1787,45 @@ void aiInitSprite(spritetype *pSprite) pSprite->flags = 15; break; } + + #ifdef NOONE_EXTENSIONS + if (gModernMap) { + + if (pSprite->type == kDudeModernCustom) { + aiGenDudeInitSprite(pSprite, pXSprite); + genDudePrepare(pSprite, kGenDudePropertyAll); + } + + if (pXSprite->dudeFlag4) { + + // restore dude's path + if (spriRangeIsFine(targetMarker)) { + pXSprite->target = targetMarker; + pXSprite->targetX = targetX; + pXSprite->targetY = targetY; + pXSprite->targetZ = targetZ; + } + + // reset target spot progress + pXSprite->data3 = 0; + + // make dude follow the markers + bool uwater = spriteIsUnderwater(pSprite); + if (stateTimer > 0) { + if (uwater) aiPatrolState(pSprite, kAiStatePatrolWaitW); + else if (pXSprite->unused2) aiPatrolState(pSprite, kAiStatePatrolWaitC); + else aiPatrolState(pSprite, kAiStatePatrolWaitL); + pXSprite->stateTimer = stateTimer; // restore state timer + } + else if (uwater) aiPatrolState(pSprite, kAiStatePatrolMoveW); + else if (pXSprite->unused2) aiPatrolState(pSprite, kAiStatePatrolMoveC); + else aiPatrolState(pSprite, kAiStatePatrolMoveL); + + } + + } + #endif + } //--------------------------------------------------------------------------- diff --git a/source/games/blood/src/callback.cpp b/source/games/blood/src/callback.cpp index c1621d09f..44a81d23f 100644 --- a/source/games/blood/src/callback.cpp +++ b/source/games/blood/src/callback.cpp @@ -251,6 +251,7 @@ void Respawn(int nSprite) // 9 #ifdef NOONE_EXTENSIONS if (!gModernMap || pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4; else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535); + switch (pSprite->type) { default: pSprite->clipdist = getDudeInfo(nType + kDudeBase)->clipdist; @@ -261,6 +262,12 @@ void Respawn(int nSprite) // 9 seqSpawn(genDudeSeqStartId(pXSprite), 3, pSprite->extra, -1); break; } + + // return dude to the patrol state + if (gModernMap && pXSprite->dudeFlag4) { + pXSprite->data3 = 0; + pXSprite->target = -1; + } #else pSprite->clipdist = getDudeInfo(nType + kDudeBase)->clipdist; pXSprite->health = getDudeInfo(nType + kDudeBase)->startHealth << 4; diff --git a/source/games/blood/src/common_game.h b/source/games/blood/src/common_game.h index e3ff6bc7b..9f9f3f2fa 100644 --- a/source/games/blood/src/common_game.h +++ b/source/games/blood/src/common_game.h @@ -51,7 +51,7 @@ enum kExplodeMax = 8, kLensSize = 80, - kViewEffectMax = 19, + kViewEffectMax = 20, kNoTile = -1, @@ -401,6 +401,14 @@ kAiStateSearch = 3, kAiStateChase = 4, kAiStateRecoil = 5, kAiStateAttack = 6, +kAiStatePatrolBase = 7, +kAiStatePatrolWaitL = kAiStatePatrolBase, +kAiStatePatrolWaitC, +kAiStatePatrolWaitW, +kAiStatePatrolMoveL, +kAiStatePatrolMoveC, +kAiStatePatrolMoveW, +kAiStatePatrolMax, }; enum diff --git a/source/games/blood/src/dude.cpp b/source/games/blood/src/dude.cpp index 7007da4ef..9ff99f20a 100644 --- a/source/games/blood/src/dude.cpp +++ b/source/games/blood/src/dude.cpp @@ -1553,7 +1553,7 @@ DUDEINFO dudeInfo[kDudeMax-kDudeBase] = 20, 10240, // hear distance 51200, // seeing distance - kAng120, // vision periphery + kAng90, // vision periphery // 0, 618, // melee distance 5, // flee health @@ -1561,7 +1561,7 @@ DUDEINFO dudeInfo[kDudeMax-kDudeBase] = 0x0000, // change target chance 0x0000, // change target to kin chance 0x8000, // alertChance - 0, // lockout + 1, // lockout 46603, // frontSpeed 34952, // sideSpeed 13981, // backSpeed diff --git a/source/games/blood/src/eventq.cpp b/source/games/blood/src/eventq.cpp index 407a18f00..ab499193a 100644 --- a/source/games/blood/src/eventq.cpp +++ b/source/games/blood/src/eventq.cpp @@ -458,7 +458,7 @@ void evSend(int nIndex, int nType, int rxId, COMMAND_ID command) PLAYER* pPlayer = NULL; if (playerRXRngIsFine(rxId)) { - if ((pPlayer = getPlayerById((kChannelPlayer0 - kChannelPlayer7) + kMaxPlayers)) != nullptr) + if ((pPlayer = getPlayerById((rxId - kChannelPlayer7) + kMaxPlayers)) != nullptr) trMessageSprite(pPlayer->nSprite, event); } else if (rxId == kChannelAllPlayers) @@ -468,6 +468,7 @@ void evSend(int nIndex, int nType, int rxId, COMMAND_ID command) if ((pPlayer = getPlayerById(i)) != nullptr) trMessageSprite(pPlayer->nSprite, event); } + return; } } diff --git a/source/games/blood/src/eventq.h b/source/games/blood/src/eventq.h index 2ecb4522d..23c106ab4 100644 --- a/source/games/blood/src/eventq.h +++ b/source/games/blood/src/eventq.h @@ -97,6 +97,7 @@ kCmdCounterSector = 12, kCmdCallback = 20, kCmdRepeat = 21, + kCmdSpritePush = 30, kCmdSpriteImpact = 31, kCmdSpritePickup = 32, @@ -113,8 +114,20 @@ kCmdSectorExit = 43, kCmdWallPush = 50, kCmdWallImpact = 51, kCmdWallTouch = 52, +#ifdef NOONE_EXTENSIONS +kCmdSectorMotionPause = 13, // stops motion of the sector +kCmdSectorMotionContinue = 14, // continues motion of the sector +kCmdModernUse = 53, // used by most of modern types +kCmdModernPatrolOff = 54, // to manipulate dudeFlags +kCmdModernPatrolOn = 55, // to manipulate dudeFlags +kCmdModernDeafOff = 56, // to manipulate dudeFlags +kCmdModernDeafOn = 57, // to manipulate dudeFlags +kCmdModernBlindOff = 58, // to manipulate dudeFlags +kCmdModernBlindOn = 59, // to manipulate dudeFlags +kCmdModernAlarmOff = 60, // to manipulate dudeFlags +kCmdModernAlarmOn = 61, // to manipulate dudeFlags +#endif -kCmdModernUse = 53, // used by most of modern types kCmdNumberic = 64, // 64: 0, 65: 1 and so on up to 255 kCmdModernFeaturesEnable = 100, // must be in object with kChannelMapModernize RX / TX kCmdModernFeaturesDisable = 200, // must be in object with kChannelMapModernize RX / TX diff --git a/source/games/blood/src/nnexts.cpp b/source/games/blood/src/nnexts.cpp index d6f9528b4..54acaaed3 100644 --- a/source/games/blood/src/nnexts.cpp +++ b/source/games/blood/src/nnexts.cpp @@ -48,6 +48,7 @@ short gPhysSpritesCount; // current count short gImpactSpritesList[]; short gImpactSpritesCount; + TRPLAYERCTRL gPlayerCtrl[kMaxPlayers]; TRCONDITION gCondition[kMaxTrackingConditions]; @@ -99,22 +100,113 @@ THINGINFO_EXTRA gThingInfoExtra[] = { }; DUDEINFO_EXTRA gDudeInfoExtra[] = { - false, false, false, false, false, false, false, true, - false, false, false, true, true, true, true, false, - true, false, false, false, false, true, false, true, - false, true, false, true, false, true, false, true, - false, true, true, true, false, true, false, false, - false, true, false, false, false, true, false, false, - false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, - false, false, false, false, false, true, false, true, - false, false, false, false, false, false, false, false, - false, true, false, true, false, false, false, false, - false, false, false, false, + + { false, false, 1, -1, -1, -1, -1, -1 }, // 200 + { false, false, 0, 9, 13, 13, 17, 14 }, // 201 + { false, false, 0, 9, 13, 13, 17, 14 }, // 202 + { false, true, 0, 8, 0, 8, -1, -1 }, // 203 + { false, false, 0, 8, 0, 8, -1, -1 }, // 204 + { false, true, 1, -1, -1, -1, -1, -1 }, // 205 + { true, true, 0, 0, 0, 0, -1, -1 }, // 206 + { true, false, 0, 0, 0, 0, -1, -1 }, // 207 + { true, false, 1, -1, -1, -1, -1, -1 }, // 208 + { true, false, 1, -1, -1, -1, -1, -1 }, // 209 + { true, true, 0, 0, 0, 0, -1, -1 }, // 210 + { false, true, 0, 8, 0, 8, -1, -1 }, // 211 + { false, true, 0, 6, 0, 6, -1, -1 }, // 212 + { false, true, 0, 7, 0, 7, -1, -1 }, // 213 + { false, true, 0, 7, 0, 7, -1, -1 }, // 214 + { false, true, 0, 7, 0, 7, -1, -1 }, // 215 + { false, true, 0, 7, 0, 7, -1, -1 }, // 216 + { false, true, 0, 9, 10, 10, -1, -1 }, // 217 + { false, true, 0, 0, 0, 0, -1, -1 }, // 218 + { true, false, 7, 7, 7, 7, -1, -1 }, // 219 + { false, true, 0, 7, 0, 7, -1, -1 }, // 220 + { false, false, -1, -1, -1, -1, -1, -1 }, // 221 + { false, true, -1, -1, -1, -1, -1, -1 }, // 222 + { false, false, -1, -1, -1, -1, -1, -1 }, // 223 + { false, true, -1, -1, -1, -1, -1, -1 }, // 224 + { false, false, -1, -1, -1, -1, -1, -1 }, // 225 + { false, false, -1, -1, -1, -1, -1, -1 }, // 226 + { false, false, 0, 7, 0, 7, -1, -1 }, // 227 + { false, false, 0, 7, 0, 7, -1, -1 }, // 228 + { false, false, 0, 8, 0, 8, -1, -1 }, // 229 + { false, false, 0, 9, 13, 13, 17, 14 }, // 230 + { false, false, -1, -1, -1, -1, -1, -1 }, // 231 + { false, false, -1, -1, -1, -1, -1, -1 }, // 232 + { false, false, -1, -1, -1, -1, -1, -1 }, // 233 + { false, false, -1, -1, -1, -1, -1, -1 }, // 234 + { false, false, -1, -1, -1, -1, -1, -1 }, // 235 + { false, false, -1, -1, -1, -1, -1, -1 }, // 236 + { false, false, -1, -1, -1, -1, -1, -1 }, // 237 + { false, false, -1, -1, -1, -1, -1, -1 }, // 238 + { false, false, -1, -1, -1, -1, -1, -1 }, // 239 + { false, false, -1, -1, -1, -1, -1, -1 }, // 240 + { false, false, -1, -1, -1, -1, -1, -1 }, // 241 + { false, false, -1, -1, -1, -1, -1, -1 }, // 242 + { false, false, -1, -1, -1, -1, -1, -1 }, // 243 + { false, true, -1, -1, -1, -1, -1, -1 }, // 244 + { false, true, 0, 6, 0, 6, -1, -1 }, // 245 + { false, false, 0, 9, 13, 13, 17, 14 }, // 246 + { false, false, 0, 9, 13, 13, 14, 14 }, // 247 + { false, false, 0, 9, 13, 13, 14, 14 }, // 248 + { false, false, 0, 9, 13, 13, 17, 14 }, // 249 + { false, true, 0, 6, 8, 8, 10, 9 }, // 250 + { false, true, 0, 8, 9, 9, 11, 10 }, // 251 + { false, false, -1, -1, -1, -1, -1, -1 }, // 252 + { false, false, -1, -1, -1, -1, -1, -1 }, // 253 + { false, false, 0, 9, 17, 13, 17, 14 }, // 254 + { false, false, -1, -1, -1, -1, -1, -1 }, // 255 + }; + +AISTATE genPatrolStates[] = { + + //------------------------------------------------------------------------------- + + { kAiStatePatrolWaitL, 0, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitL, 7, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + + { kAiStatePatrolMoveL, 9, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveL, 8, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveL, 0, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveL, 6, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveL, 7, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + + //------------------------------------------------------------------------------- + + { kAiStatePatrolWaitW, 0, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitW, 10, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitW, 13, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitW, 17, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitW, 8, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitW, 9, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + + { kAiStatePatrolMoveW, 0, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveW, 10, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveW, 13, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveW, 8, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveW, 9, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveW, 7, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveW, 6, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + + //------------------------------------------------------------------------------- + + { kAiStatePatrolWaitC, 17, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitC, 11, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitC, 10, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + { kAiStatePatrolWaitC, 14, -1, 0, NULL, NULL, aiPatrolThink, NULL }, + + { kAiStatePatrolMoveC, 14, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveC, 10, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + { kAiStatePatrolMoveC, 9, -1, 0, NULL, aiPatrolMove, aiPatrolThink, NULL }, + + //------------------------------------------------------------------------------- + +}; + + // for actor.cpp //------------------------------------------------------------------------- @@ -246,7 +338,7 @@ void nnExtInitModernStuff(bool bSaveLoad) { nnExtResetGlobals(); // use true random only for single player mode, otherwise use Blood's default one. - /*if (gGameOptions.nGameType == 0 && !VanillaMode() && !DemoRecordStatus()) { + if (gGameOptions.nGameType == 0 && !VanillaMode() && !DemoRecordStatus()) { gStdRandom.seed(std::random_device()()); @@ -259,7 +351,7 @@ void nnExtInitModernStuff(bool bSaveLoad) { gAllowTrueRandom = true; } - }*/ + } for (int i = 0; i < kMaxXSprites; i++) { @@ -270,7 +362,7 @@ void nnExtInitModernStuff(bool bSaveLoad) { switch (pSprite->type) { case kModernRandomTX: case kModernSequentialTX: - if (pSprite->flags & kModernTypeFlag2) gEventRedirectsUsed = true; + if (pXSprite->command == kCmdLink) gEventRedirectsUsed = true; break; case kDudeModernCustom: case kDudeModernCustomBurning: @@ -754,7 +846,7 @@ spritetype* randomSpawnDude(spritetype* pSource) { //------------------------- void nnExtProcessSuperSprites() { - + // process tracking conditions if (gTrackingCondsCount > 0) { for (int i = 0; i < gTrackingCondsCount; i++) { @@ -1544,8 +1636,7 @@ void trPlayerCtrlEraseStuff(XSPRITE* pXSource, PLAYER* pPlayer) { fallthrough__; case 5: // erase powerups for (int i = 0; i < kMaxPowerUps; i++) pPlayer->pwUpTime[i] = 0; - if (pXSource->data2) break; - //fallthrough__; + break; } } @@ -1650,37 +1741,43 @@ void useObjResizer(XSPRITE* pXSource, short objType, int objIndex) { sector[objIndex].ceilingypan_ = (float)ClipRange(pXSource->data4, 0, 255); break; // for sprites - case 3: + case OBJ_SPRITE: { + bool fit = false; // resize by seq scaling if (sprite[pXSource->reference].flags & kModernTypeFlag1) { + if (valueIsBetween(pXSource->data1, -255, 32767)) { int mulDiv = (valueIsBetween(pXSource->data2, 0, 257)) ? pXSource->data2 : 256; if (pXSource->data1 > 0) xsprite[sprite[objIndex].extra].scale = mulDiv * ClipHigh(pXSource->data1, 25); else if (pXSource->data1 < 0) xsprite[sprite[objIndex].extra].scale = mulDiv / ClipHigh(abs(pXSource->data1), 25); else xsprite[sprite[objIndex].extra].scale = 0; - - // request properties update for custom dude - switch (sprite[objIndex].type) { - case kDudeModernCustom: - case kDudeModernCustomBurning: - gGenDudeExtra[objIndex].updReq[kGenDudePropertySpriteSize] = true; - gGenDudeExtra[objIndex].updReq[kGenDudePropertyAttack] = true; - gGenDudeExtra[objIndex].updReq[kGenDudePropertyMass] = true; - gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true; - evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate); - break; + fit = true; } - } // resize by repeats } else { - if (valueIsBetween(pXSource->data1, -1, 32767)) + if (valueIsBetween(pXSource->data1, -1, 32767)) { sprite[objIndex].xrepeat = ClipRange(pXSource->data1, 0, 255); - - if (valueIsBetween(pXSource->data2, -1, 32767)) + fit = true; + } + + if (valueIsBetween(pXSource->data2, -1, 32767)) { sprite[objIndex].yrepeat = ClipRange(pXSource->data2, 0, 255); + fit = true; + } + + } + + if (fit && (sprite[objIndex].type == kDudeModernCustom || sprite[objIndex].type == kDudeModernCustomBurning)) { + + // request properties update for custom dude + gGenDudeExtra[objIndex].updReq[kGenDudePropertySpriteSize] = true; + gGenDudeExtra[objIndex].updReq[kGenDudePropertyAttack] = true; + gGenDudeExtra[objIndex].updReq[kGenDudePropertyMass] = true; + gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true; + evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate); } @@ -1690,6 +1787,7 @@ void useObjResizer(XSPRITE* pXSource, short objType, int objIndex) { if (valueIsBetween(pXSource->data4, -1, 65535)) sprite[objIndex].yoffset = ClipRange(pXSource->data4, 0, 255); break; + } case OBJ_WALL: if (valueIsBetween(pXSource->data1, -1, 32767)) wall[objIndex].xrepeat = ClipRange(pXSource->data1, 0, 255); @@ -1779,6 +1877,7 @@ void usePropertiesChanger(XSPRITE* pXSource, short objType, int objIndex) { default: // store physics attributes in xsprite to avoid setting hitag for modern types! int flags = (pXSprite->physAttr != 0) ? pXSprite->physAttr : 0; + int oldFlags = flags; if (thing2debris) { @@ -1790,52 +1889,53 @@ void usePropertiesChanger(XSPRITE* pXSource, short objType, int objIndex) { else flags &= ~(kPhysGravity | kPhysFalling); pSprite->flags &= ~(kPhysMove | kPhysGravity | kPhysFalling); - xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0; pXSprite->restState = pXSprite->state; + xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0; + pXSprite->restState = pXSprite->state; } else { + static char digits[6]; + memset(digits, 0, sizeof(digits)); + sprintf(digits, "%d", pXSource->data2); + + int digit1 = (digits[0] >= 48 && digits[0] <= 57) ? (digits[0] - 57) + 9 : 0; + int digit2 = (digits[1] >= 48 && digits[1] <= 57) ? (digits[1] - 57) + 9 : 0; + // first digit of data2: set main physics attributes - switch (pXSource->data2) { + switch (digit1) { case 0: flags &= ~kPhysMove; flags &= ~(kPhysGravity | kPhysFalling); break; - - case 1: case 10: case 11: case 12: case 13: + case 1: flags |= kPhysMove; flags &= ~(kPhysGravity | kPhysFalling); break; - - case 2: case 20: case 21: case 22: case 23: + case 2: flags &= ~kPhysMove; flags |= (kPhysGravity | kPhysFalling); break; - - case 3: case 30: case 31: case 32: case 33: + case 3: flags |= kPhysMove; flags |= (kPhysGravity | kPhysFalling); break; } // second digit of data2: set physics flags - switch (pXSource->data2) { - case 0: case 1: case 2: case 3: - case 10: case 20: case 30: + switch (digit2) { + case 0: flags &= ~kPhysDebrisVector; flags &= ~kPhysDebrisExplode; break; - - case 11: case 21: case 31: + case 1: flags |= kPhysDebrisVector; flags &= ~kPhysDebrisExplode; break; - - case 12: case 22: case 32: + case 2: flags &= ~kPhysDebrisVector; flags |= kPhysDebrisExplode; break; - - case 13: case 23: case 33: + case 3: flags |= kPhysDebrisVector; flags |= kPhysDebrisExplode; break; @@ -1848,6 +1948,9 @@ void usePropertiesChanger(XSPRITE* pXSource, short objType, int objIndex) { // adding physics sprite in list if ((flags & kPhysGravity) != 0 || (flags & kPhysMove) != 0) { + if (oldFlags == 0) + xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0; + if (nIndex != -1) pXSprite->physAttr = flags; // just update physics attributes else if ((nIndex = debrisGetFreeIndex()) < 0) viewSetSystemMessage("Max (%d) Physics affected sprites reached!", kMaxSuperXSprites); @@ -2173,6 +2276,8 @@ void useEffectGen(XSPRITE* pXSource, spritetype* pSprite) { if (pSource->flags & kModernTypeFlag1) { pEffect->pal = pSource->pal; + pEffect->xoffset = pSource->xoffset; + pEffect->yoffset = pSource->yoffset; pEffect->xrepeat = pSource->xrepeat; pEffect->yrepeat = pSource->yrepeat; pEffect->shade = pSource->shade; @@ -2292,9 +2397,57 @@ void useSectorWindGen(XSPRITE* pXSource, sectortype* pSector) { } } +void useSpriteDamager(XSPRITE* pXSource, int objType, int objIndex) { + spritetype* pSource = &sprite[pXSource->reference]; + sectortype* pSector = §or[pSource->sectnum]; -void useSpriteDamager(XSPRITE* pXSource, spritetype* pSprite) { + int top, bottom, i; + bool floor, ceil, wall, enter; + + switch (objType) { + case OBJ_SPRITE: + damageSprites(pXSource, &sprite[objIndex]); + break; + case OBJ_SECTOR: + GetSpriteExtents(pSource, &top, &bottom); + floor = (bottom >= pSector->floorz); ceil = (top <= pSector->ceilingz); + wall = (pSource->cstat & 0x10); enter = (!floor && !ceil && !wall); + for (i = headspritesect[objIndex]; i != -1; i = nextspritesect[i]) { + if (!IsDudeSprite(&sprite[i]) || !xspriRangeIsFine(sprite[i].extra)) + continue; + else if (enter) + damageSprites(pXSource, &sprite[i]); + else if (floor && (gSpriteHit[sprite[i].extra].florhit & 0xc000) == 0x4000 && (gSpriteHit[sprite[i].extra].florhit & 0x3fff) == objIndex) + damageSprites(pXSource, &sprite[i]); + else if (ceil && (gSpriteHit[sprite[i].extra].ceilhit & 0xc000) == 0x4000 && (gSpriteHit[sprite[i].extra].ceilhit & 0x3fff) == objIndex) + damageSprites(pXSource, &sprite[i]); + else if (wall && (gSpriteHit[sprite[i].extra].hit & 0xc000) == 0x8000 && sectorofwall(gSpriteHit[sprite[i].extra].hit & 0x3fff) == objIndex) + damageSprites(pXSource, &sprite[i]); + } + break; + case -1: + for (i = headspritestat[kStatDude]; i != -1; i = nextspritestat[i]) { + if (sprite[i].statnum != kStatDude) continue; + switch (pXSource->data1) { + case 667: + if (IsPlayerSprite(&sprite[i])) continue; + damageSprites(pXSource, &sprite[i]); + break; + case 668: + if (!IsPlayerSprite(&sprite[i])) continue; + damageSprites(pXSource, &sprite[i]); + break; + default: + damageSprites(pXSource, &sprite[i]); + break; + } + } + break; + } +} + +void damageSprites(XSPRITE* pXSource, spritetype* pSprite) { spritetype* pSource = &sprite[pXSource->reference]; if (!IsDudeSprite(pSprite) || !xspriRangeIsFine(pSprite->extra) || xsprite[pSprite->extra].health <= 0 || pXSource->data3 < 0) return; @@ -2350,6 +2503,7 @@ void useSeqSpawnerGen(XSPRITE* pXSource, int objType, int index) { return; } + spritetype* pSource = &sprite[pXSource->reference]; switch (objType) { case OBJ_SECTOR: if (pXSource->data2 <= 0) { @@ -2410,8 +2564,61 @@ void useSeqSpawnerGen(XSPRITE* pXSource, int objType, int index) { return; case OBJ_SPRITE: if (pXSource->data2 <= 0) seqKill(3, sprite[index].extra); - else { + else if (sectRangeIsFine(sprite[index].sectnum)) { + if (pXSource->data3 > 0) { + int nSprite = InsertSprite(sprite[index].sectnum, kStatDecoration); + int top, bottom; GetSpriteExtents(&sprite[index], &top, &bottom); + sprite[nSprite].x = sprite[index].x; + sprite[nSprite].y = sprite[index].y; + switch (pXSource->data3) { + default: + sprite[nSprite].z = sprite[index].z; + break; + case 2: + sprite[nSprite].z = bottom; + break; + case 3: + sprite[nSprite].z = top; + break; + case 4: + sprite[nSprite].z = sprite[index].z + (tilesiz[sprite[index].picnum].y / 2 + picanm[sprite[index].picnum].yofs); + break; + case 5: + case 6: + if (!sectRangeIsFine(sprite[index].sectnum)) sprite[nSprite].z = top; + else sprite[nSprite].z = (pXSource->data3 == 5) ? sector[sprite[nSprite].sectnum].floorz : sector[sprite[nSprite].sectnum].ceilingz; + break; + } + + if (nSprite >= 0) { + + int nXSprite = dbInsertXSprite(nSprite); + seqSpawn(pXSource->data2, 3, nXSprite, -1); + if (pSource->flags & kModernTypeFlag1) { + + sprite[nSprite].pal = pSource->pal; + sprite[nSprite].shade = pSource->shade; + sprite[nSprite].xrepeat = pSource->xrepeat; + sprite[nSprite].yrepeat = pSource->yrepeat; + sprite[nSprite].xoffset = pSource->xoffset; + sprite[nSprite].yoffset = pSource->yoffset; + + } + + if (pSource->flags & kModernTypeFlag2) { + + sprite[nSprite].cstat |= pSource->cstat; + + } + + // should be: the more is seqs, the shorter is timer + evPost(nSprite, OBJ_SPRITE, 1000, kCallbackRemove); + } + } else { + seqSpawn(pXSource->data2, 3, sprite[index].extra, -1); + + } if (pXSource->data4 > 0) sfxPlay3DSound(&sprite[index], pXSource->data4, -1, 0); } @@ -2773,6 +2980,7 @@ bool condCheckSector(XSPRITE* pXCond, int cmpOp, bool PUSH) { switch (cond) { default: break; case 50: return pXSect->Underwater; + case 51: return condCmp(pXSect->Depth, arg1, arg2, cmpOp); case 55: // compare floor height (in %) case 56: { // compare ceil height (in %) int h = 0; int curH = 0; @@ -2793,6 +3001,8 @@ bool condCheckSector(XSPRITE* pXCond, int cmpOp, bool PUSH) { return false; } } + case 57: // this sector in movement? + return sectorInMotion(objIndex); } } else { switch (cond) { @@ -2869,10 +3079,10 @@ bool condCheckPlayer(XSPRITE* pXCond, int cmpOp, bool PUSH) { } } - if (!spriRangeIsFine(objIndex) || !pPlayer) - condError(pXCond, "\nPlayer conditions:\nObject #%d (objType: %d) is not a player!", objIndex, objType); + spritetype* pSpr = NULL; + if (spriRangeIsFine(objIndex) && pPlayer) pSpr = pPlayer->pSprite; + else condError(pXCond, "\nPlayer conditions:\nObject #%d (objType: %d) is not a player!", objIndex, objType); - spritetype* pSpr = pPlayer->pSprite; //XSPRITE* pXSpr = pPlayer->pXSprite; switch (cond) { case 0: // check if this player is connected if (!condCmp(pPlayer->nPlayer + 1, arg1, arg2, cmpOp) || !spriRangeIsFine(pPlayer->nSprite)) return false; @@ -2947,12 +3157,13 @@ bool condCheckDude(XSPRITE* pXCond, int cmpOp, bool PUSH) { XSPRITE* pXSpr = &xsprite[pSpr->extra]; if (pSpr->flags & kHitagRespawn || pSpr->statnum == kStatRespawn) return false; - else if (IsPlayerSprite(pSpr))condError(pXCond, "Dude conditions:\nObject #%d (objType: %d) is not an enemy!", objIndex, objType); + else if (IsPlayerSprite(pSpr)) condError(pXCond, "Dude conditions:\nObject #%d (objType: %d) is not an enemy!", objIndex, objType); switch (cond) { default: break; case 0: // dude have any targets? - if (!spriRangeIsFine(pXSpr->target) || !IsDudeSprite(&sprite[pXSpr->target])) return false; + if (!spriRangeIsFine(pXSpr->target)) return false; + else if (!IsDudeSprite(&sprite[pXSpr->target]) && sprite[pXSpr->target].type != kMarkerPath) return false; else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target); return true; case 1: return aiFightDudeIsAffected(pXSpr); // dude affected by ai fight? @@ -2986,9 +3197,35 @@ bool condCheckDude(XSPRITE* pXCond, int cmpOp, bool PUSH) { else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target); return true; - } + } + case 5: return pXSpr->dudeFlag4; + case 6: return pXSpr->dudeDeaf; + case 7: return pXSpr->dudeGuard; + case 8: return pXSpr->dudeAmbush; + case 9: // check if the marker is busy with another dude + case 10: // check if the marker is reached + if (!pXSpr->dudeFlag4 || !spriRangeIsFine(pXSpr->target) || sprite[pXSpr->target].type != kMarkerPath) return false; + switch (cond) { + case 9: + var = aiPatrolMarkerBusy(pSpr->index, pXSpr->target); + if (!spriRangeIsFine(var)) return false; + else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target); + break; + case 10: + if (!aiPatrolMarkerReached(pSpr, pXSpr)) return false; + else if (PUSH) condPush(pXCond, OBJ_SPRITE, pXSpr->target); + break; + } + return true; + case 11: // compare spot progress value in % + if (!pXSpr->dudeFlag4 || !spriRangeIsFine(pXSpr->target) || sprite[pXSpr->target].type != kMarkerPath) var = 0; + else if (pXSpr->data3 < 0 || pXSpr->data3 > kMaxPatrolSpotValue) var = 0; + else var = (kPercFull * pXSpr->data3) / kMaxPatrolSpotValue; + return condCmp(var, arg1, arg2, cmpOp); + case 15: return getDudeInfo(pSpr->type)->lockOut; // dude allowed to interact with objects? case 20: // kDudeModernCustom conditions case 21: + case 22: switch (pSpr->type) { case kDudeModernCustom: case kDudeModernCustomBurning: @@ -3003,6 +3240,9 @@ bool condCheckDude(XSPRITE* pXCond, int cmpOp, bool PUSH) { if (!spriRangeIsFine(var) && pSpr->owner == kMaxSprites - 1) return true; else if (PUSH) condPush(pXCond, OBJ_SPRITE, var); return false; + case 22: // are required amount of dudes is summoned? + return condCmp(gGenDudeExtra[pSpr->index].slaveCount, arg1, arg2, cmpOp); + break; } fallthrough__; default: @@ -3038,7 +3278,8 @@ bool condCheckSprite(XSPRITE* pXCond, int cmpOp, bool PUSH) { default: break; case 0: return condCmp((pSpr->ang & 2047), arg1, arg2, cmpOp); case 5: return condCmp(pSpr->statnum, arg1, arg2, cmpOp); - case 6: return ((pSpr->flags & kHitagRespawn) && (pSpr->statnum & kStatRespawn)); + case 6: return ((pSpr->flags & kHitagRespawn) || pSpr->statnum == kStatRespawn); + case 7: return condCmp(spriteGetSlope(pSpr->index), arg1, arg2, cmpOp); case 10: return condCmp(pSpr->clipdist, arg1, arg2, cmpOp); case 15: if (!spriRangeIsFine(pSpr->owner)) return false; @@ -3334,10 +3575,23 @@ void modernTypeTrigger(int destObjType, int destObjIndex, EVENT event) { if (destObjType != OBJ_SPRITE) break; useTeleportTarget(pXSource, &sprite[destObjIndex]); break; + // changes slope of sprite or sector + case kModernSlopeChanger: + switch (destObjType) { + case OBJ_SPRITE: + case OBJ_SECTOR: + useSlopeChanger(pXSource, destObjType, destObjIndex); + break; + } + break; case kModernSpriteDamager: - // damages xsprite via TX ID - if (destObjType != OBJ_SPRITE) break; - useSpriteDamager(pXSource, &sprite[destObjIndex]); + // damages xsprite via TX ID or xsprites in a sector + switch (destObjType) { + case OBJ_SPRITE: + case OBJ_SECTOR: + useSpriteDamager(pXSource, destObjType, destObjIndex); + break; + } break; // can spawn any effect passed in data2 on it's or txID sprite case kModernEffectSpawner: @@ -3383,6 +3637,11 @@ void modernTypeTrigger(int destObjType, int destObjIndex, EVENT event) { case kModernObjPropertiesChanger: usePropertiesChanger(pXSource, destObjType, destObjIndex); break; + // updated vanilla sound gen that now allows to play sounds on TX ID sprites + case kGenModernSound: + if (destObjType != OBJ_SPRITE) break; + useSoundGen(pXSource, &sprite[destObjIndex]); + break; } } @@ -3639,6 +3898,147 @@ int aiFightGetFineTargetDist(spritetype* pSprite, spritetype* pTarget) { return dist; } +int sectorInMotion(int nSector) { + + for (int i = 0; i < kMaxBusyCount; i++) { + if (gBusy->at0 == nSector) return i; + } + + return -1; +} + +void sectorPauseMotion(int nSector) { + + dassert(xsectRangeIsFine(sector[nSector].extra)); + xsector[sector[nSector].extra].unused1 = 1; + SectorEndSound(nSector, xsector[sector[nSector].extra].state); + /*for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) { + spritetype* pSprite = &sprite[nSprite]; + if (pSprite->statnum != kStatDecoration || pSprite->type != kSoundSector) + continue; + + sfxKill3DSound(pSprite); + + }*/ + return; + +} + +void sectorContinueMotion(int nSector, EVENT event) { + + if (gBusyCount >= kMaxBusyCount) + return; + + dassert(xsectRangeIsFine(sector[nSector].extra)); + XSECTOR* pXSector = &xsector[sector[nSector].extra]; + pXSector->unused1 = 0; + + int nDelta = 1; + switch (event.cmd) { + case kCmdOff: + pXSector->state = 1; + nDelta = (65536 - pXSector->busy) / ClipLow((pXSector->busyTimeB * 120) / 10, 1); + break; + case kCmdOn: + pXSector->state = 0; + nDelta = (65536 - pXSector->busy) / ClipLow((pXSector->busyTimeA * 120) / 10, 1); + break; + case kCmdToggle: + case kCmdSectorMotionContinue: + nDelta = (65536 - pXSector->busy) / ClipLow((((pXSector->state) ? pXSector->busyTimeB : pXSector->busyTimeA) * 120) / 10, 1); + if (event.cmd == kCmdToggle) pXSector->state ^= pXSector->state; + break; + } + + if (!nDelta) return; + int busyFunc = BUSYID_0; + switch (sector[nSector].type) { + case kSectorZMotion: + busyFunc = BUSYID_2; + break; + case kSectorZMotionSprite: + busyFunc = BUSYID_1; + break; + case kSectorSlideMarked: + case kSectorSlide: + busyFunc = BUSYID_3; + break; + case kSectorRotateMarked: + case kSectorRotate: + busyFunc = BUSYID_4; + break; + case kSectorRotateStep: + busyFunc = BUSYID_5; + break; + case kSectorPath: + busyFunc = BUSYID_7; + break; + default: + ThrowError("Unsupported sector type %d", sector[nSector].type); + break; + } + + nDelta = (pXSector->state) ? -nDelta : nDelta; + gBusy[gBusyCount].at0 = nSector; + gBusy[gBusyCount].at4 = nDelta; + gBusy[gBusyCount].at8 = pXSector->busy; + gBusy[gBusyCount].atc = (BUSYID)busyFunc; + gBusyCount++; + + SectorStartSound(nSector, pXSector->state); + +} + +bool modernTypeOperateSector(int nSector, sectortype* pSector, XSECTOR* pXSector, EVENT event) { + + if (event.cmd >= kCmdLock && event.cmd <= kCmdToggleLock) { + + switch (event.cmd) { + case kCmdLock: + pXSector->locked = 1; + break; + case kCmdUnlock: + pXSector->locked = 0; + break; + case kCmdToggleLock: + pXSector->locked = pXSector->locked ^ 1; + break; + } + + switch (pSector->type) { + case kSectorCounter: + if (pXSector->locked != 1) break; + SetSectorState(nSector, pXSector, 0); + evPost(nSector, 6, 0, kCallbackCounterCheck); + break; + } + + return true; + + // continue motion of the paused sector + } else if (pXSector->unused1) { + + switch (event.cmd) { + case kCmdOff: + case kCmdOn: + case kCmdToggle: + case kCmdSectorMotionContinue: + sectorContinueMotion(nSector, event); + return true; + } + + // pause motion of the sector + } else if (event.cmd == kCmdSectorMotionPause) { + + sectorPauseMotion(nSector); + return true; + + } + + return false; + +} + bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite, EVENT event) { if (event.cmd >= kCmdLock && event.cmd <= kCmdToggleLock) { @@ -3675,6 +4075,9 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite case kCmdOn: if (!pXSprite->state) SetSpriteState(nSprite, pXSprite, 1); if (IsPlayerSprite(pSprite) || pXSprite->health <= 0) break; + else if (pXSprite->aiState->stateType >= kAiStatePatrolBase && pXSprite->aiState->stateType < kAiStatePatrolMax) + break; + switch (pXSprite->aiState->stateType) { case kAiStateIdle: case kAiStateGenIdle: @@ -3682,6 +4085,32 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite break; } break; + case kCmdModernPatrolOff: + if (pXSprite->aiState->stateType < kAiStatePatrolBase || pXSprite->aiState->stateType >= kAiStatePatrolMax) break; + else aiPatrolStop(pSprite, -1); + break; + case kCmdModernPatrolOn: + if (pXSprite->aiState->stateType >= kAiStatePatrolBase && pXSprite->aiState->stateType < kAiStatePatrolMax) break; + else if (spriteIsUnderwater(pSprite)) aiPatrolState(pSprite, kAiStatePatrolWaitW); + else aiPatrolState(pSprite, kAiStatePatrolWaitL); + pXSprite->data3 = 0; + break; + case kCmdModernAlarmOff: + case kCmdModernAlarmOn: + case kCmdModernBlindOff: + case kCmdModernBlindOn: + case kCmdModernDeafOff: + case kCmdModernDeafOn: + if (!pXSprite->dudeFlag4) break; + switch (event.cmd) { + case kCmdModernAlarmOff: pXSprite->dudeAmbush = 0; break; + case kCmdModernAlarmOn: pXSprite->dudeAmbush = 1; break; + case kCmdModernBlindOff: pXSprite->dudeGuard = 0; break; + case kCmdModernBlindOn: pXSprite->dudeGuard = 1; break; + case kCmdModernDeafOff: pXSprite->dudeDeaf = 0; break; + case kCmdModernDeafOn: pXSprite->dudeDeaf = 1; break; + } + break; default: if (!pXSprite->state) evPost(nSprite, 3, 0, kCmdOn); else evPost(nSprite, 3, 0, kCmdOff); @@ -3703,29 +4132,25 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite if (gGameOptions.nMonsterSettings && pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeVanillaMax) { spritetype* pSpawn = NULL; - if ((pSpawn = randomSpawnDude(pSprite)) == NULL) - return false; // go normal OperateSprite(); - + if ((pSpawn = randomSpawnDude(pSprite)) == NULL + && (pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0)) == NULL) { + return true; + } + XSPRITE* pXSpawn = &xsprite[pSpawn->extra]; gKillMgr.AddNewKill(1); - switch (pXSprite->data1) { - case kDudeBurningInnocent: - case kDudeBurningCultist: - case kDudeBurningZombieButcher: - case kDudeBurningTinyCaleb: - case kDudeBurningBeast: - pXSpawn->health = getDudeInfo(pXSprite->data1)->startHealth << 4; - pXSpawn->burnTime = 10; - pXSpawn->target = -1; + if (IsBurningDude(pSpawn)) { + pXSpawn->health = getDudeInfo(pXSprite->data1)->startHealth << 4; + pXSpawn->burnTime = 10; + pXSpawn->target = -1; + if (!pXSpawn->dudeFlag4) aiActivateDude(&bloodActors[pXSpawn->reference]); - break; - default: - if (pSprite->flags & kModernTypeFlag3) aiActivateDude(&bloodActors[pXSpawn->reference]); - break; + } else if ((pSprite->flags & kModernTypeFlag3) && !pXSpawn->dudeFlag4) { + aiActivateDude(&bloodActors[pXSpawn->reference]); } + } return true; - case kModernRandomTX: // random Event Switch takes random data field and uses it as TX ID case kModernSequentialTX: // sequential Switch takes values from data fields starting from data1 and uses it as TX ID if (pXSprite->command == kCmdLink) return true; // work as event redirector @@ -3739,20 +4164,41 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite break; } return true; - case kMarkerWarpDest: case kModernSpriteDamager: - if (pXSprite->txID <= 0) { + switch (event.cmd) { + case kCmdOff: + if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0); + break; + case kCmdOn: + evKill(nSprite, 3); // queue overflow protect + if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1); + fallthrough__; + case kCmdRepeat: + if (pXSprite->txID > 0) modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command); + else if (pXSprite->data1 == 0 && sectRangeIsFine(pSprite->sectnum)) useSpriteDamager(pXSprite, OBJ_SECTOR, pSprite->sectnum); + else if (pXSprite->data1 >= 666 && pXSprite->data1 < 669) useSpriteDamager(pXSprite, -1, -1); + else { + PLAYER* pPlayer = getPlayerById(pXSprite->data1); - if (pPlayer != NULL && SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1 && pXSprite->data1 > 0) { - switch (pSprite->type) { - case kMarkerWarpDest: - useTeleportTarget(pXSprite, pPlayer->pSprite); + if (pPlayer != NULL) + useSpriteDamager(pXSprite, OBJ_SPRITE, pPlayer->pSprite->index); + } + + if (pXSprite->busyTime > 0) + evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat); break; - case kModernSpriteDamager: - useSpriteDamager(pXSprite, pPlayer->pSprite); + default: + if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn); + else evPost(nSprite, 3, 0, kCmdOff); break; } - } + return true; + case kMarkerWarpDest: + if (pXSprite->txID <= 0) { + + PLAYER* pPlayer = getPlayerById(pXSprite->data1); + if (pPlayer != NULL && SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1) + useTeleportTarget(pXSprite, pPlayer->pSprite); return true; } fallthrough__; @@ -3763,6 +4209,7 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite return true; } fallthrough__; + case kModernSlopeChanger: case kModernObjSizeChanger: case kModernObjPicnumChanger: case kModernSectorFXChanger: @@ -3996,7 +4443,6 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite } } return true; - case kGenModernMissileUniversal: case kGenModernSound: switch (event.cmd) { case kCmdOff: @@ -4007,14 +4453,29 @@ bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1); fallthrough__; case kCmdRepeat: - switch (pSprite->type) { - case kGenModernMissileUniversal: - useUniMissileGen(3, pSprite->extra); + if (pXSprite->txID) modernTypeSendCommand(nSprite, pXSprite->txID, (COMMAND_ID)pXSprite->command); + else useSoundGen(pXSprite, pSprite); + + if (pXSprite->busyTime > 0) + evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat); break; - case kGenModernSound: - useSoundGen(pSprite, pXSprite); + default: + if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn); + else evPost(nSprite, 3, 0, kCmdOff); break; } + return true; + case kGenModernMissileUniversal: + switch (event.cmd) { + case kCmdOff: + if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0); + break; + case kCmdOn: + evKill(nSprite, 3); // queue overflow protect + if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1); + fallthrough__; + case kCmdRepeat: + useUniMissileGen(3, pSprite->extra); if (pXSprite->txID) evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); if (pXSprite->busyTime > 0) evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat); break; @@ -4345,9 +4806,10 @@ void useUniMissileGen(int, int nXSprite) { } -void useSoundGen(spritetype* pSource, XSPRITE* pXSource) { +void useSoundGen(XSPRITE* pXSource, spritetype* pSprite) { + //spritetype* pSource = &sprite[pXSource->reference]; int pitch = pXSource->data4 << 1; if (pitch < 2000) pitch = 0; - sfxPlay3DSoundCP(pSource, pXSource->data2, -1, 0, pitch, pXSource->data3); + sfxPlay3DSoundCP(pSprite, pXSource->data2, -1, 0, pitch, pXSource->data3); } void useIncDecGen(XSPRITE* pXSource, short objType, int objIndex) { @@ -4413,6 +4875,56 @@ void useIncDecGen(XSPRITE* pXSource, short objType, int objIndex) { } + +void useSlopeChanger(XSPRITE* pXSource, int objType, int objIndex) { + + //spritetype* pSource = &sprite[pXSource->reference]; + int slope = ClipRange(pXSource->data2, -32767, 32767); + if (objType == OBJ_SECTOR) { + sectortype* pSect = §or[objIndex]; + switch (pXSource->data1) { + case 2: + case 0: + if (slope == 0) { + if (pSect->floorstat & 0x0002) pSect->floorstat &= ~0x0002; + } else if (!(pSect->floorstat & 0x0002)) { + pSect->floorstat |= 0x0002; + } + pSect->floorheinum = slope; + if (pXSource->data1 == 0) break; + fallthrough__; + case 1: + if (slope == 0) { + if (pSect->ceilingstat & 0x0002) pSect->ceilingstat &= ~0x0002; + } else if (!(pSect->ceilingstat & 0x0002)) { + pSect->ceilingstat |= 0x0002; + } + pSect->ceilingheinum = slope; + break; + } + + if (slope != 0) { + for (int i = headspritesect[objIndex]; i != -1; i = nextspritesect[i]) { + if (sprite[i].extra > 0 && xsprite[sprite[i].extra].physAttr > 0) { + if (!(xsprite[sprite[i].extra].physAttr & kPhysFalling)) xsprite[sprite[i].extra].physAttr |= kPhysFalling; + if (zvel[i] == 0) zvel[i] = 1; + + } else if ((sprite[i].statnum == kStatThing || sprite[i].statnum == kStatDude) && (sprite[i].flags & kPhysGravity)){ + if (!(sprite[i].flags & kPhysFalling)) sprite[i].flags |= kPhysFalling; + if (zvel[i] == 0) zvel[i] = 1; + } + } + } + + + } else if (objType == OBJ_SPRITE) { + spritetype* pSpr = &sprite[objIndex]; + if (!(pSpr->cstat & CSTAT_SPRITE_ALIGNMENT_SLOPE)) pSpr->cstat |= CSTAT_SPRITE_ALIGNMENT_SLOPE; + if (!(pSpr->cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) pSpr->cstat |= CSTAT_SPRITE_ALIGNMENT_FLOOR; + spriteSetSlope(objIndex, slope); + } +} + void useDataChanger(XSPRITE* pXSource, int objType, int objIndex) { spritetype* pSource = &sprite[pXSource->reference]; @@ -5081,6 +5593,585 @@ bool setDataValueOfObject(int objType, int objIndex, int dataIndex, int value) { } } +/// patrol functions +// ------------------------------------------------ + +AISTATE* aiInPatrolState(AISTATE* pAiState) { + + for (int i = 0; i < kPatrolStateSize; i++) { + if (pAiState == &genPatrolStates[i]) + return pAiState; + } + return NULL; + +} + +bool aiPatrolCrouching(AISTATE* pAiState) { + return (pAiState->stateType == kAiStatePatrolWaitC || pAiState->stateType == kAiStatePatrolMoveC); +} + +bool aiPatrolWaiting(AISTATE* pAiState) { + return (pAiState->stateType == kAiStatePatrolWaitL || pAiState->stateType == kAiStatePatrolWaitW || pAiState->stateType == kAiStatePatrolWaitC); +} + +bool aiPatrolMoving(AISTATE* pAiState) { + return (pAiState->stateType == kAiStatePatrolMoveL || pAiState->stateType == kAiStatePatrolMoveW || pAiState->stateType == kAiStatePatrolMoveC); +} + +void aiPatrolState(spritetype* pSprite, int state) { + + dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); + XSPRITE* pXSprite = &xsprite[pSprite->extra]; + + int seq = -1, i, start, end; bool crouch; + DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[pSprite->type - kDudeBase]; + switch (state) { + case kAiStatePatrolWaitL: + seq = pExtra->idlgseqofs; + start = 0; end = 2; + crouch = false; + break; + case kAiStatePatrolMoveL: + seq = pExtra->mvegseqofs; + start = 2; end = 7; + crouch = false; + break; + case kAiStatePatrolWaitW: + seq = pExtra->idlwseqofs; + start = 7; end = 13; + crouch = false; + break; + case kAiStatePatrolMoveW: + seq = pExtra->mvewseqofs; + start = 13; end = 20; + crouch = false; + break; + case kAiStatePatrolWaitC: + seq = pExtra->idlcseqofs; + start = 20; end = 24; + crouch = true; + break; + case kAiStatePatrolMoveC: + seq = pExtra->mvecseqofs; + start = 24; end = kPatrolStateSize; + crouch = true; + break; + } + + if (seq < 0) + return aiPatrolStop(pSprite, -1); + + for (i = start; i < end; i++) { + + AISTATE* curState = &genPatrolStates[i]; + if (curState->stateType != state || seq != curState->seqId) continue; + aiChooseDirection(pSprite, pXSprite, getangle(pXSprite->targetX - pSprite->x, pXSprite->targetY - pSprite->y)); + if (pSprite->type == kDudeModernCustom) aiGenDudeNewState(pSprite, &genPatrolStates[i]); + else aiNewState(pSprite, pXSprite, &genPatrolStates[i]); + + pXSprite->unused2 = crouch; + + // these don't have idle crouch seq for some reason... + if (state == kAiStatePatrolWaitC && (pSprite->type == kDudeCultistTesla || pSprite->type == kDudeCultistTNT)) { + seqKill(OBJ_SPRITE, pSprite->extra); + pSprite->picnum = 3385; // set idle picnum + } + + return; + + } + + if (i == end) { + viewSetSystemMessage("No patrol state #%d found for dude #%d (type = %d)", state, pSprite->index, pSprite->type); + aiPatrolStop(pSprite, -1); + } +} + +// check if some dude already follows the given marker +int aiPatrolMarkerBusy(int nExcept, int nMarker) { + for (int i = headspritestat[kStatDude]; i != -1; i = nextspritestat[i]) { + if (!IsDudeSprite(&sprite[i]) || sprite[i].index == nExcept || !xspriRangeIsFine(sprite[i].extra)) + continue; + + XSPRITE* pXDude = &xsprite[sprite[i].extra]; + if (pXDude->health > 0 && pXDude->target >= 0 && sprite[pXDude->target].type == kMarkerPath && pXDude->target == nMarker) + return sprite[i].index; + } + + return -1; +} + +bool aiPatrolMarkerReached(spritetype* pSprite, XSPRITE* pXSprite) { + + if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax) { + + DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[pSprite->type - kDudeBase]; + if (spriRangeIsFine(pXSprite->target) && sprite[pXSprite->target].type == kMarkerPath) { + short okDist = ClipLow(sprite[pXSprite->target].clipdist << 1, 4); + if (spriteIsUnderwater(pSprite) || pExtra->flying) + return CheckProximity(&sprite[pXSprite->target], pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, okDist); + + // ignore z of marker for ground + spritetype* pMarker = &sprite[pXSprite->target]; + int oX = klabs(pMarker->x - pSprite->x) >> 4; + int oY = klabs(pMarker->y - pSprite->y) >> 4; + return (approxDist(oX, oY) <= okDist); + } + + } + + return false; + +} + +void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite) { + + int path = -1; int next = -1; int i = 0; + if (pXSprite->target <= 0) { + + long closest = 20000000; // select closest marker that dude can see + for (i = headspritestat[kStatPathMarker]; i != -1; i = nextspritestat[i]) { + + long dist = 0; + int dx = sprite[i].x - pSprite->x; + int dy = sprite[i].y - pSprite->y; + int eyeAboveZ = (getDudeInfo(pSprite->type)->eyeHeight * pSprite->yrepeat) << 2; + if (cansee(sprite[i].x, sprite[i].y, sprite[i].z, sprite[i].sectnum, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum) + && (dist = approxDist(dx, dy)) <= closest && xspriRangeIsFine(sprite[i].extra) && !xsprite[sprite[i].extra].locked + && !xsprite[sprite[i].extra].DudeLockout) { + + closest = dist; + path = i; + + } + + } + + // set next marker + } else if (sprite[pXSprite->target].type == kMarkerPath && xspriRangeIsFine(sprite[pXSprite->target].extra)) { + + int total = 0, random = 0; next = xsprite[sprite[pXSprite->target].extra].data2; + for (i = headspritestat[kStatPathMarker]; i != -1; i = nextspritestat[i]) { + if (!xspriRangeIsFine(sprite[i].extra) || xsprite[sprite[i].extra].data1 != next) continue; + else if (!xsprite[sprite[i].extra].locked && !xsprite[sprite[i].extra].DudeLockout) + total++; + } + + if (total <= 0) { + //viewSetSystemMessage("Follow: No markers with id #%d found for dude #%d!", next, pSprite->index); + return; + } + + random = nnExtRandom(0, total); + for (i = headspritestat[kStatPathMarker]; i >= 0; i = nextspritestat[i]) { + if (sprite[i].index == pXSprite->target || !xspriRangeIsFine(sprite[i].extra) || xsprite[sprite[i].extra].data1 != next) continue; + else if (xsprite[sprite[i].extra].locked || xsprite[sprite[i].extra].DudeLockout) continue; + else if (total > 1 && (random != total-- || (aiPatrolMarkerBusy(pSprite->index, sprite[i].index) >= 0 && !Chance(0x0500)))) continue; + path = sprite[i].index; + break; + } + } + + if (!spriRangeIsFine(path)) + return; + + pXSprite->target = path; + pXSprite->targetX = sprite[path].x; + pXSprite->targetY = sprite[path].y; + pXSprite->targetZ = sprite[path].z; + sprite[path].owner = pSprite->index; + +} + +void aiPatrolStop(spritetype* pSprite, int target, bool alarm) { + if (xspriRangeIsFine(pSprite->extra)) { + + XSPRITE* pXSprite = &xsprite[pSprite->extra]; + pXSprite->data3 = 0; // reset spot progress + pXSprite->unused2 = 0; // reset the crouch status + if (pXSprite->health <= 0) + return; + + if (pXSprite->target >= 0 && sprite[pXSprite->target].type == kMarkerPath) { + if (target < 0) pSprite->ang = sprite[pXSprite->target].ang & 2047; + pXSprite->target = -1; + } + + bool flag4 = pXSprite->dudeFlag4; + pXSprite->dudeFlag4 = 0; + if (spriRangeIsFine(target) && IsDudeSprite(&sprite[target]) && xspriRangeIsFine(sprite[target].extra)) { + + aiSetTarget(pXSprite, target); + aiActivateDude(pSprite, pXSprite); + if (alarm) + aiPatrolAlarm(pSprite, Chance(0x0500)); + + } else { + + + aiInitSprite(pSprite); + aiSetTarget(pXSprite, pXSprite->targetX, pXSprite->targetY, pXSprite->targetZ); + + + } + pXSprite->dudeFlag4 = flag4; // this must be kept so enemy can patrol after respawn again + } + return; +} + +void aiPatrolMoveZ(spritetype* pSprite, XSPRITE* pXSprite) { + if (!spriRangeIsFine(pXSprite->target)) + return; + + DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type); + spritetype* pTarget = &sprite[pXSprite->target]; + + int z = pSprite->z + pDudeInfo->eyeHeight; + int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024; + int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4; + pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047; + if (klabs(nAng) > 341) { + pSprite->ang = (pSprite->ang + 512) & 2047; + return; + } + + int dz = (pTarget->z - z) * 5; + zvel[pSprite->index] = dz; + +} + +void aiPatrolMove(spritetype* pSprite, XSPRITE* pXSprite) { + + if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) + return; + + int dudeIdx; + switch (pSprite->type) { + default: dudeIdx = pSprite->type - kDudeBase; break; + case kDudeCultistShotgunProne: dudeIdx = kDudeCultistShotgun - kDudeBase; break; + case kDudeCultistTommyProne: dudeIdx = kDudeCultistTommy - kDudeBase; break; + } + + DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[dudeIdx]; + if (pExtra->flying || spriteIsUnderwater(pSprite)) { + pSprite->flags &= ~kPhysGravity; + aiPatrolMoveZ(pSprite, pXSprite); + + } else if (!pExtra->flying) { + pSprite->flags |= kPhysGravity | kPhysFalling; + } + + if (pSprite->type == kDudeModernCustom) { + + aiGenDudeMoveForward(pSprite, pXSprite); + + } else { + + DUDEINFO* pDudeInfo = &dudeInfo[dudeIdx]; + int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4; + int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024; + int frontSpeed = pDudeInfo->frontSpeed; + + pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047; + if (klabs(nAng) <= 341) { + xvel[pSprite->index] += mulscale30(frontSpeed, Cos(pSprite->ang)); + yvel[pSprite->index] += mulscale30(frontSpeed, Sin(pSprite->ang)); + } + + } + + int vel = (aiPatrolCrouching(pXSprite->aiState)) ? kMaxPatrolCrouchVelocity : kMaxPatrolVelocity; + xvel[pSprite->index] = ClipRange(xvel[pSprite->index], -vel, vel); + yvel[pSprite->index] = ClipRange(yvel[pSprite->index], -vel, vel); + return; +} + +void aiPatrolAlarm(spritetype* pSprite, bool chain) { + + static short chainChance = 0; + if (chainChance <= 0) chainChance = 0x1000; + XSPRITE* pXSprite = &xsprite[pSprite->extra]; + DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type); + spritetype* pDude = NULL; XSPRITE* pXDude = NULL; + int target = pXSprite->target; + for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) { + pDude = &sprite[nSprite]; + if (pDude->index == pSprite->index || !IsDudeSprite(pDude) || IsPlayerSprite(pDude) || pDude->extra < 0) + continue; + + pXDude = &xsprite[pDude->extra]; + if (pXDude->health > 0 && approxDist(pDude->x - pSprite->x, pDude->y - pSprite->y) < pDudeInfo->seeDist) { + if (aiInPatrolState(pXDude->aiState)) aiPatrolStop(pDude, pXDude->target); + if (pXDude->target >= 0 || pXDude->target == pXSprite->target) + continue; + + if (spriRangeIsFine(target)) aiSetTarget(pXDude, target); + else aiSetTarget(pXDude, pXSprite->targetX, pXSprite->targetY, pXSprite->targetZ); + + aiActivateDude(pDude, pXDude); + if (chain) { + aiPatrolAlarm(pDude, Chance(chainChance)); + chainChance -= 0x0100; + } + + consoleSysMsg("Dude #%d alarms dude #%d", pSprite->index, pDude->index); + } + } + +} + +bool isTouchingSprite(int nXSprite1, int nXSprite2) { + + if (!xspriRangeIsFine(nXSprite1) || !xspriRangeIsFine(nXSprite2)) + return false; + + int nHSprite = -1; + if ((gSpriteHit[nXSprite1].hit & 0xc000) == 0xc000) nHSprite = gSpriteHit[nXSprite1].hit & 0x3fff; + else if ((gSpriteHit[nXSprite1].florhit & 0xc000) == 0xc000) nHSprite = gSpriteHit[nXSprite1].florhit & 0x3fff; + else if ((gSpriteHit[nXSprite1].ceilhit & 0xc000) == 0xc000) nHSprite = gSpriteHit[nXSprite1].ceilhit & 0x3fff; + return (spriRangeIsFine(nHSprite) && sprite[nHSprite].extra == nXSprite2); +} + +bool aiCanCrouch(spritetype* pSprite) { + + if (pSprite->type >= kDudeBase && pSprite->type < kDudeVanillaMax) + return (gDudeInfoExtra[pSprite->type - kDudeBase].idlcseqofs >= 0 && gDudeInfoExtra[pSprite->type - kDudeBase].mvecseqofs >= 0); + else if (pSprite->type == kDudeModernCustom || pSprite->type == kDudeModernCustomBurning) + return gGenDudeExtra[pSprite->index].canDuck; + + return false; + +} + + +bool readyForCrit(spritetype* pHunter, spritetype* pVictim) { + + if (!(pHunter->type >= kDudeBase && pHunter->type < kDudeMax) || !(pVictim->type >= kDudeBase && pVictim->type < kDudeMax)) + return false; + + int x, y, dx, dy, nDist; + x = pVictim->x; + y = pVictim->y; + + + dx = x - pHunter->x; + dy = y - pHunter->y; + if ((nDist = approxDist(dx, dy)) >= (6000 / ClipLow(gGameOptions.nDifficulty >> 1, 1))) + return false; + + DUDEINFO* pDudeInfo = getDudeInfo(pVictim->type); + int nDeltaAngle = ((getangle(dx, dy) + 1024 - pVictim->ang) & 2047) - 1024; + return (klabs(nDeltaAngle) < (pDudeInfo->periphery >> 1)); +} + +int aiPatrolSearchTargets(spritetype* pSprite, XSPRITE* pXSprite) { + + dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); + DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type); + int i, x, y, z, dx, dy, nDist, eyeAboveZ; + PLAYER* pPlayer = NULL; + + int target = -1; + // search for targets + for (i = headspritestat[kStatDude]; i >= 0; i = nextspritestat[i]) { + + target = -1; + spritetype* pSpr = &sprite[i]; + if (!xspriRangeIsFine(pSpr->extra)) + continue; + + XSPRITE* pXSpr = &xsprite[pSpr->extra]; + if (pSprite->owner == pSpr->index || pXSpr->health == 0) + continue; + + x = pSpr->x; + y = pSpr->y; + z = pSpr->z; + + dx = x - pSprite->x; + dy = y - pSprite->y; + nDist = approxDist(dx, dy); + + int seeChance = 0x0000, seeDist = pDudeInfo->seeDist / 3; eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2; + if (nDist > seeDist || !cansee(x, y, z, pSpr->sectnum, pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum)) continue; + else if ((pPlayer = getPlayerById(pSpr->type)) == NULL) { // check if this dude is a target for some others + + if (pXSpr->target != pSprite->index) continue; + else return pSpr->index; + + } + + bool invisible = (powerupCheck(pPlayer, kPwUpShadowCloak) > 0); + int hearDist = pDudeInfo->hearDist >> 1, hearChance = 0x0000; + int periphery = ClipLow(pDudeInfo->periphery, kAng90); + int nDeltaAngle = 1024; + + if (!invisible) { + + switch (pPlayer->lifeMode) { + case kModeHuman: + case kModeHumanShrink: + if (pPlayer->lifeMode == kModeHumanShrink) { + seeDist -= mulscale8(164, seeDist); + hearDist -= mulscale8(164, hearDist); + } + if (pPlayer->posture == kPostureCrouch) { + seeDist -= mulscale8(64, seeDist); + hearDist -= mulscale8(128, hearDist); + } + break; + case kModeHumanGrown: + if (pPlayer->posture != kPostureCrouch) { + seeDist += mulscale8(72, seeDist); + hearDist += mulscale8(64, hearDist); + } else { + seeDist += mulscale8(48, seeDist); + } + break; + } + + seeDist = ClipLow(seeDist, 0); + hearDist = ClipLow(hearDist, 0); + if (!pXSprite->dudeDeaf) + hearChance = mulscale8(1, + ClipLow(((hearDist - nDist) + (abs(xvel[pSpr->index]) + abs(yvel[pSpr->index]) + abs(zvel[pSpr->index]))) >> 6, 0)); + + if (!pXSprite->dudeGuard) { + + seeChance = 100 - mulscale8(ClipRange(5 - gGameOptions.nDifficulty, 1, 4), nDist >> 1); + nDeltaAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024; + + } + + } + + if (!isTouchingSprite(pSprite->extra, pSpr->extra) && !isTouchingSprite(pSpr->extra, pSprite->extra)) { + + + if (nDist < hearDist && hearChance > 0) { + consoleSysMsg("Patrol dude #%d hearing the Player #%d.", pSprite->index, pPlayer->nPlayer + 1); + pXSprite->data3 += hearChance; + } + + if (nDist < seeDist && klabs(nDeltaAngle) < periphery && seeChance > 0) { + consoleSysMsg("Patrol dude #%d seeing the Player #%d.", pSprite->index, pPlayer->nPlayer + 1); + pXSprite->data3 += seeChance; + } + + if ((pXSprite->data3 = ClipRange(pXSprite->data3, 0, kMaxPatrolSpotValue)) == kMaxPatrolSpotValue) { + target = pSpr->index; + break; + } + + + } else { + + consoleSysMsg("Patrol dude #%d spot the Player #%d via touch.", pSprite->index, pPlayer->nPlayer + 1); + if (invisible) pPlayer->pwUpTime[kPwUpShadowCloak] = 0; + target = pSpr->index; + break; + + } + + //int perc = (100 * ClipHigh(pXSprite->data3, kMaxPatrolSpotValue)) / kMaxPatrolSpotValue; + //viewSetSystemMessage("%d / %d / %d / %d", hearChance, seeDist, seeChance, perc); + + } + + if (target >= 0) return target; + else pXSprite->data3 -= ClipLow(((100 * pXSprite->data3) / kMaxPatrolSpotValue) >> 2, 3); + return -1; +} + +void aiPatrolThink(spritetype* pSprite, XSPRITE* pXSprite) { + + dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax); + + int nTarget = -1; + if ((nTarget = aiPatrolSearchTargets(pSprite, pXSprite)) != -1) { + aiPatrolStop(pSprite, nTarget, pXSprite->dudeAmbush); + return; + } + + int omarker = pXSprite->target; + spritetype* pMarker = NULL; XSPRITE* pXMarker = NULL; + if (spriRangeIsFine(omarker)) { + pMarker = &sprite[omarker]; + pXMarker = &xsprite[sprite[omarker].extra]; + } + + DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[pSprite->type - kDudeBase]; + bool crouch = aiPatrolCrouching(pXSprite->aiState), uwater = spriteIsUnderwater(pSprite); + bool isFinal = (spriRangeIsFine(omarker) && omarker == pXSprite->target && xsprite[sprite[omarker].extra].data2 < 0); + + if (pSprite->type == kDudeModernCustom && ((uwater && !canSwim(pSprite)) || !canWalk(pSprite))) aiPatrolStop(pSprite, -1); + else if (omarker <= 0) aiPatrolSetMarker(pSprite, pXSprite); + else if (aiPatrolWaiting(pXSprite->aiState)) { + + if (pXSprite->stateTimer > 0 || (pXMarker && pXMarker->data1 == pXMarker->data2)) + return; + + aiPatrolSetMarker(pSprite, pXSprite); + if (isFinal) { + aiPatrolStop(pSprite, -1); + return; + } + + } else if (aiPatrolMarkerReached(pSprite, pXSprite)) { + + pMarker = &sprite[omarker]; + pXMarker = &xsprite[sprite[omarker].extra]; + + if (pMarker->owner == pSprite->index) + pMarker->owner = -1; + + if (pMarker->flags > 0) { + if ((pMarker->flags & kModernTypeFlag1) && (pMarker->flags & kModernTypeFlag2)) + crouch = !crouch; + else if (pMarker->flags & kModernTypeFlag2) + crouch = false; + else if (pMarker->flags & kModernTypeFlag1 && aiCanCrouch(pSprite)) + crouch = true; + } + + trTriggerSprite(omarker, pXMarker, kCmdToggle); // trigger it! + + if (pXMarker->waitTime > 0 || pXMarker->data1 == pXMarker->data2) { + + if (uwater) aiPatrolState(pSprite, kAiStatePatrolWaitW); + else if (crouch) aiPatrolState(pSprite, kAiStatePatrolWaitC); + else aiPatrolState(pSprite, kAiStatePatrolWaitL); + + xvel[pSprite->index] = yvel[pSprite->index] = 0; + if (pExtra->flying) zvel[pSprite->index] = 0; + pSprite->ang = sprite[pXMarker->reference].ang & 2047; + if (pXMarker->waitTime) + pXSprite->stateTimer = (pXMarker->waitTime * 120) / 10; + + return; + + } else { + + aiPatrolSetMarker(pSprite, pXSprite); + + } + + // final marker reached, just make enemy to be normal + if (isFinal) { + aiPatrolStop(pSprite, -1); + return; + } + + } + + if (uwater) aiPatrolState(pSprite, kAiStatePatrolMoveW); + else if (crouch) aiPatrolState(pSprite, kAiStatePatrolMoveC); + else aiPatrolState(pSprite, kAiStatePatrolMoveL); + return; + +} +// ------------------------------------------------ + int listTx(XSPRITE* pXRedir, int tx) { if (txIsRanged(pXRedir)) { if (tx == -1) tx = pXRedir->data1; diff --git a/source/games/blood/src/nnexts.h b/source/games/blood/src/nnexts.h index 3a1ef8f85..50c5a4d37 100644 --- a/source/games/blood/src/nnexts.h +++ b/source/games/blood/src/nnexts.h @@ -37,6 +37,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "actor.h" #include "dude.h" #include "player.h" +#include "triggers.h" BEGIN_BLD_NS @@ -66,6 +67,11 @@ enum kCondRange = 100, }; +#define kPatrolStateSize 27 +#define kMaxPatrolVelocity 500000 +#define kMaxPatrolCrouchVelocity (kMaxPatrolVelocity >> 1) +#define kMaxPatrolSpotValue 500 + // modern statnums @@ -83,6 +89,7 @@ kStatModernMax = 40, // modern sprite types enum { +kModernSlopeChanger = 16, kModernCustomDudeSpawn = 24, kModernRandomTX = 25, kModernSequentialTX = 26, @@ -185,7 +192,12 @@ struct MISSILEINFO_EXTRA { struct DUDEINFO_EXTRA { bool flying; // used by kModernDudeTargetChanger (ai fight) bool melee; // used by kModernDudeTargetChanger (ai fight) - bool annoying; // used by kModernDudeTargetChanger (ai fight) + int idlgseqofs : 6; // used for patrol + int mvegseqofs : 6; // used for patrol + int idlwseqofs : 6; // used for patrol + int mvewseqofs : 6; // used for patrol + int idlcseqofs : 6; // used for patrol + int mvecseqofs : 6; // used for patrol }; struct TRPLAYERCTRL { // this one for controlling the player using triggers (movement speed, jumps and other stuff) @@ -228,6 +240,7 @@ extern short gSightSpritesCount; extern short gPhysSpritesCount; extern short gImpactSpritesCount; extern short gTrackingCondsCount; +extern AISTATE genPatrolStates[kPatrolStateSize]; // - FUNCTIONS ------------------------------------------------------------------ bool nnExtEraseModernStuff(spritetype* pSprite, XSPRITE* pXSprite); @@ -271,15 +284,16 @@ spritetype* aiFightGetTargetInRange(spritetype* pSprite, int minDist, int maxDis spritetype* aiFightTargetIsPlayer(XSPRITE* pXSprite); spritetype* aiFightGetMateTargets(XSPRITE* pXSprite); // ------------------------------------------------------------------------- // +void useSlopeChanger(XSPRITE* pXSource, int objType, int objIndex); void useSectorWindGen(XSPRITE* pXSource, sectortype* pSector); void useEffectGen(XSPRITE* pXSource, spritetype* pSprite); void useSeqSpawnerGen(XSPRITE* pXSource, int objType, int index); -void useSpriteDamager(XSPRITE* pXSource, spritetype* pSprite); +void damageSprites(XSPRITE* pXSource, spritetype* pSprite); void useTeleportTarget(XSPRITE* pXSource, spritetype* pSprite); void useObjResizer(XSPRITE* pXSource, short objType, int objIndex); void useRandomItemGen(spritetype* pSource, XSPRITE* pXSource); void useUniMissileGen(int, int nXSprite); -void useSoundGen(spritetype* pSource, XSPRITE* pXSource); +void useSoundGen(XSPRITE* pXSource, spritetype* pSprite); void useIncDecGen(XSPRITE* pXSource, short objType, int objIndex); void useDataChanger(XSPRITE* pXSource, int objType, int objIndex); void useSectorLigthChanger(XSPRITE* pXSource, XSECTOR* pXSector); @@ -305,6 +319,7 @@ void trPlayerCtrlUsePackItem(XSPRITE* pXSource, PLAYER* pPlayer, int evCmd); // ------------------------------------------------------------------------- // void modernTypeTrigger(int type, int nDest, EVENT event); char modernTypeSetSpriteState(int nSprite, XSPRITE* pXSprite, int nState); +bool modernTypeOperateSector(int nSector, sectortype* pSector, XSECTOR* pXSector, EVENT event); bool modernTypeOperateSprite(int nSprite, spritetype* pSprite, XSPRITE* pXSprite, EVENT event); bool modernTypeOperateWall(int nWall, walltype* pWall, XWALL* pXWall, EVENT event); void modernTypeSendCommand(int nSprite, int channel, COMMAND_ID command); @@ -355,6 +370,20 @@ XSPRITE* evrListRedirectors(int objType, int objXIndex, XSPRITE* pXRedir, int* t XSPRITE* evrIsRedirector(int nSprite); int listTx(XSPRITE* pXRedir, int tx); void seqSpawnerOffSameTx(XSPRITE* pXSource); +// ------------------------------------------------------------------------- // +void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite); +void aiPatrolThink(spritetype* pSprite, XSPRITE* pXSprite); +void aiPatrolStop(spritetype* pSprite, int target, bool alarm = false); +void aiPatrolAlarm(spritetype* pSprite, bool chain); +void aiPatrolState(spritetype* pSprite, int state); +void aiPatrolMove(spritetype* pSprite, XSPRITE* pXSprite); +int aiPatrolMarkerBusy(int nExcept, int nMarker); +bool aiPatrolMarkerReached(spritetype* pSprite, XSPRITE* pXSprite); +AISTATE* aiInPatrolState(AISTATE* pAiState); +// ------------------------------------------------------------------------- // +bool readyForCrit(spritetype* pHunter, spritetype* pVictim); +int sectorInMotion(int nSector); +#endif //////////////////////////////////////////////////////////////////////// // This file provides modern features for mappers. diff --git a/source/games/blood/src/player.cpp b/source/games/blood/src/player.cpp index 5e560d796..a7716d34c 100644 --- a/source/games/blood/src/player.cpp +++ b/source/games/blood/src/player.cpp @@ -2017,6 +2017,32 @@ int playerDamageSprite(DBloodActor* source, PLAYER *pPlayer, DAMAGE_TYPE nDamage } FragPlayer(pPlayer, nSource); trTriggerSprite(nSprite, pXSprite, kCmdOff); + + #ifdef NOONE_EXTENSIONS + // allow drop items and keys in multiplayer + if (gModernMap && gGameOptions.nGameType != 0 && pPlayer->pXSprite->health <= 0) { + + spritetype* pItem = NULL; + if (pPlayer->pXSprite->dropMsg && (pItem = actDropItem(pPlayer->pSprite, pPlayer->pXSprite->dropMsg)) != NULL) + evPost(pItem->index, OBJ_SPRITE, 500, kCallbackRemove); + + if (pPlayer->pXSprite->key) { + + int i; // if all players have this key, don't drop it + for (i = connecthead; i >= 0; i = connectpoint2[i]) { + if (!gPlayer[i].hasKey[pPlayer->pXSprite->key]) + break; + } + + if (i == 0 && (pItem = actDropKey(pPlayer->pSprite, (pPlayer->pXSprite->key + kItemKeyBase) - 1)) != NULL) + evPost(pItem->index, OBJ_SPRITE, 500, kCallbackRemove); + + } + + + } + #endif + } assert(getSequence(pDudeInfo->seqStartID + nDeathSeqID) != NULL); seqSpawn(pDudeInfo->seqStartID+nDeathSeqID, 3, nXSprite, nKneelingPlayer); diff --git a/source/games/blood/src/triggers.cpp b/source/games/blood/src/triggers.cpp index 533be2d25..535712cf5 100644 --- a/source/games/blood/src/triggers.cpp +++ b/source/games/blood/src/triggers.cpp @@ -132,26 +132,7 @@ char SetSectorState(int nSector, XSECTOR *pXSector, int nState) } int gBusyCount = 0; - -enum BUSYID { - BUSYID_0 = 0, - BUSYID_1, - BUSYID_2, - BUSYID_3, - BUSYID_4, - BUSYID_5, - BUSYID_6, - BUSYID_7, -}; - -struct BUSY { - int index; - int delta; - int busy; - int type; -}; - -BUSY gBusy[128]; +BUSY gBusy[]; void AddBusy(int a1, BUSYID a2, int nDelta) { @@ -164,7 +145,7 @@ void AddBusy(int a1, BUSYID a2, int nDelta) } if (i == gBusyCount) { - if (gBusyCount == 128) + if (gBusyCount == kMaxBusyCount) return; gBusy[i].index = a1; gBusy[i].type = a2; @@ -1544,22 +1525,10 @@ void OperateSector(unsigned int nSector, XSECTOR *pXSector, EVENT event) sectortype *pSector = §or[nSector]; #ifdef NOONE_EXTENSIONS - if (gModernMap) { - switch (pSector->type) { - // reset counter sector state and make it work again after unlock, so it can be used again. - case kSectorCounter: - switch (event.cmd) { - case kCmdUnlock: - case kCmdToggleLock: - if (pXSector->locked != 1) break; - SetSectorState(nSector, pXSector, 0); - evPost(nSector, 6, 0, kCallbackCounterCheck); - break; - } - break; - } - } + if (gModernMap && modernTypeOperateSector(nSector, pSector, pXSector, event)) + return; #endif + switch (event.cmd) { case kCmdLock: pXSector->locked = 1; @@ -1583,6 +1552,9 @@ void OperateSector(unsigned int nSector, XSECTOR *pXSector, EVENT event) pXSector->stopOff = 1; break; default: + #ifdef NOONE_EXTENSIONS + if (gModernMap && pXSector->unused1) break; + #endif switch (pSector->type) { case kSectorZMotionSprite: OperateDoor(nSector, pXSector, event, BUSYID_1); @@ -1992,9 +1964,15 @@ void trProcessBusy(void) memset(velCeil, 0, sizeof(velCeil)); for (int i = gBusyCount-1; i >= 0; i--) { - int oldBusy = gBusy[i].busy; - gBusy[i].busy = ClipRange(oldBusy+gBusy[i].delta*4, 0, 65536); - int nStatus = gBusyProc[gBusy[i].type](gBusy[i].index, gBusy[i].busy); + int nStatus; + int oldBusy = gBusy[i].at8; + gBusy[i].at8 = ClipRange(oldBusy+gBusy[i].at4*4, 0, 65536); + #ifdef NOONE_EXTENSIONS + if (!gModernMap || !xsector[sector[gBusy[i].at0].extra].unused1) nStatus = gBusyProc[gBusy[i].atc](gBusy[i].at0, gBusy[i].at8); + else nStatus = 3; // allow to pause/continue motion for sectors any time by sending special command + #else + nStatus = gBusyProc[gBusy[i].atc](gBusy[i].at0, gBusy[i].at8); + #endif switch (nStatus) { case 1: gBusy[i].busy = oldBusy; diff --git a/source/games/blood/src/triggers.h b/source/games/blood/src/triggers.h index 68e05cb6a..f80b913cf 100644 --- a/source/games/blood/src/triggers.h +++ b/source/games/blood/src/triggers.h @@ -29,7 +29,31 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "dude.h" #include "player.h" -BEGIN_BLD_NS +enum BUSYID { + BUSYID_0 = 0, + BUSYID_1, + BUSYID_2, + BUSYID_3, + BUSYID_4, + BUSYID_5, + BUSYID_6, + BUSYID_7, +}; + +#define kMaxBusyCount 128 +struct BUSY { + int index; + int delta; + int busy; +/* int at0; + int at4; + int at8;*/ + BUSYID atc; +}; + +extern BUSY gBusy[kMaxBusyCount]; +extern int gBusyCount; + void trTriggerSector(unsigned int nSector, XSECTOR *pXSector, int command); void trMessageSector(unsigned int nSector, EVENT event); void trTriggerWall(unsigned int nWall, XWALL *pXWall, int command); @@ -42,6 +66,9 @@ void trInit(void); void trTextOver(int nId); char SetSpriteState(int nSprite, XSPRITE* pXSprite, int nState); char SetWallState(int nWall, XWALL* pXWall, int nState); +char SetSectorState(int nSector, XSECTOR* pXSector, int nState); void TeleFrag(int nKiller, int nSector); +void SectorStartSound(int nSector, int nState); +void SectorEndSound(int nSector, int nState); END_BLD_NS diff --git a/source/games/blood/src/view.h b/source/games/blood/src/view.h index b3a05747f..04959aae9 100644 --- a/source/games/blood/src/view.h +++ b/source/games/blood/src/view.h @@ -95,6 +95,7 @@ enum VIEW_EFFECT { VIEW_EFFECT_16, VIEW_EFFECT_17, VIEW_EFFECT_18, + kViewEffectSpotProgress, }; enum VIEWPOS {