//-------------------------------------------------------------------------
/*
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 "ns.h"	// Must come before everything else!

#include <random>

#include "build.h"
#include "compat.h"
#include "mmulti.h"
#include "common_game.h"

#include "ai.h"
#include "actor.h"
#include "blood.h"
#include "db.h"
#include "endgame.h"
#include "eventq.h"
#include "aiunicult.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 "qav.h"
#include "sfx.h"
#include "sound.h"
#include "triggers.h"
#include "trig.h"
#include "view.h"
#include "sectorfx.h"
#include "messages.h"
#include "weapon.h"

BEGIN_BLD_NS

int basePath[kMaxSectors];

void FireballTrapSeqCallback(int, int);
#ifdef NOONE_EXTENSIONS
void UniMissileTrapSeqCallback(int, int);
#endif
void MGunFireSeqCallback(int, int);
void MGunOpenSeqCallback(int, int);

int nFireballTrapClient = seqRegisterClient(FireballTrapSeqCallback);
#ifdef NOONE_EXTENSIONS
int nUniMissileTrapClient = seqRegisterClient(UniMissileTrapSeqCallback);
#endif
int nMGunFireClient = seqRegisterClient(MGunFireSeqCallback);
int nMGunOpenClient = seqRegisterClient(MGunOpenSeqCallback);



unsigned int GetWaveValue(unsigned int nPhase, int nType)
{
    switch (nType)
    {
    case 0:
        return 0x8000-(Cos((nPhase<<10)>>16)>>15);
    case 1:
        return nPhase;
    case 2:
        return 0x10000-(Cos((nPhase<<9)>>16)>>14);
    case 3:
        return Sin((nPhase<<9)>>16)>>14;
    }
    return nPhase;
}

char SetSpriteState(int nSprite, XSPRITE* pXSprite, int nState)
{
    if ((pXSprite->busy & 0xffff) == 0 && pXSprite->state == nState)
        return 0;
    pXSprite->busy = nState << 16;
    pXSprite->state = nState;
    evKill(nSprite, 3);
    if ((sprite[nSprite].flags & kHitagRespawn) != 0 && sprite[nSprite].inittype >= kDudeBase && sprite[nSprite].inittype < kDudeMax)
    {
        pXSprite->respawnPending = 3;
        evPost(nSprite, 3, gGameOptions.nMonsterRespawnTime, kCallbackRespawn);
        return 1;
    }
    if (pXSprite->restState != nState && pXSprite->waitTime > 0)
        evPost(nSprite, 3, (pXSprite->waitTime * 120) / 10, pXSprite->restState ? kCmdOn : kCmdOff);
    if (pXSprite->txID)
    {
        if (pXSprite->command != kCmdLink && pXSprite->triggerOn && pXSprite->state)
            evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
        if (pXSprite->command != kCmdLink && pXSprite->triggerOff && !pXSprite->state)
            evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
    }
    return 1;
}



char SetWallState(int nWall, XWALL *pXWall, int nState)
{
    if ((pXWall->busy&0xffff) == 0 && pXWall->state == nState)
        return 0;
    pXWall->busy = nState<<16;
    pXWall->state = nState;
    evKill(nWall, 0);
    if (pXWall->restState != nState && pXWall->waitTime > 0)
        evPost(nWall, 0, (pXWall->waitTime*120) / 10, pXWall->restState ? kCmdOn : kCmdOff);
    if (pXWall->txID)
    {
        if (pXWall->command != kCmdLink && pXWall->triggerOn && pXWall->state)
            evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command);
        if (pXWall->command != kCmdLink && pXWall->triggerOff && !pXWall->state)
            evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command);
    }
    return 1;
}

char SetSectorState(int nSector, XSECTOR *pXSector, int nState)
{
    if ((pXSector->busy&0xffff) == 0 && pXSector->state == nState)
        return 0;
    pXSector->busy = nState<<16;
    pXSector->state = nState;
    evKill(nSector, 6);
    if (nState == 1)
    {
        if (pXSector->command != kCmdLink && pXSector->triggerOn && pXSector->txID)
            evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command);
        if (pXSector->stopOn)
        {
            pXSector->stopOn = 0;
            pXSector->stopOff = 0;
        }
        else if (pXSector->reTriggerA)
            evPost(nSector, 6, (pXSector->waitTimeA * 120) / 10, kCmdOff);
    }
    else
    {
        if (pXSector->command != kCmdLink && pXSector->triggerOff && pXSector->txID)
            evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command);
        if (pXSector->stopOff)
        {
            pXSector->stopOn = 0;
            pXSector->stopOff = 0;
        }
        else if (pXSector->reTriggerB)
            evPost(nSector, 6, (pXSector->waitTimeB * 120) / 10, kCmdOn);
    }
    return 1;
}

int gBusyCount = 0;

enum BUSYID {
    BUSYID_0 = 0,
    BUSYID_1,
    BUSYID_2,
    BUSYID_3,
    BUSYID_4,
    BUSYID_5,
    BUSYID_6,
    BUSYID_7,
};

struct BUSY {
    int at0;
    int at4;
    int at8;
    BUSYID atc;
};

BUSY gBusy[128];

void AddBusy(int a1, BUSYID a2, int nDelta)
{
    dassert(nDelta != 0);
    int i;
    for (i = 0; i < gBusyCount; i++)
    {
        if (gBusy[i].at0 == a1 && gBusy[i].atc == a2)
            break;
    }
    if (i == gBusyCount)
    {
        if (gBusyCount == 128)
            return;
        gBusy[i].at0 = a1;
        gBusy[i].atc = a2;
        gBusy[i].at8 = nDelta > 0 ? 0 : 65536;
        gBusyCount++;
    }
    gBusy[i].at4 = nDelta;
}

void ReverseBusy(int a1, BUSYID a2)
{
    int i;
    for (i = 0; i < gBusyCount; i++)
    {
        if (gBusy[i].at0 == a1 && gBusy[i].atc == a2)
        {
            gBusy[i].at4 = -gBusy[i].at4;
            break;
        }
    }
}

unsigned int GetSourceBusy(EVENT a1)
{
    int nIndex = a1.index;
    switch (a1.type)
    {
    case 6:
    {
        int nXIndex = sector[nIndex].extra;
        dassert(nXIndex > 0 && nXIndex < kMaxXSectors);
        return xsector[nXIndex].busy;
    }
    case 0:
    {
        int nXIndex = wall[nIndex].extra;
        dassert(nXIndex > 0 && nXIndex < kMaxXWalls);
        return xwall[nXIndex].busy;
    }
    case 3:
    {
        int nXIndex = sprite[nIndex].extra;
        dassert(nXIndex > 0 && nXIndex < kMaxXSprites);
        return xsprite[nXIndex].busy;
    }
    }
    return 0;
}

void LifeLeechOperate(spritetype *pSprite, XSPRITE *pXSprite, EVENT event)
{
    switch (event.cmd) {
    case kCmdSpritePush:
    {
        int nPlayer = pXSprite->data4;
        if (nPlayer >= 0 && nPlayer < gNetPlayers)
        {
            PLAYER *pPlayer = &gPlayer[nPlayer];
            if (pPlayer->pXSprite->health > 0)
            {
                pPlayer->ammoCount[8] = ClipHigh(pPlayer->ammoCount[8]+pXSprite->data3, gAmmoInfo[8].max);
                pPlayer->hasWeapon[9] = 1;
                if (pPlayer->curWeapon != 9)
                {
                    pPlayer->weaponState = 0;
                    pPlayer->nextWeapon = 9;
                }
                evKill(pSprite->index, 3);
            }
        }
        break;
    }
    case kCmdSpriteProximity:
    {
        int nTarget = pXSprite->target;
        if (nTarget >= 0 && nTarget < kMaxSprites)
        {
            if (!pXSprite->stateTimer)
            {
                spritetype *pTarget = &sprite[nTarget];
                if (pTarget->statnum == kStatDude && !(pTarget->flags&32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites)
                {
                    int top, bottom;
                    GetSpriteExtents(pSprite, &top, &bottom);
                    int nType = pTarget->type-kDudeBase;
                    DUDEINFO *pDudeInfo = &dudeInfo[nType];
                    int z1 = (top-pSprite->z)-256;
                    int x = pTarget->x;
                    int y = pTarget->y;
                    int z = pTarget->z;
                    int nDist = approxDist(x - pSprite->x, y - pSprite->y);
                    if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum))
                    {
                        int t = divscale(nDist, 0x1aaaaa, 12);
                        x += (xvel[nTarget]*t)>>12;
                        y += (yvel[nTarget]*t)>>12;
                        int angBak = pSprite->ang;
                        pSprite->ang = getangle(x-pSprite->x, y-pSprite->y);
                        int dx = Cos(pSprite->ang)>>16;
                        int dy = Sin(pSprite->ang)>>16;
                        int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
                        int dz = divscale(tz - top - 256, nDist, 10);
                        int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
                        int t2;
                        if (!pXSprite->data3)
                            t2 = 120 / 10.0;
                        else
                            t2 = (3*120) / 10.0;
                        spritetype *pMissile = actFireMissile(pSprite, 0, z1, dx, dy, dz, nMissileType);
                        if (pMissile)
                        {
                            pMissile->owner = pSprite->owner;
                            pXSprite->stateTimer = 1;
                            evPost(pSprite->index, 3, t2, kCallbackLeechStateTimer);
                            pXSprite->data3 = ClipLow(pXSprite->data3-1, 0);
                        }
                        pSprite->ang = angBak;
                    }
                }
            }
        }
        return;
    }
    }
    actPostSprite(pSprite->index, kStatFree);
}

void ActivateGenerator(int);

void OperateSprite(int nSprite, XSPRITE *pXSprite, EVENT event)
{
    spritetype *pSprite = &sprite[nSprite];
    #ifdef NOONE_EXTENSIONS
        if (gModernMap) {
            switch (event.cmd) {
                case kCmdUnlock:
                case kCmdToggleLock:
                    switch (pSprite->type) {
                        case kModernWindGenerator:
                            if (pXSprite->locked) stopWindOnSectors(pXSprite);
                            break;
                    }
                    break;
            }
        }
    #endif

    switch (event.cmd) {
        case kCmdLock:
            pXSprite->locked = 1;
            return;
        case kCmdUnlock:
            pXSprite->locked = 0;
            return;
        case kCmdToggleLock:
            pXSprite->locked = pXSprite->locked ^ 1;
            return;
    }

    #ifdef NOONE_EXTENSIONS
    if (gModernMap) {
        
        switch (pSprite->type) {
            
            // allow triggering players
            case kDudePlayer1:
            case kDudePlayer2:
            case kDudePlayer3:
            case kDudePlayer4:
            case kDudePlayer5:
            case kDudePlayer6:
            case kDudePlayer7:
            case kDudePlayer8:
                switch (event.cmd) {
                    case kCmdOff:
                        SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        SetSpriteState(nSprite, pXSprite, 1);
                        break;
                    default:
                        SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                        break;
                }
                return;
            // add linking for path markers and stacks feature
            case kMarkerLowWater:
            case kMarkerUpWater:
            case kMarkerUpGoo:
            case kMarkerLowGoo:
            case kMarkerUpLink:
            case kMarkerLowLink:
            case kMarkerUpStack:
            case kMarkerLowStack:
            case kMarkerPath:
                switch (pXSprite->command) {
                    case kCmdLink:
                        if (pXSprite->txID <= 0) return;
                        evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
                        return;
                }
                break; // go normal operate switch
            case kMarkerDudeSpawn:
                if (gGameOptions.nMonsterSettings && pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeVanillaMax) {

                    spritetype* pSpawn = NULL;
                    // add spawn random dude feature - works only if at least 2 data fields are not empty.
                    if ((pSpawn = spawnRandomDude(pSprite)) == NULL)
                        pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0);

                    if (pSpawn) {
                        XSPRITE *pXSpawn = &xsprite[pSpawn->extra];
                        gKillMgr.sub_263E0(1);
                        switch (pXSprite->data1) {
                            case kDudeBurningInnocent:
                            case kDudeBurningCultist:
                            case kDudeBurningZombieButcher:
                            case kDudeBurningTinyCaleb:
                            case kDudeBurningBeast: {
                                pXSpawn->health = dudeInfo[pXSprite->data1 - kDudeBase].startHealth << 4;
                                pXSpawn->burnTime = 10;
                                pXSpawn->target = -1;
                                aiActivateDude(pSpawn, pXSpawn);
                                break;
                            default:
                                if (pSprite->flags & kModernTypeFlag3) aiActivateDude(pSpawn, pXSpawn);
                                break;
                            }
                        }

                    }
                }
                return;
            // Random Event Switch takes random data field and uses it as TX ID
            case kModernRandomTX: {

                int tx = 0; int maxRetries = 10;
                // set range of TX ID if data2 and data3 is empty.
                if (pXSprite->data1 > 0 && pXSprite->data2 <= 0 && pXSprite->data3 <= 0 && pXSprite->data4 > 0) {

                    // data1 must be less than data4
                    if (pXSprite->data1 > pXSprite->data4) {
                        short tmp = pXSprite->data1;
                        pXSprite->data1 = pXSprite->data4;
                        pXSprite->data4 = tmp;
                    }

                    int total = pXSprite->data4 - pXSprite->data1;
                    while (maxRetries > 0) {
                        // use true random only for single player mode, otherwise use Blood's default one.
                        if (gGameOptions.nGameType == 0 && !VanillaMode() && !DemoRecordStatus()) tx = STD_Random(pXSprite->data1, pXSprite->data4);
                        else tx = Random(total) + pXSprite->data1;

                        if (tx != pXSprite->txID) break;
                        maxRetries--;
                    }

                } else {
                    while (maxRetries > 0) {
                        if ((tx = GetRandDataVal(pXSprite, kRandomizeTX)) > 0 && tx != pXSprite->txID) break;
                        maxRetries--;
                    }
                }

                pXSprite->txID = (tx > 0 && tx < kChannelUserMax) ? tx : 0;
                SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
            }
            return;
            // Sequential Switch takes values from data fields starting from data1 and uses it as TX ID
            case kModernSequentialTX: {
                bool range = false; int cnt = 3; int tx = 0;
                // set range of TX ID if data2 and data3 is empty.
                if (pXSprite->data1 > 0 && pXSprite->data2 <= 0 && pXSprite->data3 <= 0 && pXSprite->data4 > 0) {

                    // data1 must be less than data4
                    if (pXSprite->data1 > pXSprite->data4) {
                        short tmp = pXSprite->data1;
                        pXSprite->data1 = (short)pXSprite->data4;
                        pXSprite->data4 = tmp;
                    }

                    // force send command to all TX id in a range
                    if (pSprite->flags & kModernTypeFlag1) {
                        for (pXSprite->txID = pXSprite->data1; pXSprite->txID <= pXSprite->data4; pXSprite->txID++) {
                            if (pXSprite->txID > 0)
                                evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
                        }

                        pXSprite->txID = pXSprite->sysData1 = 0;
                        return;
                    }

                    // Make sure txIndex is correct as we store current index of TX ID here.
                    if (pXSprite->sysData1 < pXSprite->data1) pXSprite->sysData1 = pXSprite->data1;
                    else if (pXSprite->sysData1 > pXSprite->data4) pXSprite->sysData1 = pXSprite->data4;

                    range = true;

                } else {

                    // force send command to all TX id specified in data
                    if (pSprite->flags & kModernTypeFlag1) {
                        for (int i = 0; i <= 3; i++) {
                            if ((pXSprite->txID = GetDataVal(pSprite, i)) > 0)
                                evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
                        }

                        pXSprite->txID = pXSprite->sysData1 = 0;
                        return;
                    }

                    // Make sure txIndex is correct as we store current index of data field here.
                    if (pXSprite->sysData1 > 3) pXSprite->sysData1 = 0;
                    else if (pXSprite->sysData1 < 0) pXSprite->sysData1 = 3;

                }

                switch (event.cmd) {
                    case kCmdOff:
                        if (range == false) {
                            while (cnt-- >= 0) { // skip empty data fields
                                pXSprite->sysData1--;
                                if (pXSprite->sysData1 < 0) pXSprite->sysData1 = 3;
                                tx = GetDataVal(pSprite, pXSprite->sysData1);
                                if (tx < 0) ThrowError(" -- Current data index is negative");
                                if (tx > 0) break;
                                continue;
                            }
                        } else {
                            pXSprite->sysData1--;
                            if (pXSprite->sysData1 < pXSprite->data1) {
                                pXSprite->sysData1 = pXSprite->data4;
                            }
                            tx = pXSprite->sysData1;
                        }
                        break;
                    default:
                        if (range == false) {
                            while (cnt-- >= 0) { // skip empty data fields
                                if (pXSprite->sysData1 > 3) pXSprite->sysData1 = 0;
                                tx = GetDataVal(pSprite, pXSprite->sysData1);
                                if (tx < 0) ThrowError(" ++ Current data index is negative");
                                pXSprite->sysData1++;
                                if (tx > 0) break;
                                continue;
                            }
                        } else {
                            tx = pXSprite->sysData1;
                            if (pXSprite->sysData1 >= pXSprite->data4) {
                                pXSprite->sysData1 = pXSprite->data1;
                                break;
                            }
                            pXSprite->sysData1++;
                        }
                        break;
                }

                pXSprite->txID = (tx > 0 && tx < kChannelUserMax) ? tx : 0;
                SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
            }
            return;
            
            case kMarkerWarpDest:
                if (pXSprite->txID <= 0) {
                    if (SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1) {
                        if (pXSprite->data1 > 0) {
                            PLAYER* pPlayer = getPlayerById(pXSprite->data1);
                            if (pPlayer != NULL)
                               useTeleportTarget(pXSprite, pPlayer->pSprite);
                        }
                    }
                    return;
                }
                modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                return;
            
            case kModernSpriteDamager:
                if (pXSprite->txID <= 0) {
                    if (SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1) {
                        if (pXSprite->data1 > 0) {
                            PLAYER* pPlayer = getPlayerById(pXSprite->data1);
                            if (pPlayer != NULL)
                                useSpriteDamager(pXSprite, pPlayer->pSprite);
                        }
                    }
                    return;
                }
                modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                return;

            case kModernObjPropertiesChanger:
                if (pXSprite->txID <= 0) {
                    if (SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1) == 1)
                        usePropertiesChanger(pXSprite, -1, -1);
                    return;
                }
                modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                return;

            case kModernObjSizeChanger:
                modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                return;
            case kModernObjPicnumChanger:
            case kModernSectorFXChanger:
            case kModernObjDataChanger:
                modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                return;
            
            case kModernCustomDudeSpawn:
                if (gGameOptions.nMonsterSettings && actSpawnCustomDude(pSprite, -1) != NULL)
                    gKillMgr.sub_263E0(1);
                return;
            
            case kModernSeqSpawner:
            case kModernEffectSpawner:
                switch (event.cmd) {
                    case kCmdOff:
                        if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        evKill(nSprite, 3); // queue overflow protect
                        if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
                        fallthrough__;
                    case kCmdRepeat:
                        if (pXSprite->txID <= 0)
                            (pSprite->type == kModernSeqSpawner) ? useSeqSpawnerGen(pXSprite, 3, pSprite->xvel) : useEffectGen(pXSprite, NULL);
                        else {

                            switch (pXSprite->command) {
                                case kCmdLink:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // just send command to change properties
                                    break;
                                case kCmdUnlock:
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // send normal command first
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse);  // then send command to change properties
                                    break;
                                default:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // send first command to change properties
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // then send normal command
                                    break;
                            }

                        }

                        if (pXSprite->busyTime > 0)
                            evPost(nSprite, 3, ClipLow((int(pXSprite->busyTime) + Random2(pXSprite->data1)) * 120 / 10, 0), kCmdRepeat);
                        break;
                    default:
                        if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
                        else evPost(nSprite, 3, 0, kCmdOff);
                        break;
                }
                return;

            case kModernWindGenerator:
                switch (event.cmd) {
                    case kCmdOff:
                        stopWindOnSectors(pXSprite);
                        if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        evKill(nSprite, 3); // queue overflow protect
                        if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
                        fallthrough__;
                    case kCmdRepeat:
                        if (pXSprite->txID <= 0) useSectorWindGen(pXSprite, NULL);
                        else {

                            switch (pXSprite->command) {
                                case kCmdLink:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // just send command to change properties
                                    break;
                                case kCmdUnlock:
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // send normal command first
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse);  // then send command to change properties
                                    break;
                                default:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // send first command to change properties
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // then send normal command
                                    break;
                            }

                        }

                        if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
                        break;
                    default:
                        if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
                        else evPost(nSprite, 3, 0, kCmdOff);
                        break;
                }
                return;

            case kModernDudeTargetChanger: {
                
                // this one is required if data4 of generator was dynamically changed
                // it turns monsters in normal idle state instead of genIdle, so they
                // not ignore the world.
                bool activated = false;
                if (pXSprite->dropMsg == 3 && 3 != pXSprite->data4) {
                    activateDudes(pXSprite->txID);
                    activated = true;
                }

                switch (event.cmd) {
                    case kCmdOff:
                        if (pXSprite->data4 == 3 && activated == false) activateDudes(pXSprite->txID);
                        if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        evKill(nSprite, 3); // queue overflow protect
                        if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
                        fallthrough__;
                    case kCmdRepeat:
                        if (pXSprite->txID <= 0 || !getDudesForTargetChg(pXSprite)) {
                            freeAllTargets(pXSprite);
                            evPost(nSprite, 3, 0, kCmdOff);
                            break;
                        }
                        else {

                            switch (pXSprite->command) {
                                case kCmdLink:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // just send command to change properties
                                    break;
                                case kCmdUnlock:
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // send normal command first
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse);  // then send command to change properties
                                    break;
                                default:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // send first command to change properties
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // then send normal command
                                    break;
                            }

                        }

                        if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
                        break;
                    default:
                        if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
                        else evPost(nSprite, 3, 0, kCmdOff);
                        break;
                    }

                    pXSprite->dropMsg = (short)pXSprite->data4;
            }
            return;

            case kModernObjDataAccumulator:
                switch (event.cmd) {
                    case kCmdOff:
                        if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        evKill(nSprite, 3); // queue overflow protect
                        if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
                        fallthrough__;
                    case kCmdRepeat:

                        // force OFF after *all* TX objects reach the goal value
                        if (pSprite->flags == 0 && goalValueIsReached(pXSprite)) {
                            evPost(nSprite, 3, 0, kCmdOff);
                            break;
                        }

                        if (pXSprite->txID > 0 && pXSprite->data1 > 0 && pXSprite->data1 <= 4) {

                            switch (pXSprite->command) {
                                case kCmdLink:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // just send command to change properties
                                    break;
                                case kCmdUnlock:
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // send normal command first
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse);  // then send command to change properties
                                    break;
                                default:
                                    evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // send first command to change properties
                                    evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // then send normal command
                                    break;
                            }

                            if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, kCmdRepeat);
                        }
                        break;
                    default:
                        if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
                        else evPost(nSprite, 3, 0, kCmdOff);
                        break;
                }
                return;

            case kModernRandom:
            case kModernRandom2:
                switch (event.cmd) {
                    case kCmdOff:
                        if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        evKill(nSprite, 3); // queue overflow protect
                        if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
                        fallthrough__;
                    case kCmdRepeat:
                        ActivateGenerator(nSprite);
                        if (pXSprite->busyTime > 0)
                            evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat);
                        break;
                    default:
                        if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
                        else evPost(nSprite, 3, 0, kCmdOff);
                        break;
                }
                return;

            case kModernThingEnemyLifeLeech:
                dudeLeechOperate(pSprite, pXSprite, event);
                return;

            case kGenModernMissileUniversal:
                switch (event.cmd) {
                    case kCmdOff:
                        if (pXSprite->state == 1) SetSpriteState(nSprite, pXSprite, 0);
                        break;
                    case kCmdOn:
                        evKill(nSprite, 3); // queue overflow protect
                        if (pXSprite->state == 0) SetSpriteState(nSprite, pXSprite, 1);
                        fallthrough__;
                    case kCmdRepeat:
                        ActivateGenerator(nSprite);
                        if (pXSprite->txID) evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
                        if (pXSprite->busyTime > 0) evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat);
                        break;
                    default:
                        if (pXSprite->state == 0) evPost(nSprite, 3, 0, kCmdOn);
                        else evPost(nSprite, 3, 0, kCmdOff);
                        break;
                }
                return;
            case kModernPlayerControl: // WIP
                PLAYER* pPlayer = NULL; int nPlayer = pXSprite->data1; int oldCmd = -1;
                if ((pPlayer = getPlayerById(nPlayer)) == NULL || pPlayer->pXSprite->health <= 0)  return;
                else if (pXSprite->command < kCmdNumberic + 3 && pXSprite->command > kCmdNumberic + 4
                    && !modernTypeSetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1)) return;
                
                TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
                if (event.cmd >= kCmdNumberic) {
                    switch (event.cmd) {
                        case kCmdNumberic + 3: // start playing qav scene
                            if (pCtrl->qavScene.index != nSprite || pXSprite->Interrutable)
                                trPlayerCtrlStartScene(pXSprite, pPlayer);
                            return;
                        case kCmdNumberic + 4: { // stop playing qav scene
                            int scnIndex = pCtrl->qavScene.index;
                            if (spriRangeIsFine(scnIndex) && (scnIndex == nSprite || event.type != 3 || sprite[event.index].type != kModernPlayerControl)) {
                                if (scnIndex != nSprite) pXSprite = &xsprite[sprite[scnIndex].extra];
                                trPlayerCtrlStopScene(pXSprite, pPlayer);
                            }
                            return;
                        }
                        default:
                            oldCmd = pXSprite->command;
                            pXSprite->command = event.cmd; // convert event command to current sprite command
                            break;
                    }
                }

                /// !!! COMMANDS OF THE CURRENT SPRITE, NOT OF THE EVENT !!! ///
                switch (pXSprite->command) {
                    case kCmdLink: // copy properties of sprite to player
                        trPlayerCtrlLink(pXSprite, pPlayer);
                        break;
                    case kCmdNumberic: // player life form
                        if (pXSprite->data2 >= kModeHuman || pXSprite->data2 <= kModeHumanShrink) {
                            playerSetRace(pPlayer, pXSprite->data2);
                            switch (pPlayer->lifeMode) {
                                case kModeHuman:
                                case kModeBeast:
                                    resetPlayerSize(pPlayer);
                                    break;
                                case kModeHumanShrink:
                                    shrinkPlayerSize(pPlayer, 2);
                                    break;
                                case kModeHumanGrown:
                                    growPlayerSize(pPlayer, 2);
                                    break;
                            }
                        }
                        break;

                    case kCmdNumberic + 1: // 65
                        // player movement speed (for all races and postures)
                        if (valueIsBetween(pXSprite->data2, -1, 32767)) {
                            int speed = pXSprite->data2 << 1;
                            for (int i = 0; i < kModeMax; i++) {
                                for (int a = 0; a < kPostureMax; a++) {
                                    POSTURE* curPosture = &pPlayer->pPosture[i][a]; POSTURE defPosture = gPostureDefaults[i][a];
                                    if (pXSprite->data2 == 100) {
                                        curPosture->frontAccel  = defPosture.frontAccel;
                                        curPosture->sideAccel   = defPosture.sideAccel;
                                        curPosture->backAccel   = defPosture.backAccel;
                                    } else if (speed >= 0) {
                                        curPosture->frontAccel  = ClipRange(mulscale8(defPosture.frontAccel, speed), 0, 65535);
                                        curPosture->sideAccel   = ClipRange(mulscale8(defPosture.sideAccel, speed), 0, 65535);
                                        curPosture->backAccel   = ClipRange(mulscale8(defPosture.backAccel, speed), 0, 65535);
                                    }
                                }
                            }

                            //viewSetSystemMessage("MOVEMENT: %d %d %d", pXSprite->rxID,pSprite->index, gPosture[0][0].frontAccel);
                        }

                        // player jump height (for all races and stand posture only)
                        if (valueIsBetween(pXSprite->data3, -1, 32767)) {
                            int jump = pXSprite->data3 * 3;
                            for (int i = 0; i < kModeMax; i++) {
                                POSTURE* curPosture = &pPlayer->pPosture[i][kPostureStand]; POSTURE defPosture = gPostureDefaults[i][kPostureStand];
                                if (pXSprite->data3 == 100) {
                                    curPosture->normalJumpZ     = defPosture.normalJumpZ;
                                    curPosture->pwupJumpZ       = defPosture.pwupJumpZ;
                                } else if (jump >= 0) {
                                    curPosture->normalJumpZ     = ClipRange(mulscale8(defPosture.normalJumpZ, jump), -0x200000, 0);
                                    curPosture->pwupJumpZ       = ClipRange(mulscale8(defPosture.pwupJumpZ, jump), -0x200000, 0);
                                }
                            }
                            
                            //viewSetSystemMessage("JUMPING: %d", gPosture[0][0].normalJumpZ);
                        }
                        break;

                    case kCmdNumberic + 2: // 66
                        // player screen effects
                        if (pXSprite->data3 < 0) break;
                        switch (pXSprite->data2) {
                            case 1: // tilting
                                pPlayer->tiltEffect = ClipRange(pXSprite->data3, 0, 220);
                                break;
                            case 2: // pain
                                pPlayer->painEffect = pXSprite->data3;
                                break;
                            case 3: // blind
                                pPlayer->blindEffect = pXSprite->data3;
                                break;
                            case 4: // pickup
                                pPlayer->pickupEffect = pXSprite->data3;
                                break;
                            case 5: // quakeEffect
                                pPlayer->quakeEffect = pXSprite->data3;
                                break;
                            case 6: // visibility
                                pPlayer->visibility = pXSprite->data3;
                                break;
                            case 7: // delirium
                                pPlayer->pwUpTime[kPwUpDeliriumShroom] = ClipHigh(pXSprite->data3 << 1, 432000);
                            break;
                        }
                        break;

                    case kCmdNumberic + 3: // 67
                        // start playing qav scene
                        if (pCtrl->qavScene.index != nSprite || pXSprite->Interrutable)
                            trPlayerCtrlStartScene(pXSprite, pPlayer);
                        break;

                    case kCmdNumberic + 4: // 68
                         // stop playing qav scene
                        if (pCtrl->qavScene.index == nSprite)
                            trPlayerCtrlStopScene(pXSprite, pPlayer);
                        break;

                    case kCmdNumberic + 5: // 69
                        // set player sprite and look angles
                        if (pXSprite->data4 == 0) {
                            
                            // look angle
                            if (valueIsBetween(pXSprite->data2, -128, 128)) {
                                CONSTEXPR int upAngle = 289; CONSTEXPR int downAngle = -347;
                                CONSTEXPR double lookStepUp = 4.0 * upAngle / 60.0;
                                CONSTEXPR double lookStepDown = -4.0 * downAngle / 60.0;

                                int look = pXSprite->data2 << 5;
                                if (look > 0) pPlayer->q16look = fix16_min(mulscale8(F16(lookStepUp), look), F16(upAngle));
                                else if (look < 0) pPlayer->q16look = -fix16_max(mulscale8(F16(lookStepDown), abs(look)), F16(downAngle));
                                else pPlayer->q16look = 0;
                            }

                            // angle
                            // TO-DO: if tx > 0, take a look on TX ID sprite
                            if (pXSprite->data3 == 1) pPlayer->pSprite->ang = pSprite->ang;
                            else if (valueIsBetween(pXSprite->data3, 1, kAng180)) 
                                pPlayer->pSprite->ang = pXSprite->data3;
                        }
                        //viewSetSystemMessage("ANGLE: %d, SLOPE: %d", pPlayer->pSprite->ang, pPlayer->q16look);
                        break;

                    case kCmdNumberic + 6: // 70
                         // erase player stuff...
                        switch (pXSprite->data2) {
                            // erase all
                            case 0:
                                fallthrough__;

                            // erase weapons
                            case 1:
                                // erase all
                                WeaponLower(pPlayer);

                                for (int i = 0; i < 14; i++) {
                                    pPlayer->hasWeapon[i] = false;
                                    // also erase ammo
                                    if (i < 12) pPlayer->ammoCount[i] = 0;
                                }

                                pPlayer->hasWeapon[1] = true;
                                pPlayer->curWeapon = 0;
                                pPlayer->nextWeapon = 1;

                                WeaponRaise(pPlayer);
                                if (pXSprite->data2) break;
                                fallthrough__;
                            
                            // erase all armor
                            case 2:
                                for (int i = 0; i < 3; i++) pPlayer->armor[i] = 0;
                                if (pXSprite->data2) break;
                                fallthrough__;
                            
                            // erase all pack items
                            case 3:
                                for (int i = 0; i < 5; i++) {
                                    pPlayer->packSlots[i].isActive = false;
                                    pPlayer->packSlots[i].curAmount = 0;
                                    pPlayer->packItemId = -1;
                                }
                                if (pXSprite->data2) break;
                                fallthrough__;

                            // erase all keys
                            case 4:
                                for (int i = 0; i < 8; i++) pPlayer->hasKey[i] = false;
                                if (pXSprite->data2) break;
                        }
                        break;

                    case kCmdNumberic + 7: // 71
                        // give something to player...
                        switch (pXSprite->data2) {
                            case 1: // give N weapon and default ammo for it
                            case 2: // give just N ammo for selected weapon
                                if (pXSprite->data3 <= 0 || pXSprite->data3 > 12) break;
                                for (int i = 0; i < 12; i++) {
                                    if (gWeaponItemData[i].type != pXSprite->data3) continue;
                                        
                                    WEAPONITEMDATA* pWeaponData = &gWeaponItemData[i]; int nAmmoType = pWeaponData->ammoType;
                                    if (pXSprite->data2 == 1) {
                                        pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pWeaponData->count, gAmmoInfo[nAmmoType].max);
                                    } else {
                                        pPlayer->ammoCount[nAmmoType] = ClipHigh(pPlayer->ammoCount[nAmmoType] + pXSprite->data4, gAmmoInfo[nAmmoType].max);
                                        break;
                                    }
                                        
                                    pPlayer->hasWeapon[pXSprite->data3] = true;

                                    if (pXSprite->data4 == 0) { // switch on it
                                        pPlayer->nextWeapon = 0;

                                        if (pPlayer->sceneQav >= 0 && spriRangeIsFine(pCtrl->qavScene.index)) {
                                            XSPRITE* pXScene = &xsprite[sprite[pCtrl->qavScene.index].extra];
                                            pXScene->dropMsg = pXSprite->data3;
                                        } else if (pPlayer->curWeapon != pXSprite->data3) {
                                            pPlayer->input.newWeapon = pXSprite->data3;
                                            WeaponRaise(pPlayer);
                                        }
                                    }
                                    break;
                                }
                                break;
                        }
                        break;
                    case kCmdNumberic + 8: // 72
                        // use inventory item
                        if (pXSprite->data2 > 0 && pXSprite->data2 <= 5) {
                            unsigned int invItem = pXSprite->data2 - 1;
                            packUseItem(pPlayer, invItem);
                            
                            // force remove after use
                            if (pXSprite->data4 == 1) {
                                pPlayer->packSlots[invItem].isActive = false;
                                pPlayer->packSlots[invItem].curAmount = 0;
                            }

                        }
                        break;
                }
                if (oldCmd > -1) pXSprite->command = oldCmd;
                return;
        }
    } 
    #endif

    if (pSprite->statnum == kStatDude && pSprite->type >= kDudeBase && pSprite->type < kDudeMax) {
        
        switch (event.cmd) {
            case kCmdOff:
                SetSpriteState(nSprite, pXSprite, 0);
                break;
            case kCmdSpriteProximity:
                if (pXSprite->state) break;
                fallthrough__;
            case kCmdOn:
            case kCmdSpritePush:
            case kCmdSpriteTouch:
                if (!pXSprite->state) SetSpriteState(nSprite, pXSprite, 1);
                aiActivateDude(pSprite, pXSprite);
                break;
        }

        return;
    }


    switch (pSprite->type) {
    case kTrapMachinegun:
        if (pXSprite->health <= 0) break; 
        switch (event.cmd) {
            case kCmdOff:
                if (!SetSpriteState(nSprite, pXSprite, 0)) break;
                seqSpawn(40, 3, pSprite->extra, -1);
                break;
            case kCmdOn:
                if (!SetSpriteState(nSprite, pXSprite, 1)) break;
                seqSpawn(38, 3, pSprite->extra, nMGunOpenClient);
                if (pXSprite->data1 > 0)
                    pXSprite->data2 = pXSprite->data1;
                break;
        }
        break;
    case kThingFallingRock:
        if (SetSpriteState(nSprite, pXSprite, 1))
            pSprite->flags |= 7;
        break;
    case kThingWallCrack:
        if (SetSpriteState(nSprite, pXSprite, 0))
            actPostSprite(nSprite, kStatFree);
        break;
    case kThingCrateFace:
        if (SetSpriteState(nSprite, pXSprite, 0))
            actPostSprite(nSprite, kStatFree);
        break;
    case kTrapZapSwitchable:
        switch (event.cmd) {
            case kCmdOff:
                pXSprite->state = 0;
                pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
                pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
                break;
            case kCmdOn:
                pXSprite->state = 1;
                pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE;
                pSprite->cstat |= CSTAT_SPRITE_BLOCK;
                break;
            case kCmdToggle:
                pXSprite->state ^= 1;
                pSprite->cstat ^= CSTAT_SPRITE_INVISIBLE;
                pSprite->cstat ^= CSTAT_SPRITE_BLOCK;
                break;
        }
        break;
    case kTrapFlame:
        switch (event.cmd) {
            case kCmdOff:
                if (!SetSpriteState(nSprite, pXSprite, 0)) break;
                seqSpawn(40, 3, pSprite->extra, -1);
                sfxKill3DSound(pSprite, 0, -1);
                break;
            case kCmdOn:
                if (!SetSpriteState(nSprite, pXSprite, 1)) break;
                seqSpawn(38, 3, pSprite->extra, -1);
                sfxPlay3DSound(pSprite, 441, 0, 0);
                break;
        }
        break;
    case kSwitchPadlock:
        switch (event.cmd) {
            case kCmdOff:
                SetSpriteState(nSprite, pXSprite, 0);
                break;
            case kCmdOn:
                if (!SetSpriteState(nSprite, pXSprite, 1)) break;
                seqSpawn(37, 3, pSprite->extra, -1);
                break;
            default:
                SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                if (pXSprite->state) seqSpawn(37, 3, pSprite->extra, -1);
                break;
        }
        break;
    case kSwitchToggle:
        switch (event.cmd) {
            case kCmdOff:
                if (!SetSpriteState(nSprite, pXSprite, 0)) break;
                sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
                break;
            case kCmdOn:
                if (!SetSpriteState(nSprite, pXSprite, 1)) break;
                sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
                break;
            default:
                if (!SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1)) break;
                if (pXSprite->state) sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
                else sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
                break;
        }
        break;
    case kSwitchOneWay:
        switch (event.cmd) {
            case kCmdOff:
                if (!SetSpriteState(nSprite, pXSprite, 0)) break;
                sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
                break;
            case kCmdOn:
                if (!SetSpriteState(nSprite, pXSprite, 1)) break;
                sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
                break;
            default:
                if (!SetSpriteState(nSprite, pXSprite, pXSprite->restState ^ 1)) break;
                if (pXSprite->state) sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
                else sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
                break;
        }
        break;
    case kSwitchCombo:
        switch (event.cmd) {
            case kCmdOff:
                pXSprite->data1--;
                if (pXSprite->data1 < 0)
                    pXSprite->data1 += pXSprite->data3;
                break;
            default:
                pXSprite->data1++;
                if (pXSprite->data1 >= pXSprite->data3)
                    pXSprite->data1 -= pXSprite->data3;
                break;
        }
        
        sfxPlay3DSound(pSprite, pXSprite->data4, -1, 0);
        
        if (pXSprite->command == kCmdLink && pXSprite->txID > 0)
            evSend(nSprite, 3, pXSprite->txID, kCmdLink);

        if (pXSprite->data1 == pXSprite->data2) 
            SetSpriteState(nSprite, pXSprite, 1);
        else 
            SetSpriteState(nSprite, pXSprite, 0);

        break;
    case kMarkerDudeSpawn:
        if (gGameOptions.nMonsterSettings && pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeMax) {
            spritetype* pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0);
            if (pSpawn) {
                XSPRITE *pXSpawn = &xsprite[pSpawn->extra];
                gKillMgr.sub_263E0(1);
                switch (pXSprite->data1) {
                    case kDudeBurningInnocent:
                    case kDudeBurningCultist:
                    case kDudeBurningZombieButcher:
                    case kDudeBurningTinyCaleb:
                    case kDudeBurningBeast: {
                        pXSpawn->health = dudeInfo[pXSprite->data1 - kDudeBase].startHealth << 4;
                        pXSpawn->burnTime = 10;
                        pXSpawn->target = -1;
                        aiActivateDude(pSpawn, pXSpawn);
                        break;
                    default:
                        break;
                    }
                }
            }
        }
        break;
    case kMarkerEarthQuake:
        pXSprite->triggerOn = 0;
        pXSprite->isTriggered = 1;
        SetSpriteState(nSprite, pXSprite, 1);
        for (int p = connecthead; p >= 0; p = connectpoint2[p]) {
            spritetype *pPlayerSprite = gPlayer[p].pSprite;
            int dx = (pSprite->x - pPlayerSprite->x)>>4;
            int dy = (pSprite->y - pPlayerSprite->y)>>4;
            int dz = (pSprite->z - pPlayerSprite->z)>>8;
            int nDist = dx*dx+dy*dy+dz*dz+0x40000;
            gPlayer[p].quakeEffect = divscale16(pXSprite->data1, nDist);
        }
        break;
    case kThingTNTBarrel:
        if (pSprite->flags & kHitagRespawn) return;
        fallthrough__;
    case kThingArmedTNTStick:
    case kThingArmedTNTBundle:
    case kThingArmedSpray:
        actExplodeSprite(pSprite);
        break;
    case kTrapExploder:
        switch (event.cmd) {
            case kCmdOn:
                SetSpriteState(nSprite, pXSprite, 1);
                break;
            default:
                pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE;
                actExplodeSprite(pSprite);
                break;
        }
        break;
    case kThingArmedRemoteBomb:
        if (pSprite->statnum != kStatRespawn) {
            if (event.cmd != kCmdOn) actExplodeSprite(pSprite);
            else {
                sfxPlay3DSound(pSprite, 454, 0, 0);
                evPost(nSprite, 3, 18, kCmdOff);
            }
        }
        break;

        /*if (pSprite->statnum != kStatRespawn) {
            switch (event.cmd) {
                case kCmdOn:
                    actExplodeSprite(pSprite);
                    break;
                default:
                    sfxPlay3DSound(pSprite, 454, 0, 0);
                    evPost(nSprite, 3, 18, kCmdOff);
                    break;
            }
        }*/
        break;
    
    case kThingArmedProxBomb:
