#ifndef H_ENEMY
#define H_ENEMY

#include "character.h"
#include "objects.h"

#define STALK_BOX       (1024 * 3)
#define ESCAPE_BOX      (1024 * 5)
#define ATTACK_BOX      STALK_BOX

#define MAX_SHOT_DIST   (64 * 1024)

struct Enemy : Character {

    struct Path {
        int16       index;
        int16       count;
        uint16      *boxes;
        TR::Level   *level;

        Path(TR::Level *level, uint16 *boxes, int count) : index(0), count(count), boxes(new uint16[count]), level(level) {
            memcpy(this->boxes, boxes, count * sizeof(boxes[0]));
        }

        ~Path() {
            delete[] boxes;
        }

        bool getNextPoint(TR::Level *level, vec3 &point) {
            if (index >= count - 1)
                return false;

            TR::Box &a = level->boxes[boxes[index++]];
            TR::Box &b = level->boxes[boxes[index]];

            int minX = max(a.minX, b.minX);
            int minZ = max(a.minZ, b.minZ);
            int maxX = min(a.maxX, b.maxX);
            int maxZ = min(a.maxZ, b.maxZ);

            point.x = float(minX + 512) + randf() * (maxX - minX - 1024);
            point.y = float((a.floor + b.floor) / 2);
            point.z = float(minZ + 512) + randf() * (maxZ - minZ - 1024);

            return true;
        }
    };

    enum AI {
        AI_FIXED, AI_RANDOM
    } ai;

    enum Mood {
        MOOD_SLEEP, MOOD_STALK, MOOD_ATTACK, MOOD_ESCAPE 
    } mood;

    bool  wound;
    int   nextState;

    uint16 targetBox;
    vec3   waypoint;

    float thinkTime;
    float length;       // dist from center to head (jaws)
    float aggression;
    int   radius;
    int   hitSound;

    Character *target;
    Path      *path;

    float targetDist;
    float targetAngle;
    bool  targetDead;
    bool  targetInView;     // target in enemy view zone
    bool  targetFromView;   // enemy in target view zone
    bool  targetCanAttack;

    Enemy(IGame *game, int entity, float health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(TR::NO_BOX), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), hitSound(-1), target(NULL), path(NULL) {
        targetDist   = +INF;
        targetInView = targetFromView = targetCanAttack = false;
        waypoint     = pos;
    }

    virtual ~Enemy() {
        delete path;
    }

    virtual bool getSaveData(SaveEntity &data) {
        Character::getSaveData(data);
        data.extraSize = sizeof(data.extra.enemy);
        data.extra.enemy.health    = health;
        data.extra.enemy.spec.mood = mood;
        data.extra.enemy.targetBox = targetBox;
        return true;
    }

    virtual void setSaveData(const SaveEntity &data) {
        Character::setSaveData(data);
        health    = data.extra.enemy.health;
        mood      = Mood(data.extra.enemy.spec.mood);
        targetBox = data.extra.enemy.targetBox;
        updateZone();
    }

    virtual bool activate() {
        return health > 0.0f && Character::activate();
    }

    virtual void updateVelocity() {
        if (stand == STAND_AIR && (!flying || health <= 0.0f))
            applyGravity(velocity.y);
        else
            velocity = getDir() * animation.getSpeed();

        if (health <= 0.0f) 
            velocity.x = velocity.z = 0.0f;
    }

    bool checkPoint(int x, int z) {
        TR::Box &a = level->boxes[box];
        if (a.contains(x, z))
            return true;

        bool big = getEntity().isBigEnemy();
        TR::Overlap *o = &level->overlaps[a.overlap.index];
        do {
            TR::Box &b = level->boxes[o->boxIndex];
            if (!b.contains(x, z))
                continue;
            if (big && b.overlap.blockable)
                continue;
            if (getZones()[o->boxIndex] == zone) {
                int d = a.floor - b.floor;
                if (d <= stepHeight && d >= dropHeight)
                    return true;
            }
        } while (!(o++)->end);

        return false;
    }

    void clipByBox(vec3 &pos) {
        int px = int(pos.x);
        int pz = int(pos.z);
        int nx = px;
        int nz = pz;

        TR::Box &a = level->boxes[box];
            
        if (!checkPoint(px - radius, pz)) nx = a.minX + radius;
        if (!checkPoint(px + radius, pz)) nx = a.maxX - radius;
        if (!checkPoint(px, pz - radius)) nz = a.minZ + radius;
        if (!checkPoint(px, pz + radius)) nz = a.maxZ - radius;

        if (px != nx) pos.x = float(nx);
        if (pz != nz) pos.z = float(nz);
    }

    void collideEnemies() {
        if (getEntity().isBigEnemy())
            return;

        Controller *c = Controller::first;
        while (c) {
            if (c != this && c->getEntity().isEnemy()) {
                Enemy *enemy = (Enemy*)c;
                if (enemy->health > 0.0f) {
                    vec3 dir = vec3(enemy->pos.x - pos.x, 0.0f, enemy->pos.z - pos.z);
                    float D = dir.length2();
                    float R = float((enemy->radius + radius) / 2);
                    if (D < R * R) {
                        D = sqrtf(D);
                        pos -= dir.normal() * (R - D);
                    }
                }
            }
            c = c->next;
        }
    }

    virtual void updatePosition() {
        if (!flags.active) return;

        vec3 p = pos;
        pos += velocity * (30.0f * Core::deltaTime);

        collideEnemies();

        clipByBox(pos);

        TR::Level::FloorInfo info;
        getFloorInfo(getRoomIndex(), pos, info);
        if (stand == STAND_AIR && !flying && info.floor < pos.y) {
            stand = STAND_GROUND;
            pos.y = info.floor;
        }

        if (info.boxIndex != 0xFFFF && zone == getZones()[info.boxIndex] && !level->boxes[info.boxIndex].overlap.block) {
            switch (stand) {
                case STAND_GROUND : {
                    float fallSpeed = 2048.0f * Core::deltaTime;
                    decrease(info.floor - pos.y, pos.y, fallSpeed);
                    break;
                }
                case STAND_AIR    : 
                    pos.y = clamp(pos.y, info.ceiling, info.floor);
                    break;
                default : ;
            }
        } else
            pos = p;

        updateRoom();
    }

    void setOverrides(bool active, int chest, int head) {
        if (active && head  > -1) {
            animation.overrides[head] = animation.getJointRot(head);
            animation.overrideMask |=  (1 << head);
        } else
            animation.overrideMask &= ~(1 << head);

        if (active && chest  > -1) {
            animation.overrides[chest] = animation.getJointRot(chest);
            animation.overrideMask |=  (1 << chest);
        } else
            animation.overrideMask &= ~(1 << chest);
    }

    bool targetIsVisible(float maxDist) {
        if (targetInView && targetDist < maxDist && target->health > 0.0f) {
            TR::Location from, to;
            from.room = getRoomIndex();
            from.pos  = pos;
            to.pos    = target->pos;

        // vertical offset to ~gun/head height
            from.pos.y -= 768.0f;
            if (target->stand != STAND_UNDERWATER && target->stand != STAND_ONWATER)
                to.pos.y -= 768.0f;

            return trace(from, to);
        }
        return false;
    }

    virtual void lookAt(Controller *target) {
        Character::lookAt(targetInView ? target : NULL);
    }

    void turn(bool tilt, float w) {
        float speed = animation.getSpeed();

        if (!target || speed == 0.0f || w == 0.0f) {
            angle.z = lerp(angle.z, 0.0f, 4.0f * Core::deltaTime);
            return;
        }

        vec3  d = waypoint - pos;
        float a = clampAngle(normalizeAngle(PIH - d.angleY() - angle.y));

        w /= 30.0f;

        float minDist = speed * PIH / w;

        if ( (a > PIH || a < -PIH) && (SQR(d.x) + SQR(d.z) < SQR(minDist)) )
            w *= 0.5f;

        a = clamp(a, -w, w);

        angle.y += a * 30.0f * Core::deltaTime;
        angle.z = lerp(angle.z, tilt ? a * 2.0f : 0.0f, 4.0f * Core::deltaTime);
    }

    int lift(float delta, float speed) {
        speed *= Core::deltaTime;
        decrease(delta, pos.y, speed);
        if (speed != 0.0f) {
            return speed < 0 ? FORTH : BACK;
        }
        return 0;
    }

    virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
        if (hitSound > -1 && health > 0.0f) 
            game->playSound(hitSound, pos, Sound::PAN);
        Character::hit(damage, enemy, hitType);
        wound = true;
    };

    void bite(int joint, const vec3 &offset, float damage) {
        ASSERT(target);
        target->hit(damage, this);
        if (joint >= 0)
            game->addEntity(TR::Entity::BLOOD, target->getRoomIndex(), getJoint(joint) * offset);
    }

    Mood getMoodFixed() {
        bool inZone = zone == target->zone;

        if (mood == MOOD_SLEEP || mood == MOOD_STALK)
            return inZone ? MOOD_ATTACK : (wound ? MOOD_ESCAPE : mood);
        
        if (mood == MOOD_ATTACK)
            return inZone ? mood : MOOD_SLEEP;

        return inZone ? MOOD_ATTACK : mood;
    }

    Mood getMoodRandom() {
        bool inZone = zone == target->zone;
        bool brave  = rand() < (mood != MOOD_ESCAPE ? 0x7800 : 0x0100) && inZone;
            
        if (mood == MOOD_SLEEP || mood == MOOD_STALK) {
            if (wound && !brave)
                return MOOD_ESCAPE;
            if (inZone) {
                int dx = abs(int(pos.x - target->pos.x));
                int dz = abs(int(pos.z - target->pos.z));
                return ((dx <= ATTACK_BOX && dz <= ATTACK_BOX) || (mood == MOOD_STALK && targetBox == TR::NO_BOX)) ? MOOD_ATTACK : MOOD_STALK;
            }
            return mood;
        }

        if (mood == MOOD_ATTACK)
            return (wound && !brave) ? MOOD_ESCAPE : (!inZone ? MOOD_SLEEP : mood);

        return brave ? MOOD_STALK : mood;
    }
    
    bool think(bool fixedLogic) {
        thinkTime += Core::deltaTime;
        if (thinkTime < 1.0f / 30.0f)
            return false;
        thinkTime -= 1.0f / 30.0f;

        int zoneOld = zone;
        updateZone();

        target = (Character*)game->getLara(pos);

        vec3 targetVec  = target->pos - pos - getDir() * length;
        targetDist      = targetVec.length();
        targetAngle     = clampAngle(atan2f(targetVec.x, targetVec.z) - angle.y);
        targetDead      = target->health <= 0;
        targetInView    = targetVec.dot(getDir()) > 0;
        targetFromView  = targetVec.dot(target->getDir()) < 0;
        targetCanAttack = targetInView && fabsf(targetVec.y) <= 256.0f;

        int targetBoxOld = targetBox;

        bool inZone = zone == target->zone;

        if (target->health <= 0.0f || !inZone)
            targetBox = TR::NO_BOX;

    // update mood
        if (mood != MOOD_ATTACK && targetBox != TR::NO_BOX && !checkBox(targetBox)) {
            if (!inZone)
                mood = MOOD_SLEEP;
            targetBox = TR::NO_BOX;
        }

        mood = target->health <= 0 ? MOOD_SLEEP : (ai == AI_FIXED ? getMoodFixed() : getMoodRandom());

    // set behavior and target
        int box;

        switch (mood) {
            case MOOD_SLEEP :
                if (targetBox == TR::NO_BOX && checkBox(box = getRandomZoneBox()) && isStalkBox(box)) {
                    mood = MOOD_STALK;
                    gotoBox(box);
                }
                break;
            case MOOD_STALK :
                if ((targetBox == TR::NO_BOX || !isStalkBox(targetBox)) && checkBox(box = getRandomZoneBox())) {
                    if (isStalkBox(box))
                        gotoBox(box);
                    else
                        if (targetBox == TR::NO_BOX) {
                            if (!inZone)
                                mood = MOOD_SLEEP;
                            gotoBox(box);
                        }
                }
                break;
            case MOOD_ATTACK :
                if (randf() > aggression)
                    break;
                targetBox = TR::NO_BOX;
                break;
            case MOOD_ESCAPE :
                if (targetBox == TR::NO_BOX && checkBox(box = getRandomZoneBox())) {
                    if (isEscapeBox(box))
                        gotoBox(box);
                    else
                        if (inZone && isStalkBox(box)) {
                            mood = MOOD_STALK;
                            gotoBox(box);
                        }                    
                }
                break;
        }

        if (targetBox == TR::NO_BOX)
            gotoBox(target->box);

        if (path && this->box != path->boxes[path->index - 1] && this->box != path->boxes[path->index])
            targetBoxOld = TR::NO_BOX;

        if (zoneOld != zone)
            targetBoxOld = TR::NO_BOX;

        if (targetBoxOld != targetBox) {
            if (findPath(stepHeight, dropHeight, getEntity().isBigEnemy()))
                nextWaypoint();
            else
                targetBox = TR::NO_BOX;
        }

        if (targetBox != TR::NO_BOX && path) {
            vec3 d = pos - waypoint;

            if (fabsf(d.x) < 512 && fabsf(d.y) < 512 && fabsf(d.z) < 512)
                nextWaypoint();
        }

        return true;
    }

    void nextWaypoint() {
        if (!path->getNextPoint(level, waypoint))
            waypoint = target->pos;
    }

    uint16 getRandomZoneBox() {
        return game->getRandomBox(zone, getZones());
    }

    void gotoBox(int box) {
        targetBox = box;
    }

    bool checkBox(int box) {
        if (zone != getZones()[box])
            return false;

        TR::Box    &b = game->getLevel()->boxes[box];
        uint16   type = getEntity().type;

        if (b.overlap.block)
            return false;

        if (type == TR::Entity::ENEMY_REX || type == TR::Entity::ENEMY_MUTANT_1 || type == TR::Entity::ENEMY_CENTAUR) {
            if (b.overlap.blockable)
                return false;
        } else
            if (b.overlap.block)
                return false;

        return int(pos.x) < int(b.minX) || int(pos.x) > int(b.maxX) || int(pos.z) < int(b.minZ) || int(pos.z) > int(b.maxZ);
    }

    bool isStalkBox(int box) {
        TR::Box    &b = game->getLevel()->boxes[box];

        int x = (b.minX + b.maxX) / 2 - int(target->pos.x);
        if (abs(x) > STALK_BOX) return false;

        int z = (b.minZ + b.maxZ) / 2 - int(target->pos.z);
        if (abs(z) > STALK_BOX) return false;

        int target_quadrant = angleQuadrant(target->angle.y, 0.0);
        int box_quadrant = z > 0 ? (x > 0 ? 2 : 1) : (x > 0 ? 3 : 0);

        if (target_quadrant == box_quadrant) return false;

        int controller_quadrant = pos.z > target->pos.z ? (pos.x > target->pos.x ? 2 : 1) : (pos.x > target->pos.x ? 3 : 0);

        if (target_quadrant == controller_quadrant && abs(target_quadrant - box_quadrant) == 2) return false;

        return true;
    }

    bool isEscapeBox(int box) {
        TR::Box    &b = game->getLevel()->boxes[box];

        int x = (b.minX + b.maxX) / 2 - int(target->pos.x);
        if (abs(x) < ESCAPE_BOX) return false;

        int z = (b.minZ + b.maxZ) / 2 - int(target->pos.z);
        if (abs(z) < ESCAPE_BOX) return false;

        return !((pos.x > target->pos.x) ^ (x > 0)) || !((pos.z > target->pos.z) ^ (z > 0));
    }

    bool findPath(int ascend, int descend, bool big) {
        delete path;
        path = NULL;

        uint16 *boxes;
        uint16 count = game->findPath(ascend, descend, big, box, targetBox, getZones(), &boxes);
        if (count) {
            path = new Path(level, boxes, count);
            return true;
        }

        return false;
    }

    void shot(TR::Entity::Type type, int joint, const vec3 &offset, float rx, float ry) {
        vec3 from = getJoint(joint) * offset;
        vec3 to   = target->getBoundingBox().center();

        Bullet *bullet = (Bullet*)game->addEntity(type, getRoomIndex(), from);
        if (bullet) {
            vec3 dir = to - from;
            vec3 ang = vec3(-atan2f(dir.y, sqrtf(dir.x * dir.x + dir.z * dir.z)), atan2f(dir.x, dir.z), 0.0f);
            ang += vec3(rx, ry, 0.0f);
            bullet->setAngle(ang);
        }
    }

    void shot(TR::Entity::Type type, int joint, const vec3 &offset) {
        shot(type, joint, offset, (randf() * 2.0f - 1.0f) * (1.5f * DEG2RAD), (randf() * 2.0f - 1.0f) * (1.5f * DEG2RAD));
    }

    bool isVisible() {
        for (int i = 0; i < 2; i++) {
            ICamera *camera = game->getCamera(i);
            if (!camera) continue;

            TR::Location eye = camera->eye;
            TR::Location loc;
            loc.room = getRoomIndex();
            loc.box  = box;
            loc.pos  = pos;
            loc.pos.y -= 1024;

            if (trace(eye, loc))
                return true;
        }
        return false;
    }
};


