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

7430 lines
171 KiB
C++
Raw Normal View History

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