#ifdef NOONE_EXTENSIONS
    case kModernThingTNTProx:
#endif
        if (pSprite->statnum != kStatRespawn) {
            switch (event.cmd) {
                case kCmdSpriteProximity:
                    if (pXSprite->state) break;
                    sfxPlay3DSound(pSprite, 452, 0, 0);
                    evPost(nSprite, 3, 30, kCmdOff);
                    pXSprite->state = 1;
                    fallthrough__;
                case kCmdOn:
                    sfxPlay3DSound(pSprite, 451, 0, 0);
                    pXSprite->Proximity = 1;
                    break;
                default:
                    actExplodeSprite(pSprite);
                    break;
            }
        }
        break;
    case kThingDroppedLifeLeech:
        LifeLeechOperate(pSprite, pXSprite, event);
        break;
    case kGenTrigger:
    case kGenDripWater:
    case kGenDripBlood:
    case kGenMissileFireball:
    case kGenMissileEctoSkull:
    case kGenDart:
    case kGenBubble:
    case kGenBubbleMulti:
    case kGenSound:
        switch (event.cmd) {
            case kCmdOff:
                SetSpriteState(nSprite, pXSprite, 0);
                break;
            case kCmdRepeat:
                if (pSprite->type != kGenTrigger) ActivateGenerator(nSprite);
                if (pXSprite->txID) evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
                if (pXSprite->busyTime > 0) {
                    int nRand = Random2(pXSprite->data1);
                    evPost(nSprite, 3, 120*(nRand+pXSprite->busyTime) / 10, kCmdRepeat);
                }
                break;
            default:
                if (!pXSprite->state) {
                    SetSpriteState(nSprite, pXSprite, 1);
                    evPost(nSprite, 3, 0, kCmdRepeat);
                }
                break;
        }
        break;
    case kSoundPlayer:
        if (gGameOptions.nGameType != 0 || gMe->pXSprite->health <= 0) break;
        gMe->restTime = 0; sndStartSample(pXSprite->data1, -1, 1, 0);
        break;
    case kThingObjectGib:
    case kThingObjectExplode:
    case kThingBloodBits:
    case kThingBloodChunks:
    case kThingZombieHead:
        switch (event.cmd) {
            case kCmdOff:
                if (!SetSpriteState(nSprite, pXSprite, 0)) break;
                actActivateGibObject(pSprite, pXSprite);
                break;
            case kCmdOn:
                if (!SetSpriteState(nSprite, pXSprite, 1)) break;
                actActivateGibObject(pSprite, pXSprite);
                break;
            default:
                if (!SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1)) break;
                actActivateGibObject(pSprite, pXSprite);
                break;
        }
        break;
    default:
        switch (event.cmd) {
            case kCmdOff:
                SetSpriteState(nSprite, pXSprite, 0);
                break;
            case kCmdOn:
                SetSpriteState(nSprite, pXSprite, 1);
                break;
            default:
                SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
                break;
        }
        break;
    }
}

