1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-07 05:37:01 +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; prev = index;
index = animIndex; index = animIndex;
next = anims[index].nextAnimation - model->animation; next = anims[index].nextAnimation - model->animation;
dir = 1.0f;
time = (animFrame <= 0 ? -animFrame : (animFrame - anim->frameStart)) / 30.0f; time = (animFrame <= 0 ? -animFrame : (animFrame - anim->frameStart)) / 30.0f;
timeMax = (anim->frameEnd - anim->frameStart + lerpToNext) / 30.0f; timeMax = (anim->frameEnd - anim->frameStart + lerpToNext) / 30.0f;
framesCount = anim->frameEnd - anim->frameStart + 1; 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); 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); destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
} }
room = destRoom;
} }
pos = pos.lerp(destPos, Core::deltaTime * lerpFactor); pos = pos.lerp(destPos, Core::deltaTime * lerpFactor);
@@ -153,8 +154,8 @@ struct Camera : Controller {
} }
// play underwater sound when camera goes under water // play underwater sound when camera goes under water
if (lastRoom != room && !level->rooms[lastRoom].flags.water && level->rooms[room].flags.water) // if (lastRoom != room && !level->rooms[lastRoom].flags.water && level->rooms[room].flags.water)
playSound(TR::SND_UNDERWATER, vec3(0.0f), 0); // playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::REPLAY); // TODO: loop sound
} }
mViewInv = mat4(pos, target, vec3(0, -1, 0)); mViewInv = mat4(pos, target, vec3(0, -1, 0));

View File

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

View File

