1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-14 00:54:05 +02:00

#14 Raptor & T-Rex behavior, add hitmask for enemies #8 camera shake effect

This commit is contained in:
XProger
2017-05-19 09:49:30 +03:00
parent 8d677d5c7d
commit 669350a02d
8 changed files with 445 additions and 154 deletions

View File

@@ -798,7 +798,7 @@ struct ZoneCache {
return items = new Item(zone, count, zones, boxes, items);
}
uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
if (boxStart == 0xFFFF || boxEnd == 0xFFFF)
return 0;
@@ -859,6 +859,9 @@ struct ZoneCache {
// has same zone
if (zones[index] != zone)
continue;
// check passability
if (big && level->boxes[index].overlap.blockable)
continue;
// check blocking (doors)
if (level->boxes[index].overlap.block)
continue;

View File

@@ -28,6 +28,8 @@ struct Camera : Controller {
bool firstPerson;
bool isVR;
float shake;
Camera(IGame *game, Lara *owner) : Controller(game, owner ? owner->entity : 0), owner(owner), frustum(new Frustum()), timer(0.0f), actTargetEntity(-1), actCamera(-1), reflectPlane(NULL), isVR(false) {
changeView(false);
cutscene = owner->getEntity().type != TR::Entity::LARA && level->cameraFrames;
@@ -90,6 +92,9 @@ struct Camera : Controller {
}
virtual void update() {
if (shake > 0.0f)
shake = max(0.0f, shake - Core::deltaTime);
#ifndef LEVEL_EDITOR
if (cutscene) { // cutscene
timer += Core::deltaTime * 30;
@@ -149,8 +154,8 @@ struct Camera : Controller {
advAngle.y = lerp(clampAngle(advAngle.y), 0.0f, t);
}
#endif
angle = owner->angle + advAngle;
if (owner->health > 0)
angle = owner->angle + advAngle;
angle.z = 0.0f;
if (owner->stand == Lara::STAND_ONWATER)
@@ -275,13 +280,14 @@ struct Camera : Controller {
Core::mViewInv = mViewInv;
Core::mView = Core::mViewInv.inverse();
if (shake > 0.0f)
Core::mView.translate(vec3(0.0f, sinf(shake * PI * 7) * shake * 48.0f, 0.0f));
if (isVR)
Core::mView.translate(Core::mViewInv.right.xyz * (-Core::eye * 32.0f));
Core::mProj = getProjMatrix();
// TODO: camera shake
// TODO: temporal anti-aliasing
// Core::mProj.e02 = (randf() - 0.5f) * 32.0f / Core::width;
// Core::mProj.e12 = (randf() - 0.5f) * 32.0f / Core::height;

View File

@@ -20,7 +20,7 @@ struct IGame {
virtual MeshBuilder* getMesh() { return NULL; }
virtual Controller* getCamera() { return NULL; }
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) { return 0; }
virtual uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) { return 0; }
virtual uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) { return 0; }
virtual void setClipParams(float clipSign, float clipHeight) {}
virtual void setWaterParams(float height) {}
virtual void updateParams() {}
@@ -28,6 +28,7 @@ struct IGame {
virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {}
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {}
virtual void renderCompose(int roomIndex, bool genShadowMask = false) {}
virtual void fxQuake(float time) {}
};
struct Controller {
@@ -265,14 +266,14 @@ struct Controller {
}
bool collide(Controller *controller) {
int collide(Controller *controller, bool checkBoxes = true) {
TR::Model *a = getModel();
TR::Model *b = getModel();
TR::Model *b = controller->getModel();
if (!a || !b)
return false;
return 0;
if (!getBoundingBox().intersect(controller->getBoundingBox()))
return false;
if (checkBoxes && !getBoundingBox().intersect(controller->getBoundingBox()))
return 0;
ASSERT(a->mCount <= 34);
ASSERT(b->mCount <= 34);
@@ -283,12 +284,15 @@ struct Controller {
getSpheres(aSpheres);
controller->getSpheres(bSpheres);
for (int i = 0; i < a->mCount; i++)
for (int j = 0; j < b->mCount; j++)
if (aSpheres[i].intersect(bSpheres[j]))
return true;
return false;
int mask = 0;
for (int i = 0; i < a->mCount; i++)
if (aSpheres[i].radius > 0.0f)
for (int j = 0; j < b->mCount; j++)
if (bSpheres[j].radius > 0.0f && bSpheres[j].intersect(aSpheres[i])) {
mask |= (1 << i);
break;
}
return mask;
}
vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham
@@ -452,6 +456,7 @@ struct Controller {
if (cmd == TR::ANIM_CMD_EFFECT) {
switch (fx) {
case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break;
case TR::EFFECT_FLOOR_SHAKE : game->fxQuake(0.5f * max(0.0f, 1.0f - (pos - ((Controller*)level->cameraController)->pos).length2() / (15 * 1024 * 15 * 1024) )); break;
case TR::EFFECT_LARA_BUBBLES : doBubbles(); break;
default : cmdEffect(fx); break;
}

View File

@@ -496,16 +496,29 @@ namespace Debug {
TR::Model *m = controller->getModel();
if (!m) continue;
Box box = controller->getBoundingBoxLocal();
Debug::Draw::box(matrix, box.min, box.max, vec4(1.0));
bool bboxIntersect = false;
Sphere spheres[34];
ASSERT(m->mCount <= 34);
controller->getSpheres(spheres);
int mask = 0;
for (int j = 0; j < level.entitiesCount; j++) {
TR::Entity &t = level.entities[j];
if (j == i || ((!t.isEnemy() || !t.flags.active) && t.type != TR::Entity::LARA)) continue;
Controller *enemy = (Controller*)t.controller;
if (!controller->getBoundingBox().intersect(enemy->getBoundingBox()))
continue;
bboxIntersect = true;
mask |= controller->collide(enemy);
}
Box box = controller->getBoundingBoxLocal();
Debug::Draw::box(matrix, box.min, box.max, bboxIntersect ? vec4(1, 0, 0, 1): vec4(1));
for (int joint = 0; joint < m->mCount; joint++) {
Sphere &sphere = spheres[joint];
Debug::Draw::sphere(sphere.center, sphere.radius, vec4(0, 1, 1, 0.5f));
Debug::Draw::sphere(sphere.center, sphere.radius, (mask & (1 << joint)) ? vec4(1, 0, 0, 0.5f) : vec4(0, 1, 1, 0.5f));
/*
{ //if (e.id != 0) {
char buf[255];

View File

@@ -54,6 +54,7 @@ struct Enemy : Character {
vec3 waypoint;
float thinkTime;
float length; // dist from center to head (jaws)
float aggression;
int radius;
int stepHeight;
@@ -65,11 +66,20 @@ struct Enemy : Character {
int jointChest;
int jointHead;
Enemy(IGame *game, int entity, int health, int radius, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(0.0f), aggression(aggression), radius(radius), target(NULL), path(NULL) {
float targetDist;
bool targetDead;
bool targetInView; // target in enemy view zone
bool targetFromView; // enemy in target view zone
bool targetCanAttack;
Enemy(IGame *game, int entity, int health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), target(NULL), path(NULL) {
stepHeight = 256;
dropHeight = -256;
jointChest = jointHead = -1;
targetDist = +INF;
targetInView = targetFromView = targetCanAttack = false;
}
virtual ~Enemy() {
@@ -105,11 +115,14 @@ struct Enemy : Character {
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)
@@ -181,12 +194,12 @@ struct Enemy : Character {
animation.overrideMask &= ~(1 << chest);
}
void lookAt(int target, int chest, int head) {
void lookAt(int target, int chest, int head, bool rotate = false) {
float speed = 8.0f * Core::deltaTime;
quat rot;
if (chest > -1) {
if (aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot))
if (rotate && aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot))
rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed);
else
rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed);
@@ -194,7 +207,7 @@ struct Enemy : Character {
}
if (head > -1) {
if (aim(target, head, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot))
if (rotate && aim(target, head, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot))
rotHead = rotHead.slerp(rot, speed);
else
rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed);
@@ -244,7 +257,6 @@ struct Enemy : Character {
return 0;
}
virtual void hit(int damage, Controller *enemy = NULL) {
Character::hit(damage, enemy);
wound = true;
@@ -301,9 +313,18 @@ struct Enemy : Character {
if (!target) {
mood = MOOD_SLEEP;
targetDist = +INF;
targetInView = targetFromView = targetCanAttack = false;
return true;
}
vec3 targetVec = target->pos - pos - getDir() * length;
targetDist = targetVec.length();
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;
// update mood
@@ -364,7 +385,7 @@ struct Enemy : Character {
targetBoxOld = -1;
if (targetBoxOld != targetBox) {
if (findPath(stepHeight, dropHeight))
if (findPath(stepHeight, dropHeight, getEntity().isBigEnemy()))
nextWaypoint();
else
targetBox = -1;
@@ -449,12 +470,12 @@ struct Enemy : Character {
return !((e.x > t.x) ^ (x > 0)) || !((e.z > t.z) ^ (z > 0));
}
bool findPath(int ascend, int descend) {
bool findPath(int ascend, int descend, bool big) {
delete path;
path = NULL;
uint16 *boxes;
uint16 count = game->findPath(ascend, descend, box, targetBox, getZones(), &boxes);
uint16 count = game->findPath(ascend, descend, big, box, targetBox, getZones(), &boxes);
if (count) {
path = new Path(level, boxes, count);
return true;
@@ -474,6 +495,10 @@ struct Enemy : Character {
struct Wolf : Enemy {
enum {
HIT_MASK = 0x774F, // body, head, front legs
};
enum {
ANIM_DEATH = 20,
ANIM_DEATH_RUN = 21,
@@ -496,7 +521,7 @@ struct Wolf : Enemy {
STATE_BITE ,
};
Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 0.25f) {
Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) {
dropHeight = -1024;
jointChest = 2;
jointHead = 3;
@@ -511,11 +536,9 @@ struct Wolf : Enemy {
if (!think(false))
return state;
float angle, dist;
float angle;
getTargetInfo(0, NULL, NULL, &angle, NULL);
dist = (target && target->health > 0) ? (pos - target->pos).length() : +INF;
bool inZone = target ? target->zone == zone : false;
if (nextState == state)
@@ -546,15 +569,18 @@ struct Wolf : Enemy {
case STATE_GROWL :
if (nextState != STATE_NONE) return nextState;
if (mood == MOOD_ESCAPE) return STATE_RUN;
if (dist < WOLF_DIST_BITE) return STATE_BITE;
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 (dist < WOLF_DIST_BITE) return STATE_BITE;
if (dist > WOLF_DIST_STALK) return STATE_RUN;
if (mood == MOOD_ATTACK) return STATE_RUN;
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 (randf() < 0.012f) {
nextState = STATE_HOWL;
return STATE_GROWL;
@@ -562,15 +588,15 @@ struct Wolf : Enemy {
if (mood == MOOD_SLEEP) return STATE_GROWL;
break;
case STATE_RUN :
if (dist < WOLF_DIST_ATTACK) {
if (dist < WOLF_DIST_ATTACK * 0.5f && fabsf(angle) < PI * 0.5f) {
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 && dist < WOLF_DIST_STALK) {
if (mood == MOOD_STALK && targetDist < WOLF_DIST_STALK) {
nextState = STATE_STALK;
return STATE_GROWL;
}
@@ -578,7 +604,7 @@ struct Wolf : Enemy {
break;
case STATE_ATTACK :
case STATE_BITE :
if (nextState == STATE_NONE && target->health > 0 && collide(target)) {
if (nextState == STATE_NONE && targetInView && (collide(target) & HIT_MASK)) {
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_ATTACK ? 50 : 100);
nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL;
}
@@ -599,6 +625,7 @@ struct Wolf : Enemy {
virtual void updatePosition() {
float angleY = 0.0f;
if (state == STATE_RUN || state == STATE_WALK || state == STATE_STALK)
getTargetInfo(0, NULL, NULL, &angleY, NULL);
@@ -610,7 +637,7 @@ struct Wolf : Enemy {
}
Enemy::updatePosition();
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_STALK, jointChest, jointHead);
setOverrides(state != STATE_DEATH, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead);
}
};
@@ -625,6 +652,10 @@ struct Wolf : Enemy {
struct Bear : Enemy {
enum {
HIT_MASK = 0x2406C, // front legs and head
};
enum {
ANIM_DEATH_HIND = 19,
ANIM_DEATH = 20,
@@ -644,7 +675,7 @@ struct Bear : Enemy {
STATE_DEATH ,
};
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 0.5f) {
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 500.0f, 0.5f) {
jointChest = 13;
jointHead = 14;
nextState = STATE_NONE;
@@ -654,73 +685,74 @@ struct Bear : Enemy {
if (!getEntity().flags.active)
return state;
if (!think(false))
if (!think(true))
return state;
if (nextState == state)
nextState = STATE_NONE;
float dist = target ? (pos - target->pos).length() : +INF;
bool targetDead = target->health <= 0;
switch (state) {
case STATE_WALK :
if (targetDead && collide(target))
return STATE_STOP; // eat lara! >:E
else
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;
}
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 dist <= BEAR_DIST_EAT ? STATE_EAT : STATE_WALK;
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 (collide(target)) {
return STATE_HOWL;
}
if (mood == MOOD_ESCAPE) {
if (wound) {
nextState = STATE_NONE;
return STATE_HOWL;
}
if (mood == MOOD_SLEEP || randf() < 0.003f) {
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;
return STATE_HOWL;
}
if (dist > BEAR_DIST_HOWL || randf() < 0.05f) {
else if (targetDist > BEAR_DIST_HOWL || randf() < 0.05f)
nextState = STATE_STOP;
return STATE_HOWL;
}
return STATE_HOWL;
break;
case STATE_RUN :
if (collide(target))
if (collide(target) & HIT_MASK)
target->hit(3, this);
if (targetDead || mood == MOOD_SLEEP)
return STATE_STOP;
if (dist < BEAR_DIST_HOWL && randf() < 0.025f) {
nextState = STATE_HOWL;
return STATE_STOP;
} else if (dist < BEAR_DIST_BITE) {
nextState = STATE_NONE;
return STATE_BITE;
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 (dist < BEAR_DIST_ATTACK) return STATE_ATTACK;
if (targetDist < BEAR_DIST_ATTACK) return STATE_ATTACK;
return STATE_HIND;
case STATE_BITE :
case STATE_ATTACK :
if (nextState == STATE_NONE && collide(target)) {
if (nextState == STATE_NONE && (collide(target) & HIT_MASK)) {
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_BITE ? 200 : 400);
nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL;
}
@@ -778,7 +810,7 @@ struct Bat : Enemy {
STATE_DEATH,
};
Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.03f) {
Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.0f, 0.03f) {
stand = STAND_AIR;
stepHeight = 20 * 1024;
dropHeight = -20 * 1024;
@@ -837,4 +869,236 @@ struct Bat : Enemy {
}
};
#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)
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 (!getEntity().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(1, 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(10, 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(10000, this);
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;
}
float angleY = 0.0f;
getTargetInfo(0, NULL, NULL, &angleY, NULL);
if (state == STATE_RUN || state == STATE_WALK)
turn(angleY, state == STATE_RUN ? REX_TURN_FAST : REX_TURN_SLOW);
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH && state != STATE_FATAL);
}
};
#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 (!getEntity().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(animation.getJoints(getMatrix(), jointHead, true).pos, 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;
}
float angleY = 0.0f;
getTargetInfo(0, NULL, NULL, &angleY, NULL);
if (state == STATE_RUN || state == STATE_WALK)
turn(angleY, state == STATE_RUN ? RAPTOR_TURN_FAST : RAPTOR_TURN_SLOW);
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH);
}
};
#endif

View File

@@ -602,11 +602,8 @@ namespace TR {
struct Entity {
typedef int16 Type;
enum Type : int16 { NONE = -1, TR1_TYPES(DECL_ENUM) } type;
enum { NONE = -1, TR1_TYPES(DECL_ENUM) };
int16 type;
int16 room;
int32 x, y, z;
angle rotation;
@@ -624,6 +621,14 @@ namespace TR {
return type >= ENEMY_TWIN && type <= ENEMY_LARSON;
}
bool isBigEnemy() {
return type == ENEMY_REX || type == ENEMY_MUTANT_1 || type == ENEMY_CENTAUR;
}
bool isDoor() {
return (type >= DOOR_1 && type <= DOOR_6) || type == DOOR_LIFT;
}
int isItem() {
return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) ||
(type >= PUZZLE_1 && type <= PUZZLE_4) ||

View File

@@ -1135,6 +1135,16 @@ struct Lara : Character {
virtual void hit(int damage, Controller *enemy = NULL) {
health -= damage;
if (damage == 10000) { // T-Rex attack (fatal)
pos = enemy->pos;
angle = enemy->angle;
meshSwap(1, TR::MODEL_LARA_SPEC, BODY_UPPER | BODY_LOWER);
meshSwap(2, level->extra.weapons[Weapon::SHOTGUN], 0);
meshSwap(3, level->extra.weapons[Weapon::UZIS], 0);
animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 1);
}
};
bool waterOut() {
@@ -1864,8 +1874,6 @@ struct Lara : Character {
}
virtual void update() {
collisionOffset = vec3(0.0f);
checkCollisions();
Character::update();
}
@@ -1998,7 +2006,9 @@ struct Lara : Character {
vTilt *= rotFactor.y;
updateTilt(state == STATE_RUN || stand == STAND_UNDERWATER, vTilt.x, vTilt.y);
if ((velocity + collisionOffset).length2() >= 1.0f)
collisionOffset = vec3(0.0f);
if (checkCollisions() || (velocity + collisionOffset).length2() >= 1.0f)
move();
if (getEntity().type != TR::Entity::LARA) {
@@ -2019,7 +2029,7 @@ struct Lara : Character {
return getEntity().type == TR::Entity::LARA ? pos : chestOffset;
}
void checkCollisions() {
bool checkCollisions() {
// check static objects (TODO: check linked rooms?)
TR::Room &room = getRoom();
Box box(pos - vec3(LARA_RADIUS, LARA_HEIGHT, LARA_RADIUS), pos + vec3(LARA_RADIUS, 0.0f, LARA_RADIUS));
@@ -2038,27 +2048,40 @@ struct Lara : Character {
if (!canHitAnim()) {
hitDir = -1;
return;
return false;
}
// check enemies
// check enemies & doors
for (int i = 0; i < level->entitiesCount; i++) {
TR::Entity &e = level->entities[i];
if (!e.flags.active || !e.isEnemy()) continue;
Character *enemy = (Character*)e.controller;
if (enemy->health <= 0) continue;
Controller *controller = (Controller*)e.controller;
vec3 dir = pos - vec3(0.0f, 128.0f, 0.0f) - enemy->pos;
vec3 p = dir.rotateY(-enemy->angle.y);
if (e.isEnemy()) {
if (e.type != TR::Entity::ENEMY_REX && (!e.flags.active || ((Character*)e.controller)->health <= 0)) continue;
} else
if (!e.isDoor()) continue;
Box enemyBox = enemy->getBoundingBoxLocal();
if (!enemyBox.contains(p))
vec3 dir = pos - vec3(0.0f, 128.0f, 0.0f) - controller->pos;
vec3 p = dir.rotateY(controller->angle.y);
Box box = controller->getBoundingBoxLocal();
if (!box.contains(p))
continue;
// get shift
p += enemyBox.pushOut2D(p);
p = (p.rotateY(enemy->angle.y) + enemy->pos) - pos;
collisionOffset += vec3(p.x, 0.0f, p.z);
if (e.isEnemy()) { // enemy collision
if (!collide(controller, false))
continue;
velocity.x = velocity.y = 0.0f;
} else { // door collision
p += box.pushOut2D(p);
p = (p.rotateY(-controller->angle.y) + controller->pos) - pos;
collisionOffset += vec3(p.x, 0.0f, p.z);
}
if (e.type == TR::Entity::ENEMY_REX && ((Character*)e.controller)->health <= 0)
return true;
if (e.isDoor())
return true;
// get hit dir
if (hitDir == -1) {
@@ -2068,10 +2091,11 @@ struct Lara : Character {
}
hitDir = angleQuadrant(dir.rotateY(angle.y + PI * 0.5f).angleY());
return;
return true;
}
hitDir = -1;
return false;
}
void move() {
@@ -2126,42 +2150,6 @@ struct Lara : Character {
pos = opos;
}
/*
TR::Animation *anim = animation;
Box eBox = Box(pos - vec3(128.0f, 0.0f, 128.0f), pos + vec3(128.0, getHeight(), 128.0f)); // getBoundingBox();
// check static meshes in the room
if (canPassGap) {
TR::Room &r = level->rooms[e.room];
for (int i = 0; i < r.meshesCount; i++) {
TR::Room::Mesh &m = r.meshes[i];
TR::StaticMesh *sm = level->getMeshByID(m.meshID);
if (sm->flags != 2) continue; // no have collision box
Box mBox;
vec3 offset(m.x, m.y, m.z);
sm->getBox(true, m.rotation, mBox);
mBox.min += offset;
mBox.max += offset;
if (eBox.intersect(mBox)) {
canPassGap = false;
break;
}
}
}
// check entities in the room
if (canPassGap)
for (int i = 0; i < level->entitiesCount; i++)
if (i != entity && level->entities[i].room == e.room && level->entities[i].controller) {
Box mBox = ((Controller*)level->entities[i].controller)->getBoundingBox();
if (eBox.intersect(mBox)) {
canPassGap = false;
break;
}
}
*/
// get current leading foot in animation
int rightStart = 0;
if (state == STATE_RUN) rightStart = 6;

View File

@@ -57,8 +57,8 @@ struct Level : IGame {
return item->boxes[int(randf() * item->count)];
}
virtual uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
return zoneCache->findPath(ascend, descend, boxStart, boxEnd, zones, boxes);
virtual uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
return zoneCache->findPath(ascend, descend, big, boxStart, boxEnd, zones, boxes);
}
virtual void setClipParams(float clipSign, float clipHeight) {
@@ -107,6 +107,10 @@ struct Level : IGame {
Core::pass = Core::passCompose;
renderScene(roomIndex);
}
virtual void fxQuake(float time) {
camera->shake = time;
}
//==============================
Level(Stream &stream, Stream *snd, bool demo, bool home) : level(stream, demo), lara(NULL) {
@@ -137,24 +141,28 @@ struct Level : IGame {
case TR::Entity::ENEMY_BAT :
entity.controller = new Bat(this, i);
break;
case TR::Entity::ENEMY_TWIN :
case TR::Entity::ENEMY_CROCODILE_LAND :
case TR::Entity::ENEMY_CROCODILE_WATER :
case TR::Entity::ENEMY_LION_MALE :
case TR::Entity::ENEMY_LION_FEMALE :
case TR::Entity::ENEMY_PUMA :
case TR::Entity::ENEMY_GORILLA :
case TR::Entity::ENEMY_RAT_LAND :
case TR::Entity::ENEMY_RAT_WATER :
case TR::Entity::ENEMY_REX :
case TR::Entity::ENEMY_RAPTOR :
case TR::Entity::ENEMY_TWIN :
case TR::Entity::ENEMY_CROCODILE_LAND :
case TR::Entity::ENEMY_CROCODILE_WATER :
case TR::Entity::ENEMY_LION_MALE :
case TR::Entity::ENEMY_LION_FEMALE :
case TR::Entity::ENEMY_PUMA :
case TR::Entity::ENEMY_GORILLA :
case TR::Entity::ENEMY_RAT_LAND :
case TR::Entity::ENEMY_RAT_WATER :
case TR::Entity::ENEMY_REX :
entity.controller = new Rex(this, i);
break;
case TR::Entity::ENEMY_RAPTOR :
entity.controller = new Raptor(this, i);
break;
case TR::Entity::ENEMY_MUTANT_1 :
case TR::Entity::ENEMY_MUTANT_2 :
case TR::Entity::ENEMY_MUTANT_3 :
case TR::Entity::ENEMY_CENTAUR :
case TR::Entity::ENEMY_MUMMY :
case TR::Entity::ENEMY_CENTAUR :
case TR::Entity::ENEMY_MUMMY :
case TR::Entity::ENEMY_LARSON :
entity.controller = new Enemy(this, i, 100, 10, 0.0f);
entity.controller = new Enemy(this, i, 100, 10, 0.0f, 0.0f);
break;
case TR::Entity::DOOR_1 :
case TR::Entity::DOOR_2 :
@@ -205,7 +213,6 @@ struct Level : IGame {
case TR::Entity::MOVING_BLOCK :
entity.controller = new MovingBlock(this, i);
break;
case 1592 :
case TR::Entity::FALLING_CEILING_1 :
case TR::Entity::FALLING_CEILING_2 :
case TR::Entity::FALLING_SWORD :