mirror of
https://github.com/DrBeef/Raze.git
synced 2024-12-01 00:21:31 +00:00
ac40e86ab2
# Conflicts: # source/games/blood/src/actor.cpp # Conflicts: # source/games/blood/src/actor.cpp
7386 lines
181 KiB
C++
7386 lines
181 KiB
C++
//-------------------------------------------------------------------------
|
|
/*
|
|
Copyright (C) 2010-2019 EDuke32 developers and contributors
|
|
Copyright (C) 2019 Nuke.YKT
|
|
|
|
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.
|
|
*/
|
|
//-------------------------------------------------------------------------
|
|
//#include <iostream>
|
|
|
|
#include "ns.h" // Must come before everything else!
|
|
|
|
|
|
#include "build.h"
|
|
#include "automap.h"
|
|
#include "savegamehelp.h"
|
|
|
|
#include "blood.h"
|
|
|
|
BEGIN_BLD_NS
|
|
|
|
static DBloodActor* actDropObject(DBloodActor* actor, int nType);
|
|
|
|
|
|
VECTORDATA gVectorData[] = { // this is constant EXCEPT for [VECTOR_TYPE_20].maxDist. What were they thinking...
|
|
|
|
// Tine
|
|
{
|
|
kDamageBullet,
|
|
17,
|
|
174762,
|
|
1152,
|
|
10240,
|
|
0,
|
|
1,
|
|
20480,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_43, FX_6, FX_NONE, 502,
|
|
FX_43, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_7, 502,
|
|
FX_43, FX_6, FX_7, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, 503,
|
|
FX_43, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 503,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Shell
|
|
{
|
|
kDamageBullet,
|
|
4,
|
|
65536,
|
|
0,
|
|
8192,
|
|
0,
|
|
1,
|
|
12288,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_5, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_43, FX_6, FX_NONE, -1,
|
|
FX_43, FX_0, FX_NONE, -1,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_43, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Bullet
|
|
{
|
|
kDamageBullet,
|
|
7,
|
|
21845,
|
|
0,
|
|
32768,
|
|
0,
|
|
1,
|
|
12288,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_5, FX_7, 510,
|
|
FX_NONE, FX_5, FX_7, 511,
|
|
FX_43, FX_6, FX_NONE, 512,
|
|
FX_43, FX_0, FX_NONE, 513,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_7, 512,
|
|
FX_43, FX_6, FX_7, 512,
|
|
FX_NONE, FX_NONE, FX_NONE, 513,
|
|
FX_43, FX_NONE, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
|
|
},
|
|
|
|
// Tommy AP
|
|
{
|
|
kDamageBullet,
|
|
20,
|
|
65536,
|
|
0,
|
|
16384,
|
|
0,
|
|
1,
|
|
20480,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_5, FX_7, 510,
|
|
FX_NONE, FX_5, FX_7, 511,
|
|
FX_43, FX_6, FX_NONE, 512,
|
|
FX_43, FX_0, FX_NONE, 513,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_7, 512,
|
|
FX_43, FX_6, FX_7, 512,
|
|
FX_NONE, FX_NONE, FX_NONE, 513,
|
|
FX_43, FX_NONE, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Shell AP
|
|
{
|
|
kDamageBullet,
|
|
6,
|
|
87381,
|
|
0,
|
|
12288,
|
|
0,
|
|
1,
|
|
6144,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_5, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_43, FX_6, FX_NONE, -1,
|
|
FX_43, FX_0, FX_NONE, -1,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_43, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Tommy regular
|
|
{
|
|
kDamageBullet,
|
|
12,
|
|
65536,
|
|
0,
|
|
16384,
|
|
0,
|
|
1,
|
|
12288,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_43, FX_5, FX_7, 510,
|
|
FX_NONE, FX_5, FX_7, 511,
|
|
FX_43, FX_6, FX_NONE, 512,
|
|
FX_43, FX_0, FX_NONE, 513,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_7, 512,
|
|
FX_43, FX_6, FX_7, 512,
|
|
FX_NONE, FX_NONE, FX_NONE, 513,
|
|
FX_43, FX_NONE, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_6, FX_NONE, 513,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Bat bite
|
|
{
|
|
kDamageBullet,
|
|
4,
|
|
0,
|
|
921,
|
|
0,
|
|
0,
|
|
1,
|
|
4096,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Eel bite
|
|
{
|
|
kDamageBullet,
|
|
12,
|
|
0,
|
|
1177,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Gill bite
|
|
{
|
|
kDamageBullet,
|
|
9,
|
|
0,
|
|
1177,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Beast slash
|
|
{
|
|
kDamageExplode,
|
|
50,
|
|
43690,
|
|
1024,
|
|
8192,
|
|
0,
|
|
4,
|
|
32768,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Axe
|
|
{
|
|
kDamageBullet,
|
|
18,
|
|
436906,
|
|
1024,
|
|
16384,
|
|
0,
|
|
2,
|
|
20480,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Cleaver
|
|
{
|
|
kDamageBullet,
|
|
9,
|
|
218453,
|
|
1024,
|
|
0,
|
|
0,
|
|
1,
|
|
24576,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Phantasm slash
|
|
{
|
|
kDamageBullet,
|
|
20,
|
|
436906,
|
|
1024,
|
|
16384,
|
|
0,
|
|
3,
|
|
24576,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Gargoyle Slash
|
|
{
|
|
kDamageBullet,
|
|
16,
|
|
218453,
|
|
1024,
|
|
8192,
|
|
0,
|
|
4,
|
|
20480,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
},
|
|
|
|
// Cerberus bite
|
|
{
|
|
kDamageBullet,
|
|
19,
|
|
218453,
|
|
614,
|
|
8192,
|
|
0,
|
|
2,
|
|
24576,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Hound bite
|
|
{
|
|
kDamageBullet,
|
|
10,
|
|
218453,
|
|
614,
|
|
8192,
|
|
0,
|
|
2,
|
|
24576,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Rat bite
|
|
{
|
|
kDamageBullet,
|
|
4,
|
|
0,
|
|
921,
|
|
0,
|
|
0,
|
|
1,
|
|
24576,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Spider bite
|
|
{
|
|
kDamageBullet,
|
|
8,
|
|
0,
|
|
614,
|
|
0,
|
|
0,
|
|
1,
|
|
24576,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Unk
|
|
{
|
|
kDamageBullet,
|
|
9,
|
|
0,
|
|
512,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_5, FX_NONE, 500,
|
|
FX_NONE, FX_5, FX_NONE, 501,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_0, FX_NONE, 503,
|
|
FX_NONE, FX_4, FX_NONE, -1,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_6, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 502,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
{
|
|
(DAMAGE_TYPE)-1,
|
|
0,
|
|
0,
|
|
2560,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_34, FX_35, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Tchernobog burn vector
|
|
{
|
|
kDamageBurn,
|
|
2,
|
|
0,
|
|
0,
|
|
0,
|
|
15,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// Vodoo 1.0 vector
|
|
{
|
|
kDamageSpirit,
|
|
25,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
},
|
|
|
|
// 22 kVectorGenDudePunch
|
|
{
|
|
kDamageFall,
|
|
37,
|
|
874762,
|
|
620,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
FX_NONE, FX_NONE, FX_NONE, -1,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
FX_NONE, FX_NONE, FX_NONE, 357,
|
|
},
|
|
};
|
|
|
|
const ITEMDATA gItemData[] = {
|
|
{
|
|
0,
|
|
2552,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2553,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2554,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2555,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2556,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2557,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2558,
|
|
-8,
|
|
0,
|
|
32,
|
|
32,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
519,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
0,
|
|
},
|
|
{
|
|
0,
|
|
822,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2169,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2433,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
517,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
783,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
896,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
825,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
827,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
4,
|
|
},
|
|
{
|
|
0,
|
|
828,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
829,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
830,
|
|
-8,
|
|
0,
|
|
80,
|
|
64,
|
|
1,
|
|
},
|
|
{
|
|
0,
|
|
831,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
863,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
760,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
2,
|
|
},
|
|
{
|
|
0,
|
|
836,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
851,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2428,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
839,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
3,
|
|
},
|
|
{
|
|
0,
|
|
768,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
840,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
841,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
842,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
843,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
683,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
521,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
604,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
520,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
803,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
518,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
522,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
523,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
837,
|
|
-8,
|
|
0,
|
|
80,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2628,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2586,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2578,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2602,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
2594,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
753,
|
|
-8,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
753,
|
|
-8,
|
|
7,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
3558,
|
|
-128,
|
|
0,
|
|
64,
|
|
64,
|
|
-1,
|
|
},
|
|
{
|
|
0,
|
|
3558,
|
|
-128,
|
|
7,
|
|
64,
|
|
64,
|
|
-1,
|
|
}
|
|
};
|
|
|
|
const AMMOITEMDATA gAmmoItemData[] = {
|
|
{
|
|
0,
|
|
618,
|
|
-8,
|
|
0,
|
|
40,
|
|
40,
|
|
480,
|
|
6,
|
|
7
|
|
},
|
|
{
|
|
0,
|
|
589,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
1,
|
|
5,
|
|
6
|
|
},
|
|
{
|
|
0,
|
|
589,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
1,
|
|
5,
|
|
6
|
|
},
|
|
{
|
|
0,
|
|
809,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
5,
|
|
5,
|
|
6
|
|
},
|
|
{
|
|
0,
|
|
811,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
1,
|
|
10,
|
|
11
|
|
},
|
|
{
|
|
0,
|
|
810,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
1,
|
|
11,
|
|
12
|
|
},
|
|
{
|
|
0,
|
|
820,
|
|
-8,
|
|
0,
|
|
24,
|
|
24,
|
|
10,
|
|
8,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
619,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
4,
|
|
2,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
812,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
15,
|
|
2,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
813,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
15,
|
|
3,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
525,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
100,
|
|
9,
|
|
10
|
|
},
|
|
{
|
|
0,
|
|
814,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
15,
|
|
255,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
817,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
100,
|
|
3,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
548,
|
|
-8,
|
|
0,
|
|
24,
|
|
24,
|
|
32,
|
|
7,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
0,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
6,
|
|
255,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
0,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
6,
|
|
255,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
816,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
8,
|
|
1,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
818,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
8,
|
|
255,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
819,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
8,
|
|
255,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
801,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
6,
|
|
4,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0
|
|
},
|
|
};
|
|
|
|
const WEAPONITEMDATA gWeaponItemData[] = {
|
|
{
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
-1,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
559,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
3,
|
|
2,
|
|
8
|
|
},
|
|
{
|
|
0,
|
|
558,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
4,
|
|
3,
|
|
50
|
|
},
|
|
{
|
|
0,
|
|
524,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
2,
|
|
1,
|
|
9
|
|
},
|
|
{
|
|
0,
|
|
525,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
10,
|
|
9,
|
|
100
|
|
},
|
|
{
|
|
0,
|
|
539,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
8,
|
|
7,
|
|
64
|
|
},
|
|
{
|
|
0,
|
|
526,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
5,
|
|
4,
|
|
6
|
|
},
|
|
{
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
-1,
|
|
0
|
|
},
|
|
{
|
|
0,
|
|
618,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
7,
|
|
6,
|
|
480
|
|
},
|
|
{
|
|
0,
|
|
589,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
6,
|
|
5,
|
|
1
|
|
},
|
|
{
|
|
0,
|
|
800,
|
|
-8,
|
|
0,
|
|
48,
|
|
48,
|
|
9,
|
|
8,
|
|
35
|
|
}
|
|
};
|
|
|
|
const MissileType missileInfo[] = {
|
|
// Cleaver
|
|
{
|
|
2138,
|
|
978670,
|
|
512,
|
|
40,
|
|
40,
|
|
-16,
|
|
16,
|
|
},
|
|
// Regular flare
|
|
{
|
|
2424,
|
|
3145728,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
32,
|
|
},
|
|
// Tesla alt
|
|
{
|
|
3056,
|
|
2796202,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
32,
|
|
},
|
|
// Flare alt
|
|
{
|
|
2424,
|
|
2446677,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
4,
|
|
},
|
|
// Spray flame
|
|
{
|
|
0,
|
|
1118481,
|
|
0,
|
|
24,
|
|
24,
|
|
-128,
|
|
16,
|
|
},
|
|
// Fireball
|
|
{
|
|
0,
|
|
1118481,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
32,
|
|
},
|
|
// Tesla regular
|
|
{
|
|
2130,
|
|
2796202,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
16,
|
|
},
|
|
// EctoSkull
|
|
{
|
|
870,
|
|
699050,
|
|
0,
|
|
32,
|
|
32,
|
|
-24,
|
|
32,
|
|
},
|
|
// Hellhound flame
|
|
{
|
|
0,
|
|
1118481,
|
|
0,
|
|
24,
|
|
24,
|
|
-128,
|
|
16,
|
|
},
|
|
// Puke
|
|
{
|
|
0,
|
|
838860,
|
|
0,
|
|
16,
|
|
16,
|
|
-16,
|
|
16,
|
|
},
|
|
// Reserved
|
|
{
|
|
0,
|
|
838860,
|
|
0,
|
|
8,
|
|
8,
|
|
0,
|
|
16,
|
|
},
|
|
// Stone gargoyle projectile
|
|
{
|
|
3056,
|
|
2097152,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
16,
|
|
},
|
|
// Napalm launcher
|
|
{
|
|
0,
|
|
2446677,
|
|
0,
|
|
30,
|
|
30,
|
|
-128,
|
|
24,
|
|
},
|
|
// Cerberus fireball
|
|
{
|
|
0,
|
|
2446677,
|
|
0,
|
|
30,
|
|
30,
|
|
-128,
|
|
24,
|
|
},
|
|
// Tchernobog fireball
|
|
{
|
|
0,
|
|
1398101,
|
|
0,
|
|
24,
|
|
24,
|
|
-128,
|
|
16,
|
|
},
|
|
// Regular life leech
|
|
{
|
|
2446,
|
|
2796202,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
16,
|
|
},
|
|
// Dropped life leech (enough ammo)
|
|
{
|
|
3056,
|
|
2446677,
|
|
0,
|
|
16,
|
|
16,
|
|
-128,
|
|
16,
|
|
},
|
|
// Dropped life leech (no ammo)
|
|
{
|
|
3056,
|
|
1747626,
|
|
0,
|
|
32,
|
|
32,
|
|
-128,
|
|
16,
|
|
}
|
|
};
|
|
|
|
const THINGINFO thingInfo[] = {
|
|
//TNT Barrel
|
|
{
|
|
25,
|
|
250,
|
|
32,
|
|
11,
|
|
4096,
|
|
80,
|
|
384,
|
|
907,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 256, 128, 64, 0, 0, 128,
|
|
},
|
|
|
|
// Armed Proxy Dynamite
|
|
{
|
|
5,
|
|
5,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
256,
|
|
3444,
|
|
-16,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 256, 256, 64, 0, 0, 512,
|
|
},
|
|
// Armed Remote Dynamite
|
|
{
|
|
5,
|
|
5,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
256,
|
|
3457,
|
|
-16,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 256, 256, 64, 0, 0, 512,
|
|
},
|
|
// Vase1
|
|
{
|
|
1,
|
|
20,
|
|
32,
|
|
3,
|
|
32768,
|
|
80,
|
|
0,
|
|
739,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 0, 256, 128, 0, 0, 0,
|
|
},
|
|
// Vase2
|
|
{
|
|
1,
|
|
150,
|
|
32,
|
|
3,
|
|
32768,
|
|
80,
|
|
0,
|
|
642,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 256, 256, 128, 0, 0, 0,
|
|
},
|
|
// Crate face
|
|
{
|
|
10,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
462,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 0, 0, 256, 0, 0, 0,
|
|
},
|
|
// Glass window
|
|
{
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
266,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 0, 256, 256, 0, 0, 0,
|
|
},
|
|
// Flourescent Light
|
|
{
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
796,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 0, 256, 256, 0, 0, 512,
|
|
},
|
|
// Wall Crack
|
|
{
|
|
50,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1127,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 0, 0, 256, 0, 0, 0,
|
|
},
|
|
// Wood Beam
|
|
{
|
|
8,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1142,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 0, 256, 128, 0, 0, 0,
|
|
},
|
|
// Spider's Web
|
|
{
|
|
4,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1069,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 256, 64, 256, 0, 0, 128,
|
|
},
|
|
// Metal Grate
|
|
{
|
|
40,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
483,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
64, 0, 128, 256, 0, 0, 0,
|
|
},
|
|
// Flammable Tree
|
|
{
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 256, 0, 256, 0, 0, 128,
|
|
},
|
|
// MachineGun Trap
|
|
{
|
|
1000,
|
|
0,
|
|
0,
|
|
8,
|
|
0,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 0, 128, 256, 0, 0, 512,
|
|
},
|
|
// Falling Rock
|
|
{
|
|
0,
|
|
15,
|
|
8,
|
|
3,
|
|
32768,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Kickable Pail
|
|
{
|
|
0,
|
|
8,
|
|
48,
|
|
3,
|
|
49152,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Gib Object
|
|
{
|
|
10,
|
|
2,
|
|
0,
|
|
0,
|
|
32768,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 0, 256, 256, 0, 0, 128,
|
|
},
|
|
// Explode Object
|
|
{
|
|
20,
|
|
2,
|
|
0,
|
|
0,
|
|
32768,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0, 0, 0, 256, 0, 0, 128,
|
|
},
|
|
// Armed stick Of TNT
|
|
{
|
|
5,
|
|
14,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
256,
|
|
3422,
|
|
-32,
|
|
0,
|
|
32,
|
|
32,
|
|
64, 256, 128, 64, 0, 0, 256,
|
|
},
|
|
// Armed bundle Of TNT
|
|
{
|
|
5,
|
|
14,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
256,
|
|
3433,
|
|
-32,
|
|
0,
|
|
32,
|
|
32,
|
|
64, 256, 128, 64, 0, 0, 256,
|
|
},
|
|
// Armed aerosol
|
|
{
|
|
5,
|
|
14,
|
|
16,
|
|
3,
|
|
32768,
|
|
1600,
|
|
256,
|
|
3467,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
64, 256, 128, 64, 0, 0, 256,
|
|
},
|
|
// Bone (Flesh Garg.)
|
|
{
|
|
5,
|
|
6,
|
|
16,
|
|
3,
|
|
32768,
|
|
1600,
|
|
256,
|
|
1462,
|
|
0,
|
|
0,
|
|
32,
|
|
32,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Some alpha stuff
|
|
{
|
|
8,
|
|
3,
|
|
16,
|
|
11,
|
|
32768,
|
|
1600,
|
|
256,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 0, 256, 256, 0, 0, 0,
|
|
},
|
|
// WaterDrip
|
|
{
|
|
0,
|
|
1,
|
|
1,
|
|
2,
|
|
0,
|
|
0,
|
|
0,
|
|
1147,
|
|
0,
|
|
10,
|
|
0,
|
|
0,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// BloodDrip
|
|
{
|
|
0,
|
|
1,
|
|
1,
|
|
2,
|
|
0,
|
|
0,
|
|
0,
|
|
1160,
|
|
0,
|
|
2,
|
|
0,
|
|
0,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Blood chucks1
|
|
{
|
|
15,
|
|
4,
|
|
4,
|
|
3,
|
|
24576,
|
|
0,
|
|
257,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
128, 64, 256, 256, 0, 0, 256,
|
|
},
|
|
// Blood chucks2
|
|
{
|
|
30,
|
|
30,
|
|
8,
|
|
3,
|
|
8192,
|
|
0,
|
|
257,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
128, 64, 256, 256, 0, 0, 64,
|
|
},
|
|
// Axe Zombie Head
|
|
{
|
|
60,
|
|
5,
|
|
32,
|
|
3,
|
|
40960,
|
|
1280,
|
|
257,
|
|
3405,
|
|
0,
|
|
0,
|
|
40,
|
|
40,
|
|
128, 64, 256, 256, 0, 0, 64,
|
|
},
|
|
// Napalm's Alt Fire explosion
|
|
{
|
|
80,
|
|
30,
|
|
32,
|
|
3,
|
|
57344,
|
|
1600,
|
|
256,
|
|
3281,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Fire Pod Explosion
|
|
{
|
|
80,
|
|
30,
|
|
32,
|
|
3,
|
|
57344,
|
|
1600,
|
|
256,
|
|
2020,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 0, 256, 256, 0, 0, 0,
|
|
},
|
|
// Green Pod Explosion
|
|
{
|
|
80,
|
|
30,
|
|
32,
|
|
3,
|
|
57344,
|
|
1600,
|
|
256,
|
|
1860,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 0, 256, 256, 0, 0, 0,
|
|
},
|
|
// Life Leech
|
|
{
|
|
150,
|
|
30,
|
|
48,
|
|
3,
|
|
32768,
|
|
1600,
|
|
257,
|
|
800,
|
|
-128,
|
|
0,
|
|
48,
|
|
48,
|
|
64, 64, 112, 64, 0, 96, 96,
|
|
},
|
|
// Voodoo Head
|
|
{
|
|
1,
|
|
30,
|
|
48,
|
|
3,
|
|
32768,
|
|
1600,
|
|
0,
|
|
2443,
|
|
-128,
|
|
0,
|
|
16,
|
|
16,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// 433 - kModernThingTNTProx
|
|
{
|
|
5,
|
|
5,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
256,
|
|
3444,
|
|
-16,
|
|
7,
|
|
32,
|
|
32,
|
|
256, 256, 256, 64, 0, 0, 512,
|
|
},
|
|
// 434 - kModernThingThrowableRock
|
|
{
|
|
5,
|
|
6,
|
|
16,
|
|
3,
|
|
32768,
|
|
1600,
|
|
256,
|
|
1462,
|
|
0,
|
|
0,
|
|
32,
|
|
32,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// 435 - kModernThingEnemyLifeLeech
|
|
{
|
|
150,
|
|
30,
|
|
48,
|
|
3,
|
|
32768,
|
|
1600,
|
|
257,
|
|
800,
|
|
-128,
|
|
0,
|
|
44,
|
|
44,
|
|
0, 1024, 512, 1024, 0, 64, 512,
|
|
},
|
|
};
|
|
|
|
const EXPLOSION explodeInfo[] = {
|
|
{
|
|
40,
|
|
10,
|
|
10,
|
|
75,
|
|
450,
|
|
0,
|
|
60,
|
|
80,
|
|
40
|
|
},
|
|
{
|
|
80,
|
|
20,
|
|
10,
|
|
150,
|
|
900,
|
|
0,
|
|
60,
|
|
160,
|
|
60
|
|
},
|
|
{
|
|
120,
|
|
40,
|
|
15,
|
|
225,
|
|
1350,
|
|
0,
|
|
60,
|
|
240,
|
|
80
|
|
},
|
|
{
|
|
80,
|
|
5,
|
|
10,
|
|
120,
|
|
20,
|
|
10,
|
|
60,
|
|
0,
|
|
40
|
|
},
|
|
{
|
|
120,
|
|
10,
|
|
10,
|
|
180,
|
|
40,
|
|
10,
|
|
60,
|
|
0,
|
|
80
|
|
},
|
|
{
|
|
160,
|
|
15,
|
|
10,
|
|
240,
|
|
60,
|
|
10,
|
|
60,
|
|
0,
|
|
120
|
|
},
|
|
{
|
|
40,
|
|
20,
|
|
10,
|
|
120,
|
|
0,
|
|
10,
|
|
30,
|
|
60,
|
|
40
|
|
},
|
|
{
|
|
80,
|
|
20,
|
|
10,
|
|
150,
|
|
800,
|
|
5,
|
|
60,
|
|
160,
|
|
60
|
|
},
|
|
};
|
|
|
|
static const short gPlayerGibThingComments[] = {
|
|
734, 735, 736, 737, 738, 739, 740, 741, 3038, 3049
|
|
};
|
|
|
|
const int DudeDifficulty[5] = {
|
|
512, 384, 256, 208, 160
|
|
};
|
|
|
|
int gPostCount = 0;
|
|
|
|
struct POSTPONE {
|
|
short sprite;
|
|
short status;
|
|
};
|
|
|
|
POSTPONE gPost[kMaxSprites];
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool IsUnderwaterSector(int nSector)
|
|
{
|
|
int nXSector = sector[nSector].extra;
|
|
if (nXSector > 0 && xsector[nXSector].Underwater)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actInitTraps()
|
|
{
|
|
BloodStatIterator it(kStatTraps);
|
|
while (auto act = it.Next())
|
|
{
|
|
spritetype* pSprite = &act->s();
|
|
if (pSprite->type == kTrapExploder)
|
|
{
|
|
pSprite->cstat &= ~1;
|
|
pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
if (!act->hasX()) continue;
|
|
auto x = &act->x();
|
|
x->waitTime = ClipLow(x->waitTime, 1);
|
|
x->state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actInitThings()
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (!act->hasX()) continue;
|
|
spritetype* pSprite = &act->s();
|
|
XSPRITE* pXSprite = &act->x();
|
|
|
|
int nType = pSprite->type - kThingBase;
|
|
pXSprite->health = thingInfo[nType].startHealth << 4;
|
|
#ifdef NOONE_EXTENSIONS
|
|
// allow level designer to set custom clipdist.
|
|
// this is especially useful for various Gib and Explode objects which have clipdist 1 for some reason predefined,
|
|
// but what if it have voxel model...?
|
|
if (!gModernMap)
|
|
#endif
|
|
pSprite->clipdist = thingInfo[nType].clipdist;
|
|
|
|
pSprite->flags = thingInfo[nType].flags;
|
|
if (pSprite->flags & kPhysGravity) pSprite->flags |= kPhysFalling;
|
|
act->xvel() = act->yvel() = act->zvel() = 0;
|
|
|
|
switch (pSprite->type) {
|
|
case kThingArmedProxBomb:
|
|
case kTrapMachinegun:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingTNTProx:
|
|
#endif
|
|
pXSprite->state = 0;
|
|
break;
|
|
case kThingBloodChunks:
|
|
{
|
|
SEQINST* pInst = GetInstance(3, pSprite->extra);
|
|
if (pInst)
|
|
{
|
|
auto seq = getSequence(pInst->nSeqID);
|
|
if (!seq) break;
|
|
seqSpawn(pInst->nSeqID, act);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
pXSprite->state = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actInitDudes()
|
|
{
|
|
if (gGameOptions.nMonsterSettings == 0)
|
|
{
|
|
gKillMgr.SetCount(0);
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act = it.Next())
|
|
{
|
|
spritetype* pSprite = &act->s();
|
|
if (act->hasX() && act->x().key > 0) // Drop Key
|
|
actDropObject(act, kItemKeyBase + (act->x().key - 1));
|
|
DeleteSprite(act);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// by NoOne: WTF is this?
|
|
///////////////
|
|
char unk[kDudeMax - kDudeBase];
|
|
memset(unk, 0, sizeof(unk));
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act = it.Next())
|
|
{
|
|
spritetype* pSprite = &act->s();
|
|
if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax)
|
|
I_Error("Non-enemy sprite (%d) in the enemy sprite list.\n", pSprite->index);
|
|
unk[pSprite->type - kDudeBase] = 1;
|
|
}
|
|
|
|
gKillMgr.CountTotalKills();
|
|
///////////////
|
|
|
|
for (int i = 0; i < kDudeMax - kDudeBase; i++)
|
|
for (int j = 0; j < 7; j++)
|
|
dudeInfo[i].damageVal[j] = MulScale(DudeDifficulty[gGameOptions.nDifficulty], dudeInfo[i].startDamage[j], 8);
|
|
|
|
it.Reset(kStatDude);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (!act->hasX()) continue;
|
|
spritetype* pSprite = &act->s();
|
|
XSPRITE* pXSprite = &act->x();
|
|
|
|
int nType = pSprite->type - kDudeBase;
|
|
int seqStartId = dudeInfo[nType].seqStartID;
|
|
if (!act->IsPlayerActor())
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
pSprite->cstat |= 4096 + CSTAT_SPRITE_BLOCK_HITSCAN + CSTAT_SPRITE_BLOCK;
|
|
seqStartId = genDudeSeqStartId(pXSprite); // Custom Dude stores its SEQ in data2
|
|
pXSprite->sysData1 = pXSprite->data3; // move sndStartId to sysData1, because data3 used by the game;
|
|
pXSprite->data3 = 0;
|
|
break;
|
|
|
|
case kDudePodMother: // FakeDude type (no seq, custom flags, clipdist and cstat)
|
|
if (gModernMap) break;
|
|
[[fallthrough]];
|
|
default:
|
|
pSprite->clipdist = dudeInfo[nType].clipdist;
|
|
pSprite->cstat |= 4096 + CSTAT_SPRITE_BLOCK_HITSCAN + CSTAT_SPRITE_BLOCK;
|
|
break;
|
|
}
|
|
#else
|
|
pSprite->clipdist = dudeInfo[nType].clipdist;
|
|
pSprite->cstat |= 4096 + CSTAT_SPRITE_BLOCK_HITSCAN + CSTAT_SPRITE_BLOCK;
|
|
#endif
|
|
|
|
act->xvel() = act->yvel() = act->zvel() = 0;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add a way to set custom hp for every enemy - should work only if map just started and not loaded.
|
|
if (!gModernMap || pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[nType].startHealth << 4;
|
|
else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535);
|
|
#else
|
|
pXSprite->health = dudeInfo[nType].startHealth << 4;
|
|
#endif
|
|
|
|
}
|
|
|
|
if (getSequence(seqStartId)) seqSpawn(seqStartId, 3, pSprite->extra);
|
|
}
|
|
aiInit();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actInit(bool bSaveLoad)
|
|
{
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (!gModernMap) nnExtResetGlobals();
|
|
else nnExtInitModernStuff(bSaveLoad);
|
|
#endif
|
|
|
|
BloodStatIterator it(kStatItem);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (act->s().type == kItemWeaponVoodooDoll)
|
|
{
|
|
act->s().type = kItemAmmoVoodooDoll;
|
|
break;
|
|
}
|
|
}
|
|
|
|
actInitTraps();
|
|
actInitThings();
|
|
actInitDudes();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void ConcussSprite(DBloodActor* source, DBloodActor* actor, int x, int y, int z, int damage)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
int dx = pSprite->x - x;
|
|
int dy = pSprite->y - y;
|
|
int dz = (pSprite->z - z) >> 4;
|
|
int dist2 = 0x40000 + dx * dx + dy * dy + dz * dz;
|
|
assert(dist2 > 0);
|
|
damage = scale(0x40000, damage, dist2);
|
|
|
|
if (pSprite->flags & kPhysMove)
|
|
{
|
|
int mass = 0;
|
|
if (actor->IsDudeActor())
|
|
{
|
|
mass = getDudeInfo(pSprite->type)->mass;
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (pSprite->type == kDudeModernCustom || pSprite->type == kDudeModernCustomBurning)
|
|
{
|
|
mass = getSpriteMassBySize(pSprite);
|
|
}
|
|
#endif
|
|
}
|
|
else if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
|
|
{
|
|
mass = thingInfo[pSprite->type - kThingBase].mass;
|
|
}
|
|
else
|
|
{
|
|
Printf(PRINT_HIGH, "Unexpected type in ConcussSprite(): Sprite: %d Type: %d Stat: %d", (int)pSprite->index, (int)pSprite->type, (int)pSprite->statnum);
|
|
return;
|
|
}
|
|
|
|
if (mass > 0)
|
|
{
|
|
int size = (tileWidth(pSprite->picnum) * pSprite->xrepeat * tileHeight(pSprite->picnum) * pSprite->yrepeat) >> 1;
|
|
int t = scale(damage, size, mass);
|
|
int nSprite = pSprite->index;
|
|
actor->xvel() += MulScale(t, dx, 16);
|
|
actor->yvel() += MulScale(t, dy, 16);
|
|
actor->zvel() += MulScale(t, dz, 16);
|
|
}
|
|
}
|
|
actDamageSprite(source, actor, kDamageExplode, damage);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int actWallBounceVector(int* x, int* y, int nWall, int a4)
|
|
{
|
|
int wx, wy;
|
|
GetWallNormal(nWall, &wx, &wy);
|
|
int t = DMulScale(*x, wx, *y, wy, 16);
|
|
int t2 = mulscale16r(t, a4 + 0x10000);
|
|
*x -= MulScale(wx, t2, 16);
|
|
*y -= MulScale(wy, t2, 16);
|
|
return mulscale16r(t, 0x10000 - a4);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int actFloorBounceVector(int* x, int* y, int* z, int nSector, int a5)
|
|
{
|
|
int t = 0x10000 - a5;
|
|
if (sector[nSector].floorheinum == 0)
|
|
{
|
|
int t2 = MulScale(*z, t, 16);
|
|
*z = -(*z - t2);
|
|
return t2;
|
|
}
|
|
walltype* pWall = &wall[sector[nSector].wallptr];
|
|
walltype* pWall2 = &wall[pWall->point2];
|
|
int angle = getangle(pWall2->x - pWall->x, pWall2->y - pWall->y) + 512;
|
|
int t2 = sector[nSector].floorheinum << 4;
|
|
int t3 = approxDist(-0x10000, t2);
|
|
int t4 = DivScale(-0x10000, t3, 16);
|
|
int t5 = DivScale(t2, t3, 16);
|
|
int t6 = MulScale(t5, Cos(angle), 30);
|
|
int t7 = MulScale(t5, Sin(angle), 30);
|
|
int t8 = TMulScale(*x, t6, *y, t7, *z, t4, 16);
|
|
int t9 = MulScale(t8, 0x10000 + a5, 16);
|
|
*x -= MulScale(t6, t9, 16);
|
|
*y -= MulScale(t7, t9, 16);
|
|
*z -= MulScale(t4, t9, 16);
|
|
return mulscale16r(t8, t);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actRadiusDamage(DBloodActor* source, int x, int y, int z, int nSector, int nDist, int baseDmg, int distDmg, DAMAGE_TYPE dmgType, int flags, int burn)
|
|
{
|
|
uint8_t sectmap[(kMaxSectors + 7) >> 3];
|
|
auto pOwner = source->GetOwner();
|
|
const bool newSectCheckMethod = !cl_bloodvanillaexplosions && pOwner && pOwner->IsDudeActor() && !VanillaMode(); // use new sector checking logic
|
|
GetClosestSpriteSectors(nSector, x, y, nDist, sectmap, nullptr, newSectCheckMethod);
|
|
nDist <<= 4;
|
|
if (flags & 2)
|
|
{
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act2 = it.Next())
|
|
{
|
|
if (act2 != source || (flags & 1))
|
|
{
|
|
auto pSprite2 = &act2->s();
|
|
if (act2->hasX())
|
|
{
|
|
if (pSprite2->flags & 0x20) continue;
|
|
if (!TestBitString(sectmap, pSprite2->sectnum)) continue;
|
|
if (!CheckProximity(pSprite2, x, y, z, nSector, nDist)) continue;
|
|
|
|
int dx = abs(x - pSprite2->x);
|
|
int dy = abs(y - pSprite2->y);
|
|
int dz = abs(z - pSprite2->z) >> 4;
|
|
int dist = ksqrt(dx * dx + dy * dy + dz * dz);
|
|
if (dist > nDist) continue;
|
|
|
|
int totaldmg;
|
|
if (dist != 0) totaldmg = baseDmg + ((nDist - dist) * distDmg) / nDist;
|
|
else totaldmg = baseDmg + distDmg;
|
|
|
|
actDamageSprite(source, act2, dmgType, totaldmg << 4);
|
|
if (burn) actBurnSprite(pOwner, act2, burn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (flags & 4)
|
|
{
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act2 = it.Next())
|
|
{
|
|
auto pSprite2 = &act2->s();
|
|
|
|
if (pSprite2->flags & 0x20) continue;
|
|
if (!TestBitString(sectmap, pSprite2->sectnum)) continue;
|
|
if (!CheckProximity(pSprite2, x, y, z, nSector, nDist)) continue;
|
|
|
|
XSPRITE* pXSprite2 = &act2->x();
|
|
if (pXSprite2->locked) continue;
|
|
|
|
int dx = abs(x - pSprite2->x);
|
|
int dy = abs(y - pSprite2->y);
|
|
int dist = ksqrt(dx * dx + dy * dy);
|
|
if (dist > nDist) continue;
|
|
|
|
int totaldmg;
|
|
if (dist != 0) totaldmg = baseDmg + ((nDist - dist) * distDmg) / nDist;
|
|
else totaldmg = baseDmg + distDmg;
|
|
|
|
actDamageSprite(source, act2, dmgType, totaldmg << 4);
|
|
if (burn) actBurnSprite(pOwner, act2, burn);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actNapalmMove(DBloodActor* actor)
|
|
{
|
|
auto pXSprite = &actor->x();
|
|
auto pSprite = &actor->s();
|
|
auto pOwner = actor->GetOwner();
|
|
|
|
actPostSprite(actor, kStatDecoration);
|
|
seqSpawn(9, actor);
|
|
if (Chance(0x8000)) pSprite->cstat |= 4;
|
|
|
|
sfxPlay3DSound(pSprite, 303, 24 + (pSprite->flags & 3), 1);
|
|
actRadiusDamage(pOwner, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 128, 0, 60, kDamageExplode, 15, 120);
|
|
|
|
if (pXSprite->data4 > 1)
|
|
{
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
int spawnparam[2];
|
|
spawnparam[0] = pXSprite->data4 >> 1;
|
|
spawnparam[1] = pXSprite->data4 - spawnparam[0];
|
|
int ang = pSprite->ang;
|
|
actor->xvel() = 0;
|
|
actor->yvel() = 0;
|
|
actor->zvel() = 0;
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
int t1 = Random(0x33333) + 0x33333;
|
|
int rndang = Random2(0x71);
|
|
pSprite->ang = (rndang + ang + 2048) & 2047;
|
|
auto spawned = actFireThing(actor, 0, 0, -0x93d0, kThingNapalmBall, t1);
|
|
spawned->SetOwner(actor->GetOwner());
|
|
seqSpawn(61, spawned, nNapalmClient);
|
|
spawned->x().data4 = spawnparam[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actSpawnFloor(DBloodActor* actor)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
short sector = pSprite->sectnum;
|
|
int x = pSprite->x;
|
|
int y = pSprite->y;
|
|
updatesector(x, y, §or);
|
|
int zFloor = getflorzofslope(sector, x, y);
|
|
auto spawned = actSpawnSprite(sector, x, y, zFloor, 3, 0);
|
|
if (spawned) spawned->s().cstat &= ~257;
|
|
return spawned;
|
|
}
|
|
|
|
static DBloodActor* actDropAmmo(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
auto pSprite = &actor->s();
|
|
if (pSprite->statnum < kMaxStatus && nType >= kItemAmmoBase && nType < kItemAmmoMax)
|
|
{
|
|
auto act2 = actSpawnFloor(actor);
|
|
const AMMOITEMDATA* pAmmo = &gAmmoItemData[nType - kItemAmmoBase];
|
|
auto pSprite2 = &act2->s();
|
|
pSprite2->type = nType;
|
|
pSprite2->picnum = pAmmo->picnum;
|
|
pSprite2->shade = pAmmo->shade;
|
|
pSprite2->xrepeat = pAmmo->xrepeat;
|
|
pSprite2->yrepeat = pAmmo->yrepeat;
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static DBloodActor* actDropWeapon(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
auto pSprite = &actor->s();
|
|
if (pSprite->statnum < kMaxStatus && nType >= kItemWeaponBase && nType < kItemWeaponMax)
|
|
{
|
|
auto act2 = actSpawnFloor(actor);
|
|
const WEAPONITEMDATA* pWeapon = &gWeaponItemData[nType - kItemWeaponBase];
|
|
auto pSprite2 = &act2->s();
|
|
pSprite2->type = nType;
|
|
pSprite2->picnum = pWeapon->picnum;
|
|
pSprite2->shade = pWeapon->shade;
|
|
pSprite2->xrepeat = pWeapon->xrepeat;
|
|
pSprite2->yrepeat = pWeapon->yrepeat;
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static DBloodActor* actDropItem(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
auto pSprite = &actor->s();
|
|
if (pSprite->statnum < kMaxStatus && nType >= kItemBase && nType < kItemMax)
|
|
{
|
|
auto act2 = actSpawnFloor(actor);
|
|
const ITEMDATA* pItem = &gItemData[nType - kItemBase];
|
|
auto pSprite2 = &act2->s();
|
|
pSprite2->type = nType;
|
|
pSprite2->picnum = pItem->picnum;
|
|
pSprite2->shade = pItem->shade;
|
|
pSprite2->xrepeat = pItem->xrepeat;
|
|
pSprite2->yrepeat = pItem->yrepeat;
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actDropKey(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
auto pSprite = &actor->s();
|
|
if (pSprite->statnum < kMaxStatus && nType >= kItemKeyBase && nType < kItemKeyMax)
|
|
{
|
|
auto act2 = actDropItem(actor, nType);
|
|
if (act2 && gGameOptions.nGameType == 1)
|
|
{
|
|
act2->addX();
|
|
auto pSprite2 = &act2->s();
|
|
act2->x().respawn = 3;
|
|
act2->hit().florhit = 0;
|
|
act2->hit().ceilhit = 0;
|
|
}
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actDropFlag(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
auto pSprite = &actor->s();
|
|
if (pSprite->statnum < kMaxStatus && (nType == 147 || nType == 148))
|
|
{
|
|
auto act2 = actDropItem(actor, nType);
|
|
if (act2 && gGameOptions.nGameType == 3)
|
|
{
|
|
evPost(act2, 1800, kCallbackReturnFlag);
|
|
}
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actDropObject(DBloodActor* actor, int nType)
|
|
{
|
|
DBloodActor* act2 = nullptr;
|
|
|
|
if (nType >= kItemKeyBase && nType < kItemKeyMax) act2 = actDropKey(actor, nType);
|
|
else if (nType == kItemFlagA || nType == kItemFlagB) act2 = actDropFlag(actor, nType);
|
|
else if (nType >= kItemBase && nType < kItemMax) act2 = actDropItem(actor, nType);
|
|
else if (nType >= kItemAmmoBase && nType < kItemAmmoMax) act2 = actDropAmmo(actor, nType);
|
|
else if (nType >= kItemWeaponBase && nType < kItemWeaponMax) act2 = actDropWeapon(actor, nType);
|
|
|
|
if (act2)
|
|
{
|
|
int top, bottom;
|
|
GetActorExtents(act2, &top, &bottom);
|
|
if (bottom >= act2->s().z)
|
|
act2->s().z -= bottom - act2->s().z;
|
|
}
|
|
|
|
return act2;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool actHealDude(DBloodActor* actor, int add, int threshold)
|
|
{
|
|
if (!actor) return false;
|
|
auto pXDude = &actor->x();
|
|
add <<= 4;
|
|
threshold <<= 4;
|
|
if (pXDude->health < (unsigned)threshold)
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
if (actor->IsPlayerActor()) sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 780, pSprite->sectnum);
|
|
pXDude->health = min<uint32_t>(pXDude->health + add, threshold);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
static bool actKillModernDude(DBloodActor* actor, DAMAGE_TYPE damageType)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = &actor->x();
|
|
GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
|
|
removeDudeStuff(pSprite);
|
|
if (pXSprite->txID <= 0 || getNextIncarnation(pXSprite) == nullptr)
|
|
{
|
|
if (pExtra->weaponType == kGenDudeWeaponKamikaze && Chance(0x4000) && damageType != kDamageSpirit && damageType != kDamageDrown)
|
|
{
|
|
doExplosion(pSprite, pXSprite->data1 - kTrapExploder);
|
|
if (Chance(0x9000)) damageType = (DAMAGE_TYPE)3;
|
|
}
|
|
|
|
if (damageType == kDamageBurn)
|
|
{
|
|
if (pExtra->availDeaths[kDamageBurn] && !spriteIsUnderwater(pSprite))
|
|
{
|
|
if (pExtra->canBurn)
|
|
{
|
|
pSprite->type = kDudeModernCustomBurning;
|
|
if (pXSprite->data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation
|
|
pSprite->pal = 0;
|
|
|
|
aiGenDudeNewState(pSprite, &genDudeBurnGoto);
|
|
actHealDude(pXSprite, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
|
|
if (pXSprite->burnTime <= 0) pXSprite->burnTime = 1200;
|
|
actor->dudeExtra.time = PlayClock + 360;
|
|
return true;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
pXSprite->burnTime = 0;
|
|
pXSprite->burnSource = -1;
|
|
damageType = kDamageFall;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pXSprite->locked = 1; // lock while transforming
|
|
|
|
aiSetGenIdleState(pSprite, pXSprite); // set idle state
|
|
|
|
if (pXSprite->key > 0) // drop keys
|
|
actDropObject(pSprite, kItemKeyBase + pXSprite->key - 1);
|
|
|
|
if (pXSprite->dropMsg > 0) // drop items
|
|
actDropObject(pSprite, pXSprite->dropMsg);
|
|
|
|
pSprite->flags &= ~kPhysMove; xvel[pSprite->index] = yvel[pSprite->index] = 0;
|
|
|
|
playGenDudeSound(pSprite, kGenDudeSndTransforming);
|
|
int seqId = pXSprite->data2 + kGenDudeSeqTransform;
|
|
if (getSequence(seqId)) seqSpawn(seqId, actor, -1);
|
|
else
|
|
{
|
|
seqKill(actor);
|
|
DBloodActor* pEffectA = gFX.fxSpawnActor((FX_ID)52, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, pSprite->ang);
|
|
if (pEffectA != nullptr)
|
|
{
|
|
auto pEffect = &pEffectA->s();
|
|
pEffect->cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
|
|
pEffect->pal = 6;
|
|
pEffect->xrepeat = pSprite->xrepeat;
|
|
pEffect->yrepeat = pSprite->yrepeat;
|
|
}
|
|
|
|
GIBTYPE nGibType;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
if (Chance(0x3000)) nGibType = GIBTYPE_6;
|
|
else if (Chance(0x2000)) nGibType = GIBTYPE_5;
|
|
else nGibType = GIBTYPE_17;
|
|
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
CGibPosition gibPos(pSprite->x, pSprite->y, top);
|
|
CGibVelocity gibVel(actor->xvel() >> 1, actor->yvel() >> 1, -0xccccc);
|
|
GibSprite(pSprite, nGibType, &gibPos, &gibVel);
|
|
}
|
|
}
|
|
|
|
pXSprite->sysData1 = kGenDudeTransformStatus; // in transform
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static bool actKillDudeStage1(DBloodActor* actor, DAMAGE_TYPE damageType)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = &actor->x();
|
|
switch (pSprite->type)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
if (actKillModernDude(actor, damageType)) return true;
|
|
break;
|
|
#endif
|
|
case kDudeCerberusTwoHead: // Cerberus
|
|
seqSpawn(dudeInfo[pSprite->type - kDudeBase].seqStartID + 1, actor, -1);
|
|
return true;
|
|
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
case kDudeCultistTesla:
|
|
case kDudeCultistTNT:
|
|
if (damageType == kDamageBurn && pXSprite->medium == kMediumNormal)
|
|
{
|
|
pSprite->type = kDudeBurningCultist;
|
|
aiNewState(actor, &cultistBurnGoto);
|
|
actHealDude(actor, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kDudeBeast:
|
|
if (damageType == kDamageBurn && pXSprite->medium == kMediumNormal)
|
|
{
|
|
pSprite->type = kDudeBurningBeast;
|
|
aiNewState(actor, &beastBurnGoto);
|
|
actHealDude(actor, dudeInfo[53].startHealth, dudeInfo[53].startHealth);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kDudeInnocent:
|
|
if (damageType == kDamageBurn && pXSprite->medium == kMediumNormal)
|
|
{
|
|
pSprite->type = kDudeBurningInnocent;
|
|
aiNewState(actor, &innocentBurnGoto);
|
|
actHealDude(actor, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kDudeTinyCaleb:
|
|
if (cl_bloodvanillaenemies || VanillaMode())
|
|
break;
|
|
if (damageType == kDamageBurn && pXSprite->medium == kMediumNormal)
|
|
{
|
|
pSprite->type = kDudeBurningTinyCaleb;
|
|
aiNewState(actor, &tinycalebBurnGoto);
|
|
actHealDude(actor, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkAddFrag(DBloodActor* killerActor, DBloodActor* actor)
|
|
{
|
|
auto pKillerSprite = &killerActor->s();
|
|
auto pSprite = &actor->s();
|
|
if (VanillaMode())
|
|
{
|
|
if (killerActor->IsPlayerActor())
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[pKillerSprite->type - kDudePlayer1];
|
|
if (gGameOptions.nGameType == 1)
|
|
pPlayer->fragCount++;
|
|
}
|
|
}
|
|
else if (gGameOptions.nGameType == 1 && killerActor->IsPlayerActor() && pSprite->statnum == kStatDude)
|
|
{
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeBat:
|
|
case kDudeRat:
|
|
case kDudeInnocent:
|
|
case kDudeBurningInnocent:
|
|
break;
|
|
default:
|
|
PLAYER* pKillerPlayer = &gPlayer[pKillerSprite->type - kDudePlayer1];
|
|
pKillerPlayer->fragCount++;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkDropObjects(DBloodActor* actor)
|
|
{
|
|
auto pXSprite = &actor->x();
|
|
|
|
if (pXSprite->key > 0) actDropObject(actor, kItemKeyBase + pXSprite->key - 1);
|
|
if (pXSprite->dropMsg > 0) actDropObject(actor, pXSprite->dropMsg);
|
|
|
|
switch (actor->s().type)
|
|
{
|
|
case kDudeCultistTommy:
|
|
{
|
|
int nRand = Random(100);
|
|
if (nRand < 10) actDropObject(actor, kItemWeaponTommygun);
|
|
else if (nRand < 50) actDropObject(actor, kItemAmmoTommygunFew);
|
|
break;
|
|
}
|
|
case kDudeCultistShotgun:
|
|
{
|
|
int nRand = Random(100);
|
|
if (nRand <= 10) actDropObject(actor, kItemWeaponSawedoff);
|
|
else if (nRand <= 50) actDropObject(actor, kItemAmmoSawedoffFew);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static int checkDamageType(DBloodActor* actor, DAMAGE_TYPE damageType)
|
|
{
|
|
int nSeq;
|
|
auto pSprite = &actor->s();
|
|
|
|
switch (damageType)
|
|
{
|
|
case kDamageExplode:
|
|
nSeq = 2;
|
|
switch (pSprite->type)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
{
|
|
playGenDudeSound(pSprite, kGenDudeSndDeathExplode);
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
|
if (!pExtra->availDeaths[damageType])
|
|
{
|
|
nSeq = 1;
|
|
damageType = kDamageFall;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
case kDudeCultistTommyProne:
|
|
case kDudeBurningInnocent:
|
|
case kDudeBurningCultist:
|
|
case kDudeInnocent:
|
|
case kDudeCultistShotgunProne:
|
|
case kDudeCultistTesla:
|
|
case kDudeCultistTNT:
|
|
case kDudeCultistBeast:
|
|
case kDudeTinyCaleb:
|
|
case kDudeBurningTinyCaleb:
|
|
sfxPlay3DSound(pSprite, 717, -1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
case kDamageBurn:
|
|
nSeq = 3;
|
|
sfxPlay3DSound(pSprite, 351, -1, 0);
|
|
break;
|
|
case kDamageSpirit:
|
|
switch (pSprite->type) {
|
|
case kDudeZombieAxeNormal:
|
|
case kDudeZombieAxeBuried:
|
|
nSeq = 14;
|
|
break;
|
|
case kDudeZombieButcher:
|
|
nSeq = 11;
|
|
break;
|
|
default:
|
|
nSeq = 1;
|
|
break;
|
|
}
|
|
break;
|
|
case kDamageFall:
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
nSeq = 1;
|
|
break;
|
|
default:
|
|
nSeq = 1;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
nSeq = 1;
|
|
break;
|
|
}
|
|
return nSeq;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void spawnGibs(DBloodActor* actor, int type, int velz)
|
|
{
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
CGibPosition gibPos(actor->s().x, actor->s().y, top);
|
|
CGibVelocity gibVel(actor->xvel() >> 1, actor->yvel() >> 1, velz);
|
|
GibSprite(&actor->s(), GIBTYPE_27, &gibPos, &gibVel);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void zombieAxeNormalDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
int nType = pSprite->type - kDudeBase;
|
|
|
|
sfxPlay3DSound(pSprite, 1107 + Random(2), -1, 0);
|
|
if (nSeq == 2)
|
|
{
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, nDudeToGibClient1);
|
|
spawnGibs(actor, GIBTYPE_27, -0xccccc);
|
|
}
|
|
else if (nSeq == 1 && Chance(0x4000))
|
|
{
|
|
seqSpawn(dudeInfo[nType].seqStartID + 7, actor, nDudeToGibClient1);
|
|
evPost(actor, 0, kCallbackFXZombieSpurt);
|
|
sfxPlay3DSound(pSprite, 362, -1, 0);
|
|
actor->x().data1 = 35;
|
|
actor->x().data2 = 5;
|
|
|
|
spawnGibs(actor, GIBTYPE_27, -0x111111);
|
|
}
|
|
else if (nSeq == 14)seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
else if (nSeq == 3) seqSpawn(dudeInfo[nType].seqStartID + 13, actor, nDudeToGibClient2);
|
|
else seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, nDudeToGibClient1);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void burningCultistDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
if (Chance(0x4000) && nSeq == 3) sfxPlay3DSound(pSprite, 718, -1, 0);
|
|
else sfxPlay3DSound(pSprite, 1018 + Random(2), -1, 0);
|
|
|
|
int nType = pSprite->type - kDudeBase;
|
|
if (Chance(0x8000))
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
GibSprite(pSprite, GIBTYPE_7, nullptr, nullptr);
|
|
seqSpawn(dudeInfo[nType].seqStartID + 16 - Random(1), actor, nDudeToGibClient1);
|
|
}
|
|
else
|
|
seqSpawn(dudeInfo[nType].seqStartID + 15, actor, nDudeToGibClient2);
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void modernCustomDudeDeath(DBloodActor* actor, int nSeq, int damageType)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = &actor->x();
|
|
|
|
playGenDudeSound(pSprite, kGenDudeSndDeathNormal);
|
|
int dudeToGib = (actCheckRespawn(pSprite)) ? -1 : ((nSeq == 3) ? nDudeToGibClient2 : nDudeToGibClient1);
|
|
if (nSeq == 3)
|
|
{
|
|
GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
|
|
if (pExtra->availDeaths[kDmgBurn] == 3) seqSpawn((15 + Random(2)) + pXSprite->data2, actor, dudeToGib);
|
|
else if (pExtra->availDeaths[kDmgBurn] == 2) seqSpawn(16 + pXSprite->data2, actor, dudeToGib);
|
|
else if (pExtra->availDeaths[kDmgBurn] == 1) seqSpawn(15 + pXSprite->data2, actor, dudeToGib);
|
|
else if (getSequence(pXSprite->data2 + nSeq))seqSpawn(nSeq + pXSprite->data2, actor, dudeToGib);
|
|
else seqSpawn(1 + pXSprite->data2, actor, dudeToGib);
|
|
|
|
}
|
|
else
|
|
{
|
|
seqSpawn(nSeq + pXSprite->data2, actor, dudeToGib);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void modernCustomDudeBurningDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
|
|
playGenDudeSound(pSprite, kGenDudeSndDeathExplode);
|
|
int dudeToGib = (actCheckRespawn(pSprite)) ? -1 : nDudeToGibClient1;
|
|
|
|
if (Chance(0x4000)) spawnGibs(actor, GIBTYPE_27, -0xccccc);
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
|
int seqofs = actor->x().data2;
|
|
if (pExtra->availDeaths[kDmgBurn] == 3) seqSpawn((15 + Random(2)) + seqofs, actor, dudeToGib);
|
|
else if (pExtra->availDeaths[kDmgBurn] == 2) seqSpawn(16 + seqofs, actor, dudeToGib);
|
|
else if (pExtra->availDeaths[kDmgBurn] == 1) seqSpawn(15 + seqofs, actor, dudeToGib);
|
|
else seqSpawn(1 + seqofs, actor, dudeToGib);
|
|
}
|
|
#endif
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void zombieAxeBurningDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
int nType = pSprite->type - kDudeBase;
|
|
|
|
if (Chance(0x8000) && nSeq == 3)
|
|
sfxPlay3DSound(pSprite, 1109, -1, 0);
|
|
else
|
|
sfxPlay3DSound(pSprite, 1107 + Random(2), -1, 0);
|
|
|
|
if (Chance(0x8000))
|
|
{
|
|
seqSpawn(dudeInfo[nType].seqStartID + 13, actor, nDudeToGibClient1);
|
|
spawnGibs(actor, GIBTYPE_27, -0xccccc);
|
|
}
|
|
else
|
|
seqSpawn(dudeInfo[nType].seqStartID + 13, actor, nDudeToGibClient2);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void zombieButcherDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
int nType = pSprite->type - kDudeBase;
|
|
|
|
if (nSeq == 14)
|
|
{
|
|
sfxPlay3DSound(pSprite, 1206, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + 11, actor, -1);
|
|
return;
|
|
}
|
|
sfxPlay3DSound(pSprite, 1204 + Random(2), -1, 0);
|
|
if (nSeq == 3)
|
|
seqSpawn(dudeInfo[nType].seqStartID + 10, actor, -1);
|
|
else
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void genericDeath(DBloodActor* actor, int nSeq, int sound1, int seqnum)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
if (Chance(0x4000) && nSeq == 3) sfxPlay3DSound(pSprite, sound1 + 2, -1, 0);
|
|
else sfxPlay3DSound(pSprite, sound1 + Random(2), -1, 0);
|
|
seqSpawn(seqnum, actor, -1);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actKillDude(DBloodActor* killerActor, DBloodActor* actor, DAMAGE_TYPE damageType, int damage)
|
|
{
|
|
spritetype* pKillerSprite = &killerActor->s();
|
|
auto pSprite = &actor->s();
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax&& actor->hasX());
|
|
int nType = pSprite->type - kDudeBase;
|
|
XSPRITE* pXSprite = &actor->x();
|
|
|
|
if (actKillDudeStage1(actor, damageType)) return;
|
|
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
{
|
|
if (gPlayer[p].fragger() == actor && gPlayer[p].deathTime > 0)
|
|
gPlayer[p].setFragger(nullptr);
|
|
}
|
|
if (pSprite->type != kDudeCultistBeast)
|
|
trTriggerSprite(actor, kCmdOff);
|
|
|
|
pSprite->flags |= 7;
|
|
checkAddFrag(killerActor, actor);
|
|
checkDropObjects(actor);
|
|
|
|
int nSeq = checkDamageType(actor, damageType);
|
|
|
|
if (!getSequence(getDudeInfo(nType + kDudeBase)->seqStartID + nSeq))
|
|
{
|
|
seqKill(actor);
|
|
gKillMgr.AddKill(pSprite);
|
|
actPostSprite(actor, kStatFree);
|
|
return;
|
|
}
|
|
|
|
auto Owner = actor->GetOwner();
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeZombieAxeNormal:
|
|
zombieAxeNormalDeath(actor, nSeq);
|
|
break;
|
|
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
case kDudeCultistTesla:
|
|
case kDudeCultistTNT:
|
|
sfxPlay3DSound(pSprite, 1018 + Random(2), -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, nSeq == 3 ? nDudeToGibClient2 : nDudeToGibClient1);
|
|
break;
|
|
|
|
case kDudeBurningCultist:
|
|
burningCultistDeath(actor, nSeq);
|
|
damageType = kDamageExplode;
|
|
break;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
modernCustomDudeDeath(actor, nSeq, damageType);
|
|
genDudePostDeath(pSprite, damageType, damage);
|
|
return;
|
|
|
|
case kDudeModernCustomBurning:
|
|
modernCustomDudeBurningDeath(actor, nSeq);
|
|
genDudePostDeath(pSprite, kDamageExplode, damage);
|
|
return;
|
|
#endif
|
|
|
|
case kDudeBurningZombieAxe:
|
|
zombieAxeBurningDeath(actor, nSeq);
|
|
damageType = kDamageExplode;
|
|
break;
|
|
|
|
case kDudeBurningZombieButcher:
|
|
genericDeath(actor, nSeq, 1204, dudeInfo[4].seqStartID + 10);
|
|
break;
|
|
|
|
case kDudeBurningInnocent:
|
|
damageType = kDamageExplode;
|
|
seqSpawn(dudeInfo[nType].seqStartID + 7, actor, nDudeToGibClient1);
|
|
break;
|
|
|
|
case kDudeZombieButcher:
|
|
zombieButcherDeath(actor, nSeq);
|
|
break;
|
|
|
|
case kDudeGargoyleFlesh:
|
|
genericDeath(actor, nSeq, 1403, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeGargoyleStone:
|
|
genericDeath(actor, nSeq, 1453, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudePhantasm:
|
|
genericDeath(actor, nSeq, 1603, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeHellHound:
|
|
genericDeath(actor, nSeq, 1303, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeHand:
|
|
genericDeath(actor, nSeq, 1903, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderBrown:
|
|
if (Owner) Owner->dudeExtra.at6.u1.xval2--;
|
|
genericDeath(actor, nSeq, 1803, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderRed:
|
|
if (Owner) Owner->dudeExtra.at6.u1.xval2--;
|
|
genericDeath(actor, nSeq, 1803, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderBlack:
|
|
if (Owner) Owner->dudeExtra.at6.u1.xval2--;
|
|
genericDeath(actor, nSeq, 1803, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderMother:
|
|
sfxPlay3DSound(pSprite, 1850, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
|
|
case kDudeGillBeast:
|
|
genericDeath(actor, nSeq, 1703, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeBoneEel:
|
|
genericDeath(actor, nSeq, 1503, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeBat:
|
|
genericDeath(actor, nSeq, 2003, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeRat:
|
|
genericDeath(actor, nSeq, 2103, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudePodGreen:
|
|
case kDudeTentacleGreen:
|
|
case kDudePodFire:
|
|
case kDudeTentacleFire:
|
|
if ((pSprite->cstat & CSTAT_SPRITE_YFLIP)) pSprite->cstat &= ~CSTAT_SPRITE_YFLIP;
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudePodGreen:
|
|
genericDeath(actor, nSeq, 2203, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
case kDudeTentacleGreen:
|
|
sfxPlay3DSound(pSprite, damage == 5 ? 2471 : 2472, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
case kDudePodFire:
|
|
sfxPlay3DSound(pSprite, damage == 5 ? 2451 : 2452, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
case kDudeTentacleFire:
|
|
sfxPlay3DSound(pSprite, 2501, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case kDudePodMother:
|
|
case kDudeTentacleMother:
|
|
genericDeath(actor, nSeq, 2203, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeCerberusTwoHead:
|
|
case kDudeCerberusOneHead:
|
|
genericDeath(actor, nSeq, 2303, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeTchernobog:
|
|
sfxPlay3DSound(pSprite, 2380, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
|
|
case kDudeBurningTinyCaleb:
|
|
damageType = kDamageExplode;
|
|
seqSpawn(dudeInfo[nType].seqStartID + 11, actor, nDudeToGibClient1);
|
|
break;
|
|
|
|
case kDudeBeast:
|
|
sfxPlay3DSound(pSprite, 9000 + Random(2), -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, nSeq == 3 ? nDudeToGibClient2 : nDudeToGibClient1);
|
|
break;
|
|
|
|
case kDudeBurningBeast:
|
|
damageType = kDamageExplode;
|
|
seqSpawn(dudeInfo[nType].seqStartID + 12, actor, nDudeToGibClient1);
|
|
break;
|
|
|
|
default:
|
|
seqSpawn(getDudeInfo(nType + kDudeBase)->seqStartID + nSeq, actor, -1);
|
|
break;
|
|
}
|
|
|
|
if (damageType == kDamageExplode)
|
|
{
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
for (int i = 0; i < 3; i++)
|
|
if (pDudeInfo->nGibType[i] > -1)
|
|
GibSprite(pSprite, (GIBTYPE)pDudeInfo->nGibType[i], nullptr, nullptr);
|
|
for (int i = 0; i < 4; i++)
|
|
fxSpawnBlood(pSprite, damage);
|
|
}
|
|
gKillMgr.AddKill(pSprite);
|
|
actCheckRespawn(pSprite);
|
|
pSprite->type = kThingBloodChunks;
|
|
actPostSprite(actor, kStatThing);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static int actDamageDude(DBloodActor* source, DBloodActor* actor, int damage, DAMAGE_TYPE damageType)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
XSPRITE* pXSprite = &actor->x();
|
|
|
|
if (!actor->IsDudeActor())
|
|
{
|
|
Printf(PRINT_HIGH, "Bad Dude Failed: initial=%d type=%d %s\n", (int)pSprite->inittype, (int)pSprite->type, (int)(pSprite->flags & kHitagRespawn) ? "RESPAWN" : "NORMAL");
|
|
return damage >> 4;
|
|
//I_Error("Bad Dude Failed: initial=%d type=%d %s\n", (int)pSprite->inittype, (int)pSprite->type, (int)(pSprite->flags & 16) ? "RESPAWN" : "NORMAL");
|
|
}
|
|
|
|
int nType = pSprite->type - kDudeBase;
|
|
int nDamageFactor = getDudeInfo(nType + kDudeBase)->damageVal[damageType];
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (pSprite->type == kDudeModernCustom)
|
|
nDamageFactor = actor->genDudeExtra().dmgControl[damageType];
|
|
#endif
|
|
|
|
if (!nDamageFactor) return 0;
|
|
else if (nDamageFactor != 256) damage = MulScale(damage, nDamageFactor, 8);
|
|
|
|
if (!IsPlayerSprite(pSprite))
|
|
{
|
|
if (pXSprite->health <= 0) return 0;
|
|
damage = aiDamageSprite(source, actor, damageType, damage);
|
|
if (pXSprite->health <= 0)
|
|
actKillDude(source, actor, ((damageType == kDamageExplode && damage < 160) ? kDamageFall : damageType), damage);
|
|
}
|
|
else
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
|
|
if (pXSprite->health > 0 || playerSeqPlaying(pPlayer, 16))
|
|
damage = playerDamageSprite(source, pPlayer, damageType, damage);
|
|
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static int actDamageThing(DBloodActor* source, DBloodActor* actor, int damage, DAMAGE_TYPE damageType, PLAYER* pSourcePlayer)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
XSPRITE* pXSprite = &actor->x();
|
|
|
|
assert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
|
|
int nType = pSprite->type - kThingBase;
|
|
int nDamageFactor = thingInfo[nType].dmgControl[damageType];
|
|
|
|
if (!nDamageFactor) return 0;
|
|
else if (nDamageFactor != 256) damage = MulScale(damage, nDamageFactor, 8);
|
|
|
|
pXSprite->health = ClipLow(pXSprite->health - damage, 0);
|
|
if (pXSprite->health <= 0)
|
|
{
|
|
auto Owner = actor->GetOwner();
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingDroppedLifeLeech:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingEnemyLifeLeech:
|
|
#endif
|
|
GibSprite(pSprite, GIBTYPE_14, nullptr, nullptr);
|
|
pXSprite->data1 = pXSprite->data2 = pXSprite->data3 = pXSprite->DudeLockout = 0;
|
|
pXSprite->stateTimer = pXSprite->data4 = pXSprite->isTriggered = 0;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (Owner && Owner->s().type == kDudeModernCustom)
|
|
Owner->SetSpecialOwner(); // indicates if custom dude had life leech.
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
if (!(pSprite->flags & kHitagRespawn))
|
|
actor->SetOwner(source);
|
|
break;
|
|
}
|
|
|
|
trTriggerSprite(actor, kCmdOff);
|
|
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingObjectGib:
|
|
case kThingObjectExplode:
|
|
case kThingBloodBits:
|
|
case kThingBloodChunks:
|
|
case kThingZombieHead:
|
|
if (damageType == 3 && pSourcePlayer && PlayClock > pSourcePlayer->laughCount && Chance(0x4000))
|
|
{
|
|
sfxPlay3DSound(pSourcePlayer->pSprite, gPlayerGibThingComments[Random(10)], 0, 2);
|
|
pSourcePlayer->laughCount = PlayClock + 3600;
|
|
}
|
|
break;
|
|
case kTrapMachinegun:
|
|
seqSpawn(28, 3, pSprite->extra, -1);
|
|
break;
|
|
|
|
case kThingFluorescent:
|
|
seqSpawn(12, 3, pSprite->extra, -1);
|
|
GibSprite(pSprite, GIBTYPE_6, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingSpiderWeb:
|
|
seqSpawn(15, 3, pSprite->extra, -1);
|
|
break;
|
|
|
|
case kThingMetalGrate:
|
|
seqSpawn(21, 3, pSprite->extra, -1);
|
|
GibSprite(pSprite, GIBTYPE_4, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingFlammableTree:
|
|
switch (pXSprite->data1)
|
|
{
|
|
case -1:
|
|
GibSprite(pSprite, GIBTYPE_14, nullptr, nullptr);
|
|
sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 312, pSprite->sectnum);
|
|
actPostSprite(actor, kStatFree);
|
|
break;
|
|
|
|
case 0:
|
|
seqSpawn(25, actor, nTreeToGibClient);
|
|
sfxPlay3DSound(pSprite, 351, -1, 0);
|
|
break;
|
|
|
|
case 1:
|
|
seqSpawn(26, 3, pSprite->extra, nTreeToGibClient);
|
|
sfxPlay3DSound(pSprite, 351, -1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int actDamageSprite(DBloodActor* source, DBloodActor* actor, DAMAGE_TYPE damageType, int damage)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
|
|
if (pSprite->flags & 32 || !actor->hasX())
|
|
return 0;
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
|
if ((pXSprite->health == 0 && pSprite->statnum != kStatDude) || pXSprite->locked)
|
|
return 0;
|
|
|
|
if (source == nullptr) source = actor;
|
|
|
|
PLAYER* pSourcePlayer = nullptr;
|
|
if (source->IsPlayerActor()) pSourcePlayer = &gPlayer[source->s().type - kDudePlayer1];
|
|
if (!gGameOptions.bFriendlyFire && IsTargetTeammate(pSourcePlayer, pSprite)) return 0;
|
|
|
|
switch (pSprite->statnum)
|
|
{
|
|
case kStatDude:
|
|
damage = actDamageDude(source, actor, damage, damageType);
|
|
break;
|
|
case kStatThing:
|
|
damage = actDamageThing(source, actor, damage, damageType, pSourcePlayer);
|
|
break;
|
|
}
|
|
|
|
return damage >> 4;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// this was condensed to the parts actually in use.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actHitcodeToData(int a1, HITINFO* pHitInfo, DBloodActor** pActor, walltype** a7)
|
|
{
|
|
assert(pHitInfo != nullptr);
|
|
int nSprite = -1;
|
|
int nWall = -1;
|
|
walltype* pWall = nullptr;
|
|
switch (a1)
|
|
{
|
|
case 3:
|
|
case 5:
|
|
nSprite = pHitInfo->hitsprite;
|
|
break;
|
|
case 0:
|
|
case 4:
|
|
nWall = pHitInfo->hitwall;
|
|
if (nWall >= 0 && nWall < kMaxWalls) pWall = &wall[nWall];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (pActor) *pActor = nSprite == -1 ? nullptr : &bloodActors[nSprite];
|
|
if (a7) *a7 = pWall;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actImpactMissile(DBloodActor* missileActor, int hitCode)
|
|
{
|
|
auto pMissile = &missileActor->s();
|
|
XSPRITE* pXMissile = &missileActor->x();
|
|
auto missileOwner = missileActor->GetOwner();
|
|
|
|
DBloodActor* actorHit = nullptr;
|
|
walltype* pWallHit = nullptr;
|
|
|
|
actHitcodeToData(hitCode, &gHitInfo, &actorHit, &pWallHit);
|
|
spritetype* pSpriteHit = actorHit ? &actorHit->s() : nullptr;
|
|
XSPRITE* pXSpriteHit = actorHit && actorHit->hasX() ? &actorHit->x() : nullptr;
|
|
|
|
const THINGINFO* pThingInfo = nullptr;
|
|
DUDEINFO* pDudeInfo = nullptr;
|
|
|
|
if (hitCode == 3 && pSpriteHit)
|
|
{
|
|
switch (pSpriteHit->statnum)
|
|
{
|
|
case kStatThing:
|
|
pThingInfo = &thingInfo[pSpriteHit->type - kThingBase];
|
|
break;
|
|
case kStatDude:
|
|
pDudeInfo = getDudeInfo(pSpriteHit->type);
|
|
break;
|
|
}
|
|
}
|
|
switch (pMissile->type)
|
|
{
|
|
case kMissileLifeLeechRegular:
|
|
if (hitCode == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
DAMAGE_TYPE rand1 = (DAMAGE_TYPE)Random(7);
|
|
int rand2 = (7 + Random(7)) << 4;
|
|
int nDamage = actDamageSprite(missileOwner, actorHit, rand1, rand2);
|
|
|
|
if ((pThingInfo && pThingInfo->dmgControl[kDamageBurn] != 0) || (pDudeInfo && pDudeInfo->damageVal[kDamageBurn] != 0))
|
|
actBurnSprite(missileActor->GetOwner(), actorHit, 360);
|
|
|
|
// by NoOne: make Life Leech heal user, just like it was in 1.0x versions
|
|
if (gGameOptions.weaponsV10x && !VanillaMode() && pDudeInfo != nullptr)
|
|
{
|
|
if (missileOwner->IsDudeActor() && missileOwner->hasX() && missileOwner->x().health != 0)
|
|
actHealDude(missileOwner, nDamage >> 2, getDudeInfo(missileOwner->s().type)->startHealth);
|
|
}
|
|
}
|
|
|
|
if (pMissile->extra > 0)
|
|
{
|
|
actPostSprite(missileActor, kStatDecoration);
|
|
if (pMissile->ang == 1024) sfxPlay3DSound(pMissile, 307, -1, 0);
|
|
pMissile->type = kSpriteDecoration;
|
|
seqSpawn(9, missileActor, -1);
|
|
}
|
|
else
|
|
{
|
|
actPostSprite(pMissile->index, kStatFree);
|
|
}
|
|
|
|
break;
|
|
case kMissileTeslaAlt:
|
|
teslaHit(pMissile, hitCode);
|
|
switch (hitCode)
|
|
{
|
|
case 0:
|
|
case 4:
|
|
if (pWallHit)
|
|
{
|
|
spritetype* pFX = gFX.fxSpawn(FX_52, pMissile->sectnum, pMissile->x, pMissile->y, pMissile->z, 0);
|
|
if (pFX) pFX->ang = (GetWallAngle(pWallHit) + 512) & 2047;
|
|
}
|
|
break;
|
|
}
|
|
GibSprite(pMissile, GIBTYPE_24, NULL, NULL);
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissilePukeGreen:
|
|
seqKill(missileActor);
|
|
if (hitCode == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
int nOwner = pMissile->owner;
|
|
int nDamage = (15 + Random(7)) << 4;
|
|
actDamageSprite(nOwner, pSpriteHit, kDamageBullet, nDamage);
|
|
}
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileArcGargoyle:
|
|
sfxKill3DSound(pMissile, -1, -1);
|
|
sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 306, pMissile->sectnum);
|
|
GibSprite(pMissile, GIBTYPE_6, NULL, NULL);
|
|
|
|
if (hitCode == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
int nDamage = (25 + Random(20)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
}
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileLifeLeechAltNormal:
|
|
case kMissileLifeLeechAltSmall:
|
|
sfxKill3DSound(pMissile, -1, -1);
|
|
sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 306, pMissile->sectnum);
|
|
|
|
if (hitCode == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
int nDmgMul = (pMissile->type == kMissileLifeLeechAltSmall) ? 6 : 3;
|
|
int nDamage = (nDmgMul + Random(nDmgMul)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
}
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileFireball:
|
|
case kMissileFireballNapalm:
|
|
if (hitCode == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
if (pThingInfo && pSpriteHit->type == kThingTNTBarrel && actorHit->x().burnTime == 0)
|
|
evPost(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
int nDamage = (50 + Random(50)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
actExplodeSprite(missileActor);
|
|
break;
|
|
|
|
case kMissileFlareAlt:
|
|
sfxKill3DSound(pMissile, -1, -1);
|
|
actExplodeSprite(missileActor);
|
|
break;
|
|
|
|
case kMissileFlareRegular:
|
|
sfxKill3DSound(pMissile, -1, -1);
|
|
if ((hitCode == 3 && pSpriteHit) && (pThingInfo || pDudeInfo))
|
|
{
|
|
if ((pThingInfo && pThingInfo->dmgControl[kDamageBurn] != 0) || (pDudeInfo && pDudeInfo->damageVal[kDamageBurn] != 0))
|
|
{
|
|
if (pThingInfo && pSpriteHit->type == kThingTNTBarrel && actorHit->x().burnTime == 0)
|
|
evPost(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
actBurnSprite(missileOwner, actorHit, 480);
|
|
actRadiusDamage(missileOwner, pMissile->x, pMissile->y, pMissile->z, pMissile->sectnum, 16, 20, 10, kDamageBullet, 6, 480);
|
|
|
|
// by NoOne: allow additional bullet damage for Flare Gun
|
|
if (gGameOptions.weaponsV10x && !VanillaMode())
|
|
{
|
|
int nDamage = (20 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int nDamage = (20 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
|
|
if (surfType[pSpriteHit->picnum] == kSurfFlesh)
|
|
{
|
|
pMissile->picnum = 2123;
|
|
missileActor->SetTarget(actorHit);
|
|
pXMissile->targetZ = pMissile->z - pSpriteHit->z;
|
|
pXMissile->goalAng = getangle(pMissile->x - pSpriteHit->x, pMissile->y - pSpriteHit->y) - pSpriteHit->ang;
|
|
pXMissile->state = 1;
|
|
actPostSprite(pMissile->index, kStatFlare);
|
|
pMissile->cstat &= ~257;
|
|
break;
|
|
}
|
|
}
|
|
GibSprite(pMissile, GIBTYPE_17, NULL, NULL);
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileFlameSpray:
|
|
case kMissileFlameHound:
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if ((pSpriteHit->statnum == kStatThing || pSpriteHit->statnum == kStatDude) && pXSpriteHit->burnTime == 0)
|
|
evPost(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
actBurnSprite(missileOwner, actorHit, (4 + gGameOptions.nDifficulty) << 2);
|
|
actDamageSprite(missileOwner, actorHit, kDamageBurn, 8);
|
|
}
|
|
break;
|
|
|
|
case kMissileFireballCerberus:
|
|
actExplodeSprite(missileActor);
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if ((pSpriteHit->statnum == kStatThing || pSpriteHit->statnum == kStatDude) && pXSpriteHit->burnTime == 0)
|
|
evPost(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
actBurnSprite(missileOwner, actorHit, (4 + gGameOptions.nDifficulty) << 2);
|
|
actDamageSprite(missileOwner, actorHit, kDamageBurn, 8);
|
|
int nDamage = (25 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
actExplodeSprite(missileActor);
|
|
break;
|
|
|
|
case kMissileFireballTchernobog:
|
|
actExplodeSprite(missileActor);
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if ((pSpriteHit->statnum == kStatThing || pSpriteHit->statnum == kStatDude) && pXSpriteHit->burnTime == 0)
|
|
evPost(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
actBurnSprite(missileOwner, actorHit, 32);
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, 12);
|
|
int nDamage = (25 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
actExplodeSprite(missileActor);
|
|
break;
|
|
|
|
case kMissileEctoSkull:
|
|
sfxKill3DSound(pMissile, -1, -1);
|
|
sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 522, pMissile->sectnum);
|
|
actPostSprite(pMissile->index, kStatDebris);
|
|
seqSpawn(20, 3, pMissile->extra, -1);
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if (pSpriteHit->statnum == kStatDude)
|
|
{
|
|
int nDamage = (25 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case kMissileButcherKnife:
|
|
actPostSprite(missileActor, kStatDebris);
|
|
pMissile->cstat &= ~16;
|
|
pMissile->type = kSpriteDecoration;
|
|
seqSpawn(20, 3, pMissile->extra, -1);
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if (pSpriteHit->statnum == kStatDude)
|
|
{
|
|
int nDamage = (10 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
int nType = missileOwner->s().type - kDudeBase;
|
|
if (missileOwner->x().health > 0)
|
|
actHealDude(missileOwner, 10, getDudeInfo(nType + kDudeBase)->startHealth);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case kMissileTeslaRegular:
|
|
sfxKill3DSound(pMissile, -1, -1);
|
|
sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 518, pMissile->sectnum);
|
|
GibSprite(pMissile, (hitCode == 2) ? GIBTYPE_23 : GIBTYPE_22, NULL, NULL);
|
|
evKill(missileActor);
|
|
seqKill(missileActor);
|
|
actPostSprite(missileActor, kStatFree);
|
|
if (hitCode == 3 && actorHit)
|
|
{
|
|
int nDamage = (15 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageTesla, nDamage);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
seqKill(missileActor);
|
|
actPostSprite(missileActor, kStatFree);
|
|
if (hitCode == 3 && actorHit)
|
|
{
|
|
int nDamage = (10 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageFall, nDamage);
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (gModernMap && pXSpriteHit && pXSpriteHit->state != pXSpriteHit->restState && pXSpriteHit->Impact)
|
|
trTriggerSprite(actorHit, kCmdSpriteImpact);
|
|
#endif
|
|
pMissile->cstat &= ~257;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actKickObject(DBloodActor* kicker, DBloodActor* kicked)
|
|
{
|
|
int nSpeed = ClipLow(approxDist(kicker->xvel(), kicker->yvel()) * 2, 0xaaaaa);
|
|
kicked->xvel() = MulScale(nSpeed, Cos(kicker->s().ang + Random2(85)), 30);
|
|
kicked->yvel() = MulScale(nSpeed, Sin(kicker->s().ang + Random2(85)), 30);
|
|
kicked->zvel() = MulScale(nSpeed, -0x2000, 14);
|
|
kicked->s().flags = 7;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actTouchFloor(DBloodActor* actor, int nSector)
|
|
{
|
|
assert(actor != nullptr);
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
sectortype* pSector = §or[nSector];
|
|
XSECTOR* pXSector = nullptr;
|
|
if (pSector->extra > 0) pXSector = &xsector[pSector->extra];
|
|
|
|
bool doDamage = (pXSector && (pSector->type == kSectorDamage || pXSector->damageType > 0));
|
|
// don't allow damage for damage sectors if they are not enabled
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (gModernMap && doDamage && pSector->type == kSectorDamage && !pXSector->state)
|
|
doDamage = false;
|
|
#endif
|
|
|
|
if (doDamage) {
|
|
DAMAGE_TYPE nDamageType;
|
|
if (pSector->type == kSectorDamage) nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType, kDamageFall, kDamageTesla);
|
|
else nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType - 1, kDamageFall, kDamageTesla);
|
|
|
|
int nDamage;
|
|
if (pXSector->data) nDamage = ClipRange(pXSector->data, 0, 1000);
|
|
else nDamage = 1000;
|
|
|
|
actDamageSprite(actor, actor, nDamageType, scale(4, nDamage, 120) << 4);
|
|
}
|
|
if (tileGetSurfType(nSector + 0x4000) == kSurfLava)
|
|
{
|
|
actDamageSprite(actor, actor, kDamageBurn, 16);
|
|
sfxPlay3DSound(&actor->s(), 352, 5, 2);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkCeilHit(DBloodActor* actor)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = actor->hasX() ? &actor->x() : nullptr;
|
|
|
|
Collision coll(actor->hit().ceilhit);
|
|
switch (coll.type)
|
|
{
|
|
case kHitWall:
|
|
break;
|
|
case kHitSprite:
|
|
if (coll.actor->hasX())
|
|
{
|
|
auto actor2 = coll.actor;
|
|
spritetype* pSprite2 = &actor2->s();
|
|
XSPRITE* pXSprite2 = &actor2->x();
|
|
if ((pSprite2->statnum == kStatThing || pSprite2->statnum == kStatDude) && (actor->xvel() != 0 || actor->yvel() != 0 || actor->zvel() != 0))
|
|
{
|
|
if (pSprite2->statnum == kStatThing)
|
|
{
|
|
int nType = pSprite2->type - kThingBase;
|
|
const THINGINFO* pThingInfo = &thingInfo[nType];
|
|
if (pThingInfo->flags & 1) pSprite2->flags |= 1;
|
|
if (pThingInfo->flags & 2) pSprite2->flags |= 4;
|
|
// Inlined ?
|
|
actor2->xvel() += MulScale(4, pSprite2->x - pSprite->x, 2);
|
|
actor2->yvel() += MulScale(4, pSprite2->y - pSprite->y, 2);
|
|
}
|
|
else
|
|
{
|
|
pSprite2->flags |= 5;
|
|
actor2->xvel() += MulScale(4, pSprite2->x - pSprite->x, 2);
|
|
actor2->yvel() += MulScale(4, pSprite2->y - pSprite->y, 2);
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add size shroom abilities
|
|
if ((actor->IsPlayerActor() && isShrinked(pSprite)) || (IsPlayerSprite(pSprite2) && isGrown(pSprite2))) {
|
|
|
|
int mass1 = getDudeInfo(pSprite2->type)->mass;
|
|
int mass2 = getDudeInfo(pSprite->type)->mass;
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
mass2 = getSpriteMassBySize(pSprite);
|
|
break;
|
|
}
|
|
if (mass1 > mass2)
|
|
{
|
|
int dmg = abs((mass1 - mass2) * (pSprite2->clipdist - pSprite->clipdist));
|
|
if (actor2->IsDudeActor())
|
|
{
|
|
if (dmg > 0) actDamageSprite(pSprite2->index, pSprite, (Chance(0x2000)) ? kDamageFall : (Chance(0x4000)) ? kDamageExplode : kDamageBullet, dmg);
|
|
if (Chance(0x0200)) actKickObject(actor2, actor);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (!actor->IsPlayerActor() || gPlayer[pSprite->type - kDudePlayer1].godMode == 0)
|
|
{
|
|
switch (pSprite2->type)
|
|
{
|
|
case kDudeTchernobog:
|
|
actDamageSprite(actor2, actor, kDamageExplode, pXSprite->health << 2);
|
|
break;
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
int dmg = 0;
|
|
if (!actor->IsDudeActor() || (dmg = ClipLow((getSpriteMassBySize(pSprite2) - getSpriteMassBySize(pSprite)) >> 1, 0)) == 0)
|
|
break;
|
|
|
|
if (!actor->IsPlayerActor())
|
|
{
|
|
actDamageSprite(actor2, actor, kDamageFall, dmg);
|
|
if (pXSprite && !actor->isActive()) aiActivateDude(actor);
|
|
}
|
|
else if (powerupCheck(&gPlayer[pSprite->type - kDudePlayer1], kPwUpJumpBoots) > 0) actDamageSprite(actor2, actor, kDamageExplode, dmg);
|
|
else actDamageSprite(actor2, actor, kDamageFall, dmg);
|
|
break;
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pSprite2->type == kTrapSawCircular)
|
|
{
|
|
if (!pXSprite2->state) actDamageSprite(actor, actor, kDamageBullet, 1);
|
|
else {
|
|
pXSprite2->data1 = 1;
|
|
pXSprite2->data2 = ClipHigh(pXSprite2->data2 + 8, 600);
|
|
actDamageSprite(actor, actor, kDamageBullet, 16);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkHit(DBloodActor* actor)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = actor->hasX() ? &actor->x() : nullptr;
|
|
|
|
Collision coll(actor->hit().hit);
|
|
switch (coll.type)
|
|
{
|
|
case kHitWall:
|
|
break;
|
|
case kHitSprite:
|
|
if (coll.actor->hasX())
|
|
{
|
|
auto actor2 = coll.actor;
|
|
spritetype* pSprite2 = &actor2->s();
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add size shroom abilities
|
|
if ((actor2->IsPlayerActor() && isShrinked(pSprite2)) || (actor->IsPlayerActor() && isGrown(pSprite)))
|
|
{
|
|
if (actor->xvel() != 0 && actor2->IsDudeActor())
|
|
{
|
|
int mass1 = getDudeInfo(pSprite->type)->mass;
|
|
int mass2 = getDudeInfo(pSprite2->type)->mass;
|
|
switch (pSprite2->type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
mass2 = getSpriteMassBySize(pSprite2);
|
|
break;
|
|
}
|
|
if (mass1 > mass2)
|
|
{
|
|
actKickObject(actor, actor2);
|
|
sfxPlay3DSound(pSprite, 357, -1, 1);
|
|
int dmg = (mass1 - mass2) + abs(FixedToInt(actor->xvel()));
|
|
if (dmg > 0) actDamageSprite(actor, actor2, (Chance(0x2000)) ? kDamageFall : kDamageBullet, dmg);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (pSprite2->type)
|
|
{
|
|
case kThingKickablePail:
|
|
actKickObject(actor, actor2);
|
|
break;
|
|
|
|
case kThingZombieHead:
|
|
sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
|
|
actKickObject(actor, actor2);
|
|
actDamageSprite(nullptr, actor2, kDamageFall, 80);
|
|
break;
|
|
|
|
case kDudeBurningInnocent:
|
|
case kDudeBurningCultist:
|
|
case kDudeBurningZombieAxe:
|
|
case kDudeBurningZombieButcher:
|
|
// This does not make sense
|
|
pXSprite->burnTime = ClipLow(pXSprite->burnTime - 4, 0);
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkFloorHit(DBloodActor* actor)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = actor->hasX() ? &actor->x() : nullptr;
|
|
|
|
Collision coll(actor->hit().florhit);
|
|
switch (coll.type)
|
|
{
|
|
case kHitWall:
|
|
break;
|
|
case kHitSector:
|
|
actTouchFloor(actor, coll.index);
|
|
break;
|
|
case kHitSprite:
|
|
if (coll.actor->hasX())
|
|
{
|
|
auto actor2 = coll.actor;
|
|
spritetype* pSprite2 = &actor2->s();
|
|
XSPRITE* pXSprite2 = &actor2->x();
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add size shroom abilities
|
|
if ((actor2->IsPlayerActor() && isShrinked(pSprite2)) || (actor->IsPlayerActor() && isGrown(pSprite)))
|
|
{
|
|
|
|
int mass1 = getDudeInfo(pSprite->type)->mass;
|
|
int mass2 = getDudeInfo(pSprite2->type)->mass;
|
|
switch (pSprite2->type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
mass2 = getSpriteMassBySize(pSprite2);
|
|
break;
|
|
}
|
|
|
|
if (mass1 > mass2 && IsDudeSprite(pSprite2))
|
|
{
|
|
if ((IsPlayerSprite(pSprite2) && Chance(0x500)) || !IsPlayerSprite(pSprite2))
|
|
actKickObject(actor, actor2);
|
|
|
|
int dmg = (mass1 - mass2) + pSprite->clipdist;
|
|
if (dmg > 0) actDamageSprite(actor, actor2, (Chance(0x2000)) ? kDamageFall : kDamageBullet, dmg);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PLAYER* pPlayer = nullptr;
|
|
if (actor->IsPlayerActor()) pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
|
|
|
|
switch (pSprite2->type)
|
|
{
|
|
case kThingKickablePail:
|
|
if (pPlayer)
|
|
{
|
|
if (pPlayer->kickPower > PlayClock) return;
|
|
pPlayer->kickPower = PlayClock + 60;
|
|
}
|
|
actKickObject(actor, actor2);
|
|
sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
|
|
sfxPlay3DSound(pSprite, 374, 0, 0);
|
|
break;
|
|
case kThingZombieHead:
|
|
if (pPlayer)
|
|
{
|
|
if (pPlayer->kickPower > PlayClock) return;
|
|
pPlayer->kickPower = PlayClock + 60;
|
|
}
|
|
actKickObject(actor, actor2);
|
|
sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
|
|
actDamageSprite(-1, pSprite2, kDamageFall, 80);
|
|
break;
|
|
case kTrapSawCircular:
|
|
if (!pXSprite2->state) actDamageSprite(actor, actor, kDamageBullet, 1);
|
|
else
|
|
{
|
|
pXSprite2->data1 = 1;
|
|
pXSprite2->data2 = ClipHigh(pXSprite2->data2 + 8, 600);
|
|
actDamageSprite(actor, actor, kDamageBullet, 16);
|
|
}
|
|
break;
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
case kDudeZombieAxeNormal:
|
|
case kDudeZombieButcher:
|
|
case kDudeZombieAxeBuried:
|
|
case kDudeGargoyleFlesh:
|
|
case kDudeGargoyleStone:
|
|
case kDudePhantasm:
|
|
case kDudeHellHound:
|
|
case kDudeHand:
|
|
case kDudeSpiderBrown:
|
|
case kDudeSpiderRed:
|
|
case kDudeSpiderBlack:
|
|
case kDudeGillBeast:
|
|
case kDudeBat:
|
|
case kDudeRat:
|
|
case kDudePodGreen:
|
|
case kDudeTentacleGreen:
|
|
case kDudePodFire:
|
|
case kDudeTentacleFire:
|
|
case kDudePodMother:
|
|
case kDudeTentacleMother:
|
|
case kDudeCerberusTwoHead:
|
|
case kDudeCerberusOneHead:
|
|
case kDudeTchernobog:
|
|
case kDudePlayer1:
|
|
case kDudePlayer2:
|
|
case kDudePlayer3:
|
|
case kDudePlayer4:
|
|
case kDudePlayer5:
|
|
case kDudePlayer6:
|
|
case kDudePlayer7:
|
|
case kDudePlayer8:
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (pPlayer && !isShrinked(pSprite))
|
|
#else
|
|
if (pPlayer)
|
|
#endif
|
|
actDamageSprite(actor, actor2, kDamageBullet, 8);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void ProcessTouchObjects(DBloodActor* actor)
|
|
{
|
|
checkCeilHit(actor);
|
|
checkHit(actor);
|
|
checkFloorHit(actor);
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add more trigger statements for Touch flag
|
|
if (gModernMap && actor->IsDudeActor())
|
|
{
|
|
DBloodActor* actor2 = nullptr;
|
|
for (int i : { actor->hit().hit, actor->hit().florhit, actor->hit().ceilhit})
|
|
{
|
|
Collision coll = i;
|
|
if (coll.type == kHitSprite)
|
|
{
|
|
actor2 = coll.actor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (actor2 && actor2->hasX())
|
|
{
|
|
XSPRITE* pXHSprite = &actor2->x();
|
|
if (pXHSprite->Touch && !pXHSprite->isTriggered && (!pXHSprite->DudeLockout || actor->IsPlayerActor()))
|
|
trTriggerSprite(actor2, kCmdSpriteTouch);
|
|
}
|
|
|
|
// Touch walls
|
|
Collision coll = actor->hit().hit;
|
|
int nHWall = -1;
|
|
if (coll.type == kHitWall)
|
|
{
|
|
nHWall = coll.index;
|
|
if (wallRangeIsFine(nHWall) && xwallRangeIsFine(wall[nHWall].extra))
|
|
{
|
|
XWALL* pXHWall = &xwall[wall[nHWall].extra];
|
|
if (pXHWall->triggerTouch && !pXHWall->isTriggered && (!pXHWall->dudeLockout || actor->IsPlayerActor()))
|
|
trTriggerWall(nHWall, pXHWall, kCmdWallTouch);
|
|
}
|
|
}
|
|
|
|
// enough to reset SpriteHit values
|
|
if (nHWall != -1 || actor2) actor->xvel() += 5;
|
|
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actAirDrag(DBloodActor* actor, int a2)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
|
|
int wind_x = 0;
|
|
int wind_y = 0;
|
|
int nSector = pSprite->sectnum;
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
sectortype* pSector = §or[nSector];
|
|
int nXSector = pSector->extra;
|
|
if (nXSector > 0)
|
|
{
|
|
assert(nXSector < kMaxXSectors);
|
|
XSECTOR* pXSector = &xsector[nXSector];
|
|
if (pXSector->windVel && (pXSector->windAlways || pXSector->busy))
|
|
{
|
|
int wind = pXSector->windVel << 12;
|
|
if (!pXSector->windAlways && pXSector->busy) wind = MulScale(wind, pXSector->busy, 16);
|
|
wind_x = MulScale(wind, Cos(pXSector->windAng), 30);
|
|
wind_y = MulScale(wind, Sin(pXSector->windAng), 30);
|
|
}
|
|
}
|
|
actor->xvel() += MulScale(wind_x - actor->xvel(), a2, 16);
|
|
actor->yvel() += MulScale(wind_y - actor->yvel(), a2, 16);
|
|
actor->zvel() -= MulScale(actor->zvel(), a2, 16);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int MoveThing(DBloodActor* actor)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
assert(actor->hasX());
|
|
XSPRITE* pXSprite = &actor->x();
|
|
int lhit = 0;
|
|
assert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
|
|
const THINGINFO* pThingInfo = &thingInfo[pSprite->type - kThingBase];
|
|
int nSector = pSprite->sectnum;
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
int top, bottom;
|
|
|
|
GetActorExtents(actor, &top, &bottom);
|
|
const int bakCompat = enginecompatibility_mode;
|
|
if (actor->xvel() || actor->yvel())
|
|
{
|
|
short bakCstat = pSprite->cstat;
|
|
pSprite->cstat &= ~257;
|
|
if ((pSprite->owner >= 0) && !cl_bloodvanillaexplosions && !VanillaMode())
|
|
enginecompatibility_mode = ENGINECOMPATIBILITY_NONE; // improved clipmove accuracy
|
|
lhit = actor->hit().hit = ClipMove(&pSprite->x, &pSprite->y, &pSprite->z, &nSector, actor->xvel() >> 12, actor->yvel() >> 12, pSprite->clipdist << 2, (pSprite->z - top) / 4, (bottom - pSprite->z) / 4, CLIPMASK0);
|
|
enginecompatibility_mode = bakCompat; // restore
|
|
pSprite->cstat = bakCstat;
|
|
assert(nSector >= 0);
|
|
if (pSprite->sectnum != nSector)
|
|
{
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
ChangeSpriteSect(pSprite->index, nSector);
|
|
}
|
|
|
|
Collision coll(actor->hit().hit);
|
|
if (coll.type == kHitWall)
|
|
{
|
|
int nHitWall = coll.index;
|
|
actWallBounceVector(&actor->xvel(), &actor->yvel(), nHitWall, pThingInfo->elastic);
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingZombieHead:
|
|
sfxPlay3DSound(pSprite, 607, 0, 0);
|
|
actDamageSprite(nullptr, actor, kDamageFall, 80);
|
|
break;
|
|
|
|
case kThingKickablePail:
|
|
sfxPlay3DSound(pSprite, 374, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
|
|
}
|
|
|
|
pSprite->z += actor->zvel() >> 8;
|
|
|
|
int ceilZ, ceilHit, floorZ, floorHit;
|
|
GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist << 2, CLIPMASK0);
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
if ((pSprite->flags & 2) && bottom < floorZ)
|
|
{
|
|
pSprite->z += 455;
|
|
actor->zvel() += 58254;
|
|
if (pSprite->type == kThingZombieHead)
|
|
{
|
|
auto* fxActor = gFX.fxSpawnActor(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
|
|
if (fxActor)
|
|
{
|
|
int v34 = (PlayClock * 3) & 2047;
|
|
int v30 = (PlayClock * 5) & 2047;
|
|
int vbx = (PlayClock * 11) & 2047;
|
|
int v2c = 0x44444;
|
|
int v28 = 0;
|
|
int v24 = 0;
|
|
RotateVector(&v2c, &v28, vbx);
|
|
RotateVector(&v2c, &v24, v30);
|
|
RotateVector(&v28, &v24, v34);
|
|
fxActor->xvel() = actor->xvel() + v2c;
|
|
fxActor->yvel() = actor->yvel() + v28;
|
|
fxActor->zvel() = actor->zvel() + v24;
|
|
}
|
|
}
|
|
}
|
|
if (CheckLink(pSprite)) GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist << 2, CLIPMASK0);
|
|
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (bottom >= floorZ)
|
|
{
|
|
actTouchFloor(actor, pSprite->sectnum);
|
|
actor->hit().florhit = floorHit;
|
|
pSprite->z += floorZ - bottom;
|
|
|
|
int v20 = actor->zvel() - velFloor[pSprite->sectnum];
|
|
if (v20 > 0)
|
|
{
|
|
|
|
pSprite->flags |= 4;
|
|
int vax = actFloorBounceVector(&actor->xvel(), &actor->yvel(), (int*)&v20, pSprite->sectnum, pThingInfo->elastic);
|
|
int nDamage = MulScale(vax, vax, 30) - pThingInfo->dmgResist;
|
|
if (nDamage > 0) actDamageSprite(actor, actor, kDamageFall, nDamage);
|
|
|
|
actor->zvel() = v20;
|
|
if (velFloor[pSprite->sectnum] == 0 && abs(actor->zvel()) < 0x10000)
|
|
{
|
|
actor->zvel() = 0;
|
|
pSprite->flags &= ~4;
|
|
}
|
|
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingNapalmBall:
|
|
if (actor->zvel() == 0 || Chance(0xA000)) actNapalmMove(actor);
|
|
break;
|
|
|
|
case kThingZombieHead:
|
|
if (abs(actor->zvel()) > 0x80000)
|
|
{
|
|
sfxPlay3DSound(pSprite, 607, 0, 0);
|
|
actDamageSprite(nullptr, actor, kDamageFall, 80);
|
|
}
|
|
break;
|
|
|
|
case kThingKickablePail:
|
|
if (abs(actor->zvel()) > 0x80000)
|
|
sfxPlay3DSound(pSprite, 374, 0, 0);
|
|
break;
|
|
}
|
|
|
|
lhit = kHitSector | nSector;
|
|
}
|
|
else if (actor->zvel() == 0)
|
|
|
|
pSprite->flags &= ~4;
|
|
}
|
|
else
|
|
{
|
|
actor->hit().florhit = 0;
|
|
|
|
if (pSprite->flags & 2)
|
|
pSprite->flags |= 4;
|
|
}
|
|
|
|
if (top <= ceilZ)
|
|
{
|
|
actor->hit().ceilhit = ceilHit;
|
|
pSprite->z += ClipLow(ceilZ - top, 0);
|
|
if (actor->zvel() < 0)
|
|
{
|
|
actor->xvel() = MulScale(actor->xvel(), 0xc000, 16);
|
|
actor->yvel() = MulScale(actor->yvel(), 0xc000, 16);
|
|
actor->zvel() = MulScale(-actor->zvel(), 0x4000, 16);
|
|
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingZombieHead:
|
|
if (abs(actor->zvel()) > 0x80000)
|
|
{
|
|
sfxPlay3DSound(pSprite, 607, 0, 0);
|
|
actDamageSprite(nullptr, actor, kDamageFall, 80);
|
|
}
|
|
break;
|
|
|
|
case kThingKickablePail:
|
|
if (abs(actor->zvel()) > 0x80000)
|
|
sfxPlay3DSound(pSprite, 374, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else actor->hit().ceilhit = 0;
|
|
|
|
if (bottom >= floorZ)
|
|
{
|
|
int nVel = approxDist(actor->xvel(), actor->yvel());
|
|
int nVelClipped = ClipHigh(nVel, 0x11111);
|
|
Collision coll(floorHit);
|
|
|
|
if (coll.type == kHitSprite)
|
|
{
|
|
auto hitActor = coll.actor;
|
|
auto hitSpr = &hitActor->s();
|
|
if ((hitSpr->cstat & 0x30) == 0)
|
|
{
|
|
actor->xvel() += MulScale(4, pSprite->x - hitSpr->x, 2);
|
|
actor->yvel() += MulScale(4, pSprite->y - hitSpr->y, 2);
|
|
lhit = actor->hit().hit;
|
|
}
|
|
}
|
|
if (nVel > 0)
|
|
{
|
|
int t = DivScale(nVelClipped, nVel, 16);
|
|
actor->xvel() -= MulScale(t, actor->xvel(), 16);
|
|
actor->yvel() -= MulScale(t, actor->yvel(), 16);
|
|
}
|
|
}
|
|
if (actor->xvel() || actor->yvel())
|
|
pSprite->ang = getangle(actor->xvel(), actor->yvel());
|
|
return lhit;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void MoveDude(DBloodActor* actor)
|
|
{
|
|
auto const pSprite = &actor->s();
|
|
auto const pXSprite = &actor->x();
|
|
PLAYER* pPlayer = nullptr;
|
|
if (actor->IsPlayerActor()) pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
|
|
if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
|
|
{
|
|
Printf(PRINT_HIGH, "%d: pSprite->type >= kDudeBase && pSprite->type < kDudeMax", pSprite->type);
|
|
return;
|
|
}
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
int bz = (bottom - pSprite->z) / 4;
|
|
int tz = (pSprite->z - top) / 4;
|
|
int wd = pSprite->clipdist << 2;
|
|
int nSector = pSprite->sectnum;
|
|
int nAiStateType = (pXSprite->aiState) ? pXSprite->aiState->stateType : -1;
|
|
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
|
|
if (actor->xvel() || actor->yvel())
|
|
{
|
|
if (pPlayer && gNoClip)
|
|
{
|
|
pSprite->x += actor->xvel() >> 12;
|
|
pSprite->y += actor->yvel() >> 12;
|
|
if (!FindSector(pSprite->x, pSprite->y, &nSector))
|
|
nSector = pSprite->sectnum;
|
|
}
|
|
else
|
|
{
|
|
short bakCstat = pSprite->cstat;
|
|
pSprite->cstat &= ~257;
|
|
actor->hit().hit = ClipMove(&pSprite->x, &pSprite->y, &pSprite->z, &nSector, actor->xvel() >> 12, actor->yvel() >> 12, wd, tz, bz, CLIPMASK0);
|
|
if (nSector == -1)
|
|
{
|
|
nSector = pSprite->sectnum;
|
|
if (pSprite->statnum == kStatDude || pSprite->statnum == kStatThing)
|
|
actDamageSprite(pSprite->index, pSprite, kDamageFall, 1000 << 4);
|
|
}
|
|
|
|
if (sector[nSector].type >= kSectorPath && sector[nSector].type <= kSectorRotate)
|
|
{
|
|
short nSector2 = nSector;
|
|
if (pushmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector2, wd, tz, bz, CLIPMASK0) == -1)
|
|
actDamageSprite(actor, actor, kDamageFall, 1000 << 4);
|
|
if (nSector2 != -1)
|
|
nSector = nSector2;
|
|
}
|
|
assert(nSector >= 0);
|
|
pSprite->cstat = bakCstat;
|
|
}
|
|
Collision coll = actor->hit().hit;
|
|
switch (actor->hit().hit & 0xc000)
|
|
{
|
|
case kHitSprite:
|
|
{
|
|
spritetype* pHitSprite = &coll.actor->s();
|
|
XSPRITE* pHitXSprite = coll.actor->hasX() ? &coll.actor->x() : nullptr;;
|
|
|
|
auto Owner = coll.actor->GetOwner();
|
|
|
|
if (pHitSprite->statnum == kStatProjectile && !(pHitSprite->flags & 32) && actor != Owner)
|
|
{
|
|
HITINFO hitInfo = gHitInfo;
|
|
gHitInfo.hitsprite = pSprite->index;
|
|
actImpactMissile(actor, 3);
|
|
gHitInfo = hitInfo;
|
|
}
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (!gModernMap && pHitXSprite && pHitXSprite->Touch && !pHitXSprite->state && !pHitXSprite->isTriggered)
|
|
trTriggerSprite(coll.actor, kCmdSpriteTouch);
|
|
#else
|
|
if (pHitXSprite && pHitXSprite->Touch && !pHitXSprite->state && !pHitXSprite->isTriggered)
|
|
trTriggerSprite(coll.actor, kCmdSpriteTouch);
|
|
#endif
|
|
|
|
if (pDudeInfo->lockOut && pHitXSprite && pHitXSprite->Push && !pHitXSprite->key && !pHitXSprite->DudeLockout && !pHitXSprite->state && !pHitXSprite->busy && !pPlayer)
|
|
trTriggerSprite(coll.actor, kCmdSpritePush);
|
|
|
|
break;
|
|
}
|
|
case kHitWall:
|
|
{
|
|
int nHitWall = coll.index;
|
|
walltype* pHitWall = &wall[nHitWall];
|
|
XWALL* pHitXWall = nullptr;
|
|
if (pHitWall->extra > 0) pHitXWall = &xwall[pHitWall->extra];
|
|
|
|
if (pDudeInfo->lockOut && pHitXWall && pHitXWall->triggerPush && !pHitXWall->key && !pHitXWall->dudeLockout && !pHitXWall->state && !pHitXWall->busy && !pPlayer)
|
|
trTriggerWall(nHitWall, pHitXWall, kCmdWallPush);
|
|
|
|
if (pHitWall->nextsector != -1)
|
|
{
|
|
sectortype* pHitSector = §or[pHitWall->nextsector];
|
|
XSECTOR* pHitXSector = nullptr;
|
|
if (pHitSector->extra > 0)
|
|
pHitXSector = &xsector[pHitSector->extra];
|
|
|
|
if (pDudeInfo->lockOut && pHitXSector && pHitXSector->Wallpush && !pHitXSector->Key && !pHitXSector->dudeLockout && !pHitXSector->state && !pHitXSector->busy && !pPlayer)
|
|
trTriggerSector(pHitWall->nextsector, pHitXSector, kCmdSectorPush);
|
|
|
|
if (top < pHitSector->ceilingz || bottom > pHitSector->floorz)
|
|
{
|
|
// ???
|
|
}
|
|
}
|
|
actWallBounceVector((int*)&actor->xvel(), (int*)&actor->yvel(), nHitWall, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
|
|
}
|
|
if (pSprite->sectnum != nSector)
|
|
{
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
XSECTOR* pXSector;
|
|
int nXSector = sector[pSprite->sectnum].extra;
|
|
if (nXSector > 0)
|
|
pXSector = &xsector[nXSector];
|
|
else
|
|
pXSector = nullptr;
|
|
if (pXSector && pXSector->Exit && (pPlayer || !pXSector->dudeLockout))
|
|
trTriggerSector(pSprite->sectnum, pXSector, kCmdSectorExit);
|
|
ChangeSpriteSect(pSprite->index, nSector);
|
|
|
|
nXSector = sector[nSector].extra;
|
|
pXSector = (nXSector > 0) ? &xsector[nXSector] : nullptr;
|
|
if (pXSector && pXSector->Enter && (pPlayer || !pXSector->dudeLockout))
|
|
{
|
|
if (sector[nSector].type == kSectorTeleport)
|
|
pXSector->data = pPlayer ? pSprite->index : -1;
|
|
trTriggerSector(nSector, pXSector, kCmdSectorEnter);
|
|
}
|
|
|
|
nSector = pSprite->sectnum;
|
|
}
|
|
int bUnderwater = 0;
|
|
int bDepth = 0;
|
|
if (sector[nSector].extra > 0)
|
|
{
|
|
XSECTOR* pXSector = &xsector[sector[nSector].extra];
|
|
if (pXSector->Underwater) bUnderwater = 1;
|
|
if (pXSector->Depth) bDepth = 1;
|
|
}
|
|
auto pUpperLink = getUpperLink(nSector);
|
|
auto pLowerLink = getLowerLink(nSector);
|
|
if (pUpperLink && (pUpperLink->s().type == kMarkerUpWater || pUpperLink->s().type == kMarkerUpGoo)) bDepth = 1;
|
|
if (pLowerLink && (pLowerLink->s().type == kMarkerLowWater || pLowerLink->s().type == kMarkerLowGoo)) bDepth = 1;
|
|
if (pPlayer) wd += 16;
|
|
if (actor->zvel()) pSprite->z += actor->zvel() >> 8;
|
|
|
|
int ceilZ, ceilHit, floorZ, floorHit;
|
|
GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
if (pSprite->flags & 2)
|
|
{
|
|
int vc = 58254;
|
|
if (bDepth)
|
|
{
|
|
if (bUnderwater)
|
|
{
|
|
int cz = getceilzofslope(nSector, pSprite->x, pSprite->y);
|
|
if (cz > top)
|
|
vc += ((bottom - cz) * -80099) / (bottom - top);
|
|
else
|
|
vc = 0;
|
|
}
|
|
else
|
|
{
|
|
int fz = getflorzofslope(nSector, pSprite->x, pSprite->y);
|
|
if (fz < bottom)
|
|
vc += ((bottom - fz) * -80099) / (bottom - top);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bUnderwater)
|
|
vc = 0;
|
|
else if (bottom >= floorZ)
|
|
vc = 0;
|
|
}
|
|
if (vc)
|
|
{
|
|
pSprite->z += ((vc * 4) / 2) >> 8;
|
|
actor->zvel() += vc;
|
|
}
|
|
}
|
|
if (pPlayer && actor->zvel() > 0x155555 && !pPlayer->fallScream && pXSprite->height > 0)
|
|
{
|
|
const bool playerAlive = (pXSprite->health > 0) || VanillaMode(); // only trigger falling scream if player is alive or vanilla mode
|
|
if (playerAlive)
|
|
{
|
|
pPlayer->fallScream = 1;
|
|
sfxPlay3DSound(pSprite, 719, 0, 0);
|
|
}
|
|
}
|
|
vec3_t const oldpos = pSprite->pos;
|
|
int nLink = CheckLink(pSprite);
|
|
if (nLink)
|
|
{
|
|
GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
|
|
if (pPlayer)
|
|
playerCorrectInertia(pPlayer, &oldpos);
|
|
switch (nLink)
|
|
{
|
|
case kMarkerLowStack:
|
|
if (pPlayer == gView)
|
|
setgotpic(sector[pSprite->sectnum].floorpicnum);
|
|
break;
|
|
case kMarkerUpStack:
|
|
if (pPlayer == gView)
|
|
setgotpic(sector[pSprite->sectnum].ceilingpicnum);
|
|
break;
|
|
case kMarkerLowWater:
|
|
case kMarkerLowGoo:
|
|
pXSprite->medium = kMediumNormal;
|
|
if (pPlayer)
|
|
{
|
|
pPlayer->posture = 0;
|
|
pPlayer->bubbleTime = 0;
|
|
if (!pPlayer->cantJump && (pPlayer->input.actions & SB_JUMP))
|
|
{
|
|
actor->zvel() = -0x6aaaa;
|
|
pPlayer->cantJump = 1;
|
|
}
|
|
sfxPlay3DSound(pSprite, 721, -1, 0);
|
|
}
|
|
else
|
|
{
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
aiNewState(&bloodActors[pXSprite->reference], &cultistGoto);
|
|
break;
|
|
case kDudeGillBeast:
|
|
aiNewState(&bloodActors[pXSprite->reference], &gillBeastGoto);
|
|
pSprite->flags |= 6;
|
|
break;
|
|
case kDudeBoneEel:
|
|
actKillDude(pSprite->index, pSprite, kDamageFall, 1000 << 4);
|
|
break;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (IsDudeSprite(pSprite) && pXSprite->health > 0 && aiInPatrolState(nAiStateType))
|
|
aiPatrolState(pSprite, kAiStatePatrolMoveL); // continue patrol when going from water
|
|
#endif
|
|
}
|
|
break;
|
|
case kMarkerUpWater:
|
|
case kMarkerUpGoo:
|
|
{
|
|
int chance = 0xa00; int medium = kMediumWater;
|
|
if (nLink == kMarkerUpGoo) {
|
|
medium = kMediumGoo;
|
|
chance = 0x400;
|
|
}
|
|
|
|
pXSprite->medium = medium;
|
|
|
|
if (pPlayer)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
// look for palette in data2 of marker. If value <= 0, use default ones.
|
|
if (gModernMap)
|
|
{
|
|
pPlayer->nWaterPal = 0;
|
|
auto pUpper = getUpperLink(nSector);
|
|
if (pUpper && pUpper->hasX()) pPlayer->nWaterPal = pUpper->x().data2;
|
|
}
|
|
#endif
|
|
|
|
pPlayer->posture = 1;
|
|
pXSprite->burnTime = 0;
|
|
pPlayer->bubbleTime = abs(actor->zvel()) >> 12;
|
|
evPost(actor, 0, kCallbackPlayerBubble);
|
|
sfxPlay3DSound(pSprite, 720, -1, 0);
|
|
}
|
|
else
|
|
{
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
pXSprite->burnTime = 0;
|
|
evPost(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(pSprite, 720, -1, 0);
|
|
aiNewState(actor, &cultistSwimGoto);
|
|
break;
|
|
case kDudeBurningCultist:
|
|
{
|
|
const bool fixRandomCultist = !cl_bloodvanillaenemies && (pSprite->inittype >= kDudeBase) && (pSprite->inittype < kDudeMax) && !VanillaMode(); // fix burning cultists randomly switching types underwater
|
|
if (Chance(chance))
|
|
pSprite->type = kDudeCultistTommy;
|
|
else
|
|
pSprite->type = kDudeCultistShotgun;
|
|
if (fixRandomCultist) // fix burning cultists randomly switching types underwater
|
|
pSprite->type = pSprite->inittype; // restore back to spawned cultist type
|
|
pXSprite->burnTime = 0;
|
|
evPost(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(pSprite, 720, -1, 0);
|
|
aiNewState(actor, &cultistSwimGoto);
|
|
break;
|
|
}
|
|
case kDudeZombieAxeNormal:
|
|
pXSprite->burnTime = 0;
|
|
evPost(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(pSprite, 720, -1, 0);
|
|
aiNewState(actor, &zombieAGoto);
|
|
break;
|
|
case kDudeZombieButcher:
|
|
pXSprite->burnTime = 0;
|
|
evPost(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(pSprite, 720, -1, 0);
|
|
aiNewState(actor, &zombieFGoto);
|
|
break;
|
|
case kDudeGillBeast:
|
|
pXSprite->burnTime = 0;
|
|
evPost(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(pSprite, 720, -1, 0);
|
|
aiNewState(actor, &gillBeastSwimGoto);
|
|
|
|
pSprite->flags &= ~6;
|
|
break;
|
|
case kDudeGargoyleFlesh:
|
|
case kDudeHellHound:
|
|
case kDudeSpiderBrown:
|
|
case kDudeSpiderRed:
|
|
case kDudeSpiderBlack:
|
|
case kDudeBat:
|
|
case kDudeRat:
|
|
case kDudeBurningInnocent:
|
|
actKillDude(pSprite->index, pSprite, kDamageFall, 1000 << 4);
|
|
break;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (gModernMap) {
|
|
|
|
if (pSprite->type == kDudeModernCustom) {
|
|
|
|
evPost(actor, 0, kCallbackEnemeyBubble);
|
|
if (!canSwim(pSprite)) actKillDude(actor, actor, kDamageFall, 1000 << 4);
|
|
break;
|
|
}
|
|
|
|
// continue patrol when fall into water
|
|
if (IsDudeSprite(pSprite) && pXSprite->health > 0 && aiInPatrolState(nAiStateType))
|
|
aiPatrolState(pSprite, kAiStatePatrolMoveW);
|
|
|
|
}
|
|
#endif
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (pPlayer && bottom >= floorZ)
|
|
{
|
|
int floorZ2 = floorZ;
|
|
int floorHit2 = floorHit;
|
|
GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist << 2, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
|
|
if (bottom <= floorZ && pSprite->z - floorZ2 < bz)
|
|
{
|
|
floorZ = floorZ2;
|
|
floorHit = floorHit2;
|
|
}
|
|
}
|
|
if (floorZ <= bottom)
|
|
{
|
|
actor->hit().florhit = floorHit;
|
|
pSprite->z += floorZ - bottom;
|
|
int v30 = actor->zvel() - velFloor[pSprite->sectnum];
|
|
if (v30 > 0)
|
|
{
|
|
int vax = actFloorBounceVector((int*)&actor->xvel(), (int*)&actor->yvel(), (int*)&v30, pSprite->sectnum, 0);
|
|
int nDamage = MulScale(vax, vax, 30);
|
|
if (pPlayer)
|
|
{
|
|
pPlayer->fallScream = 0;
|
|
|
|
if (nDamage > (15 << 4) && (pSprite->flags & 4))
|
|
playerLandingSound(pPlayer);
|
|
if (nDamage > (30 << 4))
|
|
sfxPlay3DSound(pSprite, 701, 0, 0);
|
|
}
|
|
nDamage -= 100 << 4;
|
|
if (nDamage > 0)
|
|
actDamageSprite(actor, actor, kDamageFall, nDamage);
|
|
actor->zvel() = v30;
|
|
if (abs(actor->zvel()) < 0x10000)
|
|
{
|
|
actor->zvel() = velFloor[pSprite->sectnum];
|
|
|
|
pSprite->flags &= ~4;
|
|
}
|
|
else
|
|
|
|
pSprite->flags |= 4;
|
|
switch (tileGetSurfType(floorHit))
|
|
{
|
|
case kSurfWater:
|
|
gFX.fxSpawn(FX_9, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
|
|
break;
|
|
case kSurfLava:
|
|
{
|
|
spritetype* pFX = gFX.fxSpawn(FX_10, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
|
|
if (pFX)
|
|
{
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
auto pFX2 = gFX.fxSpawnActor(FX_14, pFX->sectnum, pFX->x, pFX->y, pFX->z, 0);
|
|
if (pFX2)
|
|
{
|
|
pFX2->xvel() = Random2(0x6aaaa);
|
|
pFX2->yvel() = Random2(0x6aaaa);
|
|
pFX2->zvel() = -(int)Random(0xd5555);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (actor->zvel() == 0)
|
|
|
|
pSprite->flags &= ~4;
|
|
}
|
|
else
|
|
{
|
|
actor->hit().florhit = 0;
|
|
|
|
if (pSprite->flags & 2)
|
|
pSprite->flags |= 4;
|
|
}
|
|
if (top <= ceilZ)
|
|
{
|
|
actor->hit().ceilhit = ceilHit;
|
|
pSprite->z += ClipLow(ceilZ - top, 0);
|
|
|
|
if (actor->zvel() <= 0 && (pSprite->flags & 4))
|
|
actor->zvel() = MulScale(-actor->zvel(), 0x2000, 16);
|
|
}
|
|
else
|
|
actor->hit().ceilhit = 0;
|
|
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
pXSprite->height = ClipLow(floorZ - bottom, 0) >> 8;
|
|
if (actor->xvel() || actor->yvel())
|
|
{
|
|
Collision coll = floorHit;
|
|
if (coll.type == kHitSprite)
|
|
{
|
|
auto hitAct = coll.actor;
|
|
if ((hitAct->s().cstat & 0x30) == 0)
|
|
{
|
|
actor->xvel() += MulScale(4, pSprite->x - hitAct->s().x, 2);
|
|
actor->yvel() += MulScale(4, pSprite->y - hitAct->s().y, 2);
|
|
return;
|
|
}
|
|
}
|
|
int nXSector = sector[pSprite->sectnum].extra;
|
|
if (nXSector > 0 && xsector[nXSector].Underwater)
|
|
return;
|
|
if (pXSprite->height >= 0x100)
|
|
return;
|
|
int nDrag = gDudeDrag;
|
|
if (pXSprite->height > 0)
|
|
nDrag -= scale(gDudeDrag, pXSprite->height, 0x100);
|
|
actor->xvel() -= mulscale16r(actor->xvel(), nDrag);
|
|
actor->yvel() -= mulscale16r(actor->yvel(), nDrag);
|
|
|
|
if (approxDist(actor->xvel(), actor->yvel()) < 0x1000)
|
|
actor->xvel() = actor->yvel() = 0;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int MoveMissile(DBloodActor* actor)
|
|
{
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = &actor->x();
|
|
auto Owner = actor->GetOwner();
|
|
int cliptype = -1;
|
|
int bakCstat = 0;
|
|
spritetype* pOwner = nullptr;
|
|
if (Owner && Owner->IsDudeActor())
|
|
{
|
|
pOwner = &Owner->s();
|
|
bakCstat = pOwner->cstat;
|
|
pOwner->cstat &= ~257;
|
|
}
|
|
gHitInfo.hitsect = -1;
|
|
gHitInfo.hitwall = -1;
|
|
gHitInfo.hitsprite = -1;
|
|
if (pSprite->type == kMissileFlameSpray) actAirDrag(actor, 0x1000);
|
|
|
|
if (pXSprite->target != -1 && (actor->xvel() || actor->yvel() || actor->zvel()))
|
|
{
|
|
auto target = actor->GetTarget();
|
|
spritetype* pTarget = &target->s();
|
|
XSPRITE* pXTarget = target->hasX() ? &target->x() : nullptr;
|
|
|
|
if (pTarget->statnum == kStatDude && pXTarget && pXTarget->health > 0)
|
|
{
|
|
int nTargetAngle = getangle(-(pTarget->y - pSprite->y), pTarget->x - pSprite->x);
|
|
int vx = missileInfo[pSprite->type - kMissileBase].velocity;
|
|
int vy = 0;
|
|
RotatePoint(&vx, &vy, (nTargetAngle + 1536) & 2047, 0, 0);
|
|
actor->xvel() = vx;
|
|
actor->yvel() = vy;
|
|
int dx = pTarget->x - pSprite->x;
|
|
int dy = pTarget->y - pSprite->y;
|
|
int dz = pTarget->z - pSprite->z;
|
|
|
|
int deltaz = dz / 10;
|
|
if (pTarget->z < pSprite->z) deltaz = -deltaz;
|
|
actor->zvel() += deltaz;
|
|
}
|
|
}
|
|
int vx = actor->xvel() >> 12;
|
|
int vy = actor->yvel() >> 12;
|
|
int vz = actor->zvel() >> 8;
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
int i = 1;
|
|
const int bakCompat = enginecompatibility_mode;
|
|
const bool isFlameSprite = (pSprite->type == kMissileFlameSpray || pSprite->type == kMissileFlameHound); // do not use accurate clipmove for flame based sprites (changes damage too much)
|
|
while (1)
|
|
{
|
|
int x = pSprite->x;
|
|
int y = pSprite->y;
|
|
int z = pSprite->z;
|
|
int nSector2 = pSprite->sectnum;
|
|
clipmoveboxtracenum = 1;
|
|
const short bakSpriteCstat = pSprite->cstat;
|
|
if (pOwner && !isFlameSprite && !cl_bloodvanillaexplosions && !VanillaMode())
|
|
{
|
|
enginecompatibility_mode = ENGINECOMPATIBILITY_NONE; // improved clipmove accuracy
|
|
pSprite->cstat &= ~257; // remove self collisions for accurate clipmove
|
|
}
|
|
Collision clipmoveresult = ClipMove(&x, &y, &z, &nSector2, vx, vy, pSprite->clipdist << 2, (z - top) / 4, (bottom - z) / 4, CLIPMASK0);
|
|
enginecompatibility_mode = bakCompat; // restore
|
|
pSprite->cstat = bakSpriteCstat;
|
|
clipmoveboxtracenum = 3;
|
|
short nSector = nSector2;
|
|
if (nSector2 < 0)
|
|
{
|
|
cliptype = -1;
|
|
break;
|
|
}
|
|
if (clipmoveresult.type == kHitSprite)
|
|
{
|
|
gHitInfo.hitsprite = clipmoveresult.legacyVal;
|
|
cliptype = 3;
|
|
}
|
|
else if (clipmoveresult.type == kHitWall)
|
|
{
|
|
gHitInfo.hitwall = clipmoveresult.index;
|
|
if (wall[clipmoveresult.index].nextsector == -1) cliptype = 0;
|
|
else
|
|
{
|
|
int32_t fz, cz;
|
|
getzsofslope(wall[clipmoveresult.index].nextsector, x, y, &cz, &fz);
|
|
if (z <= cz || z >= fz) cliptype = 0;
|
|
else cliptype = 4;
|
|
}
|
|
}
|
|
if (cliptype == 4)
|
|
{
|
|
walltype* pWall = &wall[gHitInfo.hitwall];
|
|
if (pWall->extra > 0)
|
|
{
|
|
XWALL* pXWall = &xwall[pWall->extra];
|
|
if (pXWall->triggerVector)
|
|
{
|
|
trTriggerWall(gHitInfo.hitwall, pXWall, kCmdWallImpact);
|
|
if (!(pWall->cstat & 64))
|
|
{
|
|
cliptype = -1;
|
|
if (i-- > 0)
|
|
continue;
|
|
cliptype = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (cliptype >= 0 && cliptype != 3)
|
|
{
|
|
int nAngle = getangle(actor->xvel(), actor->yvel());
|
|
x -= MulScale(Cos(nAngle), 16, 30);
|
|
y -= MulScale(Sin(nAngle), 16, 30);
|
|
int nVel = approxDist(actor->xvel(), actor->yvel());
|
|
vz -= scale(0x100, actor->zvel(), nVel);
|
|
updatesector(x, y, &nSector);
|
|
nSector2 = nSector;
|
|
}
|
|
int ceilZ, ceilHit, floorZ, floorHit;
|
|
GetZRangeAtXYZ(x, y, z, nSector2, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist << 2, CLIPMASK0);
|
|
GetActorExtents(actor, &top, &bottom);
|
|
top += vz;
|
|
bottom += vz;
|
|
if (bottom >= floorZ)
|
|
{
|
|
actor->hit().florhit = floorHit;
|
|
vz += floorZ - bottom;
|
|
cliptype = 2;
|
|
}
|
|
if (top <= ceilZ)
|
|
{
|
|
actor->hit().ceilhit = ceilHit;
|
|
vz += ClipLow(ceilZ - top, 0);
|
|
cliptype = 1;
|
|
}
|
|
pSprite->x = x;
|
|
pSprite->y = y;
|
|
pSprite->z = z + vz;
|
|
updatesector(x, y, &nSector);
|
|
if (nSector >= 0 && nSector != pSprite->sectnum)
|
|
{
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
ChangeSpriteSect(pSprite->index, nSector);
|
|
}
|
|
CheckLink(pSprite);
|
|
gHitInfo.hitsect = pSprite->sectnum;
|
|
gHitInfo.hitx = pSprite->x;
|
|
gHitInfo.hity = pSprite->y;
|
|
gHitInfo.hitz = pSprite->z;
|
|
break;
|
|
}
|
|
if (pOwner) pOwner->cstat = bakCstat;
|
|
|
|
return cliptype;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actExplodeSprite(DBloodActor* actor)
|
|
{
|
|
if (!actor->hasX()) return;
|
|
auto pSprite = &actor->s();
|
|
auto pXSprite = &actor->x();
|
|
//auto Owner = actor->GetOwner();
|
|
|
|
if (pSprite->statnum == kStatExplosion) return;
|
|
sfxKill3DSound(pSprite, -1, -1);
|
|
evKill(pSprite->index, 3);
|
|
|
|
int nType = kExplosionStandard;
|
|
|
|
switch (pSprite->type)
|
|
{
|
|
case kMissileFireballNapalm:
|
|
nType = kExplosionNapalm;
|
|
seqSpawn(4, actor, -1);
|
|
if (Chance(0x8000)) pSprite->cstat |= 4;
|
|
sfxPlay3DSound(pSprite, 303, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kMissileFlareAlt:
|
|
nType = kExplosionFireball;
|
|
seqSpawn(9, actor, -1);
|
|
if (Chance(0x8000)) pSprite->cstat |= 4;
|
|
sfxPlay3DSound(pSprite, 306, 24 + (pSprite->index & 3), FX_GlobalChannel); // ouch...
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kMissileFireballCerberus:
|
|
case kMissileFireballTchernobog:
|
|
nType = kExplosionFireball;
|
|
seqSpawn(5, actor, -1);
|
|
sfxPlay3DSound(pSprite, 304, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingArmedTNTStick:
|
|
nType = kExplosionSmall;
|
|
if (actor->hit().florhit == 0) seqSpawn(4, actor, -1);
|
|
else seqSpawn(3, actor, -1);
|
|
sfxPlay3DSound(pSprite, 303, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingArmedProxBomb:
|
|
case kThingArmedRemoteBomb:
|
|
case kThingArmedTNTBundle:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingTNTProx:
|
|
#endif
|
|
nType = kExplosionStandard;
|
|
if (actor->hit().florhit == 0) seqSpawn(4, actor, -1);
|
|
else
|
|
seqSpawn(3, actor, -1);
|
|
sfxPlay3DSound(pSprite, 304, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingArmedSpray:
|
|
nType = kExplosionSpray;
|
|
seqSpawn(5, actor, -1);
|
|
sfxPlay3DSound(pSprite, 307, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingTNTBarrel:
|
|
{
|
|
auto spawned = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0, 1);
|
|
spawned->SetOwner(actor->GetOwner());
|
|
if (actCheckRespawn(pSprite))
|
|
{
|
|
pXSprite->state = 1;
|
|
pXSprite->health = thingInfo[0].startHealth << 4;
|
|
}
|
|
else actPostSprite(actor, kStatFree);
|
|
|
|
nType = kExplosionLarge;
|
|
seqSpawn(4, spawned, -1);
|
|
actor = spawned;
|
|
pSprite = &spawned->s();
|
|
pXSprite = &spawned->x();
|
|
|
|
sfxPlay3DSound(pSprite, 305, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_14, nullptr, nullptr);
|
|
break;
|
|
}
|
|
case kTrapExploder:
|
|
{
|
|
// Defaults for exploder
|
|
nType = 1;
|
|
int nSnd = 304;
|
|
int nSeq = 4;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// allow to customize hidden exploder trap
|
|
if (gModernMap)
|
|
{
|
|
nType = pXSprite->data1; // Explosion type
|
|
int tSeq = pXSprite->data2; // SEQ id
|
|
int tSnd = pXSprite->data3; // Sound Id
|
|
|
|
if (nType <= 1 || nType > kExplodeMax) { nType = 1; nSeq = 4; nSnd = 304; }
|
|
else if (nType == 2) { nSeq = 4; nSnd = 305; }
|
|
else if (nType == 3) { nSeq = 9; nSnd = 307; }
|
|
else if (nType == 4) { nSeq = 5; nSnd = 307; }
|
|
else if (nType <= 6) { nSeq = 4; nSnd = 303; }
|
|
else if (nType == 7) { nSeq = 4; nSnd = 303; }
|
|
else if (nType == 8) { nType = 0; nSeq = 3; nSnd = 303; }
|
|
|
|
// Override previous sound and seq assigns
|
|
if (tSeq > 0) nSeq = tSeq;
|
|
if (tSnd > 0) nSnd = tSnd;
|
|
}
|
|
#endif
|
|
|
|
if (getSequence(nSeq)) seqSpawn(nSeq, actor, -1);
|
|
sfxPlay3DSound(pSprite, nSnd, -1, 0);
|
|
break;
|
|
}
|
|
case kThingPodFireBall:
|
|
nType = kExplosionFireball;
|
|
seqSpawn(9, actor, -1);
|
|
sfxPlay3DSound(pSprite, 307, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
sub_746D4(pSprite, 240);
|
|
break;
|
|
|
|
default:
|
|
nType = kExplosionStandard;
|
|
seqSpawn(4, actor, -1);
|
|
if (Chance(0x8000)) pSprite->cstat |= 4;
|
|
sfxPlay3DSound(pSprite, 303, -1, 0);
|
|
GibSprite(pSprite, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
}
|
|
actor->xvel() = actor->yvel() = actor->zvel() = 0;
|
|
actPostSprite(actor, kStatExplosion);
|
|
pSprite->xrepeat = pSprite->yrepeat = explodeInfo[nType].repeat;
|
|
|
|
pSprite->flags &= ~3;
|
|
pSprite->type = nType;
|
|
const EXPLOSION* pExplodeInfo = &explodeInfo[nType];
|
|
pXSprite->target = 0;
|
|
pXSprite->data1 = pExplodeInfo->ticks;
|
|
pXSprite->data2 = pExplodeInfo->quakeEffect;
|
|
pXSprite->data3 = pExplodeInfo->flashEffect;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actActivateGibObject(DBloodActor* actor)
|
|
{
|
|
auto pXSprite = &actor->x();
|
|
auto pSprite = &actor->s();
|
|
|
|
int gib1 = ClipRange(pXSprite->data1, 0, 31);
|
|
int gib2 = ClipRange(pXSprite->data2, 0, 31);
|
|
int gib3 = ClipRange(pXSprite->data3, 0, 31);
|
|
int sound = pXSprite->data4;
|
|
int dropmsg = pXSprite->dropMsg;
|
|
|
|
if (gib1 > 0) GibSprite(pSprite, (GIBTYPE)(gib1 - 1), nullptr, nullptr);
|
|
if (gib2 > 0) GibSprite(pSprite, (GIBTYPE)(gib2 - 1), nullptr, nullptr);
|
|
if (gib3 > 0 && pXSprite->burnTime > 0) GibSprite(pSprite, (GIBTYPE)(gib3 - 1), nullptr, nullptr);
|
|
if (sound > 0) sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, sound, pSprite->sectnum);
|
|
if (dropmsg > 0) actDropObject(pSprite, dropmsg);
|
|
|
|
if (!(pSprite->cstat & 32768) && !(pSprite->flags & kHitagRespawn))
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckProximity()
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
if (pSprite->flags & 32) continue;
|
|
|
|
if (actor->hasX())
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingBloodBits:
|
|
case kThingBloodChunks:
|
|
case kThingZombieHead:
|
|
if (pXSprite->locked && PlayClock >= pXSprite->targetX) pXSprite->locked = 0;
|
|
break;
|
|
}
|
|
|
|
if (pXSprite->burnTime > 0)
|
|
{
|
|
pXSprite->burnTime = ClipLow(pXSprite->burnTime - 4, 0);
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageFall, 8);
|
|
}
|
|
|
|
if (pXSprite->Proximity)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
// don't process locked or 1-shot things for proximity
|
|
if (gModernMap && (pXSprite->locked || pXSprite->isTriggered))
|
|
continue;
|
|
#endif
|
|
|
|
if (pSprite->type == kThingDroppedLifeLeech) pXSprite->target = -1;
|
|
BloodStatIterator it1(kStatDude);
|
|
while (auto dudeactor = it1.Next())
|
|
{
|
|
auto nextdude = it1.Peek();
|
|
spritetype* pSprite2 = &dudeactor->s();
|
|
|
|
if (pSprite2->flags & 32 || !dudeactor->hasX()) continue;
|
|
|
|
XSPRITE* pXSprite2 = &dudeactor->x();
|
|
if ((unsigned int)pXSprite2->health > 0)
|
|
{
|
|
int proxyDist = 96;
|
|
#ifdef NOONE_EXTENSIONS
|
|
// allow dudeLockout for proximity flag
|
|
if (gModernMap && pSprite->type != kThingDroppedLifeLeech && pXSprite->DudeLockout && !dudeactor->IsPlayerActor())
|
|
continue;
|
|
|
|
if (pSprite->type == kModernThingEnemyLifeLeech) proxyDist = 512;
|
|
#endif
|
|
if (pSprite->type == kThingDroppedLifeLeech && pXSprite->target == -1)
|
|
{
|
|
auto Owner = actor->GetOwner();
|
|
if (!Owner->IsPlayerActor()) continue;
|
|
|
|
spritetype* pOwner = &Owner->s();
|
|
PLAYER* pPlayer = &gPlayer[pOwner->type - kDudePlayer1];
|
|
PLAYER* pPlayer2 = dudeactor->IsPlayerActor() ? &gPlayer[pSprite2->type - kDudePlayer1] : nullptr;
|
|
|
|
if (dudeactor == Owner || pSprite2->type == kDudeZombieAxeBuried || pSprite2->type == kDudeRat || pSprite2->type == kDudeBat) continue;
|
|
if (gGameOptions.nGameType == 3 && pPlayer2 && pPlayer->teamId == pPlayer2->teamId) continue;
|
|
if (gGameOptions.nGameType == 1 && pPlayer2) continue;
|
|
proxyDist = 512;
|
|
}
|
|
|
|
if (CheckProximity(pSprite2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, proxyDist))
|
|
{
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingDroppedLifeLeech:
|
|
if (!Chance(0x4000) && nextdude) continue;
|
|
if (pSprite2->cstat & CLIPMASK0) pXSprite->target = pSprite2->index;
|
|
else continue;
|
|
break;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingTNTProx:
|
|
if (!dudeactor->IsPlayerActor()) continue;
|
|
pSprite->pal = 0;
|
|
break;
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
if (pXSprite->target != pSprite2->index) continue;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (pSprite->owner == -1) pSprite->owner = pSprite2->index;
|
|
trTriggerSprite(actor, kCmdSpriteProximity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckThings()
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
|
|
if (pSprite->flags & 32) continue;
|
|
if (!actor->hasX()) continue;
|
|
|
|
auto pXSprite = &actor->x();
|
|
int nSector = pSprite->sectnum;
|
|
|
|
int nXSector = sector[nSector].extra;
|
|
XSECTOR* pXSector = NULL;
|
|
if (nXSector > 0)
|
|
{
|
|
assert(nXSector > 0 && nXSector < kMaxXSectors);
|
|
assert(xsector[nXSector].reference == nSector);
|
|
pXSector = &xsector[nXSector];
|
|
}
|
|
if (pXSector && pXSector->panVel && (pXSector->panAlways || pXSector->state || pXSector->busy))
|
|
{
|
|
int nType = pSprite->type - kThingBase;
|
|
const THINGINFO* pThingInfo = &thingInfo[nType];
|
|
if (pThingInfo->flags & 1) pSprite->flags |= 1;
|
|
if (pThingInfo->flags & 2) pSprite->flags |= 4;
|
|
}
|
|
|
|
if (pSprite->flags & 3)
|
|
{
|
|
viewBackupSpriteLoc(actor);
|
|
if (pXSector && pXSector->panVel)
|
|
{
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (getflorzofslope(nSector, pSprite->x, pSprite->y) <= bottom)
|
|
{
|
|
int angle = pXSector->panAngle;
|
|
int speed = 0;
|
|
if (pXSector->panAlways || pXSector->state || pXSector->busy)
|
|
{
|
|
speed = pXSector->panVel << 9;
|
|
if (!pXSector->panAlways && pXSector->busy) speed = MulScale(speed, pXSector->busy, 16);
|
|
}
|
|
if (sector[nSector].floorstat & 64) angle = (angle + GetWallAngle(sector[nSector].wallptr) + 512) & 2047;
|
|
|
|
actor->xvel() += MulScale(speed, Cos(angle), 30);
|
|
actor->yvel() += MulScale(speed, Sin(angle), 30);
|
|
}
|
|
}
|
|
actAirDrag(actor, 128);
|
|
|
|
if (((pSprite->index >> 8) & 15) == (gFrameCount & 15) && (pSprite->flags & 2)) pSprite->flags |= 4;
|
|
if ((pSprite->flags & 4) || actor->xvel() || actor->yvel() || actor->zvel() || velFloor[pSprite->sectnum] || velCeil[pSprite->sectnum])
|
|
{
|
|
Collision hit = MoveThing(actor);
|
|
if (hit.type)
|
|
{
|
|
if (pXSprite->Impact) trTriggerSprite(actor, kCmdOff);
|
|
|
|
switch (pSprite->type)
|
|
{
|
|
case kThingDripWater:
|
|
case kThingDripBlood:
|
|
MakeSplash(&bloodActors[pXSprite->reference]);
|
|
break;
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingThrowableRock:
|
|
seqSpawn(24, actor, -1);
|
|
if (hit.type = kHitSprite)
|
|
{
|
|
pSprite->xrepeat = 32;
|
|
pSprite->yrepeat = 32;
|
|
actDamageSprite(actor->GetOwner(), hit.actor, kDamageFall, pXSprite->data1);
|
|
}
|
|
break;
|
|
#endif
|
|
case kThingBone:
|
|
seqSpawn(24, actor, -1);
|
|
if (hit.type = kHitSprite)
|
|
{
|
|
actDamageSprite(actor->GetOwner(), hit.actor, kDamageFall, 12);
|
|
}
|
|
break;
|
|
|
|
case kThingPodGreenBall:
|
|
if (hit.type == kHitSector)
|
|
{
|
|
actRadiusDamage(actor->GetOwner(), pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 200, 1, 20, kDamageExplode, 6, 0);
|
|
evPost(actor, 0, kCallbackFXPodBloodSplat);
|
|
}
|
|
else if (hit.type == kHitSprite)
|
|
{
|
|
actDamageSprite(actor->GetOwner(), hit.actor, kDamageFall, 12);
|
|
evPost(actor, 0, kCallbackFXPodBloodSplat);
|
|
}
|
|
break;
|
|
|
|
case kThingPodFireBall:
|
|
if (hit.type == kHitSprite) actExplodeSprite(actor);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckProjectiles()
|
|
{
|
|
BloodStatIterator it(kStatProjectile);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
|
|
if (pSprite->flags & 32)
|
|
continue;
|
|
viewBackupSpriteLoc(actor);
|
|
int hit = MoveMissile(actor);
|
|
if (hit >= 0) actImpactMissile(&bloodActors[pSprite->index], hit);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckExplosion()
|
|
{
|
|
BloodStatIterator it(kStatExplosion);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
|
|
if (pSprite->flags & 32)
|
|
continue;
|
|
|
|
if (!actor->hasX()) continue;
|
|
XSPRITE* pXSprite = &actor->x();
|
|
|
|
auto Owner = actor->GetOwner();
|
|
auto pOwner = Owner ? &Owner->s() : nullptr;
|
|
int nType = pSprite->type;
|
|
assert(nType >= 0 && nType < kExplodeMax);
|
|
const EXPLOSION* pExplodeInfo = &explodeInfo[nType];
|
|
int x = pSprite->x;
|
|
int y = pSprite->y;
|
|
int z = pSprite->z;
|
|
int nSector = pSprite->sectnum;
|
|
int radius = pExplodeInfo->radius;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// Allow to override explosion radius by data4 field of any sprite which have statnum 2 set in editor
|
|
// or of Hidden Exploder.
|
|
if (gModernMap && pXSprite->data4 > 0)
|
|
radius = pXSprite->data4;
|
|
#endif
|
|
|
|
uint8_t sectormap[(kMaxSectors + 7) >> 3];
|
|
|
|
// GetClosestSpriteSectors() has issues checking some sectors due to optimizations
|
|
// the new flag newSectCheckMethod for GetClosestSpriteSectors() does rectify these issues, but this may cause unintended side effects for level scripted explosions
|
|
// so only allow this new checking method for dude spawned explosions
|
|
short gAffectedXWalls[kMaxXWalls];
|
|
const bool newSectCheckMethod = !cl_bloodvanillaexplosions && pOwner && pOwner->IsDudeActor() && !VanillaMode(); // use new sector checking logic
|
|
GetClosestSpriteSectors(nSector, x, y, radius, sectormap, gAffectedXWalls, newSectCheckMethod);
|
|
|
|
for (int i = 0; i < kMaxXWalls; i++)
|
|
{
|
|
int nWall = gAffectedXWalls[i];
|
|
if (nWall == -1)
|
|
break;
|
|
XWALL* pXWall = &xwall[wall[nWall].extra];
|
|
trTriggerWall(nWall, pXWall, kCmdWallImpact);
|
|
}
|
|
|
|
BloodStatIterator it1(kStatDude);
|
|
while (auto dudeactor = it1.Next())
|
|
{
|
|
spritetype* pDude = &dudeactor->s();
|
|
|
|
if (pDude->flags & 32) continue;
|
|
|
|
if (TestBitString(sectormap, pDude->sectnum))
|
|
{
|
|
if (pXSprite->data1 && CheckProximity(pDude, x, y, z, nSector, radius))
|
|
{
|
|
if (pExplodeInfo->dmg && pXSprite->target == 0)
|
|
{
|
|
pXSprite->target = 1;
|
|
actDamageSprite(Owner, dudeactor, kDamageFall, (pExplodeInfo->dmg + Random(pExplodeInfo->dmgRng)) << 4);
|
|
}
|
|
if (pExplodeInfo->dmgType) ConcussSprite(actor, dudeactor, x, y, z, pExplodeInfo->dmgType);
|
|
|
|
if (pExplodeInfo->burnTime)
|
|
{
|
|
assert(dudeactor->hasX());
|
|
XSPRITE* pXDude = &dudeactor->x();
|
|
if (!pXDude->burnTime) evPost(dudeactor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(Owner, dudeactor, pExplodeInfo->burnTime << 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
it1.Reset(kStatThing);
|
|
while (auto thingactor = it1.Next())
|
|
{
|
|
spritetype* pThing = &thingactor->s();
|
|
|
|
if (pThing->flags & 32) continue;
|
|
|
|
if (TestBitString(sectormap, pThing->sectnum))
|
|
{
|
|
if (pXSprite->data1 && CheckProximity(pThing, x, y, z, nSector, radius) && thingactor->hasX())
|
|
{
|
|
XSPRITE* pXThing = &thingactor->x();
|
|
if (!pXThing->locked)
|
|
{
|
|
if (pExplodeInfo->dmgType) ConcussSprite(Owner, thingactor, x, y, z, pExplodeInfo->dmgType);
|
|
|
|
if (pExplodeInfo->burnTime)
|
|
{
|
|
if (pThing->type == kThingTNTBarrel && !pXThing->burnTime)
|
|
evPost(thingactor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(Owner, thingactor, pExplodeInfo->burnTime << 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
{
|
|
spritetype* pSprite2 = gPlayer[p].pSprite;
|
|
int dx = (x - pSprite2->x) >> 4;
|
|
int dy = (y - pSprite2->y) >> 4;
|
|
int dz = (z - pSprite2->z) >> 8;
|
|
int nDist = dx * dx + dy * dy + dz * dz + 0x40000;
|
|
int t = DivScale(pXSprite->data2, nDist, 16);
|
|
gPlayer[p].flickerEffect += t;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (pXSprite->data1 != 0)
|
|
{
|
|
// add impulse for sprites from physics list
|
|
if (gPhysSpritesCount > 0 && pExplodeInfo->dmgType != 0)
|
|
{
|
|
for (int i = 0; i < gPhysSpritesCount; i++)
|
|
{
|
|
if (gPhysSpritesList[i] == -1) continue;
|
|
auto physactor = &bloodActors[gPhysSpritesList[i]];
|
|
spritetype* pDebris = &physactor->s();
|
|
if (pDebris->sectnum < 0 || (pDebris->flags & kHitagFree) != 0) continue;
|
|
|
|
if (!TestBitString(sectormap, pDebris->sectnum) || !CheckProximity(pDebris, x, y, z, nSector, radius)) continue;
|
|
else debrisConcuss(Owner ? Owner->s().index : -1, i, x, y, z, pExplodeInfo->dmgType);
|
|
}
|
|
}
|
|
|
|
// trigger sprites from impact list
|
|
if (gImpactSpritesCount > 0) {
|
|
for (int i = 0; i < gImpactSpritesCount; i++)
|
|
{
|
|
if (gImpactSpritesList[i] == -1) continue;
|
|
auto impactactor = &bloodActors[gImpactSpritesList[i]];
|
|
auto impactsprite = &impactactor->s();
|
|
if (impactsprite->sectnum < 0 || (impactsprite->flags & kHitagFree) != 0)
|
|
continue;
|
|
|
|
if (impactsprite->extra <= 0)
|
|
continue;
|
|
XSPRITE* pXImpact = &impactactor->x();
|
|
if (/*pXImpact->state == pXImpact->restState ||*/ !TestBitString(sectormap, impactsprite->sectnum) || !CheckProximity(impactsprite, x, y, z, nSector, radius))
|
|
continue;
|
|
|
|
trTriggerSprite(impactsprite->index, pXImpact, kCmdSpriteImpact);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!gModernMap || !(pSprite->flags & kModernTypeFlag1))
|
|
{
|
|
// if data4 > 0, do not remove explosion. This can be useful when designer wants put explosion generator in map manually via sprite statnum 2.
|
|
pXSprite->data1 = ClipLow(pXSprite->data1 - 4, 0);
|
|
pXSprite->data2 = ClipLow(pXSprite->data2 - 4, 0);
|
|
pXSprite->data3 = ClipLow(pXSprite->data3 - 4, 0);
|
|
}
|
|
#else
|
|
pXSprite->data1 = ClipLow(pXSprite->data1 - 4, 0);
|
|
pXSprite->data2 = ClipLow(pXSprite->data2 - 4, 0);
|
|
pXSprite->data3 = ClipLow(pXSprite->data3 - 4, 0);
|
|
#endif
|
|
|
|
if (pXSprite->data1 == 0 && pXSprite->data2 == 0 && pXSprite->data3 == 0 && seqGetStatus(actor) < 0)
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckTraps()
|
|
{
|
|
BloodStatIterator it(kStatTraps);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
|
|
if ((pSprite->flags & 32) || !actor->hasX())
|
|
continue;
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
|
switch (pSprite->type) {
|
|
case kTrapSawCircular:
|
|
pXSprite->data2 = ClipLow(pXSprite->data2 - 4, 0);
|
|
break;
|
|
|
|
case kTrapFlame:
|
|
if (pXSprite->state && seqGetStatus(actor) < 0)
|
|
{
|
|
int x = pSprite->x;
|
|
int y = pSprite->y;
|
|
int z = pSprite->z;
|
|
int t = (pXSprite->data1 << 23) / 120;
|
|
int dx = MulScale(t, Cos(pSprite->ang), 30);
|
|
int dy = MulScale(t, Sin(pSprite->ang), 30);
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_32, pSprite->sectnum, x, y, z, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->xvel() = dx + Random2(0x8888);
|
|
pFX->yvel() = dy + Random2(0x8888);
|
|
pFX->zvel() = Random2(0x8888);
|
|
}
|
|
x += (dx / 2) >> 12;
|
|
y += (dy / 2) >> 12;
|
|
}
|
|
dy = SinScale16(pSprite->ang);
|
|
dx = CosScale16(pSprite->ang);
|
|
gVectorData[kVectorTchernobogBurn].maxDist = pXSprite->data1 << 9;
|
|
actFireVector(pSprite, 0, 0, dx, dy, Random2(0x8888), kVectorTchernobogBurn);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckDudes()
|
|
{
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
|
|
if (pSprite->flags & 32)
|
|
continue;
|
|
|
|
if (actor->hasX())
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
const bool fixBurnGlitch = !cl_bloodvanillaenemies && IsBurningDude(pSprite) && !VanillaMode(); // if enemies are burning, always apply burning damage per tick
|
|
if ((pXSprite->burnTime > 0) || fixBurnGlitch)
|
|
{
|
|
switch (pSprite->type)
|
|
{
|
|
case kDudeBurningInnocent:
|
|
case kDudeBurningCultist:
|
|
case kDudeBurningZombieAxe:
|
|
case kDudeBurningZombieButcher:
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
break;
|
|
|
|
default:
|
|
pXSprite->burnTime = ClipLow(pXSprite->burnTime - 4, 0);
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// handle incarnations of custom dude
|
|
if (pSprite->type == kDudeModernCustom && pXSprite->txID > 0 && pXSprite->sysData1 == kGenDudeTransformStatus)
|
|
{
|
|
actor->xvel() = actor->yvel() = 0;
|
|
if (seqGetStatus(actor) < 0) genDudeTransform(pSprite);
|
|
}
|
|
#endif
|
|
if (pSprite->type == kDudeCerberusTwoHead)
|
|
{
|
|
if (pXSprite->health <= 0 && seqGetStatus(actor) < 0)
|
|
{
|
|
pXSprite->health = dudeInfo[28].startHealth << 4;
|
|
pSprite->type = kDudeCerberusOneHead;
|
|
if (pXSprite->target != -1) aiSetTarget(pXSprite, pXSprite->target);
|
|
aiActivateDude(actor);
|
|
}
|
|
}
|
|
if (pXSprite->Proximity && !pXSprite->isTriggered)
|
|
{
|
|
BloodStatIterator it1(kStatDude);
|
|
while (auto actor2 = it1.Next())
|
|
{
|
|
spritetype* pSprite2 = &actor->s();
|
|
if (pSprite2->flags & 32) continue;
|
|
|
|
XSPRITE* pXSprite2 = &actor->x();
|
|
|
|
if ((unsigned int)pXSprite2->health > 0 && IsPlayerSprite(pSprite2))
|
|
{
|
|
if (CheckProximity(pSprite2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 128))
|
|
trTriggerSprite(actor, kCmdSpriteProximity);
|
|
}
|
|
}
|
|
}
|
|
if (actor->IsPlayerActor())
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
|
|
if (pPlayer->voodooTargets) voodooTarget(pPlayer);
|
|
if (pPlayer->hand && Chance(0x8000)) actDamageSprite(actor, actor, kDamageDrown, 12);
|
|
|
|
if (pPlayer->isUnderwater)
|
|
{
|
|
char bActive = packItemActive(pPlayer, 1);
|
|
|
|
if (bActive || pPlayer->godMode) pPlayer->underwaterTime = 1200;
|
|
else pPlayer->underwaterTime = ClipLow(pPlayer->underwaterTime - 4, 0);
|
|
|
|
if (pPlayer->underwaterTime < 1080 && packCheckItem(pPlayer, 1) && !bActive)
|
|
packUseItem(pPlayer, 1);
|
|
|
|
if (!pPlayer->underwaterTime)
|
|
{
|
|
pPlayer->chokeEffect += 4;
|
|
if (Chance(pPlayer->chokeEffect))
|
|
actDamageSprite(actor, actor, kDamageDrown, 3 << 4);
|
|
}
|
|
else
|
|
pPlayer->chokeEffect = 0;
|
|
|
|
if (actor->xvel() || actor->yvel())
|
|
sfxPlay3DSound(pSprite, 709, 100, 2);
|
|
|
|
pPlayer->bubbleTime = ClipLow(pPlayer->bubbleTime - 4, 0);
|
|
}
|
|
else if (gGameOptions.nGameType == 0)
|
|
{
|
|
if (pPlayer->pXSprite->health > 0 && pPlayer->restTime >= 1200 && Chance(0x200))
|
|
{
|
|
pPlayer->restTime = -1;
|
|
sfxPlay3DSound(pSprite, 3100 + Random(11), 0, 2);
|
|
}
|
|
}
|
|
}
|
|
ProcessTouchObjects(actor);
|
|
}
|
|
}
|
|
|
|
it.Reset(kStatDude);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
if (pSprite->flags & 32 || !actor->hasX()) continue;
|
|
|
|
int nSector = pSprite->sectnum;
|
|
viewBackupSpriteLoc(actor);
|
|
int nXSector = sector[nSector].extra;
|
|
XSECTOR* pXSector = NULL;
|
|
|
|
if (nXSector > 0)
|
|
{
|
|
assert(nXSector > 0 && nXSector < kMaxXSectors);
|
|
assert(xsector[nXSector].reference == nSector);
|
|
pXSector = &xsector[nXSector];
|
|
}
|
|
if (pXSector)
|
|
{
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (getflorzofslope(nSector, pSprite->x, pSprite->y) <= bottom)
|
|
{
|
|
int angle = pXSector->panAngle;
|
|
int speed = 0;
|
|
if (pXSector->panAlways || pXSector->state || pXSector->busy)
|
|
{
|
|
speed = pXSector->panVel << 9;
|
|
if (!pXSector->panAlways && pXSector->busy)
|
|
speed = MulScale(speed, pXSector->busy, 16);
|
|
}
|
|
if (sector[nSector].floorstat & 64)
|
|
angle = (angle + GetWallAngle(sector[nSector].wallptr) + 512) & 2047;
|
|
int dx = MulScale(speed, Cos(angle), 30);
|
|
int dy = MulScale(speed, Sin(angle), 30);
|
|
actor->xvel() += dx;
|
|
actor->yvel() += dy;
|
|
}
|
|
}
|
|
if (pXSector && pXSector->Underwater) actAirDrag(actor, 5376);
|
|
else actAirDrag(actor, 128);
|
|
|
|
if ((pSprite->flags & 4) || actor->xvel() || actor->yvel() || actor->zvel() || velFloor[pSprite->sectnum] || velCeil[pSprite->sectnum])
|
|
MoveDude(actor);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actCheckFlares()
|
|
{
|
|
BloodStatIterator it(kStatFlare);
|
|
while (auto actor = it.Next())
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
if ((pSprite->flags & 32) || !actor->hasX()) continue;
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
|
auto target = actor->GetTarget();
|
|
if (!target) continue;
|
|
|
|
viewBackupSpriteLoc(actor);
|
|
spritetype* pTarget = &target->s();
|
|
auto pXTarget = target->hasX()? &target->x() : nullptr;
|
|
if (pTarget->statnum == kMaxStatus)
|
|
{
|
|
GibSprite(pSprite, GIBTYPE_17, NULL, NULL);
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
if (pXTarget && pXTarget->health > 0)
|
|
{
|
|
int x = pTarget->x + mulscale30r(Cos(pXSprite->goalAng + pTarget->ang), pTarget->clipdist * 2);
|
|
int y = pTarget->y + mulscale30r(Sin(pXSprite->goalAng + pTarget->ang), pTarget->clipdist * 2);
|
|
int z = pTarget->z + pXSprite->targetZ;
|
|
vec3_t pos = { x, y, z };
|
|
setsprite(pSprite->index, &pos);
|
|
actor->xvel() = target->xvel();
|
|
actor->yvel() = target->yvel();
|
|
actor->zvel() = target->zvel();
|
|
}
|
|
else
|
|
{
|
|
GibSprite(pSprite, GIBTYPE_17, NULL, NULL);
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actProcessSprites(void)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (gModernMap) nnExtProcessSuperSprites();
|
|
#endif
|
|
|
|
actCheckProximity();
|
|
actCheckThings();
|
|
actCheckProjectiles();
|
|
actCheckExplosion();
|
|
actCheckTraps();
|
|
actCheckDudes();
|
|
actCheckFlares();
|
|
aiProcessDudes();
|
|
gFX.fxProcess();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* actSpawnSprite(int nSector, int x, int y, int z, int nStat, bool setextra)
|
|
{
|
|
int nSprite = InsertSprite(nSector, nStat);
|
|
if (nSprite >= 0) sprite[nSprite].extra = -1;
|
|
else
|
|
{
|
|
BloodStatIterator it(kStatPurge);
|
|
nSprite = it.NextIndex();
|
|
assert(nSprite >= 0);
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
ChangeSpriteSect(nSprite, nSector);
|
|
actPostSprite(nSprite, nStat);
|
|
}
|
|
DBloodActor* actor = &bloodActors[nSprite];
|
|
|
|
vec3_t pos = { x, y, z };
|
|
setsprite(nSprite, &pos);
|
|
spritetype* pSprite = &actor->s();
|
|
pSprite->type = kSpriteDecoration;
|
|
if (setextra && !actor->hasX())
|
|
{
|
|
actor->addExtra();
|
|
actor->hit().florhit = 0;
|
|
actor->hit().ceilhit = 0;
|
|
if (!VanillaMode()) actor->SetTarget(nullptr);
|
|
}
|
|
return actor;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
spritetype *actSpawnDude(spritetype *pSource, short nType, int a3, int a4)
|
|
{
|
|
XSPRITE* pXSource = &xsprite[pSource->extra];
|
|
spritetype *pSprite2 = actSpawnSprite(pSource, kStatDude);
|
|
if (!pSprite2) return NULL;
|
|
XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
|
|
int angle = pSource->ang;
|
|
int nDude = nType-kDudeBase;
|
|
int x, y, z;
|
|
z = a4 + pSource->z;
|
|
if (a3 < 0)
|
|
{
|
|
x = pSource->x;
|
|
y = pSource->y;
|
|
}
|
|
else
|
|
{
|
|
x = pSource->x+mulscale30r(Cos(angle), a3);
|
|
y = pSource->y+mulscale30r(Sin(angle), a3);
|
|
}
|
|
pSprite2->type = nType;
|
|
pSprite2->ang = angle;
|
|
vec3_t pos = { x, y, z };
|
|
setsprite(pSprite2->index, &pos);
|
|
pSprite2->cstat |= 0x1101;
|
|
pSprite2->clipdist = getDudeInfo(nDude+kDudeBase)->clipdist;
|
|
pXSprite2->health = getDudeInfo(nDude+kDudeBase)->startHealth<<4;
|
|
pXSprite2->respawn = 1;
|
|
if (getSequence(getDudeInfo(nDude+kDudeBase)->seqStartID))
|
|
seqSpawn(getDudeInfo(nDude+kDudeBase)->seqStartID, 3, pSprite2->extra, -1);
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add a way to inherit some values of spawner type 18 by dude.
|
|
// This way designer can count enemies via switches and do many other interesting things.
|
|
if (gModernMap && pSource->flags & kModernTypeFlag1) {
|
|
switch (pSource->type) { // allow inheriting only for selected source types
|
|
case kMarkerDudeSpawn:
|
|
//inherit pal?
|
|
if (pSprite2->pal <= 0) pSprite2->pal = pSource->pal;
|
|
|
|
// inherit spawn sprite trigger settings, so designer can count monsters.
|
|
pXSprite2->txID = pXSource->txID;
|
|
pXSprite2->command = pXSource->command;
|
|
pXSprite2->triggerOn = pXSource->triggerOn;
|
|
pXSprite2->triggerOff = pXSource->triggerOff;
|
|
|
|
// inherit drop items
|
|
pXSprite2->dropMsg = pXSource->dropMsg;
|
|
|
|
// inherit dude flags
|
|
pXSprite2->dudeDeaf = pXSource->dudeDeaf;
|
|
pXSprite2->dudeGuard = pXSource->dudeGuard;
|
|
pXSprite2->dudeAmbush = pXSource->dudeAmbush;
|
|
pXSprite2->dudeFlag4 = pXSource->dudeFlag4;
|
|
pXSprite2->unused1 = pXSource->unused1;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
aiInitSprite(pSprite2);
|
|
return pSprite2;
|
|
}
|
|
|
|
spritetype * actSpawnSprite(spritetype *pSource, int nStat)
|
|
{
|
|
int nSprite = InsertSprite(pSource->sectnum, nStat);
|
|
if (nSprite < 0)
|
|
{
|
|
StatIterator it(kStatPurge);
|
|
nSprite = it.NextIndex();
|
|
assert(nSprite >= 0);
|
|
assert(pSource->sectnum >= 0 && pSource->sectnum < kMaxSectors);
|
|
ChangeSpriteSect(nSprite, pSource->sectnum);
|
|
actPostSprite(nSprite, nStat);
|
|
}
|
|
spritetype *pSprite = &sprite[nSprite];
|
|
pSprite->x = pSource->x;
|
|
pSprite->y = pSource->y;
|
|
pSprite->z = pSource->z;
|
|
xvel[nSprite] = xvel[pSource->index];
|
|
yvel[nSprite] = yvel[pSource->index];
|
|
zvel[nSprite] = zvel[pSource->index];
|
|
pSprite->flags = 0;
|
|
int nXSprite = dbInsertXSprite(nSprite);
|
|
gSpriteHit[nXSprite].florhit = 0;
|
|
gSpriteHit[nXSprite].ceilhit = 0;
|
|
if (!VanillaMode())
|
|
xsprite[nXSprite].target = -1;
|
|
return pSprite;
|
|
}
|
|
|
|
spritetype * actSpawnThing(int nSector, int x, int y, int z, int nThingType)
|
|
{
|
|
assert(nThingType >= kThingBase && nThingType < kThingMax);
|
|
auto actor = actSpawnSprite(nSector, x, y, z, 4, 1);
|
|
spritetype* pSprite = &actor->s();
|
|
int nType = nThingType-kThingBase;
|
|
int nThing = pSprite->index;
|
|
int nXThing = pSprite->extra;
|
|
pSprite->type = nThingType;
|
|
assert(nXThing > 0 && nXThing < kMaxXSprites);
|
|
XSPRITE *pXThing = &xsprite[nXThing];
|
|
const THINGINFO *pThingInfo = &thingInfo[nType];
|
|
pXThing->health = pThingInfo->startHealth<<4;
|
|
pSprite->clipdist = pThingInfo->clipdist;
|
|
pSprite->flags = pThingInfo->flags;
|
|
if (pSprite->flags & 2)
|
|
pSprite->flags |= 4;
|
|
pSprite->cstat |= pThingInfo->cstat;
|
|
pSprite->picnum = pThingInfo->picnum;
|
|
pSprite->shade = pThingInfo->shade;
|
|
pSprite->pal = pThingInfo->pal;
|
|
if (pThingInfo->xrepeat)
|
|
pSprite->xrepeat = pThingInfo->xrepeat;
|
|
if (pThingInfo->yrepeat)
|
|
pSprite->yrepeat = pThingInfo->yrepeat;
|
|
show2dsprite.Set(pSprite->index);
|
|
switch (nThingType) {
|
|
case kThingVoodooHead:
|
|
pXThing->data1 = 0;
|
|
pXThing->data2 = 0;
|
|
pXThing->data3 = 0;
|
|
pXThing->data4 = 0;
|
|
pXThing->state = 1;
|
|
pXThing->triggerOnce = 1;
|
|
pXThing->isTriggered = 0;
|
|
break;
|
|
case kThingDroppedLifeLeech:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingEnemyLifeLeech:
|
|
#endif
|
|
pXThing->data1 = 0;
|
|
pXThing->data2 = 0;
|
|
pXThing->data3 = 0;
|
|
pXThing->data4 = 0;
|
|
pXThing->state = 1;
|
|
pXThing->triggerOnce = 0;
|
|
pXThing->isTriggered = 0;
|
|
break;
|
|
case kThingZombieHead:
|
|
pXThing->data1 = 8;
|
|
pXThing->data2 = 0;
|
|
pXThing->data3 = 0;
|
|
pXThing->data4 = 318;
|
|
pXThing->targetX = PlayClock+180;
|
|
pXThing->locked = 1;
|
|
pXThing->state = 1;
|
|
pXThing->triggerOnce = 0;
|
|
pXThing->isTriggered = 0;
|
|
break;
|
|
case kThingBloodBits:
|
|
case kThingBloodChunks:
|
|
pXThing->data1 = (nThingType == kThingBloodBits) ? 19 : 8;
|
|
pXThing->data2 = 0;
|
|
pXThing->data3 = 0;
|
|
pXThing->data4 = 318;
|
|
pXThing->targetX = PlayClock+180;
|
|
pXThing->locked = 1;
|
|
pXThing->state = 1;
|
|
pXThing->triggerOnce = 0;
|
|
pXThing->isTriggered = 0;
|
|
break;
|
|
case kThingArmedTNTStick:
|
|
evPost(nThing, 3, 0, kCallbackFXDynPuff);
|
|
sfxPlay3DSound(pSprite, 450, 0, 0);
|
|
break;
|
|
case kThingArmedTNTBundle:
|
|
sfxPlay3DSound(pSprite, 450, 0, 0);
|
|
evPost(nThing, 3, 0, kCallbackFXDynPuff);
|
|
break;
|
|
case kThingArmedSpray:
|
|
evPost(nThing, 3, 0, kCallbackFXDynPuff);
|
|
break;
|
|
}
|
|
return pSprite;
|
|
}
|
|
|
|
spritetype * actFireThing_(spritetype *pSprite, int a2, int a3, int a4, int thingType, int a6)
|
|
{
|
|
assert(thingType >= kThingBase && thingType < kThingMax);
|
|
int x = pSprite->x+MulScale(a2, Cos(pSprite->ang+512), 30);
|
|
int y = pSprite->y+MulScale(a2, Sin(pSprite->ang+512), 30);
|
|
int z = pSprite->z+a3;
|
|
x += MulScale(pSprite->clipdist, Cos(pSprite->ang), 28);
|
|
y += MulScale(pSprite->clipdist, Sin(pSprite->ang), 28);
|
|
if (HitScan(pSprite, z, x-pSprite->x, y-pSprite->y, 0, CLIPMASK0, pSprite->clipdist) != -1)
|
|
{
|
|
x = gHitInfo.hitx-MulScale(pSprite->clipdist<<1, Cos(pSprite->ang), 28);
|
|
y = gHitInfo.hity-MulScale(pSprite->clipdist<<1, Sin(pSprite->ang), 28);
|
|
}
|
|
spritetype *pThing = actSpawnThing(pSprite->sectnum, x, y, z, thingType);
|
|
pThing->owner = pSprite->index;
|
|
pThing->ang = pSprite->ang;
|
|
xvel[pThing->index] = MulScale(a6, Cos(pThing->ang), 30);
|
|
yvel[pThing->index] = MulScale(a6, Sin(pThing->ang), 30);
|
|
zvel[pThing->index] = MulScale(a6, a4, 14);
|
|
xvel[pThing->index] += xvel[pSprite->index]/2;
|
|
yvel[pThing->index] += yvel[pSprite->index]/2;
|
|
zvel[pThing->index] += zvel[pSprite->index]/2;
|
|
return pThing;
|
|
}
|
|
|
|
DBloodActor* actFireThing(DBloodActor* pSprite, int a2, int a3, int a4, int thingType, int a6)
|
|
{
|
|
auto spr = actFireThing_(&pSprite->s(), a2, a3, a4, thingType, a6);
|
|
return &bloodActors[spr->index];
|
|
}
|
|
|
|
spritetype* actFireMissile(spritetype *pSprite, int a2, int a3, int a4, int a5, int a6, int nType)
|
|
{
|
|
|
|
assert(nType >= kMissileBase && nType < kMissileMax);
|
|
char v4 = 0;
|
|
int nSprite = pSprite->index;
|
|
const MissileType *pMissileInfo = &missileInfo[nType-kMissileBase];
|
|
int x = pSprite->x+MulScale(a2, Cos(pSprite->ang+512), 30);
|
|
int y = pSprite->y+MulScale(a2, Sin(pSprite->ang+512), 30);
|
|
int z = pSprite->z+a3;
|
|
int clipdist = pMissileInfo->clipDist+pSprite->clipdist;
|
|
x += MulScale(clipdist, Cos(pSprite->ang), 28);
|
|
y += MulScale(clipdist, Sin(pSprite->ang), 28);
|
|
int hit = HitScan(pSprite, z, x-pSprite->x, y-pSprite->y, 0, CLIPMASK0, clipdist);
|
|
if (hit != -1)
|
|
{
|
|
if (hit == 3 || hit == 0)
|
|
{
|
|
v4 = 1;
|
|
x = gHitInfo.hitx-MulScale(Cos(pSprite->ang), 16, 30);
|
|
y = gHitInfo.hity-MulScale(Sin(pSprite->ang), 16, 30);
|
|
}
|
|
else
|
|
{
|
|
x = gHitInfo.hitx-MulScale(pMissileInfo->clipDist<<1, Cos(pSprite->ang), 28);
|
|
y = gHitInfo.hity-MulScale(pMissileInfo->clipDist<<1, Sin(pSprite->ang), 28);
|
|
}
|
|
}
|
|
auto actor = actSpawnSprite(pSprite->sectnum, x, y, z, 5, 1);
|
|
spritetype* pMissile = &actor->s();
|
|
int nMissile = pMissile->index;
|
|
show2dsprite.Set(nMissile);
|
|
pMissile->type = nType;
|
|
pMissile->shade = pMissileInfo->shade;
|
|
pMissile->pal = 0;
|
|
pMissile->clipdist = pMissileInfo->clipDist;
|
|
pMissile->flags = 1;
|
|
pMissile->xrepeat = pMissileInfo->xrepeat;
|
|
pMissile->yrepeat = pMissileInfo->yrepeat;
|
|
pMissile->picnum = pMissileInfo->picnum;
|
|
pMissile->ang = (pSprite->ang+pMissileInfo->angleOfs)&2047;
|
|
xvel[nMissile] = MulScale(pMissileInfo->velocity, a4, 14);
|
|
yvel[nMissile] = MulScale(pMissileInfo->velocity, a5, 14);
|
|
zvel[nMissile] = MulScale(pMissileInfo->velocity, a6, 14);
|
|
pMissile->owner = pSprite->index;
|
|
pMissile->cstat |= 1;
|
|
int nXSprite = pMissile->extra;
|
|
assert(nXSprite > 0 && nXSprite < kMaxXSprites);
|
|
xsprite[nXSprite].target = -1;
|
|
evPost(nMissile, 3, 600, kCallbackRemove);
|
|
|
|
actBuildMissile(pMissile, nXSprite, nSprite);
|
|
|
|
if (v4)
|
|
{
|
|
actImpactMissile(&bloodActors[pMissile->index], hit);
|
|
pMissile = NULL;
|
|
}
|
|
return pMissile;
|
|
}
|
|
|
|
void actBuildMissile(spritetype* pMissile, int nXSprite, int nSprite) {
|
|
int nMissile = pMissile->index;
|
|
switch (pMissile->type) {
|
|
case kMissileLifeLeechRegular:
|
|
evPost(nMissile, 3, 0, kCallbackFXFlameLick);
|
|
break;
|
|
case kMissileTeslaAlt:
|
|
evPost(nMissile, 3, 0, kCallbackFXTeslaAlt);
|
|
break;
|
|
case kMissilePukeGreen:
|
|
seqSpawn(29, 3, nXSprite, -1);
|
|
break;
|
|
case kMissileButcherKnife:
|
|
pMissile->cstat |= 16;
|
|
break;
|
|
case kMissileTeslaRegular:
|
|
sfxPlay3DSound(pMissile, 251, 0, 0);
|
|
break;
|
|
case kMissileEctoSkull:
|
|
seqSpawn(2, 3, nXSprite, -1);
|
|
sfxPlay3DSound(pMissile, 493, 0, 0);
|
|
break;
|
|
case kMissileFireballNapalm:
|
|
seqSpawn(61, 3, nXSprite, nNapalmClient);
|
|
sfxPlay3DSound(pMissile, 441, 0, 0);
|
|
break;
|
|
case kMissileFireball:
|
|
seqSpawn(22, 3, nXSprite, nFireballClient);
|
|
sfxPlay3DSound(pMissile, 441, 0, 0);
|
|
break;
|
|
case kMissileFlameHound:
|
|
seqSpawn(27, 3, nXSprite, -1);
|
|
xvel[nMissile] += xvel[nSprite] / 2 + Random2(0x11111);
|
|
yvel[nMissile] += yvel[nSprite] / 2 + Random2(0x11111);
|
|
zvel[nMissile] += zvel[nSprite] / 2 + Random2(0x11111);
|
|
break;
|
|
case kMissileFireballCerberus:
|
|
seqSpawn(61, 3, nXSprite, dword_2192E0);
|
|
sfxPlay3DSound(pMissile, 441, 0, 0);
|
|
break;
|
|
case kMissileFireballTchernobog:
|
|
seqSpawn(23, 3, nXSprite, dword_2192D8);
|
|
xvel[nMissile] += xvel[nSprite] / 2 + Random2(0x11111);
|
|
yvel[nMissile] += yvel[nSprite] / 2 + Random2(0x11111);
|
|
zvel[nMissile] += zvel[nSprite] / 2 + Random2(0x11111);
|
|
break;
|
|
case kMissileFlameSpray:
|
|
if (Chance(0x8000))
|
|
seqSpawn(0, 3, nXSprite, -1);
|
|
else
|
|
seqSpawn(1, 3, nXSprite, -1);
|
|
xvel[nMissile] += xvel[nSprite] + Random2(0x11111);
|
|
yvel[nMissile] += yvel[nSprite] + Random2(0x11111);
|
|
zvel[nMissile] += zvel[nSprite] + Random2(0x11111);
|
|
break;
|
|
case kMissileFlareAlt:
|
|
evPost(nMissile, 3, 30, kCallbackFXFlareBurst);
|
|
evPost(nMissile, 3, 0, kCallbackFXFlareSpark);
|
|
sfxPlay3DSound(pMissile, 422, 0, 0);
|
|
break;
|
|
case kMissileFlareRegular:
|
|
evPost(nMissile, 3, 0, kCallbackFXFlareSpark);
|
|
sfxPlay3DSound(pMissile, 422, 0, 0);
|
|
break;
|
|
case kMissileLifeLeechAltSmall:
|
|
evPost(nMissile, 3, 0, kCallbackFXArcSpark);
|
|
break;
|
|
case kMissileArcGargoyle:
|
|
sfxPlay3DSound(pMissile, 252, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int actGetRespawnTime(spritetype *pSprite) {
|
|
if (pSprite->extra <= 0) return -1;
|
|
XSPRITE *pXSprite = &xsprite[pSprite->extra];
|
|
if (IsDudeSprite(pSprite) && !IsPlayerSprite(pSprite)) {
|
|
if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nMonsterSettings == 2))
|
|
return gGameOptions.nMonsterRespawnTime;
|
|
return -1;
|
|
}
|
|
|
|
if (IsWeaponSprite(pSprite)) {
|
|
if (pXSprite->respawn == 3 || gGameOptions.nWeaponSettings == 1) return 0;
|
|
else if (pXSprite->respawn != 1 && gGameOptions.nWeaponSettings != 0)
|
|
return gGameOptions.nWeaponRespawnTime;
|
|
return -1;
|
|
}
|
|
|
|
if (IsAmmoSprite(pSprite)) {
|
|
if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nWeaponSettings != 0))
|
|
return gGameOptions.nWeaponRespawnTime;
|
|
return -1;
|
|
}
|
|
|
|
if (IsItemSprite(pSprite)) {
|
|
if (pXSprite->respawn == 3 && gGameOptions.nGameType == 1) return 0;
|
|
else if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nItemSettings != 0)) {
|
|
switch (pSprite->type) {
|
|
case kItemShadowCloak:
|
|
case kItemTwoGuns:
|
|
case kItemReflectShots:
|
|
return gGameOptions.nSpecialRespawnTime;
|
|
case kItemDeathMask:
|
|
return gGameOptions.nSpecialRespawnTime<<1;
|
|
default:
|
|
return gGameOptions.nItemRespawnTime;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool actCheckRespawn(spritetype *pSprite)
|
|
{
|
|
int nSprite = pSprite->index;
|
|
int nXSprite = pSprite->extra;
|
|
if (nXSprite > 0)
|
|
{
|
|
XSPRITE *pXSprite = &xsprite[nXSprite];
|
|
int nRespawnTime = actGetRespawnTime(pSprite);
|
|
if (nRespawnTime < 0)
|
|
return 0;
|
|
pXSprite->respawnPending = 1;
|
|
if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
|
|
{
|
|
pXSprite->respawnPending = 3;
|
|
if (pSprite->type == kThingTNTBarrel)
|
|
pSprite->cstat |= 32768;
|
|
}
|
|
if (nRespawnTime > 0)
|
|
{
|
|
if (pXSprite->respawnPending == 1)
|
|
nRespawnTime = MulScale(nRespawnTime, 0xa000, 16);
|
|
pSprite->owner = pSprite->statnum;
|
|
actPostSprite(pSprite->index, kStatRespawn);
|
|
pSprite->flags |= kHitagRespawn;
|
|
if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
|
|
{
|
|
pSprite->cstat &= ~257;
|
|
pSprite->x = baseSprite[nSprite].x;
|
|
pSprite->y = baseSprite[nSprite].y;
|
|
pSprite->z = baseSprite[nSprite].z;
|
|
}
|
|
evPost(nSprite, 3, nRespawnTime, kCallbackRespawn);
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool actCanSplatWall(int nWall)
|
|
{
|
|
assert(nWall >= 0 && nWall < kMaxWalls);
|
|
walltype *pWall = &wall[nWall];
|
|
if (pWall->cstat & 16384)
|
|
return 0;
|
|
if (pWall->cstat & 32768)
|
|
return 0;
|
|
int nType = GetWallType(nWall);
|
|
if (nType >= kWallBase && nType < kWallMax)
|
|
return 0;
|
|
if (pWall->nextsector != -1)
|
|
{
|
|
sectortype *pSector = §or[pWall->nextsector];
|
|
if (pSector->type >= kSectorBase && pSector->type < kSectorMax)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void actFireVector(spritetype *pShooter, int a2, int a3, int a4, int a5, int a6, VECTOR_TYPE vectorType)
|
|
{
|
|
int nShooter = pShooter->index;
|
|
assert(vectorType >= 0 && vectorType < kVectorMax);
|
|
const VECTORDATA *pVectorData = &gVectorData[vectorType];
|
|
int nRange = pVectorData->maxDist;
|
|
int hit = VectorScan(pShooter, a2, a3, a4, a5, a6, nRange, 1);
|
|
if (hit == 3)
|
|
{
|
|
int nSprite = gHitInfo.hitsprite;
|
|
assert(nSprite >= 0 && nSprite < kMaxSprites);
|
|
spritetype *pSprite = &sprite[nSprite];
|
|
if (!gGameOptions.bFriendlyFire && IsTargetTeammate(pShooter, pSprite)) return;
|
|
if (IsPlayerSprite(pSprite)) {
|
|
PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
|
|
if (powerupCheck(pPlayer, kPwUpReflectShots))
|
|
{
|
|
gHitInfo.hitsprite = nShooter;
|
|
gHitInfo.hitx = pShooter->x;
|
|
gHitInfo.hity = pShooter->y;
|
|
gHitInfo.hitz = pShooter->z;
|
|
}
|
|
}
|
|
}
|
|
int x = gHitInfo.hitx-MulScale(a4, 16, 14);
|
|
int y = gHitInfo.hity-MulScale(a5, 16, 14);
|
|
int z = gHitInfo.hitz-MulScale(a6, 256, 14);
|
|
short nSector = gHitInfo.hitsect;
|
|
uint8_t nSurf = kSurfNone;
|
|
if (nRange == 0 || approxDist(gHitInfo.hitx-pShooter->x, gHitInfo.hity-pShooter->y) < nRange)
|
|
{
|
|
switch (hit)
|
|
{
|
|
case 1:
|
|
{
|
|
int nSector = gHitInfo.hitsect;
|
|
if (sector[nSector].ceilingstat&1)
|
|
nSurf = kSurfNone;
|
|
else
|
|
nSurf = surfType[sector[nSector].ceilingpicnum];
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
int nSector = gHitInfo.hitsect;
|
|
if (sector[nSector].floorstat&1)
|
|
nSurf = kSurfNone;
|
|
else
|
|
nSurf = surfType[sector[nSector].floorpicnum];
|
|
break;
|
|
}
|
|
case 0:
|
|
{
|
|
int nWall = gHitInfo.hitwall;
|
|
assert(nWall >= 0 && nWall < kMaxWalls);
|
|
nSurf = surfType[wall[nWall].picnum];
|
|
if (actCanSplatWall(nWall))
|
|
{
|
|
int x = gHitInfo.hitx-MulScale(a4, 16, 14);
|
|
int y = gHitInfo.hity-MulScale(a5, 16, 14);
|
|
int z = gHitInfo.hitz-MulScale(a6, 256, 14);
|
|
int nSurf = surfType[wall[nWall].picnum];
|
|
assert(nSurf < kSurfMax);
|
|
if (pVectorData->surfHit[nSurf].fx1 >= 0)
|
|
{
|
|
spritetype *pFX = gFX.fxSpawn(pVectorData->surfHit[nSurf].fx1, nSector, x, y, z, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->ang = (GetWallAngle(nWall)+512)&2047;
|
|
pFX->cstat |= 16;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
int nWall = gHitInfo.hitwall;
|
|
assert(nWall >= 0 && nWall < kMaxWalls);
|
|
nSurf = surfType[wall[nWall].overpicnum];
|
|
int nXWall = wall[nWall].extra;
|
|
if (nXWall > 0)
|
|
{
|
|
XWALL *pXWall = &xwall[nXWall];
|
|
if (pXWall->triggerVector)
|
|
trTriggerWall(nWall, pXWall, kCmdWallImpact);
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
int nSprite = gHitInfo.hitsprite;
|
|
nSurf = surfType[sprite[nSprite].picnum];
|
|
assert(nSprite >= 0 && nSprite < kMaxSprites);
|
|
spritetype *pSprite = &sprite[nSprite];
|
|
x -= MulScale(a4, 112, 14);
|
|
y -= MulScale(a5, 112, 14);
|
|
z -= MulScale(a6, 112<<4, 14);
|
|
int shift = 4;
|
|
if (vectorType == kVectorTine && !IsPlayerSprite(pSprite))
|
|
shift = 3;
|
|
actDamageSprite(nShooter, pSprite, pVectorData->dmgType, pVectorData->dmg<<shift);
|
|
int nXSprite = pSprite->extra;
|
|
if (nXSprite > 0)
|
|
{
|
|
XSPRITE *pXSprite = &xsprite[nXSprite];
|
|
if (pXSprite->Vector)
|
|
trTriggerSprite(nSprite, pXSprite, kCmdSpriteImpact);
|
|
}
|
|
if (pSprite->statnum == kStatThing)
|
|
{
|
|
int t = thingInfo[pSprite->type-kThingBase].mass;
|
|
if (t > 0 && pVectorData->impulse)
|
|
{
|
|
int t2 = DivScale(pVectorData->impulse, t, 8);
|
|
xvel[nSprite] += MulScale(a4, t2, 16);
|
|
yvel[nSprite] += MulScale(a5, t2, 16);
|
|
zvel[nSprite] += MulScale(a6, t2, 16);
|
|
}
|
|
if (pVectorData->burnTime)
|
|
{
|
|
XSPRITE *pXSprite = &xsprite[nXSprite];
|
|
if (!pXSprite->burnTime)
|
|
evPost(nSprite, 3, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(sprite[nShooter].owner, pXSprite, pVectorData->burnTime);
|
|
}
|
|
}
|
|
if (pSprite->statnum == kStatDude)
|
|
{
|
|
int t = getDudeInfo(pSprite->type)->mass;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (IsDudeSprite(pSprite)) {
|
|
switch (pSprite->type) {
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
t = getSpriteMassBySize(pSprite);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (t > 0 && pVectorData->impulse)
|
|
{
|
|
int t2 = DivScale(pVectorData->impulse, t, 8);
|
|
xvel[nSprite] += MulScale(a4, t2, 16);
|
|
yvel[nSprite] += MulScale(a5, t2, 16);
|
|
zvel[nSprite] += MulScale(a6, t2, 16);
|
|
}
|
|
if (pVectorData->burnTime)
|
|
{
|
|
XSPRITE *pXSprite = &xsprite[nXSprite];
|
|
if (!pXSprite->burnTime)
|
|
evPost(nSprite, 3, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(sprite[nShooter].owner, pXSprite, pVectorData->burnTime);
|
|
}
|
|
if (Chance(pVectorData->fxChance))
|
|
{
|
|
int t = gVectorData[19].maxDist;
|
|
a4 += Random3(4000);
|
|
a5 += Random3(4000);
|
|
a6 += Random3(4000);
|
|
if (HitScan(pSprite, gHitInfo.hitz, a4, a5, a6, CLIPMASK1, t) == 0)
|
|
{
|
|
if (approxDist(gHitInfo.hitx-pSprite->x, gHitInfo.hity-pSprite->y) <= t)
|
|
{
|
|
int nWall = gHitInfo.hitwall;
|
|
int nSector = gHitInfo.hitsect;
|
|
if (actCanSplatWall(nWall))
|
|
{
|
|
int x = gHitInfo.hitx - MulScale(a4, 16, 14);
|
|
int y = gHitInfo.hity - MulScale(a5, 16, 14);
|
|
int z = gHitInfo.hitz - MulScale(a6, 16<<4, 14);
|
|
int nSurf = surfType[wall[nWall].picnum];
|
|
const VECTORDATA *pVectorData = &gVectorData[19];
|
|
FX_ID t2 = pVectorData->surfHit[nSurf].fx2;
|
|
FX_ID t3 = pVectorData->surfHit[nSurf].fx3;
|
|
spritetype *pFX = NULL;
|
|
if (t2 > FX_NONE && (t3 == FX_NONE || Chance(0x4000)))
|
|
pFX = gFX.fxSpawn(t2, nSector, x, y, z, 0);
|
|
else if(t3 > FX_NONE)
|
|
pFX = gFX.fxSpawn(t3, nSector, x, y, z, 0);
|
|
if (pFX)
|
|
{
|
|
zvel[pFX->index] = 0x2222;
|
|
pFX->ang = (GetWallAngle(nWall)+512)&2047;
|
|
pFX->cstat |= 16;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < pVectorData->bloodSplats; i++)
|
|
if (Chance(pVectorData->splatChance))
|
|
fxSpawnBlood(pSprite, pVectorData->dmg<<4);
|
|
}
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add impulse for sprites from physics list
|
|
if (gPhysSpritesCount > 0 && pVectorData->impulse) {
|
|
|
|
if (xspriRangeIsFine(pSprite->extra)) {
|
|
|
|
XSPRITE* pXSprite = &xsprite[pSprite->extra];
|
|
if (pXSprite->physAttr & kPhysDebrisVector) {
|
|
|
|
int impulse = DivScale(pVectorData->impulse, ClipLow(gSpriteMass[pSprite->extra].mass, 10), 6);
|
|
xvel[nSprite] += MulScale(a4, impulse, 16);
|
|
yvel[nSprite] += MulScale(a5, impulse, 16);
|
|
zvel[nSprite] += MulScale(a6, impulse, 16);
|
|
|
|
if (pVectorData->burnTime != 0) {
|
|
if (!xsprite[nXSprite].burnTime) evPost(nSprite, 3, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(sprite[nShooter].owner, &xsprite[nXSprite], pVectorData->burnTime);
|
|
}
|
|
|
|
if (pSprite->type >= kThingBase && pSprite->type < kThingMax) {
|
|
pSprite->statnum = kStatThing; // temporary change statnum property
|
|
actDamageSprite(nShooter, pSprite, pVectorData->dmgType, pVectorData->dmg << 4);
|
|
pSprite->statnum = kStatDecoration; // return statnum property back
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
assert(nSurf < kSurfMax);
|
|
#ifdef NOONE_EXTENSIONS
|
|
|
|
// let the patrol enemies hear surface hit sounds!
|
|
|
|
if (pVectorData->surfHit[nSurf].fx2 >= 0) {
|
|
|
|
spritetype* pFX2 = gFX.fxSpawn(pVectorData->surfHit[nSurf].fx2, nSector, x, y, z, 0);
|
|
if (pFX2 && gModernMap)
|
|
pFX2->owner = pShooter->index;
|
|
}
|
|
|
|
if (pVectorData->surfHit[nSurf].fx3 >= 0) {
|
|
|
|
spritetype* pFX3 = gFX.fxSpawn(pVectorData->surfHit[nSurf].fx3, nSector, x, y, z, 0);
|
|
if (pFX3 && gModernMap)
|
|
pFX3->owner = pShooter->index;
|
|
|
|
}
|
|
|
|
#else
|
|
if (pVectorData->surfHit[nSurf].fx2 >= 0)
|
|
gFX.fxSpawn(pVectorData->surfHit[nSurf].fx2, nSector, x, y, z, 0);
|
|
if (pVectorData->surfHit[nSurf].fx3 >= 0)
|
|
gFX.fxSpawn(pVectorData->surfHit[nSurf].fx3, nSector, x, y, z, 0);
|
|
#endif
|
|
|
|
if (pVectorData->surfHit[nSurf].fxSnd >= 0)
|
|
sfxPlay3DSound(x, y, z, pVectorData->surfHit[nSurf].fxSnd, nSector);
|
|
}
|
|
|
|
void FireballSeqCallback(int, DBloodActor* actor)
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
int nSprite = pXSprite->reference;
|
|
spritetype *pSprite = &actor->s();
|
|
spritetype *pFX = gFX.fxSpawn(FX_11, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
|
|
if (pFX)
|
|
{
|
|
int nFX = pFX->index;
|
|
xvel[nFX] = actor->xvel();
|
|
yvel[nFX] = actor->yvel();
|
|
zvel[nFX] = actor->zvel();
|
|
}
|
|
}
|
|
|
|
|
|
void NapalmSeqCallback(int, DBloodActor* actor)
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
spritetype *pSprite = &actor->s();
|
|
spritetype *pFX = gFX.fxSpawn(FX_12, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
|
|
if (pFX)
|
|
{
|
|
int nFX = pFX->index;
|
|
xvel[nFX] = actor->xvel();
|
|
yvel[nFX] = actor->yvel();
|
|
zvel[nFX] = actor->zvel();
|
|
}
|
|
}
|
|
|
|
void sub_3888C(int, DBloodActor* actor)
|
|
{
|
|
spritetype* pSprite = &actor->s();
|
|
spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
|
|
if (pFX)
|
|
{
|
|
int nFX = pFX->index;
|
|
xvel[nFX] = actor->xvel();
|
|
yvel[nFX] = actor->yvel();
|
|
zvel[nFX] = actor->zvel();
|
|
}
|
|
}
|
|
|
|
void sub_38938(int, DBloodActor* actor)
|
|
{
|
|
spritetype *pSprite = &actor->s();
|
|
spritetype *pFX = gFX.fxSpawn(FX_33, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
|
|
if (pFX)
|
|
{
|
|
int nFX = pFX->index;
|
|
xvel[nFX] = actor->xvel();
|
|
yvel[nFX] = actor->yvel();
|
|
zvel[nFX] = actor->zvel();
|
|
}
|
|
}
|
|
|
|
void TreeToGibCallback(int, DBloodActor* actor)
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
spritetype *pSprite = &actor->s();
|
|
pSprite->type = kThingObjectExplode;
|
|
pXSprite->state = 1;
|
|
pXSprite->data1 = 15;
|
|
pXSprite->data2 = 0;
|
|
pXSprite->data3 = 0;
|
|
pXSprite->health = thingInfo[17].startHealth;
|
|
pXSprite->data4 = 312;
|
|
pSprite->cstat |= 257;
|
|
}
|
|
|
|
void DudeToGibCallback1(int, DBloodActor* actor)
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
spritetype* pSprite = &actor->s();
|
|
pSprite->type = kThingBloodChunks;
|
|
pXSprite->data1 = 8;
|
|
pXSprite->data2 = 0;
|
|
pXSprite->data3 = 0;
|
|
pXSprite->health = thingInfo[26].startHealth;
|
|
pXSprite->data4 = 319;
|
|
pXSprite->triggerOnce = 0;
|
|
pXSprite->isTriggered = 0;
|
|
pXSprite->locked = 0;
|
|
pXSprite->targetX = PlayClock;
|
|
pXSprite->state = 1;
|
|
}
|
|
|
|
void DudeToGibCallback2(int, DBloodActor* actor)
|
|
{
|
|
XSPRITE* pXSprite = &actor->x();
|
|
spritetype* pSprite = &actor->s();
|
|
pSprite->type = kThingBloodChunks;
|
|
pXSprite->data1 = 3;
|
|
pXSprite->data2 = 0;
|
|
pXSprite->data3 = 0;
|
|
pXSprite->health = thingInfo[26].startHealth;
|
|
pXSprite->data4 = 319;
|
|
pXSprite->triggerOnce = 0;
|
|
pXSprite->isTriggered = 0;
|
|
pXSprite->locked = 0;
|
|
pXSprite->targetX = PlayClock;
|
|
pXSprite->state = 1;
|
|
}
|
|
|
|
void actPostSprite(int nSprite, int nStatus)
|
|
{
|
|
int n;
|
|
assert(gPostCount < kMaxSprites);
|
|
assert(nSprite < kMaxSprites && sprite[nSprite].statnum < kMaxStatus);
|
|
assert(nStatus >= 0 && nStatus <= kStatFree);
|
|
if (sprite[nSprite].flags&32)
|
|
{
|
|
for (n = 0; n < gPostCount; n++)
|
|
if (gPost[n].sprite == nSprite)
|
|
break;
|
|
assert(n < gPostCount);
|
|
}
|
|
else
|
|
{
|
|
n = gPostCount;
|
|
sprite[nSprite].flags |= 32;
|
|
gPostCount++;
|
|
}
|
|
gPost[n].sprite = nSprite;
|
|
gPost[n].status = nStatus;
|
|
}
|
|
|
|
void actPostSprite(DBloodActor* actor, int status)
|
|
{
|
|
actPostSprite(actor->s().index, status);
|
|
}
|
|
|
|
void actPostProcess(void)
|
|
{
|
|
for (int i = 0; i < gPostCount; i++)
|
|
{
|
|
POSTPONE *pPost = &gPost[i];
|
|
int nSprite = pPost->sprite;
|
|
spritetype *pSprite = &sprite[nSprite];
|
|
pSprite->flags &= ~32;
|
|
int nStatus = pPost->status;
|
|
if (nStatus == kStatFree)
|
|
{
|
|
evKill(nSprite, 3);
|
|
if (sprite[nSprite].extra > 0)
|
|
seqKill(3, sprite[nSprite].extra);
|
|
DeleteSprite(nSprite);
|
|
}
|
|
else
|
|
ChangeSpriteStat(nSprite, nStatus);
|
|
}
|
|
gPostCount = 0;
|
|
}
|
|
|
|
void MakeSplash(DBloodActor* actor)
|
|
{
|
|
auto pXSprite = &actor->x();
|
|
auto pSprite = &actor->s();
|
|
pSprite->flags &= ~2;
|
|
int nXSprite = pSprite->extra;
|
|
pSprite->z -= 4 << 8;
|
|
int nSurface = tileGetSurfType(gSpriteHit[nXSprite].florhit);
|
|
switch (pSprite->type) {
|
|
case kThingDripWater:
|
|
switch (nSurface) {
|
|
case kSurfWater:
|
|
seqSpawn(6, 3, nXSprite, -1);
|
|
sfxPlay3DSound(pSprite, 356, -1, 0);
|
|
break;
|
|
default:
|
|
seqSpawn(7, 3, nXSprite, -1);
|
|
sfxPlay3DSound(pSprite, 354, -1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
case kThingDripBlood:
|
|
seqSpawn(8, 3, nXSprite, -1);
|
|
sfxPlay3DSound(pSprite, 354, -1, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, POSTPONE& w, POSTPONE* def)
|
|
{
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("sprite", w.sprite)
|
|
("status", w.status)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, SPRITEHIT& w, SPRITEHIT* def)
|
|
{
|
|
int empty = 0;
|
|
if (arc.isReading()) w = {};
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("hit", w.hit, &empty)
|
|
("ceilhit", w.ceilhit, &empty)
|
|
("florhit", w.florhit, &empty)
|
|
.EndObject();
|
|
}
|
|
return arc;
|
|
}
|
|
|
|
void SerializeActor(FSerializer& arc)
|
|
{
|
|
if (arc.BeginObject("actor"))
|
|
{
|
|
arc("maxdist20", gVectorData[kVectorTchernobogBurn].maxDist) // The code messes around with this field so better save it.
|
|
.SparseArray("spritehit", gSpriteHit, kMaxSprites, activeXSprites)
|
|
("postcount", gPostCount)
|
|
.Array("post", gPost, gPostCount)
|
|
.EndObject();
|
|
|
|
if (arc.isReading() && gGameOptions.nMonsterSettings != 0)
|
|
{
|
|
for (int i = 0; i < kDudeMax - kDudeBase; i++)
|
|
for (int j = 0; j < 7; j++)
|
|
dudeInfo[i].damageVal[j] = MulScale(DudeDifficulty[gGameOptions.nDifficulty], dudeInfo[i].startDamage[j], 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
spritetype* actDropObject(spritetype* pSprite, int nType)
|
|
{
|
|
auto act = actDropObject(&bloodActors[pSprite->index], nType);
|
|
return act ? &act->s() : nullptr;
|
|
}
|
|
|
|
bool actHealDude(XSPRITE* pXDude, int a2, int a3)
|
|
{
|
|
return actHealDude(&bloodActors[pXDude->reference], a2, a3);
|
|
}
|
|
|
|
void actKillDude(int a1, spritetype* pSprite, DAMAGE_TYPE a3, int a4)
|
|
{
|
|
actKillDude(&bloodActors[a1], &bloodActors[pSprite->index], a3, a4);
|
|
}
|
|
|
|
int actDamageSprite(int nSource, spritetype* pSprite, DAMAGE_TYPE damageType, int damage)
|
|
{
|
|
return actDamageSprite(nSource == -1 ? nullptr : &bloodActors[nSource], &bloodActors[pSprite->index], damageType, damage);
|
|
}
|
|
|
|
|
|
END_BLD_NS
|