void SetupGibWallState(walltype *pWall, XWALL *pXWall)
{
    walltype *pWall2 = NULL;
    if (pWall->nextwall >= 0)
        pWall2 = &wall[pWall->nextwall];
    if (pXWall->state)
    {
        pWall->cstat &= ~65;
        if (pWall2)
        {
            pWall2->cstat &= ~65;
            pWall->cstat &= ~16;
            pWall2->cstat &= ~16;
        }
        return;
    }
    char bVector = pXWall->triggerVector != 0;
    pWall->cstat |= 1;
    if (bVector)
        pWall->cstat |= 64;
    if (pWall2)
    {
        pWall2->cstat |= 1;
        if (bVector)
            pWall2->cstat |= 64;
        pWall->cstat |= 16;
        pWall2->cstat |= 16;
    }
}

void OperateWall(int nWall, XWALL *pXWall, EVENT event) {
    walltype *pWall = &wall[nWall];
    
    switch (event.cmd) {
        case kCmdLock:
            pXWall->locked = 1;
            return;
        case kCmdUnlock:
            pXWall->locked = 0;
            return;
        case kCmdToggleLock:
            pXWall->locked ^= 1;
            return;
    }
    
    // make 1-Way switch type for walls to work...
    #ifdef NOONE_EXTENSIONS
        if (gModernMap) {
        
            switch (pWall->type) {
                case kSwitchOneWay:
                    switch (event.cmd) {
                        case kCmdOff:
                            SetWallState(nWall, pXWall, 0);
                            break;
                        case kCmdOn:
                            SetWallState(nWall, pXWall, 1);
                            break;
                        default:
                            SetWallState(nWall, pXWall, pXWall->restState ^ 1);
                            break;
                        }
                    return;
                default:
                    break;
            }

        }
    #endif
    switch (pWall->type) {
        case kWallGib:
            if (GetWallType(nWall) != pWall->type) break;
            char bStatus;
            switch (event.cmd) {
                case kCmdOn:
                case kCmdWallImpact:
                    bStatus = SetWallState(nWall, pXWall, 1);
                    break;
                case kCmdOff:
                    bStatus = SetWallState(nWall, pXWall, 0);
                    break;
                default:
                    bStatus = SetWallState(nWall, pXWall, pXWall->state ^ 1);
                    break;
            }

            if (bStatus) {
                SetupGibWallState(pWall, pXWall);
                if (pXWall->state) {
                    CGibVelocity vel(100, 100, 250);
                    int nType = ClipRange(pXWall->data, 0, 31);
                    if (nType > 0)
                        GibWall(nWall, (GIBTYPE)nType, &vel);
                }
            }
            return;
        default:
            switch (event.cmd) {
                case kCmdOff:
                    SetWallState(nWall, pXWall, 0);
                    break;
                case kCmdOn:
                    SetWallState(nWall, pXWall, 1);
                    break;
                default:
                    SetWallState(nWall, pXWall, pXWall->state ^ 1);
                    break;
            }
            return;
    }


}

void SectorStartSound(int nSector, int nState)
{
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->statnum == kStatDecoration && pSprite->type == kSoundSector)
        {
            int nXSprite = pSprite->extra;
            dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
            XSPRITE *pXSprite = &xsprite[nXSprite];
            if (nState)
            {
                if (pXSprite->data3)
                    sfxPlay3DSound(pSprite, pXSprite->data3, 0, 0);
            }
            else
            {
                if (pXSprite->data1)
                    sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
            }
        }
    }
}

void SectorEndSound(int nSector, int nState)
{
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->statnum == kStatDecoration && pSprite->type == kSoundSector)
        {
            int nXSprite = pSprite->extra;
            dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
            XSPRITE *pXSprite = &xsprite[nXSprite];
            if (nState)
            {
                if (pXSprite->data2)
                    sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
            }
            else
            {
                if (pXSprite->data4)
                    sfxPlay3DSound(pSprite, pXSprite->data4, 0, 0);
            }
        }
    }
}

void PathSound(int nSector, int nSound)
{
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->statnum == kStatDecoration && pSprite->type == kSoundSector)
            sfxPlay3DSound(pSprite, nSound, 0, 0);
    }
}

void DragPoint(int nWall, int x, int y)
{
    viewInterpolateWall(nWall, &wall[nWall]);
    wall[nWall].x = x;
    wall[nWall].y = y;

    int vsi = numwalls;
    int vb = nWall;
    do
    {
        if (wall[vb].nextwall >= 0)
        {
            vb = wall[wall[vb].nextwall].point2;
            viewInterpolateWall(vb, &wall[vb]);
            wall[vb].x = x;
            wall[vb].y = y;
        }
        else
        {
            vb = nWall;
            do
            {
                if (wall[lastwall(vb)].nextwall >= 0)
                {
                    vb = wall[lastwall(vb)].nextwall;
                    viewInterpolateWall(vb, &wall[vb]);
                    wall[vb].x = x;
                    wall[vb].y = y;
                }
                else
                    break;
                vsi--;
            } while (vb != nWall && vsi > 0);
            break;
        }
        vsi--;
    } while (vb != nWall && vsi > 0);
}

void TranslateSector(int nSector, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, char a12)
{
    int x, y;
    int nXSector = sector[nSector].extra;
    XSECTOR *pXSector = &xsector[nXSector];
    int v20 = interpolate(a6, a9, a2);
    int vc = interpolate(a6, a9, a3);
    int v28 = vc - v20;
    int v24 = interpolate(a7, a10, a2);
    int v8 = interpolate(a7, a10, a3);
    int v2c = v8 - v24;
    int v44 = interpolate(a8, a11, a2);
    int vbp = interpolate(a8, a11, a3);
    int v14 = vbp - v44;
    int nWall = sector[nSector].wallptr;
    if (a12)
    {
        for (int i = 0; i < sector[nSector].wallnum; nWall++, i++)
        {
            x = baseWall[nWall].x;
            y = baseWall[nWall].y;
            if (vbp)
                RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
            DragPoint(nWall, x+vc-a4, y+v8-a5);
        }
    }
    else
    {
        for (int i = 0; i < sector[nSector].wallnum; nWall++, i++)
        {
            int v10 = wall[nWall].point2;
            x = baseWall[nWall].x;
            y = baseWall[nWall].y;
            if (wall[nWall].cstat&16384)
            {
                if (vbp)
                    RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
                DragPoint(nWall, x+vc-a4, y+v8-a5);
                if ((wall[v10].cstat&49152) == 0)
                {
                    x = baseWall[v10].x;
                    y = baseWall[v10].y;
                    if (vbp)
                        RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
                    DragPoint(v10, x+vc-a4, y+v8-a5);
                }
                continue;
            }
            if (wall[nWall].cstat&32768)
            {
                if (vbp)
                    RotatePoint((int*)&x, (int*)&y, -vbp, a4, a5);
                DragPoint(nWall, x-(vc-a4), y-(v8-a5));
                if ((wall[v10].cstat&49152) == 0)
                {
                    x = baseWall[v10].x;
                    y = baseWall[v10].y;
                    if (vbp)
                        RotatePoint((int*)&x, (int*)&y, -vbp, a4, a5);
                    DragPoint(v10, x-(vc-a4), y-(v8-a5));
                }
                continue;
            }
        }
    }
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        // allow to move markers by sector movements in game if flags 1 is added in editor.
        switch (pSprite->statnum) {
            case kStatMarker:
            case kStatPathMarker:
                #ifdef NOONE_EXTENSIONS
                    if (!gModernMap || !(pSprite->flags & 0x1)) continue;
                #else
                    continue;
                #endif
                break;
        }

        x = baseSprite[nSprite].x;
        y = baseSprite[nSprite].y;
        if (sprite[nSprite].cstat&8192)
        {
            if (vbp)
                RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
            viewBackupSpriteLoc(nSprite, pSprite);
            pSprite->ang = (pSprite->ang+v14)&2047;
            pSprite->x = x+vc-a4;
            pSprite->y = y+v8-a5;
        }
        else if (sprite[nSprite].cstat&16384)
        {
            if (vbp)
                RotatePoint((int*)& x, (int*)& y, -vbp, a4, a4);
            viewBackupSpriteLoc(nSprite, pSprite);
            pSprite->ang = (pSprite->ang-v14)&2047;
            pSprite->x = x-(vc-a4);
            pSprite->y = y-(v8-a5);
        }
        else if (pXSector->Drag)
        {
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            int floorZ = getflorzofslope(nSector, pSprite->x, pSprite->y);
            if (!(pSprite->cstat&48) && floorZ <= bottom)
            {
                if (v14)
                    RotatePoint((int*)&pSprite->x, (int*)&pSprite->y, v14, v20, v24);
                viewBackupSpriteLoc(nSprite, pSprite);
                pSprite->ang = (pSprite->ang+v14)&2047;
                pSprite->x += v28;
                pSprite->y += v2c;
            }
        }
    }
}

void ZTranslateSector(int nSector, XSECTOR *pXSector, int a3, int a4)
{
    sectortype *pSector = &sector[nSector];
    viewInterpolateSector(nSector, pSector);
    int dz = pXSector->onFloorZ-pXSector->offFloorZ;
    if (dz != 0)
    {
        int oldZ = pSector->floorz;
        baseFloor[nSector] = pSector->floorz = pXSector->offFloorZ + mulscale16(dz, GetWaveValue(a3, a4));
        velFloor[nSector] += (pSector->floorz-oldZ)<<8;
        for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
        {
            spritetype *pSprite = &sprite[nSprite];
            if (pSprite->statnum == kStatMarker || pSprite->statnum == kStatPathMarker)
                continue;
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            if (pSprite->cstat&8192)
            {
                viewBackupSpriteLoc(nSprite, pSprite);
                pSprite->z += pSector->floorz-oldZ;
            }
            else if (pSprite->flags&2)
                pSprite->flags |= 4;
            else if (oldZ <= bottom && !(pSprite->cstat&48))
            {
                viewBackupSpriteLoc(nSprite, pSprite);
                pSprite->z += pSector->floorz-oldZ;
            }
        }
    }
    dz = pXSector->onCeilZ-pXSector->offCeilZ;
    if (dz != 0)
    {
        int oldZ = pSector->ceilingz;
        baseCeil[nSector] = pSector->ceilingz = pXSector->offCeilZ + mulscale16(dz, GetWaveValue(a3, a4));
        velCeil[nSector] += (pSector->ceilingz-oldZ)<<8;
        for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
        {
            spritetype *pSprite = &sprite[nSprite];
            if (pSprite->statnum == kStatMarker || pSprite->statnum == kStatPathMarker)
                continue;
            if (pSprite->cstat&16384)
            {
                viewBackupSpriteLoc(nSprite, pSprite);
                pSprite->z += pSector->ceilingz-oldZ;
            }
        }
    }
}

int GetHighestSprite(int nSector, int nStatus, int *a3)
{
    *a3 = sector[nSector].floorz;
    int v8 = -1;
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        if (sprite[nSprite].statnum == nStatus || nStatus == kStatFree)
        {
            spritetype *pSprite = &sprite[nSprite];
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            if (top-pSprite->z > *a3)
            {
                *a3 = top-pSprite->z;
                v8 = nSprite;
            }
        }
    }
    return v8;
}

int GetCrushedSpriteExtents(unsigned int nSector, int *pzTop, int *pzBot)
{
    dassert(pzTop != NULL && pzBot != NULL);
    dassert(nSector < (unsigned int)numsectors);
    int vc = -1;
    sectortype *pSector = &sector[nSector];
    int vbp = pSector->ceilingz;
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->statnum == kStatDude || pSprite->statnum == kStatThing)
        {
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            if (vbp > top)
            {
                vbp = top;
                *pzTop = top;
                *pzBot = bottom;
                vc = nSprite;
            }
        }
    }
    return vc;
}

int VCrushBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    int nXSector = sector[nSector].extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    int nWave;
    if (pXSector->busy < a2)
        nWave = pXSector->busyWaveA;
    else
        nWave = pXSector->busyWaveB;
    int dz1 = pXSector->onCeilZ - pXSector->offCeilZ;
    int vc = pXSector->offCeilZ;
    if (dz1 != 0)
        vc += mulscale16(dz1, GetWaveValue(a2, nWave));
    int dz2 = pXSector->onFloorZ - pXSector->offFloorZ;
    int v10 = pXSector->offFloorZ;
    if (dz2 != 0)
        v10 += mulscale16(dz2, GetWaveValue(a2, nWave));
    int v18;
    if (GetHighestSprite(nSector, 6, &v18) >= 0 && vc >= v18)
        return 1;
    viewInterpolateSector(nSector, &sector[nSector]);
    if (dz1 != 0)
        sector[nSector].ceilingz = vc;
    if (dz2 != 0)
        sector[nSector].floorz = v10;
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        return 3;
    }
    return 0;
}

int VSpriteBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    int nXSector = sector[nSector].extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    int nWave;
    if (pXSector->busy < a2)
        nWave = pXSector->busyWaveA;
    else
        nWave = pXSector->busyWaveB;
    int dz1 = pXSector->onFloorZ - pXSector->offFloorZ;
    if (dz1 != 0)
    {
        for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
        {
            spritetype *pSprite = &sprite[nSprite];
            if (pSprite->cstat&8192)
            {
                viewBackupSpriteLoc(nSprite, pSprite);
                pSprite->z = baseSprite[nSprite].z+mulscale16(dz1, GetWaveValue(a2, nWave));
            }
        }
    }
    int dz2 = pXSector->onCeilZ - pXSector->offCeilZ;
    if (dz2 != 0)
    {
        for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
        {
            spritetype *pSprite = &sprite[nSprite];
            if (pSprite->cstat&16384)
            {
                viewBackupSpriteLoc(nSprite, pSprite);
                pSprite->z = baseSprite[nSprite].z+mulscale16(dz2, GetWaveValue(a2, nWave));
            }
        }
    }
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        return 3;
    }
    return 0;
}

int VDoorBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    int nXSector = sector[nSector].extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    int vbp;
    if (pXSector->state)
        vbp = 65536/ClipLow((120*pXSector->busyTimeA)/10, 1);
    else
        vbp = -65536/ClipLow((120*pXSector->busyTimeB)/10, 1);
    int top, bottom;
    int nSprite = GetCrushedSpriteExtents(nSector,&top,&bottom);
    if (nSprite >= 0 && a2 > pXSector->busy)
    {
        spritetype *pSprite = &sprite[nSprite];
        dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
        XSPRITE *pXSprite = &xsprite[pSprite->extra];
        if (pXSector->onCeilZ > pXSector->offCeilZ || pXSector->onFloorZ < pXSector->offFloorZ)
        {
            if (pXSector->interruptable)
            {
                if (pXSector->Crush)
                {
                    if (pXSprite->health <= 0)
                        return 2;
                    int nDamage;
                    if (pXSector->data == 0)
                        nDamage = 500;
                    else
                        nDamage = pXSector->data;
                    actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
                }
                a2 = ClipRange(a2-(vbp/2)*4, 0, 65536);
            }
            else if (pXSector->Crush && pXSprite->health > 0)
            {
                int nDamage;
                if (pXSector->data == 0)
                    nDamage = 500;
                else
                    nDamage = pXSector->data;
                actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
                a2 = ClipRange(a2-(vbp/2)*4, 0, 65536);
            }
        }
    }
    else if (nSprite >= 0 && a2 < pXSector->busy)
    {
        spritetype *pSprite = &sprite[nSprite];
        dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
        XSPRITE *pXSprite = &xsprite[pSprite->extra];
        if (pXSector->offCeilZ > pXSector->onCeilZ || pXSector->offFloorZ < pXSector->onFloorZ)
        {
            if (pXSector->interruptable)
            {
                if (pXSector->Crush)
                {
                    if (pXSprite->health <= 0)
                        return 2;
                    int nDamage;
                    if (pXSector->data == 0)
                        nDamage = 500;
                    else
                        nDamage = pXSector->data;
                    actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
                }
                a2 = ClipRange(a2+(vbp/2)*4, 0, 65536);
            }
            else if (pXSector->Crush && pXSprite->health > 0)
            {
                int nDamage;
                if (pXSector->data == 0)
                    nDamage = 500;
                else
                    nDamage = pXSector->data;
                actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
                a2 = ClipRange(a2+(vbp/2)*4, 0, 65536);
            }
        }
    }
    int nWave;
    if (pXSector->busy < a2)
        nWave = pXSector->busyWaveA;
    else
        nWave = pXSector->busyWaveB;
    ZTranslateSector(nSector, pXSector, a2, nWave);
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        return 3;
    }
    return 0;
}

int HDoorBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    sectortype *pSector = &sector[nSector];
    int nXSector = pSector->extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    int nWave;
    if (pXSector->busy < a2)
        nWave = pXSector->busyWaveA;
    else
        nWave = pXSector->busyWaveB;
    spritetype *pSprite1 = &sprite[pXSector->marker0];
    spritetype *pSprite2 = &sprite[pXSector->marker1];
    TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->type == kSectorSlide);
    ZTranslateSector(nSector, pXSector, a2, nWave);
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        return 3;
    }
    return 0;
}

int RDoorBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    sectortype *pSector = &sector[nSector];
    int nXSector = pSector->extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    int nWave;
    if (pXSector->busy < a2)
        nWave = pXSector->busyWaveA;
    else
        nWave = pXSector->busyWaveB;
    spritetype *pSprite = &sprite[pXSector->marker0];
    TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, 0, pSprite->x, pSprite->y, pSprite->ang, pSector->type == kSectorRotate);
    ZTranslateSector(nSector, pXSector, a2, nWave);
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        return 3;
    }
    return 0;
}

int StepRotateBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    sectortype *pSector = &sector[nSector];
    int nXSector = pSector->extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    spritetype *pSprite = &sprite[pXSector->marker0];
    int vbp;
    if (pXSector->busy < a2)
    {
        vbp = pXSector->data+pSprite->ang;
        int nWave = pXSector->busyWaveA;
        TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, pXSector->data, pSprite->x, pSprite->y, vbp, 1);
    }
    else
    {
        vbp = pXSector->data-pSprite->ang;
        int nWave = pXSector->busyWaveB;
        TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, vbp, pSprite->x, pSprite->y, pXSector->data, 1);
    }
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        pXSector->data = vbp&2047;
        return 3;
    }
    return 0;
}

int GenSectorBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    sectortype *pSector = &sector[nSector];
    int nXSector = pSector->extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    pXSector->busy = a2;
    if (pXSector->command == kCmdLink && pXSector->txID)
        evSend(nSector, 6, pXSector->txID, kCmdLink);
    if ((a2&0xffff) == 0)
    {
        SetSectorState(nSector, pXSector, a2>>16);
        SectorEndSound(nSector, a2>>16);
        return 3;
    }
    return 0;
}

