//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "compat.h"

#include "keys.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "weapon.h"
#include "sprite.h"
#include "actor.h"

#if MEGAWANG
#include "achievements.h"
#endif

extern long jump_grav;

extern STATE s_DebrisNinja[];
extern STATE s_DebrisRat[];
extern STATE s_DebrisCrab[];
extern STATE s_DebrisStarFish[];
extern STATE s_NinjaDieSliced[];

extern STATEp sg_NinjaGrabThroat[];

int DoActorStopFall(short SpriteNum);


int
DoScaleSprite(short SpriteNum)
    {
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    long scale_value;

    if (u->scale_speed)
        {
        u->scale_value += u->scale_speed * ACTORMOVETICS;

        scale_value = u->scale_value >> 8;

        if (u->scale_speed > 0)
            {
            if (scale_value > u->scale_tgt)
                u->scale_speed = 0;
            else
                sp->xrepeat = sp->yrepeat = scale_value;
            }
        else
            {
            if (scale_value < u->scale_tgt)
                u->scale_speed = 0;
            else
                sp->xrepeat = sp->yrepeat = scale_value;
            }

        }

    return(0);
    }

int
DoActorDie(short SpriteNum, short weapon)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = &sprite[SpriteNum];


    change_sprite_stat(SpriteNum, STAT_DEAD_ACTOR);
    SET(u->Flags, SPR_DEAD);
    RESET(u->Flags, SPR_FALLING | SPR_JUMPING);
    u->floor_dist = Z(40);

    // test for gibable dead bodies
    SET(sp->extra, SPRX_BREAKABLE);
    SET(sp->cstat, CSTAT_SPRITE_BREAKABLE);

    if (weapon < 0)
        {
        // killed by one of these non-sprites
        switch (weapon)
            {
        case WPN_NM_LAVA:
            ChangeState(SpriteNum, u->StateEnd);
            u->RotNum = 0;
            break;

        case WPN_NM_SECTOR_SQUISH:
            ChangeState(SpriteNum, u->StateEnd);
            u->RotNum = 0;
            break;
            }

        return(0);
        }


    // killed by one of these sprites
    switch (User[weapon]->ID)
        {
        // Coolie actually explodes himself
        // he is the Sprite AND Weapon
        case COOLIE_RUN_R0:
            ChangeState(SpriteNum, u->StateEnd);
            u->RotNum = 0;
            sp->xvel <<= 1;
            u->ActorActionFunc = NULL;
            sprite[SpriteNum].ang = NORM_ANGLE(sprite[SpriteNum].ang + 1024);
            break;

        case NINJA_RUN_R0:
            if(u->ID == NINJA_RUN_R0)  // Cut in half!
            {
                SPRITEp wp = &sprite[weapon];

                if(User[weapon]->WeaponNum != WPN_FIST)
                    {
                    //SpawnBlood(SpriteNum, SpriteNum, -1, -1, -1, -1);
                    InitPlasmaFountain(wp, sp);
                    InitPlasmaFountain(wp, sp);
                    PlaySound(DIGI_NINJAINHALF,&sp->x,&sp->y,&sp->z,v3df_none);
                    ChangeState(SpriteNum, &s_NinjaDieSliced[0]);
#if MEGAWANG
                    recordCutInHalf();
#endif
                    } else
                    {
                    if(RANDOM_RANGE(1000) > 500)
                        {
                        SPRITEp wp = &sprite[weapon];
                        InitPlasmaFountain(wp, sp);
                        }

                    ChangeState(SpriteNum, u->StateEnd);
                    u->RotNum = 0;
                    u->ActorActionFunc = NULL;
                    sp->xvel = 200 + RANDOM_RANGE(200);
                    u->jump_speed = -200 - RANDOM_RANGE(250);
                    DoActorBeginJump(SpriteNum);
                    sprite[SpriteNum].ang = sprite[weapon].ang;
                    }
            }
            else
            {
                // test for gibable dead bodies
                if (RANDOM_RANGE(1000) > 500)
                    SET(sp->cstat, CSTAT_SPRITE_YFLIP);
                ChangeState(SpriteNum, u->StateEnd);
	            sp->xvel = 0;
   		        u->jump_speed = 0;
    	        DoActorBeginJump(SpriteNum);
            }

            u->RotNum = 0;

            u->ActorActionFunc = NULL;
            //u->ActorActionFunc = NullAnimator;
            sprite[SpriteNum].ang = sprite[weapon].ang;
            break;

        case COOLG_RUN_R0:
        case SKEL_RUN_R0:
        case RIPPER_RUN_R0:
        case RIPPER2_RUN_R0:
        case EEL_RUN_R0:
        case STAR1:
        case SUMO_RUN_R0:
            ChangeState(SpriteNum, u->StateEnd);
            u->RotNum = 0;
            break;

        case UZI_SMOKE:
                if (RANDOM_RANGE(1000) > 500)
                    SET(sp->cstat, CSTAT_SPRITE_YFLIP);
                ChangeState(SpriteNum, u->StateEnd);
                u->RotNum = 0;
                // Rippers still gotta jump or they fall off walls weird
                if(u->ID == RIPPER_RUN_R0 || u->ID == RIPPER2_RUN_R0)
                    {
                    sp->xvel <<= 1;
                    u->jump_speed = -100 - RANDOM_RANGE(250);
                    DoActorBeginJump(SpriteNum);
                    } else
                    {
                    sp->xvel = 0;
                    u->jump_speed = -10 - RANDOM_RANGE(25);
                    DoActorBeginJump(SpriteNum);
                    }
                u->ActorActionFunc = NULL;
                // Get angle to player
                sp->ang = NORM_ANGLE(getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y) + 1024);
            break;

        case UZI_SMOKE+1: // Shotgun
                if (RANDOM_RANGE(1000) > 500)
                    SET(sp->cstat, CSTAT_SPRITE_YFLIP);
                ChangeState(SpriteNum, u->StateEnd);
                u->RotNum = 0;

                // Rippers still gotta jump or they fall off walls weird
                if(u->ID == RIPPER_RUN_R0 || u->ID == RIPPER2_RUN_R0)
                    {
                    sp->xvel = 75 + RANDOM_RANGE(100);
                    u->jump_speed = -100 - RANDOM_RANGE(150);
                    } else
                    {
                    sp->xvel = 100 + RANDOM_RANGE(200);
                    u->jump_speed = -100 - RANDOM_RANGE(250);
                    }
                DoActorBeginJump(SpriteNum);
                u->ActorActionFunc = NULL;
                // Get angle to player
                sp->ang = NORM_ANGLE(getangle(u->tgt_sp->x - sp->x, u->tgt_sp->y - sp->y) + 1024);
            break;

        default:
            ASSERT(weapon >= 0);
            switch (u->ID)
                {
                case SKULL_R0:
                case BETTY_R0:
                    ChangeState(SpriteNum, u->StateEnd);
                    break;

                default:
                    if(RANDOM_RANGE(1000) > 700)
                        {
                        SPRITEp wp = &sprite[weapon];
                        InitPlasmaFountain(wp, sp);
                        }

                    if (RANDOM_RANGE(1000) > 500)
                        SET(sp->cstat, CSTAT_SPRITE_YFLIP);
                    ChangeState(SpriteNum, u->StateEnd);
                    u->RotNum = 0;
                    u->ActorActionFunc = NULL;
                    sp->xvel = 300 + RANDOM_RANGE(400);
                    u->jump_speed = -300 - RANDOM_RANGE(350);
                    DoActorBeginJump(SpriteNum);
                    sprite[SpriteNum].ang = sprite[weapon].ang;
                    break;
                }
            break;
        }

    // These are too big to flip upside down
    switch (u->ID)
        {
        case RIPPER2_RUN_R0:
        case COOLIE_RUN_R0:
        case SUMO_RUN_R0:
        case ZILLA_RUN_R0:
            RESET(sp->cstat, CSTAT_SPRITE_YFLIP);
            break;
        }

    u->ID = 0;

    return (0);
    }

