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