int PathBusy(unsigned int nSector, unsigned int a2)
{
    dassert(nSector < (unsigned int)numsectors);
    sectortype *pSector = &sector[nSector];
    int nXSector = pSector->extra;
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    XSECTOR *pXSector = &xsector[nXSector];
    spritetype *pSprite = &sprite[basePath[nSector]];
    spritetype *pSprite1 = &sprite[pXSector->marker0];
    XSPRITE *pXSprite1 = &xsprite[pSprite1->extra];
    spritetype *pSprite2 = &sprite[pXSector->marker1];
    XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
    int nWave = pXSprite1->wave;
    TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, 1);
    ZTranslateSector(nSector, pXSector, a2, nWave);
    pXSector->busy = a2;
    if ((a2&0xffff) == 0)
    {
        evPost(nSector, 6, (120*pXSprite2->waitTime)/10, kCmdOn);
        pXSector->state = 0;
        pXSector->busy = 0;
        if (pXSprite1->data4)
            PathSound(nSector, pXSprite1->data4);
        pXSector->marker0 = pXSector->marker1;
        pXSector->data = pXSprite2->data1;
        return 3;
    }
    return 0;
}

void OperateDoor(unsigned int nSector, XSECTOR *pXSector, EVENT event, BUSYID busyWave) 
{
    switch (event.cmd) {
        case kCmdOff:
            if (!pXSector->busy) break;
            AddBusy(nSector, busyWave, -65536/ClipLow((pXSector->busyTimeB*120)/10, 1));
            SectorStartSound(nSector, 1);
            break;
        case kCmdOn:
            if (pXSector->busy == 0x10000) break;
            AddBusy(nSector, busyWave, 65536/ClipLow((pXSector->busyTimeA*120)/10, 1));
            SectorStartSound(nSector, 0);
            break;
        default:
            if (pXSector->busy & 0xffff)  {
                if (pXSector->interruptable) {
                    ReverseBusy(nSector, busyWave);
                    pXSector->state = !pXSector->state;
                }
            } else {
                char t = !pXSector->state; int nDelta;
            
                if (t) nDelta = 65536/ClipLow((pXSector->busyTimeA*120)/10, 1);
                else nDelta = -65536/ClipLow((pXSector->busyTimeB*120)/10, 1);
            
                AddBusy(nSector, busyWave, nDelta);
                SectorStartSound(nSector, pXSector->state);
            }
            break;
    }
}

char SectorContainsDudes(int nSector)
{
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        if (sprite[nSprite].statnum == kStatDude)
            return 1;
    }
    return 0;
}

void TeleFrag(int nKiller, int nSector)
{
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->statnum == kStatDude)
            actDamageSprite(nKiller, pSprite, DAMAGE_TYPE_3, 4000);
        else if (pSprite->statnum == kStatThing)
            actDamageSprite(nKiller, pSprite, DAMAGE_TYPE_3, 4000);
    }
}

void OperateTeleport(unsigned int nSector, XSECTOR *pXSector)
{
    dassert(nSector < (unsigned int)numsectors);
    int nDest = pXSector->marker0;
    dassert(nDest < kMaxSprites);
    spritetype *pDest = &sprite[nDest];
    dassert(pDest->statnum == kStatMarker);
    dassert(pDest->type == kMarkerWarpDest);
    dassert(pDest->sectnum >= 0 && pDest->sectnum < kMaxSectors);
    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
    {
        spritetype *pSprite = &sprite[nSprite];
        if (pSprite->statnum == kStatDude)
        {
            PLAYER *pPlayer;
            char bPlayer = IsPlayerSprite(pSprite);
            if (bPlayer)
                pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
            else
                pPlayer = NULL;
            if (bPlayer || !SectorContainsDudes(pDest->sectnum))
            {
                if (!(gGameOptions.uNetGameFlags&2))
                    TeleFrag(pXSector->data, pDest->sectnum);
                pSprite->x = pDest->x;
                pSprite->y = pDest->y;
                pSprite->z += sector[pDest->sectnum].floorz-sector[nSector].floorz;
                pSprite->ang = pDest->ang;
                ChangeSpriteSect(nSprite, pDest->sectnum);
                sfxPlay3DSound(pDest, 201, -1, 0);
                xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
                ClearBitString(gInterpolateSprite, nSprite);
                viewBackupSpriteLoc(nSprite, pSprite);
                if (pPlayer)
                {
                    playerResetInertia(pPlayer);
                    pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
                }
            }
        }
    }
}

void OperatePath(unsigned int nSector, XSECTOR *pXSector, EVENT event)
{
    int nSprite;
    spritetype *pSprite = NULL;
    XSPRITE *pXSprite;
    dassert(nSector < (unsigned int)numsectors);
    spritetype *pSprite2 = &sprite[pXSector->marker0];
    XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
    int nId = pXSprite2->data2;
    for (nSprite = headspritestat[kStatPathMarker]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        pSprite = &sprite[nSprite];
        if (pSprite->type == kMarkerPath)
        {
            pXSprite = &xsprite[pSprite->extra];
            if (pXSprite->data1 == nId)
                break;
        }
    }

    // trigger marker after it gets reached
    #ifdef NOONE_EXTENSIONS
        if (gModernMap && pXSprite2->state != 1)
            trTriggerSprite(pSprite2->xvel, pXSprite2, kCmdOn);
    #endif

    if (nSprite < 0) {
        viewSetSystemMessage("Unable to find path marker with id #%d for path sector #%d", nId, nSector);
        pXSector->state = 0;
        pXSector->busy = 0;
        return;
    }
        
    pXSector->marker1 = nSprite;
    pXSector->offFloorZ = pSprite2->z;
    pXSector->onFloorZ = pSprite->z;
    switch (event.cmd) {
        case kCmdOn:
            pXSector->state = 0;
            pXSector->busy = 0;
            AddBusy(nSector, BUSYID_7, 65536/ClipLow((120*pXSprite2->busyTime)/10,1));
            if (pXSprite2->data3) PathSound(nSector, pXSprite2->data3);
            break;
    }
}

void OperateSector(unsigned int nSector, XSECTOR *pXSector, EVENT event)
{
    dassert(nSector < (unsigned int)numsectors);
    sectortype *pSector = &sector[nSector];
    
    #ifdef NOONE_EXTENSIONS
        if (gModernMap) {
            switch (pSector->type) {
                // reset counter sector state and make it work again after unlock, so it can be used again.
                case kSectorCounter:
                    switch (event.cmd) {
                        case kCmdUnlock:
                        case kCmdToggleLock:
                            if (pXSector->locked != 1) break;
                            pXSector->state = 0;
                            evPost(nSector, 6, 0, kCallbackCounterCheck);
                            break;
                    }
                    break;
            }
        }
    #endif
    switch (event.cmd) {
        case kCmdLock:
            pXSector->locked = 1;
            break;
        case kCmdUnlock:
            pXSector->locked = 0;
            break;
        case kCmdToggleLock:
            pXSector->locked ^= 1;
            break;
        case kCmdStopOff:
            pXSector->stopOn = 0;
            pXSector->stopOff = 1;
            break;
        case kCmdStopOn:
            pXSector->stopOn = 1;
            pXSector->stopOff = 0;
            break;
        case kCmdStopNext:
            pXSector->stopOn = 1;
            pXSector->stopOff = 1;
            break;
        default:
            switch (pSector->type) {
                case kSectorZMotionSprite:
                    OperateDoor(nSector, pXSector, event, BUSYID_1);
                    break;
                case kSectorZMotion:
                    OperateDoor(nSector, pXSector, event, BUSYID_2);
                    break;
                case kSectorSlideMarked:
                case kSectorSlide:
                    OperateDoor(nSector, pXSector, event, BUSYID_3);
                    break;
                case kSectorRotateMarked:
                case kSectorRotate:
                    OperateDoor(nSector, pXSector, event, BUSYID_4);
                    break;
                case kSectorRotateStep:
                    switch (event.cmd) {
                        case kCmdOn:
                            pXSector->state = 0;
                            pXSector->busy = 0;
                            AddBusy(nSector, BUSYID_5, 65536/ClipLow((120*pXSector->busyTimeA)/10, 1));
                            SectorStartSound(nSector, 0);
                            break;
                        case kCmdOff:
                            pXSector->state = 1;
                            pXSector->busy = 65536;
                            AddBusy(nSector, BUSYID_5, -65536/ClipLow((120*pXSector->busyTimeB)/10, 1));
                            SectorStartSound(nSector, 1);
                            break;
                    }
                    break;
                case kSectorTeleport:
                    OperateTeleport(nSector, pXSector);
                    break;
                case kSectorPath:
                    OperatePath(nSector, pXSector, event);
                    break;
                default:
                    if (!pXSector->busyTimeA && !pXSector->busyTimeB) {
                        
                        switch (event.cmd) {
                            case kCmdOff:
                                SetSectorState(nSector, pXSector, 0);
                                break;
                            case kCmdOn:
                                SetSectorState(nSector, pXSector, 1);
                                break;
                            default:
                                SetSectorState(nSector, pXSector, pXSector->state ^ 1);
                                break;
                        }

                    } else {
                        
                        OperateDoor(nSector, pXSector, event, BUSYID_6);

                    }

                    break;
            }
            break;
    }
}

void InitPath(unsigned int nSector, XSECTOR *pXSector)
{
    int nSprite;
    spritetype *pSprite;
    XSPRITE *pXSprite;
    dassert(nSector < (unsigned int)numsectors);
    int nId = pXSector->data;
    for (nSprite = headspritestat[kStatPathMarker]; nSprite >= 0; nSprite = nextspritestat[nSprite])
    {
        pSprite = &sprite[nSprite];
        if (pSprite->type == kMarkerPath)
        {
            pXSprite = &xsprite[pSprite->extra];
            if (pXSprite->data1 == nId)
                break;
        }
    }
    
    if (nSprite < 0) {
        //ThrowError("Unable to find path marker with id #%d", nId);
        viewSetSystemMessage("Unable to find path marker with id #%d for path sector #%d", nId, nSector);
        return;
        
    }

    pXSector->marker0 = nSprite;
    basePath[nSector] = nSprite;
    if (pXSector->state)
        evPost(nSector, 6, 0, kCmdOn);
}

void LinkSector(int nSector, XSECTOR *pXSector, EVENT event)
{
    sectortype *pSector = &sector[nSector];
    int nBusy = GetSourceBusy(event);
    switch (pSector->type) {
        case kSectorZMotionSprite:
            VSpriteBusy(nSector, nBusy);
            break;
        case kSectorZMotion:
            VDoorBusy(nSector, nBusy);
            break;
        case kSectorSlideMarked:
        case kSectorSlide:
            HDoorBusy(nSector, nBusy);
            break;
        case kSectorRotateMarked:
        case kSectorRotate:
            RDoorBusy(nSector, nBusy);
            break;
        #ifdef NOONE_EXTENSIONS
        // add link support for counter sectors so they can change necessary type and count of types
        case kSectorCounter:
            pXSector->waitTimeA = xsector[sector[event.index].extra].waitTimeA;
            pXSector->data = xsector[sector[event.index].extra].data;
            break;
        #endif
        default:
            pXSector->busy = nBusy;
            if ((pXSector->busy&0xffff) == 0)
                SetSectorState(nSector, pXSector, nBusy>>16);
            break;
    }
}

void LinkSprite(int nSprite, XSPRITE *pXSprite, EVENT event) {
    spritetype *pSprite = &sprite[nSprite];
    int nBusy = GetSourceBusy(event);
    switch (pSprite->type)  {
        #ifdef NOONE_EXTENSIONS
        // these can be linked too now, so it's possible to change palette, underwater status and more...
        case kMarkerLowWater:
        case kMarkerUpWater:
        case kMarkerUpGoo:
        case kMarkerLowGoo:
        case kMarkerUpLink:
        case kMarkerLowLink:
        case kMarkerUpStack:
        case kMarkerLowStack: {
            if (event.type != 3) break;
            spritetype *pSprite2 = &sprite[event.index];
            if (pSprite2->extra < 0) break;
            XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];

            // Only lower to lower and upper to upper linking allowed.
            switch (pSprite->type) {
            case kMarkerLowWater:
            case kMarkerLowLink:
            case kMarkerLowStack:
            case kMarkerLowGoo:
                switch (pSprite2->type) {
                case kMarkerLowWater:
                case kMarkerLowLink:
                case kMarkerLowStack:
                case kMarkerLowGoo:
                    break;
                default:
                    return;
                }
                break;

            case kMarkerUpWater:
            case kMarkerUpLink:
            case kMarkerUpStack:
            case kMarkerUpGoo:
                switch (pSprite2->type) {
                case kMarkerUpWater:
                case kMarkerUpLink:
                case kMarkerUpStack:
                case kMarkerUpGoo:
                    break;
                default:
                    return;
                }
                break;
            }

            // swap link location
            /*short tmp1 = pXSprite2.data1;*/
            /*pXSprite2.data1 = pXSprite.data1;*/
            /*pXSprite.data1 = tmp1;*/

            if (pXSprite->data2 < kMaxPAL && pXSprite2->data2 < kMaxPAL)
            {
                // swap medium
                int tmp2 = pXSprite2->data2;
                pXSprite2->data2 = pXSprite->data2;
                pXSprite->data2 = tmp2;
            }


            // swap link type                       // swap link owners (sectors)
            short tmp3 = pSprite2->type;			//short tmp7 = pSprite2.owner;
            pSprite2->type = pSprite->type;			//pSprite2.owner = pSprite.owner;
            pSprite->type = tmp3;					//pSprite.owner = tmp7;

            // Deal with linked sectors
            sectortype *pSector = &sector[pSprite->sectnum];
            sectortype *pSector2 = &sector[pSprite2->sectnum];

            // Check for underwater
            XSECTOR *pXSector = NULL;	XSECTOR *pXSector2 = NULL;
            if (pSector->extra > 0) pXSector = &xsector[pSector->extra];
            if (pSector2->extra > 0) pXSector2 = &xsector[pSector2->extra];
            if (pXSector != NULL && pXSector2 != NULL) {
                bool tmp6 = pXSector->Underwater;
                pXSector->Underwater = pXSector2->Underwater;
                pXSector2->Underwater = tmp6;
            }

            // optionally swap floorpic
            if (pXSprite2->data3 == 1) {
                short tmp4 = pSector->floorpicnum;
                pSector->floorpicnum = pSector2->floorpicnum;
                pSector2->floorpicnum = tmp4;
            }

            // optionally swap ceilpic
            if (pXSprite2->data4 == 1) {
                short tmp5 = pSector->ceilingpicnum;
                pSector->ceilingpicnum = pSector2->ceilingpicnum;
                pSector2->ceilingpicnum = tmp5;
            }
        }
        break;
        // add a way to link between path markers, so path sectors can change their path on the fly.
        case kMarkerPath:
        {
            // only path marker to path marker link allowed
            if (event.type == 3)
            {
                int nXSprite2 = sprite[event.index].extra;
                // get master path marker data fields
                pXSprite->data1 = xsprite[nXSprite2].data1;
                pXSprite->data2 = xsprite[nXSprite2].data2;
                pXSprite->data3 = xsprite[nXSprite2].data3; // include soundId(?)

                // get master path marker busy and wait times
                pXSprite->busyTime = xsprite[nXSprite2].busyTime;
                pXSprite->waitTime = xsprite[nXSprite2].waitTime;

            }
        }
        break;
        #endif
        case kSwitchCombo:
        {
            if (event.type == 3)
            {
                int nSprite2 = event.index;
                int nXSprite2 = sprite[nSprite2].extra;
                dassert(nXSprite2 > 0 && nXSprite2 < kMaxXSprites);
                pXSprite->data1 = xsprite[nXSprite2].data1;
                if (pXSprite->data1 == pXSprite->data2)
                    SetSpriteState(nSprite, pXSprite, 1);
                else
                    SetSpriteState(nSprite, pXSprite, 0);
            }
        }
        break;
        default:
        {
            pXSprite->busy = nBusy;
            if ((pXSprite->busy & 0xffff) == 0)
                SetSpriteState(nSprite, pXSprite, nBusy >> 16);
        }
        break;
    }
}

void LinkWall(int nWall, XWALL *pXWall, EVENT event)
{
    int nBusy = GetSourceBusy(event);
    pXWall->busy = nBusy;
    if ((pXWall->busy & 0xffff) == 0)
        SetWallState(nWall, pXWall, nBusy>>16);
}

void trTriggerSector(unsigned int nSector, XSECTOR *pXSector, int command) {
    dassert(nSector < (unsigned int)numsectors);
    if (!pXSector->locked && !pXSector->isTriggered) {
        
        if (pXSector->triggerOnce) 
            pXSector->isTriggered = 1;
        
        if (pXSector->decoupled && pXSector->txID > 0)
            evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command);
        
        else {
            EVENT event;
            event.cmd = command;
            OperateSector(nSector, pXSector, event);
        }

    }
}

void trMessageSector(unsigned int nSector, EVENT event) {
    dassert(nSector < (unsigned int)numsectors);
    dassert(sector[nSector].extra > 0 && sector[nSector].extra < kMaxXSectors);
    XSECTOR *pXSector = &xsector[sector[nSector].extra];
    if (!pXSector->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock) {
        switch (event.cmd) {
            case kCmdLink:
                LinkSector(nSector, pXSector, event);
                break;
            #ifdef NOONE_EXTENSIONS
            case kCmdModernUse:
                pastePropertiesInObj(6, nSector, event);
                break;
            #endif
            default:
                OperateSector(nSector, pXSector, event);
                break;
        }
    }
}

void trTriggerWall(unsigned int nWall, XWALL *pXWall, int command) {
    dassert(nWall < (unsigned int)numwalls);
    if (!pXWall->locked && !pXWall->isTriggered) {
        
        if (pXWall->triggerOnce)
            pXWall->isTriggered = 1;
        
        if (pXWall->decoupled && pXWall->txID > 0)
            evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command);

        else {
            EVENT event;
            event.cmd = command;
            OperateWall(nWall, pXWall, event);
        }

    }
}

void trMessageWall(unsigned int nWall, EVENT event) {
    dassert(nWall < (unsigned int)numwalls);
    dassert(wall[nWall].extra > 0 && wall[nWall].extra < kMaxXWalls);
    
    XWALL *pXWall = &xwall[wall[nWall].extra];
    if (!pXWall->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock) {
        switch (event.cmd) {
            case kCmdLink:
                LinkWall(nWall, pXWall, event);
                break;
            #ifdef NOONE_EXTENSIONS
            case kCmdModernUse:
                pastePropertiesInObj(0, nWall, event);
                break;
            #endif
            default:
                OperateWall(nWall, pXWall, event);
                break;
        }
    }
}

void trTriggerSprite(unsigned int nSprite, XSPRITE *pXSprite, int command) {
    if (!pXSprite->locked && !pXSprite->isTriggered) {
        
        if (pXSprite->triggerOnce)
            pXSprite->isTriggered = 1;

        if (pXSprite->Decoupled && pXSprite->txID > 0)
           evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
        
        else {
            EVENT event;
            event.cmd = command;
            OperateSprite(nSprite, pXSprite, event);
        }

    }
}

void trMessageSprite(unsigned int nSprite, EVENT event) {
    if (sprite[nSprite].statnum != kStatFree) {

        XSPRITE* pXSprite = &xsprite[sprite[nSprite].extra];
        if (!pXSprite->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock) {
            switch (event.cmd) {
                case kCmdLink:
                    LinkSprite(nSprite, pXSprite, event);
                    break;
                #ifdef NOONE_EXTENSIONS
                case kCmdModernUse:
                    pastePropertiesInObj(3, nSprite, event);
                    break;
                #endif
                default:
                    OperateSprite(nSprite, pXSprite, event);
                    break;
            }
        }

    }
}

#ifdef NOONE_EXTENSIONS

// this function stops wind on all TX sectors affected by WindGen after it goes off state.
void stopWindOnSectors(XSPRITE* pXSource) {
    spritetype* pSource = &sprite[pXSource->reference];

    if (pXSource->txID <= 0) {

        if (sector[pSource->sectnum].extra >= 0)
            xsector[sector[pSource->sectnum].extra].windVel = 0;

        return;
    }

    for (int i = bucketHead[pXSource->txID]; i < bucketHead[pXSource->txID + 1]; i++) {
        if (rxBucket[i].type != 6) continue;
        XSECTOR * pXSector = &xsector[sector[rxBucket[i].index].extra];
        if ((pXSector->state == 1 && !pXSector->windAlways) || (sprite[pXSource->reference].flags & kModernTypeFlag1))
            pXSector->windVel = 0;
    }
}

void trPlayerCtrlStartScene(XSPRITE* pXSource, PLAYER* pPlayer) {

    int nSource = sprite[pXSource->reference].index; TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
    QAV* pQav = qavSceneLoad(pXSource->data2); 
    if (pQav != NULL) {

        // save current weapon
        pXSource->dropMsg = pPlayer->curWeapon;

        short nIndex = pCtrl->qavScene.index;
        if (nIndex > -1 && nIndex != nSource && sprite[nIndex].extra >= 0)
            pXSource->dropMsg = xsprite[sprite[nIndex].extra].dropMsg;

        if (nIndex < 0)
            WeaponLower(pPlayer);

        pXSource->sysData1 = ClipLow((pQav->at10 * pXSource->waitTime) / 4, 0); // how many times animation should be played

        pCtrl->qavScene.index = nSource;
        pCtrl->qavScene.qavResrc = pQav;
        pCtrl->qavScene.dummy = -1;

        pCtrl->qavScene.qavResrc->Preload();

        pPlayer->sceneQav = pXSource->data2;
        pPlayer->weaponTimer = pCtrl->qavScene.qavResrc->at10;
        pPlayer->qavCallback = (pXSource->data3 > 0) ? ClipRange(pXSource->data3 - 1, 0, 32) : -1;
        pPlayer->qavLoop = false;

    }

}

