//-------------------------------------------------------------------------
/*
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 <random>
//#include <iostream>

#include "ns.h"	// Must come before everything else!


#include "build.h"
#include "pragmas.h"
#include "mmulti.h"
#include "common.h"
#include "common_game.h"

#include "actor.h"
#include "ai.h"
#include "aibat.h"
#include "aibeast.h"
#include "aiboneel.h"
#include "aiburn.h"
#include "aicaleb.h"
#include "aicerber.h"
#include "aicult.h"
#include "aigarg.h"
#include "aighost.h"
#include "aigilbst.h"
#include "aihand.h"
#include "aihound.h"
#include "aiinnoc.h"
#include "aipod.h"
#include "airat.h"
#include "aispid.h"
#include "aitchern.h"
#include "aizomba.h"
#include "aizombf.h"
#include "aiunicult.h"
#include "blood.h"
#include "callback.h"
#include "config.h"
#include "db.h"
#include "endgame.h"
#include "eventq.h"
#include "fx.h"
#include "gameutil.h"
#include "gib.h"
#include "globals.h"
#include "levels.h"
#include "loadsave.h"
#include "player.h"
#include "seq.h"
#include "sfx.h"
#include "sound.h"
#include "tile.h"
#include "trig.h"
#include "triggers.h"
#include "view.h"
#include "warp.h"
#include "weapon.h"

BEGIN_BLD_NS

VECTORDATA gVectorData[] = {
    // Tine
    {
        DAMAGE_TYPE_2,
        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,
        1207, 1207,
    },
    // Shell
    {
        DAMAGE_TYPE_2,
        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,
        1001, 1001,
    },
    // Bullet
    {
        DAMAGE_TYPE_2,
        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,
        4001, 4002,
    },
    // Tommy AP
    {
        DAMAGE_TYPE_2,
        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,
        431, 431
    },
    // Shell AP
    {
        DAMAGE_TYPE_2,
        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,
        1002, 1002,
    },
    // Tommy regular
    {
        DAMAGE_TYPE_2,
        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,
        359, 359,
    },
    // Bat bite
    {
        DAMAGE_TYPE_2,
        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,
        521, 521,
    },
    // Eel bite
    {
        DAMAGE_TYPE_2,
        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,
        513, 513
    },
    // Gill bite
    {
        DAMAGE_TYPE_2,
        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,
        499, 499
    },
            // Beast slash
    {
        DAMAGE_TYPE_3,
        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,
        9012, 9014
    },
            // Axe
    {
        DAMAGE_TYPE_2,
        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,
        1101, 1101
    },
            // Cleaver
    {
        DAMAGE_TYPE_2,
        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,
        1207, 1207
    },
            // Phantasm slash
    {
        DAMAGE_TYPE_2,
        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,
        499, 495
    },
            // Gargoyle Slash
    {
        DAMAGE_TYPE_2,
        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,
        495, 496
    },
            // Cerberus bite
    {
        DAMAGE_TYPE_2,
        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,
        9013, 499
    },
            // Hound bite
    {
        DAMAGE_TYPE_2,
        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,
        1307, 1308
    },
            // Rat bite
    {
        DAMAGE_TYPE_2,
        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,
        499, 499
    },
            // Spider bite
    {
        DAMAGE_TYPE_2,
        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,
        499, 499
    },
            // Unk
    {
        DAMAGE_TYPE_2,
        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,
        499, 499
    },
    
    {
        (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,
        499, 499
    },
    // Tchernobog burn vector
    {
        DAMAGE_TYPE_1,
        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,
        351, 351
    },
    // Vodoo 1.0 vector
    {
        DAMAGE_TYPE_5,
        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,
        0,0
    },
    // 22 kVectorGenDudePunch
    {
    DAMAGE_TYPE_0, 
        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,
        357, 499
    },
};

ITEMDATA gItemData[] = {
    {
        0,
        2552,
        (char)-8,
        0,
        32,
        32,
        -1,
    },
    {
        0,
        2553,
        (char)-8,
        0,
        32,
        32,
        -1,
    },
    {
        0,
        2554,
        (char)-8,
        0,
        32,
        32,
        -1,
    },
    {
        0,
        2555,
        (char)-8,
        0,
        32,
        32,
        -1,
    },
    {
        0,
        2556,
        (char)-8,
        0,
        32,
        32,
        -1,
    },
    {
        0,
        2557,
        (char)-8,
        0,
        32,
        32,
        -1,
    },
    {
        0,
        -1,
        (char)-8,
        0,
        255,
        255,
        -1,
    },
    {
        0,
        519,
        (char)-8,
        0,
        48,
        48,
        0,
    },
    {
        0,
        822,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        2169,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        2433,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        517,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        783,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        896,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        825,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        827,
        (char)-8,
        0,
        40,
        40,
        4,
    },
    {
        0,
        828,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        829,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        830,
        (char)-8,
        0,
        80,
        64,
        1,
    },
    {
        0,
        831,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        863,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        760,
        (char)-8,
        0,
        40,
        40,
        2,
    },
    {
        0,
        836,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        851,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        2428,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        839,
        (char)-8,
        0,
        40,
        40,
        3,
    },
    {
        0,
        768,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        840,
        (char)-8,
        0,
        48,
        48,
        -1,
    },
    {
        0,
        841,
        (char)-8,
        0,
        48,
        48,
        -1,
    },
    {
        0,
        842,
        (char)-8,
        0,
        48,
        48,
        -1,
    },
    {
        0,
        843,
        (char)-8,
        0,
        48,
        48,
        -1,
    },
    {
        0,
        683,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        521,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        604,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        520,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        803,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        518,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        522,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        523,
        (char)-8,
        0,
        40,
        40,
        -1,
    },
    {
        0,
        837,
        (char)-8,
        0,
        80,
        64,
        -1,
    },
    {
        0,
        2628,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        2586,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        2578,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        2602,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        2594,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        753,
        (char)-8,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        753,
        (char)-8,
        7,
        64,
        64,
        -1,
    },
    {
        0,
        3558,
        (char)-128,
        0,
        64,
        64,
        -1,
    },
    {
        0,
        3558,
        (char)-128,
        7,
        64,
        64,
        -1,
    }
};

AMMOITEMDATA gAmmoItemData[] = {
    {
        0,
        618,
        (char)-8,
        0,
        40,
        40,
        480,
        6,
        7
    },
    {
        0,
        589,
        (char)-8,
        0,
        48,
        48,
        1,
        5,
        6
    },
    {
        0,
        589,
        (char)-8,
        0,
        48,
        48,
        1,
        5,
        6
    },
    {
        0,
        809,
        (char)-8,
        0,
        48,
        48,
        5,
        5,
        6
    },
    {
        0,
        811,
        (char)-8,
        0,
        48,
        48,
        1,
        10,
        11
    },
    {
        0,
        810,
        (char)-8,
        0,
        48,
        48,
        1,
        11,
        12
    },
    {
        0,
        820,
        (char)-8,
        0,
        24,
        24,
        10,
        8,
        0
    },
    {
        0,
        619,
        (char)-8,
        0,
        48,
        48,
        4,
        2,
        0
    },
    {
        0,
        812,
        (char)-8,
        0,
        48,
        48,
        15,
        2,
        0
    },
    {
        0,
        813,
        (char)-8,
        0,
        48,
        48,
        15,
        3,
        0
    },
    {
        0,
        525,
        (char)-8,
        0,
        48,
        48,
        100,
        9,
        10
    },
    {
        0,
        814,
        (char)-8,
        0,
        48,
        48,
        15,
        255,
        0
    },
    {
        0,
        817,
        (char)-8,
        0,
        48,
        48,
        100,
        3,
        0
    },
    {
        0,
        548,
        (char)-8,
        0,
        24,
        24,
        32,
        7,
        0
    },
    {
        0,
        0,
        (char)-8,
        0,
        48,
        48,
        6,
        255,
        0
    },
    {
        0,
        0,
        (char)-8,
        0,
        48,
        48,
        6,
        255,
        0
    },
    {
        0,
        816,
        (char)-8,
        0,
        48,
        48,
        8,
        1,
        0
    },
    {
        0,
        818,
        (char)-8,
        0,
        48,
        48,
        8,
        255,
        0
    },
    {
        0,
        819,
        (char)-8,
        0,
        48,
        48,
        8,
        255,
        0
    },
    {
        0,
        801,
        (char)-8,
        0,
        48,
        48,
        6,
        4,
        0
    },
    {
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    },
};

WEAPONITEMDATA gWeaponItemData[] = {
    {
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        0,
        -1,
        0
    },
    {
        0,
        559,
        (char)-8,
        0,
        48,
        48,
        3,
        2,
        8
    },
    {
        0,
        558,
        (char)-8,
        0,
        48,
        48,
        4,
        3,
        50
    },
    {
        0,
        524,
        (char)-8,
        0,
        48,
        48,
        2,
        1,
        9
    },
    {
        0,
        525,
        (char)-8,
        0,
        48,
        48,
        10,
        9,
        100
    },
    {
        0,
        539,
        (char)-8,
        0,
        48,
        48,
        8,
        7,
        64
    },
    {
        0,
        526,
        (char)-8,
        0,
        48,
        48,
        5,
        4,
        6
    },
    {
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        1,
        -1,
        0
    },
    {
        0,
        618,
        (char)-8,
        0,
        48,
        48,
        7,
        6,
        480
    },
    {
        0,
        589,
        (char)-8,
        0,
        48,
        48,
        6,
        5,
        1
    },
    {
        0,
        800,
        (char)-8,
        0,
        48,
        48,
        9,
        8,
        35
    }
};

MissileType missileInfo[] = {
    // Cleaver
    {
        2138,
        978670,
        512,
        40,
        40,
        (char)-16,
        16,
        1207, 1207
    },
    // Regular flare
    {
        2424,
        3145728,
        0,
        32,
        32,
        (char)-128,
        32,
        420, 420
    },
    // Tesla alt
    {
        3056,
        2796202,
        0,
        32,
        32,
        (char)-128,
        32,
        471, 471
    },
    // Flare alt
    {
        2424,
        2446677,
        0,
        32,
        32,
        (char)-128,
        4,
        421, 421
    },
    // Spray flame
    {
        0,
        1118481,
        0,
        24,
        24,
        (char)-128,
        16,
        1309, 351
    },
    // Fireball
    {
        0,
        1118481,
        0,
        32,
        32,
        (char)-128,
        32,
        480, 480
    },
    // Tesla regular
    {
        2130,
        2796202,
        0,
        32,
        32,
        (char)-128,
        16,
        470, 470
    },
    // EctoSkull
    {
        870,
        699050,
        0,
        32,
        32,
        (char)-24,
        32,
        489, 490
    },
    // Hellhound flame
    {
        0,
        1118481,
        0,
        24,
        24,
        (char)-128,
        16,
        462, 351
    },
    // Puke
    {
        0,
        838860,
        0,
        16,
        16,
        (char)-16,
        16,
        1203, 172
    },
    // Reserved
    {
        0,
        838860,
        0,
        8,
        8,
        (char)0,
        16,
        0,0
    },
    // Stone gargoyle projectile
    {
        3056,
        2097152,
        0,
        32,
        32,
        (char)-128,
        16,
        1457, 249
    },
    // Napalm launcher
    {
        0,
        2446677,
        0,
        30,
        30,
        (char)-128,
        24,
        480, 489
    },
    // Cerberus fireball
    {
        0,
        2446677,
        0,
        30,
        30,
        (char)-128,
        24,
        480, 489
    },
    // Tchernobog fireball
    {
        0,
        1398101,
        0,
        24,
        24,
        (char)-128,
        16,
        480, 489
    },
    // Regular life leech
    {
        2446,
        2796202,
        0,
        32,
        32,
        (char)-128,
        16,
        491, 491
    },
    // Dropped life leech (enough ammo)
    {
        3056,
        2446677,
        0,
        16,
        16,
        (char)-128,
        16,
        520, 520
    },
    // Dropped life leech (no ammo)
    {
        3056,
        1747626,
        0,
        32,
        32,
        (char)-128,
        16,
        520, 520
    }
};

THINGINFO thingInfo[] = {
    //TNT Barrel
    {
        25,
        250,
        32,
        11,
        4096,
        80,
        384,
        907,
        (char)0,
        0,
        0,
        0,
        256, 256, 128, 64, 0, 0, 128,
        1
    },
    // Armed Proxy Dynamite
    {
        5,
        5,
        16,
        3,
        24576,
        1600,
        256,
        3444,
        (char)-16,
        0,
        32,
        32,
        256, 256, 256, 64, 0, 0, 512,
        1
    },
    // Armed Remote Dynamite
    {
        5,
        5,
        16,
        3,
        24576,
        1600,
        256,
        3457,
        (char)-16,
        0,
        32,
        32,
        256, 256, 256, 64, 0, 0, 512,
        1
    },
    // Vase1
    {
        1,
        20,
        32,
        3,
        32768,
        80,
        0,
        739,
        (char)0,
        0,
        0,
        0,
        256, 0, 256, 128, 0, 0, 0,
        0
    },
    // Vase2
    {
        1,
        150,
        32,
        3,
        32768,
        80,
        0,
        642,
        (char)0,
        0,
        0,
        0,
        256, 256, 256, 128, 0, 0, 0,
        0
    },
    // Crate face
    {
        10,
        0,
        0,
        0,
        0,
        0,
        0,
        462,
        (char)0,
        0,
        0,
        0,
        0, 0, 0, 256, 0, 0, 0,
        0
    },
    // Glass window
    {
        1,
        0,
        0,
        0,
        0,
        0,
        0,
        266,
        (char)0,
        0,
        0,
        0,
        256, 0, 256, 256, 0, 0, 0,
        0,
    },
    // Flourescent Light
    {
        1,
        0,
        0,
        0,
        0,
        0,
        0,
        796,
        (char)0,
        0,
        0,
        0,
        256, 0, 256, 256, 0, 0, 512,
        0,
    },
    // Wall Crack
    {
        50,
        0,
        0,
        0,
        0,
        0,
        0,
        1127,
        (char)0,
        0,
        0,
        0,
        0, 0, 0, 256, 0, 0, 0,
        0,
    },
    // Wood Beam
    {
        8,
        0,
        0,
        0,
        0,
        0,
        0,
        1142,
        (char)0,
        0,
        0,
        0,
        256, 0, 256, 128, 0, 0, 0,
        0,
    },
    // Spider's Web
    {
        4,
        0,
        0,
        0,
        0,
        0,
        0,
        1069,
        (char)0,
        0,
        0,
        0,
        256, 256, 64, 256, 0, 0, 128,
        0,
    },
    // Metal Grate
    {
        40,
        0,
        0,
        0,
        0,
        0,
        0,
        483,
        (char)0,
        0,
        0,
        0,
        64, 0, 128, 256, 0, 0, 0,
        0,
    },
    // Flammable Tree
    {
        1,
        0,
        0,
        0,
        0,
        0,
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        0, 256, 0, 256, 0, 0, 128,
        0,
    },
    // MachineGun Trap
    {
        1000,
        0,
        0,
        8,
        0,
        0,
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        0, 0, 128, 256, 0, 0, 512,
        0,
    },
    // Falling Rock
    {
        0,
        15,
        8,
        3,
        32768,
        0,
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        0, 0, 0, 0, 0, 0, 0,
        0,
    },
    // Kickable Pail
    {
        0,
        8,
        48,
        3,
        49152,
        0,
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        0, 0, 0, 0, 0, 0, 0,
        1,
    },
    // Gib Object
    {
        10,
        2,
        0,
        0,
        32768,
        0,
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        256, 0, 256, 256, 0, 0, 128,
        0,
    },
    // Explode Object
    {
        20,
        2,
        0,
        0,
        32768,
        0,
        0,
        -1,
        (char)0,
        0,
        0,
        0,
        0, 0, 0, 256, 0, 0, 128,
        0,
    },
    // Armed stick Of TNT
    {
        5,
        14,
        16,
        3,
        24576,
        1600,
        256,
        3422,
        (char)-32,
        0,
        32,
        32,
        64, 256, 128, 64, 0, 0, 256,
        1
    },
    // Armed bundle Of TNT
    {
        5,
        14,
        16,
        3,
        24576,
        1600,
        256,
        3433,
        (char)-32,
        0,
        32,
        32,
        64, 256, 128, 64, 0, 0, 256,
        1
    },
    // Armed aerosol
    {
        5,
        14,
        16,
        3,
        32768,
        1600,
        256,
        3467,
        (char)-128,
        0,
        32,
        32,
        64, 256, 128, 64, 0, 0, 256,
        1
    },
    // Bone (Flesh Garg.)
    {
        5,
        6,
        16,
        3,
        32768,
        1600,
        256,
        1462,
        (char)0,
        0,
        32,
        32,
        0, 0, 0, 0, 0, 0, 0,
        1
    },
    // Some alpha stuff
    {
        8,
        3,
        16,
        11,
        32768,
        1600,
        256,
        -1,
        (char)0,
        0,
        0,
        0,
        256, 0, 256, 256, 0, 0, 0,
        0,
    },
    // WaterDrip 
    {
        0,
        1,
        1,
        2,
        0,
        0,
        0,
        1147,
        (char)0,
        10,
        0,
        0,
        0, 0, 0, 0, 0, 0, 0,
        0
    },
    // BloodDrip 
    {
        0,
        1,
        1,
        2,
        0,
        0,
        0,
        1160,
        (char)0,
        2,
        0,
        0,
        0, 0, 0, 0, 0, 0, 0,
        0,
    },
    // Blood chucks1 
    {
        15,
        4,
        4,
        3,
        24576,
        0,
        257,
        -1,
        (char)0,
        0,
        0,
        0,
        128, 64, 256, 256, 0, 0, 256,
        0,
    },
    // Blood chucks2
    {
        30,
        30,
        8,
        3,
        8192,
        0,
        257,
        -1,
        (char)0,
        0,
        0,
        0,
        128, 64, 256, 256, 0, 0, 64,
        0,
    },
    // Axe Zombie Head 
    {
        60,
        5,
        32,
        3,
        40960,
        1280,
        257,
        3405,
        (char)0,
        0,
        40,
        40,
        128, 64, 256, 256, 0, 0, 64,
        1,
    },
    // Napalm's Alt Fire explosion
    {
        80,
        30,
        32,
        3,
        57344,
        1600,
        256,
        3281,
        (char)-128,
        0,
        32,
        32,
        0, 0, 0, 0, 0, 0, 0,
        1,
    },
    // Fire Pod Explosion
    {
        80,
        30,
        32,
        3,
        57344,
        1600,
        256,
        2020,
        (char)-128,
        0,
        32,
        32,
        256, 0, 256, 256, 0, 0, 0,
        1,
    },
    // Green Pod Explosion
    {
        80,
        30,
        32,
        3,
        57344,
        1600,
        256,
        1860,
        (char)-128,
        0,
        32,
        32,
        256, 0, 256, 256, 0, 0, 0,
        1,
    },
    // Life Leech
    {
        150,
        30,
        48,
        3,
        32768,
        1600,
        257,
        800,
        (char)-128,
        0,
        48,
        48,
        64, 64, 112, 64, 0, 96, 96,
        1,
    },
    // Voodoo Head
    {
        1,
        30,
        48,
        3,
        32768,
        1600,
        0,
        2443,
        (char)-128,
        0,
        16,
        16,
        0, 0, 0, 0, 0, 0, 0,
        1,
    },
    // 433 - kGDXThingTNTProx
    {
        5,
        5,
        16,
        3,
        24576,
        1600,
        256,
        3444,
        (char)-16,
        7,
        32,
        32,
        256, 256, 256, 64, 0, 0, 512,
        1
    },
    // 434 - kGDXThingThrowableRock
    {
        5,
        6,
        16,
        3,
        32768,
        1600,
        256,
        1462,
        (char)0,
        0,
        32,
        32,
        0, 0, 0, 0, 0, 0, 0,
        1
    },
    // 435 - kGDXThingCustomDudeLifeLeech
    {
        150,
        30,
        48,
        3,
        32768,
        1600,
        257,
        800,
        (char)-128,
        0,
        48,
        48,
        64, 64, 112, 64, 0, 96, 96,
        1,
    },
};

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
    },
};

int gDudeDrag = 0x2a00;

short gAffectedSectors[kMaxSectors];
short gAffectedXWalls[kMaxXWalls];
short gPlayerGibThingComments[] = {
    734, 735, 736, 737, 738, 739, 740, 741, 3038, 3049
};

void FireballSeqCallback(int, int);
void sub_38938(int, int);
void NapalmSeqCallback(int, int);
void sub_3888C(int, int);
void TreeToGibCallback(int, int);
void DudeToGibCallback1(int, int);
void DudeToGibCallback2(int, int);

int nFireballClient = seqRegisterClient(FireballSeqCallback);
int dword_2192D8 = seqRegisterClient(sub_38938);
int nNapalmClient = seqRegisterClient(NapalmSeqCallback);
int dword_2192E0 = seqRegisterClient(sub_3888C);
int nTreeToGibClient = seqRegisterClient(TreeToGibCallback);
int nDudeToGibClient1 = seqRegisterClient(DudeToGibCallback1);
int nDudeToGibClient2 = seqRegisterClient(DudeToGibCallback2);

int gPostCount = 0;

struct POSTPONE {
    short at0;
    short at2;
};

POSTPONE gPost[kMaxSprites];

static char buffer[120];

bool IsItemSprite(spritetype *pSprite)
{
    return pSprite->type >= 100 && pSprite->type < 149;
}

bool IsWeaponSprite(spritetype *pSprite)
{
    return pSprite->type >= 40 && pSprite->type < 51;
}

bool IsAmmoSprite(spritetype *pSprite)
{
    return pSprite->type >= 60 && pSprite->type < 81;
}

bool IsUnderwaterSector(int nSector)
{
    int nXSector = sector[nSector].extra;
    if (nXSector > 0 && xsector[nXSector].Underwater)
        return 1;
    return 0;
}

int actSpriteOwnerToSpriteId(spritetype *pSprite)
{
    dassert(pSprite != NULL);
    if (pSprite->owner == -1)
        return -1;
    int nSprite = pSprite->owner & (kMaxSprites-1);
    if (pSprite->owner & kMaxSprites)
        nSprite = gPlayer[nSprite].pSprite->index;
    return nSprite;
}

void actPropagateSpriteOwner(spritetype *pTarget, spritetype *pSource)
{
    dassert(pTarget != NULL && pSource != NULL);
    if (IsPlayerSprite(pSource))
        pTarget->owner = (pSource->type-kDudePlayer1) | kMaxSprites;
    else
        pTarget->owner = pSource->index;
}

int actSpriteIdToOwnerId(int nSprite)
{
    if (nSprite == -1)
        return -1;
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    spritetype *pSprite = &sprite[nSprite];
    if (IsPlayerSprite(pSprite))
        nSprite = (pSprite->type-kDudePlayer1) | kMaxSprites;
    return nSprite;
}

int actOwnerIdToSpriteId(int nSprite)
{
    if (nSprite == -1)
        return -1;
    if (nSprite & kMaxSprites)
        nSprite = gPlayer[nSprite&(kMaxSprites-1)].pSprite->index;
    return nSprite;
}

bool actTypeInSector(int nSector, int nType)
{
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        if (sprite[nSprite].index == nType)
            return 1;
    }
    return 0;
}

void actAllocateSpares(void)
{
}

int DudeDifficulty[5] = {
    512, 384, 256, 208, 160
};

SPRITEMASS gSpriteMass[]; // by NoOne: cache for getSpriteMassBySize();

short gProxySpritesList[]; // by NoOne: list of additional sprites which can be triggered by Proximity
short gProxySpritesCount; // current count

short gSightSpritesList[]; // by NoOne: list of additional sprites which can be triggered by Sight
short gSightSpritesCount; // current count

short gPhysSpritesList[];  // by NoOne: list of additional sprites which can be affected by physics
short gPhysSpritesCount; // current count

void actInit(bool bSaveLoad) {
    
    // by NoOne: init code for all my stuff
    if (!VanillaMode()) {
        
        // reset counters
        gProxySpritesCount = gSightSpritesCount = gPhysSpritesCount = 0;

        // fill arrays with negative values to avoid xvel 0 situation
        memset(gSightSpritesList, -1, sizeof(gSightSpritesList));
        memset(gProxySpritesList, -1, sizeof(gProxySpritesList));
        memset(gPhysSpritesList, -1, sizeof(gPhysSpritesList));

        for (int i = 0; i < kMaxXSprites; i++) {

            if (xsprite[i].reference < 0) continue;
            XSPRITE* pXSprite = &xsprite[i];  spritetype* pSprite = &sprite[pXSprite->reference];

            switch (pSprite->type) {
                // add statnum for faster dude searching
                case kGDXDudeTargetChanger:
                    changespritestat(i, kStatGDXDudeTargetChanger);
                    break;
                // remove kStatItem status from random item generators
                case 40: // Random weapon
                case 80: // Random ammo
                    changespritestat(i, 0);
                    break;
                case kCustomDude:
                case kCustomDudeBurning:
                    getSpriteMassBySize(pSprite); // create mass cache
                    break;
            }
            
            // init after loading save file
            if (bSaveLoad) {

                // add in list of physics affected sprites
                if (pXSprite->physAttr != 0) {
                    gPhysSpritesList[gPhysSpritesCount++] = pSprite->index; // add sprite index
                    getSpriteMassBySize(pSprite); // create mass cache
                }
                
                if (pXSprite->data3 != pXSprite->sysData1) {
                    switch (pSprite->statnum) {
                        case 6:
                            switch (pSprite->type) {
                                case kCustomDude:
                                case kCustomDudeBurning:
                                    pXSprite->data3 = pXSprite->sysData1; // move sndStartId back from sysData1 to data3 
                                    break;
                            }
                            break;
                    }
                }
            }

            // make Proximity flag work not just for dudes and things...
            if (pXSprite->Proximity && gProxySpritesCount < kMaxSuperXSprites) {
                switch (pSprite->statnum) {
                    // exceptions
                    case 4: // things already treated in their functions
                    case 6: // enemies already treated in their functions
                        // senseless to have sight and proximity together
                        if (pXSprite->Sight && pXSprite->DudeLockout) pXSprite->Proximity = false;
                        break;
                    case 1: // effects
                    case 2: // explosions
                    case 3: // items
                    case 9: // purgeable sprites
                    case 13: // ???
                    case 14: // burning flares stuck
                    case 7: // inactive enemies
                    case kStatFree: // removed sprites
                    case kStatMarker: // markers
                    case 16: // path markers
                        break;
                    default:
                        // senseless to have sight and proximity together
                        if (pXSprite->Sight && pXSprite->DudeLockout) pXSprite->Proximity = false;
                        else {
                            gProxySpritesList[gProxySpritesCount++] = pSprite->xvel;
                            if (gProxySpritesCount == kMaxSuperXSprites)
                                ThrowError("Max (%d) *additional* Proximity sprites reached!",kMaxSuperXSprites);
                        }
                        break;
                }
            }

            // make Sight flag work not just for dudes and things...
            if (pXSprite->Sight && gSightSpritesCount < kMaxSuperXSprites) {
                switch (pSprite->statnum) {
                    // exceptions
                    case 1: // effects
                    case 2: // explosions
                    case 3: // items
                    case 9: // purgeable sprites
                    case 13: // ???
                    case 14: // burning flares stuck
                    case 7: // inactive enemies
                    case kStatFree: // removed sprites
                    case kStatMarker: // markers
                    case 16: // path markers
                        break;
                    default:
                        gSightSpritesList[gSightSpritesCount++] = pSprite->xvel;
                        if (gSightSpritesCount == kMaxSuperXSprites)
                            ThrowError("Max (%d) Sight sprites reached!", kMaxSuperXSprites);
                        break;
                }
            }
        }
    }
    
    for (int nSprite = headspritestat[3]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->type == 44) // Voodoo doll (ammo)
            pSprite->type = 70; // Voodoo doll (weapon)

    }
    for (int nSprite = headspritestat[11]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        int nXSprite = pSprite->extra;
        XSPRITE *pXSprite = NULL;
        if (nXSprite > 0 && nXSprite < kMaxXSprites)
            pXSprite = &xsprite[nXSprite];
        if (pSprite->type == 459)
        {
            pXSprite->state = 0;
            pXSprite->waitTime = ClipLow(pXSprite->waitTime, 1);
            pSprite->cstat &= ~1;
            pSprite->cstat |= 32768;
        }
    }
    for (int nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        int nXSprite = pSprite->extra;
        if (nXSprite <= 0 || nXSprite >= kMaxXSprites)
            ThrowError("WARNING: Sprite %d is on the wrong status list!\n", nSprite);
        XSPRITE *pXSprite = &xsprite[nXSprite];
        int nType = pSprite->type - 400;
        pSprite->clipdist = thingInfo[nType].at4;
        pSprite->flags = thingInfo[nType].at5;
        if (pSprite->flags&2)
            pSprite->flags |= 4;
        xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
        pXSprite->health = thingInfo[nType].at0<<4;
        switch (pSprite->type)
        {
        case 401:
        case 413:
        case kGDXThingTNTProx:
            pXSprite->state = 0;
            break;
        case 426:
        {
            SEQINST *pInst = GetInstance(3, nXSprite);
            if (pInst && pInst->at13)
            {
                DICTNODE *hSeq = gSysRes.Lookup(pInst->at8, "SEQ");
                if (!hSeq)
                    break;
                seqSpawn(pInst->at8, 3, nXSprite);
            }
            break;
        }
        default:
            pXSprite->state = 1;
            break;
        }
    }
    if (gGameOptions.nMonsterSettings == 0)
    {
        gKillMgr.SetCount(0);
        while (headspritestat[6] >= 0)
        {
            spritetype *pSprite = &sprite[headspritestat[6]];
            int nXSprite = pSprite->extra;
            dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
            XSPRITE *pXSprite = &xsprite[nXSprite];
            // Drop Key
            if (pXSprite->key > 0)
                actDropObject(pSprite, 99 + pXSprite->key);
            DeleteSprite(headspritestat[6]);
        }
    }
    else
    {
        // by NoOne: WTF is this?
        ///////////////
        char unk[kDudeMax-kDudeBase];
        memset(unk, 0, sizeof(unk));
        for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
        {
            spritetype *pSprite = &sprite[nSprite];
            if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax)
                ThrowError("Non-enemy sprite (%d) in the enemy sprite list.\n", nSprite);
            unk[pSprite->type-kDudeBase] = 1;
        }
        
        gKillMgr.sub_2641C();
        ///////////////

        for (int i = 0; i < kDudeMax-kDudeBase; i++)
            for (int j = 0; j < 7; j++)
                dudeInfo[i].at70[j] = mulscale8(DudeDifficulty[gGameOptions.nDifficulty], dudeInfo[i].startDamage[j]);

        for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
            spritetype *pSprite = &sprite[nSprite];
            int nXSprite = pSprite->extra;
            dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
            XSPRITE *pXSprite = &xsprite[nXSprite];
            int nType = pSprite->type-kDudeBase;
            int seqStartId = dudeInfo[nType].seqStartID;
            if (!IsPlayerSprite(pSprite)) {

                switch (pSprite->type) {
                    case 225:  // by NoOne: FakeDude type (no seq, custom flags, clipdist and cstat)
                        break;
                    case kCustomDude:
                    case kCustomDudeBurning:
                        pSprite->cstat |= 4096 + 256 + 1;
                        seqStartId = getSeqStartId(pXSprite); // by NoOne: Custom Dude stores it's SEQ in data2
                        pXSprite->sysData1 = pXSprite->data3; // by NoOne move sndStartId to sysData1, because data3 used by the game;
                        pXSprite->data3 = 0;
                        break;
                    default:
                        pSprite->clipdist = dudeInfo[nType].clipdist;
                        pSprite->cstat |= 4096 + 256 + 1;
                        break;
                }

                xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
                
                // By NoOne: add a way to set custom hp for every enemy - should work only if map just started and not loaded.
                if (pXSprite->data4 <= 0) pXSprite->health = dudeInfo[nType].startHealth << 4;
                else pXSprite->health = ClipRange(pXSprite->data4 << 4, 1, 65535);

            }

            if (gSysRes.Lookup(seqStartId, "SEQ")) seqSpawn(seqStartId, 3, nXSprite);
        }
        aiInit();
    }
}

void ConcussSprite(int a1, spritetype *pSprite, int x, int y, int z, int a6)
{
    dassert(pSprite != NULL);
    int dx = pSprite->x-x;
    int dy = pSprite->y-y;
    int dz = (pSprite->z-z)>>4;
    int dist2 = 0x40000+dx*dx+dy*dy+dz*dz;
    dassert(dist2 > 0);
    a6 = scale(0x40000, a6, dist2);
    if (pSprite->flags & 1)
    {
        int mass = 0;
        if (IsDudeSprite(pSprite)) {
            mass = dudeInfo[pSprite->type - kDudeBase].mass;
            switch (pSprite->type) {
            case kCustomDude:
            case kCustomDudeBurning:
                mass = getSpriteMassBySize(pSprite);
                break;
            }
        }
        else if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
            mass = thingInfo[pSprite->type-400].at2;
        else
            ThrowError("Unexpected type in ConcussSprite(): Sprite: %d  Type: %d  Stat: %d", (int)pSprite->index, (int)pSprite->type, (int)pSprite->statnum);
        int size = (tilesiz[pSprite->picnum].x*pSprite->xrepeat*tilesiz[pSprite->picnum].y*pSprite->yrepeat)>>1;
        dassert(mass > 0);

        int t = scale(a6, size, mass);
        dx = mulscale16(t, dx);
        dy = mulscale16(t, dy);
        dz = mulscale16(t, dz);
        int nSprite = pSprite->index;
        dassert(nSprite >= 0 && nSprite < kMaxSprites);
        xvel[nSprite] += dx;
        yvel[nSprite] += dy;
        zvel[nSprite] += dz;
    }
    actDamageSprite(a1, pSprite, DAMAGE_TYPE_3, a6);
}

int actWallBounceVector(int *x, int *y, int nWall, int a4)
{
    int wx, wy;
    GetWallNormal(nWall, &wx, &wy);
    int t = dmulscale16(*x, wx, *y, wy);
    int t2 = mulscale16r(t, a4+0x10000);
    *x -= mulscale16(wx, t2);
    *y -= mulscale16(wy, t2);
    return mulscale16r(t, 0x10000-a4);
}

int actFloorBounceVector(int *x, int *y, int *z, int nSector, int a5)
{
    int t = 0x10000-a5;
    if (sector[nSector].floorheinum == 0)
    {
        int t2 = mulscale16(*z, t);
        *z = -(*z-t2);
        return t2;
    }
    walltype *pWall = &wall[sector[nSector].wallptr];
    walltype *pWall2 = &wall[pWall->point2];
    int angle = getangle(pWall2->x-pWall->x, pWall2->y-pWall->y)+512;
    int t2 = sector[nSector].floorheinum<<4;
    int t3 = approxDist(-0x10000, t2);
    int t4 = divscale16(-0x10000, t3);
    int t5 = divscale16(t2, t3);
    int t6 = mulscale30(t5, Cos(angle));
    int t7 = mulscale30(t5, Sin(angle));
    int t8 = tmulscale16(*x, t6, *y, t7, *z, t4);
    int t9 = mulscale16(t8, 0x10000+a5);
    *x -= mulscale16(t6, t9);
    *y -= mulscale16(t7, t9);
    *z -= mulscale16(t4, t9);
    return mulscale16r(t8, t);
}

void sub_2A620(int nSprite, int x, int y, int z, int nSector, int nDist, int a7, int a8, DAMAGE_TYPE a9, int a10, int a11, int a12, int a13)
{
    UNREFERENCED_PARAMETER(a12);
    UNREFERENCED_PARAMETER(a13);
    char va0[(kMaxSectors+7)>>3];
    int nOwner = actSpriteIdToOwnerId(nSprite);
    gAffectedSectors[0] = 0;
    gAffectedXWalls[0] = 0;
    GetClosestSpriteSectors(nSector, x, y, nDist, gAffectedSectors, va0, gAffectedXWalls);
    nDist <<= 4;
    if (a10 & 2)
    {
        for (int i = headspritestat[6]; i >= 0; i = nextspritestat[i])
        {
            if (i != nSprite || (a10 & 1))
            {
                spritetype *pSprite2 = &sprite[i];
                if (pSprite2->extra > 0 && pSprite2->extra < kMaxXSprites)
                {
                    if (pSprite2->flags & 0x20)
                        continue;
                    if (!TestBitString(va0, pSprite2->sectnum))
                        continue;
                    if (!CheckProximity(pSprite2, x, y, z, nSector, nDist))
                        continue;
                    int dx = klabs(x-pSprite2->x);
                    int dy = klabs(y-pSprite2->y);
                    int dz = klabs(z-pSprite2->z)>>4;
                    int dist = ksqrt(dx*dx+dy*dy+dz*dz);
                    if (dist > nDist)
                        continue;
                    int vcx;
                    if (dist != 0)
                        vcx = a7+((nDist-dist)*a8)/nDist;
                    else
                        vcx = a7+a8;
                    actDamageSprite(nSprite, pSprite2, a9, vcx<<4);
                    if (a11)
                        actBurnSprite(nOwner, &xsprite[pSprite2->extra], a11);
                }
            }
        }
    }
    if (a10 & 4)
    {
        for (int i = headspritestat[4]; i >= 0; i = nextspritestat[i])
        {
            spritetype *pSprite2 = &sprite[i];
            if (pSprite2->flags&0x20)
                continue;
            if (!TestBitString(va0, pSprite2->sectnum))
                continue;
            if (!CheckProximity(pSprite2, x, y, z, nSector, nDist))
                continue;
            XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
            if (pXSprite2->locked)
                continue;
            int dx = klabs(x-pSprite2->x);
            int dy = klabs(y-pSprite2->y);
            int dist = ksqrt(dx*dx+dy*dy);
            if (dist > nDist)
                continue;
            int vcx;
            if (dist != 0)
                vcx = a7+((nDist-dist)*a8)/nDist;
            else
                vcx = a7+a8;
            actDamageSprite(nSprite, pSprite2, a9, vcx<<4);
            if (a11)
                actBurnSprite(nOwner, pXSprite2, a11);
        }
    }
}

void sub_2AA94(spritetype *pSprite, XSPRITE *pXSprite)
{
    int nSprite = actOwnerIdToSpriteId(pSprite->owner);
    actPostSprite(pSprite->index, 0);
    seqSpawn(9, 3, pSprite->extra);
    if (Chance(0x8000))
        pSprite->cstat |= 4;
    sfxPlay3DSound(pSprite, 303, 24+(pSprite->flags&3), 1);
    sub_2A620(nSprite, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 128, 0, 60, DAMAGE_TYPE_3, 15, 120, 0, 0);
    if (pXSprite->data4 > 1)
    {
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        int v14[2];
        v14[0] = pXSprite->data4>>1;
        v14[1] = pXSprite->data4-v14[0];
        int v4 = pSprite->ang;
        xvel[pSprite->index] = 0;
        yvel[pSprite->index] = 0;
        zvel[pSprite->index] = 0;
        for (int i = 0; i < 2; i++)
        {
            int t1 = Random(0x33333)+0x33333;
            int t2 = Random2(0x71);
            pSprite->ang = (t2+v4+2048)&2047;
            spritetype *pSprite2 = actFireThing(pSprite, 0, 0, -0x93d0, 428, t1);
            XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
            pSprite2->owner = pSprite->owner;
            seqSpawn(61, 3, pSprite2->extra, nNapalmClient);
            pXSprite2->data4 = v14[i];
        }
    }
}

spritetype *actSpawnFloor(spritetype *pSprite)
{
    short nSector = pSprite->sectnum;
    int x = pSprite->x;
    int y = pSprite->y;
    updatesector(x, y, &nSector);
    int zFloor = getflorzofslope(nSector, x, y);
    spritetype *pSprite2 = actSpawnSprite(nSector, x, y, zFloor, 3, 0);
    if (pSprite2)
        pSprite2->cstat &= ~257;
    return pSprite2;
}

spritetype *actDropAmmo(spritetype *pSprite, int nType)
{
    spritetype *pSprite2 = NULL;
    if (pSprite && pSprite->statnum < kMaxStatus && nType >= 60 && nType < 81)
    {
        pSprite2 = actSpawnFloor(pSprite);
        AMMOITEMDATA *pAmmo = &gAmmoItemData[nType-60];
        pSprite2->type = nType;
        pSprite2->picnum = pAmmo->picnum;
        pSprite2->shade = pAmmo->shade;
        pSprite2->xrepeat = pAmmo->xrepeat;
        pSprite2->yrepeat = pAmmo->yrepeat;
    }
    return pSprite2;
}

spritetype *actDropWeapon(spritetype *pSprite, int nType)
{
    spritetype *pSprite2 = NULL;
    if (pSprite && pSprite->statnum < kMaxStatus && nType >= 40 && nType < 51)
    {
        pSprite2 = actSpawnFloor(pSprite);
        WEAPONITEMDATA *pWeapon = &gWeaponItemData[nType-40];
        pSprite2->type = nType;
        pSprite2->picnum = pWeapon->picnum;
        pSprite2->shade = pWeapon->shade;
        pSprite2->xrepeat = pWeapon->xrepeat;
        pSprite2->yrepeat = pWeapon->yrepeat;
    }
    return pSprite2;
}

spritetype *actDropItem(spritetype *pSprite, int nType)
{
    spritetype *pSprite2 = NULL;
    if (pSprite && pSprite->statnum < kMaxStatus && nType >= 100 && nType < 149)
    {
        pSprite2 = actSpawnFloor(pSprite);
        ITEMDATA *pItem = &gItemData[nType-100];
        pSprite2->type = nType;
        pSprite2->picnum = pItem->picnum;
        pSprite2->shade = pItem->shade;
        pSprite2->xrepeat = pItem->xrepeat;
        pSprite2->yrepeat = pItem->yrepeat;
    }
    return pSprite2;
}

spritetype *actDropKey(spritetype *pSprite, int nType)
{
    spritetype *pSprite2 = NULL;
    if (pSprite && pSprite->statnum < kMaxStatus && nType >= 100 && nType <= 106)
    {
        pSprite2 = actDropItem(pSprite, nType);
        if (pSprite2 && gGameOptions.nGameType == 1)
        {
            if (pSprite2->extra == -1)
                dbInsertXSprite(pSprite2->index);
            xsprite[pSprite2->extra].respawn = 3;
            gSpriteHit[pSprite2->extra].florhit = 0;
            gSpriteHit[pSprite2->extra].ceilhit = 0;
        }
    }
    return pSprite2;
}

spritetype *actDropFlag(spritetype *pSprite, int nType)
{
    spritetype *pSprite2 = NULL;
    if (pSprite && pSprite->statnum < kMaxStatus && (nType == 147 || nType == 148))
    {
        pSprite2 = actDropItem(pSprite, nType);
        if (pSprite2 && gGameOptions.nGameType == 3)
        {
            evPost(pSprite2->index, 3, 1800, CALLBACK_ID_17);
        }
    }
    return pSprite2;
}

spritetype *actDropObject(spritetype *pSprite, int nType)
{
    spritetype *pSprite2 = NULL;
    if (nType >= 100 && nType <= 106)
        pSprite2 = actDropKey(pSprite, nType);
    else if (nType == 147 || nType == 148)
        pSprite2 = actDropFlag(pSprite, nType);
    else if (nType >= 100 && nType < 149)
        pSprite2 = actDropItem(pSprite, nType);
    else if (nType >= 60 && nType < 81)
        pSprite2 = actDropAmmo(pSprite, nType);
    else if (nType >= 40 && nType < 51)
        pSprite2 = actDropWeapon(pSprite, nType);
    if (pSprite2)
    {
        int top, bottom;
        GetSpriteExtents(pSprite2, &top, &bottom);
        if (bottom >= pSprite2->z)
            pSprite2->z -= bottom - pSprite2->z;
    }
    return pSprite2;
}

bool actHealDude(XSPRITE *pXDude, int a2, int a3)
{
    dassert(pXDude != NULL);
    a2 <<= 4;
    a3 <<= 4;
    if (pXDude->health < a3)
    {
        spritetype *pSprite = &sprite[pXDude->reference];
        if (IsPlayerSprite(pSprite))
            sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 780, pSprite->sectnum);
        pXDude->health = ClipHigh(pXDude->health+a2, a3);
        return 1;
    }
    return 0;
}

void actKillDude(int nKillerSprite, spritetype *pSprite, DAMAGE_TYPE damageType, int damage)
{
    spritetype *pKillerSprite = &sprite[nKillerSprite];
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    int nType = pSprite->type-kDudeBase;
    int nXSprite = pSprite->extra;
    dassert(nXSprite > 0);
    XSPRITE *pXSprite = &xsprite[pSprite->extra];
    switch (pSprite->type) {
    case kCustomDude: {
        removeDudeStuff(pSprite);
        if (pXSprite->txID <= 0 || getNextIncarnation(pXSprite) == NULL) {
            
            if (pXSprite->data1 >= 459 && pXSprite->data1 < (459 + kExplodeMax) - 1 &&
                Chance(0x4000) && damageType != 5 && damageType != 4) {

                doExplosion(pSprite, pXSprite->data1 - 459);
                if (Chance(0x9000)) damageType = (DAMAGE_TYPE) 3;
            }

            if (damageType == DAMAGE_TYPE_1) {
                if ((gSysRes.Lookup(pXSprite->data2 + 15, "SEQ") || gSysRes.Lookup(pXSprite->data2 + 16, "SEQ")) && pXSprite->medium == 0) {
                    if (gSysRes.Lookup(pXSprite->data2 + 3, "SEQ")) {
                        pSprite->type = kCustomDudeBurning;
                        if (pXSprite->data2 == kDefaultAnimationBase) // don't inherit palette for burning if using default animation
                            pSprite->pal = 0;

                        aiNewState(pSprite, pXSprite, &GDXGenDudeBurnGoto);
                        actHealDude(pXSprite, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
                        if (pXSprite->burnTime <= 0) pXSprite->burnTime = 1200;
                        gDudeExtra[pSprite->extra].at0 = (int)gFrameClock + 360;
                        return;
                    }

                } else {
                    pXSprite->burnTime = 0;
                    pXSprite->burnSource = -1;
                    damageType = DAMAGE_TYPE_0;
                }
            }
            
        } else {
            
            pXSprite->locked = 1; // lock while transforming
            
            aiSetGenIdleState(pSprite, pXSprite); // set idle state
            
            if (pXSprite->key > 0) // drop keys
                actDropObject(pSprite, 100 + pXSprite->key - 1);
            
            if (pXSprite->dropMsg > 0) // drop items
                actDropObject(pSprite, pXSprite->dropMsg);
            
            pSprite->flags &= ~kPhysMove; xvel[pSprite->index] = yvel[pSprite->index] = 0;

            int seqId = pXSprite->data2 + 18;
            if (!gSysRes.Lookup(seqId, "SEQ")) {
                seqKill(3, nXSprite);
                sfxPlayGDXGenDudeSound(pSprite, 10);
                spritetype* pEffect = gFX.fxSpawn((FX_ID)52, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, pSprite->ang);
                if (pEffect != NULL) {
                    pEffect->cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
                    pEffect->pal = 6;
                    pEffect->xrepeat = pSprite->xrepeat;
                    pEffect->yrepeat = pSprite->yrepeat;
                }
                
                GIBTYPE nGibType;
                for (int i = 0; i < 3; i++) {
                    if (Chance(0x3000)) nGibType = GIBTYPE_6;
                    else if (Chance(0x2000)) nGibType = GIBTYPE_5;
                    else nGibType = GIBTYPE_17;

                    int top, bottom;
                    GetSpriteExtents(pSprite, &top, &bottom);
                    CGibPosition gibPos(pSprite->x, pSprite->y, top);
                    CGibVelocity gibVel(xvel[pSprite->index] >> 1, yvel[pSprite->index] >> 1, -0xccccc);
                    GibSprite(pSprite, nGibType, &gibPos, &gibVel);
                }

                return;
            }
            seqSpawn(seqId, 3, nXSprite, -1);
            sfxPlayGDXGenDudeSound(pSprite, 10);
            return;
        }
        break;
    }
    case 227: // Cerberus
        seqSpawn(dudeInfo[nType].seqStartID+1, 3, nXSprite, -1);
        return;
    case 201:
    case 202:
    case 247:
    case 248:
        if (damageType == DAMAGE_TYPE_1 && pXSprite->medium == 0)
        {
            pSprite->type = 240;
            aiNewState(pSprite, pXSprite, &cultistBurnGoto);
            actHealDude(pXSprite, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
            return;
        }
        // no break
        fallthrough__;
    case 251:
        if (damageType == DAMAGE_TYPE_1 && pXSprite->medium == 0)
        {
            pSprite->type = 253;
            aiNewState(pSprite, pXSprite, &beastBurnGoto);
            actHealDude(pXSprite, dudeInfo[53].startHealth, dudeInfo[53].startHealth);
            return;
        }
        // no break
        fallthrough__;
    case 245:
        if (damageType == DAMAGE_TYPE_1 && pXSprite->medium == 0)
        {
            pSprite->type = 239;
            aiNewState(pSprite, pXSprite, &innocentBurnGoto);
            actHealDude(pXSprite, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
            return;
        }
        break;
    }
    for (int p = connecthead; p >= 0; p = connectpoint2[p])
    {
        if (gPlayer[p].at2ee == pSprite->index && gPlayer[p].at1fe > 0)
            gPlayer[p].at2ee = -1;
    }
    if (pSprite->type != 249)
        trTriggerSprite(pSprite->index, pXSprite, 0);
    pSprite->flags |= 7;
    if (VanillaMode())
    {
        if (IsPlayerSprite(pKillerSprite))
        {
            PLAYER *pPlayer = &gPlayer[pKillerSprite->type-kDudePlayer1];
            if (gGameOptions.nGameType == 1)
                pPlayer->at2c6++;
        }
    }
    else
    {
        if (gGameOptions.nGameType == 1 && IsPlayerSprite(pKillerSprite)
            && pSprite->statnum == 6 && pSprite->type != 219 && pSprite->type != 220 && pSprite->type != 245 && pSprite->type != 239)
        {
            PLAYER *pKillerPlayer = &gPlayer[pKillerSprite->type-kDudePlayer1];
            pKillerPlayer->at2c6++;
        }
    }

    if (pXSprite->key > 0)
        actDropObject(pSprite, 100+pXSprite->key-1);
    if (pXSprite->dropMsg > 0)
        actDropObject(pSprite, pXSprite->dropMsg);
    if (pSprite->type == 201)
    {
        int nRand = Random(100);
        if (nRand < 10)
            actDropObject(pSprite, 42);
        else if (nRand < 50)
            actDropObject(pSprite, 69);
    }
    else if (pSprite->type == 202)
    {
        int nRand = Random(100);
        if (nRand <= 10)
            actDropObject(pSprite, 41);
        else if (nRand <= 50)
            actDropObject(pSprite, 67);
    }
    int nSeq;
    switch (damageType)
    {
    case DAMAGE_TYPE_3:
        nSeq = 2;
        switch (pSprite->type)
        {
        case kCustomDude:
        case kCustomDudeBurning:
            sfxPlayGDXGenDudeSound(pSprite, 4);
            break;
        case 201:
        case 202:
        case 230:
        case 239:
        case 240:
        case 245:
        case 246:
        case 247:
        case 248:
        case 249:
        case 250:
        case 252:
            sfxPlay3DSound(pSprite, 717,-1,0);
            break;
        }
        break;
    case DAMAGE_TYPE_1:
        nSeq = 3;
        sfxPlay3DSound(pSprite, 351, -1, 0);
        break;
    case DAMAGE_TYPE_5:
        switch (pSprite->type)
        {
        case 203:
        case 205:
            nSeq = 14;
            break;
        case 204:
            nSeq = 11;
            break;
        default:
            nSeq = 1;
            break;
        }
        break;
    case DAMAGE_TYPE_0:
        switch (pSprite->type)
        {
        case 201:
        case 202:
            nSeq = 1;
            break;
        default:
            nSeq = 1;
            break;
        }
        break;
    default:
        nSeq = 1;
        break;
    }

    if (!gSysRes.Lookup(dudeInfo[nType].seqStartID + nSeq, "SEQ"))
    {
        seqKill(3, nXSprite);
        gKillMgr.AddKill(pSprite);
        actPostSprite(pSprite->index, 1024);
        return;
    }

    switch (pSprite->type)
    {
    case 203:
        sfxPlay3DSound(pSprite, 1107+Random(2), -1, 0);
        if (nSeq == 2)
        {
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            CGibPosition gibPos(pSprite->x, pSprite->y, top);
            CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc);
            GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
        }
        else if (nSeq == 1 && Chance(0x4000))
        {
            seqSpawn(dudeInfo[nType].seqStartID+7, 3, nXSprite, nDudeToGibClient1);
            evPost(pSprite->index, 3, 0, CALLBACK_ID_5);
            sfxPlay3DSound(pSprite, 362, -1, 0);
            pXSprite->data1 = 35;
            pXSprite->data2 = 5;
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            CGibPosition gibPos(pSprite->x, pSprite->y, top);
            CGibVelocity gibVel(xvel[pSprite->index] >> 1, yvel[pSprite->index] >> 1, -0x111111);
            GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
        }
        else if (nSeq == 14)
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        else if (nSeq == 3)
            seqSpawn(dudeInfo[nType].seqStartID+13, 3, nXSprite, nDudeToGibClient2);
        else
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
        break;
    case 201:
    case 202:
    case 247:
    case 248:
        sfxPlay3DSound(pSprite, 1018+Random(2), -1, 0);
        if (nSeq == 3)
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient2);
        else
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
        break;
    case 240:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 718, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1018+Random(2), -1, 0);
        damageType = DAMAGE_TYPE_3;
        if (Chance(0x8000))
        {
            for (int i = 0; i < 3; i++)
                GibSprite(pSprite, GIBTYPE_7, NULL, NULL);
            seqSpawn(dudeInfo[nType].seqStartID+16-Random(1), 3, nXSprite, nDudeToGibClient1);
        }
        else
            seqSpawn(dudeInfo[nType].seqStartID+15, 3, nXSprite, nDudeToGibClient2);
        break;
    case kCustomDude:
        sfxPlayGDXGenDudeSound(pSprite, 2);
        if (nSeq == 3) {
            
            bool seq15 = gSysRes.Lookup(pXSprite->data2 + 15, "SEQ"); bool seq16 = gSysRes.Lookup(pXSprite->data2 + 16, "SEQ");
            if (seq15 && seq16) seqSpawn((15 + Random(2)) + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
            else if (seq16) seqSpawn(16 + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
            else if (seq15) seqSpawn(15 + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
            else if (gSysRes.Lookup(pXSprite->data2 + nSeq, "SEQ")) seqSpawn(nSeq + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
            else seqKill(3, nXSprite);

         } else {
            seqSpawn(nSeq + pXSprite->data2, 3, nXSprite, nDudeToGibClient1);
        }

        pXSprite->txID = 0; // to avoid second trigger.
        break;

    case kCustomDudeBurning:
    {
        sfxPlayGDXGenDudeSound(pSprite, 4);
        damageType = DAMAGE_TYPE_3;

        if (Chance(0x4000)) {
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            CGibPosition gibPos(pSprite->x, pSprite->y, top);
            CGibVelocity gibVel(xvel[pSprite->index] >> 1, yvel[pSprite->index] >> 1, -0xccccc);
            GibSprite(pSprite, GIBTYPE_7, &gibPos, &gibVel);
        }

        int seqId = pXSprite->data2;
        bool seq15 = gSysRes.Lookup(pXSprite->data2 + 15, "SEQ"); bool seq16 = gSysRes.Lookup(pXSprite->data2 + 16, "SEQ");
        
        if (seq15 && seq16) seqId += (15 + Random(2));
        else if (seq16) seqId += 16;
        else seqId += 15;
        
        seqSpawn(seqId, 3, nXSprite, nDudeToGibClient1);
        break;
    }
    case 241:
        if (Chance(0x8000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1109, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1107+Random(2), -1, 0);
        damageType = DAMAGE_TYPE_3;
        if (Chance(0x8000))
        {
            seqSpawn(dudeInfo[nType].seqStartID+13, 3, nXSprite, nDudeToGibClient1);
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            CGibPosition gibPos(pSprite->x, pSprite->y, top);
            CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc);
            GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
        }
        else
            seqSpawn(dudeInfo[nType].seqStartID+13, 3, nXSprite, nDudeToGibClient2);
        break;
    case 242:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1206, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1204+Random(2), -1, 0);
        seqSpawn(dudeInfo[4].seqStartID+10, 3, nXSprite, -1);
        break;
    case 239:
        damageType = DAMAGE_TYPE_3;
        seqSpawn(dudeInfo[nType].seqStartID+7, 3, nXSprite, nDudeToGibClient1);
        break;
    case 204:
        if (nSeq == 14)
        {
            sfxPlay3DSound(pSprite, 1206, -1, 0);
            seqSpawn(dudeInfo[nType].seqStartID+11, 3, nXSprite, -1);
            break;
        }
        sfxPlay3DSound(pSprite, 1204+Random(2), -1, 0);
        if (nSeq == 3)
            seqSpawn(dudeInfo[nType].seqStartID+10, 3, nXSprite, -1);
        else
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 206:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1405, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1403+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 207:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1455, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1453+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 210:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1605, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1603+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 211:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1305, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1303+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 212:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1905, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1903+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 213:
        if (pSprite->owner != -1)
        {
            spritetype *pOwner = &sprite[actSpriteOwnerToSpriteId(pSprite)];
            gDudeExtra[pOwner->extra].at6.u1.at4--;
        }
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1805, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1803+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 214:
        if (pSprite->owner != -1)
        {
            spritetype *pOwner = &sprite[actSpriteOwnerToSpriteId(pSprite)];
            gDudeExtra[pOwner->extra].at6.u1.at4--;
        }
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1805, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1803+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 215:
        if (pSprite->owner != -1)
        {
            spritetype *pOwner = &sprite[actSpriteOwnerToSpriteId(pSprite)];
            gDudeExtra[pOwner->extra].at6.u1.at4--;
        }
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1805, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1803+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 216:
        sfxPlay3DSound(pSprite, 1850, -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 217:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1705, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1703+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 218:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 1505, -1, 0);
        else
            sfxPlay3DSound(pSprite, 1503+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 219:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2005, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2003+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 220:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2105, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2103+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 221:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2205, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2203+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 222:
        if (damage == 5)
            sfxPlay3DSound(pSprite, 2471, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2472, -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 223:
        if (damage == 5)
            sfxPlay3DSound(pSprite, 2451, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2452, -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 224:
        sfxPlay3DSound(pSprite, 2501, -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 225:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2205, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2203+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 226:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2205, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2203+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 227:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2305, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2305+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 228:
        if (Chance(0x4000) && nSeq == 3)
            sfxPlay3DSound(pSprite, 2305, -1, 0);
        else
            sfxPlay3DSound(pSprite, 2305+Random(2), -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 229:
        sfxPlay3DSound(pSprite, 2380, -1, 0);
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    case 252:
        damageType = DAMAGE_TYPE_3;
        seqSpawn(dudeInfo[nType].seqStartID+11, 3, nXSprite, nDudeToGibClient1);
        break;
    case 251:
        sfxPlay3DSound(pSprite, 9000+Random(2), -1, 0);
        if (nSeq == 3)
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient2);
        else
            seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
        break;
    case 253:
        damageType = DAMAGE_TYPE_3;
        seqSpawn(dudeInfo[nType].seqStartID+12, 3, nXSprite, nDudeToGibClient1);
        break;
    default:
        seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
        break;
    }
    
                                    // kMaxSprites - 1 = custom dude had once life leech
    if (pSprite->owner != -1 && pSprite->owner != (kMaxSprites - 1)) {
        //int owner = actSpriteIdToOwnerId(pSprite->xvel);
        int owner = pSprite->owner;
        switch (sprite[owner].type) {
        case kCustomDude:
        case kCustomDudeBurning:
            if (owner != -1) gDudeExtra[sprite[owner].extra].at6.u1.at4--;
            break;
        default:
            break;
        }
    }
    
    if (damageType == DAMAGE_TYPE_3)
    {
        DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
        for (int i = 0; i < 3; i++)
            if (pDudeInfo->nGibType[i] > -1)
                GibSprite(pSprite, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);
        for (int i = 0; i < 4; i++)
            fxSpawnBlood(pSprite, damage);
    }
    gKillMgr.AddKill(pSprite);
    actCheckRespawn(pSprite);
    pSprite->type = 426;
    actPostSprite(pSprite->index, 4);
}

int actDamageSprite(int nSource, spritetype *pSprite, DAMAGE_TYPE damageType, int damage)
{
    dassert(nSource < kMaxSprites);
    if (pSprite->flags&32)
        return 0;
    int nXSprite = pSprite->extra;
    if (nXSprite <= 0)
        return 0;
    dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
    XSPRITE *pXSprite = &xsprite[nXSprite];
    dassert(pXSprite->reference == pSprite->index);
    if ((pXSprite->health == 0 && pSprite->statnum != 6) || pXSprite->locked)
        return 0;
    if (nSource == -1)
        nSource = pSprite->index;
    PLAYER *pSourcePlayer = NULL;
    if (IsPlayerSprite(&sprite[nSource]))
        pSourcePlayer = &gPlayer[sprite[nSource].type-kDudePlayer1];
    if (!gGameOptions.bFriendlyFire && IsTargetTeammate(pSourcePlayer, pSprite))
        return 0;
    switch (pSprite->statnum)
    {
    case 6:
    {
        if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax)
        {
            sprintf(buffer, "Bad Dude Failed: initial=%d type=%d %s\n", (int)pSprite->inittype, (int)pSprite->type, (int)(pSprite->flags&16) ? "RESPAWN" : "NORMAL");
            ThrowError(buffer);
        }
        dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
        int nType = pSprite->type-kDudeBase;
        int nDamageFactor = dudeInfo[nType].at70[damageType];
        if (!nDamageFactor)
            return 0;
        if (nDamageFactor != 256)
            damage = mulscale8(damage, nDamageFactor);
        if (!IsPlayerSprite(pSprite))
        {
            if (!pXSprite->health)
                return 0;
            damage = aiDamageSprite(pSprite, pXSprite, nSource, damageType, damage);
            if (!pXSprite->health)
            {
                if (damageType == DAMAGE_TYPE_3 && damage < 160)
                    damageType = DAMAGE_TYPE_0;
                actKillDude(nSource, pSprite, damageType, damage);
            }
        }
        else
        {
            PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
            if (pXSprite->health > 0 || playerSeqPlaying(pPlayer, 16))
                damage = playerDamageSprite(nSource, pPlayer, damageType, damage);
        }
        break;
    }
    case 4:
        dassert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
        int nType = pSprite->type-kThingBase;
        int nDamageFactor = thingInfo[nType].at17[damageType];
        if (!nDamageFactor)
            return 0;
        if (nDamageFactor != 256)
            damage = mulscale8(damage, nDamageFactor);
        pXSprite->health = ClipLow(pXSprite->health-damage, 0);
        if (!pXSprite->health)
        {
            if (pSprite->type == 431 || pSprite->type == kGDXThingCustomDudeLifeLeech)
            {
                GibSprite(pSprite, GIBTYPE_14, NULL, NULL);
                pXSprite->data1 = 0;
                pXSprite->data2 = 0;
                pXSprite->data3 = 0;
                pXSprite->stateTimer = 0;
                pXSprite->data4 = 0;
                pXSprite->isTriggered = 0;
                pXSprite->DudeLockout = 0;

                if (pSprite->owner >= 0 && sprite[pSprite->owner].type == kCustomDude)
                    sprite[pSprite->owner].owner = kMaxSprites -1; // By NoOne: indicates if custom dude had life leech.
            }
            else if (!(pSprite->flags&16))
                actPropagateSpriteOwner(pSprite, &sprite[nSource]);
            trTriggerSprite(pSprite->index, pXSprite, 0);
            switch (pSprite->type)
            {
            case 416:
            case 417:
            case 425:
            case 426:
            case 427:
                if (damageType == 3 && pSourcePlayer && gFrameClock > pSourcePlayer->at312 && Chance(0x4000))
                {
                    sfxPlay3DSound(pSourcePlayer->pSprite, gPlayerGibThingComments[Random(10)], 0, 2);
                    pSourcePlayer->at312 = (int)gFrameClock+3600;
                }
                break;
            case 413:
                seqSpawn(28, 3, pSprite->extra, -1);
                break;
            case 407:
                seqSpawn(12, 3, pSprite->extra, -1);
                GibSprite(pSprite, GIBTYPE_6, NULL, NULL);
                break;
            case 410:
                seqSpawn(15, 3, pSprite->extra, -1);
                break;
            case 411:
                seqSpawn(21, 3, pSprite->extra, -1);
                GibSprite(pSprite, GIBTYPE_4, NULL, NULL);
                break;
            case 412:
                switch (pXSprite->data1)
                {
                case -1:
                    GibSprite(pSprite, GIBTYPE_14, NULL, NULL);
                    sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 312, pSprite->sectnum);
                    actPostSprite(pSprite->index, 1024);
                    break;
                case 0:
                    seqSpawn(25, 3, pSprite->extra, nTreeToGibClient);
                    sfxPlay3DSound(pSprite, 351, -1, 0);
                    break;
                case 1:
                    seqSpawn(26, 3, pSprite->extra, nTreeToGibClient);
                    sfxPlay3DSound(pSprite, 351, -1, 0);
                    break;
                }
                break;
            case 422:
                if (seqGetStatus(3, nXSprite) < 0)
                    seqSpawn(19, 3, pSprite->extra, -1);
                break;
            }
        }
        break;
    }
    return damage>>4;
}

void actHitcodeToData(int a1, HITINFO *pHitInfo, int *a3, spritetype **a4, XSPRITE **a5, int *a6, walltype **a7, XWALL **a8, int *a9, sectortype **a10, XSECTOR **a11)
{
    dassert(pHitInfo != NULL);
    int nSprite = -1;
    spritetype *pSprite = NULL;
    XSPRITE *pXSprite = NULL;
    int nWall = -1;
    walltype *pWall = NULL;
    XWALL *pXWall = NULL;
    int nSector = -1;
    sectortype *pSector = NULL;
    XSECTOR *pXSector = NULL;
    switch (a1)
    {
    case 3:
    case 5:
        nSprite = pHitInfo->hitsprite;
        dassert(nSprite >= 0 && nSprite < kMaxSprites);
        pSprite = &sprite[nSprite];
        if (pSprite->extra > 0)
            pXSprite = &xsprite[pSprite->extra];
        break;
    case 0:
    case 4:
        nWall = pHitInfo->hitwall;
        dassert(nWall >= 0 && nWall < kMaxWalls);
        pWall = &wall[nWall];
        if (pWall->extra > 0)
            pXWall = &xwall[pWall->extra];
        break;
    case 1:
    case 2:
    case 6:
        nSector = pHitInfo->hitsect;
        dassert(nSector >= 0 && nSector < kMaxSectors);
        pSector = &sector[nSector];
        if (pSector->extra > 0)
            pXSector = &xsector[pSector->extra];
        break;
    }
    if (a3)
        *a3 = nSprite;
    if (a4)
        *a4 = pSprite;
    if (a5)
        *a5 = pXSprite;
    if (a6)
        *a6 = nWall;
    if (a7)
        *a7 = pWall;
    if (a8)
        *a8 = pXWall;
    if (a9)
        *a9 = nSector;
    if (a10)
        *a10 = pSector;
    if (a11)
        *a11 = pXSector;
}

void actImpactMissile(spritetype *pMissile, int a2)
{
    int nXMissile = pMissile->extra;
    dassert(nXMissile > 0 && nXMissile < kMaxXSprites);
    XSPRITE *pXMissile = &xsprite[pMissile->extra];
    int nSpriteHit = -1;
    spritetype *pSpriteHit = NULL;
    XSPRITE *pXSpriteHit = NULL;
    int nWallHit = -1;
    walltype *pWallHit = NULL;
    XWALL *pXWallHit = NULL;
    int nSectorHit = -1;
    sectortype *pSectorHit = NULL;
    XSECTOR *pXSectorHit = NULL;
    actHitcodeToData(a2, &gHitInfo, &nSpriteHit, &pSpriteHit, &pXSpriteHit, &nWallHit, &pWallHit, &pXWallHit, &nSectorHit, &pSectorHit, &pXSectorHit);
    THINGINFO *pThingInfo = NULL;
    DUDEINFO *pDudeInfo = NULL;
    if (a2 == 3 && pSpriteHit)
    {
        if (pSpriteHit->statnum == 4)
        {
            dassert(pXSpriteHit != NULL);
            pThingInfo = &thingInfo[pSpriteHit->type-kThingBase];
        }
        else if (pSpriteHit->statnum == 6)
        {
            dassert(pXSpriteHit != NULL);
            pDudeInfo = &dudeInfo[pSpriteHit->type-kDudeBase];
        }
    }
    switch (pMissile->type)
    {
    case 315:
        if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
        {
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            DAMAGE_TYPE rand1 = (DAMAGE_TYPE)Random(7);
            int rand2 = (7+Random(7))<<4;
            int nDamage = actDamageSprite(nOwner, pSpriteHit, rand1, rand2);
            if ((pThingInfo && pThingInfo->at17[DAMAGE_TYPE_1] != 0) || (pDudeInfo && pDudeInfo->at70[DAMAGE_TYPE_1] != 0))
                actBurnSprite(pMissile->owner, pXSpriteHit, 360);

            // by NoOne: make Life Leech heal user, just like it was in 1.0x versions
            if (gGameOptions.weaponsV10x && !VanillaMode() && !DemoRecordStatus() && pDudeInfo != NULL) {
                spritetype* pSource = &sprite[nOwner];
                XSPRITE* pXSource = (pSource->extra >= 0) ? &xsprite[pSource->extra] : NULL;

                if (IsDudeSprite(pSource) && pXSource != NULL && pXSource->health != 0)
                    actHealDude(pXSource, nDamage >> 2, dudeInfo[pSource->type - kDudeBase].startHealth);
            }
        }
        if (pMissile->extra > 0)
        {
            actPostSprite(pMissile->index, 0);
            if (pMissile->ang == 1024)
                sfxPlay3DSound(pMissile, 307, -1, 0);
            pMissile->type = 0;
            seqSpawn(9, 3, pMissile->extra, -1);
        }
        else
        {
            actPostSprite(pMissile->index, 1024);
        }
        break;
    case 302:
        sub_51340(pMissile, a2);
        if ((a2 == 0 || a2 == 4) && pWallHit)
        {
            spritetype *pFX = gFX.fxSpawn(FX_52, pMissile->sectnum, pMissile->x, pMissile->y, pMissile->z, 0);
            if (pFX)
                pFX->ang = (GetWallAngle(nWallHit)+512)&2047;
        }
        GibSprite(pMissile, GIBTYPE_24, NULL, NULL);
        actPostSprite(pMissile->index, 1024);
        break;
    case 309:
        seqKill(3, nXMissile);
        if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
        {
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            int nDamage = (15+Random(7))<<4;
            actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
        }
        actPostSprite(pMissile->index, 1024);
        break;
    case 311:
        sfxKill3DSound(pMissile, -1, -1);
        sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 306, pMissile->sectnum);
        GibSprite(pMissile, GIBTYPE_6, NULL, NULL);
        if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
        {
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            int nDamage = (25+Random(20))<<4;
            actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_5, nDamage);
        }
        actPostSprite(pMissile->index, 1024);
        break;
    case 316:
    case 317:
        sfxKill3DSound(pMissile, -1, -1);
        sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 306, pMissile->sectnum);
        if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
        {
            if (pDudeInfo)
            {
            }
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            int nDmgMul = 3;
            if (pMissile->type == 317)
                nDmgMul = 6;
            int nDamage = (nDmgMul+Random(nDmgMul))<<4;
            actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_5, nDamage);
        }
        actPostSprite(pMissile->index, 1024);
        break;
    case 305:
    case 312:
        if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
        {
            if (pThingInfo && pSpriteHit->type == 400 && pXSpriteHit->burnTime == 0)
                evPost(nSpriteHit, 3, 0, CALLBACK_ID_0);
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            int nDamage = (50+Random(50))<<4;
            actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
        }
        actExplodeSprite(pMissile);
        break;
    case 303:
        sfxKill3DSound(pMissile, -1, -1);
        actExplodeSprite(pMissile);
        break;
    case 301:
        sfxKill3DSound(pMissile, -1, -1);
        if (a2 == 3 && pSpriteHit)
        {
            if (pThingInfo || pDudeInfo)
            {
                int nOwner = actSpriteOwnerToSpriteId(pMissile);
                if ((pThingInfo && pThingInfo->at17[DAMAGE_TYPE_1] != 0) || (pDudeInfo && pDudeInfo->at70[DAMAGE_TYPE_1] != 0))
                {
                    if (pThingInfo && pSpriteHit->type == 400 && pXSpriteHit->burnTime == 0)
                        evPost(nSpriteHit, 3, 0, CALLBACK_ID_0);
                    actBurnSprite(pMissile->owner, pXSpriteHit, 480);
                    sub_2A620(nOwner, pMissile->x, pMissile->y, pMissile->z, pMissile->sectnum, 16, 20, 10, DAMAGE_TYPE_2, 6, 480, 0, 0);

                    // by NoOne: allow additional bullet damage for Flare Gun
                    if (gGameOptions.weaponsV10x && !VanillaMode() && !DemoRecordStatus()) {
                        int nDamage = (20 + Random(10)) << 4;
                        actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
                    }
                }
                else
                {
                    int nDamage = (20+Random(10))<<4;
                    actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
                }
                if (surfType[pSpriteHit->picnum] == kSurfFlesh)
                {
                    pMissile->picnum = 2123;
                    pXMissile->target = nSpriteHit;
                    pXMissile->targetZ = pMissile->z-pSpriteHit->z;
                    pXMissile->goalAng = getangle(pMissile->x-pSpriteHit->x, pMissile->y-pSpriteHit->y)-pSpriteHit->ang;
                    pXMissile->state = 1;
                    actPostSprite(pMissile->index, 14);
                    pMissile->cstat &= ~257;
                    break;
                }
            }
        }
        GibSprite(pMissile, GIBTYPE_17, NULL, NULL);
        actPostSprite(pMissile->index, 1024);
        break;
    case 304:
    case 308:
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            if (pObject->extra > 0)
            {
                XSPRITE *pXObject = &xsprite[pObject->extra];
                if ((pObject->statnum == 4 || pObject->statnum == 6) && pXObject->burnTime == 0)
                    evPost(nObject, 3, 0, CALLBACK_ID_0);
                int nOwner = actSpriteOwnerToSpriteId(pMissile);
                actBurnSprite(pMissile->owner, pXObject, (4+gGameOptions.nDifficulty)<<2);
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_1, 8);
            }
        }
        break;
    case 313:
        actExplodeSprite(pMissile);
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            if (pObject->extra > 0)
            {
                XSPRITE *pXObject = &xsprite[pObject->extra];
                if ((pObject->statnum == 4 || pObject->statnum == 6) && pXObject->burnTime == 0)
                    evPost(nObject, 3, 0, CALLBACK_ID_0);
                int nOwner = actSpriteOwnerToSpriteId(pMissile);
                actBurnSprite(pMissile->owner, pXObject, (4+gGameOptions.nDifficulty)<<2);
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_1, 8);
                int nDamage = (25+Random(10))<<4;
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_2, nDamage);
            }
        }
        actExplodeSprite(pMissile);
        break;
    case 314:
        actExplodeSprite(pMissile);
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            if (pObject->extra > 0)
            {
                XSPRITE *pXObject = &xsprite[pObject->extra];
                if ((pObject->statnum == 4 || pObject->statnum == 6) && pXObject->burnTime == 0)
                    evPost(nObject, 3, 0, CALLBACK_ID_0);
                int nOwner = actSpriteOwnerToSpriteId(pMissile);
                actBurnSprite(pMissile->owner, pXObject, 32);
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_5, 12);
                int nDamage = (25+Random(10))<<4;
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_2, nDamage);
            }
        }
        actExplodeSprite(pMissile);
        break;
    case 307:
        sfxKill3DSound(pMissile, -1, -1);
        sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 522, pMissile->sectnum);
        actPostSprite(pMissile->index, 15);
        seqSpawn(20, 3, pMissile->extra, -1);
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            if (pObject->statnum == 6)
            {
                int nOwner = actSpriteOwnerToSpriteId(pMissile);
                int nDamage = (25+Random(10))<<4;
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_5, nDamage);
            }
        }
        break;
    case 300:
        actPostSprite(pMissile->index, 15);
        pMissile->cstat &= ~16;
        pMissile->type = 0;
        seqSpawn(20, 3, pMissile->extra, -1);
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            if (pObject->statnum == 6)
            {
                int nOwner = actSpriteOwnerToSpriteId(pMissile);
                int nDamage = (10+Random(10))<<4;
                actDamageSprite(nOwner, pObject, DAMAGE_TYPE_5, nDamage);
                spritetype *pOwner = &sprite[nOwner];
                XSPRITE *pXOwner = &xsprite[pOwner->extra];
                int nType = pOwner->type-kDudeBase;
                if (pXOwner->health > 0)
                    actHealDude(pXOwner, 10, dudeInfo[nType].startHealth);
            }
        }
        break;
    case 306:
        sfxKill3DSound(pMissile, -1, -1);
        sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 518, pMissile->sectnum);
        GibSprite(pMissile, (a2 == 2) ? GIBTYPE_23 : GIBTYPE_22, NULL, NULL);
        evKill(pMissile->index, 3);
        seqKill(3, nXMissile);
        actPostSprite(pMissile->index, 1024);
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            int nDamage = (15+Random(10))<<4;
            actDamageSprite(nOwner, pObject, DAMAGE_TYPE_6, nDamage);
        }
        break;
    case 310:
    default:
        seqKill(3, nXMissile);
        actPostSprite(pMissile->index, 1024);
        if (a2 == 3)
        {
            int nObject = gHitInfo.hitsprite;
            dassert(nObject >= 0 && nObject < kMaxSprites);
            spritetype *pObject = &sprite[nObject];
            int nOwner = actSpriteOwnerToSpriteId(pMissile);
            int nDamage = (10+Random(10))<<4;
            actDamageSprite(nOwner, pObject, DAMAGE_TYPE_0, nDamage);
        }
        break;
    }
    pMissile->cstat &= ~257;
}

void actKickObject(spritetype *pSprite1, spritetype *pSprite2)
{
    int nSprite1 = pSprite1->index;
    int nSprite2 = pSprite2->index;
    int nSpeed = ClipLow(approxDist(xvel[nSprite1], yvel[nSprite1])*2, 0xaaaaa);
    xvel[nSprite2] = mulscale30(nSpeed, Cos(pSprite1->ang+Random2(85)));
    yvel[nSprite2] = mulscale30(nSpeed, Sin(pSprite1->ang+Random2(85)));
    zvel[nSprite2] = mulscale(nSpeed, -0x2000, 14);
    pSprite2->flags = 7;
}

void actTouchFloor(spritetype *pSprite, int nSector)
{
    dassert(pSprite != NULL);
    dassert(nSector >= 0 && nSector < kMaxSectors);
    sectortype * pSector = &sector[nSector];
    XSECTOR * pXSector = NULL;
    if (pSector->extra > 0)
        pXSector = &xsector[pSector->extra];

    if (pXSector && (pSector->type == 618 || pXSector->damageType > 0))
    {
        DAMAGE_TYPE nDamageType;
        if (pSector->type == 618)
            nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType, DAMAGE_TYPE_0, DAMAGE_TYPE_6);
        else
            nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType - 1, DAMAGE_TYPE_0, DAMAGE_TYPE_6);
        int nDamage;
        if (pXSector->data)
            nDamage = ClipRange(pXSector->data, 0, 1000);
        else
            nDamage = 1000;
        actDamageSprite(pSprite->index, pSprite, nDamageType, scale(4, nDamage, 120) << 4);
    }
    if (tileGetSurfType(nSector + 0x4000) == kSurfLava)
    {
        actDamageSprite(pSprite->index, pSprite, DAMAGE_TYPE_1, 16);
        sfxPlay3DSound(pSprite, 352, 5, 2);
    }
}

void ProcessTouchObjects(spritetype *pSprite, int nXSprite)
{
    int nSprite = pSprite->index;
    XSPRITE *pXSprite = &xsprite[nXSprite];
    SPRITEHIT *pSpriteHit = &gSpriteHit[nXSprite];
    PLAYER *pPlayer = NULL;
    if (IsPlayerSprite(pSprite))
        pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
    int nHitSprite = pSpriteHit->ceilhit & 0x3fff;
    switch (pSpriteHit->ceilhit&0xc000)
    {
    case 0x8000:
        break;
    case 0xc000:
        if (sprite[nHitSprite].extra > 0)
        {
            spritetype *pSprite2 = &sprite[nHitSprite];
            XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
            if ((pSprite2->statnum == 4 || pSprite2->statnum == 6) && (xvel[nSprite] != 0 || yvel[nSprite] != 0 || zvel[nSprite] != 0))
            {
                if (pSprite2->statnum == 4)
                {
                    int nType = pSprite2->type-kThingBase;
                    THINGINFO *pThingInfo = &thingInfo[nType];
                    if (pThingInfo->at5&1)
                        pSprite2->flags |= 1;
                    if (pThingInfo->at5&2)
                        pSprite2->flags |= 4;
                    // Inlined ?
                    xvel[pSprite2->index] += mulscale(4, pSprite2->x-sprite[nSprite].x, 2);
                    yvel[pSprite2->index] += mulscale(4, pSprite2->y-sprite[nSprite].y, 2);
                }
                else
                {
                    pSprite2->flags |= 5;
                    xvel[pSprite2->index] += mulscale(4, pSprite2->x-sprite[nSprite].x, 2);
                    yvel[pSprite2->index] += mulscale(4, pSprite2->y-sprite[nSprite].y, 2);
                    
                    // by NoOne: add size shroom abilities
                    if ((IsPlayerSprite(pSprite) && isShrinked(pSprite)) || (IsPlayerSprite(pSprite2) && isGrown(pSprite2))) {

                        int mass1 = dudeInfo[pSprite2->type - kDudeBase].mass;
                        int mass2 = dudeInfo[pSprite->type - kDudeBase].mass;
                        switch (pSprite->type) {
                            case kCustomDude:
                            case kCustomDudeBurning:
                                mass2 = getSpriteMassBySize(pSprite);
                                break;
                        }
                        if (mass1 > mass2) {
                            int dmg = abs((mass1 - mass2) * (pSprite2->clipdist - pSprite->clipdist));
                            if (IsDudeSprite(pSprite2)) {
                                if (dmg > 0)
                                    actDamageSprite(pSprite2->xvel, pSprite, (Chance(0x2000)) ? DAMAGE_TYPE_0 : (Chance(0x4000)) ? DAMAGE_TYPE_3 : DAMAGE_TYPE_2, dmg);

                                if (Chance(0x0200))
                                    actKickObject(pSprite2, pSprite);
                            }
                        }
                    }
                    
                    if (!IsPlayerSprite(pSprite) || gPlayer[pSprite->type - kDudePlayer1].at31a == 0) {
                        switch (pSprite2->type) {
                            case 229:
                                actDamageSprite(pSprite2->index, pSprite, DAMAGE_TYPE_3, pXSprite->health << 2);
                                break;
                            case kCustomDude:
                            case kCustomDudeBurning:
                                int dmg = (getSpriteMassBySize(pSprite2) - getSpriteMassBySize(pSprite)) + pSprite2->clipdist;
                                if (dmg > 0) {
                                    if (IsPlayerSprite(pSprite) && powerupCheck(&gPlayer[pSprite->type - kDudePlayer1],15) > 0)
                                        actDamageSprite(pSprite2->xvel, pSprite, DAMAGE_TYPE_3, dmg);
                                    else
                                        actDamageSprite(pSprite2->xvel, pSprite, DAMAGE_TYPE_0, dmg);
                                }

                                if (!IsPlayerSprite(pSprite) && pSprite2->extra >= 0 && !isActive(pSprite2->xvel))
                                    aiActivateDude(pSprite2, &xsprite[pSprite2->extra]);
                                break;

                        }
                            
                    }
                }
            }
            if (pSprite2->type == 454)
            {
                if (pXSprite2->state)
                {
                    pXSprite2->data1 = 1;
                    pXSprite2->data2 = ClipHigh(pXSprite2->data2+8, 600);
                    actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 16);
                }
                else
                    actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 1);
            }
        }
        break;
    }
    nHitSprite = pSpriteHit->hit & 0x3fff;
    switch (pSpriteHit->hit&0xc000)
    {
    case 0x8000:
        break;
    case 0xc000:
        if (sprite[nHitSprite].extra > 0)
        {
            spritetype *pSprite2 = &sprite[nHitSprite];
            //XSPRITE *pXSprite2 = &Xsprite[pSprite2->extra];
            
            // by NoOne: add size shroom abilities
            if ((IsPlayerSprite(pSprite2) && isShrinked(pSprite2)) || (IsPlayerSprite(pSprite) && isGrown(pSprite))) {
                if (xvel[pSprite->xvel] != 0 && IsDudeSprite(pSprite2)) {
                    int mass1 = dudeInfo[pSprite->type - kDudeBase].mass;
                    int mass2 = dudeInfo[pSprite2->type - kDudeBase].mass;
                    switch (pSprite2->type) {
                        case kCustomDude:
                        case kCustomDudeBurning:
                            mass2 = getSpriteMassBySize(pSprite2);
                            break;
                    }
                    if (mass1 > mass2) {
                        actKickObject(pSprite, pSprite2);
                        sfxPlay3DSound(pSprite, 357, -1, 1);
                        int dmg = (mass1 - mass2) + abs(xvel[pSprite->xvel] >> 16);
                        if (dmg > 0)
                            actDamageSprite(nSprite, pSprite2, (Chance(0x2000)) ? DAMAGE_TYPE_0 : DAMAGE_TYPE_2, dmg);
                    }
                }
            }

            switch (pSprite->type) {
                case kCustomDude:
                case kCustomDudeBurning:
                {
                    if (IsDudeSprite(pSprite2) && !IsPlayerSprite(pSprite2)) {
                        int mass1 = getSpriteMassBySize(pSprite);
                        int mass2 = getSpriteMassBySize(pSprite2);

                        if ((mass1 - mass2) >= mass2) {
                            if ((pXSprite->target == pSprite2->xvel && !dudeIsMelee(pXSprite) && Chance(0x0500)) || pXSprite->target != pSprite2->xvel) 
                                actKickObject(pSprite, pSprite2);
                            if (pSprite2->extra >= 0 && !isActive(pSprite2->xvel))
                                aiActivateDude(pSprite2, &xsprite[pSprite2->extra]);
                        }
                    }
                    break;
                }
            }
            
            switch (pSprite2->type)
            {
            case 415:
                actKickObject(pSprite, pSprite2);
                break;
            case 427:
                sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
                actKickObject(pSprite, pSprite2);
                actDamageSprite(-1, pSprite2, DAMAGE_TYPE_0, 80);
                break;
            case 239:
            case 240:
            case 241:
            case 242:
                // This does not make sense
                pXSprite->burnTime = ClipLow(pXSprite->burnTime-4, 0);
                actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
                break;
            }
        }
        break;
    }
    nHitSprite = pSpriteHit->florhit & 0x3fff;
    switch (pSpriteHit->florhit&0xc000)
    {
    case 0x8000:
        break;
    case 0x4000:
        actTouchFloor(pSprite, nHitSprite);
        break;
    case 0xc000:
        if (sprite[nHitSprite].extra > 0)
        {
            spritetype *pSprite2 = &sprite[nHitSprite];
            XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
            
            // by NoOne: add size shroom abilities
            if ((IsPlayerSprite(pSprite2) && isShrinked(pSprite2)) || (IsPlayerSprite(pSprite) && isGrown(pSprite))) {
                
                int mass1 = dudeInfo[pSprite->type - kDudeBase].mass;
                int mass2 = dudeInfo[pSprite2->type - kDudeBase].mass;
                switch (pSprite2->type) {
                    case kCustomDude:
                    case kCustomDudeBurning:
                        mass2 = getSpriteMassBySize(pSprite2);
                        break;
                }
                if (mass1 > mass2 && IsDudeSprite(pSprite2)) {
                    if ((IsPlayerSprite(pSprite2) && Chance(0x500)) || !IsPlayerSprite(pSprite2))
                        actKickObject(pSprite, pSprite2);

                    int dmg = (mass1 - mass2) + pSprite->clipdist;
                    if (dmg > 0)
                        actDamageSprite(nSprite, pSprite2, (Chance(0x2000)) ? DAMAGE_TYPE_0 : DAMAGE_TYPE_2, dmg);
                }
            }

            switch (pSprite->type) {
                case kCustomDude:
                case kCustomDudeBurning:
                {
                    if (IsDudeSprite(pSprite2) && !IsPlayerSprite(pSprite2)) {
                        int mass1 = getSpriteMassBySize(pSprite);
                        int mass2 = getSpriteMassBySize(pSprite2);

                        if ((mass1 - mass2) >= mass2) {
                            if (Chance((pXSprite->target == pSprite2->xvel) ? 0x0500 : 0x1000)) actKickObject(pSprite, pSprite2);
                            if (pSprite2->extra >= 0 && !isActive(pSprite2->xvel))
                                aiActivateDude(pSprite2, &xsprite[pSprite2->extra]);
                        }
                    }
                    break;
                }
            }

            
            switch (pSprite2->type)
            {
            case 415:
                if (pPlayer)
                {
                    if (pPlayer->at30e > gFrameClock)
                        return;
                    pPlayer->at30e = (int)gFrameClock+60;
                }
                actKickObject(pSprite, pSprite2);
                sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
                sfxPlay3DSound(pSprite, 374, 0, 0);
                break;
            case 427:
                if (pPlayer)
                {
                    if (pPlayer->at30e > gFrameClock)
                        return;
                    pPlayer->at30e = (int)gFrameClock+60;
                }
                actKickObject(pSprite, pSprite2);
                sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
                actDamageSprite(-1, pSprite2, DAMAGE_TYPE_0, 80);
                break;
            case 454:
                if (pXSprite2->state)
                {
                    pXSprite2->data1 = 1;
                    pXSprite2->data2 = ClipHigh(pXSprite2->data2+8, 600);
                    actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 16);
                }
                else
                    actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 1);
                break;
            case 201:
            case 202:
            case 203:
            case 204:
            case 205:
            case 206:
            case 207:
            case 210:
            case 211:
            case 212:
            case 213:
            case 214:
            case 215:
            case 217:
            case 219:
            case 220:
            case 221:
            case 222:
            case 223:
            case 224:
            case 225:
            case 226:
            case 227:
            case 228:
            case 229:
            case 231:
            case 232:
            case 233:
            case 234:
            case 235:
            case 236:
            case 237:
            case 238:
                if (pPlayer && !isShrinked(pSprite))
                    actDamageSprite(nSprite, pSprite2,DAMAGE_TYPE_2, 8);
                break;
            }
        }
        break;
    }

    // by NoOne: add more trigger statements for Touch flag
    if (!VanillaMode()) {
        
        // Touch sprites
        int nHSprite = -1;
        if ((gSpriteHit[nXSprite].hit & 0xc000) == 0xc000)
            nHSprite = gSpriteHit[nXSprite].hit & 0x3fff;
        else if ((gSpriteHit[nXSprite].florhit & 0xc000) == 0xc000)
            nHSprite = gSpriteHit[nXSprite].florhit & 0x3fff;
        else if ((gSpriteHit[nXSprite].ceilhit & 0xc000) == 0xc000)
            nHSprite = gSpriteHit[nXSprite].ceilhit & 0x3fff;

        if (nHSprite >= 0 && sprite[nHSprite].extra >= 0) {
            XSPRITE* pXHSprite = &xsprite[sprite[nHSprite].extra];
            if (pXHSprite->Touch && !pXHSprite->isTriggered && (!pXHSprite->DudeLockout || IsPlayerSprite(pSprite)))
                trTriggerSprite(nHSprite, pXHSprite, 33);
        }

        // Touch walls
        int nHWall = -1;
        if ((gSpriteHit[nXSprite].hit & 0xc000) == 0x8000) {
            if ((nHWall = gSpriteHit[nXSprite].hit & 0x3fff) >= 0 && wall[nHWall].extra >= 0) {
                XWALL* pXHWall = &xwall[wall[nHWall].extra];
                if (pXHWall->triggerTouch && !pXHWall->isTriggered && (!pXHWall->dudeLockout || IsPlayerSprite(pSprite)))
                    trTriggerWall(nHWall, pXHWall, 52);
            }
        }
    }
}

void actAirDrag(spritetype *pSprite, int a2)
{
    int vbp = 0;
    int v4 = 0;
    int nSector = pSprite->sectnum;
    dassert(nSector >= 0 && nSector < kMaxSectors);
    sectortype *pSector = &sector[nSector];
    int nXSector = pSector->extra;
    if (nXSector > 0)
    {
        dassert(nXSector < kMaxXSectors);
        XSECTOR *pXSector = &xsector[nXSector];
        if (pXSector->windVel && (pXSector->windAlways || pXSector->busy))
        {
            int vcx = pXSector->windVel<<12;
            if (!pXSector->windAlways && pXSector->busy)
                vcx = mulscale16(vcx, pXSector->busy);
            vbp = mulscale30(vcx, Cos(pXSector->windAng));
            v4 = mulscale30(vcx, Sin(pXSector->windAng));
        }
    }
    xvel[pSprite->index] += mulscale16(vbp-xvel[pSprite->index], a2);
    yvel[pSprite->index] += mulscale16(v4-yvel[pSprite->index], a2);
    zvel[pSprite->index] -= mulscale16(zvel[pSprite->index], a2);
}

int MoveThing(spritetype *pSprite)
{
    int nXSprite = pSprite->extra;
    dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pSprite->index;
    int v8 = 0;
    dassert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
    THINGINFO *pThingInfo = &thingInfo[pSprite->type-kThingBase];
    int nSector = pSprite->sectnum;
    dassert(nSector >= 0 && nSector < kMaxSectors);
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    if (xvel[nSprite] || yvel[nSprite])
    {
        short bakCstat = pSprite->cstat;
        pSprite->cstat &= ~257;
        v8 = gSpriteHit[nXSprite].hit = ClipMove((int*)&pSprite->x, (int*)&pSprite->y, (int*)&pSprite->z, &nSector, xvel[nSprite]>>12, yvel[nSprite]>>12, pSprite->clipdist<<2, (pSprite->z-top)/4, (bottom-pSprite->z)/4, CLIPMASK0);
        pSprite->cstat = bakCstat;
        dassert(nSector >= 0);
        if (pSprite->sectnum != nSector)
        {
            dassert(nSector >= 0 && nSector < kMaxSectors);
            ChangeSpriteSect(nSprite, nSector);
        }
        if ((gSpriteHit[nXSprite].hit&0xc000) == 0x8000)
        {
            int nHitWall = gSpriteHit[nXSprite].hit&0x3fff;
            actWallBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], nHitWall, pThingInfo->at7);
            switch (pSprite->type)
            {
            case 427:
                sfxPlay3DSound(pSprite, 607, 0, 0);
                actDamageSprite(-1, pSprite, DAMAGE_TYPE_0, 80);
                break;
            case 415:
                sfxPlay3DSound(pSprite, 374, 0, 0);
                break;
            }
        }
    }
    else
    {
        dassert(nSector >= 0 && nSector < kMaxSectors);
        FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
    }
    if (zvel[nSprite])
        pSprite->z += zvel[nSprite]>>8;
    int ceilZ, ceilHit, floorZ, floorHit;
    GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0);
    GetSpriteExtents(pSprite, &top, &bottom);
    if ((pSprite->flags & 2) && bottom < floorZ)
    {
        pSprite->z += 455;
        zvel[nSprite] += 58254;
        if (pSprite->type == 427)
        {
            spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
            if (pFX)
            {
                int v34 = ((int)gFrameClock*3)&2047;
                int v30 = ((int)gFrameClock*5)&2047;
                int vbx = ((int)gFrameClock*11)&2047;
                int v2c = 0x44444;
                int v28 = 0;
                int v24 = 0;
                RotateVector(&v2c,&v28,vbx);
                RotateVector(&v2c,&v24,v30);
                RotateVector(&v28,&v24,v34);
                xvel[pFX->index] = xvel[pSprite->index]+v2c;
                yvel[pFX->index] = yvel[pSprite->index]+v28;
                zvel[pFX->index] = zvel[pSprite->index]+v24;
            }
        }
    }
    if (CheckLink(pSprite))
        GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0);
    GetSpriteExtents(pSprite, &top, &bottom);
    if (bottom >= floorZ)
    {
        actTouchFloor(pSprite, pSprite->sectnum);
        gSpriteHit[nXSprite].florhit = floorHit;
        pSprite->z += floorZ-bottom;
        int v20 = zvel[nSprite]-velFloor[pSprite->sectnum];
        if (v20 > 0)
        {
            pSprite->flags |= 4;
            int vax = actFloorBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], (int*)&v20, pSprite->sectnum, pThingInfo->at7);
            int nDamage = mulscale(vax, vax, 30)-pThingInfo->atb;
            if (nDamage > 0)
                actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, nDamage);
            zvel[nSprite] = v20;
            if (velFloor[pSprite->sectnum] == 0 && klabs(zvel[nSprite]) < 0x10000)
            {
                zvel[nSprite] = 0;
                pSprite->flags &= ~4;
            }
            switch (pSprite->type)
            {
            case 428:
                if (zvel[nSprite] == 0 || Chance(0xA000))
                    sub_2AA94(pSprite, pXSprite);
                break;
            case 427:
                if (klabs(zvel[nSprite]) > 0x80000)
                {
                    sfxPlay3DSound(pSprite, 607, 0, 0);
                    actDamageSprite(-1, pSprite, DAMAGE_TYPE_0, 80);
                }
                break;
            case 415:
                if (klabs(zvel[nSprite]) > 0x80000)
                    sfxPlay3DSound(pSprite, 374, 0, 0);
                break;
            }
            v8 = 0x4000|nSector;
        }
        else if (zvel[nSprite] == 0)
            pSprite->flags &= ~4;
    }
    else
    {
        gSpriteHit[nXSprite].florhit = 0;
        if (pSprite->flags&2)
            pSprite->flags |= 4;
    }
    if (top <= ceilZ)
    {
        gSpriteHit[nXSprite].ceilhit = ceilHit;
        pSprite->z += ClipLow(ceilZ-top, 0);
        if (zvel[nSprite] < 0)
        {
            xvel[nSprite] = mulscale16(xvel[nSprite], 0xc000);
            yvel[nSprite] = mulscale16(yvel[nSprite], 0xc000);
            zvel[nSprite] = mulscale16(-zvel[nSprite], 0x4000);
            switch (pSprite->type)
            {
            case 427:
                if (klabs(zvel[nSprite]) > 0x80000)
                {
                    sfxPlay3DSound(pSprite, 607, 0, 0);
                    actDamageSprite(-1, pSprite, DAMAGE_TYPE_0, 80);
                }
                break;
            case 415:
                if (klabs(zvel[nSprite]) > 0x80000)
                    sfxPlay3DSound(pSprite, 374, 0, 0);
                break;
            }
        }
    }
    else
        gSpriteHit[nXSprite].ceilhit = 0;
    if (bottom >= floorZ)
    {
        int nVel = approxDist(xvel[nSprite], yvel[nSprite]);
        int nVelClipped = ClipHigh(nVel, 0x11111);
        if ((floorHit & 0xc000) == 0xc000)
        {
            int nHitSprite = floorHit & 0x3fff;
            if ((sprite[nHitSprite].cstat & 0x30) == 0)
            {
                xvel[nSprite] += mulscale(4, pSprite->x - sprite[nHitSprite].x, 2);
                yvel[nSprite] += mulscale(4, pSprite->y - sprite[nHitSprite].y, 2);
                v8 = gSpriteHit[nXSprite].hit;
            }
        }
        if (nVel > 0)
        {
            int t = divscale16(nVelClipped, nVel);
            xvel[nSprite] -= mulscale16(t, xvel[nSprite]);
            yvel[nSprite] -= mulscale16(t, yvel[nSprite]);
        }
    }
    if (xvel[nSprite] || yvel[nSprite])
        pSprite->ang = getangle(xvel[nSprite], yvel[nSprite]);
    return v8;
}

void MoveDude(spritetype *pSprite)
{
    int nXSprite = pSprite->extra;
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pSprite->index;
    PLAYER *pPlayer = NULL;
    if (IsPlayerSprite(pSprite))
        pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
    dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
    DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    int bz = (bottom-pSprite->z)/4;
    int tz = (pSprite->z-top)/4;
    int wd = pSprite->clipdist<<2;
    int nSector = pSprite->sectnum;
    dassert(nSector >= 0 && nSector < kMaxSectors);
    if (xvel[nSprite] || yvel[nSprite])
    {
        if (pPlayer && gNoClip)
        {
            pSprite->x += xvel[nSprite]>>12;
            pSprite->y += yvel[nSprite]>>12;
            if (!FindSector(pSprite->x, pSprite->y, &nSector))
                nSector = pSprite->sectnum;
        }
        else
        {
            short bakCstat = pSprite->cstat;
            pSprite->cstat &= ~257;
            gSpriteHit[nXSprite].hit = ClipMove((int*)&pSprite->x, (int*)&pSprite->y, (int*)&pSprite->z, &nSector, xvel[nSprite]>>12, yvel[nSprite]>>12, wd, tz, bz, CLIPMASK0);
            if (nSector == -1)
            {
                nSector = pSprite->sectnum;
                if (pSprite->statnum == 6 || pSprite->statnum == 4)
                    actDamageSprite(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000<<4);
            }
            if (sector[nSector].type >= 612 && sector[nSector].type <= 617)
            {
                short nSector2 = nSector;
                if (pushmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector2, wd, tz, bz, CLIPMASK0) == -1)
                    actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, 1000 << 4);
                if (nSector2 != -1)
                    nSector = nSector2;
            }
            dassert(nSector >= 0);
            pSprite->cstat = bakCstat;
        }
        switch (gSpriteHit[nXSprite].hit&0xc000)
        {
        case 0xc000:
        {
            int nHitSprite = gSpriteHit[nXSprite].hit&0x3fff;
            spritetype *pHitSprite = &sprite[nHitSprite];
            XSPRITE *pHitXSprite = NULL;
            // Should be pHitSprite here
            if (pSprite->extra > 0)
                pHitXSprite = &xsprite[pHitSprite->extra];
            int nOwner = actSpriteOwnerToSpriteId(pHitSprite);
            if (pHitSprite->statnum == 5 && !(pHitSprite->flags&32) && pSprite->index != nOwner)
            {
                HITINFO hitInfo = gHitInfo;
                gHitInfo.hitsprite = nSprite;
                actImpactMissile(pHitSprite, 3);
                gHitInfo = hitInfo;
            }
                                                  // by NoOne: this is why touch for things never worked; they always ON
            if (pHitXSprite && pHitXSprite->Touch /*&& !pHitXSprite->state*/ && !pHitXSprite->isTriggered) {
                if (!pHitXSprite->DudeLockout || IsPlayerSprite(pSprite)) // allow dudeLockout for Touch flag
                    trTriggerSprite(nHitSprite, pHitXSprite, 33);
            } if (pDudeInfo->lockOut && pHitXSprite && pHitXSprite->Push && !pHitXSprite->key && !pHitXSprite->DudeLockout && !pHitXSprite->state && !pHitXSprite->busy && !pPlayer)
                trTriggerSprite(nHitSprite, pHitXSprite, 30);
            break;
        }
        case 0x8000:
        {
            int nHitWall = gSpriteHit[nXSprite].hit&0x3fff;
            walltype *pHitWall = &wall[nHitWall];
            XWALL *pHitXWall = NULL;
            if (pHitWall->extra > 0)
                pHitXWall = &xwall[pHitWall->extra];
            if (pDudeInfo->lockOut && pHitXWall && pHitXWall->triggerPush && !pHitXWall->key && !pHitXWall->dudeLockout && !pHitXWall->state && !pHitXWall->busy && !pPlayer)
                trTriggerWall(nHitWall, pHitXWall, 50);
            if (pHitWall->nextsector != -1)
            {
                sectortype *pHitSector = &sector[pHitWall->nextsector];
                XSECTOR *pHitXSector = NULL;
                if (pHitSector->extra > 0)
                    pHitXSector = &xsector[pHitSector->extra];
                if (pDudeInfo->lockOut && pHitXSector && pHitXSector->Wallpush && !pHitXSector->Key && !pHitXSector->at37_7 && !pHitXSector->state && !pHitXSector->busy && !pPlayer)
                    trTriggerSector(pHitWall->nextsector, pHitXSector, 40);
                if (top < pHitSector->ceilingz || bottom > pHitSector->floorz)
                {
                    // ???
                }
            }
            actWallBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], nHitWall, 0);
            break;
        }
        }
    }
    else
    {
        dassert(nSector >= 0 && nSector < kMaxSectors);
        FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
    }
    if (pSprite->sectnum != nSector)
    {
        dassert(nSector >= 0 && nSector < kMaxSectors);
        XSECTOR *pXSector;
        int nXSector = sector[pSprite->sectnum].extra;
        if (nXSector > 0)
            pXSector = &xsector[nXSector];
        else
            pXSector = NULL;
        if (pXSector && pXSector->Exit && (pPlayer || !pXSector->at37_7))
            trTriggerSector(pSprite->sectnum, pXSector, 43);
        ChangeSpriteSect(nSprite, nSector);
        nXSector = sector[nSector].extra;
        if (nXSector > 0)
            pXSector = &xsector[nXSector];
        else
            pXSector = NULL;
        if (pXSector && pXSector->Enter && (pPlayer || !pXSector->at37_7))
        {
            if (sector[nSector].type == 604)
                pXSector->data = pPlayer ? nSprite : -1;
            trTriggerSector(nSector, pXSector, 42);
        }
        nSector = pSprite->sectnum;
    }
    char bUnderwater = 0;
    char bDepth = 0;
    if (sector[nSector].extra > 0)
    {
        XSECTOR *pXSector = &xsector[sector[nSector].extra];
        if (pXSector->Underwater)
            bUnderwater = 1;
        if (pXSector->Depth)
            bDepth = 1;
    }
    int nUpperLink = gUpperLink[nSector];
    int nLowerLink = gLowerLink[nSector];
    if (nUpperLink >= 0 && (sprite[nUpperLink].type == 9 || sprite[nUpperLink].type == 13))
        bDepth = 1;
    if (nLowerLink >= 0 && (sprite[nLowerLink].type == 10 || sprite[nLowerLink].type == 14))
        bDepth = 1;
    if (pPlayer)
        wd += 16;
    if (zvel[nSprite])
        pSprite->z += zvel[nSprite]>>8;
    int ceilZ, ceilHit, floorZ, floorHit;
    GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, CLIPMASK0, PARALLAXCLIP_CEILING|PARALLAXCLIP_FLOOR);
    GetSpriteExtents(pSprite, &top, &bottom);
    if (pSprite->flags & 2)
    {
        int vc = 58254;
        if (bDepth)
        {
            if (bUnderwater)
            {
                int cz = getceilzofslope(nSector, pSprite->x, pSprite->y);
                if (cz > top)
                    vc += ((bottom-cz)*-80099) / (bottom-top);
                else
                    vc = 0;
            }
            else
            {
                int fz = getflorzofslope(nSector, pSprite->x, pSprite->y);
                if (fz < bottom)
                    vc += ((bottom-fz)*-80099) / (bottom-top);
            }
        }
        else
        {
            if (bUnderwater)
                vc = 0;
            else if (bottom >= floorZ)
                vc =  0;
        }
        if (vc)
        {
            pSprite->z += ((vc*4)/2)>>8;
            zvel[nSprite] += vc;
        }
    }
    if (pPlayer && zvel[nSprite] > 0x155555 && !pPlayer->at31b && pXSprite->height > 0)
    {
        pPlayer->at31b = 1;
        sfxPlay3DSound(pSprite, 719, 0, 0);
    }
    vec3_t const oldpos = pSprite->pos;
    int nLink = CheckLink(pSprite);
    if (nLink)
    {
        GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, CLIPMASK0, PARALLAXCLIP_CEILING|PARALLAXCLIP_FLOOR);
        if (pPlayer)
        {
            if (bVanilla)
                playerResetInertia(pPlayer);
            else
                playerCorrectInertia(pPlayer, &oldpos);
        }
        switch (nLink)
        {
        case 12:
            if (pPlayer == gView)
                SetBitString(gotpic, sector[pSprite->sectnum].floorpicnum);
            break;
        case 11:
            if (pPlayer == gView)
                SetBitString(gotpic, sector[pSprite->sectnum].ceilingpicnum);
            break;
        case 10:
        case 14:
            pXSprite->medium = 0;
            if (pPlayer)
            {
                pPlayer->at2f = 0;
                pPlayer->at302 = 0;
                if (!pPlayer->at31c && pPlayer->atc.buttonFlags.jump)
                {
                    zvel[nSprite] = -0x6aaaa;
                    pPlayer->at31c = 1;
                }
                sfxPlay3DSound(pSprite, 721, -1, 0);
            }
            else
            {
                switch (pSprite->type)
                {
                case 201:
                case 202:
                    aiNewState(pSprite, pXSprite, &cultistGoto);
                    break;
                case 217:
                    aiNewState(pSprite, pXSprite, &gillBeastGoto);
                    pSprite->flags |= 6;
                    break;
                case 218:
                    actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000<<4);
                    break;
                }
            }
            break;
        // By NoOne: part of "change of global view palette for stacks" feature
        case kMarkerUpWater:
        case kMarkerUpGoo:
        {
            pXSprite->medium = nLink == kMarkerUpGoo ? 2 : 1;

            if (pPlayer)
            {
                // look for palette in data2 of marker. If value <= 0, use default ones.
                pPlayer->nWaterPal = 0;
                int nXUpper = sprite[gUpperLink[nSector]].extra;
                if (nXUpper >= 0)
                    pPlayer->nWaterPal = xsprite[nXUpper].data2;

                pPlayer->at2f = 1;
                pXSprite->burnTime = 0;
                pPlayer->at302 = klabs(zvel[nSprite]) >> 12;
                evPost(nSprite, 3, 0, CALLBACK_ID_10);
                sfxPlay3DSound(pSprite, 720, -1, 0);
            }
            else
            {
                switch (pSprite->type)
                {
                case 201:
                case 202:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &cultistSwimGoto);
                    break;
                case 240:
                {
                    // There is no difference between water and goo except following chance:
                    if (Chance(nLink == kMarkerUpGoo ? 0x400 : 0xa00))
                    {
                        pSprite->type = 201;
                        pXSprite->burnTime = 0;
                        evPost(nSprite, 3, 0, CALLBACK_ID_11);
                        sfxPlay3DSound(pSprite, 720, -1, 0);
                        aiNewState(pSprite, pXSprite, &cultistSwimGoto);
                    }
                    else
                    {
                        pSprite->type = 202;
                        pXSprite->burnTime = 0;
                        evPost(nSprite, 3, 0, CALLBACK_ID_11);
                        sfxPlay3DSound(pSprite, 720, -1, 0);
                        aiNewState(pSprite, pXSprite, &cultistSwimGoto);
                    }
                    break;
                }
                case 203:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &zombieAGoto);
                    break;
                case 204:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &zombieFGoto);
                    break;
                case 217:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &gillBeastSwimGoto);
                    pSprite->flags &= ~6;
                    break;
                case 206:
                case 211:
                case 213:
                case 214:
                case 215:
                case 219:
                case 220:
                case 239:
                    actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000 << 4);
                    break;
                case kCustomDude:
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    if (!canSwim(pSprite)) actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000 << 4);
                    break;
                }
            }
            break;
        }
        /*case 13:
            pXSprite->medium = 2;
            if (pPlayer)
            {
                pPlayer->changeTargetKin = 1;
                pXSprite->burnTime = 0;
                pPlayer->at302 = klabs(zvel[nSprite])>>12;
                evPost(nSprite, 3, 0, CALLBACK_ID_10);
                sfxPlay3DSound(pSprite, 720, -1, 0);
            }
            else
            {
                switch (pSprite->type)
                {
                case 201:
                case 202:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &cultistSwimGoto);
                    break;
                case 240:
                    if (Chance(0x400))
                    {
                        pSprite->type = 201;
                        pXSprite->burnTime = 0;
                        evPost(nSprite, 3, 0, CALLBACK_ID_11);
                        sfxPlay3DSound(pSprite, 720, -1, 0);
                        aiNewState(pSprite, pXSprite, &cultistSwimGoto);
                    }
                    else
                    {
                        pSprite->type = 202;
                        pXSprite->burnTime = 0;
                        evPost(nSprite, 3, 0, CALLBACK_ID_11);
                        sfxPlay3DSound(pSprite, 720, -1, 0);
                        aiNewState(pSprite, pXSprite, &cultistSwimGoto);
                    }
                    break;
                case 203:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &zombieAGoto);
                    break;
                case 204:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &zombieFGoto);
                    break;
                case 217:
                    pXSprite->burnTime = 0;
                    evPost(nSprite, 3, 0, CALLBACK_ID_11);
                    sfxPlay3DSound(pSprite, 720, -1, 0);
                    aiNewState(pSprite, pXSprite, &gillBeastSwimGoto);
                    pSprite->flags &= ~6;
                    break;
                case 206:
                case 211:
                case 213:
                case 214:
                case 215:
                case 219:
                case 220:
                case 239:
                    actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000<<4);
                    break;
                }
            }
            break;*/
        }
    }
    GetSpriteExtents(pSprite, &top, &bottom);
    if (pPlayer && bottom >= floorZ)
    {
        int floorZ2 = floorZ;
        int floorHit2 = floorHit;
        GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0, PARALLAXCLIP_CEILING|PARALLAXCLIP_FLOOR);
        if (bottom <= floorZ && pSprite->z - floorZ2 < bz)
        {
            floorZ = floorZ2;
            floorHit = floorHit2;
        }
    }
    if (floorZ <= bottom)
    {
        gSpriteHit[nXSprite].florhit = floorHit;
        pSprite->z += floorZ-bottom;
        int v30 = zvel[nSprite]-velFloor[pSprite->sectnum];
        if (v30 > 0)
        {
            int vax = actFloorBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], (int*)&v30, pSprite->sectnum, 0);
            int nDamage = mulscale(vax, vax, 30);
            if (pPlayer)
            {
                pPlayer->at31b = 0;
                if (nDamage > (15<<4) && (pSprite->flags&4))
                    playerLandingSound(pPlayer);
                if (nDamage > (30<<4))
                    sfxPlay3DSound(pSprite, 701, 0, 0);
            }
            nDamage -= 100<<4;
            if (nDamage > 0)
                actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, nDamage);
            zvel[nSprite] = v30;
            if (klabs(zvel[nSprite]) < 0x10000)
            {
                zvel[nSprite] = velFloor[pSprite->sectnum];
                pSprite->flags &= ~4;
            }
            else
                pSprite->flags |= 4;
            switch (tileGetSurfType(floorHit))
            {
            case kSurfWater:
                gFX.fxSpawn(FX_9, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
                break;
            case kSurfLava:
            {
                spritetype *pFX = gFX.fxSpawn(FX_10, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
                if (pFX)
                {
                    for (int i = 0; i < 7; i++)
                    {
                        spritetype *pFX2 = gFX.fxSpawn(FX_14, pFX->sectnum, pFX->x, pFX->y, pFX->z, 0);
                        if (pFX2)
                        {
                            xvel[pFX2->index] = Random2(0x6aaaa);
                            yvel[pFX2->index] = Random2(0x6aaaa);
                            zvel[pFX2->index] = -Random(0xd5555);
                        }
                    }
                }
                break;
            }
            }
        }
        else if (zvel[nSprite] == 0)
            pSprite->flags &= ~4;
    }
    else
    {
        gSpriteHit[nXSprite].florhit = 0;
        if (pSprite->flags&2)
            pSprite->flags |= 4;
    }
    if (top <= ceilZ)
    {
        gSpriteHit[nXSprite].ceilhit = ceilHit;
        pSprite->z += ClipLow(ceilZ-top, 0);
        if (zvel[nSprite] <= 0 && (pSprite->flags&4))
            zvel[nSprite] = mulscale16(-zvel[nSprite], 0x2000);
    }
    else
        gSpriteHit[nXSprite].ceilhit = 0;
    GetSpriteExtents(pSprite,&top,&bottom);

    pXSprite->height = ClipLow(floorZ-bottom, 0)>>8;
    if (xvel[nSprite] || yvel[nSprite])
    {
        if ((floorHit & 0xc000) == 0xc000)
        {
            int nHitSprite = floorHit & 0x3fff;
            if ((sprite[nHitSprite].cstat & 0x30) == 0)
            {
                xvel[nSprite] += mulscale(4, pSprite->x - sprite[nHitSprite].x, 2);
                yvel[nSprite] += mulscale(4, pSprite->y - sprite[nHitSprite].y, 2);
                return;
            }
        }
        int nXSector = sector[pSprite->sectnum].extra;
        if (nXSector > 0 && xsector[nXSector].Underwater)
            return;
        if (pXSprite->height >= 0x100)
            return;
        int nDrag = gDudeDrag;
        if (pXSprite->height > 0)
            nDrag -= scale(gDudeDrag, pXSprite->height, 0x100);
        xvel[nSprite] -= mulscale16r(xvel[nSprite], nDrag);
        yvel[nSprite] -= mulscale16r(yvel[nSprite], nDrag);

        if (approxDist(xvel[nSprite], yvel[nSprite]) < 0x1000)
            xvel[nSprite] = yvel[nSprite] = 0;
    }
}

