From c996704243f13cdbbda86728e4d03f2ff2fdad22 Mon Sep 17 00:00:00 2001 From: XProger Date: Sun, 4 Mar 2018 13:29:30 +0300 Subject: [PATCH] #14 add Larson AI; #3 hit and empty ammo sounds, fix T-Rex death; #22 fix waterfall and static sound sources for flipped rooms, fix muzzle flash; #11 fix value check in settings, skip cutscene if inventory button pressed, fix blackscreen if cutscene audio isn't available; #15 fix TR2 PSX format loader --- src/camera.h | 124 ++++--------------------------------------- src/character.h | 55 ++++++++++++++++++-- src/controller.h | 114 ++++++++++++++++++++++++++++++++++++++++ src/enemy.h | 133 +++++++++++++++++++++++++++++++++++++++++++++-- src/format.h | 77 +++++++++++++++------------ src/inventory.h | 2 +- src/lara.h | 132 +++++++--------------------------------------- src/level.h | 33 ++++++++++-- src/trigger.h | 65 +++++++++++++++++++++++ 9 files changed, 462 insertions(+), 273 deletions(-) diff --git a/src/camera.h b/src/camera.h index 607aefd..ab025cb 100644 --- a/src/camera.h +++ b/src/camera.h @@ -233,12 +233,19 @@ struct Camera : ICamera { } void traceClip(float offset, TR::Location &to) { - trace(target, to); + owner->trace(target, to); uint16 ownerBoxIndex = level->getSector(target.room, target.pos)->boxIndex; uint16 cameraBoxIndex = level->getSector(to.room, to.pos)->boxIndex; - TR::Box cBox = level->boxes[(cameraBoxIndex != TR::NO_BOX && !level->boxes[ownerBoxIndex].contains(int(to.pos.x), int(to.pos.z))) ? cameraBoxIndex : ownerBoxIndex]; + if (ownerBoxIndex == TR::NO_BOX) { + ASSERT(false); + return; + } + + TR::Box cBox = level->boxes[ownerBoxIndex]; + if (cameraBoxIndex != TR::NO_BOX && !level->boxes[ownerBoxIndex].contains(int(to.pos.x), int(to.pos.z))) + cBox = level->boxes[cameraBoxIndex]; clipBox(to.room, to.pos, cBox); cBox.expand(-256); @@ -269,117 +276,6 @@ struct Camera : ICamera { level->getSector(to.room, to.pos); } - int traceX(const TR::Location &from, TR::Location &to) { - vec3 d = to.pos - from.pos; - if (fabsf(d.x) < EPS) return 1; - - d.yz() *= 1024 / d.x; - - vec3 p = from.pos; - - p.x = float(int(p.x) / 1024 * 1024); - if (d.x > 0) p.x += 1023; - - p.yz() += d.yz() * ((p.x - from.pos.x) / 1024); - - float s = float(sign(d.x)); - d.x = 1024; - d *= s; - - int16 roomIndex = from.room; - while ((p.x - to.pos.x) * s < 0) { - if (level->isBlocked(roomIndex, p)) { - to.pos = p; - to.room = roomIndex; - return -1; - } - - to.room = roomIndex; - if (level->isBlocked(roomIndex, vec3(p.x + s, p.y, p.z))) { - to.pos = p; - return 0; - } - - p += d; - } - - to.room = roomIndex; - return 1; - } - - int traceZ(const TR::Location &from, TR::Location &to) { - vec3 d = to.pos - from.pos; - if (fabsf(d.z) < EPS) return 1; - - d.xy() *= 1024 / d.z; - - vec3 p = from.pos; - - p.z = float(int(p.z) / 1024 * 1024); - if (d.z > 0) p.z += 1023; - - p.xy() += d.xy() * ((p.z - from.pos.z) / 1024); - - float s = float(sign(d.z)); - d.z = 1024; - d *= s; - - int16 roomIndex = from.room; - while ((p.z - to.pos.z) * s < 0) { - if (level->isBlocked(roomIndex, p)) { - to.pos = p; - to.room = roomIndex; - return -1; - } - - to.room = roomIndex; - if (level->isBlocked(roomIndex, vec3(p.x, p.y, p.z + s))) { - to.pos = p; - return 0; - } - - p += d; - } - - to.room = roomIndex; - return 1; - } - - bool trace(const TR::Location &from, TR::Location &to) { - int rx, rz; - - if (fabsf(to.pos.x - from.pos.x) < fabsf(to.pos.z - from.pos.z)) { - rz = traceZ(from, to); - if (!rz) return false; - rx = traceX(from, to); - } else { - rx = traceX(from, to); - if (!rx) return false; - rz = traceZ(from, to); - } - TR::Room::Sector *sector = level->getSector(to.room, to.pos); - return clipHeight(from, to, sector) && rx == 1 && rz == 1; - } - - bool clipHeight(const TR::Location &from, TR::Location &to, TR::Room::Sector *sector) { - vec3 dir = to.pos - from.pos; - - float y = level->getFloor(sector, to.pos); - if (to.pos.y <= y || from.pos.y >= y) { - y = level->getCeiling(sector, to.pos); - if (to.pos.y >= y || from.pos.y <= y) - return true; - } - - ASSERT(dir.y != 0.0f); - - float d = (y - from.pos.y) / dir.y; - to.pos.y = y; - to.pos.x = from.pos.x + dir.x * d; - to.pos.z = from.pos.z + dir.z * d; - return false; - } - void clipBox(int16 roomIndex, const vec3 &pos, TR::Box &box) { const TR::Room &room = level->rooms[roomIndex]; @@ -431,7 +327,7 @@ struct Camera : ICamera { float floor = level->getFloor(sector, eye.pos) - 256; if (to.pos.y >= floor && eye.pos.y >= floor) { - trace(target, eye); + owner->trace(target, eye); sector = level->getSector(eye.room, eye.pos); floor = level->getFloor(sector, eye.pos) - 256; } diff --git a/src/character.h b/src/character.h index d73d8c1..16c91f6 100644 --- a/src/character.h +++ b/src/character.h @@ -3,6 +3,7 @@ #include "controller.h" #include "collision.h" +#include "sprite.h" struct Character : Controller { float health; @@ -49,6 +50,7 @@ struct Character : Controller { int box; bool flying; + bool fullChestRotation; Collision collision; @@ -63,6 +65,7 @@ struct Character : Controller { rotHead = rotChest = quat(0, 0, 0, 1); flying = getEntity().type == TR::Entity::ENEMY_BAT; + fullChestRotation = false; updateZone(); } @@ -234,9 +237,12 @@ struct Character : Controller { quat rot; if (jointChest > -1) { - if (aim(target, jointChest, rangeChest, rot)) - rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); - else + if (aim(target, jointChest, rangeChest, rot)) { + if (fullChestRotation) + rotChest = rotChest.slerp(rot, speed); + else + rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); + } else rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); animation.overrides[jointChest] = rotChest * animation.overrides[jointChest]; } @@ -249,6 +255,49 @@ struct Character : Controller { animation.overrides[jointHead] = rotHead * animation.overrides[jointHead]; } } + + void addSparks(uint32 mask) { + Sphere spheres[MAX_SPHERES]; + int count; + getSpheres(spheres, count); + for (int i = 0; i < count; i++) + if (mask & (1 << i)) { + vec3 sprPos = spheres[i].center + (vec3(randf(), randf(), randf()) * 2.0f - 1.0f) * spheres[i].radius; + game->addEntity(TR::Entity::SPARKLES, getRoomIndex(), sprPos); + } + } + + void addBlood(const vec3 &sprPos, const vec3 &sprVel) { + Sprite *sprite = (Sprite*)game->addEntity(TR::Entity::BLOOD, getRoomIndex(), sprPos, 0); + if (sprite) + sprite->velocity = sprVel; + } + + void addBlood(float radius, float height, const vec3 &sprVel) { + vec3 p = pos + vec3((randf() * 2.0f - 1.0f) * radius, -randf() * height, (randf() * 2.0f - 1.0f) * radius); + addBlood(p, sprVel); + } + + void addBloodSpikes() { + float ang = randf() * PI * 2.0f; + addBlood(64.0f, 512.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * 20.0f); + } + + void addBloodBlade() { + float ang = angle.y + (randf() - 0.5f) * 30.0f * DEG2RAD; + addBlood(64.0f, 744.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * speed); + } + + void addBloodSlam(Controller *trapSlam) { + for (int i = 0; i < 6; i++) + addBloodSpikes(); + } + + void addRicochet(const vec3 &pos, bool sound) { + game->addEntity(TR::Entity::RICOCHET, getRoomIndex(), pos); + if (sound) + game->playSound(TR::SND_RICOCHET, pos, Sound::PAN); + } }; #endif \ No newline at end of file diff --git a/src/controller.h b/src/controller.h index c6b6e76..1ed9567 100644 --- a/src/controller.h +++ b/src/controller.h @@ -82,6 +82,8 @@ struct IGame { virtual Controller* addEntity(TR::Entity::Type type, int room, const vec3 &pos, float angle = 0.0f) { return NULL; } virtual void removeEntity(Controller *controller) {} + virtual void addMuzzleFlash(Controller *owner, int joint, const vec3 &offset, int lightIndex) {} + virtual bool invUse(int playerIndex, 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; } @@ -812,6 +814,118 @@ struct Controller { return pos; } + int traceX(const TR::Location &from, TR::Location &to) { + vec3 d = to.pos - from.pos; + if (fabsf(d.x) < EPS) return 1; + + d.yz() *= 1024 / d.x; + + vec3 p = from.pos; + + p.x = float(int(p.x) / 1024 * 1024); + if (d.x > 0) p.x += 1023; + + p.yz() += d.yz() * ((p.x - from.pos.x) / 1024); + + float s = float(sign(d.x)); + d.x = 1024; + d *= s; + + int16 roomIndex = from.room; + while ((p.x - to.pos.x) * s < 0) { + if (level->isBlocked(roomIndex, p)) { + to.pos = p; + to.room = roomIndex; + return -1; + } + + to.room = roomIndex; + if (level->isBlocked(roomIndex, vec3(p.x + s, p.y, p.z))) { + to.pos = p; + return 0; + } + + p += d; + } + + to.room = roomIndex; + return 1; + } + + int traceZ(const TR::Location &from, TR::Location &to) { + vec3 d = to.pos - from.pos; + if (fabsf(d.z) < EPS) return 1; + + d.xy() *= 1024 / d.z; + + vec3 p = from.pos; + + p.z = float(int(p.z) / 1024 * 1024); + if (d.z > 0) p.z += 1023; + + p.xy() += d.xy() * ((p.z - from.pos.z) / 1024); + + float s = float(sign(d.z)); + d.z = 1024; + d *= s; + + int16 roomIndex = from.room; + while ((p.z - to.pos.z) * s < 0) { + if (level->isBlocked(roomIndex, p)) { + to.pos = p; + to.room = roomIndex; + return -1; + } + + to.room = roomIndex; + if (level->isBlocked(roomIndex, vec3(p.x, p.y, p.z + s))) { + to.pos = p; + return 0; + } + + p += d; + } + + to.room = roomIndex; + return 1; + } + + bool trace(const TR::Location &from, TR::Location &to) { + int rx, rz; + + if (fabsf(to.pos.x - from.pos.x) < fabsf(to.pos.z - from.pos.z)) { + rz = traceZ(from, to); + if (!rz) return false; + rx = traceX(from, to); + } else { + rx = traceX(from, to); + if (!rx) return false; + rz = traceZ(from, to); + } + TR::Room::Sector *sector = level->getSector(to.room, to.pos); + return clipHeight(from, to, sector) && rx == 1 && rz == 1; + } + + bool clipHeight(const TR::Location &from, TR::Location &to, TR::Room::Sector *sector) { + vec3 dir = to.pos - from.pos; + + float y = level->getFloor(sector, to.pos); + if (to.pos.y <= y || from.pos.y >= y) { + y = level->getCeiling(sector, to.pos); + if (to.pos.y >= y || from.pos.y <= y) + return true; + } + + ASSERT(dir.y != 0.0f); + + float d = (y - from.pos.y) / dir.y; + to.pos.y = y; + to.pos.x = from.pos.x + dir.x * d; + to.pos.z = from.pos.z + dir.z * d; + return false; + } + + bool checkRange(Controller *target, float range) { vec3 d = target->pos - pos; return fabsf(d.x) < range && fabsf(d.z) < range && fabsf(d.y) < range; diff --git a/src/enemy.h b/src/enemy.h index f12e966..ba54e82 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -306,6 +306,9 @@ struct Enemy : Character { int targetBoxOld = targetBox; + if (target->health <= 0.0f) + targetBox = -1; + // update mood bool inZone = zone == target->zone; @@ -667,7 +670,7 @@ struct Lion : Enemy { } virtual int getStateGround() { - if (!think(false)) + if (!think(true)) return state; float angle; @@ -1462,6 +1465,11 @@ struct ScionTarget : Enemy { } }; +#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 { @@ -1471,13 +1479,15 @@ struct Human : Enemy { STATE_RUN, STATE_AIM, STATE_DEATH, - STATE_UNKNOWN, + STATE_WAIT, STATE_FIRE }; - int animDeath; + 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; } @@ -1486,6 +1496,7 @@ struct Human : Enemy { if (health <= 0.0f) onDead(); Enemy::deactivate(removeFromList); + getRoom().removeDynLight(entity); } virtual int getStateDeath() { @@ -1499,19 +1510,135 @@ struct Human : Enemy { } virtual void updatePosition() { + fullChestRotation = state == STATE_FIRE || state == STATE_AIM; + + if (state == STATE_RUN || state == STATE_WALK) { + float angleY = 0.0f; + getTargetInfo(0, NULL, NULL, &angleY, NULL); + turn(angleY, state == STATE_RUN ? HUMAN_TURN_FAST : HUMAN_TURN_SLOW); + } else + turn(0, HUMAN_TURN_SLOW); + Enemy::updatePosition(); setOverrides(true, jointChest, jointHead); lookAt(target); } virtual void onDead() {} + + bool targetIsVisible() { + if (targetInView && targetDist < HUMAN_DIST_SHOT && 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; + } + + 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)) { + target->hit(damage, this); + target->addBlood(target->getJoint(rand() % target->getModel()->mCount).pos, vec3(0)); + 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, pos); + float floor = level->getFloor(sector, 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; + + float angle; + getTargetInfo(0, NULL, NULL, &angle, NULL); + + 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()) + 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()) + 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()) + 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; } }; diff --git a/src/format.h b/src/format.h index 493754b..439d1f5 100644 --- a/src/format.h +++ b/src/format.h @@ -926,6 +926,8 @@ namespace TR { SND_UZIS_SHOT = 43, SND_MAGNUMS_SHOT = 44, SND_SHOTGUN_SHOT = 45, + SND_EMPTY = 48, + SND_HIT_UNDERWATER = 50, SND_UNDERWATER = 60, @@ -1295,11 +1297,11 @@ namespace TR { uint8 align; uint32 waterLevel; - struct DynLight { + struct DynLight { int32 id; vec4 pos; vec4 color; - } dynLights[2]; + } dynLights[2]; // 1 is reserved for main light int32 dynLightsCount; @@ -1358,15 +1360,17 @@ namespace TR { DynLight *light = NULL; for (int i = 0; i < dynLightsCount; i++) if (dynLights[i].id == id) { + float maxRadius = min(dynLights[i].color.w, color.w); // radius is invSqrt light = &dynLights[i]; + light->color.w = maxRadius; break; } - // 1 is additional second light, can be overridden + + // 1 is additional second light, can be overridden if (!light) { - if (dynLightsCount < 2) { - light = &dynLights[dynLightsCount]; - dynLightsCount = min(2, dynLightsCount + 1); - } else + if (dynLightsCount < COUNT(dynLights)) + light = &dynLights[dynLightsCount++]; + else light = &dynLights[1]; } @@ -2948,6 +2952,7 @@ namespace TR { stream.read(r.info); // room data stream.read(d.size); + int startOffset = stream.pos; if (version == VER_TR1_PSX) stream.seek(2); d.vertices = stream.read(d.vCount) ? new Room::Data::Vertex[d.vCount] : NULL; for (int i = 0; i < d.vCount; i++) { @@ -3000,8 +3005,14 @@ namespace TR { stream.seek(2); int tmp = stream.pos; - stream.seek(stream.read(d.rCount) * FACE4_SIZE); // uint32 colored (not existing in file) - stream.seek(stream.read(d.tCount) * FACE3_SIZE); + if (version == VER_TR2_PSX) { + stream.read(d.rCount); + stream.seek(sizeof(uint16) * d.rCount); + if ((stream.pos - startOffset) % 4) stream.seek(2); + stream.seek(sizeof(uint16) * 4 * d.rCount); + } else + stream.seek(stream.read(d.rCount) * FACE4_SIZE); // uint32 colored (not existing in file) + stream.read(d.tCount); stream.setPos(tmp); d.fCount = d.rCount + d.tCount; @@ -3009,23 +3020,23 @@ namespace TR { int idx = 0; - stream.seek(sizeof(d.rCount)); + int16 tmpCount; + stream.read(tmpCount); + ASSERT(tmpCount == d.rCount); if (version == VER_TR2_PSX) { - ASSERT(false); // TODO - /* - for (int i = 0; i < d.fCount; i++) - stream.read(d.rectangles[i].flags.value); + for (int i = 0; i < d.rCount; i++) + stream.raw(&d.faces[i].flags.value, sizeof(uint16)); if ((stream.pos - startOffset) % 4) stream.seek(2); for (int i = 0; i < d.rCount; i++) { - Rectangle &v = d.rectangles[i]; - stream.raw(v.vertices, sizeof(v.vertices)); - v.vertices[0] >>= 2; - v.vertices[1] >>= 2; - v.vertices[2] >>= 2; - v.vertices[3] >>= 2; - v.colored = false; + Face &f = d.faces[i]; + f.vCount = 4; + stream.raw(f.vertices, sizeof(uint16) * 4); + f.vertices[0] >>= 2; + f.vertices[1] >>= 2; + f.vertices[2] >>= 2; + f.vertices[3] >>= 2; + f.colored = false; } - */ } else for (int i = 0; i < d.rCount; i++) readFace(stream, d.faces[idx++], false, false); @@ -3034,21 +3045,21 @@ namespace TR { swap(d.faces[j].vertices[2], d.faces[j].vertices[3]); } - stream.seek(sizeof(d.tCount)); + stream.read(tmpCount); + ASSERT(tmpCount == d.tCount); if (version == VER_TR2_PSX) { - ASSERT(false); // TODO - /* stream.seek(2); for (int i = 0; i < d.tCount; i++) { - Triangle &v = d.triangles[i]; - stream.raw(&v.flags.value, sizeof(uint16)); - stream.raw(v.vertices, sizeof(v.vertices)); - v.vertices[0] >>= 2; - v.vertices[1] >>= 2; - v.vertices[2] >>= 2; - v.colored = false; + Face &f = d.faces[d.rCount + i]; + f.vCount = 3; + stream.raw(&f.flags.value, sizeof(uint16)); + stream.raw(f.vertices, sizeof(uint16) * 3); + f.vertices[0] >>= 2; + f.vertices[1] >>= 2; + f.vertices[2] >>= 2; + f.vertices[3] = 0; + f.colored = false; } - */ } else { for (int i = 0; i < d.tCount; i++) readFace(stream, d.faces[idx++], false, true); diff --git a/src/inventory.h b/src/inventory.h index 5274a2c..87d5c98 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -41,7 +41,7 @@ struct OptionItem { } bool checkValue(uint8 value) const { - if (value >= maxValue) return false; + if (value > maxValue) return false; Core::Settings stg; switch (title) { case STR_OPT_DETAIL_FILTER : stg.detail.setFilter((Core::Settings::Quality)value); return stg.detail.filter == value; diff --git a/src/lara.h b/src/lara.h index 5144bc6..c87d4be 100644 --- a/src/lara.h +++ b/src/lara.h @@ -52,8 +52,6 @@ #define MAX_TRIGGER_ACTIONS 64 #define DESCENT_SPEED 2048.0f -#define MUZZLE_FLASH_TIME 0.1f -#define FLASH_LIGHT_COLOR vec4(0.6f, 0.5f, 0.1f, 1.0f / 3072.0f) #define TARGET_MAX_DIST (8.0f * 1024.0f) struct Lara : Character { @@ -183,6 +181,11 @@ struct Lara : Character { STATE_WATER_OUT, STATE_MAX }; + #define LARA_RGUN_JOINT 10 + #define LARA_LGUN_JOINT 13 + #define LARA_RGUN_OFFSET vec3( 10, -50, 0) + #define LARA_LGUN_OFFSET vec3(-10, -50, 0) + enum { BODY_HIP = 0x0001, BODY_LEG_L1 = 0x0002, @@ -226,7 +229,6 @@ struct Lara : Character { struct Arm { Controller *tracking; // tracking target (main target) Controller *target; // target for shooting - float shotTimer; quat rot, rotAbs; Weapon::Anim::Type anim; @@ -466,7 +468,6 @@ struct Lara : Character { } for (int i = 0; i < 2; i++) { - arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; arms[i].rot = quat(0, 0, 0, 1); arms[i].rotAbs = quat(0, 0, 0, 1); } @@ -908,6 +909,11 @@ struct Lara : Character { } void doShot(bool rightHand, bool leftHand) { + if (wpnAmmo && *wpnAmmo != UNLIMITED_AMMO && *wpnAmmo <= 0) { // check for no ammo + game->playSound(TR::SND_EMPTY, pos, Sound::PAN); + wpnChange(Weapon::PISTOLS); + } + int count = wpnCurrent == Weapon::SHOTGUN ? 6 : 2; float nearDist = 32.0f * 1024.0f; vec3 nearPos; @@ -931,11 +937,12 @@ struct Lara : Character { *wpnAmmo -= 1; } - arm->shotTimer = 0.0f; shots++; - int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); + game->addMuzzleFlash(this, i ? LARA_LGUN_JOINT : LARA_RGUN_JOINT, i ? LARA_LGUN_OFFSET : LARA_RGUN_OFFSET, 1 + camera->cameraIndex); + // TODO: use new trace code + int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); vec3 p = getJoint(joint).pos; vec3 d = arm->rotAbs * vec3(0, 0, 1); vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f; @@ -961,19 +968,12 @@ struct Lara : Character { } if (shots) { - Core::lightPos[1 + camera->cameraIndex] = (getJoint(10).pos + getJoint(13).pos) * 0.5f; - Core::lightColor[1 + camera->cameraIndex] = FLASH_LIGHT_COLOR; - game->playSound(wpnGetSound(), pos, Sound::PAN); game->playSound(TR::SND_RICOCHET, nearPos, Sound::PAN); if (wpnAmmo && *wpnAmmo != UNLIMITED_AMMO && wpnCurrent == Weapon::SHOTGUN) *wpnAmmo -= 1; } - - if (wpnAmmo && *wpnAmmo != UNLIMITED_AMMO && *wpnAmmo <= 0) { - wpnChange(Weapon::PISTOLS); - } } void updateWeapon() { @@ -1422,8 +1422,8 @@ struct Lara : Character { case TR::Effect::LARA_HANDSFREE : break;//meshSwap(1, level->extra.weapons[wpnCurrent], BODY_LEG_L1 | BODY_LEG_R1); break; case TR::Effect::DRAW_RIGHTGUN : drawGun(true); break; case TR::Effect::DRAW_LEFTGUN : drawGun(false); break; - case TR::Effect::SHOT_RIGHTGUN : arms[0].shotTimer = 0; break; - case TR::Effect::SHOT_LEFTGUN : arms[1].shotTimer = 0; break; + case TR::Effect::SHOT_RIGHTGUN : game->addMuzzleFlash(this, LARA_RGUN_JOINT, LARA_RGUN_OFFSET, 1 + camera->cameraIndex); break; + case TR::Effect::SHOT_LEFTGUN : game->addMuzzleFlash(this, LARA_LGUN_JOINT, LARA_LGUN_OFFSET, 1 + camera->cameraIndex); break; case TR::Effect::MESH_SWAP_1 : case TR::Effect::MESH_SWAP_2 : case TR::Effect::MESH_SWAP_3 : Character::cmdEffect(fx); @@ -1433,43 +1433,6 @@ struct Lara : Character { } } - void addSparks(uint32 mask) { - Sphere spheres[MAX_SPHERES]; - int count; - getSpheres(spheres, count); - for (int i = 0; i < count; i++) - if (mask & (1 << i)) { - vec3 sprPos = spheres[i].center + (vec3(randf(), randf(), randf()) * 2.0f - 1.0f) * spheres[i].radius; - game->addEntity(TR::Entity::SPARKLES, getRoomIndex(), sprPos); - } - } - - void addBlood(const vec3 &sprPos, const vec3 &sprVel) { - Sprite *sprite = (Sprite*)game->addEntity(TR::Entity::BLOOD, getRoomIndex(), sprPos, 0); - if (sprite) - sprite->velocity = sprVel; - } - - void addBlood(float radius, float height, const vec3 &sprVel) { - vec3 p = pos + vec3((randf() * 2.0f - 1.0f) * radius, -randf() * height, (randf() * 2.0f - 1.0f) * radius); - addBlood(p, sprVel); - } - - void addBloodSpikes() { - float ang = randf() * PI * 2.0f; - addBlood(64.0f, 512.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * 20.0f); - } - - void addBloodBlade() { - float ang = angle.y + (randf() - 0.5f) * 30.0f * DEG2RAD; - addBlood(64.0f, 744.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * speed); - } - - void addBloodSlam(Controller *trapSlam) { - for (int i = 0; i < 6; i++) - addBloodSpikes(); - } - void bakeEnvironment() { flags.invisible = true; if (!environment) @@ -1508,10 +1471,10 @@ struct Lara : Character { game->stopTrack(); Core::lightColor[1 + 0] = Core::lightColor[1 + 1] = vec4(0, 0, 0, 1); - arms[0].shotTimer = arms[1].shotTimer = MUZZLE_FLASH_TIME + 1.0f; arms[0].tracking = arms[1].tracking = NULL; arms[0].target = arms[1].target = NULL; viewTarget = NULL; + velocity = vec3(0.0f); animation.overrideMask = 0; switch (hitType) { @@ -2793,31 +2756,13 @@ struct Lara : Character { usedKey = TR::Entity::LARA; if (camera->mode != Camera::MODE_CUTSCENE && camera->mode != Camera::MODE_STATIC) - camera->mode = emptyHands() ? Camera::MODE_FOLLOW : Camera::MODE_COMBAT; - } - - void updateFlash() { - float minTime = MUZZLE_FLASH_TIME; - - for (int i = 0; i < 2; i++) - if (arms[i].shotTimer < MUZZLE_FLASH_TIME) { - arms[i].shotTimer += Core::deltaTime; - minTime = min(minTime, arms[i].shotTimer); - } - - if (minTime < MUZZLE_FLASH_TIME) { - float intensity = clamp((0.1f - minTime) * 20.0f, EPS, 1.0f); - - Core::lightColor[1 + camera->cameraIndex] = FLASH_LIGHT_COLOR * vec4(intensity, intensity, intensity, 1.0f / sqrtf(intensity)); - Core::lightPos[1 + camera->cameraIndex] = (getJoint(10).pos + getJoint(13).pos) * 0.5f; - } else - Core::lightColor[1 + camera->cameraIndex] = vec4(0, 0, 0, 1); + camera->mode = (emptyHands() || health <= 0.0f) ? Camera::MODE_FOLLOW : Camera::MODE_COMBAT; } virtual void updateAnimation(bool commands) { Controller::updateAnimation(commands); updateWeapon(); - updateFlash(); + if (stand == STAND_UNDERWATER) specular = 0.0f; else @@ -3266,22 +3211,6 @@ struct Lara : Character { return mask; } - void renderMuzzleFlash(MeshBuilder *mesh, const Basis &basis, const vec3 &offset, float time) { - ASSERT(level->extra.muzzleFlash); - if (time > MUZZLE_FLASH_TIME) return; - float alpha = min(1.0f, (0.1f - time) * 20.0f); - float lum = 3.0f; - Basis b(basis); - b.w = 1.0f; - b.rotate(quat(vec3(1, 0, 0), -PI * 0.5f)); - b.translate(offset); - if (level->version & (TR::VER_TR2 | TR::VER_TR3)) - lum = alpha; - Core::active.shader->setParam(uMaterial, vec4(lum, 0.0f, 0.0f, alpha)); - Core::setBasis(&b, 1); - mesh->renderModel(level->extra.muzzleFlash); - } - virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { uint32 visMask = visibleMask; if (Core::pass != Core::passShadow && camera->firstPerson && camera->viewIndex == -1 && game->getCamera() == camera) // hide head in first person view // TODO: fix for firstPerson with viewIndex always == -1 @@ -3292,31 +3221,6 @@ struct Lara : Character { if (braid) braid->render(mesh); - if (wpnCurrent != Weapon::SHOTGUN && Core::pass != Core::passShadow && (arms[0].shotTimer < MUZZLE_FLASH_TIME || arms[1].shotTimer < MUZZLE_FLASH_TIME)) { - game->setShader(Core::pass, Shader::FLASH, false, true); - - int meshTransp = mesh->transparent; - float zOffset; - if (level->version & (TR::VER_TR2 | TR::VER_TR3)) { - mesh->transparent = 2; - Core::setBlending(bmAdd); - zOffset = 180; - } else { - Core::setBlending(bmAlpha); - zOffset = 150; - } - - renderMuzzleFlash(mesh, joints[10], vec3(-10, -50, zOffset), arms[0].shotTimer); - renderMuzzleFlash(mesh, joints[13], vec3( 10, -50, zOffset), arms[1].shotTimer); - - mesh->transparent = meshTransp; - switch (mesh->transparent) { - case 0 : Core::setBlending(bmNone); break; - case 1 : Core::setBlending(bmAlpha); break; - case 2 : Core::setBlending(bmAdd); break; - } - } - if (state == STATE_MIDAS_DEATH /* && Core::pass == Core::passCompose */) { game->setRoomParams(getRoomIndex(), Shader::MIRROR, 1.2f, 1.0f, 0.2f, 1.0f, false); /* catsuit test diff --git a/src/level.h b/src/level.h index 47ddc74..8976610 100644 --- a/src/level.h +++ b/src/level.h @@ -45,6 +45,7 @@ struct Level : IGame { Sound::Sample *sndSoundtrack; Sound::Sample *sndUnderwater; Sound::Sample *sndCurrent; + bool waitSoundtrack; bool playNextTrack; bool lastTitle; @@ -486,6 +487,15 @@ struct Level : IGame { delete controller; } + virtual void addMuzzleFlash(Controller *owner, int joint, const vec3 &offset, int lightIndex) { + MuzzleFlash *mf = (MuzzleFlash*)addEntity(TR::Entity::MUZZLE_FLASH, owner->getRoomIndex(), offset, 0); + if (mf) { + mf->owner = owner; + mf->joint = joint; + mf->lightIndex = lightIndex; + } + } + virtual bool invUse(int playerIndex, TR::Entity::Type type) { if (!players[playerIndex]->useItem(type)) return inventory.use(type); @@ -525,8 +535,8 @@ struct Level : IGame { switch (b.flags.mode) { case 0 : if (level.version & TR::VER_TR1) flags |= Sound::UNIQUE; break; // TODO check this case 1 : flags |= Sound::REPLAY; break; - case 2 : if (level.version & TR::VER_TR1) flags |= Sound::FLIPPED | Sound::UNFLIPPED | Sound::LOOP; break; - case 3 : if (!(level.version & TR::VER_TR1)) flags |= Sound::FLIPPED | Sound::UNFLIPPED | Sound::LOOP | Sound::UNIQUE; break; + case 2 : if (level.version & TR::VER_TR1) flags |= Sound::LOOP; break; + case 3 : if (!(level.version & TR::VER_TR1)) flags |= Sound::LOOP | Sound::UNIQUE; break; } } if (b.flags.gain) volume = max(0.0f, volume - randf() * 0.25f); @@ -546,8 +556,9 @@ struct Level : IGame { } static void playAsync(Stream *stream, void *userData) { - if (!stream) return; Level *level = (Level*)userData; + level->waitSoundtrack = false; + if (!stream) return; level->sndSoundtrack = Sound::play(stream, vec3(0.0f), 0.01f, 1.0f, Sound::MUSIC); if (level->sndSoundtrack) { @@ -582,6 +593,7 @@ struct Level : IGame { if (track == 0xFF) return; + waitSoundtrack = true; getGameTrack(level.version, track, playAsync, this); } @@ -633,7 +645,10 @@ struct Level : IGame { for (int i = 0; i < level.soundSourcesCount; i++) { TR::SoundSource &src = level.soundSources[i]; - playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), Sound::PAN | src.flags); + int flags = Sound::PAN; + if (src.flags & 64) flags |= Sound::FLIPPED; + if (src.flags & 128) flags |= Sound::UNFLIPPED; + playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), flags); } lastTitle = false; @@ -684,6 +699,8 @@ struct Level : IGame { } void addPlayer(int index) { + if (level.isCutsceneLevel()) return; + if (!players[index]) { players[index] = (Lara*)addEntity(TR::Entity::LARA, 0, vec3(0.0f), 0.0f); players[index]->camera->cameraIndex = index; @@ -790,6 +807,7 @@ struct Level : IGame { case TR::Entity::RICOCHET : return new Sprite(this, index, true, Sprite::FRAME_RANDOM); case TR::Entity::CENTAUR_STATUE : return new CentaurStatue(this, index); case TR::Entity::CABIN : return new Cabin(this, index); + case TR::Entity::MUZZLE_FLASH : return new MuzzleFlash(this, index); case TR::Entity::LAVA_PARTICLE : return new LavaParticle(this, index); case TR::Entity::TRAP_LAVA_EMITTER : return new TrapLavaEmitter(this, index); case TR::Entity::FLAME : return new Flame(this, index); @@ -1402,7 +1420,7 @@ struct Level : IGame { playNextTrack = false; } - if (level.isCutsceneLevel()) { + if (level.isCutsceneLevel() && waitSoundtrack) { if (!sndSoundtrack && TR::LEVEL_INFO[level.id].ambientTrack != TR::NO_TRACK) { if (camera->timer > 0.0f) // for the case that audio stops before animation ends loadNextLevel(); @@ -1425,6 +1443,11 @@ struct Level : IGame { if ((Input::state[0][cInventory] || Input::state[1][cInventory]) && !level.isTitle() && inventory.titleTimer < 1.0f && !inventory.active && inventory.lastKey == cMAX) { int playerIndex = Input::state[0][cInventory] ? 0 : 1; + if (level.isCutsceneLevel()) { + loadNextLevel(); + return; + } + if (player->health <= 0.0f) inventory.toggle(playerIndex, Inventory::PAGE_OPTION, TR::Entity::INV_PASSPORT); else diff --git a/src/trigger.h b/src/trigger.h index 9936161..d062d7f 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -204,6 +204,68 @@ struct TrapFlameEmitter : Controller { }; +#define MUZZLE_FLASH_TIME 0.1f +#define FLASH_LIGHT_COLOR vec4(0.6f, 0.5f, 0.1f, 1.0f / 3072.0f) + +struct MuzzleFlash : Controller { + Controller *owner; + int joint; + int lightIndex; + + MuzzleFlash(IGame *game, int entity) : Controller(game, entity), owner(NULL), joint(0), lightIndex(-1) { + pos.z += (level->version & (TR::VER_TR2 | TR::VER_TR3)) ? 180.0f : 150.0f; + activate(); + } + + virtual void update() { + if (timer < MUZZLE_FLASH_TIME) { + timer += Core::deltaTime; + + if (timer < MUZZLE_FLASH_TIME) { + float intensity = clamp((MUZZLE_FLASH_TIME - timer) * 20.0f, EPS, 1.0f); + + vec4 lightPos = vec4(owner->getJoint(joint).pos, 0); + vec4 lightColor = FLASH_LIGHT_COLOR * vec4(intensity, intensity, intensity, 1.0f / sqrtf(intensity)); + if (lightIndex > -1) { + ASSERT(lightIndex + 1 < MAX_LIGHTS); + Core::lightPos[lightIndex] = lightPos; + Core::lightColor[lightIndex] = lightColor; + } else + getRoom().addDynLight(owner->entity, lightPos, lightColor); + } else { + if (lightIndex > -1) { + ASSERT(lightIndex < MAX_LIGHTS); + Core::lightPos[lightIndex] = vec4(0); + Core::lightColor[lightIndex] = vec4(0, 0, 0, 1); + } else + getRoom().removeDynLight(owner->entity); + game->removeEntity(this); + } + } + } + + virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { + ASSERT(level->extra.muzzleFlash); + ASSERT(owner); + + float alpha = min(1.0f, (0.1f - timer) * 20.0f); + float lum = 3.0f; + Basis b = owner->getJoint(joint); + b.w = 1.0f; + b.rotate(quat(vec3(1, 0, 0), -PI * 0.5f)); + b.translate(pos); + if (level->version & (TR::VER_TR2 | TR::VER_TR3)) + lum = alpha; + + game->setShader(Core::pass, Shader::FLASH, false, true); + Core::active.shader->setParam(uMaterial, vec4(lum, 0.0f, 0.0f, alpha)); + Core::setBasis(&b, 1); + + mesh->renderModel(level->extra.muzzleFlash); + } +}; + + #define LAVA_PARTICLE_DAMAGE 10 #define LAVA_V_SPEED -165 #define LAVA_H_SPEED 32 @@ -1380,6 +1442,9 @@ struct Waterfall : Controller { Waterfall(IGame *game, int entity) : Controller(game, entity), timer(0.0f) {} virtual void update() { + if (getEntity().room != getRoomIndex()) // room is flipped + return; + vec3 delta = (game->getLara(pos)->pos - pos) * (1.0f / 1024.0f); if (delta.length2() > 100.0f) return;