#define WOLF_TURN_FAST   (DEG2RAD * 150)
#define WOLF_TURN_SLOW   (DEG2RAD * 60)
#define WOLF_DIST_STALK  STALK_BOX
#define WOLF_DIST_BITE   345
#define WOLF_DIST_ATTACK (1024 + 512)

struct Wolf : Enemy {

    enum {
        HIT_MASK = 0x774F,  // body, head, front legs
    };

    enum {
        ANIM_DEATH      = 20,
        ANIM_DEATH_RUN  = 21,
        ANIM_DEATH_JUMP = 22,
    };

    enum {
        STATE_NONE   ,
        STATE_STOP   ,
        STATE_WALK   ,
        STATE_RUN    ,
        STATE_JUMP   , // unused
        STATE_STALK  ,
        STATE_ATTACK ,
        STATE_HOWL   ,
        STATE_SLEEP  ,
        STATE_GROWL  ,
        STATE_TURN   , // unused
        STATE_DEATH  ,
        STATE_BITE   ,
    };

    Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) {
        dropHeight = -1024;
        jointChest = 2;
        jointHead  = 3;
        hitSound   = TR::SND_HIT_WOLF;
        nextState  = STATE_NONE;
        animation.time = animation.timeMax;
        updateAnimation(false);
    }

    virtual int getStateGround() {
        if (!think(false))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_SLEEP    :
                if (mood == MOOD_ESCAPE || target->zone == zone)
                    nextState = STATE_GROWL;
                else if (rand() < 32)
                    nextState = STATE_WALK;
                else
                    break;
                return STATE_STOP;
            case STATE_STOP     : return nextState != STATE_NONE ? nextState : STATE_WALK;
            case STATE_WALK     : 
                if (mood != MOOD_SLEEP) {
                    nextState = STATE_NONE;
                    return STATE_STALK;
                }
                if (rand() < 32) {
                    nextState = STATE_SLEEP;
                    return STATE_STOP;
                }
                break;
            case STATE_GROWL    :
                if (nextState != STATE_NONE) return nextState;
                if (mood == MOOD_ESCAPE)     return STATE_RUN;
                if (targetDist < WOLF_DIST_BITE && targetCanAttack) return STATE_BITE;
                if (mood == MOOD_STALK)      return STATE_STALK;
                if (mood == MOOD_SLEEP)      return STATE_STOP;
                return STATE_RUN;
            case STATE_STALK    : 
                if (mood == MOOD_ESCAPE)            return STATE_RUN;
                if (targetDist < WOLF_DIST_BITE && targetCanAttack) return STATE_BITE;
                if (targetDist > WOLF_DIST_STALK)   return STATE_RUN;
                if (mood == MOOD_ATTACK) {
                    if (!targetInView || targetFromView || targetDist > WOLF_DIST_ATTACK)
                        return STATE_RUN;
                }
                if (rand() < 384) {
                    nextState = STATE_HOWL;
                    return STATE_GROWL;
                }
                if (mood == MOOD_SLEEP)     return STATE_GROWL;
                break;
            case STATE_RUN      :
                if (targetDist < WOLF_DIST_ATTACK && targetInView) {
                    if (targetDist < WOLF_DIST_ATTACK * 0.5f && targetFromView) {
                        nextState = STATE_NONE;
                        return STATE_ATTACK;
                    }
                    nextState = STATE_STALK;
                    return STATE_GROWL;
                }
                if (mood == MOOD_STALK && targetDist < WOLF_DIST_STALK) {
                    nextState = STATE_STALK;
                    return STATE_GROWL;
                }
                if (mood == MOOD_SLEEP) return STATE_GROWL;
                break;
            case STATE_ATTACK :
            case STATE_BITE   :
                if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
                    bite(6, vec3(0.0f, -14.0f, 174.0f), state == STATE_ATTACK ? 50.0f : 100.0f);
                    nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL;
                }
                return state == STATE_ATTACK ? STATE_RUN : state;
        }

        return state;
    }

    virtual int getStateDeath() {
        switch (state) {
            case STATE_DEATH : return state;
            case STATE_RUN   : return animation.setAnim(ANIM_DEATH_RUN);
            case STATE_JUMP  : return animation.setAnim(ANIM_DEATH_JUMP);
            default          : return animation.setAnim(ANIM_DEATH);
        }
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN || state == STATE_WALK || state == STATE_STALK, state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW);

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        Enemy::updatePosition();
        setOverrides(state != STATE_DEATH, jointChest, jointHead);
        lookAt(target);
    }
};


#define LION_DIST_ATTACK 1024
#define LION_TURN_FAST   (DEG2RAD * 150)
#define LION_TURN_SLOW   (DEG2RAD * 60)

struct Lion : Enemy {

    enum {
        HIT_MASK = 0x380066,
    };

    enum {
        ANIM_DEATH_LION = 7,
        ANIM_DEATH_PUMA = 4,
    };

    enum {
        STATE_NONE   ,
        STATE_STOP   ,
        STATE_WALK   ,
        STATE_RUN    ,
        STATE_ATTACK ,
        STATE_DEATH  ,
        STATE_ROAR   ,
        STATE_BITE   ,
    };

    Lion(IGame *game, int entity) : Enemy(game, entity, 6, 341, 400.0f, 0.25f) {
        dropHeight = -1024;
        jointChest = 19;
        jointHead  = 20;
        switch (getEntity().type) {
            case TR::Entity::ENEMY_LION_MALE :
                hitSound   = TR::SND_HIT_LION;
                health     = 30.0f;
                aggression = 1.0f;
                break;
            case TR::Entity::ENEMY_LION_FEMALE :
                hitSound = TR::SND_HIT_LION;
                health   = 25.0f;
                break;
            case TR::Entity::ENEMY_PUMA :
                health   = 45.0f;
                break;
            default : ;
        }
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP    :
                if (nextState != STATE_NONE)
                    return nextState;
                if (mood == MOOD_SLEEP)
                    return STATE_WALK;
                if (targetInView && (collide(target) & HIT_MASK))
                    return STATE_BITE;
                if (targetInView && targetDist < LION_DIST_ATTACK)
                    return STATE_ATTACK;
                return STATE_RUN;
            case STATE_WALK     : 
                if (mood != MOOD_SLEEP)
                    return STATE_STOP;
                if (randf() < 0.0004f) {
                    nextState = STATE_ROAR;
                    return STATE_STOP;
                }
                break;
            case STATE_RUN      :
                if ((mood == MOOD_SLEEP) ||
                    (targetInView && targetDist < LION_DIST_ATTACK) ||
                    (targetInView && (collide(target) & HIT_MASK)))
                    return STATE_STOP;
                if (mood == MOOD_ESCAPE && randf() < 0.0004f) {
                    nextState = STATE_ROAR;
                    return STATE_STOP;
                }
                break;
            case STATE_ATTACK :
            case STATE_BITE   :
                if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
                    bite(21, vec3(-2.0f, -10.0f, 132.0f), state == STATE_ATTACK ? 150.0f : 250.0f);
                    nextState = STATE_STOP;
                }
        }

        return state;
    }

    virtual int getStateDeath() {
        if (state == STATE_DEATH)
            return state;
        int deathAnim = (getEntity().type == TR::Entity::ENEMY_PUMA) ? ANIM_DEATH_PUMA : ANIM_DEATH_LION;
        return animation.setAnim(deathAnim + rand() % 2);
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN || state == STATE_WALK || state == STATE_ROAR, state == STATE_RUN ? LION_TURN_FAST : LION_TURN_SLOW);

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};


#define GORILLA_DIST_ATTACK     430
#define GORILLA_DIST_AGGRESSION 2048
#define GORILLA_TURN_FAST       (DEG2RAD * 150)

struct Gorilla : Enemy {

    enum {
        HIT_MASK = 0x00FF00,
    };

    enum {
        ANIM_DEATH = 7,
        ANIM_CLIMB = 19,
    };

    enum {
        STATE_NONE   ,
        STATE_STOP   ,
        STATE_UNUSED ,
        STATE_RUN    ,
        STATE_ATTACK ,
        STATE_DEATH  ,
        STATE_IDLE1  ,
        STATE_IDLE2  ,
        STATE_LEFT   ,
        STATE_RIGHT  ,
        STATE_JUMP   ,
        STATE_CLIMB  ,
    };

    enum {
        FLAG_ATTACK = 1,
        FLAG_LEFT   = 2,
        FLAG_RIGHT  = 4,
    };

