From 669350a02dab2a57fe9cba1797c604d721870a73 Mon Sep 17 00:00:00 2001 From: XProger Date: Fri, 19 May 2017 09:49:30 +0300 Subject: [PATCH] #14 Raptor & T-Rex behavior, add hitmask for enemies #8 camera shake effect --- src/cache.h | 5 +- src/camera.h | 12 +- src/controller.h | 29 ++-- src/debug.h | 19 ++- src/enemy.h | 384 +++++++++++++++++++++++++++++++++++++++-------- src/format.h | 13 +- src/lara.h | 96 ++++++------ src/level.h | 41 ++--- 8 files changed, 445 insertions(+), 154 deletions(-) diff --git a/src/cache.h b/src/cache.h index 8cc94d0..1d85636 100644 --- a/src/cache.h +++ b/src/cache.h @@ -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; diff --git a/src/camera.h b/src/camera.h index 393eb62..b96b38a 100644 --- a/src/camera.h +++ b/src/camera.h @@ -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; diff --git a/src/controller.h b/src/controller.h index 025e142..40c4902 100644 --- a/src/controller.h +++ b/src/controller.h @@ -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; } diff --git a/src/debug.h b/src/debug.h index f4b7aa1..64565a6 100644 --- a/src/debug.h +++ b/src/debug.h @@ -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]; diff --git a/src/enemy.h b/src/enemy.h index ac48a42..3aaf10e 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -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 diff --git a/src/format.h b/src/format.h index 2e6b398..9072c80 100644 --- a/src/format.h +++ b/src/format.h @@ -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) || diff --git a/src/lara.h b/src/lara.h index 68a5981..9a6e091 100644 --- a/src/lara.h +++ b/src/lara.h @@ -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; diff --git a/src/level.h b/src/level.h index df5797b..628cb69 100644 --- a/src/level.h +++ b/src/level.h @@ -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 :