VOID
DoDebrisCurrent(SPRITEp sp)
    {
    int xvect, yvect;
    int nx, ny;
    int ret=0;
    USERp u = User[sp - sprite];
    SECT_USERp sectu = SectUser[sp->sectnum];

    //sp->clipdist = (256+128)>>2;

    nx = DIV4(sectu->speed) * (long) sintable[NORM_ANGLE(sectu->ang + 512)] >> 14;
    ny = DIV4(sectu->speed) * (long) sintable[sectu->ang] >> 14;

    // faster than move_sprite
    //move_missile(sp-sprite, nx, ny, 0, Z(2), Z(0), 0, ACTORMOVETICS);
    ret = move_sprite(sp-sprite, nx, ny, 0, u->ceiling_dist, u->floor_dist, 0, ACTORMOVETICS);

    // attempt to move away from wall
    if (ret)
        {
        short rang = RANDOM_P2(2048);

        nx = DIV4(sectu->speed) * (long) sintable[NORM_ANGLE(sectu->ang + rang + 512)] >> 14;
        ny = DIV4(sectu->speed) * (long) sintable[NORM_ANGLE(sectu->ang + rang)] >> 14;

        move_sprite(sp-sprite, nx, ny, 0, u->ceiling_dist, u->floor_dist, 0, ACTORMOVETICS);
        }

    sp->z = u->loz;
    }