    Gorilla(IGame *game, int entity) : Enemy(game, entity, 22, 341, 250.0f, 1.0f) {
        dropHeight = -1024;
        stepHeight =  1024;
        jointChest = -1;//7;
        jointHead  = 14;
        flags.unused = 0;
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;
        
        if (nextState == state)
            nextState = STATE_NONE;

        if (targetDist < GORILLA_DIST_AGGRESSION)
            flags.unused |= FLAG_ATTACK;

        switch (state) {
            case STATE_STOP    :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetCanAttack && targetDist < GORILLA_DIST_ATTACK)
                    return STATE_ATTACK;
                if (!(flags.unused & FLAG_ATTACK) && zone == target->zone && targetInView) {
                    int r = rand() % 512;
                    if (r < 120) return STATE_JUMP;
                    if (r < 240) return STATE_IDLE1;
                    if (r < 360) return STATE_IDLE2;
                    if (r < 480) return STATE_RUN;
                    return (r % 2) ? STATE_LEFT : STATE_RIGHT;
                }
                return STATE_RUN;
            case STATE_RUN      :
                if (!flags.unused && targetInView)
                    return STATE_STOP;
                if (targetInView && (collide(target) & HIT_MASK)) {
                    nextState = STATE_ATTACK;
                    return STATE_STOP;
                }
                if (mood != MOOD_ESCAPE) {
                    int r = rand();
                    if (r < 160)
                        nextState = STATE_JUMP;
                    else if (r < 320)
                        nextState = STATE_IDLE1;
                    else if (r < 480)
                        nextState = STATE_IDLE2;
                    else
                        break;
                    return STATE_STOP;
                }
                break;
            case STATE_ATTACK :
                if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
                    bite(15, vec3(0.0f, -19.0f, 75.0f), 200.0f);
                    nextState = STATE_STOP;
                }
                break;
            case STATE_LEFT  :
            case STATE_RIGHT :
                return STATE_STOP;
            default : ;
        }

        return state;
    }

    virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
        Enemy::hit(damage, enemy, hitType);
        flags.unused |= FLAG_ATTACK;
    };

    virtual int getStateDeath() {
        if (state == STATE_DEATH)
            return state;
        return animation.setAnim(ANIM_DEATH + rand() % 2);
    }

    void strafe(int dir) {
        switch (state) {
            case STATE_STOP :
                if (flags.unused & FLAG_LEFT)  angle.y += PI * 0.5f;
                if (flags.unused & FLAG_RIGHT) angle.y -= PI * 0.5f;
                flags.unused &= ~(FLAG_LEFT | FLAG_RIGHT);
                break;
            case STATE_LEFT :
                if (!(flags.unused & FLAG_LEFT)) {
                    flags.unused |= FLAG_LEFT;
                    angle.y -= PI * 0.5f;
                }
                break;
            case STATE_RIGHT :
                if (!(flags.unused & FLAG_RIGHT)) {
                    flags.unused |= FLAG_RIGHT;
                    angle.y += PI * 0.5f;
                }
                break;
            default : ;
        }
    }

    virtual void updateAnimation(bool commands) {
        Enemy::updateAnimation(commands);

        strafe(state);

        if ((state == STATE_LEFT || state == STATE_RIGHT) && animation.isPrepareToNext && animation.anims[animation.next].state == STATE_STOP)
            animation.rot = (state == STATE_LEFT ? -PI : PI) * 0.5f;
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN, GORILLA_TURN_FAST);

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        vec3 old = pos;

        TR::Level::FloorInfo infoA, infoB;
        getFloorInfo(getRoomIndex(), old, infoA);
        old.y = infoA.floor;

        Enemy::updatePosition();

        getFloorInfo(getRoomIndex(), pos, infoB);

        if (infoB.floor < old.y - 384)
            climb(old);

        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }

    void climb(const vec3 &old) {
        int ox = int(old.x) / 1024;
        int oz = int(old.z) / 1024;

        int cx = int(pos.x) / 1024;
        int cz = int(pos.z) / 1024;

        if ((ox == cx) == (oz == cz))
            return;

        strafe(STATE_STOP);

        if (oz == cz) {
            if (ox < cx) {
                pos.x   = float(cx * 1024 - 75);
                angle.y = PI * 0.5f;
            } else {
                pos.x   = float(ox * 1024 + 75);
                angle.y = -PI * 0.5f;
            }
        } else {
            if (oz < cz) {
                pos.z   = float(cz * 1024 - 75);
                angle.y = 0.0f;
            } else {
                pos.z   = float(oz * 1024 + 75);
                angle.y = -PI;
            }
        }

        pos.y = old.y;
        animation.setAnim(ANIM_CLIMB);
    }
};


#define RAT_TURN_SLOW   (DEG2RAD * 90)
#define RAT_TURN_FAST   (DEG2RAD * 180)
#define RAT_DIST_BITE   341.0f
#define RAT_DIST_ATTACK 1536.0f
#define RAT_WAIT        0.01f
#define RAT_DAMAGE      20

struct Rat : Enemy {

    enum {
        HIT_MASK = 0x0300018F,
    };

    enum {
        ANIM_DEATH_LAND  = 8,
        ANIM_DEATH_WATER = 2,
    };

    enum {
    // land
        STATE_NONE   ,
        STATE_STOP   ,
        STATE_ATTACK ,
        STATE_RUN    ,
        STATE_BITE   ,
        STATE_DEATH  ,
        STATE_WAIT   ,
    // water
        STATE_WATER_SWIM = 1,
        STATE_WATER_BITE    ,
        STATE_WATER_DEATH   ,
    };

    int modelLand, modelWater;

    Rat(IGame *game, int entity) : Enemy(game, entity, 5, 204, 200.0f, 0.25f) {
        hitSound   = TR::SND_HIT_RAT;
        jointChest = 1;
        jointHead  = 2;

        modelLand  = level->getModelIndex(TR::Entity::ENEMY_RAT_LAND)  - 1;
        modelWater = level->getModelIndex(TR::Entity::ENEMY_RAT_WATER) - 1;
    }

    const virtual TR::Model* getModel() {
        bool water = getRoom().flags.water || modelWater == -1;
        stand = water ? STAND_ONWATER : STAND_GROUND;

        int modelIndex = water ? modelWater : modelLand;

        if (modelIndex == -1) {
            water = modelWater != -1;
            modelIndex = water ? modelWater : modelLand;
        }

        ASSERT(modelIndex > -1);
        const TR::Model *model = &level->models[modelIndex];
        if (animation.model != model) {
            targetBox = TR::NO_BOX;
            animation.setModel(model);

            int16 rIndex = getRoomIndex();
            if (water) {
                TR::Room::Sector *sector = level->getWaterLevelSector(rIndex, pos);
                if (sector) {
                    pos.y = float(sector->ceiling * 256);
                    roomIndex = rIndex;
                }
            } else {
                TR::Room::Sector *sector = level->getSector(rIndex, pos);
                if (sector) {
                    pos.y = float(sector->floor * 256);
                    roomIndex = rIndex;
                }
            }

            nextState = STATE_NONE;
            state     = STATE_NONE;

            if (health <= 0.0f) {
                getStateDeath();
                animation.goEnd(false);
            }

            updateZone();
        }
        return animation.model;
    }

    virtual int getStateGround() {
        if (!think(false))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetCanAttack && targetDist < RAT_DIST_BITE)
                    return STATE_BITE;
                return STATE_RUN;
            case STATE_RUN :
                if (targetInView && (collide(target) & HIT_MASK))
                    return STATE_STOP;
                if (targetCanAttack && targetDist < RAT_DIST_ATTACK)
                    return STATE_ATTACK;
                if (targetInView && randf() < RAT_WAIT) {
                    nextState = STATE_WAIT;
                    return STATE_STOP;
                }
                break;
            case STATE_ATTACK :
            case STATE_BITE   :
                if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
                    bite(3, vec3(0.0f, -11.0f, 108.0f), RAT_DAMAGE);
                    nextState = state == STATE_ATTACK ? STATE_RUN : STATE_STOP;
                }
                break;
            case STATE_WAIT :
                if (mood == MOOD_SLEEP || randf() < RAT_WAIT)
                    return STATE_STOP;
            default : ;
        }

        return state;
    }

    virtual int getStateOnwater() {
        if (!think(false))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        if (animation.frameIndex % 4 == 0)
            game->waterDrop(getJoint(jointHead).pos, 96.0f, 0.02f);

        switch (state) {
            case STATE_WATER_SWIM :
                if (targetInView && (collide(target) & HIT_MASK))
                    return STATE_WATER_BITE;
                break;
            case STATE_WATER_BITE :
                if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
                    game->waterDrop(getJoint(jointHead).pos, 256.0f, 0.2f);
                    bite(3, vec3(0.0f, -11.0f, 108.0f), RAT_DAMAGE);
                    nextState = STATE_WATER_SWIM;
                }
                return STATE_NONE;
            default : ;
        }

        return state;
    }

    virtual void updatePosition() {
        turn((stand == STAND_GROUND && state == STATE_RUN) || (stand == STAND_ONWATER && state == STATE_WATER_SWIM), RAT_TURN_FAST);

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        Enemy::updatePosition();
        setOverrides(state != STATE_DEATH, jointChest, jointHead);
        lookAt(target);
    }

    virtual int getStateDeath() {
        bool water = getRoom().flags.water;
        if ((water && state == STATE_WATER_DEATH) || (!water && state == STATE_DEATH))
            return state;
        return animation.setAnim(water ? ANIM_DEATH_WATER : ANIM_DEATH_LAND);
    }
};


#define CROCODILE_TURN_SLOW   (DEG2RAD * 90)
#define CROCODILE_TURN_FAST   (DEG2RAD * 180)
#define CROCODILE_DIST_BITE   435.0f
#define CROCODILE_DIST_TURN   (1024 * 3)
#define CROCODILE_LIFT_SPEED  960.0f
#define CROCODILE_DAMAGE      25

struct Crocodile : Enemy {

    enum {
        HIT_MASK = 0x000003FC,
    };

    enum {
        ANIM_DEATH_LAND  = 11,
        ANIM_DEATH_WATER = 4,
    };

    enum {
    // land
        STATE_NONE   ,
        STATE_STOP   ,
        STATE_RUN    ,
        STATE_WALK   ,
        STATE_TURN   ,
        STATE_BITE   ,
        STATE_UNUSED ,
        STATE_DEATH  ,
    // water
        STATE_WATER_SWIM = 1,
        STATE_WATER_BITE    ,
        STATE_WATER_DEATH   ,
    };

    int modelLand, modelWater;

    Crocodile(IGame *game, int entity) : Enemy(game, entity, 20, 341, 600.0f, 0.25f) {
        jointChest = 1;
        jointHead  = 8;

        modelLand  = level->getModelIndex(TR::Entity::ENEMY_CROCODILE_LAND)  - 1;
        modelWater = level->getModelIndex(TR::Entity::ENEMY_CROCODILE_WATER) - 1;

        bool water = getRoom().flags.water || modelWater == -1;
        flying     = water;
        stand      = water ? STAND_UNDERWATER : STAND_GROUND;
    }

    const virtual TR::Model* getModel() {
        bool water = getRoom().flags.water || modelWater == -1;
        stand  = water ? STAND_UNDERWATER : STAND_GROUND;

        int modelIndex = water ? modelWater : modelLand;

        if (modelIndex == -1) {
            water = modelWater != -1;
            modelIndex = water ? modelWater : modelLand;
        }

        ASSERT(modelIndex > -1);
        const TR::Model *model = &level->models[modelIndex];
        if (animation.model != model) {
            targetBox = TR::NO_BOX;
            animation.setModel(model);
            flying = water;

            int16 rIndex = getRoomIndex();
            if (water) {
                TR::Room::Sector *sector = level->getWaterLevelSector(rIndex, pos);
                if (sector) {
                    pos.y = float(sector->ceiling * 256);
                    roomIndex = rIndex;
                }
            } else {
                TR::Room::Sector *sector = level->getSector(rIndex, pos);
                if (sector) {
                    pos.y = float(sector->floor * 256);
                    roomIndex = rIndex;
                }
            }

            nextState = STATE_NONE;
            state     = STATE_NONE;

            if (health <= 0.0f) {
                getStateDeath();
                animation.goEnd(false);
            }

            updateZone();
        }
        return animation.model;
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (targetCanAttack && targetDist < CROCODILE_DIST_BITE)
                    return STATE_BITE;
                switch (mood) {
                    case MOOD_ESCAPE : return STATE_RUN;
                    case MOOD_ATTACK : return (targetInView || targetDist < CROCODILE_DIST_TURN) ? STATE_RUN : STATE_TURN;
                    case MOOD_STALK  : return STATE_WALK;
                    default          : return state;
                }
            case STATE_RUN  :
                if (targetInView && (collide(target) & HIT_MASK))
                    return STATE_STOP;
                switch (mood) {
                    case MOOD_SLEEP  : return STATE_STOP;
                    case MOOD_STALK  : return STATE_WALK;
                    case MOOD_ATTACK : if (targetDist > CROCODILE_DIST_TURN && !targetInView) return STATE_STOP;
                    default          : return state;
                }
            case STATE_WALK :
                if (targetInView && (collide(target) & HIT_MASK))
                    return STATE_STOP;
                switch (mood) {
                    case MOOD_SLEEP  : return STATE_STOP;
                    case MOOD_ATTACK :
                    case MOOD_ESCAPE : return STATE_RUN;
                    default          : return state;
                }
            case STATE_TURN :
                return targetInView ? STATE_WALK : state;
            case STATE_BITE   :
                if (nextState == STATE_NONE) {
                    bite(9, vec3(5.0f, -21.0f, 467.0f), CROCODILE_DAMAGE);
                    nextState = STATE_STOP;
                }
                break;
            default : ;
        }