@@ -6,7 +6,7 @@
#include "mesh.h" #include "mesh.h"
#include "animation.h" #include "animation.h"
#define GRAVITY 6.0f #define GRAVITY (6.0f * 30.0f)
#define NO_OVERLAP 0x7FFFFFFF #define NO_OVERLAP 0x7FFFFFFF
#define SPRITE_FPS 10.0f #define SPRITE_FPS 10.0f
@@ -165,11 +165,13 @@ struct Controller {
int16 a = level->soundsMap[id]; int16 a = level->soundsMap[id];
if (a == -1) return; if (a == -1) return;
TR::SoundInfo &b = level->soundsInfo[a]; TR::SoundInfo &b = level->soundsInfo[a];
if (b.chance == 0 || (rand() & 0x7fff) <= b.chance) { 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]; 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 bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; }
virtual void doCustomCommand (int curFrame, int prevFrame) {} virtual void doCustomCommand (int curFrame, int prevFrame) {}
virtual void updateVelocity() {}
virtual void checkRoom() {} virtual void checkRoom() {}
virtual void cmdOffset(const vec3 &offset) { virtual void cmdOffset(const vec3 &offset) {

View File

@@ -518,7 +518,8 @@ namespace Debug {
char buf[255]; char buf[255];
sprintf(buf, "DIP = %d, TRI = %d, SND = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount); 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); 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); Debug::Draw::text(vec2(16, 32), vec4(1.0f), buf);
int rate = anim.anims[anim.index].frameRate; 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); 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" #include "character.h"
struct Enemy : Character { 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) { virtual bool activate(ActionCommand *cmd) {
Controller::activate(cmd); Controller::activate(cmd);
@@ -23,9 +24,7 @@ struct Enemy : Character {
} }
virtual void updateVelocity() { virtual void updateVelocity() {
TR::Animation *anim = animation; velocity = getDir() * animation.getSpeed();
float speed = anim->speed + anim->accel * (animation.time * 30.0f);
velocity = getDir() * speed;
} }
virtual void updatePosition() { virtual void updatePosition() {
@@ -38,8 +37,13 @@ struct Enemy : Character {
pos = p; pos = p;
return; 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(); updateEntity();
checkRoom(); checkRoom();
} }
@@ -80,17 +84,55 @@ struct Enemy : Character {
} }
} }
virtual int getInput() { bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) {
if (target > -1) { if (target == -1) return false;
vec3 a = (((Controller*)level->entities[target].controller)->pos - pos).normal(); 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 b = getDir();
vec3 n = vec3(0, 1, 0); vec3 n = vec3(0, 1, 0);
float d = atan2(b.cross(a).dot(n), a.dot(b)); if (angleX) *angleX = 0.0f;
if (fabsf(d) > 0.01f) if (angleY) *angleY = atan2(b.cross(a).dot(n), a.dot(b));
return d < 0 ? LEFT : RIGHT; }
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; 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_MAX (PI / 6.0f)
#define WOLF_TILT_SPEED WOLF_TILT_MAX #define WOLF_TILT_SPEED WOLF_TILT_MAX
struct Wolf : Enemy { struct Wolf : Enemy {
enum { enum {
@@ -117,9 +158,9 @@ struct Wolf : Enemy {
STATE_HOWL = 7, STATE_HOWL = 7,
STATE_SLEEP = 8, STATE_SLEEP = 8,
STATE_GROWL = 9, STATE_GROWL = 9,
STATE_10 = 10, // WTF? STATE_TURN = 10,
STATE_DEATH = 11, STATE_DEATH = 11,
STATE_ATTACK = 12, STATE_BITE = 12,
}; };
enum { enum {
@@ -127,45 +168,47 @@ struct Wolf : Enemy {
JOINT_HEAD = 3 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() { 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(); TR::Entity &e = getEntity();
if (!e.flags.active) if (!e.flags.active)
return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP; return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP;
switch (state) { switch (state) {
case STATE_SLEEP : return STATE_STOP; case STATE_SLEEP : return target > -1 ? STATE_STOP : state;
case STATE_STOP : return STATE_HOWL; case STATE_STOP : return target > -1 ? STATE_HOWL : STATE_SLEEP;
case STATE_GROWL : return randf() > 0.5f ? STATE_STALKING : STATE_RUN; case STATE_HOWL : return state;
case STATE_STALKING : if (health < 70) return STATE_RUN; break; 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)) { if ((state == STATE_JUMP || state == STATE_BITE) && !bitten) {
vec3 v = ((Controller*)level->entities[target].controller)->pos - pos; float dist;
float d = v.length(); if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
if (state == STATE_STALKING && d < 512) bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).getPos(), state == STATE_BITE ? 100 : 50);
return STATE_ATTACK;
if (state == STATE_RUN && d > 512 && d < 1024)
return STATE_JUMP;
} }
if (state == STATE_JUMP) if (state == STATE_JUMP)
@@ -174,20 +217,13 @@ struct Wolf : Enemy {
return state; return state;
} }
virtual void updateState() { virtual int getStateDeath() {
Enemy::updateState(); switch (state) {
float w = 0.0f; case STATE_DEATH : return state;
if (state == STATE_RUN || state == STATE_STALKING) { case STATE_RUN : return animation.setAnim(ANIM_DEATH_RUN);
w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW; case STATE_JUMP : return animation.setAnim(ANIM_DEATH_JUMP);
if (input & LEFT) w = -w; default : return animation.setAnim(ANIM_DEATH);
}
if (w != 0.0f) {
w *= Core::deltaTime;
angle.y += w;
velocity = velocity.rotateY(-w);
}
} else
velocity = vec3(0.0f);
} }
virtual void updatePosition() { virtual void updatePosition() {
@@ -207,34 +243,151 @@ struct Wolf : Enemy {
struct Bear : Enemy { struct Bear : Enemy {
enum { 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() { 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; 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 { struct Bat : Enemy {
enum { enum {
STATE_AWAKE = 1, ANIM_DEATH = 4,
STATE_FLY = 2,
}; };
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() { Bat(TR::Level *level, int entity) : Enemy(level, entity, 1) { stand = STAND_AIR; }
return STAND_AIR;
}
virtual int getStateAir() { virtual int getStateAir() {
animation.time = 0.0f; if (!getEntity().flags.active) {
return STATE_AWAKE; 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 #endif

View File

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

View File

@@ -32,7 +32,9 @@ namespace Game {
void update() { void update() {
float dt = Core::deltaTime; float dt = Core::deltaTime;
if (Input::down[ikR]) // slow motion (for animation debugging) 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(); 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 "character.h"
#include "trigger.h" #include "trigger.h"
#include "sprite.h" #include "sprite.h"
#include "inventory.h"
#define TURN_FAST PI #define TURN_FAST PI
#define TURN_FAST_BACK PI * 3.0f / 4.0f #define TURN_FAST_BACK PI * 3.0f / 4.0f
@@ -189,9 +190,12 @@ struct Lara : Character {
} arms[2]; } arms[2];
ActionCommand actionList[MAX_TRIGGER_ACTIONS]; ActionCommand actionList[MAX_TRIGGER_ACTIONS];
Inventory inventory;
int lastPickUp; 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(); initMeshOverrides();
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f;
@@ -214,12 +218,12 @@ struct Lara : Character {
pos = vec3(43182, 2473, 51556); pos = vec3(43182, 2473, 51556);
angle = vec3(0.0f, PI * 0.5f, 0.0f); angle = vec3(0.0f, PI * 0.5f, 0.0f);
getEntity().room = 12; getEntity().room = 12;
*/
// level 2 (pool) // level 2 (pool)
pos = vec3(70067, -256, 29104); pos = vec3(70067, -256, 29104);
angle = vec3(0.0f, -0.68f, 0.0f); angle = vec3(0.0f, -0.68f, 0.0f);
getEntity().room = 15; getEntity().room = 15;
/*
// level 2 (blade) // level 2 (blade)
pos = vec3(27221, -1024, 29205); pos = vec3(27221, -1024, 29205);
angle = vec3(0.0f, PI * 0.5f, 0.0f); angle = vec3(0.0f, PI * 0.5f, 0.0f);
@@ -245,6 +249,16 @@ struct Lara : Character {
angle = vec3(0.0f, PI, 0.0f); angle = vec3(0.0f, PI, 0.0f);
getEntity().room = 19; 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 // level 3a
pos = vec3(41015, 3584, 34494); pos = vec3(41015, 3584, 34494);
angle = vec3(0.0f, -PI, 0.0f); angle = vec3(0.0f, -PI, 0.0f);
@@ -287,18 +301,17 @@ struct Lara : Character {
arm.animation.updateInfo(); arm.animation.updateInfo();
wpnSetState(wState); wpnSetState(wState);
LOG("set anim\n");
} }
int wpnGetDamage() { int wpnGetDamage() {
switch (wpnCurrent) { switch (wpnCurrent) {
case Weapon::PISTOLS : return 10; case Weapon::PISTOLS : return 1;
case Weapon::SHOTGUN : return 15; case Weapon::SHOTGUN : return 1;
case Weapon::MAGNUMS : return 20; case Weapon::MAGNUMS : return 2;
case Weapon::UZIS : return 5; case Weapon::UZIS : return 1;
default : return 0; default : ;
} }
return 0;
} }
void wpnSetState(Weapon::State wState) { void wpnSetState(Weapon::State wState) {
@@ -537,6 +550,13 @@ struct Lara : Character {
} }
void updateWeapon() { void updateWeapon() {
if (input & DEATH) {
arms[0].shotTimer = arms[1].shotTimer = MUZZLE_FLASH_TIME + 1.0f;
animation.overrideMask = 0;
target = -1;
return;
}
updateTargets(); updateTargets();
updateOverrides(); updateOverrides();
@@ -802,11 +822,17 @@ struct Lara : Character {
} else } else
return false; return false;
} }
virtual void cmdEmpty() { virtual void cmdEmpty() {
wpnHide(); wpnHide();
} }
virtual void hit(int damage, Controller *enemy = NULL) {
health -= damage;
if (enemy && health > 0)
playSound(TR::SND_HIT, pos, Sound::PAN);
};
bool waterOut() { bool waterOut() {
// TODO: playSound 36 // TODO: playSound 36
vec3 dst = pos + getDir() * 32.0f; vec3 dst = pos + getDir() * 32.0f;
@@ -860,6 +886,22 @@ struct Lara : Character {
return fabsf(shortAngle(rotation, getEntity().rotation)) < PI * 0.25f; 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() { void checkTrigger() {
if (actionCommand) return; if (actionCommand) return;
@@ -881,17 +923,21 @@ struct Lara : Character {
break; break;
case TR::Level::Trigger::SWITCH : case TR::Level::Trigger::SWITCH :
actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; 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; return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation)) if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return; return;
break; break;
case TR::Level::Trigger::KEY : case TR::Level::Trigger::KEY :
actionState = STATE_USE_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; return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation)) if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return; 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; break;
case TR::Level::Trigger::PICKUP : case TR::Level::Trigger::PICKUP :
if (!isActive) // check if item is not picked up if (!isActive) // check if item is not picked up
@@ -925,7 +971,7 @@ struct Lara : Character {
return; 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]; TR::FloorData::TriggerCommand &cmd = info.trigCmd[i];
switch (cmd.action) { switch (cmd.action) {
@@ -999,7 +1045,7 @@ struct Lara : Character {
int extra = stand != STAND_AIR ? 256 : 0; 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) { if (stand != STAND_GROUND) {
pos.y = float(info.floor); pos.y = float(info.floor);
updateEntity(); updateEntity();
@@ -1293,7 +1339,9 @@ struct Lara : Character {
} }
virtual int getInput() { virtual int getInput() {
input = 0; input = Character::getInput();
if (input & DEATH) return input;
int &p = Input::joy.POV; int &p = Input::joy.POV;
if (Input::down[ikW] || Input::down[ikUp] || p == 8 || p == 1 || p == 2) input |= FORTH; 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; 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[ikShift] || Input::down[ikJoyLB]) input |= WALK;
if (Input::down[ikE] || Input::down[ikCtrl] || Input::down[ikJoyA]) input |= ACTION; 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 (Input::down[ikQ] || Input::down[ikAlt] || Input::down[ikJoyY]) input |= WEAPON;
if (health <= 0) input = DEATH;
return input; return input;
} }
virtual void doCustomCommand(int curFrame, int prevFrame) { virtual void doCustomCommand(int curFrame, int prevFrame) {
if (state == STATE_PICK_UP) { 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; int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER;
if (curFrame >= pickupFrame) if (animation.isFrameActive(pickupFrame)) {
level->entities[lastPickUp].flags.invisible = true; // TODO: add to inventory item.flags.invisible = true;
inventory.add(item.type, 1);
}
} }
} }
} }
@@ -1384,7 +1434,7 @@ struct Lara : Character {
switch (stand) { switch (stand) {
case STAND_AIR : case STAND_AIR :
velocity.y += GRAVITY * 30.0f * Core::deltaTime; velocity.y += GRAVITY * Core::deltaTime;
break; break;
case STAND_GROUND : case STAND_GROUND :
case STAND_SLIDE : case STAND_SLIDE :
@@ -1611,6 +1661,7 @@ struct Lara : Character {
} }
void renderMuzzleFlash(MeshBuilder *mesh, const mat4 &matrix, const vec3 &offset, float time) { void renderMuzzleFlash(MeshBuilder *mesh, const mat4 &matrix, const vec3 &offset, float time) {
ASSERT(level->extra.muzzleFlash);
if (time > MUZZLE_FLASH_TIME) return; if (time > MUZZLE_FLASH_TIME) return;
float alpha = min(1.0f, (0.1f - time) * 20.0f); float alpha = min(1.0f, (0.1f - time) * 20.0f);
float lum = 3.0f; float lum = 3.0f;
@@ -1619,7 +1670,7 @@ struct Lara : Character {
m.rotateX(-PI * 0.5f); m.rotateX(-PI * 0.5f);
m.translate(offset); m.translate(offset);
Core::active.shader->setParam(uColor, vec4(lum, lum, lum, alpha)); 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) { virtual void render(Frustum *frustum, MeshBuilder *mesh) {

View File

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

View File

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

View File

@@ -88,6 +88,15 @@ float shortAngle(float a, float b) {
return clampAngle(n - int(n / PI2) * PI2); 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 { struct vec2 {
float x, y; float x, y;