int MoveMissile(spritetype *pSprite)
{
    int nXSprite = pSprite->extra;
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int vdi = -1;
    spritetype *pOwner = NULL;
    int bakCstat = 0;
    if (pSprite->owner >= 0)
    {
        int nOwner = actSpriteOwnerToSpriteId(pSprite);
        pOwner = &sprite[nOwner];
        if (IsDudeSprite(pOwner))
        {
            bakCstat = pOwner->cstat;
            pOwner->cstat &= ~257;
        }
        else
            pOwner = NULL;
    }
    gHitInfo.hitsect = -1;
    gHitInfo.hitwall = -1;
    gHitInfo.hitsprite = -1;
    if (pSprite->type == 304)
        actAirDrag(pSprite, 0x1000);
    int nSprite = pSprite->index;
    if (pXSprite->target != -1 && (xvel[nSprite] || yvel[nSprite] || zvel[nSprite]))
    {
        spritetype *pTarget = &sprite[pXSprite->target];
        XSPRITE *pXTarget;
        if (pTarget->extra > 0)
            pXTarget = &xsprite[pTarget->extra];
        else
            pXTarget = NULL;
        if (pTarget->statnum == 6 && pXTarget && pXTarget->health > 0)
        {
            int nTargetAngle = getangle(-(pTarget->y-pSprite->y), pTarget->x-pSprite->x);
            int UNUSED(nAngle) = getangle(xvel[nSprite]>>12,yvel[nSprite]>>12);
            int vx = missileInfo[pSprite->type-300].at2;
            int vy = 0;
            RotatePoint(&vx, &vy, (nTargetAngle+1536)&2047, 0, 0);
            xvel[nSprite] = vx;
            yvel[nSprite] = vy;
            int dx = pTarget->x-pSprite->x;
            int dy = pTarget->y-pSprite->y;
            int dz = pTarget->z-pSprite->z;
            // Inlined
            int vax = dz/10;
            if (pTarget->z < pSprite->z)
                vax = -vax;
            zvel[nSprite] += vax;
            ksqrt(dx*dx+dy*dy+dz*dz);
        }
    }
    int vx = xvel[nSprite]>>12;
    int vy = yvel[nSprite]>>12;
    int vz = zvel[nSprite]>>8;
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    int i = 1;
    while (1)
    {
        int x = pSprite->x;
        int y = pSprite->y;
        int z = pSprite->z;
        int nSector2 = pSprite->sectnum;
        clipmoveboxtracenum = 1;
        int vdx = ClipMove(&x, &y, &z, &nSector2, vx, vy, pSprite->clipdist<<2, (z-top)/4, (bottom-z)/4, CLIPMASK0);
        clipmoveboxtracenum = 3;
        short nSector = nSector2;
        if (nSector2 < 0)
        {
            vdi = -1;
            break;
        }
        if (vdx)
        {
            int nHitSprite = vdx & 0x3fff;
            if ((vdx&0xc000) == 0xc000)
            {
                gHitInfo.hitsprite = nHitSprite;
                vdi = 3;
            }
            else if ((vdx & 0xc000) == 0x8000)
            {
                gHitInfo.hitwall = nHitSprite;
                if (wall[nHitSprite].nextsector == -1)
                    vdi = 0;
                else
                {
                    int32_t fz, cz;
                    getzsofslope(wall[nHitSprite].nextsector, x, y, &cz, &fz);
                    if (z <= cz || z >= fz)
                        vdi = 0;
                    else
                        vdi = 4;
                }
            }
        }
        if (vdi == 4)
        {
            walltype *pWall = &wall[gHitInfo.hitwall];
            if (pWall->extra > 0)
            {
                XWALL *pXWall = &xwall[pWall->extra];
                if (pXWall->triggerVector)
                {
                    trTriggerWall(gHitInfo.hitwall, pXWall, 51);
                    if (!(pWall->cstat&64))
                    {
                        vdi = -1;
                        if (i-- > 0)
                            continue;
                        vdi = 0;
                        break;
                    }
                }
            }
        }
        if (vdi >= 0 && vdi != 3)
        {
            int nAngle = getangle(xvel[nSprite], yvel[nSprite]);
            x -= mulscale30(Cos(nAngle), 16);
            y -= mulscale30(Sin(nAngle), 16);
            int nVel = approxDist(xvel[nSprite], yvel[nSprite]);
            vz -= scale(0x100, zvel[nSprite], nVel);
            updatesector(x, y, &nSector);
            nSector2 = nSector;
        }
        int ceilZ, ceilHit, floorZ, floorHit;
        GetZRangeAtXYZ(x, y, z, nSector2, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0);
        GetSpriteExtents(pSprite, &top, &bottom);
        top += vz;
        bottom += vz;
        if (bottom >= floorZ)
        {
            gSpriteHit[nXSprite].florhit = floorHit;
            vz += floorZ-bottom;
            vdi = 2;
        }
        if (top <= ceilZ)
        {
            gSpriteHit[nXSprite].ceilhit = ceilHit;
            vz += ClipLow(ceilZ-top, 0);
            vdi = 1;
        }
        pSprite->x = x;
        pSprite->y = y;
        pSprite->z = z+vz;
        updatesector(x, y, &nSector);
        if (nSector >= 0 && nSector != pSprite->sectnum)
        {
            dassert(nSector >= 0 && nSector < kMaxSectors);
            ChangeSpriteSect(nSprite, nSector);
        }
        CheckLink(pSprite);
        gHitInfo.hitsect = pSprite->sectnum;
        gHitInfo.hitx = pSprite->x;
        gHitInfo.hity = pSprite->y;
        gHitInfo.hitz = pSprite->z;
        break;
    }
    if (pOwner)
        pOwner->cstat = bakCstat;
    return vdi;
}

