raze/source/games/blood/src/nnexts.cpp

9449 lines
282 KiB
C++
Raw Normal View History

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
Copyright (C) NoOne
This file is part of NBlood.
NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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.
*/
//-------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////
// This file provides modern features for mappers.
// For full documentation please visit http://cruo.bloodgame.ru/xxsystem
///////////////////////////////////////////////////////////////////
#include "ns.h"
#ifdef NOONE_EXTENSIONS
#include <random>
#include "blood.h"
#include "savegamehelp.h"
BEGIN_BLD_NS
inline int mulscale8(int a, int b) { return MulScale(a, b, 8); }
bool gAllowTrueRandom = false;
bool gEventRedirectsUsed = false;
2022-05-06 16:27:44 +00:00
CVARD(Bool, nnext_showconditionsprites, false, 0, "makes kModernCondition sprites visable")
2020-12-06 20:56:09 +00:00
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
short gEffectGenCallbacks[] = {
2021-12-29 21:56:21 +00:00
kCallbackFXFlameLick,
kCallbackFXFlareSpark,
kCallbackFXFlareSparkLite,
kCallbackFXZombieSpurt,
kCallbackFXBloodSpurt,
kCallbackFXArcSpark,
kCallbackFXTeslaAlt,
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
};
TRPLAYERCTRL gPlayerCtrl[kMaxPlayers];
TRCONDITION gCondition[kMaxTrackingConditions];
int gTrackingCondsCount;
std::default_random_engine gStdRandom;
const VECTORINFO_EXTRA gVectorInfoExtra[] = {
2021-12-29 21:56:21 +00:00
1207,1207, 1001,1001, 4001,4002,
431,431, 1002,1002, 359,359,
521,521, 513,513, 499,499,
9012,9014, 1101,1101, 1207,1207,
499,495, 495,496, 9013,499,
1307,1308, 499,499, 499,499,
499,499, 499,499, 351,351,
0,0, 357,499
};
const MISSILEINFO_EXTRA gMissileInfoExtra[] = {
2021-12-29 21:56:21 +00:00
1207, 1207, false, false, false, false, false, true, false, true,
420, 420, false, true, true, false, false, false, false, true,
471, 471, false, false, false, false, false, false, true, false,
421, 421, false, true, false, true, false, false, false, false,
1309, 351, false, true, false, false, false, false, false, true,
480, 480, false, true, false, true, false, false, false, false,
470, 470, false, false, false, false, false, false, true, true,
489, 490, false, false, false, false, false, true, false, true,
462, 351, false, true, false, false, false, false, false, true,
1203, 172, false, false, true, false, false, false, false, true,
0,0, false, false, true, false, false, false, false, true,
1457, 249, false, false, false, false, false, true, false, true,
480, 489, false, true, false, true, false, false, false, false,
480, 489, false, false, false, true, false, false, false, false,
480, 489, false, false, false, true, false, false, false, false,
491, 491, true, true, true, true, true, true, true, true,
520, 520, false, false, false, false, false, true, false, true,
520, 520, false, false, false, false, false, true, false, true,
};
const THINGINFO_EXTRA gThingInfoExtra[] = {
2021-12-29 21:56:21 +00:00
true, true, true, false, false,
false, false, false, false, false,
false, false, false, false, false,
true, false, false, true, true,
true, true, false, false, false,
false, false, true, true, true,
true, true, true, true, true,
true,
};
const DUDEINFO_EXTRA gDudeInfoExtra[] = {
2021-12-29 21:56:21 +00:00
{ 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
2020-12-06 20:56:09 +00:00
};
AISTATE genPatrolStates[] = {
2021-12-29 21:56:21 +00:00
//-------------------------------------------------------------------------------
{ 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 },
{ kAiStatePatrolTurnL, 9, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnL, 8, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnL, 0, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnL, 6, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnL, 7, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, 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 },
{ kAiStatePatrolTurnW, 0, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnW, 10, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnW, 13, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnW, 8, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnW, 9, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnW, 7, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnW, 6, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, 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 },
{ kAiStatePatrolTurnC, 14, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnC, 10, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
{ kAiStatePatrolTurnC, 9, -1, 0, aiPatrolRandGoalAng, aiPatrolTurn, aiPatrolThink, NULL },
//-------------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
};
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
CONDITION_TYPE_NAMES gCondTypeNames[7] = {
2021-12-29 21:56:21 +00:00
{kCondGameBase, kCondGameMax, "Game"},
{kCondMixedBase, kCondMixedMax, "Mixed"},
{kCondWallBase, kCondWallMax, "Wall"},
{kCondSectorBase, kCondSectorMax, "Sector"},
{kCondPlayerBase, kCondPlayerMax, "Player"},
{kCondDudeBase, kCondDudeMax, "Enemy"},
{kCondSpriteBase, kCondSpriteMax, "Sprite"},
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
};
// for actor.cpp
//-------------------------------------------------------------------------
2021-08-27 11:53:49 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-23 08:52:31 +00:00
static DBloodActor* nnExtSpawnDude(DBloodActor* sourceactor, DBloodActor* origin, int nType, int a3, int a4)
2021-08-27 11:53:49 +00:00
{
2021-12-29 21:56:21 +00:00
DBloodActor* pDudeActor = nullptr;
2021-08-27 11:53:49 +00:00
2021-12-29 21:56:21 +00:00
if (nType < kDudeBase || nType >= kDudeMax || (pDudeActor = actSpawnSprite(origin, kStatDude)) == NULL)
return NULL;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2022-08-16 21:17:01 +00:00
int angle = origin->int_ang();
auto pos = origin->spr.pos.plusZ(a4 * zinttoworld);
if (a3 >= 0)
2021-12-29 21:56:21 +00:00
{
pos.X += mulscale30r(Cos(angle), a3) * inttoworld;
pos.Y += mulscale30r(Sin(angle), a3) * inttoworld;
2021-12-29 21:56:21 +00:00
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
SetActor(pDudeActor, pos);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
pDudeActor->spr.type = nType;
2022-08-16 21:20:03 +00:00
pDudeActor->set_int_ang(angle);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
pDudeActor->spr.cstat |= CSTAT_SPRITE_BLOOD_BIT1 | CSTAT_SPRITE_BLOCK_ALL;
2022-09-09 16:32:42 +00:00
pDudeActor->set_native_clipdist(getDudeInfo(nType)->clipdist);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
pDudeActor->xspr.respawn = 1;
pDudeActor->xspr.health = getDudeInfo(nType)->startHealth << 4;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (fileSystem.FindResource(getDudeInfo(nType)->seqStartID, "SEQ"))
seqSpawn(getDudeInfo(nType)->seqStartID, pDudeActor, -1);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
// add a way to inherit some values of spawner by dude.
if (sourceactor->spr.flags & kModernTypeFlag1) {
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
//inherit pal?
if (pDudeActor->spr.pal <= 0)
pDudeActor->spr.pal = sourceactor->spr.pal;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
// inherit spawn sprite trigger settings, so designer can count monsters.
pDudeActor->xspr.txID = sourceactor->xspr.txID;
pDudeActor->xspr.command = sourceactor->xspr.command;
pDudeActor->xspr.triggerOn = sourceactor->xspr.triggerOn;
pDudeActor->xspr.triggerOff = sourceactor->xspr.triggerOff;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
// inherit drop items
pDudeActor->xspr.dropMsg = sourceactor->xspr.dropMsg;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
// inherit dude flags
pDudeActor->xspr.dudeDeaf = sourceactor->xspr.dudeDeaf;
pDudeActor->xspr.dudeGuard = sourceactor->xspr.dudeGuard;
pDudeActor->xspr.dudeAmbush = sourceactor->xspr.dudeAmbush;
pDudeActor->xspr.dudeFlag4 = sourceactor->xspr.dudeFlag4;
pDudeActor->xspr.unused1 = sourceactor->xspr.unused1;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
aiInitSprite(pDudeActor);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
gKillMgr.AddKill(pDudeActor);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
bool burning = IsBurningDude(pDudeActor);
if (burning) {
pDudeActor->xspr.burnTime = 10;
pDudeActor->SetTarget(nullptr);
}
if ((burning || (sourceactor->spr.flags & kModernTypeFlag3)) && !pDudeActor->xspr.dudeFlag4)
aiActivateDude(pDudeActor);
return pDudeActor;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-08-27 12:01:51 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
bool nnExtIsImmune(DBloodActor* actor, int dmgType, int minScale)
2021-08-27 12:01:51 +00:00
{
2021-12-29 21:56:21 +00:00
if (dmgType >= kDmgFall && dmgType < kDmgMax && actor->hasX() && actor->xspr.locked != 1)
{
if (actor->spr.type >= kThingBase && actor->spr.type < kThingMax)
{
return (thingInfo[actor->spr.type - kThingBase].dmgControl[dmgType] <= minScale);
}
else if (actor->IsDudeActor())
{
if (actor->IsPlayerActor()) return (gPlayer[actor->spr.type - kDudePlayer1].damageControl[dmgType]);
else if (actor->spr.type == kDudeModernCustom) return (actor->genDudeExtra.dmgControl[dmgType] <= minScale);
else return (getDudeInfo(actor->spr.type)->damageVal[dmgType] <= minScale);
}
}
2021-12-29 21:56:21 +00:00
return true;
}
2021-08-27 12:11:59 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool nnExtEraseModernStuff(DBloodActor* actor)
2021-08-27 12:11:59 +00:00
{
2021-12-29 21:56:21 +00:00
bool erased = false;
switch (actor->spr.type) {
// erase all modern types if the map is not extended
case kModernSpriteDamager:
case kModernCustomDudeSpawn:
case kModernRandomTX:
case kModernSequentialTX:
case kModernSeqSpawner:
case kModernObjPropertiesChanger:
case kModernObjPicnumChanger:
case kModernObjSizeChanger:
case kModernDudeTargetChanger:
case kModernSectorFXChanger:
case kModernObjDataChanger:
case kModernObjDataAccumulator:
case kModernEffectSpawner:
case kModernWindGenerator:
case kModernPlayerControl:
case kModernCondition:
case kModernConditionFalse:
case kModernSlopeChanger:
case kModernStealthRegion:
actor->spr.type = kSpriteDecoration;
erased = true;
break;
case kItemModernMapLevel:
case kDudeModernCustom:
case kDudeModernCustomBurning:
case kModernThingTNTProx:
case kModernThingEnemyLifeLeech:
actor->spr.type = kSpriteDecoration;
ChangeActorStat(actor, kStatDecoration);
erased = true;
break;
// also erase some modernized vanilla types which was not active
case kMarkerWarpDest:
if (actor->spr.statnum == kStatMarker) break;
actor->spr.type = kSpriteDecoration;
erased = true;
break;
}
2021-12-29 21:56:21 +00:00
if (actor->xspr.Sight)
{
actor->xspr.Sight = false; // it does not work in vanilla at all
erased = true;
}
2021-12-29 21:56:21 +00:00
if (actor->xspr.Proximity)
{
// proximity works only for things and dudes in vanilla
switch (actor->spr.statnum)
{
case kStatThing:
case kStatDude:
break;
default:
actor->xspr.Proximity = false;
erased = true;
break;
}
}
2021-12-29 21:56:21 +00:00
return erased;
}
//---------------------------------------------------------------------------
//
2021-08-29 07:44:08 +00:00
//
//---------------------------------------------------------------------------
void nnExtTriggerObject(EventObject& eob, int command, DBloodActor* initiator)
{
2021-12-29 21:56:21 +00:00
if (eob.isSector())
{
trTriggerSector(eob.sector(), command, initiator);
2021-12-29 21:56:21 +00:00
}
else if (eob.isWall())
{
trTriggerWall(eob.wall(), command, initiator);
2021-12-29 21:56:21 +00:00
}
else if (eob.isActor())
{
auto objActor = eob.actor();
if (!objActor || !objActor->hasX()) return;
trTriggerSprite(objActor, command, initiator);
2021-12-29 21:56:21 +00:00
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void nnExtResetGlobals()
{
gAllowTrueRandom = gEventRedirectsUsed = false;
// reset counters
gProxySpritesCount = gSightSpritesCount = gPhysSpritesCount = gImpactSpritesCount = 0;
// fill arrays with negative values to avoid index 0 situation
memset(gSightSpritesList, 0, sizeof(gSightSpritesList));
memset(gProxySpritesList, 0, sizeof(gProxySpritesList));
memset(gPhysSpritesList, 0, sizeof(gPhysSpritesList));
memset(gImpactSpritesList, 0, sizeof(gImpactSpritesList));
// reset tracking conditions, if any
for (size_t i = 0; i < countof(gCondition); i++)
{
TRCONDITION* pCond = &gCondition[i];
for (unsigned k = 0; k < kMaxTracedObjects; k++)
{
pCond->obj[k].obj = EventObject(nullptr);
}
pCond->actor = nullptr;
pCond->length = 0;
}
gTrackingCondsCount = 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2022-05-06 16:27:44 +00:00
CCMD(nnext_ifshow)
{
if (!isBlood()) return;
BloodSpriteIterator it;
int cnt = 0;
while (auto actor = it.Next())
{
if (actor->spr.type == kModernCondition || actor->spr.type == kModernConditionFalse)
{
if (actor->spr.cstat & CSTAT_SPRITE_INVISIBLE)
{
actor->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
cnt++;
}
}
}
if (cnt <= 0)
Printf("No condition sprites found!\n");
Printf("%d sprites are visible now.\n", cnt);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void nnExtInitModernStuff(TArray<DBloodActor*>& actors)
{
nnExtResetGlobals();
// use true random only for single player mode, otherwise use Blood's default one.
if (gGameOptions.nGameType == 0 && !VanillaMode())
{
gStdRandom.seed(std::random_device()());
// since true random is not working if compiled with old mingw versions, we should
// check if it works in game and if not - switch to using in-game random function.
for (int i = kMaxRandomizeRetries; i >= 0; i--)
{
std::uniform_int_distribution<int> dist_a_b(0, 100);
if (gAllowTrueRandom || i <= 0) break;
else if (dist_a_b(gStdRandom) != 0)
gAllowTrueRandom = true;
}
}
for (auto actor : actors)
{
if (!actor->exists() || !actor->hasX()) continue;
switch (actor->spr.type) {
case kModernRandomTX:
case kModernSequentialTX:
if (actor->xspr.command == kCmdLink) gEventRedirectsUsed = true;
break;
case kDudeModernCustom:
case kDudeModernCustomBurning:
getSpriteMassBySize(actor); // create mass cache
break;
case kModernCondition:
case kModernConditionFalse:
if (!actor->xspr.rxID && actor->xspr.data1 > kCondGameMax) condError(actor, "\nThe condition must have RX ID!\nSPRITE #%d", actor->GetIndex());
else if (!actor->xspr.txID && !actor->spr.flags)
{
Printf(PRINT_HIGH, "The condition must have TX ID or hitag to be set: RX ID %d, SPRITE #%d", actor->xspr.rxID, actor->GetIndex());
}
break;
}
// auto set going On and going Off if both are empty
if (actor->xspr.txID && !actor->xspr.triggerOn && !actor->xspr.triggerOff)
actor->xspr.triggerOn = actor->xspr.triggerOff = true;
// copy custom start health to avoid overwrite by kThingBloodChunks
if (actor->IsDudeActor())
actor->xspr.sysData2 = actor->xspr.data4;
// check reserved statnums
if (actor->spr.statnum >= kStatModernBase && actor->spr.statnum < kStatModernMax)
{
bool sysStat = true;
switch (actor->spr.statnum)
{
case kStatModernStealthRegion:
sysStat = (actor->spr.type != kModernStealthRegion);
break;
case kStatModernDudeTargetChanger:
sysStat = (actor->spr.type != kModernDudeTargetChanger);
break;
case kStatModernCondition:
sysStat = (actor->spr.type != kModernCondition && actor->spr.type != kModernConditionFalse);
break;
case kStatModernEventRedirector:
sysStat = (actor->spr.type != kModernRandomTX && actor->spr.type != kModernSequentialTX);
break;
case kStatModernWindGen:
sysStat = (actor->spr.type != kModernWindGenerator);
break;
case kStatModernPlayerLinker:
case kStatModernQavScene:
sysStat = (actor->spr.type != kModernPlayerControl);
break;
}
if (sysStat)
I_Error("Sprite statnum %d on sprite #%d is in a range of reserved (%d - %d)!", actor->spr.statnum, actor->GetIndex(), kStatModernBase, kStatModernMax);
}
switch (actor->spr.type)
{
case kModernRandomTX:
case kModernSequentialTX:
if (actor->xspr.command != kCmdLink) break;
// add statnum for faster redirects search
ChangeActorStat(actor, kStatModernEventRedirector);
break;
case kModernWindGenerator:
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
ChangeActorStat(actor, kStatModernWindGen);
break;
case kModernDudeTargetChanger:
case kModernObjDataAccumulator:
case kModernRandom:
case kModernRandom2:
case kModernStealthRegion:
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
switch (actor->spr.type)
{
// stealth regions for patrolling enemies
case kModernStealthRegion:
ChangeActorStat(actor, kStatModernStealthRegion);
break;
// add statnum for faster dude searching
case kModernDudeTargetChanger:
ChangeActorStat(actor, kStatModernDudeTargetChanger);
if (actor->xspr.busyTime <= 0) actor->xspr.busyTime = 5;
actor->xspr.command = kCmdLink;
break;
// remove kStatItem status from random item generators
case kModernRandom:
case kModernRandom2:
ChangeActorStat(actor, kStatDecoration);
actor->xspr.sysData1 = actor->xspr.command; // save the command so spawned item can inherit it
actor->xspr.command = kCmdLink; // generator itself can't send commands
break;
}
break;
case kModernThingTNTProx:
actor->xspr.Proximity = true;
break;
case kDudeModernCustom:
{
if (actor->xspr.txID <= 0) break;
int found = 0;
BloodStatIterator it(kStatDude);
while (DBloodActor* iactor = it.Next())
{
if (iactor->xspr.rxID != actor->xspr.txID) continue;
else if (found) I_Error("\nCustom dude (TX ID %d):\nOnly one incarnation allowed per channel!", actor->xspr.txID);
ChangeActorStat(iactor, kStatInactive);
found++;
}
break;
}
case kDudePodMother:
case kDudeTentacleMother:
actor->xspr.state = 1;
break;
case kModernPlayerControl:
switch (actor->xspr.command)
{
case kCmdLink:
{
if (actor->xspr.data1 < 1 || actor->xspr.data1 > kMaxPlayers)
I_Error("\nPlayer Control (SPRITE #%d):\nPlayer out of a range (data1 = %d)", actor->GetIndex(), actor->xspr.data1);
//if (numplayers < actor->xspr.data1)
//I_Error("\nPlayer Control (SPRITE #%d):\n There is no player #%d", actor->GetIndex(), actor->xspr.data1);
if (actor->xspr.rxID && actor->xspr.rxID != kChannelLevelStart)
I_Error("\nPlayer Control (SPRITE #%d) with Link command should have no RX ID!", actor->GetIndex());
if (actor->xspr.txID && actor->xspr.txID < kChannelUser)
I_Error("\nPlayer Control (SPRITE #%d):\nTX ID should be in range of %d and %d!", actor->GetIndex(), kChannelUser, kChannelMax);
// only one linker per player allowed
BloodStatIterator it(kStatModernPlayerLinker);
while (auto iactor = it.Next())
{
if (actor->xspr.data1 == iactor->xspr.data1)
I_Error("\nPlayer Control (SPRITE #%d):\nPlayer %d already linked with different player control sprite #%d!", actor->GetIndex(), actor->xspr.data1, iactor->GetIndex());
}
actor->xspr.sysData1 = -1;
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
ChangeActorStat(actor, kStatModernPlayerLinker);
break;
}
case 67: // play qav animation
if (actor->xspr.txID >= kChannelUser && !actor->xspr.waitTime) actor->xspr.waitTime = 1;
2021-12-29 21:56:21 +00:00
ChangeActorStat(actor, kStatModernQavScene);
break;
}
break;
case kModernCondition:
case kModernConditionFalse:
if (actor->xspr.busyTime > 0)
{
if (actor->xspr.waitTime > 0)
{
actor->xspr.busyTime += ClipHigh(((actor->xspr.waitTime * 120) / 10), 4095); actor->xspr.waitTime = 0;
Printf(PRINT_HIGH, "Summing busyTime and waitTime for tracking condition #%d, RX ID %d. Result = %d ticks", actor->GetIndex(), actor->xspr.rxID, actor->xspr.busyTime);
}
actor->xspr.busy = actor->xspr.busyTime;
}
if (actor->xspr.waitTime && actor->xspr.command >= kCmdNumberic)
condError(actor, "Delay is not available when using numberic commands (%d - %d)", kCmdNumberic, 255);
actor->xspr.Decoupled = false; // must go through operateSprite always
actor->xspr.Sight = actor->xspr.Impact = actor->xspr.Touch = actor->xspr.triggerOff = false;
actor->xspr.Proximity = actor->xspr.Push = actor->xspr.Vector = actor->xspr.triggerOn = false;
actor->xspr.state = actor->xspr.restState = 0;
2022-08-22 16:31:23 +00:00
actor->xspr.TargetPos = {-1, -1, -1};
2021-12-29 21:56:21 +00:00
2022-08-22 16:31:23 +00:00
actor->xspr.sysData2 = -1;
2021-12-29 21:56:21 +00:00
actor->SetTarget(nullptr);
ChangeActorStat(actor, kStatModernCondition);
auto oldStat = actor->spr.cstat;
2022-05-06 16:27:44 +00:00
actor->spr.cstat = 0;
2021-12-29 21:56:21 +00:00
if (oldStat & CSTAT_SPRITE_BLOCK)
actor->spr.cstat |= CSTAT_SPRITE_BLOCK;
if (oldStat & CSTAT_SPRITE_MOVE_FORWARD) actor->spr.cstat |= CSTAT_SPRITE_MOVE_FORWARD;
else if (oldStat & CSTAT_SPRITE_MOVE_REVERSE) actor->spr.cstat |= CSTAT_SPRITE_MOVE_REVERSE;
2022-05-06 16:27:44 +00:00
if (!nnext_showconditionsprites)
actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
2021-12-29 21:56:21 +00:00
break;
}
// the following trigger flags are senseless to have together
if ((actor->xspr.Touch && (actor->xspr.Proximity || actor->xspr.Sight) && actor->xspr.DudeLockout)
|| (actor->xspr.Touch && actor->xspr.Proximity && !actor->xspr.Sight)) actor->xspr.Touch = false;
if (actor->xspr.Proximity && actor->xspr.Sight && actor->xspr.DudeLockout)
actor->xspr.Proximity = false;
// very quick fix for floor sprites with Touch trigger flag if their Z is equals sector florz / ceilgz
if (actor->insector() && actor->xspr.Touch && (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) {
if (actor->spr.pos.Z == actor->sector()->floorz) actor->spr.pos.Z -= zmaptoworld;
else if (actor->spr.pos.Z == actor->sector()->ceilingz) actor->spr.pos.Z += zmaptoworld;
2021-12-29 21:56:21 +00:00
}
// make Proximity flag work not just for dudes and things...
if (actor->xspr.Proximity && gProxySpritesCount < kMaxSuperXSprites)
{
switch (actor->spr.statnum)
{
case kStatFX: case kStatExplosion: case kStatItem:
case kStatPurge: case kStatSpares: case kStatFlare:
case kStatInactive: case kStatFree: case kStatMarker:
case kStatThing: case kStatDude:
2021-12-29 21:56:21 +00:00
case kStatModernPlayerLinker:
break;
default:
gProxySpritesList[gProxySpritesCount++] = actor;
if (gProxySpritesCount == kMaxSuperXSprites)
I_Error("Max (%d) *additional* Proximity sprites reached!", kMaxSuperXSprites);
break;
}
}
// make Sight, Screen, Aim flags work not just for dudes and things...
if ((actor->xspr.Sight || actor->xspr.unused3) && gSightSpritesCount < kMaxSuperXSprites)
{
switch (actor->spr.statnum)
{
case kStatFX: case kStatExplosion: case kStatItem:
case kStatPurge: case kStatSpares: case kStatFlare:
case kStatInactive: case kStatFree: case kStatMarker:
case kStatModernPlayerLinker:
2021-12-29 21:56:21 +00:00
break;
default:
gSightSpritesList[gSightSpritesCount++] = actor;
if (gSightSpritesCount == kMaxSuperXSprites)
I_Error("Max (%d) Sight sprites reached!", kMaxSuperXSprites);
break;
}
}
// make Impact flag work for sprites that affected by explosions...
if (actor->xspr.Impact && gImpactSpritesCount < kMaxSuperXSprites)
{
switch (actor->spr.statnum)
{
case kStatFX: case kStatExplosion: case kStatItem:
case kStatPurge: case kStatSpares: case kStatFlare:
case kStatInactive: case kStatFree: case kStatMarker:
case kStatModernPlayerLinker:
2021-12-29 21:56:21 +00:00
break;
default:
gImpactSpritesList[gImpactSpritesCount++] = actor;
if (gImpactSpritesCount == kMaxSuperXSprites)
I_Error("Max (%d) *additional* Impact sprites reached!", kMaxSuperXSprites);
break;
}
}
}
// collect objects for tracking conditions
BloodStatIterator it2(kStatModernCondition);
while (auto iactor = it2.Next())
{
if (iactor->xspr.busyTime <= 0 || iactor->xspr.isTriggered) continue;
else if (gTrackingCondsCount >= kMaxTrackingConditions)
I_Error("\nMax (%d) tracking conditions reached!", kMaxTrackingConditions);
int count = 0;
TRCONDITION* pCond = &gCondition[gTrackingCondsCount];
for (auto iactor2 : actors)
{
if (!iactor->exists() || !iactor2->hasX() || iactor2->xspr.txID != iactor->xspr.rxID || iactor2 == iactor)
continue;
switch (iactor2->spr.type)
{
case kSwitchToggle: // exceptions
case kSwitchOneWay: // exceptions
continue;
}
if (iactor2->spr.type == kModernCondition || iactor2->spr.type == kModernConditionFalse)
condError(iactor, "Tracking condition always must be first in condition sequence!");
if (count >= kMaxTracedObjects)
condError(iactor, "Max(%d) objects to track reached for condition #%d, RXID: %d!");
pCond->obj[count].obj = EventObject(iactor2);
pCond->obj[count++].cmd = (uint8_t)iactor2->xspr.command;
}
for (auto& sect : sector)
{
if (!sect.hasX() || sect.xs().txID != iactor->xspr.rxID) continue;
else if (count >= kMaxTracedObjects)
condError(iactor, "Max(%d) objects to track reached for condition #%d, RXID: %d!");
pCond->obj[count].obj = EventObject(&sect);
pCond->obj[count++].cmd = sect.xs().command;
}
for (auto& wal : wall)
{
if (!wal.hasX() || wal.xw().txID != iactor->xspr.rxID)
continue;
switch (wal.type) {
case kSwitchToggle: // exceptions
case kSwitchOneWay: // exceptions
continue;
}
if (count >= kMaxTracedObjects)
condError(iactor, "Max(%d) objects to track reached for condition #%d, RXID: %d!");
pCond->obj[count].obj = EventObject(&wal);
pCond->obj[count++].cmd = wal.xw().command;
}
if (iactor->xspr.data1 > kCondGameMax && count == 0)
Printf(PRINT_HIGH, "No objects to track found for condition #%d, RXID: %d!", iactor->GetIndex(), iactor->xspr.rxID);
pCond->length = count;
pCond->actor = iactor;
gTrackingCondsCount++;
}
}
// The following functions required for random event features
2021-08-27 15:09:55 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
int nnExtRandom(int a, int b)
2021-08-27 15:09:55 +00:00
{
2021-12-29 21:56:21 +00:00
if (!gAllowTrueRandom) return Random(((b + 1) - a)) + a;
// used for better randomness in single player
std::uniform_int_distribution<int> dist_a_b(a, b);
return dist_a_b(gStdRandom);
}
2021-12-29 21:56:21 +00:00
static int GetDataVal(DBloodActor* actor, int data)
2021-08-27 15:09:55 +00:00
{
2021-12-29 21:56:21 +00:00
if (!actor->hasX()) return -1;
switch (data) {
case 0: return actor->xspr.data1;
case 1: return actor->xspr.data2;
case 2: return actor->xspr.data3;
case 3: return actor->xspr.data4;
}
2021-12-29 21:56:21 +00:00
return -1;
}
2021-08-27 15:09:55 +00:00
//---------------------------------------------------------------------------
//
// tries to get random data field of sprite
2021-08-27 15:09:55 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
static int randomGetDataValue(DBloodActor* actor, int randType)
{
if (actor == NULL || !actor->hasX()) return -1;
int random = 0; int bad = 0; int maxRetries = kMaxRandomizeRetries;
int rData[4];
rData[0] = actor->xspr.data1; rData[2] = actor->xspr.data3;
rData[1] = actor->xspr.data2; rData[3] = actor->xspr.data4;
// randomize only in case if at least 2 data fields fits.
for (int i = 0; i < 4; i++)
{
switch (randType) {
case kRandomizeItem:
if (rData[i] >= kItemWeaponBase && rData[i] < kItemMax) break;
else bad++;
break;
case kRandomizeDude:
if (rData[i] >= kDudeBase && rData[i] < kDudeMax) break;
else bad++;
break;
case kRandomizeTX:
if (rData[i] > kChannelZero && rData[i] < kChannelUserMax) break;
else bad++;
break;
default:
bad++;
break;
}
}
if (bad < 3)
{
// try randomize few times
while (maxRetries > 0)
{
random = nnExtRandom(0, 3);
if (rData[random] > 0) return rData[random];
else maxRetries--;
}
}
return -1;
}
//---------------------------------------------------------------------------
//
// this function drops random item using random pickup generator(s)
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
static DBloodActor* randomDropPickupObject(DBloodActor* sourceactor, int prevItem)
{
2021-12-29 21:56:21 +00:00
DBloodActor* spawned = nullptr;
int selected = -1;
int maxRetries = 9;
if (sourceactor->hasX())
{
while ((selected = randomGetDataValue(sourceactor, kRandomizeItem)) == prevItem) if (maxRetries-- <= 0) break;
if (selected > 0)
{
spawned = actDropObject(sourceactor, selected);
if (spawned)
{
sourceactor->xspr.dropMsg = uint8_t(spawned->spr.type); // store dropped item type in dropMsg
spawned->spr.pos = sourceactor->spr.pos;
2021-12-29 21:56:21 +00:00
if ((sourceactor->spr.flags & kModernTypeFlag1) && (sourceactor->xspr.txID > 0 || (sourceactor->xspr.txID != 3 && sourceactor->xspr.lockMsg > 0)))
{
spawned->addX();
2021-12-29 21:56:21 +00:00
// inherit spawn sprite trigger settings, so designer can send command when item picked up.
spawned->xspr.txID = sourceactor->xspr.txID;
spawned->xspr.command = sourceactor->xspr.sysData1;
spawned->xspr.triggerOn = sourceactor->xspr.triggerOn;
spawned->xspr.triggerOff = sourceactor->xspr.triggerOff;
2021-12-29 21:56:21 +00:00
spawned->xspr.Pickup = true;
2021-12-29 21:56:21 +00:00
}
}
}
}
return spawned;
}
//---------------------------------------------------------------------------
//
// this function spawns random dude using dudeSpawn
//
//---------------------------------------------------------------------------
DBloodActor* randomSpawnDude(DBloodActor* sourceactor, DBloodActor* origin, int a3, int a4)
2020-12-05 17:32:49 +00:00
{
2021-12-29 21:56:21 +00:00
DBloodActor* spawned = NULL; int selected = -1;
if ((selected = randomGetDataValue(sourceactor, kRandomizeDude)) > 0)
spawned = nnExtSpawnDude(sourceactor, origin, selected, a3, 0);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
return spawned;
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
static void windGenDoVerticalWind(int factor, sectortype* pSector)
2021-10-12 21:42:18 +00:00
{
double maxZ = 0, zdiff;
bool maxZfound = false;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
// find maxz marker first
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.type == kMarkerOn && actor->spr.statnum != kStatMarker)
{
maxZ = actor->spr.pos.Z;
2021-12-29 21:56:21 +00:00
maxZfound = true;
break;
}
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
it.Reset(pSector);
while (auto actor = it.Next())
{
switch (actor->spr.statnum)
{
case kStatFree:
continue;
case kStatFX:
if (actor->vel.Z) break;
2021-12-29 21:56:21 +00:00
continue;
case kStatThing:
case kStatDude:
if (actor->spr.flags & kPhysGravity) break;
continue;
default:
if (actor->hasX() && actor->xspr.physAttr & kPhysGravity) break;
continue;
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
if (maxZfound && actor->spr.pos.Z <= maxZ)
2021-12-29 21:56:21 +00:00
{
zdiff = actor->spr.pos.Z - maxZ;
if (actor->vel.Z < 0) actor->vel.Z += actor->vel.Z * zdiff / 4096;
2021-12-29 21:56:21 +00:00
continue;
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
double val = -factor / 1024.;
if (actor->vel.Z >= 0) actor->vel.Z += val;
else actor->vel.Z = val;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2022-09-16 16:21:49 +00:00
actor->spr.pos.Z += actor->vel.Z / 16.;
2021-12-29 21:56:21 +00:00
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void nnExtProcessSuperSprites()
{
2021-12-29 21:56:21 +00:00
// process tracking conditions
if (gTrackingCondsCount > 0)
{
for (int i = 0; i < gTrackingCondsCount; i++)
{
TRCONDITION const* pCond = &gCondition[i];
auto aCond = pCond->actor;
if (aCond->xspr.locked || aCond->xspr.isTriggered || ++aCond->xspr.busy < aCond->xspr.busyTime)
continue;
2022-05-06 16:27:44 +00:00
if (pCond->length > 0)
2021-12-29 21:56:21 +00:00
{
aCond->xspr.busy = 0;
for (unsigned k = 0; k < pCond->length; k++)
{
EVENT evn;
evn.target = pCond->obj[k].obj;
evn.cmd = pCond->obj[k].cmd;
evn.funcID = kCallbackMax;
evn.initiator = nullptr;
2021-12-29 21:56:21 +00:00
useCondition(pCond->actor, evn);
}
}
2022-05-06 16:27:44 +00:00
else if (aCond->xspr.data1 >= kCondGameBase && aCond->xspr.data1 < kCondGameMax)
{
EVENT evn;
evn.target = EventObject(pCond->actor);
evn.cmd = (int8_t)aCond->xspr.command;
evn.funcID = kCallbackMax;
evn.initiator = nullptr;
2022-05-06 16:27:44 +00:00
useCondition(pCond->actor, evn);
}
2021-12-29 21:56:21 +00:00
}
}
// process floor oriented kModernWindGenerator to create a vertical wind in the sectors
BloodStatIterator it(kStatModernWindGen);
while (auto windactor = it.Next())
{
if (!(windactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR) || windactor->spr.statnum >= kMaxStatus || !windactor->hasX())
continue;
if (!windactor->xspr.state || windactor->xspr.locked)
continue;
int j, rx;
bool fWindAlways = (windactor->spr.flags & kModernTypeFlag1);
if (windactor->xspr.txID) {
rx = windactor->xspr.txID;
for (j = bucketHead[rx]; j < bucketHead[rx + 1]; j++)
{
if (!rxBucket[j].isSector()) continue;
auto pSector = rxBucket[j].sector();
XSECTOR* pXSector = &pSector->xs();
if ((!pXSector->locked) && (fWindAlways || pXSector->windAlways || pXSector->busy))
windGenDoVerticalWind(windactor->xspr.sysData2, pSector);
}
DBloodActor* pXRedir = nullptr; // check redirected TX buckets
while ((pXRedir = evrListRedirectors(OBJ_SPRITE, nullptr, nullptr, windactor, pXRedir, &rx)) != nullptr)
{
for (j = bucketHead[rx]; j < bucketHead[rx + 1]; j++)
{
if (!rxBucket[j].isSector()) continue;
auto pSector = rxBucket[j].sector();
XSECTOR* pXSector = &pSector->xs();
if ((!pXSector->locked) && (fWindAlways || pXSector->windAlways || pXSector->busy))
windGenDoVerticalWind(windactor->xspr.sysData2, pSector);
}
}
}
else if (windactor->insector())
2021-12-29 21:56:21 +00:00
{
sectortype* pSect = windactor->sector();
2021-12-29 21:56:21 +00:00
XSECTOR* pXSector = (pSect->hasX()) ? &pSect->xs() : nullptr;
if ((fWindAlways) || (pXSector && !pXSector->locked && (pXSector->windAlways || pXSector->busy)))
windGenDoVerticalWind(windactor->xspr.sysData2, windactor->sector());
2021-12-29 21:56:21 +00:00
}
}
// process additional proximity sprites
if (gProxySpritesCount > 0)
{
for (int i = 0; i < gProxySpritesCount; i++)
{
DBloodActor* pProx = gProxySpritesList[i];
if (!pProx || !pProx->hasX()) continue;
if (!pProx->xspr.Proximity || (!pProx->xspr.Interrutable && pProx->xspr.state != pProx->xspr.restState) || pProx->xspr.locked == 1
|| pProx->xspr.isTriggered) continue; // don't process locked or triggered sprites
int okDist = (pProx->IsDudeActor()) ? 96 : ClipLow(pProx->native_clipdist() * 3, 32);
auto pos = pProx->spr.pos;
auto pSect = pProx->sector();
2021-12-29 21:56:21 +00:00
if (!pProx->xspr.DudeLockout)
{
BloodStatIterator itr(kStatDude);
while (auto affected = itr.Next())
{
if (!affected->hasX() || affected->xspr.health <= 0) continue;
else if (CheckProximity(affected, pos, pSect, okDist))
2021-12-29 21:56:21 +00:00
{
trTriggerSprite(pProx, kCmdSpriteProximity, affected);
2021-12-29 21:56:21 +00:00
break;
}
}
}
else
{
for (int a = connecthead; a >= 0; a = connectpoint2[a])
{
PLAYER* pPlayer = &gPlayer[a];
if (!pPlayer || !pPlayer->actor->hasX() || pPlayer->actor->xspr.health <= 0)
continue;
if (pPlayer->actor->xspr.health > 0 && CheckProximity(gPlayer->actor, pos, pSect, okDist))
2021-12-29 21:56:21 +00:00
{
trTriggerSprite(pProx, kCmdSpriteProximity, pPlayer->actor);
2021-12-29 21:56:21 +00:00
}
}
}
}
}
// process sight sprites (for players only)
if (gSightSpritesCount > 0)
{
for (int i = 0; i < gSightSpritesCount; i++)
{
DBloodActor* pSight = gSightSpritesList[i];
if (!pSight || !pSight->hasX()) continue;
if ((!pSight->xspr.Interrutable && pSight->xspr.state != pSight->xspr.restState) || pSight->xspr.locked == 1 ||
pSight->xspr.isTriggered) continue; // don't process locked or triggered sprites
// sprite is drawn for one of players
if ((pSight->xspr.unused3 & kTriggerSpriteScreen) && (pSight->spr.cstat2 & CSTAT2_SPRITE_MAPPED))
{
trTriggerSprite(pSight, kCmdSpriteSight);
pSight->spr.cstat2 &= ~CSTAT2_SPRITE_MAPPED;
continue;
}
2022-08-22 16:37:46 +00:00
auto pSightSect = pSight->sector();
2022-08-22 16:37:46 +00:00
double ztop2, zbot2;
2021-12-29 21:56:21 +00:00
for (int a = connecthead; a >= 0; a = connectpoint2[a])
{
PLAYER* pPlayer = &gPlayer[a];
if (!pPlayer || !pPlayer->actor->hasX() || pPlayer->actor->xspr.health <= 0)
continue;
auto plActor = pPlayer->actor;
GetActorExtents(plActor, &ztop2, &zbot2);
2022-08-22 16:37:46 +00:00
if (cansee(pSight->spr.pos, pSightSect, DVector3(plActor->spr.pos.XY(), ztop2), plActor->sector()))
2021-12-29 21:56:21 +00:00
{
if (pSight->xspr.Sight)
{
trTriggerSprite(pSight, kCmdSpriteSight, plActor);
2021-12-29 21:56:21 +00:00
}
if (pSight->xspr.unused3 & kTriggerSpriteAim)
{
bool vector = (pSight->spr.cstat & CSTAT_SPRITE_BLOCK_HITSCAN);
if (!vector)
pSight->spr.cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
HitScan(pPlayer->actor, pPlayer->zWeapon, pPlayer->aim.dx, pPlayer->aim.dy, pPlayer->aim.dz, CLIPMASK0 | CLIPMASK1, 0);
if (!vector)
pSight->spr.cstat &= ~CSTAT_SPRITE_BLOCK_HITSCAN;
if (gHitInfo.actor() == pSight)
{
trTriggerSprite(gHitInfo.actor(), kCmdSpriteSight, plActor);
2021-12-29 21:56:21 +00:00
}
}
}
}
}
}
// process Debris sprites for movement
if (gPhysSpritesCount > 0)
{
for (int i = 0; i < gPhysSpritesCount; i++)
{
DBloodActor* debrisactor = gPhysSpritesList[i];
if (debrisactor == nullptr || !debrisactor->hasX()) continue;
if (debrisactor->spr.statnum == kStatFree || (debrisactor->spr.flags & kHitagFree) != 0)
{
gPhysSpritesList[i] = nullptr;
continue;
}
if (!(debrisactor->xspr.physAttr & kPhysMove) && !(debrisactor->xspr.physAttr & kPhysGravity))
{
gPhysSpritesList[i] = nullptr;
continue;
}
XSECTOR* pXSector = (debrisactor->sector()->hasX()) ? &debrisactor->sector()->xs() : nullptr;
2021-12-29 21:56:21 +00:00
viewBackupSpriteLoc(debrisactor);
bool uwater = false;
int mass = debrisactor->spriteMass.mass;
int airVel = debrisactor->spriteMass.airVel;
2022-09-03 22:38:26 +00:00
double top, bottom;
2021-12-29 21:56:21 +00:00
GetActorExtents(debrisactor, &top, &bottom);
if (pXSector != nullptr)
{
if ((uwater = pXSector->Underwater) != 0) airVel <<= 6;
if (pXSector->panVel != 0 && getflorzofslopeptr(debrisactor->sector(), debrisactor->spr.pos) <= bottom)
2021-12-29 21:56:21 +00:00
{
int angle = pXSector->panAngle.Buildang(); int speed = 0;
2021-12-29 21:56:21 +00:00
if (pXSector->panAlways || pXSector->state || pXSector->busy)
{
speed = pXSector->panVel << 9;
if (!pXSector->panAlways && pXSector->busy)
speed = MulScale(speed, pXSector->busy, 16);
}
if (debrisactor->sector()->floorstat & CSTAT_SECTOR_ALIGN)
angle = (angle + GetWallAngle(debrisactor->sector()->firstWall()) + 512) & 2047;
2021-12-29 21:56:21 +00:00
int dx = MulScale(speed, Cos(angle), 30);
int dy = MulScale(speed, Sin(angle), 30);
2022-09-01 19:02:20 +00:00
debrisactor->add_int_bvel_x(dx);
2022-09-01 19:05:25 +00:00
debrisactor->add_int_bvel_y(dy);
2021-12-29 21:56:21 +00:00
}
}
actAirDrag(debrisactor, airVel);
if (debrisactor->xspr.physAttr & kPhysDebrisTouch)
{
PLAYER* pPlayer = NULL;
for (int a = connecthead; a != -1; a = connectpoint2[a])
{
pPlayer = &gPlayer[a];
DBloodActor* pact = pPlayer->actor;
if (pact && pact->hit.hit.type == kHitSprite && pact->hit.hit.actor() == debrisactor)
{
2022-09-01 18:49:35 +00:00
int nSpeed = approxDist(pact->int_vel().X, pact->int_vel().Y);
2021-12-29 21:56:21 +00:00
nSpeed = ClipLow(nSpeed - MulScale(nSpeed, mass, 6), 0x9000 - (mass << 3));
2022-09-01 19:02:20 +00:00
debrisactor->add_int_bvel_x(MulScale(nSpeed, Cos(pPlayer->actor->int_ang()), 30));
2022-09-01 19:05:25 +00:00
debrisactor->add_int_bvel_y(MulScale(nSpeed, Sin(pPlayer->actor->int_ang()), 30));
2021-12-29 21:56:21 +00:00
debrisactor->hit.hit.setSprite(pPlayer->actor);
}
}
}
if (debrisactor->xspr.physAttr & kPhysGravity) debrisactor->xspr.physAttr |= kPhysFalling;
if ((debrisactor->xspr.physAttr & kPhysFalling) || debrisactor->vel.X != 0 || debrisactor->vel.Y != 0 || debrisactor->vel.Z != 0 || debrisactor->sector()->velFloor || debrisactor->sector()->velCeil)
2021-12-29 21:56:21 +00:00
debrisMove(i);
2022-09-03 08:12:09 +00:00
if (debrisactor->vel.X != 0 || debrisactor->int_vel().Y)
2022-09-03 22:38:26 +00:00
debrisactor->xspr.goalAng = VecToAngle(debrisactor->vel);
2021-12-29 21:56:21 +00:00
2022-09-03 22:38:26 +00:00
debrisactor->norm_ang();
DAngle ang = debrisactor->spr.angle;
2021-12-29 21:56:21 +00:00
if ((uwater = spriteIsUnderwater(debrisactor)) == false) evKillActor(debrisactor, kCallbackEnemeyBubble);
else if (Chance(0x1000 - mass))
{
2022-09-03 08:12:09 +00:00
if (debrisactor->vel.Z > 0x100) debrisBubble(debrisactor);
2022-09-03 22:38:26 +00:00
if (absangle(ang, debrisactor->xspr.goalAng) < minAngle) // need to be very careful with comparing angles for equality!
2021-12-29 21:56:21 +00:00
{
2022-09-03 22:38:26 +00:00
debrisactor->xspr.goalAng += RandomAngle(kAng60);
debrisactor->norm_ang();
2021-12-29 21:56:21 +00:00
debrisBubble(debrisactor);
}
}
2022-09-03 22:38:26 +00:00
int vdist = max((int)(abs(debrisactor->vel.X) + abs(debrisactor->vel.Y) * 2048.), (uwater) ? 1 : 0);
auto angStep = DAngle::fromBuild(vdist);
2021-12-29 21:56:21 +00:00
2022-09-03 22:38:26 +00:00
if (ang < debrisactor->xspr.goalAng) debrisactor->spr.angle = min(ang + angStep, debrisactor->xspr.goalAng);
else if (ang > debrisactor->xspr.goalAng) debrisactor->spr.angle = max(ang - angStep, debrisactor->xspr.goalAng);
2021-12-29 21:56:21 +00:00
2022-09-03 22:38:26 +00:00
auto pSector = debrisactor->sector();
double fz, cz;
getzsofslopeptr(pSector, debrisactor->spr.pos, &cz, &fz);
2021-12-29 21:56:21 +00:00
GetActorExtents(debrisactor, &top, &bottom);
2022-09-03 22:38:26 +00:00
if (fz >= bottom && pSector->lowerLink == nullptr && !(pSector->ceilingstat & CSTAT_SECTOR_SKY)) debrisactor->spr.pos.Z += max(cz - top, 0.);
if (cz <= top && pSector->upperLink == nullptr && !(pSector->floorstat & CSTAT_SECTOR_SKY)) debrisactor->spr.pos.Z += min(fz - bottom, 0.);
2021-12-29 21:56:21 +00:00
}
}
}
2021-08-27 17:12:22 +00:00
//---------------------------------------------------------------------------
//
// this function plays sound predefined in missile info
2021-08-27 17:12:22 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void sfxPlayMissileSound(DBloodActor* actor, int missileId)
2021-08-27 17:12:22 +00:00
{
2021-12-29 21:56:21 +00:00
const MISSILEINFO_EXTRA* pMissType = &gMissileInfoExtra[missileId - kMissileBase];
sfxPlay3DSound(actor, Chance(0x5000) ? pMissType->fireSound[0] : pMissType->fireSound[1], -1, 0);
}
2021-08-27 17:12:22 +00:00
//---------------------------------------------------------------------------
//
// this function plays sound predefined in vector info
2021-08-27 17:12:22 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void sfxPlayVectorSound(DBloodActor* actor, int vectorId)
2021-08-27 17:12:22 +00:00
{
2021-12-29 21:56:21 +00:00
const VECTORINFO_EXTRA* pVectorData = &gVectorInfoExtra[vectorId];
sfxPlay3DSound(actor, Chance(0x5000) ? pVectorData->fireSound[0] : pVectorData->fireSound[1], -1, 0);
}
2021-08-27 17:12:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int getSpriteMassBySize(DBloodActor* actor)
{
2022-09-09 16:32:42 +00:00
int mass = 0; int seqId = -1; int clipDist = actor->native_clipdist();
2021-12-29 21:56:21 +00:00
if (!actor->hasX())
{
I_Error("getSpriteMassBySize: actor->spr.hasX == false");
}
else if (actor->IsDudeActor())
{
switch (actor->spr.type)
{
case kDudePodMother: // fake dude, no seq
break;
case kDudeModernCustom:
case kDudeModernCustomBurning:
seqId = actor->xspr.data2;
clipDist = actor->genDudeExtra.initVals[2];
break;
default:
seqId = getDudeInfo(actor->spr.type)->seqStartID;
break;
}
}
else
{
seqId = seqGetID(actor);
}
SPRITEMASS* cached = &actor->spriteMass;
if (((seqId >= 0 && seqId == cached->seqId) || actor->spr.picnum == cached->picnum) && actor->spr.xrepeat == cached->xrepeat &&
actor->spr.yrepeat == cached->yrepeat && clipDist == cached->clipdist)
{
return cached->mass;
}
int picnum = actor->spr.picnum;
int massDiv = 30;
int addMul = 2;
int subMul = 2;
if (seqId >= 0)
{
auto pSeq = getSequence(seqId);
if (pSeq)
{
picnum = seqGetTile(&pSeq->frames[0]);
}
else
picnum = actor->spr.picnum;
}
2022-09-09 16:32:42 +00:00
clipDist = ClipLow(actor->native_clipdist(), 1);
2021-12-29 21:56:21 +00:00
int x = tileWidth(picnum);
int y = tileHeight(picnum);
int xrepeat = actor->spr.xrepeat;
int yrepeat = actor->spr.yrepeat;
// take surface type into account
switch (tileGetSurfType(actor->spr.picnum))
{
case 1: massDiv = 16; break; // stone
case 2: massDiv = 18; break; // metal
case 3: massDiv = 21; break; // wood
case 4: massDiv = 25; break; // flesh
case 5: massDiv = 28; break; // water
case 6: massDiv = 26; break; // dirt
case 7: massDiv = 27; break; // clay
case 8: massDiv = 35; break; // snow
case 9: massDiv = 22; break; // ice
case 10: massDiv = 37; break; // leaves
case 11: massDiv = 33; break; // cloth
case 12: massDiv = 36; break; // plant
case 13: massDiv = 24; break; // goo
case 14: massDiv = 23; break; // lava
}
mass = ((x + y) * (clipDist / 2)) / massDiv;
if (xrepeat > 64) mass += ((xrepeat - 64) * addMul);
else if (xrepeat < 64 && mass > 0)
{
for (int i = 64 - xrepeat; i > 0; i--)
{
if ((mass -= subMul) <= 100 && subMul-- <= 1)
{
mass -= i;
break;
}
}
}
if (yrepeat > 64) mass += ((yrepeat - 64) * addMul);
else if (yrepeat < 64 && mass > 0)
{
for (int i = 64 - yrepeat; i > 0; i--)
{
if ((mass -= subMul) <= 100 && subMul-- <= 1)
{
mass -= i;
break;
}
}
}
if (mass <= 0) cached->mass = 1 + Random(10);
else cached->mass = ClipRange(mass, 1, 65535);
cached->airVel = ClipRange(400 - cached->mass, 32, 400);
cached->fraction = ClipRange(60000 - (cached->mass << 7), 8192, 60000);
cached->xrepeat = actor->spr.xrepeat;
cached->yrepeat = actor->spr.yrepeat;
cached->picnum = actor->spr.picnum;
cached->seqId = seqId;
2022-09-09 16:32:42 +00:00
cached->clipdist = actor->native_clipdist();
2021-12-29 21:56:21 +00:00
return cached->mass;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
static int debrisGetIndex(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (!actor->hasX() || actor->xspr.physAttr == 0)
return -1;
2021-12-29 21:56:21 +00:00
for (int i = 0; i < gPhysSpritesCount; i++)
{
if (gPhysSpritesList[i] != actor) continue;
return i;
}
2021-12-29 21:56:21 +00:00
return -1;
}
2021-12-29 21:56:21 +00:00
int debrisGetFreeIndex(void)
{
2021-12-29 21:56:21 +00:00
for (int i = 0; i < kMaxSuperXSprites; i++)
{
if (gPhysSpritesList[i] == nullptr) return i;
auto actor = gPhysSpritesList[i];
if (actor->spr.statnum == kStatFree) return i;
2021-12-29 21:56:21 +00:00
else if ((actor->spr.flags & kHitagFree) || !gPhysSpritesList[i]->hasX()) return i;
else if (gPhysSpritesList[i]->xspr.physAttr == 0) return i;
}
2021-12-29 21:56:21 +00:00
return -1;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2022-09-25 12:16:26 +00:00
void debrisConcuss(DBloodActor* owneractor, int listIndex, const DVector3& pos, int dmg)
{
2021-12-29 21:56:21 +00:00
DBloodActor* actor = gPhysSpritesList[listIndex];
if (actor != nullptr && actor->hasX())
{
2022-09-25 12:16:26 +00:00
auto dv = actor->spr.pos - pos;
dmg = int(dmg * (0x4000 / (0x4000 + dv.LengthSquared())));
2021-12-29 21:56:21 +00:00
bool thing = (actor->spr.type >= kThingBase && actor->spr.type < kThingMax);
int size = (tileWidth(actor->spr.picnum) * actor->spr.xrepeat * tileHeight(actor->spr.picnum) * actor->spr.yrepeat) >> 1;
if (actor->xspr.physAttr & kPhysDebrisExplode)
{
if (actor->spriteMass.mass > 0)
{
2022-09-25 12:16:26 +00:00
double t = double(dmg) * size / actor->spriteMass.mass;
2021-12-29 21:56:21 +00:00
2022-09-25 12:16:26 +00:00
actor->vel += dv * t / (1 << 24);
2021-12-29 21:56:21 +00:00
}
2021-12-29 21:56:21 +00:00
if (thing)
actor->spr.statnum = kStatThing; // temporary change statnum property
}
2021-12-29 21:56:21 +00:00
actDamageSprite(owneractor, actor, kDamageExplode, dmg);
2021-12-29 21:56:21 +00:00
if (thing)
actor->spr.statnum = kStatDecoration; // return statnum property back
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-12 22:46:21 +00:00
void debrisBubble(DBloodActor* actor)
2021-09-01 19:53:33 +00:00
{
2022-09-25 12:49:13 +00:00
double top, bottom;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &top, &bottom);
for (unsigned int i = 0; i < 1 + Random(5); i++) {
2022-09-25 12:49:13 +00:00
double nDist = actor->spr.xrepeat * tileWidth(actor->spr.picnum) * (REPEAT_SCALE * 0.5); // original code ended with * 8 which is 1/2 map unit.
DAngle nAngle = RandomAngle();
DVector3 pos;
pos.XY() = actor->spr.pos.XY() + nAngle.ToVector() * nDist;
pos.Z = bottom - RandomD(bottom - top, 8);
auto pFX = gFX.fxSpawnActor((FX_ID)(FX_23 + Random(3)), actor->sector(), pos, nullAngle);
2021-12-29 21:56:21 +00:00
if (pFX) {
2022-09-03 08:09:28 +00:00
pFX->vel.X = actor->vel.X + Random2F(0x1aaaa);
pFX->vel.Y = actor->vel.Y + Random2F(0x1aaaa);
pFX->vel.Z = actor->vel.Z + Random2F(0x1aaaa);
2021-12-29 21:56:21 +00:00
}
2021-12-29 21:56:21 +00:00
}
if (Chance(0x2000))
evPostActor(actor, 0, kCallbackEnemeyBubble);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-12 22:48:50 +00:00
void debrisMove(int listIndex)
{
2021-12-29 21:56:21 +00:00
DBloodActor* actor = gPhysSpritesList[listIndex];
auto pSector = actor->sector();
2021-12-29 21:56:21 +00:00
if (!actor->hasX() || !pSector)
{
gPhysSpritesList[listIndex] = nullptr;
return;
}
2022-09-24 15:33:59 +00:00
double top, bottom;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &top, &bottom);
Collision moveHit;
moveHit.setNone();
2022-09-24 15:33:59 +00:00
double floorDist = (bottom - actor->spr.pos.Z) * 0.25;
double ceilDist = (actor->spr.pos.Z - top) * 0.25;
2022-09-09 16:07:44 +00:00
int clipDist = actor->int_clipdist();
2021-12-29 21:56:21 +00:00
int mass = actor->spriteMass.mass;
bool uwater = false;
int tmpFraction = actor->spriteMass.fraction;
if (pSector->hasX() && pSector->xs().Underwater)
{
tmpFraction >>= 1;
uwater = true;
}
2022-09-24 15:33:59 +00:00
if (actor->vel.X != 0 || actor->vel.Y != 0)
2021-12-29 21:56:21 +00:00
{
auto oldcstat = actor->spr.cstat;
actor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
2022-09-24 15:33:59 +00:00
ClipMove(actor->spr.pos, &pSector, actor->vel.XY(), clipDist, ceilDist, floorDist, CLIPMASK0, moveHit);
2021-12-29 21:56:21 +00:00
actor->hit.hit = moveHit;
actor->spr.cstat = oldcstat;
if (actor->sector() != pSector)
2021-12-29 21:56:21 +00:00
{
if (!pSector) return;
else ChangeActorSect(actor, pSector);
}
if (pSector->type >= kSectorPath && pSector->type <= kSectorRotate)
{
auto pSector2 = pSector;
if (pushmove(actor, &pSector2, clipDist, ceilDist, floorDist, CLIPMASK0) != -1)
2021-12-29 21:56:21 +00:00
pSector = pSector2;
}
if (actor->hit.hit.type == kHitWall)
{
moveHit = actor->hit.hit;
actWallBounceVector(actor, moveHit.hitWall, FixedToFloat(tmpFraction));
2021-12-29 21:56:21 +00:00
}
}
else
2021-12-29 21:56:21 +00:00
{
updatesectorz(actor->spr.pos, &pSector);
if (!pSector) return;
2021-12-29 21:56:21 +00:00
}
if (actor->sector() != pSector)
2021-12-29 21:56:21 +00:00
{
assert(pSector);
ChangeActorSect(actor, pSector);
pSector = actor->sector();
2021-12-29 21:56:21 +00:00
}
if (pSector->hasX())
uwater = pSector->xs().Underwater;
2022-09-16 16:21:49 +00:00
actor->spr.pos.Z += actor->vel.Z;
2021-12-29 21:56:21 +00:00
2022-09-24 15:33:59 +00:00
double ceilZ, floorZ;
2021-12-29 21:56:21 +00:00
Collision ceilColl, floorColl;
GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, clipDist, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
GetActorExtents(actor, &top, &bottom);
if ((actor->xspr.physAttr & kPhysDebrisSwim) && uwater)
{
2022-09-24 15:33:59 +00:00
double vc = 0;
double cz = getceilzofslopeptr(pSector, actor->spr.pos);
double fz = getflorzofslopeptr(pSector, actor->spr.pos);
2022-09-24 15:33:59 +00:00
double div = max(bottom - top, 1 / 256.);
2021-12-29 21:56:21 +00:00
2022-09-24 15:33:59 +00:00
if (pSector->lowerLink) cz += (cz < 0) ? 5. : -5.;
if (top > cz && (!(actor->xspr.physAttr & kPhysDebrisFloat) || fz <= bottom * 4))
actor->vel.Z -= (bottom - ceilZ) * mass / 64.;
2021-12-29 21:56:21 +00:00
if (fz < bottom)
2022-09-24 15:33:59 +00:00
vc = 0.888888 + ((bottom - fz) * -1.222222) / div;
2021-12-29 21:56:21 +00:00
if (vc)
{
2022-09-24 15:33:59 +00:00
actor->spr.pos.Z += vc * 2;
actor->vel.Z = vc;
2021-12-29 21:56:21 +00:00
}
}
else if ((actor->xspr.physAttr & kPhysGravity) && bottom < floorZ)
{
actor->spr.pos.Z += 1.777;
2022-09-24 15:33:59 +00:00
actor->vel.Z += 0.888888;
2021-12-29 21:56:21 +00:00
}
int i;
if ((i = CheckLink(actor)) != 0)
{
GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, clipDist, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
if (!(actor->spr.cstat & CSTAT_SPRITE_INVISIBLE))
{
switch (i)
{
case kMarkerUpWater:
case kMarkerUpGoo:
int pitch = (150000 - (actor->spriteMass.mass << 9)) + Random3(8192);
sfxPlay3DSoundCP(actor, 720, -1, 0, pitch, 75 - Random(40));
if (!spriteIsUnderwater(actor))
{
evKillActor(actor, kCallbackEnemeyBubble);
}
else
{
evPostActor(actor, 0, kCallbackEnemeyBubble);
for (int ii = 2; ii <= 5; ii++)
{
if (Chance(0x5000 * ii))
evPostActor(actor, Random(5), kCallbackEnemeyBubble);
}
}
break;
}
}
}
GetActorExtents(actor, &top, &bottom);
if (floorZ <= bottom) {
actor->hit.florhit = floorColl;
double veldiff = actor->vel.Z - actor->sector()->velFloor;
2021-12-29 21:56:21 +00:00
if (veldiff > 0)
2021-12-29 21:56:21 +00:00
{
actor->xspr.physAttr |= kPhysFalling;
auto vec4 = actFloorBounceVector(actor, veldiff, actor->sector(), FixedToFloat(tmpFraction));
actor->vel = vec4.XYZ();
veldiff = actor->vel.Z;
2021-12-29 21:56:21 +00:00
if (abs(actor->vel.Z) < 1)
2021-12-29 21:56:21 +00:00
{
actor->vel.Z = actor->sector()->velFloor;
2021-12-29 21:56:21 +00:00
actor->xspr.physAttr &= ~kPhysFalling;
}
moveHit = floorColl;
DBloodActor* pFX = NULL, * pFX2 = NULL;
switch (tileGetSurfType(floorColl))
{
case kSurfLava:
2022-09-24 15:33:59 +00:00
if ((pFX = gFX.fxSpawnActor(FX_10, actor->sector(), DVector3(actor->spr.pos.XY(), floorZ), 0)) == NULL) break;
2021-12-29 21:56:21 +00:00
for (i = 0; i < 7; i++)
{
if ((pFX2 = gFX.fxSpawnActor(FX_14, pFX->sector(), pFX->spr.pos, 0)) == NULL) continue;
pFX2->vel.X = Random2F(0x6aaaa);
pFX2->vel.Y = Random2F(0x6aaaa);
pFX2->vel.Z = -Random2F(0xd5555);
2021-12-29 21:56:21 +00:00
}
break;
case kSurfWater:
2022-09-24 15:33:59 +00:00
gFX.fxSpawnActor(FX_9, actor->sector(), DVector3(actor->spr.pos.XY(), floorZ), 0);
2021-12-29 21:56:21 +00:00
break;
}
}
2022-09-03 08:12:09 +00:00
else if (actor->vel.Z == 0)
2021-12-29 21:56:21 +00:00
{
actor->xspr.physAttr &= ~kPhysFalling;
}
}
else
{
actor->hit.florhit.setNone();
if (actor->xspr.physAttr & kPhysGravity)
actor->xspr.physAttr |= kPhysFalling;
}
if (top <= ceilZ)
{
actor->hit.ceilhit = moveHit = ceilColl;
2022-09-24 15:33:59 +00:00
actor->spr.pos.Z += max(ceilZ - top, 0.);
if (actor->vel.Z <= 0 && (actor->xspr.physAttr & kPhysFalling))
actor->vel.Z *= 0.875;
2021-12-29 21:56:21 +00:00
}
else
{
actor->hit.ceilhit.setNone();
GetActorExtents(actor, &top, &bottom);
}
if (moveHit.type != kHitNone && actor->xspr.Impact && !actor->xspr.locked && !actor->xspr.isTriggered && (actor->xspr.state == actor->xspr.restState || actor->xspr.Interrutable)) {
if (actor->spr.type >= kThingBase && actor->spr.type < kThingMax)
ChangeActorStat(actor, kStatThing);
trTriggerSprite(actor, kCmdToggle, actor);
2021-12-29 21:56:21 +00:00
}
2022-09-03 08:12:09 +00:00
if (actor->vel.X == 0 && actor->vel.Y == 0) return;
2021-12-29 21:56:21 +00:00
else if (floorColl.type == kHitSprite)
{
if ((floorColl.actor()->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == 0)
{
2022-09-24 15:33:59 +00:00
actor->vel.XY() += (actor->spr.pos - floorColl.actor()->spr.pos) / 4096.;
2021-12-29 21:56:21 +00:00
return;
}
}
2022-09-24 15:33:59 +00:00
actor->xspr.height = int(max(floorZ - bottom, 0.));
2021-12-29 21:56:21 +00:00
if (uwater || actor->xspr.height >= 0x100)
return;
2022-09-24 15:33:59 +00:00
double nDrag = 0.1640625;
2021-12-29 21:56:21 +00:00
if (actor->xspr.height > 0)
2022-09-24 15:33:59 +00:00
nDrag *= 1 - actor->xspr.height / 256.;
2021-12-29 21:56:21 +00:00
2022-09-24 15:33:59 +00:00
actor->vel.XY() *= 1 - nDrag;
if (actor->vel.XY().LengthSquared() < 1 / 256.)
actor->ZeroVelocityXY();
}
2021-08-28 07:50:01 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool ceilIsTooLow(DBloodActor* actor)
2021-08-28 07:50:01 +00:00
{
2021-12-29 21:56:21 +00:00
if (actor != nullptr)
{
sectortype* pSector = actor->sector();
2022-09-16 17:03:02 +00:00
double a = pSector->ceilingz - pSector->floorz;
double top, bottom;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &top, &bottom);
2022-09-16 17:03:02 +00:00
if (a > top - bottom) return true;
2021-12-29 21:56:21 +00:00
}
return false;
}
2021-08-28 07:50:01 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiSetGenIdleState(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
switch (actor->spr.type)
{
case kDudeModernCustom:
case kDudeModernCustomBurning:
aiGenDudeNewState(actor, &genIdle);
break;
default:
aiNewState(actor, &genIdle);
break;
}
}
2021-08-28 07:50:01 +00:00
//---------------------------------------------------------------------------
//
// this function stops wind on all TX sectors affected by WindGen after it goes off state.
2021-08-28 07:50:01 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void windGenStopWindOnSectors(DBloodActor* sourceactor)
2021-08-28 07:50:01 +00:00
{
if (sourceactor->xspr.txID <= 0 && sourceactor->sector()->hasX())
2021-12-29 21:56:21 +00:00
{
sourceactor->sector()->xs().windVel = 0;
2021-12-29 21:56:21 +00:00
return;
}
for (int i = bucketHead[sourceactor->xspr.txID]; i < bucketHead[sourceactor->xspr.txID + 1]; i++)
{
if (!rxBucket[i].isSector()) continue;
auto pSector = rxBucket[i].sector();
XSECTOR* pXSector = &pSector->xs();
2021-12-29 21:56:21 +00:00
if ((pXSector->state == 1 && !pXSector->windAlways)
|| ((sourceactor->spr.flags & kModernTypeFlag1) && !(sourceactor->spr.flags & kModernTypeFlag2)))
{
pXSector->windVel = 0;
}
}
2021-12-29 21:56:21 +00:00
// check redirected TX buckets
int rx = -1;
DBloodActor* pXRedir = nullptr;
while ((pXRedir = evrListRedirectors(OBJ_SPRITE, nullptr, nullptr, sourceactor, pXRedir, &rx)) != nullptr)
{
for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++)
{
if (!rxBucket[i].isSector()) continue;
auto pSector = rxBucket[i].sector();
XSECTOR* pXSector = &pSector->xs();
2021-12-29 21:56:21 +00:00
if ((pXSector->state == 1 && !pXSector->windAlways) || (sourceactor->spr.flags & kModernTypeFlag2))
pXSector->windVel = 0;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
2021-12-29 21:56:21 +00:00
void trPlayerCtrlStartScene(DBloodActor* sourceactor, PLAYER* pPlayer, bool force)
{
2021-12-29 21:56:21 +00:00
TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
if (pCtrl->qavScene.initiator != nullptr && !force) return;
2021-12-29 21:56:21 +00:00
QAV* pQav = playerQavSceneLoad(sourceactor->xspr.data2);
if (pQav != nullptr)
{
// save current weapon
sourceactor->xspr.dropMsg = pPlayer->curWeapon;
2021-12-29 21:56:21 +00:00
auto initiator = pCtrl->qavScene.initiator;
if (initiator != nullptr && initiator != sourceactor && initiator->hasX())
sourceactor->xspr.dropMsg = initiator->xspr.dropMsg;
2021-12-29 21:56:21 +00:00
if (initiator == nullptr)
WeaponLower(pPlayer);
2021-12-29 21:56:21 +00:00
sourceactor->xspr.sysData1 = ClipLow((pQav->duration * sourceactor->xspr.waitTime) / 4, 0); // how many times animation should be played
2021-12-29 21:56:21 +00:00
pCtrl->qavScene.initiator = sourceactor;
pCtrl->qavScene.qavResrc = pQav;
pCtrl->qavScene.dummy = -1;
2021-12-29 21:56:21 +00:00
//pCtrl->qavScene.qavResrc->Preload();
2021-12-29 21:56:21 +00:00
pPlayer->sceneQav = sourceactor->xspr.data2;
pPlayer->weaponTimer = pCtrl->qavScene.qavResrc->duration;
pPlayer->qavCallback = (sourceactor->xspr.data3 > 0) ? ClipRange(sourceactor->xspr.data3 - 1, 0, 32) : -1;
pPlayer->qavLoop = false;
pPlayer->qavLastTick = I_GetTime(pCtrl->qavScene.qavResrc->ticrate);
pPlayer->qavTimer = pCtrl->qavScene.qavResrc->duration;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trPlayerCtrlStopScene(PLAYER* pPlayer)
{
2021-12-29 21:56:21 +00:00
TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
auto initiator = pCtrl->qavScene.initiator;
if (initiator->hasX())
{
initiator->xspr.sysData1 = 0;
}
2021-12-29 21:56:21 +00:00
if (pCtrl->qavScene.initiator != nullptr)
{
pCtrl->qavScene.initiator = nullptr;
pCtrl->qavScene.qavResrc = nullptr;
pPlayer->sceneQav = -1;
2021-12-29 21:56:21 +00:00
// restore weapon
if (pPlayer->actor->xspr.health > 0)
{
int oldWeapon = (initiator->hasX() && initiator->xspr.dropMsg != 0) ? initiator->xspr.dropMsg : 1;
pPlayer->newWeapon = pPlayer->curWeapon = oldWeapon;
WeaponRaise(pPlayer);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void trPlayerCtrlLink(DBloodActor* sourceactor, PLAYER* pPlayer, bool checkCondition)
{
// save player's sprite index to let the tracking condition know it after savegame loading...
auto actor = pPlayer->actor;
sourceactor->prevmarker = actor;
actor->xspr.txID = sourceactor->xspr.txID;
actor->xspr.command = kCmdToggle;
actor->xspr.triggerOn = sourceactor->xspr.triggerOn;
actor->xspr.triggerOff = sourceactor->xspr.triggerOff;
actor->xspr.busyTime = sourceactor->xspr.busyTime;
actor->xspr.waitTime = sourceactor->xspr.waitTime;
actor->xspr.restState = sourceactor->xspr.restState;
actor->xspr.Push = sourceactor->xspr.Push;
actor->xspr.Impact = sourceactor->xspr.Impact;
actor->xspr.Vector = sourceactor->xspr.Vector;
actor->xspr.Touch = sourceactor->xspr.Touch;
actor->xspr.Sight = sourceactor->xspr.Sight;
actor->xspr.Proximity = sourceactor->xspr.Proximity;
actor->xspr.Decoupled = sourceactor->xspr.Decoupled;
actor->xspr.Interrutable = sourceactor->xspr.Interrutable;
actor->xspr.DudeLockout = sourceactor->xspr.DudeLockout;
actor->xspr.data1 = sourceactor->xspr.data1;
actor->xspr.data2 = sourceactor->xspr.data2;
actor->xspr.data3 = sourceactor->xspr.data3;
actor->xspr.data4 = sourceactor->xspr.data4;
actor->xspr.key = sourceactor->xspr.key;
actor->xspr.dropMsg = sourceactor->xspr.dropMsg;
// let's check if there is tracking condition expecting objects with this TX id
if (checkCondition && sourceactor->xspr.txID >= kChannelUser)
{
for (int i = 0; i < gTrackingCondsCount; i++)
{
TRCONDITION* pCond = &gCondition[i];
if (pCond->actor->xspr.rxID != sourceactor->xspr.txID)
continue;
// search for player control sprite and replace it with actual player sprite
for (unsigned k = 0; k < pCond->length; k++)
{
if (!pCond->obj[k].obj.isActor() || pCond->obj[k].obj.actor() != sourceactor) continue;
pCond->obj[k].obj = EventObject(pPlayer->actor);
pCond->obj[k].cmd = (uint8_t)pPlayer->actor->xspr.command;
break;
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void trPlayerCtrlSetRace(int value, PLAYER* pPlayer)
2021-09-02 20:16:18 +00:00
{
2021-12-29 21:56:21 +00:00
playerSetRace(pPlayer, value);
switch (pPlayer->lifeMode)
{
case kModeHuman:
case kModeBeast:
playerSizeReset(pPlayer);
break;
case kModeHumanShrink:
playerSizeShrink(pPlayer, 2);
break;
case kModeHumanGrown:
playerSizeGrow(pPlayer, 2);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void trPlayerCtrlSetMoveSpeed(int value, PLAYER* pPlayer)
2021-09-02 20:16:18 +00:00
{
2021-12-29 21:56:21 +00:00
int speed = ClipRange(value, 0, 500);
for (int i = 0; i < kModeMax; i++)
{
for (int a = 0; a < kPostureMax; a++)
{
POSTURE* curPosture = &pPlayer->pPosture[i][a]; POSTURE* defPosture = &gPostureDefaults[i][a];
curPosture->frontAccel = (defPosture->frontAccel * speed) / kPercFull;
curPosture->sideAccel = (defPosture->sideAccel * speed) / kPercFull;
curPosture->backAccel = (defPosture->backAccel * speed) / kPercFull;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void trPlayerCtrlSetJumpHeight(int value, PLAYER* pPlayer)
2021-09-02 20:16:18 +00:00
{
2021-12-29 21:56:21 +00:00
int jump = ClipRange(value, 0, 500);
for (int i = 0; i < kModeMax; i++)
{
POSTURE* curPosture = &pPlayer->pPosture[i][kPostureStand]; POSTURE* defPosture = &gPostureDefaults[i][kPostureStand];
curPosture->normalJumpZ = (defPosture->normalJumpZ * jump) / kPercFull;
curPosture->pwupJumpZ = (defPosture->pwupJumpZ * jump) / kPercFull;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void trPlayerCtrlSetScreenEffect(int value, int timeval, PLAYER* pPlayer)
{
int eff = ClipLow(value, 0);
int time = (eff > 0) ? timeval : 0;
switch (eff) {
case 0: // clear all
case 1: // tilting
pPlayer->tiltEffect = ClipRange(time, 0, 220);
if (eff) break;
[[fallthrough]];
case 2: // pain
pPlayer->painEffect = ClipRange(time, 0, 2048);
if (eff) break;
[[fallthrough]];
case 3: // blind
pPlayer->blindEffect = ClipRange(time, 0, 2048);
if (eff) break;
[[fallthrough]];
case 4: // pickup
pPlayer->pickupEffect = ClipRange(time, 0, 2048);
if (eff) break;
[[fallthrough]];
case 5: // quakeEffect
pPlayer->quakeEffect = ClipRange(time, 0, 2048);
if (eff) break;
[[fallthrough]];
case 6: // visibility
pPlayer->visibility = ClipRange(time, 0, 2048);
if (eff) break;
[[fallthrough]];
case 7: // delirium
pPlayer->pwUpTime[kPwUpDeliriumShroom] = ClipHigh(time << 1, 432000);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-02 20:16:18 +00:00
void trPlayerCtrlSetLookAngle(int value, PLAYER* pPlayer)
{
2021-12-29 21:56:21 +00:00
double const upAngle = 289;
double const downAngle = -347;
double const lookStepUp = 4.0 * upAngle / 60.0;
double const lookStepDown = -4.0 * downAngle / 60.0;
double const look = value << 5;
double adjustment;
2021-12-29 21:56:21 +00:00
if (look > 0)
{
adjustment = min(MulScaleF(lookStepUp, look, 8), upAngle);
}
else if (look < 0)
{
adjustment = -max(MulScaleF(lookStepDown, abs(look), 8), downAngle);
}
else
{
adjustment = 0;
}
pPlayer->horizon.settarget(buildfhoriz(100. * tan(adjustment * pi::pi() * (1. / 1024.))));
pPlayer->horizon.lockinput();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-02 20:16:18 +00:00
void trPlayerCtrlEraseStuff(int value, PLAYER* pPlayer)
{
2021-12-29 21:56:21 +00:00
switch (value)
{
case 0: // erase all
[[fallthrough]];
case 1: // erase weapons
WeaponLower(pPlayer);
for (int i = 0; i < 14; i++) {
pPlayer->hasWeapon[i] = false;
// also erase ammo
if (i < 12) pPlayer->ammoCount[i] = 0;
}
pPlayer->hasWeapon[kWeapPitchFork] = true;
2021-12-29 21:56:21 +00:00
pPlayer->curWeapon = kWeapNone;
pPlayer->nextWeapon = kWeapPitchFork;
WeaponRaise(pPlayer);
if (value) break;
[[fallthrough]];
case 2: // erase all armor
for (int i = 0; i < 3; i++) pPlayer->armor[i] = 0;
if (value) break;
[[fallthrough]];
case 3: // erase all pack items
for (int i = 0; i < 5; i++) {
pPlayer->packSlots[i].isActive = false;
pPlayer->packSlots[i].curAmount = 0;
}
pPlayer->packItemId = -1;
if (value) break;
[[fallthrough]];
case 4: // erase all keys
for (int i = 0; i < 8; i++) pPlayer->hasKey[i] = false;
if (value) break;
[[fallthrough]];
case 5: // erase powerups
for (int i = 0; i < kMaxPowerUps; i++) pPlayer->pwUpTime[i] = 0;
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void trPlayerCtrlGiveStuff(int data2, int weapon, int data4, PLAYER* pPlayer, TRPLAYERCTRL* pCtrl)
{
switch (data2)
{
case 1: // give N weapon and default ammo for it
case 2: // give just N ammo for selected weapon
if (weapon <= 0 || weapon > 13)
{
Printf(PRINT_HIGH, "Weapon #%d is out of a weapons range!", weapon);
break;
}
else if (data2 == 2 && data4 == 0)
{
Printf(PRINT_HIGH, "Zero ammo for weapon #%d is specified!", weapon);
break;
}
switch (weapon)
{
case kWeapProximity: // remote bomb
case kWeapRemote: // prox bomb
pPlayer->hasWeapon[weapon] = true;
weapon--;
pPlayer->ammoCount[weapon] = ClipHigh(pPlayer->ammoCount[weapon] + ((data2 == 2) ? data4 : 1), gAmmoInfo[weapon].max);
weapon++;
break;
default:
for (int i = 0; i < 11; i++)
{
if (gWeaponItemData[i].type != weapon) continue;
const WEAPONITEMDATA* pWeaponData = &gWeaponItemData[i];
int nAmmoType = pWeaponData->ammoType;
switch (data2) {
case 1:
pPlayer->hasWeapon[weapon] = true;
if (pPlayer->ammoCount[nAmmoType] >= pWeaponData->count) break;
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponData->count, gAmmoInfo[nAmmoType].max);
break;
case 2:
pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + data4, gAmmoInfo[nAmmoType].max);
break;
}
break;
}
break;
}
if (pPlayer->hasWeapon[weapon] && data4 == 0) // switch on it
{
pPlayer->nextWeapon = kWeapNone;
if (pPlayer->sceneQav >= 0 && pCtrl->qavScene.initiator && pCtrl->qavScene.initiator->hasX())
{
pCtrl->qavScene.initiator->xspr.dropMsg = weapon;
}
else if (pPlayer->curWeapon != weapon)
{
pPlayer->newWeapon = weapon;
WeaponRaise(pPlayer);
}
}
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-02 20:16:18 +00:00
void trPlayerCtrlUsePackItem(int data2, int data3, int data4, PLAYER* pPlayer, int evCmd)
{
2021-12-29 21:56:21 +00:00
unsigned int invItem = data2 - 1;
switch (evCmd)
{
case kCmdOn:
if (!pPlayer->packSlots[invItem].isActive) packUseItem(pPlayer, invItem);
break;
case kCmdOff:
if (pPlayer->packSlots[invItem].isActive) packUseItem(pPlayer, invItem);
break;
default:
packUseItem(pPlayer, invItem);
break;
}
switch (data4)
{
case 2: // both
case 0: // switch on it
if (pPlayer->packSlots[invItem].curAmount > 0) pPlayer->packItemId = invItem;
if (!data4) break;
[[fallthrough]];
case 1: // force remove after use
pPlayer->packSlots[invItem].isActive = false;
pPlayer->packSlots[invItem].curAmount = 0;
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-02 20:16:18 +00:00
void trPlayerCtrlUsePowerup(DBloodActor* sourceactor, PLAYER* pPlayer, int evCmd)
{
2021-12-29 21:56:21 +00:00
bool relative = (sourceactor->spr.flags & kModernTypeFlag1);
2021-12-29 21:56:21 +00:00
int nPower = (kMinAllowedPowerup + sourceactor->xspr.data2) - 1;
int nTime = ClipRange(abs(sourceactor->xspr.data3) * 100, -gPowerUpInfo[nPower].maxTime, gPowerUpInfo[nPower].maxTime);
if (sourceactor->xspr.data3 < 0)
nTime = -nTime;
2021-12-29 21:56:21 +00:00
if (pPlayer->pwUpTime[nPower])
{
if (!relative && nTime <= 0)
powerupDeactivate(pPlayer, nPower);
}
2021-12-29 21:56:21 +00:00
if (nTime != 0)
{
if (pPlayer->pwUpTime[nPower] <= 0)
powerupActivate(pPlayer, nPower); // MUST activate first for powerups like kPwUpDeathMask
2021-12-29 21:56:21 +00:00
// ...so we able to change time amount
if (relative) pPlayer->pwUpTime[nPower] += nTime;
else pPlayer->pwUpTime[nPower] = nTime;
}
2021-12-29 21:56:21 +00:00
if (pPlayer->pwUpTime[nPower] <= 0)
powerupDeactivate(pPlayer, nPower);
return;
}
2021-08-28 10:11:03 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void useObjResizer(DBloodActor* sourceactor, int targType, sectortype* targSect, walltype* targWall, DBloodActor* targetactor)
{
switch (targType)
{
// for sectors
case OBJ_SECTOR:
if (!targSect) return;
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
targSect->floorxpan_ = (float)ClipRange(sourceactor->xspr.data1, 0, 255);
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
targSect->floorypan_ = (float)ClipRange(sourceactor->xspr.data2, 0, 255);
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
targSect->ceilingxpan_ = (float)ClipRange(sourceactor->xspr.data3, 0, 255);
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
targSect->ceilingypan_ = (float)ClipRange(sourceactor->xspr.data4, 0, 255);
break;
// for sprites
case OBJ_SPRITE:
{
bool fit = false;
// resize by seq scaling
if (sourceactor->spr.flags & kModernTypeFlag1)
{
if (valueIsBetween(sourceactor->xspr.data1, -255, 32767))
{
int mulDiv = (valueIsBetween(sourceactor->xspr.data2, 0, 257)) ? sourceactor->xspr.data2 : 256;
if (sourceactor->xspr.data1 > 0) targetactor->xspr.scale = mulDiv * ClipHigh(sourceactor->xspr.data1, 25);
else if (sourceactor->xspr.data1 < 0) targetactor->xspr.scale = mulDiv / ClipHigh(abs(sourceactor->xspr.data1), 25);
else targetactor->xspr.scale = 0;
fit = true;
}
// resize by repeats
}
else
{
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
{
targetactor->spr.xrepeat = ClipRange(sourceactor->xspr.data1, 0, 255);
fit = true;
}
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
{
targetactor->spr.yrepeat = ClipRange(sourceactor->xspr.data2, 0, 255);
fit = true;
}
}
if (fit && (targetactor->spr.type == kDudeModernCustom || targetactor->spr.type == kDudeModernCustomBurning))
{
// request properties update for custom dude
targetactor->genDudeExtra.updReq[kGenDudePropertySpriteSize] = true;
targetactor->genDudeExtra.updReq[kGenDudePropertyAttack] = true;
targetactor->genDudeExtra.updReq[kGenDudePropertyMass] = true;
targetactor->genDudeExtra.updReq[kGenDudePropertyDmgScale] = true;
evPostActor(targetactor, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
}
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
targetactor->spr.xoffset = ClipRange(sourceactor->xspr.data3, 0, 255);
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
targetactor->spr.yoffset = ClipRange(sourceactor->xspr.data4, 0, 255);
break;
}
case OBJ_WALL:
if (!targWall) return;
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
targWall->xrepeat = ClipRange(sourceactor->xspr.data1, 0, 255);
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
targWall->yrepeat = ClipRange(sourceactor->xspr.data2, 0, 255);
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
targWall->xpan_ = (float)ClipRange(sourceactor->xspr.data3, 0, 255);
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
targWall->ypan_ = (float)ClipRange(sourceactor->xspr.data4, 0, 255);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void usePropertiesChanger(DBloodActor* sourceactor, int objType, sectortype* pSector, walltype* pWall, DBloodActor* targetactor)
2021-09-02 21:26:18 +00:00
{
2022-05-06 16:27:44 +00:00
bool flag1 = (sourceactor->spr.flags & kModernTypeFlag1);
bool flag2 = (sourceactor->spr.flags & kModernTypeFlag2);
bool flag4 = (sourceactor->spr.flags & kModernTypeFlag4);
short data3 = sourceactor->xspr.data3;
short data4 = sourceactor->xspr.data4;
2021-12-29 21:56:21 +00:00
switch (objType)
{
case OBJ_WALL:
{
if (!pWall) return;
2022-05-06 16:27:44 +00:00
// data1 = set this wall as first wall of sector or as sector.alignto wall
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
{
if (pWall->sectorp())
{
switch (sourceactor->xspr.data1) {
case 1:
// You got to be f***ing kidding...
//setfirstwall(nSector, objIndex);
Printf("Setting first wall not supported!"); // this will require a lot of mess...
pWall->allocX();
break;
case 2:
pWall->sectorp()->slopewallofs = max<int>(pWall - pWall->sectorp()->firstWall(), 0);
break;
}
}
}
2021-12-29 21:56:21 +00:00
// data3 = set wall hitag
2022-05-06 16:27:44 +00:00
if (valueIsBetween(data3, -1, 32767))
2021-12-29 21:56:21 +00:00
{
2022-05-06 16:27:44 +00:00
if (flag1)
{
if (flag4) pWall->hitag &= ~data3;
else pWall->hitag |= data3;
pWall->hitag |= sourceactor->xspr.data3;
}
2021-12-29 21:56:21 +00:00
else pWall->hitag = sourceactor->xspr.data3;
}
// data4 = set wall cstat
2022-05-06 16:27:44 +00:00
if (valueIsBetween(data4, -1, 65535))
2021-12-29 21:56:21 +00:00
{
auto old = pWall->cstat;
2022-05-06 16:27:44 +00:00
// relative
if (flag1)
{
if (flag4) pWall->cstat &= ~EWallFlags::FromInt(data4);
else pWall->cstat |= EWallFlags::FromInt(data4);
}
// absolute
else
{
pWall->cstat = EWallFlags::FromInt(data4);
if (!flag2)
{
// check for exceptions
pWall->cstat |= old & (CSTAT_WALL_BOTTOM_SWAP | CSTAT_WALL_ALIGN_BOTTOM | CSTAT_WALL_1WAY);
pWall->cstat = (pWall->cstat & ~CSTAT_WALL_MOVE_MASK) | (old & CSTAT_WALL_MOVE_MASK);
}
2021-12-29 21:56:21 +00:00
}
}
}
break;
case OBJ_SPRITE:
{
bool thing2debris = false;
int old = -1;
// data3 = set sprite hitag
2022-05-06 16:27:44 +00:00
if (valueIsBetween(data3, -1, 32767))
2021-12-29 21:56:21 +00:00
{
old = targetactor->spr.flags;
2022-05-06 16:27:44 +00:00
// relative
if (flag1)
{
if (flag4) targetactor->spr.flags &= ~data3;
else targetactor->spr.flags |= data3;
}
// absolute
else
{
targetactor->spr.flags = data3;
}
2021-12-29 21:56:21 +00:00
// and handle exceptions
if ((old & kHitagFree) && !(targetactor->spr.flags & kHitagFree)) targetactor->spr.flags |= kHitagFree;
if ((old & kHitagRespawn) && !(targetactor->spr.flags & kHitagRespawn)) targetactor->spr.flags |= kHitagRespawn;
// prepare things for different (debris) physics.
2022-05-06 16:27:44 +00:00
thing2debris = (targetactor->spr.statnum == kStatThing && debrisGetFreeIndex() >= 0);
2021-12-29 21:56:21 +00:00
}
2022-05-06 16:27:44 +00:00
2021-12-29 21:56:21 +00:00
// data2 = sprite physics settings
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767) || thing2debris)
{
switch (targetactor->spr.statnum)
{
case kStatDude: // dudes already treating in game
case kStatFree:
case kStatMarker:
case kStatPathMarker:
break;
default:
// store physics attributes in xsprite to avoid setting hitag for modern types!
int flags = (targetactor->xspr.physAttr != 0) ? targetactor->xspr.physAttr : 0;
int oldFlags = flags;
if (thing2debris)
{
// converting thing to debris
if ((targetactor->spr.flags & kPhysMove) != 0) flags |= kPhysMove;
else flags &= ~kPhysMove;
if ((targetactor->spr.flags & kPhysGravity) != 0) flags |= (kPhysGravity | kPhysFalling);
else flags &= ~(kPhysGravity | kPhysFalling);
targetactor->spr.flags &= ~(kPhysMove | kPhysGravity | kPhysFalling);
targetactor->ZeroVelocity();
2021-12-29 21:56:21 +00:00
targetactor->xspr.restState = targetactor->xspr.state;
}
else
{
// WTF is this?!?
char digits[6] = {};
snprintf(digits, 6, "%d", sourceactor->xspr.data2);
for (unsigned int i = 0; i < sizeof(digits); i++)
digits[i] = (digits[i] >= 48 && digits[i] <= 57) ? (digits[i] - 57) + 9 : 0;
// first digit of data2: set main physics attributes
switch (digits[0])
{
case 0:
flags &= ~kPhysMove;
flags &= ~(kPhysGravity | kPhysFalling);
break;
case 1:
flags |= kPhysMove;
flags &= ~(kPhysGravity | kPhysFalling);
break;
case 2:
flags &= ~kPhysMove;
flags |= (kPhysGravity | kPhysFalling);
break;
case 3:
flags |= kPhysMove;
flags |= (kPhysGravity | kPhysFalling);
break;
}
// second digit of data2: touch physics flags
switch (digits[1])
{
case 0:
flags &= ~kPhysDebrisTouch;
break;
case 1:
flags |= kPhysDebrisTouch;
break;
}
// third digit of data2: weapon physics flags
switch (digits[2])
{
case 0:
flags &= ~kPhysDebrisVector;
flags &= ~kPhysDebrisExplode;
break;
case 1:
flags |= kPhysDebrisVector;
flags &= ~kPhysDebrisExplode;
break;
case 2:
flags &= ~kPhysDebrisVector;
flags |= kPhysDebrisExplode;
break;
case 3:
flags |= kPhysDebrisVector;
flags |= kPhysDebrisExplode;
break;
}
// fourth digit of data2: swimming / flying physics flags
switch (digits[3])
{
case 0:
flags &= ~kPhysDebrisSwim;
flags &= ~kPhysDebrisFly;
flags &= ~kPhysDebrisFloat;
break;
case 1:
flags |= kPhysDebrisSwim;
flags &= ~kPhysDebrisFly;
flags &= ~kPhysDebrisFloat;
break;
case 2:
flags |= kPhysDebrisSwim;
flags |= kPhysDebrisFloat;
flags &= ~kPhysDebrisFly;
break;
case 3:
flags |= kPhysDebrisFly;
flags &= ~kPhysDebrisSwim;
flags &= ~kPhysDebrisFloat;
break;
case 4:
flags |= kPhysDebrisFly;
flags |= kPhysDebrisFloat;
flags &= ~kPhysDebrisSwim;
break;
case 5:
flags |= kPhysDebrisSwim;
flags |= kPhysDebrisFly;
flags &= ~kPhysDebrisFloat;
break;
case 6:
flags |= kPhysDebrisSwim;
flags |= kPhysDebrisFly;
flags |= kPhysDebrisFloat;
break;
}
}
int nIndex = debrisGetIndex(targetactor); // check if there is no sprite in list
// adding physics sprite in list
if ((flags & kPhysGravity) != 0 || (flags & kPhysMove) != 0)
{
if (oldFlags == 0)
targetactor->ZeroVelocity();
2021-12-29 21:56:21 +00:00
if (nIndex != -1)
{
targetactor->xspr.physAttr = flags; // just update physics attributes
}
else if ((nIndex = debrisGetFreeIndex()) < 0)
{
viewSetSystemMessage("Max (%d) Physics affected sprites reached!", kMaxSuperXSprites);
}
else
{
targetactor->xspr.physAttr = flags; // update physics attributes
// allow things to became debris, so they use different physics...
if (targetactor->spr.statnum == kStatThing) ChangeActorStat(targetactor, 0);
// set random goal ang for swimming so they start turning
2022-09-03 22:38:26 +00:00
if ((flags & kPhysDebrisSwim) && targetactor->vel.isZero())
targetactor->xspr.goalAng = (targetactor->spr.angle + DAngle::fromBuild(Random3(kAng45))).Normalized360();
2021-12-29 21:56:21 +00:00
if (targetactor->xspr.physAttr & kPhysDebrisVector)
targetactor->spr.cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
gPhysSpritesList[nIndex] = targetactor;
if (nIndex >= gPhysSpritesCount) gPhysSpritesCount++;
getSpriteMassBySize(targetactor); // create physics cache
}
// removing physics from sprite in list (don't remove sprite from list)
}
else if (nIndex != -1)
{
targetactor->xspr.physAttr = flags;
targetactor->ZeroVelocity();
2021-12-29 21:56:21 +00:00
if (targetactor->spr.lotag >= kThingBase && targetactor->spr.lotag < kThingMax)
ChangeActorStat(targetactor, kStatThing); // if it was a thing - restore statnum
}
break;
}
}
// data4 = sprite cstat
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
{
2022-05-06 16:27:44 +00:00
// relative
if (flag1)
2021-12-29 21:56:21 +00:00
{
2022-05-06 16:27:44 +00:00
if (flag4) targetactor->spr.cstat &= ~ESpriteFlags::FromInt(data4);
else targetactor->spr.cstat |= ESpriteFlags::FromInt(data4);
2021-12-29 21:56:21 +00:00
}
2022-05-06 16:27:44 +00:00
// absolute
else
{
auto oldstat = targetactor->spr.cstat;
// set new cstat
if ((sourceactor->spr.flags & kModernTypeFlag1)) targetactor->spr.cstat |= ESpriteFlags::FromInt(sourceactor->xspr.data4); // relative
else targetactor->spr.cstat = ESpriteFlags::FromInt(sourceactor->xspr.data4 & 0xffff); // absolute
// and handle exceptions
if ((oldstat & CSTAT_SPRITE_BLOOD_BIT1)) targetactor->spr.cstat |= CSTAT_SPRITE_BLOOD_BIT1; //kSpritePushable
if ((oldstat & CSTAT_SPRITE_YCENTER)) targetactor->spr.cstat |= CSTAT_SPRITE_YCENTER;
targetactor->spr.cstat = (targetactor->spr.cstat & ~CSTAT_SPRITE_MOVE_MASK) | (oldstat & CSTAT_SPRITE_MOVE_MASK);
#if 0
// looks very broken.
if (old & 0x6000)
{
if (!(targetactor->spr.cstat & 0x6000))
targetactor->spr.cstat |= 0x6000; // kSpriteMoveMask
if ((old & 0x0) && !(targetactor->spr.cstat & 0x0)) targetactor->spr.cstat |= 0x0; // kSpriteMoveNone
else if ((old & 0x2000) && !(targetactor->spr.cstat & 0x2000)) targetactor->spr.cstat |= 0x2000; // kSpriteMoveForward, kSpriteMoveFloor
else if ((old & 0x4000) && !(targetactor->spr.cstat & 0x4000)) targetactor->spr.cstat |= 0x4000; // kSpriteMoveReverse, kSpriteMoveCeiling
}
2021-12-18 19:07:47 +00:00
#endif
2022-05-06 16:27:44 +00:00
}
2021-12-29 21:56:21 +00:00
}
}
break;
case OBJ_SECTOR:
{
if (!pSector) return;
XSECTOR* pXSector = &pSector->xs();
// data1 = sector underwater status and depth level
if (sourceactor->xspr.data1 >= 0 && sourceactor->xspr.data1 < 2) {
pXSector->Underwater = (sourceactor->xspr.data1) ? true : false;
auto aLower = barrier_cast<DBloodActor*>(pSector->lowerLink);
DBloodActor* aUpper = nullptr;
if (aLower)
{
// must be sure we found exact same upper link
for (auto& sec : sector)
{
aUpper = barrier_cast<DBloodActor*>(sec.upperLink);
if (aUpper == nullptr || aUpper->xspr.data1 != aLower->xspr.data1)
{
aUpper = nullptr;
continue;
}
break;
}
}
// treat sectors that have links, so warp can detect underwater status properly
if (aLower)
{
if (pXSector->Underwater)
{
switch (aLower->spr.type)
{
case kMarkerLowStack:
case kMarkerLowLink:
aLower->xspr.sysData1 = aLower->spr.type;
aLower->spr.type = kMarkerLowWater;
break;
default:
if (pSector->ceilingpicnum < 4080 || pSector->ceilingpicnum > 4095) aLower->xspr.sysData1 = kMarkerLowLink;
else aLower->xspr.sysData1 = kMarkerLowStack;
break;
}
}
else if (aLower->xspr.sysData1 > 0) aLower->spr.type = aLower->xspr.sysData1;
else if (pSector->ceilingpicnum < 4080 || pSector->ceilingpicnum > 4095) aLower->spr.type = kMarkerLowLink;
else aLower->spr.type = kMarkerLowStack;
}
if (aUpper)
{
if (pXSector->Underwater)
{
switch (aUpper->spr.type)
{
case kMarkerUpStack:
case kMarkerUpLink:
aUpper->xspr.sysData1 = aUpper->spr.type;
aUpper->spr.type = kMarkerUpWater;
break;
default:
if (pSector->floorpicnum < 4080 || pSector->floorpicnum > 4095) aUpper->xspr.sysData1 = kMarkerUpLink;
else aUpper->xspr.sysData1 = kMarkerUpStack;
break;
}
}
else if (aUpper->xspr.sysData1 > 0) aUpper->spr.type = aUpper->xspr.sysData1;
else if (pSector->floorpicnum < 4080 || pSector->floorpicnum > 4095) aUpper->spr.type = kMarkerUpLink;
else aUpper->spr.type = kMarkerUpStack;
}
// search for dudes in this sector and change their underwater status
BloodSectIterator it(pSector);
while (auto iactor = it.Next())
{
if (iactor->spr.statnum != kStatDude || !iactor->IsDudeActor() || !iactor->hasX())
continue;
PLAYER* pPlayer = getPlayerById(iactor->spr.type);
if (pXSector->Underwater)
{
if (aLower)
iactor->xspr.medium = (aLower->spr.type == kMarkerUpGoo) ? kMediumGoo : kMediumWater;
if (pPlayer)
{
int waterPal = kMediumWater;
if (aLower)
{
if (aLower->xspr.data2 > 0) waterPal = aLower->xspr.data2;
else if (aLower->spr.type == kMarkerUpGoo) waterPal = kMediumGoo;
}
pPlayer->nWaterPal = waterPal;
pPlayer->posture = kPostureSwim;
pPlayer->actor->xspr.burnTime = 0;
}
}
else
{
iactor->xspr.medium = kMediumNormal;
if (pPlayer)
{
pPlayer->posture = (!(pPlayer->input.actions & SB_CROUCH)) ? kPostureStand : kPostureCrouch;
pPlayer->nWaterPal = 0;
}
}
}
}
else if (sourceactor->xspr.data1 > 9) pXSector->Depth = 7;
else if (sourceactor->xspr.data1 > 1) pXSector->Depth = sourceactor->xspr.data1 - 2;
// data2 = sector visibility
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
pSector->visibility = ClipRange(sourceactor->xspr.data2, 0, 234);
2022-05-06 16:27:44 +00:00
2021-12-29 21:56:21 +00:00
// data3 = sector ceil cstat
2022-05-06 16:27:44 +00:00
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
{
// relative
if (flag1)
{
if (flag4) pSector->ceilingstat &= ~ESectorFlags::FromInt(data3);
else pSector->ceilingstat |= ESectorFlags::FromInt(data3);
}
// absolute
else pSector->ceilingstat = ESectorFlags::FromInt(data3);
2021-12-29 21:56:21 +00:00
}
// data4 = sector floor cstat
2022-05-06 16:27:44 +00:00
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
{
// relative
if (flag1)
{
if (flag4) pSector->floorstat &= ~ESectorFlags::FromInt(data4);
else pSector->floorstat |= ESectorFlags::FromInt(data4);
}
// absolute
else pSector->floorstat = ESectorFlags::FromInt(data4);
2021-12-29 21:56:21 +00:00
}
}
break;
// no TX id
case -1:
// data2 = global visibility
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
gVisibility = ClipRange(sourceactor->xspr.data2, 0, 4096);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useVelocityChanger(DBloodActor* actor, sectortype* sect, DBloodActor* initiator, DBloodActor* pSprite)
//void useVelocityChanger(XSPRITE* pXSource, int causerID, short objType, int objIndex)
{
#define kVelShift 8
#define kScaleVal 0x10000
int t, r = 0, nAng = 0, vAng = 0;
int xv = 0, yv = 0, zv = 0;
bool relative = (actor->spr.flags & kModernTypeFlag1);
bool toDstAng = (actor->spr.flags & kModernTypeFlag2);
bool toSrcAng = (actor->spr.flags & kModernTypeFlag4);
bool toRndAng = (actor->spr.flags & kModernTypeFlag8);
bool chgDstAng = !(actor->spr.flags & kModernTypeFlag16);
bool toEvnAng = (toDstAng && toSrcAng && initiator != NULL);
bool toAng = (toDstAng || toSrcAng || toEvnAng || toRndAng);
bool toAng180 = (toRndAng && (toDstAng || toSrcAng || toEvnAng));
if (actor)
{
if ((r = MulScale(actor->xspr.data4 << kVelShift, kScaleVal, 14)) != 0)
r = nnExtRandom(-r, r);
if (valueIsBetween(actor->xspr.data3, -32767, 32767))
{
if ((zv = MulScale(actor->xspr.data3 << kVelShift, kScaleVal, 14)) != 0)
zv += r;
}
if (!toAng)
{
if (valueIsBetween(actor->xspr.data1, -32767, 32767))
{
if ((xv = MulScale(actor->xspr.data1 << kVelShift, kScaleVal, 14)) != 0)
xv += r;
}
if (valueIsBetween(actor->xspr.data2, -32767, 32767))
{
if ((yv = MulScale(actor->xspr.data2 << kVelShift, kScaleVal, 14)) != 0)
yv += r;
}
}
else
{
2022-08-16 21:17:01 +00:00
if (toEvnAng) nAng = initiator->int_ang();
else if (toSrcAng) nAng = actor->int_ang();
else nAng = pSprite->int_ang();
nAng = nAng & 2047;
if (!toAng180 && toRndAng)
{
t = nAng;
while (t == nAng)
nAng = nnExtRandom(0, kAng360);
}
if (chgDstAng)
changeSpriteAngle(pSprite, nAng);
if ((t = (actor->xspr.data1 << kVelShift)) != 0)
t += r;
xv = MulScale(t, Cos(nAng) >> 16, 14);
yv = MulScale(t, Sin(nAng) >> 16, 14);
}
if (actor->xspr.physAttr)
{
t = 1;
switch (pSprite->spr.statnum) {
case kStatThing:
break;
case kStatFX:
t = 0;
[[fallthrough]];
case kStatDude:
case kStatProjectile:
if (actor->xspr.physAttr & kPhysMove) pSprite->spr.flags |= kPhysMove; else pSprite->spr.flags &= ~kPhysMove;
if (actor->xspr.physAttr & kPhysGravity) pSprite->spr.flags |= kPhysGravity; else pSprite->spr.flags &= ~kPhysGravity;
if (actor->xspr.physAttr & kPhysFalling) pSprite->spr.flags |= kPhysFalling; else pSprite->spr.flags &= ~kPhysFalling;
break;
}
// debris physics for sprites that is allowed
if (t && ((t = debrisGetIndex(pSprite)) >= 0 || (t = debrisGetFreeIndex()) >= 0))
{
pSprite->addX();
if (pSprite->spr.statnum == kStatThing)
{
pSprite->spr.flags &= ~(kPhysMove | kPhysGravity | kPhysFalling);
ChangeActorStat(pSprite, 0);
}
pSprite->xspr.physAttr = actor->xspr.physAttr, gPhysSpritesList[t] = pSprite;
getSpriteMassBySize(pSprite);
if (t >= gPhysSpritesCount)
gPhysSpritesCount++;
}
}
if (relative)
{
2022-09-01 19:02:20 +00:00
pSprite->add_int_bvel_x(xv);
2022-09-01 19:05:25 +00:00
pSprite->add_int_bvel_y(yv);
2022-09-01 19:08:05 +00:00
pSprite->add_int_bvel_z(zv);
}
else
{
2022-09-01 19:18:16 +00:00
pSprite->set_int_bvel_x(xv);
2022-09-01 19:24:29 +00:00
pSprite->set_int_bvel_y(yv);
2022-09-01 19:40:03 +00:00
pSprite->set_int_bvel_z(zv);
}
vAng = getVelocityAngle(pSprite);
if (toAng)
{
DAngle angl;
if (toAng180) angl = DAngle180;
else angl = DAngle::fromBuild(nAng - vAng);
auto velv = pSprite->vel.XY();
auto pt = rotatepoint(pSprite->spr.pos.XY(), velv, angl);
2022-09-03 08:11:05 +00:00
pSprite->vel.XY() = pt;
vAng = getVelocityAngle(pSprite);
}
if (chgDstAng)
changeSpriteAngle(pSprite, vAng);
if (pSprite->ownerActor)
{
// hack to make player projectiles damage it's owner
if (pSprite->spr.statnum == kStatProjectile && pSprite->IsPlayerActor())
pSprite->ownerActor = pSprite;
}
viewCorrectPrediction();
//if (pXSource->rxID == 157)
//viewSetSystemMessage("%d: %d / %d / %d, C: %d", pSprite->spr.sectnum, pSprite->vel.X, pSprite->vel.Y, pSprite->vel.Z, sprite[causerID].type);
}
else if (sect)
{
BloodSectIterator it(sect);
while(auto act = it.Next())
{
useVelocityChanger(actor, nullptr, initiator, act);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void useTeleportTarget(DBloodActor* sourceactor, DBloodActor* actor)
{
PLAYER* pPlayer = getPlayerById(actor->spr.type);
XSECTOR* pXSector = (sourceactor->sector()->hasX()) ? &sourceactor->sector()->xs() : nullptr;
2021-12-29 21:56:21 +00:00
bool isDude = (!pPlayer && actor->IsDudeActor());
if (actor->sector() != sourceactor->sector())
ChangeActorSect(actor, sourceactor->sector());
2021-12-29 21:56:21 +00:00
2022-08-29 17:27:52 +00:00
actor->spr.pos.XY() =sourceactor->spr.pos.XY();
2022-09-13 22:42:59 +00:00
double zTop, zBot;
2021-12-29 21:56:21 +00:00
GetActorExtents(sourceactor, &zTop, &zBot);
2022-09-13 22:42:59 +00:00
actor->spr.pos.Z = zBot;
2021-12-29 21:56:21 +00:00
clampSprite(actor, 0x01);
if (sourceactor->spr.flags & kModernTypeFlag1) // force telefrag
TeleFrag(actor, sourceactor->sector());
2021-12-29 21:56:21 +00:00
if (actor->spr.flags & kPhysGravity)
actor->spr.flags |= kPhysFalling;
if (pXSector)
{
if (pXSector->Enter && (pPlayer || (isDude && !pXSector->dudeLockout)))
trTriggerSector(sourceactor->sector(), kCmdSectorEnter, actor);
2021-12-29 21:56:21 +00:00
if (pXSector->Underwater)
{
DBloodActor* aUpper = nullptr;
auto aLink = barrier_cast<DBloodActor*>(sourceactor->sector()->lowerLink);
2021-12-29 21:56:21 +00:00
if (aLink)
{
// must be sure we found exact same upper link
for (auto& sec : sector)
{
aUpper = barrier_cast<DBloodActor*>(sec.upperLink);
if (aUpper == nullptr || aUpper->xspr.data1 != aLink->xspr.data1)
{
aUpper = nullptr;
continue;
}
break;
}
}
if (aUpper)
actor->xspr.medium = (aLink->spr.type == kMarkerUpGoo) ? kMediumGoo : kMediumWater;
if (pPlayer)
{
int waterPal = kMediumWater;
if (aUpper)
{
if (aLink->xspr.data2 > 0) waterPal = aLink->xspr.data2;
else if (aLink->spr.type == kMarkerUpGoo) waterPal = kMediumGoo;
}
pPlayer->nWaterPal = waterPal;
pPlayer->posture = kPostureSwim;
pPlayer->actor->xspr.burnTime = 0;
}
}
else
{
actor->xspr.medium = kMediumNormal;
if (pPlayer)
{
pPlayer->posture = (!(pPlayer->input.actions & SB_CROUCH)) ? kPostureStand : kPostureCrouch;
pPlayer->nWaterPal = 0;
}
}
}
if (actor->spr.statnum == kStatDude && actor->IsDudeActor() && !actor->IsPlayerActor())
{
2022-08-22 16:31:23 +00:00
auto pos = actor->xspr.TargetPos;
2021-12-29 21:56:21 +00:00
auto target = actor->GetTarget();
aiInitSprite(actor);
if (target != nullptr && target->IsDudeActor())
2021-12-29 21:56:21 +00:00
{
2022-08-22 16:31:23 +00:00
actor->xspr.TargetPos = pos;
2021-12-29 21:56:21 +00:00
actor->SetTarget(target);
aiActivateDude(actor);
}
}
#if 0
2021-12-29 21:56:21 +00:00
if (sourceactor->xspr.data2 == 1)
{
if (pPlayer)
{
pPlayer->angle.settarget(sourceactor->spr.angle);
pPlayer->angle.lockinput();
2021-12-29 21:56:21 +00:00
}
else if (isDude) sourceactor->xspr.goalAng = actor->spr.angle = sourceactor->spr.angle;
else actor->spr.__int_angle = sourceactor->spr.angle;
2021-12-29 21:56:21 +00:00
}
#endif
2021-12-29 21:56:21 +00:00
if (sourceactor->xspr.data3 == 1)
{
actor->ZeroVelocity();
}
else if (sourceactor->xspr.data3 > 0)
{
// change movement direction according source angle
if (sourceactor->xspr.data3 & kModernTypeFlag2)
{
auto velv = actor->vel.XY();
auto pt = rotatepoint(actor->spr.pos.XY(), velv, sourceactor->spr.angle - VecToAngle(velv));
2022-09-03 08:11:05 +00:00
actor->vel.XY() = pt;
}
if (sourceactor->xspr.data3 & kModernTypeFlag4)
2022-09-03 08:11:05 +00:00
actor->vel.Z = 0;
}
if (sourceactor->xspr.data2 == 1)
2022-08-16 21:15:49 +00:00
changeSpriteAngle(actor, sourceactor->int_ang());
2021-12-29 21:56:21 +00:00
viewBackupSpriteLoc(actor);
if (sourceactor->xspr.data4 > 0)
sfxPlay3DSound(sourceactor, sourceactor->xspr.data4, -1, 0);
if (pPlayer)
{
playerResetInertia(pPlayer);
if (sourceactor->xspr.data2 == 1)
pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
}
}
2021-09-02 21:42:25 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useEffectGen(DBloodActor* sourceactor, DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (!actor) actor = sourceactor;
int fxId = (sourceactor->xspr.data3 <= 0) ? sourceactor->xspr.data2 : sourceactor->xspr.data2 + Random(sourceactor->xspr.data3 + 1);
if (!actor->hasX()) return;
else if (fxId >= kEffectGenCallbackBase)
{
int length = sizeof(gEffectGenCallbacks) / sizeof(gEffectGenCallbacks[0]);
if (fxId < kEffectGenCallbackBase + length)
{
fxId = gEffectGenCallbacks[fxId - kEffectGenCallbackBase];
evKillActor(actor, (CALLBACK_ID)fxId);
evPostActor(actor, 0, (CALLBACK_ID)fxId);
}
}
else if (valueIsBetween(fxId, 0, kFXMax))
{
double pos, top, bottom;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &top, &bottom);
DBloodActor* pEffect = nullptr;
// select where exactly effect should be spawned
switch (sourceactor->xspr.data4)
{
case 1:
pos = bottom;
break;
case 2: // middle
pos = actor->spr.pos.Z + (tileHeight(actor->spr.picnum) / 2 + tileTopOffset(actor->spr.picnum)) * actor->spr.yrepeat * REPEAT_SCALE;
2021-12-29 21:56:21 +00:00
break;
case 3:
case 4:
if (actor->insector())
{
if (sourceactor->xspr.data4 == 3)
pos = getflorzofslopeptr(actor->sector(), actor->spr.pos.X, actor->spr.pos.Y);
else
pos = getceilzofslopeptr(actor->sector(), actor->spr.pos.X, actor->spr.pos.Y);
break;
}
[[fallthrough]];
2021-12-29 21:56:21 +00:00
default:
pos = top;
break;
}
if ((pEffect = gFX.fxSpawnActor((FX_ID)fxId, actor->sector(), DVector3(actor->spr.pos.XY(), pos), 0)) != nullptr)
2021-12-29 21:56:21 +00:00
{
pEffect->SetOwner(sourceactor);
if (sourceactor->spr.flags & kModernTypeFlag1)
{
pEffect->spr.pal = sourceactor->spr.pal;
pEffect->spr.xoffset = sourceactor->spr.xoffset;
pEffect->spr.yoffset = sourceactor->spr.yoffset;
pEffect->spr.xrepeat = sourceactor->spr.xrepeat;
pEffect->spr.yrepeat = sourceactor->spr.yrepeat;
pEffect->spr.shade = sourceactor->spr.shade;
}
if (sourceactor->spr.flags & kModernTypeFlag2)
{
pEffect->spr.cstat = sourceactor->spr.cstat;
if (pEffect->spr.cstat & CSTAT_SPRITE_INVISIBLE)
pEffect->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
}
2022-05-06 16:27:44 +00:00
if (sourceactor->spr.flags & kModernTypeFlag4)
{
pEffect->spr.angle = sourceactor->spr.angle;
2022-05-06 16:27:44 +00:00
}
2021-12-29 21:56:21 +00:00
if (pEffect->spr.cstat & CSTAT_SPRITE_ONE_SIDE)
pEffect->spr.cstat &= ~CSTAT_SPRITE_ONE_SIDE;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void useSectorWindGen(DBloodActor* sourceactor, sectortype* pSector)
{
XSECTOR* pXSector = nullptr;
if (pSector != nullptr)
{
pXSector = &pSector->xs();
}
else if (sourceactor->sector()->hasX())
2021-12-29 21:56:21 +00:00
{
pSector = sourceactor->sector();
2021-12-29 21:56:21 +00:00
pXSector = &pSector->xs();
}
else
{
pSector = sourceactor->sector();
2021-12-29 21:56:21 +00:00
pSector->allocX();
pXSector = &pSector->xs();
pXSector->windAlways = 1;
}
int windVel = ClipRange(sourceactor->xspr.data2, 0, 32767);
if ((sourceactor->xspr.data1 & 0x0001))
windVel = nnExtRandom(0, windVel);
// process vertical wind in nnExtProcessSuperSprites();
if ((sourceactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR))
{
sourceactor->xspr.sysData2 = windVel << 1;
return;
}
pXSector->windVel = windVel;
if ((sourceactor->spr.flags & kModernTypeFlag1))
pXSector->panAlways = pXSector->windAlways = 1;
2022-08-16 21:17:01 +00:00
int ang = sourceactor->int_ang();
2021-12-29 21:56:21 +00:00
if (sourceactor->xspr.data4 <= 0)
{
if ((sourceactor->xspr.data1 & 0x0002))
{
2022-08-16 21:17:01 +00:00
while (sourceactor->int_ang() == ang)
2022-09-16 16:48:50 +00:00
sourceactor->spr.angle = RandomAngle();
2021-12-29 21:56:21 +00:00
}
}
else if (sourceactor->spr.cstat & CSTAT_SPRITE_MOVE_FORWARD) sourceactor->spr.angle += mapangle(sourceactor->xspr.data4);
else if (sourceactor->spr.cstat & CSTAT_SPRITE_MOVE_REVERSE) sourceactor->spr.angle -= mapangle(sourceactor->xspr.data4);
2021-12-29 21:56:21 +00:00
else if (sourceactor->xspr.sysData1 == 0)
{
if ((ang += sourceactor->xspr.data4) >= kAng180) sourceactor->xspr.sysData1 = 1;
2022-08-16 21:20:03 +00:00
sourceactor->set_int_ang(ClipHigh(ang, kAng180));
2021-12-29 21:56:21 +00:00
}
else
{
if ((ang -= sourceactor->xspr.data4) <= -kAng180) sourceactor->xspr.sysData1 = 0;
2022-08-16 21:20:03 +00:00
sourceactor->set_int_ang(ClipLow(ang, -kAng180));
2021-12-29 21:56:21 +00:00
}
pXSector->windAng = sourceactor->spr.angle;
2021-12-29 21:56:21 +00:00
if (sourceactor->xspr.data3 > 0 && sourceactor->xspr.data3 < 4)
{
switch (sourceactor->xspr.data3)
{
case 1:
pXSector->panFloor = true;
pXSector->panCeiling = false;
break;
case 2:
pXSector->panFloor = false;
pXSector->panCeiling = true;
break;
case 3:
pXSector->panFloor = true;
pXSector->panCeiling = true;
break;
}
if (pXSector->panCeiling)
{
StartInterpolation(pSector, Interp_Sect_CeilingPanX);
StartInterpolation(pSector, Interp_Sect_CeilingPanY);
}
if (pXSector->panFloor)
{
StartInterpolation(pSector, Interp_Sect_FloorPanX);
StartInterpolation(pSector, Interp_Sect_FloorPanY);
}
int oldPan = pXSector->panVel;
pXSector->panAngle = pXSector->windAng;
pXSector->panVel = pXSector->windVel;
// add to panList if panVel was set to 0 previously
if (oldPan == 0 && pXSector->panVel != 0)
{
if (!panList.Contains(pSector))
panList.Push(pSector);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useSpriteDamager(DBloodActor* sourceactor, int objType, sectortype* targSect, DBloodActor* targetactor)
2021-09-02 21:52:24 +00:00
{
sectortype* pSector = sourceactor->sector();
2021-12-29 21:56:21 +00:00
int top, bottom;
bool floor, ceil, wall, enter;
switch (objType)
{
case OBJ_SPRITE:
damageSprites(sourceactor, targetactor);
break;
case OBJ_SECTOR:
{
GetActorExtents(sourceactor, &top, &bottom);
floor = (bottom >= pSector->int_floorz());
ceil = (top <= pSector->int_ceilingz());
2021-12-29 21:56:21 +00:00
wall = (sourceactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_WALL);
enter = (!floor && !ceil && !wall);
BloodSectIterator it(targSect);
while (auto iactor = it.Next())
{
auto& hit = iactor->hit;
if (!iactor->IsDudeActor() || !iactor->hasX())
continue;
else if (enter)
damageSprites(sourceactor, iactor);
else if (floor && hit.florhit.type == kHitSector && hit.florhit.hitSector == targSect)
damageSprites(sourceactor, iactor);
else if (ceil && hit.ceilhit.type == kHitSector && hit.ceilhit.hitSector == targSect)
damageSprites(sourceactor, iactor);
else if (wall && hit.hit.type == kHitWall && hit.hit.hitWall->sectorp() == targSect)
damageSprites(sourceactor, iactor);
}
break;
}
case -1:
{
BloodStatIterator it(kStatDude);
while (auto iactor = it.Next())
{
if (iactor->spr.statnum != kStatDude) continue;
switch (sourceactor->xspr.data1)
{
case 667:
if (iactor->IsPlayerActor()) continue;
damageSprites(sourceactor, iactor);
break;
case 668:
if (iactor->IsPlayerActor()) continue;
damageSprites(sourceactor, iactor);
break;
default:
damageSprites(sourceactor, iactor);
break;
}
}
break;
}
}
2020-12-06 20:56:09 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void damageSprites(DBloodActor* sourceactor, DBloodActor* actor)
{
if (!actor->IsDudeActor() || !actor->hasX() || actor->xspr.health <= 0 || sourceactor->xspr.data3 < 0)
return;
int health = 0;
PLAYER* pPlayer = getPlayerById(actor->spr.type);
int dmgType = (sourceactor->xspr.data2 >= kDmgFall) ? ClipHigh(sourceactor->xspr.data2, kDmgElectric) : -1;
int dmg = actor->xspr.health << 4;
int armor[3];
bool godMode = (pPlayer && ((dmgType >= 0 && pPlayer->damageControl[dmgType]) || powerupCheck(pPlayer, kPwUpDeathMask) || pPlayer->godMode)); // kneeling
if (godMode || actor->xspr.locked) return;
else if (sourceactor->xspr.data3)
{
if (sourceactor->spr.flags & kModernTypeFlag1) dmg = ClipHigh(sourceactor->xspr.data3 << 1, 65535);
else if (actor->xspr.sysData2 > 0) dmg = (ClipHigh(actor->xspr.sysData2 << 4, 65535) * sourceactor->xspr.data3) / kPercFull;
else dmg = ((getDudeInfo(actor->spr.type)->startHealth << 4) * sourceactor->xspr.data3) / kPercFull;
health = actor->xspr.health - dmg;
}
if (dmgType >= kDmgFall)
{
if (dmg < (int)actor->xspr.health << 4)
{
if (!nnExtIsImmune(actor, dmgType, 0))
{
if (pPlayer)
{
playerDamageArmor(pPlayer, (DAMAGE_TYPE)dmgType, dmg);
for (int i = 0; i < 3; armor[i] = pPlayer->armor[i], pPlayer->armor[i] = 0, i++);
actDamageSprite(sourceactor, actor, (DAMAGE_TYPE)dmgType, dmg);
for (int i = 0; i < 3; pPlayer->armor[i] = armor[i], i++);
}
else
{
actDamageSprite(sourceactor, actor, (DAMAGE_TYPE)dmgType, dmg);
}
}
else
{
//Printf(PRINT_HIGH, "Dude type %d is immune to damage type %d!", actor->spr.type, dmgType);
}
}
else if (!pPlayer) actKillDude(sourceactor, actor, (DAMAGE_TYPE)dmgType, dmg);
else playerDamageSprite(sourceactor, pPlayer, (DAMAGE_TYPE)dmgType, dmg);
}
else if ((actor->xspr.health = ClipLow(health, 1)) > 16);
else if (!pPlayer) actKillDude(sourceactor, actor, kDamageBullet, dmg);
else playerDamageSprite(sourceactor, pPlayer, kDamageBullet, dmg);
if (actor->xspr.health > 0)
{
if (!(sourceactor->spr.flags & kModernTypeFlag8))
actor->xspr.health = health;
bool showEffects = !(sourceactor->spr.flags & kModernTypeFlag2); // show it by default
bool forceRecoil = (sourceactor->spr.flags & kModernTypeFlag4);
if (showEffects)
{
switch (dmgType)
{
case kDmgBurn:
if (actor->xspr.burnTime > 0) break;
actBurnSprite(sourceactor, actor, ClipLow(dmg >> 1, 128));
evKillActor(actor, kCallbackFXFlameLick);
evPostActor(actor, 0, kCallbackFXFlameLick); // show flames
break;
case kDmgElectric:
forceRecoil = true; // show tesla recoil animation
break;
case kDmgBullet:
evKillActor(actor, kCallbackFXBloodSpurt);
for (int i = 1; i < 6; i++)
{
if (Chance(0x16000 >> i))
fxSpawnBlood(actor, dmg << 4);
}
break;
case kDmgChoke:
if (!pPlayer || !Chance(0x2000)) break;
else pPlayer->blindEffect += dmg << 2;
}
}
if (forceRecoil && !pPlayer)
{
actor->xspr.data3 = 32767;
actor->dudeExtra.teslaHit = (dmgType == kDmgElectric) ? 1 : 0;
if (actor->xspr.aiState->stateType != kAiStateRecoil)
RecoilDude(actor);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void useSeqSpawnerGen(DBloodActor* sourceactor, int objType, sectortype* pSector, walltype* pWall, DBloodActor* iactor)
{
if (sourceactor->xspr.data2 > 0 && !getSequence(sourceactor->xspr.data2))
{
Printf(PRINT_HIGH, "Missing sequence #%d", sourceactor->xspr.data2);
return;
}
switch (objType)
{
case OBJ_SECTOR:
{
if (sourceactor->xspr.data2 <= 0)
{
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 1)
seqKill(SS_FLOOR, pSector);
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 2)
seqKill(SS_CEILING, pSector);
}
else
{
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 1)
seqSpawn(sourceactor->xspr.data2, SS_FLOOR, pSector, -1);
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 2)
seqSpawn(sourceactor->xspr.data2, SS_CEILING, pSector, -1);
}
return;
}
case OBJ_WALL:
{
if (sourceactor->xspr.data2 <= 0)
{
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 1)
seqKill(SS_WALL, pWall);
if ((sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 2) && (pWall->cstat & CSTAT_WALL_MASKED))
seqKill(SS_MASKED, pWall);
}
else
{
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 1)
seqSpawn(sourceactor->xspr.data2, SS_WALL, pWall, -1);
if (sourceactor->xspr.data3 == 3 || sourceactor->xspr.data3 == 2) {
if (!pWall->twoSided()) {
if (sourceactor->xspr.data3 == 3)
seqSpawn(sourceactor->xspr.data2, SS_WALL, pWall, -1);
}
else {
if (!(pWall->cstat & CSTAT_WALL_MASKED))
pWall->cstat |= CSTAT_WALL_MASKED;
seqSpawn(sourceactor->xspr.data2, SS_MASKED, pWall, -1);
}
}
if (sourceactor->xspr.data4 > 0)
{
DVector3 cpos;
cpos.XY() = pWall->center();
2021-12-29 21:56:21 +00:00
auto pMySector = pWall->sectorp();
double ceilZ, floorZ;
getzsofslopeptr(pSector, cpos, &ceilZ, &floorZ);
double ceilZ2, floorZ2;
getzsofslopeptr(pWall->nextSector(), cpos, &ceilZ2, &floorZ2);
ceilZ = max(ceilZ, ceilZ2);
floorZ = min(floorZ, floorZ2);
cpos.Z = (ceilZ + floorZ) * 0.5;
sfxPlay3DSound(cpos, sourceactor->xspr.data4, pSector);
2021-12-29 21:56:21 +00:00
}
}
return;
}
case OBJ_SPRITE:
{
if (sourceactor->xspr.data2 <= 0) seqKill(iactor);
else if (iactor->insector())
2021-12-29 21:56:21 +00:00
{
if (sourceactor->xspr.data3 > 0)
{
auto spawned = InsertSprite(iactor->sector(), kStatDecoration);
2021-12-29 21:56:21 +00:00
if (spawned != nullptr)
{
double top, bottom;
GetActorExtents(spawned, &top, &bottom);
DVector3 pos = iactor->spr.pos;
switch (sourceactor->xspr.data3)
{
default:
break;
case 2:
pos.Z = bottom;
break;
case 3:
pos.Z = top;
break;
case 4:
// this had no value shift and no yrepeat handling, which looks like a bug.
pos.Z += (tileHeight(iactor->spr.picnum) / 2 + tileTopOffset(iactor->spr.picnum)) * iactor->spr.yrepeat * REPEAT_SCALE;
break;
case 5:
case 6:
if (!iactor->insector()) pos.Z = top;
else pos.Z = ((sourceactor->xspr.data3 == 5) ?
getflorzofslopeptr(spawned->sector(), spawned->spr.pos) :
getceilzofslopeptr(spawned->sector(), spawned->spr.pos));
break;
}
spawned->spr.pos = pos;
2021-12-29 21:56:21 +00:00
spawned->addX();
seqSpawn(sourceactor->xspr.data2, spawned, -1);
if (sourceactor->spr.flags & kModernTypeFlag1)
{
spawned->spr.pal = sourceactor->spr.pal;
spawned->spr.shade = sourceactor->spr.shade;
spawned->spr.xrepeat = sourceactor->spr.xrepeat;
spawned->spr.yrepeat = sourceactor->spr.yrepeat;
spawned->spr.xoffset = sourceactor->spr.xoffset;
spawned->spr.yoffset = sourceactor->spr.yoffset;
}
if (sourceactor->spr.flags & kModernTypeFlag2)
{
spawned->spr.cstat |= sourceactor->spr.cstat;
}
2022-05-06 16:27:44 +00:00
if (sourceactor->spr.flags & kModernTypeFlag4)
{
spawned->spr.angle = sourceactor->spr.angle;
2022-05-06 16:27:44 +00:00
}
2021-12-29 21:56:21 +00:00
// should be: the more is seqs, the shorter is timer
evPostActor(spawned, 1000, kCallbackRemove);
}
}
else
{
seqSpawn(sourceactor->xspr.data2, iactor, -1);
}
if (sourceactor->xspr.data4 > 0)
sfxPlay3DSound(iactor, sourceactor->xspr.data4, -1, 0);
}
return;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void condPush(DBloodActor* actor, const EventObject& iactor) { actor->condition[0] = iactor; }
void condPush(DBloodActor* actor, DBloodActor* iactor) { actor->condition[0] = EventObject(iactor); }
void condPush(DBloodActor* actor, walltype* iactor) { actor->condition[0] = EventObject(iactor); }
void condPush(DBloodActor* actor, sectortype* iactor) { actor->condition[0] = EventObject(iactor); }
EventObject condGet(DBloodActor* actor) { return actor->condition[0]; }
void condBackup(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
actor->condition[1] = actor->condition[0];
}
void condRestore(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
actor->condition[0] = actor->condition[1];
}
// normal comparison
2021-12-29 21:56:21 +00:00
bool condCmp(int val, int arg1, int arg2, int comOp)
2021-10-13 22:03:52 +00:00
{
2021-12-29 21:56:21 +00:00
if (comOp & 0x2000) return (comOp & CSTAT_SPRITE_BLOCK) ? (val > arg1) : (val >= arg1); // blue sprite
else if (comOp & 0x4000) return (comOp & CSTAT_SPRITE_BLOCK) ? (val < arg1) : (val <= arg1); // green sprite
else if (comOp & CSTAT_SPRITE_BLOCK)
{
if (arg1 > arg2) I_Error("Value of argument #1 (%d) must be less than value of argument #2 (%d)", arg1, arg2);
return (val >= arg1 && val <= arg2);
}
else return (val == arg1);
}
2021-10-13 21:58:04 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void condError(DBloodActor* aCond, const char* pzFormat, ...)
{
2021-12-29 21:56:21 +00:00
char buffer[256];
char buffer2[512];
FString condType = "Unknown";
for (int i = 0; i < 7; i++)
{
if (aCond->xspr.data1 < gCondTypeNames[i].rng1 || aCond->xspr.data1 >= gCondTypeNames[i].rng2) continue;
condType = gCondTypeNames[i].name;
condType.ToUpper();
break;
}
snprintf(buffer, 256, "\n\n%s CONDITION RX: %d, TX: %d, SPRITE: #%d RETURNS:\n", condType.GetChars(), aCond->xspr.rxID, aCond->xspr.txID, aCond->GetIndex());
va_list args;
va_start(args, pzFormat);
vsnprintf(buffer2, 512, pzFormat, args);
I_Error("%s%s", buffer, buffer2);
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
}
2021-10-13 22:03:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-13 22:46:15 +00:00
bool condCheckGame(DBloodActor* aCond, const EVENT& event, int cmpOp, bool PUSH)
{
2022-05-06 16:27:44 +00:00
int cond = aCond->xspr.data1;
2021-12-29 21:56:21 +00:00
int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3;
int arg3 = aCond->xspr.data4;
switch (cond) {
case 1: return condCmp(gFrameCount / (kTicsPerSec * 60), arg1, arg2, cmpOp); // compare level minutes
case 2: return condCmp((gFrameCount / kTicsPerSec) % 60, arg1, arg2, cmpOp); // compare level seconds
case 3: return condCmp(((gFrameCount % kTicsPerSec) * 33) / 10, arg1, arg2, cmpOp); // compare level mseconds
case 4: return condCmp(gFrameCount, arg1, arg2, cmpOp); // compare level time (unsafe)
case 5: return condCmp(gKillMgr.Kills, arg1, arg2, cmpOp); // compare current global kills counter
case 6: return condCmp(gKillMgr.TotalKills, arg1, arg2, cmpOp); // compare total global kills counter
case 7: return condCmp(gSecretMgr.Founds, arg1, arg2, cmpOp); // compare how many secrets found
case 8: return condCmp(gSecretMgr.Total, arg1, arg2, cmpOp); // compare total secrets
/*----------------------------------------------------------------------------------------------------------------------------------*/
case 20: return condCmp(gVisibility, arg1, arg2, cmpOp); // compare global visibility value
/*----------------------------------------------------------------------------------------------------------------------------------*/
case 30: return Chance((0x10000 * arg3) / kPercFull); // check chance
case 31: return condCmp(nnExtRandom(arg1, arg2), arg1, arg2, cmpOp);
/*----------------------------------------------------------------------------------------------------------------------------------*/
case 47:
{
BloodStatIterator it(ClipRange(arg3, 0, kMaxStatus));
int c = 0;
while (it.Next()) c++;
return condCmp(c, arg1, arg2, cmpOp); // compare counter of specific statnum sprites
}
case 48: return condCmp(Numsprites, arg1, arg2, cmpOp); // compare counter of total sprites
}
condError(aCond, "Unexpected condition id (%d)!", cond);
return false;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-13 22:46:15 +00:00
bool condCheckMixed(DBloodActor* aCond, const EVENT& event, int cmpOp, bool PUSH)
2021-09-03 19:23:22 +00:00
{
2021-12-29 21:56:21 +00:00
//int var = -1;
int cond = aCond->xspr.data1 - kCondMixedBase; int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3; int arg3 = aCond->xspr.data4;
auto eob = condGet(aCond);
switch (cond)
{
case 0: return (eob.isSector());
case 5: return (eob.isWall());
case 10: return (eob.isActor() && eob.actor());
case 15: // x-index is fine?
if (eob.isWall()) return eob.wall()->hasX();
if (eob.isSector()) return eob.sector()->hasX();
if (eob.isActor()) return eob.actor() && eob.actor()->hasX();
break;
case 20: // type in a range?
if (eob.isWall()) return condCmp(eob.wall()->type, arg1, arg2, cmpOp);
if (eob.isSector()) return condCmp(eob.sector()->type, arg1, arg2, cmpOp);
if (eob.isActor()) return eob.actor() && condCmp(eob.actor()->spr.type, arg1, arg2, cmpOp);
break;
case 24:
case 25: case 26: case 27:
case 28: case 29: case 30:
case 31: case 32: case 33:
if (eob.isWall())
{
walltype* pObj = eob.wall();
switch (cond)
{
case 24: return condCmp(surfType[pObj->picnum], arg1, arg2, cmpOp);
case 25: return condCmp(pObj->picnum, arg1, arg2, cmpOp);
case 26: return condCmp(pObj->pal, arg1, arg2, cmpOp);
case 27: return condCmp(pObj->shade, arg1, arg2, cmpOp);
case 28: return (arg3) ? condCmp((pObj->cstat & EWallFlags::FromInt(arg3)), arg1, arg2, cmpOp) : (pObj->cstat & EWallFlags::FromInt(arg1));
case 29: return (arg3) ? condCmp((pObj->hitag & arg3), arg1, arg2, cmpOp) : (pObj->hitag & arg1);
2021-12-29 21:56:21 +00:00
case 30: return condCmp(pObj->xrepeat, arg1, arg2, cmpOp);
case 31: return condCmp(pObj->xpan(), arg1, arg2, cmpOp);
case 32: return condCmp(pObj->yrepeat, arg1, arg2, cmpOp);
case 33: return condCmp(pObj->ypan(), arg1, arg2, cmpOp);
}
}
else if (eob.isActor())
{
auto actor = eob.actor();
if (!actor) break;
switch (cond)
{
case 24: return condCmp(surfType[actor->spr.picnum], arg1, arg2, cmpOp);
case 25: return condCmp(actor->spr.picnum, arg1, arg2, cmpOp);
case 26: return condCmp(actor->spr.pal, arg1, arg2, cmpOp);
case 27: return condCmp(actor->spr.shade, arg1, arg2, cmpOp);
case 28: return (arg3) ? condCmp((actor->spr.cstat & ESpriteFlags::FromInt(arg3)), arg1, arg2, cmpOp) : (actor->spr.cstat & ESpriteFlags::FromInt(arg1));
case 29: return (arg3) ? condCmp((actor->spr.hitag & arg3), arg1, arg2, cmpOp) : (actor->spr.hitag & arg1);
2021-12-29 21:56:21 +00:00
case 30: return condCmp(actor->spr.xrepeat, arg1, arg2, cmpOp);
case 31: return condCmp(actor->spr.xoffset, arg1, arg2, cmpOp);
case 32: return condCmp(actor->spr.yrepeat, arg1, arg2, cmpOp);
case 33: return condCmp(actor->spr.yoffset, arg1, arg2, cmpOp);
}
}
else if (eob.sector())
{
sectortype* pObj = eob.sector();
switch (cond)
{
case 24:
switch (arg3)
{
default: return (condCmp(surfType[pObj->floorpicnum], arg1, arg2, cmpOp) || condCmp(surfType[pObj->ceilingpicnum], arg1, arg2, cmpOp));
case 1: return condCmp(surfType[pObj->floorpicnum], arg1, arg2, cmpOp);
case 2: return condCmp(surfType[pObj->ceilingpicnum], arg1, arg2, cmpOp);
}
break;
case 25:
switch (arg3)
{
default: return (condCmp(pObj->floorpicnum, arg1, arg2, cmpOp) || condCmp(pObj->ceilingpicnum, arg1, arg2, cmpOp));
case 1: return condCmp(pObj->floorpicnum, arg1, arg2, cmpOp);
case 2: return condCmp(pObj->ceilingpicnum, arg1, arg2, cmpOp);
}
break;
case 26:
switch (arg3)
{
default: return (condCmp(pObj->floorpal, arg1, arg2, cmpOp) || condCmp(pObj->ceilingpal, arg1, arg2, cmpOp));
case 1: return condCmp(pObj->floorpal, arg1, arg2, cmpOp);
case 2: return condCmp(pObj->ceilingpal, arg1, arg2, cmpOp);
}
break;
case 27:
switch (arg3)
{
default: return (condCmp(pObj->floorshade, arg1, arg2, cmpOp) || condCmp(pObj->ceilingshade, arg1, arg2, cmpOp));
case 1: return condCmp(pObj->floorshade, arg1, arg2, cmpOp);
case 2: return condCmp(pObj->ceilingshade, arg1, arg2, cmpOp);
}
break;
case 28:
{
auto a = ESectorFlags::FromInt(arg1);
switch (arg3)
{
default: return ((pObj->floorstat & a) || (pObj->ceilingstat & a));
case 1: return (pObj->floorstat & a);
case 2: return (pObj->ceilingstat & a);
}
break;
}
case 29: return (arg3) ? condCmp((pObj->hitag & arg3), arg1, arg2, cmpOp) : (pObj->hitag & arg1);
2021-12-29 21:56:21 +00:00
case 30: return condCmp(pObj->floorxpan(), arg1, arg2, cmpOp);
case 31: return condCmp(pObj->ceilingxpan(), arg1, arg2, cmpOp);
case 32: return condCmp(pObj->floorypan(), arg1, arg2, cmpOp);
case 33: return condCmp(pObj->ceilingypan(), arg1, arg2, cmpOp);
}
}
break;
case 41: case 42: case 43:
case 44: case 50: case 51:
case 52: case 53: case 54:
case 55: case 56: case 57:
case 58: case 59: case 70:
case 71:
if (eob.isWall())
{
auto pObj = eob.wall();
if (!pObj->hasX())
return condCmp(0, arg1, arg2, cmpOp);
XWALL* pXObj = &pObj->xw();
switch (cond)
{
case 41: return condCmp(pXObj->data, arg1, arg2, cmpOp);
case 50: return condCmp(pXObj->rxID, arg1, arg2, cmpOp);
case 51: return condCmp(pXObj->txID, arg1, arg2, cmpOp);
case 52: return pXObj->locked;
case 53: return pXObj->triggerOn;
case 54: return pXObj->triggerOff;
case 55: return pXObj->triggerOnce;
case 56: return pXObj->isTriggered;
case 57: return pXObj->state;
case 58: return condCmp((kPercFull * pXObj->busy) / 65536, arg1, arg2, cmpOp);
case 59: return pXObj->dudeLockout;
case 70:
switch (arg3)
{
default: return (condCmp(seqGetID(SS_WALL, pObj), arg1, arg2, cmpOp) || condCmp(seqGetID(SS_MASKED, pObj), arg1, arg2, cmpOp));
case 1: return condCmp(seqGetID(SS_WALL, pObj), arg1, arg2, cmpOp);
case 2: return condCmp(seqGetID(SS_MASKED, pObj), arg1, arg2, cmpOp);
}
break;
case 71:
switch (arg3)
{
default: return (condCmp(seqGetStatus(SS_WALL, pObj), arg1, arg2, cmpOp) || condCmp(seqGetStatus(SS_MASKED, pObj), arg1, arg2, cmpOp));
case 1: return condCmp(seqGetStatus(SS_WALL, pObj), arg1, arg2, cmpOp);
case 2: return condCmp(seqGetStatus(SS_MASKED, pObj), arg1, arg2, cmpOp);
}
break;
}
}
else if (eob.isActor())
{
auto objActor = eob.actor();
if (!objActor) break;
if (!objActor->hasX())
return condCmp(0, arg1, arg2, cmpOp);
switch (cond)
{
case 41: case 42:
case 43: case 44:
return condCmp(getDataFieldOfObject(eob, 1 + cond - 41), arg1, arg2, cmpOp);
case 50: return condCmp(objActor->xspr.rxID, arg1, arg2, cmpOp);
case 51: return condCmp(objActor->xspr.txID, arg1, arg2, cmpOp);
case 52: return objActor->xspr.locked;
case 53: return objActor->xspr.triggerOn;
case 54: return objActor->xspr.triggerOff;
case 55: return objActor->xspr.triggerOnce;
case 56: return objActor->xspr.isTriggered;
case 57: return objActor->xspr.state;
case 58: return condCmp((kPercFull * objActor->xspr.busy) / 65536, arg1, arg2, cmpOp);
case 59: return objActor->xspr.DudeLockout;
case 70: return condCmp(seqGetID(objActor), arg1, arg2, cmpOp);
case 71: return condCmp(seqGetStatus(objActor), arg1, arg2, cmpOp);
}
}
else if (eob.isSector())
{
auto pObj = eob.sector();
if (!pObj->hasX())
return condCmp(0, arg1, arg2, cmpOp);
XSECTOR* pXObj = &pObj->xs();
switch (cond) {
case 41: return condCmp(pXObj->data, arg1, arg2, cmpOp);
case 50: return condCmp(pXObj->rxID, arg1, arg2, cmpOp);
case 51: return condCmp(pXObj->txID, arg1, arg2, cmpOp);
case 52: return pXObj->locked;
case 53: return pXObj->triggerOn;
case 54: return pXObj->triggerOff;
case 55: return pXObj->triggerOnce;
case 56: return pXObj->isTriggered;
case 57: return pXObj->state;
case 58: return condCmp((kPercFull * pXObj->busy) / 65536, arg1, arg2, cmpOp);
case 59: return pXObj->dudeLockout;
case 70:
// wall???
switch (arg3)
{
default: return (condCmp(seqGetID(SS_CEILING, pObj), arg1, arg2, cmpOp) || condCmp(seqGetID(SS_FLOOR, pObj), arg1, arg2, cmpOp));
case 1: return condCmp(seqGetID(SS_CEILING, pObj), arg1, arg2, cmpOp);
case 2: return condCmp(seqGetID(SS_FLOOR, pObj), arg1, arg2, cmpOp);
}
break;
case 71:
switch (arg3)
{
default: return (condCmp(seqGetStatus(SS_CEILING, pObj), arg1, arg2, cmpOp) || condCmp(seqGetStatus(SS_FLOOR, pObj), arg1, arg2, cmpOp));
case 1: return condCmp(seqGetStatus(SS_CEILING, pObj), arg1, arg2, cmpOp);
case 2: return condCmp(seqGetStatus(SS_FLOOR, pObj), arg1, arg2, cmpOp);
}
break;
}
}
break;
case 99: return condCmp(event.cmd, arg1, arg2, cmpOp); // this codition received specified command?
}
condError(aCond, "Unexpected condition id (%d)!", cond);
return false;
}
2021-10-13 22:49:45 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool condCheckSector(DBloodActor* aCond, int cmpOp, bool PUSH)
{
int cond = aCond->xspr.data1 - kCondSectorBase;
int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3; //int arg3 = aCond->xspr.data4;
auto eob = condGet(aCond);
if (!eob.isSector())
condError(aCond, "Sector expected, got %s", eob.description().GetChars());
sectortype* pSect = eob.sector();
XSECTOR* pXSect = pSect->hasX() ? &pSect->xs() : nullptr;
if (cond < (kCondRange >> 1))
{
switch (cond)
{
default: break;
case 0: return condCmp(pSect->visibility, arg1, arg2, cmpOp);
case 5: return condCmp(pSect->floorheinum, arg1, arg2, cmpOp);
case 6: return condCmp(pSect->ceilingheinum, arg1, arg2, cmpOp);
case 10: // required sprite type is in current sector?
BloodSectIterator it(pSect);
while (auto iactor = it.Next())
{
if (!condCmp(iactor->spr.type, arg1, arg2, cmpOp)) continue;
else if (PUSH) condPush(aCond, iactor);
return true;
}
return false;
}
}
else if (pXSect)
{
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;
switch (pSect->type)
{
case kSectorZMotion:
case kSectorRotate:
case kSectorSlide:
2022-05-06 16:27:44 +00:00
if (cond == 55)// 60)
2021-12-29 21:56:21 +00:00
{
h = ClipLow(abs(pXSect->onFloorZ - pXSect->offFloorZ), 1);
curH = abs(pSect->int_floorz() - pXSect->offFloorZ);
2021-12-29 21:56:21 +00:00
}
else
{
h = ClipLow(abs(pXSect->onCeilZ - pXSect->offCeilZ), 1);
curH = abs(pSect->int_ceilingz() - pXSect->offCeilZ);
2021-12-29 21:56:21 +00:00
}
return condCmp((kPercFull * curH) / h, arg1, arg2, cmpOp);
default:
condError(aCond, "Usupported sector type %d", pSect->type);
return false;
}
}
case 57: // this sector in movement?
return !pXSect->unused1;
}
}
else
{
switch (cond)
{
default: return false;
case 55:
case 56:
return condCmp(0, arg1, arg2, cmpOp);
}
}
condError(aCond, "Unexpected condition id (%d)!", cond);
return false;
}
2021-10-13 22:03:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool condCheckWall(DBloodActor* aCond, int cmpOp, bool PUSH)
{
int var = -1;
int cond = aCond->xspr.data1 - kCondWallBase; int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3; //int arg3 = aCond->xspr.data4;
auto eob = condGet(aCond);
if (!eob.isWall())
condError(aCond, "Wall expected, got %s", eob.description().GetChars());
walltype* pWall = eob.wall();
if (cond < (kCondRange >> 1))
{
switch (cond)
{
default: break;
case 0:
return condCmp(pWall->overpicnum, arg1, arg2, cmpOp);
case 5:
if (PUSH) condPush(aCond, pWall->sectorp());
return true;
case 10: // this wall is a mirror? // must be as constants here
return (pWall->type != kWallStack && condCmp(pWall->picnum, 4080, (4080 + 16) - 1, 0));
case 15:
if (!pWall->twoSided()) return false;
else if (PUSH) condPush(aCond, pWall->nextSector());
return true;
case 20:
if (!pWall->twoSided()) return false;
else if (PUSH) condPush(aCond, pWall->nextWall());
return true;
case 25: // next wall belongs to sector? (Note: This was 'sector of next wall' which is same as case 15 because we do not allow bad links!)
if (!pWall->twoSided()) return false;
else if (PUSH) condPush(aCond, pWall->nextSector());
return true;
}
}
condError(aCond, "Unexpected condition id (%d)!", cond);
return false;
}
2021-10-13 22:03:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool condCheckPlayer(DBloodActor* aCond, int cmpOp, bool PUSH)
{
int var = -1;
PLAYER* pPlayer = NULL;
int cond = aCond->xspr.data1 - kCondPlayerBase;
int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3;
int arg3 = aCond->xspr.data4;
auto eob = condGet(aCond);
if (!eob.isActor() || !eob.actor())
condError(aCond, "Sprite expected, got %s", eob.description().GetChars());
auto objActor = eob.actor();
for (int i = 0; i < kMaxPlayers; i++)
{
if (objActor != gPlayer[i].actor) continue;
pPlayer = &gPlayer[i];
break;
}
if (!pPlayer)
{
condError(aCond, "Player expected, got %s", eob.description().GetChars());
return false;
}
switch (cond) {
case 0: // check if this player is connected
if (!condCmp(pPlayer->nPlayer + 1, arg1, arg2, cmpOp) || pPlayer->actor == nullptr) return false;
else if (PUSH) condPush(aCond, pPlayer->actor);
return (pPlayer->nPlayer >= 0);
case 1: return condCmp((gGameOptions.nGameType != 3) ? 0 : pPlayer->teamId + 1, arg1, arg2, cmpOp); // compare team
case 2: return (arg1 > 0 && arg1 < 8 && pPlayer->hasKey[arg1 - 1]);
case 3: return (arg1 > 0 && arg1 < 15 && pPlayer->hasWeapon[arg1 - 1]);
case 4: return condCmp(pPlayer->curWeapon, arg1, arg2, cmpOp);
case 5: return (arg1 > 0 && arg1 < 6 && condCmp(pPlayer->packSlots[arg1 - 1].curAmount, arg2, arg3, cmpOp));
case 6: return (arg1 > 0 && arg1 < 6 && pPlayer->packSlots[arg1 - 1].isActive);
case 7: return condCmp(pPlayer->packItemId + 1, arg1, arg2, cmpOp);
case 8: // check for powerup amount in seconds
if (arg3 > 0 && arg3 <= (kMaxAllowedPowerup - (kMinAllowedPowerup << 1) + 1)) {
var = (kMinAllowedPowerup + arg3) - 1; // allowable powerups
return condCmp(pPlayer->pwUpTime[var] / 100, arg1, arg2, cmpOp);
}
condError(aCond, "Unexpected powerup #%d", arg3);
return false;
case 9:
if (!pPlayer->fragger) return false;
else if (PUSH) condPush(aCond, pPlayer->fragger);
return true;
case 10: // check keys pressed
switch (arg1) {
case 1: return (pPlayer->input.fvel > 0); // forward
case 2: return (pPlayer->input.fvel < 0); // backward
case 3: return (pPlayer->input.svel < 0); // left
case 4: return (pPlayer->input.svel > 0); // right
2021-12-29 21:56:21 +00:00
case 5: return !!(pPlayer->input.actions & SB_JUMP); // jump
case 6: return !!(pPlayer->input.actions & SB_CROUCH); // crouch
case 7: return !!(pPlayer->input.actions & SB_FIRE); // normal fire weapon
case 8: return !!(pPlayer->input.actions & SB_ALTFIRE); // alt fire weapon
case 9: return !!(pPlayer->input.actions & SB_OPEN); // use
default:
condError(aCond, "Specify a correct key!");
break;
}
return false;
case 11: return (pPlayer->isRunning);
case 12: return (pPlayer->fallScream); // falling in abyss?
case 13: return condCmp(pPlayer->lifeMode + 1, arg1, arg2, cmpOp);
case 14: return condCmp(pPlayer->posture + 1, arg1, arg2, cmpOp);
case 46: return condCmp(pPlayer->sceneQav, arg1, arg2, cmpOp);
case 47: return (pPlayer->godMode || powerupCheck(pPlayer, kPwUpDeathMask));
case 48: return isShrinked(pPlayer->actor);
case 49: return isGrown(pPlayer->actor);
}
condError(aCond, "Unexpected condition #%d!", cond);
return false;
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
}
2021-10-13 22:03:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool condCheckDude(DBloodActor* aCond, int cmpOp, bool PUSH)
{
int var = -1;
int cond = aCond->xspr.data1 - kCondDudeBase;
int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3;
int arg3 = aCond->xspr.data4;
2021-12-29 21:56:21 +00:00
auto eob = condGet(aCond);
if (!eob.isActor() || !eob.actor())
condError(aCond, "Sprite expected, got %s", eob.description().GetChars());
auto objActor = eob.actor();
if (!objActor->hasX() || objActor->spr.type == kThingBloodChunks)
condError(aCond, "Sprite #%d is dead!", objActor->GetIndex());
if (!objActor->IsDudeActor() || objActor->IsPlayerActor())
condError(aCond, "Object #%d is not an enemy!", objActor->GetIndex());
auto targ = objActor->GetTarget();
switch (cond)
{
default: break;
case 0: // dude have any targets?
if (!targ) return false;
else if (!targ->IsDudeActor() && targ->spr.type != kMarkerPath) return false;
else if (PUSH) condPush(aCond, targ);
return true;
2021-12-29 21:56:21 +00:00
case 1: return aiFightDudeIsAffected(objActor); // dude affected by ai fight?
case 2: // distance to the target in a range?
case 3: // is the target visible?
case 4: // is the target visible with periphery?
{
if (!targ)
condError(aCond, "Dude #%d has no target!", objActor->GetIndex());
DUDEINFO* pInfo = getDudeInfo(objActor->spr.type);
2022-08-22 16:37:46 +00:00
double height = (pInfo->eyeHeight * objActor->spr.yrepeat) * REPEAT_SCALE;
auto delta = targ->spr.pos.XY() - objActor->spr.pos.XY();
2021-12-29 21:56:21 +00:00
switch (cond)
{
case 2:
var = condCmp(int(delta.Length() * 16), arg1 * 512, arg2 * 512, cmpOp);
2021-12-29 21:56:21 +00:00
break;
case 3:
case 4:
2022-08-22 16:37:46 +00:00
var = cansee(objActor->spr.pos, objActor->sector(), targ->spr.pos.plusZ(-height), targ->sector());
2021-12-29 21:56:21 +00:00
if (cond == 4 && var > 0)
{
DAngle absang = absangle(VecToAngle(delta), objActor->spr.angle);
var = absang < (arg1 <= 0 ? pInfo->Periphery() : min(mapangle(arg1), DAngle360));
2021-12-29 21:56:21 +00:00
}
break;
}
if (var <= 0) return false;
else if (PUSH) condPush(aCond, targ);
return true;
}
case 5: return objActor->xspr.dudeFlag4;
case 6: return objActor->xspr.dudeDeaf;
case 7: return objActor->xspr.dudeGuard;
case 8: return objActor->xspr.dudeAmbush;
case 9: return (objActor->xspr.unused1 & kDudeFlagStealth);
case 10: // check if the marker is busy with another dude
case 11: // check if the marker is reached
if (!objActor->xspr.dudeFlag4 || !targ || targ->spr.type != kMarkerPath) return false;
switch (cond) {
case 10:
{
auto check = aiPatrolMarkerBusy(objActor, targ);
if (!check) return false;
else if (PUSH) condPush(aCond, check);
break;
}
case 11:
if (!aiPatrolMarkerReached(objActor)) return false;
else if (PUSH) condPush(aCond, targ);
break;
}
return true;
case 12: // compare spot progress value in %
if (!objActor->xspr.dudeFlag4 || !targ || targ->spr.type != kMarkerPath) var = 0;
else if (!(objActor->xspr.unused1 & kDudeFlagStealth) || objActor->xspr.data3 < 0 || objActor->xspr.data3 > kMaxPatrolSpotValue) var = 0;
else var = (kPercFull * objActor->xspr.data3) / kMaxPatrolSpotValue;
return condCmp(var, arg1, arg2, cmpOp);
case 15: return getDudeInfo(objActor->spr.type)->lockOut; // dude allowed to interact with objects?
case 16: return condCmp(objActor->xspr.aiState->stateType, arg1, arg2, cmpOp);
case 17: return condCmp(objActor->xspr.stateTimer, arg1, arg2, cmpOp);
case 20: // kDudeModernCustom conditions
case 21:
case 22:
case 23:
case 24:
switch (objActor->spr.type)
{
case kDudeModernCustom:
case kDudeModernCustomBurning:
switch (cond) {
case 20: // life leech is thrown?
{
DBloodActor* act = objActor->genDudeExtra.pLifeLeech;
if (!act) return false;
else if (PUSH) condPush(aCond, act);
return true;
}
case 21: // life leech is destroyed?
{
DBloodActor* act = objActor->genDudeExtra.pLifeLeech;
if (!act) return false;
if (objActor->GetSpecialOwner()) return true;
else if (PUSH) condPush(aCond, act);
return false;
}
case 22: // are required amount of dudes is summoned?
return condCmp(objActor->genDudeExtra.slaveCount, arg1, arg2, cmpOp);
case 23: // check if dude can...
switch (arg3)
{
case 1: return objActor->genDudeExtra.canAttack;
case 2: return objActor->genDudeExtra.canBurn;
case 3: return objActor->genDudeExtra.canDuck;
case 4: return objActor->genDudeExtra.canElectrocute;
case 5: return objActor->genDudeExtra.canFly;
case 6: return objActor->genDudeExtra.canRecoil;
case 7: return objActor->genDudeExtra.canSwim;
case 8: return objActor->genDudeExtra.canWalk;
default: condError(aCond, "Invalid argument %d", arg3); break;
}
break;
case 24: // compare weapon dispersion
return condCmp(objActor->genDudeExtra.baseDispersion, arg1, arg2, cmpOp);
}
break;
default:
condError(aCond, "Dude #%d is not a Custom Dude!", objActor->GetIndex());
return false;
}
}
condError(aCond, "Unexpected condition #%d!", cond);
return false;
}
2021-10-13 22:03:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-13 23:06:50 +00:00
bool condCheckSprite(DBloodActor* aCond, int cmpOp, bool PUSH)
{
2021-12-29 21:56:21 +00:00
int var = -1, var2 = -1, var3 = -1; PLAYER* pPlayer = NULL; bool retn = false;
int cond = aCond->xspr.data1 - kCondSpriteBase; int arg1 = aCond->xspr.data2;
int arg2 = aCond->xspr.data3; int arg3 = aCond->xspr.data4;
auto eob = condGet(aCond);
if (!eob.isActor() || !eob.actor())
condError(aCond, "Sprite expected, got %s", eob.description().GetChars());
auto objActor = eob.actor();
if (cond < (kCondRange >> 1))
{
switch (cond)
{
default: break;
2022-08-16 21:25:11 +00:00
case 0: return condCmp((arg3 == 0) ? (objActor->int_ang() & 2047) : objActor->int_ang(), arg1, arg2, cmpOp);
2021-12-29 21:56:21 +00:00
case 5: return condCmp(objActor->spr.statnum, arg1, arg2, cmpOp);
case 6: return ((objActor->spr.flags & kHitagRespawn) || objActor->spr.statnum == kStatRespawn);
case 7: return condCmp(spriteGetSlope(objActor), arg1, arg2, cmpOp);
2022-09-09 16:32:42 +00:00
case 10: return condCmp(objActor->native_clipdist(), arg1, arg2, cmpOp);
2021-12-29 21:56:21 +00:00
case 15:
if (!objActor->GetOwner()) return false;
else if (PUSH) condPush(aCond, objActor->GetOwner());
return true;
case 20: // stays in a sector?
if (!objActor->insector()) return false;
else if (PUSH) condPush(aCond, objActor->sector());
2021-12-29 21:56:21 +00:00
return true;
case 25:
2022-05-06 16:27:44 +00:00
if (arg3 == 1)
2021-12-29 21:56:21 +00:00
{
2022-05-06 16:27:44 +00:00
if (arg1 == 0)
{
2022-09-01 18:49:35 +00:00
if ((var = condCmp(objActor->int_vel().X, arg1, arg2, cmpOp)) == true) return var;
if ((var = condCmp(objActor->int_vel().Y, arg1, arg2, cmpOp)) == true) return var;
if ((var = condCmp(objActor->int_vel().Z, arg1, arg2, cmpOp)) == true) return var;
2022-05-06 16:27:44 +00:00
}
2022-09-01 18:49:35 +00:00
else if (arg1 == 1) return condCmp(objActor->int_vel().X, arg1, arg2, cmpOp);
else if (arg1 == 2) return condCmp(objActor->int_vel().Y, arg1, arg2, cmpOp);
else if (arg1 == 3) return condCmp(objActor->int_vel().Z, arg1, arg2, cmpOp);
2022-09-01 18:39:25 +00:00
}
2022-09-03 08:12:09 +00:00
else if (arg1 == 0) return (objActor->vel.X != 0 || objActor->vel.Y != 0 || objActor->int_vel().Z);
2022-09-01 18:49:35 +00:00
else if (arg1 == 1) return (objActor->int_vel().X);
else if (arg1 == 2) return (objActor->int_vel().Y);
2022-09-01 18:43:35 +00:00
else if (arg1 == 3) return (objActor->int_vel().Z);
2021-12-29 21:56:21 +00:00
break;
case 30:
if (!spriteIsUnderwater(objActor) && !spriteIsUnderwater(objActor, true)) return false;
else if (PUSH) condPush(aCond, objActor->sector());
2021-12-29 21:56:21 +00:00
return true;
case 31:
if (arg1 == -1)
{
for (var = 0; var < kDmgMax; var++)
{
if (!nnExtIsImmune(objActor, arg1, 0))
return false;
}
return true;
}
return nnExtIsImmune(objActor, arg1, 0);
case 35: // hitscan: ceil?
case 36: // hitscan: floor?
case 37: // hitscan: wall?
case 38: // hitscan: sprite?
switch (arg1)
{
case 0: arg1 = CLIPMASK0 | CLIPMASK1; break;
case 1: arg1 = CLIPMASK0; break;
case 2: arg1 = CLIPMASK1; break;
}
if ((pPlayer = getPlayerById(objActor->spr.type)) != NULL)
var = HitScan(objActor, pPlayer->zWeapon, pPlayer->aim.dx, pPlayer->aim.dy, pPlayer->aim.dz, arg1, arg3 << 1);
else if (objActor->IsDudeActor())
var = HitScan(objActor, objActor->spr.pos.Z, bcos(objActor->int_ang()), bsin(objActor->int_ang()), (!objActor->hasX()) ? 0 : objActor->dudeSlope, arg1, arg3 << 1);
else if ((objActor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_FLOOR)
2021-12-29 21:56:21 +00:00
{
var3 = (objActor->spr.cstat & CSTAT_SPRITE_YFLIP) ? 0x10000 << 1 : -(0x10000 << 1);
var = HitScan(objActor, objActor->spr.pos.Z, Cos(objActor->int_ang()) >> 16, Sin(objActor->int_ang()) >> 16, var3, arg1, arg3 << 1);
2021-12-29 21:56:21 +00:00
}
else
{
var = HitScan(objActor, objActor->spr.pos.Z, bcos(objActor->int_ang()), bsin(objActor->int_ang()), 0, arg1, arg3 << 1);
2021-12-29 21:56:21 +00:00
}
if (var < 0)
return false;
2021-12-29 21:56:21 +00:00
switch (cond) {
case 35:
if (var != 1) return false;
else if (PUSH) condPush(aCond, gHitInfo.hitSector);
return true;
case 36:
if (var != 2) return false;
else if (PUSH) condPush(aCond, gHitInfo.hitSector);
return true;
case 37:
if (var != 0 && var != 4) return false;
else if (PUSH) condPush(aCond, gHitInfo.hitWall);
return true;
case 38:
if (var != 3) return false;
else if (PUSH) condPush(aCond, gHitInfo.actor());
return true;
2021-12-29 21:56:21 +00:00
}
break;
2021-12-29 21:56:21 +00:00
case 45: // this sprite is a target of some dude?
BloodStatIterator it(kStatDude);
while (auto iactor = it.Next())
{
if (objActor == iactor) continue;
if (iactor->IsDudeActor() && iactor->hasX())
{
if (iactor->xspr.health <= 0 || iactor->GetTarget() != objActor) continue;
else if (PUSH) condPush(aCond, iactor);
return true;
}
}
return false;
}
}
else if (objActor->hasX())
{
switch (cond)
{
default: break;
case 50: // compare hp (in %)
if (objActor->IsDudeActor()) var = (objActor->xspr.sysData2 > 0) ? ClipRange(objActor->xspr.sysData2 << 4, 1, 65535) : getDudeInfo(objActor->spr.type)->startHealth << 4;
else if (objActor->spr.type == kThingBloodChunks) return condCmp(0, arg1, arg2, cmpOp);
else if (objActor->spr.type >= kThingBase && objActor->spr.type < kThingMax) var = thingInfo[objActor->spr.type - kThingBase].startHealth << 4;
return condCmp((kPercFull * objActor->xspr.health) / ClipLow(var, 1), arg1, arg2, cmpOp);
case 55: // touching ceil of sector?
if (objActor->hit.ceilhit.type != kHitSector) return false;
else if (PUSH) condPush(aCond, objActor->hit.ceilhit.hitSector);
return true;
case 56: // touching floor of sector?
if (objActor->hit.florhit.type != kHitSector) return false;
else if (PUSH) condPush(aCond, objActor->hit.florhit.hitSector);
return true;
case 57: // touching walls of sector?
if (objActor->hit.hit.type != kHitWall) return false;
else if (PUSH) condPush(aCond, objActor->hit.hit.hitWall);
return true;
case 58: // touching another sprite?
{
DBloodActor* actorvar = nullptr;
// Caution: The hit pointers here may be stale, so be careful with them.
switch (arg3)
{
case 0:
case 1:
if (objActor->hit.florhit.type == kHitSprite) actorvar = objActor->hit.florhit.safeActor();
if (arg3 || var >= 0) break;
[[fallthrough]];
case 2:
if (objActor->hit.hit.type == kHitSprite) actorvar = objActor->hit.hit.safeActor();
if (arg3 || var >= 0) break;
[[fallthrough]];
case 3:
if (objActor->hit.ceilhit.type == kHitSprite) actorvar = objActor->hit.ceilhit.safeActor();
break;
}
if (actorvar == nullptr && objActor->insector())
2021-12-29 21:56:21 +00:00
{
// check if something is touching this sprite
BloodSectIterator it(objActor->sector());
2021-12-29 21:56:21 +00:00
while (auto iactor = it.Next())
{
if (iactor->spr.flags & kHitagRespawn) continue;
auto& hit = iactor->hit;
switch (arg3)
{
case 0:
case 1:
if (hit.ceilhit.type == kHitSprite && hit.ceilhit.safeActor() == objActor) actorvar = iactor;
if (arg3 || actorvar) break;
[[fallthrough]];
case 2:
if (hit.hit.type == kHitSprite && hit.hit.safeActor() == objActor) actorvar = iactor;
if (arg3 || actorvar) break;
[[fallthrough]];
case 3:
if (hit.florhit.type == kHitSprite && hit.florhit.safeActor() == objActor) actorvar = iactor;
break;
}
}
}
if (actorvar == nullptr) return false;
else if (PUSH) condPush(aCond, actorvar);
return true;
}
case 65: // compare burn time (in %)
var = (objActor->IsDudeActor()) ? 2400 : 1200;
if (!condCmp((kPercFull * objActor->xspr.burnTime) / var, arg1, arg2, cmpOp)) return false;
else if (PUSH && objActor->GetBurnSource()) condPush(aCond, objActor->GetBurnSource());
return true;
case 66: // any flares stuck in this sprite?
{
BloodStatIterator it(kStatFlare);
while (auto flareactor = it.Next())
{
if (!flareactor->hasX() || (flareactor->spr.flags & kHitagFree))
continue;
if (flareactor->GetTarget() != objActor) continue;
else if (PUSH) condPush(aCond, flareactor);
return true;
}
return false;
}
case 70:
return condCmp(getSpriteMassBySize(objActor), arg1, arg2, cmpOp); // mass of the sprite in a range?
}
}
else
{
switch (cond)
{
default: return false;
case 50:
case 65:
case 70:
return condCmp(0, arg1, arg2, cmpOp);
}
}
condError(aCond, "Unexpected condition id (%d)!", cond);
return false;
}
//---------------------------------------------------------------------------
//
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
// this updates index of object in all conditions
// only used when spawning players
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void condUpdateObjectIndex(DBloodActor* oldActor, DBloodActor* newActor)
{
2021-12-29 21:56:21 +00:00
// update index in tracking conditions first
for (int i = 0; i < gTrackingCondsCount; i++)
{
TRCONDITION* pCond = &gCondition[i];
for (unsigned k = 0; k < pCond->length; k++)
{
if (!pCond->obj[k].obj.isActor() || pCond->obj[k].obj.actor() != oldActor) continue;
pCond->obj[k].obj = EventObject(newActor);
break;
}
}
2021-12-29 21:56:21 +00:00
// puke...
auto oldSerial = EventObject(oldActor);
auto newSerial = EventObject(newActor);
2021-12-29 21:56:21 +00:00
// then update serials
BloodStatIterator it(kStatModernCondition);
while (auto iActor = it.Next())
{
if (iActor->condition[0] == oldSerial) iActor->condition[0] = newSerial;
if (iActor->condition[1] == oldSerial) iActor->condition[1] = newSerial;
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
2021-12-29 21:56:21 +00:00
}
}
2021-08-28 16:26:24 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool modernTypeSetSpriteState(DBloodActor* actor, int nState, DBloodActor* initiator)
2021-08-28 16:26:24 +00:00
{
2021-12-29 21:56:21 +00:00
if ((actor->xspr.busy & 0xffff) == 0 && actor->xspr.state == nState)
return false;
actor->xspr.busy = IntToFixed(nState);
actor->xspr.state = nState;
evKillActor(actor, initiator);
2021-12-29 21:56:21 +00:00
if (actor->xspr.restState != nState && actor->xspr.waitTime > 0)
evPostActor(actor, (actor->xspr.waitTime * 120) / 10, actor->xspr.restState ? kCmdOn : kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
if (actor->xspr.txID != 0 && ((actor->xspr.triggerOn && actor->xspr.state) || (actor->xspr.triggerOff && !actor->xspr.state)))
modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
return true;
}
2021-08-28 16:26:24 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void modernTypeSendCommand(DBloodActor* actor, int destChannel, COMMAND_ID command, DBloodActor* initiator)
2021-08-28 16:26:24 +00:00
{
2021-12-29 21:56:21 +00:00
switch (command)
{
case kCmdLink:
evSendActor(actor, destChannel, kCmdModernUse, initiator); // just send command to change properties
2021-12-29 21:56:21 +00:00
return;
case kCmdUnlock:
evSendActor(actor, destChannel, command, initiator); // send normal command first
evSendActor(actor, destChannel, kCmdModernUse, initiator); // then send command to change properties
2021-12-29 21:56:21 +00:00
return;
default:
evSendActor(actor, destChannel, kCmdModernUse, initiator); // send first command to change properties
evSendActor(actor, destChannel, command, initiator); // then send normal command
2021-12-29 21:56:21 +00:00
return;
}
}
2021-10-03 10:29:05 +00:00
//---------------------------------------------------------------------------
//
// this function used by various new modern types.
2021-10-03 10:29:05 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void modernTypeTrigger(int destObjType, sectortype* destSect, walltype* destWall, DBloodActor* destactor, EVENT& event)
{
if (!event.isActor()) return;
auto sourceactor = event.getActor();
if (!sourceactor || !sourceactor->hasX()) return;
switch (destObjType) {
case OBJ_SECTOR:
if (!destSect || !destSect->hasX()) return;
break;
case OBJ_WALL:
if (!destWall || !destWall->hasX()) return;
break;
case OBJ_SPRITE:
{
if (!destactor) return;
if (destactor->spr.flags & kHitagFree) return;
// allow redirect events received from some modern types.
// example: it allows to spawn FX effect if event was received from kModernEffectGen
// on many TX channels instead of just one.
DBloodActor* initiator = event.initiator;
2021-12-29 21:56:21 +00:00
switch (destactor->spr.type)
{
case kModernRandomTX:
case kModernSequentialTX:
if (destactor->xspr.command != kCmdLink || destactor->xspr.locked) break; // no redirect mode detected
switch (destactor->spr.type)
{
case kModernRandomTX:
useRandomTx(destactor, (COMMAND_ID)sourceactor->xspr.command, false, initiator); // set random TX id
2021-12-29 21:56:21 +00:00
break;
case kModernSequentialTX:
if (destactor->spr.flags & kModernTypeFlag1)
{
seqTxSendCmdAll(destactor, sourceactor, (COMMAND_ID)sourceactor->xspr.command, true, initiator);
2021-12-29 21:56:21 +00:00
return;
}
useSequentialTx(destactor, (COMMAND_ID)sourceactor->xspr.command, false, initiator); // set next TX id
2021-12-29 21:56:21 +00:00
break;
}
if (destactor->xspr.txID <= 0 || destactor->xspr.txID >= kChannelUserMax) return;
modernTypeSendCommand(sourceactor, destactor->xspr.txID, (COMMAND_ID)sourceactor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
return;
}
break;
}
default:
return;
}
switch (sourceactor->spr.type)
{
// allows teleport any sprite from any location to the source destination
case kMarkerWarpDest:
if (destObjType != OBJ_SPRITE) break;
useTeleportTarget(sourceactor, destactor);
break;
// changes slope of sprite or sector
case kModernSlopeChanger:
switch (destObjType)
{
case OBJ_SPRITE:
case OBJ_SECTOR:
useSlopeChanger(sourceactor, destObjType, destSect, destactor);
break;
}
break;
case kModernSpriteDamager:
// damages xsprite via TX ID or xsprites in a sector
switch (destObjType)
{
case OBJ_SPRITE:
case OBJ_SECTOR:
useSpriteDamager(sourceactor, destObjType, destSect, destactor);
break;
}
break;
// can spawn any effect passed in data2 on it's or txID sprite
case kModernEffectSpawner:
if (destObjType != OBJ_SPRITE) break;
useEffectGen(sourceactor, destactor);
break;
// takes data2 as SEQ ID and spawns it on it's or TX ID object
case kModernSeqSpawner:
useSeqSpawnerGen(sourceactor, destObjType, destSect, destWall, destactor);
break;
// creates wind on TX ID sector
case kModernWindGenerator:
if (destObjType != OBJ_SECTOR || sourceactor->xspr.data2 < 0) break;
useSectorWindGen(sourceactor, destSect);
break;
// size and pan changer of sprite/wall/sector via TX ID
case kModernObjSizeChanger:
useObjResizer(sourceactor, destObjType, destSect, destWall, destactor);
break;
// iterate data filed value of destination object
case kModernObjDataAccumulator:
useIncDecGen(sourceactor, destObjType, destSect, destWall, destactor);
break;
// change data field value of destination object
case kModernObjDataChanger:
useDataChanger(sourceactor, destObjType, destSect, destWall, destactor);
break;
// change sector lighting dynamically
case kModernSectorFXChanger:
if (destObjType != OBJ_SECTOR) break;
useSectorLightChanger(sourceactor, destSect);
break;
// change target of dudes and make it fight
case kModernDudeTargetChanger:
if (destObjType != OBJ_SPRITE) break;
useTargetChanger(sourceactor, destactor);
break;
// change picture and palette of TX ID object
case kModernObjPicnumChanger:
usePictureChanger(sourceactor, destObjType, destSect, destWall, destactor);
break;
// change various properties
case kModernObjPropertiesChanger:
usePropertiesChanger(sourceactor, destObjType, destSect, destWall, destactor);
break;
// updated vanilla sound gen that now allows to play sounds on TX ID sprites
// change velocity of the sprite
case kModernVelocityChanger:
switch (destObjType) {
case OBJ_SPRITE:
case OBJ_SECTOR:
useVelocityChanger(sourceactor, destSect, event.initiator, destactor);
break;
}
break;
2021-12-29 21:56:21 +00:00
case kGenModernSound:
if (destObjType != OBJ_SPRITE) break;
useSoundGen(sourceactor, destactor);
break;
// updated ecto skull gen that allows to fire missile from TX ID sprites
case kGenModernMissileUniversal:
if (destObjType != OBJ_SPRITE) break;
useUniMissileGen(sourceactor, destactor);
break;
// spawn enemies on TX ID sprites
case kMarkerDudeSpawn:
if (destObjType != OBJ_SPRITE) break;
useDudeSpawn(sourceactor, destactor);
break;
// spawn custom dude on TX ID sprites
case kModernCustomDudeSpawn:
if (destObjType != OBJ_SPRITE) break;
useCustomDudeSpawn(sourceactor, destactor);
break;
}
}
2021-10-03 10:31:19 +00:00
//---------------------------------------------------------------------------
//
// the following functions required for kModernDudeTargetChanger
2021-10-03 10:31:19 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
DBloodActor* aiFightGetTargetInRange(DBloodActor* actor, int minDist, int maxDist, int data, int teamMode)
2021-10-03 10:31:19 +00:00
{
2021-12-29 21:56:21 +00:00
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
BloodStatIterator it(kStatDude);
while (auto targactor = it.Next())
{
if (!aiFightDudeCanSeeTarget(actor, pDudeInfo, targactor)) continue;
2021-10-03 10:34:47 +00:00
2021-12-29 21:56:21 +00:00
int dist = aiFightGetTargetDist(actor, pDudeInfo, targactor);
if (dist < minDist || dist > maxDist) continue;
else if (actor->GetTarget() == targactor) return targactor;
else if (!targactor->IsDudeActor() || targactor == actor || targactor->IsPlayerActor()) continue;
else if (IsBurningDude(targactor) || !IsKillableDude(targactor) || targactor->GetOwner() == actor) continue;
else if ((teamMode == 1 && actor->xspr.rxID == targactor->xspr.rxID) || aiFightMatesHaveSameTarget(actor, targactor, 1)) continue;
else if (data == 666 || targactor->xspr.data1 == data)
{
if (actor->GetTarget())
{
int fineDist1 = aiFightGetFineTargetDist(actor, actor->GetTarget());
int fineDist2 = aiFightGetFineTargetDist(actor, targactor);
if (fineDist1 < fineDist2)
continue;
}
return targactor;
}
}
return nullptr;
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-03 10:34:47 +00:00
DBloodActor* aiFightTargetIsPlayer(DBloodActor* actor)
2021-10-03 10:34:47 +00:00
{
2021-12-29 21:56:21 +00:00
auto targ = actor->GetTarget();
if (targ && targ->IsPlayerActor()) return targ;
return nullptr;
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-03 10:34:47 +00:00
DBloodActor* aiFightGetMateTargets(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
int rx = actor->xspr.rxID;
2021-12-29 21:56:21 +00:00
for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++)
{
if (rxBucket[i].isActor())
{
auto mate = rxBucket[i].actor();
if (!mate || !mate->hasX() || mate == actor || !mate->IsDudeActor())
continue;
2021-12-29 21:56:21 +00:00
if (mate->GetTarget())
{
if (!mate->GetTarget()->IsPlayerActor())
return mate->GetTarget();
}
}
}
return nullptr;
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool aiFightMatesHaveSameTarget(DBloodActor* leaderactor, DBloodActor* targetactor, int allow)
2021-10-03 10:34:47 +00:00
{
2021-12-29 21:56:21 +00:00
int rx = leaderactor->xspr.rxID;
2021-12-29 21:56:21 +00:00
for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto mate = rxBucket[i].actor();
2021-12-29 21:56:21 +00:00
if (!mate || !mate->hasX() || mate == leaderactor || !mate->IsDudeActor())
continue;
2021-12-29 21:56:21 +00:00
if (mate->GetTarget() == targetactor && allow-- <= 0)
return true;
}
return false;
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool aiFightDudeCanSeeTarget(DBloodActor* dudeactor, DUDEINFO* pDudeInfo, DBloodActor* targetactor)
2021-10-03 10:34:47 +00:00
{
auto dv = targetactor->spr.pos.XY() - dudeactor->spr.pos.XY();
2021-12-29 21:56:21 +00:00
// check target
if (dv.Length() < pDudeInfo->SeeDist())
2021-12-29 21:56:21 +00:00
{
2022-08-22 16:37:46 +00:00
double height = (pDudeInfo->eyeHeight * dudeactor->spr.yrepeat) * REPEAT_SCALE;
2021-12-29 21:56:21 +00:00
// is there a line of sight to the target?
2022-08-22 16:37:46 +00:00
if (cansee(dudeactor->spr.pos, dudeactor->sector(), targetactor->spr.pos.plusZ(-height), targetactor->sector()))
2021-12-29 21:56:21 +00:00
{
/*int nAngle = getangle(dx, dy);
int losAngle = ((1024 + nAngle - dudeactor->spr.angle) & 2047) - 1024;
2021-12-29 21:56:21 +00:00
// is the target visible?
if (abs(losAngle) < 2048) // 360 deg periphery here*/
return true;
}
2021-12-29 21:56:21 +00:00
}
2021-12-29 21:56:21 +00:00
return false;
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
// this function required if monsters in genIdle ai state. It wakes up monsters
// when kModernDudeTargetChanger goes to off state, so they won't ignore the world.
2021-10-03 10:34:47 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void aiFightActivateDudes(int rx)
2021-10-03 10:34:47 +00:00
{
2021-12-29 21:56:21 +00:00
for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto dudeactor = rxBucket[i].actor();
2021-12-29 21:56:21 +00:00
if (!dudeactor || !dudeactor->hasX() || !dudeactor->IsDudeActor() || dudeactor->xspr.aiState->stateType != kAiStateGenIdle) continue;
aiInitSprite(dudeactor);
}
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
// this function sets target to -1 for all dudes that hunting for nSprite
2021-10-03 10:34:47 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void aiFightFreeTargets(DBloodActor* actor)
2021-10-03 10:34:47 +00:00
{
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatDude);
while (auto targetactor = it.Next())
{
if (!targetactor->IsDudeActor() || !targetactor->hasX()) continue;
else if (targetactor->GetTarget() == actor)
2022-08-22 16:41:41 +00:00
aiSetTarget(targetactor, targetactor->spr.pos);
2021-12-29 21:56:21 +00:00
}
}
2021-10-03 10:34:47 +00:00
//---------------------------------------------------------------------------
//
// this function sets target to -1 for all targets that hunting for dudes affected by selected kModernDudeTargetChanger
2021-10-03 10:34:47 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void aiFightFreeAllTargets(DBloodActor* sourceactor)
2021-10-03 10:34:47 +00:00
{
2021-12-29 21:56:21 +00:00
auto txID = sourceactor->xspr.txID;
if (txID <= 0) return;
for (int i = bucketHead[txID]; i < bucketHead[txID + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto actor = rxBucket[i].actor();
2021-12-29 21:56:21 +00:00
if (actor && actor->hasX())
aiFightFreeTargets(actor);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool aiFightDudeIsAffected(DBloodActor* dudeactor)
{
2021-12-29 21:56:21 +00:00
if (dudeactor->xspr.rxID <= 0 || dudeactor->xspr.locked == 1) return false;
BloodStatIterator it(kStatModernDudeTargetChanger);
while (auto actor = it.Next())
{
if (!actor->hasX()) continue;
if (actor->xspr.txID <= 0 || actor->xspr.state != 1) continue;
for (int i = bucketHead[actor->xspr.txID]; i < bucketHead[actor->xspr.txID + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto rxactor = rxBucket[i].actor();
2021-12-29 21:56:21 +00:00
if (!rxactor || !rxactor->hasX() || !rxactor->IsDudeActor()) continue;
else if (rxactor == dudeactor) return true;
}
}
return false;
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
// this function tells if there any dude found for kModernDudeTargetChanger
2021-09-03 19:23:22 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool aiFightGetDudesForBattle(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
auto txID = actor->xspr.txID;
for (int i = bucketHead[txID]; i < bucketHead[txID + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto rxactor = rxBucket[i].actor();
if (!rxactor || !rxactor->hasX() || !rxactor->IsDudeActor()) continue;
if (rxactor->xspr.health > 0) return true;
}
2021-12-29 21:56:21 +00:00
// check redirected TX buckets
int rx = -1;
DBloodActor* pXRedir = nullptr;
while ((pXRedir = evrListRedirectors(OBJ_SPRITE, nullptr, nullptr, actor, pXRedir, &rx)) != nullptr)
{
for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto rxactor = rxBucket[i].actor();
if (!rxactor || !rxactor->hasX() || !rxactor->IsDudeActor()) continue;
if (rxactor->xspr.health > 0) return true;
}
}
return false;
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void aiFightAlarmDudesInSight(DBloodActor* actor, int max)
2021-10-03 12:17:24 +00:00
{
2021-12-29 21:56:21 +00:00
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
2021-10-03 12:17:24 +00:00
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatDude);
while (auto dudeactor = it.Next())
{
if (dudeactor == actor || !dudeactor->IsDudeActor() || !dudeactor->hasX())
continue;
2021-10-03 12:17:24 +00:00
2021-12-29 21:56:21 +00:00
if (aiFightDudeCanSeeTarget(actor, pDudeInfo, dudeactor))
{
if (dudeactor->GetTarget() != nullptr || dudeactor->xspr.rxID > 0)
continue;
2022-08-22 16:41:41 +00:00
aiSetTarget(dudeactor, dudeactor->spr.pos);
2021-12-29 21:56:21 +00:00
aiActivateDude(dudeactor);
if (max-- < 1)
break;
}
}
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool aiFightUnitCanFly(DBloodActor* dude)
2021-10-03 12:17:24 +00:00
{
2021-12-29 21:56:21 +00:00
return (dude->IsDudeActor() && gDudeInfoExtra[dude->spr.type - kDudeBase].flying);
}
2021-10-03 12:17:24 +00:00
bool aiFightIsMeleeUnit(DBloodActor* dude)
{
2021-12-29 21:56:21 +00:00
if (dude->spr.type == kDudeModernCustom) return (dude->hasX() && dudeIsMelee(dude));
else return (dude->IsDudeActor() && gDudeInfoExtra[dude->spr.type - kDudeBase].melee);
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
int aiFightGetTargetDist(DBloodActor* actor, DUDEINFO* pDudeInfo, DBloodActor* target)
2021-10-03 12:11:07 +00:00
{
2022-08-23 20:30:40 +00:00
auto dvec = target->spr.pos.XY() - actor->spr.pos.XY();
int dist = approxDist(dvec);
2021-12-29 21:56:21 +00:00
if (dist <= pDudeInfo->meleeDist) return 0;
if (dist >= pDudeInfo->seeDist) return 13;
if (dist <= pDudeInfo->seeDist / 12) return 1;
if (dist <= pDudeInfo->seeDist / 11) return 2;
if (dist <= pDudeInfo->seeDist / 10) return 3;
if (dist <= pDudeInfo->seeDist / 9) return 4;
if (dist <= pDudeInfo->seeDist / 8) return 5;
if (dist <= pDudeInfo->seeDist / 7) return 6;
if (dist <= pDudeInfo->seeDist / 6) return 7;
if (dist <= pDudeInfo->seeDist / 5) return 8;
if (dist <= pDudeInfo->seeDist / 4) return 9;
if (dist <= pDudeInfo->seeDist / 3) return 10;
if (dist <= pDudeInfo->seeDist / 2) return 11;
return 12;
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-03 12:11:07 +00:00
int aiFightGetFineTargetDist(DBloodActor* actor, DBloodActor* target)
{
2022-08-23 20:30:40 +00:00
auto dvec = target->spr.pos.XY() - actor->spr.pos.XY();
return approxDist(dvec);
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-11-24 00:18:05 +00:00
void sectorKillSounds(sectortype* pSector)
2021-10-03 12:18:47 +00:00
{
2021-12-29 21:56:21 +00:00
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.type != kSoundSector) continue;
sfxKill3DSound(actor);
}
}
2020-12-06 20:56:09 +00:00
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
void sectorPauseMotion(sectortype* pSector, DBloodActor* initiator)
2021-09-03 19:23:22 +00:00
{
2021-12-29 21:56:21 +00:00
if (!pSector->hasX()) return;
XSECTOR* pXSector = &pSector->xs();
pXSector->unused1 = 1;
2020-12-06 20:56:09 +00:00
evKillSector(pSector, initiator);
2021-12-29 21:56:21 +00:00
sectorKillSounds(pSector);
if ((pXSector->busy == 0 && !pXSector->state) || (pXSector->busy == 65536 && pXSector->state))
SectorEndSound(pSector, pXSector->state);
2020-12-06 20:56:09 +00:00
}
2021-10-03 12:11:07 +00:00
//---------------------------------------------------------------------------
//
//
//
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void sectorContinueMotion(sectortype* pSector, EVENT event)
{
if (!pSector->hasX()) return;
XSECTOR* pXSector = &pSector->xs();
pXSector->unused1 = 0;
int busyTimeA = pXSector->busyTimeA;
int waitTimeA = pXSector->waitTimeA;
int busyTimeB = pXSector->busyTimeB;
int waitTimeB = pXSector->waitTimeB;
if (pSector->type == kSectorPath)
{
if (!pXSector->marker0) return;
busyTimeA = busyTimeB = pXSector->marker0->xspr.busyTime;
waitTimeA = waitTimeB = pXSector->marker0->xspr.waitTime;
}
if (!pXSector->interruptable && event.cmd != kCmdSectorMotionContinue
&& ((!pXSector->state && pXSector->busy) || (pXSector->state && pXSector->busy != 65536)))
{
event.cmd = kCmdSectorMotionContinue;
}
else if (event.cmd == kCmdToggle)
{
event.cmd = (pXSector->state) ? kCmdOn : kCmdOff;
}
int nDelta = 1;
switch (event.cmd)
{
case kCmdOff:
if (pXSector->busy == 0)
{
if (pXSector->reTriggerB && waitTimeB) evPostSector(pSector, (waitTimeB * 120) / 10, kCmdOff, event.initiator);
2021-12-29 21:56:21 +00:00
return;
}
pXSector->state = 1;
nDelta = 65536 / ClipLow((busyTimeB * 120) / 10, 1);
break;
case kCmdOn:
if (pXSector->busy == 65536)
{
if (pXSector->reTriggerA && waitTimeA) evPostSector(pSector, (waitTimeA * 120) / 10, kCmdOn, event.initiator);
2021-12-29 21:56:21 +00:00
return;
}
pXSector->state = 0;
nDelta = 65536 / ClipLow((busyTimeA * 120) / 10, 1);
break;
case kCmdSectorMotionContinue:
nDelta = 65536 / ClipLow((((pXSector->state) ? busyTimeB : busyTimeA) * 120) / 10, 1);
break;
}
//bool crush = pXSector->Crush;
int busyFunc = BUSYID_0;
switch (pSector->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:
I_Error("Unsupported sector type %d", pSector->type);
break;
}
SectorStartSound(pSector, pXSector->state);
nDelta = (pXSector->state) ? -nDelta : nDelta;
BUSY b = { pSector, nDelta, (int)pXSector->busy, (BUSYID)busyFunc };
gBusy.Push(b);
2020-12-06 20:56:09 +00:00
}
2021-10-03 12:11:07 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool modernTypeOperateSector(sectortype* pSector, const EVENT& event)
{
auto pXSector = &pSector->xs();
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(pSector, 0, event.initiator.Get());
2021-12-29 21:56:21 +00:00
evPostSector(pSector, 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(pSector, event);
return true;
}
// pause motion of the sector
}
else if (event.cmd == kCmdSectorMotionPause)
{
sectorPauseMotion(pSector, event.initiator.Get());
2021-12-29 21:56:21 +00:00
return true;
}
return false;
2020-12-06 20:56:09 +00:00
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
2021-10-03 12:11:07 +00:00
//
//
//
//---------------------------------------------------------------------------
void useCustomDudeSpawn(DBloodActor* pSource, DBloodActor* pActor)
{
2022-09-09 16:32:42 +00:00
genDudeSpawn(pSource, pActor, pActor->native_clipdist() << 1);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
void useDudeSpawn(DBloodActor* pSource, DBloodActor* pActor)
2021-10-03 12:18:47 +00:00
{
2022-09-09 16:32:42 +00:00
if (randomSpawnDude(pSource, pActor, pActor->native_clipdist() << 1, 0) == nullptr)
nnExtSpawnDude(pSource, pActor, pActor->xspr.data1, pActor->native_clipdist() << 1, 0);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-03 19:23:22 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool modernTypeOperateSprite(DBloodActor* actor, EVENT& event)
{
DBloodActor* initiator = event.initiator;
2021-12-29 21:56:21 +00:00
if (event.cmd >= kCmdLock && event.cmd <= kCmdToggleLock)
{
switch (event.cmd)
{
case kCmdLock:
actor->xspr.locked = 1;
break;
case kCmdUnlock:
actor->xspr.locked = 0;
break;
case kCmdToggleLock:
actor->xspr.locked = actor->xspr.locked ^ 1;
break;
}
switch (actor->spr.type)
{
case kModernCondition:
case kModernConditionFalse:
actor->xspr.restState = 0;
if (actor->xspr.busyTime <= 0) break;
else if (!actor->xspr.locked) actor->xspr.busy = 0;
break;
}
return true;
}
else if (event.cmd == kCmdDudeFlagsSet)
{
if (!event.isActor())
{
viewSetSystemMessage("Only sprites can use command #%d", event.cmd);
return true;
}
else
{
auto pEvActor = event.getActor();
if (pEvActor && pEvActor->hasX())
{
// copy dude flags from the source to destination sprite
aiPatrolFlagsMgr(pEvActor, actor, true, false);
}
}
}
if (actor->spr.statnum == kStatDude && actor->IsDudeActor())
{
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.state) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
if (!actor->xspr.state) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
if (!actor->IsDudeActor() || actor->IsPlayerActor() || actor->xspr.health <= 0) break;
else if (actor->xspr.aiState->stateType >= kAiStatePatrolBase && actor->xspr.aiState->stateType < kAiStatePatrolMax)
break;
switch (actor->xspr.aiState->stateType)
{
case kAiStateIdle:
case kAiStateGenIdle:
aiActivateDude(actor);
break;
}
break;
case kCmdDudeFlagsSet:
if (event.isActor())
{
auto pEvActor = event.getActor();
if (!pEvActor || !pEvActor->hasX()) break;
else aiPatrolFlagsMgr(pEvActor, actor, false, true); // initialize patrol dude with possible new flags
}
break;
default:
if (!actor->xspr.state) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
}
switch (actor->spr.type)
{
default:
return false; // no modern type found to work with, go normal OperateSprite();
case kThingBloodBits:
case kThingBloodChunks:
// dude to thing morphing causing a lot of problems since it continues receiving commands after dude is dead.
// this leads to weird stuff like exploding with gargoyle gib or corpse disappearing immediately.
// let's allow only specific commands here to avoid this.
if (actor->spr.inittype < kDudeBase || actor->spr.inittype >= kDudeMax) return false;
else if (event.cmd != kCmdToggle && event.cmd != kCmdOff && event.cmd != kCmdSpriteImpact) return true;
DudeToGibCallback1(0, actor); // set proper gib type just in case DATAs was changed from the outside.
return false;
case kModernCondition:
case kModernConditionFalse:
if (!actor->xspr.isTriggered) useCondition(actor, event);
return true;
// add spawn random dude feature - works only if at least 2 data fields are not empty.
case kMarkerDudeSpawn:
if (!gGameOptions.nMonsterSettings) return true;
else if (!(actor->spr.flags & kModernTypeFlag4)) useDudeSpawn(actor, actor);
else if (actor->xspr.txID) evSendActor(actor, actor->xspr.txID, kCmdModernUse, initiator);
2021-12-29 21:56:21 +00:00
return true;
case kModernCustomDudeSpawn:
if (!gGameOptions.nMonsterSettings) return true;
else if (!(actor->spr.flags & kModernTypeFlag4)) useCustomDudeSpawn(actor, actor);
else if (actor->xspr.txID) evSendActor(actor, actor->xspr.txID, kCmdModernUse, initiator);
2021-12-29 21:56:21 +00:00
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 (actor->xspr.command == kCmdLink) return true; // work as event redirector
switch (actor->spr.type)
{
case kModernRandomTX:
useRandomTx(actor, (COMMAND_ID)actor->xspr.command, true, initiator);
2021-12-29 21:56:21 +00:00
break;
case kModernSequentialTX:
if (!(actor->spr.flags & kModernTypeFlag1)) useSequentialTx(actor, (COMMAND_ID)actor->xspr.command, true, initiator);
else seqTxSendCmdAll(actor, actor, (COMMAND_ID)actor->xspr.command, false, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
case kModernSpriteDamager:
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID > 0) modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
else if (actor->xspr.data1 == 0 && actor->insector()) useSpriteDamager(actor, OBJ_SECTOR, actor->sector(), nullptr);
2021-12-29 21:56:21 +00:00
else if (actor->xspr.data1 >= 666 && actor->xspr.data1 < 669) useSpriteDamager(actor, -1, nullptr, nullptr);
else
{
PLAYER* pPlayer = getPlayerById(actor->xspr.data1);
if (pPlayer != NULL)
useSpriteDamager(actor, OBJ_SPRITE, 0, pPlayer->actor);
}
if (actor->xspr.busyTime > 0)
evPostActor(actor, actor->xspr.busyTime, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
case kMarkerWarpDest:
if (actor->xspr.txID <= 0)
{
2021-12-29 21:56:21 +00:00
PLAYER* pPlayer = getPlayerById(actor->xspr.data1);
if (pPlayer != NULL && SetSpriteState(actor, actor->xspr.state ^ 1, initiator) == 1)
2021-12-29 21:56:21 +00:00
useTeleportTarget(actor, pPlayer->actor);
return true;
}
[[fallthrough]];
case kModernObjPropertiesChanger:
if (actor->xspr.txID <= 0)
{
if (SetSpriteState(actor, actor->xspr.state ^ 1, initiator) == 1)
2021-12-29 21:56:21 +00:00
usePropertiesChanger(actor, -1, nullptr, nullptr, nullptr);
return true;
}
[[fallthrough]];
case kModernSlopeChanger:
case kModernObjSizeChanger:
case kModernObjPicnumChanger:
case kModernSectorFXChanger:
case kModernObjDataChanger:
modernTypeSetSpriteState(actor, actor->xspr.state ^ 1, initiator);
2021-12-29 21:56:21 +00:00
return true;
case kModernSeqSpawner:
case kModernEffectSpawner:
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
if (actor->spr.type == kModernSeqSpawner) seqSpawnerOffSameTx(actor);
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID > 0) modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
else if (actor->spr.type == kModernSeqSpawner) useSeqSpawnerGen(actor, OBJ_SPRITE, nullptr, nullptr, actor);
else useEffectGen(actor, nullptr);
if (actor->xspr.busyTime > 0)
evPostActor(actor, ClipLow((int(actor->xspr.busyTime) + Random2(actor->xspr.data1)) * 120 / 10, 0), kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
case kModernWindGenerator:
switch (event.cmd)
{
case kCmdOff:
windGenStopWindOnSectors(actor);
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID > 0) modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
else useSectorWindGen(actor, nullptr);
if (actor->xspr.busyTime > 0) evPostActor(actor, actor->xspr.busyTime, kCmdRepeat, initiator);
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
break;
}
return true;
2021-12-29 21:56:21 +00:00
case kModernVelocityChanger:
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID > 0) modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
else useVelocityChanger(actor, actor->sector(), initiator, nullptr);
if (actor->xspr.busyTime > 0) evPostActor(actor, actor->xspr.busyTime, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
2021-12-29 21:56:21 +00:00
case kModernDudeTargetChanger:
// this one is required if data4 of generator was dynamically changed
// it turns monsters in normal idle state instead of genIdle, so they not ignore the world.
if (actor->xspr.dropMsg == 3 && 3 != actor->xspr.data4)
aiFightActivateDudes(actor->xspr.txID);
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.data4 == 3) aiFightActivateDudes(actor->xspr.txID);
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID <= 0 || !aiFightGetDudesForBattle(actor))
{
aiFightFreeAllTargets(actor);
evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
else
{
modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
}
if (actor->xspr.busyTime > 0) evPostActor(actor, actor->xspr.busyTime, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
actor->xspr.dropMsg = uint8_t(actor->xspr.data4);
return true;
case kGenTrigger:
if (!(actor->spr.flags & kModernTypeFlag1)) return false; // work as vanilla generator
switch (event.cmd) { // work as fast generator
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID)
evSendActor(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
if (actor->xspr.busyTime > 0) evPostActor(actor, actor->xspr.busyTime, kCmdRepeat, initiator);
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
break;
}
return true;
2021-12-29 21:56:21 +00:00
case kModernObjDataAccumulator:
switch (event.cmd) {
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
// force OFF after *all* TX objects reach the goal value
if (actor->spr.flags == kModernTypeFlag0 && incDecGoalValueIsReached(actor))
{
evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
if (actor->xspr.busyTime > 0) evPostActor(actor, actor->xspr.busyTime, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
case kModernRandom:
case kModernRandom2:
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
useRandomItemGen(actor);
if (actor->xspr.busyTime > 0)
evPostActor(actor, (120 * actor->xspr.busyTime) / 10, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
case kModernThingTNTProx:
if (actor->spr.statnum != kStatRespawn)
{
switch (event.cmd)
{
case kCmdSpriteProximity:
if (actor->xspr.state) break;
sfxPlay3DSound(actor, 452, 0, 0);
evPostActor(actor, 30, kCmdOff, nullptr);
2021-12-29 21:56:21 +00:00
actor->xspr.state = 1;
[[fallthrough]];
case kCmdOn:
sfxPlay3DSound(actor, 451, 0, 0);
actor->xspr.Proximity = 1;
break;
default:
actExplodeSprite(actor);
break;
}
}
return true;
case kModernThingEnemyLifeLeech:
dudeLeechOperate(actor, event);
return true;
case kModernPlayerControl:
{ // WIP
PLAYER* pPlayer = NULL;
int cmd = (event.cmd >= kCmdNumberic) ? event.cmd : actor->xspr.command;
int playerID;
if ((actor->xspr.txID == kChannelEventCauser || actor->xspr.data1 == 0) && initiator && initiator->IsPlayerActor())
playerID = initiator->spr.type;
else
playerID = actor->xspr.data1;
if ((pPlayer = getPlayerById(playerID)) == NULL
|| ((cmd < 67 || cmd > 68) && !modernTypeSetSpriteState(actor, actor->xspr.state ^ 1, initiator)))
2021-12-29 21:56:21 +00:00
return true;
TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
/// !!! COMMANDS OF THE CURRENT SPRITE, NOT OF THE EVENT !!! ///
if ((cmd -= kCmdNumberic) < 0) return true;
else if (pPlayer->actor->xspr.health <= 0)
{
switch (cmd) {
case 36:
actHealDude(pPlayer->actor, ((actor->xspr.data2 > 0) ? ClipHigh(actor->xspr.data2, 200) : getDudeInfo(pPlayer->actor->spr.type)->startHealth), 200);
pPlayer->curWeapon = kWeapPitchFork;
break;
}
return true;
}
switch (cmd)
{
case 0: // 64 (player life form)
if (actor->xspr.data2 < kModeHuman || actor->xspr.data2 > kModeHumanGrown) break;
else trPlayerCtrlSetRace(actor->xspr.data2, pPlayer);
break;
case 1: // 65 (move speed and jump height)
// player movement speed (for all races and postures)
if (valueIsBetween(actor->xspr.data2, -1, 32767))
trPlayerCtrlSetMoveSpeed(actor->xspr.data2, pPlayer);
// player jump height (for all races and stand posture only)
if (valueIsBetween(actor->xspr.data3, -1, 32767))
trPlayerCtrlSetJumpHeight(actor->xspr.data3, pPlayer);
break;
case 2: // 66 (player screen effects)
if (actor->xspr.data3 < 0) break;
else trPlayerCtrlSetScreenEffect(actor->xspr.data2, actor->xspr.data3, pPlayer);
break;
case 3: // 67 (start playing qav scene)
trPlayerCtrlStartScene(actor, pPlayer, (actor->xspr.data4 == 1) ? true : false);
break;
case 4: // 68 (stop playing qav scene)
if (actor->xspr.data2 > 0 && actor->xspr.data2 != pPlayer->sceneQav) break;
else trPlayerCtrlStopScene(pPlayer);
break;
case 5: // 69 (set player look angle, TO-DO: if tx > 0, take a look on TX ID sprite)
//data4 is reserved
if (actor->xspr.data4 != 0) break;
else if (valueIsBetween(actor->xspr.data2, -128, 128))
trPlayerCtrlSetLookAngle(actor->xspr.data2, pPlayer);
break;
case 6: // 70 (erase player stuff...)
if (actor->xspr.data2 < 0) break;
else trPlayerCtrlEraseStuff(actor->xspr.data2, pPlayer);
break;
case 7: // 71 (give something to player...)
if (actor->xspr.data2 <= 0) break;
else trPlayerCtrlGiveStuff(actor->xspr.data2, actor->xspr.data3, actor->xspr.data4, pPlayer, pCtrl);
break;
case 8: // 72 (use inventory item)
if (actor->xspr.data2 < 1 || actor->xspr.data2 > 5) break;
else trPlayerCtrlUsePackItem(actor->xspr.data2, actor->xspr.data3, actor->xspr.data4, pPlayer, event.cmd);
break;
case 9: // 73 (set player's sprite angle, TO-DO: if tx > 0, take a look on TX ID sprite)
//data4 is reserved
if (actor->xspr.data4 != 0) break;
else if (actor->spr.flags & kModernTypeFlag1)
{
pPlayer->angle.settarget(actor->spr.angle);
pPlayer->angle.lockinput();
2021-12-29 21:56:21 +00:00
}
else if (valueIsBetween(actor->xspr.data2, -kAng360, kAng360))
{
pPlayer->angle.settarget(DAngle::fromBuild(actor->xspr.data2));
pPlayer->angle.lockinput();
2021-12-29 21:56:21 +00:00
}
break;
case 10: // 74 (de)activate powerup
if (actor->xspr.data2 <= 0 || actor->xspr.data2 > (kMaxAllowedPowerup - (kMinAllowedPowerup << 1) + 1)) break;
trPlayerCtrlUsePowerup(actor, pPlayer, event.cmd);
break;
// case 11: // 75 (print the book)
// data2: RFF TXT id
// data3: background tile
// data4: font base tile
// pal: font / background palette
// hitag:
// d1: 0: print whole text at a time, 1: print line by line, 2: word by word, 3: letter by letter
// d2: 1: force pause the game (sp only)
// d3: 1: inherit palette for font, 2: inherit palette for background, 3: both
// busyTime: speed of word/letter/line printing
// waitTime: if TX ID > 0 and TX ID object is book reader, trigger it?
//break;
}
}
return true;
case kGenModernSound:
switch (event.cmd) {
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID) modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
else useSoundGen(actor, actor);
if (actor->xspr.busyTime > 0)
evPostActor(actor, (120 * actor->xspr.busyTime) / 10, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
case kGenModernMissileUniversal:
switch (event.cmd)
{
case kCmdOff:
if (actor->xspr.state == 1) SetSpriteState(actor, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
evKillActor(actor, initiator); // queue overflow protect
if (actor->xspr.state == 0) SetSpriteState(actor, 1, initiator);
2021-12-29 21:56:21 +00:00
[[fallthrough]];
case kCmdRepeat:
if (actor->xspr.txID) modernTypeSendCommand(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
2021-12-29 21:56:21 +00:00
else useUniMissileGen(actor, actor);
if (actor->xspr.busyTime > 0)
evPostActor(actor, (120 * actor->xspr.busyTime) / 10, kCmdRepeat, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
if (actor->xspr.state == 0) evPostActor(actor, 0, kCmdOn, initiator);
else evPostActor(actor, 0, kCmdOff, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool modernTypeOperateWall(walltype* pWall, const EVENT& event)
{
DBloodActor* initiator = event.initiator.Get();
2021-12-29 21:56:21 +00:00
switch (pWall->type)
{
case kSwitchOneWay:
switch (event.cmd) {
case kCmdOff:
SetWallState(pWall, 0, initiator);
2021-12-29 21:56:21 +00:00
break;
case kCmdOn:
SetWallState(pWall, 1, initiator);
2021-12-29 21:56:21 +00:00
break;
default:
SetWallState(pWall, pWall->xw().restState ^ 1, initiator);
2021-12-29 21:56:21 +00:00
break;
}
return true;
default:
return false; // no modern type found to work with, go normal OperateWall();
}
}
2021-10-03 12:47:40 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool txIsRanged(DBloodActor* sourceactor)
{
2021-12-29 21:56:21 +00:00
if (!sourceactor->hasX()) return false;
if (sourceactor->xspr.data1 > 0 && sourceactor->xspr.data2 <= 0 && sourceactor->xspr.data3 <= 0 && sourceactor->xspr.data4 > 0)
{
if (sourceactor->xspr.data1 > sourceactor->xspr.data4)
{
// data1 must be less than data4
int tmp = sourceactor->xspr.data1; sourceactor->xspr.data1 = sourceactor->xspr.data4;
sourceactor->xspr.data4 = tmp;
}
return true;
}
return false;
}
2021-10-03 12:47:40 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void seqTxSendCmdAll(DBloodActor* sourceactor, DBloodActor* actor, COMMAND_ID cmd, bool modernSend, DBloodActor* initiator)
{
2021-12-29 21:56:21 +00:00
bool ranged = txIsRanged(sourceactor);
if (ranged)
{
for (sourceactor->xspr.txID = sourceactor->xspr.data1; sourceactor->xspr.txID <= sourceactor->xspr.data4; sourceactor->xspr.txID++)
{
if (sourceactor->xspr.txID <= 0 || sourceactor->xspr.txID >= kChannelUserMax) continue;
else if (!modernSend) evSendActor(actor, sourceactor->xspr.txID, cmd, initiator);
else modernTypeSendCommand(actor, sourceactor->xspr.txID, cmd, initiator);
2021-12-29 21:56:21 +00:00
}
}
else
{
for (int i = 0; i <= 3; i++)
{
sourceactor->xspr.txID = GetDataVal(sourceactor, i);
if (sourceactor->xspr.txID <= 0 || sourceactor->xspr.txID >= kChannelUserMax) continue;
else if (!modernSend) evSendActor(actor, sourceactor->xspr.txID, cmd, initiator);
else modernTypeSendCommand(actor, sourceactor->xspr.txID, cmd, initiator);
2021-12-29 21:56:21 +00:00
}
}
sourceactor->xspr.txID = sourceactor->xspr.sysData1 = 0;
}
2021-10-03 12:47:40 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useRandomTx(DBloodActor* sourceactor, COMMAND_ID cmd, bool setState, DBloodActor* initiator)
{
2021-12-29 21:56:21 +00:00
int tx = 0; int maxRetries = kMaxRandomizeRetries;
if (txIsRanged(sourceactor))
{
while (maxRetries-- >= 0)
{
if ((tx = nnExtRandom(sourceactor->xspr.data1, sourceactor->xspr.data4)) != sourceactor->xspr.txID)
break;
}
}
else
{
while (maxRetries-- >= 0)
{
if ((tx = randomGetDataValue(sourceactor, kRandomizeTX)) > 0 && tx != sourceactor->xspr.txID)
break;
}
}
2021-12-29 21:56:21 +00:00
sourceactor->xspr.txID = (tx > 0 && tx < kChannelUserMax) ? tx : 0;
if (setState)
SetSpriteState(sourceactor, sourceactor->xspr.state ^ 1, initiator);
2021-12-29 21:56:21 +00:00
//evSendActor(sourceactor->spr.index, sourceactor->xspr.txID, (COMMAND_ID)sourceactor->xspr.command);
}
2021-10-03 12:47:40 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useSequentialTx(DBloodActor* sourceactor, COMMAND_ID cmd, bool setState, DBloodActor* initiator)
{
2021-12-29 21:56:21 +00:00
bool range = txIsRanged(sourceactor); int cnt = 3; int tx = 0;
2021-12-29 21:56:21 +00:00
if (range)
{
// make sure sysData is correct as we store current index of TX ID here.
if (sourceactor->xspr.sysData1 < sourceactor->xspr.data1) sourceactor->xspr.sysData1 = sourceactor->xspr.data1;
else if (sourceactor->xspr.sysData1 > sourceactor->xspr.data4) sourceactor->xspr.sysData1 = sourceactor->xspr.data4;
2021-12-29 21:56:21 +00:00
}
else
{
// make sure sysData is correct as we store current index of data field here.
if (sourceactor->xspr.sysData1 > 3) sourceactor->xspr.sysData1 = 0;
else if (sourceactor->xspr.sysData1 < 0) sourceactor->xspr.sysData1 = 3;
}
2021-12-29 21:56:21 +00:00
switch (cmd)
{
case kCmdOff:
if (!range)
{
while (cnt-- >= 0) // skip empty data fields
{
if (sourceactor->xspr.sysData1-- < 0) sourceactor->xspr.sysData1 = 3;
if ((tx = GetDataVal(sourceactor, sourceactor->xspr.sysData1)) <= 0) continue;
else break;
}
}
else
{
if (--sourceactor->xspr.sysData1 < sourceactor->xspr.data1) sourceactor->xspr.sysData1 = sourceactor->xspr.data4;
tx = sourceactor->xspr.sysData1;
}
break;
2021-10-03 12:47:40 +00:00
2021-12-29 21:56:21 +00:00
default:
if (!range)
{
while (cnt-- >= 0) // skip empty data fields
{
if (sourceactor->xspr.sysData1 > 3) sourceactor->xspr.sysData1 = 0;
if ((tx = GetDataVal(sourceactor, sourceactor->xspr.sysData1++)) <= 0) continue;
else break;
}
}
else
{
tx = sourceactor->xspr.sysData1;
if (sourceactor->xspr.sysData1 >= sourceactor->xspr.data4)
{
sourceactor->xspr.sysData1 = sourceactor->xspr.data1;
break;
}
sourceactor->xspr.sysData1++;
}
break;
}
2021-12-29 21:56:21 +00:00
sourceactor->xspr.txID = (tx > 0 && tx < kChannelUserMax) ? tx : 0;
if (setState)
SetSpriteState(sourceactor, sourceactor->xspr.state ^ 1, initiator);
2021-12-29 21:56:21 +00:00
//evSendActor(sourceactor->spr.index, sourceactor->xspr.txID, (COMMAND_ID)sourceactor->xspr.command);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-10-03 12:47:40 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
int useCondition(DBloodActor* sourceactor, EVENT& event)
2021-10-03 12:47:40 +00:00
{
2021-12-29 21:56:21 +00:00
bool srcIsCondition = false;
auto const pActor = event.isActor() ? event.getActor() : nullptr;
if (event.isActor() && pActor == nullptr) return -1;
if (event.isActor() && pActor != sourceactor)
srcIsCondition = (pActor->spr.type == kModernCondition || pActor->spr.type == kModernConditionFalse);
// if it's a tracking condition, it must ignore all the commands sent from objects
if (sourceactor->xspr.busyTime > 0 && event.funcID != kCallbackMax) return -1;
else if (!srcIsCondition) // save object serials in the stack and make copy of initial object
{
condPush(sourceactor, event.target);
condBackup(sourceactor);
}
else // or grab serials of objects from previous conditions
{
sourceactor->condition[0] = pActor->condition[0];
sourceactor->condition[1] = pActor->condition[1];
}
int cond = sourceactor->xspr.data1;
bool ok = false;
bool RVRS = (sourceactor->spr.type == kModernConditionFalse);
bool RSET = (sourceactor->xspr.command == kCmdNumberic + 36);
bool PUSH = (sourceactor->xspr.command == kCmdNumberic);
int comOp = sourceactor->spr.cstat; // comparison operator
if (sourceactor->xspr.restState == 0)
{
if (cond == 0) ok = true; // dummy
else if (cond >= kCondGameBase && cond < kCondGameMax) ok = condCheckGame(sourceactor, event, comOp, PUSH);
else if (cond >= kCondMixedBase && cond < kCondMixedMax) ok = condCheckMixed(sourceactor, event, comOp, PUSH);
else if (cond >= kCondWallBase && cond < kCondWallMax) ok = condCheckWall(sourceactor, comOp, PUSH);
else if (cond >= kCondSectorBase && cond < kCondSectorMax) ok = condCheckSector(sourceactor, comOp, PUSH);
else if (cond >= kCondPlayerBase && cond < kCondPlayerMax) ok = condCheckPlayer(sourceactor, comOp, PUSH);
else if (cond >= kCondDudeBase && cond < kCondDudeMax) ok = condCheckDude(sourceactor, comOp, PUSH);
else if (cond >= kCondSpriteBase && cond < kCondSpriteMax) ok = condCheckSprite(sourceactor, comOp, PUSH);
else condError(sourceactor, "Unexpected condition id %d!", cond);
sourceactor->xspr.state = (ok ^ RVRS);
if (sourceactor->xspr.waitTime > 0 && sourceactor->xspr.state > 0)
{
sourceactor->xspr.restState = 1;
evKillActor(sourceactor);
evPostActor(sourceactor, (sourceactor->xspr.waitTime * 120) / 10, kCmdRepeat, event.initiator);
2021-12-29 21:56:21 +00:00
return -1;
}
}
else if (event.cmd == kCmdRepeat)
{
sourceactor->xspr.restState = 0;
}
else
{
return -1;
}
2022-05-06 16:27:44 +00:00
int retn = sourceactor->xspr.state;
if (retn)
2021-12-29 21:56:21 +00:00
{
sourceactor->xspr.isTriggered = sourceactor->xspr.triggerOnce;
if (RSET)
condRestore(sourceactor); // reset focus to the initial object
// send command to rx bucket
if (sourceactor->xspr.txID)
{
evSendActor(sourceactor, sourceactor->xspr.txID, (COMMAND_ID)sourceactor->xspr.command, sourceactor->condition[0].isActor()? sourceactor->condition[0].actor() : nullptr);
}
2021-12-29 21:56:21 +00:00
if (sourceactor->spr.flags) {
// send it for object currently in the focus
if (sourceactor->spr.flags & kModernTypeFlag1)
{
nnExtTriggerObject(event.target, sourceactor->xspr.command, sourceactor);
2021-12-29 21:56:21 +00:00
}
// send it for initial object
if ((sourceactor->spr.flags & kModernTypeFlag2) && (sourceactor->condition[0] != sourceactor->condition[1] || !(sourceactor->spr.hitag & kModernTypeFlag1)))
{
auto co = condGet(sourceactor);
nnExtTriggerObject(co, sourceactor->xspr.command, sourceactor);
2021-12-29 21:56:21 +00:00
}
}
}
2022-05-06 16:27:44 +00:00
return retn;
2021-12-29 21:56:21 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useRandomItemGen(DBloodActor* actor)
{
// let's first search for previously dropped items and remove it
if (actor->xspr.dropMsg > 0)
{
BloodStatIterator it(kStatItem);
while (auto iactor = it.Next())
{
if ((unsigned int)iactor->spr.type == actor->xspr.dropMsg && iactor->spr.pos == actor->spr.pos)
2021-12-29 21:56:21 +00:00
{
gFX.fxSpawnActor((FX_ID)29, actor->sector(), actor->spr.pos, 0);
2021-12-29 21:56:21 +00:00
iactor->spr.type = kSpriteDecoration;
actPostSprite(iactor, kStatFree);
break;
}
}
}
// then drop item
auto dropactor = randomDropPickupObject(actor, actor->xspr.dropMsg);
if (dropactor != nullptr)
{
clampSprite(dropactor);
// check if generator affected by physics
if (debrisGetIndex(actor) != -1)
{
dropactor->addX();
int nIndex = debrisGetFreeIndex();
if (nIndex >= 0)
{
dropactor->xspr.physAttr |= kPhysMove | kPhysGravity | kPhysFalling; // must fall always
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
gPhysSpritesList[nIndex] = dropactor;
if (nIndex >= gPhysSpritesCount) gPhysSpritesCount++;
getSpriteMassBySize(dropactor); // create mass cache
}
}
}
}
2021-12-29 21:56:21 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useUniMissileGen(DBloodActor* sourceactor, DBloodActor* actor)
{
if (actor == nullptr) actor = sourceactor;
int dx = 0, dy = 0, dz = 0;
2021-12-29 21:56:21 +00:00
if (sourceactor->xspr.data1 < kMissileBase || sourceactor->xspr.data1 >= kMissileMax)
return;
2021-12-29 21:56:21 +00:00
if (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)
{
if (actor->spr.cstat & CSTAT_SPRITE_YFLIP) dz = 0x4000;
else dz = -0x4000;
}
else
{
2022-08-16 21:15:49 +00:00
dx = bcos(actor->int_ang());
dy = bsin(actor->int_ang());
2021-12-29 21:56:21 +00:00
dz = sourceactor->xspr.data3 << 6; // add slope controlling
if (dz > 0x10000) dz = 0x10000;
else if (dz < -0x10000) dz = -0x10000;
}
auto missileactor = actFireMissile(actor, 0, 0, dx, dy, dz, actor->xspr.data1);
if (missileactor)
{
int from; // inherit some properties of the generator
if ((from = (sourceactor->spr.flags & kModernTypeFlag3)) > 0)
{
int canInherit = 0xF;
if (missileactor->hasX() && seqGetStatus(missileactor) >= 0)
{
canInherit &= ~0x8;
SEQINST* pInst = GetInstance(missileactor);
Seq* pSeq = pInst->pSequence;
for (int i = 0; i < pSeq->nFrames; i++)
{
if ((canInherit & 0x4) && pSeq->frames[i].palette != 0) canInherit &= ~0x4;
if ((canInherit & 0x2) && pSeq->frames[i].xrepeat != 0) canInherit &= ~0x2;
if ((canInherit & 0x1) && pSeq->frames[i].yrepeat != 0) canInherit &= ~0x1;
}
}
if (canInherit != 0)
{
if (canInherit & 0x2)
missileactor->spr.xrepeat = (from == kModernTypeFlag1) ? sourceactor->spr.xrepeat : actor->spr.xrepeat;
if (canInherit & 0x1)
missileactor->spr.yrepeat = (from == kModernTypeFlag1) ? sourceactor->spr.yrepeat : actor->spr.yrepeat;
if (canInherit & 0x4)
missileactor->spr.pal = (from == kModernTypeFlag1) ? sourceactor->spr.pal : actor->spr.pal;
if (canInherit & 0x8)
missileactor->spr.shade = (from == kModernTypeFlag1) ? sourceactor->spr.shade : actor->spr.shade;
}
}
// add velocity controlling
if (sourceactor->xspr.data2 > 0)
{
int velocity = sourceactor->xspr.data2 << 12;
2022-09-01 19:18:16 +00:00
missileactor->set_int_bvel_x(MulScale(velocity, dx, 14));
2022-09-01 19:24:29 +00:00
missileactor->set_int_bvel_y(MulScale(velocity, dy, 14));
2022-09-01 19:40:03 +00:00
missileactor->set_int_bvel_z(MulScale(velocity, dz, 14));
2021-12-29 21:56:21 +00:00
}
// add bursting for missiles
if (missileactor->spr.type != kMissileFlareAlt && sourceactor->xspr.data4 > 0)
evPostActor(missileactor, ClipHigh(sourceactor->xspr.data4, 500), kCallbackMissileBurst);
}
}
2021-08-29 06:57:23 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useSoundGen(DBloodActor* sourceactor, DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
int pitch = sourceactor->xspr.data4 << 1;
if (pitch < 2000) pitch = 0;
sfxPlay3DSoundCP(actor, sourceactor->xspr.data2, -1, 0, pitch, sourceactor->xspr.data3);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useIncDecGen(DBloodActor* sourceactor, int objType, sectortype* destSect, walltype* destWall, DBloodActor* objactor)
{
char buffer[7];
int data = -65535;
short tmp = 0;
int dataIndex = 0;
snprintf(buffer, 7, "%d", abs(sourceactor->xspr.data1));
int len = int(strlen(buffer));
for (int i = 0; i < len; i++)
{
dataIndex = (buffer[i] - 52) + 4;
if ((data = getDataFieldOfObject(objType, destSect, destWall, objactor, dataIndex)) == -65535)
{
Printf(PRINT_HIGH, "\nWrong index of data (%c) for IncDec Gen! Only 1, 2, 3 and 4 indexes allowed!\n", buffer[i]);
continue;
}
if (sourceactor->xspr.data2 < sourceactor->xspr.data3)
{
data = ClipRange(data, sourceactor->xspr.data2, sourceactor->xspr.data3);
if ((data += sourceactor->xspr.data4) >= sourceactor->xspr.data3)
{
switch (sourceactor->spr.flags)
{
case kModernTypeFlag0:
case kModernTypeFlag1:
if (data > sourceactor->xspr.data3) data = sourceactor->xspr.data3;
break;
case kModernTypeFlag2:
if (data > sourceactor->xspr.data3) data = sourceactor->xspr.data3;
if (!incDecGoalValueIsReached(sourceactor)) break;
tmp = sourceactor->xspr.data3;
sourceactor->xspr.data3 = sourceactor->xspr.data2;
sourceactor->xspr.data2 = tmp;
break;
case kModernTypeFlag3:
if (data > sourceactor->xspr.data3) data = sourceactor->xspr.data2;
break;
}
}
}
else if (sourceactor->xspr.data2 > sourceactor->xspr.data3)
{
data = ClipRange(data, sourceactor->xspr.data3, sourceactor->xspr.data2);
if ((data -= sourceactor->xspr.data4) <= sourceactor->xspr.data3)
{
switch (sourceactor->spr.flags)
{
case kModernTypeFlag0:
case kModernTypeFlag1:
if (data < sourceactor->xspr.data3) data = sourceactor->xspr.data3;
break;
case kModernTypeFlag2:
if (data < sourceactor->xspr.data3) data = sourceactor->xspr.data3;
if (!incDecGoalValueIsReached(sourceactor)) break;
tmp = sourceactor->xspr.data3;
sourceactor->xspr.data3 = sourceactor->xspr.data2;
sourceactor->xspr.data2 = tmp;
break;
case kModernTypeFlag3:
if (data < sourceactor->xspr.data3) data = sourceactor->xspr.data2;
break;
}
}
}
sourceactor->xspr.sysData1 = data;
setDataValueOfObject(objType, destSect, destWall, objactor, dataIndex, data);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void sprite2sectorSlope(DBloodActor* actor, sectortype* pSector, int rel, bool forcez)
2021-12-29 21:56:21 +00:00
{
2022-09-13 22:42:59 +00:00
int slope = 0;
double z = 0;
2021-12-29 21:56:21 +00:00
switch (rel) {
default:
z = getflorzofslopeptr(actor->sector(), actor->spr.pos);
2021-12-29 21:56:21 +00:00
if ((actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR) && actor->hasX() && actor->xspr.Touch) z--;
slope = pSector->floorheinum;
break;
case 1:
z = getceilzofslopeptr(actor->sector(), actor->spr.pos);
2021-12-29 21:56:21 +00:00
if ((actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR) && actor->hasX() && actor->xspr.Touch) z++;
slope = pSector->ceilingheinum;
break;
}
spriteSetSlope(actor, slope);
2022-09-13 22:42:59 +00:00
if (forcez) actor->spr.pos.Z = z;
2021-12-29 21:56:21 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useSlopeChanger(DBloodActor* sourceactor, int objType, sectortype* pSect, DBloodActor* objActor)
{
int slope, oslope;
bool flag2 = (sourceactor->spr.flags & kModernTypeFlag2);
if (sourceactor->spr.flags & kModernTypeFlag1) slope = ClipRange(sourceactor->xspr.data2, -32767, 32767);
else slope = (32767 / kPercFull) * ClipRange(sourceactor->xspr.data2, -kPercFull, kPercFull);
if (objType == OBJ_SECTOR)
{
switch (sourceactor->xspr.data1)
{
case 2:
case 0:
// just set floor slope
if (flag2)
{
pSect->setfloorslope(slope);
}
else
{
// force closest floor aligned sprites to inherit slope of the sector's floor
oslope = pSect->floorheinum;
BloodSectIterator it(pSect);
while (auto iactor = it.Next())
{
if (!(iactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) continue;
else if (getflorzofslopeptr(pSect, iactor->spr.pos) - kSlopeDist <= iactor->spr.pos.Z)
2021-12-29 21:56:21 +00:00
{
sprite2sectorSlope(iactor, pSect, 0, true);
// set temporary slope of floor
pSect->floorheinum = slope;
// force sloped sprites to be on floor slope z
sprite2sectorSlope(iactor, pSect, 0, true);
// restore old slope for next sprite
pSect->floorheinum = oslope;
}
}
// finally set new slope of floor
pSect->setfloorslope(slope);
}
if (sourceactor->xspr.data1 == 0) break;
[[fallthrough]];
case 1:
// just set ceiling slope
if (flag2)
{
pSect->setceilingslope(slope);
}
else
{
oslope = pSect->ceilingheinum;
BloodSectIterator it(pSect);
while (auto iactor = it.Next())
{
if (!(iactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) continue;
else if (getceilzofslopeptr(pSect, iactor->spr.pos) + kSlopeDist >= iactor->spr.pos.Z)
2021-12-29 21:56:21 +00:00
{
sprite2sectorSlope(iactor, pSect, 1, true);
2021-12-29 21:56:21 +00:00
// set new slope of ceiling
pSect->ceilingheinum = slope;
// force sloped sprites to be on ceiling slope z
sprite2sectorSlope(iactor, pSect, 1, true);
// restore old slope for next sprite
pSect->ceilingheinum = oslope;
}
}
// finally set new slope of ceiling
pSect->setceilingslope(slope);
}
break;
}
// let's give a little impulse to the physics sprites...
BloodSectIterator it(pSect);
while (auto iactor = it.Next())
{
if (iactor->hasX() && iactor->xspr.physAttr > 0)
{
iactor->xspr.physAttr |= kPhysFalling;
2022-09-01 19:46:30 +00:00
iactor->add_int_bvel_z(1);
2021-12-29 21:56:21 +00:00
}
else if ((iactor->spr.statnum == kStatThing || iactor->spr.statnum == kStatDude) && (iactor->spr.flags & kPhysGravity))
{
iactor->spr.flags |= kPhysFalling;
2022-09-01 19:46:30 +00:00
iactor->add_int_bvel_z(1);
2021-12-29 21:56:21 +00:00
}
}
}
else if (objType == OBJ_SPRITE)
{
if (!(objActor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) objActor->spr.cstat |= CSTAT_SPRITE_ALIGNMENT_FLOOR;
if ((objActor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_SLOPE) != CSTAT_SPRITE_ALIGNMENT_SLOPE)
objActor->spr.cstat |= CSTAT_SPRITE_ALIGNMENT_SLOPE;
switch (sourceactor->xspr.data4)
{
case 1:
case 2:
case 3:
if (!objActor->insector()) break;
2021-12-29 21:56:21 +00:00
switch (sourceactor->xspr.data4)
{
case 1: sprite2sectorSlope(objActor, objActor->sector(), 0, flag2); break;
case 2: sprite2sectorSlope(objActor, objActor->sector(), 1, flag2); break;
2021-12-29 21:56:21 +00:00
case 3:
if (getflorzofslopeptr(objActor->sector(), objActor->spr.pos) - kSlopeDist <= objActor->spr.pos.Z) sprite2sectorSlope(objActor, objActor->sector(), 0, flag2);
if (getceilzofslopeptr(objActor->sector(), objActor->spr.pos) + kSlopeDist >= objActor->spr.pos.Z) sprite2sectorSlope(objActor, objActor->sector(), 1, flag2);
2021-12-29 21:56:21 +00:00
break;
}
break;
default:
spriteSetSlope(objActor, slope);
2021-12-29 21:56:21 +00:00
break;
}
}
2020-12-06 20:56:09 +00:00
}
2021-10-13 17:15:44 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void useDataChanger(DBloodActor* sourceactor, int objType, sectortype* pSector, walltype* pWall, DBloodActor* objActor)
2021-10-13 17:17:45 +00:00
{
bool flag1 = (sourceactor->spr.flags & kModernTypeFlag1);
switch (objType) {
2021-12-29 21:56:21 +00:00
case OBJ_SECTOR:
if (flag1 || valueIsBetween(sourceactor->xspr.data1, -1, 32767)) setDataValueOfObject(objType, pSector, pWall, nullptr, 1, sourceactor->xspr.data1);
2021-12-29 21:56:21 +00:00
break;
case OBJ_SPRITE:
if (flag1 || valueIsBetween(sourceactor->xspr.data1, -1, 32767)) setDataValueOfObject(objType, pSector, pWall, objActor, 1, sourceactor->xspr.data1);
if (flag1 || valueIsBetween(sourceactor->xspr.data2, -1, 32767)) setDataValueOfObject(objType, pSector, pWall, objActor, 2, sourceactor->xspr.data1);
if (flag1 || valueIsBetween(sourceactor->xspr.data3, -1, 32767)) setDataValueOfObject(objType, pSector, pWall, objActor, 3, sourceactor->xspr.data1);
if (flag1 || valueIsBetween(sourceactor->xspr.data4, -1, 65535)) setDataValueOfObject(objType, pSector, pWall, objActor, 4, sourceactor->xspr.data1);
2021-12-29 21:56:21 +00:00
break;
case OBJ_WALL:
if (flag1 || valueIsBetween(sourceactor->xspr.data1, -1, 32767)) setDataValueOfObject(objType, pSector, pWall, nullptr, 1, sourceactor->xspr.data1);
2021-12-29 21:56:21 +00:00
break;
}
}
2021-10-13 17:15:44 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void useSectorLightChanger(DBloodActor* sourceactor, sectortype* pSector)
2021-10-13 17:17:45 +00:00
{
bool relative = (sourceactor->spr.flags & kModernTypeFlag16);
2021-12-29 21:56:21 +00:00
auto pXSector = &pSector->xs();
2021-10-13 17:17:45 +00:00
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
{
if (relative)
pXSector->wave = ClipHigh(pXSector->wave + sourceactor->xspr.data1, 11);
else
pXSector->wave = ClipHigh(sourceactor->xspr.data1, 11);
}
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data2, -128, 128))
{
if (relative)
pXSector->amplitude = ClipRange(pXSector->amplitude + sourceactor->xspr.data2, -127, 127);
else
pXSector->amplitude = sourceactor->xspr.data2;
}
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
{
if (relative)
pXSector->freq = ClipHigh(pXSector->freq + sourceactor->xspr.data3, 255);
else
pXSector->freq = ClipHigh(sourceactor->xspr.data3, 255);
}
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
{
if (relative)
pXSector->phase = ClipHigh(pXSector->phase + sourceactor->xspr.data4, 255);
else
pXSector->phase = ClipHigh(sourceactor->xspr.data4, 255);
}
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
2021-12-29 21:56:21 +00:00
if (sourceactor->spr.flags)
{
if (sourceactor->spr.flags != kModernTypeFlag1)
{
pXSector->shadeAlways = (sourceactor->spr.flags & kModernTypeFlag1) ? true : false;
pXSector->shadeFloor = (sourceactor->spr.flags & kModernTypeFlag2) ? true : false;
pXSector->shadeCeiling = (sourceactor->spr.flags & kModernTypeFlag4) ? true : false;
pXSector->shadeWalls = (sourceactor->spr.flags & kModernTypeFlag8) ? true : false;
pXSector->color = (sourceactor->spr.pal) ? true : false;
auto cstat = sourceactor->spr.cstat;
if ((cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_FLOOR)
{
// !!! xsector pal bits must be extended
if (cstat & CSTAT_SPRITE_ONE_SIDE)
{
if (cstat & CSTAT_SPRITE_YFLIP)
pXSector->ceilpal = sourceactor->spr.pal;
else
pXSector->floorpal = sourceactor->spr.pal;
}
else
{
pXSector->floorpal = sourceactor->spr.pal;
pXSector->ceilpal = sourceactor->spr.pal;
}
}
2021-12-29 21:56:21 +00:00
}
else
{
pXSector->shadeAlways = true;
}
}
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
// add to shadeList
if (!shadeList.Contains(pSector))
shadeList.Push(pSector);
}
2021-10-13 17:15:44 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-10-13 17:17:45 +00:00
void useTargetChanger(DBloodActor* sourceactor, DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (!actor->IsDudeActor() || actor->spr.statnum != kStatDude)
{
switch (actor->spr.type) // can be dead dude turned in gib
{
// make current target and all other dudes not attack this dude anymore
case kThingBloodBits:
case kThingBloodChunks:
aiFightFreeTargets(actor);
return;
default:
return;
}
}
int receiveHp = 33 + Random(33);
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
int matesPerEnemy = 1;
// dude is burning?
if (actor->xspr.burnTime > 0 && actor->GetBurnSource())
{
if (IsBurningDude(actor)) return;
else
{
auto burnactor = actor->GetBurnSource();
if (burnactor->hasX())
{
if (sourceactor->xspr.data2 == 1 && actor->xspr.rxID == burnactor->xspr.rxID)
{
actor->xspr.burnTime = 0;
// heal dude a bit in case of friendly fire
int startHp = (actor->xspr.sysData2 > 0) ? ClipRange(actor->xspr.sysData2 << 4, 1, 65535) : pDudeInfo->startHealth << 4;
if (actor->xspr.health < (unsigned)startHp) actHealDude(actor, receiveHp, startHp);
}
else if (burnactor->xspr.health <= 0)
{
actor->xspr.burnTime = 0;
}
}
}
}
auto playeractor = aiFightTargetIsPlayer(actor);
// special handling for player(s) if target changer data4 > 2.
if (playeractor != nullptr)
{
auto actLeech = leechIsDropped(actor);
if (sourceactor->xspr.data4 == 3)
{
2022-08-22 16:41:41 +00:00
aiSetTarget(actor, actor->spr.pos);
2021-12-29 21:56:21 +00:00
aiSetGenIdleState(actor);
if (actor->spr.type == kDudeModernCustom && actLeech)
removeLeech(actLeech);
}
else if (sourceactor->xspr.data4 == 4)
{
2022-08-22 16:41:41 +00:00
aiSetTarget(actor, playeractor->spr.pos);
2021-12-29 21:56:21 +00:00
if (actor->spr.type == kDudeModernCustom && actLeech)
removeLeech(actLeech);
}
}
int maxAlarmDudes = 8 + Random(8);
auto targetactor = actor->GetTarget();
if (targetactor && targetactor->hasX() && playeractor == nullptr)
{
if (aiFightUnitCanFly(actor) && aiFightIsMeleeUnit(targetactor) && !aiFightUnitCanFly(targetactor))
actor->spr.flags |= 0x0002;
else if (aiFightUnitCanFly(actor))
actor->spr.flags &= ~0x0002;
if (!targetactor->IsDudeActor() || targetactor->xspr.health < 1 || !aiFightDudeCanSeeTarget(actor, pDudeInfo, targetactor))
{
2022-08-22 16:41:41 +00:00
aiSetTarget(actor, actor->spr.pos);
2021-12-29 21:56:21 +00:00
}
// dude attack or attacked by target that does not fit by data id?
else if (sourceactor->xspr.data1 != 666 && targetactor->xspr.data1 != sourceactor->xspr.data1)
{
if (aiFightDudeIsAffected(targetactor))
{
// force stop attack target
2022-08-22 16:41:41 +00:00
aiSetTarget(actor, actor->spr.pos);
2021-12-29 21:56:21 +00:00
if (actor->GetBurnSource() == targetactor)
{
actor->xspr.burnTime = 0;
actor->SetBurnSource(nullptr);
}
// force stop attack dude
2022-08-22 16:41:41 +00:00
aiSetTarget(targetactor, targetactor->spr.pos);
2021-12-29 21:56:21 +00:00
if (targetactor->GetBurnSource() == actor)
{
targetactor->xspr.burnTime = 0;
targetactor->SetBurnSource(nullptr);
}
}
}
else if (sourceactor->xspr.data2 == 1 && actor->xspr.rxID == targetactor->xspr.rxID)
{
auto mateactor = targetactor;
// heal dude
int startHp = (actor->xspr.sysData2 > 0) ? ClipRange(actor->xspr.sysData2 << 4, 1, 65535) : pDudeInfo->startHealth << 4;
if (actor->xspr.health < (unsigned)startHp) actHealDude(actor, receiveHp, startHp);
// heal mate
startHp = (mateactor->xspr.sysData2 > 0) ? ClipRange(mateactor->xspr.sysData2 << 4, 1, 65535) : getDudeInfo(mateactor->spr.type)->startHealth << 4;
if (mateactor->xspr.health < (unsigned)startHp) actHealDude(mateactor, receiveHp, startHp);
auto matetarget = mateactor->GetTarget();
if (matetarget != nullptr && matetarget->hasX())
{
// force mate stop attack dude, if he does
if (matetarget == actor)
{
2022-08-22 16:41:41 +00:00
aiSetTarget(mateactor, mateactor->spr.pos);
2021-12-29 21:56:21 +00:00
}
else if (actor->xspr.rxID != matetarget->xspr.rxID)
{
// force dude to attack same target that mate have
aiSetTarget(actor, matetarget);
return;
}
else
{
// force mate to stop attack another mate
2022-08-22 16:41:41 +00:00
aiSetTarget(mateactor, mateactor->spr.pos);
2021-12-29 21:56:21 +00:00
}
}
// force dude stop attack mate, if target was not changed previously
if (actor == mateactor)
2022-08-22 16:41:41 +00:00
aiSetTarget(actor, actor->spr.pos);
2021-12-29 21:56:21 +00:00
}
// check if targets aims player then force this target to fight with dude
else if (aiFightTargetIsPlayer(actor) != nullptr)
{
aiSetTarget(targetactor, actor);
}
int mDist = 3;
if (aiFightIsMeleeUnit(actor)) mDist = 2;
if (targetactor != nullptr && aiFightGetTargetDist(actor, pDudeInfo, targetactor) < mDist)
{
if (!isActive(actor)) aiActivateDude(actor);
return;
}
// lets try to look for target that fits better by distance
else if ((PlayClock & 256) != 0 && (targetactor == nullptr || aiFightGetTargetDist(actor, pDudeInfo, targetactor) >= mDist))
{
auto newtargactor = aiFightGetTargetInRange(actor, 0, mDist, sourceactor->xspr.data1, sourceactor->xspr.data2);
if (newtargactor != nullptr)
{
// Make prev target not aim in dude
if (targetactor)
{
2022-08-22 16:41:41 +00:00
aiSetTarget(targetactor, targetactor->spr.pos);
2021-12-29 21:56:21 +00:00
if (!isActive(newtargactor))
aiActivateDude(newtargactor);
}
// Change target for dude
aiSetTarget(actor, newtargactor);
if (!isActive(actor))
aiActivateDude(actor);
// ...and change target of target to dude to force it fight
if (sourceactor->xspr.data3 > 0 && newtargactor->GetTarget() != actor)
{
aiSetTarget(newtargactor, actor);
if (!isActive(newtargactor))
aiActivateDude(newtargactor);
}
return;
}
}
}
if ((targetactor == nullptr || playeractor != nullptr) && (PlayClock & 32) != 0)
{
// try find first target that dude can see
BloodStatIterator it(kStatDude);
while (auto newtargactor = it.Next())
{
if (newtargactor->GetTarget() == actor)
{
aiSetTarget(actor, newtargactor);
return;
}
// skip non-dudes and players
if (!newtargactor->IsDudeActor() || (newtargactor->IsPlayerActor() && sourceactor->xspr.data4 > 0) || newtargactor->GetOwner() == actor) continue;
// avoid self aiming, those who dude can't see, and those who dude own
else if (!aiFightDudeCanSeeTarget(actor, pDudeInfo, newtargactor) || actor == newtargactor) continue;
// if Target Changer have data1 = 666, everyone can be target, except AI team mates.
else if (sourceactor->xspr.data1 != 666 && sourceactor->xspr.data1 != newtargactor->xspr.data1) continue;
// don't attack immortal, burning dudes and mates
if (IsBurningDude(newtargactor) || !IsKillableDude(newtargactor) || (sourceactor->xspr.data2 == 1 && actor->xspr.rxID == newtargactor->xspr.rxID))
continue;
if (sourceactor->xspr.data2 == 0 || (sourceactor->xspr.data2 == 1 && !aiFightMatesHaveSameTarget(actor, newtargactor, matesPerEnemy)))
{
// Change target for dude
aiSetTarget(actor, newtargactor);
if (!isActive(actor))
aiActivateDude(actor);
// ...and change target of target to dude to force it fight
if (sourceactor->xspr.data3 > 0 && newtargactor->GetTarget() != actor)
{
aiSetTarget(newtargactor, actor);
if (playeractor == nullptr && !isActive(newtargactor))
aiActivateDude(newtargactor);
if (sourceactor->xspr.data3 == 2)
aiFightAlarmDudesInSight(newtargactor, maxAlarmDudes);
}
return;
}
break;
}
}
// got no target - let's ask mates if they have targets
if ((actor->GetTarget() == nullptr || playeractor != nullptr) && sourceactor->xspr.data2 == 1 && (PlayClock & 64) != 0)
{
DBloodActor* pMateTargetActor = aiFightGetMateTargets(actor);
if (pMateTargetActor != nullptr && pMateTargetActor->hasX())
{
if (aiFightDudeCanSeeTarget(actor, pDudeInfo, pMateTargetActor))
{
if (pMateTargetActor->GetTarget() == nullptr)
{
aiSetTarget(pMateTargetActor, actor);
if (pMateTargetActor->IsDudeActor() && !isActive(pMateTargetActor))
aiActivateDude(pMateTargetActor);
}
aiSetTarget(actor, pMateTargetActor);
if (!isActive(actor))
aiActivateDude(actor);
return;
// try walk in mate direction in case if not see the target
}
else if (pMateTargetActor->GetTarget() && aiFightDudeCanSeeTarget(actor, pDudeInfo, pMateTargetActor->GetTarget()))
{
actor->SetTarget(pMateTargetActor);
auto pMate = pMateTargetActor->GetTarget();
2022-08-22 16:31:45 +00:00
actor->xspr.TargetPos = pMate->spr.pos;
2021-12-29 21:56:21 +00:00
if (!isActive(actor))
aiActivateDude(actor);
return;
}
}
}
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void usePictureChanger(DBloodActor* sourceactor, int objType, sectortype* targSect, walltype* targWall, DBloodActor* objActor)
2021-08-29 11:28:13 +00:00
{
2021-12-29 21:56:21 +00:00
switch (objType) {
case OBJ_SECTOR:
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
targSect->floorpicnum = sourceactor->xspr.data1;
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
targSect->ceilingpicnum = sourceactor->xspr.data2;
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
targSect->floorpal = uint8_t(sourceactor->xspr.data3);
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data4, -1, 65535))
targSect->ceilingpal = uint8_t(sourceactor->xspr.data4);
break;
case OBJ_SPRITE:
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
objActor->spr.picnum = sourceactor->xspr.data1;
2021-12-29 21:56:21 +00:00
if (sourceactor->xspr.data2 >= 0) objActor->spr.shade = (sourceactor->xspr.data2 > 127) ? 127 : sourceactor->xspr.data2;
else if (sourceactor->xspr.data2 < -1) objActor->spr.shade = (sourceactor->xspr.data2 < -127) ? -127 : sourceactor->xspr.data2;
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
objActor->spr.pal = uint8_t(sourceactor->xspr.data3);
break;
case OBJ_WALL:
if (valueIsBetween(sourceactor->xspr.data1, -1, 32767))
targWall->picnum = sourceactor->xspr.data1;
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data2, -1, 32767))
targWall->overpicnum = sourceactor->xspr.data2;
2021-12-29 21:56:21 +00:00
if (valueIsBetween(sourceactor->xspr.data3, -1, 32767))
targWall->pal = uint8_t(sourceactor->xspr.data3);
break;
}
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
QAV* playerQavSceneLoad(int qavId)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
QAV* pQav = getQAV(qavId);
if (!pQav) viewSetSystemMessage("Failed to load QAV animation #%d", qavId);
return pQav;
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void playerQavSceneProcess(PLAYER* pPlayer, QAVSCENE* pQavScene)
{
2021-12-29 21:56:21 +00:00
auto initiator = pQavScene->initiator;
if (initiator->hasX())
{
if (initiator->xspr.waitTime > 0 && --initiator->xspr.sysData1 <= 0)
{
if (initiator->xspr.txID >= kChannelUser)
{
for (int i = bucketHead[initiator->xspr.txID]; i < bucketHead[initiator->xspr.txID + 1]; i++)
{
if (rxBucket[i].isActor())
{
auto rxactor = rxBucket[i].actor();
if (!rxactor || !rxactor->hasX() || rxactor == initiator) continue;
2021-12-29 21:56:21 +00:00
if (rxactor->spr.type == kModernPlayerControl && rxactor->xspr.command == 67)
{
if (rxactor->xspr.data2 == initiator->xspr.data2 || rxactor->xspr.locked) continue;
else trPlayerCtrlStartScene(rxactor, pPlayer, true);
return;
}
2021-12-29 21:56:21 +00:00
}
nnExtTriggerObject(rxBucket[i], initiator->xspr.command, pPlayer->actor);
2021-12-29 21:56:21 +00:00
}
}
trPlayerCtrlStopScene(pPlayer);
}
else
{
playerQavScenePlay(pPlayer);
pPlayer->weaponTimer = ClipLow(pPlayer->weaponTimer -= 4, 0);
}
}
else
{
pQavScene->initiator = nullptr;
pPlayer->sceneQav = -1;
pQavScene->qavResrc = NULL;
}
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void playerQavSceneDraw(PLAYER* pPlayer, int a2, double a3, double a4, int a5)
{
2021-12-29 21:56:21 +00:00
if (pPlayer == NULL || pPlayer->sceneQav == -1) return;
2021-12-29 21:56:21 +00:00
QAVSCENE* pQavScene = &gPlayerCtrl[pPlayer->nPlayer].qavScene;
auto actor = pQavScene->initiator;
2021-12-29 21:56:21 +00:00
if (pQavScene->qavResrc != NULL)
{
QAV* pQAV = pQavScene->qavResrc;
int v4;
double interpfrac;
qavProcessTimer(pPlayer, pQAV, &v4, &interpfrac);
2021-12-29 21:56:21 +00:00
int flags = 2; int nInv = powerupCheck(pPlayer, kPwUpShadowCloak);
if (nInv >= 120 * 8 || (nInv != 0 && (PlayClock & 32)))
{
a2 = -128; flags |= 1;
}
2021-12-29 21:56:21 +00:00
// draw as weapon
if (!(actor->spr.flags & kModernTypeFlag1))
{
pQAV->x = a3; pQAV->y = a4;
pQAV->Draw(v4, flags, a2, a5, true, interpfrac);
2021-12-29 21:56:21 +00:00
// draw fullscreen (currently 4:3 only)
}
else
{
// What an awful hack. This throws proper ordering out of the window, but there is no way to reproduce this better with strict layering of elements.
// From the above commit it seems to be incomplete anyway...
pQAV->Draw(v4, flags, a2, a5, false, interpfrac);
2021-12-29 21:56:21 +00:00
}
}
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void playerQavScenePlay(PLAYER* pPlayer)
{
2021-12-29 21:56:21 +00:00
if (pPlayer == NULL) return;
QAVSCENE* pQavScene = &gPlayerCtrl[pPlayer->nPlayer].qavScene;
if (pPlayer->sceneQav == -1 && pQavScene->initiator != nullptr)
pPlayer->sceneQav = pQavScene->initiator->xspr.data2;
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
2021-12-29 21:56:21 +00:00
if (pQavScene->qavResrc != NULL)
{
QAV* pQAV = pQavScene->qavResrc;
int nTicks = pQAV->duration - pPlayer->weaponTimer;
pQAV->Play(nTicks - 4, nTicks, pPlayer->qavCallback, pPlayer);
}
}
2021-12-29 21:56:21 +00:00
void playerQavSceneReset(PLAYER* pPlayer)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
QAVSCENE* pQavScene = &gPlayerCtrl[pPlayer->nPlayer].qavScene;
pQavScene->initiator = nullptr;
pQavScene->dummy = pPlayer->sceneQav = -1;
pQavScene->qavResrc = NULL;
}
2021-12-29 21:56:21 +00:00
bool playerSizeShrink(PLAYER* pPlayer, int divider)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
pPlayer->actor->xspr.scale = 256 / divider;
playerSetRace(pPlayer, kModeHumanShrink);
return true;
}
2021-12-29 21:56:21 +00:00
bool playerSizeGrow(PLAYER* pPlayer, int multiplier)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
pPlayer->actor->xspr.scale = 256 * multiplier;
playerSetRace(pPlayer, kModeHumanGrown);
return true;
}
2021-12-29 21:56:21 +00:00
bool playerSizeReset(PLAYER* pPlayer)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
playerSetRace(pPlayer, kModeHuman);
pPlayer->actor->xspr.scale = 0;
return true;
}
2021-12-29 21:56:21 +00:00
void playerDeactivateShrooms(PLAYER* pPlayer)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
powerupDeactivate(pPlayer, kPwUpGrowShroom);
pPlayer->pwUpTime[kPwUpGrowShroom] = 0;
2021-12-29 21:56:21 +00:00
powerupDeactivate(pPlayer, kPwUpShrinkShroom);
pPlayer->pwUpTime[kPwUpShrinkShroom] = 0;
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
PLAYER* getPlayerById(int id)
2021-10-13 18:11:37 +00:00
{
2021-12-29 21:56:21 +00:00
// relative to connected players
if (id >= 1 && id <= kMaxPlayers)
{
id = id - 1;
for (int i = connecthead; i >= 0; i = connectpoint2[i])
{
if (id == gPlayer[i].nPlayer)
return &gPlayer[i];
}
2021-12-29 21:56:21 +00:00
// absolute sprite type
}
else if (id >= kDudePlayer1 && id <= kDudePlayer8)
{
for (int i = connecthead; i >= 0; i = connectpoint2[i])
{
if (id == gPlayer[i].actor->spr.type)
return &gPlayer[i];
}
}
2021-12-29 21:56:21 +00:00
//viewSetSystemMessage("There is no player id #%d", id);
return NULL;
}
2021-10-13 18:11:37 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool IsBurningDude(DBloodActor* actor)
2021-08-29 11:44:04 +00:00
{
2021-12-29 21:56:21 +00:00
if (actor == NULL) return false;
switch (actor->spr.type)
{
case kDudeBurningInnocent:
case kDudeBurningCultist:
case kDudeBurningZombieAxe:
case kDudeBurningZombieButcher:
case kDudeBurningTinyCaleb:
case kDudeBurningBeast:
case kDudeModernCustomBurning:
return true;
}
2021-12-29 21:56:21 +00:00
return false;
}
2021-08-29 11:44:04 +00:00
bool IsKillableDude(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
switch (actor->spr.type)
{
case kDudeGargoyleStatueFlesh:
case kDudeGargoyleStatueStone:
return false;
default:
if (!actor->IsDudeActor() || actor->xspr.locked == 1) return false;
return true;
}
}
2021-08-29 11:44:04 +00:00
bool isGrown(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (powerupCheck(&gPlayer[actor->spr.type - kDudePlayer1], kPwUpGrowShroom) > 0) return true;
else if (actor->hasX() && actor->xspr.scale >= 512) return true;
else return false;
}
2021-08-29 11:44:04 +00:00
bool isShrinked(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (powerupCheck(&gPlayer[actor->spr.type - kDudePlayer1], kPwUpShrinkShroom) > 0) return true;
else if (actor->hasX() && actor->xspr.scale > 0 && actor->xspr.scale <= 128) return true;
else return false;
}
2021-12-29 21:56:21 +00:00
bool isActive(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (!actor->hasX())
return false;
2021-12-29 21:56:21 +00:00
switch (actor->xspr.aiState->stateType)
{
case kAiStateIdle:
case kAiStateGenIdle:
case kAiStateSearch:
case kAiStateMove:
case kAiStateOther:
return false;
default:
return true;
}
}
2021-10-13 18:26:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
int getDataFieldOfObject(EventObject& eob, int dataIndex)
{
2021-12-29 21:56:21 +00:00
int data = -65535;
2021-12-29 21:56:21 +00:00
if (eob.isActor())
{
auto actor = eob.actor();
if (actor)
{
switch (dataIndex)
{
case 1: return actor->xspr.data1;
case 2: return actor->xspr.data2;
case 3:
switch (actor->spr.type)
{
case kDudeModernCustom: return actor->xspr.sysData1;
default: return actor->xspr.data3;
}
case 4: return actor->xspr.data4;
default: return data;
}
}
}
else if (eob.isSector())
{
return eob.sector()->xs().data;
}
else if (eob.isWall())
{
return eob.wall()->xw().data;
}
return data;
}
int getDataFieldOfObject(int objType, sectortype* sect, walltype* wal, DBloodActor* actor, int dataIndex)
{
2021-12-29 21:56:21 +00:00
int data = -65535;
switch (objType)
{
case OBJ_SPRITE:
switch (dataIndex)
{
case 1: return actor->xspr.data1;
case 2: return actor->xspr.data2;
case 3:
switch (actor->spr.type)
{
case kDudeModernCustom: return actor->xspr.sysData1;
default: return actor->xspr.data3;
}
case 4: return actor->xspr.data4;
default: return data;
}
case OBJ_SECTOR: return sect->xs().data;
case OBJ_WALL: return wal->xw().data;
default: return data;
}
}
2021-10-13 18:26:52 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool setDataValueOfObject(int objType, sectortype* sect, walltype* wal, DBloodActor* objActor, int dataIndex, int value)
2021-10-03 15:47:59 +00:00
{
2021-12-29 21:56:21 +00:00
switch (objType)
{
case OBJ_SPRITE:
{
int type = objActor->spr.type;
// exceptions
if (objActor->IsDudeActor() && objActor->xspr.health <= 0) return true;
switch (type)
{
case kThingBloodBits:
case kThingBloodChunks:
case kThingZombieHead:
return true;
break;
}
switch (dataIndex)
{
case 1:
objActor->xspr.data1 = value;
switch (type)
{
case kSwitchCombo:
if (value == objActor->xspr.data2) SetSpriteState(objActor, 1, nullptr);
else SetSpriteState(objActor, 0, nullptr);
2021-12-29 21:56:21 +00:00
break;
case kDudeModernCustom:
case kDudeModernCustomBurning:
objActor->genDudeExtra.updReq[kGenDudePropertyWeapon] = true;
objActor->genDudeExtra.updReq[kGenDudePropertyDmgScale] = true;
evPostActor(objActor, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
break;
}
return true;
case 2:
objActor->xspr.data2 = value;
switch (type)
{
case kDudeModernCustom:
case kDudeModernCustomBurning:
objActor->genDudeExtra.updReq[kGenDudePropertySpriteSize] = true;
objActor->genDudeExtra.updReq[kGenDudePropertyMass] = true;
objActor->genDudeExtra.updReq[kGenDudePropertyDmgScale] = true;
objActor->genDudeExtra.updReq[kGenDudePropertyStates] = true;
objActor->genDudeExtra.updReq[kGenDudePropertyAttack] = true;
evPostActor(objActor, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
break;
}
return true;
case 3:
objActor->xspr.data3 = value;
switch (type)
{
case kDudeModernCustom:
case kDudeModernCustomBurning:
objActor->xspr.sysData1 = value;
break;
}
return true;
case 4:
objActor->xspr.data4 = value;
return true;
default:
return false;
}
}
case OBJ_SECTOR:
sect->xs().data = value;
return true;
case OBJ_WALL:
wal->xw().data = value;
return true;
default:
return false;
}
}
2021-10-13 18:26:52 +00:00
//---------------------------------------------------------------------------
//
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
// a replacement of vanilla CanMove for patrol dudes
2021-10-13 18:26:52 +00:00
//
//---------------------------------------------------------------------------
bool nnExtCanMove(DBloodActor* actor, DBloodActor* target, DAngle nAngle, int nRange_)
2021-08-29 11:59:06 +00:00
{
double nRange = nRange_ * inttoworld;
DVector3 pos = actor->spr.pos;
DVector2 nAngVect = nAngle.ToVector();
auto pSector = actor->sector();
HitScan(actor, pos.Z * zworldtoint, nAngVect.X * (1 << 14), nAngVect.Y * (1 << 14), 0, CLIPMASK0, nRange);
double nDist = (actor->spr.pos.XY() - gHitInfo.hitpos.XY()).Length();
if (target != nullptr && nDist - actor->fClipdist() < nRange)
2021-12-29 21:56:21 +00:00
return (target == gHitInfo.actor());
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
pos += nAngVect * nRange;
updatesectorz(pos, &pSector);
if (!pSector) return false;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (pSector->hasX()) {
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
XSECTOR* pXSector = &pSector->xs();
return !((pSector->type == kSectorDamage || pXSector->damageType > 0) && pXSector->state && !nnExtIsImmune(actor, pXSector->damageType, 16));
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
}
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
return true;
2020-12-06 20:56:09 +00:00
}
2021-10-13 18:26:52 +00:00
//---------------------------------------------------------------------------
//
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
// a replacement of vanilla aiChooseDirection for patrol dudes
2021-10-13 18:26:52 +00:00
//
//---------------------------------------------------------------------------
2022-09-03 22:38:26 +00:00
void nnExtAiSetDirection(DBloodActor* actor, DAngle direction)
2021-08-29 11:59:06 +00:00
{
2021-12-29 21:56:21 +00:00
assert(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax);
2022-09-03 22:38:26 +00:00
DAngle vc = deltaangle(actor->spr.angle, direction);
double nCos = actor->spr.angle.Cos();
double nSin = actor->spr.angle.Sin();
double t1 = actor->vel.X * nCos + actor->vel.Y * nSin;
int range = FloatToFixed(t1 * (15 / 8192.));
DAngle v8 = vc > nullAngle ? DAngle180 / 3 : -DAngle180 / 3;
if (nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle + vc, range))
actor->xspr.goalAng = actor->spr.angle + vc;
else if (nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle + vc / 2, range))
actor->xspr.goalAng = actor->spr.angle + vc / 2;
else if (nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle - vc / 2, range))
actor->xspr.goalAng = actor->spr.angle - vc / 2;
else if (nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle + v8, range))
actor->xspr.goalAng = actor->spr.angle + v8;
else if (nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle, range))
actor->xspr.goalAng = actor->spr.angle;
else if (nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle - v8, range))
actor->xspr.goalAng = actor->spr.angle - v8;
2021-12-29 21:56:21 +00:00
else
2022-09-03 22:38:26 +00:00
actor->xspr.goalAng = actor->spr.angle + DAngle180 / 3;
2021-12-29 21:56:21 +00:00
if (actor->xspr.dodgeDir)
{
2022-09-03 22:38:26 +00:00
if (!nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle + DAngle90 * actor->xspr.dodgeDir, 512))
2021-12-29 21:56:21 +00:00
{
actor->xspr.dodgeDir = -actor->xspr.dodgeDir;
2022-09-03 22:38:26 +00:00
if (!nnExtCanMove(actor, actor->GetTarget(), actor->spr.angle + DAngle90 * actor->xspr.dodgeDir, 512))
2021-12-29 21:56:21 +00:00
actor->xspr.dodgeDir = 0;
}
}
2020-12-06 20:56:09 +00:00
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-08-29 12:36:40 +00:00
//---------------------------------------------------------------------------
//
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
/// patrol functions
2021-08-29 12:36:40 +00:00
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
void aiPatrolState(DBloodActor* actor, int state)
{
assert(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax&& actor->hasX());
assert(actor->GetTarget());
auto markeractor = actor->GetTarget();
assert(markeractor->spr.type == kMarkerPath);
bool nSeqOverride = false, crouch = false;
int i, seq = -1, start = 0, end = kPatrolStateSize;
const DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[actor->spr.type - kDudeBase];
switch (state) {
case kAiStatePatrolWaitL:
seq = pExtra->idlgseqofs;
start = 0; end = 2;
break;
case kAiStatePatrolMoveL:
seq = pExtra->mvegseqofs;
start = 2, end = 7;
break;
case kAiStatePatrolTurnL:
seq = pExtra->mvegseqofs;
start = 7, end = 12;
break;
case kAiStatePatrolWaitW:
seq = pExtra->idlwseqofs;
start = 12; end = 18;
break;
case kAiStatePatrolMoveW:
seq = pExtra->mvewseqofs;
start = 18; end = 25;
break;
case kAiStatePatrolTurnW:
seq = pExtra->mvewseqofs;
start = 25; end = 32;
break;
case kAiStatePatrolWaitC:
seq = pExtra->idlcseqofs;
start = 32; end = 36;
crouch = true;
break;
case kAiStatePatrolMoveC:
seq = pExtra->mvecseqofs;
start = 36; end = 39;
crouch = true;
break;
case kAiStatePatrolTurnC:
seq = pExtra->mvecseqofs;
start = 39; end = kPatrolStateSize;
crouch = true;
break;
}
if (markeractor->xspr.data4 > 0) seq = markeractor->xspr.data4, nSeqOverride = true;
else if (!nSeqOverride && state == kAiStatePatrolWaitC && (actor->spr.type == kDudeCultistTesla || actor->spr.type == kDudeCultistTNT))
seq = 11537, nSeqOverride = true; // these don't have idle crouch seq for some reason...
if (seq < 0)
return aiPatrolStop(actor, nullptr);
for (i = start; i < end; i++)
{
AISTATE* newState = &genPatrolStates[i];
if (newState->stateType != state || (!nSeqOverride && seq != newState->seqId))
continue;
if (actor->spr.type == kDudeModernCustom) aiGenDudeNewState(actor, newState);
else aiNewState(actor, newState);
if (crouch) actor->xspr.unused1 |= kDudeFlagCrouch;
else actor->xspr.unused1 &= ~kDudeFlagCrouch;
if (nSeqOverride)
seqSpawn(seq, actor);
return;
}
if (i == end)
{
viewSetSystemMessage("No patrol state #%d found for dude #%d (type = %d)", state, actor->GetIndex(), actor->spr.type);
aiPatrolStop(actor, nullptr);
}
2020-12-06 20:56:09 +00:00
}
2021-08-29 12:36:40 +00:00
//---------------------------------------------------------------------------
//
2020-12-06 20:56:09 +00:00
// check if some dude already follows the given marker
2021-08-29 12:36:40 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
DBloodActor* aiPatrolMarkerBusy(DBloodActor* except, DBloodActor* marker)
2021-08-29 12:36:40 +00:00
{
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatDude);
while (auto actor = it.Next())
{
if (!actor->IsDudeActor() || actor == except || !actor->hasX())
continue;
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
auto targ = actor->GetTarget();
if (actor->xspr.health > 0 && targ != nullptr && targ->spr.type == kMarkerPath && targ == marker)
return actor;
}
return nullptr;
2020-12-06 20:56:09 +00:00
}
2021-08-29 12:36:40 +00:00
//---------------------------------------------------------------------------
//
// check if some dude already follows the given marker
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
bool aiPatrolMarkerReached(DBloodActor* actor)
2021-08-29 12:36:40 +00:00
{
2021-12-29 21:56:21 +00:00
assert(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
const DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[actor->spr.type - kDudeBase];
auto markeractor = actor->GetTarget();
if (markeractor && markeractor->spr.type == kMarkerPath)
{
double okDist = max(markeractor->fClipdist() * 8, 4.);
auto ov = markeractor->spr.pos.XY() - actor->spr.pos.XY(); // this was already shifted right by 4 in the old code.
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
if (ov.Length() <= okDist)
2021-12-29 21:56:21 +00:00
{
if (spriteIsUnderwater(actor) || pExtra->flying)
{
okDist = markeractor->fClipdist() * 16;
double ztop, zbot, ztop2, zbot2;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &ztop, &zbot);
GetActorExtents(markeractor, &ztop2, &zbot2);
2020-12-06 20:56:09 +00:00
double oZ1 = abs(zbot - ztop2);
double oZ2 = abs(ztop - zbot2);
2021-12-29 21:56:21 +00:00
if (oZ1 > okDist && oZ2 > okDist)
return false;
}
return true;
}
}
return false;
2020-12-06 20:56:09 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
DBloodActor* findNextMarker(DBloodActor* mark, bool back)
2021-09-05 17:25:08 +00:00
{
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatPathMarker);
while (auto next = it.Next())
{
if (!next->hasX() || next == mark) continue;
2021-09-05 17:25:08 +00:00
2021-12-29 21:56:21 +00:00
if ((next->xspr.locked || next->xspr.isTriggered || next->xspr.DudeLockout) || (back && next->xspr.data2 != mark->xspr.data1) || (!back && next->xspr.data1 != mark->xspr.data2))
continue;
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
return next;
}
return nullptr;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool markerIsNode(DBloodActor* mark, bool back)
2021-09-05 17:25:08 +00:00
{
2021-12-29 21:56:21 +00:00
int cnt = 0;
2021-09-05 17:25:08 +00:00
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatPathMarker);
while (auto next = it.Next())
{
if (!next->hasX() || next == mark) continue;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if ((next->xspr.locked || next->xspr.isTriggered || next->xspr.DudeLockout) || (back && next->xspr.data2 != mark->xspr.data1) || (!back && next->xspr.data1 != mark->xspr.data2))
continue;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (++cnt > 1)
return true;
}
return false;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-08-29 13:57:42 +00:00
void aiPatrolSetMarker(DBloodActor* actor)
2021-08-29 13:00:32 +00:00
{
2021-12-29 21:56:21 +00:00
auto targetactor = actor->GetTarget();
DBloodActor* selected = nullptr;
int closest = 200000;
// select closest marker that dude can see
if (targetactor == nullptr)
{
2022-08-22 16:37:46 +00:00
double zt1, zb1, zt2, zb2;
int dist;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &zt2, &zb2);
BloodStatIterator it(kStatPathMarker);
while (auto nextactor = it.Next())
{
if (!nextactor->hasX()) continue;
if (nextactor->xspr.locked || nextactor->xspr.isTriggered || nextactor->xspr.DudeLockout || (dist = approxDist(nextactor->spr.pos.XY() - actor->spr.pos.XY())) > closest)
2021-12-29 21:56:21 +00:00
continue;
GetActorExtents(nextactor, &zt1, &zb1);
2022-08-22 16:37:46 +00:00
if (cansee(DVector3(nextactor->spr.pos.XY(), zt1), nextactor->sector(), DVector3(actor->spr.pos.XY(), zt2), actor->sector()))
2021-12-29 21:56:21 +00:00
{
closest = dist;
selected = nextactor;
}
}
}
// set next marker
else if (targetactor->spr.type == kMarkerPath && targetactor->hasX())
{
// idea: which one of next (allowed) markers are closer to the potential target?
// idea: -3 select random next marker that dude can see in radius of reached marker
// if reached marker is in radius of another marker with -3, but greater radius, use that marker
// idea: for nodes only flag32 = specify if enemy must return back to node or allowed to select
// another marker which belongs that node?
DBloodActor* prevactor = nullptr;
DBloodActor* firstFinePath = nullptr;
int next;
int breakChance = 0;
if (actor->prevmarker)
{
prevactor = actor->prevmarker;
}
bool node = markerIsNode(targetactor, false);
actor->xspr.unused2 = aiPatrolGetPathDir(actor, targetactor); // decide if it should go back or forward
if (actor->xspr.unused2 == kPatrolMoveBackward && Chance(0x8000) && node)
actor->xspr.unused2 = kPatrolMoveForward;
bool back = (actor->xspr.unused2 == kPatrolMoveBackward); next = (back) ? targetactor->xspr.data1 : targetactor->xspr.data2;
BloodStatIterator it(kStatPathMarker);
while (auto nextactor = it.Next())
{
if (nextactor == targetactor || !nextactor->hasX()) continue;
2022-08-22 16:31:45 +00:00
else if (actor->xspr.TargetPos.X >= 0 && nextactor == prevactor && node)
2021-12-29 21:56:21 +00:00
{
if (targetactor->xspr.data2 == prevactor->xspr.data1)
continue;
}
if ((nextactor->xspr.locked || nextactor->xspr.isTriggered || nextactor->xspr.DudeLockout) || (back && nextactor->xspr.data2 != next) || (!back && nextactor->xspr.data1 != next))
continue;
if (firstFinePath == nullptr) firstFinePath = nextactor;
if (aiPatrolMarkerBusy(actor, nextactor) && !Chance(0x0010)) continue;
else selected = nextactor;
breakChance += nnExtRandom(1, 5);
if (breakChance >= 5)
break;
}
if (firstFinePath == nullptr)
{
viewSetSystemMessage("No markers with id #%d found for dude #%d! (back = %d)", next, actor->GetIndex(), back);
return;
}
if (selected == nullptr)
selected = firstFinePath;
}
if (!selected)
return;
actor->SetTarget(selected);
selected->SetOwner(actor);
actor->prevmarker = targetactor; // keep previous marker index here, use actual sprite coords when selecting direction
2020-12-06 20:56:09 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-05 17:21:18 +00:00
void aiPatrolStop(DBloodActor* actor, DBloodActor* targetactor, bool alarm)
{
2021-12-29 21:56:21 +00:00
if (actor->hasX())
{
actor->xspr.data3 = 0; // reset spot progress
actor->xspr.unused1 &= ~kDudeFlagCrouch; // reset the crouch status
actor->xspr.unused2 = kPatrolMoveForward; // reset path direction
actor->prevmarker = nullptr;
2022-08-22 16:31:45 +00:00
actor->xspr.TargetPos.X = -1; // reset the previous marker index
2021-12-29 21:56:21 +00:00
if (actor->xspr.health <= 0)
return;
auto mytarget = actor->GetTarget();
if (mytarget && mytarget->spr.type == kMarkerPath)
{
2022-08-16 21:25:11 +00:00
if (targetactor == nullptr) actor->set_int_ang(mytarget->int_ang() & 2047);
2021-12-29 21:56:21 +00:00
actor->SetTarget(nullptr);
}
bool patrol = actor->xspr.dudeFlag4;
actor->xspr.dudeFlag4 = 0;
if (targetactor && targetactor->hasX() && targetactor->IsDudeActor())
{
aiSetTarget(actor, targetactor);
aiActivateDude(actor);
// alarm only when in non-recoil state?
//if (((actor->xspr.unused1 & kDudeFlagStealth) && stype != kAiStateRecoil) || !(actor->xspr.unused1 & kDudeFlagStealth)) {
2022-05-06 16:27:44 +00:00
//if (alarm) aiPatrolAlarmFull(actor, targetactor, Chance(0x0100));
if (alarm) aiPatrolAlarmLite(actor, targetactor);
2021-12-29 21:56:21 +00:00
//}
}
else
{
aiInitSprite(actor);
2022-08-22 16:41:41 +00:00
aiSetTarget(actor, actor->xspr.TargetPos);
2021-12-29 21:56:21 +00:00
}
actor->xspr.dudeFlag4 = patrol; // this must be kept so enemy can patrol after respawn again
}
2020-12-06 20:56:09 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
void aiPatrolRandGoalAng(DBloodActor* actor)
2021-09-05 17:10:50 +00:00
{
2022-09-03 22:38:26 +00:00
DAngle goal = DAngle90;
2021-12-29 21:56:21 +00:00
if (Chance(0x4000))
2022-09-03 22:38:26 +00:00
goal = DAngle360 / 3;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (Chance(0x4000))
2022-09-03 22:38:26 +00:00
goal = DAngle180;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (Chance(0x8000))
goal = -goal;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2022-09-03 22:38:26 +00:00
actor->xspr.goalAng = (actor->spr.angle + goal).Normalized360();
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-09-05 17:10:50 +00:00
void aiPatrolTurn(DBloodActor* actor)
{
DAngle nTurnRange = mapangle((getDudeInfo(actor->spr.type)->angSpeed << 1) >> 4);
2022-09-03 22:38:26 +00:00
DAngle nAng = deltaangle(actor->spr.angle, actor->xspr.goalAng);
actor->spr.angle += clamp(nAng, -nTurnRange, nTurnRange);
2020-12-06 20:56:09 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void aiPatrolMove(DBloodActor* actor)
{
auto targetactor = actor->GetTarget();
if (!(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax) || !targetactor)
return;
int dudeIdx = actor->spr.type - kDudeBase;
switch (actor->spr.type)
{
case kDudeCultistShotgunProne: dudeIdx = kDudeCultistShotgun - kDudeBase; break;
case kDudeCultistTommyProne: dudeIdx = kDudeCultistTommy - kDudeBase; break;
}
DUDEINFO* pDudeInfo = &dudeInfo[dudeIdx];
const DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[dudeIdx];
int dx = (targetactor->int_pos().X - actor->int_pos().X);
int dy = (targetactor->int_pos().Y - actor->int_pos().Y);
int dz = (targetactor->int_pos().Z - (actor->int_pos().Z - pDudeInfo->eyeHeight)) * 6;
2021-12-29 21:56:21 +00:00
int vel = (actor->xspr.unused1 & kDudeFlagCrouch) ? kMaxPatrolCrouchVelocity : kMaxPatrolVelocity;
2022-09-03 22:38:26 +00:00
DAngle goalAng = DAngle180 / 3;
2021-12-29 21:56:21 +00:00
if (pExtra->flying || spriteIsUnderwater(actor))
{
2022-09-03 22:38:26 +00:00
goalAng *= 0.5;
2022-09-01 19:40:03 +00:00
actor->set_int_bvel_z(dz);
2021-12-29 21:56:21 +00:00
if (actor->spr.flags & kPhysGravity)
actor->spr.flags &= ~kPhysGravity;
}
else if (!pExtra->flying)
{
actor->spr.flags |= kPhysGravity | kPhysFalling;
}
2022-09-03 22:38:26 +00:00
DAngle nTurnRange = DAngle::fromBuild((pDudeInfo->angSpeed << 2) >> 4);
DAngle nAng = deltaangle(actor->spr.angle, actor->xspr.goalAng);
actor->spr.angle += clamp(nAng, -nTurnRange, nTurnRange);
2021-12-29 21:56:21 +00:00
if (abs(nAng) > goalAng || ((targetactor->xspr.waitTime > 0 || targetactor->xspr.data1 == targetactor->xspr.data2) && aiPatrolMarkerReached(actor)))
{
actor->ZeroVelocityXY();
2021-12-29 21:56:21 +00:00
return;
}
if (actor->hit.hit.type == kHitSprite)
{
2022-05-06 16:27:44 +00:00
actor->xspr.dodgeDir = Chance(0x5000) ? 1 : -1;
2021-12-29 21:56:21 +00:00
auto hitactor = actor->hit.hit.actor();
2022-05-06 16:27:44 +00:00
if (hitactor)
{
if (hitactor->hasX() && hitactor->xspr.health)
{
hitactor->xspr.dodgeDir = (actor->xspr.dodgeDir > 0) ? -1 : 1;
2022-09-03 08:12:09 +00:00
if (hitactor->vel.X != 0 || hitactor->int_vel().Y)
2022-05-06 16:27:44 +00:00
aiMoveDodge(hitactor);
}
}
aiMoveDodge(actor);
2021-12-29 21:56:21 +00:00
}
else
{
2022-05-06 16:27:44 +00:00
int frontSpeed = pDudeInfo->frontSpeed;
switch (actor->spr.type)
{
case kDudeModernCustom:
case kDudeModernCustomBurning:
frontSpeed = actor->genDudeExtra.moveSpeed;
break;
}
frontSpeed = aiPatrolGetVelocity(pDudeInfo->frontSpeed, targetactor->xspr.busyTime);
2022-09-01 19:02:20 +00:00
actor->add_int_bvel_x(MulScale(frontSpeed, Cos(actor->int_ang()), 30));
2022-09-01 19:05:25 +00:00
actor->add_int_bvel_y(MulScale(frontSpeed, Sin(actor->int_ang()), 30));
2021-12-29 21:56:21 +00:00
}
vel = MulScale(vel, approxDist(dx, dy) << 6, 16);
2022-09-01 19:18:16 +00:00
actor->set_int_bvel_x(ClipRange(actor->int_vel().X, -vel, vel));
2022-09-01 19:24:29 +00:00
actor->set_int_bvel_y(ClipRange(actor->int_vel().Y, -vel, vel));
2020-12-06 20:56:09 +00:00
}
2021-09-05 17:10:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
void aiPatrolAlarmLite(DBloodActor* actor, DBloodActor* targetactor)
2021-08-29 15:07:09 +00:00
{
2021-12-29 21:56:21 +00:00
if (!actor->hasX() || !actor->IsDudeActor())
return;
if (actor->xspr.health <= 0)
return;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2022-08-22 16:37:46 +00:00
double zt1, zb1, zt2, zb2; //int eaz1 = (getDudeInfo(actor->spr.type)->eyeHeight * actor->spr.yrepeat) << 2;
2021-12-29 21:56:21 +00:00
GetActorExtents(actor, &zt1, &zb1);
GetActorExtents(targetactor, &zt2, &zb2);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatDude);
while (auto dudeactor = it.Next())
{
if (dudeactor == actor || !dudeactor->IsDudeActor() || dudeactor->IsPlayerActor() || !dudeactor->hasX())
continue;
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
if (dudeactor->xspr.health <= 0)
continue;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2022-08-22 16:37:46 +00:00
double eaz2 = (getDudeInfo(targetactor->spr.type)->eyeHeight * targetactor->spr.yrepeat) * REPEAT_SCALE;
int nDist = approxDist(dudeactor->spr.pos.XY() - actor->spr.pos.XY());
2022-08-22 16:37:46 +00:00
if (nDist >= kPatrolAlarmSeeDist || !cansee(DVector3(actor->spr.pos, zt1), actor->sector(), dudeactor->spr.pos.plusZ(-eaz2), dudeactor->sector()))
2021-12-29 21:56:21 +00:00
{
nDist = approxDist(dudeactor->spr.pos.XY() - targetactor->spr.pos.XY());
2022-08-22 16:37:46 +00:00
if (nDist >= kPatrolAlarmSeeDist || !cansee(DVector3(targetactor->spr.pos, zt2), targetactor->sector(), dudeactor->spr.pos.plusZ(-eaz2), dudeactor->sector()))
2021-12-29 21:56:21 +00:00
continue;
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (aiInPatrolState(dudeactor->xspr.aiState)) aiPatrolStop(dudeactor, dudeactor->GetTarget());
if (dudeactor->GetTarget() && dudeactor->GetTarget() == actor->GetTarget())
continue;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
aiSetTarget(dudeactor, targetactor);
aiActivateDude(dudeactor);
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-08-29 15:07:09 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
void aiPatrolAlarmFull(DBloodActor* actor, DBloodActor* targetactor, bool chain)
2021-08-29 15:07:09 +00:00
{
2021-12-29 21:56:21 +00:00
if (!actor->hasX() || !actor->IsDudeActor())
return;
if (actor->xspr.health <= 0)
return;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
int eaz2 = (getDudeInfo(actor->spr.type)->eyeHeight * actor->spr.yrepeat) << 2;
int x2 = actor->int_pos().X, y2 = actor->int_pos().Y, z2 = actor->int_pos().Z - eaz2;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
auto pSect2 = actor->sector();
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
int tzt, tzb;
GetActorExtents(targetactor, &tzt, &tzb);
int x3 = targetactor->int_pos().X, y3 = targetactor->int_pos().Y, z3 = tzt;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
auto pSect3 = targetactor->sector();
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
BloodStatIterator it(kStatDude);
while (auto dudeactor = it.Next())
{
if (dudeactor == actor || !dudeactor->IsDudeActor() || dudeactor->IsPlayerActor() || !dudeactor->hasX())
continue;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
if (dudeactor->xspr.health <= 0)
continue;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
int eaz1 = (getDudeInfo(dudeactor->spr.type)->eyeHeight * dudeactor->spr.yrepeat) << 2;
int x1 = dudeactor->int_pos().X, y1 = dudeactor->int_pos().Y, z1 = dudeactor->int_pos().Z - eaz1;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
auto pSect1 = dudeactor->sector();
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
int nDist1 = approxDist(x1 - x2, y1 - y2);
int nDist2 = approxDist(x1 - x3, y1 - y3);
//int hdist = (dudeactor->xspr.dudeDeaf) ? 0 : getDudeInfo(dudeactor->spr.type)->hearDist / 4;
int sdist = (dudeactor->xspr.dudeGuard) ? 0 : getDudeInfo(dudeactor->spr.type)->seeDist / 2;
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
if (//(nDist1 < hdist || nDist2 < hdist) ||
((nDist1 < sdist && cansee(x1, y1, z1, pSect1, x2, y2, z2, pSect2)) || (nDist2 < sdist && cansee(x1, y1, z1, pSect1, x3, y3, z3, pSect3)))) {
if (aiInPatrolState(dudeactor->xspr.aiState)) aiPatrolStop(dudeactor, dudeactor->GetTarget());
if (dudeactor->GetTarget() && dudeactor->GetTarget() == actor->GetTarget())
continue;
if (actor->GetTarget()) aiSetTarget(dudeactor, actor->GetTarget());
2022-08-22 16:41:41 +00:00
else aiSetTarget(dudeactor, actor->spr.pos);
2021-12-29 21:56:21 +00:00
aiActivateDude(dudeactor);
if (chain)
aiPatrolAlarmFull(dudeactor, targetactor, Chance(0x0010));
//Printf("Dude #%d alarms dude #%d", actor->GetIndex(), dudeactor->spr.index);
}
}
2020-12-06 20:56:09 +00:00
}
2021-08-29 15:16:38 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
bool spritesTouching(DBloodActor* actor1, DBloodActor* actor2)
2021-08-29 15:16:38 +00:00
{
2021-12-29 21:56:21 +00:00
if (!actor1->hasX() || !actor2->hasX())
return false;
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
auto hit = &actor1->hit;
DBloodActor* hitactor = nullptr;
if (hit->hit.type == kHitSprite) hitactor = hit->hit.actor();
else if (hit->florhit.type == kHitSprite) hitactor = hit->florhit.actor();
else if (hit->ceilhit.type == kHitSprite) hitactor = hit->ceilhit.actor();
else return false;
return hitactor->hasX() && hitactor == actor2;
2020-12-06 20:56:09 +00:00
}
2021-08-29 15:16:38 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool aiCanCrouch(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (actor->spr.type >= kDudeBase && actor->spr.type < kDudeVanillaMax)
return (gDudeInfoExtra[actor->spr.type - kDudeBase].idlcseqofs >= 0 && gDudeInfoExtra[actor->spr.type - kDudeBase].mvecseqofs >= 0);
else if (actor->spr.type == kDudeModernCustom || actor->spr.type == kDudeModernCustomBurning)
return actor->genDudeExtra.canDuck;
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
return false;
2020-12-06 20:56:09 +00:00
}
2021-08-29 15:16:38 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
bool readyForCrit(DBloodActor* hunter, DBloodActor* victim)
2021-08-29 15:16:38 +00:00
{
2021-12-29 21:56:21 +00:00
if (!(hunter->spr.type >= kDudeBase && hunter->spr.type < kDudeMax) || !(victim->spr.type >= kDudeBase && victim->spr.type < kDudeMax))
return false;
2020-12-06 20:56:09 +00:00
auto dvect = victim->spr.pos.XY() - hunter->spr.pos.XY();
if (approxDist(dvect) >= (7000 / ClipLow(gGameOptions.nDifficulty >> 1, 1)))
2021-12-29 21:56:21 +00:00
return false;
return abs(getincangle(victim->int_ang(), getangle(dvect))) <= kAng45;
2020-12-06 20:56:09 +00:00
}
2021-08-29 15:16:38 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-12-29 21:56:21 +00:00
DBloodActor* aiPatrolSearchTargets(DBloodActor* actor)
{
enum { kMaxPatrolFoundSounds = 256 }; // should be the maximum amount of sound channels the engine can play at the same time.
PATROL_FOUND_SOUNDS patrolBonkles[kMaxPatrolFoundSounds];
assert(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax);
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type); PLAYER* pPlayer = NULL;
for (int i = 0; i < kMaxPatrolFoundSounds; i++)
{
patrolBonkles[i].snd = patrolBonkles[i].cur = 0;
patrolBonkles[i].max = ClipLow((gGameOptions.nDifficulty + 1) >> 1, 1);
}
int i, j, f, mod, x, y, z, dx, dy, nDist, eyeAboveZ, sndCnt = 0, seeDist, hearDist, feelDist, seeChance, hearChance;
bool stealth = (actor->xspr.unused1 & kDudeFlagStealth);
bool blind = (actor->xspr.dudeGuard);
bool deaf = (actor->xspr.dudeDeaf);
DBloodActor* newtarget = nullptr;
// search for player targets
for (i = connecthead; i != -1; i = connectpoint2[i])
{
pPlayer = &gPlayer[i];
if (!pPlayer->actor->hasX()) continue;
auto plActor = pPlayer->actor;
if (plActor->xspr.health <= 0)
continue;
newtarget = nullptr;
seeChance = hearChance = 0x0000;
x = plActor->int_pos().X, y = plActor->int_pos().Y, z = plActor->int_pos().Z,
dx = x - actor->int_pos().X, dy = y - actor->int_pos().Y;
nDist = approxDist(dx, dy);
2021-12-29 21:56:21 +00:00
seeDist = (stealth) ? pDudeInfo->seeDist / 3 : pDudeInfo->seeDist >> 1;
hearDist = pDudeInfo->hearDist; feelDist = hearDist >> 1;
// TO-DO: is there any dudes that sees this patrol dude and sees target?
if (nDist <= seeDist)
{
eyeAboveZ = (pDudeInfo->eyeHeight * actor->spr.yrepeat) << 2;
if (nDist < seeDist >> 3) GetActorExtents(pPlayer->actor, &z, &j); //use ztop of the target sprite
if (!cansee(x, y, z, plActor->sector(), actor->int_pos().X, actor->int_pos().Y, actor->int_pos().Z - eyeAboveZ, actor->sector()))
2021-12-29 21:56:21 +00:00
continue;
}
else
continue;
bool invisible = (powerupCheck(pPlayer, kPwUpShadowCloak) > 0);
if (spritesTouching(actor, pPlayer->actor) || spritesTouching(pPlayer->actor, actor))
{
DPrintf(DMSG_SPAMMY, "Patrol dude #%d spot the Player #%d via touch.", actor->GetIndex(), pPlayer->nPlayer + 1);
if (invisible) pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
newtarget = pPlayer->actor;
break;
}
if (!deaf)
{
soundEngine->EnumerateChannels([&](FSoundChan* chan)
{
int sndx = 0, sndy = 0;
sectortype* searchsect = nullptr;
if (chan->SourceType == SOURCE_Actor)
{
auto emitterActor = (DBloodActor*)chan->Source;
if (emitterActor == nullptr) return false; // not a valid source.
sndx = emitterActor->int_pos().X;
sndy = emitterActor->int_pos().Y;
2021-12-29 21:56:21 +00:00
// sound attached to the sprite
if (pPlayer->actor != emitterActor && emitterActor->GetOwner() != actor)
{
if (!emitterActor->insector()) return false;
searchsect = emitterActor->sector();
2021-12-29 21:56:21 +00:00
}
}
else if (chan->SourceType == SOURCE_Unattached)
{
if (chan->UserData < 0 || !validSectorIndex(chan->UserData)) return false; // not a vaild sector sound.
sndx = int(chan->Point[0] * 16);
sndy = int(chan->Point[1] * -16);
searchsect = &sector[chan->UserData];
}
if (searchsect == nullptr) return false;
int nDist = approxDist(sndx - actor->int_pos().X, sndy - actor->int_pos().Y);
2021-12-29 21:56:21 +00:00
if (nDist > hearDist) return false;
int sndnum = chan->OrgID;
// N same sounds per single enemy
for (int f = 0; f < sndCnt; f++)
{
if (patrolBonkles[f].snd != sndnum) continue;
else if (++patrolBonkles[f].cur >= patrolBonkles[f].max)
return false;
}
if (sndCnt < kMaxPatrolFoundSounds - 1)
patrolBonkles[sndCnt++].snd = sndnum;
bool found = false;
BloodSectIterator it(searchsect);
while (auto act = it.Next())
{
if (act->GetOwner() == pPlayer->actor)
{
found = true;
break;
}
}
if (!found) return false;
f = ClipLow((hearDist - nDist) / 8, 0);
int sndvol = int(chan->Volume * (80.f / 0.8f));
hearChance += mulscale8(sndvol, f) + Random(gGameOptions.nDifficulty);
return (hearChance >= kMaxPatrolSpotValue);
});
2022-05-06 16:27:44 +00:00
/*
2021-12-29 21:56:21 +00:00
if (invisible && hearChance >= kMaxPatrolSpotValue >> 2)
{
newtarget = pPlayer->actor;
pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
invisible = false;
break;
}
2022-05-06 16:27:44 +00:00
*/
2021-12-29 21:56:21 +00:00
}
if (!invisible && (!deaf || !blind))
{
if (stealth)
{
switch (pPlayer->lifeMode)
{
case kModeHuman:
case kModeHumanShrink:
if (pPlayer->lifeMode == kModeHumanShrink)
{
seeDist -= mulscale8(164, seeDist);
feelDist -= mulscale8(164, feelDist);
}
if (pPlayer->posture == kPostureCrouch)
{
seeDist -= mulscale8(64, seeDist);
feelDist -= mulscale8(128, feelDist);
}
break;
case kModeHumanGrown:
if (pPlayer->posture != kPostureCrouch)
{
seeDist += mulscale8(72, seeDist);
feelDist += mulscale8(64, feelDist);
}
else
{
seeDist += mulscale8(48, seeDist);
}
break;
}
}
bool itCanHear = false; bool itCanSee = false;
feelDist = ClipLow(feelDist, 0);
seeDist = ClipLow(seeDist, 0);
if (hearDist)
{
DBloodActor* act = pPlayer->actor;
itCanHear = (!deaf && (nDist < hearDist || hearChance > 0));
2022-09-03 08:12:09 +00:00
if (act && itCanHear && nDist < feelDist && (act->vel.X != 0 || act->vel.Y != 0 || act->int_vel().Z))
2022-09-01 18:49:35 +00:00
hearChance += ClipLow(mulscale8(1, ClipLow(((feelDist - nDist) + (abs(act->int_vel().X) + abs(act->int_vel().Y) + abs(act->int_vel().Z))) >> 6, 0)), 0);
2021-12-29 21:56:21 +00:00
}
if (seeDist)
{
int periphery = ClipLow(pDudeInfo->periphery, kAng60);
int nDeltaAngle = abs(getincangle(actor->int_ang(), getangle(dx, dy)));
2021-12-29 21:56:21 +00:00
if ((itCanSee = (!blind && nDist < seeDist && nDeltaAngle < periphery)) == true)
{
int base = 100 + ((20 * gGameOptions.nDifficulty) - (nDeltaAngle / 5));
//seeChance = base - MulScale(ClipRange(5 - gGameOptions.nDifficulty, 1, 4), nDist >> 1, 16);
//scale(0x40000, a6, dist2);
int d = nDist >> 2;
int m = DivScale(d, 0x2000, 8);
int t = MulScale(d, m, 8);
//int n = mulscale8(nDeltaAngle >> 2, 64);
seeChance = ClipRange(DivScale(base, t, 8), 0, kMaxPatrolSpotValue >> 1);
//seeChance = scale(0x1000, base, t);
//viewSetSystemMessage("SEE CHANCE: %d, BASE %d, DIST %d, T %d", seeChance, base, nDist, t);
//itCanSee = false;
}
}
if (!itCanSee && !itCanHear)
continue;
if (stealth)
{
// search in stealth regions to modify spot chances
BloodStatIterator it(kStatModernStealthRegion);
while (auto steal = it.Next())
{
if (!steal->hasX())
continue;
if (steal->xspr.locked) // ignore locked regions
continue;
bool fixd = (steal->spr.flags & kModernTypeFlag1); // fixed percent value
bool both = (steal->spr.flags & kModernTypeFlag4); // target AND dude must be in this region
bool dude = (both || (steal->spr.flags & kModernTypeFlag2)); // dude must be in this region
bool trgt = (both || !dude); // target must be in this region
bool crouch = (steal->spr.flags & kModernTypeFlag8); // target must crouch
//bool floor = (iactor->spr.cstat & CSTAT_SPRITE_BLOCK); // target (or dude?) must touch floor of the sector
if (trgt)
{
if (steal->xspr.data1 > 0)
{
if ((steal->spr.pos.XY() - plActor->spr.pos.XY()).Length() >= steal->xspr.data1)
2021-12-29 21:56:21 +00:00
continue;
}
else if (plActor->sector() != steal->sector())
2021-12-29 21:56:21 +00:00
continue;
if (crouch && pPlayer->posture == kPostureStand)
continue;
}
if (dude)
{
if (steal->xspr.data1 > 0)
{
if ((steal->spr.pos.XY() - plActor->spr.pos.XY()).Length() >= steal->xspr.data1)
2021-12-29 21:56:21 +00:00
continue;
}
else if (plActor->sector() != steal->sector())
2021-12-29 21:56:21 +00:00
continue;
}
if (itCanHear)
{
if (fixd)
hearChance = ClipLow(hearChance, steal->xspr.data2);
mod = (hearChance * steal->xspr.data2) / kPercFull;
if (fixd) hearChance = mod; else hearChance += mod;
hearChance = ClipRange(hearChance, -kMaxPatrolSpotValue, kMaxPatrolSpotValue);
}
if (itCanSee)
{
if (fixd)
seeChance = ClipLow(seeChance, steal->xspr.data3);
mod = (seeChance * steal->xspr.data3) / kPercFull;
if (fixd) seeChance = mod; else seeChance += mod;
seeChance = ClipRange(seeChance, -kMaxPatrolSpotValue, kMaxPatrolSpotValue);
}
// trigger this region if target gonna be spot
if (steal->xspr.txID && actor->xspr.data3 + hearChance + seeChance >= kMaxPatrolSpotValue)
trTriggerSprite(steal, kCmdToggle, pPlayer->actor);
2021-12-29 21:56:21 +00:00
// continue search another stealth regions to affect chances
}
}
if (itCanHear && hearChance > 0)
{
DPrintf(DMSG_SPAMMY, "Patrol dude #%d hearing the Player #%d.", actor->GetIndex(), pPlayer->nPlayer + 1);
actor->xspr.data3 = ClipRange(actor->xspr.data3 + hearChance, -kMaxPatrolSpotValue, kMaxPatrolSpotValue);
if (!stealth)
{
newtarget = pPlayer->actor;
break;
}
}
if (itCanSee && seeChance > 0)
{
//DPrintf(DMSG_SPAMMY, "Patrol dude #%d seeing the Player #%d.", actor->GetIndex(), pPlayer->nPlayer + 1);
//actor->xspr.data3 += seeChance;
actor->xspr.data3 = ClipRange(actor->xspr.data3 + seeChance, -kMaxPatrolSpotValue, kMaxPatrolSpotValue);
if (!stealth)
{
newtarget = pPlayer->actor;
break;
}
}
}
// add check for corpses?
if ((actor->xspr.data3 = ClipRange(actor->xspr.data3, 0, kMaxPatrolSpotValue)) == kMaxPatrolSpotValue)
{
newtarget = pPlayer->actor;
break;
}
//int perc = (100 * ClipHigh(actor->xspr.data3, kMaxPatrolSpotValue)) / kMaxPatrolSpotValue;
//viewSetSystemMessage("%d / %d / %d / %d", hearChance, seeDist, seeChance, perc);
}
if (newtarget) return newtarget;
actor->xspr.data3 -= ClipLow(((kPercFull * actor->xspr.data3) / kMaxPatrolSpotValue) >> 2, 3);
return nullptr;
2020-12-06 20:56:09 +00:00
}
2021-09-04 08:43:02 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void aiPatrolFlagsMgr(DBloodActor* sourceactor, DBloodActor* destactor, bool copy, bool init)
{
// copy flags
if (copy)
{
destactor->xspr.dudeFlag4 = sourceactor->xspr.dudeFlag4;
destactor->xspr.dudeAmbush = sourceactor->xspr.dudeAmbush;
destactor->xspr.dudeGuard = sourceactor->xspr.dudeGuard;
destactor->xspr.dudeDeaf = sourceactor->xspr.dudeDeaf;
destactor->xspr.unused1 = sourceactor->xspr.unused1;
if (sourceactor->xspr.unused1 & kDudeFlagStealth) destactor->xspr.unused1 |= kDudeFlagStealth;
else destactor->xspr.unused1 &= ~kDudeFlagStealth;
}
// do init
if (init && destactor->IsDudeActor() && !destactor->IsPlayerActor())
2021-12-29 21:56:21 +00:00
{
if (!destactor->xspr.dudeFlag4)
{
if (aiInPatrolState(destactor->xspr.aiState))
aiPatrolStop(destactor, nullptr);
}
else
{
if (aiInPatrolState(destactor->xspr.aiState))
return;
destactor->SetTarget(nullptr);
destactor->xspr.stateTimer = 0;
aiPatrolSetMarker(destactor);
if (spriteIsUnderwater(destactor)) aiPatrolState(destactor, kAiStatePatrolWaitW);
else aiPatrolState(destactor, kAiStatePatrolWaitL);
destactor->xspr.data3 = 0; // reset the spot progress
}
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-04 08:52:12 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool aiPatrolGetPathDir(DBloodActor* actor, DBloodActor* marker)
2021-09-04 08:53:56 +00:00
{
2021-12-29 21:56:21 +00:00
if (actor->xspr.unused2 == kPatrolMoveForward) return (marker->xspr.data2 == -2) ? (bool)kPatrolMoveBackward : (bool)kPatrolMoveForward;
else return (findNextMarker(marker, kPatrolMoveBackward) != nullptr) ? (bool)kPatrolMoveBackward : (bool)kPatrolMoveForward;
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2021-09-04 08:52:12 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
2021-09-04 08:52:12 +00:00
void aiPatrolThink(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
assert(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax);
DBloodActor* targetactor;
2022-05-06 16:27:44 +00:00
unsigned int stateTimer;
2021-12-29 21:56:21 +00:00
auto markeractor = actor->GetTarget();
if ((targetactor = aiPatrolSearchTargets(actor)) != nullptr)
{
aiPatrolStop(actor, targetactor, actor->xspr.dudeAmbush);
return;
}
bool crouch = (actor->xspr.unused1 & kDudeFlagCrouch), uwater = spriteIsUnderwater(actor);
if (markeractor == nullptr || (actor->spr.type == kDudeModernCustom && ((uwater && !canSwim(actor)) || !canWalk(actor))))
{
aiPatrolStop(actor, nullptr);
return;
}
const DUDEINFO_EXTRA* pExtra = &gDudeInfoExtra[actor->spr.type - kDudeBase];
bool isFinal = ((!actor->xspr.unused2 && markeractor->xspr.data2 == -1) || (actor->xspr.unused2 && markeractor->xspr.data1 == -1));
bool reached = false;
if (aiPatrolWaiting(actor->xspr.aiState))
{
//viewSetSystemMessage("WAIT %d / %d", actor->xspr.targetY, actor->xspr.stateTimer);
if (actor->xspr.stateTimer > 0 || markeractor->xspr.data1 == markeractor->xspr.data2)
{
if (pExtra->flying)
actor->vel.Z = Random2F(0x8000);
2021-12-29 21:56:21 +00:00
// turn while waiting
if (markeractor->spr.flags & kModernTypeFlag16)
{
stateTimer = actor->xspr.stateTimer;
if (--actor->xspr.unused4 <= 0)
{
if (uwater) aiPatrolState(actor, kAiStatePatrolTurnW);
else if (crouch) aiPatrolState(actor, kAiStatePatrolTurnC);
else aiPatrolState(actor, kAiStatePatrolTurnL);
actor->xspr.unused4 = kMinPatrolTurnDelay + Random(kPatrolTurnDelayRange);
}
// must restore stateTimer for waiting
actor->xspr.stateTimer = stateTimer;
}
return;
}
// trigger at departure
if (markeractor->xspr.triggerOff)
{
// send command
if (markeractor->xspr.txID)
{
evSendActor(markeractor, markeractor->xspr.txID, (COMMAND_ID)markeractor->xspr.command, actor);
2021-12-29 21:56:21 +00:00
// copy dude flags for current dude
}
else if (markeractor->xspr.command == kCmdDudeFlagsSet)
{
aiPatrolFlagsMgr(markeractor, actor, true, true);
if (!actor->xspr.dudeFlag4) // this dude is not in patrol anymore
return;
}
}
// release the enemy
if (isFinal)
{
aiPatrolStop(actor, nullptr);
return;
}
// move next marker
aiPatrolSetMarker(actor);
}
else if (aiPatrolTurning(actor->xspr.aiState))
{
//viewSetSystemMessage("TURN");
2022-09-03 22:38:26 +00:00
if (absangle(actor->spr.angle, actor->xspr.goalAng) < minAngle)
2021-12-29 21:56:21 +00:00
{
// save imer for waiting
stateTimer = actor->xspr.stateTimer;
if (uwater) aiPatrolState(actor, kAiStatePatrolWaitW);
else if (crouch) aiPatrolState(actor, kAiStatePatrolWaitC);
else aiPatrolState(actor, kAiStatePatrolWaitL);
// must restore it
actor->xspr.stateTimer = stateTimer;
}
return;
}
else if ((reached = aiPatrolMarkerReached(actor)) == true)
{
markeractor->xspr.isTriggered = markeractor->xspr.triggerOnce; // can't select this marker for path anymore if true
if (markeractor->spr.flags > 0)
{
if ((markeractor->spr.flags & kModernTypeFlag2) && (markeractor->spr.flags & kModernTypeFlag1)) crouch = !crouch;
else if (markeractor->spr.flags & kModernTypeFlag2) crouch = false;
else if ((markeractor->spr.flags & kModernTypeFlag1) && aiCanCrouch(actor)) crouch = true;
}
if (markeractor->xspr.waitTime > 0 || markeractor->xspr.data1 == markeractor->xspr.data2)
{
// take marker's angle
if (!(markeractor->spr.flags & kModernTypeFlag4))
{
2022-09-03 22:38:26 +00:00
actor->xspr.goalAng = ((!(markeractor->spr.flags & kModernTypeFlag8) && actor->xspr.unused2) ? markeractor->spr.angle+ DAngle180 : markeractor->spr.angle).Normalized360();
if (absangle(actor->spr.angle, actor->xspr.goalAng) > minAngle) // let the enemy play move animation while turning
2021-12-29 21:56:21 +00:00
return;
}
if (markeractor->GetOwner() == actor)
markeractor->SetOwner(aiPatrolMarkerBusy(actor, markeractor));
// trigger at arrival
if (markeractor->xspr.triggerOn)
{
// send command
if (markeractor->xspr.txID)
{
evSendActor(markeractor, markeractor->xspr.txID, (COMMAND_ID)markeractor->xspr.command, actor);
2021-12-29 21:56:21 +00:00
}
else if (markeractor->xspr.command == kCmdDudeFlagsSet)
{
// copy dude flags for current dude
aiPatrolFlagsMgr(markeractor, actor, true, true);
if (!actor->xspr.dudeFlag4) // this dude is not in patrol anymore
return;
}
}
if (uwater) aiPatrolState(actor, kAiStatePatrolWaitW);
else if (crouch) aiPatrolState(actor, kAiStatePatrolWaitC);
else aiPatrolState(actor, kAiStatePatrolWaitL);
if (markeractor->xspr.waitTime)
actor->xspr.stateTimer = (markeractor->xspr.waitTime * 120) / 10;
if (markeractor->spr.flags & kModernTypeFlag16)
actor->xspr.unused4 = kMinPatrolTurnDelay + Random(kPatrolTurnDelayRange);
return;
}
else
{
if (markeractor->GetOwner() == actor)
markeractor->SetOwner(aiPatrolMarkerBusy(actor, markeractor));
if (markeractor->xspr.triggerOn || markeractor->xspr.triggerOff)
{
if (markeractor->xspr.txID)
{
// send command at arrival
if (markeractor->xspr.triggerOn)
evSendActor(markeractor, markeractor->xspr.txID, (COMMAND_ID)markeractor->xspr.command, actor);
2021-12-29 21:56:21 +00:00
// send command at departure
if (markeractor->xspr.triggerOff)
evSendActor(markeractor, markeractor->xspr.txID, (COMMAND_ID)markeractor->xspr.command, actor);
2021-12-29 21:56:21 +00:00
// copy dude flags for current dude
}
else if (markeractor->xspr.command == kCmdDudeFlagsSet)
{
aiPatrolFlagsMgr(markeractor, actor, true, true);
if (!actor->xspr.dudeFlag4) // this dude is not in patrol anymore
return;
}
}
// release the enemy
if (isFinal)
{
aiPatrolStop(actor, nullptr);
return;
}
// move the next marker
aiPatrolSetMarker(actor);
}
}
2022-09-03 22:38:26 +00:00
nnExtAiSetDirection(actor, VecToAngle(markeractor->spr.pos - actor->spr.pos));
2021-12-29 21:56:21 +00:00
if (aiPatrolMoving(actor->xspr.aiState) && !reached) return;
else if (uwater) aiPatrolState(actor, kAiStatePatrolMoveW);
else if (crouch) aiPatrolState(actor, kAiStatePatrolMoveC);
else aiPatrolState(actor, kAiStatePatrolMoveL);
return;
2020-12-06 20:56:09 +00:00
}
2021-08-29 17:09:29 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-12-06 20:56:09 +00:00
2021-12-29 21:56:21 +00:00
int listTx(DBloodActor* actor, int tx)
{
if (txIsRanged(actor))
{
if (tx == -1) tx = actor->xspr.data1;
else if (tx < actor->xspr.data4) tx++;
else tx = -1;
}
else
{
if (tx == -1)
{
for (int i = 0; i <= 3; i++)
{
if ((tx = GetDataVal(actor, i)) <= 0) continue;
else return tx;
}
}
else
{
int saved = tx; bool savedFound = false;
for (int i = 0; i <= 3; i++)
{
tx = GetDataVal(actor, i);
if (savedFound && tx > 0) return tx;
else if (tx != saved) continue;
else savedFound = true;
}
}
tx = -1;
}
return tx;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
DBloodActor* evrIsRedirector(DBloodActor* actor)
2021-09-01 18:20:10 +00:00
{
2021-12-29 21:56:21 +00:00
if (actor)
{
switch (actor->spr.type)
{
case kModernRandomTX:
case kModernSequentialTX:
if (actor->hasX() && actor->xspr.command == kCmdLink && !actor->xspr.locked) return actor;
break;
}
}
2021-12-29 21:56:21 +00:00
return nullptr;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
DBloodActor* evrListRedirectors(int objType, sectortype* pSector, walltype* pWall, DBloodActor* objActor, DBloodActor* pXRedir, int* tx)
{
if (!gEventRedirectsUsed) return nullptr;
else if (pXRedir && (*tx = listTx(pXRedir, *tx)) != -1)
return pXRedir;
int id = 0;
switch (objType)
{
case OBJ_SECTOR:
{
if (!pSector->hasX()) return nullptr;
id = pSector->xs().txID;
break;
}
case OBJ_SPRITE:
if (!objActor) return nullptr;
id = objActor->xspr.txID;
break;
case OBJ_WALL:
{
if (!pWall->hasX()) return nullptr;
id = pWall->xw().txID;
break;
}
default:
return nullptr;
}
bool prevFound = false;
for (int i = bucketHead[id]; i < bucketHead[id + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto rxactor = rxBucket[i].actor();
auto pXSpr = evrIsRedirector(rxactor);
if (!pXSpr) continue;
else if (prevFound || pXRedir == nullptr)
{
*tx = listTx(pXSpr, *tx);
return pXSpr;
}
else if (pXRedir != pXSpr) continue;
else prevFound = true;
}
*tx = -1;
return NULL;
}
//---------------------------------------------------------------------------
//
// this function checks if all TX objects have the same value
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
bool incDecGoalValueIsReached(DBloodActor* actor)
{
2021-12-29 21:56:21 +00:00
if (actor->xspr.data3 != actor->xspr.sysData1) return false;
char buffer[7];
snprintf(buffer, 7, "%d", abs(actor->xspr.data1));
int len = int(strlen(buffer));
int rx = -1;
2021-12-29 21:56:21 +00:00
for (int i = bucketHead[actor->xspr.txID]; i < bucketHead[actor->xspr.txID + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto rxactor = rxBucket[i].actor();
2021-12-29 21:56:21 +00:00
if (evrIsRedirector(rxactor)) continue;
for (int a = 0; a < len; a++)
{
if (getDataFieldOfObject(rxBucket[i], (buffer[a] - 52) + 4) != actor->xspr.data3)
return false;
}
}
2021-12-29 21:56:21 +00:00
DBloodActor* pXRedir = nullptr; // check redirected TX buckets
while ((pXRedir = evrListRedirectors(OBJ_SPRITE, nullptr, nullptr, actor, pXRedir, &rx)) != nullptr)
{
for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++)
{
for (int a = 0; a < len; a++)
{
if (getDataFieldOfObject(rxBucket[i], (buffer[a] - 52) + 4) != actor->xspr.data3)
return false;
}
}
}
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void seqSpawnerOffSameTx(DBloodActor* actor)
{
for (auto&sect : sector)
2021-12-29 21:56:21 +00:00
{
BloodSectIterator it(&sect);
while (auto iactor = it.Next())
2021-12-29 21:56:21 +00:00
{
if (iactor->spr.type != kModernSeqSpawner || !iactor->hasX() || iactor == actor) continue;
if (/*iactor->xspr.txID == actor->xspr.txID &&*/ iactor->xspr.state == 1)
{
evKillActor(iactor);
iactor->xspr.state = 0;
}
2021-12-29 21:56:21 +00:00
}
}
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
}
2021-08-29 17:09:29 +00:00
//---------------------------------------------------------------------------
//
// this function can be called via sending numbered command to TX kChannelModernEndLevelCustom
// it allows to set custom next level instead of taking it from INI file.
2021-08-29 17:09:29 +00:00
//
//---------------------------------------------------------------------------
2021-12-29 21:56:21 +00:00
void levelEndLevelCustom(int nLevel)
2021-08-29 17:09:29 +00:00
{
2021-12-29 21:56:21 +00:00
gGameOptions.uGameFlags |= GF_AdvanceLevel;
gNextLevel = FindMapByIndex(currentLevel->cluster, nLevel + 1);
}
2021-08-29 17:09:29 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-11-24 00:05:17 +00:00
void callbackUniMissileBurst(DBloodActor* actor, sectortype*) // 22
{
2021-12-29 21:56:21 +00:00
if (!actor) return;
if (actor->spr.statnum != kStatProjectile) return;
auto nAngVec = actor->vel.XY().Angle().ToVector();
double nRadius = FixedToFloat(0x55555);
2021-12-29 21:56:21 +00:00
for (int i = 0; i < 8; i++)
{
auto burstactor = actSpawnSprite(actor, 5);
if (!burstactor) break;
2021-12-29 21:56:21 +00:00
burstactor->spr.type = actor->spr.type;
burstactor->spr.shade = actor->spr.shade;
burstactor->spr.picnum = actor->spr.picnum;
2021-12-29 21:56:21 +00:00
burstactor->spr.cstat = actor->spr.cstat;
if ((burstactor->spr.cstat & CSTAT_SPRITE_BLOCK))
{
burstactor->spr.cstat &= ~CSTAT_SPRITE_BLOCK; // we don't want missiles impact each other
evPostActor(burstactor, 100, kCallbackMissileSpriteBlock); // so set blocking flag a bit later
}
2021-12-29 21:56:21 +00:00
burstactor->spr.pal = actor->spr.pal;
2022-09-09 16:32:42 +00:00
burstactor->set_native_clipdist(actor->native_clipdist() / 4);
2021-12-29 21:56:21 +00:00
burstactor->spr.flags = actor->spr.flags;
burstactor->spr.xrepeat = actor->spr.xrepeat / 2;
burstactor->spr.yrepeat = actor->spr.yrepeat / 2;
2022-08-16 21:20:03 +00:00
burstactor->set_int_ang(((actor->int_ang() + missileInfo[actor->spr.type - kMissileBase].angleOfs) & 2047));
2021-12-29 21:56:21 +00:00
burstactor->SetOwner(actor);
2021-12-29 21:56:21 +00:00
actBuildMissile(burstactor, actor);
auto spAngVec = DAngle::fromBam(i << 29).ToVector().Rotated90CW() * nRadius;
if (i & 1) spAngVec *= 0.5;
burstactor->vel += DVector3(DVector2(0, spAngVec.X).Rotated(nAngVec.X, nAngVec.Y), spAngVec.Y);
2021-12-29 21:56:21 +00:00
evPostActor(burstactor, 960, kCallbackRemove);
}
evPostActor(actor, 0, kCallbackRemove);
}
2021-08-29 17:09:29 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-11-24 00:05:17 +00:00
void callbackMakeMissileBlocking(DBloodActor* actor, sectortype*) // 23
{
2021-12-29 21:56:21 +00:00
if (!actor || actor->spr.statnum != kStatProjectile) return;
actor->spr.cstat |= CSTAT_SPRITE_BLOCK;
}
2021-11-24 00:05:17 +00:00
void callbackGenDudeUpdate(DBloodActor* actor, sectortype*) // 24
{
2021-12-29 21:56:21 +00:00
if (actor)
genDudeUpdate(actor);
}
2021-12-29 21:56:21 +00:00
void clampSprite(DBloodActor* actor, int which)
2021-08-29 17:09:29 +00:00
{
2022-09-16 16:21:49 +00:00
double zTop, zBot;
if (actor->insector())
2021-12-29 21:56:21 +00:00
{
GetActorExtents(actor, &zTop, &zBot);
if (which & 0x01)
actor->spr.pos.Z += min(getflorzofslopeptr(actor->sector(), actor->spr.pos) - zBot, 0.);
2021-12-29 21:56:21 +00:00
if (which & 0x02)
actor->spr.pos.Z += max(getceilzofslopeptr(actor->sector(), actor->spr.pos) - zTop, 0.);
2021-12-29 21:56:21 +00:00
}
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void killEvents(int nRx, int nCmd)
{
for (int i = bucketHead[nRx]; i < bucketHead[nRx + 1]; i++)
{
if (nCmd == kCmdEventKillFull)
evKill_(rxBucket[i]);
}
}
void triggerTouchSprite(DBloodActor* actor, DBloodActor* hActor)
{
if (hActor && hActor->hasX())
{
if (hActor->xspr.Touch && !hActor->xspr.isTriggered && (!hActor->xspr.DudeLockout || actor->IsPlayerActor()))
trTriggerSprite(hActor, kCmdSpriteTouch, actor);
// enough to reset gSpriteHit values
2022-09-01 19:02:20 +00:00
actor->add_int_bvel_x(5);
}
}
void triggerTouchWall(DBloodActor* actor, walltype* pHWall)
{
if (pHWall && pHWall->hasX())
{
if (pHWall->xw().triggerTouch && !pHWall->xw().isTriggered && (!pHWall->xw().dudeLockout || actor->IsPlayerActor()))
trTriggerWall(pHWall, kCmdWallTouch, actor);
// enough to reset gSpriteHit values
2022-09-01 19:02:20 +00:00
actor->add_int_bvel_x(5);
}
}
void changeSpriteAngle(DBloodActor* pSpr, int nAng)
{
if (!pSpr->IsDudeActor())
2022-08-16 21:20:03 +00:00
pSpr->set_int_ang(nAng);
else
{
PLAYER* pPlayer = getPlayerById(pSpr->spr.type);
if (pPlayer)
pPlayer->angle.ang = DAngle::fromBuild(nAng);
else
{
2022-08-16 21:20:03 +00:00
pSpr->set_int_ang(nAng);
if (pSpr->hasX())
2022-09-03 22:38:26 +00:00
pSpr->xspr.goalAng = pSpr->spr.angle;
}
}
}
int getVelocityAngle(DBloodActor* pSpr)
{
2022-09-01 18:43:35 +00:00
return getangle(pSpr->int_vel().X >> 12, pSpr->int_vel().Y >> 12);
}
#if 0
bool xsprIsFine(DBloodActor* pSpr)
{
if (pSpr && pSpr->hasX() && !(pSpr->spr.flags & kHitagFree))
{
if (!(pSpr->spr.flags & kHitagRespawn) || (pSpr->spr.statnum != kStatThing && pSpr->spr.statnum != kStatDude))
return true;
}
return false;
}
#endif
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
FSerializer& Serialize(FSerializer& arc, const char* keyname, GENDUDEEXTRA& w, GENDUDEEXTRA* def)
{
2021-12-29 21:56:21 +00:00
if (arc.BeginObject(keyname))
{
arc.Array("initvals", w.initVals, 3)
.Array("availdeaths", w.availDeaths, kDamageMax)
("movespeed", w.moveSpeed)
("firedist", w.fireDist)
("throwdist", w.throwDist)
("curweapon", w.curWeapon)
("weapontype", w.weaponType)
("basedispersion", w.baseDispersion)
("slavecount", w.slaveCount)
("lifeleech", w.pLifeLeech)
.Array("slaves", w.slave, w.slaveCount)
.Array("dmgcontrol", w.dmgControl, kDamageMax)
.Array("updreq", w.updReq, kGenDudePropertyMax)
("flags", w.flags)
.EndObject();
}
return arc;
}
FSerializer& Serialize(FSerializer& arc, const char* keyname, SPRITEMASS& w, SPRITEMASS* def)
{
2021-12-29 21:56:21 +00:00
static SPRITEMASS nul;
if (arc.isReading()) w = {};
if (arc.BeginObject(keyname))
{
arc("seq", w.seqId, &nul.seqId)
("picnum", w.picnum, &nul.picnum)
("xrepeat", w.xrepeat, &nul.xrepeat)
("yrepeat", w.yrepeat, &nul.yrepeat)
("clipdist", w.clipdist)
("mass", w.mass)
("airvel", w.airVel)
("fraction", w.fraction)
.EndObject();
}
return arc;
}
FSerializer& Serialize(FSerializer& arc, const char* keyname, OBJECTS_TO_TRACK& w, OBJECTS_TO_TRACK* def)
{
2021-12-29 21:56:21 +00:00
static OBJECTS_TO_TRACK nul;
if (arc.isReading()) w = {};
if (arc.BeginObject(keyname))
{
arc("obj", w.obj, &nul.obj)
("cmd", w.cmd, &nul.cmd)
.EndObject();
}
return arc;
}
FSerializer& Serialize(FSerializer& arc, const char* keyname, TRCONDITION& w, TRCONDITION* def)
{
2021-12-29 21:56:21 +00:00
static TRCONDITION nul;
if (arc.isReading()) w = {};
if (arc.BeginObject(keyname))
{
arc("length", w.length, &nul.length)
("xindex", w.actor, &nul.actor)
.Array("obj", w.obj, w.length)
.EndObject();
}
return arc;
}
void SerializeNNExts(FSerializer& arc)
{
2021-12-29 21:56:21 +00:00
if (arc.BeginObject("nnexts"))
{
arc("proxyspritescount", gProxySpritesCount)
.Array("proxyspriteslist", gProxySpritesList, gProxySpritesCount)
("sightspritescount", gSightSpritesCount)
.Array("sightspriteslist", gSightSpritesList, gSightSpritesCount)
("physspritescount", gPhysSpritesCount)
.Array("physspriteslist", gPhysSpritesList, gPhysSpritesCount)
("impactspritescount", gImpactSpritesCount)
.Array("impactspriteslist", gImpactSpritesList, gImpactSpritesCount)
("eventredirects", gEventRedirectsUsed)
("trconditioncount", gTrackingCondsCount)
.Array("trcondition", gCondition, gTrackingCondsCount);
gSprNSect.Serialize(arc);
arc.EndObject();
2021-12-29 21:56:21 +00:00
}
}
///////////////////////////////////////////////////////////////////
// This file provides modern features for mappers.
// For full documentation please visit http://cruo.bloodgame.ru/xxsystem
///////////////////////////////////////////////////////////////////
END_BLD_NS
#endif