1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-06 13:16:52 +02:00

#8 fix loosing camera room; #10 temporary turn off underwater sound; #9 add rewind mode for samples; #14 add basic AI logic for bears and bats; #3 health, death and basic inventory logic; #15 add fast & slow-motion while holding R & T keys

This commit is contained in:
XProger
2016-11-23 01:38:00 +03:00
parent 251f99339b
commit ddf9ae25cc
13 changed files with 443 additions and 124 deletions

View File

@@ -54,6 +54,7 @@ struct Animation {
prev = index;
index = animIndex;
next = anims[index].nextAnimation - model->animation;
dir = 1.0f;
time = (animFrame <= 0 ? -animFrame : (animFrame - anim->frameStart)) / 30.0f;
timeMax = (anim->frameEnd - anim->frameStart + lerpToNext) / 30.0f;
framesCount = anim->frameEnd - anim->frameStart + 1;

View File

@@ -123,6 +123,7 @@ struct Camera : Controller {
vec3 eye = lastDest + dir.cross(vec3(0, 1, 0)).normal() * 2048.0f - vec3(0.0f, 512.0f, 0.0f);
destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
}
room = destRoom;
}
pos = pos.lerp(destPos, Core::deltaTime * lerpFactor);
@@ -153,8 +154,8 @@ struct Camera : Controller {
}
// play underwater sound when camera goes under water
if (lastRoom != room && !level->rooms[lastRoom].flags.water && level->rooms[room].flags.water)
playSound(TR::SND_UNDERWATER, vec3(0.0f), 0);
// if (lastRoom != room && !level->rooms[lastRoom].flags.water && level->rooms[room].flags.water)
// playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::REPLAY); // TODO: loop sound
}
mViewInv = mat4(pos, target, vec3(0, -1, 0));

View File