void actExplodeSprite(spritetype *pSprite)
{
    int nXSprite = pSprite->extra;
    if (nXSprite <= 0 || nXSprite >= kMaxXSprites)
        return;
    if (pSprite->statnum == 2)
        return;
    sfxKill3DSound(pSprite, -1, -1);
    evKill(pSprite->index, 3);
    int nType;
    switch (pSprite->type)
    {
    case 312:
        nType = 7;
        seqSpawn(4, 3, nXSprite, -1);
        if (Chance(0x8000))
            pSprite->cstat |= 4;
        sfxPlay3DSound(pSprite, 303, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    case 303:
        nType = 3;
        seqSpawn(9, 3, nXSprite, -1);
        if (Chance(0x8000))
            pSprite->cstat |= 4;
        sfxPlay3DSound(pSprite, 306, 24+(pSprite->index&3), 1);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    case 313:
    case 314:
        nType = 3;
        seqSpawn(5, 3, nXSprite, -1);
        sfxPlay3DSound(pSprite, 304, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    case 418:
        nType = 0;
        if (gSpriteHit[nXSprite].florhit == 0)
            seqSpawn(4,3,nXSprite,-1);
        else
            seqSpawn(3,3,nXSprite,-1);
        sfxPlay3DSound(pSprite, 303, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    case 401:
    case 402:
    case 419:
    case kGDXThingTNTProx:
        nType = 1;
        if (gSpriteHit[nXSprite].florhit == 0)
            seqSpawn(4,3,nXSprite,-1);
        else
            seqSpawn(3,3,nXSprite,-1);
        sfxPlay3DSound(pSprite, 304, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    case 420:
        nType = 4;
        seqSpawn(5, 3, nXSprite, -1);
        sfxPlay3DSound(pSprite, 307, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    case 400:
    {
        spritetype *pSprite2 = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0, 1);
        pSprite2->owner = pSprite->owner;
        if (actCheckRespawn(pSprite))
        {
            XSPRITE *pXSprite = &xsprite[nXSprite];
            pXSprite->state = 1;
            pXSprite->health = thingInfo[0].at0<<4;
        }
        else
            actPostSprite(pSprite->index, 1024);
        nType = 2;
        nXSprite = pSprite2->extra;
        seqSpawn(4, 3, nXSprite, -1);
        sfxPlay3DSound(pSprite2, 305, -1, 0);
        GibSprite(pSprite2, GIBTYPE_14, NULL, NULL);
        pSprite = pSprite2;
        break;
    }
    case 459:	// By NoOne: allow to customize hidden exploder thing
	{
		// Defaults for exploder
		nType = 1; int nSnd = 304; int nSeq = 4;

		// Temp variables for override via data fields
		int tSnd = 0; int tSeq = 0;


		XSPRITE *pXSPrite = &xsprite[nXSprite];
		nType = pXSPrite->data1;  // Explosion type
		tSeq = pXSPrite->data2; // SEQ id
		tSnd = pXSPrite->data3; // Sound Id

		if (nType <= 1 || nType > kExplodeMax) { nType = 1; nSeq = 4; nSnd = 304; }
		else if (nType == 2) { nSeq = 4; nSnd = 305; }
		else if (nType == 3) { nSeq = 9; nSnd = 307; }
		else if (nType == 4) { nSeq = 5; nSnd = 307; }
		else if (nType <= 6) { nSeq = 4; nSnd = 303; }
		else if (nType == 7) { nSeq = 4; nSnd = 303; }
		else if (nType == 8) { nType = 0; nSeq = 3; nSnd = 303; }

		// Override previous sound and seq assigns
		if (tSeq > 0) nSeq = tSeq;
		if (tSnd > 0) nSnd = tSnd;

		if (gSysRes.Lookup(pXSPrite->data2, "SEQ"))
		    seqSpawn(nSeq, 3, nXSprite, -1);

		sfxPlay3DSound(pSprite, nSnd, -1, 0);
	}
        break;
    case 429:
        nType = 3;
        seqSpawn(9, 3, nXSprite, -1);
        sfxPlay3DSound(pSprite, 307, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        sub_746D4(pSprite, 240);
        break;
    default:
        nType = 1;
        seqSpawn(4, 3, nXSprite, -1);
        if (Chance(0x8000))
            pSprite->cstat |= 4;
        sfxPlay3DSound(pSprite, 303, -1, 0);
        GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
        break;
    }
    int nSprite = pSprite->index;
    xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
    actPostSprite(nSprite, 2);
    pSprite->xrepeat = pSprite->yrepeat = explodeInfo[nType].at0;
    pSprite->flags &= ~3;
    pSprite->type = nType;
    EXPLOSION *pExplodeInfo = &explodeInfo[nType];
    xsprite[nXSprite].target = 0;
    xsprite[nXSprite].data1 = pExplodeInfo->atf;
    xsprite[nXSprite].data2 = pExplodeInfo->at13;
    xsprite[nXSprite].data3 = pExplodeInfo->at17;
}

void actActivateGibObject(spritetype *pSprite, XSPRITE *pXSprite)
{
    int vdx = ClipRange(pXSprite->data1, 0, 31);
    int vc = ClipRange(pXSprite->data2, 0, 31);
    int v4 = ClipRange(pXSprite->data3, 0, 31);
    int vbp = pXSprite->data4;
    int v8 = pXSprite->dropMsg;
    if (vdx > 0)
        GibSprite(pSprite, (GIBTYPE)(vdx-1), NULL, NULL);
    if (vc > 0)
        GibSprite(pSprite, (GIBTYPE)(vc-1), NULL, NULL);
    if (v4 > 0 && pXSprite->burnTime > 0)
        GibSprite(pSprite, (GIBTYPE)(v4-1), NULL, NULL);
    if (vbp > 0)
        sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, vbp, pSprite->sectnum);
    if (v8 > 0)
        actDropObject(pSprite, v8);
    if (!(pSprite->cstat&32768) && !(pSprite->flags&16))
        actPostSprite(pSprite->index, 1024);
}

bool IsUnderWater(spritetype *pSprite)
{
    int nSector = pSprite->sectnum;
    int nXSector = sector[nSector].extra;
    if (nXSector > 0 && nXSector < kMaxXSectors)
        if (xsector[nXSector].Underwater)
            return 1;
    return 0;
}

void MakeSplash(spritetype *pSprite, XSPRITE *pXSprite);

void actProcessSprites(void)
{
    int nSprite;
    int nNextSprite;
    
    if (!VanillaMode()) {

        // by NoOne: process additional proximity sprites
        if (gProxySpritesCount > 0) {
            for (int i = 0; i < gProxySpritesCount; i++) {
                if (sprite[gProxySpritesList[i]].extra < 0) continue;

                XSPRITE * pXProxSpr = &xsprite[sprite[gProxySpritesList[i]].extra];
                if (!pXProxSpr->Proximity || (!pXProxSpr->Interrutable && pXProxSpr->state != pXProxSpr->restState)  || pXProxSpr->locked == 1 
                    || pXProxSpr->isTriggered) continue;  // don't process locked or triggered sprites

                int x = sprite[gProxySpritesList[i]].x;	int y = sprite[gProxySpritesList[i]].y;
                int z = sprite[gProxySpritesList[i]].z;	int index = sprite[gProxySpritesList[i]].xvel;
                int sectnum = sprite[gProxySpritesList[i]].sectnum;

                if (!pXProxSpr->DudeLockout) {

                    for (int nAffected = headspritestat[6]; nAffected >= 0; nAffected = nextspritestat[nAffected]) {
                        if ((sprite[nAffected].flags & 32) || xsprite[sprite[nAffected].extra].health <= 0) continue;
                        else if (CheckProximity(&sprite[nAffected], x, y, z, sectnum, 96)) {
                            trTriggerSprite(index, pXProxSpr, 35);
                            break;
                        }
                    }

                } else {

                    for (int a = connecthead; a >= 0; a = connectpoint2[a]) {
                        if (gPlayer[a].pXSprite->health > 0 && CheckProximity(gPlayer[a].pSprite, x, y, z, sectnum, 96)) {
                            trTriggerSprite(index, pXProxSpr, 35);
                            break;
                        }
                    }

                }
            }
        }

        // by NoOne: process sight sprites (for players only)
        if (gSightSpritesCount > 0) {
            for (int i = 0; i < gSightSpritesCount; i++) {
                if (sprite[gSightSpritesList[i]].extra < 0) continue;

                XSPRITE * pXSightSpr = &xsprite[sprite[gSightSpritesList[i]].extra];
                if (!pXSightSpr->Sight || (!pXSightSpr->Interrutable && pXSightSpr->state != pXSightSpr->restState) || pXSightSpr->locked == 1 || 
                    pXSightSpr->isTriggered) continue; // don't process locked or triggered sprites

                int x = sprite[gSightSpritesList[i]].x;	int y = sprite[gSightSpritesList[i]].y;
                int z = sprite[gSightSpritesList[i]].z;	int index = sprite[gSightSpritesList[i]].xvel;
                int sectnum = sprite[gSightSpritesList[i]].sectnum;

                for (int a = connecthead; a >= 0; a = connectpoint2[a]) {
                    spritetype* pPlaySprite = gPlayer[a].pSprite;
                    if (gPlayer[a].pXSprite->health > 0 && cansee(x, y, z, sectnum, pPlaySprite->x, pPlaySprite->y, pPlaySprite->z, pPlaySprite->sectnum)) {
                        trTriggerSprite(index, pXSightSpr, 34);
                        break;
                    }
                }
            }
        }

        // by NoOne: process Debris sprites for movement
        if (gPhysSpritesCount > 0) {
            //System.err.println("PHYS COUNT: "+gPhysSpritesCount);
            for (int i = 0; i < gPhysSpritesCount; i++) {
                if (gPhysSpritesList[i] == -1) continue;
                else if (sprite[gPhysSpritesList[i]].statnum == kStatFree || (sprite[gPhysSpritesList[i]].flags & kHitagFree) != 0) {
                    gPhysSpritesList[i] = -1;
                    continue;
                }
                
                XSPRITE* pXDebris = &xsprite[sprite[gPhysSpritesList[i]].extra];
                if (!(pXDebris->physAttr & kPhysMove) && !(pXDebris->physAttr & kPhysGravity)) {
                    gPhysSpritesList[i] = -1;
                    continue;
                }

                spritetype* pDebris = &sprite[gPhysSpritesList[i]];
                XSECTOR* pXSector = (sector[pDebris->sectnum].extra >= 0) ? &xsector[sector[pDebris->sectnum].extra] : NULL;

                viewBackupSpriteLoc(pDebris->xvel, pDebris);
                int airVel = gSpriteMass[pDebris->extra].airVel;
                if (pXSector != NULL) {
                    if (pXSector->Underwater) airVel <<= 6;
                    if (pXSector->panVel != 0) {
                        int top, bottom;
                        GetSpriteExtents(pDebris,&top,&bottom);

                        if (getflorzofslope(pDebris->sectnum, pDebris->x, pDebris->y) <= bottom)
                        {
                            int angle = pXSector->panAngle;
                            int speed = 0;
                            if (pXSector->panAlways || pXSector->state || pXSector->busy)
                            {
                                speed = pXSector->panVel << 9;
                                if (!pXSector->panAlways && pXSector->busy)
                                    speed = mulscale16(speed, pXSector->busy);
                            }
                            if (sector[pDebris->sectnum].floorstat & 64)
                                angle = (angle + GetWallAngle(sector[pDebris->sectnum].wallptr) + 512) & 2047;
                            int dx = mulscale30(speed, Cos(angle));
                            int dy = mulscale30(speed, Sin(angle));
                            xvel[pDebris->xvel] += dx;
                            yvel[pDebris->xvel] += dy;
                        }
                    }
                }

                actAirDrag(pDebris, airVel);

                if (((pDebris->index >> 8) & 15) == (gFrame & 15) && (pXDebris->physAttr & kPhysGravity))
                    pXDebris->physAttr |= kPhysFalling;

                if ((pXDebris->physAttr & 4) == 0 && xvel[pDebris->xvel] == 0 && yvel[pDebris->xvel] == 0 &&
                    zvel[pDebris->xvel] == 0 && velFloor[pDebris->sectnum] == 0 && velCeil[pDebris->sectnum] == 0)
                    continue;

                debrisMove(i);

            }
        }
    }

    for (nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags&32)
            continue;
        int nXSprite = pSprite->extra;
        if (nXSprite > 0)
        {
            XSPRITE *pXSprite = &xsprite[nXSprite];
            if (pSprite->type == 425 || pSprite->type == 426 || pSprite->type == 427)
                if (pXSprite->locked && gFrameClock >= pXSprite->targetX)
                    pXSprite->locked = 0;
            if (pXSprite->burnTime > 0)
            {
                pXSprite->burnTime = ClipLow(pXSprite->burnTime-4,0);
                actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
            }
                                       // by NoOne: don't process locked or 1-shot things for proximity
            if (pXSprite->Proximity && (VanillaMode() || (pXSprite->locked != 1 && pXSprite->isTriggered != true))) {
                if (pSprite->type == 431) pXSprite->target = -1;
                for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nNextSprite)
                {
                    
                    nNextSprite = nextspritestat[nSprite2];
                    spritetype *pSprite2 = &sprite[nSprite2];
                    if (pSprite2->flags&32) continue;
                    XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
                    if ((unsigned int)pXSprite2->health > 0)
                    {
                   
                        // by NoOne: allow dudeLockout for proximity flag
                        if (pSprite->type != 431 && pXSprite->DudeLockout && !IsPlayerSprite(pSprite2)) 
                            continue;

                        int proxyDist = 96;
                        if (pSprite->type == kGDXThingCustomDudeLifeLeech) proxyDist = 512;
                        else if (pSprite->type == 431 && pXSprite->target == -1)
                        {
                            int nOwner = actOwnerIdToSpriteId(pSprite->owner);
                            spritetype *pOwner = &sprite[nOwner];
                            PLAYER *pPlayer = &gPlayer[pOwner->type-kDudePlayer1];
                            PLAYER *pPlayer2 = NULL;
                            if (IsPlayerSprite(pSprite2))
                                pPlayer2 = &gPlayer[pSprite2->type-kDudePlayer1];
                            if (nSprite2 == nOwner || pSprite2->type == 205 || pSprite2->type == 220 || pSprite2->type == 219)
                                continue;
                            if (gGameOptions.nGameType == 3 && pPlayer2 && pPlayer->at2ea == pPlayer2->at2ea)
                                continue;
                            if (gGameOptions.nGameType == 1 && pPlayer2)
                                continue;
                            proxyDist = 512;
                        }
                        if (CheckProximity(pSprite2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, proxyDist)) {

                            switch (pSprite->type) {
                                case kGDXThingTNTProx:
                                    if (!IsPlayerSprite(pSprite2)) continue;
                                    pSprite->pal = 0;
                                    break;
                                case 431:
                                    if (!Chance(0x4000) && nNextSprite >= 0) continue;
                                    if (pSprite2->cstat & CLIPMASK0) pXSprite->target = pSprite2->index;
                                    else continue;
                                    break;
                                case kGDXThingCustomDudeLifeLeech:
                                    if (pXSprite->target != pSprite2->xvel) continue;
                                    break;
                            }
                            if (pSprite->owner == -1) actPropagateSpriteOwner(pSprite, pSprite2);
                            trTriggerSprite(nSprite, pXSprite, 35);
                        }
                    }
                }
            }
        }
    }
    for (nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        int nSector = pSprite->sectnum;
        int nXSprite = pSprite->extra;
        dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
        int nXSector = sector[nSector].extra;
        XSECTOR *pXSector = NULL;
        if (nXSector > 0)
        {
            dassert(nXSector > 0 && nXSector < kMaxXSectors);
            dassert(xsector[nXSector].reference == nSector);
            pXSector = &xsector[nXSector];
        }
        if (pXSector && pXSector->panVel && (pXSector->panAlways || pXSector->state || pXSector->busy))
        {
            int nType = pSprite->type - kThingBase;
            THINGINFO *pThingInfo = &thingInfo[nType];
            if (pThingInfo->at5 & 1)
                pSprite->flags |= 1;
            if (pThingInfo->at5 & 2)
                pSprite->flags |= 4;
        }
        if (pSprite->flags&3)
        {
            viewBackupSpriteLoc(nSprite, pSprite);
            if (pXSector && pXSector->panVel)
            {
                int top, bottom;
                GetSpriteExtents(pSprite, &top, &bottom);
                if (getflorzofslope(nSector, pSprite->x, pSprite->y) <= bottom)
                {
                    int angle = pXSector->panAngle;
                    int speed = 0;
                    if (pXSector->panAlways || pXSector->state || pXSector->busy)
                    {
                        speed = pXSector->panVel << 9;
                        if (!pXSector->panAlways && pXSector->busy)
                            speed = mulscale16(speed, pXSector->busy);
                    }
                    if (sector[nSector].floorstat&64)
                        angle = (angle+GetWallAngle(sector[nSector].wallptr)+512)&2047;
                    int dx = mulscale30(speed, Cos(angle));
                    int dy = mulscale30(speed, Sin(angle));
                    xvel[nSprite] += dx;
                    yvel[nSprite] += dy;
                }
            }
            actAirDrag(pSprite, 128);
            if (((pSprite->index>>8)&15) == (gFrame&15) && (pSprite->flags&2))
                pSprite->flags |= 4;
            if ((pSprite->flags&4) || xvel[nSprite] || yvel[nSprite] || zvel[nSprite] ||
                velFloor[pSprite->sectnum] || velCeil[pSprite->sectnum])
            {
                int hit = MoveThing(pSprite);
                if (hit)
                {
                    int nXSprite = pSprite->extra;
                    if (nXSprite)
                    {
                        XSPRITE *pXSprite = &xsprite[nXSprite];
                        if (pXSprite->Impact)
                            trTriggerSprite(nSprite, pXSprite, 0);
                        switch (pSprite->type)
                        {
                        case 423:
                        case 424:
                            MakeSplash(pSprite, pXSprite);
                            break;
                        case kGDXThingThrowableRock:
                            seqSpawn(24, 3, nXSprite, -1);
                            if ((hit & 0xc000) == 0xc000)
                            {
                                pSprite->xrepeat = 32;
                                pSprite->yrepeat = 32;
                                int nObject = hit & 0x3fff;
                                dassert(nObject >= 0 && nObject < kMaxSprites);
                                spritetype * pObject = &sprite[nObject];
                                actDamageSprite(actSpriteOwnerToSpriteId(pSprite), pObject, DAMAGE_TYPE_0, pXSprite->data1);
                            }
                            break;
                        case 421:
                            seqSpawn(24, 3, nXSprite, -1);
                            if ((hit&0xc000) == 0xc000)
                            {
                                int nObject = hit & 0x3fff;
                                dassert(nObject >= 0 && nObject < kMaxSprites);
                                spritetype *pObject = &sprite[nObject];
                                actDamageSprite(actSpriteOwnerToSpriteId(pSprite), pObject, DAMAGE_TYPE_0, 12);
                            }
                            break;
                        case 430:
                            if ((hit&0xc000) == 0x4000)
                            {
                                sub_2A620(actSpriteOwnerToSpriteId(pSprite), pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 200, 1, 20, DAMAGE_TYPE_3, 6, 0, 0, 0);
                                evPost(pSprite->index, 3, 0, CALLBACK_ID_19);
                            }
                            else
                            {
                                int nObject = hit & 0x3fff;
                                if ((hit&0xc000) != 0xc000 && (nObject < 0 || nObject >= 4096))
                                    break;
                                dassert(nObject >= 0 && nObject < kMaxSprites);
                                spritetype *pObject = &sprite[nObject];
                                actDamageSprite(actSpriteOwnerToSpriteId(pSprite), pObject, DAMAGE_TYPE_0, 12);
                                evPost(pSprite->index, 3, 0, CALLBACK_ID_19);
                            }
                            break;
                        case 429:
                        {
                            int nObject = hit & 0x3fff;
                            if ((hit&0xc000) != 0xc000 && (nObject < 0 || nObject >= 4096))
                                break;
                            dassert(nObject >= 0 && nObject < kMaxSprites);
                            int UNUSED(nOwner) = actSpriteOwnerToSpriteId(pSprite);
                            actExplodeSprite(pSprite);
                            break;
                        }
                        }
                    }
                }
            }
        }
    }
    for (nSprite = headspritestat[5]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        viewBackupSpriteLoc(nSprite, pSprite);
        int hit = MoveMissile(pSprite);
        if (hit >= 0)
            actImpactMissile(pSprite, hit);
    }
    for (nSprite = headspritestat[2]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        char v24c[(kMaxSectors+7)>>3];
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        int nOwner = actSpriteOwnerToSpriteId(pSprite);
        int nType = pSprite->type;
        dassert(nType >= 0 && nType < kExplodeMax);
        EXPLOSION *pExplodeInfo = &explodeInfo[nType];
        int nXSprite = pSprite->extra;
        dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
        XSPRITE *pXSprite = &xsprite[nXSprite];
        int x = pSprite->x;
        int y = pSprite->y;
        int z = pSprite->z;
        int nSector = pSprite->sectnum;
        gAffectedSectors[0] = -1;
        gAffectedXWalls[0] = -1;
        
        // By NoOne: Allow to override explosion radius by data4 field of any sprite which have statnum 2 set in editor
        // or of Hidden Exploder.
        int radius = pXSprite->data4;
        if (pXSprite->data4 <= 0)
            radius = pExplodeInfo->radius;
        
        GetClosestSpriteSectors(nSector, x, y, radius, gAffectedSectors, v24c, gAffectedXWalls);
        
        for (int i = 0; i < kMaxXWalls; i++)
        {
            int nWall = gAffectedXWalls[i];
            if (nWall == -1)
                break;
            XWALL *pXWall = &xwall[wall[nWall].extra];
            trTriggerWall(nWall, pXWall, 51);
        }
        
        for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
        {
            spritetype *pDude = &sprite[nSprite2];
            if (pDude->flags & 32)
                continue;
            if (TestBitString(v24c, pDude->sectnum))
            {
                if (pXSprite->data1 && CheckProximity(pDude, x, y, z, nSector, radius))
                {
                    if (pExplodeInfo->at1 && pXSprite->target == 0)
                    {
                        pXSprite->target = 1;
                        actDamageSprite(nOwner, pDude, DAMAGE_TYPE_0, (pExplodeInfo->at1+Random(pExplodeInfo->at2))<<4);
                    }
                    if (pExplodeInfo->dmgType)
                        ConcussSprite(nOwner, pDude, x, y, z, pExplodeInfo->dmgType);
                    if (pExplodeInfo->atb)
                    {
                        dassert(pDude->extra > 0 && pDude->extra < kMaxXSprites);
                        XSPRITE *pXDude = &xsprite[pDude->extra];
                        if (!pXDude->burnTime)
                            evPost(nSprite2, 3, 0, CALLBACK_ID_0);
                        actBurnSprite(pSprite->owner, pXDude, pExplodeInfo->atb<<2);
                    }
                }
            }
        }
        
        for (int nSprite2 = headspritestat[4]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
        {
            spritetype *pThing = &sprite[nSprite2];
            if (pThing->flags & 32)
                continue;
            if (TestBitString(v24c, pThing->sectnum))
            {
                if (pXSprite->data1 && CheckProximity(pThing, x, y, z, nSector, radius))
                {
                    XSPRITE *pXSprite2 = &xsprite[pThing->extra];
                    if (!pXSprite2->locked)
                    {
                        if (pExplodeInfo->dmgType)
                            ConcussSprite(nOwner, pThing, x, y, z, pExplodeInfo->dmgType);
                        if (pExplodeInfo->atb)
                        {
                            dassert(pThing->extra > 0 && pThing->extra < kMaxXSprites);
                            XSPRITE *pXThing = &xsprite[pThing->extra];
                            if (pThing->type == 400 && !pXThing->burnTime)
                                evPost(nSprite2, 3, 0, CALLBACK_ID_0);
                            actBurnSprite(pSprite->owner, pXThing, pExplodeInfo->atb<<2);
                        }
                    }
                }
            }
        }
        
        // by NoOne: add impulse for sprites from physics list
        if (gPhysSpritesCount > 0 && pExplodeInfo->dmgType != 0 && pXSprite->data1 != 0) {
            for (int i = 0; i < gPhysSpritesCount; i++) {
                if (gPhysSpritesList[i] == -1) continue;
                else if (sprite[gPhysSpritesList[i]].sectnum < 0 || (sprite[gPhysSpritesList[i]].flags & kHitagFree) != 0)
                    continue;

                spritetype* pDebris = &sprite[gPhysSpritesList[i]];
                if (!TestBitString(v24c, pDebris->sectnum) || !CheckProximity(pDebris, x, y, z, nSector, radius)) continue;
                else debrisConcuss(nOwner, i, x, y, z, pExplodeInfo->dmgType);
            }
        }

        for (int p = connecthead; p >= 0; p = connectpoint2[p])
        {
            spritetype *pSprite2 = gPlayer[p].pSprite;
            int dx = (x - pSprite2->x)>>4;
            int dy = (y - pSprite2->y)>>4;
            int dz = (z - pSprite2->z)>>8;
            int nDist = dx*dx+dy*dy+dz*dz+0x40000;
            int t = divscale16(pXSprite->data2, nDist);
            gPlayer[p].at35a += t;
        }
        
        // By NoOne: if data4 > 0, do not remove explosion. This can be useful when designer wants put explosion generator in map manually
	    // via sprite statnum 2.
        if (!(pSprite->flags & kModernTypeFlag1)) {
            pXSprite->data1 = ClipLow(pXSprite->data1 - 4, 0);
            pXSprite->data2 = ClipLow(pXSprite->data2 - 4, 0);
            pXSprite->data3 = ClipLow(pXSprite->data3 - 4, 0);
        }

        if (pXSprite->data1 == 0 && pXSprite->data2 == 0 && pXSprite->data3 == 0 && seqGetStatus(3, nXSprite) < 0)
            actPostSprite(nSprite, 1024);
    }
   
    for (nSprite = headspritestat[11]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        int nXSprite = pSprite->extra;
        dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
        XSPRITE *pXSprite = &xsprite[nXSprite];
        switch (pSprite->type)
        {
        case 454:
            pXSprite->data2 = ClipLow(pXSprite->data2-4, 0);
            break;
        case 452:
            if (pXSprite->state && seqGetStatus(3, nXSprite) < 0)
            {
                int x = pSprite->x;
                int y = pSprite->y;
                int z = pSprite->z;
                int t = (pXSprite->data1<<23)/120;
                int dx = mulscale30(t, Cos(pSprite->ang));
                int dy = mulscale30(t, Sin(pSprite->ang));
                for (int i = 0; i < 2; i++)
                {
                    spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, z, 0);
                    if (pFX)
                    {
                        xvel[pFX->index] = dx + Random2(0x8888);
                        yvel[pFX->index] = dy + Random2(0x8888);
                        zvel[pFX->index] = Random2(0x8888);
                    }
                    x += (dx/2)>>12;
                    y += (dy/2)>>12;
                }
                dy = Sin(pSprite->ang)>>16;
                dx = Cos(pSprite->ang)>>16;
                gVectorData[VECTOR_TYPE_20].maxDist = pXSprite->data1<<9;
                actFireVector(pSprite, 0, 0, dx, dy, Random2(0x8888), VECTOR_TYPE_20);
            }
            break;
        }
    }
    for (nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        int nXSprite = pSprite->extra;
        if (nXSprite > 0)
        {
            XSPRITE *pXSprite = &xsprite[nXSprite];
            if (pXSprite->burnTime > 0)
            {
                switch (pSprite->type)
                {
                case 239:
                case 240:
                case 241:
                case 242:
                    actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
                    break;
                default:
                    pXSprite->burnTime = ClipLow(pXSprite->burnTime-4, 0);
                    actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
                    break;
                }
            }

            // By NoOne: handle incarnations of custom dude
            if (pSprite->type == kCustomDude && pXSprite->txID > 0 && pXSprite->health <= 0 && seqGetStatus(3, nXSprite) < 0) {
                XSPRITE* pXIncarnation = getNextIncarnation(pXSprite);
                if (pXIncarnation != NULL) {
                    spritetype* pIncarnation = &sprite[pXIncarnation->reference];
                    pXSprite->key = pXSprite->dropMsg = pXSprite->locked = 0;

                    // save incarnation's going on and off options
                    bool triggerOn = pXIncarnation->triggerOn;
                    bool triggerOff = pXIncarnation->triggerOff;

                    // then remove it from incarnation so it will not send the commands
                    pXIncarnation->triggerOn = false;
                    pXIncarnation->triggerOff = false;

                    // trigger dude death before transform
                    trTriggerSprite(nSprite, pXSprite, COMMAND_ID_0);

                    pSprite->type = pIncarnation->type;
                    pSprite->flags = pIncarnation->flags;
                    pSprite->pal = pIncarnation->pal;
                    pSprite->shade = pIncarnation->shade;
                    pSprite->clipdist = pIncarnation->clipdist;
                    pSprite->xrepeat = pIncarnation->xrepeat;
                    pSprite->yrepeat = pIncarnation->yrepeat;

                    pXSprite->txID = pXIncarnation->txID;
                    pXSprite->command = pXIncarnation->command;
                    pXSprite->triggerOn = triggerOn;
                    pXSprite->triggerOff = triggerOff;
                    pXSprite->busyTime = pXIncarnation->busyTime;
                    pXSprite->waitTime = pXIncarnation->waitTime;

                    pXSprite->burnTime = 0;
                    pXSprite->burnSource = -1;

                    pXSprite->data1 = pXIncarnation->data1;
                    short oldData2 = pXSprite->data2; pXSprite->data2 = pXIncarnation->data2; // seq new seqId and save old one.
                   
                    // if incarnation is active dude, it's sndStartId will be stored in sysData1, otherwise it will be data3
                    if (pIncarnation->statnum == 6 && pIncarnation->type == kCustomDude) pXSprite->sysData1 = pXIncarnation->sysData1;
                    else pXIncarnation->data3;
                    
                    pXSprite->data4 = pXIncarnation->data4;

                    pXSprite->dudeGuard = pXIncarnation->dudeGuard;
                    pXSprite->dudeDeaf = pXIncarnation->dudeDeaf;
                    pXSprite->dudeAmbush = pXIncarnation->dudeAmbush;
                    pXSprite->dudeFlag4 = pXIncarnation->dudeFlag4;

                    pXSprite->dropMsg = pXIncarnation->dropMsg;
                    pXSprite->key = pXIncarnation->key;
                    
                    pXSprite->locked = pXIncarnation->locked;
                    pXSprite->Decoupled = pXIncarnation->Decoupled;

                    // clear drop items of the incarnation
                    pXIncarnation->key = pXIncarnation->dropMsg = 0;

                    // set hp
                    if (pXSprite->data4 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
                    else pXSprite->health = ClipRange(pXSprite->data4 << 4, 1, 65535); 
                    
                    int seqId = dudeInfo[pSprite->type - kDudeBase].seqStartID;
                    switch (pSprite->type) {
                        case 225: // fake dude
                        case 226: // fake dude
                            break;
                        case kCustomDude:
                        case kCustomDudeBurning:
                            seqId = getSeqStartId(pXSprite);
                            if (seqId != oldData2)
                                getSpriteMassBySize(pSprite); // create or refresh mass cache 
                            fallthrough__; // go below
                        default:
                            seqSpawn(seqId, 3, nXSprite, -1);
                            
                            // save target
                            int target = pXSprite->target;

                            // re-init sprite
                            aiInitSprite(pSprite);

                            // try to restore target
                            if (target == -1) aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
                            else aiSetTarget(pXSprite, target);

                            // finally activate it
                            aiActivateDude(pSprite, pXSprite);
                            
                            break;
                    }

                    // remove the incarnation in case if non-locked
                    if (pXIncarnation->locked == 0) {
                        pXIncarnation->txID = pIncarnation->type = 0;
                        actPostSprite(pIncarnation->xvel, kStatFree);
                    // or restore triggerOn and off options
                    } else {
                        pXIncarnation->triggerOn = triggerOn;
                        pXIncarnation->triggerOff = triggerOff;
                    }
                
                } else {
                    
                    // just trigger dude death
                    trTriggerSprite(nSprite, pXSprite, COMMAND_ID_0);

                }
            }

            if (pSprite->type == 227)
            {
                if (pXSprite->health <= 0 && seqGetStatus(3, nXSprite) < 0)
                {
                    pXSprite->health = dudeInfo[28].startHealth<<4;
                    pSprite->type = 228;
                    if (pXSprite->target != -1)
                        aiSetTarget(pXSprite, pXSprite->target);
                    aiActivateDude(pSprite, pXSprite);
                }
            }
            if (pXSprite->Proximity && !pXSprite->isTriggered)
            {
                for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nNextSprite)
                {
                    nNextSprite = nextspritestat[nSprite2];
                    spritetype *pSprite2 = &sprite[nSprite2];
                    if (pSprite2->flags&32)
                        continue;
                    XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
                    if ((unsigned int)pXSprite2->health > 0 && pSprite2->type >= kDudePlayer1 && pSprite2->type <= kDudePlayer8)
                    {
                        if (CheckProximity(pSprite2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 128))
                        {
                            trTriggerSprite(nSprite, pXSprite, 35);
                        }
                    }
                }
            }
            if (IsPlayerSprite(pSprite))
            {
                PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
                if (pPlayer->at34e)
                    sub_41250(pPlayer);
                if (pPlayer->at376 && Chance(0x8000))
                    actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_4, 12);
                if (pPlayer->at87)
                {
                    char bActive = packItemActive(pPlayer, 1);
                    if (bActive || pPlayer->at31a)
                        pPlayer->at2f2 = 1200;
                    else
                        pPlayer->at2f2 = ClipLow(pPlayer->at2f2-4, 0);
                    if (pPlayer->at2f2 < 1080 && packCheckItem(pPlayer, 1) && !bActive)
                        packUseItem(pPlayer, 1);
                    if (!pPlayer->at2f2)
                    {
                        pPlayer->at36e += 4;
                        if (Chance(pPlayer->at36e))
                            actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_4, 3<<4);
                    }
                    else
                        pPlayer->at36e = 0;
                    if (xvel[nSprite] || yvel[nSprite])
                        sfxPlay3DSound(pSprite, 709, 100, 2);
                    pPlayer->at302 = ClipLow(pPlayer->at302-4, 0);
                }
                else if (gGameOptions.nGameType == 0)
                {
                    if (pPlayer->pXSprite->health > 0 && pPlayer->at30a >= 1200 && Chance(0x200))
                    {
                        pPlayer->at30a = -1;
                        sfxPlay3DSound(pSprite, 3100+Random(11), 0, 2);
                    }
                }
            }
            ProcessTouchObjects(pSprite, nXSprite);
        }
    }
    for (nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        int nXSprite = pSprite->extra;
        dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
        int nSector = pSprite->sectnum;
        viewBackupSpriteLoc(nSprite, pSprite);
        int nXSector = sector[nSector].extra;
        XSECTOR *pXSector = NULL;
        if (nXSector > 0)
        {
            dassert(nXSector > 0 && nXSector < kMaxXSectors);
            dassert(xsector[nXSector].reference == nSector);
            pXSector = &xsector[nXSector];
        }
        if (pXSector)
        {
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            if (getflorzofslope(nSector, pSprite->x, pSprite->y) <= bottom)
            {
                int angle = pXSector->panAngle;
                int speed = 0;
                if (pXSector->panAlways || pXSector->state || pXSector->busy)
                {
                    speed = pXSector->panVel << 9;
                    if (!pXSector->panAlways && pXSector->busy)
                        speed = mulscale16(speed, pXSector->busy);
                }
                if (sector[nSector].floorstat&64)
                    angle = (angle+GetWallAngle(sector[nSector].wallptr)+512)&2047;
                int dx = mulscale30(speed, Cos(angle));
                int dy = mulscale30(speed, Sin(angle));
                xvel[nSprite] += dx;
                yvel[nSprite] += dy;
            }
        }
        if (pXSector && pXSector->Underwater)
            actAirDrag(pSprite, 5376);
        else
            actAirDrag(pSprite, 128);
        if ((pSprite->flags&4) || xvel[nSprite] || yvel[nSprite] || zvel[nSprite] ||
            velFloor[pSprite->sectnum] || velCeil[pSprite->sectnum])
            MoveDude(pSprite);
    }
    for (nSprite = headspritestat[14]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->flags & 32)
            continue;
        int nXSprite = pSprite->extra;
        dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
        XSPRITE *pXSprite = &xsprite[nXSprite];
        int nTarget = pXSprite->target;
        dassert(nTarget >= 0);
        viewBackupSpriteLoc(nSprite, pSprite);
        dassert(nTarget < kMaxSprites);
        spritetype *pTarget = &sprite[nTarget];
        if (pTarget->statnum == kMaxStatus)
        {
            GibSprite(pSprite, GIBTYPE_17, NULL, NULL);
            actPostSprite(pSprite->index, 1024);
        }
        if (pTarget->extra > 0 && xsprite[pTarget->extra].health > 0)
        {
            int x = pTarget->x+mulscale30r(Cos(pXSprite->goalAng+pTarget->ang), pTarget->clipdist*2);
            int y = pTarget->y+mulscale30r(Sin(pXSprite->goalAng+pTarget->ang), pTarget->clipdist*2);
            int z = pTarget->z+pXSprite->targetZ;
            vec3_t pos = { x, y, z };
            setsprite(nSprite,&pos);
            xvel[nSprite] = xvel[nTarget];
            yvel[nSprite] = yvel[nTarget];
            zvel[nSprite] = zvel[nTarget];
        }
        else
        {
            GibSprite(pSprite, GIBTYPE_17, NULL, NULL);
            actPostSprite(pSprite->index, 1024);
        }
    }
    aiProcessDudes();
    gFX.fxProcess();
}