void trPlayerCtrlStopScene(XSPRITE* pXSource, PLAYER* pPlayer) {

    TRPLAYERCTRL* pCtrl = &gPlayerCtrl[pPlayer->nPlayer];
    //viewSetSystemMessage("OFF %d", pCtrl->qavScene.index);

    pXSource->sysData1 = 0;
    pCtrl->qavScene.index = -1;
    pCtrl->qavScene.qavResrc = NULL;
    pPlayer->sceneQav = -1;

    // restore weapon
    if (pPlayer->pXSprite->health > 0) {
        int oldWeapon = (pXSource->dropMsg != 0) ? pXSource->dropMsg : 1;
        pPlayer->input.newWeapon = pPlayer->curWeapon = oldWeapon;
        WeaponRaise(pPlayer);
    }

}

void trPlayerCtrlLink(XSPRITE* pXSource, PLAYER* pPlayer) {

    pPlayer->pXSprite->txID             = pXSource->txID;
    pPlayer->pXSprite->command          = pXSource->data2;
    pPlayer->pXSprite->triggerOn        = pXSource->triggerOn;
    pPlayer->pXSprite->triggerOff       = pXSource->triggerOff;
    pPlayer->pXSprite->busyTime         = pXSource->busyTime;
    pPlayer->pXSprite->waitTime         = pXSource->waitTime;
    pPlayer->pXSprite->restState        = pXSource->restState;

    pPlayer->pXSprite->Push             = pXSource->Push;
    pPlayer->pXSprite->Impact           = pXSource->Impact;
    pPlayer->pXSprite->Vector           = pXSource->Vector;
    pPlayer->pXSprite->Touch            = pXSource->Touch;
    pPlayer->pXSprite->Sight            = pXSource->Sight;
    pPlayer->pXSprite->Proximity        = pXSource->Proximity;

    pPlayer->pXSprite->Decoupled        = pXSource->Decoupled;
    pPlayer->pXSprite->Interrutable     = pXSource->Interrutable;
    pPlayer->pXSprite->DudeLockout      = pXSource->DudeLockout;

    //pPlayer->pXSprite->data1            = pXSource->data1;
    //pPlayer->pXSprite->data2            = pXSource->data2;
    //pPlayer->pXSprite->data3            = pXSource->data3;
    //pPlayer->pXSprite->data4            = pXSource->data4;

    pPlayer->pXSprite->key              = pXSource->key;
    pPlayer->pXSprite->dropMsg          = pXSource->dropMsg;

}

void useObjResizer(XSPRITE* pXSource, short objType, int objIndex) {
    switch (objType) {
        // for sectors
        case 6:
            if (valueIsBetween(pXSource->data1, -1, 32767))
                sector[objIndex].floorxpanning = ClipRange(pXSource->data1, 0, 255);

            if (valueIsBetween(pXSource->data2, -1, 32767))
                sector[objIndex].floorypanning = ClipRange(pXSource->data2, 0, 255);

            if (valueIsBetween(pXSource->data3, -1, 32767))
                sector[objIndex].ceilingxpanning = ClipRange(pXSource->data3, 0, 255);

            if (valueIsBetween(pXSource->data4, -1, 65535))
                sector[objIndex].ceilingypanning = ClipRange(pXSource->data4, 0, 255);
            break;
            // for sprites
        case 3:

            // resize by seq scaling
            if (sprite[pXSource->reference].flags & kModernTypeFlag1) {
                if (valueIsBetween(pXSource->data1, -255, 32767)) {
                    int mulDiv = (valueIsBetween(pXSource->data2, 0, 257)) ? pXSource->data2 : 256;
                    if (pXSource->data1 > 0) xsprite[sprite[objIndex].extra].scale = mulDiv * ClipHigh(pXSource->data1, 25);
                    else if (pXSource->data1 < 0) xsprite[sprite[objIndex].extra].scale = mulDiv / ClipHigh(abs(pXSource->data1), 25);
                    else xsprite[sprite[objIndex].extra].scale = 0;

                    // request properties update for custom dude
                    switch (sprite[objIndex].type) {
                        case kDudeModernCustom:
                        case kDudeModernCustomBurning:
                            gGenDudeExtra[objIndex].updReq[kGenDudePropertySpriteSize] = true;
                            gGenDudeExtra[objIndex].updReq[kGenDudePropertyAttack] = true;
                            gGenDudeExtra[objIndex].updReq[kGenDudePropertyMass] = true;
                            gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true;
                            evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
                            break;
                    }
                }

                // resize by repeats
            } else {

                if (valueIsBetween(pXSource->data1, -1, 32767))
                    sprite[objIndex].xrepeat = ClipRange(pXSource->data1, 0, 255);

                if (valueIsBetween(pXSource->data2, -1, 32767))
                    sprite[objIndex].yrepeat = ClipRange(pXSource->data2, 0, 255);

            }

            if (valueIsBetween(pXSource->data3, -1, 32767))
                sprite[objIndex].xoffset = ClipRange(pXSource->data3, 0, 255);

            if (valueIsBetween(pXSource->data4, -1, 65535))
                sprite[objIndex].yoffset = ClipRange(pXSource->data4, 0, 255);
            break;

            // for walls
        case 0:
            if (valueIsBetween(pXSource->data1, -1, 32767))
                wall[objIndex].xrepeat = ClipRange(pXSource->data1, 0, 255);

            if (valueIsBetween(pXSource->data2, -1, 32767))
                wall[objIndex].yrepeat = ClipRange(pXSource->data2, 0, 255);

            if (valueIsBetween(pXSource->data3, -1, 32767))
                wall[objIndex].xpanning = ClipRange(pXSource->data3, 0, 255);

            if (valueIsBetween(pXSource->data4, -1, 65535))
                wall[objIndex].ypanning = ClipRange(pXSource->data4, 0, 255);
            break;
    }

}

void usePropertiesChanger(XSPRITE* pXSource, short objType, int objIndex) {

    spritetype* pSource = &sprite[pXSource->reference];

    switch (objType) {

        // for walls
        case 0: {
            walltype* pWall = &wall[objIndex]; int old = -1;

            // data3 = set wall hitag
            if (valueIsBetween(pXSource->data3, -1, 32767)) {
                if ((pSource->flags & kModernTypeFlag1)) pWall->hitag = pWall->hitag |= pXSource->data3;
                else pWall->hitag = pXSource->data3;
            }

            // data4 = set wall cstat
            if (valueIsBetween(pXSource->data4, -1, 65535)) {
                old = pWall->cstat;

                // set new cstat
                if ((pSource->flags & kModernTypeFlag1)) pWall->cstat = pWall->cstat |= pXSource->data4; // relative
                else pWall->cstat = pXSource->data4;    // absolute

                                                        // and hanlde exceptions
                if ((old & 0x2) && !(pWall->cstat & 0x2)) pWall->cstat |= 0x2; // kWallBottomSwap
                if ((old & 0x4) && !(pWall->cstat & 0x4)) pWall->cstat |= 0x4; // kWallBottomOrg, kWallOutsideOrg
                if ((old & 0x20) && !(pWall->cstat & 0x20)) pWall->cstat |= 0x20; // kWallOneWay

                if (old & 0xc000) {

                    if (!(pWall->cstat & 0xc000))
                        pWall->cstat |= 0xc000; // kWallMoveMask

                    if ((old & 0x0) && !(pWall->cstat & 0x0)) pWall->cstat |= 0x0; // kWallMoveNone
                    else if ((old & 0x4000) && !(pWall->cstat & 0x4000)) pWall->cstat |= 0x4000; // kWallMoveForward
                    else if ((old & 0x8000) && !(pWall->cstat & 0x8000)) pWall->cstat |= 0x8000; // kWallMoveBackward

                }
            }

            break;
        }

              // for sprites
        case 3: {
            spritetype* pSprite = &sprite[objIndex]; bool thing2debris = false;
            XSPRITE* pXSprite = &xsprite[pSprite->extra]; int old = -1;

            // data3 = set sprite hitag
            if (valueIsBetween(pXSource->data3, -1, 32767)) {
                old = pSprite->hitag; 

                // set new hitag
                if ((pSource->flags & kModernTypeFlag1)) pSprite->hitag = pSource->hitag |= pXSource->data3; // relative
                else pSprite->hitag = pXSource->data3;  // absolute

                                                        // and handle exceptions
                if ((old & kHitagFree) && !(pSprite->hitag & kHitagFree)) pSprite->hitag |= kHitagFree;
                if ((old & kHitagRespawn) && !(pSprite->hitag & kHitagRespawn)) pSprite->hitag |= kHitagRespawn;

                // prepare things for different (debris) physics.
                if (pSprite->statnum == kStatThing && debrisGetFreeIndex() >= 0) thing2debris = true;

            }

            // data2 = sprite physics settings
            if ((pXSource->data2 >= 0 && pXSource->data3 <= 33) || thing2debris) {
                switch (pSprite->statnum) {
                    case kStatDude: // dudes already treating in game
                    case kStatFree:
                    case kStatMarker:
                    case kStatPathMarker: // path marker
                        break;
                    default:
                        // store physics attributes in xsprite to avoid setting hitag for modern types!
                        int flags = (pXSprite->physAttr != 0) ? pXSprite->physAttr : 0;

                        if (thing2debris) {

                            // converting thing to debris
                            if ((pSprite->hitag & kPhysMove) != 0) flags |= kPhysMove;
                            else flags &= ~kPhysMove;

                            if ((pSprite->hitag & kPhysGravity) != 0) flags |= (kPhysGravity | kPhysFalling);
                            else flags &= ~(kPhysGravity | kPhysFalling);

                            pSprite->hitag &= ~(kPhysMove | kPhysGravity | kPhysFalling);
                            xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0;  pXSprite->restState = pXSprite->state;

                        } else {

                            // first digit of data2: set main physics attributes
                            switch (pXSource->data2) {
                                case 0:
                                    flags &= ~kPhysMove;
                                    flags &= ~(kPhysGravity | kPhysFalling);
                                    break;

                                case 1: case 10: case 11: case 12: case 13:
                                    flags |= kPhysMove;
                                    flags &= ~(kPhysGravity | kPhysFalling);
                                    break;

                                case 2: case 20: case 21: case 22: case 23:
                                    flags &= ~kPhysMove;
                                    flags |= (kPhysGravity | kPhysFalling);
                                    break;

                                case 3: case 30: case 31: case 32: case 33:
                                    flags |= kPhysMove;
                                    flags |= (kPhysGravity | kPhysFalling);
                                    break;
                            }

                            // second digit of data2: set physics flags
                            switch (pXSource->data2) {
                                case 0: case 1: case 2: case 3:
                                case 10: case 20: case 30:
                                    flags &= ~kPhysDebrisVector;
                                    flags &= ~kPhysDebrisExplode;
                                    break;

                                case 11: case 21: case 31:
                                    flags |= kPhysDebrisVector;
                                    flags &= ~kPhysDebrisExplode;
                                    break;

                                case 12: case 22: case 32:
                                    flags &= ~kPhysDebrisVector;
                                    flags |= kPhysDebrisExplode;
                                    break;

                                case 13: case 23: case 33:
                                    flags |= kPhysDebrisVector;
                                    flags |= kPhysDebrisExplode;
                                    break;
                            }

                        }

                        int nIndex = isDebris(objIndex); // check if there is no sprite in list

                                                         // adding physics sprite in list
                        if ((flags & kPhysGravity) != 0 || (flags & kPhysMove) != 0) {

                            if (nIndex != -1) pXSprite->physAttr = flags; // just update physics attributes
                            else if ((nIndex = debrisGetFreeIndex()) < 0)
                                viewSetSystemMessage("Max (%d) Physics affected sprites reached!", kMaxSuperXSprites);
                            else {

                                pXSprite->physAttr = flags; // update physics attributes

                                                            // allow things to became debris, so they use different physics...
                                if (pSprite->statnum == kStatThing) changespritestat(objIndex, 0);
                                //actPostSprite(nDest, kStatDecoration); // !!!! not working here for some reason

                                gPhysSpritesList[nIndex] = objIndex;
                                if (nIndex >= gPhysSpritesCount) gPhysSpritesCount++;
                                getSpriteMassBySize(pSprite); // create physics cache

                            }

                            // removing physics from sprite in list (don't remove sprite from list)
                        } else if (nIndex != -1) {

                            pXSprite->physAttr = flags;
                            xvel[objIndex] = yvel[objIndex] = zvel[objIndex] = 0;
                            if (pSprite->lotag >= kThingBase && pSprite->lotag < kThingMax)
                                changespritestat(objIndex, kStatThing);  // if it was a thing - restore statnum

                        }

                        break;
                }
            }

            // data4 = sprite cstat
            if (valueIsBetween(pXSource->data4, -1, 65535)) {

                old = pSprite->cstat;

                // set new cstat
                if ((pSource->flags & kModernTypeFlag1)) pSprite->cstat = pSprite->cstat |= pXSource->data4; // relative
                else pSprite->cstat = pXSource->data4; // absolute

                                                       // and handle exceptions
                if ((old & 0x1000) && !(pSprite->cstat & 0x1000)) pSprite->cstat |= 0x1000; //kSpritePushable
                if ((old & 0x80) && !(pSprite->cstat & 0x80)) pSprite->cstat |= 0x80; // kSpriteOriginAlign

                if (old & 0x6000) {

                    if (!(pSprite->cstat & 0x6000))
                        pSprite->cstat |= 0x6000; // kSpriteMoveMask

                    if ((old & 0x0) && !(pSprite->cstat & 0x0)) pSprite->cstat |= 0x0; // kSpriteMoveNone
                    else if ((old & 0x2000) && !(pSprite->cstat & 0x2000)) pSprite->cstat |= 0x2000; // kSpriteMoveForward, kSpriteMoveFloor
                    else if ((old & 0x4000) && !(pSprite->cstat & 0x4000)) pSprite->cstat |= 0x4000; // kSpriteMoveReverse, kSpriteMoveCeiling

                }

            }

            break;
        }

              // for sectors
        case 6: {

            XSECTOR* pXSector = &xsector[sector[objIndex].extra];

            // data1 = sector underwater status and depth level
            if (pXSource->data1 == 0) pXSector->Underwater = false;
            else if (pXSource->data1 == 1) pXSector->Underwater = true;
            else if (pXSource->data1 > 9) pXSector->Depth = 7;
            else if (pXSource->data1 > 1) pXSector->Depth = pXSource->data1 - 2;


            // data2 = sector visibility
            if (valueIsBetween(pXSource->data2, -1, 32767)) {
                if (pXSource->data2 > 234) sector[objIndex].visibility = 234;
                else sector[objIndex].visibility = pXSource->data2;
            }

            // data3 = sector ceil cstat
            if (valueIsBetween(pXSource->data3, -1, 32767)) {
                if ((pSource->flags & kModernTypeFlag1)) sector[objIndex].ceilingstat = sector[objIndex].ceilingstat |= pXSource->data3;
                else sector[objIndex].ceilingstat = pXSource->data3;
            }

            // data4 = sector floor cstat
            if (valueIsBetween(pXSource->data4, -1, 65535)) {
                if ((pSource->flags & kModernTypeFlag1)) sector[objIndex].floorstat = sector[objIndex].floorstat |= pXSource->data4;
                else sector[objIndex].floorstat = pXSource->data4;
            }

            break;

        }

              // no TX id
        case -1: {

            // data2 = global visibility
            if (valueIsBetween(pXSource->data2, -1, 32767))
                gVisibility = ClipRange(pXSource->data2, 0, 4096);
        }

               break;
    }

}

void useTeleportTarget(XSPRITE* pXSource, spritetype* pSprite) {
    spritetype* pSource = &sprite[pXSource->reference];
    XSECTOR* pXSector = (sector[pSource->sectnum].extra >= 0) ? &xsector[sector[pSource->sectnum].extra] : NULL;

    pSprite->x = pSource->x; pSprite->y = pSource->y;
    pSprite->z += (sector[pSource->sectnum].floorz - sector[pSprite->sectnum].floorz);

    if (pSource->flags & kModernTypeFlag1) // force telefrag
        TeleFrag(pSprite->xvel, pSource->sectnum);

    changespritesect((short)pSprite->xvel, pSource->sectnum);
    if (pXSector != NULL && pXSector->Underwater) xsprite[pSprite->extra].medium = kMediumWater;
    else xsprite[pSprite->extra].medium = kMediumNormal;

    if (pXSource->data2 == 1) {
        pSprite->ang = pSource->ang;
        if (IsDudeSprite(pSprite) && xspriRangeIsFine(pSprite->index))
            xsprite[pSprite->extra].goalAng = pSprite->ang;
    }

    if (pXSource->data3 == 1) 
        xvel[pSprite->xvel] = yvel[pSprite->xvel] = zvel[pSprite->xvel] = 0;

    viewBackupSpriteLoc(pSprite->xvel, pSprite);

    if (pXSource->data4 > 0)
        sfxPlay3DSound(pSource, pXSource->data4, -1, 0);

    if (IsPlayerSprite(pSprite)) {

        PLAYER* pPlayer = &gPlayer[pSprite->type - kDudePlayer1];
        playerResetInertia(pPlayer);

        if (pXSource->data2 == 1)
            pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
    }
}


void useEffectGen(XSPRITE * pXSource, spritetype * pSprite) {
    if (pSprite == NULL) pSprite = &sprite[pXSource->reference];
    if (pSprite->extra < 0) return;

    int fxId = pXSource->data2 + Random(pXSource->data3);
    int pos, top, bottom; GetSpriteExtents(pSprite, &top, &bottom); spritetype* pEffect = NULL;

    // select where exactly effect should be spawned
    switch (pXSource->data4) {
        case 1:
            pos = bottom; // bottom of sprite
            break;
        default:
            pos = top; // top of sprite
            break;
    }

    if (fxId > 0 && fxId < 57 && (pEffect = gFX.fxSpawn((FX_ID) fxId, pSprite->sectnum, pSprite->x, pSprite->y, pos, 0)) != NULL) {

        if (pEffect->cstat & CSTAT_SPRITE_ONE_SIDED) pEffect->cstat &= ~CSTAT_SPRITE_ONE_SIDED;

        if (pSprite->flags & kModernTypeFlag1) {
            pEffect->pal = pSprite->pal;
            pEffect->xrepeat = pSprite->xrepeat;
            pEffect->yrepeat = pSprite->yrepeat;
            pEffect->shade = pSprite->shade;
        }
    }
}


void useSectorWindGen(XSPRITE* pXSource, sectortype* pSector) {

    spritetype* pSource = &sprite[pXSource->reference];
    XSECTOR* pXSector = NULL; bool forceWind = false; 
    int nXSector = 0;
    if (pSector == NULL) {

        if (sector[pSource->sectnum].extra < 0) {
            int nXSector = dbInsertXSector(pSource->sectnum);
            if (nXSector > 0) pXSector = &xsector[nXSector];
            else return;

            forceWind = true;

        } else {
            pXSector = &xsector[sector[pSource->sectnum].extra];
            nXSector = sector[pXSector->reference].extra;
        }

    } else {
        pXSector = &xsector[pSector->extra];
        nXSector = sector[pXSector->reference].extra;
    }

    if (pSource->flags) {
        pXSector->panAlways = 1;
        pXSector->windAlways = 1;
    } else if (forceWind) 
        pXSector->windAlways = 1;

    if (pXSource->data2 > 32766) pXSource->data2 = 32767;

    if (pXSource->data1 == 1 || pXSource->data1 == 3) pXSector->windVel = Random(pXSource->data2);
    else pXSector->windVel = pXSource->data2;

    if (pXSource->data1 == 2 || pXSource->data1 == 3) {
        short ang = pSource->ang;
        while (pSource->ang == ang)
            pSource->ang = Random3(kAng360);
    }

    pXSector->windAng = pSource->ang;

    if (pXSource->data3 > 0 && pXSource->data3 < 4) {
        switch (pXSource->data3) {
            case 1:
                pXSector->panFloor = true;
                pXSector->panCeiling = false;
                break;
            case 2:
                pXSector->panFloor = false;
                pXSector->panCeiling = true;
                break;
            case 3:
                pXSector->panFloor = true;
                pXSector->panCeiling = true;
                break;
        }

        short oldPan = pXSector->panVel;
        pXSector->panAngle = pXSector->windAng;
        pXSector->panVel = pXSector->windVel;

        // add to panList if panVel was set to 0 previously
        if (oldPan == 0 && pXSector->panVel != 0 && panCount < kMaxXSprites) {

            int i;
            for (i = 0; i < panCount; i++) {
                if (panList[i] != nXSector) continue;
                break;
            }

            if (i == panCount)
                panList[panCount++] = nXSector;
        }

    }
}



void useSpriteDamager(XSPRITE* pXSource, spritetype* pSprite) {
    spritetype* pSource = &sprite[pXSource->reference];
    if (pSprite != NULL && xspriRangeIsFine(pSprite->extra) && xsprite[pSprite->extra].health > 0) {
        DAMAGE_TYPE dmgType = (DAMAGE_TYPE)ClipRange(pXSource->data2, kDmgFall, kDmgElectric);
        int dmg = (pXSource->data3 == 0) ? 65535 : ClipRange(pXSource->data3 << 1, 1, 65535);
        if (pXSource->data2 >= 0) actDamageSprite(pSource->index, pSprite, dmgType, dmg);
        else if (pXSource->data2 == -1 && IsDudeSprite(pSprite)) {
            PLAYER* pPlayer = getPlayerById(pSprite->type);
            if (pPlayer == NULL || !pPlayer->godMode) {
                xsprite[pSprite->extra].health = ClipLow(xsprite[pSprite->extra].health - dmg, 0);
                if (xsprite[pSprite->extra].health == 0) {
                    if (pPlayer == NULL) actKillDude(pSource->index, pSprite, DAMAGE_TYPE_0, 4);
                    else playerDamageSprite(pSource->index, pPlayer, DAMAGE_TYPE_0, 4);
                }
            }
        }
    }
}

