1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-04-21 11:31:56 +02:00
openlara/src/enemy.h
2020-07-11 14:34:33 +03:00

3507 lines
106 KiB
C

#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;
// TODO: check for some quadrant shit
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, 375.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, ShaderType type, bool caustics) {
if (environment && (flags.unused & 4)) {
game->setRoomParams(getRoomIndex(), shMirror, 1.5f, 2.0f, 2.5f, 1.0f, false);
environment->bind(sEnvironment);
Controller::render(frustum, mesh, type, caustics);
} else {
Enemy::render(frustum, mesh, type, caustics);
}
}
};
#endif