spritetype * actSpawnSprite(int nSector, int x, int y, int z, int nStat, char a6)
{
    int nSprite = InsertSprite(nSector, nStat);
    if (nSprite >= 0)
        sprite[nSprite].extra = -1;
    else
    {
        nSprite = headspritestat[9];
        dassert(nSprite >= 0);
        dassert(nSector >= 0 && nSector < kMaxSectors);
        ChangeSpriteSect(nSprite, nSector);
        actPostSprite(nSprite, nStat);
    }
    vec3_t pos = { x, y, z };
    setsprite(nSprite, &pos);
    spritetype *pSprite = &sprite[nSprite];
    pSprite->type = 0;
    if (a6 && pSprite->extra == -1)
    {
        int nXSprite = dbInsertXSprite(nSprite);
        gSpriteHit[nXSprite].florhit = 0;
        gSpriteHit[nXSprite].ceilhit = 0;
    }
    return pSprite;
}

spritetype * actSpawnSprite(spritetype *pSource, int nStat);

spritetype *actSpawnDude(spritetype *pSource, short nType, int a3, int a4)
{
    XSPRITE* pXSource = &xsprite[pSource->extra];
    spritetype *pSprite2 = actSpawnSprite(pSource, 6);
    if (!pSprite2) return NULL;
    XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
    int angle = pSource->ang;
    int nDude = nType-kDudeBase;
    int x, y, z;
    z = a4 + pSource->z;
    if (a3 < 0)
    {
        x = pSource->x;
        y = pSource->y;
    }
    else
    {
        x = pSource->x+mulscale30r(Cos(angle), a3);
        y = pSource->y+mulscale30r(Sin(angle), a3);
    }
    pSprite2->type = nType;
    pSprite2->ang = angle;
    vec3_t pos = { x, y, z };
    setsprite(pSprite2->index, &pos);
    pSprite2->cstat |= 0x1101;
    pSprite2->clipdist = dudeInfo[nDude].clipdist;
    pXSprite2->health = dudeInfo[nDude].startHealth<<4;
    if (gSysRes.Lookup(dudeInfo[nDude].seqStartID, "SEQ"))
        seqSpawn(dudeInfo[nDude].seqStartID, 3, pSprite2->extra, -1);
    
    // By NoOne: 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 (pSource->flags & kModernTypeFlag1) {
        switch (pSource->type) { // allow inheriting only for selected source types
            case 18:
                //inherit pal?
                if (pSprite2->pal <= 0) pSprite2->pal = pSource->pal;

                // inherit spawn sprite trigger settings, so designer can count monsters.
                pXSprite2->txID = pXSource->txID;
                pXSprite2->command = pXSource->command;
                pXSprite2->triggerOn = pXSource->triggerOn;
                pXSprite2->triggerOff = pXSource->triggerOff;

                // inherit drop items
                pXSprite2->dropMsg = pXSource->dropMsg;

                // inherit dude flags
                pXSprite2->dudeDeaf = pXSource->dudeDeaf;
                pXSprite2->dudeGuard = pXSource->dudeGuard;
                pXSprite2->dudeAmbush = pXSource->dudeAmbush;
                pXSprite2->dudeFlag4 = pXSource->dudeFlag4;
                break;
        }
    }

    aiInitSprite(pSprite2);
    return pSprite2;
}