        return state;
    }

    virtual int getStateUnderwater() {
        if (!think(false))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        if (animation.frameIndex % 4 == 0)
            game->waterDrop(getJoint(jointHead).pos, 96.0f, 0.02f);

        switch (state) {
            case STATE_WATER_SWIM :
                if (targetInView && collide(target))
                    return STATE_WATER_BITE;
                break;
            case STATE_WATER_BITE :
                if (collide(target)) {
                    if (nextState != STATE_NONE)
                        return state;
                    bite(9, vec3(5.0f, -21.0f, 467.0f), CROCODILE_DAMAGE);
                    nextState = STATE_WATER_SWIM;
                }
                return STATE_WATER_SWIM;
            default : ;
        }

        return state;
    }

    virtual void updatePosition() {
        if (state == STATE_TURN)
            angle.y += CROCODILE_TURN_FAST * Core::deltaTime;
        else
            turn((stand == STAND_GROUND && (state == STATE_RUN || state == STATE_WALK)) || (stand == STAND_UNDERWATER && state == STATE_WATER_SWIM), CROCODILE_TURN_FAST);
        angle.z = 0.0f;

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        if (flying) {
            lift(waypoint.y - pos.y, CROCODILE_LIFT_SPEED);
            int16 rIndex = getRoomIndex();
            TR::Room::Sector *sector = level->getWaterLevelSector(rIndex, pos);
            if (sector) {
                float waterLevel = float(sector->ceiling * 256) + 256;
                if (pos.y < waterLevel)
                    pos.y = waterLevel;
            }
        }

        Enemy::updatePosition();
        setOverrides(state != STATE_DEATH, jointChest, jointHead);
        lookAt(target);
    }

    virtual int getStateDeath() {
        bool water = getRoom().flags.water;
        if ((water && state == STATE_WATER_DEATH) || (!water && state == STATE_DEATH))
            return state;
        return animation.setAnim(water ? ANIM_DEATH_WATER : ANIM_DEATH_LAND);
    }
};


#define BEAR_DIST_EAT    768
#define BEAR_DIST_HOWL   2048
#define BEAR_DIST_BITE   1024
#define BEAR_DIST_ATTACK 600

#define BEAR_TURN_FAST   (DEG2RAD * 150)
#define BEAR_TURN_SLOW   (DEG2RAD * 60)

struct Bear : Enemy {

    enum {
        HIT_MASK = 0x2406C, // front legs and head
    };

    enum {
        ANIM_DEATH_HIND = 19,
        ANIM_DEATH      = 20,
    };

    enum {
        STATE_NONE   = -1,
        STATE_WALK   ,
        STATE_STOP   ,
        STATE_HIND   ,
        STATE_RUN    ,
        STATE_HOWL   ,
        STATE_GROWL  ,
        STATE_BITE   ,
        STATE_ATTACK ,
        STATE_EAT    ,
        STATE_DEATH  ,
    };

    Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 500.0f, 0.5f) {
        jointChest = 13;
        jointHead  = 14;
        hitSound   = TR::SND_HIT_BEAR;
        nextState  = STATE_NONE;
    }

    virtual int getStateGround() {
        if (!flags.active)
            return state;

        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_WALK     :
                if (nextState != STATE_NONE) return STATE_STOP;
                if (targetDead && targetInView && (collide(target) & HIT_MASK))
                    return nextState = STATE_STOP; // eat lara! >:E
                if (mood != MOOD_SLEEP) {
                    if (mood == MOOD_ESCAPE)
                        nextState = STATE_NONE;
                    return STATE_STOP;
                } else if (randf() < 0.003f) {
                    nextState = STATE_GROWL;
                    return STATE_STOP;
                }
                break;
            case STATE_STOP     :
                if (targetDead)
                    return (targetDist < BEAR_DIST_EAT && targetCanAttack) ? STATE_EAT : STATE_WALK;
                else
                    return nextState != STATE_NONE ? nextState : (mood == MOOD_SLEEP ? STATE_WALK : STATE_RUN);
            case STATE_HIND     :
                if (wound) {
                    nextState = STATE_NONE;
                    return STATE_HOWL;
                }

                if (targetInView && (collide(target) & HIT_MASK)) return STATE_HOWL;

                if (mood == MOOD_ESCAPE)
                    nextState = STATE_NONE;
                else if (mood == MOOD_SLEEP || randf() < 0.003f)
                    nextState = STATE_GROWL;
                else if (targetDist > BEAR_DIST_HOWL || randf() < 0.05f)
                    nextState = STATE_STOP;

                return STATE_HOWL;
                break;
            case STATE_RUN      :
                if (collide(target) & HIT_MASK)
                    target->hit(3, this);                
                if (targetDead || mood == MOOD_SLEEP)
                    return STATE_STOP;
                if (nextState != STATE_NONE) return STATE_STOP;
                if (targetInView) {
                    if (!wound && targetDist < BEAR_DIST_HOWL && randf() < 0.025f) {
                        nextState = STATE_HOWL;
                        return STATE_STOP;
                    } 
                    if (targetDist < BEAR_DIST_BITE) return STATE_BITE;
                }
                break;
            case STATE_HOWL     :
                if (wound) {
                    nextState = STATE_NONE;
                    return STATE_STOP;
                }
                if (nextState != STATE_NONE) return nextState;
                if (mood == MOOD_SLEEP || mood == MOOD_ESCAPE) return STATE_STOP;
                if (targetDist < BEAR_DIST_ATTACK) return STATE_ATTACK;
                return STATE_HIND;
            case STATE_BITE     :
            case STATE_ATTACK   :
                if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
                    bite(14, vec3(0.0f, 96.0f, 335.0f), state == STATE_BITE ? 200.0f : 400.0f);
                    nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL;
                }
                break;
        }

        return state;
    }

    virtual int getStateDeath() {
        switch (state) {
            case STATE_HIND : return STATE_HOWL;
            case STATE_RUN  :
            case STATE_WALK : return STATE_STOP;
            case STATE_HOWL :
            case STATE_STOP : return STATE_DEATH;
        }
        return state;// == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN || state == STATE_WALK || state == STATE_HIND, state == STATE_RUN ? BEAR_TURN_FAST : BEAR_TURN_SLOW);

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        Enemy::updatePosition();
        setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_HIND, jointChest, jointHead);
        lookAt(target);
    }
};


#define BAT_TURN_SPEED  (DEG2RAD * 300)
#define BAT_LIFT_SPEED  512.0f

struct Bat : Enemy {

    enum {
        ANIM_DEATH = 4,
    };

    enum {
        STATE_NONE,
        STATE_AWAKE,
        STATE_FLY,
        STATE_ATTACK,
        STATE_CIRCLING,
        STATE_DEATH,
    };

    Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.0f, 0.03f) {
        stand = STAND_AIR;
        stepHeight =  20 * 1024;
        dropHeight = -20 * 1024;
        jointHead  = 4;
    }

    virtual int getStateAir() {
        if (!flags.active) {
            animation.time = 0.0f;
            animation.dir  = 0.0f;
            return STATE_AWAKE;
        }

        if (!think(false))
            return state;

        switch (state) {
            case STATE_AWAKE  : return STATE_FLY;
            case STATE_ATTACK : 
                if (!collide(target)) {
                    mood = MOOD_SLEEP;
                    return STATE_FLY;
                } else
                    bite(4, vec3(0.0f, 16.0f, 45.0f), 2);
                break;
            case STATE_FLY    : 
                if (collide(target)) {
                    mood = MOOD_ATTACK;
                    return STATE_ATTACK;
                }
                break;
        }

        return state;
    }

    virtual int getStateDeath() {
        return state == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
    }

    virtual void updatePosition() {
        turn(state == STATE_FLY || state == STATE_ATTACK, BAT_TURN_SPEED);

        if (flying) {
            float wy = waypoint.y - (target->stand != STAND_ONWATER ? 765.0f : 64.0f);
            lift(wy - pos.y, BAT_LIFT_SPEED);
        }
        Enemy::updatePosition();
    }

    virtual void deactivate(bool removeFromList = false) {
        if (health <= 0.0f) {
            TR::Level::FloorInfo info;
            getFloorInfo(getRoomIndex(), pos, info);
            if (info.floor > pos.y)
                return;
            pos.y = info.floor;
        }
        Enemy::deactivate(removeFromList);
    }
};


#define REX_DIST_BITE       1500
#define REX_DIST_BITE_MAX   4096
#define REX_DIST_WALK       5120
#define REX_TURN_FAST       (DEG2RAD * 120)
#define REX_TURN_SLOW       (DEG2RAD * 60)
#define REX_DAMAGE          1000
#define REX_DAMAGE_WALK     1
#define REX_DAMAGE_RUN      10

struct Rex : Enemy {

    enum {
        HIT_MASK = (1 << 12) | (1 << 13),  // head
    };

    enum {
        STATE_NONE,
        STATE_STOP,
        STATE_WALK,
        STATE_RUN,
        STATE_UNUSED,
        STATE_DEATH,
        STATE_BAWL,
        STATE_BITE,
        STATE_FATAL,
    };

    Rex(IGame *game, int entity) : Enemy(game, entity, 100, 341, 2000.0f, 1.0f) {
        jointChest = 10;
        jointHead  = 12;    
        nextState  = STATE_NONE;
    }

    virtual int getStateGround() {
        if (!flags.active)
            return state;

        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        if (targetDead) {
            return (state == STATE_STOP || state == STATE_WALK) ? STATE_WALK : STATE_STOP;
            if (state != STATE_STOP) return STATE_STOP;
            return STATE_WALK;
        }

        int mask = collide(target);

        // if Lara is behind and watching Rex we need to rotate
        bool walk = targetFromView && !targetInView && mood != MOOD_ESCAPE;
        if (!walk && targetCanAttack && targetDist > REX_DIST_BITE && targetDist < REX_DIST_BITE_MAX)
            walk = true;

        switch (state) {
            case STATE_STOP : 
                if (nextState != STATE_NONE)                        return nextState;
                if (targetCanAttack && targetDist < REX_DIST_BITE)  return STATE_BITE;
                if (mood == MOOD_SLEEP || walk)                     return STATE_WALK;
                return STATE_RUN;
            case STATE_WALK :
                if (mask) target->hit(REX_DAMAGE_WALK, this);
                if (mood != MOOD_SLEEP && !walk)    return STATE_STOP;
                if (targetInView && randf() < 0.015f) {
                    nextState = STATE_BAWL;
                    return STATE_STOP;
                }
                break;
            case STATE_RUN :
                if (mask) target->hit(REX_DAMAGE_RUN, this);
                if ((targetCanAttack && targetDist < REX_DIST_WALK) || walk)
                    return STATE_STOP;
                if (targetInView && mood != MOOD_ESCAPE && randf() < 0.015f) {
                    nextState = STATE_BAWL;
                    return STATE_STOP;
                }
                if (mood == MOOD_SLEEP)
                    return STATE_STOP;
                break;
            case STATE_BITE :
                if (mask & HIT_MASK) {
                    target->hit(REX_DAMAGE, this, TR::HIT_REX);
                    return STATE_FATAL;
                }
                nextState = STATE_WALK;
                break;
        }

        return state;
    }

    virtual int getStateDeath() {
        return state == STATE_STOP ? STATE_DEATH : STATE_STOP;
    }

    virtual void updatePosition() {
        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            angle.z = 0.0f;
            return;
        }

        turn(state == STATE_RUN || state == STATE_WALK, state == STATE_RUN ? REX_TURN_FAST : REX_TURN_SLOW);

        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};

#define RAPTOR_DIST_BITE        680
#define RAPTOR_DIST_ATTACK      (1024 + 512)

#define RAPTOR_TURN_FAST        (DEG2RAD * 120)
#define RAPTOR_TURN_SLOW        (DEG2RAD * 30)

struct Raptor : Enemy {

    enum {
        HIT_MASK = 0xFF7C00,  // hands and head
    };