int
DoActorSectorDamage(short SpriteNum)
    {
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    SECT_USERp sectu = SectUser[sp->sectnum];
    SECTORp sectp = &sector[sp->sectnum];

    if (u->Health <= 0)
        return(FALSE);

    if (sectu && sectu->damage)
        {
        if (TEST(sectu->flags, SECTFU_DAMAGE_ABOVE_SECTOR))
            {
            if ((u->DamageTics -= synctics) < 0)
                {
                u->DamageTics = 60;
                u->Health -= sectu->damage;

                if (u->Health <= 0)
                    {
                    UpdateSinglePlayKills(SpriteNum);
                    DoActorDie(SpriteNum, WPN_NM_LAVA);
                    return(TRUE);
                    }
                }
            }
        else
        if (SPRITEp_BOS(sp) >= sectp->floorz)
            {
            if ((u->DamageTics -= synctics) < 0)
                {
                u->DamageTics = 60;
                u->Health -= sectu->damage;

                if (u->Health <= 0)
                    {
                    UpdateSinglePlayKills(SpriteNum);
                    DoActorDie(SpriteNum, WPN_NM_LAVA);
                    return(TRUE);
                    }
                }
            }
        }

    // note that most squishing is done in vator.c
    if (u->lo_sectp && u->hi_sectp && labs(u->loz - u->hiz) < DIV2(SPRITEp_SIZE_Z(sp)))
    //if (u->lo_sectp && u->hi_sectp && labs(u->loz - u->hiz) < SPRITEp_SIZE_Z(sp))
        {
        u->Health = 0;
        if (SpawnShrap(SpriteNum, WPN_NM_SECTOR_SQUISH))
            {
            UpdateSinglePlayKills(SpriteNum);
            SetSuicide(SpriteNum);
            }
        else
            {
            ASSERT(TRUE == FALSE);
            //DoActorDie(SpriteNum, WPN_NM_SECTOR_SQUISH);
            }

        return(TRUE);
        }

    return(FALSE);
    }


int
move_debris(short SpriteNum, long xchange, long ychange, long zchange)
    {
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    long nx, ny;

    u->ret = move_sprite(SpriteNum, xchange, ychange, zchange,
        u->ceiling_dist, u->floor_dist, 0, ACTORMOVETICS);

    return (!u->ret);
    }

// !AIC - Supposed to allow floating of DEBRIS (dead bodies, flotsam, jetsam).  Or if water has
// current move with the current.