spritetype * actSpawnSprite(spritetype *pSource, int nStat)
{
    int nSprite = InsertSprite(pSource->sectnum, nStat);
    if (nSprite < 0)
    {
        nSprite = headspritestat[9];
        dassert(nSprite >= 0);
        dassert(pSource->sectnum >= 0 && pSource->sectnum < kMaxSectors);
        ChangeSpriteSect(nSprite, pSource->sectnum);
        actPostSprite(nSprite, nStat);
    }
    spritetype *pSprite = &sprite[nSprite];
    pSprite->x = pSource->x;
    pSprite->y = pSource->y;
    pSprite->z = pSource->z;
    xvel[nSprite] = xvel[pSource->index];
    yvel[nSprite] = yvel[pSource->index];
    zvel[nSprite] = zvel[pSource->index];
    pSprite->flags = 0;
    int nXSprite = dbInsertXSprite(nSprite);
    gSpriteHit[nXSprite].florhit = 0;
    gSpriteHit[nXSprite].ceilhit = 0;
    return pSprite;
}

spritetype * actSpawnThing(int nSector, int x, int y, int z, int nThingType)
{
    dassert(nThingType >= kThingBase && nThingType < kThingMax);
    spritetype *pSprite = actSpawnSprite(nSector, x, y, z, 4, 1);
    int nType = nThingType-kThingBase;
    int nThing = pSprite->index;
    int nXThing = pSprite->extra;
    pSprite->type = nThingType;
    dassert(nXThing > 0 && nXThing < kMaxXSprites);
    XSPRITE *pXThing = &xsprite[nXThing];
    THINGINFO *pThingInfo = &thingInfo[nType];
    pXThing->health = pThingInfo->at0<<4;
    pSprite->clipdist = pThingInfo->at4;
    pSprite->flags = pThingInfo->at5;
    if (pSprite->flags & 2)
        pSprite->flags |= 4;
    pSprite->cstat |= pThingInfo->atf;
    pSprite->picnum = pThingInfo->at11;
    pSprite->shade = pThingInfo->at13;
    pSprite->pal = pThingInfo->at14;
    if (pThingInfo->at15)
        pSprite->xrepeat = pThingInfo->at15;
    if (pThingInfo->at16)
        pSprite->yrepeat = pThingInfo->at16;
    SetBitString(show2dsprite, pSprite->index);
    switch (nThingType)
    {
    case 432:
        pXThing->data1 = 0;
        pXThing->data2 = 0;
        pXThing->data3 = 0;
        pXThing->data4 = 0;
        pXThing->state = 1;
        pXThing->triggerOnce = 1;
        pXThing->isTriggered = 0;
        break;
    case 431:
    case kGDXThingCustomDudeLifeLeech:
        pXThing->data1 = 0;
        pXThing->data2 = 0;
        pXThing->data3 = 0;
        pXThing->data4 = 0;
        pXThing->state = 1;
        pXThing->triggerOnce = 0;
        pXThing->isTriggered = 0;
        break;
    case 427:
        pXThing->data1 = 8;
        pXThing->data2 = 0;
        pXThing->data3 = 0;
        pXThing->data4 = 318;
        pXThing->targetX = (int)gFrameClock+180.0;
        pXThing->locked = 1;
        pXThing->state = 1;
        pXThing->triggerOnce = 0;
        pXThing->isTriggered = 0;
        break;
    case 425:
    case 426:
        if (nThingType == 425)
            pXThing->data1 = 19;
        else if (nThingType == 426)
            pXThing->data1 = 8;
        pXThing->data2 = 0;
        pXThing->data3 = 0;
        pXThing->data4 = 318;
        pXThing->targetX = (int)gFrameClock+180.0;
        pXThing->locked = 1;
        pXThing->state = 1;
        pXThing->triggerOnce = 0;
        pXThing->isTriggered = 0;
        break;
    case 418:
        evPost(nThing, 3, 0, CALLBACK_ID_8);
        sfxPlay3DSound(pSprite, 450, 0, 0);
        break;
    case 419:
        sfxPlay3DSound(pSprite, 450, 0, 0);
        evPost(nThing, 3, 0, CALLBACK_ID_8);
        break;
    case 420:
        evPost(nThing, 3, 0, CALLBACK_ID_8);
        break;
    }
    return pSprite;
}