    enum {
        ANIM_DEATH_1 = 9,
        ANIM_DEATH_2 = 10,
    };

    enum {
        STATE_NONE = -1,
        STATE_DEATH,
        STATE_STOP,
        STATE_WALK,
        STATE_RUN,
        STATE_ATTACK_1,
        STATE_UNUSED,
        STATE_BAWL,
        STATE_ATTACK_2,
        STATE_BITE,
    };

    Raptor(IGame *game, int entity) : Enemy(game, entity, 20, 341, 400.0f, 0.5f) {
        jointChest = -1;
        jointHead  = 21;    
        nextState  = STATE_NONE;
    }

    virtual int getStateGround() {
        if (!flags.active)
            return state;

        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        if (targetDead) {
            return (state == STATE_STOP || state == STATE_WALK) ? STATE_WALK : STATE_STOP;
            if (state != STATE_STOP) return STATE_STOP;
            return STATE_WALK;
        }

        int mask = collide(target);

        switch (state) {
            case STATE_STOP : 
                if (nextState != STATE_NONE) return nextState;
                if ((mask & HIT_MASK) || (targetCanAttack && targetDist < RAPTOR_DIST_BITE)) return STATE_BITE;
                if (targetCanAttack && targetDist < RAPTOR_DIST_ATTACK) return STATE_ATTACK_1;
                if (mood == MOOD_SLEEP) return STATE_WALK;
                return STATE_RUN;
            case STATE_WALK :
                if (nextState != STATE_NONE) return STATE_STOP;
                if (mood != MOOD_SLEEP) return STATE_STOP;
                if (targetInView && randf() < 0.01f) {
                    nextState = STATE_BAWL;
                    return STATE_STOP;
                }
                break;
            case STATE_RUN :
                if (nextState != STATE_NONE) return STATE_STOP;
                if (mask & HIT_MASK) return STATE_STOP;
                if (targetCanAttack && targetDist < RAPTOR_DIST_ATTACK)
                    return (randf() < 0.25) ? STATE_STOP : STATE_ATTACK_2;
                if (mood == MOOD_ESCAPE && targetInView) {
                    nextState = STATE_BAWL;
                    return STATE_STOP;
                }
                if (mood == MOOD_SLEEP)
                    return STATE_STOP;
                break;
            case STATE_ATTACK_1 :
            case STATE_ATTACK_2 :
            case STATE_BITE     :
                if (nextState == STATE_NONE && targetInView && (mask & HIT_MASK)) {
                    bite(22, vec3(0.0f, 66.0f, 318.0f), 100);                    
                    nextState = state == STATE_ATTACK_2 ? STATE_RUN : STATE_STOP;
                }
                break;
        }

        return state;
    }

    virtual int getStateDeath() {
        if (state == STATE_DEATH) return state;
        return animation.setAnim((rand() % 2) ? ANIM_DEATH_1 : ANIM_DEATH_2);
    }

    virtual void updatePosition() {
        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            angle.z = 0.0f;
            return;
        }

        turn(state == STATE_RUN || state == STATE_WALK, state == STATE_RUN ? RAPTOR_TURN_FAST : RAPTOR_TURN_SLOW);
        
        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};


#define MUTANT_TURN_FAST        (DEG2RAD * 180)
#define MUTANT_TURN_SLOW        (DEG2RAD * 60)
#define MUTANT_LIFT_SPEED       512.0f
#define MUTANT_DIST_ATTACK_1    600
#define MUTANT_DIST_ATTACK_2    (2048 + 512)
#define MUTANT_DIST_ATTACK_3    300
#define MUTANT_DIST_SHOT        3840
#define MUTANT_DIST_STALK       (4096 + 512)
#define MUTANT_PART_DAMAGE      100

struct Mutant : Enemy {

    enum {
        HIT_MASK = 0x0678,
    };

    enum {
        STATE_NONE,
        STATE_STOP,
        STATE_WALK,
        STATE_RUN,
        STATE_ATTACK_1,
        STATE_DEATH,
        STATE_LOOKING,
        STATE_ATTACK_2,
        STATE_ATTACK_3,
        STATE_AIM_1,
        STATE_AIM_2,
        STATE_FIRE,
        STATE_IDLE,
        STATE_FLY,
    };

    enum {
        FLAG_FLY      = 1,
        FLAG_BULLET_1 = 2,
        FLAG_BULLET_2 = 4,
    };

    Mutant(IGame *game, int entity) : Enemy(game, entity, 50, 341, 150.0f, 1.0f) {
        if (getEntity().type != TR::Entity::ENEMY_MUTANT_1) {
            initMeshOverrides();
            layers[0].mask = 0xffe07fff;
            aggression     = 0.25f;
        }

        flags.unused = 0;
        jointChest   = 1;
        jointHead    = 2;
    }

    virtual void setSaveData(const SaveEntity &data) {
        Character::setSaveData(data);
        if (flags.invisible)
            deactivate(true);
    }

    virtual void update() {
        bool exploded = explodeMask != 0;

        if (health <= 0.0f && !exploded) {
            game->playSound(TR::SND_MUTANT_DEATH, pos, Sound::PAN);
            explode(0xffffffff, MUTANT_PART_DAMAGE);
        }

        Enemy::update();

        if (exploded && !explodeMask) {
            deactivate(true);
            flags.invisible = true;
        }
    }

    virtual int getStateGround() {
        if (state == STATE_FLY) {
            stand  = STAND_AIR;
            flying = true;
            updateZone();
            return getStateAir();
        }

        stepHeight = 256;
        dropHeight = -stepHeight;

        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        if (getEntity().type != TR::Entity::ENEMY_MUTANT_3) {
            if (flags.unused & (FLAG_BULLET_1 | FLAG_BULLET_2)) {
                if (targetAngle > PI * 0.25f)
                    flags.unused &= ~(FLAG_BULLET_1 | FLAG_BULLET_2);
            } else {
                if (targetAngle < PI * 0.25f && state != STATE_FIRE && (targetDist > MUTANT_DIST_SHOT || zone != target->zone) && targetIsVisible(MAX_SHOT_DIST))
                    flags.unused |= (rand() % 2) ? FLAG_BULLET_1 : FLAG_BULLET_2;
            }
        }

        if (getEntity().type == TR::Entity::ENEMY_MUTANT_1) { // flying mutant
            if (mood == MOOD_ESCAPE || (zone != target->zone && !(flags.unused & (FLAG_BULLET_1 | FLAG_BULLET_2)))) {
                flags.unused |= FLAG_FLY;
            }
        }

        int mask = collide(target);

        switch (state) {
            case STATE_STOP :
                if (flags.unused & FLAG_FLY)
                    return STATE_FLY;
                if ((targetCanAttack && targetDist < MUTANT_DIST_ATTACK_3) || (mask & HIT_MASK))
                    return STATE_ATTACK_3;
                if ((targetCanAttack && targetDist < MUTANT_DIST_ATTACK_1))
                    return STATE_ATTACK_1;
                if (flags.unused & FLAG_BULLET_1)
                    return STATE_AIM_1;
                if (flags.unused & FLAG_BULLET_2)
                    return STATE_AIM_2;
                if (mood == MOOD_SLEEP || (mood == MOOD_STALK && targetDist < MUTANT_DIST_STALK))
                    return STATE_LOOKING;
                return STATE_RUN;
            case STATE_WALK :
                if (flags.unused)
                    return STATE_STOP;
                if (mood == MOOD_ATTACK || mood == MOOD_ESCAPE)
                    return STATE_STOP;
                if (mood == MOOD_SLEEP || (mood == MOOD_STALK && target->zone != zone)) {
                    if (rand() < 50)
                        return STATE_LOOKING;
                } else if (mood == MOOD_STALK && targetDist > MUTANT_DIST_STALK)
                    return STATE_STOP;
                break;
            case STATE_RUN :
                if (flags.unused & FLAG_FLY)
                    return STATE_STOP;
                if (mask & HIT_MASK)
                    return STATE_STOP;
                if (targetCanAttack && targetDist < MUTANT_DIST_ATTACK_1)
                    return STATE_STOP;
                if (targetInView && targetDist < MUTANT_DIST_ATTACK_2)
                    return STATE_ATTACK_2;
                if (flags.unused & (FLAG_BULLET_1 | FLAG_BULLET_2))
                    return STATE_STOP;
                if (mood == MOOD_SLEEP || (mood == MOOD_STALK && targetDist < MUTANT_DIST_STALK))
                    return STATE_STOP;
                break;
            case STATE_LOOKING :
                if (flags.unused)
                    return STATE_STOP;
                switch (mood) {
                    case MOOD_SLEEP :
                        if (rand() < 256)
                            return STATE_WALK;
                        break;
                    case MOOD_STALK :
                        if (targetDist < MUTANT_DIST_STALK) {
                            if (target->zone == zone && rand() < 256)
                                return STATE_WALK;
                        } else
                            return STATE_STOP;
                        break;
                    case MOOD_ATTACK :
                    case MOOD_ESCAPE :
                        return STATE_STOP;
                }
            case STATE_ATTACK_1 :
            case STATE_ATTACK_2 :
            case STATE_ATTACK_3 :
                if (nextState == STATE_NONE && (mask & HIT_MASK)) {
                    float damage = state == STATE_ATTACK_1 ? 150.0f : (state == STATE_ATTACK_2 ? 100.0f : 200.0f);
                    bite(10, vec3(-27.0f, 98.0f, 0.0f), damage);
                    nextState = STATE_STOP;
                }
                break;
            case STATE_AIM_1 :
                return (flags.unused & FLAG_BULLET_1) ? STATE_FIRE : STATE_STOP;
            case STATE_AIM_2 :
                return (flags.unused & FLAG_BULLET_2) ? STATE_FIRE : STATE_STOP;
            case STATE_FIRE :
                if (flags.unused & FLAG_BULLET_1)
                    shot(TR::Entity::MUTANT_BULLET, 9, vec3(-35.0f, 269.0f, 0.0f));
                if (flags.unused & FLAG_BULLET_2)
                    shot(TR::Entity::CENTAUR_BULLET, 14, vec3(51.0f, 213.0f, 0.0f));
                flags.unused &= ~(FLAG_BULLET_1 | FLAG_BULLET_2);
                break;
            case STATE_IDLE :
                return STATE_STOP;
            default : ;
        }

        return state;
    }

    virtual int getStateAir() {
        if (state != STATE_FLY) {
            stand  = STAND_GROUND;
            flying = false;
            updateZone();
            return getStateGround();
        }

        stepHeight = 30 * 1024;
        dropHeight = -stepHeight;

        if (!think(true))
            return state;

        if ((flags.unused & FLAG_FLY) && mood != MOOD_ESCAPE && zone == target->zone)
            flags.unused &= ~FLAG_FLY;

        if (!(flags.unused & FLAG_FLY)) {
            int16 roomIndex = getRoomIndex();
            TR::Room::Sector *sector = level->getSector(roomIndex, pos);
            float floor = level->getFloor(sector, pos) - 128.0f;
            if (pos.y >= floor)
                return STATE_STOP;
        }

        return STATE_FLY;
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN || state == STATE_WALK || state == STATE_FLY, (state == STATE_RUN || state == STATE_FLY) ? MUTANT_TURN_FAST : MUTANT_TURN_SLOW);

        if (flying)
            lift(target->pos.y - pos.y, MUTANT_LIFT_SPEED);

        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};

#define GIANT_MUTANT_TURN_SLOW    (DEG2RAD * 90)
#define GIANT_MUTANT_MIN_ANGLE    (DEG2RAD * 10)
#define GIANT_MUTANT_MAX_ANGLE    (DEG2RAD * 45)
#define GIANT_MUTANT_DAMAGE       500
#define GIANT_MUTANT_DAMAGE_WALK  5
#define GIANT_MUTANT_DAMAGE_FATAL 1000
#define GIANT_MUTANT_DIST_ATTACK  2600
#define GIANT_MUTANT_DIST_FATAL   2250
#define GIANT_MUTANT_PART_DAMAGE  250

struct GiantMutant : Enemy {

    enum {
        HIT_MASK_HAND  = 0x3FF8000,
        HIT_MASK_HANDS = 0x3FFFFF0,
    };

    enum {
        ANIM_DEATH = 13,
    };

    enum {
        STATE_NONE,
        STATE_STOP,
        STATE_TURN_LEFT,
        STATE_TURN_RIGHT,
        STATE_ATTACK_1,
        STATE_ATTACK_2,
        STATE_ATTACK_3,
        STATE_WALK,
        STATE_BORN,
        STATE_FALL,
        STATE_UNUSED,
        STATE_FATAL,
    };