int
DoActorDebris(short SpriteNum)
    {
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    SECTORp sectp = &sector[sp->sectnum];
    long nx, ny;

    // This was move from DoActorDie so actor's can't be walked through until they are on the floor
    RESET(sp->cstat, CSTAT_SPRITE_BLOCK|CSTAT_SPRITE_BLOCK_HITSCAN);

    // Don't let some actors float
    switch(u->ID)
        {
        case HORNET_RUN_R0:
        case BUNNY_RUN_R0:
            KillSprite(SpriteNum);
            return(0);
        case ZILLA_RUN_R0:
            getzsofslope(sp->sectnum, sp->x, sp->y, &u->hiz, &u->loz);
            u->lo_sectp = &sector[sp->sectnum];
            u->hi_sectp = &sector[sp->sectnum];
            u->lo_sp = NULL;
            u->hi_sp = NULL;
            break;
        }

    if (TEST(sectp->extra, SECTFX_SINK))
        {
        if (TEST(sectp->extra, SECTFX_CURRENT))
            {
            DoDebrisCurrent(sp);
            }
        else
            {
            //nx = sp->xvel * ACTORMOVETICS * (long) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
            //ny = sp->xvel * ACTORMOVETICS * (long) sintable[sp->ang] >> 14;
            nx = ACTORMOVETICS * (long) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
            ny = ACTORMOVETICS * (long) sintable[sp->ang] >> 14;

            //sp->clipdist = (256+128)>>2;

            if (!move_debris(SpriteNum, nx, ny, 0L))
                {
                sp->ang = RANDOM_P2(2048);
                }
            }

        if (SectUser[sp->sectnum] && SectUser[sp->sectnum]->depth > 10)	// JBF: added null check
            {
            u->WaitTics = (u->WaitTics + (ACTORMOVETICS << 3)) & 1023;
            //sp->z = Z(2) + u->loz + ((Z(4) * (long) sintable[u->WaitTics]) >> 14);
            sp->z = u->loz - ((Z(2) * (long) sintable[u->WaitTics]) >> 14);
            }
        }
    else
        {
        sp->z = u->loz;
        }

    return(0);
    }


int
DoFireFly(short SpriteNum)
    {
    SPRITEp sp = &sprite[SpriteNum];
    USERp u = User[SpriteNum];
    long nx, ny;

    nx = 4 * ACTORMOVETICS * (long) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = 4 * ACTORMOVETICS * (long) sintable[sp->ang] >> 14;

    sp->clipdist = 256>>2;
    if (!move_actor(SpriteNum, nx, ny, 0L))
        {
        sp->ang = NORM_ANGLE(sp->ang + 1024);
        }

    u->WaitTics = (u->WaitTics + (ACTORMOVETICS << 1)) & 2047;

    sp->z = u->sz + ((Z(32) * (long) sintable[u->WaitTics]) >> 14);
    return(0);
    }

int
DoGenerateSewerDebris(short SpriteNum)
    {
    SPRITEp sp = &sprite[SpriteNum], np;
    USERp u = User[SpriteNum], nu;
    short n;

    static STATEp Debris[] =
        {
        s_DebrisNinja,
        s_DebrisRat,
        s_DebrisCrab,
        s_DebrisStarFish
        };

    u->Tics -= ACTORMOVETICS;

    if (u->Tics <= 0)
        {
        u->Tics = u->WaitTics;

        n = SpawnSprite(STAT_DEAD_ACTOR, 0, Debris[RANDOM_P2(4<<8)>>8], sp->sectnum, sp->x, sp->y, sp->z, sp->ang, 200);
        np = &sprite[n];
        nu = User[n];

        SetOwner(SpriteNum, n);
        }

    return(0);
    }

// !AIC - Tries to keep actors correctly on the floor.  More that a bit messy.

