1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-04-22 03:51:58 +02:00

#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

This commit is contained in:
XProger 2018-03-04 13:29:30 +03:00
parent 13c436dbb1
commit c996704243
9 changed files with 462 additions and 273 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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;
}
};

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;