spritetype * actFireThing(spritetype *pSprite, int a2, int a3, int a4, int thingType, int a6)
{
    dassert(thingType >= kThingBase && thingType < kThingMax);
    int x = pSprite->x+mulscale30(a2, Cos(pSprite->ang+512));
    int y = pSprite->y+mulscale30(a2, Sin(pSprite->ang+512));
    int z = pSprite->z+a3;
    x += mulscale28(pSprite->clipdist, Cos(pSprite->ang));
    y += mulscale28(pSprite->clipdist, Sin(pSprite->ang));
    if (HitScan(pSprite, z, x-pSprite->x, y-pSprite->y, 0, CLIPMASK0, pSprite->clipdist) != -1)
    {
        x = gHitInfo.hitx-mulscale28(pSprite->clipdist<<1, Cos(pSprite->ang));
        y = gHitInfo.hity-mulscale28(pSprite->clipdist<<1, Sin(pSprite->ang));
    }
    spritetype *pThing = actSpawnThing(pSprite->sectnum, x, y, z, thingType);
    actPropagateSpriteOwner(pThing, pSprite);
    pThing->ang = pSprite->ang;
    xvel[pThing->index] = mulscale30(a6, Cos(pThing->ang));
    yvel[pThing->index] = mulscale30(a6, Sin(pThing->ang));
    zvel[pThing->index] = mulscale(a6, a4, 14);
    xvel[pThing->index] += xvel[pSprite->index]/2;
    yvel[pThing->index] += yvel[pSprite->index]/2;
    zvel[pThing->index] += zvel[pSprite->index]/2;
    return pThing;
}