void useSeqSpawnerGen(XSPRITE* pXSource, int objType, int index) {
    if (pXSource->data2 > 0 && !gSysRes.Lookup(pXSource->data2, "SEQ")) {
        consoleSysMsg("Missing sequence #%d",pXSource->data2);
        return;
    }

    switch (objType) {
        case 6:
            if (pXSource->data2 <= 0) {
                if (pXSource->data3 == 3 || pXSource->data3 == 1)
                    seqKill(2, sector[index].extra);
                if (pXSource->data3 == 3 || pXSource->data3 == 2)
                    seqKill(1, sector[index].extra);
            }
            else {
                if (pXSource->data3 == 3 || pXSource->data3 == 1)
                    seqSpawn(pXSource->data2, 2, sector[index].extra, -1);
                if (pXSource->data3 == 3 || pXSource->data3 == 2)
                    seqSpawn(pXSource->data2, 1, sector[index].extra, -1);
            }
            return;

        case 0:
            if (pXSource->data2 <= 0) {
                if (pXSource->data3 == 3 || pXSource->data3 == 1)
                    seqKill(0, wall[index].extra);
                if ((pXSource->data3 == 3 || pXSource->data3 == 2) && (wall[index].cstat & CSTAT_WALL_MASKED))
                    seqKill(4, wall[index].extra);
            }
            else {

                if (pXSource->data3 == 3 || pXSource->data3 == 1)
                    seqSpawn(pXSource->data2, 0, wall[index].extra, -1);
                if (pXSource->data3 == 3 || pXSource->data3 == 2) {

                    if (wall[index].nextwall < 0) {
                        if (pXSource->data3 == 3)
                            seqSpawn(pXSource->data2, 0, wall[index].extra, -1);

                    }
                    else {
                        if (!(wall[index].cstat & CSTAT_WALL_MASKED))
                            wall[index].cstat |= CSTAT_WALL_MASKED;

                        seqSpawn(pXSource->data2, 4, wall[index].extra, -1);
                    }
                }

                if (pXSource->data4 > 0) {

                    int cx, cy, cz;
                    cx = (wall[index].x + wall[wall[index].point2].x) >> 1;
                    cy = (wall[index].y + wall[wall[index].point2].y) >> 1;
                    int nSector = sectorofwall(index);
                    int32_t ceilZ, floorZ;
                    getzsofslope(nSector, cx, cy, &ceilZ, &floorZ);
                    int32_t ceilZ2, floorZ2;
                    getzsofslope(wall[index].nextsector, cx, cy, &ceilZ2, &floorZ2);
                    ceilZ = ClipLow(ceilZ, ceilZ2);
                    floorZ = ClipHigh(floorZ, floorZ2);
                    cz = (ceilZ + floorZ) >> 1;

                    sfxPlay3DSound(cx, cy, cz, pXSource->data4, nSector);

                }

            }
            return;

        case 3:
            if (pXSource->data2 <= 0) seqKill(3, sprite[index].extra);
            else {
                seqSpawn(pXSource->data2, 3, sprite[index].extra, -1);
                if (pXSource->data4 > 0) sfxPlay3DSound(&sprite[index], pXSource->data4, -1, 0);
            }
            return;
    }
}

bool valueIsBetween(int val, int min, int max) {
    return (val > min && val < max);
}

char modernTypeSetSpriteState(int nSprite, XSPRITE *pXSprite, int nState)
{
    if ((pXSprite->busy&0xffff) == 0 && pXSprite->state == nState) return 0;
    pXSprite->busy = nState<<16;
    pXSprite->state = nState;
    evKill(nSprite, 3);
    if ((sprite[nSprite].flags & kHitagRespawn) != 0 && sprite[nSprite].inittype >= kDudeBase && sprite[nSprite].inittype < kDudeMax) {
        pXSprite->respawnPending = 3;
        evPost(nSprite, 3, gGameOptions.nMonsterRespawnTime, kCallbackRespawn);
        return 1;
    }

    if (pXSprite->restState != nState && pXSprite->waitTime > 0)
        evPost(nSprite, 3, (pXSprite->waitTime*120) / 10, pXSprite->restState ? kCmdOn : kCmdOff);

    if (pXSprite->txID != 0 && ((pXSprite->triggerOn && pXSprite->state) || (pXSprite->triggerOff && !pXSprite->state))) {

        //Sending new command instead of link is *required*, because types above
        //are universal and can paste properties in different objects.
        switch (pXSprite->command) {
            case kCmdLink:
            case kCmdModernUse:
                evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // just send command to change properties
                return 1;
            case kCmdUnlock:
                evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // send normal command first
                evSend(nSprite, 3, pXSprite->txID, kCmdModernUse);  // then send command to change properties
                return 1;
            default:
                evSend(nSprite, 3, pXSprite->txID, kCmdModernUse); // send first command to change properties
                evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command); // then send normal command
                return 1;
        }

    }
    return 1;
}

// this function used by various new modern types.
void pastePropertiesInObj(int type, int nDest, EVENT event) {
   
    if (event.type != 3) return;
    spritetype* pSource = &sprite[event.index];
    
    if (!xspriRangeIsFine(pSource->extra)) return;
    XSPRITE* pXSource = &xsprite[pSource->extra];

    switch (type) {
        case 6:
            if (!xsectRangeIsFine(sector[nDest].extra)) return;
            break;
        case 0:
            if (!xwallRangeIsFine(wall[nDest].extra)) return;
            break;
        case 3:
            if (!xspriRangeIsFine(sprite[nDest].extra)) return;
            break;
        default:
            return;
    }

    if (pSource->type == kMarkerWarpDest) {
        /* - Allows teleport any sprite from any location to the source destination - */
        useTeleportTarget(pXSource, &sprite[nDest]);
        return;

    } else if (pSource->type == kModernSpriteDamager) {
        /* - damages xsprite via TX ID	- */
        if (type != 3) return;
        useSpriteDamager(pXSource, &sprite[nDest]);
        return;

    } if (pSource->type == kModernEffectSpawner) {
        /* - Effect Spawner can spawn any effect passed in data2 on it's or txID sprite - */
        if (pXSource->data2 < 0 || pXSource->data2 >= kFXMax) return;
        else if (type == 3)  useEffectGen(pXSource, &sprite[nDest]);
        return;

    }
    else if (pSource->type == kModernSeqSpawner) {
        /* - SEQ Spawner takes data2 as SEQ ID and spawns it on it's or TX ID sprite - */
        useSeqSpawnerGen(pXSource, type, nDest);
        return;
    }
    else if (pSource->type == kModernWindGenerator) {
        /* - Wind generator via TX or for current sector if TX ID not specified - */
        /* - sprite.ang = sector wind direction									- */
        /* - data1 = randomness settings										- */
        /* - 		 0: no randomness											- */
        /* - 		 1: randomize wind velocity in data2						- */
        /* - 		 2: randomize current generator sprite angle				- */
        /* - 		 3: randomize both wind velocity and sprite angle			- */
        /* - data2 = wind velocity												- */
        /* - data3 = enable panning according current wind speed and direction	- */
        /* - data4 = pan floor and ceiling settings								- */
        /* - 		 0: use sector pan settings									- */
        /* - 		 1: pan only floor											- */
        /* - 		 2: pan only ceiling										- */
        /* - 		 3: pan both												- */

        /* - hi-tag = 1: force windAlways and panAlways							- */

        if (pXSource->data2 < 0) return;
        else if (type == 6) useSectorWindGen(pXSource, &sector[nDest]);
        return;
    } else if (pSource->type == kModernObjSizeChanger) {
        /* - size and pan changer of sprite/wall/sector via TX ID 	- */
        /* - data1 = sprite xrepeat / wall xrepeat / floor xpan 	- */
        /* - data2 = sprite yrepeat / wall yrepeat / floor ypan 	- */
        /* - data3 = sprite xoffset / wall xoffset / ceil xpan 		- */
        /* - data3 = sprite yoffset / wall yoffset / ceil ypan 		- */

        useObjResizer(pXSource, type, nDest);
        return;

    } else if (pSource->type == kModernObjDataAccumulator) {
        /* - Object Data Accumulator allows to perform sum and sub operations in data fields of object - */
        /* - data1 = destination data index 															- */
        /* - data2 = min value																			- */
        /* - data3 = max value																			- */
        /* - data4 = step value																			- */
        /* - min > max = sub, 	min < max = sum															- */

        /* - flags: 0 = force OFF if goal value was reached for all objects		     					- */
        /* - flags: 2 = force swap min and max if goal value was reached								- */
        /* - flags: 3 = force reset counter	                                           					- */

        int data = getDataFieldOfObject(type, nDest, pXSource->data1);
        if (data == -65535) return;

        if (pXSource->data2 < pXSource->data3) {

            if (data < pXSource->data2) data = pXSource->data2;
            if (data > pXSource->data3) data = pXSource->data3;

            if ((data += pXSource->data4) >= pXSource->data3) {

                switch (pSource->flags) {
                    case kModernTypeFlag0:
                    case kModernTypeFlag1:
                        if (data > pXSource->data3) data = pXSource->data3;
                        break;
                    case kModernTypeFlag2: {
                        if (data > pXSource->data3) data = pXSource->data3;
                        if (!goalValueIsReached(pXSource)) break;
                        short tmp = pXSource->data3;
                        pXSource->data3 = pXSource->data2;
                        pXSource->data2 = tmp;
                    }
                        break;
                    case kModernTypeFlag3:
                        if (data > pXSource->data3) data = pXSource->data2;
                        break;
                }
            }

        } else if (pXSource->data2 > pXSource->data3) {

            if (data > pXSource->data2) data = pXSource->data2;
            if (data < pXSource->data3) data = pXSource->data3;

            if ((data -= pXSource->data4) <= pXSource->data3) {
                switch (pSource->flags) {
                    case kModernTypeFlag0:
                    case kModernTypeFlag1:
                        if (data < pXSource->data3) data = pXSource->data3;
                        break;
                    case kModernTypeFlag2: {
                        if (data < pXSource->data3) data = pXSource->data3;
                        if (!goalValueIsReached(pXSource)) break;
                        short tmp = pXSource->data3;
                        pXSource->data3 = pXSource->data2;
                        pXSource->data2 = tmp;
                    }
                        break;
                    case kModernTypeFlag3:
                        if (data < pXSource->data3) data = pXSource->data2;
                        break;
                }
            }
        }
        
        setDataValueOfObject(type, nDest, pXSource->data1, data);
        
        return;
    
    } else if (pSource->type == kModernObjDataChanger) {

        /* - Data field changer via TX - */
        /* - data1 = sprite data1 / sector data / wall data	- */
        /* - data2 = sprite data2	- */
        /* - data3 = sprite data3	- */
        /* - data4 = sprite data4	- */

        /* - flags: 1 = treat "ignore value" as actual value - */

        switch (type) {
            case 6:
                if ((pSource->flags & kModernTypeFlag1) || (pXSource->data1 != -1 && pXSource->data1 != 32767))
                    setDataValueOfObject(type, nDest, 1, pXSource->data1);
                break;

            case 3:
                if ((pSource->flags & kModernTypeFlag1) || (pXSource->data1 != -1 && pXSource->data1 != 32767))
                    setDataValueOfObject(type, nDest, 1, pXSource->data1);

                if ((pSource->flags & kModernTypeFlag1) || (pXSource->data2 != -1 && pXSource->data2 != 32767))
                    setDataValueOfObject(type, nDest, 2, pXSource->data2);

                if ((pSource->flags & kModernTypeFlag1) || (pXSource->data3 != -1 && pXSource->data3 != 32767))
                    setDataValueOfObject(type, nDest, 3, pXSource->data3);

                if ((pSource->flags & kModernTypeFlag1) || pXSource->data4 != 65535)
                    setDataValueOfObject(type, nDest, 4, pXSource->data4);
                break;

            case 0:
                if ((pSource->flags & kModernTypeFlag1) || (pXSource->data1 != -1 && pXSource->data1 != 32767))
                    setDataValueOfObject(type, nDest, 1, pXSource->data1);
                break;
        }

    } else if (pSource->type == kModernSectorFXChanger) {

        /* - FX Wave changer for sector via TX - */
            /* - data1 = Wave 	- */
            /* - data2 = Amplitude	- */
            /* - data3 = Freq	- */
            /* - data4 = Phase	- */

        if (type != 6) return;
        XSECTOR* pXSector = &xsector[sector[nDest].extra];

        if (valueIsBetween(pXSource->data1, -1, 32767))
            pXSector->wave = (pXSource->data1 > 11) ? 11 : pXSource->data1;

        int oldAmplitude = pXSector->amplitude;
        if (pXSource->data2 >= 0) pXSector->amplitude = (pXSource->data2 > 127) ? 127 : pXSource->data2;
        else if (pXSource->data2 < -1) pXSector->amplitude = (pXSource->data2 < -127) ? -127 : pXSource->data2;

        if (valueIsBetween(pXSource->data3, -1, 32767))
            pXSector->freq = (pXSource->data3 > 255) ? 255 : pXSource->data3;

        if (valueIsBetween(pXSource->data4, -1, 65535))
            pXSector->phase = (pXSource->data4 > 255) ? 255 : pXSource->data4;

        // force shadeAlways
        if (pSource->flags & kModernTypeFlag1)
            pXSector->shadeAlways = true;

        // add to shadeList if amplitude was set to 0 previously
        if (oldAmplitude == 0 && pXSector->amplitude != 0 && shadeCount < kMaxXSectors) {
            
            bool found = false;
            for (int i = 0; i < shadeCount; i++) {
                if (shadeList[i] != sector[nDest].extra) continue;
                found = true; 
                break;
            }

            if (!found)
                shadeList[shadeCount++] = sector[nDest].extra;
        }

    } else if (pSource->type == kModernDudeTargetChanger) {

        /* - Target changer for dudes via TX																		- */

        /* - data1 = target dude data1 value (can be zero)															- */
        /* 			 666: attack everyone, even if data1 id does not fit, except mates (if any)						- */
        /* - data2 = 0: AI deathmatch mode																			- */
        /*			 1: AI team deathmatch mode																		- */
        /* - data3 = 0: do not force target to fight dude back and *do not* awake some inactive monsters in sight	- */
        /* 			 1: force target to fight dude back	and *do not* awake some inactive monsters in sight			- */
        /*			 2: force target to fight dude back	and awake some inactive monsters in sight					- */
        /* - data4 = 0: do not ignore player(s) (even if enough targets in sight)									- */
        /*			 1: try to ignore player(s) (while enough targets in sight)										- */
        /*			 2: ignore player(s) (attack only when no targets in sight at all)								- */
        /*			 3: go to idle state if no targets in sight and ignore player(s) always							- */
        /*			 4: follow player(s) when no targets in sight, attack targets if any in sight					- */

        if (type != 3) return;
        else if (!IsDudeSprite(&sprite[nDest]) && sprite[nDest].statnum != kStatDude) {
            switch (sprite[nDest].type) { // can be dead dude turned in gib
                // make current target and all other dudes not attack this dude anymore
                case kThingBloodBits:
                case kThingBloodChunks:
                    freeTargets(nDest);
                    return;
                default:
                    return;
            }
        }

        spritetype* pSprite = &sprite[nDest]; XSPRITE* pXSprite = &xsprite[pSprite->extra];
        spritetype* pTarget = NULL; XSPRITE* pXTarget = NULL; int receiveHp = 33 + Random(33);
        DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; int matesPerEnemy = 1;

        // dude is burning?
        if (pXSprite->burnTime > 0 && spriRangeIsFine(pXSprite->burnSource)) {
            
            if (IsBurningDude(pSprite)) return;
            else {
                spritetype* pBurnSource = &sprite[pXSprite->burnSource];
                if (pBurnSource->extra >= 0) {
                    if (pXSource->data2 == 1 && isMateOf(pXSprite, &xsprite[pBurnSource->extra])) {
                        pXSprite->burnTime = 0;

                        // heal dude a bit in case of friendly fire
                        if (pXSprite->data4 > 0 && pXSprite->health < pXSprite->data4)
                            actHealDude(pXSprite, receiveHp, pXSprite->data4);
                        else if (pXSprite->health < pDudeInfo->startHealth)
                            actHealDude(pXSprite, receiveHp, pDudeInfo->startHealth);
                    }
                    else if (xsprite[pBurnSource->extra].health <= 0) {
                        pXSprite->burnTime = 0;
                    }
                }
            }
        }

        spritetype* pPlayer = targetIsPlayer(pXSprite);
        // special handling for player(s) if target changer data4 > 2.
        if (pPlayer != NULL) {
            if (pXSource->data4 == 3) {
                aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
                aiSetGenIdleState(pSprite, pXSprite);
                if (pSprite->type == kDudeModernCustom && leechIsDropped(pSprite))
                    removeLeech(leechIsDropped(pSprite));
            }
            else if (pXSource->data4 == 4) {
                aiSetTarget(pXSprite, pPlayer->x, pPlayer->y, pPlayer->z);
                if (pSprite->type == kDudeModernCustom && leechIsDropped(pSprite))
                    removeLeech(leechIsDropped(pSprite));
            }
        }

        int maxAlarmDudes = 8 + Random(8);
        if (pXSprite->target > -1 && sprite[pXSprite->target].extra > -1 && pPlayer == NULL) {
            pTarget = &sprite[pXSprite->target]; pXTarget = &xsprite[pTarget->extra];

            if (unitCanFly(pSprite) && isMeleeUnit(pTarget) && !unitCanFly(pTarget))
                pSprite->flags |= 0x0002;
            else if (unitCanFly(pSprite))
                pSprite->flags &= ~0x0002;

            if (!IsDudeSprite(pTarget) || pXTarget->health < 1 || !dudeCanSeeTarget(pXSprite, pDudeInfo, pTarget)) {
                aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
            }
            // dude attack or attacked by target that does not fit by data id?
            else if (pXSource->data1 != 666 && pXTarget->data1 != pXSource->data1) {
                if (affectedByTargetChg(pXTarget)) {

                    // force stop attack target
                    aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
                    if (pXSprite->burnSource == pTarget->xvel) {
                        pXSprite->burnTime = 0;
                        pXSprite->burnSource = -1;
                    }

                    // force stop attack dude
                    aiSetTarget(pXTarget, pTarget->x, pTarget->y, pTarget->z);
                    if (pXTarget->burnSource == pSprite->xvel) {
                        pXTarget->burnTime = 0;
                        pXTarget->burnSource = -1;
                    }
                }
                
            }
            // instantly kill annoying spiders, rats, hands etc if dude is big enough
            else if (isAnnoyingUnit(pTarget) && !isAnnoyingUnit(pSprite) && tilesiz[pSprite->picnum].y >= 60 &&
                getTargetDist(pSprite, pDudeInfo, pTarget) < 2) {

                actKillDude(pSource->xvel, pTarget, DAMAGE_TYPE_0, 65535);
                aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);

            }
            else if (pXSource->data2 == 1 && isMateOf(pXSprite, pXTarget)) {
                spritetype* pMate = pTarget; XSPRITE* pXMate = pXTarget;

                // heal dude
                if (pXSprite->data4 > 0 && pXSprite->health < pXSprite->data4)
                    actHealDude(pXSprite, receiveHp, pXSprite->data4);
                else if (pXSprite->health < pDudeInfo->startHealth)
                    actHealDude(pXSprite, receiveHp, pDudeInfo->startHealth);

                // heal mate
                if (pXMate->data4 > 0 && pXMate->health < pXMate->data4)
                    actHealDude(pXMate, receiveHp, pXMate->data4);
                else {
                    DUDEINFO* pTDudeInfo = &dudeInfo[pMate->type - kDudeBase];
                    if (pXMate->health < pTDudeInfo->startHealth)
                        actHealDude(pXMate, receiveHp, pTDudeInfo->startHealth);
                }

                if (pXMate->target > -1 && sprite[pXMate->target].extra >= 0) {
                    pTarget = &sprite[pXMate->target];
                    // force mate stop attack dude, if he does
                    if (pXMate->target == pSprite->xvel) {
                        aiSetTarget(pXMate, pMate->x, pMate->y, pMate->z);
                    }
                    else if (!isMateOf(pXSprite, &xsprite[pTarget->extra])) {
                        // force dude to attack same target that mate have
                        aiSetTarget(pXSprite, pTarget->xvel);
                        return;

                    }
                    else {
                        // force mate to stop attack another mate
                        aiSetTarget(pXMate, pMate->x, pMate->y, pMate->z);
                    }
                }

                // force dude stop attack mate, if target was not changed previously
                if (pXSprite->target == pMate->xvel)
                    aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);

                
            }
            // check if targets aims player then force this target to fight with dude
            else if (targetIsPlayer(pXTarget) != NULL) {
                aiSetTarget(pXTarget, pSprite->xvel);
            }

            int mDist = 3; if (isMeleeUnit(pSprite)) mDist = 2;
            if (pXSprite->target >= 0 && getTargetDist(pSprite, pDudeInfo, &sprite[pXSprite->target]) < mDist) {
                if (!isActive(pSprite->xvel)) aiActivateDude(pSprite, pXSprite);
                return;
            }
            // lets try to look for target that fits better by distance
            else if (((int)gFrameClock & 256) != 0 && (pXSprite->target < 0 || getTargetDist(pSprite, pDudeInfo, pTarget) >= mDist)) {
                pTarget = getTargetInRange(pSprite, 0, mDist, pXSource->data1, pXSource->data2);
                if (pTarget != NULL) {
                    pXTarget = &xsprite[pTarget->extra];

                    // Make prev target not aim in dude
                    if (pXSprite->target > -1) {
                        spritetype* prvTarget = &sprite[pXSprite->target];
                        aiSetTarget(&xsprite[prvTarget->extra], prvTarget->x, prvTarget->y, prvTarget->z);
                        if (!isActive(pTarget->xvel))
                            aiActivateDude(pTarget, pXTarget);
                    }

                    // Change target for dude
                    aiSetTarget(pXSprite, pTarget->xvel);
                    if (!isActive(pSprite->xvel))
                        aiActivateDude(pSprite, pXSprite);

                    // ...and change target of target to dude to force it fight
                    if (pXSource->data3 > 0 && pXTarget->target != pSprite->xvel) {
                        aiSetTarget(pXTarget, pSprite->xvel);
                        if (!isActive(pTarget->xvel))
                            aiActivateDude(pTarget, pXTarget);
                    }
                    return;
                }
            }
        }

        if ((pXSprite->target < 0 || pPlayer != NULL) && ((int)gFrameClock & 32) != 0) {
            // try find first target that dude can see
            for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
                pTarget = &sprite[nSprite]; pXTarget = &xsprite[pTarget->extra];

                if (pXTarget->target == pSprite->xvel) {
                    aiSetTarget(pXSprite, pTarget->xvel);
                    return;
                }

                // skip non-dudes and players
                if (!IsDudeSprite(pTarget) || (IsPlayerSprite(pTarget) && pXSource->data4 > 0) || pTarget->owner == pSprite->xvel) continue;
                // avoid self aiming, those who dude can't see, and those who dude own
                else if (!dudeCanSeeTarget(pXSprite, pDudeInfo, pTarget) || pSprite->xvel == pTarget->xvel) continue;
                // if Target Changer have data1 = 666, everyone can be target, except AI team mates.
                else if (pXSource->data1 != 666 && pXSource->data1 != pXTarget->data1) continue;
                // don't attack immortal, burning dudes and mates
                if (IsBurningDude(pTarget) || !IsKillableDude(pTarget) || (pXSource->data2 == 1 && isMateOf(pXSprite, pXTarget)))
                    continue;

                if (pXSource->data2 == 0 || (pXSource->data2 == 1 && !isMatesHaveSameTarget(pXSprite, pTarget, matesPerEnemy))) {

                    // Change target for dude
                    aiSetTarget(pXSprite, pTarget->xvel);
                    if (!isActive(pSprite->xvel))
                        aiActivateDude(pSprite, pXSprite);

                    // ...and change target of target to dude to force it fight
                    if (pXSource->data3 > 0 && pXTarget->target != pSprite->xvel) {
                        aiSetTarget(pXTarget, pSprite->xvel);
                        if (!isActive(pTarget->xvel))
                            aiActivateDude(pTarget, pXTarget);

                        if (pXSource->data3 == 2)
                            disturbDudesInSight(pTarget, maxAlarmDudes);
                    }
                    return;
                }
                break;
            }
        }

        // got no target - let's ask mates if they have targets
        if ((pXSprite->target < 0 || pPlayer != NULL) && pXSource->data2 == 1 && ((int)gFrameClock & 64) != 0) {
            spritetype* pMateTarget = NULL;
            if ((pMateTarget = getMateTargets(pXSprite)) != NULL && pMateTarget->extra > 0) {
                XSPRITE* pXMateTarget = &xsprite[pMateTarget->extra];
                if (dudeCanSeeTarget(pXSprite, pDudeInfo, pMateTarget)) {
                    if (pXMateTarget->target < 0) {
                        aiSetTarget(pXMateTarget, pSprite->xvel);
                        if (IsDudeSprite(pMateTarget) && !isActive(pMateTarget->xvel))
                            aiActivateDude(pMateTarget, pXMateTarget);
                    }

                    aiSetTarget(pXSprite, pMateTarget->xvel);
                    if (!isActive(pSprite->xvel))
                        aiActivateDude(pSprite, pXSprite);
                    return;

                    // try walk in mate direction in case if not see the target
                }
                else if (pXMateTarget->target >= 0 && dudeCanSeeTarget(pXSprite, pDudeInfo, &sprite[pXMateTarget->target])) {
                    spritetype* pMate = &sprite[pXMateTarget->target];
                    pXSprite->target = pMateTarget->xvel;
                    pXSprite->targetX = pMate->x;
                    pXSprite->targetY = pMate->y;
                    pXSprite->targetZ = pMate->z;
                    if (!isActive(pSprite->xvel))
                        aiActivateDude(pSprite, pXSprite);
                    return;
                }
            }
        }

    } else if (pSource->type == kModernObjPicnumChanger) {

        /* - picnum changer can change picnum of sprite/wall/sector via TX ID - */
        /* - data1 = sprite pic / wall pic / sector floor pic 				 - */
        /* - data2 = sprite shade / wall overpic / sector ceil pic 		    - */
        /* - data3 = sprite pal / wall pal / sector floor pic				- */

        switch (type) {
            // for sectors
            case 6:
            {
                if (valueIsBetween(pXSource->data1, -1, 32767))
                    sector[nDest].floorpicnum = pXSource->data1;

                if (valueIsBetween(pXSource->data2, -1, 32767))
                    sector[nDest].ceilingpicnum = pXSource->data2;

                XSECTOR *pXSector = &xsector[sector[nDest].extra];
                if (valueIsBetween(pXSource->data3, -1, 32767)) {
                    sector[nDest].floorpal = pXSource->data3;
                    if (pSource->flags & kModernTypeFlag1)
                        pXSector->floorpal = pXSource->data3;
                }

                if (valueIsBetween(pXSource->data4, -1, 65535)) {
                    sector[nDest].ceilingpal = pXSource->data4;
                    if (pSource->flags & kModernTypeFlag1)
                        pXSector->ceilpal = pXSource->data4;
                }
                break;
            }
            // for sprites
            case 3:
                if (valueIsBetween(pXSource->data1, -1, 32767))
                    sprite[nDest].picnum = pXSource->data1;

                if (pXSource->data2 >= 0) sprite[nDest].shade = (pXSource->data2 > 127) ? 127 : pXSource->data2;
                else if (pXSource->data2 < -1) sprite[nDest].shade = (pXSource->data2 < -127) ? -127 : pXSource->data2;

                if (valueIsBetween(pXSource->data3, -1, 32767))
                    sprite[nDest].pal = pXSource->data3;
                break;
            // for walls
            case 0:
                if (valueIsBetween(pXSource->data1, -1, 32767))
                    wall[nDest].picnum = pXSource->data1;

                if (valueIsBetween(pXSource->data2, -1, 32767))
                    wall[nDest].overpicnum = pXSource->data2;

                if (valueIsBetween(pXSource->data3, -1, 32767))
                    wall[nDest].pal = pXSource->data3;
                break;
        }

    } else if (pSource->type == kModernObjPropertiesChanger) {
        /* - properties changer can change various properties - */
        usePropertiesChanger(pXSource, type, nDest);
    }
}

