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