    GiantMutant(IGame *game, int entity) : Enemy(game, entity, 500, 341, 375.0f, 1.0f) {
        hitSound   = TR::SND_HIT_MUTANT;
        stand      = STAND_AIR;
        jointChest = -1;
        jointHead  = -1; // 3; TODO: fix head orientation
        rangeHead  = vec4(-0.5f, 0.5f, -0.5f, 0.5f) * PI;
        invertAim  = true;
    }

    virtual void setSaveData(const SaveEntity &data) {
        Character::setSaveData(data);
        if (flags.invisible)
            deactivate(true);
    }

    void update() {
        bool exploded = explodeMask != 0;

        Enemy::update();

        if (health <= 0.0f && !exploded && animation.index == ANIM_DEATH && flags.state == TR::Entity::asInactive) {
            flags.state = TR::Entity::asActive;
            game->playSound(TR::SND_MUTANT_DEATH, pos, Sound::PAN);
            explode(0xffffffff, GIANT_MUTANT_PART_DAMAGE);
            game->checkTrigger(this, true);
        }

        setOverrides(true, jointChest, jointHead);
        lookAt(target);

        if (exploded && !explodeMask) {
            deactivate(true);
            flags.invisible = true;
        }
    }

    virtual int getStateAir() {
        if (state == STATE_BORN)
            return STATE_FALL;
        return state;
    }

    virtual int getStateGround() {
        if (health <= 0)
            return state;

        if (!think(true))
            return state;

        if (!target || target->health <= 0.0f)
            return STATE_STOP;

        int mask = collide(target);

        if (mask) target->hit(GIANT_MUTANT_DAMAGE_WALK, this);

        switch (state) {
            case STATE_FALL :
                animation.setState(STATE_STOP);
                game->shakeCamera(5.0f);
                break;
            case STATE_STOP :
                flags.unused = false;
                if (targetAngle >  GIANT_MUTANT_MAX_ANGLE) return STATE_TURN_RIGHT;
                if (targetAngle < -GIANT_MUTANT_MAX_ANGLE) return STATE_TURN_LEFT;
                if (targetDist < GIANT_MUTANT_DIST_ATTACK) {
                    if (target->health <= GIANT_MUTANT_DAMAGE) {
                        if (targetDist < GIANT_MUTANT_DIST_FATAL)
                            return STATE_ATTACK_3;
                    } else
                        return ((rand() % 2) ? STATE_ATTACK_1 : STATE_ATTACK_2);
                }
                return STATE_WALK;
            case STATE_WALK :
                if (targetDist  <  GIANT_MUTANT_DIST_ATTACK ||
                    targetAngle >  GIANT_MUTANT_MAX_ANGLE   ||
                    targetAngle < -GIANT_MUTANT_MAX_ANGLE)
                    return STATE_STOP;
                break;
            case STATE_TURN_RIGHT :
                if (targetAngle < GIANT_MUTANT_MAX_ANGLE)
                    return STATE_STOP;
                break;
            case STATE_TURN_LEFT :
                if (targetAngle > -GIANT_MUTANT_MAX_ANGLE)
                    return STATE_STOP;
                break;
            case STATE_ATTACK_1 :
            case STATE_ATTACK_2 :
                if (!flags.unused && (
                    (state == STATE_ATTACK_1 && (mask & HIT_MASK_HAND)) ||
                    (state == STATE_ATTACK_2 && (mask & HIT_MASK_HANDS)))) {
                    target->hit(GIANT_MUTANT_DAMAGE, this);
                    flags.unused = true;
                }
                break;
            case STATE_ATTACK_3 :
                if (target->stand != STAND_HANG) {
                    target->hit(GIANT_MUTANT_DAMAGE_FATAL, this, TR::HIT_GIANT_MUTANT);
                    return STATE_FATAL;
                }
                break;
            default : ;
        }
        
        return state;
    }

    virtual int getStateDeath() {
        if (animation.index != ANIM_DEATH)
            return animation.setAnim(ANIM_DEATH);
        return state;
    }

    int turn(float delta, float speed) {
        float w = speed * Core::deltaTime;

        updateTilt(delta, w, speed * 0.1f);

        if (delta != 0.0f) {
            decrease(delta, angle.y, w);
            if (speed != 0.0f) {
                velocity = velocity.rotateY(-w);
                return speed < 0 ? LEFT : RIGHT;
            }
        }

        angle.z = 0.0f;
        return 0;
    }

    virtual void updatePosition() {
        float angleY = 0.0f;
        if (target && target->health > 0.0f && fabsf(targetAngle) > GIANT_MUTANT_MIN_ANGLE)
            if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK || state == STATE_STOP)
                angleY = targetAngle;
        
        if (angleY != 0.0f) {
            turn(targetAngle, GIANT_MUTANT_TURN_SLOW);
        }

        Enemy::updatePosition();
        //setOverrides(true, jointChest, jointHead);
        //lookAt(target);
    }
};

#define CENTAUR_TURN_FAST   (DEG2RAD * 120)
#define CENTAUR_DIST_RUN    (1024 + 512)
#define CENTAUR_PART_DAMAGE 100

struct Centaur : Enemy {

    enum {
        HIT_MASK = 0x030199,
    };

    enum {
        ANIM_DEATH = 8,
    };

    enum {
        STATE_NONE,
        STATE_STOP,
        STATE_FIRE,
        STATE_RUN,
        STATE_AIM,
        STATE_DEATH,
        STATE_IDLE,
    };

    Centaur(IGame *game, int entity) : Enemy(game, entity, 120, 341, 400.0f, 1.0f) {
        jointChest = 10;
        jointHead  = 17;
    }

    virtual void setSaveData(const SaveEntity &data) {
        Character::setSaveData(data);
        if (flags.invisible)
            deactivate(true);
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetCanAttack && targetDist < CENTAUR_DIST_RUN)
                    return STATE_RUN;
                if (targetIsVisible(MAX_SHOT_DIST))
                    return STATE_AIM;
                return STATE_RUN;
            case STATE_RUN :
                if (targetCanAttack && targetDist < CENTAUR_DIST_RUN) {
                    nextState = STATE_IDLE;
                    return STATE_STOP;
                }
                if (targetIsVisible(MAX_SHOT_DIST)) {
                    nextState = STATE_AIM;
                    return STATE_STOP;
                }
                if (rand() < 96) {
                    nextState = STATE_IDLE;
                    return STATE_STOP;
                }
                break;
            case STATE_AIM :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetIsVisible(MAX_SHOT_DIST))
                    return STATE_FIRE;
                return STATE_STOP;
            case STATE_FIRE :
                if (nextState != STATE_NONE)
                    break;
                nextState = STATE_AIM;
                shot(TR::Entity::CENTAUR_BULLET, 13, vec3(11.0f, 415.0f, 41.0f));
                break;
            case STATE_IDLE :
                if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
                    bite(5, vec3(50.0f, 30.0f, 0.0f), 200);
                    nextState = STATE_STOP;
                }
                break;
        }

        return state;
    }

    virtual int getStateDeath() {
        if (state == STATE_DEATH) return state;
        return animation.setAnim(ANIM_DEATH);
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN, CENTAUR_TURN_FAST);

        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }

    virtual void deactivate(bool removeFromList = false) {
        if (!removeFromList) {
            if (!explodeMask)
                explode(0xffffffff, CENTAUR_PART_DAMAGE);
            return;
        }
        Enemy::deactivate(removeFromList);
    }

    virtual void update() {
        bool exploded = explodeMask != 0;

        Enemy::update();

        if (exploded && !explodeMask) {
            deactivate(true);
            flags.invisible = true;
        }
    }
};


struct Mummy : Enemy {
    enum {
        STATE_NONE,
        STATE_IDLE,
        STATE_FALL,
    };

    Mummy(IGame *game, int entity) : Enemy(game, entity, 18, 341, 150.0f, 0.0f) {
        jointHead = 2;
    }

    virtual void update() {
        if (state == STATE_IDLE && (health <= 0.0f || collide(game->getLara(pos)))) {
            animation.setState(STATE_FALL);
            health = 0.0f;
        }
        Enemy::update();
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;
        return state;
    }

    virtual void updatePosition() {
        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};


#define DOPPELGANGER_ROOM_CENTER (vec3(36, 0, 60) * 1024.0f)

struct Doppelganger : Enemy {
    enum {
        ANIM_FALL = 34,
    };

    Doppelganger(IGame *game, int entity) : Enemy(game, entity, 1000, 341, 150.0f, 0.0f) {
        jointChest = 7;
        jointHead  = 14;
    }

    virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
        enemy->hit(damage * 10, this);
    };

    virtual void update() {
        if (!target)
            target = (Character*)game->getLara(pos);

        if (stand != STAND_AIR) {
            pos      = DOPPELGANGER_ROOM_CENTER * 2.0f - target->pos;
            pos.y    = target->pos.y;
            angle    = target->angle;
            angle.y -= PI;
        }

        Enemy::updateRoom();

        TR::Level::FloorInfo info;
        getFloorInfo(getRoomIndex(), target->pos, info);
        float laraHeight = info.floor - target->pos.y;

        getFloorInfo(getRoomIndex(), pos, info);
        float selfHeight = info.floor - pos.y;

        if (stand != STAND_AIR && target->stand == Character::STAND_GROUND && selfHeight > 1024 && laraHeight < 256) {
            animation = Animation(level, target->getModel());
            animation.setAnim(ANIM_FALL, 1);
            stand = STAND_AIR;
            velocity.x = velocity.y = 0.0f;
        }

        if (stand == STAND_AIR) {
            if (selfHeight < 128.0f) {
                game->checkTrigger(this, true);
                flags.invisible = true;
                deactivate(true);
            } else {
                updateAnimation(true);
                applyGravity(velocity.y);
                pos += velocity * (30.0f * Core::deltaTime);
            }
        } else {
            animation.frameA = target->animation.frameA;
            animation.frameB = target->animation.frameB;
            animation.delta  = target->animation.delta;
        }
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;
        return state;
    }

    virtual void updatePosition() {
        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};


struct ScionTarget : Enemy {
    ScionTarget(IGame *game, int entity) : Enemy(game, entity, 5, 0, 0, 0) {}

    virtual void update() {
        Controller::update();

        if (health <= 0.0f) {
            if (timer == 0.0f) {
                flags.invisible = true;
                game->checkTrigger(this, true);
                timer = 3.0f;
            }

            if (timer > 0.0f) {
                int index = int(timer / 0.3f);
                timer -= Core::deltaTime;

                if (index != int(timer / 0.3f)) {
                    vec3 p = pos + vec3((randf() * 2.0f - 1.0f) * 512.0f, (randf() * 2.0f - 1.0f) * 64.0f - 500.0f, (randf() * 2.0f - 1.0f) * 512.0f);
                    game->addEntity(TR::Entity::EXPLOSION, getRoomIndex(), p);
                    game->shakeCamera(0.5f);
                }

                if (timer < 0.0f) 
                    deactivate(true);
            }
        }
    }
};

#define HUMAN_WAIT       0.01f
#define HUMAN_DIST_WALK  (1024 * 3)
#define HUMAN_DIST_SHOT  (1024 * 7)
#define HUMAN_TURN_SLOW  (DEG2RAD * 90)
#define HUMAN_TURN_FAST  (DEG2RAD * 180)

struct Human : Enemy {
    enum {
        STATE_NONE,
        STATE_STOP,
        STATE_WALK,
        STATE_RUN,
        STATE_AIM,
        STATE_DEATH,
        STATE_WAIT, // == STATE_FIRE for MrT and Cowboy
        STATE_FIRE
    };

    int jointGun;
    int animDeath;

    Human(IGame *game, int entity, float health) : Enemy(game, entity, health, 100, 0.0f, 1.0f), animDeath(-1) {
        jointGun   = 0;
        jointChest = 7;
        jointHead  = 8;
    }

    virtual void deactivate(bool removeFromList = false) {
        if (health <= 0.0f)
            onDead();
        Enemy::deactivate(removeFromList);
        getRoom().removeDynLight(entity);
    }

    virtual int getStateDeath() {
        return (animDeath == -1 || state == STATE_DEATH || state == STATE_NONE) ? STATE_DEATH : animation.setAnim(animDeath);
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;
        return state;
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN || state == STATE_WALK, state == STATE_RUN ? HUMAN_TURN_FAST : HUMAN_TURN_SLOW);
        
        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }

    virtual void onDead() {}

    bool doShot(float damage, const vec3 &muzzleOffset) {
        game->addMuzzleFlash(this, jointGun, muzzleOffset, -1);

        if (targetDist < HUMAN_DIST_SHOT && randf() < ((HUMAN_DIST_SHOT - targetDist) / HUMAN_DIST_SHOT - 0.25f)) {
            bite(-1, vec3(0.0f), damage);
            game->addEntity(TR::Entity::BLOOD, target->getRoomIndex(), target->getJoint(rand() % target->getModel()->mCount).pos);
            game->playSound(target->stand == STAND_UNDERWATER ? TR::SND_HIT_UNDERWATER : TR::SND_HIT, target->pos, Sound::PAN);
            return true;
        }

        int16 roomIndex = getRoomIndex();
        TR::Room::Sector *sector = level->getSector(roomIndex, target->pos);
        float floor = level->getFloor(sector, target->pos) - 64.0f;
        vec3 p = vec3(target->pos.x + randf() * 512.0f - 256.0f, floor, target->pos.z + randf() * 512.0f - 256.0f);

        target->addRicochet(p, true);
        return false;
    }
};