// the following functions required for kModernDudeTargetChanger
//---------------------------------------
spritetype* getTargetInRange(spritetype* pSprite, int minDist, int maxDist, short data, short teamMode) {
    DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; XSPRITE* pXSprite = &xsprite[pSprite->extra];
    spritetype* pTarget = NULL; XSPRITE* pXTarget = NULL; spritetype* cTarget = NULL;
    for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        pTarget = &sprite[nSprite];  pXTarget = &xsprite[pTarget->extra];
        if (!dudeCanSeeTarget(pXSprite, pDudeInfo, pTarget)) continue;

        int dist = getTargetDist(pSprite, pDudeInfo, pTarget);
        if (dist < minDist || dist > maxDist) continue;
        else if (pXSprite->target == pTarget->xvel) return pTarget;
        else if (!IsDudeSprite(pTarget) || pTarget->xvel == pSprite->xvel || IsPlayerSprite(pTarget)) continue;
        else if (IsBurningDude(pTarget) || !IsKillableDude(pTarget) || pTarget->owner == pSprite->xvel) continue;
        else if ((teamMode == 1 && isMateOf(pXSprite, pXTarget)) || isMatesHaveSameTarget(pXSprite,pTarget,1)) continue;
        else if (data == 666 || pXTarget->data1 == data) {

            if (pXSprite->target > 0) {
                cTarget = &sprite[pXSprite->target];
                int fineDist1 = getFineTargetDist(pSprite, cTarget);
                int fineDist2 = getFineTargetDist(pSprite, pTarget);
                if (fineDist1 < fineDist2)
                    continue;
            }
            return pTarget;
        }
    }

    return NULL;
}

bool isMateOf(XSPRITE* pXDude, XSPRITE* pXSprite) {
    return (pXDude->rxID == pXSprite->rxID);
}

spritetype* targetIsPlayer(XSPRITE* pXSprite) {

    if (pXSprite->target >= 0) {
        if (IsPlayerSprite(&sprite[pXSprite->target]))
            return &sprite[pXSprite->target];
    }

    return NULL;
}

bool isTargetAimsDude(XSPRITE* pXTarget, spritetype* pDude) {
    return (pXTarget->target == pDude->xvel);
}

spritetype* getMateTargets(XSPRITE* pXSprite) {
    int rx = pXSprite->rxID; spritetype* pMate = NULL; XSPRITE* pXMate = NULL;

    for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
        if (rxBucket[i].type == 3) {
            pMate = &sprite[rxBucket[i].index];
            if (pMate->extra < 0 || pMate->xvel == sprite[pXSprite->reference].xvel || !IsDudeSprite(pMate))
                continue;

            pXMate = &xsprite[pMate->extra];
            if (pXMate->target > -1) {
                if (!IsPlayerSprite(&sprite[pXMate->target]))
                    return &sprite[pXMate->target];
            }

        }
    }

    return NULL;
}

bool isMatesHaveSameTarget(XSPRITE* pXLeader, spritetype* pTarget, int allow) {
    int rx = pXLeader->rxID; spritetype* pMate = NULL; XSPRITE* pXMate = NULL;

    for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {

        if (rxBucket[i].type != 3)
            continue;

        pMate = &sprite[rxBucket[i].index];
        if (pMate->extra < 0 || pMate->xvel == sprite[pXLeader->reference].xvel || !IsDudeSprite(pMate))
            continue;

        pXMate = &xsprite[pMate->extra];
        if (pXMate->target == pTarget->xvel && allow-- <= 0)
            return true;
    }

    return false;

}

bool isActive(int nSprite) {
    if (sprite[nSprite].extra < 0 || sprite[nSprite].extra >= kMaxXSprites)
        return false;
    
    XSPRITE* pXDude = &xsprite[sprite[nSprite].extra];
    switch (pXDude->aiState->stateType) {
        case kAiStateIdle:
        case kAiStateGenIdle:
        case kAiStateSearch:
        case kAiStateMove:
        case kAiStateOther:
            return false;
        default:
            return true;
    }
}

bool dudeCanSeeTarget(XSPRITE* pXDude, DUDEINFO* pDudeInfo, spritetype* pTarget) {
    spritetype* pDude = &sprite[pXDude->reference];
    int dx = pTarget->x - pDude->x; int dy = pTarget->y - pDude->y;
    
    // check target
    if (approxDist(dx, dy) < pDudeInfo->seeDist) {
        int eyeAboveZ = pDudeInfo->eyeHeight * pDude->yrepeat << 2;

        // is there a line of sight to the target?
        if (cansee(pDude->x, pDude->y, pDude->z, pDude->sectnum, pTarget->x, pTarget->y, pTarget->z - eyeAboveZ, pTarget->sectnum)) {
            /*int nAngle = getangle(dx, dy);
            int losAngle = ((1024 + nAngle - pDude->ang) & 2047) - 1024;

            // is the target visible?
            if (klabs(losAngle) < 2048) // 360 deg periphery here*/
                return true;
        }
    }

    return false;

}

// this function required if monsters in genIdle ai state. It wakes up monsters
// when kModernDudeTargetChanger goes to off state, so they won't ignore the world.
void activateDudes(int rx) {
    for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
        if (rxBucket[i].type != 3) continue;
        spritetype * pDude = &sprite[rxBucket[i].index]; XSPRITE * pXDude = &xsprite[pDude->extra];
        if (!IsDudeSprite(pDude) || pXDude->aiState->stateType != kAiStateGenIdle) continue;
            aiInitSprite(pDude);
    }
}


// this function sets target to -1 for all dudes that hunting for nSprite
void freeTargets(int nSprite) {
    for (int nTarget = headspritestat[kStatDude]; nTarget >= 0; nTarget = nextspritestat[nTarget]) {
        if (!IsDudeSprite(&sprite[nTarget]) || sprite[nTarget].extra < 0) continue;
        else if (xsprite[sprite[nTarget].extra].target == nSprite)
            aiSetTarget(&xsprite[sprite[nTarget].extra], sprite[nTarget].x, sprite[nTarget].y, sprite[nTarget].z);
    }

    return;
}

// this function sets target to -1 for all targets that hunting for dudes affected by selected kModernDudeTargetChanger
void freeAllTargets(XSPRITE* pXSource) {
    if (pXSource->txID <= 0) return;
    for (int i = bucketHead[pXSource->txID]; i < bucketHead[pXSource->txID + 1]; i++) {
        if (rxBucket[i].type == 3 && sprite[rxBucket[i].index].extra >= 0)
            freeTargets(rxBucket[i].index);
    }

    return;
}

bool affectedByTargetChg(XSPRITE* pXDude) {
    if (pXDude->rxID <= 0 || pXDude->locked == 1) return false;
    for (int nSprite = headspritestat[kStatModernDudeTargetChanger]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        XSPRITE* pXSprite = (sprite[nSprite].extra >= 0) ? &xsprite[sprite[nSprite].extra] : NULL;
        if (pXSprite == NULL || pXSprite->txID <= 0 || pXSprite->state != 1) continue;
        for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
            if (rxBucket[i].type != 3) continue;

            spritetype* pSprite = &sprite[rxBucket[i].index];
            if (pSprite->extra < 0 || !IsDudeSprite(pSprite)) continue;
            else if (pSprite->xvel == sprite[pXDude->reference].xvel) return true;
        }
    }
    return false;
}

int getDataFieldOfObject(int objType, int objIndex, int dataIndex) {
    int data = -65535;
    switch (objType) {
    case 3:
        switch (dataIndex) {
        case 1:
            return xsprite[sprite[objIndex].extra].data1;
        case 2:
            return xsprite[sprite[objIndex].extra].data2;
        case 3:
            switch (sprite[objIndex].type) {
                case kDudeModernCustom:
                    return xsprite[sprite[objIndex].extra].sysData1;
                default:
                    return xsprite[sprite[objIndex].extra].data3;
            }
        case 4:
            return xsprite[sprite[objIndex].extra].data4;
        default:
            return data;
        }
    case 0:
        return xsector[sector[objIndex].extra].data;
    case 6:
        return xwall[wall[objIndex].extra].data;
    default:
        return data;
    }
}

bool setDataValueOfObject(int objType, int objIndex, int dataIndex, int value) {
    switch (objType) {
        case 3: {

            XSPRITE* pXSprite = &xsprite[sprite[objIndex].extra];

            // exceptions
            if (IsDudeSprite(&sprite[objIndex]) && pXSprite->health <= 0) return true;
            /*switch (sprite[objIndex].type) {
                case kThingBloodBits:
                case kThingBloodChunks:
                case kThingZombieHead:
                case kThingObjectGib:
                case kThingObjectExplode:
                    if (pXSprite->data1 > 0 || pXSprite->data2 > 0 || pXSprite->data3 > 0 || pXSprite->data4 > 0) return true;
                    break;
            }*/

            switch (dataIndex) {
            case 1:
                xsprite[sprite[objIndex].extra].data1 = value;
                switch (sprite[objIndex].type) {
                case kSwitchCombo:
                    if (value == xsprite[sprite[objIndex].extra].data2) SetSpriteState(objIndex, &xsprite[sprite[objIndex].extra], 1);
                    else SetSpriteState(objIndex, &xsprite[sprite[objIndex].extra], 0);
                    break;
                case kDudeModernCustom:
                case kDudeModernCustomBurning:
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertyWeapon] = true;
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true;
                    evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
                    break;
                }
                return true;
            case 2:
                xsprite[sprite[objIndex].extra].data2 = value;
                switch (sprite[objIndex].type) {
                case kDudeModernCustom:
                case kDudeModernCustomBurning:
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertySpriteSize] = true;
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertyMass] = true;
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertyDmgScale] = true;
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertyStates] = true;
                    gGenDudeExtra[objIndex].updReq[kGenDudePropertyAttack] = true;
                    evPost(objIndex, 3, kGenDudeUpdTimeRate, kCallbackGenDudeUpdate);
                    break;
                }
                return true;
            case 3:
                xsprite[sprite[objIndex].extra].data3 = value;
                switch (sprite[objIndex].type) {
                case kDudeModernCustom:
                case kDudeModernCustomBurning:
                    xsprite[sprite[objIndex].extra].sysData1 = value;
                    break;
                }
                return true;
            case 4:
                xsprite[sprite[objIndex].extra].data4 = value;
                return true;
            default:
                return false;
            }
        }
        case 0:
            xsector[sector[objIndex].extra].data = value;
            return true;
        case 6:
            xwall[wall[objIndex].extra].data = value;
            return true;
    default:
        return false;
    }
}

// this function checks if all TX objects have the same value
bool goalValueIsReached(XSPRITE* pXSprite) {
    for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
        if (getDataFieldOfObject(rxBucket[i].type, rxBucket[i].index, pXSprite->data1) != pXSprite->data3)
            return false;
    }
    return true;
}

// this function tells if there any dude found for kModernDudeTargetChanger
bool getDudesForTargetChg(XSPRITE* pXSprite) {
    for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
        if (rxBucket[i].type != 3) continue;
        else if (IsDudeSprite(&sprite[rxBucket[i].index]) &&
            xsprite[sprite[rxBucket[i].index].extra].health > 0) return true;
    }

    return false;
}

void disturbDudesInSight(spritetype* pSprite, int max) {
    spritetype* pDude = NULL; XSPRITE* pXDude = NULL;
    XSPRITE* pXSprite = &xsprite[pSprite->extra];
    DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
    for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        pDude = &sprite[nSprite];
        if (pDude->xvel == pSprite->xvel || !IsDudeSprite(pDude) || pDude->extra < 0)
            continue;
        pXDude = &xsprite[pDude->extra];
        if (dudeCanSeeTarget(pXSprite, pDudeInfo, pDude)) {
            if (pXDude->target != -1 || pXDude->rxID > 0)
                continue;

            aiSetTarget(pXDude, pDude->x, pDude->y, pDude->z);
            aiActivateDude(pDude, pXDude);
            if (max-- < 1)
                break;
        }
    }
}

int getTargetDist(spritetype* pSprite, DUDEINFO* pDudeInfo, spritetype* pTarget) {
    int x = pTarget->x; int y = pTarget->y;
    int dx = x - pSprite->x; int dy = y - pSprite->y;

    int dist = approxDist(dx, dy);
    if (dist <= pDudeInfo->meleeDist) return 0;
    if (dist >= pDudeInfo->seeDist) return 13;
    if (dist <= pDudeInfo->seeDist / 12) return 1;
    if (dist <= pDudeInfo->seeDist / 11) return 2;
    if (dist <= pDudeInfo->seeDist / 10) return 3;
    if (dist <= pDudeInfo->seeDist / 9) return 4;
    if (dist <= pDudeInfo->seeDist / 8) return 5;
    if (dist <= pDudeInfo->seeDist / 7) return 6;
    if (dist <= pDudeInfo->seeDist / 6) return 7;
    if (dist <= pDudeInfo->seeDist / 5) return 8;
    if (dist <= pDudeInfo->seeDist / 4) return 9;
    if (dist <= pDudeInfo->seeDist / 3) return 10;
    if (dist <= pDudeInfo->seeDist / 2) return 11;
    return 12;
}

int getFineTargetDist(spritetype* pSprite, spritetype* pTarget) {
    int x = pTarget->x; int y = pTarget->y;
    int dx = x - pSprite->x; int dy = y - pSprite->y;

    int dist = approxDist(dx, dy);
    return dist;
}

bool IsBurningDude(spritetype* pSprite) {
    if (pSprite == NULL) return false;
    switch (pSprite->type) {
    case kDudeBurningInnocent: // burning dude
    case kDudeBurningCultist: // cultist burning
    case kDudeBurningZombieAxe: // axe zombie burning
    case kDudeBurningZombieButcher: // fat zombie burning
    case kDudeBurningTinyCaleb: // tiny caleb burning
    case kDudeBurningBeast: // beast burning
    case kDudeModernCustomBurning:
        return true;
    }

    return false;
}

bool IsKillableDude(spritetype* pSprite) {
    switch (pSprite->type) {
    case kDudeGargoyleStatueFlesh: // flesh statue
    case kDudeGargoyleStatueStone: // stone statue
        return false;
    default:
        if (!IsDudeSprite(pSprite) || xsprite[pSprite->extra].locked == 1) return false;
        return true;
    }
}

bool isAnnoyingUnit(spritetype* pDude) {
    switch (pDude->type) {
    case kDudeHand: // hand
    case kDudeSpiderBrown: // brown spider
    case kDudeSpiderRed: // red spider
    case kDudeSpiderBlack: // black spider
    case kDudeSpiderMother: // mother spider
    case kDudeBoneEel: // eel
    case kDudeBat: // bat
    case kDudeRat: // rat
    case kDudePodGreen: // green pod
    case kDudeTentacleGreen: // green tentacle
    case kDudeTentacleFire: // fire tentacle
    case kDudeTentacleMother: // mother tentacle
    case kDudePodFire: // fire pod
        return true;
    default:
        return false;
    }
}