VOID
KeepActorOnFloor(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    SECTORp sectp;
    long depth;

    sectp = &sector[sp->sectnum];

    RESET(sp->cstat, CSTAT_SPRITE_YFLIP); // If upside down, reset it

    if (TEST(u->Flags, SPR_JUMPING | SPR_FALLING))
        return;

    if (u->lo_sectp && SectUser[u->lo_sectp - sector])
        depth = SectUser[u->lo_sectp - sector]->depth;
    else
        depth = 0;

    if (TEST(sectp->extra, SECTFX_SINK) &&
        depth > 35 &&
        u->ActorActionSet && u->ActorActionSet->Swim)
        {
        if (TEST(u->Flags, SPR_SWIMMING))
            {
            if (u->Rot != u->ActorActionSet->Run && u->Rot != u->ActorActionSet->Swim && u->Rot != u->ActorActionSet->Stand)
                {
                // was swimming but have now stopped
                RESET(u->Flags, SPR_SWIMMING);
                RESET(sp->cstat, CSTAT_SPRITE_YCENTER);
                u->oz = sp->z = u->loz;
                return;
                }

            if (u->Rot == u->ActorActionSet->Run)
                {
                NewStateGroup(SpriteNum, u->ActorActionSet->Swim);
                }

            // are swimming
            u->oz = sp->z = u->loz - Z(depth);
            }
        else
            {
            // only start swimming if you are running
            if (u->Rot == u->ActorActionSet->Run || u->Rot == u->ActorActionSet->Swim)
                {
                NewStateGroup(SpriteNum, u->ActorActionSet->Swim);
                u->oz = sp->z = u->loz - Z(depth);
                SET(u->Flags, SPR_SWIMMING);
                SET(sp->cstat, CSTAT_SPRITE_YCENTER);
                }
            else
                {
                RESET(u->Flags, SPR_SWIMMING);
                RESET(sp->cstat, CSTAT_SPRITE_YCENTER);
                u->oz = sp->z = u->loz;
                }
            }

        return;
        }

    // NOT in a swimming situation
    RESET(u->Flags, SPR_SWIMMING);
    RESET(sp->cstat, CSTAT_SPRITE_YCENTER);

    #if 1
    if (TEST(u->Flags, SPR_MOVED))
        {
        u->oz = sp->z = u->loz;
        }
    else
        {
        long ceilz,ceilhit,florz,florhit;
        FAFgetzrangepoint(sp->x, sp->y, sp->z, sp->sectnum,
            &ceilz, &ceilhit, &florz, &florhit);

        u->oz = sp->z = florz;
        }
    #endif


    }

int
DoActorBeginSlide(short SpriteNum, short ang, short vel, short dec)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    SET(u->Flags, SPR_SLIDING);

    u->slide_ang = ang;
    u->slide_vel = vel;
    u->slide_dec = dec;

    //DoActorSlide(SpriteNum);

    return (0);
    }

// !AIC - Sliding can occur in different directions from movement of the actor.
// Has its own set of variables

int
DoActorSlide(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    long nx, ny;

    nx = u->slide_vel * (long) sintable[NORM_ANGLE(u->slide_ang + 512)] >> 14;
    ny = u->slide_vel * (long) sintable[u->slide_ang] >> 14;

    if (!move_actor(SpriteNum, nx, ny, 0L))
        {
        RESET(u->Flags, SPR_SLIDING);
        return (FALSE);
        }

    u->slide_vel -= u->slide_dec * ACTORMOVETICS;

    if (u->slide_vel < 20)
        {
        RESET(u->Flags, SPR_SLIDING);
        }

    return (TRUE);
    }

// !AIC - Actor jumping and falling

int
DoActorBeginJump(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    SET(u->Flags, SPR_JUMPING);
    RESET(u->Flags, SPR_FALLING);

    // u->jump_speed = should be set before calling

    // set up individual actor jump gravity
    u->jump_grav = ACTOR_GRAVITY;

    // Change sprites state to jumping
    if(u->ActorActionSet)
        {
        if (TEST(u->Flags, SPR_DEAD))
            NewStateGroup(SpriteNum, u->ActorActionSet->DeathJump);
        else
            NewStateGroup(SpriteNum, u->ActorActionSet->Jump);
        }
    u->StateFallOverride = NULL;

    //DO NOT CALL DoActorJump! DoActorStopFall can cause an infinite loop and
    //stack overflow if it is called.
    //DoActorJump(SpriteNum);

    return (0);
    }

int
DoActorJump(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    int jump_adj;

    // precalculate jump value to adjust jump speed by
    jump_adj = u->jump_grav * ACTORMOVETICS;

    // adjust jump speed by gravity - if jump speed greater than 0 player
    // have started falling
    if ((u->jump_speed += jump_adj) > 0)
        {
        //DSPRINTF(ds,"Actor Jump Height %d", labs(sp->z - sector[sp->sectnum].floorz)>>8 );
        MONO_PRINT(ds);

        // Start falling
        DoActorBeginFall(SpriteNum);
        return (0);
        }

    // adjust height by jump speed
    sp->z += u->jump_speed * ACTORMOVETICS;

    // if player gets to close the ceiling while jumping
    if (sp->z < u->hiz + Z(PIC_SIZY(SpriteNum)))
        {
        // put player at the ceiling
        sp->z = u->hiz + Z(PIC_SIZY(SpriteNum));

        // reverse your speed to falling
        u->jump_speed = -u->jump_speed;

        //DSPRINTF(ds,"Jump: sp_num %d, hi_num %d, hi_sect %d",SpriteNum, u->hi_sp - sprite, u->hi_sectp - sector);
        MONO_PRINT(ds);

        // Change sprites state to falling
        DoActorBeginFall(SpriteNum);
        }

    return (0);
    }


