mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-23 20:43:02 +00:00
7130 lines
160 KiB
C++
7130 lines
160 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
|
|
|
|
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,
|
|
CSTAT_SPRITE_YCENTER | CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
907,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
256, 256, 128, 64, 0, 0, 128,
|
|
},
|
|
|
|
// Armed Proxy Dynamite
|
|
{
|
|
5,
|
|
5,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
3444,
|
|
-16,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 256, 256, 64, 0, 0, 512,
|
|
},
|
|
// Armed Remote Dynamite
|
|
{
|
|
5,
|
|
5,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
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,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
3422,
|
|
-32,
|
|
0,
|
|
32,
|
|
32,
|
|
64, 256, 128, 64, 0, 0, 256,
|
|
},
|
|
// Armed bundle Of TNT
|
|
{
|
|
5,
|
|
14,
|
|
16,
|
|
3,
|
|
24576,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
3433,
|
|
-32,
|
|
0,
|
|
32,
|
|
32,
|
|
64, 256, 128, 64, 0, 0, 256,
|
|
},
|
|
// Armed aerosol
|
|
{
|
|
5,
|
|
14,
|
|
16,
|
|
3,
|
|
32768,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
3467,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
64, 256, 128, 64, 0, 0, 256,
|
|
},
|
|
// Bone (Flesh Garg.)
|
|
{
|
|
5,
|
|
6,
|
|
16,
|
|
3,
|
|
32768,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
1462,
|
|
0,
|
|
0,
|
|
32,
|
|
32,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Some alpha stuff
|
|
{
|
|
8,
|
|
3,
|
|
16,
|
|
11,
|
|
32768,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
-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,
|
|
CSTAT_SPRITE_BLOCK_ALL,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
128, 64, 256, 256, 0, 0, 256,
|
|
},
|
|
// Blood chucks2
|
|
{
|
|
30,
|
|
30,
|
|
8,
|
|
3,
|
|
8192,
|
|
0,
|
|
CSTAT_SPRITE_BLOCK_ALL,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
128, 64, 256, 256, 0, 0, 64,
|
|
},
|
|
// Axe Zombie Head
|
|
{
|
|
60,
|
|
5,
|
|
32,
|
|
3,
|
|
40960,
|
|
1280,
|
|
CSTAT_SPRITE_BLOCK_ALL,
|
|
3405,
|
|
0,
|
|
0,
|
|
40,
|
|
40,
|
|
128, 64, 256, 256, 0, 0, 64,
|
|
},
|
|
// Napalm's Alt Fire explosion
|
|
{
|
|
80,
|
|
30,
|
|
32,
|
|
3,
|
|
57344,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
3281,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// Fire Pod Explosion
|
|
{
|
|
80,
|
|
30,
|
|
32,
|
|
3,
|
|
57344,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
2020,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 0, 256, 256, 0, 0, 0,
|
|
},
|
|
// Green Pod Explosion
|
|
{
|
|
80,
|
|
30,
|
|
32,
|
|
3,
|
|
57344,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
1860,
|
|
-128,
|
|
0,
|
|
32,
|
|
32,
|
|
256, 0, 256, 256, 0, 0, 0,
|
|
},
|
|
// Life Leech
|
|
{
|
|
150,
|
|
30,
|
|
48,
|
|
3,
|
|
32768,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_ALL,
|
|
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,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
3444,
|
|
-16,
|
|
7,
|
|
32,
|
|
32,
|
|
256, 256, 256, 64, 0, 0, 512,
|
|
},
|
|
// 434 - kModernThingThrowableRock
|
|
{
|
|
5,
|
|
6,
|
|
16,
|
|
3,
|
|
32768,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_HITSCAN,
|
|
1462,
|
|
0,
|
|
0,
|
|
32,
|
|
32,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
},
|
|
// 435 - kModernThingEnemyLifeLeech
|
|
{
|
|
150,
|
|
30,
|
|
48,
|
|
3,
|
|
32768,
|
|
1600,
|
|
CSTAT_SPRITE_BLOCK_ALL,
|
|
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 int16_t gPlayerGibThingComments[] = {
|
|
734, 735, 736, 737, 738, 739, 740, 741, 3038, 3049
|
|
};
|
|
|
|
const int16_t DudeDifficulty[5] = {
|
|
512, 384, 256, 208, 160
|
|
};
|
|
|
|
struct POSTPONE
|
|
{
|
|
DBloodActor* sprite;
|
|
int status;
|
|
};
|
|
|
|
TArray<POSTPONE> gPost;
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool IsUnderwaterSector(sectortype* pSector)
|
|
{
|
|
return !!pSector->hasX() && pSector->xs().Underwater;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actInitTraps()
|
|
{
|
|
BloodStatIterator it(kStatTraps);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (act->spr.type == kTrapExploder)
|
|
{
|
|
act->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
|
|
act->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
if (!act->hasX()) continue;
|
|
act->xspr.waitTime = ClipLow(act->xspr.waitTime, 1);
|
|
act->xspr.state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actInitThings()
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (!act->hasX()) continue;
|
|
|
|
int nType = act->spr.type - kThingBase;
|
|
act->xspr.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
|
|
act->spr.clipdist = thingInfo[nType].clipdist;
|
|
|
|
act->spr.flags = thingInfo[nType].flags;
|
|
if (act->spr.flags & kPhysGravity) act->spr.flags |= kPhysFalling;
|
|
act->ZeroVelocity();
|
|
|
|
switch (act->spr.type)
|
|
{
|
|
case kThingArmedProxBomb:
|
|
case kTrapMachinegun:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingTNTProx:
|
|
#endif
|
|
act->xspr.state = 0;
|
|
break;
|
|
case kThingBloodChunks:
|
|
{
|
|
SEQINST* pInst = GetInstance(act);
|
|
if (pInst)
|
|
{
|
|
auto seq = getSequence(pInst->nSeqID);
|
|
if (!seq) break;
|
|
seqSpawn(pInst->nSeqID, act);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
act->xspr.state = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actInitDudes()
|
|
{
|
|
if (gGameOptions.nMonsterSettings == 0)
|
|
{
|
|
gKillMgr.SetCount(0);
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (act->hasX() && act->xspr.key > 0) // Drop Key
|
|
actDropObject(act, kItemKeyBase + (act->xspr.key - 1));
|
|
DeleteSprite(act);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// by NoOne: WTF is this?
|
|
///////////////
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (act->spr.type < kDudeBase || act->spr.type >= kDudeMax)
|
|
I_Error("Non-enemy sprite (%d) in the enemy sprite list.\n", act->GetIndex());
|
|
}
|
|
|
|
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;
|
|
|
|
int nType = act->spr.type - kDudeBase;
|
|
int seqStartId = dudeInfo[nType].seqStartID;
|
|
if (!act->IsPlayerActor())
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
switch (act->spr.type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
act->spr.cstat |= CSTAT_SPRITE_BLOOD_BIT1 | CSTAT_SPRITE_BLOCK_ALL;
|
|
if (act->xspr.data2 > 0 && getSequence(act->xspr.data2))
|
|
seqStartId = act->xspr.data2; // Custom Dude stores it's SEQ in data2
|
|
|
|
seqStartId = genDudeSeqStartId(act); // Custom Dude stores its SEQ in data2
|
|
act->xspr.sysData1 = act->xspr.data3; // move sndStartId to sysData1, because data3 used by the game;
|
|
act->xspr.data3 = 0;
|
|
break;
|
|
|
|
case kDudePodMother: // FakeDude type (no seq, custom flags, clipdist and cstat)
|
|
if (gModernMap) break;
|
|
[[fallthrough]];
|
|
default:
|
|
act->spr.clipdist = dudeInfo[nType].clipdist;
|
|
act->spr.cstat |= CSTAT_SPRITE_BLOOD_BIT1 | CSTAT_SPRITE_BLOCK_ALL;
|
|
break;
|
|
}
|
|
#else
|
|
act->spr.clipdist = dudeInfo[nType].clipdist;
|
|
act->spr.cstat |= CSTAT_SPRITE_BLOOD_BIT1 | CSTAT_SPRITE_BLOCK_ALL;
|
|
#endif
|
|
|
|
act->ZeroVelocity();
|
|
|
|
#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 || act->xspr.sysData2 <= 0) act->xspr.health = dudeInfo[nType].startHealth << 4;
|
|
else act->xspr.health = ClipRange(act->xspr.sysData2 << 4, 1, 65535);
|
|
#else
|
|
act->xspr.health = dudeInfo[nType].startHealth << 4;
|
|
#endif
|
|
|
|
}
|
|
|
|
if (getSequence(seqStartId)) seqSpawn(seqStartId, act);
|
|
}
|
|
aiInit();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actInit(TArray<DBloodActor*>& actors)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (!gModernMap) nnExtResetGlobals();
|
|
else nnExtInitModernStuff(actors);
|
|
#endif
|
|
|
|
BloodStatIterator it(kStatItem);
|
|
while (auto act = it.Next())
|
|
{
|
|
if (act->spr.type == kItemWeaponVoodooDoll)
|
|
{
|
|
act->spr.type = kItemAmmoVoodooDoll;
|
|
break;
|
|
}
|
|
}
|
|
|
|
actInitTraps();
|
|
actInitThings();
|
|
actInitDudes();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void ConcussSprite(DBloodActor* source, DBloodActor* actor, const DVector3& pos, double damage)
|
|
{
|
|
auto vect = actor->spr.pos - pos;
|
|
|
|
double dist2 = vect.LengthSquared() + 0x4000;
|
|
damage *= 0x4000 / dist2;
|
|
|
|
if (actor->spr.flags & kPhysMove)
|
|
{
|
|
int mass = 0;
|
|
if (actor->IsDudeActor())
|
|
{
|
|
mass = getDudeInfo(actor->spr.type)->mass;
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (actor->spr.type == kDudeModernCustom || actor->spr.type == kDudeModernCustomBurning)
|
|
{
|
|
mass = getSpriteMassBySize(actor);
|
|
}
|
|
#endif
|
|
}
|
|
else if (actor->spr.type >= kThingBase && actor->spr.type < kThingMax)
|
|
{
|
|
mass = thingInfo[actor->spr.type - kThingBase].mass;
|
|
}
|
|
else
|
|
{
|
|
Printf(PRINT_HIGH, "Unexpected type in ConcussSprite(): Sprite: %d Type: %d Stat: %d", actor->GetIndex(), (int)actor->spr.type, (int)actor->spr.statnum);
|
|
return;
|
|
}
|
|
|
|
if (mass > 0)
|
|
{
|
|
int size = (tileWidth(actor->spr.picnum) * actor->spr.xrepeat * tileHeight(actor->spr.picnum) * actor->spr.yrepeat) >> 1;
|
|
int t = Scale(damage, size, mass);
|
|
actor->add_int_bvel_x((int)MulScaleF(t, vect.X, 12));
|
|
actor->add_int_bvel_y((int)MulScaleF(t, vect.Y, 12));
|
|
actor->add_int_bvel_z((int)MulScaleF(t, vect.Z, 12));
|
|
}
|
|
}
|
|
actDamageSprite(source, actor, kDamageExplode, damage);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actWallBounceVector(DBloodActor* actor, walltype* pWall, double factor)
|
|
{
|
|
auto delta = pWall->delta().Unit();
|
|
auto vel = actor->vel.XY();
|
|
double t = vel.X * -delta.Y + vel.Y * delta.X;
|
|
double t2 = t * (factor+1);
|
|
actor->vel.XY() = (vel - DVector2(-delta.Y * t2, delta.X * t2));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DVector4 actFloorBounceVector(DBloodActor* actor, double oldz, sectortype* pSector, double factor)
|
|
{
|
|
DVector4 retval;
|
|
double t = 1. - factor;
|
|
if (pSector->floorheinum == 0)
|
|
{
|
|
double t2 = oldz * t;
|
|
return { actor->vel.X, actor->vel.Y, oldz - t2, t2};
|
|
}
|
|
|
|
walltype* pWall = pSector->firstWall();
|
|
auto p = actor->int_vel();
|
|
int angle = getangle(pWall->delta()) + 512;
|
|
int t2 = pSector->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(p.X, t6, p.Y, t7, oldz, t4, 16);
|
|
int t9 = MulScale(t8, 0x10000 + FloatToFixed(factor), 16);
|
|
retval.X = FixedToFloat(p.X - MulScale(t6, t9, 16));
|
|
retval.Y = FixedToFloat(p.Y - MulScale(t7, t9, 16));
|
|
retval.Z = FixedToFloat(oldz - MulScale(t4, t9, 16));
|
|
retval.W = FixedToFloat(t8 * t);
|
|
return retval;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actRadiusDamage(DBloodActor* source, const DVector3& pos, sectortype* pSector, int nDist, int baseDmg, int distDmg, DAMAGE_TYPE dmgType, int flags, int burn)
|
|
{
|
|
auto pOwner = source->GetOwner();
|
|
const bool newSectCheckMethod = !cl_bloodvanillaexplosions && pOwner && pOwner->IsDudeActor() && !VanillaMode(); // use new sector checking logic
|
|
auto sectorMap = GetClosestSpriteSectors(pSector, pos.XY(), nDist, nullptr, newSectCheckMethod);
|
|
nDist <<= 4;
|
|
if (flags & 2)
|
|
{
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto act2 = it.Next())
|
|
{
|
|
if (act2 != source || (flags & 1))
|
|
{
|
|
if (act2->hasX())
|
|
{
|
|
if (act2->spr.flags & 0x20) continue;
|
|
if (!CheckSector(sectorMap, act2)) continue;
|
|
if (!CheckProximity(act2, pos, pSector, nDist)) continue;
|
|
|
|
int dist = int((pos - act2->spr.pos).Length() * worldtoint);
|
|
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())
|
|
{
|
|
if (act2->spr.flags & 0x20) continue;
|
|
if (!CheckSector(sectorMap, act2)) continue;
|
|
if (!CheckProximity(act2, pos, pSector, nDist)) continue;
|
|
|
|
if (act2->xspr.locked) continue;
|
|
|
|
int dist = int((pos.XY() - act2->spr.pos.XY()).Length() * worldtoint);
|
|
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 pOwner = actor->GetOwner();
|
|
|
|
actPostSprite(actor, kStatDecoration);
|
|
seqSpawn(9, actor);
|
|
if (Chance(0x8000)) actor->spr.cstat |= CSTAT_SPRITE_XFLIP;
|
|
|
|
sfxPlay3DSound(actor, 303, 24 + (actor->spr.flags & 3), 1);
|
|
actRadiusDamage(pOwner, actor->spr.pos, actor->sector(), 128, 0, 60, kDamageExplode, 15, 120);
|
|
|
|
if (actor->xspr.data4 > 1)
|
|
{
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
int spawnparam[2];
|
|
spawnparam[0] = actor->xspr.data4 >> 1;
|
|
spawnparam[1] = actor->xspr.data4 - spawnparam[0];
|
|
auto ang = actor->spr.angle;
|
|
actor->ZeroVelocity();
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
int t1 = Random(0x33333) + 0x33333;
|
|
auto rndang = DAngle::fromBuild(Random2(0x71));
|
|
actor->spr.angle = (ang + rndang).Normalized360();
|
|
auto spawned = actFireThing(actor, 0, 0, -0x93d0, kThingNapalmBall, t1);
|
|
spawned->SetOwner(actor->GetOwner());
|
|
seqSpawn(61, spawned, nNapalmClient);
|
|
spawned->xspr.data4 = spawnparam[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actSpawnFloor(DBloodActor* actor)
|
|
{
|
|
auto pSector = actor->sector();
|
|
auto pos = actor->spr.pos;
|
|
updatesector(pos, &pSector);
|
|
double zFloor = getflorzofslopeptrf(pSector, pos.X, pos.Y);
|
|
auto spawned = actSpawnSprite(pSector, DVector3(pos.XY(), zFloor), 3, 0);
|
|
if (spawned) spawned->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
return spawned;
|
|
}
|
|
|
|
static DBloodActor* actDropAmmo(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
if (actor->spr.statnum < kMaxStatus && nType >= kItemAmmoBase && nType < kItemAmmoMax)
|
|
{
|
|
auto act2 = actSpawnFloor(actor);
|
|
const AMMOITEMDATA* pAmmo = &gAmmoItemData[nType - kItemAmmoBase];
|
|
act2->spr.type = nType;
|
|
act2->spr.picnum = pAmmo->picnum;
|
|
act2->spr.shade = pAmmo->shade;
|
|
act2->spr.xrepeat = pAmmo->xrepeat;
|
|
act2->spr.yrepeat = pAmmo->yrepeat;
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static DBloodActor* actDropWeapon(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
if (actor->spr.statnum < kMaxStatus && nType >= kItemWeaponBase && nType < kItemWeaponMax)
|
|
{
|
|
auto act2 = actSpawnFloor(actor);
|
|
const WEAPONITEMDATA* pWeapon = &gWeaponItemData[nType - kItemWeaponBase];
|
|
act2->spr.type = nType;
|
|
act2->spr.picnum = pWeapon->picnum;
|
|
act2->spr.shade = pWeapon->shade;
|
|
act2->spr.xrepeat = pWeapon->xrepeat;
|
|
act2->spr.yrepeat = pWeapon->yrepeat;
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static DBloodActor* actDropItem(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
if (actor->spr.statnum < kMaxStatus && nType >= kItemBase && nType < kItemMax)
|
|
{
|
|
auto act2 = actSpawnFloor(actor);
|
|
const ITEMDATA* pItem = &gItemData[nType - kItemBase];
|
|
act2->spr.type = nType;
|
|
act2->spr.picnum = pItem->picnum;
|
|
act2->spr.shade = pItem->shade;
|
|
act2->spr.xrepeat = pItem->xrepeat;
|
|
act2->spr.yrepeat = pItem->yrepeat;
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actDropKey(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
if (actor->spr.statnum < kMaxStatus && nType >= kItemKeyBase && nType < kItemKeyMax)
|
|
{
|
|
auto act2 = actDropItem(actor, nType);
|
|
if (act2 && gGameOptions.nGameType == 1)
|
|
{
|
|
act2->addX();
|
|
act2->xspr.respawn = 3;
|
|
act2->hit.florhit.setNone();
|
|
act2->hit.ceilhit.setNone();
|
|
}
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static DBloodActor* actDropFlag(DBloodActor* actor, int nType)
|
|
{
|
|
if (!actor) return nullptr;
|
|
if (actor->spr.statnum < kMaxStatus && (nType == 147 || nType == 148))
|
|
{
|
|
auto act2 = actDropItem(actor, nType);
|
|
if (act2 && gGameOptions.nGameType == 3)
|
|
{
|
|
evPostActor(act2, 1800, kCallbackReturnFlag);
|
|
}
|
|
return act2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
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)
|
|
{
|
|
double top, bottom;
|
|
GetActorExtents(act2, &top, &bottom);
|
|
if (bottom >= act2->spr.pos.Z)
|
|
act2->spr.pos.Z -= (bottom - act2->spr.pos.Z);
|
|
}
|
|
|
|
return act2;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool actHealDude(DBloodActor* actor, int add, int threshold)
|
|
{
|
|
if (!actor) return false;
|
|
add <<= 4;
|
|
threshold <<= 4;
|
|
if (actor->xspr.health < (unsigned)threshold)
|
|
{
|
|
if (actor->IsPlayerActor()) sfxPlay3DSound(actor->spr.pos, 780, actor->sector());
|
|
actor->xspr.health = min<uint32_t>(actor->xspr.health + add, threshold);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
static bool actKillModernDude(DBloodActor* actor, DAMAGE_TYPE damageType)
|
|
{
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
removeDudeStuff(actor);
|
|
if (actor->xspr.txID <= 0 || getNextIncarnation(actor) == nullptr)
|
|
{
|
|
if (pExtra->weaponType == kGenDudeWeaponKamikaze && Chance(0x4000) && damageType != kDamageSpirit && damageType != kDamageDrown)
|
|
{
|
|
doExplosion(actor, actor->xspr.data1 - kTrapExploder);
|
|
if (Chance(0x9000)) damageType = kDamageExplode;
|
|
}
|
|
|
|
if (damageType == kDamageBurn)
|
|
{
|
|
if (pExtra->availDeaths[kDamageBurn] && !spriteIsUnderwater(actor))
|
|
{
|
|
if (pExtra->canBurn)
|
|
{
|
|
actor->spr.type = kDudeModernCustomBurning;
|
|
if (actor->xspr.data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation
|
|
actor->spr.pal = 0;
|
|
|
|
aiGenDudeNewState(actor, &genDudeBurnGoto);
|
|
actHealDude(actor, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
|
|
if (actor->xspr.burnTime <= 0) actor->xspr.burnTime = 1200;
|
|
actor->dudeExtra.time = PlayClock + 360;
|
|
return true;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
actor->xspr.burnTime = 0;
|
|
actor->SetBurnSource(nullptr);
|
|
damageType = kDamageFall;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
actor->xspr.locked = 1; // lock while transforming
|
|
|
|
aiSetGenIdleState(actor); // set idle state
|
|
|
|
if (actor->xspr.key > 0) // drop keys
|
|
actDropObject(actor, kItemKeyBase + actor->xspr.key - 1);
|
|
|
|
if (actor->xspr.dropMsg > 0) // drop items
|
|
actDropObject(actor, actor->xspr.dropMsg);
|
|
|
|
actor->spr.flags &= ~kPhysMove;
|
|
actor->ZeroVelocityXY();
|
|
|
|
playGenDudeSound(actor, kGenDudeSndTransforming);
|
|
int seqId = actor->xspr.data2 + kGenDudeSeqTransform;
|
|
if (getSequence(seqId)) seqSpawn(seqId, actor, -1);
|
|
else
|
|
{
|
|
seqKill(actor);
|
|
DBloodActor* pEffect = gFX.fxSpawnActor((FX_ID)52, actor->sector(), actor->spr.pos, actor->int_ang());
|
|
if (pEffect != nullptr)
|
|
{
|
|
pEffect->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
|
|
pEffect->spr.pal = 6;
|
|
pEffect->spr.xrepeat = actor->spr.xrepeat;
|
|
pEffect->spr.yrepeat = actor->spr.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;
|
|
|
|
double top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
DVector3 gibPos(actor->spr.pos.XY(), top);
|
|
CGibVelocity gibVel(actor->int_vel().X >> 1, actor->int_vel().Y >> 1, -0xccccc);
|
|
GibSprite(actor, nGibType, &gibPos, &gibVel);
|
|
}
|
|
}
|
|
|
|
actor->xspr.sysData1 = kGenDudeTransformStatus; // in transform
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static bool actKillDudeStage1(DBloodActor* actor, DAMAGE_TYPE damageType)
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
if (actKillModernDude(actor, damageType)) return true;
|
|
break;
|
|
#endif
|
|
case kDudeCerberusTwoHead: // Cerberus
|
|
seqSpawn(dudeInfo[actor->spr.type - kDudeBase].seqStartID + 1, actor, -1);
|
|
return true;
|
|
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
case kDudeCultistTesla:
|
|
case kDudeCultistTNT:
|
|
if (damageType == kDamageBurn && actor->xspr.medium == kMediumNormal)
|
|
{
|
|
actor->spr.type = kDudeBurningCultist;
|
|
aiNewState(actor, &cultistBurnGoto);
|
|
actHealDude(actor, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kDudeBeast:
|
|
if (damageType == kDamageBurn && actor->xspr.medium == kMediumNormal)
|
|
{
|
|
actor->spr.type = kDudeBurningBeast;
|
|
aiNewState(actor, &beastBurnGoto);
|
|
actHealDude(actor, dudeInfo[53].startHealth, dudeInfo[53].startHealth);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case kDudeInnocent:
|
|
if (damageType == kDamageBurn && actor->xspr.medium == kMediumNormal)
|
|
{
|
|
actor->spr.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 && actor->xspr.medium == kMediumNormal)
|
|
{
|
|
actor->spr.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)
|
|
{
|
|
if (VanillaMode())
|
|
{
|
|
if (killerActor->IsPlayerActor())
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[killerActor->spr.type - kDudePlayer1];
|
|
if (gGameOptions.nGameType == 1)
|
|
pPlayer->fragCount++;
|
|
}
|
|
}
|
|
else if (gGameOptions.nGameType == 1 && killerActor->IsPlayerActor() && actor->spr.statnum == kStatDude)
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeBat:
|
|
case kDudeRat:
|
|
case kDudeInnocent:
|
|
case kDudeBurningInnocent:
|
|
break;
|
|
default:
|
|
PLAYER* pKillerPlayer = &gPlayer[killerActor->spr.type - kDudePlayer1];
|
|
pKillerPlayer->fragCount++;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkDropObjects(DBloodActor* actor)
|
|
{
|
|
if (actor->xspr.key > 0) actDropObject(actor, kItemKeyBase + actor->xspr.key - 1);
|
|
if (actor->xspr.dropMsg > 0) actDropObject(actor, actor->xspr.dropMsg);
|
|
|
|
switch (actor->spr.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;
|
|
|
|
switch (damageType)
|
|
{
|
|
case kDamageExplode:
|
|
nSeq = 2;
|
|
switch (actor->spr.type)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
{
|
|
playGenDudeSound(actor, 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(actor, 717, -1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
case kDamageBurn:
|
|
nSeq = 3;
|
|
sfxPlay3DSound(actor, 351, -1, 0);
|
|
break;
|
|
case kDamageSpirit:
|
|
switch (actor->spr.type) {
|
|
case kDudeZombieAxeNormal:
|
|
case kDudeZombieAxeBuried:
|
|
nSeq = 14;
|
|
break;
|
|
case kDudeZombieButcher:
|
|
nSeq = 11;
|
|
break;
|
|
default:
|
|
nSeq = 1;
|
|
break;
|
|
}
|
|
break;
|
|
case kDamageFall:
|
|
switch (actor->spr.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)
|
|
{
|
|
double top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
DVector3 gibPos(actor->spr.pos.XY(), top);
|
|
CGibVelocity gibVel(actor->int_vel().X >> 1, actor->int_vel().Y >> 1, velz);
|
|
GibSprite(actor, GIBTYPE_27, &gibPos, &gibVel);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void zombieAxeNormalDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
int nType = actor->spr.type - kDudeBase;
|
|
|
|
sfxPlay3DSound(actor, 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);
|
|
evPostActor(actor, 0, kCallbackFXZombieSpurt);
|
|
sfxPlay3DSound(actor, 362, -1, 0);
|
|
actor->xspr.data1 = 35;
|
|
actor->xspr.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)
|
|
{
|
|
if (Chance(0x4000) && nSeq == 3) sfxPlay3DSound(actor, 718, -1, 0);
|
|
else sfxPlay3DSound(actor, 1018 + Random(2), -1, 0);
|
|
|
|
int nType = actor->spr.type - kDudeBase;
|
|
if (Chance(0x8000))
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
GibSprite(actor, 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)
|
|
{
|
|
playGenDudeSound(actor, kGenDudeSndDeathNormal);
|
|
int dudeToGib = (actCheckRespawn(actor)) ? -1 : ((nSeq == 3) ? nDudeToGibClient2 : nDudeToGibClient1);
|
|
if (nSeq == 3)
|
|
{
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
if (pExtra->availDeaths[kDmgBurn] == 3) seqSpawn((15 + Random(2)) + actor->xspr.data2, actor, dudeToGib);
|
|
else if (pExtra->availDeaths[kDmgBurn] == 2) seqSpawn(16 + actor->xspr.data2, actor, dudeToGib);
|
|
else if (pExtra->availDeaths[kDmgBurn] == 1) seqSpawn(15 + actor->xspr.data2, actor, dudeToGib);
|
|
else if (getSequence(actor->xspr.data2 + nSeq))seqSpawn(nSeq + actor->xspr.data2, actor, dudeToGib);
|
|
else seqSpawn(1 + actor->xspr.data2, actor, dudeToGib);
|
|
|
|
}
|
|
else
|
|
{
|
|
seqSpawn(nSeq + actor->xspr.data2, actor, dudeToGib);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void modernCustomDudeBurningDeath(DBloodActor* actor, int nSeq)
|
|
{
|
|
playGenDudeSound(actor, kGenDudeSndDeathExplode);
|
|
int dudeToGib = (actCheckRespawn(actor)) ? -1 : nDudeToGibClient1;
|
|
|
|
if (Chance(0x4000)) spawnGibs(actor, GIBTYPE_27, -0xccccc);
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
int seqofs = actor->xspr.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)
|
|
{
|
|
int nType = actor->spr.type - kDudeBase;
|
|
|
|
if (Chance(0x8000) && nSeq == 3)
|
|
sfxPlay3DSound(actor, 1109, -1, 0);
|
|
else
|
|
sfxPlay3DSound(actor, 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)
|
|
{
|
|
int nType = actor->spr.type - kDudeBase;
|
|
|
|
if (nSeq == 14)
|
|
{
|
|
sfxPlay3DSound(actor, 1206, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + 11, actor, -1);
|
|
return;
|
|
}
|
|
sfxPlay3DSound(actor, 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)
|
|
{
|
|
if (Chance(0x4000) && nSeq == 3) sfxPlay3DSound(actor, sound1 + 2, -1, 0);
|
|
else sfxPlay3DSound(actor, sound1 + Random(2), -1, 0);
|
|
seqSpawn(seqnum, actor, -1);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actKillDude(DBloodActor* killerActor, DBloodActor* actor, DAMAGE_TYPE damageType, int damage)
|
|
{
|
|
assert(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax&& actor->hasX());
|
|
int nType = actor->spr.type - kDudeBase;
|
|
|
|
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].fragger = nullptr;
|
|
}
|
|
if (actor->spr.type != kDudeCultistBeast)
|
|
trTriggerSprite(actor, kCmdOff, killerActor);
|
|
|
|
actor->spr.flags |= 7;
|
|
checkAddFrag(killerActor, actor);
|
|
checkDropObjects(actor);
|
|
|
|
int nSeq = checkDamageType(actor, damageType);
|
|
|
|
if (!getSequence(getDudeInfo(nType + kDudeBase)->seqStartID + nSeq))
|
|
{
|
|
seqKill(actor);
|
|
gKillMgr.AddKill(actor);
|
|
actPostSprite(actor, kStatFree);
|
|
return;
|
|
}
|
|
|
|
auto Owner = actor->GetOwner();
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeZombieAxeNormal:
|
|
zombieAxeNormalDeath(actor, nSeq);
|
|
break;
|
|
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
case kDudeCultistTesla:
|
|
case kDudeCultistTNT:
|
|
sfxPlay3DSound(actor, 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(actor, damageType, damage);
|
|
return;
|
|
|
|
case kDudeModernCustomBurning:
|
|
modernCustomDudeBurningDeath(actor, nSeq);
|
|
genDudePostDeath(actor, 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.stats.birthCounter--;
|
|
genericDeath(actor, nSeq, 1803, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderRed:
|
|
if (Owner) Owner->dudeExtra.stats.birthCounter--;
|
|
genericDeath(actor, nSeq, 1803, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderBlack:
|
|
if (Owner) Owner->dudeExtra.stats.birthCounter--;
|
|
genericDeath(actor, nSeq, 1803, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
|
|
case kDudeSpiderMother:
|
|
sfxPlay3DSound(actor, 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 ((actor->spr.cstat & CSTAT_SPRITE_YFLIP)) actor->spr.cstat &= ~CSTAT_SPRITE_YFLIP;
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudePodGreen:
|
|
genericDeath(actor, nSeq, 2203, dudeInfo[nType].seqStartID + nSeq);
|
|
break;
|
|
case kDudeTentacleGreen:
|
|
sfxPlay3DSound(actor, damage == 5 ? 2471 : 2472, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
case kDudePodFire:
|
|
sfxPlay3DSound(actor, damage == 5 ? 2451 : 2452, -1, 0);
|
|
seqSpawn(dudeInfo[nType].seqStartID + nSeq, actor, -1);
|
|
break;
|
|
case kDudeTentacleFire:
|
|
sfxPlay3DSound(actor, 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(actor, 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(actor, 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(actor->spr.type);
|
|
for (int i = 0; i < 3; i++)
|
|
if (pDudeInfo->nGibType[i] > -1)
|
|
GibSprite(actor, (GIBTYPE)pDudeInfo->nGibType[i], nullptr, nullptr);
|
|
for (int i = 0; i < 4; i++)
|
|
fxSpawnBlood(actor, damage);
|
|
}
|
|
gKillMgr.AddKill(actor);
|
|
actCheckRespawn(actor);
|
|
actor->spr.type = kThingBloodChunks;
|
|
actPostSprite(actor, kStatThing);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static int actDamageDude(DBloodActor* source, DBloodActor* actor, int damage, DAMAGE_TYPE damageType)
|
|
{
|
|
if (!actor->IsDudeActor())
|
|
{
|
|
Printf(PRINT_HIGH, "Bad Dude Failed: initial=%d type=%d %s\n", (int)actor->spr.inittype, (int)actor->spr.type, (int)(actor->spr.flags & kHitagRespawn) ? "RESPAWN" : "NORMAL");
|
|
return damage >> 4;
|
|
//I_Error("Bad Dude Failed: initial=%d type=%d %s\n", (int)actor->spr.inittype, (int)actor->spr.type, (int)(actor->spr.flags & 16) ? "RESPAWN" : "NORMAL");
|
|
}
|
|
|
|
int nType = actor->spr.type - kDudeBase;
|
|
int nDamageFactor = getDudeInfo(nType + kDudeBase)->damageVal[damageType];
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (actor->spr.type == kDudeModernCustom)
|
|
nDamageFactor = actor->genDudeExtra.dmgControl[damageType];
|
|
#endif
|
|
|
|
if (!nDamageFactor) return 0;
|
|
else if (nDamageFactor != 256) damage = MulScale(damage, nDamageFactor, 8);
|
|
|
|
if (!actor->IsPlayerActor())
|
|
{
|
|
if (actor->xspr.health <= 0) return 0;
|
|
damage = aiDamageSprite(source, actor, damageType, damage);
|
|
if (actor->xspr.health <= 0)
|
|
actKillDude(source, actor, ((damageType == kDamageExplode && damage < 160) ? kDamageFall : damageType), damage);
|
|
}
|
|
else
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[actor->spr.type - kDudePlayer1];
|
|
if (actor->xspr.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)
|
|
{
|
|
assert(actor->spr.type >= kThingBase && actor->spr.type < kThingMax);
|
|
int nType = actor->spr.type - kThingBase;
|
|
int nDamageFactor = thingInfo[nType].dmgControl[damageType];
|
|
|
|
if (!nDamageFactor) return 0;
|
|
else if (nDamageFactor != 256) damage = MulScale(damage, nDamageFactor, 8);
|
|
|
|
actor->xspr.health = ClipLow(actor->xspr.health - damage, 0);
|
|
if (actor->xspr.health <= 0)
|
|
{
|
|
auto Owner = actor->GetOwner();
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingDroppedLifeLeech:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingEnemyLifeLeech:
|
|
#endif
|
|
GibSprite(actor, GIBTYPE_14, nullptr, nullptr);
|
|
actor->xspr.data1 = actor->xspr.data2 = actor->xspr.data3 = actor->xspr.DudeLockout = 0;
|
|
actor->xspr.stateTimer = actor->xspr.data4 = actor->xspr.isTriggered = 0;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (Owner && Owner->spr.type == kDudeModernCustom)
|
|
Owner->SetSpecialOwner(); // indicates if custom dude had life leech.
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
if (!(actor->spr.flags & kHitagRespawn))
|
|
actor->SetOwner(source);
|
|
break;
|
|
}
|
|
|
|
trTriggerSprite(actor, kCmdOff, source);
|
|
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingObjectGib:
|
|
case kThingObjectExplode:
|
|
case kThingBloodBits:
|
|
case kThingBloodChunks:
|
|
case kThingZombieHead:
|
|
if (damageType == 3 && pSourcePlayer && PlayClock > pSourcePlayer->laughCount && Chance(0x4000))
|
|
{
|
|
sfxPlay3DSound(pSourcePlayer->actor, gPlayerGibThingComments[Random(10)], 0, 2);
|
|
pSourcePlayer->laughCount = PlayClock + 3600;
|
|
}
|
|
break;
|
|
case kTrapMachinegun:
|
|
seqSpawn(28, actor, -1);
|
|
break;
|
|
|
|
case kThingFluorescent:
|
|
seqSpawn(12, actor, -1);
|
|
GibSprite(actor, GIBTYPE_6, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingSpiderWeb:
|
|
seqSpawn(15, actor, -1);
|
|
break;
|
|
|
|
case kThingMetalGrate:
|
|
seqSpawn(21, actor, -1);
|
|
GibSprite(actor, GIBTYPE_4, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingFlammableTree:
|
|
switch (actor->xspr.data1)
|
|
{
|
|
case -1:
|
|
GibSprite(actor, GIBTYPE_14, nullptr, nullptr);
|
|
sfxPlay3DSound(actor->spr.pos, 312, actor->sector());
|
|
actPostSprite(actor, kStatFree);
|
|
break;
|
|
|
|
case 0:
|
|
seqSpawn(25, actor, nTreeToGibClient);
|
|
sfxPlay3DSound(actor, 351, -1, 0);
|
|
break;
|
|
|
|
case 1:
|
|
seqSpawn(26, actor, nTreeToGibClient);
|
|
sfxPlay3DSound(actor, 351, -1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int actDamageSprite(DBloodActor* source, DBloodActor* actor, DAMAGE_TYPE damageType, int damage)
|
|
{
|
|
if (actor->spr.flags & 32 || !actor->hasX())
|
|
return 0;
|
|
|
|
if ((actor->xspr.health == 0 && actor->spr.statnum != kStatDude) || actor->xspr.locked)
|
|
return 0;
|
|
|
|
if (source == nullptr) source = actor;
|
|
|
|
PLAYER* pSourcePlayer = nullptr;
|
|
if (source->IsPlayerActor()) pSourcePlayer = &gPlayer[source->spr.type - kDudePlayer1];
|
|
if (!gGameOptions.bFriendlyFire && IsTargetTeammate(pSourcePlayer, actor)) return 0;
|
|
|
|
switch (actor->spr.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** ppWall)
|
|
{
|
|
assert(pHitInfo != nullptr);
|
|
DBloodActor* actor = nullptr;
|
|
walltype* pWall = nullptr;
|
|
switch (a1)
|
|
{
|
|
case 3:
|
|
case 5:
|
|
actor = pHitInfo->actor();
|
|
break;
|
|
case 0:
|
|
case 4:
|
|
pWall = pHitInfo->hitWall;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (pActor) *pActor = actor;
|
|
if (ppWall) *ppWall = pWall;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actImpactMissile(DBloodActor* missileActor, int hitCode)
|
|
{
|
|
auto missileOwner = missileActor->GetOwner();
|
|
|
|
DBloodActor* actorHit = nullptr;
|
|
walltype* pWallHit = nullptr;
|
|
|
|
actHitcodeToData(hitCode, &gHitInfo, &actorHit, &pWallHit);
|
|
|
|
const THINGINFO* pThingInfo = nullptr;
|
|
DUDEINFO* pDudeInfo = nullptr;
|
|
|
|
if (hitCode == 3 && actorHit)
|
|
{
|
|
switch (actorHit->spr.statnum)
|
|
{
|
|
case kStatThing:
|
|
pThingInfo = &thingInfo[actorHit->spr.type - kThingBase];
|
|
break;
|
|
case kStatDude:
|
|
pDudeInfo = getDudeInfo(actorHit->spr.type);
|
|
break;
|
|
}
|
|
}
|
|
switch (missileActor->spr.type)
|
|
{
|
|
case kMissileLifeLeechRegular:
|
|
if (hitCode == 3 && actorHit && (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->xspr.health != 0)
|
|
actHealDude(missileOwner, nDamage >> 2, getDudeInfo(missileOwner->spr.type)->startHealth);
|
|
}
|
|
}
|
|
|
|
if (missileActor->hasX())
|
|
{
|
|
actPostSprite(missileActor, kStatDecoration);
|
|
if (missileActor->spr.angle == DAngle180) sfxPlay3DSound(missileActor, 307, -1, 0);
|
|
missileActor->spr.type = kSpriteDecoration;
|
|
seqSpawn(9, missileActor, -1);
|
|
}
|
|
else
|
|
{
|
|
actPostSprite(missileActor, kStatFree);
|
|
}
|
|
|
|
break;
|
|
case kMissileTeslaAlt:
|
|
teslaHit(missileActor, hitCode);
|
|
switch (hitCode)
|
|
{
|
|
case 0:
|
|
case 4:
|
|
if (pWallHit)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_52, missileActor->sector(), missileActor->spr.pos, 0);
|
|
if (pFX) pFX->set_int_ang((GetWallAngle(pWallHit) + 512) & 2047);
|
|
}
|
|
break;
|
|
}
|
|
GibSprite(missileActor, GIBTYPE_24, NULL, NULL);
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissilePukeGreen:
|
|
seqKill(missileActor);
|
|
if (hitCode == 3 && actorHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
int nDamage = (15 + Random(7)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileArcGargoyle:
|
|
sfxKill3DSound(missileActor, -1, -1);
|
|
sfxPlay3DSound(missileActor->spr.pos, 306, missileActor->sector());
|
|
GibSprite(missileActor, GIBTYPE_6, NULL, NULL);
|
|
|
|
if (hitCode == 3 && actorHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
int nDamage = (25 + Random(20)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
}
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileLifeLeechAltNormal:
|
|
case kMissileLifeLeechAltSmall:
|
|
sfxKill3DSound(missileActor, -1, -1);
|
|
sfxPlay3DSound(missileActor->spr.pos, 306, missileActor->sector());
|
|
|
|
if (hitCode == 3 && actorHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
int nDmgMul = (missileActor->spr.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 && actorHit && (pThingInfo || pDudeInfo))
|
|
{
|
|
if (pThingInfo && actorHit->spr.type == kThingTNTBarrel && actorHit->xspr.burnTime == 0)
|
|
evPostActor(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
int nDamage = (50 + Random(50)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageBullet, nDamage);
|
|
}
|
|
actExplodeSprite(missileActor);
|
|
break;
|
|
|
|
case kMissileFlareAlt:
|
|
sfxKill3DSound(missileActor, -1, -1);
|
|
actExplodeSprite(missileActor);
|
|
break;
|
|
|
|
case kMissileFlareRegular:
|
|
sfxKill3DSound(missileActor, -1, -1);
|
|
if ((hitCode == 3 && actorHit) && (pThingInfo || pDudeInfo))
|
|
{
|
|
if ((pThingInfo && pThingInfo->dmgControl[kDamageBurn] != 0) || (pDudeInfo && pDudeInfo->damageVal[kDamageBurn] != 0))
|
|
{
|
|
if (pThingInfo && actorHit->spr.type == kThingTNTBarrel && actorHit->xspr.burnTime == 0)
|
|
evPostActor(actorHit, 0, kCallbackFXFlameLick);
|
|
|
|
actBurnSprite(missileOwner, actorHit, 480);
|
|
actRadiusDamage(missileOwner, missileActor->spr.pos, missileActor->sector(), 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[actorHit->spr.picnum] == kSurfFlesh)
|
|
{
|
|
missileActor->spr.picnum = 2123;
|
|
missileActor->SetTarget(actorHit);
|
|
missileActor->xspr.TargetPos.Z = (missileActor->spr.pos.Z - actorHit->spr.pos.Z);
|
|
missileActor->xspr.goalAng = VecToAngle(missileActor->spr.pos.XY() - actorHit->spr.pos.XY()) - actorHit->spr.angle;
|
|
missileActor->xspr.state = 1;
|
|
actPostSprite(missileActor, kStatFlare);
|
|
missileActor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
break;
|
|
}
|
|
}
|
|
GibSprite(missileActor, GIBTYPE_17, NULL, NULL);
|
|
actPostSprite(missileActor, kStatFree);
|
|
break;
|
|
|
|
case kMissileFlameSpray:
|
|
case kMissileFlameHound:
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if ((actorHit->spr.statnum == kStatThing || actorHit->spr.statnum == kStatDude) && actorHit->xspr.burnTime == 0)
|
|
evPostActor(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 ((actorHit->spr.statnum == kStatThing || actorHit->spr.statnum == kStatDude) && actorHit->xspr.burnTime == 0)
|
|
evPostActor(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 ((actorHit->spr.statnum == kStatThing || actorHit->spr.statnum == kStatDude) && actorHit->xspr.burnTime == 0)
|
|
evPostActor(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(missileActor, -1, -1);
|
|
sfxPlay3DSound(missileActor->spr.pos, 522, missileActor->sector());
|
|
actPostSprite(missileActor, kStatDebris);
|
|
seqSpawn(20, missileActor, -1);
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if (actorHit->spr.statnum == kStatDude)
|
|
{
|
|
int nDamage = (25 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case kMissileButcherKnife:
|
|
actPostSprite(missileActor, kStatDebris);
|
|
missileActor->spr.cstat &= ~CSTAT_SPRITE_ALIGNMENT_WALL;
|
|
missileActor->spr.type = kSpriteDecoration;
|
|
seqSpawn(20, missileActor, -1);
|
|
if (hitCode == 3 && actorHit && actorHit->hasX())
|
|
{
|
|
if (actorHit->spr.statnum == kStatDude)
|
|
{
|
|
int nDamage = (10 + Random(10)) << 4;
|
|
actDamageSprite(missileOwner, actorHit, kDamageSpirit, nDamage);
|
|
int nType = missileOwner->spr.type - kDudeBase;
|
|
if (missileOwner->xspr.health > 0)
|
|
actHealDude(missileOwner, 10, getDudeInfo(nType + kDudeBase)->startHealth);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case kMissileTeslaRegular:
|
|
sfxKill3DSound(missileActor, -1, -1);
|
|
sfxPlay3DSound(missileActor->spr.pos, 518, missileActor->sector());
|
|
GibSprite(missileActor, (hitCode == 2) ? GIBTYPE_23 : GIBTYPE_22, NULL, NULL);
|
|
evKillActor(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 && actorHit && actorHit->hasX() && actorHit->xspr.state != actorHit->xspr.restState && actorHit->xspr.Impact)
|
|
trTriggerSprite(actorHit, kCmdSpriteImpact, missileActor);
|
|
#endif
|
|
missileActor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actKickObject(DBloodActor* kicker, DBloodActor* kicked)
|
|
{
|
|
int nSpeed = ClipLow(approxDist(kicker->int_vel().X, kicker->int_vel().Y) * 2, 0xaaaaa);
|
|
kicked->set_int_bvel_x(MulScale(nSpeed, Cos(kicker->int_ang() + Random2(85)), 30));
|
|
kicked->set_int_bvel_y(MulScale(nSpeed, Sin(kicker->int_ang() + Random2(85)), 30));
|
|
kicked->set_int_bvel_z(MulScale(nSpeed, -0x2000, 14));
|
|
kicked->spr.flags = 7;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actTouchFloor(DBloodActor* actor, sectortype* pSector)
|
|
{
|
|
XSECTOR* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
|
|
|
|
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(pSector->floorpicnum) == kSurfLava)
|
|
{
|
|
actDamageSprite(actor, actor, kDamageBurn, 16);
|
|
sfxPlay3DSound(actor, 352, 5, 2);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkCeilHit(DBloodActor* actor)
|
|
{
|
|
auto& coll = actor->hit.ceilhit;
|
|
switch (coll.type)
|
|
{
|
|
case kHitWall:
|
|
break;
|
|
case kHitSprite:
|
|
auto actor2 = coll.actor();
|
|
if (actor2 && actor2->hasX())
|
|
{
|
|
if ((actor2->spr.statnum == kStatThing || actor2->spr.statnum == kStatDude) && (actor->vel.X != 0 || actor->vel.Y != 0 || actor->vel.Z != 0))
|
|
{
|
|
auto adelta = actor2->spr.pos - actor->spr.pos;
|
|
if (actor2->spr.statnum == kStatThing)
|
|
{
|
|
int nType = actor2->spr.type - kThingBase;
|
|
const THINGINFO* pThingInfo = &thingInfo[nType];
|
|
if (pThingInfo->flags & 1) actor2->spr.flags |= 1;
|
|
if (pThingInfo->flags & 2) actor2->spr.flags |= 4;
|
|
// Inlined ?
|
|
actor2->add_int_bvel_x(int(adelta.X * 16));
|
|
actor2->add_int_bvel_y(int(adelta.Y * 16));
|
|
}
|
|
else
|
|
{
|
|
actor2->spr.flags |= 5;
|
|
actor2->add_int_bvel_x(int(adelta.X * 16));
|
|
actor2->add_int_bvel_y(int(adelta.Y * 16));
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add size shroom abilities
|
|
if ((actor->IsPlayerActor() && isShrinked(actor)) || (actor2->IsPlayerActor() && isGrown(actor2))) {
|
|
|
|
int mass1 = getDudeInfo(actor2->spr.type)->mass;
|
|
int mass2 = getDudeInfo(actor->spr.type)->mass;
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
mass2 = getSpriteMassBySize(actor);
|
|
break;
|
|
}
|
|
if (mass1 > mass2)
|
|
{
|
|
int dmg = abs((mass1 - mass2) * (actor2->spr.clipdist - actor->spr.clipdist));
|
|
if (actor2->IsDudeActor())
|
|
{
|
|
if (dmg > 0) actDamageSprite(actor2, actor, (Chance(0x2000)) ? kDamageFall : (Chance(0x4000)) ? kDamageExplode : kDamageBullet, dmg);
|
|
if (Chance(0x0200)) actKickObject(actor2, actor);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (!actor->IsPlayerActor() || gPlayer[actor->spr.type - kDudePlayer1].godMode == 0)
|
|
{
|
|
switch (actor2->spr.type)
|
|
{
|
|
case kDudeTchernobog:
|
|
actDamageSprite(actor2, actor, kDamageExplode, actor->xspr.health << 2);
|
|
break;
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
int dmg = 0;
|
|
if (!actor->IsDudeActor() || (dmg = ClipLow((getSpriteMassBySize(actor2) - getSpriteMassBySize(actor)) >> 1, 0)) == 0)
|
|
break;
|
|
|
|
if (!actor->IsPlayerActor())
|
|
{
|
|
actDamageSprite(actor2, actor, kDamageFall, dmg);
|
|
if (actor->hasX() && !actor->isActive()) aiActivateDude(actor);
|
|
}
|
|
else if (powerupCheck(&gPlayer[actor->spr.type - kDudePlayer1], kPwUpJumpBoots) > 0) actDamageSprite(actor2, actor, kDamageExplode, dmg);
|
|
else actDamageSprite(actor2, actor, kDamageFall, dmg);
|
|
break;
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (actor2->spr.type == kTrapSawCircular && actor2->hasX())
|
|
{
|
|
if (!actor2->xspr.state) actDamageSprite(actor, actor, kDamageBullet, 1);
|
|
else {
|
|
actor2->xspr.data1 = 1;
|
|
actor2->xspr.data2 = ClipHigh(actor2->xspr.data2 + 8, 600);
|
|
actDamageSprite(actor, actor, kDamageBullet, 16);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkHit(DBloodActor* actor)
|
|
{
|
|
const auto& coll = actor->hit.hit;
|
|
switch (coll.type)
|
|
{
|
|
case kHitWall:
|
|
break;
|
|
case kHitSprite:
|
|
if (coll.actor()->hasX())
|
|
{
|
|
auto actor2 = coll.actor();
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add size shroom abilities
|
|
if ((actor2->IsPlayerActor() && isShrinked(actor2)) || (actor->IsPlayerActor() && isGrown(actor)))
|
|
{
|
|
if (actor->vel.X != 0 && actor2->IsDudeActor())
|
|
{
|
|
int mass1 = getDudeInfo(actor->spr.type)->mass;
|
|
int mass2 = getDudeInfo(actor2->spr.type)->mass;
|
|
switch (actor2->spr.type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
mass2 = getSpriteMassBySize(actor2);
|
|
break;
|
|
}
|
|
if (mass1 > mass2)
|
|
{
|
|
actKickObject(actor, actor2);
|
|
sfxPlay3DSound(actor, 357, -1, 1);
|
|
int dmg = (mass1 - mass2) + abs(FixedToInt(actor->int_vel().X));
|
|
if (dmg > 0) actDamageSprite(actor, actor2, (Chance(0x2000)) ? kDamageFall : kDamageBullet, dmg);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (actor2->spr.type)
|
|
{
|
|
case kThingKickablePail:
|
|
actKickObject(actor, actor2);
|
|
break;
|
|
|
|
case kThingZombieHead:
|
|
sfxPlay3DSound(actor->spr.pos, 357, actor->sector());
|
|
actKickObject(actor, actor2);
|
|
actDamageSprite(nullptr, actor2, kDamageFall, 80);
|
|
break;
|
|
|
|
case kDudeBurningInnocent:
|
|
case kDudeBurningCultist:
|
|
case kDudeBurningZombieAxe:
|
|
case kDudeBurningZombieButcher:
|
|
// This does not make sense
|
|
actor->xspr.burnTime = ClipLow(actor->xspr.burnTime - 4, 0);
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void checkFloorHit(DBloodActor* actor)
|
|
{
|
|
const auto& coll = actor->hit.florhit;
|
|
switch (coll.type)
|
|
{
|
|
case kHitWall:
|
|
break;
|
|
case kHitSector:
|
|
actTouchFloor(actor, coll.hitSector);
|
|
break;
|
|
case kHitSprite:
|
|
if (coll.actor()->hasX())
|
|
{
|
|
auto actor2 = coll.actor();
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add size shroom abilities
|
|
if ((actor2->IsPlayerActor() && isShrinked(actor2)) || (actor->IsPlayerActor() && isGrown(actor)))
|
|
{
|
|
|
|
int mass1 = getDudeInfo(actor->spr.type)->mass;
|
|
int mass2 = getDudeInfo(actor2->spr.type)->mass;
|
|
switch (actor2->spr.type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
mass2 = getSpriteMassBySize(actor2);
|
|
break;
|
|
}
|
|
|
|
if (mass1 > mass2 && actor2->IsDudeActor())
|
|
{
|
|
if ((actor2->IsPlayerActor() && Chance(0x500)) || !actor2->IsPlayerActor())
|
|
actKickObject(actor, actor2);
|
|
|
|
int dmg = (mass1 - mass2) + actor->spr.clipdist;
|
|
if (dmg > 0) actDamageSprite(actor, actor2, (Chance(0x2000)) ? kDamageFall : kDamageBullet, dmg);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PLAYER* pPlayer = nullptr;
|
|
if (actor->IsPlayerActor()) pPlayer = &gPlayer[actor->spr.type - kDudePlayer1];
|
|
|
|
switch (actor2->spr.type)
|
|
{
|
|
case kThingKickablePail:
|
|
if (pPlayer)
|
|
{
|
|
if (pPlayer->kickPower > PlayClock) return;
|
|
pPlayer->kickPower = PlayClock + 60;
|
|
}
|
|
actKickObject(actor, actor2);
|
|
sfxPlay3DSound(actor->spr.pos, 357, actor->sector());
|
|
sfxPlay3DSound(actor, 374, 0, 0);
|
|
break;
|
|
case kThingZombieHead:
|
|
if (pPlayer)
|
|
{
|
|
if (pPlayer->kickPower > PlayClock) return;
|
|
pPlayer->kickPower = PlayClock + 60;
|
|
}
|
|
actKickObject(actor, actor2);
|
|
sfxPlay3DSound(actor->spr.pos, 357, actor->sector());
|
|
actDamageSprite(nullptr, actor2, kDamageFall, 80);
|
|
break;
|
|
case kTrapSawCircular:
|
|
if (!actor2->xspr.state) actDamageSprite(actor, actor, kDamageBullet, 1);
|
|
else
|
|
{
|
|
actor2->xspr.data1 = 1;
|
|
actor2->xspr.data2 = ClipHigh(actor2->xspr.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(actor))
|
|
#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 (auto* coll : { &actor->hit.hit, &actor->hit.florhit, &actor->hit.ceilhit })
|
|
{
|
|
if (coll->type == kHitSprite)
|
|
{
|
|
actor2 = coll->actor();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (actor2 && actor2->hasX())
|
|
{
|
|
triggerTouchSprite(actor, actor2);
|
|
}
|
|
|
|
// Touch walls
|
|
const auto& coll = actor->hit.hit;
|
|
walltype* pHWall = nullptr;
|
|
if (coll.type == kHitWall)
|
|
{
|
|
pHWall = coll.hitWall;
|
|
if (pHWall && pHWall->hasX())
|
|
{
|
|
triggerTouchWall(actor, pHWall);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actAirDrag(DBloodActor* actor, int a2)
|
|
{
|
|
int wind_x = 0;
|
|
int wind_y = 0;
|
|
assert(actor->sector());
|
|
sectortype* pSector = actor->sector();
|
|
if (pSector->hasX())
|
|
{
|
|
XSECTOR* pXSector = &pSector->xs();
|
|
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->add_int_bvel_x(MulScale(wind_x - actor->int_vel().X, a2, 16));
|
|
actor->add_int_bvel_y(MulScale(wind_y - actor->int_vel().Y, a2, 16));
|
|
actor->add_int_bvel_z(-MulScale(actor->int_vel().Z, a2, 16));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static Collision MoveThing(DBloodActor* actor)
|
|
{
|
|
assert(actor->hasX());
|
|
assert(actor->spr.type >= kThingBase && actor->spr.type < kThingMax);
|
|
const THINGINFO* pThingInfo = &thingInfo[actor->spr.type - kThingBase];
|
|
auto pSector = actor->sector();
|
|
assert(pSector);
|
|
int top, bottom;
|
|
Collision lhit;
|
|
|
|
lhit.setNone();
|
|
GetActorExtents(actor, &top, &bottom);
|
|
const int bakCompat = enginecompatibility_mode;
|
|
if (actor->vel.X != 0 || actor->vel.Y != 0)
|
|
{
|
|
auto bakCstat = actor->spr.cstat;
|
|
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
if ((actor->GetOwner()) && !cl_bloodvanillaexplosions && !VanillaMode())
|
|
enginecompatibility_mode = ENGINECOMPATIBILITY_NONE; // improved clipmove accuracy
|
|
ClipMove(actor->spr.pos, &pSector, actor->int_vel().X >> 12, actor->int_vel().Y >> 12, actor->spr.clipdist << 2, (actor->int_pos().Z - top) / 4, (bottom - actor->int_pos().Z) / 4, CLIPMASK0, lhit);
|
|
actor->hit.hit = lhit;
|
|
enginecompatibility_mode = bakCompat; // restore
|
|
actor->spr.cstat = bakCstat;
|
|
assert(pSector);
|
|
if (actor->sector() != pSector)
|
|
{
|
|
assert(pSector);
|
|
ChangeActorSect(actor, pSector);
|
|
}
|
|
|
|
auto& coll = actor->hit.hit;
|
|
if (coll.type == kHitWall)
|
|
{
|
|
actWallBounceVector(actor, coll.hitWall, FixedToFloat(pThingInfo->elastic));
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingZombieHead:
|
|
sfxPlay3DSound(actor, 607, 0, 0);
|
|
actDamageSprite(nullptr, actor, kDamageFall, 80);
|
|
break;
|
|
|
|
case kThingKickablePail:
|
|
sfxPlay3DSound(actor, 374, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(pSector);
|
|
auto pSector2 = pSector;
|
|
updatesectorz(actor->spr.pos, &pSector2);
|
|
if (pSector2) pSector = pSector2;
|
|
}
|
|
|
|
actor->spr.pos.Z += actor->vel.Z;
|
|
|
|
int ceilZ, floorZ;
|
|
Collision ceilColl, floorColl;
|
|
GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, actor->spr.clipdist << 2, CLIPMASK0);
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
if ((actor->spr.flags & 2) && bottom < floorZ)
|
|
{
|
|
actor->spr.pos.Z += 1.777;
|
|
actor->add_int_bvel_z(58254);
|
|
if (actor->spr.type == kThingZombieHead)
|
|
{
|
|
auto* fxActor = gFX.fxSpawnActor(FX_27, actor->sector(), actor->spr.pos, 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->set_int_bvel_x(actor->int_vel().X + v2c);
|
|
fxActor->set_int_bvel_y(actor->int_vel().Y + v28);
|
|
fxActor->set_int_bvel_z(actor->int_vel().Z + v24);
|
|
}
|
|
}
|
|
}
|
|
if (CheckLink(actor)) GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, actor->spr.clipdist << 2, CLIPMASK0);
|
|
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (bottom >= floorZ)
|
|
{
|
|
actTouchFloor(actor, actor->sector());
|
|
actor->hit.florhit = floorColl;
|
|
actor->add_int_z(floorZ - bottom);
|
|
|
|
double veldiff = actor->vel.Z - actor->sector()->velFloor;
|
|
if (veldiff > 0)
|
|
{
|
|
actor->spr.flags |= 4;
|
|
|
|
auto vec4 = actFloorBounceVector(actor, veldiff, actor->sector(), FixedToFloat(pThingInfo->elastic));
|
|
actor->vel.XY() = vec4.XY();
|
|
int vax = FloatToFixed(vec4.W);
|
|
|
|
int nDamage = MulScale(vax, vax, 30) - pThingInfo->dmgResist;
|
|
if (nDamage > 0) actDamageSprite(actor, actor, kDamageFall, nDamage);
|
|
|
|
actor->set_int_bvel_z(FloatToFixed(vec4.Z));
|
|
if (actor->sector()->velFloor == 0 && abs(actor->vel.Z) < 1)
|
|
{
|
|
actor->vel.Z = 0;
|
|
actor->spr.flags &= ~4;
|
|
}
|
|
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingNapalmBall:
|
|
if (actor->vel.Z == 0 || Chance(0xA000)) actNapalmMove(actor);
|
|
break;
|
|
|
|
case kThingZombieHead:
|
|
if (abs(actor->vel.Z) > 8)
|
|
{
|
|
sfxPlay3DSound(actor, 607, 0, 0);
|
|
actDamageSprite(nullptr, actor, kDamageFall, 80);
|
|
}
|
|
break;
|
|
|
|
case kThingKickablePail:
|
|
if (abs(actor->vel.Z) > 8)
|
|
sfxPlay3DSound(actor, 374, 0, 0);
|
|
break;
|
|
}
|
|
|
|
lhit.setSector(pSector);
|
|
}
|
|
else if (actor->vel.Z == 0)
|
|
|
|
actor->spr.flags &= ~4;
|
|
}
|
|
else
|
|
{
|
|
actor->hit.florhit.setNone();
|
|
|
|
if (actor->spr.flags & 2)
|
|
actor->spr.flags |= 4;
|
|
}
|
|
|
|
if (top <= ceilZ)
|
|
{
|
|
actor->hit.ceilhit = ceilColl;
|
|
actor->add_int_z(ClipLow(ceilZ - top, 0));
|
|
if (actor->vel.Z < 0)
|
|
{
|
|
actor->vel.XY() *= 0.75;
|
|
actor->vel.Z *= -0.25;
|
|
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingZombieHead:
|
|
if (abs(actor->vel.Z) > 8)
|
|
{
|
|
sfxPlay3DSound(actor, 607, 0, 0);
|
|
actDamageSprite(nullptr, actor, kDamageFall, 80);
|
|
}
|
|
break;
|
|
|
|
case kThingKickablePail:
|
|
if (abs(actor->vel.Z) > 8)
|
|
sfxPlay3DSound(actor, 374, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else actor->hit.ceilhit.setNone();
|
|
|
|
if (bottom >= floorZ)
|
|
{
|
|
double nVel = actor->vel.XY().Length();
|
|
double nVelClipped = min(nVel, (16. / 15.));
|
|
Collision& coll = floorColl;
|
|
|
|
if (coll.type == kHitSprite)
|
|
{
|
|
auto hitActor = coll.actor();
|
|
if ((hitActor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_FACING)
|
|
{
|
|
actor->vel.XY() += (actor->spr.pos.XY() - hitActor->spr.pos.XY()) / 4096.;
|
|
lhit = actor->hit.hit;
|
|
}
|
|
}
|
|
if (nVel > 0)
|
|
{
|
|
actor->vel.XY() -= actor->vel.XY() * nVelClipped / nVel;
|
|
}
|
|
}
|
|
if (actor->vel.X != 0 || actor->vel.Y != 0)
|
|
actor->spr.angle = VecToAngle(actor->vel);
|
|
return lhit;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void MoveDude(DBloodActor* actor)
|
|
{
|
|
PLAYER* pPlayer = nullptr;
|
|
if (actor->IsPlayerActor()) pPlayer = &gPlayer[actor->spr.type - kDudePlayer1];
|
|
if (!(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax))
|
|
{
|
|
Printf(PRINT_HIGH, "%d: actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax", actor->spr.type);
|
|
return;
|
|
}
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
int bz = (bottom - actor->int_pos().Z) / 4;
|
|
int tz = (actor->int_pos().Z - top) / 4;
|
|
int wd = actor->spr.clipdist << 2;
|
|
auto pSector = actor->sector();
|
|
int nAiStateType = (actor->xspr.aiState) ? actor->xspr.aiState->stateType : -1;
|
|
|
|
assert(pSector);
|
|
|
|
if (actor->vel.X != 0 || actor->int_vel().Y)
|
|
{
|
|
if (pPlayer && gNoClip)
|
|
{
|
|
actor->add_int_pos({ actor->int_vel().X >> 12, actor->int_vel().Y >> 12, 0 });
|
|
updatesector(actor->spr.pos, &pSector);
|
|
if (!pSector) pSector = actor->sector();
|
|
}
|
|
else
|
|
{
|
|
auto bakCstat = actor->spr.cstat;
|
|
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
ClipMove(actor->spr.pos, &pSector, actor->int_vel().X >> 12, actor->int_vel().Y >> 12, wd, tz, bz, CLIPMASK0, actor->hit.hit);
|
|
if (pSector == nullptr)
|
|
{
|
|
pSector = actor->sector();
|
|
if (actor->spr.statnum == kStatDude || actor->spr.statnum == kStatThing)
|
|
actDamageSprite(actor, actor, kDamageFall, 1000 << 4);
|
|
}
|
|
|
|
if (pSector->type >= kSectorPath && pSector->type <= kSectorRotate)
|
|
{
|
|
auto pSector2 = pSector;
|
|
if (pushmove(actor, &pSector2, wd, tz, bz, CLIPMASK0) == -1)
|
|
actDamageSprite(actor, actor, kDamageFall, 1000 << 4);
|
|
if (pSector2 != nullptr)
|
|
pSector = pSector2;
|
|
}
|
|
assert(pSector);
|
|
actor->spr.cstat = bakCstat;
|
|
}
|
|
auto& coll = actor->hit.hit;
|
|
switch (coll.type)
|
|
{
|
|
case kHitSprite:
|
|
{
|
|
auto hitActor = coll.actor();
|
|
auto Owner = hitActor->GetOwner();
|
|
|
|
if (hitActor->spr.statnum == kStatProjectile && !(hitActor->spr.flags & 32) && actor != Owner)
|
|
{
|
|
auto hitInfo = gHitInfo;
|
|
gHitInfo.hitActor = actor;
|
|
actImpactMissile(hitActor, 3);
|
|
gHitInfo = hitInfo;
|
|
}
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (!gModernMap && hitActor->hasX() && hitActor->xspr.Touch && !hitActor->xspr.state && !hitActor->xspr.isTriggered)
|
|
trTriggerSprite(coll.actor(), kCmdSpriteTouch, actor);
|
|
#else
|
|
if (hitActor->hasX() && hitActor->xspr.Touch && !hitActor->xspr.state && !hitActor->xspr.isTriggered)
|
|
trTriggerSprite(coll.actor, kCmdSpriteTouch);
|
|
#endif
|
|
|
|
if (pDudeInfo->lockOut && hitActor->hasX() && hitActor->xspr.Push && !hitActor->xspr.key && !hitActor->xspr.DudeLockout && !hitActor->xspr.state && !hitActor->xspr.busy && !pPlayer)
|
|
trTriggerSprite(coll.actor(), kCmdSpritePush, actor);
|
|
|
|
break;
|
|
}
|
|
case kHitWall:
|
|
{
|
|
walltype* pHitWall = coll.hitWall;
|
|
XWALL* pHitXWall = nullptr;
|
|
if (pHitWall->hasX()) pHitXWall = &pHitWall->xw();
|
|
|
|
if (pDudeInfo->lockOut && pHitXWall && pHitXWall->triggerPush && !pHitXWall->key && !pHitXWall->dudeLockout && !pHitXWall->state && !pHitXWall->busy && !pPlayer)
|
|
trTriggerWall(pHitWall, kCmdWallPush, actor);
|
|
|
|
if (pHitWall->twoSided())
|
|
{
|
|
sectortype* pHitSector = pHitWall->nextSector();
|
|
XSECTOR* pHitXSector = pHitSector->hasX() ? &pHitSector->xs() : nullptr;
|
|
|
|
if (pDudeInfo->lockOut && pHitXSector && pHitXSector->Wallpush && !pHitXSector->Key && !pHitXSector->dudeLockout && !pHitXSector->state && !pHitXSector->busy && !pPlayer)
|
|
trTriggerSector(pHitSector, kCmdSectorPush, actor);
|
|
|
|
if (top < pHitSector->int_ceilingz() || bottom > pHitSector->int_floorz())
|
|
{
|
|
// ???
|
|
}
|
|
}
|
|
actWallBounceVector(actor, pHitWall, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(pSector);
|
|
auto pSector2 = pSector;
|
|
updatesectorz(actor->spr.pos, &pSector2);
|
|
if (pSector2) pSector = pSector2;
|
|
}
|
|
|
|
XSECTOR* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
|
|
|
|
if (actor->sector() != pSector)
|
|
{
|
|
assert(actor->sector());
|
|
auto pOldSector = actor->sector();
|
|
XSECTOR* pXOldSector = pOldSector->hasX() ? &pOldSector->xs() : nullptr;
|
|
|
|
if (pXOldSector && pXOldSector->Exit && (pPlayer || !pXOldSector->dudeLockout))
|
|
trTriggerSector(pOldSector, kCmdSectorExit, actor);
|
|
ChangeActorSect(actor, pSector);
|
|
|
|
if (pXSector && pXSector->Enter && (pPlayer || !pXSector->dudeLockout))
|
|
{
|
|
if (pSector->type == kSectorTeleport)
|
|
pXSector->actordata = actor;
|
|
trTriggerSector(pSector, kCmdSectorEnter, actor);
|
|
}
|
|
|
|
pSector = actor->sector();
|
|
}
|
|
int bUnderwater = 0;
|
|
int bDepth = 0;
|
|
if (pXSector)
|
|
{
|
|
if (pXSector->Underwater) bUnderwater = 1;
|
|
if (pXSector->Depth) bDepth = 1;
|
|
}
|
|
DCoreActor* pUpperLink = pSector->upperLink;
|
|
DCoreActor* pLowerLink = pSector->lowerLink;
|
|
if (pUpperLink && (pUpperLink->spr.type == kMarkerUpWater || pUpperLink->spr.type == kMarkerUpGoo)) bDepth = 1;
|
|
if (pLowerLink && (pLowerLink->spr.type == kMarkerLowWater || pLowerLink->spr.type == kMarkerLowGoo)) bDepth = 1;
|
|
if (pPlayer) wd += 16;
|
|
if (actor->int_vel().Z) actor->add_int_z(actor->int_vel().Z >> 8);
|
|
|
|
int ceilZ, floorZ;
|
|
Collision ceilColl, floorColl;
|
|
GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, wd, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
if (actor->spr.flags & 2)
|
|
{
|
|
int vc = 58254;
|
|
if (bDepth)
|
|
{
|
|
if (bUnderwater)
|
|
{
|
|
int cz = getceilzofslopeptr(pSector, actor->spr.pos);
|
|
if (cz > top)
|
|
vc += ((bottom - cz) * -80099) / (bottom - top);
|
|
else
|
|
vc = 0;
|
|
}
|
|
else
|
|
{
|
|
int fz = getflorzofslopeptr(pSector, actor->spr.pos);
|
|
if (fz < bottom)
|
|
vc += ((bottom - fz) * -80099) / (bottom - top);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bUnderwater)
|
|
vc = 0;
|
|
else if (bottom >= floorZ)
|
|
vc = 0;
|
|
}
|
|
if (vc)
|
|
{
|
|
actor->add_int_z(((vc * 4) / 2) >> 8);
|
|
actor->add_int_bvel_z(vc);
|
|
}
|
|
}
|
|
if (pPlayer && actor->vel.Z > 0x155555 && !pPlayer->fallScream && actor->xspr.height > 0)
|
|
{
|
|
const bool playerAlive = (actor->xspr.health > 0) || VanillaMode(); // only trigger falling scream if player is alive or vanilla mode
|
|
if (playerAlive)
|
|
{
|
|
pPlayer->fallScream = 1;
|
|
sfxPlay3DSound(actor, 719, 0, 0);
|
|
}
|
|
}
|
|
vec3_t const oldpos = actor->int_pos();
|
|
int nLink = CheckLink(actor);
|
|
if (nLink)
|
|
{
|
|
GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, wd, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
|
|
if (pPlayer)
|
|
playerCorrectInertia(pPlayer, &oldpos);
|
|
switch (nLink)
|
|
{
|
|
case kMarkerLowStack:
|
|
if (pPlayer == gView)
|
|
gotpic.Set(actor->sector()->floorpicnum);
|
|
break;
|
|
case kMarkerUpStack:
|
|
if (pPlayer == gView)
|
|
gotpic.Set(actor->sector()->ceilingpicnum);
|
|
break;
|
|
case kMarkerLowWater:
|
|
case kMarkerLowGoo:
|
|
actor->xspr.medium = kMediumNormal;
|
|
if (pPlayer)
|
|
{
|
|
pPlayer->posture = 0;
|
|
pPlayer->bubbleTime = 0;
|
|
if (!pPlayer->cantJump && (pPlayer->input.actions & SB_JUMP))
|
|
{
|
|
actor->vel.Z = FixedToFloat(-0x6aaaa);
|
|
pPlayer->cantJump = 1;
|
|
}
|
|
sfxPlay3DSound(actor, 721, -1, 0);
|
|
}
|
|
else
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
aiNewState(actor, &cultistGoto);
|
|
break;
|
|
case kDudeGillBeast:
|
|
aiNewState(actor, &gillBeastGoto);
|
|
actor->spr.flags |= 6;
|
|
break;
|
|
case kDudeBoneEel:
|
|
actKillDude(actor, actor, kDamageFall, 1000 << 4);
|
|
break;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (actor->IsDudeActor() && actor->xspr.health > 0 && aiInPatrolState(nAiStateType))
|
|
aiPatrolState(actor, 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;
|
|
}
|
|
|
|
actor->xspr.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 = barrier_cast<DBloodActor*>(pSector->upperLink);
|
|
if (pUpper && pUpper->hasX()) pPlayer->nWaterPal = pUpper->xspr.data2;
|
|
}
|
|
#endif
|
|
|
|
pPlayer->posture = 1;
|
|
actor->xspr.burnTime = 0;
|
|
pPlayer->bubbleTime = abs(actor->int_vel().Z) >> 12;
|
|
evPostActor(actor, 0, kCallbackPlayerBubble);
|
|
sfxPlay3DSound(actor, 720, -1, 0);
|
|
}
|
|
else
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeCultistTommy:
|
|
case kDudeCultistShotgun:
|
|
actor->xspr.burnTime = 0;
|
|
evPostActor(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(actor, 720, -1, 0);
|
|
aiNewState(actor, &cultistSwimGoto);
|
|
break;
|
|
case kDudeBurningCultist:
|
|
{
|
|
const bool fixRandomCultist = !cl_bloodvanillaenemies && (actor->spr.inittype >= kDudeBase) && (actor->spr.inittype < kDudeMax) && (actor->spr.inittype != actor->spr.type) && !VanillaMode(); // fix burning cultists randomly switching types underwater
|
|
if (Chance(chance))
|
|
actor->spr.type = kDudeCultistTommy;
|
|
else
|
|
actor->spr.type = kDudeCultistShotgun;
|
|
if (fixRandomCultist) // fix burning cultists randomly switching types underwater
|
|
actor->spr.type = actor->spr.inittype; // restore back to spawned cultist type
|
|
actor->xspr.burnTime = 0;
|
|
evPostActor(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(actor, 720, -1, 0);
|
|
aiNewState(actor, &cultistSwimGoto);
|
|
break;
|
|
}
|
|
case kDudeZombieAxeNormal:
|
|
actor->xspr.burnTime = 0;
|
|
evPostActor(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(actor, 720, -1, 0);
|
|
aiNewState(actor, &zombieAGoto);
|
|
break;
|
|
case kDudeZombieButcher:
|
|
actor->xspr.burnTime = 0;
|
|
evPostActor(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(actor, 720, -1, 0);
|
|
aiNewState(actor, &zombieFGoto);
|
|
break;
|
|
case kDudeGillBeast:
|
|
actor->xspr.burnTime = 0;
|
|
evPostActor(actor, 0, kCallbackEnemeyBubble);
|
|
sfxPlay3DSound(actor, 720, -1, 0);
|
|
aiNewState(actor, &gillBeastSwimGoto);
|
|
|
|
actor->spr.flags &= ~6;
|
|
break;
|
|
case kDudeGargoyleFlesh:
|
|
case kDudeHellHound:
|
|
case kDudeSpiderBrown:
|
|
case kDudeSpiderRed:
|
|
case kDudeSpiderBlack:
|
|
case kDudeBat:
|
|
case kDudeRat:
|
|
case kDudeBurningInnocent:
|
|
actKillDude(actor, actor, kDamageFall, 1000 << 4);
|
|
break;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (gModernMap) {
|
|
|
|
if (actor->spr.type == kDudeModernCustom) {
|
|
|
|
evPostActor(actor, 0, kCallbackEnemeyBubble);
|
|
if (!canSwim(actor)) actKillDude(actor, actor, kDamageFall, 1000 << 4);
|
|
break;
|
|
}
|
|
|
|
// continue patrol when fall into water
|
|
if (actor->IsDudeActor() && actor->xspr.health > 0 && aiInPatrolState(nAiStateType))
|
|
aiPatrolState(actor, kAiStatePatrolMoveW);
|
|
|
|
}
|
|
#endif
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (pPlayer && bottom >= floorZ)
|
|
{
|
|
int floorZ2 = floorZ;
|
|
auto floorColl2 = floorColl;
|
|
GetZRange(actor, &ceilZ, &ceilColl, &floorZ, &floorColl, actor->spr.clipdist << 2, CLIPMASK0, PARALLAXCLIP_CEILING | PARALLAXCLIP_FLOOR);
|
|
if (bottom <= floorZ && actor->int_pos().Z - floorZ2 < bz)
|
|
{
|
|
floorZ = floorZ2;
|
|
floorColl = floorColl2;
|
|
}
|
|
}
|
|
if (floorZ <= bottom)
|
|
{
|
|
actor->hit.florhit = floorColl;
|
|
actor->add_int_z(floorZ - bottom);
|
|
double veldiff = actor->vel.Z - actor->sector()->velFloor;
|
|
if (veldiff > 0)
|
|
{
|
|
auto vec4 = actFloorBounceVector(actor, veldiff, actor->sector(), 0);
|
|
actor->vel.XY() = vec4.XY();
|
|
int vax = FloatToFixed(vec4.W);
|
|
|
|
int nDamage = MulScale(vax, vax, 30);
|
|
if (pPlayer)
|
|
{
|
|
pPlayer->fallScream = 0;
|
|
|
|
if (nDamage > (15 << 4) && (actor->spr.flags & 4))
|
|
playerLandingSound(pPlayer);
|
|
if (nDamage > (30 << 4))
|
|
sfxPlay3DSound(actor, 701, 0, 0);
|
|
}
|
|
nDamage -= 100 << 4;
|
|
if (nDamage > 0)
|
|
actDamageSprite(actor, actor, kDamageFall, nDamage);
|
|
actor->set_int_bvel_z(FloatToFixed(vec4.Z));
|
|
if (abs(actor->vel.Z) < 1)
|
|
{
|
|
actor->vel.Z = actor->sector()->velFloor;
|
|
actor->spr.flags &= ~4;
|
|
}
|
|
else
|
|
actor->spr.flags |= 4;
|
|
|
|
double ffloorZ = floorZ * zinttoworld;
|
|
switch (tileGetSurfType(floorColl))
|
|
{
|
|
case kSurfWater:
|
|
gFX.fxSpawnActor(FX_9, actor->sector(), DVector3(actor->spr.pos, ffloorZ), 0);
|
|
break;
|
|
case kSurfLava:
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_10, actor->sector(), DVector3(actor->spr.pos, ffloorZ), 0);
|
|
if (pFX)
|
|
{
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
auto pFX2 = gFX.fxSpawnActor(FX_14, pFX->sector(), pFX->spr.pos, 0);
|
|
if (pFX2)
|
|
{
|
|
pFX2->vel.X = Random2F(0x6aaaa);
|
|
pFX2->vel.Y = Random2F(0x6aaaa);
|
|
pFX2->vel.Z = -Random2F(0xd5555);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (actor->vel.Z == 0)
|
|
|
|
actor->spr.flags &= ~4;
|
|
}
|
|
else
|
|
{
|
|
actor->hit.florhit.setNone();
|
|
|
|
if (actor->spr.flags & 2)
|
|
actor->spr.flags |= 4;
|
|
}
|
|
if (top <= ceilZ)
|
|
{
|
|
actor->hit.ceilhit = ceilColl;
|
|
actor->add_int_z(ClipLow(ceilZ - top, 0));
|
|
|
|
if (actor->int_vel().Z <= 0 && (actor->spr.flags & 4))
|
|
actor->set_int_bvel_z(MulScale(-actor->int_vel().Z, 0x2000, 16));
|
|
}
|
|
else
|
|
actor->hit.ceilhit.setNone();
|
|
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
actor->xspr.height = ClipLow(floorZ - bottom, 0) >> 8;
|
|
if (actor->vel.X != 0 || actor->int_vel().Y)
|
|
{
|
|
if (floorColl.type == kHitSprite)
|
|
{
|
|
auto hitAct = floorColl.actor();
|
|
if ((hitAct->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_FACING)
|
|
{
|
|
actor->add_int_bvel_x(MulScale(4, actor->int_pos().X - hitAct->int_pos().X, 2));
|
|
actor->add_int_bvel_y(MulScale(4, actor->int_pos().Y - hitAct->int_pos().Y, 2));
|
|
return;
|
|
}
|
|
}
|
|
if (IsUnderwaterSector(actor->sector()))
|
|
return;
|
|
if (actor->xspr.height >= 0x100)
|
|
return;
|
|
int nDrag = gDudeDrag;
|
|
if (actor->xspr.height > 0)
|
|
nDrag -= Scale(gDudeDrag, actor->xspr.height, 0x100);
|
|
actor->add_int_bvel_x(-mulscale16r(actor->int_vel().X, nDrag));
|
|
actor->add_int_bvel_y(-mulscale16r(actor->int_vel().Y, nDrag));
|
|
|
|
if (approxDist(actor->int_vel().X, actor->int_vel().Y) < 0x1000)
|
|
actor->ZeroVelocityXY();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int MoveMissile(DBloodActor* actor)
|
|
{
|
|
auto Owner = actor->GetOwner();
|
|
int cliptype = -1;
|
|
ESpriteFlags bakCstat = 0;
|
|
if (Owner)
|
|
{
|
|
bakCstat = Owner->spr.cstat;
|
|
if (Owner->IsDudeActor())
|
|
{
|
|
Owner->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
}
|
|
}
|
|
gHitInfo.clearObj();
|
|
if (actor->spr.type == kMissileFlameSpray) actAirDrag(actor, 0x1000);
|
|
|
|
if (actor->GetTarget() != nullptr && (actor->vel.X != 0 || actor->vel.Y != 0 || actor->int_vel().Z))
|
|
{
|
|
auto target = actor->GetTarget();
|
|
|
|
if (target->spr.statnum == kStatDude && target->hasX() && target->xspr.health > 0)
|
|
{
|
|
int nTargetAngle = getangle(-(target->int_pos().Y - actor->int_pos().Y), target->int_pos().X - actor->int_pos().X); // X and Y are swapped here!
|
|
int vx = missileInfo[actor->spr.type - kMissileBase].velocity;
|
|
int vy = 0;
|
|
auto rpt = rotatepoint(DVector2(0,0), DVector2(vx, 0), DAngle::fromBuild(nTargetAngle + 1536));
|
|
actor->set_int_bvel_x(rpt.X); // we were rotating an int vector here so scale matches.
|
|
actor->set_int_bvel_y(rpt.Y);
|
|
int dz = target->int_pos().Z - actor->int_pos().Z;
|
|
|
|
int deltaz = dz / 10;
|
|
if (target->int_pos().Z < actor->int_pos().Z) deltaz = -deltaz;
|
|
actor->add_int_bvel_z(deltaz);
|
|
}
|
|
}
|
|
auto vel = actor->vel;
|
|
double top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
int i = 1;
|
|
const int bakCompat = enginecompatibility_mode;
|
|
const bool isFlameSprite = (actor->spr.type == kMissileFlameSpray || actor->spr.type == kMissileFlameHound); // do not use accurate clipmove for flame based sprites (changes damage too much)
|
|
while (1)
|
|
{
|
|
auto ppos = actor->spr.pos;
|
|
auto pSector2 = actor->sector();
|
|
const auto bakSpriteCstat = actor->spr.cstat;
|
|
if (Owner && !isFlameSprite && !cl_bloodvanillaexplosions && !VanillaMode())
|
|
{
|
|
enginecompatibility_mode = ENGINECOMPATIBILITY_NONE; // improved clipmove accuracy
|
|
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL; // remove self collisions for accurate clipmove
|
|
}
|
|
Collision clipmoveresult;
|
|
ClipMove(ppos, &pSector2, vel.XY(), actor->spr.clipdist << 2, (ppos.Z - top) / 4, (bottom - ppos.Z) / 4, CLIPMASK0, clipmoveresult, 1);
|
|
enginecompatibility_mode = bakCompat; // restore
|
|
actor->spr.cstat = bakSpriteCstat;
|
|
auto pSector = pSector2;
|
|
if (pSector2 == nullptr)
|
|
{
|
|
cliptype = -1;
|
|
break;
|
|
}
|
|
if (clipmoveresult.type == kHitSprite)
|
|
{
|
|
gHitInfo.hitActor = clipmoveresult.actor();
|
|
cliptype = 3;
|
|
}
|
|
else if (clipmoveresult.type == kHitWall)
|
|
{
|
|
gHitInfo.hitWall = clipmoveresult.hitWall;
|
|
if (!gHitInfo.hitWall->twoSided()) cliptype = 0;
|
|
else
|
|
{
|
|
double fz, cz;
|
|
getzsofslopeptr(clipmoveresult.hitWall->nextSector(), ppos, &cz, &fz);
|
|
if (ppos.Z <= cz || ppos.Z >= fz) cliptype = 0;
|
|
else cliptype = 4;
|
|
}
|
|
}
|
|
if (cliptype == 4)
|
|
{
|
|
walltype* pWall = clipmoveresult.hitWall;
|
|
if (pWall->hasX())
|
|
{
|
|
XWALL* pXWall = &pWall->xw();
|
|
if (pXWall->triggerVector)
|
|
{
|
|
trTriggerWall(pWall, kCmdWallImpact, Owner? Owner : actor);
|
|
if (!(pWall->cstat & CSTAT_WALL_BLOCK_HITSCAN))
|
|
{
|
|
cliptype = -1;
|
|
if (i-- > 0)
|
|
continue;
|
|
cliptype = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (cliptype >= 0 && cliptype != 3)
|
|
{
|
|
double nVel = actor->vel.XY().Length();
|
|
ppos.XY() -= actor->vel.XY() / nVel;
|
|
vel.Z -= actor->vel.Z / nVel;
|
|
updatesector(ppos, &pSector);
|
|
pSector2 = pSector;
|
|
}
|
|
double ceilZ, floorZ;
|
|
Collision ceilColl, floorColl;
|
|
GetZRangeAtXYZ(ppos, pSector2, &ceilZ, &ceilColl, &floorZ, &floorColl, actor->spr.clipdist << 2, CLIPMASK0);
|
|
GetActorExtents(actor, &top, &bottom);
|
|
top += vel.Z;
|
|
bottom += vel.Z;
|
|
if (bottom >= floorZ)
|
|
{
|
|
actor->hit.florhit = floorColl;
|
|
vel.Z += floorZ - bottom;
|
|
cliptype = 2;
|
|
}
|
|
if (top <= ceilZ)
|
|
{
|
|
actor->hit.ceilhit = ceilColl;
|
|
vel.Z += std::max(ceilZ - top, 0.);
|
|
cliptype = 1;
|
|
}
|
|
actor->spr.pos = ppos.plusZ(vel.Z);
|
|
updatesector(ppos, &pSector);
|
|
if (pSector != nullptr && pSector != actor->sector())
|
|
{
|
|
assert(pSector);
|
|
ChangeActorSect(actor, pSector);
|
|
}
|
|
CheckLink(actor);
|
|
gHitInfo.hitSector = actor->sector();
|
|
gHitInfo.hitpos = actor->spr.pos;
|
|
break;
|
|
}
|
|
if (Owner) Owner->spr.cstat = bakCstat;
|
|
|
|
return cliptype;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actExplodeSprite(DBloodActor* actor)
|
|
{
|
|
if (!actor->hasX()) return;
|
|
|
|
if (actor->spr.statnum == kStatExplosion) return;
|
|
sfxKill3DSound(actor, -1, -1);
|
|
evKillActor(actor);
|
|
|
|
int nType = kExplosionStandard;
|
|
|
|
switch (actor->spr.type)
|
|
{
|
|
case kMissileFireballNapalm:
|
|
nType = kExplosionNapalm;
|
|
seqSpawn(4, actor, -1);
|
|
if (Chance(0x8000)) actor->spr.cstat |= CSTAT_SPRITE_XFLIP;
|
|
sfxPlay3DSound(actor, 303, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kMissileFlareAlt:
|
|
nType = kExplosionFireball;
|
|
seqSpawn(9, actor, -1);
|
|
if (Chance(0x8000)) actor->spr.cstat |= CSTAT_SPRITE_XFLIP;
|
|
sfxPlay3DSound(actor, 306, 24 + (actor->GetIndex() & 3), FX_GlobalChannel);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kMissileFireballCerberus:
|
|
case kMissileFireballTchernobog:
|
|
nType = kExplosionFireball;
|
|
seqSpawn(5, actor, -1);
|
|
sfxPlay3DSound(actor, 304, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingArmedTNTStick:
|
|
nType = kExplosionSmall;
|
|
if (actor->hit.florhit.type == kHitNone) seqSpawn(4, actor, -1);
|
|
else seqSpawn(3, actor, -1);
|
|
sfxPlay3DSound(actor, 303, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingArmedProxBomb:
|
|
case kThingArmedRemoteBomb:
|
|
case kThingArmedTNTBundle:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingTNTProx:
|
|
#endif
|
|
nType = kExplosionStandard;
|
|
if (actor->hit.florhit.type == kHitNone) seqSpawn(4, actor, -1);
|
|
else
|
|
seqSpawn(3, actor, -1);
|
|
sfxPlay3DSound(actor, 304, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingArmedSpray:
|
|
nType = kExplosionSpray;
|
|
seqSpawn(5, actor, -1);
|
|
sfxPlay3DSound(actor, 307, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
|
|
case kThingTNTBarrel:
|
|
{
|
|
auto spawned = actSpawnSprite(actor->sector(), actor->spr.pos, 0, 1);
|
|
spawned->SetOwner(actor->GetOwner());
|
|
if (actCheckRespawn(actor))
|
|
{
|
|
actor->xspr.state = 1;
|
|
actor->xspr.health = thingInfo[0].startHealth << 4;
|
|
}
|
|
else actPostSprite(actor, kStatFree);
|
|
|
|
nType = kExplosionLarge;
|
|
seqSpawn(4, spawned, -1);
|
|
actor = spawned;
|
|
|
|
sfxPlay3DSound(actor, 305, -1, 0);
|
|
GibSprite(actor, 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 = actor->xspr.data1; // Explosion type
|
|
int tSeq = actor->xspr.data2; // SEQ id
|
|
int tSnd = actor->xspr.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(actor, nSnd, -1, 0);
|
|
break;
|
|
}
|
|
case kThingPodFireBall:
|
|
nType = kExplosionFireball;
|
|
seqSpawn(9, actor, -1);
|
|
sfxPlay3DSound(actor, 307, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
fxSpawnPodStuff(actor, 240);
|
|
break;
|
|
|
|
default:
|
|
nType = kExplosionStandard;
|
|
seqSpawn(4, actor, -1);
|
|
if (Chance(0x8000)) actor->spr.cstat |= CSTAT_SPRITE_XFLIP;
|
|
sfxPlay3DSound(actor, 303, -1, 0);
|
|
GibSprite(actor, GIBTYPE_5, nullptr, nullptr);
|
|
break;
|
|
}
|
|
actor->ZeroVelocity();
|
|
actPostSprite(actor, kStatExplosion);
|
|
actor->spr.xrepeat = actor->spr.yrepeat = explodeInfo[nType].repeat;
|
|
|
|
actor->spr.flags &= ~3;
|
|
actor->spr.type = nType;
|
|
const EXPLOSION* pExplodeInfo = &explodeInfo[nType];
|
|
actor->SetTarget(nullptr);
|
|
actor->explosionhackflag = true;
|
|
actor->xspr.data1 = pExplodeInfo->ticks;
|
|
actor->xspr.data2 = pExplodeInfo->quakeEffect;
|
|
actor->xspr.data3 = pExplodeInfo->flashEffect;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actActivateGibObject(DBloodActor* actor)
|
|
{
|
|
int gib1 = ClipRange(actor->xspr.data1, 0, 31);
|
|
int gib2 = ClipRange(actor->xspr.data2, 0, 31);
|
|
int gib3 = ClipRange(actor->xspr.data3, 0, 31);
|
|
int sound = actor->xspr.data4;
|
|
int dropmsg = actor->xspr.dropMsg;
|
|
|
|
if (gib1 > 0) GibSprite(actor, (GIBTYPE)(gib1 - 1), nullptr, nullptr);
|
|
if (gib2 > 0) GibSprite(actor, (GIBTYPE)(gib2 - 1), nullptr, nullptr);
|
|
if (gib3 > 0 && actor->xspr.burnTime > 0) GibSprite(actor, (GIBTYPE)(gib3 - 1), nullptr, nullptr);
|
|
if (sound > 0) sfxPlay3DSound(actor->spr.pos, sound, actor->sector());
|
|
if (dropmsg > 0) actDropObject(actor, dropmsg);
|
|
|
|
if (!(actor->spr.cstat & CSTAT_SPRITE_INVISIBLE) && !(actor->spr.flags & kHitagRespawn))
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckProximity()
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.flags & 32) continue;
|
|
|
|
if (actor->hasX())
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingBloodBits:
|
|
case kThingBloodChunks:
|
|
case kThingZombieHead:
|
|
if (actor->xspr.locked && PlayClock >= actor->xspr.TargetPos.X) actor->xspr.locked = 0;
|
|
break;
|
|
}
|
|
|
|
if (actor->xspr.burnTime > 0)
|
|
{
|
|
actor->xspr.burnTime = ClipLow(actor->xspr.burnTime - 4, 0);
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
}
|
|
|
|
if (actor->xspr.Proximity)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
// don't process locked or 1-shot things for proximity
|
|
if (gModernMap && (actor->xspr.locked || actor->xspr.isTriggered))
|
|
continue;
|
|
#endif
|
|
|
|
if (actor->spr.type == kThingDroppedLifeLeech) actor->SetTarget(nullptr);
|
|
BloodStatIterator it1(kStatDude);
|
|
while (auto dudeactor = it1.Next())
|
|
{
|
|
auto nextdude = it1.Peek();
|
|
|
|
if (dudeactor->spr.flags & 32 || !dudeactor->hasX()) continue;
|
|
|
|
if ((unsigned int)dudeactor->xspr.health > 0)
|
|
{
|
|
int proxyDist = 96;
|
|
#ifdef NOONE_EXTENSIONS
|
|
// allow dudeLockout for proximity flag
|
|
if (gModernMap && actor->spr.type != kThingDroppedLifeLeech && actor->xspr.DudeLockout && !dudeactor->IsPlayerActor())
|
|
continue;
|
|
|
|
if (actor->spr.type == kModernThingEnemyLifeLeech) proxyDist = 512;
|
|
#endif
|
|
if (actor->spr.type == kThingDroppedLifeLeech && actor->GetTarget() == nullptr)
|
|
{
|
|
auto Owner = actor->GetOwner();
|
|
if (!Owner->IsPlayerActor()) continue;
|
|
|
|
PLAYER* pPlayer = &gPlayer[Owner->spr.type - kDudePlayer1];
|
|
PLAYER* pPlayer2 = dudeactor->IsPlayerActor() ? &gPlayer[dudeactor->spr.type - kDudePlayer1] : nullptr;
|
|
|
|
if (dudeactor == Owner || dudeactor->spr.type == kDudeZombieAxeBuried || dudeactor->spr.type == kDudeRat || dudeactor->spr.type == kDudeBat) continue;
|
|
if (gGameOptions.nGameType == 3 && pPlayer2 && pPlayer->teamId == pPlayer2->teamId) continue;
|
|
if (gGameOptions.nGameType == 1 && pPlayer2) continue;
|
|
proxyDist = 512;
|
|
}
|
|
|
|
if (CheckProximity(dudeactor, actor->spr.pos, actor->sector(), proxyDist))
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingDroppedLifeLeech:
|
|
if (!Chance(0x4000) && nextdude) continue;
|
|
if (dudeactor->spr.cstat & CSTAT_SPRITE_BLOCK) actor->SetTarget(dudeactor);
|
|
else continue;
|
|
break;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingTNTProx:
|
|
if (!dudeactor->IsPlayerActor()) continue;
|
|
actor->spr.pal = 0;
|
|
break;
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
if (actor->GetTarget() != dudeactor) continue;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (actor->GetOwner() == nullptr) actor->SetOwner(dudeactor);
|
|
trTriggerSprite(actor, kCmdSpriteProximity, dudeactor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckThings()
|
|
{
|
|
BloodStatIterator it(kStatThing);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.flags & 32) continue;
|
|
if (!actor->hasX()) continue;
|
|
|
|
auto pSector = actor->sector();
|
|
|
|
XSECTOR* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
|
|
if (pXSector && pXSector->panVel && (pXSector->panAlways || pXSector->state || pXSector->busy))
|
|
{
|
|
int nType = actor->spr.type - kThingBase;
|
|
const THINGINFO* pThingInfo = &thingInfo[nType];
|
|
if (pThingInfo->flags & 1) actor->spr.flags |= 1;
|
|
if (pThingInfo->flags & 2) actor->spr.flags |= 4;
|
|
}
|
|
|
|
if (actor->spr.flags & 3)
|
|
{
|
|
viewBackupSpriteLoc(actor);
|
|
if (pXSector && pXSector->panVel)
|
|
{
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (getflorzofslopeptr(pSector, actor->spr.pos) <= 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 (pSector->floorstat & CSTAT_SECTOR_ALIGN) angle = (angle + GetWallAngle(pSector->firstWall()) + 512) & 2047;
|
|
|
|
actor->add_int_bvel_x(MulScale(speed, Cos(angle), 30));
|
|
actor->add_int_bvel_y(MulScale(speed, Sin(angle), 30));
|
|
}
|
|
}
|
|
actAirDrag(actor, 128);
|
|
|
|
if (((actor->GetIndex() >> 8) & 15) == (gFrameCount & 15) && (actor->spr.flags & 2)) actor->spr.flags |= 4;
|
|
if ((actor->spr.flags & 4) || actor->vel.X != 0 || actor->vel.Y != 0 || actor->vel.Z != 0 || actor->sector()->velFloor || actor->sector()->velCeil)
|
|
{
|
|
Collision hit = MoveThing(actor);
|
|
if (hit.type)
|
|
{
|
|
if (actor->xspr.Impact) trTriggerSprite(actor, kCmdOff, hit.type == kHitSprite? hit.safeActor() : nullptr);
|
|
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingDripWater:
|
|
case kThingDripBlood:
|
|
MakeSplash(actor);
|
|
break;
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingThrowableRock:
|
|
seqSpawn(24, actor, -1);
|
|
if (hit.type == kHitSprite)
|
|
{
|
|
actor->spr.xrepeat = 32;
|
|
actor->spr.yrepeat = 32;
|
|
actDamageSprite(actor->GetOwner(), hit.actor(), kDamageFall, actor->xspr.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(), actor->spr.pos, actor->sector(), 200, 1, 20, kDamageExplode, 6, 0);
|
|
evPostActor(actor, 0, kCallbackFXPodBloodSplat);
|
|
}
|
|
else if (hit.type == kHitSprite)
|
|
{
|
|
actDamageSprite(actor->GetOwner(), hit.actor(), kDamageFall, 12);
|
|
evPostActor(actor, 0, kCallbackFXPodBloodSplat);
|
|
}
|
|
break;
|
|
|
|
case kThingPodFireBall:
|
|
actExplodeSprite(actor);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckProjectiles()
|
|
{
|
|
BloodStatIterator it(kStatProjectile);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.flags & 32)
|
|
continue;
|
|
viewBackupSpriteLoc(actor);
|
|
int hit = MoveMissile(actor);
|
|
if (hit >= 0) actImpactMissile(actor, hit);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
static TArray<walltype*> affectedXWalls; // keep this outside the function so that it only needs to be allocated once
|
|
|
|
static void actCheckExplosion()
|
|
{
|
|
BloodStatIterator it(kStatExplosion);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.flags & 32)
|
|
continue;
|
|
|
|
if (!actor->hasX()) continue;
|
|
|
|
auto Owner = actor->GetOwner();
|
|
int nType = actor->spr.type;
|
|
assert(nType >= 0 && nType < kExplodeMax);
|
|
const EXPLOSION* pExplodeInfo = &explodeInfo[nType];
|
|
const auto apos = actor->spr.pos;
|
|
const int x = actor->int_pos().X;
|
|
const int y = actor->int_pos().Y;
|
|
const int z = actor->int_pos().Z;
|
|
auto pSector = actor->sector();
|
|
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 && actor->xspr.data4 > 0)
|
|
radius = actor->xspr.data4;
|
|
#endif
|
|
|
|
// 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
|
|
affectedXWalls.Clear();
|
|
const bool newSectCheckMethod = !cl_bloodvanillaexplosions && Owner && Owner->IsDudeActor() && !VanillaMode(); // use new sector checking logic
|
|
auto sectorMap = GetClosestSpriteSectors(pSector, apos.XY(), radius, &affectedXWalls, newSectCheckMethod);
|
|
|
|
for (auto pWall : affectedXWalls)
|
|
{
|
|
trTriggerWall(pWall, kCmdWallImpact, Owner);
|
|
}
|
|
|
|
BloodStatIterator it1(kStatDude);
|
|
while (auto dudeactor = it1.Next())
|
|
{
|
|
if (dudeactor->spr.flags & 32) continue;
|
|
|
|
if (CheckSector(sectorMap, dudeactor))
|
|
{
|
|
if (actor->xspr.data1 && CheckProximity(dudeactor, apos, pSector, radius))
|
|
{
|
|
if (pExplodeInfo->dmg && actor->explosionhackflag)
|
|
{
|
|
actor->explosionhackflag = false;
|
|
actDamageSprite(Owner, dudeactor, kDamageFall, (pExplodeInfo->dmg + Random(pExplodeInfo->dmgRng)) << 4);
|
|
}
|
|
if (pExplodeInfo->dmgType) ConcussSprite(actor, dudeactor, apos, pExplodeInfo->dmgType);
|
|
|
|
if (pExplodeInfo->burnTime && dudeactor->hasX())
|
|
{
|
|
if (!dudeactor->xspr.burnTime) evPostActor(dudeactor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(Owner, dudeactor, pExplodeInfo->burnTime << 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
it1.Reset(kStatThing);
|
|
while (auto thingactor = it1.Next())
|
|
{
|
|
if (thingactor->spr.flags & 32) continue;
|
|
|
|
if (CheckSector(sectorMap, thingactor))
|
|
{
|
|
if (actor->xspr.data1 && CheckProximity(thingactor, apos, pSector, radius) && thingactor->hasX())
|
|
{
|
|
if (!thingactor->xspr.locked)
|
|
{
|
|
if (pExplodeInfo->dmgType) ConcussSprite(Owner, thingactor, apos, pExplodeInfo->dmgType);
|
|
|
|
if (pExplodeInfo->burnTime)
|
|
{
|
|
if (thingactor->spr.type == kThingTNTBarrel && !thingactor->xspr.burnTime)
|
|
evPostActor(thingactor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(Owner, thingactor, pExplodeInfo->burnTime << 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
{
|
|
auto pos = gPlayer[p].actor->int_pos();
|
|
int dx = (x - pos.X) >> 4;
|
|
int dy = (y - pos.Y) >> 4;
|
|
int dz = (z - pos.Z) >> 8;
|
|
int nDist = dx * dx + dy * dy + dz * dz + 0x40000;
|
|
int t = DivScale(actor->xspr.data2, nDist, 16);
|
|
gPlayer[p].flickerEffect += t;
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (actor->xspr.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] == nullptr) continue;
|
|
DBloodActor* physactor = gPhysSpritesList[i];
|
|
if (!physactor->insector() || (physactor->spr.flags & kHitagFree) != 0) continue;
|
|
|
|
if (!CheckSector(sectorMap, physactor) || !CheckProximity(physactor, apos, pSector, radius)) continue;
|
|
else debrisConcuss(Owner, i, x, y, z, pExplodeInfo->dmgType);
|
|
}
|
|
}
|
|
|
|
// trigger sprites from impact list
|
|
if (gImpactSpritesCount > 0) {
|
|
for (int i = 0; i < gImpactSpritesCount; i++)
|
|
{
|
|
if (gImpactSpritesList[i] == nullptr) continue;
|
|
|
|
DBloodActor* impactactor = gImpactSpritesList[i];
|
|
if (!impactactor->hasX() || !impactactor->insector() || (impactactor->spr.flags & kHitagFree) != 0) continue;
|
|
|
|
if (!CheckSector(sectorMap, impactactor) || !CheckProximity(impactactor, apos, pSector, radius))
|
|
continue;
|
|
|
|
trTriggerSprite(impactactor, kCmdSpriteImpact, Owner);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!gModernMap || !(actor->spr.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.
|
|
actor->xspr.data1 = ClipLow(actor->xspr.data1 - 4, 0);
|
|
actor->xspr.data2 = ClipLow(actor->xspr.data2 - 4, 0);
|
|
actor->xspr.data3 = ClipLow(actor->xspr.data3 - 4, 0);
|
|
}
|
|
#else
|
|
actor->xspr.data1 = ClipLow(actor->xspr.data1 - 4, 0);
|
|
actor->xspr.data2 = ClipLow(actor->xspr.data2 - 4, 0);
|
|
actor->xspr.data3 = ClipLow(actor->xspr.data3 - 4, 0);
|
|
#endif
|
|
|
|
if (actor->xspr.data1 == 0 && actor->xspr.data2 == 0 && actor->xspr.data3 == 0 && seqGetStatus(actor) < 0)
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckTraps()
|
|
{
|
|
BloodStatIterator it(kStatTraps);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if ((actor->spr.flags & 32) || !actor->hasX())
|
|
continue;
|
|
|
|
switch (actor->spr.type) {
|
|
case kTrapSawCircular:
|
|
actor->xspr.data2 = ClipLow(actor->xspr.data2 - 4, 0);
|
|
break;
|
|
|
|
case kTrapFlame:
|
|
if (actor->xspr.state && seqGetStatus(actor) < 0)
|
|
{
|
|
int x = actor->int_pos().X;
|
|
int y = actor->int_pos().Y;
|
|
int z = actor->int_pos().Z;
|
|
int t = (actor->xspr.data1 << 23) / 120;
|
|
int dx = MulScale(t, Cos(actor->int_ang()), 30);
|
|
int dy = MulScale(t, Sin(actor->int_ang()), 30);
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_32, actor->sector(), x, y, z, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->set_int_bvel_x(dx + Random2(0x8888));
|
|
pFX->set_int_bvel_y(dy + Random2(0x8888));
|
|
pFX->vel.Z = Random2F(0x8888);
|
|
}
|
|
x += (dx / 2) >> 12;
|
|
y += (dy / 2) >> 12;
|
|
}
|
|
dy = bsin(actor->int_ang());
|
|
dx = bcos(actor->int_ang());
|
|
gVectorData[kVectorTchernobogBurn].maxDist = actor->xspr.data1 << 9;
|
|
actFireVector(actor, 0, 0, dx, dy, Random2(0x8888), kVectorTchernobogBurn);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void actCheckDudes()
|
|
{
|
|
BloodStatIterator it(kStatDude);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.flags & 32)
|
|
continue;
|
|
|
|
if (actor->hasX())
|
|
{
|
|
const bool fixBurnGlitch = !cl_bloodvanillaenemies && IsBurningDude(actor) && !VanillaMode(); // if enemies are burning, always apply burning damage per tick
|
|
if ((actor->xspr.burnTime > 0) || fixBurnGlitch)
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeBurningInnocent:
|
|
case kDudeBurningCultist:
|
|
case kDudeBurningZombieAxe:
|
|
case kDudeBurningZombieButcher:
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
break;
|
|
|
|
default:
|
|
actor->xspr.burnTime = ClipLow(actor->xspr.burnTime - 4, 0);
|
|
actDamageSprite(actor->GetBurnSource(), actor, kDamageBurn, 8);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
// handle incarnations of custom dude
|
|
if (actor->spr.type == kDudeModernCustom && actor->xspr.txID > 0 && actor->xspr.sysData1 == kGenDudeTransformStatus)
|
|
{
|
|
actor->ZeroVelocityXY();
|
|
if (seqGetStatus(actor) < 0) genDudeTransform(actor);
|
|
}
|
|
#endif
|
|
if (actor->spr.type == kDudeCerberusTwoHead)
|
|
{
|
|
if (actor->xspr.health <= 0 && seqGetStatus(actor) < 0)
|
|
{
|
|
actor->xspr.health = dudeInfo[28].startHealth << 4;
|
|
actor->spr.type = kDudeCerberusOneHead;
|
|
if (actor->GetTarget() != nullptr) aiSetTarget(actor, actor->GetTarget());
|
|
aiActivateDude(actor);
|
|
}
|
|
}
|
|
if (actor->xspr.Proximity && !actor->xspr.isTriggered)
|
|
{
|
|
BloodStatIterator it1(kStatDude);
|
|
while (auto actor2 = it1.Next())
|
|
{
|
|
if (actor2->spr.flags & 32) continue;
|
|
|
|
if (actor2->IsPlayerActor() && (unsigned int)actor2->xspr.health > 0)
|
|
{
|
|
if (CheckProximity(actor2, actor->spr.pos, actor->sector(), 128))
|
|
trTriggerSprite(actor, kCmdSpriteProximity, actor2);
|
|
}
|
|
}
|
|
}
|
|
if (actor->IsPlayerActor())
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[actor->spr.type - kDudePlayer1];
|
|
if (pPlayer->voodooTargets) voodooTarget(pPlayer);
|
|
if (pPlayer->hand && Chance(0x8000)) actDamageSprite(actor, actor, kDamageDrown, 12);
|
|
|
|
if (pPlayer->isUnderwater)
|
|
{
|
|
bool 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->vel.X != 0 || actor->int_vel().Y)
|
|
sfxPlay3DSound(actor, 709, 100, 2);
|
|
|
|
pPlayer->bubbleTime = ClipLow(pPlayer->bubbleTime - 4, 0);
|
|
}
|
|
else if (gGameOptions.nGameType == 0)
|
|
{
|
|
if (pPlayer->actor->xspr.health > 0 && pPlayer->restTime >= 1200 && Chance(0x200))
|
|
{
|
|
pPlayer->restTime = -1;
|
|
sfxPlay3DSound(actor, 3100 + Random(11), 0, 2);
|
|
}
|
|
}
|
|
}
|
|
ProcessTouchObjects(actor);
|
|
}
|
|
}
|
|
|
|
it.Reset(kStatDude);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.flags & 32 || !actor->hasX()) continue;
|
|
|
|
auto pSector = actor->sector();
|
|
viewBackupSpriteLoc(actor);
|
|
XSECTOR* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
|
|
|
|
if (pXSector)
|
|
{
|
|
int top, bottom;
|
|
GetActorExtents(actor, &top, &bottom);
|
|
if (getflorzofslopeptr(pSector, actor->spr.pos) <= 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 (pSector->floorstat & CSTAT_SECTOR_ALIGN)
|
|
angle = (angle + GetWallAngle(pSector->firstWall()) + 512) & 2047;
|
|
int dx = MulScale(speed, Cos(angle), 30);
|
|
int dy = MulScale(speed, Sin(angle), 30);
|
|
actor->add_int_bvel_x(dx);
|
|
actor->add_int_bvel_y(dy);
|
|
}
|
|
}
|
|
if (pXSector && pXSector->Underwater) actAirDrag(actor, 5376);
|
|
else actAirDrag(actor, 128);
|
|
|
|
if ((actor->spr.flags & 4) || actor->vel.X != 0 || actor->vel.Y != 0 || actor->vel.Z != 0 || actor->sector()->velFloor || actor->sector()->velCeil)
|
|
MoveDude(actor);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actCheckFlares()
|
|
{
|
|
BloodStatIterator it(kStatFlare);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if ((actor->spr.flags & 32) || !actor->hasX()) continue;
|
|
|
|
auto target = actor->GetTarget();
|
|
if (!target) continue;
|
|
|
|
viewBackupSpriteLoc(actor);
|
|
if (target->spr.statnum == kMaxStatus)
|
|
{
|
|
GibSprite(actor, GIBTYPE_17, NULL, NULL);
|
|
actPostSprite(actor, kStatFree);
|
|
}
|
|
if (target->hasX() && target->xspr.health > 0)
|
|
{
|
|
DVector3 pos = target->spr.pos;
|
|
pos.XY() += (actor->xspr.goalAng + target->spr.angle).ToVector() * target->spr.clipdist * 2 * inttoworld;
|
|
pos.Z += actor->xspr.TargetPos.Z;
|
|
SetActor(actor, pos);
|
|
actor->vel = target->vel;
|
|
}
|
|
else
|
|
{
|
|
GibSprite(actor, 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(sectortype* pSector, const DVector3& pos, int nStat, bool setextra)
|
|
{
|
|
DBloodActor* actor = InsertSprite(pSector, nStat);
|
|
|
|
SetActor(actor, pos);
|
|
actor->spr.type = kSpriteDecoration;
|
|
if (setextra && !actor->hasX())
|
|
{
|
|
actor->addX();
|
|
actor->hit.florhit.setNone();
|
|
actor->hit.ceilhit.setNone();
|
|
if (!VanillaMode()) actor->SetTarget(nullptr);
|
|
}
|
|
return actor;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* actSpawnSprite(DBloodActor* source, int nStat)
|
|
{
|
|
DBloodActor* actor = InsertSprite(source->sector(), nStat);
|
|
|
|
actor->spr.pos = source->spr.pos;
|
|
actor->vel = source->vel;
|
|
actor->spr.flags = 0;
|
|
actor->addX();
|
|
actor->hit.florhit.setNone();
|
|
actor->hit.ceilhit.setNone();
|
|
if (!VanillaMode()) actor->SetTarget(nullptr);
|
|
return actor;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* actSpawnDude(DBloodActor* source, int nType, int a3, int a4)
|
|
{
|
|
auto spawned = actSpawnSprite(source, kStatDude);
|
|
if (!spawned) return nullptr;
|
|
int angle = source->int_ang();
|
|
int nDude = nType - kDudeBase;
|
|
|
|
auto pos = source->spr.pos;
|
|
pos.Z += a4 * zinttoworld;
|
|
|
|
if (a3 >= 0)
|
|
{
|
|
pos.X += mulscale30r(Cos(angle), a3) * inttoworld;
|
|
pos.Y += mulscale30r(Sin(angle), a3) * inttoworld;
|
|
}
|
|
spawned->spr.type = nType;
|
|
if (!VanillaMode())
|
|
spawned->spr.inittype = nType;
|
|
spawned->set_int_ang(angle);
|
|
SetActor(spawned, pos);
|
|
|
|
spawned->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL | CSTAT_SPRITE_BLOOD_BIT1;
|
|
spawned->spr.clipdist = getDudeInfo(nDude + kDudeBase)->clipdist;
|
|
spawned->xspr.health = getDudeInfo(nDude + kDudeBase)->startHealth << 4;
|
|
spawned->xspr.respawn = 1;
|
|
if (getSequence(getDudeInfo(nDude + kDudeBase)->seqStartID))
|
|
seqSpawn(getDudeInfo(nDude + kDudeBase)->seqStartID, spawned, -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 && source->spr.flags & kModernTypeFlag1)
|
|
{
|
|
// allow inheriting only for selected source types
|
|
switch (source->spr.type)
|
|
{
|
|
case kMarkerDudeSpawn:
|
|
//inherit pal?
|
|
if (spawned->spr.pal <= 0) spawned->spr.pal = source->spr.pal;
|
|
|
|
// inherit spawn sprite trigger settings, so designer can count monsters.
|
|
spawned->xspr.txID = source->xspr.txID;
|
|
spawned->xspr.command = source->xspr.command;
|
|
spawned->xspr.triggerOn = source->xspr.triggerOn;
|
|
spawned->xspr.triggerOff = source->xspr.triggerOff;
|
|
|
|
// inherit drop items
|
|
spawned->xspr.dropMsg = source->xspr.dropMsg;
|
|
|
|
// inherit dude flags
|
|
spawned->xspr.dudeDeaf = source->xspr.dudeDeaf;
|
|
spawned->xspr.dudeGuard = source->xspr.dudeGuard;
|
|
spawned->xspr.dudeAmbush = source->xspr.dudeAmbush;
|
|
spawned->xspr.dudeFlag4 = source->xspr.dudeFlag4;
|
|
spawned->xspr.unused1 = source->xspr.unused1;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
aiInitSprite(spawned);
|
|
return spawned;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* actSpawnThing(sectortype* pSector, int x, int y, int z, int nThingType)
|
|
{
|
|
DVector3 pos(x * inttoworld, y * inttoworld, z * zinttoworld);
|
|
assert(nThingType >= kThingBase && nThingType < kThingMax);
|
|
auto actor = actSpawnSprite(pSector, pos, 4, 1);
|
|
int nType = nThingType - kThingBase;
|
|
actor->spr.type = nThingType;
|
|
assert(actor->hasX());
|
|
const THINGINFO* pThingInfo = &thingInfo[nType];
|
|
actor->xspr.health = pThingInfo->startHealth << 4;
|
|
actor->spr.clipdist = pThingInfo->clipdist;
|
|
actor->spr.flags = pThingInfo->flags;
|
|
if (actor->spr.flags & 2) actor->spr.flags |= 4;
|
|
actor->spr.cstat |= pThingInfo->cstat;
|
|
actor->spr.picnum = pThingInfo->picnum;
|
|
actor->spr.shade = pThingInfo->shade;
|
|
actor->spr.pal = pThingInfo->pal;
|
|
if (pThingInfo->xrepeat) actor->spr.xrepeat = pThingInfo->xrepeat;
|
|
if (pThingInfo->yrepeat) actor->spr.yrepeat = pThingInfo->yrepeat;
|
|
actor->spr.cstat2 |= CSTAT2_SPRITE_MAPPED;
|
|
switch (nThingType)
|
|
{
|
|
case kThingVoodooHead:
|
|
actor->xspr.data1 = 0;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.data4 = 0;
|
|
actor->xspr.state = 1;
|
|
actor->xspr.triggerOnce = 1;
|
|
actor->xspr.isTriggered = 0;
|
|
break;
|
|
|
|
case kThingDroppedLifeLeech:
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernThingEnemyLifeLeech:
|
|
#endif
|
|
actor->xspr.data1 = 0;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.data4 = 0;
|
|
actor->xspr.state = 1;
|
|
actor->xspr.triggerOnce = 0;
|
|
actor->xspr.isTriggered = 0;
|
|
break;
|
|
|
|
case kThingZombieHead:
|
|
actor->xspr.data1 = 8;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.data4 = 318;
|
|
actor->xspr.TargetPos.X = PlayClock + 180;
|
|
actor->xspr.locked = 1;
|
|
actor->xspr.state = 1;
|
|
actor->xspr.triggerOnce = 0;
|
|
actor->xspr.isTriggered = 0;
|
|
break;
|
|
|
|
case kThingBloodBits:
|
|
case kThingBloodChunks:
|
|
actor->xspr.data1 = (nThingType == kThingBloodBits) ? 19 : 8;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.data4 = 319;
|
|
actor->xspr.TargetPos.X = PlayClock + 180;
|
|
actor->xspr.locked = 1;
|
|
actor->xspr.state = 1;
|
|
actor->xspr.triggerOnce = 0;
|
|
actor->xspr.isTriggered = 0;
|
|
break;
|
|
|
|
case kThingArmedTNTStick:
|
|
evPostActor(actor, 0, kCallbackFXDynPuff);
|
|
sfxPlay3DSound(actor, 450, 0, 0);
|
|
break;
|
|
|
|
case kThingArmedTNTBundle:
|
|
sfxPlay3DSound(actor, 450, 0, 0);
|
|
evPostActor(actor, 0, kCallbackFXDynPuff);
|
|
break;
|
|
|
|
case kThingArmedSpray:
|
|
evPostActor(actor, 0, kCallbackFXDynPuff);
|
|
break;
|
|
}
|
|
return actor;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* actFireThing(DBloodActor* actor, int a2, int a3, int a4, int thingType, int a6)
|
|
{
|
|
assert(thingType >= kThingBase && thingType < kThingMax);
|
|
int x = actor->int_pos().X + MulScale(a2, Cos(actor->int_ang() + 512), 30);
|
|
int y = actor->int_pos().Y + MulScale(a2, Sin(actor->int_ang() + 512), 30);
|
|
int z = actor->int_pos().Z + a3;
|
|
x += MulScale(actor->spr.clipdist, Cos(actor->int_ang()), 28);
|
|
y += MulScale(actor->spr.clipdist, Sin(actor->int_ang()), 28);
|
|
if (HitScan(actor, z, x - actor->int_pos().X, y - actor->int_pos().Y, 0, CLIPMASK0, actor->spr.clipdist) != -1)
|
|
{
|
|
x = gHitInfo.int_hitpos().X - MulScale(actor->spr.clipdist << 1, Cos(actor->int_ang()), 28);
|
|
y = gHitInfo.int_hitpos().Y - MulScale(actor->spr.clipdist << 1, Sin(actor->int_ang()), 28);
|
|
}
|
|
auto fired = actSpawnThing(actor->sector(), x, y, z, thingType);
|
|
fired->SetOwner(actor);
|
|
fired->spr.angle = actor->spr.angle;
|
|
fired->set_int_bvel_x(MulScale(a6, Cos(fired->int_ang()), 30));
|
|
fired->set_int_bvel_y(MulScale(a6, Sin(fired->int_ang()), 30));
|
|
fired->set_int_bvel_z(MulScale(a6, a4, 14));
|
|
fired->vel += actor->vel * 0.5;
|
|
return fired;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actBuildMissile(DBloodActor* spawned, DBloodActor* actor)
|
|
{
|
|
switch (spawned->spr.type)
|
|
{
|
|
case kMissileLifeLeechRegular:
|
|
evPostActor(spawned, 0, kCallbackFXFlameLick);
|
|
break;
|
|
case kMissileTeslaAlt:
|
|
evPostActor(spawned, 0, kCallbackFXTeslaAlt);
|
|
break;
|
|
case kMissilePukeGreen:
|
|
seqSpawn(29, spawned, -1);
|
|
break;
|
|
case kMissileButcherKnife:
|
|
spawned->spr.cstat |= CSTAT_SPRITE_ALIGNMENT_WALL;
|
|
break;
|
|
case kMissileTeslaRegular:
|
|
sfxPlay3DSound(spawned, 251, 0, 0);
|
|
break;
|
|
case kMissileEctoSkull:
|
|
seqSpawn(2, spawned, -1);
|
|
sfxPlay3DSound(spawned, 493, 0, 0);
|
|
break;
|
|
case kMissileFireballNapalm:
|
|
seqSpawn(61, spawned, nNapalmClient);
|
|
sfxPlay3DSound(spawned, 441, 0, 0);
|
|
break;
|
|
case kMissileFireball:
|
|
seqSpawn(22, spawned, nFireballClient);
|
|
sfxPlay3DSound(spawned, 441, 0, 0);
|
|
break;
|
|
case kMissileFlameHound:
|
|
seqSpawn(27, spawned, -1);
|
|
spawned->vel += actor->vel * 0.5;
|
|
spawned->vel.X += Random2F(0x11111);
|
|
spawned->vel.Y += Random2F(0x11111);
|
|
spawned->vel.Z += Random2F(0x11111);
|
|
break;
|
|
case kMissileFireballCerberus:
|
|
seqSpawn(61, spawned, dword_2192E0);
|
|
sfxPlay3DSound(spawned, 441, 0, 0);
|
|
break;
|
|
case kMissileFireballTchernobog:
|
|
seqSpawn(23, spawned, dword_2192D8);
|
|
spawned->vel += actor->vel * 0.5;
|
|
spawned->vel.X += Random2F(0x11111);
|
|
spawned->vel.Y += Random2F(0x11111);
|
|
spawned->vel.Z += Random2F(0x11111);
|
|
break;
|
|
case kMissileFlameSpray:
|
|
if (Chance(0x8000)) seqSpawn(0, spawned, -1);
|
|
else seqSpawn(1, spawned, -1);
|
|
spawned->vel += actor->vel * 0.5;
|
|
spawned->vel.X += Random2F(0x11111);
|
|
spawned->vel.Y += Random2F(0x11111);
|
|
spawned->vel.Z += Random2F(0x11111);
|
|
break;
|
|
case kMissileFlareAlt:
|
|
evPostActor(spawned, 30, kCallbackFXFlareBurst);
|
|
evPostActor(spawned, 0, kCallbackFXFlareSpark);
|
|
sfxPlay3DSound(spawned, 422, 0, 0);
|
|
break;
|
|
case kMissileFlareRegular:
|
|
evPostActor(spawned, 0, kCallbackFXFlareSpark);
|
|
sfxPlay3DSound(spawned, 422, 0, 0);
|
|
break;
|
|
case kMissileLifeLeechAltSmall:
|
|
evPostActor(spawned, 0, kCallbackFXArcSpark);
|
|
break;
|
|
case kMissileArcGargoyle:
|
|
sfxPlay3DSound(spawned, 252, 0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DBloodActor* actFireMissile(DBloodActor* actor, int a2, int a3, int a4, int a5, int a6, int nType)
|
|
{
|
|
|
|
assert(nType >= kMissileBase && nType < kMissileMax);
|
|
bool impact = false;
|
|
const MissileType* pMissileInfo = &missileInfo[nType - kMissileBase];
|
|
int x = actor->int_pos().X + MulScale(a2, Cos(actor->int_ang() + 512), 30);
|
|
int y = actor->int_pos().Y + MulScale(a2, Sin(actor->int_ang() + 512), 30);
|
|
int z = actor->int_pos().Z + a3;
|
|
int clipdist = pMissileInfo->clipDist + actor->spr.clipdist;
|
|
x += MulScale(clipdist, Cos(actor->int_ang()), 28);
|
|
y += MulScale(clipdist, Sin(actor->int_ang()), 28);
|
|
int hit = HitScan(actor, z, x - actor->int_pos().X, y - actor->int_pos().Y, 0, CLIPMASK0, clipdist);
|
|
if (hit != -1)
|
|
{
|
|
if (hit == 3 || hit == 0)
|
|
{
|
|
impact = true;
|
|
x = gHitInfo.int_hitpos().X - MulScale(Cos(actor->int_ang()), 16, 30);
|
|
y = gHitInfo.int_hitpos().Y - MulScale(Sin(actor->int_ang()), 16, 30);
|
|
}
|
|
else
|
|
{
|
|
x = gHitInfo.int_hitpos().X - MulScale(pMissileInfo->clipDist << 1, Cos(actor->int_ang()), 28);
|
|
y = gHitInfo.int_hitpos().Y - MulScale(pMissileInfo->clipDist << 1, Sin(actor->int_ang()), 28);
|
|
}
|
|
}
|
|
DVector3 pos(x * inttoworld, y * inttoworld, z * zinttoworld);
|
|
auto spawned = actSpawnSprite(actor->sector(), pos, 5, 1);
|
|
|
|
spawned->spr.cstat2 |= CSTAT2_SPRITE_MAPPED;
|
|
spawned->spr.type = nType;
|
|
spawned->spr.shade = pMissileInfo->shade;
|
|
spawned->spr.pal = 0;
|
|
spawned->spr.clipdist = pMissileInfo->clipDist;
|
|
spawned->spr.flags = 1;
|
|
spawned->spr.xrepeat = pMissileInfo->xrepeat;
|
|
spawned->spr.yrepeat = pMissileInfo->yrepeat;
|
|
spawned->spr.picnum = pMissileInfo->picnum;
|
|
spawned->set_int_ang((actor->int_ang() + pMissileInfo->angleOfs) & 2047);
|
|
spawned->set_int_bvel_x(MulScale(pMissileInfo->velocity, a4, 14));
|
|
spawned->set_int_bvel_y(MulScale(pMissileInfo->velocity, a5, 14));
|
|
spawned->set_int_bvel_z(MulScale(pMissileInfo->velocity, a6, 14));
|
|
spawned->SetOwner(actor);
|
|
spawned->spr.cstat |= CSTAT_SPRITE_BLOCK;
|
|
spawned->SetTarget(nullptr);
|
|
evPostActor(spawned, 600, kCallbackRemove);
|
|
|
|
actBuildMissile(spawned, actor);
|
|
|
|
if (impact)
|
|
{
|
|
actImpactMissile(spawned, hit);
|
|
return nullptr;
|
|
}
|
|
return spawned;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int actGetRespawnTime(DBloodActor* actor)
|
|
{
|
|
if (!actor->hasX()) return -1;
|
|
|
|
if (actor->IsDudeActor() && !actor->IsPlayerActor())
|
|
{
|
|
if (actor->xspr.respawn == 2 || (actor->xspr.respawn != 1 && gGameOptions.nMonsterSettings == 2))
|
|
return gGameOptions.nMonsterRespawnTime;
|
|
return -1;
|
|
}
|
|
|
|
if (actor->IsWeaponActor())
|
|
{
|
|
if (actor->xspr.respawn == 3 || gGameOptions.nWeaponSettings == 1) return 0;
|
|
else if (actor->xspr.respawn != 1 && gGameOptions.nWeaponSettings != 0)
|
|
return gGameOptions.nWeaponRespawnTime;
|
|
return -1;
|
|
}
|
|
|
|
if (actor->IsAmmoActor())
|
|
{
|
|
if (actor->xspr.respawn == 2 || (actor->xspr.respawn != 1 && gGameOptions.nWeaponSettings != 0))
|
|
return gGameOptions.nWeaponRespawnTime;
|
|
return -1;
|
|
}
|
|
|
|
if (actor->IsItemActor())
|
|
{
|
|
if (actor->xspr.respawn == 3 && gGameOptions.nGameType == 1) return 0;
|
|
else if (actor->xspr.respawn == 2 || (actor->xspr.respawn != 1 && gGameOptions.nItemSettings != 0))
|
|
{
|
|
switch (actor->spr.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(DBloodActor* actor)
|
|
{
|
|
if (actor->hasX())
|
|
{
|
|
int nRespawnTime = actGetRespawnTime(actor);
|
|
if (nRespawnTime < 0) return 0;
|
|
|
|
actor->xspr.respawnPending = 1;
|
|
if (actor->spr.type >= kThingBase && actor->spr.type < kThingMax)
|
|
{
|
|
actor->xspr.respawnPending = 3;
|
|
if (actor->spr.type == kThingTNTBarrel) actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
}
|
|
if (nRespawnTime > 0)
|
|
{
|
|
if (actor->xspr.respawnPending == 1) nRespawnTime = MulScale(nRespawnTime, 0xa000, 16);
|
|
actor->spr.intowner = actor->spr.statnum;
|
|
actPostSprite(actor, kStatRespawn);
|
|
actor->spr.flags |= kHitagRespawn;
|
|
|
|
if (!(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax))
|
|
{
|
|
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
actor->spr.pos = actor->basePoint;
|
|
}
|
|
evPostActor(actor, nRespawnTime, kCallbackRespawn);
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool actCanSplatWall(walltype* pWall)
|
|
{
|
|
if (pWall->cstat & (CSTAT_WALL_MOVE_MASK)) return 0;
|
|
|
|
int nType = pWall->type;
|
|
if (nType >= kWallBase && nType < kWallMax) return 0;
|
|
|
|
if (pWall->twoSided())
|
|
{
|
|
sectortype* pSector = pWall->nextSector();
|
|
if (pSector->type >= kSectorBase && pSector->type < kSectorMax) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actFireVector(DBloodActor* shooter, int a2, int a3, int a4, int a5, int a6, VECTOR_TYPE vectorType)
|
|
{
|
|
assert(vectorType >= 0 && vectorType < kVectorMax);
|
|
const VECTORDATA* pVectorData = &gVectorData[vectorType];
|
|
int nRange = pVectorData->maxDist;
|
|
int hit = VectorScan(shooter, a2, a3, a4, a5, a6, nRange, 1);
|
|
if (hit == 3)
|
|
{
|
|
auto hitactor = gHitInfo.actor();
|
|
assert(hitactor != nullptr);
|
|
if (!gGameOptions.bFriendlyFire && IsTargetTeammate(shooter, hitactor)) return;
|
|
if (hitactor->IsPlayerActor())
|
|
{
|
|
PLAYER* pPlayer = &gPlayer[hitactor->spr.type - kDudePlayer1];
|
|
if (powerupCheck(pPlayer, kPwUpReflectShots))
|
|
{
|
|
gHitInfo.hitActor = shooter;
|
|
gHitInfo.hitpos = shooter->spr.pos;
|
|
}
|
|
}
|
|
}
|
|
int x = gHitInfo.int_hitpos().X - MulScale(a4, 16, 14);
|
|
int y = gHitInfo.int_hitpos().Y - MulScale(a5, 16, 14);
|
|
int z = gHitInfo.int_hitpos().Z - MulScale(a6, 256, 14);
|
|
auto pSector = gHitInfo.hitSector;
|
|
uint8_t nSurf = kSurfNone;
|
|
if (nRange == 0 || approxDist(gHitInfo.hitpos.XY() - shooter->spr.pos.XY()) < nRange)
|
|
{
|
|
switch (hit)
|
|
{
|
|
case 1:
|
|
{
|
|
if (pSector->ceilingstat & CSTAT_SECTOR_SKY)
|
|
nSurf = kSurfNone;
|
|
else
|
|
nSurf = surfType[pSector->ceilingpicnum];
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if (pSector->floorstat & CSTAT_SECTOR_SKY)
|
|
nSurf = kSurfNone;
|
|
else
|
|
nSurf = surfType[pSector->floorpicnum];
|
|
break;
|
|
}
|
|
case 0:
|
|
{
|
|
auto pWall = gHitInfo.hitWall;
|
|
nSurf = surfType[pWall->picnum];
|
|
if (actCanSplatWall(pWall))
|
|
{
|
|
int xx = gHitInfo.int_hitpos().X - MulScale(a4, 16, 14);
|
|
int yy = gHitInfo.int_hitpos().Y - MulScale(a5, 16, 14);
|
|
int zz = gHitInfo.int_hitpos().Z - MulScale(a6, 256, 14);
|
|
int nnSurf = surfType[pWall->picnum];
|
|
assert(nnSurf < kSurfMax);
|
|
if (pVectorData->surfHit[nnSurf].fx1 >= 0)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(pVectorData->surfHit[nnSurf].fx1, pSector, xx, yy, zz, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->set_int_ang((GetWallAngle(pWall) + 512) & 2047);
|
|
pFX->spr.cstat |= CSTAT_SPRITE_ALIGNMENT_WALL;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
auto pWall = gHitInfo.hitWall;
|
|
nSurf = surfType[pWall->overpicnum];
|
|
if (pWall->hasX())
|
|
{
|
|
if (pWall->xw().triggerVector)
|
|
trTriggerWall(pWall, kCmdWallImpact, shooter);
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
auto actor = gHitInfo.actor();
|
|
nSurf = surfType[actor->spr.picnum];
|
|
x -= MulScale(a4, 112, 14);
|
|
y -= MulScale(a5, 112, 14);
|
|
z -= MulScale(a6, 112 << 4, 14);
|
|
int shift = 4;
|
|
if (vectorType == kVectorTine && !actor->IsPlayerActor()) shift = 3;
|
|
|
|
actDamageSprite(shooter, actor, pVectorData->dmgType, pVectorData->dmg << shift);
|
|
if (actor->hasX() && actor->xspr.Vector) trTriggerSprite(actor, kCmdSpriteImpact, shooter);
|
|
|
|
if (actor->spr.statnum == kStatThing)
|
|
{
|
|
int t = thingInfo[actor->spr.type - kThingBase].mass;
|
|
if (t > 0 && pVectorData->impulse)
|
|
{
|
|
int t2 = DivScale(pVectorData->impulse, t, 8);
|
|
actor->add_int_bvel_x(MulScale(a4, t2, 16));
|
|
actor->add_int_bvel_y(MulScale(a5, t2, 16));
|
|
actor->add_int_bvel_z(MulScale(a6, t2, 16));
|
|
}
|
|
if (pVectorData->burnTime)
|
|
{
|
|
if (!actor->xspr.burnTime) evPostActor(actor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(shooter->GetOwner(), actor, pVectorData->burnTime);
|
|
}
|
|
}
|
|
if (actor->spr.statnum == kStatDude && actor->hasX())
|
|
{
|
|
int t = getDudeInfo(actor->spr.type)->mass;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
if (actor->IsDudeActor())
|
|
{
|
|
switch (actor->spr.type)
|
|
{
|
|
case kDudeModernCustom:
|
|
case kDudeModernCustomBurning:
|
|
t = getSpriteMassBySize(actor);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (t > 0 && pVectorData->impulse)
|
|
{
|
|
int t2 = DivScale(pVectorData->impulse, t, 8);
|
|
actor->add_int_bvel_x(MulScale(a4, t2, 16));
|
|
actor->add_int_bvel_y(MulScale(a5, t2, 16));
|
|
actor->add_int_bvel_z(MulScale(a6, t2, 16));
|
|
}
|
|
if (pVectorData->burnTime)
|
|
{
|
|
if (!actor->xspr.burnTime) evPostActor(actor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(shooter->GetOwner(), actor, pVectorData->burnTime);
|
|
}
|
|
if (Chance(pVectorData->fxChance))
|
|
{
|
|
int tt = gVectorData[19].maxDist;
|
|
a4 += Random3(4000);
|
|
a5 += Random3(4000);
|
|
a6 += Random3(4000);
|
|
if (HitScan(actor, gHitInfo.hitpos.Z, a4, a5, a6, CLIPMASK1, tt) == 0)
|
|
{
|
|
if (approxDist(gHitInfo.hitpos.XY() - actor->spr.pos.XY()) <= tt)
|
|
{
|
|
auto pWall = gHitInfo.hitWall;
|
|
auto pSector1 = gHitInfo.hitSector;
|
|
if (actCanSplatWall(pWall))
|
|
{
|
|
int xx = gHitInfo.int_hitpos().X - MulScale(a4, 16, 14);
|
|
int yy = gHitInfo.int_hitpos().Y - MulScale(a5, 16, 14);
|
|
int zz = gHitInfo.int_hitpos().Z - MulScale(a6, 16 << 4, 14);
|
|
int nnSurf = surfType[pWall->picnum];
|
|
const VECTORDATA* pVectorData1 = &gVectorData[19];
|
|
FX_ID t2 = pVectorData1->surfHit[nnSurf].fx2;
|
|
FX_ID t3 = pVectorData1->surfHit[nnSurf].fx3;
|
|
|
|
DBloodActor* pFX = nullptr;
|
|
if (t2 > FX_NONE && (t3 == FX_NONE || Chance(0x4000))) pFX = gFX.fxSpawnActor(t2, pSector1, xx, yy, zz, 0);
|
|
else if (t3 > FX_NONE) pFX = gFX.fxSpawnActor(t3, pSector1, xx, yy, zz, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->vel.Z = FixedToFloat(0x2222);
|
|
pFX->set_int_ang((GetWallAngle(pWall) + 512) & 2047);
|
|
pFX->spr.cstat |= CSTAT_SPRITE_ALIGNMENT_WALL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < pVectorData->bloodSplats; i++)
|
|
if (Chance(pVectorData->splatChance))
|
|
fxSpawnBlood(actor, pVectorData->dmg << 4);
|
|
}
|
|
#ifdef NOONE_EXTENSIONS
|
|
// add impulse for sprites from physics list
|
|
if (gPhysSpritesCount > 0 && pVectorData->impulse)
|
|
{
|
|
|
|
if (actor->hasX())
|
|
{
|
|
if (actor->xspr.physAttr & kPhysDebrisVector) {
|
|
|
|
int impulse = DivScale(pVectorData->impulse, ClipLow(actor->spriteMass.mass, 10), 6);
|
|
actor->add_int_bvel_x(MulScale(a4, impulse, 16));
|
|
actor->add_int_bvel_y(MulScale(a5, impulse, 16));
|
|
actor->add_int_bvel_z(MulScale(a6, impulse, 16));
|
|
|
|
if (pVectorData->burnTime != 0) {
|
|
if (!actor->xspr.burnTime) evPostActor(actor, 0, kCallbackFXFlameLick);
|
|
actBurnSprite(shooter->GetOwner(), actor, pVectorData->burnTime);
|
|
}
|
|
|
|
if (actor->spr.type >= kThingBase && actor->spr.type < kThingMax) {
|
|
actor->spr.statnum = kStatThing; // temporary change statnum property
|
|
actDamageSprite(shooter, actor, pVectorData->dmgType, pVectorData->dmg << 4);
|
|
actor->spr.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) {
|
|
|
|
auto pFX2 = gFX.fxSpawnActor(pVectorData->surfHit[nSurf].fx2, pSector, x, y, z, 0);
|
|
if (pFX2 && gModernMap)
|
|
pFX2->SetOwner(shooter);
|
|
}
|
|
|
|
if (pVectorData->surfHit[nSurf].fx3 >= 0) {
|
|
|
|
auto pFX3 = gFX.fxSpawnActor(pVectorData->surfHit[nSurf].fx3, pSector, x, y, z, 0);
|
|
if (pFX3 && gModernMap)
|
|
pFX3->SetOwner(shooter);
|
|
|
|
}
|
|
|
|
#else
|
|
if (pVectorData->surfHit[nSurf].fx2 >= 0)
|
|
gFX.fxSpawnActor(pVectorData->surfHit[nSurf].fx2, pSector, x, y, z, 0);
|
|
if (pVectorData->surfHit[nSurf].fx3 >= 0)
|
|
gFX.fxSpawnActor(pVectorData->surfHit[nSurf].fx3, pSector, x, y, z, 0);
|
|
#endif
|
|
|
|
if (pVectorData->surfHit[nSurf].fxSnd >= 0)
|
|
sfxPlay3DSound(x, y, z, pVectorData->surfHit[nSurf].fxSnd, pSector);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void FireballSeqCallback(int, DBloodActor* actor)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_11, actor->sector(), actor->spr.pos, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->vel = actor->vel;
|
|
}
|
|
}
|
|
|
|
void NapalmSeqCallback(int, DBloodActor* actor)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_12, actor->sector(), actor->spr.pos, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->vel = actor->vel;
|
|
}
|
|
}
|
|
|
|
void Fx32Callback(int, DBloodActor* actor)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_32, actor->sector(), actor->spr.pos, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->vel = actor->vel;
|
|
}
|
|
}
|
|
|
|
void Fx33Callback(int, DBloodActor* actor)
|
|
{
|
|
auto pFX = gFX.fxSpawnActor(FX_33, actor->sector(), actor->spr.pos, 0);
|
|
if (pFX)
|
|
{
|
|
pFX->vel = actor->vel;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void TreeToGibCallback(int, DBloodActor* actor)
|
|
{
|
|
actor->spr.type = kThingObjectExplode;
|
|
actor->xspr.state = 1;
|
|
actor->xspr.data1 = 15;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.health = thingInfo[17].startHealth;
|
|
actor->xspr.data4 = 312;
|
|
actor->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL;
|
|
}
|
|
|
|
void DudeToGibCallback1(int, DBloodActor* actor)
|
|
{
|
|
actor->spr.type = kThingBloodChunks;
|
|
actor->xspr.data1 = 8;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.health = thingInfo[26].startHealth;
|
|
actor->xspr.data4 = 319;
|
|
actor->xspr.triggerOnce = 0;
|
|
actor->xspr.isTriggered = 0;
|
|
actor->xspr.locked = 0;
|
|
actor->xspr.TargetPos.X = PlayClock;
|
|
actor->xspr.state = 1;
|
|
}
|
|
|
|
void DudeToGibCallback2(int, DBloodActor* actor)
|
|
{
|
|
actor->spr.type = kThingBloodChunks;
|
|
actor->xspr.data1 = 3;
|
|
actor->xspr.data2 = 0;
|
|
actor->xspr.data3 = 0;
|
|
actor->xspr.health = thingInfo[26].startHealth;
|
|
actor->xspr.data4 = 319;
|
|
actor->xspr.triggerOnce = 0;
|
|
actor->xspr.isTriggered = 0;
|
|
actor->xspr.locked = 0;
|
|
actor->xspr.TargetPos.X = PlayClock;
|
|
actor->xspr.state = 1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void actPostSprite(DBloodActor* actor, int nStatus)
|
|
{
|
|
assert(nStatus >= 0 && nStatus <= kStatFree);
|
|
if (actor->spr.flags & 32)
|
|
{
|
|
for (auto& post : gPost)
|
|
if (post.sprite == actor)
|
|
{
|
|
post.status = nStatus;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
actor->spr.flags |= 32;
|
|
gPost.Push({ actor, nStatus });
|
|
}
|
|
}
|
|
|
|
void actPostProcess(void)
|
|
{
|
|
for (auto& p : gPost)
|
|
{
|
|
p.sprite->spr.flags &= ~32;
|
|
int nStatus = p.status;
|
|
if (nStatus == kStatFree)
|
|
{
|
|
if (p.sprite->spr.statnum != kStatFree)
|
|
{
|
|
evKillActor(p.sprite);
|
|
if (p.sprite->hasX()) seqKill(p.sprite);
|
|
DeleteSprite(p.sprite);
|
|
}
|
|
}
|
|
else
|
|
ChangeActorStat(p.sprite, nStatus);
|
|
}
|
|
gPost.Clear();
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void MakeSplash(DBloodActor* actor)
|
|
{
|
|
actor->spr.flags &= ~2;
|
|
actor->spr.pos.Z -= 4;
|
|
int nSurface = tileGetSurfType(actor->hit.florhit);
|
|
switch (actor->spr.type)
|
|
{
|
|
case kThingDripWater:
|
|
switch (nSurface)
|
|
{
|
|
case kSurfWater:
|
|
seqSpawn(6, actor, -1);
|
|
sfxPlay3DSound(actor, 356, -1, 0);
|
|
break;
|
|
default:
|
|
seqSpawn(7, actor, -1);
|
|
sfxPlay3DSound(actor, 354, -1, 0);
|
|
break;
|
|
}
|
|
break;
|
|
case kThingDripBlood:
|
|
seqSpawn(8, actor, -1);
|
|
sfxPlay3DSound(actor, 354, -1, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void actBurnSprite(DBloodActor* pSource, DBloodActor* pTarget, int nTime)
|
|
{
|
|
pTarget->xspr.burnTime = ClipHigh(pTarget->xspr.burnTime + nTime, pTarget->spr.statnum == kStatDude ? 2400 : 1200);
|
|
pTarget->SetBurnSource(pSource);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
FSerializer& Serialize(FSerializer& arc, const char* keyname, SPRITEHIT& w, SPRITEHIT* def)
|
|
{
|
|
if (arc.BeginObject(keyname))
|
|
{
|
|
arc("hit", w.hit)
|
|
("ceilhit", w.ceilhit)
|
|
("florhit", w.florhit)
|
|
.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.
|
|
.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
END_BLD_NS
|