#define LARSON_DAMAGE 50

struct Larson : Human {

    Larson(IGame *game, int entity) : Human(game, entity, 50) {
        animDeath = 15;
        jointGun  = 14;
    }

    virtual int getStateGround() {
        if (!think(false))
            return state;

        fullChestRotation = state == STATE_FIRE || state == STATE_AIM;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (nextState != STATE_NONE)
                    return nextState;
                if (mood == MOOD_SLEEP)
                    return randf() < HUMAN_WAIT ? STATE_WAIT : STATE_WALK;
                if (mood == MOOD_ESCAPE)
                    return STATE_RUN;
                return STATE_WALK;
            case STATE_WAIT : 
                if (mood != MOOD_SLEEP)
                    return STATE_STOP;
                if (randf() < HUMAN_WAIT) {
                    nextState = STATE_WALK;
                    return STATE_STOP;
                }
                break;
            case STATE_WALK :
                if (mood == MOOD_SLEEP && randf() < HUMAN_WAIT)
                    nextState = STATE_WAIT;
                else if (mood == MOOD_ESCAPE)
                    nextState = STATE_RUN;
                else if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (!targetInView || targetDist > HUMAN_DIST_WALK)
                    nextState = STATE_RUN;
                else
                    break;
                return STATE_STOP;
            case STATE_RUN :
                if (mood == MOOD_SLEEP && randf() < HUMAN_WAIT)
                    nextState = STATE_WAIT;
                else if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (targetInView && targetDist < HUMAN_DIST_WALK)
                    nextState = STATE_WALK;
                else
                    break;
                return STATE_STOP;
            case STATE_AIM :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_FIRE;
                return STATE_STOP;
            case STATE_FIRE :
                if (nextState == STATE_NONE) {
                    doShot(LARSON_DAMAGE, vec3(-50, 0, 20));
                    nextState = STATE_AIM;
                }
                if (mood == MOOD_ESCAPE || target->health <= 0.0f)
                    nextState = STATE_STOP;
                break;
        }

        return state;
    }
};


#define PIERRE_MIN_HEALTH   40
#define PIERRE_DAMAGE       25

struct Pierre : Human {

    Pierre(IGame *game, int entity) : Human(game, entity, 70) {
        animDeath = 12;
    }

    virtual void onDead() {
        game->addEntity(TR::Entity::MAGNUMS,           getRoomIndex(), pos, 0);
        game->addEntity(TR::Entity::SCION_PICKUP_DROP, getRoomIndex(), pos, 0);
        game->addEntity(TR::Entity::KEY_ITEM_1,        getRoomIndex(), pos, 0);
    }

    virtual int getStateGround() {
        if (!think(false))
            return state;

        if (!flags.once && health <= PIERRE_MIN_HEALTH) {
            health = PIERRE_MIN_HEALTH;
            timer += Core::deltaTime;
        }

        if (timer > 0.0f && isVisible()) // time to run away!
            timer = 0.0f;

        if (getRoom().flags.water)
            timer = 1.0f;

        if (timer > 0.4f) {
            flags.invisible = true;
            deactivate(true);
        }

        fullChestRotation = state == STATE_FIRE || state == STATE_AIM;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (nextState != STATE_NONE)
                    return nextState;
                if (mood == MOOD_SLEEP)
                    return randf() < HUMAN_WAIT ? STATE_WAIT : STATE_WALK;
                if (mood == MOOD_ESCAPE)
                    return STATE_RUN;
                return STATE_WALK;
            case STATE_WAIT : 
                if (mood != MOOD_SLEEP)
                    return STATE_STOP;
                if (randf() < HUMAN_WAIT) {
                    nextState = STATE_WALK;
                    return STATE_STOP;
                }
                break;
            case STATE_WALK :
                if (mood == MOOD_SLEEP && randf() < HUMAN_WAIT)
                    nextState = STATE_WAIT;
                else if (mood == MOOD_ESCAPE)
                    nextState = STATE_RUN;
                else if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (!targetInView || targetDist > HUMAN_DIST_WALK)
                    nextState = STATE_RUN;
                else
                    break;
                return STATE_STOP;
            case STATE_RUN :
                if (mood == MOOD_SLEEP && randf() < HUMAN_WAIT)
                    nextState = STATE_WAIT;
                else if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (targetInView && targetDist < HUMAN_DIST_WALK)
                    nextState = STATE_WALK;
                else
                    break;
                return STATE_STOP;
            case STATE_AIM :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_FIRE;
                return STATE_STOP;
            case STATE_FIRE :
                if (nextState == STATE_NONE) {
                    jointGun = 11; doShot(PIERRE_DAMAGE, vec3(60, 0, 50));
                    jointGun = 14; doShot(PIERRE_DAMAGE, vec3(-60, 0, 50));
                    nextState = STATE_AIM;
                }
                if (mood == MOOD_ESCAPE && (rand() % 2))
                    nextState = STATE_STOP;
                break;
        }

        return state;
    }
};


#define SKATERBOY_DIST_MIN      2560
#define SKATERBOY_DIST_MAX      4096
#define SKATERBOY_TURN_FAST     (120 * DEG2RAD)
#define SKATERBOY_DAMAGE_STAND  50.0f
#define SKATERBOY_DAMAGE_MOVE   40.0f

struct SkaterBoy : Human {

    enum {
        STATE_STOP,
        STATE_STAND_FIRE,
        STATE_MOVE,
        STATE_STEP,
        STATE_MOVE_FIRE,
        STATE_DEATH
    };

    Controller *board;

    SkaterBoy(IGame *game, int entity) : Human(game, entity, 125) {
        animDeath  = 13;
        jointChest = 1;

        board = game->addEntity(TR::Entity::ENEMY_SKATEBOARD, getRoomIndex(), pos, 0.0f);
        board->activate();
    }

    virtual void onDead() {
        game->addEntity(TR::Entity::UZIS, getRoomIndex(), pos, 0);
    }

    virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
        bool flag = health >= 120;
        Human::hit(damage, enemy, hitType);
        if (flag && health < 120) {
            game->playTrack(56, true);
        }
    };
    
    virtual int getStateGround() {
        if (!think(false))
            return state;

        fullChestRotation = state == STATE_STAND_FIRE || state == STATE_MOVE_FIRE;

        switch (state) {
            case STATE_STOP :
                flags.unused = 0;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_STAND_FIRE;
                return STATE_MOVE;
            case STATE_MOVE :
                flags.unused = 0;
                if (rand() < 512)
                    return STATE_STEP;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return (mood != MOOD_ESCAPE && targetDist > SKATERBOY_DIST_MIN && targetDist < SKATERBOY_DIST_MAX) ? STATE_STOP : STATE_MOVE_FIRE;
                break;
            case STATE_STEP :
                if (rand() < 1024)
                    return STATE_MOVE;
                break;
            case STATE_STAND_FIRE :
            case STATE_MOVE_FIRE  :
                if (!flags.unused && targetIsVisible(HUMAN_DIST_SHOT)) {
                    float damage = state == STATE_STAND_FIRE ? SKATERBOY_DAMAGE_STAND : SKATERBOY_DAMAGE_MOVE;
                    jointGun = 7; doShot(damage, vec3(0, -32, 0));
                    jointGun = 4; doShot(damage, vec3(0, -32, 0));
                    flags.unused = 1;
                }

                if (mood == MOOD_ESCAPE || targetDist < 1024)
                    return STATE_RUN;
                break;
        }

        return state;
    }

    virtual void update() {
        Human::update();
        board->pos   = pos;
        board->angle = angle;
        if (board->animation.index != animation.index)
            board->animation.setAnim(animation.index);
        board->animation.time = animation.time - Core::deltaTime;
    }

    virtual void deactivate(bool removeFromList) {
        board->deactivate(removeFromList);
        Human::deactivate(removeFromList);
    }
};


#define COWBOY_DIST_WALK (3 * 1024)
#define COWBOY_DAMAGE    70

struct Cowboy : Human {

    Cowboy(IGame *game, int entity) : Human(game, entity, 150) {
        animDeath  = 7;
        jointChest = 1;
        jointHead  = 2;
    }

    virtual void onDead() {
        game->addEntity(TR::Entity::MAGNUMS, getRoomIndex(), pos, 0);
    }

    virtual int getStateGround() {
        if (!think(false))
            return state;

        fullChestRotation = state == STATE_WAIT || state == STATE_AIM;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_AIM;
                if (mood == MOOD_SLEEP)
                    return STATE_WALK;
                return STATE_RUN;
            case STATE_WALK : 
                if (mood == MOOD_ESCAPE || !targetInView)
                    nextState = STATE_RUN;
                else if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (targetDist > COWBOY_DIST_WALK)
                    nextState = STATE_RUN;
                else
                    break;
                return STATE_STOP;
            case STATE_RUN :
                if (mood == MOOD_ESCAPE || !targetInView)
                    break;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (targetDist < COWBOY_DIST_WALK && targetInView)
                    nextState = STATE_WALK;
                else
                    break;
                return STATE_STOP;
            case STATE_AIM :
                flags.unused = 7;
                if (nextState != STATE_NONE)
                    return STATE_STOP;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_WAIT; // STATE_FIRE
                return STATE_STOP;
            case STATE_WAIT : // STATE_FIRE
                if (animation.frameIndex != flags.unused && (animation.frameIndex == 0 || animation.frameIndex == 4)) {
                    jointGun = (flags.unused == 7) ? 8 : 5;
                    doShot(COWBOY_DAMAGE, vec3(0, -40, 40));
                    flags.unused = animation.frameIndex;
                }

                if (mood == MOOD_ESCAPE)
                    nextState = STATE_RUN;
                break;
        }

        return state;
    }
};


#define MRT_DIST_WALK (4 * 1024)
#define MRT_DAMAGE    150

struct MrT : Human {

    MrT(IGame *game, int entity) : Human(game, entity, 200) {
        animDeath  = 14;
        jointGun   = 9;
        jointChest = 1;
        jointHead  = 2;
        state = STATE_RUN;
    }

    virtual void onDead() {
        game->addEntity(TR::Entity::SHOTGUN, getRoomIndex(), pos, 0);
    }

    virtual int getStateGround() {
        if (!think(false))
            return state;

        fullChestRotation = state == STATE_WAIT || state == STATE_AIM;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                if (nextState != STATE_NONE)
                    return nextState;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_AIM;
                if (mood == MOOD_SLEEP)
                    return STATE_WALK;
                return STATE_RUN;
            case STATE_WALK : 
                if (mood == MOOD_ESCAPE || !targetInView)
                    nextState = STATE_RUN;
                else if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (targetDist > MRT_DIST_WALK)
                    nextState = STATE_RUN;
                else
                    break;
                return STATE_STOP;
            case STATE_RUN :
                if (mood == MOOD_ESCAPE || !targetInView)
                    break;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    nextState = STATE_AIM;
                else if (targetDist < MRT_DIST_WALK && targetInView)
                    nextState = STATE_WALK;
                else
                    break;
                return STATE_STOP;
            case STATE_AIM :
                flags.unused = false;
                if (nextState != STATE_NONE)
                    return STATE_STOP;
                if (targetIsVisible(HUMAN_DIST_SHOT))
                    return STATE_WAIT; // STATE_FIRE
                return STATE_STOP;
            case STATE_WAIT : // STATE_FIRE
                if (!flags.unused) {
                    doShot(MRT_DAMAGE, vec3(-20, -20, 300));
                    flags.unused = true;
                }
                if (mood == MOOD_ESCAPE)
                    nextState = STATE_RUN;
                break;
        }

        return state;
    }
};

#define NATLA_FIRE_ANGLE    (30 * DEG2RAD)
#define NATLA_FAINT_TIME    16.0f
#define NATLA_HALF_HEALTH   200.0f
#define NATLA_FAINT_HEALTH  -8192.0f
#define NATLA_LIFT_SPEED    MUTANT_LIFT_SPEED

struct Natla : Human {

    enum {
        STATE_NONE,
        STATE_STOP,
        STATE_FLY,
        STATE_RUN,
        STATE_AIM,
        STATE_FAINT,
        STATE_FIRE,
        STATE_FALL,
        STATE_STAND,
        STATE_DEATH,
    };

    enum {
        FLAG_FLY = 1,
    };

    Natla(IGame *game, int entity) : Human(game, entity, 400) {
        jointChest = 1;
        jointHead  = 2;
    }