spritetype* actFireMissile(spritetype *pSprite, int a2, int a3, int a4, int a5, int a6, int nType)
{
    
    dassert(nType >= kMissileBase && nType < kMissileMax);
    char v4 = 0;
    int nSprite = pSprite->index;
    MissileType *pMissileInfo = &missileInfo[nType-kMissileBase];
    int x = pSprite->x+mulscale30(a2, Cos(pSprite->ang+512));
    int y = pSprite->y+mulscale30(a2, Sin(pSprite->ang+512));
    int z = pSprite->z+a3;
    int clipdist = pMissileInfo->atd+pSprite->clipdist;
    x += mulscale28(clipdist, Cos(pSprite->ang));
    y += mulscale28(clipdist, Sin(pSprite->ang));
    int hit = HitScan(pSprite, z, x-pSprite->x, y-pSprite->y, 0, CLIPMASK0, clipdist);
    if (hit != -1)
    {
        if (hit == 3 || hit == 0)
        {
            v4 = 1;
            x = gHitInfo.hitx-mulscale30(Cos(pSprite->ang), 16);
            y = gHitInfo.hity-mulscale30(Sin(pSprite->ang), 16);
        }
        else
        {
            x = gHitInfo.hitx-mulscale28(pMissileInfo->atd<<1, Cos(pSprite->ang));
            y = gHitInfo.hity-mulscale28(pMissileInfo->atd<<1, Sin(pSprite->ang));
        }
    }
    spritetype *pMissile = actSpawnSprite(pSprite->sectnum, x, y, z, 5, 1);
    int nMissile = pMissile->index;
    SetBitString(show2dsprite, nMissile);
    pMissile->type = nType;
    pMissile->shade = pMissileInfo->atc;
    pMissile->pal = 0;
    pMissile->clipdist = pMissileInfo->atd;
    pMissile->flags = 1;
    pMissile->xrepeat = pMissileInfo->ata;
    pMissile->yrepeat = pMissileInfo->atb;
    pMissile->picnum = pMissileInfo->picnum;
    pMissile->ang = (pSprite->ang+pMissileInfo->at6)&2047;
    xvel[nMissile] = mulscale(pMissileInfo->at2, a4, 14);
    yvel[nMissile] = mulscale(pMissileInfo->at2, a5, 14);
    zvel[nMissile] = mulscale(pMissileInfo->at2, a6, 14);
    actPropagateSpriteOwner(pMissile, pSprite);
    pMissile->cstat |= 1;
    int nXSprite = pMissile->extra;
    dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
    xsprite[nXSprite].target = -1;
    evPost(nMissile, 3, 600, CALLBACK_ID_1);
   
    actBuildMissile(pMissile, nXSprite, nSprite);
    
    if (v4)
    {
        actImpactMissile(pMissile, hit);
        pMissile = NULL;
    }
    return pMissile;
}

void actBuildMissile(spritetype* pMissile, int nXSprite, int nSprite) {
    int nMissile = pMissile->index;
    switch (pMissile->type)
    {
    case 315:
        evPost(nMissile, 3, 0, CALLBACK_ID_0);
        break;
    case 302:
        evPost(nMissile, 3, 0, CALLBACK_ID_15);
        break;
    case 309:
        seqSpawn(29, 3, nXSprite, -1);
        break;
    case 300:
        pMissile->cstat |= 16;
        break;
    case 306:
        sfxPlay3DSound(pMissile, 251, 0, 0);
        break;
    case 307:
        seqSpawn(2, 3, nXSprite, -1);
        sfxPlay3DSound(pMissile, 493, 0, 0);
        break;
    case 312:
        seqSpawn(61, 3, nXSprite, nNapalmClient);
        sfxPlay3DSound(pMissile, 441, 0, 0);
        break;
    case 305:
        seqSpawn(22, 3, nXSprite, nFireballClient);
        sfxPlay3DSound(pMissile, 441, 0, 0);
        break;
    case 308:
        seqSpawn(27, 3, nXSprite, -1);
        xvel[nMissile] += xvel[nSprite] / 2 + Random2(0x11111);
        yvel[nMissile] += yvel[nSprite] / 2 + Random2(0x11111);
        zvel[nMissile] += zvel[nSprite] / 2 + Random2(0x11111);
        break;
    case 313:
        seqSpawn(61, 3, nXSprite, dword_2192E0);
        sfxPlay3DSound(pMissile, 441, 0, 0);
        break;
    case 314:
        seqSpawn(23, 3, nXSprite, dword_2192D8);
        xvel[nMissile] += xvel[nSprite] / 2 + Random2(0x11111);
        yvel[nMissile] += yvel[nSprite] / 2 + Random2(0x11111);
        zvel[nMissile] += zvel[nSprite] / 2 + Random2(0x11111);
        break;
    case 304:
        if (Chance(0x8000))
            seqSpawn(0, 3, nXSprite, -1);
        else
            seqSpawn(1, 3, nXSprite, -1);
        xvel[nMissile] += xvel[nSprite] + Random2(0x11111);
        yvel[nMissile] += yvel[nSprite] + Random2(0x11111);
        zvel[nMissile] += zvel[nSprite] + Random2(0x11111);
        break;
    case 303:
        evPost(nMissile, 3, 30, CALLBACK_ID_2);
        evPost(nMissile, 3, 0, CALLBACK_ID_3);
        sfxPlay3DSound(pMissile, 422, 0, 0);
        break;
    case 301:
        evPost(nMissile, 3, 0, CALLBACK_ID_3);
        sfxPlay3DSound(pMissile, 422, 0, 0);
        break;
    case 317:
        evPost(nMissile, 3, 0, CALLBACK_ID_7);
        break;
    case 311:
        sfxPlay3DSound(pMissile, 252, 0, 0);
        break;
    }
}

int actGetRespawnTime(spritetype *pSprite)
{
    int nXSprite = pSprite->extra;
    if (nXSprite <= 0)
        return -1;
    XSPRITE *pXSprite = &xsprite[nXSprite];
    if (IsDudeSprite(pSprite) && !IsPlayerSprite(pSprite))
    {
        if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nMonsterSettings == 2))
            return gGameOptions.nMonsterRespawnTime;
        return -1;
    }
    if (IsWeaponSprite(pSprite))
    {
        if (pXSprite->respawn == 3 || gGameOptions.nWeaponSettings == 1)
            return 0;
        if (pXSprite->respawn != 1 && gGameOptions.nWeaponSettings != 0)
            return gGameOptions.nWeaponRespawnTime;
        return -1;
    }
    if (IsAmmoSprite(pSprite))
    {
        if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nWeaponSettings != 0))
            return gGameOptions.nWeaponRespawnTime;
        return -1;
    }
    if (IsItemSprite(pSprite))
    {
        if (pXSprite->respawn == 3 && gGameOptions.nGameType == 1)
            return 0;
        if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nItemSettings != 0))
        {
            switch (pSprite->type)
            {
            case 113:
            case 117:
            case 124:
                return gGameOptions.nSpecialRespawnTime;
            case 114:
                return gGameOptions.nSpecialRespawnTime<<1;
            default:
                return gGameOptions.nItemRespawnTime;
            }
        }
        return -1;
    }
    return -1;
}

bool actCheckRespawn(spritetype *pSprite)
{
    int nSprite = pSprite->index;
    int nXSprite = pSprite->extra;
    if (nXSprite > 0)
    {
        XSPRITE *pXSprite = &xsprite[nXSprite];
        int nRespawnTime = actGetRespawnTime(pSprite);
        if (nRespawnTime < 0)
            return 0;
        pXSprite->respawnPending = 1;
        if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
        {
            pXSprite->respawnPending = 3;
            if (pSprite->type == 400)
                pSprite->cstat |= 32768;
        }
        if (nRespawnTime > 0)
        {
            if (pXSprite->respawnPending == 1)
                nRespawnTime = mulscale16(nRespawnTime, 0xa000);
            pSprite->owner = pSprite->statnum;
            actPostSprite(pSprite->index, 8);
            pSprite->flags |= 16;
            if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
            {
                pSprite->cstat &= ~257;
                pSprite->x = baseSprite[nSprite].x;
                pSprite->y = baseSprite[nSprite].y;
                pSprite->z = baseSprite[nSprite].z;
            }
            evPost(nSprite, 3, nRespawnTime, CALLBACK_ID_9);
        }
        return 1;
    }
    return  0;
}

bool actCanSplatWall(int nWall)
{
    dassert(nWall >= 0 && nWall < kMaxWalls);
    walltype *pWall = &wall[nWall];
    if (pWall->cstat & 16384)
        return 0;
    if (pWall->cstat & 32768)
        return 0;
    int nType = GetWallType(nWall);
    if (nType >= 500 && nType < 512)
        return 0;
    if (pWall->nextsector != -1)
    {
        sectortype *pSector = &sector[pWall->nextsector];
        if (pSector->type >= 600 && pSector->type < 620)
            return 0;
    }
    return 1;
}

void actFireVector(spritetype *pShooter, int a2, int a3, int a4, int a5, int a6, VECTOR_TYPE vectorType)
{
    int nShooter = pShooter->index;
    dassert(vectorType >= 0 && vectorType < kVectorMax);
    VECTORDATA *pVectorData = &gVectorData[vectorType];
    int nRange = pVectorData->maxDist;
    int hit = VectorScan(pShooter, a2, a3, a4, a5, a6, nRange, 1);
    if (hit == 3)
    {
        int nSprite = gHitInfo.hitsprite;
        dassert(nSprite >= 0 && nSprite < kMaxSprites);
        spritetype *pSprite = &sprite[nSprite];
        if (!gGameOptions.bFriendlyFire && IsTargetTeammate(pShooter, pSprite))
            return;
        if (IsPlayerSprite(pSprite))
        {
            PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
            if (powerupCheck(pPlayer, 24))
            {
                gHitInfo.hitsprite = nShooter;
                gHitInfo.hitx = pShooter->x;
                gHitInfo.hity = pShooter->y;
                gHitInfo.hitz = pShooter->z;
            }
        }
    }
    int x = gHitInfo.hitx-mulscale(a4, 16, 14);
    int y = gHitInfo.hity-mulscale(a5, 16, 14);
    int z = gHitInfo.hitz-mulscale(a6, 256, 14);
    short nSector = gHitInfo.hitsect;
    char nSurf = kSurfNone;
    if (nRange == 0 || approxDist(gHitInfo.hitx-pShooter->x, gHitInfo.hity-pShooter->y) < nRange)
    {
        switch (hit)
        {
        case 1:
        {
            int nSector = gHitInfo.hitsect;
            if (sector[nSector].ceilingstat&1)
                nSurf = kSurfNone;
            else
                nSurf = surfType[sector[nSector].ceilingpicnum];
            break;
        }
        case 2:
        {
            int nSector = gHitInfo.hitsect;
            if (sector[nSector].floorstat&1)
                nSurf = kSurfNone;
            else
                nSurf = surfType[sector[nSector].floorpicnum];
            break;
        }
        case 0:
        {
            int nWall = gHitInfo.hitwall;
            dassert(nWall >= 0 && nWall < kMaxWalls);
            nSurf = surfType[wall[nWall].picnum];
            if (actCanSplatWall(nWall))
            {
                int x = gHitInfo.hitx-mulscale(a4, 16, 14);
                int y = gHitInfo.hity-mulscale(a5, 16, 14);
                int z = gHitInfo.hitz-mulscale(a6, 256, 14);
                int nSurf = surfType[wall[nWall].picnum];
                dassert(nSurf < kSurfMax);
                if (pVectorData->at1d[nSurf].at0 >= 0)
                {
                    spritetype *pFX = gFX.fxSpawn(pVectorData->at1d[nSurf].at0, nSector, x, y, z, 0);
                    if (pFX)
                    {
                        pFX->ang = (GetWallAngle(nWall)+512)&2047;
                        pFX->cstat |= 16;
                    }
                }
            }
            break;
        }
        case 4:
        {
            int nWall = gHitInfo.hitwall;
            dassert(nWall >= 0 && nWall < kMaxWalls);
            nSurf = surfType[wall[nWall].overpicnum];
            int nXWall = wall[nWall].extra;
            if (nXWall > 0)
            {
                XWALL *pXWall = &xwall[nXWall];
                if (pXWall->triggerVector)
                    trTriggerWall(nWall, pXWall, 51);
            }
            break;
        }
        case 3:
        {
            int nSprite = gHitInfo.hitsprite;
            nSurf = surfType[sprite[nSprite].picnum];
            dassert(nSprite >= 0 && nSprite < kMaxSprites);
            spritetype *pSprite = &sprite[nSprite];
            x -= mulscale(a4, 112, 14);
            y -= mulscale(a5, 112, 14);
            z -= mulscale(a6, 112<<4, 14);
            int shift = 4;
            if (vectorType == VECTOR_TYPE_0 && !IsPlayerSprite(pSprite))
                shift = 3;
            actDamageSprite(nShooter, pSprite, pVectorData->at0, pVectorData->at1<<shift);
            int nXSprite = pSprite->extra;
            if (nXSprite > 0)
            {
                XSPRITE *pXSprite = &xsprite[nXSprite];
                if (pXSprite->Vector)
                    trTriggerSprite(nSprite, pXSprite, 31);
            }
            if (pSprite->statnum == 4)
            {
                int t = thingInfo[pSprite->type-kThingBase].at2;
                if (t > 0 && pVectorData->at5)
                {
                    int t2 = divscale(pVectorData->at5, t, 8);
                    xvel[nSprite] += mulscale16(a4, t2);
                    yvel[nSprite] += mulscale16(a5, t2);
                    zvel[nSprite] += mulscale16(a6, t2);
                }
                if (pVectorData->at11)
                {
                    XSPRITE *pXSprite = &xsprite[nXSprite];
                    if (!pXSprite->burnTime)
                        evPost(nSprite, 3, 0, CALLBACK_ID_0);
                    actBurnSprite(actSpriteIdToOwnerId(nShooter), pXSprite, pVectorData->at11);
                }
            }
            if (pSprite->statnum == 6)
            {
                int t = pSprite->type == 426 ? 0 : dudeInfo[pSprite->type-kDudeBase].mass;
                
                if (IsDudeSprite(pSprite)) {
                    switch (pSprite->type) {
                    case kCustomDude:
                    case kCustomDudeBurning:
                        t = getSpriteMassBySize(pSprite);
                        break;
                    }
                }

                if (t > 0 && pVectorData->at5)
                {
                    int t2 = divscale(pVectorData->at5, t, 8);
                    xvel[nSprite] += mulscale16(a4, t2);
                    yvel[nSprite] += mulscale16(a5, t2);
                    zvel[nSprite] += mulscale16(a6, t2);
                }
                if (pVectorData->at11)
                {
                    XSPRITE *pXSprite = &xsprite[nXSprite];
                    if (!pXSprite->burnTime)
                        evPost(nSprite, 3, 0, CALLBACK_ID_0);
                    actBurnSprite(actSpriteIdToOwnerId(nShooter), pXSprite, pVectorData->at11);
                }
                if (Chance(pVectorData->atd))
                {
                    int t = gVectorData[19].maxDist;
                    a4 += Random3(4000);
                    a5 += Random3(4000);
                    a6 += Random3(4000);
                    if (HitScan(pSprite, gHitInfo.hitz, a4, a5, a6, CLIPMASK1, t) == 0)
                    {
                        if (approxDist(gHitInfo.hitx-pSprite->x, gHitInfo.hity-pSprite->y) <= t)
                        {
                            int nWall = gHitInfo.hitwall;
                            int nSector = gHitInfo.hitsect;
                            if (actCanSplatWall(nWall))
                            {
                                int x = gHitInfo.hitx - mulscale(a4, 16, 14);
                                int y = gHitInfo.hity - mulscale(a5, 16, 14);
                                int z = gHitInfo.hitz - mulscale(a6, 16<<4, 14);
                                int nSurf = surfType[wall[nWall].picnum];
                                VECTORDATA *pVectorData = &gVectorData[19];
                                FX_ID t2 = pVectorData->at1d[nSurf].at1;
                                FX_ID t3 = pVectorData->at1d[nSurf].at2;
                                spritetype *pFX = NULL;
                                if (t2 > FX_NONE && (t3 == FX_NONE || Chance(0x4000)))
                                    pFX = gFX.fxSpawn(t2, nSector, x, y, z, 0);
                                else if(t3 > FX_NONE)
                                    pFX = gFX.fxSpawn(t3, nSector, x, y, z, 0);
                                if (pFX)
                                {
                                    zvel[pFX->index] = 0x2222;
                                    pFX->ang = (GetWallAngle(nWall)+512)&2047;
                                    pFX->cstat |= 16;
                                }
                            }
                        }
                    }
                }
                for (int i = 0; i < pVectorData->at15; i++)
                    if (Chance(pVectorData->at19))
                        fxSpawnBlood(pSprite, pVectorData->at1<<4);
            }

            // by NoOne: add impulse for sprites from physics list
            if (gPhysSpritesCount > 0 && pVectorData->at5) {
                int nIndex = isDebris(pSprite->index);
                if (nIndex != -1 && (xsprite[pSprite->extra].physAttr & kPhysDebrisVector)) {
                    int impulse = divscale(pVectorData->at5, ClipLow(gSpriteMass[pSprite->extra].mass, 10), 6);
                    xvel[nSprite] += mulscale16(a4, impulse);
                    yvel[nSprite] += mulscale16(a5, impulse);
                    zvel[nSprite] += mulscale16(a6, impulse);

                    if (pVectorData->at11 != 0) {
                        if (!xsprite[nXSprite].burnTime) evPost(nSprite, 3, 0, CALLBACK_ID_0);
                        actBurnSprite(actSpriteIdToOwnerId(nShooter), &xsprite[nXSprite], pVectorData->at11);
                    }

                    if (pSprite->index >= kThingBase && pSprite->index < kThingMax)
                        actPostSprite(pSprite->index, 4); // if it was a thing, return it's statnum back
                }
            }

            break;
        }
        }
    }
    dassert(nSurf < kSurfMax);
    if (pVectorData->at1d[nSurf].at1 >= 0)
        gFX.fxSpawn(pVectorData->at1d[nSurf].at1, nSector, x, y, z, 0);
    if (pVectorData->at1d[nSurf].at2 >= 0)
        gFX.fxSpawn(pVectorData->at1d[nSurf].at2, nSector, x, y, z, 0);
    if (pVectorData->at1d[nSurf].at3 >= 0)
        sfxPlay3DSound(x, y, z, pVectorData->at1d[nSurf].at3, nSector);
}

void FireballSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_11, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
    if (pFX)
    {
        int nFX = pFX->index;
        xvel[nFX] = xvel[nSprite];
        yvel[nFX] = yvel[nSprite];
        zvel[nFX] = zvel[nSprite];
    }
}

void NapalmSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_12, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
    if (pFX)
    {
        int nFX = pFX->index;
        xvel[nFX] = xvel[nSprite];
        yvel[nFX] = yvel[nSprite];
        zvel[nFX] = zvel[nSprite];
    }
}

