diff --git a/src/camera.h b/src/camera.h index d03a958..50e4511 100644 --- a/src/camera.h +++ b/src/camera.h @@ -31,7 +31,6 @@ struct Camera : ICamera { int room; float timer; - float shake; Basis prevBasis; @@ -43,7 +42,7 @@ struct Camera : ICamera { bool firstPerson; bool isVR; - Camera(IGame *game, Character *owner) : ICamera(), game(game), level(game->getLevel()), owner(owner), frustum(new Frustum()), timer(-1.0f), shake(0.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL), isVR(false) { + Camera(IGame *game, Character *owner) : ICamera(), game(game), level(game->getLevel()), owner(owner), frustum(new Frustum()), timer(-1.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL), isVR(false) { changeView(false); if (owner->getEntity().type != TR::Entity::LARA && level->cameraFrames) { state = STATE_CUTSCENE; diff --git a/src/controller.h b/src/controller.h index c4b7558..827b593 100644 --- a/src/controller.h +++ b/src/controller.h @@ -17,10 +17,11 @@ struct Controller; struct ICamera { - vec4 *reflectPlane; - vec3 pos; + vec4 *reflectPlane; + vec3 pos; + float shake; - ICamera() : reflectPlane(NULL) {} + ICamera() : reflectPlane(NULL), pos(0.0f), shake(0.0f) {} virtual void setup(bool calcMatrices) {} virtual int getRoomIndex() const { return TR::NO_ROOM; } @@ -52,6 +53,9 @@ struct IGame { virtual void checkTrigger(Controller *controller, bool heavy) {} + virtual int addSprite(TR::Entity::Type type, int room, int x, int y, int z, int frame = -1, bool empty = false) { return -1; } + virtual int addEnemy(TR::Entity::Type type, int room, const vec3 &pos, float angle) { return -1; } + virtual bool invUse(TR::Entity::Type type) { return false; } virtual void invAdd(TR::Entity::Type type, int count = 1) {} virtual int* invCount(TR::Entity::Type type) { return NULL; } @@ -65,7 +69,7 @@ struct IGame { struct Controller { static Controller *first; Controller *next; - enum ActiveState { asNone, asActive, asInactive } activeState; + enum ActiveState { asNone, asActive, asInactive }; IGame *game; TR::Level *level; @@ -94,7 +98,17 @@ struct Controller { uint32 mask; } *layers; - Controller(IGame *game, int entity) : next(NULL), activeState(asNone), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(NULL) { + uint32 explodeMask; + struct ExplodePart { + Basis basis; + vec3 velocity; + int roomIndex; + } *explodeParts; + + ActiveState activeState; + bool invertAim; + + Controller(IGame *game, int entity) : next(NULL), activeState(asNone), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(0), explodeMask(0), explodeParts(0), invertAim(false) { TR::Entity &e = getEntity(); pos = vec3(float(e.x), float(e.y), float(e.z)); angle = vec3(0.0f, e.rotation, 0.0f); @@ -125,6 +139,7 @@ struct Controller { virtual ~Controller() { delete[] joints; delete[] layers; + delete[] explodeParts; deactivate(true); } @@ -195,6 +210,7 @@ struct Controller { } void initMeshOverrides() { + if (layers) return; layers = new MeshLayer[MAX_LAYERS]; memset(layers, 0, sizeof(MeshLayer) * MAX_LAYERS); layers[0].model = getEntity().modelIndex - 1; @@ -223,6 +239,8 @@ struct Controller { Basis b = animation.getJoints(Basis(getMatrix()), joint); vec3 delta = (b.inverse() * t).normal(); + if (invertAim) + delta = -delta; float angleY = clampAngle(atan2f(delta.x, delta.z)); float angleX = clampAngle(asinf(delta.y)); @@ -526,8 +544,37 @@ struct Controller { animation.framePrev = animation.frameIndex; } + void updateExplosion() { + if (!explodeMask) return; + TR::Model *model = getModel(); + for (int i = 0; i < model->mCount; i++) + if (explodeMask & (1 << i)) { + ExplodePart &part = explodeParts[i]; + part.velocity.y += GRAVITY * Core::deltaTime; + + quat q = quat(vec3(1, 0, 0), PI * Core::deltaTime) * quat(vec3(0, 0, 1), PI * 2.0f * Core::deltaTime); + part.basis = Basis(part.basis.rot * q, part.basis.pos + explodeParts[i].velocity * (Core::deltaTime * 30.0f)); + + vec3 p = part.basis.pos; + TR::Room::Sector *s = level->getSector(part.roomIndex, int(p.x), int(p.y), int(p.z)); + + if (s && s->floor * 256.0f < p.y) { + explodeMask &= ~(1 << i); + + game->addSprite(TR::Entity::EXPLOSION, part.roomIndex, int(p.x), int(p.y), int(p.z)); + game->playSound(TR::SND_EXPLOSION, pos, 0); // Sound::Flags::PAN ? + } + } + + if (!explodeMask) { + delete[] explodeParts; + explodeParts = NULL; + } + } + virtual void update() { updateAnimation(true); + updateExplosion(); } void updateLights(bool lerp = true) { @@ -587,6 +634,34 @@ struct Controller { return matrix; } + void explode(int32 mask) { + TR::Model *model = getModel(); + + if (!layers) initMeshOverrides(); + + mask &= layers[0].mask; + layers[0].mask &= ~mask; + + explodeParts = new ExplodePart[model->mCount]; + explodeMask = 0; + + animation.getJoints(getMatrix(), -1, true, joints); + int roomIndex = getRoomIndex(); + for (int i = 0; i < model->mCount; i++) { + if (!(mask & (1 << i))) + continue; + explodeMask |= (1 << i); + float angle = randf() * PI * 2.0f; + float speed = randf() * 256.0f; + + ExplodePart &part = explodeParts[i]; + part.basis = joints[i]; + part.basis.w = 1.0f; + part.velocity = vec3(cosf(angle), (randf() - 0.5f) * 0.25f, sinf(angle)) * speed; + part.roomIndex = roomIndex; + } + } + void renderShadow(MeshBuilder *mesh) { TR::Entity &entity = getEntity(); @@ -621,16 +696,18 @@ struct Controller { Core::active.shader->setParam(uMaterial, vec4(vec3(0.5f * (1.0f - alpha)), alpha)); Core::active.shader->setParam(uAmbient, vec3(0.0f)); + Core::setDepthWrite(false); Core::setBlending(bmMultiply); mesh->renderShadowBlob(); Core::setBlending(bmNone); + Core::setDepthWrite(true); } virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { // TODO: animation.calcJoints mat4 matrix = getMatrix(); Box box = animation.getBoundingBox(vec3(0, 0, 0), 0); - if (frustum && !frustum->isVisible(matrix, box.min, box.max)) + if (!explodeMask && frustum && !frustum->isVisible(matrix, box.min, box.max)) return; TR::Entity &entity = getEntity(); @@ -643,16 +720,23 @@ struct Controller { animation.getJoints(matrix, -1, true, joints); if (layers) { - int mask = 0; + uint32 mask = 0; for (int i = MAX_LAYERS - 1; i >= 0; i--) { - int vmask = layers[i].mask & ~mask; + uint32 vmask = (layers[i].mask & ~mask) | (!i ? explodeMask : 0); if (!vmask) continue; mask |= layers[i].mask; // set meshes visibility for (int j = 0; j < model->mCount; j++) joints[j].w = (vmask & (1 << j)) ? 1.0f : -1.0f; // AHAHA + if (explodeMask) { + TR::Model *model = getModel(); + for (int i = 0; i < model->mCount; i++) + if (explodeMask & (1 << i)) + joints[i] = explodeParts[i].basis; + } + // if (entity.type == TR::Entity::LARA && Core::eye != 0) // joints[14].w = -1.0f; // render diff --git a/src/enemy.h b/src/enemy.h index 574d07f..5fd5e5b 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -57,6 +57,7 @@ struct Enemy : Character { float length; // dist from center to head (jaws) float aggression; int radius; + int hitSound; Character *target; Path *path; @@ -85,7 +86,13 @@ struct Enemy : Character { } virtual void updateVelocity() { - velocity = getDir() * animation.getSpeed(); + if (stand == STAND_AIR && (!flying || health <= 0.0f)) + velocity.y += GRAVITY * Core::deltaTime; + else + velocity = getDir() * animation.getSpeed(); + + if (health <= 0.0f) + velocity.x = velocity.y = 0.0f; } bool checkPoint(int x, int z) { @@ -126,7 +133,7 @@ struct Enemy : Character { if (px != nx) pos.x = float(nx); if (pz != nz) pos.z = float(nz); - } + } virtual void updatePosition() { if (!getEntity().flags.active) return; @@ -137,7 +144,11 @@ struct Enemy : Character { clipByBox(pos); TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info); + level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), 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) { @@ -221,6 +232,8 @@ struct Enemy : Character { virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) { Character::hit(damage, enemy, hitType); wound = true; + if (hitSound > -1) + game->playSound(hitSound, pos, Sound::PAN); }; void bite(const vec3 &pos, float damage) { @@ -486,6 +499,7 @@ struct Wolf : Enemy { dropHeight = -1024; jointChest = 2; jointHead = 3; + hitSound = TR::SND_HIT_WOLF; nextState = STATE_NONE; animation.time = animation.timeMax; updateAnimation(false); @@ -605,6 +619,18 @@ struct Wolf : Enemy { } }; +struct Lion : Enemy { + Lion(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) { + hitSound = TR::SND_HIT_LION; + } +}; + +struct Rat : Enemy { + Rat(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) { + hitSound = TR::SND_HIT_RAT; + } +}; + #define BEAR_DIST_EAT 768 #define BEAR_DIST_HOWL 2048 #define BEAR_DIST_BITE 1024 @@ -640,7 +666,8 @@ struct Bear : Enemy { Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 500.0f, 0.5f) { jointChest = 13; - jointHead = 14; + jointHead = 14; + hitSound = TR::SND_HIT_BEAR; nextState = STATE_NONE; } @@ -814,13 +841,6 @@ struct Bat : Enemy { return state == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH); } - virtual void updateVelocity() { - if (state != STATE_DEATH) - Enemy::updateVelocity(); - else - velocity = vec3(0.0f, velocity.y + GRAVITY * Core::deltaTime, 0.0f); - } - virtual void updatePosition() { float angleY = 0.0f; if (state == STATE_FLY || state == STATE_ATTACK) @@ -1065,4 +1085,121 @@ struct Raptor : Enemy { } }; -#endif +struct Mutant : Enemy { + Mutant(IGame *game, int entity) : Enemy(game, entity, 50, 341, 150.0f, 1.0f) { + TR::Entity &e = getEntity(); + if (e.type != TR::Entity::ENEMY_MUTANT_1) { + initMeshOverrides(); + layers[0].mask = 0xffe07fff; + aggression = 0.25f; + } + + jointChest = 1; + jointHead = 2; + nextState = 0; + } + + virtual void update() { + bool exploded = explodeMask != 0; + + if (health <= 0.0f && !exploded) { + game->playSound(TR::SND_MUTANT_DEATH, pos, 0); + explode(0xffffffff); + } + + Enemy::update(); + + if (exploded && !explodeMask) { + deactivate(true); + getEntity().flags.invisible = true; + } + } + + virtual int getStateGround() { + if (!think(true)) + return state; + return state; + } + + virtual void updatePosition() { + Enemy::updatePosition(); + setOverrides(true, jointChest, jointHead); + lookAt(target); + } +}; + +struct GiantMutant : Enemy { + enum { + STATE_STOP = 1, + STATE_BORN = 8, + STATE_FALL = 9, + }; + + 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 = 3; + rangeHead = vec4(-0.5f, 0.5f, -0.5f, 0.5f) * PI; + invertAim = true; + } + + virtual int getStateGround() { + if (!think(true)) + return state; + return state; + } + + void update() { + bool exploded = explodeMask != 0; + + if (health <= 0.0f && !exploded) { + game->playSound(TR::SND_MUTANT_DEATH, pos, 0); + explode(0xffffffff); + } + + switch (state) { + case STATE_BORN : animation.setState(STATE_FALL); break; + case STATE_FALL : + if (stand == STAND_GROUND) { + animation.setState(STATE_STOP); + game->getCamera()->shake = 5.0f; + } + break; + } + + Enemy::update(); + + setOverrides(true, jointChest, jointHead); + lookAt(target); + + if (exploded && !explodeMask) { + game->checkTrigger(this, true); + deactivate(true); + getEntity().flags.invisible = true; + } + } +}; + + +struct Centaur : Enemy { + Centaur(IGame *game, int entity) : Enemy(game, entity, 20, 341, 400.0f, 0.5f) { + jointChest = 10; + jointHead = 17; + } + + virtual int getStateGround() { + if (!think(true)) + return state; + return state; + } + + virtual void updatePosition() { + Enemy::updatePosition(); + setOverrides(true, jointChest, jointHead); + lookAt(target); + } +}; + + +#endif \ No newline at end of file diff --git a/src/format.h b/src/format.h index 258ed90..d9758c1 100644 --- a/src/format.h +++ b/src/format.h @@ -254,6 +254,9 @@ namespace TR { SND_SHOTGUN_RELOAD = 9, SND_RICOCHET = 10, + SND_HIT_BEAR = 16, + SND_HIT_WOLF = 20, + SND_SCREAM = 30, SND_HIT = 31, @@ -266,7 +269,13 @@ namespace TR { SND_UNDERWATER = 60, SND_FLOOD = 81, + + SND_HIT_LION = 85, + + SND_HIT_RAT = 95, + SND_EXPLOSION = 104, + SND_INV_SPIN = 108, SND_INV_HOME = 109, SND_INV_CONTROLS = 110, @@ -278,9 +287,15 @@ namespace TR { SND_HEALTH = 116, SND_STAIRS2SLOPE = 119, + + SND_HIT_SKATEBOY = 132, + + SND_HIT_MUTANT = 142, + SND_DART = 151, - SND_EXPLOSION = 170, + SND_TNT = 170, + SND_MUTANT_DEATH = 171, SND_SECRET = 173, }; @@ -730,7 +745,10 @@ namespace TR { && type != ENEMY_REX && type != ENEMY_RAPTOR && type != ENEMY_MUTANT_1 + && type != ENEMY_MUTANT_2 + && type != ENEMY_MUTANT_3 && type != ENEMY_CENTAUR + && type != ENEMY_GIANT_MUTANT && type != ENEMY_MUMMY && type != ENEMY_NATLA) opaque = true; @@ -2016,6 +2034,9 @@ namespace TR { } int16 getModelIndex(Entity::Type type) const { + if (type == TR::Entity::ENEMY_MUTANT_2 || type == TR::Entity::ENEMY_MUTANT_3) + type = TR::Entity::ENEMY_MUTANT_1; // hardcoded mutant models remapping + for (int i = 0; i < modelsCount; i++) if (type == models[i].type) return i + 1; @@ -2107,8 +2128,8 @@ namespace TR { int sz = (z - room.info.z) >> 10; if (sz <= 0 || sz >= room.xSectors - 1) { - sx = clamp(sx, 1, room.xSectors - 2); - sz = clamp(sz, 0, room.zSectors - 1); + sx = clamp(sx, 0, room.xSectors - 1); + sz = clamp(sz, 1, room.zSectors - 2); } else sx = clamp(sx, 0, room.xSectors - 1); diff --git a/src/inventory.h b/src/inventory.h index 1eecfae..515ede1 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -180,8 +180,17 @@ struct Inventory { // add(TR::Entity::INV_MEDIKIT_SMALL, 999); // add(TR::Entity::INV_MEDIKIT_BIG, 999); // add(TR::Entity::INV_SCION, 1); -// add(TR::Entity::INV_KEY_1, 1); -// add(TR::Entity::INV_PUZZLE_1, 1); + #ifdef _DEBUG + add(TR::Entity::INV_KEY_1, 3); + add(TR::Entity::INV_KEY_2, 3); + add(TR::Entity::INV_KEY_3, 3); + add(TR::Entity::INV_KEY_4, 3); + + add(TR::Entity::INV_PUZZLE_1, 3); + add(TR::Entity::INV_PUZZLE_2, 3); + add(TR::Entity::INV_PUZZLE_3, 3); + add(TR::Entity::INV_PUZZLE_4, 3); + #endif } if (id == TR::TITLE) { diff --git a/src/level.h b/src/level.h index f263c1d..a672ca5 100644 --- a/src/level.h +++ b/src/level.h @@ -209,9 +209,10 @@ struct Level : IGame { playSound(TR::SND_STAIRS2SLOPE, vec3(), 0); break; case TR::Effect::EXPLOSION : - playSound(TR::SND_EXPLOSION, vec3(0), 0); + playSound(TR::SND_TNT, vec3(0), 0); camera->shake = 1.0f; break; + default : ; } } @@ -219,6 +220,38 @@ struct Level : IGame { lara->checkTrigger(controller, heavy); } + virtual int addSprite(TR::Entity::Type type, int room, int x, int y, int z, int frame = -1, bool empty = false) { + return Sprite::add(this, type, room, x, y, z, frame, empty); + } + + virtual int addEnemy(TR::Entity::Type type, int room, const vec3 &pos, float angle) { + int index = level.entityAdd(type, room, int(pos.x), int(pos.y), int(pos.z), TR::angle(angle), -1); + if (index > -1) { + TR::Entity &e = level.entities[index]; + Enemy *enemy = NULL; + switch (type) { + case TR::Entity::ENEMY_MUTANT_1 : + case TR::Entity::ENEMY_MUTANT_2 : + case TR::Entity::ENEMY_MUTANT_3 : + enemy = new Mutant(this, index); + break; + case TR::Entity::ENEMY_CENTAUR : + enemy = new Centaur(this, index); + break; + case TR::Entity::ENEMY_GIANT_MUTANT : + enemy = new GiantMutant(this, index); + break; + default : ; + } + + ASSERT(enemy); + e.controller = enemy; + e.flags.active = TR::ACTIVE; + enemy->activate(); + } + return index; + } + virtual bool invUse(TR::Entity::Type type) { if (!lara->useItem(type)) return inventory.use(type); @@ -338,15 +371,14 @@ 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 : + entity.controller = new Lion(this, i); + break; case TR::Entity::ENEMY_RAT_LAND : case TR::Entity::ENEMY_RAT_WATER : + entity.controller = new Rat(this, i); + break; case TR::Entity::ENEMY_REX : entity.controller = new Rex(this, i); break; @@ -356,9 +388,23 @@ struct Level : IGame { case TR::Entity::ENEMY_MUTANT_1 : case TR::Entity::ENEMY_MUTANT_2 : case TR::Entity::ENEMY_MUTANT_3 : + entity.controller = new Mutant(this, i); + break; case TR::Entity::ENEMY_CENTAUR : + entity.controller = new Centaur(this, i); + break; case TR::Entity::ENEMY_MUMMY : + case TR::Entity::ENEMY_TWIN : + case TR::Entity::ENEMY_CROCODILE_LAND : + case TR::Entity::ENEMY_CROCODILE_WATER : + case TR::Entity::ENEMY_PUMA : + case TR::Entity::ENEMY_GORILLA : case TR::Entity::ENEMY_LARSON : + case TR::Entity::ENEMY_PIERRE : + case TR::Entity::ENEMY_SKATEBOY : + case TR::Entity::ENEMY_COWBOY : + case TR::Entity::ENEMY_MR_T : + case TR::Entity::ENEMY_NATLA : entity.controller = new Enemy(this, i, 100, 10, 0.0f, 0.0f); break; case TR::Entity::DOOR_1 : @@ -454,6 +500,10 @@ struct Level : IGame { case TR::Entity::BOAT : entity.controller = new Boat(this, i); break; + case TR::Entity::MUTANT_EGG_SMALL : + case TR::Entity::MUTANT_EGG_BIG : + entity.controller = new MutantEgg(this, i); + break; default : if (entity.modelIndex > 0) entity.controller = new Controller(this, i); diff --git a/src/trigger.h b/src/trigger.h index 4249490..1d78faa 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -784,6 +784,44 @@ struct Boat : Controller { }; +#define MUTANT_EGG_RANGE 4096 + +struct MutantEgg : Controller { + enum { + STATE_IDLE, + STATE_EXPLOSION, + }; + + TR::Entity::Type enemy; + + MutantEgg(IGame *game, int entity) : Controller(game, entity) { + initMeshOverrides(); + layers[0].mask = 0xff0001ff; // hide dynamic meshes + + switch (getEntity().flags.active) { + case 1 : enemy = TR::Entity::ENEMY_MUTANT_2; break; + case 2 : enemy = TR::Entity::ENEMY_CENTAUR; break; + case 4 : enemy = TR::Entity::ENEMY_GIANT_MUTANT; break; + case 8 : enemy = TR::Entity::ENEMY_MUTANT_3; break; + default : enemy = TR::Entity::ENEMY_MUTANT_1; + } + } + + virtual void update() { + if (state != STATE_EXPLOSION) { + Box box = Box(pos + vec3(-MUTANT_EGG_RANGE), pos + vec3(MUTANT_EGG_RANGE)); + TR::Entity &e = getEntity(); + if ( e.flags.once || e.type == TR::Entity::MUTANT_EGG_BIG || box.contains((((Controller*)level->laraController)->pos)) ) { + animation.setState(STATE_EXPLOSION); + layers[0].mask = 0xffffffff & ~(1 << 24); + explode(0x00fffe00); + game->addEnemy(enemy, getRoomIndex(), pos, angle.y); + } + } + Controller::update(); + } +}; + struct KeyHole : Controller { KeyHole(IGame *game, int entity) : Controller(game, entity) {} diff --git a/src/utils.h b/src/utils.h index 8d09f67..b6ba28a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -62,11 +62,21 @@ inline const T& min(const T &a, const T &b) { return a < b ? a : b; } +template +inline const T& min(const T &a, const T &b, const T &c) { + return (a < b && a < c) ? a : ((b < a && b < c) ? b : c); +} + template inline const T& max(const T &a, const T &b) { return a > b ? a : b; } +template +inline const T& max(const T &a, const T &b, const T &c) { + return (a > b && a > c) ? a : ((b > a && b > c) ? b : c); +} + template inline const T& clamp(const T &x, const T &a, const T &b) { return x < a ? a : (x > b ? b : x);