    int stage1() {
        flying = (flags.unused & FLAG_FLY);

        stepHeight =  256;
        dropHeight = -256;

        if (flying) {
            stepHeight *= 80;
            dropHeight *= 80;
        }

        if (!think(false))
            return state;

        timer += Core::deltaTime;
        bool canShot = target && target->health > 0.0f && targetIsVisible(HUMAN_DIST_SHOT);

        if (canShot && state == STATE_FLY && flying && rand() < 256)
            flags.unused &= ~FLAG_FLY;
        else if (!canShot)
            flags.unused |= FLAG_FLY;

        flying = (flags.unused & FLAG_FLY);

        if (state == nextState)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP :
                timer = 0.0f;
                return flying ? STATE_FLY : STATE_AIM;
            case STATE_FLY : {
                if (timer >= 1.0f) {
                    { //if (canShot) { ???
                        game->playSound(TR::SND_NATLA_SHOT, pos, Sound::PAN);
                        shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f));
                    }
                    timer -= 1.0f;
                }

                int16 roomIndex = getRoomIndex();
                TR::Room::Sector *sector = level->getSector(roomIndex, pos);
                float floor = level->getFloor(sector, pos) - 128.0f;

                if (!flying && pos.y >= floor)
                    return STATE_STOP;

                break;
            }
            case STATE_AIM :
                if (nextState != STATE_NONE)
                    return nextState;
                return canShot ? STATE_FIRE : STATE_STOP;
            case STATE_FIRE :
                if (nextState != STATE_NONE)
                    return state;

                game->playSound(TR::SND_NATLA_SHOT, pos, Sound::PAN);
                shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f));
                shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f), 0.0f, (randf() * 2.0f - 1.0f) * (25.0f * DEG2RAD));
                shot(TR::Entity::CENTAUR_BULLET, 4, vec3(5.0f, 220.0f, 7.0f), 0.0f, (randf() * 2.0f - 1.0f) * (25.0f * DEG2RAD));

                nextState = STATE_STOP;
                break;
        }

        return state;
    }

    int stage2() {
        stepHeight =  256;
        dropHeight = -256;
        flying     = false;

        if (!think(true))
            return state;

        timer += Core::deltaTime;
        bool canShot = target && target->health > 0.0f && fabsf(targetAngle) < NATLA_FIRE_ANGLE && targetIsVisible(HUMAN_DIST_SHOT);

        switch (state) {
            case STATE_RUN   :
            case STATE_STAND :
                if (timer >= 20.0f / 30.0f) {
                    { // if (canShot) { ???
                        game->playSound(TR::SND_NATLA_SHOT, pos, Sound::PAN);
                        shot(TR::Entity::MUTANT_BULLET, 4, vec3(5.0f, 220.0f, 7.0f));
                    }
                    timer -= 20.0f / 30.0f;
                }
                return canShot ? STATE_STAND : STATE_RUN;
            case STATE_FLY  :
                health = NATLA_FAINT_HEALTH;
                return STATE_FALL;
            case STATE_STOP :
            case STATE_AIM  :
            case STATE_FIRE :
                health = NATLA_FAINT_HEALTH;
                return STATE_FAINT;
        }
        return state;
    }

    virtual int getStateGround() {
        if (health > NATLA_HALF_HEALTH)
            return stage1();
        return stage2();
    }

    virtual int getStateDeath() {
        switch (state) {
            case STATE_FALL : {
                int16 roomIndex = getRoomIndex();
                TR::Room::Sector *sector = level->getSector(roomIndex, pos);
                float floor = level->getFloor(sector, pos);
                if (pos.y >= floor) {
                    pos.y = floor;
                    timer = 0.0f;
                    return STATE_FAINT;
                }
                return state;
            }
            case STATE_FAINT : {
                timer += Core::deltaTime;
                if (timer >= NATLA_FAINT_TIME) {
                    health = NATLA_HALF_HEALTH;
                    timer  = 0.0f;
                    flags.unused = 0;
                    game->playTrack(54, true);
                    return STATE_STAND;
                }
                return state;
            }
        }
        return STATE_DEATH;
    }

    virtual void updateVelocity() {
        if (state == STATE_FLY) {
            angle.y += targetAngle;
            updateJoints();
            angle.y -= targetAngle;
        }
        Enemy::updateVelocity();
    }

    virtual void updatePosition() {
        if (flying) {
            stand = STAND_AIR;
            lift(target->pos.y - pos.y, NATLA_LIFT_SPEED);
        }

        turn(true, state == STATE_RUN ? HUMAN_TURN_FAST : HUMAN_TURN_SLOW);
        
        Enemy::updatePosition();
        setOverrides(state != STATE_FAINT, jointChest, jointHead);
        lookAt(target);

        stand = STAND_GROUND;
    }
};

struct Dog : Enemy {

    enum {
        ANIM_SLEEP = 5,
        ANIM_DEATH = 13,
    };

    enum {
        STATE_DEATH = 10,
    };

    Dog(IGame *game, int entity) : Enemy(game, entity, 6, 10, 0.0f, 0.0f) {
        jointChest = 19;
        jointHead  = 20;
        animation.setAnim(ANIM_SLEEP);
    }

    virtual int getStateDeath() {
        if (state != STATE_DEATH)
            return animation.setAnim(ANIM_DEATH);
        return state;
    }
};

#define TIGER_WALK           1120
#define TIGER_ROAR           96
#define TIGER_DIST_ATTACK_1  341
#define TIGER_DIST_ATTACK_2  1536
#define TIGER_DIST_ATTACK_3  1024
#define TIGER_DAMAGE         100
#define TIGER_TURN_FAST      (DEG2RAD * 180)
#define TIGER_TURN_SLOW      (DEG2RAD * 90)

struct Tiger : Enemy {

    enum {
        HIT_MASK = 0x7FDC000,
    };

    enum {
        ANIM_DEATH = 11,
    };

    enum {
        STATE_NONE = -1,
        STATE_DEATH    ,
        STATE_STOP     ,
        STATE_WALK     ,
        STATE_RUN      ,
        STATE_IDLE     ,
        STATE_ROAR     ,
        STATE_ATTACK_1 ,
        STATE_ATTACK_2 ,
        STATE_ATTACK_3 ,
    };

    Tiger(IGame *game, int entity) : Enemy(game, entity, 20, 341, 200.0f, 0.25f) {
        dropHeight = -1024;
        jointChest = -1;//21;
        jointHead  = -1;//22;
        nextState  = STATE_NONE;
    }

    virtual int getStateGround() {
        if (!think(true))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        switch (state) {
            case STATE_STOP    :
                if (mood == MOOD_ESCAPE)
                    return STATE_RUN;
                if (mood == MOOD_SLEEP) {
                    int r = rand();
                    if (r < TIGER_ROAR) return STATE_ROAR;
                    if (r < TIGER_WALK) return STATE_WALK;
                    return state;
                }
                if (targetInView && targetDist < TIGER_DIST_ATTACK_1)
                    return STATE_ATTACK_1;
                if (targetInView && targetDist < TIGER_DIST_ATTACK_3)
                    return STATE_ATTACK_3;
                if (nextState != STATE_NONE)
                    return nextState;
                if (mood != MOOD_ATTACK && rand() < TIGER_ROAR)
                    return STATE_ROAR;
                return STATE_RUN;
            case STATE_WALK     : 
                if (mood == MOOD_ATTACK || mood == MOOD_ESCAPE)
                    return STATE_RUN;
                if (rand() < TIGER_ROAR) {
                    nextState = STATE_ROAR;
                    return STATE_STOP;
                }
                break;
            case STATE_RUN      : {
                bool melee = flags.unused != 0;
                flags.unused = 0;

                if (mood == MOOD_SLEEP)
                    return STATE_STOP;
                if (targetInView && melee)
                    return STATE_STOP;
                if (targetInView && targetDist < TIGER_DIST_ATTACK_2) {
                    if (target->velocity.length2() < SQR(16.0f))
                        return STATE_STOP;
                    else
                        return STATE_ATTACK_2;
                }
                if (mood != MOOD_ATTACK && rand() < TIGER_ROAR) {
                    nextState = STATE_ROAR;
                    return STATE_STOP;
                }
                break;
            }
            case STATE_ATTACK_1 :
            case STATE_ATTACK_2 :
            case STATE_ATTACK_3 :
                if (flags.unused == 0 && (collide(target) & HIT_MASK)) {
                    bite(26, vec3(19.0f, -13.0f, 3.0f), TIGER_DAMAGE);
                    flags.unused = 1;
                }
        }
        return state;
    }

    virtual int getStateDeath() {
        if (state == STATE_DEATH)
            return state;
        return animation.setAnim(ANIM_DEATH);
    }

    virtual void updatePosition() {
        turn(state == STATE_RUN || state == STATE_WALK || state == STATE_ROAR, state == STATE_RUN ? TIGER_TURN_FAST : TIGER_TURN_SLOW);

        if (state == STATE_DEATH) {
            animation.overrideMask = 0;
            return;
        }

        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }
};


#define WINSTON_DIST        1536.0f
#define WINSTON_TURN_SLOW   (DEG2RAD * 60)
#define WINSTON_FREEZE_TIME 60.0f

struct Winston : Enemy {

    enum {
        STATE_NONE ,
        STATE_STOP ,
        STATE_WALK ,
    };

    Texture *environment;

    Winston(IGame *game, int entity) : Enemy(game, entity, 20, 341, 200.0f, 0.25f), environment(NULL) {
        dropHeight = -1024;
        jointChest = 11;
        jointHead  = 25;
        nextState  = STATE_NONE;
        lookAtSpeed = 1.0f;
        timer = 0.0f;
    }

    virtual ~Winston() {
        delete environment;
    }

    virtual int getStateGround() {
        if (getRoomIndex() == 94) {
            int doorIndex = (level->version & TR::VER_TR2) ? 38 : 68;

            Controller *door = (Controller*)game->getLevel()->entities[doorIndex].controller;
            if (!door->isActive()) {
                if (timer > WINSTON_FREEZE_TIME) {
                    flags.unused |= 4;
                    if (!environment) {
                        bakeEnvironment(environment);
                    }
                } else {
                    timer += Core::deltaTime;
                }
                return STATE_STOP;
            } else {
                if (!(flags.unused & 4)) {
                    timer = 0.0f;
                }
            }
        }

        if (flags.unused & 4) {
            timer -= Core::deltaTime;
            if (timer < 0.0f) {
                timer = 0.0f;
                flags.unused &= ~4;
            }
            return STATE_STOP;
        }

        if (!think(false))
            return state;

        if (nextState == state)
            nextState = STATE_NONE;

        if (nextState != STATE_NONE)
            return nextState;

        switch (state) {
            case STATE_STOP    :
                if ((targetDist > WINSTON_DIST || !targetInView) && nextState != STATE_WALK) {
                    nextState = STATE_WALK;
                    game->playSound(TR::SND_WINSTON_WALK, pos, Sound::PAN);
                }
            case STATE_WALK     :
                if (targetDist < WINSTON_DIST) {
                    if (targetInView) {
                        nextState = STATE_STOP;
                        flags.unused &= ~1;
                    } else if (!(flags.unused & 1)) {
                        game->playSound(TR::SND_WINSTON_SCARED, pos, Sound::PAN);
                        game->playSound(TR::SND_WINSTON_TRAY, pos, Sound::PAN);
                        flags.unused |= 1;
                    }
                }
        }

        bool touch = collide(target) != 0;
        bool push  = (flags.unused & 2) != 0;

        if (!push && touch) {
            game->playSound(TR::SND_WINSTON_PUSH, pos, Sound::PAN);
            game->playSound(TR::SND_WINSTON_TRAY, pos, Sound::PAN);
            flags.unused |= 2;
        }

        if (push && !touch) {
            flags.unused &= ~2;
        }

        if (rand() < 0x100) {
            game->playSound(TR::SND_WINSTON_TRAY, pos, Sound::PAN);
        }

        return state;
    }

    virtual void updatePosition() {
        if (flags.unused & 4) {
            animation.time = 0.0f;
            animation.updateInfo();
            return;
        }

        turn(state == STATE_WALK, WINSTON_TURN_SLOW);
        angle.z = 0.0f;

        Enemy::updatePosition();
        setOverrides(true, jointChest, jointHead);
        lookAt(target);
    }

    virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) {
        if (environment && (flags.unused & 4)) {
            game->setRoomParams(getRoomIndex(), Shader::MIRROR, 1.5f, 2.0f, 2.5f, 1.0f, false);
            GAPI::Texture *dtex = Core::active.textures[sDiffuse];
            environment->bind(sDiffuse);
            Controller::render(frustum, mesh, type, caustics);
            if (dtex) dtex->bind(sDiffuse);
        } else {
            Enemy::render(frustum, mesh, type, caustics);
        }
    }
};

#endif