@@ -12,27 +12,29 @@ struct Character : Controller {
enum Stand {
STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER
} stand;
int input;
int input, lastInput;
enum { LEFT = 1 << 1,
RIGHT = 1 << 2,
FORTH = 1 << 3,
BACK = 1 << 4,
JUMP = 1 << 5,
WALK = 1 << 6,
ACTION = 1 << 7,
WEAPON = 1 << 8,
DEATH = 1 << 9 };
enum Key {
LEFT = 1 << 1,
RIGHT = 1 << 2,
FORTH = 1 << 3,
BACK = 1 << 4,
JUMP = 1 << 5,
WALK = 1 << 6,
ACTION = 1 << 7,
WEAPON = 1 << 8,
DEATH = 1 << 9
};
vec3 velocity;
float angleExt;
Character(TR::Level *level, int entity, int health) : Controller(level, entity), target(-1), health(100), tilt(0.0f), stand(STAND_GROUND), velocity(0.0f) {
Character(TR::Level *level, int entity, int health) : Controller(level, entity), target(-1), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), velocity(0.0f) {
animation.initOverrides();
rotHead = rotChest = quat(0, 0, 0, 1);
}
virtual void hit(int damage) {
virtual void hit(int damage, Controller *enemy = NULL) {
health -= damage;
};
@@ -63,6 +65,7 @@ struct Character : Controller {
health = 0;
}
virtual void updateVelocity() {}
virtual void updatePosition() {}
virtual Stand getStand() { return stand; }
virtual int getHeight() { return 0; }
@@ -74,7 +77,7 @@ struct Character : Controller {
virtual int getStateOnwater() { return state; }
virtual int getStateDeath() { return state; }
virtual int getStateDefault() { return state; }
virtual int getInput() { return 0; }
virtual int getInput() { return health <= 0 ? DEATH : 0; }
virtual void updateState() {
int state = animation.state;
@@ -117,7 +120,12 @@ struct Character : Controller {
angle.z = tilt;
}
bool isPressed(Key key) {
return (input & key) && !(lastInput & key);
}
virtual void update() {
lastInput = input;
input = getInput();
stand = getStand();
updateState();

View File

@@ -6,7 +6,7 @@
#include "mesh.h"
#include "animation.h"
#define GRAVITY 6.0f
#define GRAVITY (6.0f * 30.0f)
#define NO_OVERLAP 0x7FFFFFFF
#define SPRITE_FPS 10.0f
@@ -165,11 +165,13 @@ struct Controller {
int16 a = level->soundsMap[id];
if (a == -1) return;
TR::SoundInfo &b = level->soundsInfo[a];
if (b.chance == 0 || (rand() & 0x7fff) <= b.chance) {
uint32 c = level->soundOffsets[b.offset + rand() % ((b.flags & 0xFF) >> 2)];
int index = b.offset + rand() % b.flags.count;
uint32 c = level->soundOffsets[index];
void *p = &level->soundData[c];
Sound::play(new Stream(p, 1024 * 1024), pos, (float)b.volume / 0xFFFF, 0.0f, flags);
Sound::play(new Stream(p, 1024 * 1024), pos, (float)b.volume / 0xFFFF, 0.0f, flags | ((b.flags.replay == 1) ? Sound::REPLAY : 0), entity * 1000 + index);
}
}
@@ -360,7 +362,6 @@ struct Controller {
virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; }
virtual void doCustomCommand (int curFrame, int prevFrame) {}
virtual void updateVelocity() {}
virtual void checkRoom() {}
virtual void cmdOffset(const vec3 &offset) {

View File

@@ -518,7 +518,8 @@ namespace Debug {
char buf[255];
sprintf(buf, "DIP = %d, TRI = %d, SND = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount);
Debug::Draw::text(vec2(16, 16), vec4(1.0f), buf);
sprintf(buf, "pos = (%d, %d, %d), room = %d", entity.x, entity.y, entity.z, entity.room);
vec3 angle = ((Controller*)entity.controller)->angle * RAD2DEG;
sprintf(buf, "pos = (%d, %d, %d), angle = (%d, %d), room = %d", entity.x, entity.y, entity.z, (int)angle.x, (int)angle.y, entity.room);
Debug::Draw::text(vec2(16, 32), vec4(1.0f), buf);
int rate = anim.anims[anim.index].frameRate;
sprintf(buf, "state = %d, anim = %d, next = %d, rate = %d, frame = %.2f / %d (%f)", anim.state, anim.index, anim.next, rate, anim.time * 30.0f, anim.framesCount, anim.delta);

View File

@@ -4,8 +4,9 @@
#include "character.h"
struct Enemy : Character {
bool bitten;
Enemy(TR::Level *level, int entity, int health) : Character(level, entity, health) {}
Enemy(TR::Level *level, int entity, int health) : Character(level, entity, health), bitten(false) {}
virtual bool activate(ActionCommand *cmd) {
Controller::activate(cmd);
@@ -23,9 +24,7 @@ struct Enemy : Character {
}
virtual void updateVelocity() {
TR::Animation *anim = animation;
float speed = anim->speed + anim->accel * (animation.time * 30.0f);
velocity = getDir() * speed;
velocity = getDir() * animation.getSpeed();
}
virtual void updatePosition() {
@@ -38,8 +37,13 @@ struct Enemy : Character {
pos = p;
return;
}
if (stand == STAND_GROUND)
pos.y = float(info.floor);
switch (stand) {
case STAND_GROUND : pos.y = float(info.floor); break;
case STAND_AIR : pos.y = clamp(pos.y, float(info.ceiling), float(info.floor)); break;
default : ;
}
updateEntity();
checkRoom();
}
@@ -80,17 +84,55 @@ struct Enemy : Character {
}
}
virtual int getInput() {
if (target > -1) {
vec3 a = (((Controller*)level->entities[target].controller)->pos - pos).normal();
bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) {
if (target == -1) return false;
Character *character = (Character*)level->entities[target].controller;
if (character->health <= 0) return false;
vec3 p = character->pos;
p.y -= height;
if (pos) *pos = p;
vec3 a = p - this->pos;
if (dist) *dist = a.length();
if (angleX || angleY) {
a = a.normal();
vec3 b = getDir();
vec3 n = vec3(0, 1, 0);
float d = atan2(b.cross(a).dot(n), a.dot(b));
if (fabsf(d) > 0.01f)
return d < 0 ? LEFT : RIGHT;
if (angleX) *angleX = 0.0f;
if (angleY) *angleY = atan2(b.cross(a).dot(n), a.dot(b));
}
return true;
}
int turn(float delta, float speed) {
speed *= Core::deltaTime;
decrease(delta, angle.y, speed);
if (speed != 0.0f) {
velocity = velocity.rotateY(-speed);
return speed < 0 ? LEFT : RIGHT;
}
return 0;
}
int lift(float delta, float speed) {
speed *= Core::deltaTime;
decrease(delta, pos.y, speed);
if (speed != 0.0f) {
updateEntity();
return speed < 0 ? FORTH : BACK;
}
return 0;
}
void bite(const vec3 &pos, int damage) {
if (bitten) return;
bitten = true;
ASSERT(target > -1);
Character *c = (Character*)level->entities[target].controller;
c->hit(damage, this);
Sprite::add(level, TR::Entity::BLOOD, c->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED);
}
};
@@ -99,7 +141,6 @@ struct Enemy : Character {
#define WOLF_TILT_MAX (PI / 6.0f)
#define WOLF_TILT_SPEED WOLF_TILT_MAX
struct Wolf : Enemy {
enum {
@@ -117,9 +158,9 @@ struct Wolf : Enemy {
STATE_HOWL = 7,
STATE_SLEEP = 8,
STATE_GROWL = 9,
STATE_10 = 10, // WTF?
STATE_TURN = 10,
STATE_DEATH = 11,
STATE_ATTACK = 12,
STATE_BITE = 12,
};
enum {
@@ -127,45 +168,47 @@ struct Wolf : Enemy {
JOINT_HEAD = 3
};
Wolf(TR::Level *level, int entity) : Enemy(level, entity, 100) {}
Wolf(TR::Level *level, int entity) : Enemy(level, entity, 6) {}
virtual int getStateGround() {
// STATE_SLEEP -> STATE_STOP
// STATE_STOP -> STATE_WALK, STATE_HOWL, STATE_SLEEP, STATE_GROWL
// STATE_WALK -> NULL
// STATE_RUN -> STaTE_JUMP, STATe_GROWL, STATE_10
// STATE_STALKING -> STATE_RUN, STATE_GROWL, STATE_BITING
// STATE_JUMP -> STATE_RUN
// STATE_GROWL -> STATE_STOP, STATE_RUN, STATE_STALKING, STATE_HOWL, STATE_ATTACK
// STATE_BITING -> NULL
if (state == STATE_DEATH) return state;
if (health <= 0) {
switch (state) {
case STATE_RUN : return animation.setAnim(ANIM_DEATH_RUN);
case STATE_JUMP : return animation.setAnim(ANIM_DEATH_JUMP);
default : return animation.setAnim(ANIM_DEATH);
}
}
TR::Entity &e = getEntity();
if (!e.flags.active)
return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP;
switch (state) {
case STATE_SLEEP : return STATE_STOP;
case STATE_STOP : return STATE_HOWL;
case STATE_GROWL : return randf() > 0.5f ? STATE_STALKING : STATE_RUN;
case STATE_STALKING : if (health < 70) return STATE_RUN; break;
case STATE_SLEEP : return target > -1 ? STATE_STOP : state;
case STATE_STOP : return target > -1 ? STATE_HOWL : STATE_SLEEP;
case STATE_HOWL : return state;
case STATE_GROWL : return target > -1 ? (randf() > 0.5f ? STATE_STALKING : STATE_RUN) : STATE_STOP;
case STATE_RUN :
case STATE_STALKING : {
if (state == STATE_STALKING && health < 6) return STATE_RUN;
float angleY, dist;
if (getTargetInfo(0, NULL, NULL, &angleY, &dist)) {
float w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
input = turn(angleY, w); // also set input mask (left, right) for tilt control
if ((state == STATE_STALKING && dist < 512)) {
bitten = false;
return STATE_BITE;
}
if ((state == STATE_RUN && dist > 512 && dist < 1024)) {
bitten = false;
return STATE_JUMP;
}
} else {
target = -1;
return STATE_GROWL;
}
break;
}
}
if (target > -1 && (state == STATE_STALKING || state == STATE_RUN)) {
vec3 v = ((Controller*)level->entities[target].controller)->pos - pos;
float d = v.length();
if (state == STATE_STALKING && d < 512)
return STATE_ATTACK;
if (state == STATE_RUN && d > 512 && d < 1024)
return STATE_JUMP;
if ((state == STATE_JUMP || state == STATE_BITE) && !bitten) {
float dist;
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).getPos(), state == STATE_BITE ? 100 : 50);
}
if (state == STATE_JUMP)
@@ -174,20 +217,13 @@ struct Wolf : Enemy {
return state;
}
virtual void updateState() {
Enemy::updateState();
float w = 0.0f;
if (state == STATE_RUN || state == STATE_STALKING) {
w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
if (input & LEFT) w = -w;
if (w != 0.0f) {
w *= Core::deltaTime;
angle.y += w;
velocity = velocity.rotateY(-w);
}
} else
velocity = vec3(0.0f);
virtual int getStateDeath() {
switch (state) {
case STATE_DEATH : return state;
case STATE_RUN : return animation.setAnim(ANIM_DEATH_RUN);
case STATE_JUMP : return animation.setAnim(ANIM_DEATH_JUMP);
default : return animation.setAnim(ANIM_DEATH);
}
}
virtual void updatePosition() {
@@ -207,34 +243,151 @@ struct Wolf : Enemy {
struct Bear : Enemy {
enum {
STATE_STOP = 1,
ANIM_DEATH_HIND = 19,
ANIM_DEATH = 20,
};
Bear(TR::Level *level, int entity) : Enemy(level, entity, 100) {}
enum {
STATE_WALK = 0,
STATE_STOP = 1,
STATE_HIND = 2,
STATE_RUN = 3,
STATE_HOWL = 4,
STATE_GROWL = 5,
STATE_BITE = 6,
STATE_ATTACK = 7,
STATE_EAT = 8,
STATE_DEATH = 9,
};
enum {
JOINT_CHEST = 2,
JOINT_HEAD = 3
};
Bear(TR::Level *level, int entity) : Enemy(level, entity, 20) {}
virtual int getStateGround() {
switch (state) {
case STATE_STOP : return STATE_RUN;
case STATE_GROWL : return state;
case STATE_WALK :
case STATE_RUN :
case STATE_HIND : {
if (state == STATE_HIND && health < 6) return STATE_RUN;
float angleY, dist;
if (getTargetInfo(0, NULL, NULL, &angleY, &dist)) {
float w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
input = turn(angleY, w); // also set input mask (left, right) for tilt control
if ((state == STATE_HIND && dist < 512)) {
bitten = false;
return STATE_ATTACK;
}
if ((state == STATE_RUN && dist > 512 && dist < 1024)) {
bitten = false;
return STATE_BITE;
}
}
break;
}
}
if ((state == STATE_ATTACK || state == STATE_BITE) && !bitten) {
float dist;
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).getPos(), state == STATE_BITE ? 100 : 50);
}
return state;
}
virtual int getStateDeath() {
return state == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
}
virtual void updatePosition() {
updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX);
Enemy::updatePosition();
/*
if (state == STATE_DEATH) {
animation.overrideMask = 0;
return;
}
Enemy::updatePosition();
setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD);
lookAt(target, JOINT_CHEST, JOINT_HEAD);
*/
}
};
#define BAT_TURN_SPEED PI
#define BAT_LIFT_SPEED 512.0f
struct Bat : Enemy {
enum {
STATE_AWAKE = 1,
STATE_FLY = 2,
ANIM_DEATH = 4,
};
Bat(TR::Level *level, int entity) : Enemy(level, entity, 100) {}
enum {
STATE_AWAKE = 1,
STATE_FLY = 2,
STATE_ATTACK = 3,
STATE_CIRCLING = 4,
STATE_DEATH = 5,
};
virtual Stand getStand() {
return STAND_AIR;
}
Bat(TR::Level *level, int entity) : Enemy(level, entity, 1) { stand = STAND_AIR; }
virtual int getStateAir() {
animation.time = 0.0f;
return STATE_AWAKE;
if (!getEntity().flags.active) {
animation.time = 0.0f;
animation.dir = 0.0f;
return STATE_AWAKE;
}
switch (state) {
case STATE_AWAKE : return STATE_FLY;
case STATE_ATTACK :
case STATE_FLY : {
vec3 p;
float angleY, dist;
if (getTargetInfo(765, &p, NULL, &angleY, &dist)) {
turn(angleY, BAT_TURN_SPEED);
lift(p.y - pos.y, BAT_LIFT_SPEED);
if (dist < 128) {
if (state == STATE_ATTACK && !(animation.frameIndex % 15))
bite(pos, 2); // TODO: bite position
else
bitten = false;
return STATE_ATTACK;
} else
return STATE_FLY;
} else {
turn(PI, BAT_TURN_SPEED); // circling
return STATE_FLY;
}
}
}
return state;
}
virtual int getStateDeath() {
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);
}
};
#endif

View File

@@ -409,6 +409,8 @@ namespace TR {
SPARK = 164, // sprite
MUZZLE_FLASH = 166,
VIEW_TARGET = 169,
GLYPH = 190, // sprite
@@ -617,10 +619,13 @@ namespace TR {
};
struct SoundInfo {
uint16 offset;
uint16 volume;
uint16 chance; // If !=0 and ((rand()&0x7fff) > Chance), this sound is not played
uint16 flags; // Bits 0-1: Looped flag, bits 2-5: num samples, bits 6-7: UNUSED
uint16 offset;
uint16 volume;
uint16 chance; // If !=0 and ((rand()&0x7fff) > Chance), this sound is not played
union {
struct { uint16 replay:2, count:6, unknown:5, pitch:1, gain:1; };
uint16 value;
} flags;
};
#pragma pack(pop)
@@ -758,6 +763,10 @@ namespace TR {
bool secrets[MAX_SECRETS_COUNT];
void *cameraController;
struct {
Model *muzzleFlash;
} extra;
Level(const char *name, bool demo) {
Stream stream(name);
// read version
@@ -883,8 +892,15 @@ namespace TR {
c.g <<= 2;
c.b <<= 2;
}
// init secrets states
memset(secrets, 0, MAX_SECRETS_COUNT * sizeof(secrets[0]));
// get special models indices
memset(&extra, 0, sizeof(extra));
for (int i = 0; i < modelsCount; i++)
switch (models[i].type) {
case Entity::MUZZLE_FLASH : extra.muzzleFlash = &models[i]; break;
default : ;
}
}
~Level() {

View File

@@ -32,7 +32,9 @@ namespace Game {
void update() {
float dt = Core::deltaTime;
if (Input::down[ikR]) // slow motion (for animation debugging)
Core::deltaTime /= 50.0f;
Core::deltaTime /= 10.0f;
if (Input::down[ikT])
Core::deltaTime *= 10.0f;
level->update();

52
src/inventory.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef H_INVENTORY
#define H_INVENTORY
#include "format.h"
#define MAX_ITEMS 64
struct Inventory {
struct Item {
TR::Entity::Type type;
int count;
} items[MAX_ITEMS];
int itemsCount;
Inventory() : itemsCount(0) {}
int contains(TR::Entity::Type type) {
for (int i = 0; i < itemsCount; i++)
if (items[i].type == type)
return i;
return -1;
}
void add(TR::Entity::Type type, int count = 1) {
int i = contains(type);
if (i > -1) {
items[i].count += count;
return;
}
if(itemsCount < MAX_ITEMS) {
items[itemsCount].type = type;
items[itemsCount].count = count;
itemsCount++;
}
}
int getCount(TR::Entity::Type type) {
int i = contains(type);
if (i < 0) return 0;
return items[i].count;
}
void remove(TR::Entity::Type type, int count = 1) {
int i = contains(type);
if (i > -1)
items[i].count -= count;
}
};
#endif

View File

@@ -6,6 +6,7 @@
#include "character.h"
#include "trigger.h"
#include "sprite.h"
#include "inventory.h"
#define TURN_FAST PI
#define TURN_FAST_BACK PI * 3.0f / 4.0f
@@ -189,9 +190,12 @@ struct Lara : Character {
} arms[2];
ActionCommand actionList[MAX_TRIGGER_ACTIONS];
Inventory inventory;
int lastPickUp;
Lara(TR::Level *level, int entity, bool home) : Character(level, entity, 100), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos) {
Lara(TR::Level *level, int entity, bool home) : Character(level, entity, 1000), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos) {
getEntity().flags.active = 1;
initMeshOverrides();
for (int i = 0; i < 2; i++) {
arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f;
@@ -214,12 +218,12 @@ struct Lara : Character {
pos = vec3(43182, 2473, 51556);
angle = vec3(0.0f, PI * 0.5f, 0.0f);
getEntity().room = 12;
*/
// level 2 (pool)
pos = vec3(70067, -256, 29104);
angle = vec3(0.0f, -0.68f, 0.0f);
getEntity().room = 15;
/*
// level 2 (blade)
pos = vec3(27221, -1024, 29205);
angle = vec3(0.0f, PI * 0.5f, 0.0f);
@@ -245,6 +249,16 @@ struct Lara : Character {
angle = vec3(0.0f, PI, 0.0f);
getEntity().room = 19;
// level 2 (bat trigger)
pos = vec3(64108, -512, 16514);
angle = vec3(0.0f, -PI * 0.5f, 0.0f);
getEntity().room = 7;
// level 2 (bear)
pos = vec3(70082, -512, 26935);
angle = vec3(0.0f, PI * 0.5f, 0.0f);
getEntity().room = 15;
// level 3a
pos = vec3(41015, 3584, 34494);
angle = vec3(0.0f, -PI, 0.0f);
@@ -287,18 +301,17 @@ struct Lara : Character {
arm.animation.updateInfo();
wpnSetState(wState);
LOG("set anim\n");
}
int wpnGetDamage() {
switch (wpnCurrent) {
case Weapon::PISTOLS : return 10;
case Weapon::SHOTGUN : return 15;
case Weapon::MAGNUMS : return 20;
case Weapon::UZIS : return 5;
default : return 0;
case Weapon::PISTOLS : return 1;
case Weapon::SHOTGUN : return 1;
case Weapon::MAGNUMS : return 2;
case Weapon::UZIS : return 1;
default : ;
}
return 0;
}
void wpnSetState(Weapon::State wState) {
@@ -537,6 +550,13 @@ struct Lara : Character {
}
void updateWeapon() {
if (input & DEATH) {
arms[0].shotTimer = arms[1].shotTimer = MUZZLE_FLASH_TIME + 1.0f;
animation.overrideMask = 0;
target = -1;
return;
}
updateTargets();
updateOverrides();
@@ -807,6 +827,12 @@ struct Lara : Character {
wpnHide();
}
virtual void hit(int damage, Controller *enemy = NULL) {
health -= damage;
if (enemy && health > 0)
playSound(TR::SND_HIT, pos, Sound::PAN);
};
bool waterOut() {
// TODO: playSound 36
vec3 dst = pos + getDir() * 32.0f;
@@ -860,6 +886,22 @@ struct Lara : Character {
return fabsf(shortAngle(rotation, getEntity().rotation)) < PI * 0.25f;
}
bool useItem(TR::Entity::Type item, TR::Entity::Type slot) {
if (item == TR::Entity::NONE) {
switch (slot) {
case TR::Entity::HOLE_KEY : item = TR::Entity::KEY_1; break; // TODO: 1-4
case TR::Entity::HOLE_PUZZLE : item = TR::Entity::PUZZLE_1; break;
default : return false;
}
}
if (inventory.getCount(item) > 0) {
inventory.remove(item);
return true;
}
return false;
}
void checkTrigger() {
if (actionCommand) return;
@@ -881,17 +923,21 @@ struct Lara : Character {
break;
case TR::Level::Trigger::SWITCH :
actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN;
if ((input & ACTION) == 0 || state == actionState || !emptyHands())
if (!isPressed(ACTION) || state == actionState || !emptyHands())
return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return;
break;
case TR::Level::Trigger::KEY :
actionState = STATE_USE_KEY;
if (isActive || (input & ACTION) == 0 || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE
if (isActive || !isPressed(ACTION) || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE
return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return;
if (animation.canSetState(actionState) && !useItem(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) {
playSound(TR::SND_NO, pos, Sound::PAN);
return;
}
break;
case TR::Level::Trigger::PICKUP :
if (!isActive) // check if item is not picked up
@@ -925,7 +971,7 @@ struct Lara : Character {
return;
}
if (info.trigger == TR::Level::Trigger::KEY && i == 0) continue; // skip keyhole
if (info.trigger == TR::Level::Trigger::KEY && i == 0) continue; // skip key <20><> puzzle hole
TR::FloorData::TriggerCommand &cmd = info.trigCmd[i];
switch (cmd.action) {
@@ -999,7 +1045,7 @@ struct Lara : Character {
int extra = stand != STAND_AIR ? 256 : 0;
if (e.y + extra >= info.floor) {
if (e.y + extra >= info.floor && !(stand == STAND_AIR && velocity.y < 0)) {
if (stand != STAND_GROUND) {
pos.y = float(info.floor);
updateEntity();
@@ -1293,7 +1339,9 @@ struct Lara : Character {
}
virtual int getInput() {
input = 0;
input = Character::getInput();
if (input & DEATH) return input;
int &p = Input::joy.POV;
if (Input::down[ikW] || Input::down[ikUp] || p == 8 || p == 1 || p == 2) input |= FORTH;
if (Input::down[ikD] || Input::down[ikRight] || p == 2 || p == 3 || p == 4) input |= RIGHT;
@@ -1306,16 +1354,18 @@ struct Lara : Character {
if (Input::down[ikShift] || Input::down[ikJoyLB]) input |= WALK;
if (Input::down[ikE] || Input::down[ikCtrl] || Input::down[ikJoyA]) input |= ACTION;
if (Input::down[ikQ] || Input::down[ikAlt] || Input::down[ikJoyY]) input |= WEAPON;
if (health <= 0) input = DEATH;
return input;
}
virtual void doCustomCommand(int curFrame, int prevFrame) {
if (state == STATE_PICK_UP) {
if (!level->entities[lastPickUp].flags.invisible) {
TR::Entity &item = level->entities[lastPickUp];
if (!item.flags.invisible) {
int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER;
if (curFrame >= pickupFrame)
level->entities[lastPickUp].flags.invisible = true; // TODO: add to inventory
if (animation.isFrameActive(pickupFrame)) {
item.flags.invisible = true;
inventory.add(item.type, 1);
}
}
}
}
@@ -1384,7 +1434,7 @@ struct Lara : Character {
switch (stand) {
case STAND_AIR :
velocity.y += GRAVITY * 30.0f * Core::deltaTime;
velocity.y += GRAVITY * Core::deltaTime;
break;
case STAND_GROUND :
case STAND_SLIDE :
@@ -1611,6 +1661,7 @@ struct Lara : Character {
}
void renderMuzzleFlash(MeshBuilder *mesh, const mat4 &matrix, 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;
@@ -1619,7 +1670,7 @@ struct Lara : Character {
m.rotateX(-PI * 0.5f);
m.translate(offset);
Core::active.shader->setParam(uColor, vec4(lum, lum, lum, alpha));
renderMesh(m, mesh, level->models[47].mStart);
renderMesh(m, mesh, level->extra.muzzleFlash->mStart);
}
virtual void render(Frustum *frustum, MeshBuilder *mesh) {

View File

@@ -161,6 +161,7 @@
<ClInclude Include="..\..\frustum.h" />
<ClInclude Include="..\..\game.h" />
<ClInclude Include="..\..\input.h" />
<ClInclude Include="..\..\inventory.h" />
<ClInclude Include="..\..\lara.h" />
<ClInclude Include="..\..\level.h" />
<ClInclude Include="..\..\libs\minimp3\libc.h" />

View File

@@ -20,7 +20,7 @@
#endif
#define SND_CHANNELS_MAX 32
#define SND_FADEOFF_DIST (1024.0f * 10.0f)
#define SND_FADEOFF_DIST (1024.0f * 8.0f)
namespace Sound {
@@ -35,6 +35,7 @@ namespace Sound {
Decoder(Stream *stream, int channels) : stream(stream), channels(channels), offset(stream->pos) {}
virtual ~Decoder() { delete stream; }
virtual int decode(Frame *frames, int count) { return 0; }
virtual void replay() { stream->seek(offset - stream->pos); }
};
struct PCM : Decoder {
@@ -175,6 +176,12 @@ namespace Sound {
}
return i;
}
virtual void replay() {
mp3_done(mp3);
mp3 = mp3_create();
pos = 0;
}
};
#endif
@@ -212,6 +219,8 @@ namespace Sound {
}
return i;
}
// TODO: replay
};
#endif
@@ -223,9 +232,10 @@ namespace Sound {
enum Flags {
LOOP = 1,
PAN = 2,
REVERB_NEAR = 4,
REVERB_MIDDLE = 8,
REVERB_FAR = 16,
REPLAY = 4,
REVERB_NEAR = 8,
REVERB_MIDDLE = 16,
REVERB_FAR = 32,
};
struct Sample {
@@ -235,9 +245,10 @@ namespace Sound {
float volume;
float pitch;
int flags;
int id;
bool isPlaying;
Sample(Stream *stream, const vec3 &pos, float volume, float pitch, int flags) : decoder(NULL), pos(pos), volume(volume), pitch(pitch), flags(flags) {
Sample(Stream *stream, const vec3 &pos, float volume, float pitch, int flags, int id) : decoder(NULL), pos(pos), volume(volume), pitch(pitch), flags(flags), id(id) {
uint32 fourcc;
stream->read(fourcc);
if (fourcc == FOURCC("RIFF")) { // wav
@@ -401,11 +412,23 @@ namespace Sound {
return NULL;
}
Sample* play(Stream *stream, const vec3 &pos, float volume, float pitch, int flags) {
Sample* play(Stream *stream, const vec3 &pos, float volume = 1.0f, float pitch = 0.0f, int flags = 0, int id = - 1) {
if (!stream) return NULL;
if (flags & REPLAY)
for (int i = 0; i < channelsCount; i++)
if (channels[i]->id == id) {
channels[i]->pos = pos;
// channels[i]->pitch = pitch; // TODO
// channels[i]->gain = gain; // TODO
channels[i]->decoder->replay();
delete stream;
return channels[i];
}
if (channelsCount < SND_CHANNELS_MAX)
return channels[channelsCount++] = new Sample(stream, pos, volume, pitch, flags);
return channels[channelsCount++] = new Sample(stream, pos, volume, pitch, flags, id);
LOG("! no free channels\n");
delete stream;

View File

@@ -88,6 +88,15 @@ float shortAngle(float a, float b) {
return clampAngle(n - int(n / PI2) * PI2);
}
float decrease(float delta, float &value, float &speed) {
if (speed > 0.0f && fabsf(delta) > 0.01f) {
if (delta > 0) speed = min(delta, speed);
if (delta < 0) speed = max(delta, -speed);
value += speed;
return speed;
} else
return 0.0f;
}
struct vec2 {
float x, y;