mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-05 20:57:46 +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:
@@ -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;
|
||||
|
@@ -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));
|
||||
|
@@ -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();
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
|
291
src/enemy.h
291
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
|
||||
|
26
src/format.h
26
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() {
|
||||
|
@@ -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
52
src/inventory.h
Normal 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
|
95
src/lara.h
95
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 <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) {
|
||||
|
@@ -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" />
|
||||
|
37
src/sound.h
37
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;
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user