int
DoActorBeginFall(short SpriteNum)
    {
    USERp u = User[SpriteNum];

    SET(u->Flags, SPR_FALLING);
    RESET(u->Flags, SPR_JUMPING);

    u->jump_grav = ACTOR_GRAVITY;

    // Change sprites state to falling
    if(u->ActorActionSet)
        {
        if (TEST(u->Flags, SPR_DEAD))
            {
            NewStateGroup(SpriteNum, u->ActorActionSet->DeathFall);
            } else
            NewStateGroup(SpriteNum, u->ActorActionSet->Fall);

        if (u->StateFallOverride)
            {
            NewStateGroup(SpriteNum, u->StateFallOverride);
            }
        }

    DoActorFall(SpriteNum);

    return (0);
    }


int
DoActorFall(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    // adjust jump speed by gravity
    u->jump_speed += u->jump_grav * ACTORMOVETICS;

    // adjust player height by jump speed
    sp->z += u->jump_speed * ACTORMOVETICS;

    // Stick like glue when you hit the ground
    if (sp->z > u->loz)
        {
        DoActorStopFall(SpriteNum);
        }

    return (0);
    }

int
DoActorStopFall(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    sp->z = u->loz;

    RESET(u->Flags, SPR_FALLING | SPR_JUMPING);
    RESET(sp->cstat, CSTAT_SPRITE_YFLIP);


    // don't stand on face or wall sprites - jump again
    if (u->lo_sp && !TEST(u->lo_sp->cstat, CSTAT_SPRITE_FLOOR))
        {
        USERp tu = User[u->lo_sp - sprite];

        //sp->ang = NORM_ANGLE(sp->ang + (RANDOM_P2(64<<8)>>8) - 32);
        sp->ang = NORM_ANGLE(sp->ang + 1024 + (RANDOM_P2(512<<8)>>8));
        u->jump_speed = -350;

        //DSPRINTF(ds,"StopFall: sp_num %d, sp->picnum %d, lo_num %d, lo_sp->picnum %d",SpriteNum, sp->picnum, u->lo_sp - sprite, u->lo_sp->picnum);
        MONO_PRINT(ds);

        DoActorBeginJump(SpriteNum);
        return(0);
        }

    // Change sprites state to running
    if(u->ActorActionSet)
        {
        if (TEST(u->Flags, SPR_DEAD))
            {
            NewStateGroup(SpriteNum, u->ActorActionSet->Dead);
            PlaySound(DIGI_ACTORBODYFALL1,&sp->x,&sp->y,&sp->z,v3df_none);
            }
        else
            {
            PlaySound(DIGI_ACTORHITGROUND,&sp->x,&sp->y,&sp->z,v3df_none);

            NewStateGroup(SpriteNum, u->ActorActionSet->Run);

            if ((u->track >= 0) && (u->jump_speed) > 800 && (u->ActorActionSet->Sit))
                {
                u->WaitTics = 80;
                NewStateGroup(SpriteNum, u->ActorActionSet->Sit);
                }
            }
        }

    return (0);
    }

int
DoActorDeathMove(short SpriteNum)
    {
    ANIMATOR DoFindGround;

    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;
    long nx, ny;

    if (TEST(u->Flags, SPR_JUMPING | SPR_FALLING))
        {
        if (TEST(u->Flags, SPR_JUMPING))
            DoActorJump(SpriteNum);
        else
            DoActorFall(SpriteNum);
        }

    nx = sp->xvel * (long) sintable[NORM_ANGLE(sp->ang + 512)] >> 14;
    ny = sp->xvel * (long) sintable[sp->ang] >> 14;

    sp->clipdist = (128+64)>>2;
    move_actor(SpriteNum, nx, ny, 0);

    // only fall on top of floor sprite or sector
    DoFindGroundPoint(SpriteNum);

    return (0);
    }