bool unitCanFly(spritetype* pDude) {
    switch (pDude->type) {
    case kDudeBat: // bat
    case kDudeGargoyleFlesh: // gargoyle
    case kDudeGargoyleStone: // stone gargoyle
    case kDudePhantasm: // phantasm
        return true;
    default:
        return false;
    }
}

bool isMeleeUnit(spritetype* pDude) {
    switch (pDude->type) {
    case kDudeZombieAxeNormal: // axe zombie
    case kDudeZombieAxeBuried: // earth zombie
    case kDudeGargoyleFlesh: // gargoyle
    case kDudeHand: // hand
    case kDudeSpiderBrown: // brown spider
    case kDudeSpiderRed: // red spider
    case kDudeSpiderBlack: // black spider
    case kDudeSpiderMother: // mother spider
    case kDudeGillBeast: // gill beast
    case kDudeBoneEel: // eel
    case kDudeBat: // bat
    case kDudeRat: // rat
    case kDudeTentacleGreen: // green tentacle
    case kDudeTentacleFire: // fire tentacle
    case kDudeTentacleMother: // mother tentacle
    case kDudeZombieAxeLaying: // sleep zombie
    case kDudeInnocent: // innocent
    case kDudeTinyCaleb: // tiny caleb
    case kDudeBeast: // beast
        return true;
    case kDudeModernCustom:
        return (pDude->extra >= 0 && dudeIsMelee(&xsprite[pDude->extra]));
    default:
        return false;
    }
}

// Callback for trap that can fire any missile specified in data1
void UniMissileTrapSeqCallback(int, int nXSprite)
{

    XSPRITE* pXSprite = &xsprite[nXSprite]; int dx = 0, dy = 0, dz = 0;
    spritetype* pSprite = &sprite[pXSprite->reference];

    if (pXSprite->data1 < kMissileBase || pXSprite->data1 >= kMissileMax) 
        return;

    if (pSprite->cstat & 32) {
        if (pSprite->cstat & 8) dz = 0x4000;
        else dz = -0x4000;
    } else {
        dx = Cos(pSprite->ang) >> 16;
        dy = Sin(pSprite->ang) >> 16;
        dz = pXSprite->data3 << 6; // add slope controlling
        if (dz > 0x10000) dz = 0x10000;
        else if (dz < -0x10000) dz = -0x10000;
    }

    spritetype* pMissile = NULL;
    pMissile = actFireMissile(pSprite, 0, 0, dx, dy, dz, pXSprite->data1);
    if (pMissile != NULL) {

        // inherit some properties of the generator
        if (pSprite->flags & kModernTypeFlag1) {

            pMissile->xrepeat = pSprite->xrepeat;
            pMissile->yrepeat = pSprite->yrepeat;

            pMissile->pal = pSprite->pal;
            pMissile->shade = pSprite->shade;

        }

        // add velocity controlling
        if (pXSprite->data2 > 0) {

            int velocity = pXSprite->data2 << 12;
            xvel[pMissile->xvel] = mulscale(velocity, dx, 14);
            yvel[pMissile->xvel] = mulscale(velocity, dy, 14);
            zvel[pMissile->xvel] = mulscale(velocity, dz, 14);

        }

        // add bursting for missiles
        if (pMissile->type != kMissileFlareAlt && pXSprite->data4 > 0)
            evPost(pMissile->xvel, 3, (pXSprite->data4 > 500) ? 500 : pXSprite->data4 - 1, kCallbackMissileBurst);

    }

}
//---------------------------------------
#endif

void ProcessMotion(void)
{
    sectortype *pSector;
    int nSector;
    for (pSector = sector, nSector = 0; nSector < numsectors; nSector++, pSector++)
    {
        int nXSector = pSector->extra;
        if (nXSector <= 0)
            continue;
        XSECTOR *pXSector = &xsector[nXSector];
        if (pXSector->bobSpeed != 0)
        {
            if (pXSector->bobAlways)
                pXSector->bobTheta += pXSector->bobSpeed;
            else if (pXSector->busy == 0)
                continue;
            else
                pXSector->bobTheta += mulscale16(pXSector->bobSpeed, pXSector->busy);
            int vdi = mulscale30(Sin(pXSector->bobTheta), pXSector->bobZRange<<8);
            for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
            {
                spritetype *pSprite = &sprite[nSprite];
                if (pSprite->cstat&24576)
                {
                    viewBackupSpriteLoc(nSprite, pSprite);
                    pSprite->z += vdi;
                }
            }
            if (pXSector->bobFloor)
            {
                int floorZ = pSector->floorz;
                viewInterpolateSector(nSector, pSector);
                pSector->floorz = baseFloor[nSector]+vdi;
                for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
                {
                    spritetype *pSprite = &sprite[nSprite];
                    if (pSprite->flags&2)
                        pSprite->flags |= 4;
                    else
                    {
                        int top, bottom;
                        GetSpriteExtents(pSprite, &top, &bottom);
                        if (bottom >= floorZ && (pSprite->cstat&48) == 0)
                        {
                            viewBackupSpriteLoc(nSprite, pSprite);
                            pSprite->z += vdi;
                        }
                    }
                }
            }
            if (pXSector->bobCeiling)
            {
                int ceilZ = pSector->ceilingz;
                viewInterpolateSector(nSector, pSector);
                pSector->ceilingz = baseCeil[nSector]+vdi;
                for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
                {
                    spritetype *pSprite = &sprite[nSprite];
                    int top, bottom;
                    GetSpriteExtents(pSprite, &top, &bottom);
                    if (top <= ceilZ && (pSprite->cstat&48) == 0)
                    {
                        viewBackupSpriteLoc(nSprite, pSprite);
                        pSprite->z += vdi;
                    }
                }
            }
        }
    }
}

void AlignSlopes(void)
{
    sectortype *pSector;
    int nSector;
    for (pSector = sector, nSector = 0; nSector < numsectors; nSector++, pSector++)
    {
        if (qsector_filler[nSector])
        {
            walltype *pWall = &wall[pSector->wallptr+qsector_filler[nSector]];
            walltype *pWall2 = &wall[pWall->point2];
            int nNextSector = pWall->nextsector;
            if (nNextSector >= 0)
            {
                int x = (pWall->x+pWall2->x)/2;
                int y = (pWall->y+pWall2->y)/2;
                viewInterpolateSector(nSector, pSector);
                alignflorslope(nSector, x, y, getflorzofslope(nNextSector, x, y));
                alignceilslope(nSector, x, y, getceilzofslope(nNextSector, x, y));
            }
        }
    }
}

int(*gBusyProc[])(unsigned int, unsigned int) =
{
    VCrushBusy,
    VSpriteBusy,
    VDoorBusy,
    HDoorBusy,
    RDoorBusy,
    StepRotateBusy,
    GenSectorBusy,
    PathBusy
};

void trProcessBusy(void)
{
    memset(velFloor, 0, sizeof(velFloor));
    memset(velCeil, 0, sizeof(velCeil));
    for (int i = gBusyCount-1; i >= 0; i--)
    {
        int oldBusy = gBusy[i].at8;
        gBusy[i].at8 = ClipRange(oldBusy+gBusy[i].at4*4, 0, 65536);
        int nStatus = gBusyProc[gBusy[i].atc](gBusy[i].at0, gBusy[i].at8);
        switch (nStatus) {
            case 1:
                gBusy[i].at8 = oldBusy;
                break;
            case 2:
                gBusy[i].at8 = oldBusy;
                gBusy[i].at4 = -gBusy[i].at4;
                break;
            case 3:
                gBusy[i] = gBusy[--gBusyCount];
                break;
        }
    }
    ProcessMotion();
    AlignSlopes();
}

void InitGenerator(int);

void trInit(void)
{
    gBusyCount = 0;
    for (int i = 0; i < numwalls; i++)
    {
        baseWall[i].x = wall[i].x;
        baseWall[i].y = wall[i].y;
    }
    for (int i = 0; i < kMaxSprites; i++)
    {
        if (sprite[i].statnum < kStatFree)
        {
            sprite[i].inittype = sprite[i].type;
            baseSprite[i].x = sprite[i].x;
            baseSprite[i].y = sprite[i].y;
            baseSprite[i].z = sprite[i].z;
        }
        else
            sprite[i].inittype = -1;
    }
    for (int i = 0; i < numwalls; i++)
    {
        int nXWall = wall[i].extra;
        dassert(nXWall < kMaxXWalls);
        if (nXWall > 0)
        {
            XWALL *pXWall = &xwall[nXWall];
            if (pXWall->state)
                pXWall->busy = 65536;
        }
    }
    dassert((numsectors >= 0) && (numsectors < kMaxSectors));
    for (int i = 0; i < numsectors; i++)
    {
        sectortype *pSector = &sector[i];
        baseFloor[i] = pSector->floorz;
        baseCeil[i] = pSector->ceilingz;
        int nXSector = pSector->extra;
        if (nXSector > 0)
        {
            dassert(nXSector < kMaxXSectors);
            XSECTOR *pXSector = &xsector[nXSector];
            if (pXSector->state)
                pXSector->busy = 65536;
            switch (pSector->type)
            {
            case kSectorCounter:
                // no need to trigger once it, instead lock so it can be unlocked and used again.
                #ifdef NOONE_EXTENSIONS 
                    if (!gModernMap) 
                #endif
                    pXSector->triggerOnce = 1;
                evPost(i, 6, 0, kCallbackCounterCheck);
                break;
            case kSectorZMotion:
            case kSectorZMotionSprite:
                ZTranslateSector(i, pXSector, pXSector->busy, 1);
                break;
            case kSectorSlideMarked:
            case kSectorSlide:
            {
                spritetype *pSprite1 = &sprite[pXSector->marker0];
                spritetype *pSprite2 = &sprite[pXSector->marker1];
                TranslateSector(i, 0, -65536, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->type == kSectorSlide);
                for (int j = 0; j < pSector->wallnum; j++)
                {
                    baseWall[pSector->wallptr+j].x = wall[pSector->wallptr+j].x;
                    baseWall[pSector->wallptr+j].y = wall[pSector->wallptr+j].y;
                }
                for (int nSprite = headspritesect[i]; nSprite >= 0; nSprite = nextspritesect[nSprite])
                {
                    baseSprite[nSprite].x = sprite[nSprite].x;
                    baseSprite[nSprite].y = sprite[nSprite].y;
                    baseSprite[nSprite].z = sprite[nSprite].z;
                }
                TranslateSector(i, 0, pXSector->busy, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->type == kSectorSlide);
                ZTranslateSector(i, pXSector, pXSector->busy, 1);
                break;
            }
            case kSectorRotateMarked:
            case kSectorRotate:
            {
                spritetype *pSprite1 = &sprite[pXSector->marker0];
                TranslateSector(i, 0, -65536, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, 0, pSprite1->x, pSprite1->y, pSprite1->ang, pSector->type == kSectorRotate);
                for (int j = 0; j < pSector->wallnum; j++)
                {
                    baseWall[pSector->wallptr+j].x = wall[pSector->wallptr+j].x;
                    baseWall[pSector->wallptr+j].y = wall[pSector->wallptr+j].y;
                }
                for (int nSprite = headspritesect[i]; nSprite >= 0; nSprite = nextspritesect[nSprite])
                {
                    baseSprite[nSprite].x = sprite[nSprite].x;
                    baseSprite[nSprite].y = sprite[nSprite].y;
                    baseSprite[nSprite].z = sprite[nSprite].z;
                }
                TranslateSector(i, 0, pXSector->busy, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, 0, pSprite1->x, pSprite1->y, pSprite1->ang, pSector->type == kSectorRotate);
                ZTranslateSector(i, pXSector, pXSector->busy, 1);
                break;
            }
            case kSectorPath:
                InitPath(i, pXSector);
                break;
            default:
                break;
            }
        }
    }
    for (int i = 0; i < kMaxSprites; i++)
    {
        int nXSprite = sprite[i].extra;
        if (sprite[i].statnum < kStatFree && nXSprite > 0)
        {
            dassert(nXSprite < kMaxXSprites);
            XSPRITE *pXSprite = &xsprite[nXSprite];
            if (pXSprite->state)
                pXSprite->busy = 65536;
            switch (sprite[i].type) {
            case kSwitchPadlock:
                pXSprite->triggerOnce = 1;
                break;
            #ifdef NOONE_EXTENSIONS
            case kModernRandom:
            case kModernRandom2:
            case kModernSeqSpawner:
            case kModernObjDataAccumulator:
            case kModernDudeTargetChanger:
            case kModernEffectSpawner:
            case kModernWindGenerator:
            case kGenModernMissileUniversal:
            #endif
            case kGenTrigger:
            case kGenDripWater:
            case kGenDripBlood:
            case kGenMissileFireball:
            case kGenDart:
            case kGenBubble:
            case kGenBubbleMulti:
            case kGenSound:
                InitGenerator(i);
                break;
            case kThingArmedProxBomb:
            #ifdef NOONE_EXTENSIONS
            case kModernThingTNTProx:
            #endif
                pXSprite->Proximity = 1;
                break;
            case kThingFallingRock:
                if (pXSprite->state) sprite[i].flags |= 7;
                else sprite[i].flags &= ~7;
                break;
            }
            if (pXSprite->Vector) sprite[i].cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
            if (pXSprite->Push) sprite[i].cstat |= 4096;
        }
    }
    
    evSend(0, 0, kChannelLevelStart, kCmdOn);
    switch (gGameOptions.nGameType) {
        case 1:
            evSend(0, 0, kChannelLevelStartCoop, kCmdOn);
            break;
        case 2:
            evSend(0, 0, kChannelLevelStartMatch, kCmdOn);
            break;
        case 3:
            evSend(0, 0, kChannelLevelStartMatch, kCmdOn);
            evSend(0, 0, kChannelLevelStartTeamsOnly, kCmdOn);
            break;
    }
}

void trTextOver(int nId)
{
    const char *pzMessage = levelGetMessage(nId);
    if (pzMessage)
        viewSetMessage(pzMessage, VanillaMode() ? 0 : 8, MESSAGE_PRIORITY_INI); // 8: gold
}

void InitGenerator(int nSprite)
{
    dassert(nSprite < kMaxSprites);
    spritetype *pSprite = &sprite[nSprite];
    dassert(pSprite->statnum != kMaxStatus);
    int nXSprite = pSprite->extra;
    dassert(nXSprite > 0);
    XSPRITE *pXSprite = &xsprite[nXSprite];
    switch (sprite[nSprite].type) {
        #ifdef NOONE_EXTENSIONS
        // intialize modern generators
        case kModernRandom:
        case kModernRandom2:
            pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
            pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
            if (pXSprite->state != pXSprite->restState)
                evPost(nSprite, 3, (120 * pXSprite->busyTime) / 10, kCmdRepeat);
            return;
        case kModernDudeTargetChanger:
            pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
            pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
            if (pXSprite->busyTime <= 0) pXSprite->busyTime = 5;
            if (pXSprite->state != pXSprite->restState)
                evPost(nSprite, 3, 0, kCmdRepeat);
            return;
        case kModernEffectSpawner:
        case kModernSeqSpawner:
            if (pXSprite->state != pXSprite->restState)
                evPost(nSprite, 3, 0, kCmdRepeat);
            return;
        case kModernObjDataAccumulator:
            pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
            pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
            if (pXSprite->state != pXSprite->restState)
                evPost(nSprite, 3, 0, kCmdRepeat);
            return;
        case kModernWindGenerator:
            pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
            if (pXSprite->state != pXSprite->restState)
                evPost(nSprite, 3, 0, kCmdRepeat);
            return;
        #endif
        case kGenTrigger:
            pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;
            pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
            break;
    }
    if (pXSprite->state != pXSprite->restState && pXSprite->busyTime > 0)
        evPost(nSprite, 3, (120*(pXSprite->busyTime+Random2(pXSprite->data1)))/10, kCmdRepeat);
}

void ActivateGenerator(int nSprite)
{
    dassert(nSprite < kMaxSprites);
    spritetype *pSprite = &sprite[nSprite];
    dassert(pSprite->statnum != kMaxStatus);
    int nXSprite = pSprite->extra;
    dassert(nXSprite > 0);
    XSPRITE *pXSprite = &xsprite[nXSprite];
    switch (pSprite->type) {
        #ifdef NOONE_EXTENSIONS
            case kModernRandom:
            case kModernRandom2: {
                // let's first search for previously dropped items and remove it
                if (pXSprite->dropMsg > 0) {
                    for (short nItem = headspritestat[kStatItem]; nItem >= 0; nItem = nextspritestat[nItem]) {
                        spritetype* pItem = &sprite[nItem];
                        if ((unsigned int)pItem->type == pXSprite->dropMsg && pItem->x == pSprite->x && pItem->y == pSprite->y && pItem->z == pSprite->z) {
                            gFX.fxSpawn((FX_ID)29, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
                            deletesprite(nItem);
                            break;
                        }
                    }
                }

                // then drop item
                spritetype* pDrop = DropRandomPickupObject(pSprite, pXSprite->dropMsg);

                // check if generator affected by physics
                if (pDrop != NULL && isDebris(pSprite->xvel) != -1 && (pDrop->extra >= 0 || dbInsertXSprite(pDrop->xvel) > 0)) {
                    int nIndex = debrisGetFreeIndex();
                    if (nIndex >= 0) {
                        xsprite[pDrop->extra].physAttr |= kPhysMove | kPhysGravity | kPhysFalling; // must fall always
                        pSprite->cstat &= ~CSTAT_SPRITE_BLOCK;

                        gPhysSpritesList[nIndex] = pDrop->xvel; 
                        if (nIndex >= gPhysSpritesCount) gPhysSpritesCount++;
                        getSpriteMassBySize(pDrop); // create mass cache
                    }
                }
                break;
            }
        #endif
        case kGenDripWater:
        case kGenDripBlood: {
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            actSpawnThing(pSprite->sectnum, pSprite->x, pSprite->y, bottom, (pSprite->type == kGenDripWater) ? kThingDripWater : kThingDripBlood);
            break;
        }
        case kGenSound: {
            // allow custom pitch and volume for sounds in SFX gen.
            #ifdef NOONE_EXTENSIONS
                if (!gModernMap) sfxPlay3DSound(pSprite, pXSprite->data2, -1, 0);
                else {
                    int pitch = pXSprite->data4 << 1; if (pitch < 2000) pitch = 0;
                    sfxPlay3DSoundCP(pSprite, pXSprite->data2, -1, 0, pitch, pXSprite->data3);
                }
            #else
                sfxPlay3DSound(pSprite, pXSprite->data2, -1, 0);
            #endif

            break;
        }
        case kGenMissileFireball:
            switch (pXSprite->data2) {
                case 0:
                    FireballTrapSeqCallback(3, nXSprite);
                    break;
                case 1:
                    seqSpawn(35, 3, nXSprite, nFireballTrapClient);
                    break;
                case 2:
                    seqSpawn(36, 3, nXSprite, nFireballTrapClient);
                    break;
            }
            break;
            #ifdef NOONE_EXTENSIONS
            // kGenEctoSkull gen can now fire any missile
            case kGenModernMissileUniversal:
                if (gModernMap) UniMissileTrapSeqCallback(3, nXSprite);
                break;
            #endif
        case kGenBubble:
        case kGenBubbleMulti: {
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            gFX.fxSpawn((pSprite->type == kGenBubble) ? FX_23 : FX_26, pSprite->sectnum, pSprite->x, pSprite->y, top, 0);
            break;
        }
    }
}

void FireballTrapSeqCallback(int, int nXSprite)
{
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int nSprite = pXSprite->reference;
    spritetype *pSprite = &sprite[nSprite];
    if (pSprite->cstat&32)
        actFireMissile(pSprite, 0, 0, 0, 0, (pSprite->cstat&8) ? 0x4000 : -0x4000, kMissileFireball);
    else
        actFireMissile(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, kMissileFireball);
}


void MGunFireSeqCallback(int, int nXSprite)
{
    int nSprite = xsprite[nXSprite].reference;
    spritetype *pSprite = &sprite[nSprite];
    XSPRITE *pXSprite = &xsprite[nXSprite];
    if (pXSprite->data2 > 0 || pXSprite->data1 == 0)
    {
        if (pXSprite->data2 > 0)
        {
            pXSprite->data2--;
            if (pXSprite->data2 == 0)
                evPost(nSprite, 3, 1, kCmdOff);
        }
        int dx = (Cos(pSprite->ang)>>16)+Random2(1000);
        int dy = (Sin(pSprite->ang)>>16)+Random2(1000);
        int dz = Random2(1000);
        actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_2);
        sfxPlay3DSound(pSprite, 359, -1, 0);
    }
}

void MGunOpenSeqCallback(int, int nXSprite)
{
    seqSpawn(39, 3, nXSprite, nMGunFireClient);
}

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

void TriggersLoadSave::Load()
{
    Read(&gBusyCount, sizeof(gBusyCount));
    Read(gBusy, sizeof(gBusy));
    Read(basePath, sizeof(basePath));
}

void TriggersLoadSave::Save()
{
    Write(&gBusyCount, sizeof(gBusyCount));
    Write(gBusy, sizeof(gBusy));
    Write(basePath, sizeof(basePath));
}

static TriggersLoadSave *myLoadSave;

void TriggersLoadSaveConstruct(void)
{
    myLoadSave = new TriggersLoadSave();
}

END_BLD_NS
