From ddf9ae25ccf9cab31f58154e705afaece1e8948c Mon Sep 17 00:00:00 2001 From: XProger Date: Wed, 23 Nov 2016 01:38:00 +0300 Subject: [PATCH] #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 --- src/animation.h | 1 + src/camera.h | 5 +- src/character.h | 34 ++-- src/controller.h | 9 +- src/debug.h | 3 +- src/enemy.h | 291 +++++++++++++++++++++++------- src/format.h | 26 ++- src/game.h | 4 +- src/inventory.h | 52 ++++++ src/lara.h | 95 +++++++--- src/platform/win/OpenLara.vcxproj | 1 + src/sound.h | 37 +++- src/utils.h | 9 + 13 files changed, 443 insertions(+), 124 deletions(-) create mode 100644 src/inventory.h 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;