diff --git a/src/animation.h b/src/animation.h
index 49c20cf..8b75e50 100644
--- a/src/animation.h
+++ b/src/animation.h
@@ -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;
diff --git a/src/camera.h b/src/camera.h
index 6b40612..f8b21ee 100644
--- a/src/camera.h
+++ b/src/camera.h
@@ -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));
diff --git a/src/character.h b/src/character.h
index 7b34d40..819c4e4 100644
--- a/src/character.h
+++ b/src/character.h
@@ -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();
diff --git a/src/controller.h b/src/controller.h
index 76819a0..1e8a074 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -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) {
diff --git a/src/debug.h b/src/debug.h
index d8bb5ff..3ebb393 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -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);
diff --git a/src/enemy.h b/src/enemy.h
index c588cf7..f5d0a5c 100644
--- a/src/enemy.h
+++ b/src/enemy.h
@@ -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
diff --git a/src/format.h b/src/format.h
index f81b74c..7b26a53 100644
--- a/src/format.h
+++ b/src/format.h
@@ -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() {
diff --git a/src/game.h b/src/game.h
index e29cb48..09ea3f6 100644
--- a/src/game.h
+++ b/src/game.h
@@ -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();
diff --git a/src/inventory.h b/src/inventory.h
new file mode 100644
index 0000000..10fc2b6
--- /dev/null
+++ b/src/inventory.h
@@ -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
\ No newline at end of file
diff --git a/src/lara.h b/src/lara.h
index 2c67f38..0f65402 100644
--- a/src/lara.h
+++ b/src/lara.h
@@ -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();
@@ -802,11 +822,17 @@ struct Lara : Character {
} else
return false;
}
-
+
virtual void cmdEmpty() {
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 שך 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) {
diff --git a/src/platform/win/OpenLara.vcxproj b/src/platform/win/OpenLara.vcxproj
index 8f1a050..5ca5d78 100644
--- a/src/platform/win/OpenLara.vcxproj
+++ b/src/platform/win/OpenLara.vcxproj
@@ -161,6 +161,7 @@
+
diff --git a/src/sound.h b/src/sound.h
index fd37bd5..761d67c 100644
--- a/src/sound.h
+++ b/src/sound.h
@@ -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;
diff --git a/src/utils.h b/src/utils.h
index afe56eb..1dd24ad 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -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;