void sub_3888C(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
    if (pFX)
    {
        int nFX = pFX->index;
        xvel[nFX] = xvel[nSprite];
        yvel[nFX] = yvel[nSprite];
        zvel[nFX] = zvel[nSprite];
    }
}

void sub_38938(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_33, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
    if (pFX)
    {
        int nFX = pFX->index;
        xvel[nFX] = xvel[nSprite];
        yvel[nFX] = yvel[nSprite];
        zvel[nFX] = zvel[nSprite];
    }
}

void TreeToGibCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    pSprite->type = 417;
    pXSprite->state = 1;
    pXSprite->data1 = 15;
    pXSprite->data2 = 0;
    pXSprite->data3 = 0;
    pXSprite->health = thingInfo[17].at0;
    pXSprite->data4 = 312;
    pSprite->cstat |= 257;
}

void DudeToGibCallback1(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    pSprite->type = 426;
    pXSprite->data1 = 8;
    pXSprite->data2 = 0;
    pXSprite->data3 = 0;
    pXSprite->health = thingInfo[26].at0;
    pXSprite->data4 = 319;
    pXSprite->triggerOnce = 0;
    pXSprite->isTriggered = 0;
    pXSprite->locked = 0;
    pXSprite->targetX = (int)gFrameClock;
    pXSprite->state = 1;
}

void DudeToGibCallback2(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    pSprite->type = 426;
    pXSprite->data1 = 3;
    pXSprite->data2 = 0;
    pXSprite->data3 = 0;
    pXSprite->health = thingInfo[26].at0;
    pXSprite->data4 = 319;
    pXSprite->triggerOnce = 0;
    pXSprite->isTriggered = 0;
    pXSprite->locked = 0;
    pXSprite->targetX = (int)gFrameClock;
    pXSprite->state = 1;
}

void actPostSprite(int nSprite, int nStatus)
{
    int n;
    dassert(gPostCount < kMaxSprites);
    dassert(nSprite < kMaxSprites && sprite[nSprite].statnum < kMaxStatus);
    dassert(nStatus >= 0 && nStatus <= kStatFree);
    if (sprite[nSprite].flags&32)
    {
        for (n = 0; n < gPostCount; n++)
            if (gPost[n].at0 == nSprite)
                break;
        dassert(n < gPostCount);
    }
    else
    {
        n = gPostCount;
        sprite[nSprite].flags |= 32;
        gPostCount++;
    }
    gPost[n].at0 = nSprite;
    gPost[n].at2 = nStatus;
}

void actPostProcess(void)
{
    for (int i = 0; i < gPostCount; i++)
    {
        POSTPONE *pPost = &gPost[i];
        int nSprite = pPost->at0;
        spritetype *pSprite = &sprite[nSprite];
        pSprite->flags &= ~32;
        int nStatus = pPost->at2;
        if (nStatus == kStatFree)
        {
            evKill(nSprite, 3);
            if (sprite[nSprite].extra > 0)
                seqKill(3, sprite[nSprite].extra);
            DeleteSprite(nSprite);
        }
        else
            ChangeSpriteStat(nSprite, nStatus);
    }
    gPostCount = 0;
}

void MakeSplash(spritetype *pSprite, XSPRITE *pXSprite)
{
    UNREFERENCED_PARAMETER(pXSprite);
    pSprite->flags &= ~2;
    int nXSprite = pSprite->extra;
    pSprite->z -= 4<<8;
    int nSurface = tileGetSurfType(gSpriteHit[nXSprite].florhit);
    switch (pSprite->type)
    {
    case 423:
        if (nSurface == kSurfWater)
        {
            seqSpawn(6, 3, nXSprite, -1);
            sfxPlay3DSound(pSprite, 356, -1, 0);
        }
        else
        {
            seqSpawn(7, 3, nXSprite, -1);
            sfxPlay3DSound(pSprite, 354, -1, 0);
        }
        break;
    case 424:
        seqSpawn(8, 3, nXSprite, -1);
        sfxPlay3DSound(pSprite, 354, -1, 0);
        break;
    }
}

class ActorLoadSave : public LoadSave
{
    virtual void Load(void);
    virtual void Save(void);
};

void ActorLoadSave::Load(void)
{
    Read(gSpriteHit, sizeof(gSpriteHit));
    Read(gAffectedSectors, sizeof(gAffectedSectors));
    Read(gAffectedXWalls, sizeof(gAffectedXWalls));
    Read(&gPostCount, sizeof(gPostCount));
    Read(gPost, sizeof(gPost));
    actInit(true);
}

void ActorLoadSave::Save(void)
{
    Write(gSpriteHit, sizeof(gSpriteHit));
    Write(gAffectedSectors, sizeof(gAffectedSectors));
    Write(gAffectedXWalls, sizeof(gAffectedXWalls));
    Write(&gPostCount, sizeof(gPostCount));
    Write(gPost, sizeof(gPost));
}

static ActorLoadSave *myLoadSave;

void ActorLoadSaveConstruct(void)
{
    myLoadSave = new ActorLoadSave();
}


// By NoOne: The following functions required for random event features
//-------------------------
int GetDataVal(spritetype* pSprite, int data) {
    if (pSprite->extra >= 0) {
        switch (data) {
        case 0:
            return xsprite[pSprite->extra].data1;
        case 1:
            return xsprite[pSprite->extra].data2;
        case 2:
            return xsprite[pSprite->extra].data3;
        case 3:
            return xsprite[pSprite->extra].data4;
        }
    }
    return -1;
}


std::default_random_engine rng;
int my_random(int a, int b) {

    std::uniform_int_distribution<int> dist_a_b(a, b);
    return dist_a_b(rng);
}

// tries to get random data field of sprite
int GetRandDataVal(int *rData, spritetype* pSprite) {
    int temp[4];
    if (rData != NULL && pSprite != NULL) return -1;
    else if (pSprite != NULL) {

        if (pSprite->extra < 0)
            return -1;

        if (rData == NULL)
            rData = temp;

        XSPRITE* pXSprite = &xsprite[pSprite->extra];
        rData[0] = pXSprite->data1; rData[2] = pXSprite->data3;
        rData[1] = pXSprite->data2; rData[3] = pXSprite->data4;

    }
    else if (rData == NULL) {
        return -1;
    }

    int random = 0;
    // randomize only in case if at least 2 data fields are not empty
    int a = 1; int b = -1;
    for (int i = 0; i <= 3; i++) {
        if (rData[i] == 0) {
            if (a++ > 2)
                return -1;
        }
        else if (b == -1) {
            b++;
        }
    }

    // try randomize few times
    int maxRetries = 10;
    while (maxRetries > 0) {
             
        // use true random only for single player mode
        // otherwise use Blood's default one. In the future it maybe possible to make
        // host send info to clients about what was generated.

        if (gGameOptions.nGameType != 0 || VanillaMode() || DemoRecordStatus()) random = Random(3);
        else {
            rng.seed(std::random_device()());
            random = my_random(0, 4);
        }

        if (rData[random] > 0)   return rData[random];
        maxRetries--;
    }

     // if nothing, get first found data value from top
     return rData[b];
}

// this function drops random item using random pickup generator(s)
spritetype* DropRandomPickupObject(spritetype* pSprite, short prevItem) {
    spritetype* pSprite2 = NULL;

    int rData[4]; int selected = -1;
    rData[0] = xsprite[pSprite->extra].data1; rData[2] = xsprite[pSprite->extra].data3;
    rData[1] = xsprite[pSprite->extra].data2; rData[3] = xsprite[pSprite->extra].data4;

    // randomize only in case if at least 2 data fields fits.
    for (int i = 0; i <= 3; i++)
        if (rData[i] < kWeaponItemBase || rData[i] >= kItemMax)
            rData[i] = 0;

    int maxRetries = 9;
    while ((selected = GetRandDataVal(rData, NULL)) == prevItem) if (maxRetries-- <= 0) break;
    if (selected > 0) {
        spritetype* pSource = pSprite; XSPRITE* pXSource = &xsprite[pSource->extra];
        pSprite2 = actDropObject(pSprite, selected);
        if (pSprite2 != NULL) {
            
            pXSource->dropMsg = pSprite2->type; // store dropped item type in dropMsg
            pSprite2->x = pSource->x;
            pSprite2->y = pSource->y;
            pSprite2->z = pSource->z;

            if ((pSource->flags & kModernTypeFlag1) && (pXSource->txID > 0 || (pXSource->txID != 3 && pXSource->lockMsg > 0)) &&
                dbInsertXSprite(pSprite2->xvel) > 0) {
                
                XSPRITE * pXSprite2 = &xsprite[pSprite2->extra];

                // inherit spawn sprite trigger settings, so designer can send command when item picked up.
                pXSprite2->txID = pXSource->txID;
                pXSprite2->command = pXSource->command;
                pXSprite2->triggerOn = pXSource->triggerOn;
                pXSprite2->triggerOff = pXSource->triggerOff;

                pXSprite2->Pickup = true;

            }
        }
    }

    return pSprite2;
}

// this function spawns random dude using dudeSpawn
spritetype* spawnRandomDude(spritetype* pSprite) {
    spritetype* pSprite2 = NULL;
    
    if (pSprite->extra >= 0) {
        int rData[4]; int selected = -1;
        rData[0] = xsprite[pSprite->extra].data1; rData[2] = xsprite[pSprite->extra].data3;
        rData[1] = xsprite[pSprite->extra].data2; rData[3] = xsprite[pSprite->extra].data4;

        // randomize only in case if at least 2 data fields fits.
        for (int i = 0; i <= 3; i++)
            if (rData[i] < kDudeBase || rData[i] >= kDudeMax)
                rData[i] = 0;
       
        if ((selected = GetRandDataVal(rData,NULL)) > 0)
           pSprite2 = actSpawnDude(pSprite, selected, -1, 0);
    }

    return pSprite2;
}
//-------------------------

// By NoOne: this function plays sound predefined in missile info
bool sfxPlayMissileSound(spritetype* pSprite, int missileId) {
    MissileType* pMissType = &missileInfo[missileId - kMissileBase];
    if (Chance(0x4000))
        sfxPlay3DSound(pSprite, pMissType->fireSound[0], -1, 0);
    else
        sfxPlay3DSound(pSprite, pMissType->fireSound[1], -1, 0);

    return true;
}

// By NoOne: this function plays sound predefined in vector info
bool sfxPlayVectorSound(spritetype* pSprite, int vectorId) {
    VECTORDATA* pVectorData = &gVectorData[vectorId];
    if (Chance(0x4000))
        sfxPlay3DSound(pSprite, pVectorData->fireSound[0], -1, 0);
    else
        sfxPlay3DSound(pSprite, pVectorData->fireSound[1], -1, 0);

    return true;
}

// By NoOne: this function allows to spawn new custom dude and inherit spawner settings,
// so custom dude can have different weapons, hp and so on...
spritetype* actSpawnCustomDude(spritetype* pSprite, int nDist) {

    spritetype* pSource = pSprite; XSPRITE* pXSource = &xsprite[pSource->extra];
    spritetype* pDude = actSpawnSprite(pSprite,6); XSPRITE* pXDude = &xsprite[pDude->extra];

    int x, y, z = pSprite->z, nAngle = pSprite->ang, nType = kCustomDude;

    if (nDist > 0) {
        x = pSprite->x + mulscale30r(Cos(nAngle), nDist);
        y = pSprite->y + mulscale30r(Sin(nAngle), nDist);
    }
    else {
        x = pSprite->x;
        y = pSprite->y;
    }

    pDude->type = nType; pDude->ang = nAngle;
    vec3_t pos = { x, y, z }; setsprite(pDude->index, &pos); 
    pDude->cstat |= 0x1101; pDude->clipdist = dudeInfo[nType - kDudeBase].clipdist;

    // inherit weapon, seq and sound settings.
    pXDude->data1 = pXSource->data1;
    pXDude->data2 = pXSource->data2;
    pXDude->sysData1 = pXSource->data3; // move sndStartId from data3 to sysData1
    pXDude->data3 = 0;

    // spawn seq
    seqSpawn(getSeqStartId(pXDude), 3, pDude->extra, -1);

    // inherit movement speed.
    pXDude->busyTime = pXSource->busyTime;

    // inherit clipdist?
    if (pSource->clipdist > 0) pDude->clipdist = pSource->clipdist;

    // inherit custom hp settings
    if (pXSource->data4 <= 0) pXDude->health = dudeInfo[nType].startHealth << 4;
    else pXDude->health = ClipRange(pXSource->data4 << 4, 1, 65535);

    if (pSource->flags & kModernTypeFlag1) {
        switch (pSource->type) {
            case kGDXCustomDudeSpawn:
            //inherit pal?
            if (pDude->pal <= 0) pDude->pal = pSource->pal;

            // inherit spawn sprite trigger settings, so designer can count monsters.
            pXDude->txID = pXSource->txID;
            pXDude->command = pXSource->command;
            pXDude->triggerOn = pXSource->triggerOn;
            pXDude->triggerOff = pXSource->triggerOff;

            // inherit drop items
            pXDude->dropMsg = pXSource->dropMsg;

            // inherit required key so it can be dropped
            pXDude->key = pXSource->key;

            // inherit dude flags
            pXDude->dudeDeaf = pXSource->dudeDeaf;
            pXDude->dudeGuard = pXSource->dudeGuard;
            pXDude->dudeAmbush = pXSource->dudeAmbush;
            pXDude->dudeFlag4 = pXSource->dudeFlag4;
            break;
        }
    }

    aiInitSprite(pDude);
    return pDude;
}

int getSpriteMassBySize(spritetype* pSprite) {
    int mass = 0; int seqId = -1; Seq* pSeq = NULL;
    if (IsDudeSprite(pSprite)) {

        switch (pSprite->type) {
            case 225: // fake dude, no seq
                break;
            case kCustomDude:
            case kCustomDudeBurning:
                seqId = xsprite[pSprite->extra].data2;
                break;
            default:
                seqId = dudeInfo[pSprite->type - kDudeBase].seqStartID;
                break;
        }

    } else if (pSprite->extra >= 0) {
        
        seqId = seqGetID(3, pSprite->extra);

    }
    
    SPRITEMASS* cached = &gSpriteMass[pSprite->extra];
    if (((seqId >= 0 && seqId == cached->seqId) || pSprite->picnum == cached->picnum) && pSprite->xrepeat == cached->xrepeat &&
        pSprite->yrepeat == cached->yrepeat && pSprite->clipdist == cached->clipdist) {
        return cached->mass;
    }

    short picnum = pSprite->picnum;
    short massDiv = 30;  short addMul = 2; short subMul = 2;

    if (seqId >= 0) {
        DICTNODE* hSeq = gSysRes.Lookup(seqId, "SEQ");
        if (hSeq)
        {
            pSeq = (Seq*)gSysRes.Load(hSeq);
            picnum = seqGetTile(&pSeq->frames[0]);
        }
        else
            picnum = pSprite->picnum;
    }

    int clipDist = ClipRange(pSprite->clipdist, 1, 255);
    short x = tilesiz[picnum].x;        short y = tilesiz[picnum].y;
    short xrepeat = pSprite->xrepeat; 	short yrepeat = pSprite->yrepeat;

    // take surface type into account
    switch (tileGetSurfType(pSprite->xvel + 0xc000)) {
        case 1:  massDiv = 16; break; // stone
        case 2:  massDiv = 18; break; // metal
        case 3:  massDiv = 21; break; // wood
        case 4:  massDiv = 25; break; // flesh
        case 5:  massDiv = 28; break; // water
        case 6:  massDiv = 26; break; // dirt
        case 7:  massDiv = 27; break; // clay
        case 8:  massDiv = 35; break; // snow
        case 9:  massDiv = 22; break; // ice
        case 10: massDiv = 37; break; // leaves
        case 11: massDiv = 33; break; // cloth
        case 12: massDiv = 36; break; // plant
        case 13: massDiv = 24; break; // goo
        case 14: massDiv = 23; break; // lava
    }

    mass = ((x + y) * (clipDist / 2)) / massDiv;

    if (xrepeat > 64) mass += ((xrepeat - 64) * addMul);
    else if (xrepeat < 64 && mass > 0) {
        for (int i = 64 - xrepeat; i > 0; i--) {
            if ((mass -= subMul) <= 100 && subMul-- <= 1) {
                mass -= i;
                break;
            }
        }
    }

    if (yrepeat > 64) mass += ((yrepeat - 64) * addMul);
    else if (yrepeat < 64 && mass > 0) {
        for (int i = 64 - yrepeat; i > 0; i--) {
            if ((mass -= subMul) <= 100 && subMul-- <= 1) {
                mass -= i;
                break;
            }
        }
    }

    if (mass <= 0) cached->mass = 1 + Random(10);
    else cached->mass = ClipRange(mass, 1, 65535);

    cached->airVel = ClipRange(400 - cached->mass, 32, 400);
    cached->fraction = ClipRange(60000 - (cached->mass << 7), 8192, 60000);

    cached->xrepeat = pSprite->xrepeat;             cached->yrepeat = pSprite->yrepeat;
    cached->picnum = pSprite->picnum;               cached->seqId = seqId;
    cached->clipdist = pSprite->clipdist;

    return cached->mass;
}

int isDebris(int nSprite) {
    if (sprite[nSprite].extra < 0 || xsprite[sprite[nSprite].extra].physAttr == 0) 
        return -1;

    for (int i = 0; i < gPhysSpritesCount; i++) {
        if (gPhysSpritesList[i] != nSprite) continue;
        return i;
    }

    return -1;
}

int debrisGetFreeIndex(void) {
    for (int i = 0; i < kMaxSuperXSprites; i++) {
        if (gPhysSpritesList[i] == -1 || sprite[gPhysSpritesList[i]].statnum == kStatFree) return i;
        else if ((sprite[gPhysSpritesList[i]].flags & kHitagFree) || sprite[gPhysSpritesList[i]].extra < 0) return i;
        else if (xsprite[sprite[gPhysSpritesList[i]].extra].physAttr == 0) return i;
    }

    return -1;
}

void debrisConcuss(int nOwner, int listIndex, int x, int y, int z, int dmg) {
    spritetype* pSprite = (gPhysSpritesList[listIndex] >= 0) ? &sprite[gPhysSpritesList[listIndex]] : NULL;
    if (pSprite != NULL && pSprite->extra >= 0 && pSprite->extra < kMaxXSprites) {
        int dx = pSprite->x - x; int dy = pSprite->y - y; int dz = (pSprite->z - z) >> 4;
        dmg = scale(0x40000, dmg, 0x40000 + dx * dx + dy * dy + dz * dz);

        int size = (tilesiz[pSprite->picnum].x * pSprite->xrepeat * tilesiz[pSprite->picnum].y * pSprite->yrepeat) >> 1;
        if (xsprite[pSprite->extra].physAttr & kPhysDebrisExplode) {
            if (gSpriteMass[pSprite->extra].mass > 0) {
                int t = scale(dmg, size, gSpriteMass[pSprite->extra].mass);

                xvel[pSprite->xvel] += mulscale16(t, dx);
                yvel[pSprite->xvel] += mulscale16(t, dy);
                zvel[pSprite->xvel] += mulscale16(t, dz);
            }
        }

        if (pSprite->index >= kThingBase && pSprite->index < kThingMax)
            actPostSprite(pSprite->index, 4); // if it was a thing, return it's statnum back

        actDamageSprite(nOwner, pSprite, DAMAGE_TYPE_3, dmg);
        return;
    }
}

void debrisMove(int listIndex) {
    if (!(sprite[gPhysSpritesList[listIndex]].extra > 0 && sprite[gPhysSpritesList[listIndex]].extra < kMaxXSprites)) {
        gPhysSpritesList[listIndex] = -1;
        return;
    }
    else if (!(sprite[gPhysSpritesList[listIndex]].sectnum >= 0 && sprite[gPhysSpritesList[listIndex]].sectnum < kMaxSectors)) {
        gPhysSpritesList[listIndex] = -1;
        return;
    }

    int nSprite = gPhysSpritesList[listIndex];
    int nXSprite = sprite[nSprite].extra;       XSPRITE* pXDebris = &xsprite[nXSprite];
    spritetype* pSprite = &sprite[nSprite];     int nSector = pSprite->sectnum;

    int top, bottom;	GetSpriteExtents(pSprite, &top, &bottom);

    int moveHit = 0;
    int floorDist = (bottom - pSprite->z) / 4;
    int ceilDist = (pSprite->z - top) / 4;
    int clipDist = pSprite->clipdist << 2;

    int tmpFraction = gSpriteMass[pSprite->extra].fraction;
    if (sector[nSector].extra >= 0 && xsector[sector[nSector].extra].Underwater)
        tmpFraction >>= 1;

    if (xvel[pSprite->xvel] != 0 || yvel[pSprite->xvel] != 0) {

        short oldcstat = pSprite->cstat;
        pSprite->cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);

        moveHit = gSpriteHit[nXSprite].hit = ClipMove((int*)& pSprite->x, (int*)& pSprite->y, (int*)& pSprite->z, &nSector, xvel[nSprite] >> 12,
            yvel[nSprite] >> 12, pSprite->clipdist << 2, (pSprite->z - top) / 4, (bottom - pSprite->z) / 4, CLIPMASK0);

        pSprite->cstat = oldcstat;

        dassert(nSector >= 0);

        if (pSprite->sectnum != nSector) {
            dassert(nSector >= 0 && nSector < kMaxSectors);
            ChangeSpriteSect(nSprite, nSector);
        }

        if ((gSpriteHit[nXSprite].hit & 0xc000) == 0x8000) {
            int nHitWall = gSpriteHit[nXSprite].hit & 0x3fff;
            actWallBounceVector((int*)& xvel[nSprite], (int*)& yvel[nSprite], nHitWall, tmpFraction);
        }

    }
    else {
        dassert(nSector >= 0 && nSector < kMaxSectors);
        FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
    }

    if (zvel[nSprite])
        pSprite->z += zvel[nSprite] >> 8;

    int ceilZ, ceilHit, floorZ, floorHit;
    GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist << 2, CLIPMASK0);
    GetSpriteExtents(pSprite, &top, &bottom);

    if ((pXDebris->physAttr & kPhysGravity) && bottom < floorZ) {
        pSprite->z += 455;
        zvel[nSprite] += 58254;
    }
    int warp = CheckLink(pSprite);
    if (warp != 0) {
        GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist << 2, CLIPMASK0);
        if (!(pSprite->cstat & CSTAT_SPRITE_INVISIBLE)) {
            switch (warp) {
            case kMarkerUpWater:
            case kMarkerUpGoo:
                int pitch = (150000 - (gSpriteMass[pSprite->extra].mass << 9)) + Random3(8192);
                sfxPlay3DSoundCP(pSprite, 720, -1, 0, pitch, 75 - Random(40));
                
                if (sector[pSprite->sectnum].extra < 0 || !xsector[sector[pSprite->sectnum].extra].Underwater)
                    evKill(pSprite->xvel, 3, CALLBACK_ID_11);
                else {
                    if (Chance(0x8000))
                        evPost(pSprite->xvel, 3, 0, CALLBACK_ID_11);

                    for (int i = 2; i <= 5; i++) {
                        if (Chance(0x3000 * i))
                            evPost(pSprite->xvel, 3, 0, CALLBACK_ID_11);
                    }
                }
                break;
            }
        }
    }

    GetSpriteExtents(pSprite, &top, &bottom);

    if ((floorHit & 0xe000) == 0xc000) {
        if ((sprite[floorHit & 0x1fff].cstat & 0x30) == 0x20)
            if (klabs(bottom - floorZ) < 1024) floorZ -= 1024;
    }

    if (bottom >= floorZ) {

        gSpriteHit[nXSprite].florhit = floorHit;
        pSprite->z += floorZ - bottom;
        int v20 = zvel[nSprite] - velFloor[pSprite->sectnum];
        if (v20 > 0) {

            pXDebris->physAttr |= kPhysFalling;
            int vax = actFloorBounceVector((int*)& xvel[nSprite], (int*)& yvel[nSprite], (int*)& v20, pSprite->sectnum, tmpFraction);
            zvel[nSprite] = v20;

            if (velFloor[pSprite->sectnum] == 0 && klabs(zvel[nSprite]) < 0x10000) {
                zvel[nSprite] = 0;
                pXDebris->physAttr &= ~kPhysFalling;
            }

            moveHit = 0x4000 | nSector;

        }
        else if (zvel[nSprite] == 0)
            pXDebris->physAttr &= ~kPhysFalling;

    }
    else {

        gSpriteHit[nXSprite].florhit = 0;
        if (pXDebris->physAttr & kPhysGravity)
            pXDebris->physAttr |= kPhysFalling;
    }

    if (top <= ceilZ) {

        gSpriteHit[nXSprite].ceilhit = ceilHit;
        pSprite->z += ClipLow(ceilZ - top, 0);
        if (zvel[nSprite] < 0)
        {
            xvel[nSprite] = mulscale16(xvel[nSprite], 0xc000);
            yvel[nSprite] = mulscale16(yvel[nSprite], 0xc000);
            zvel[nSprite] = mulscale16(-zvel[nSprite], 0x4000);
        }

    }
    else {

        gSpriteHit[nXSprite].ceilhit = 0;

    }

    if (bottom >= floorZ) {
        int nVel = approxDist(xvel[nSprite], yvel[nSprite]);
        int nVelClipped = ClipHigh(nVel, 0x11111);

        if ((floorHit & 0xc000) == 0xc000) {
            int nHitSprite = floorHit & 0x3fff;
            if ((sprite[nHitSprite].cstat & 0x30) == 0)
            {
                xvel[nSprite] += mulscale(4, pSprite->x - sprite[nHitSprite].x, 2);
                yvel[nSprite] += mulscale(4, pSprite->y - sprite[nHitSprite].y, 2);
                moveHit = gSpriteHit[nXSprite].hit;
            }
        }
        if (nVel > 0)
        {
            int t = divscale16(nVelClipped, nVel);
            xvel[nSprite] -= mulscale16(t, xvel[nSprite]);
            yvel[nSprite] -= mulscale16(t, yvel[nSprite]);
        }
    }

    if (xvel[nSprite] || yvel[nSprite])
        pSprite->ang = getangle(xvel[nSprite], yvel[nSprite]);

    if (moveHit != 0 && pXDebris->Impact && pXDebris->locked != 1 && !pXDebris->isTriggered) {
        if (!pXDebris->Interrutable && pXDebris->state != pXDebris->restState) return;

        if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
            // if thing was turned in debris, change it's stat back so it will do on impact what it supposed to do...
            //actPostSprite(nSprite, 4); // !!!! not working here for some reason
            changespritestat(nSprite, 4);

        if (pXDebris->state == 1) trTriggerSprite(pSprite->xvel, pXDebris, COMMAND_ID_0);
        else trTriggerSprite(pSprite->xvel, pXDebris, COMMAND_ID_1);
    }
}

bool ceilIsTooLow(spritetype* pSprite) {
    if (pSprite != NULL) {

        sectortype* pSector = &sector[pSprite->sectnum];
        int a = pSector->ceilingz - pSector->floorz;
        int top, bottom;
        GetSpriteExtents(pSprite, &top, &bottom);
        int b = top - bottom;
        if (a > b) return true;
    }

    return false;
}

END_BLD_NS