// !AIC - Jumping a falling for shrapnel and other stuff, not actors.

int
DoBeginJump(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    SET(u->Flags, SPR_JUMPING);
    RESET(u->Flags, SPR_FALLING);

    // set up individual actor jump gravity
    u->jump_grav = ACTOR_GRAVITY;

    DoJump(SpriteNum);

    return (0);
    }

int
DoJump(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    int jump_adj;

    // precalculate jump value to adjust jump speed by
    jump_adj = u->jump_grav * ACTORMOVETICS;

    // adjust jump speed by gravity - if jump speed greater than 0 player
    // have started falling
    if ((u->jump_speed += jump_adj) > 0)
        {
        // Start falling
        DoBeginFall(SpriteNum);
        return (0);
        }

    // adjust height by jump speed
    sp->z += u->jump_speed * ACTORMOVETICS;

    // if player gets to close the ceiling while jumping
    if (sp->z < u->hiz + Z(PIC_SIZY(SpriteNum)))
        {
        // put player at the ceiling
        sp->z = u->hiz + Z(PIC_SIZY(SpriteNum));

        // reverse your speed to falling
        u->jump_speed = -u->jump_speed;

        // Change sprites state to falling
        DoBeginFall(SpriteNum);
        }

    return (0);
    }


int
DoBeginFall(short SpriteNum)
    {
    USERp u = User[SpriteNum];

    SET(u->Flags, SPR_FALLING);
    RESET(u->Flags, SPR_JUMPING);

    u->jump_grav = ACTOR_GRAVITY;

    DoFall(SpriteNum);

    return (0);
    }

#if 0
int
DoFall(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    // adjust jump speed by gravity
    u->jump_speed += u->jump_grav * ACTORMOVETICS;

    // adjust player height by jump speed
    sp->z += u->jump_speed * ACTORMOVETICS;

    // Stick like glue when you hit the ground
    if (sp->z > u->loz)
        {
        sp->z = u->loz;
        RESET(u->Flags, SPR_FALLING);
        }

    return (0);
    }
#else
int
DoFall(short SpriteNum)
    {
    USERp u = User[SpriteNum];
    SPRITEp sp = User[SpriteNum]->SpriteP;

    // adjust jump speed by gravity
    u->jump_speed += u->jump_grav * ACTORMOVETICS;

    // adjust player height by jump speed
    sp->z += u->jump_speed * ACTORMOVETICS;

    // Stick like glue when you hit the ground
    if (sp->z > u->loz - u->floor_dist)
        {
        sp->z = u->loz - u->floor_dist;
        RESET(u->Flags, SPR_FALLING);
        }

    return (0);
    }
#endif


#include "saveable.h"

static saveable_code saveable_actor_code[] = {
	SAVE_CODE(DoScaleSprite),
	SAVE_CODE(DoActorDie),
	SAVE_CODE(DoDebrisCurrent),
	SAVE_CODE(DoActorSectorDamage),
	SAVE_CODE(DoActorDebris),
	SAVE_CODE(DoFireFly),
	SAVE_CODE(DoGenerateSewerDebris),
	SAVE_CODE(KeepActorOnFloor),
	SAVE_CODE(DoActorBeginSlide),
	SAVE_CODE(DoActorSlide),
	SAVE_CODE(DoActorBeginJump),
	SAVE_CODE(DoActorJump),
	SAVE_CODE(DoActorBeginFall),
	SAVE_CODE(DoActorFall),
	SAVE_CODE(DoActorStopFall),
	SAVE_CODE(DoActorDeathMove),
	SAVE_CODE(DoBeginJump),
	SAVE_CODE(DoJump),
	SAVE_CODE(DoBeginFall),
	SAVE_CODE(DoFall)
};

saveable_module saveable_actor = {
	// code
	saveable_actor_code,
	SIZ(saveable_actor_code),

	// data
	NULL,0
};
