diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index de8cf3b..3d0e86e 100644 Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ diff --git a/src/animation.h b/src/animation.h new file mode 100644 index 0000000..49c20cf --- /dev/null +++ b/src/animation.h @@ -0,0 +1,253 @@ +#ifndef H_ANIMATION +#define H_ANIMATION + +#include "utils.h" +#include "format.h" + +struct Animation { + TR::Level *level; + TR::Model *model; + TR::Animation *anims; + int state; + float time, timeMax, delta, dir; + int index, prev, next; + int frameIndex, framePrev, framesCount; + + TR::AnimFrame *frameA, *frameB; + vec3 offset, jump; + bool isEnded, isPrepareToNext, flip; + + quat *overrides; // left & right arms animation frames + int overrideMask; + + Animation() : overrides(NULL) {} + + Animation(TR::Level *level, TR::Model *model) : level(level), model(model), anims(model ? &level->anims[model->animation] : NULL), time(0), delta(0), dir(1.0f), + index(-1), prev(0), next(0), overrides(NULL), overrideMask(0) { + if (anims) setAnim(0); + } + + ~Animation() { + delete[] overrides; + } + + inline operator TR::Animation* () const { return anims + index; } + + void initOverrides() { + overrides = new quat[model->mCount]; + overrideMask = 0; + } + + void update() { + if (!isEnded) { + time += dir * Core::deltaTime; + isEnded = time <= 0.0f || time >= timeMax; + time = clamp(time, 0.0f, timeMax - EPS); + } + updateInfo(); + } + + int setAnim(int animIndex, int animFrame = 0, bool lerpToNext = true) { + TR::Animation *anim = anims + animIndex; + isEnded = isPrepareToNext = false; + offset = jump = vec3(0.0f); + prev = index; + index = animIndex; + next = anims[index].nextAnimation - model->animation; + time = (animFrame <= 0 ? -animFrame : (animFrame - anim->frameStart)) / 30.0f; + timeMax = (anim->frameEnd - anim->frameStart + lerpToNext) / 30.0f; + framesCount = anim->frameEnd - anim->frameStart + 1; + updateInfo(); + framePrev = frameIndex - 1; + getCommand(anim, frameIndex, &offset, &jump, NULL); + return state = anim->state; + } + + void playNext() { + setAnim(next, anims[index].nextFrame); + } + + void updateInfo() { + ASSERT(model); + ASSERT(anims); + TR::Animation *anim = anims + index; + + // framePrev = frameIndex; + frameIndex = int(time * 30.0f); + + // get count of real frames + int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + // real frame index & lerp delta + int fIndex = int(time * 30.0f) / anim->frameRate; + int k = fIndex * anim->frameRate; + delta = (time * 30.0f - k) / min((int)anim->frameRate, framesCount - k); // min is because in some cases framesCount > realFramesCount / frameRate * frameRate + + // size of frame (in bytes) + int fSize = sizeof(TR::AnimFrame) + model->mCount * sizeof(uint16) * 2; + + int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; + frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; // >> 1 (div 2) because frameData is array of shorts + + int frameNext = frameIndex + 1; + isPrepareToNext = !fIndexB; + if (isPrepareToNext) { + frameNext = anim->nextFrame; + anim = &level->anims[anim->nextAnimation]; + frameNext -= anim->frameStart; + fIndexB = frameNext / anim->frameRate; + } + + getCommand(anim, frameNext, NULL, NULL, &flip); + + frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; + } + + bool isFrameActive(int index) { + return index > framePrev && index <= frameIndex; + } + + bool canSetState(int state) { + TR::Animation *anim = anims + index; + + if (state == anim->state) + return true; + + for (int i = 0; i < anim->scCount; i++) { + TR::AnimState &s = level->states[anim->scOffset + i]; + if (s.state == state) + for (int j = 0; j < s.rangesCount; j++) { + TR::AnimRange &range = level->ranges[s.rangesOffset + j]; + if (anim->frameStart + frameIndex >= range.low && anim->frameStart + frameIndex <= range.high) + return true; + } + } + + return false; + } + + bool setState(int state) { + TR::Animation *anim = anims + index; + + if (state == anim->state) + return true; + + bool exists = false; + + for (int i = 0; i < anim->scCount; i++) { + TR::AnimState &s = level->states[anim->scOffset + i]; + if (s.state == state) { + exists = true; + for (int j = 0; j < s.rangesCount; j++) { + TR::AnimRange &range = level->ranges[s.rangesOffset + j]; + if (anim->frameStart + frameIndex >= range.low && anim->frameStart + frameIndex <= range.high) { + setAnim(range.nextAnimation - model->animation, range.nextFrame); + break; + } + } + } + } + + return exists; + } + + float getSpeed() { + TR::Animation *anim = anims + index; + return anim->speed + anim->accel * (time * 30.0f); + } + + void getCommand(TR::Animation *anim, int frameIndex, vec3 *offset, vec3 *jump, bool *flip) { + int16 *ptr = &level->commands[anim->animCommand]; + + if (offset) *offset = vec3(0.0f); + if (flip) *flip = false; + + for (int i = 0; i < anim->acCount; i++) { + int cmd = *ptr++; + switch (cmd) { + case TR::ANIM_CMD_OFFSET : + if (offset) { + offset->x = (float)*ptr++; + offset->y = (float)*ptr++; + offset->z = (float)*ptr++; + } else + ptr += 3; + break; + case TR::ANIM_CMD_JUMP : + if (jump) { + jump->y = (float)*ptr++; + jump->z = (float)*ptr++; + } else + ptr += 2; + break; + case TR::ANIM_CMD_SOUND : ptr += 2; break; + case TR::ANIM_CMD_EFFECT : + if (flip) { + int frame = (*ptr++) - anim->frameStart; + int fx = (*ptr++) & 0x3FFF; + *flip = fx == TR::EFFECT_ROTATE_180 && frame == frameIndex; + } else + ptr += 2; + break; + } + } + } + + quat getJointRot(int joint) { + return lerpAngle(frameA->getAngle(joint), frameB->getAngle(joint), delta); + } + + mat4 getJoints(mat4 matrix, int joint, bool postRot = false, mat4 *joints = NULL) { + TR::Animation *anim = anims + index; + + vec3 offset = isPrepareToNext ? this->offset : vec3(0.0f); + matrix.translate(((vec3)frameA->pos).lerp(offset + frameB->pos, delta)); + + TR::Node *node = (int)model->node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model->node] : NULL; + + int sIndex = 0; + mat4 stack[16]; + + for (int i = 0; i < model->mCount; i++) { + + if (i > 0 && node) { + TR::Node &t = node[i - 1]; + + if (t.flags & 0x01) matrix = stack[--sIndex]; + if (t.flags & 0x02) stack[sIndex++] = matrix; + + ASSERT(sIndex >= 0 && sIndex < 16); + + matrix.translate(vec3((float)t.x, (float)t.y, (float)t.z)); + } + + if (i == joint && !postRot) + return matrix; + + quat q; + if (overrideMask & (1 << i)) + q = overrides[i]; + else + q = getJointRot(i); + matrix = matrix * mat4(q, vec3(0.0f)); + + if (i == joint && postRot) + return matrix; + + if (joints) + joints[i] = matrix; + } + return matrix; + } + + Box getBoundingBox(const vec3 &pos, int dir) { + vec3 min = frameA->box.min().lerp(frameB->box.min(), delta); + vec3 max = frameA->box.max().lerp(frameB->box.max(), delta); + Box box(min, max); + box.rotate90(dir); + box.min += pos; + box.max += pos; + return box; + } +}; + +#endif \ No newline at end of file diff --git a/src/camera.h b/src/camera.h index 22c96a8..6b40612 100644 --- a/src/camera.h +++ b/src/camera.h @@ -56,7 +56,9 @@ struct Camera : Controller { } virtual void update() { - actTargetEntity = owner->target; + int lookAt = -1; + if (owner->target > -1) lookAt = owner->target; + if (actTargetEntity > -1) lookAt = actTargetEntity; if (timer > 0.0f) { timer -= Core::deltaTime; @@ -65,6 +67,7 @@ struct Camera : Controller { if (room != getRoomIndex()) pos = lastDest; actTargetEntity = actCamera = -1; + target = owner->getViewPoint(); } } #ifdef FREE_CAMERA @@ -91,28 +94,28 @@ struct Camera : Controller { angle.z = 0.0f; //angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD); - float lerpFactor = (actTargetEntity == -1) ? 6.0f : 10.0f; + float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f; vec3 dir; target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime); if (actCamera > -1) { TR::Camera &c = level->cameras[actCamera]; - destPos = vec3(c.x, c.y, c.z); + destPos = vec3(float(c.x), float(c.y), float(c.z)); if (room != getRoomIndex()) pos = destPos; - if (actTargetEntity > -1) { - TR::Entity &e = level->entities[actTargetEntity]; - target = vec3(e.x, e.y, e.z); + if (lookAt > -1) { + TR::Entity &e = level->entities[lookAt]; + target = ((Controller*)e.controller)->pos; } } else { - if (actTargetEntity > -1) { - TR::Entity &e = level->entities[actTargetEntity]; - dir = (vec3(e.x, e.y, e.z) - target).normal(); + if (lookAt > -1) { + TR::Entity &e = level->entities[lookAt]; + dir = (((Controller*)e.controller)->pos - target).normal(); } else dir = getDir(); int destRoom; - if ((owner->wpnState != Lara::Weapon::IS_HIDDEN || owner->state != Lara::STATE_BACK_JUMP) || actTargetEntity > -1) { + if ((!owner->emptyHands() || owner->state != Lara::STATE_BACK_JUMP) || lookAt > -1) { vec3 eye = target - dir * CAMERA_OFFSET; destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true); lastDest = destPos; @@ -138,7 +141,7 @@ struct Camera : Controller { room = info.roomAbove; else if (info.ceiling != 0xffff8100) - pos.y = info.ceiling; + pos.y = (float)info.ceiling; } if (pos.y > info.floor) { @@ -146,7 +149,7 @@ struct Camera : Controller { room = info.roomBelow; else if (info.floor != 0xffff8100) - pos.y = info.floor; + pos.y = (float)info.floor; } // play underwater sound when camera goes under water diff --git a/src/character.h b/src/character.h new file mode 100644 index 0000000..7b34d40 --- /dev/null +++ b/src/character.h @@ -0,0 +1,137 @@ +#ifndef H_CHARACTER +#define H_CHARACTER + +#include "controller.h" + +struct Character : Controller { + int target; + int health; + float tilt; + quat rotHead, rotChest; + + enum Stand { + STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER + } stand; + int input; + + 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 }; + + 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) { + animation.initOverrides(); + rotHead = rotChest = quat(0, 0, 0, 1); + } + + virtual void hit(int damage) { + health -= damage; + }; + + virtual void checkRoom() { + TR::Level::FloorInfo info; + TR::Entity &e = getEntity(); + level->getFloorInfo(e.room, e.x, e.z, info); + + if (info.roomNext != 0xFF) + e.room = info.roomNext; + + if (info.roomBelow != 0xFF && e.y > info.floor) + e.room = info.roomBelow; + + if (info.roomAbove != 0xFF && e.y <= info.ceiling) { + if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { + stand = STAND_ONWATER; + velocity.y = 0; + pos.y = float(info.ceiling); + updateEntity(); + } else + if (stand != STAND_ONWATER) + e.room = info.roomAbove; + } + } + + virtual void cmdKill() { + health = 0; + } + + virtual void updatePosition() {} + virtual Stand getStand() { return stand; } + virtual int getHeight() { return 0; } + virtual int getStateAir() { return state; } + virtual int getStateGround() { return state; } + virtual int getStateSlide() { return state; } + virtual int getStateHang() { return state; } + virtual int getStateUnderwater() { return state; } + virtual int getStateOnwater() { return state; } + virtual int getStateDeath() { return state; } + virtual int getStateDefault() { return state; } + virtual int getInput() { return 0; } + + virtual void updateState() { + int state = animation.state; + + if (input & DEATH) + state = getStateDeath(); + else if (stand == STAND_GROUND) + state = getStateGround(); + else if (stand == STAND_SLIDE) + state = getStateSlide(); + else if (stand == STAND_HANG) + state = getStateHang(); + else if (stand == STAND_AIR) + state = getStateAir(); + else if (stand == STAND_UNDERWATER) + state = getStateUnderwater(); + else + state = getStateOnwater(); + + // try to set new state + if (!animation.setState(state)) + animation.setState(getStateDefault()); + } + + virtual void updateTilt(bool active, float tiltSpeed, float tiltMax) { + // calculate turning tilt + if (active && (input & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (input & LEFT)) || (tilt > 0.0f && (input & RIGHT)))) { + if (input & LEFT) tilt -= tiltSpeed * Core::deltaTime; + if (input & RIGHT) tilt += tiltSpeed * Core::deltaTime; + } else + if (fabsf(tilt) > 0.01f) { + if (tilt > 0.0f) + tilt -= min(tilt, tiltSpeed * 4.0f * Core::deltaTime); + else + tilt -= max(tilt, -tiltSpeed * 4.0f * Core::deltaTime); + } else + tilt = 0.0f; + tilt = clamp(tilt, -tiltMax, tiltMax); + + angle.z = tilt; + } + + virtual void update() { + input = getInput(); + stand = getStand(); + updateState(); + Controller::update(); + updateVelocity(); + updatePosition(); + } + + virtual void cmdJump(const vec3 &vel) { + velocity.x = sinf(angleExt) * vel.z; + velocity.y = vel.y; + velocity.z = cosf(angleExt) * vel.z; + stand = STAND_AIR; + } +}; + +#endif \ No newline at end of file diff --git a/src/controller.h b/src/controller.h index 4723dde..76819a0 100644 --- a/src/controller.h +++ b/src/controller.h @@ -4,51 +4,25 @@ #include "format.h" #include "frustum.h" #include "mesh.h" +#include "animation.h" #define GRAVITY 6.0f #define NO_OVERLAP 0x7FFFFFFF - #define SPRITE_FPS 10.0f struct Controller { TR::Level *level; int entity; + + Animation animation; + int &state; - enum Stand { - STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER - } stand; - int state; - int mask; - - 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 }; - - float animTime; - int animIndex, animPrev; - int animPrevFrame; - - vec3 pos, velocity; + vec3 pos; vec3 angle; - float angleExt; - int *meshes; int mCount; - // TODO: Character class - quat *animOverrides; // left & right arms animation frames - int animOverrideMask; - mat4 *joints; - int health; - float tilt; - struct ActionCommand { TR::Action action; int value; @@ -59,145 +33,39 @@ struct Controller { ActionCommand(TR::Action action, int value, float timer, ActionCommand *next = NULL) : action(action), value(value), timer(timer), next(next) {} } *actionCommand; - Controller(TR::Level *level, int entity) : level(level), entity(entity), velocity(0.0f), animTime(0.0f), animPrevFrame(0), actionCommand(NULL), mCount(0), meshes(NULL), animOverrides(NULL), animOverrideMask(0), joints(NULL) { + Controller(TR::Level *level, int entity) : level(level), entity(entity), animation(level, getModel()), state(animation.state), actionCommand(NULL), mCount(0), meshes(NULL) { TR::Entity &e = getEntity(); pos = vec3((float)e.x, (float)e.y, (float)e.z); angle = vec3(0.0f, e.rotation, 0.0f); - stand = STAND_GROUND; - animIndex = e.modelIndex > 0 ? getModel().animation : 0; - animPrev = animIndex; - state = level->anims[animIndex].state; - TR::Model &model = getModel(); - health = 100; - tilt = 0.0f; } virtual ~Controller() { delete[] meshes; - delete[] animOverrides; - delete[] joints; } void initMeshOverrides() { - TR::Model &model = getModel(); - mCount = model.mCount; + TR::Model *model = getModel(); + mCount = model->mCount; meshes = mCount ? new int[mCount] : NULL; for (int i = 0; i < mCount; i++) - meshes[i] = model.mStart + i; + meshes[i] = model->mStart + i; } - void initAnimOverrides() { - TR::Model &model = getModel(); - - animOverrides = new quat[model.mCount]; - animOverrideMask = 0; - - joints = new mat4[model.mCount]; - } - - void meshSwap(TR::Model &model, int mask) { - for (int i = 0; i < model.mCount; i++) { - int index = model.mStart + i; + void meshSwap(TR::Model *model, int mask) { + for (int i = 0; i < model->mCount; i++) { + int index = model->mStart + i; if (((1 << i) & mask) && level->meshOffsets[index]) meshes[i] = index; } } - int getFramesCount(int animIndex) { - TR::Animation &anim = level->anims[animIndex]; - return (anim.frameEnd - anim.frameStart) / anim.frameRate + 1; - } - - int getFrameIndex(int animIndex, float t) { - TR::Animation &anim = level->anims[animIndex]; - return int(t * 30.0f / anim.frameRate) % ((anim.frameEnd - anim.frameStart) / anim.frameRate + 1); - } - - void getFrames(TR::AnimFrame **frameA, TR::AnimFrame **frameB, float &t, int animIndex, float animTime, bool nextAnim = false, vec3 *move = NULL) { - TR::Animation *anim = &level->anims[animIndex]; - - t = animTime * 30.0f / anim->frameRate; - int fIndex = (int)t; - int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; - - int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2; - t -= fIndex; - - int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; - *frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; - - if (!fIndexB) { - if (move) - *move = getAnimMove(); - if (nextAnim) { - int nextFrame = anim->nextFrame; - anim = &level->anims[anim->nextAnimation]; - fIndexB = (nextFrame - anim->frameStart) / anim->frameRate; - } - } - *frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; - } - - mat4 getJoint(int index, bool postRot = false) { - mat4 matrix; - matrix.identity(); - - matrix.translate(pos); - if (angle.y != 0.0f) matrix.rotateY(angle.y); - if (angle.x != 0.0f) matrix.rotateX(angle.x); - if (angle.z != 0.0f) matrix.rotateZ(angle.z); - - TR::Animation *anim = &level->anims[animIndex]; - TR::Model &model = getModel(); - - float t; - vec3 move(0.0f); - TR::AnimFrame *frameA, *frameB; - getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move); - - TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL; - - matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t)); - - int sIndex = 0; - mat4 stack[20]; - - for (int i = 0; i < model.mCount; i++) { - - if (i > 0 && node) { - TR::Node &t = node[i - 1]; - - if (t.flags & 0x01) matrix = stack[--sIndex]; - if (t.flags & 0x02) stack[sIndex++] = matrix; - - ASSERT(sIndex >= 0 && sIndex < 20); - - matrix.translate(vec3(t.x, t.y, t.z)); - } - - if (i == index && !postRot) - return matrix; - - quat q; - if (animOverrideMask & (1 << i)) - q = animOverrides[i]; - else - q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t); - matrix = matrix * mat4(q, vec3(0.0f)); - - if (i == index && postRot) - return matrix; - } - return matrix; - } - bool aim(int target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) { if (target > -1) { TR::Entity &e = level->entities[target]; Box box = ((Controller*)e.controller)->getBoundingBox(); vec3 t = (box.min + box.max) * 0.5f; - mat4 m = getJoint(joint); + mat4 m = animation.getJoints(getMatrix(), joint); vec3 delta = (m.inverse() * t).normal(); float angleY = clampAngle(atan2(delta.x, delta.z)); @@ -233,16 +101,17 @@ struct Controller { bool insideRoom(const vec3 &pos, int room) const { TR::Room &r = level->rooms[room]; - vec3 min = vec3(r.info.x, r.info.yTop, r.info.z); - vec3 max = min + vec3(r.xSectors * 1024, r.info.yBottom - r.info.yTop, r.zSectors * 1024); + vec3 min = vec3((float)r.info.x, (float)r.info.yTop, (float)r.info.z); + vec3 max = min + vec3(float(r.xSectors * 1024), float(r.info.yBottom - r.info.yTop), float(r.zSectors * 1024)); return pos.x >= min.x && pos.x <= max.x && pos.y >= min.y && pos.y <= max.y && pos.z >= min.z && pos.z <= max.z; } - TR::Model& getModel() const { - return level->models[getEntity().modelIndex - 1]; + TR::Model* getModel() const { + int index = getEntity().modelIndex; + return index > 0 ? &level->models[index - 1] : NULL; } TR::Entity& getEntity() const { @@ -259,64 +128,6 @@ struct Controller { return getEntity().room; } - int setAnimation(int index, int frame = 0) { - animPrev = animIndex; - animIndex = index; - TR::Animation &anim = level->anims[animIndex]; - animTime = (frame <= 0 ? -frame : (frame - anim.frameStart)) / 30.0f; - ASSERT(anim.frameStart <= anim.frameEnd); - animPrevFrame = int(animTime * 30.0f) - 1; - return state = anim.state; - } - - bool canSetState(int state) { - TR::Animation *anim = &level->anims[animIndex]; - - if (state == anim->state) - return true; - - int fIndex = int(animTime * 30.0f); - - for (int i = 0; i < anim->scCount; i++) { - TR::AnimState &s = level->states[anim->scOffset + i]; - if (s.state == state) - for (int j = 0; j < s.rangesCount; j++) { - TR::AnimRange &range = level->ranges[s.rangesOffset + j]; - if (anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high) - return true; - } - } - - return false; - } - - bool setState(int state) { - TR::Animation *anim = &level->anims[animIndex]; - - if (state == anim->state) - return true; - - int fIndex = int(animTime * 30.0f); - - bool exists = false; - - for (int i = 0; i < anim->scCount; i++) { - TR::AnimState &s = level->states[anim->scOffset + i]; - if (s.state == state) { - exists = true; - for (int j = 0; j < s.rangesCount; j++) { - TR::AnimRange &range = level->ranges[s.rangesOffset + j]; - if (anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high) { - setAnimation(range.nextAnimation, range.nextFrame); - break; - } - } - } - } - - return exists; - } - int getOverlap(int fromX, int fromY, int fromZ, int toX, int toZ) const { int dx, dz; TR::Room::Sector &s = level->getSector(getEntity().room, fromX, fromZ, dx, dz); @@ -391,15 +202,7 @@ struct Controller { } virtual Box getBoundingBox() { - float t; - TR::AnimFrame *frameA, *frameB; - getFrames(&frameA, &frameB, t, animIndex, animTime, true); - - Box box(frameA->box.min().lerp(frameB->box.min(), t), frameA->box.max().lerp(frameB->box.max(), t)); - box.rotate90(getEntity().rotation.value / 0x4000); - box.min += pos; - box.max += pos; - return box; + return animation.getBoundingBox(pos, getEntity().rotation.value / 0x4000); } vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham @@ -439,7 +242,7 @@ struct Controller { int maxX = minX + 1024; int maxZ = minZ + 1024; - pos = vec3(clamp(px, minX, maxX), pos.y, clamp(pz, minZ, maxZ)) + boxNormal(px, pz) * 256.0f; + pos = vec3(float(clamp(px, minX, maxX)), pos.y, float(clamp(pz, minZ, maxZ))) + boxNormal(px, pz) * 256.0f; dir = (pos - from).normal(); } } else { @@ -472,7 +275,7 @@ struct Controller { if (rand() % 10 <= 6) return; playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN); } - + /* void collide() { TR::Entity &entity = getEntity(); @@ -510,7 +313,7 @@ struct Controller { } } } - + */ void activateNext() { // activate next entity (for triggers) if (!actionCommand || !actionCommand->next) { actionCommand = NULL; @@ -555,93 +358,27 @@ struct Controller { actionCommand = NULL; } - virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; } - virtual void doCustomCommand (int curFrame, int prevFrame) {} - virtual void updateVelocity() {} - virtual void checkRoom() {} - virtual void move() {} - virtual Stand getStand() { return STAND_AIR; } - virtual int getHeight() { return 0; } - virtual int getStateAir() { return state; } - virtual int getStateGround() { return state; } - virtual int getStateSlide() { return state; } - virtual int getStateHang() { return state; } - virtual int getStateUnderwater() { return state; } - virtual int getStateOnwater() { return state; } - virtual int getStateDeath() { return state; } - virtual int getStateDefault() { return state; } - virtual int getInputMask() { return 0; } - virtual void hit(int damage) { }; + virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; } + virtual void doCustomCommand (int curFrame, int prevFrame) {} + virtual void updateVelocity() {} + virtual void checkRoom() {} - virtual int getState(Stand stand) { - TR::Animation *anim = &level->anims[animIndex]; - - int state = anim->state; - - if (mask & DEATH) - state = getStateDeath(); - else if (stand == STAND_GROUND) - state = getStateGround(); - else if (stand == STAND_SLIDE) - state = getStateSlide(); - else if (stand == STAND_HANG) - state = getStateHang(); - else if (stand == STAND_AIR) - state = getStateAir(); - else if (stand == STAND_UNDERWATER) - state = getStateUnderwater(); - else - state = getStateOnwater(); - - // try to set new state - if (!setState(state)) - setState(getStateDefault()); - - return level->anims[animIndex].state; - } - - virtual void updateBegin() { - mask = getInputMask(); - state = getState(stand = getStand()); - } - - virtual void updateEnd() { - TR::Entity &e = getEntity(); - move(); + virtual void cmdOffset(const vec3 &offset) { + pos = pos + offset.rotateY(-angle.y); updateEntity(); + checkRoom(); } - virtual void updateState() {} + virtual void cmdJump(const vec3 &vel) {} + virtual void cmdKill() {} + virtual void cmdEmpty() {} - virtual vec3 getAnimMove() { - TR::Animation *anim = &level->anims[animIndex]; - int16 *ptr = &level->commands[anim->animCommand]; - - for (int i = 0; i < anim->acCount; i++) { - int cmd = *ptr++; - switch (cmd) { - case TR::ANIM_CMD_MOVE : { // cmd position - int16 sx = *ptr++; - int16 sy = *ptr++; - int16 sz = *ptr++; - return vec3((float)sx, (float)sy, (float)sz); - break; - } - case TR::ANIM_CMD_SPEED : // cmd jump speed - case TR::ANIM_CMD_SOUND : // play sound - case TR::ANIM_CMD_EFFECT : // special commands - ptr += 2; - break; - } - } - return vec3(0.0f); - } virtual void updateAnimation(bool commands) { - int frameIndex = int((animTime += Core::deltaTime) * 30.0f); - TR::Animation *anim = &level->anims[animIndex]; - bool endFrame = frameIndex > anim->frameEnd - anim->frameStart; - + animation.update(); + + TR::Animation *anim = animation; + // apply animation commands if (commands) { int16 *ptr = &level->commands[anim->animCommand]; @@ -649,50 +386,24 @@ struct Controller { for (int i = 0; i < anim->acCount; i++) { int cmd = *ptr++; switch (cmd) { - case TR::ANIM_CMD_MOVE : { // cmd position - int16 sx = *ptr++; - int16 sy = *ptr++; - int16 sz = *ptr++; - if (endFrame) { - pos = pos + vec3(sx, sy, sz).rotateY(-angle.y); - updateEntity(); - checkRoom(); - LOG("move: %d %d %d\n", (int)sx, (int)sy, (int)sz); - } - break; - } - case TR::ANIM_CMD_SPEED : { // cmd jump speed - int16 sy = *ptr++; - int16 sz = *ptr++; - if (endFrame) { - LOG("jump: %d %d\n", (int)sy, (int)sz); - velocity.x = sinf(angleExt) * sz; - velocity.y = sy; - velocity.z = cosf(angleExt) * sz; - stand = STAND_AIR; - } - break; - } - case TR::ANIM_CMD_EMPTY : // empty hands - break; - case TR::ANIM_CMD_KILL : // kill - break; - case TR::ANIM_CMD_SOUND : // play sound - case TR::ANIM_CMD_EFFECT : { // special commands - int frame = (*ptr++); - int id = (*ptr++) & 0x3FFF; - int idx = frame - anim->frameStart; - - if (idx > animPrevFrame && idx <= frameIndex) { + case TR::ANIM_CMD_OFFSET : ptr += 3; break; + case TR::ANIM_CMD_JUMP : ptr += 2; break; + case TR::ANIM_CMD_EMPTY : cmdEmpty(); break; + case TR::ANIM_CMD_KILL : cmdKill(); break; + case TR::ANIM_CMD_SOUND : + case TR::ANIM_CMD_EFFECT : { + int frame = (*ptr++) - anim->frameStart; + int fx = (*ptr++) & 0x3FFF; + if (animation.isFrameActive(frame)) { if (cmd == TR::ANIM_CMD_EFFECT) { - switch (id) { - case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break; + switch (fx) { + case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break; case TR::EFFECT_LARA_BUBBLES : doBubbles(); break; case TR::EFFECT_LARA_HANDSFREE : break; - default : LOG("unknown special cmd %d (anim %d)\n", id, animIndex); + default : LOG("unknown special cmd %d (anim %d)\n", fx, animation.index); } } else - playSound(id, pos, Sound::Flags::PAN); + playSound(fx, pos, Sound::Flags::PAN); } break; } @@ -700,21 +411,20 @@ struct Controller { } } - doCustomCommand(frameIndex, animPrevFrame); + if (animation.frameIndex != animation.framePrev) + doCustomCommand(animation.frameIndex, animation.framePrev); - if (endFrame) { // if animation is end - switch to next - setAnimation(anim->nextAnimation, anim->nextFrame); + if (animation.isEnded) { // if animation is end - switch to next + if (animation.offset != 0.0f) cmdOffset(animation.offset); + if (animation.jump != 0.0f) cmdJump(animation.jump); + animation.playNext(); activateNext(); } else - animPrevFrame = frameIndex; + animation.framePrev = animation.frameIndex; } virtual void update() { - updateBegin(); - updateState(); - updateAnimation(true); - updateVelocity(); - updateEnd(); + updateAnimation(true); } void renderMesh(const mat4 &matrix, MeshBuilder *mesh, uint32 offsetIndex) { @@ -739,139 +449,40 @@ struct Controller { mesh->renderShadowSpot(); } - virtual void render(Frustum *frustum, MeshBuilder *mesh) { - TR::Entity &entity = getEntity(); - TR::Model &model = getModel(); - - TR::Animation *anim = &level->anims[animIndex]; - - mat4 matrix(Core::mModel); + mat4 getMatrix() { + mat4 matrix; + matrix.identity(); matrix.translate(pos); - if (angle.y != 0.0f) matrix.rotateY(angle.y); + if (angle.y != 0.0f) matrix.rotateY(angle.y - (animation.flip ? PI * animation.delta : 0.0f)); if (angle.x != 0.0f) matrix.rotateX(angle.x); if (angle.z != 0.0f) matrix.rotateZ(angle.z); + return matrix; + } - float t; - vec3 move(0.0f); - TR::AnimFrame *frameA, *frameB; - getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move); + virtual void render(Frustum *frustum, MeshBuilder *mesh) { // TODO: animation.calcJoints + mat4 matrix = getMatrix(); - vec3 bmin = frameA->box.min().lerp(frameB->box.min(), t); - vec3 bmax = frameA->box.max().lerp(frameB->box.max(), t); - if (frustum && !frustum->isVisible(matrix, bmin, bmax)) + Box box = animation.getBoundingBox(vec3(0, 0, 0), 0); + if (frustum && !frustum->isVisible(matrix, box.min, box.max)) return; + + TR::Entity &entity = getEntity(); + TR::Model *model = getModel(); entity.flags.rendered = true; - TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL; + mat4 joints[32]; // TODO: UBO heap + ASSERT(model->mCount <= 32); - matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t)); - - int sIndex = 0; - mat4 stack[20]; - - for (int i = 0; i < model.mCount; i++) { - - if (i > 0 && node) { - TR::Node &t = node[i - 1]; - - if (t.flags & 0x01) matrix = stack[--sIndex]; - if (t.flags & 0x02) stack[sIndex++] = matrix; - - ASSERT(sIndex >= 0 && sIndex < 20); - - matrix.translate(vec3(t.x, t.y, t.z)); - } - - quat q; - if (animOverrideMask & (1 << i)) - q = animOverrides[i]; - else - q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t); - matrix = matrix * mat4(q, vec3(0.0f)); - - if (meshes) - renderMesh(matrix, mesh, meshes[i]); - else - renderMesh(matrix, mesh, model.mStart + i); - - if (joints) - joints[i] = matrix; - } + animation.getJoints(matrix, -1, true, joints); + for (int i = 0; i < model->mCount; i++) + renderMesh(joints[i], mesh, meshes ? meshes[i] : (model->mStart + i)); if (TR::castShadow(entity.type)) { TR::Level::FloorInfo info; level->getFloorInfo(entity.room, entity.x, entity.z, info, true); - renderShadow(mesh, vec3(entity.x, info.floor - 16.0f, entity.z), (bmax + bmin) * 0.5f, (bmax - bmin) * 0.8f, entity.rotation); + renderShadow(mesh, vec3(float(entity.x), info.floor - 16.0f, float(entity.z)), box.center(), box.size() * 0.8f, angle.y); } } - - quat lerpFrames(TR::AnimFrame *frameA, TR::AnimFrame *frameB, float t, int index) { - return lerpAngle(frameA->getAngle(index), frameB->getAngle(index), t); - } }; - -struct SpriteController : Controller { - - enum { - FRAME_ANIMATED = -1, - FRAME_RANDOM = -2, - }; - - int frame, flag; - bool instant; - - SpriteController(TR::Level *level, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(level, entity), instant(instant), flag(frame) { - if (frame >= 0) { // specific frame - this->frame = frame; - } else if (frame == FRAME_RANDOM) { // random frame - this->frame = rand() % getSequence().sCount; - } else if (frame == FRAME_ANIMATED) { // animated - this->frame = 0; - } - } - - TR::SpriteSequence& getSequence() { - return level->spriteSequences[-(getEntity().modelIndex + 1)]; - } - - void update() { - if (flag >= 0) return; - - bool remove = false; - animTime += Core::deltaTime; - - if (flag == FRAME_ANIMATED) { - frame = int(animTime * SPRITE_FPS); - TR::SpriteSequence &seq = getSequence(); - if (instant && frame >= seq.sCount) - remove = true; - else - frame %= seq.sCount; - } else - if (instant && animTime >= (1.0f / SPRITE_FPS)) - remove = true; - - if (remove) { - level->entityRemove(entity); - delete this; - } - } - - virtual void render(Frustum *frustum, MeshBuilder *mesh) { - mat4 m(Core::mModel); - m.translate(pos); - Core::active.shader->setParam(uModel, m); - mesh->renderSprite(-(getEntity().modelIndex + 1), frame); - } -}; - -void addSprite(TR::Level *level, TR::Entity::Type type, int room, int x, int y, int z, int frame = -1) { - int index = level->entityAdd(type, room, x, y, z, 0, -1); - if (index > -1) { - level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient; - level->entities[index].controller = new SpriteController(level, index, true, frame); - } -} - #endif \ No newline at end of file diff --git a/src/debug.h b/src/debug.h index 4d7b0be..d8bb5ff 100644 --- a/src/debug.h +++ b/src/debug.h @@ -449,13 +449,13 @@ namespace Debug { TR::Node *node = m.node < level.nodesDataSize ? (TR::Node*)&level.nodesData[m.node] : NULL; if (!node) continue; // ??? - +/* if (e.type == m.type) { ASSERT(m.animation < 0xFFFF); int fSize = sizeof(TR::AnimFrame) + m.mCount * sizeof(uint16) * 2; - TR::Animation *anim = controller ? &level.anims[controller->animIndex] : &level.anims[m.animation]; + TR::Animation *anim = controller->animation; TR::AnimFrame *frame = (TR::AnimFrame*)&level.frameData[(anim->frameOffset + (controller ? int((controller->animTime * 30.0f / anim->frameRate)) * fSize : 0) >> 1)]; //mat4 m; @@ -495,35 +495,39 @@ namespace Debug { int offset = level.meshOffsets[m.mStart + k]; TR::Mesh *mesh = (TR::Mesh*)&level.meshData[offset / 2]; Debug::Draw::sphere(matrix * joint * mesh->center, mesh->collider.radius, mesh->collider.info ? vec4(1, 0, 0, 0.5f) : vec4(0, 1, 1, 0.5f)); - /* + { //if (e.id != 0) { char buf[255]; sprintf(buf, "(%d) radius %d info %d flags %d", e.id, (int)mesh->collider.radius, (int)mesh->collider.info, (int)mesh->collider.flags); Debug::Draw::text(matrix * joint * mesh->center, vec4(0.5, 1, 0.5, 1), buf); } - */ + } - Debug::Draw::box(matrix, frame->box.min(), frame->box.max(), vec4(1.0)); + break; } +*/ } } } - void info(const TR::Level &level, const TR::Entity &entity, int state, int anim, int frame) { + void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) { 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, state = %d, anim = %d, frame = %d", entity.x, entity.y, entity.z, entity.room, state, anim, frame); + sprintf(buf, "pos = (%d, %d, %d), room = %d", entity.x, entity.y, entity.z, 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); + Debug::Draw::text(vec2(16, 48), vec4(1.0f), buf); TR::Level::FloorInfo info; level.getFloorInfo(entity.room, entity.x, entity.z, info); sprintf(buf, "floor = %d, roomBelow = %d, roomAbove = %d, height = %d", info.floorIndex, info.roomBelow, info.roomAbove, info.floor - info.ceiling); - Debug::Draw::text(vec2(16, 48), vec4(1.0f), buf); + Debug::Draw::text(vec2(16, 64), vec4(1.0f), buf); } } } diff --git a/src/enemy.h b/src/enemy.h index ad29d4d..c588cf7 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -1,26 +1,11 @@ #ifndef H_ENEMY #define H_ENEMY -#include "controller.h" +#include "character.h" -struct Enemy : Controller { - int target; - quat rotHead, rotChest; - int baseAnim; +struct Enemy : Character { - Enemy(TR::Level *level, int entity) : Controller(level, entity), target(-1) { - initAnimOverrides(); - rotHead = rotChest = quat(0, 0, 0, 1); - baseAnim = animIndex; - } - - virtual Stand getStand() { - return STAND_GROUND; - } - - virtual void hit(int damage) { - health -= damage; - }; + Enemy(TR::Level *level, int entity, int health) : Character(level, entity, health) {} virtual bool activate(ActionCommand *cmd) { Controller::activate(cmd); @@ -38,66 +23,40 @@ struct Enemy : Controller { } virtual void updateVelocity() { - TR::Animation *anim = &level->anims[animIndex]; - float speed = anim->speed + anim->accel * (animTime * 30.0f); + TR::Animation *anim = animation; + float speed = anim->speed + anim->accel * (animation.time * 30.0f); velocity = getDir() * speed; } - virtual void move() { + virtual void updatePosition() { if (!getEntity().flags.active) return; vec3 p = pos; pos += velocity * Core::deltaTime * 30.0f; TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info); + level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info, true); if (pos.y - info.floor > 1024) { pos = p; return; } if (stand == STAND_GROUND) - pos.y = info.floor; + pos.y = float(info.floor); updateEntity(); checkRoom(); } - virtual void checkRoom() { - TR::Level::FloorInfo info; - TR::Entity &e = getEntity(); - level->getFloorInfo(e.room, e.x, e.z, info); - - if (info.roomNext != 0xFF) - e.room = info.roomNext; - - if (info.roomBelow != 0xFF && e.y > info.floor) - e.room = info.roomBelow; - - if (info.roomAbove != 0xFF && e.y <= info.ceiling) { - if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { - stand = STAND_ONWATER; - velocity.y = 0; - pos.y = info.ceiling; - updateEntity(); - } else - if (stand != STAND_ONWATER) - e.room = info.roomAbove; - } - } - void setOverrides(bool active, int chest, int head) { int mask = 0; - if (head > -1) mask |= (1 << head); - if (chest > -1) mask |= (1 << chest); + if (active && head > -1) { + animation.overrides[head] = animation.getJointRot(head); + animation.overrideMask |= (1 << head); + } else + animation.overrideMask &= ~(1 << head); - if (active) - animOverrideMask |= mask; - else - animOverrideMask &= ~mask; - - TR::AnimFrame *frameA, *frameB; - float t; - - getFrames(&frameA, &frameB, t, animIndex, animTime, true); - animOverrides[chest] = lerpFrames(frameA, frameB, t, chest); - animOverrides[head] = lerpFrames(frameA, frameB, t, head); + if (active && chest > -1) { + animation.overrides[chest] = animation.getJointRot(chest); + animation.overrideMask |= (1 << chest); + } else + animation.overrideMask &= ~(1 << chest); } void lookAt(int target, int chest, int head) { @@ -105,11 +64,11 @@ struct Enemy : Controller { quat rot; if (chest > -1) { - if (aim(target, chest, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.75f, PI * 0.75f), rot)) + if (aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot)) rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); else rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); - animOverrides[chest] = rotChest * animOverrides[chest]; + animation.overrides[chest] = rotChest * animation.overrides[chest]; } if (head > -1) { @@ -117,14 +76,16 @@ struct Enemy : Controller { rotHead = rotHead.slerp(rot, speed); else rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); - animOverrides[head] = rotHead * animOverrides[head]; + animation.overrides[head] = rotHead * animation.overrides[head]; } } - virtual int getInputMask() { + virtual int getInput() { if (target > -1) { - vec3 v = (((Controller*)level->entities[target].controller)->pos - pos).normal(); - float d = atan2(v.x, v.z) - angle.y; + vec3 a = (((Controller*)level->entities[target].controller)->pos - pos).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; } @@ -135,6 +96,9 @@ struct Enemy : Controller { #define WOLF_TURN_FAST PI #define WOLF_TURN_SLOW (PI / 3.0f) +#define WOLF_TILT_MAX (PI / 6.0f) +#define WOLF_TILT_SPEED WOLF_TILT_MAX + struct Wolf : Enemy { @@ -163,7 +127,7 @@ struct Wolf : Enemy { JOINT_HEAD = 3 }; - Wolf(TR::Level *level, int entity) : Enemy(level, entity) {} + Wolf(TR::Level *level, int entity) : Enemy(level, entity, 100) {} virtual int getStateGround() { // STATE_SLEEP -> STATE_STOP @@ -178,9 +142,9 @@ struct Wolf : Enemy { if (health <= 0) { switch (state) { - case STATE_RUN : return setAnimation(baseAnim + ANIM_DEATH_RUN); - case STATE_JUMP : return setAnimation(baseAnim + ANIM_DEATH_JUMP); - default : return setAnimation(baseAnim + ANIM_DEATH); + case STATE_RUN : return animation.setAnim(ANIM_DEATH_RUN); + case STATE_JUMP : return animation.setAnim(ANIM_DEATH_JUMP); + default : return animation.setAnim(ANIM_DEATH); } } @@ -191,7 +155,7 @@ struct Wolf : Enemy { switch (state) { case STATE_SLEEP : return STATE_STOP; case STATE_STOP : return STATE_HOWL; - case STATE_GROWL : return STATE_STALKING; + case STATE_GROWL : return randf() > 0.5f ? STATE_STALKING : STATE_RUN; case STATE_STALKING : if (health < 70) return STATE_RUN; break; } @@ -215,7 +179,7 @@ struct Wolf : Enemy { float w = 0.0f; if (state == STATE_RUN || state == STATE_STALKING) { w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW; - if (mask & LEFT) w = -w; + if (input & LEFT) w = -w; if (w != 0.0f) { w *= Core::deltaTime; @@ -226,12 +190,14 @@ struct Wolf : Enemy { velocity = vec3(0.0f); } - virtual void move() { + virtual void updatePosition() { + updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX); + if (state == STATE_DEATH) { - animOverrideMask = 0; + animation.overrideMask = 0; return; } - Enemy::move(); + Enemy::updatePosition(); setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD); lookAt(target, JOINT_CHEST, JOINT_HEAD); } @@ -244,7 +210,7 @@ struct Bear : Enemy { STATE_STOP = 1, }; - Bear(TR::Level *level, int entity) : Enemy(level, entity) {} + Bear(TR::Level *level, int entity) : Enemy(level, entity, 100) {} virtual int getStateGround() { return state; @@ -259,14 +225,14 @@ struct Bat : Enemy { STATE_FLY = 2, }; - Bat(TR::Level *level, int entity) : Enemy(level, entity) {} + Bat(TR::Level *level, int entity) : Enemy(level, entity, 100) {} virtual Stand getStand() { return STAND_AIR; } virtual int getStateAir() { - animTime = 0.0f; + animation.time = 0.0f; return STATE_AWAKE; } }; diff --git a/src/format.h b/src/format.h index 746e34b..f81b74c 100644 --- a/src/format.h +++ b/src/format.h @@ -5,13 +5,14 @@ #define MAX_RESERVED_ENTITIES 64 #define MAX_SECRETS_COUNT 16 +#define MAX_TRIGGER_COMMANDS 32 namespace TR { enum { ANIM_CMD_NONE , - ANIM_CMD_MOVE , - ANIM_CMD_SPEED , + ANIM_CMD_OFFSET , + ANIM_CMD_JUMP , ANIM_CMD_EMPTY , ANIM_CMD_KILL , ANIM_CMD_SOUND , @@ -106,6 +107,15 @@ namespace TR { SND_SECRET = 173, }; + enum { + MODEL_LARA = 0, + MODEL_PISTOLS = 1, + MODEL_SHOTGUN = 2, + MODEL_MAGNUMS = 3, + MODEL_UZIS = 4, + MODEL_LARA_SPEC = 5, + }; + enum Action : uint16 { ACTIVATE , // activate item CAMERA_SWITCH , // switch to camera @@ -419,6 +429,17 @@ namespace TR { bool isEnemy() { return type >= ENEMY_TWIN && type <= ENEMY_LARSON; } + + int isItem() { + return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) || + (type >= PUZZLE_1 && type <= PUZZLE_4) || + (type >= KEY_1 && type <= KEY_4) || + (type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == ARTIFACT || type == PICKUP); + } + + bool isBlock() { + return type >= TR::Entity::BLOCK_1 && type <= TR::Entity::BLOCK_2; + } }; struct Animation { @@ -463,11 +484,13 @@ namespace TR { vec3 max() const { return vec3((float)maxX, (float)maxY, (float)maxZ); } }; + #pragma warning( push ) + #pragma warning( disable : 4200 ) // zero-sized array warning struct AnimFrame { MinMax box; - Vertex pos; // Starting offset for this model + Vertex pos; int16 aCount; - uint16 angles[0]; // angle frames in YXZ order + uint16 angles[0]; // angle frames in YXZ order vec3 getAngle(int index) { #define ANGLE_SCALE (2.0f * PI / 1024.0f) @@ -475,7 +498,7 @@ namespace TR { uint16 b = angles[index * 2 + 0]; uint16 a = angles[index * 2 + 1]; - return vec3((a & 0x3FF0) >> 4, ( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), b & 0x03FF) * ANGLE_SCALE; + return vec3(float((a & 0x3FF0) >> 4), float( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), float(b & 0x03FF)) * ANGLE_SCALE; } }; @@ -483,6 +506,7 @@ namespace TR { int16 count; // number of texture offsets - 1 in group int16 textures[0]; // offsets into objectTextures[] }; + #pragma warning( push ) struct Node { uint32 flags; @@ -715,7 +739,7 @@ namespace TR { int trigCmdCount; Trigger trigger; FloorData::TriggerInfo trigInfo; - FloorData::TriggerCommand trigCmd[16]; + FloorData::TriggerCommand trigCmd[MAX_TRIGGER_COMMANDS]; vec3 getNormal() { return vec3((float)-slantX, -4.0f, (float)-slantZ).normal(); @@ -734,7 +758,8 @@ namespace TR { bool secrets[MAX_SECRETS_COUNT]; void *cameraController; - Level(Stream &stream, bool demo) { + Level(const char *name, bool demo) { + Stream stream(name); // read version stream.read(version); // tiles @@ -1039,6 +1064,7 @@ namespace TR { info.trigInfo = (*fd++).triggerInfo; FloorData::TriggerCommand trigCmd; do { + ASSERT(info.trigCmdCount < MAX_TRIGGER_COMMANDS); trigCmd = (*fd++).triggerCmd; // trigger action info.trigCmd[info.trigCmdCount++] = trigCmd; } while (!trigCmd.end); diff --git a/src/game.h b/src/game.h index 81526b1..e29cb48 100644 --- a/src/game.h +++ b/src/game.h @@ -12,8 +12,9 @@ namespace Game { void init() { Core::init(); - Stream stream("LEVEL2_DEMO.PHD"); - level = new Level(stream, true); + level = new Level("LEVEL2_DEMO.PHD", true, false); + //level = new Level("GYM.PHD", false, true); + //level = new Level("LEVEL4.PHD", false, false); #ifndef __EMSCRIPTEN__ //Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0); @@ -29,7 +30,13 @@ namespace Game { } void update() { + float dt = Core::deltaTime; + if (Input::down[ikR]) // slow motion (for animation debugging) + Core::deltaTime /= 50.0f; + level->update(); + + Core::deltaTime = dt; } void render() { diff --git a/src/lara.h b/src/lara.h index 73f8bfd..2c67f38 100644 --- a/src/lara.h +++ b/src/lara.h @@ -3,20 +3,23 @@ /*****************************************/ /* Desine sperare qui hic intras */ /*****************************************/ -#include "controller.h" +#include "character.h" #include "trigger.h" +#include "sprite.h" #define TURN_FAST PI #define TURN_FAST_BACK PI * 3.0f / 4.0f #define TURN_NORMAL PI / 2.0f #define TURN_SLOW PI / 3.0f -#define TILT_MAX (PI / 18.0f) -#define TILT_SPEED TILT_MAX #define TURN_WATER_FAST PI * 3.0f / 4.0f #define TURN_WATER_SLOW PI * 2.0f / 3.0f + +#define TILT_MAX (PI / 18.0f) +#define TILT_SPEED TILT_MAX + #define GLIDE_SPEED 50.0f -#define LARA_HANG_OFFSET 735 +#define LARA_HANG_OFFSET 735.0f #define PICKUP_FRAME_GROUND 40 #define PICKUP_FRAME_UNDERWATER 16 @@ -28,10 +31,7 @@ #define FLASH_LIGHT_COLOR vec4(0.8f, 0.7f, 0.3f, 2048 * 2048) #define TARGET_MAX_DIST (8.0f * 1024.0f) -struct Lara : Controller { - - ActionCommand actionList[MAX_TRIGGER_ACTIONS]; - int lastPickUp; +struct Lara : Character { // http://www.tombraiderforums.com/showthread.php?t=148859 enum { @@ -165,6 +165,8 @@ struct Lara : Controller { BODY_LOWER = BODY_HIP | BODY_LEG_L | BODY_LEG_R, }; + bool home; + struct Weapon { enum Type { EMPTY, PISTOLS, SHOTGUN, MAGNUMS, UZIS, MAX }; enum State { IS_HIDDEN, IS_ARMED, IS_FIRING }; @@ -180,38 +182,32 @@ struct Lara : Controller { struct Arm { int target; - int lastFrame; float shotTimer; - float weight; - float animDir; - float animTime; - float animMaxTime; - int animIndex; quat rot, rotAbs; - Weapon::Anim anim; + Weapon::Anim anim; + Animation animation; } arms[2]; - int target; - quat rotHead, rotChest; + ActionCommand actionList[MAX_TRIGGER_ACTIONS]; + int lastPickUp; - Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), target(-1) { + Lara(TR::Level *level, int entity, bool home) : Character(level, entity, 100), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos) { initMeshOverrides(); - initAnimOverrides(); for (int i = 0; i < 2; i++) { arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; - arms[i].animTime = 0.0f; arms[i].rot = quat(0, 0, 0, 1); arms[i].rotAbs = quat(0, 0, 0, 1); } - rotHead = rotChest = quat(0, 0, 0, 1); memset(weapons, -1, sizeof(weapons)); - weapons[Weapon::PISTOLS].ammo = 0; - weapons[Weapon::SHOTGUN].ammo = 9000; - weapons[Weapon::MAGNUMS].ammo = 9000; - weapons[Weapon::UZIS ].ammo = 9000; - - wpnSet(Weapon::PISTOLS); + if (!home) { + weapons[Weapon::PISTOLS].ammo = 0; + weapons[Weapon::SHOTGUN].ammo = 9000; + weapons[Weapon::MAGNUMS].ammo = 9000; + weapons[Weapon::UZIS ].ammo = 9000; + wpnSet(Weapon::PISTOLS); + } else + meshSwap(&level->models[TR::MODEL_LARA_SPEC], BODY_UPPER | BODY_LOWER); #ifdef _DEBUG /* // gym @@ -261,33 +257,38 @@ struct Lara : Controller { */ updateEntity(); #endif + chestOffset = animation.getJoints(getMatrix(), 7).getPos(); } void wpnSet(Weapon::Type wType) { wpnCurrent = wType; wpnState = Weapon::IS_FIRING; + + arms[0].animation = arms[1].animation = Animation(level, &level->models[wType == Weapon::SHOTGUN ? TR::MODEL_SHOTGUN : TR::MODEL_PISTOLS]); + wpnSetAnim(arms[0], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); wpnSetAnim(arms[1], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); } void wpnSetAnim(Arm &arm, Weapon::State wState, Weapon::Anim wAnim, float wAnimTime, float wAnimDir, bool playing = true) { + arm.animation.setAnim(wpnGetAnimIndex(wAnim), 0, wAnim == Weapon::Anim::FIRE); + arm.animation.dir = playing ? wAnimDir : 0.0f; + if (arm.anim != wAnim) - arm.lastFrame = 0xFFFF; + arm.animation.frameIndex = 0xFFFF; - arm.anim = wAnim; - arm.animIndex = wpnGetAnimIndex(wAnim); - TR::Animation &anim = level->anims[arm.animIndex]; - - arm.animDir = playing ? wAnimDir : 0.0f; - arm.animMaxTime = (anim.frameEnd - anim.frameStart) / 30.0f; + arm.anim = wAnim; if (wAnimDir > 0.0f) - arm.animTime = wAnimTime; + arm.animation.time = wAnimTime; else if (wAnimDir < 0.0f) - arm.animTime = arm.animMaxTime + wAnimTime; + arm.animation.time = arm.animation.timeMax + wAnimTime; + arm.animation.updateInfo(); wpnSetState(wState); + + LOG("set anim\n"); } int wpnGetDamage() { @@ -333,21 +334,21 @@ struct Lara : Controller { resetMask &= ~(BODY_LEG_L1 | BODY_LEG_R1); // restore original meshes first - meshSwap(level->models[Weapon::EMPTY], resetMask); + meshSwap(&level->models[Weapon::EMPTY], resetMask); // replace some parts - meshSwap(level->models[wpnCurrent], mask); + meshSwap(&level->models[wpnCurrent], mask); // have a shotgun in inventory place it on the back if another weapon is in use if (wpnCurrent != Weapon::SHOTGUN && weapons[Weapon::SHOTGUN].ammo != -1) - meshSwap(level->models[Weapon::SHOTGUN], BODY_CHEST); + meshSwap(&level->models[Weapon::SHOTGUN], BODY_CHEST); // mesh swap to angry Lara's head while firing (from uzis model) if (wState == Weapon::IS_FIRING) - meshSwap(level->models[Weapon::UZIS], BODY_HEAD); + meshSwap(&level->models[Weapon::UZIS], BODY_HEAD); wpnState = wState; } bool emptyHands() { - return arms[0].anim == Weapon::Anim::NONE; + return wpnCurrent == Weapon::EMPTY || arms[0].anim == Weapon::Anim::NONE; } bool canDrawWeapon() { @@ -396,8 +397,8 @@ struct Lara : Controller { if (wpnCurrent != Weapon::SHOTGUN) { wpnSetAnim(arms[0], wpnState, Weapon::Anim::PREPARE, 0.0f, 1.0f); wpnSetAnim(arms[1], wpnState, Weapon::Anim::PREPARE, 0.0f, 1.0f); - } else - wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); + } else + wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); } } @@ -418,26 +419,24 @@ struct Lara : Controller { } int wpnGetAnimIndex(Weapon::Anim wAnim) { - int baseAnim = level->models[wpnCurrent == Weapon::SHOTGUN ? Weapon::SHOTGUN : Weapon::PISTOLS].animation; - if (wpnCurrent == Weapon::SHOTGUN) { switch (wAnim) { case Weapon::Anim::PREPARE : ASSERT(false); break; // rifle has no prepare animation - case Weapon::Anim::UNHOLSTER : return baseAnim + 1; - case Weapon::Anim::HOLSTER : return baseAnim + 3; + case Weapon::Anim::UNHOLSTER : return 1; + case Weapon::Anim::HOLSTER : return 3; case Weapon::Anim::HOLD : - case Weapon::Anim::AIM : return baseAnim; - case Weapon::Anim::FIRE : return baseAnim + 2; + case Weapon::Anim::AIM : return 0; + case Weapon::Anim::FIRE : return 2; default : ; } } else switch (wAnim) { - case Weapon::Anim::PREPARE : return baseAnim + 1; - case Weapon::Anim::UNHOLSTER : return baseAnim + 2; + case Weapon::Anim::PREPARE : return 1; + case Weapon::Anim::UNHOLSTER : return 2; case Weapon::Anim::HOLSTER : ASSERT(false); break; // pistols has no holster animation (it's reversed unholster) case Weapon::Anim::HOLD : - case Weapon::Anim::AIM : return baseAnim; - case Weapon::Anim::FIRE : return baseAnim + 3; + case Weapon::Anim::AIM : return 0; + case Weapon::Anim::FIRE : return 3; default : ; } return 0; @@ -456,21 +455,25 @@ struct Lara : Controller { void wpnFire() { bool armShot[2] = { false, false }; for (int i = 0; i < 2; i++) { - int frameIndex = getFrameIndex(arms[i].animIndex, arms[i].animTime); if (arms[i].anim == Weapon::Anim::FIRE) { - if (frameIndex < arms[i].lastFrame) { - if ((mask & ACTION) && (target == -1 || (target > -1 && arms[i].target > -1))) { - armShot[i] = true; - } else - wpnSetAnim(arms[i], Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, target == -1); - } - // shotgun reload sound - if (wpnCurrent == Weapon::SHOTGUN) { - if (frameIndex >= 10 && arms[i].lastFrame < 10) - playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN); + Animation &anim = arms[i].animation; + //int realFrameIndex = int(arms[i].animation.time * 30.0f / anim->frameRate) % ((anim->frameEnd - anim->frameStart) / anim->frameRate + 1); + if (anim.frameIndex != anim.framePrev) { + if (anim.frameIndex == 0) { //realFrameIndex < arms[i].animation.framePrev) { + if ((input & ACTION) && (target == -1 || (target > -1 && arms[i].target > -1))) { + armShot[i] = true; + } else + wpnSetAnim(arms[i], Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, target == -1); + } + // shotgun reload sound + if (wpnCurrent == Weapon::SHOTGUN) { + if (anim.frameIndex == 10) + playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN); + } } + } - arms[i].lastFrame = frameIndex; + arms[i].animation.framePrev = arms[i].animation.frameIndex; if (wpnCurrent == Weapon::SHOTGUN) break; } @@ -486,37 +489,35 @@ struct Lara : Controller { vec3 nearPos; bool hasShot = false; - for (int i = 0; i < count; i++) { - Arm *arm; + for (int i = 0; i < count; i++) { int armIndex; if (wpnCurrent == Weapon::SHOTGUN) { if (!rightHand) continue; - arm = &arms[0]; armIndex = 0; } else { if (!(i ? leftHand : rightHand)) continue; - arm = &arms[i]; armIndex = i; } + Arm *arm = &arms[armIndex]; arm->shotTimer = 0.0f; hasShot = true; int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); - vec3 p = getJoint(joint, false).getPos(); + vec3 p = animation.getJoints(getMatrix(), joint, false).getPos(); vec3 d = arm->rotAbs * vec3(0, 0, 1); vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f; int room; vec3 hit = trace(getRoomIndex(), p, t, room, false); if (target > -1 && checkHit(target, p, hit, hit)) { - ((Controller*)level->entities[target].controller)->hit(wpnGetDamage()); + ((Character*)level->entities[target].controller)->hit(wpnGetDamage()); hit -= d * 64.0f; - addSprite(level, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_ANIMATED); + Sprite::add(level, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_ANIMATED); } else { hit -= d * 64.0f; - addSprite(level, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_RANDOM); + Sprite::add(level, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_RANDOM); float dist = (hit - p).length(); if (dist < nearDist) { @@ -525,7 +526,7 @@ struct Lara : Controller { } } - Core::lightPos[1 + armIndex] = getJoint(armIndex == 0 ? 10 : 13, false).getPos(); + Core::lightPos[1 + armIndex] = animation.getJoints(getMatrix(), armIndex == 0 ? 10 : 13, false).getPos(); Core::lightColor[1 + armIndex] = FLASH_LIGHT_COLOR; } @@ -551,7 +552,7 @@ struct Lara : Controller { } // apply weapon state changes - if (mask & WEAPON) { + if (input & WEAPON) { if (emptyHands()) wpnDraw(); else @@ -562,18 +563,18 @@ struct Lara : Controller { bool isRifle = wpnCurrent == Weapon::SHOTGUN; for (int i = 0; i < 2; i++) { - if (arms[i].target > -1 || ((mask & ACTION) && target == -1)) { + if (arms[i].target > -1 || ((input & ACTION) && target == -1)) { if (arms[i].anim == Weapon::Anim::HOLD) wpnSetAnim(arms[i], wpnState, Weapon::Anim::AIM, 0.0f, 1.0f); } else if (arms[i].anim == Weapon::Anim::AIM) - arms[i].animDir = -1.0f; + arms[i].animation.dir = -1.0f; if (isRifle) break; } - for (int i = 0; i < 2; i++){ - arms[i].animTime += Core::deltaTime * arms[i].animDir; + for (int i = 0; i < 2; i++) { + arms[i].animation.update(); arms[i].shotTimer += Core::deltaTime; float intensity = clamp((0.1f - arms[i].shotTimer) * 20.0f, 0.0f, 1.0f); @@ -593,23 +594,26 @@ struct Lara : Controller { for (int i = 0; i < 2; i++) { Arm &arm = arms[i]; - if (arm.animDir >= 0.0f && arm.animTime >= arm.animMaxTime) + if (!arm.animation.isEnded) continue; + + if (arm.animation.dir >= 0.0f) switch (arm.anim) { - case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::UNHOLSTER, arm.animTime - arm.animMaxTime, 1.0f); break; + case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::UNHOLSTER, arm.animation.time - arm.animation.timeMax, 1.0f); break; case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; - case Weapon::Anim::AIM : - if (mask & ACTION) - wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animTime - arm.animMaxTime, wpnCurrent == Weapon::UZIS ? 2.0f : 1.0f); + case Weapon::Anim::AIM : + case Weapon::Anim::FIRE : + if (input & ACTION) + wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animation.time - arm.animation.timeMax, wpnCurrent == Weapon::UZIS ? 2.0f : 1.0f); else wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); break; default : ; }; - if (arm.animDir < 0.0f && arm.animTime <= 0.0f) + if (arm.animation.dir < 0.0f) switch (arm.anim) { case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); break; - case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::PREPARE, arm.animTime, -1.0f); break; + case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::PREPARE, arm.animation.time, -1.0f); break; case Weapon::Anim::AIM : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; default : ; }; @@ -618,66 +622,64 @@ struct Lara : Controller { void animateShotgun() { Arm &arm = arms[0]; - if (arm.animDir >= 0.0f) - switch (arm.anim) { - case Weapon::Anim::UNHOLSTER : - if (arm.animTime >= arm.animMaxTime) - wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); - else if (arm.animTime >= arm.animMaxTime * 0.3f) - wpnSetAnim(arm, Weapon::IS_ARMED, arm.anim, arm.animTime, 1.0f); - break; - case Weapon::Anim::HOLSTER : - if (arm.animTime >= arm.animMaxTime) - wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); - else if (arm.animTime >= arm.animMaxTime * 0.7f) - wpnSetAnim(arm, Weapon::IS_HIDDEN, arm.anim, arm.animTime, 1.0f); - break; - case Weapon::Anim::AIM : - if (arm.animTime >= arm.animMaxTime) { - if (mask & ACTION) - wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animTime - arm.animMaxTime, 1.0f); + if (arm.animation.dir >= 0.0f) { + if (arm.animation.isEnded) { + switch (arm.anim) { + case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; + case Weapon::Anim::HOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); break; + case Weapon::Anim::AIM : + case Weapon::Anim::FIRE : + if (input & ACTION) + wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animation.time - arm.animation.timeMax, 1.0f); else - wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); + break; + default : ; + } + } else + if (arm.animation.frameIndex != arm.animation.framePrev) { + float delta = arm.animation.time / arm.animation.timeMax; + switch (arm.anim) { + case Weapon::Anim::UNHOLSTER : if (delta >= 0.3f) wpnSetAnim(arm, Weapon::IS_ARMED, arm.anim, arm.animation.time, 1.0f); break; + case Weapon::Anim::HOLSTER : if (delta >= 0.7f) wpnSetAnim(arm, Weapon::IS_HIDDEN, arm.anim, arm.animation.time, 1.0f); break; + default : ; } - break; - default : ; - }; - - if (arm.animDir < 0.0f && arm.animTime <= 0.0f) - if (arm.anim == Weapon::Anim::AIM) + } + } else + if (arm.animation.isEnded && arm.anim == Weapon::Anim::AIM) wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); } void updateOverrides() { - // update animation overrides - TR::AnimFrame *frameA, *frameB; - float t; - // head & chest - animOverrideMask |= BODY_CHEST | BODY_HEAD; + animation.overrideMask |= BODY_CHEST | BODY_HEAD; + + animation.overrides[ 7] = animation.getJointRot( 7); + animation.overrides[14] = animation.getJointRot(14); + + /* TODO: shotgun full body animation + if (wpnCurrent == Weapon::SHOTGUN) { + animation.frameA = arms[0].animation.frameA; + animation.frameB = arms[0].animation.frameB; + animation.delta = arms[0].animation.delta; + } + */ - getFrames(&frameA, &frameB, t, animIndex, animTime, true); - animOverrides[ 7] = lerpFrames(frameA, frameB, t, 7); - animOverrides[14] = lerpFrames(frameA, frameB, t, 14); - - if (!emptyHands()) { // right arm Arm *arm = &arms[0]; - getFrames(&frameA, &frameB, t, arm->animIndex, arm->animTime); - animOverrides[ 8] = lerpFrames(frameA, frameB, t, 8); - animOverrides[ 9] = lerpFrames(frameA, frameB, t, 9); - animOverrides[10] = lerpFrames(frameA, frameB, t, 10); + animation.overrides[ 8] = arm->animation.getJointRot( 8); + animation.overrides[ 9] = arm->animation.getJointRot( 9); + animation.overrides[10] = arm->animation.getJointRot(10); // left arm if (wpnCurrent != Weapon::SHOTGUN) arm = &arms[1]; - getFrames(&frameA, &frameB, t, arm->animIndex, arm->animTime); - animOverrides[11] = lerpFrames(frameA, frameB, t, 11); - animOverrides[12] = lerpFrames(frameA, frameB, t, 12); - animOverrides[13] = lerpFrames(frameA, frameB, t, 13); + animation.overrides[11] = arm->animation.getJointRot(11); + animation.overrides[12] = arm->animation.getJointRot(12); + animation.overrides[13] = arm->animation.getJointRot(13); - animOverrideMask |= BODY_ARM_R | BODY_ARM_L; + animation.overrideMask |= (BODY_ARM_R | BODY_ARM_L); } else - animOverrideMask &= ~(BODY_ARM_R | BODY_ARM_L); + animation.overrideMask &= ~(BODY_ARM_R | BODY_ARM_L); lookAt(target); @@ -687,7 +689,7 @@ struct Lara : Controller { aimPistols(); } - void lookAt(int target) { + void lookAt(int target) { // TODO: character lookAt float speed = 8.0f * Core::deltaTime; quat rot; @@ -696,14 +698,14 @@ struct Lara : Controller { rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); else rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); - animOverrides[7] = rotChest * animOverrides[7]; + animation.overrides[7] = rotChest * animation.overrides[7]; // head if (aim(target, 14, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot)) rotHead = rotHead.slerp(rot, speed); else rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); - animOverrides[14] = rotHead * animOverrides[14]; + animation.overrides[14] = rotHead * animation.overrides[14]; } void aimShotgun() { @@ -730,12 +732,12 @@ struct Lara : Controller { if (arm.anim == Weapon::Anim::FIRE) t = 1.0f; else if (arm.anim == Weapon::Anim::AIM) - t = arm.animTime / arm.animMaxTime; + t = arm.animation.time / arm.animation.timeMax; else t = 0.0f; arm.rot = arm.rot.slerp(rot, speed); - animOverrides[joint] = animOverrides[joint].slerp(arm.rot * animOverrides[joint], t); + animation.overrides[joint] = animation.overrides[joint].slerp(arm.rot * animation.overrides[joint], t); } } @@ -745,13 +747,13 @@ struct Lara : Controller { return; } - if (!(mask & ACTION)) { + if (!(input & ACTION)) { target = getTarget(); arms[0].target = arms[1].target = target; } else if (target > -1) { TR::Entity &e = level->entities[target]; - vec3 to = vec3(e.x, e.y, e.z); + vec3 to = ((Controller*)e.controller)->pos; vec3 from = pos - vec3(0, 512, 0); arms[0].target = arms[1].target = checkOcclusion(from, to, (to - from).length()) ? target : -1; } @@ -759,20 +761,20 @@ struct Lara : Controller { int getTarget() { vec3 dir = getDir().normal(); - int dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST; + float dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST; int index = -1; for (int i = 0; i < level->entitiesCount; i++) { TR::Entity &e = level->entities[i]; if (!e.flags.active || !e.isEnemy()) continue; - Controller *controller = (Controller*)e.controller; + Character *controller = (Character*)e.controller; if (controller->health <= 0) continue; vec3 p = controller->pos; vec3 v = p - pos; if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees - int d = v.length(); + float d = v.length(); if (d < dist && checkOcclusion(pos - vec3(0, 512, 0), p, d) ) { index = i; dist = d; @@ -801,7 +803,11 @@ struct Lara : Controller { return false; } - bool waterOut(int &outState) { + virtual void cmdEmpty() { + wpnHide(); + } + + bool waterOut() { // TODO: playSound 36 vec3 dst = pos + getDir() * 32.0f; @@ -811,15 +817,9 @@ struct Lara : Controller { int h = int(pos.y - infoDst.floor); - if (h > 0 && h <= 256) { // possibility check - if (!setState(STATE_STOP)) { // can't set water out state - outState = STATE_STOP; - return true; - } - outState = state; - + if (h > 0 && h <= 256 && (state == STATE_SURF_TREAD || animation.setState(STATE_SURF_TREAD)) && animation.setState(STATE_STOP)) { // possibility check alignToWall(); - dst.y -= pos.y - infoDst.floor; + dst.y -= pos.y - infoDst.floor; pos = dst; // set new position getEntity().room = infoCur.roomAbove; @@ -840,38 +840,16 @@ struct Lara : Controller { if (abs(item.x - e.x) > 256 || abs(item.z - e.z) > 256) continue; - switch (item.type) { - case TR::Entity::WEAPON_PISTOLS : - case TR::Entity::WEAPON_SHOTGUN : - case TR::Entity::WEAPON_MAGNUMS : - case TR::Entity::WEAPON_UZIS : - case TR::Entity::AMMO_SHOTGUN : - case TR::Entity::AMMO_MAGNUMS : - case TR::Entity::AMMO_UZIS : - case TR::Entity::MEDIKIT_SMALL : - case TR::Entity::MEDIKIT_BIG : - case TR::Entity::PUZZLE_1 : - case TR::Entity::PUZZLE_2 : - case TR::Entity::PUZZLE_3 : - case TR::Entity::PUZZLE_4 : - case TR::Entity::PICKUP : - case TR::Entity::KEY_1 : - case TR::Entity::KEY_2 : - case TR::Entity::KEY_3 : - case TR::Entity::KEY_4 : - case TR::Entity::ARTIFACT : - lastPickUp = i; - angle.x = 0.0f; - pos.x = item.x; - pos.y = item.y; - pos.z = item.z; - if (stand == STAND_UNDERWATER) { // TODO: lerp to pos/angle - pos = pos - getDir() * 256.0f; - pos.y -= 256; - } - updateEntity(); - return true; - default : ; + if (item.isItem()) { + lastPickUp = i; + angle.x = 0.0f; + pos = ((Controller*)item.controller)->pos; + if (stand == STAND_UNDERWATER) { // TODO: lerp to pos/angle + pos -= getDir() * 256.0f; + pos.y -= 256; + } + updateEntity(); + return true; } } } @@ -882,7 +860,7 @@ struct Lara : Controller { return fabsf(shortAngle(rotation, getEntity().rotation)) < PI * 0.25f; } - void doTrigger() { + void checkTrigger() { if (actionCommand) return; TR::Entity &e = getEntity(); @@ -890,7 +868,7 @@ struct Lara : Controller { level->getFloorInfo(e.room, e.x, e.z, info); if (!info.trigCmdCount) return; // has no trigger - bool isActive = (level->entities[info.trigCmd[0].args].flags.active); + bool isActive = level->entities[info.trigCmd[0].args].flags.active != 0; if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated int actionState = state; @@ -903,14 +881,14 @@ struct Lara : Controller { break; case TR::Level::Trigger::SWITCH : actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; - if ((mask & ACTION) == 0 || state == actionState || !emptyHands()) + if ((input & ACTION) == 0 || 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 || (mask & ACTION) == 0 || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE + if (isActive || (input & ACTION) == 0 || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE return; if (!checkAngle(level->entities[info.trigCmd[0].args].rotation)) return; @@ -925,13 +903,13 @@ struct Lara : Controller { } // try to activate Lara state - if (!setState(actionState)) return; + if (!animation.setState(actionState)) return; if (info.trigger == TR::Level::Trigger::SWITCH || info.trigger == TR::Level::Trigger::KEY) { TR::Entity &p = level->entities[info.trigCmd[0].args]; angle.y = p.rotation; angle.x = 0; - pos = vec3(p.x, p.y, p.z) + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384 : 128); + pos = ((Controller*)p.controller)->pos + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384.0f : 128.0f); velocity = vec3(0.0f); updateEntity(); } @@ -973,7 +951,7 @@ struct Lara : Controller { vec3 offset = chestOffset; if (stand != STAND_UNDERWATER) offset.y -= 256.0f; - if (wpnState != Weapon::IS_HIDDEN) + if (!emptyHands()) offset.y -= 256.0f; return offset; @@ -981,9 +959,9 @@ struct Lara : Controller { virtual Stand getStand() { if (state == STATE_HANG || state == STATE_HANG_LEFT || state == STATE_HANG_RIGHT) { - if (mask & ACTION) + if (input & ACTION) return STAND_HANG; - setAnimation(ANIM_HANG_FALL); + animation.setAnim(ANIM_HANG_FALL); velocity = vec3(0.0f); pos.y += 128.0f; updateEntity(); @@ -1009,7 +987,7 @@ struct Lara : Controller { if (e.y + 8 >= info.floor && (abs(info.slantX) > 2 || abs(info.slantZ) > 2)) { if (stand == STAND_AIR) playSound(TR::SND_LANDING, pos, Sound::Flags::PAN); - pos.y = info.floor; + pos.y = float(info.floor); updateEntity(); if (stand == STAND_GROUND || stand == STAND_AIR) @@ -1023,7 +1001,7 @@ struct Lara : Controller { if (e.y + extra >= info.floor) { if (stand != STAND_GROUND) { - pos.y = info.floor; + pos.y = float(info.floor); updateEntity(); } return STAND_GROUND; @@ -1044,7 +1022,7 @@ struct Lara : Controller { if (state == STATE_REACH && getDir().dot(vec3(velocity.x, 0.0f, velocity.z)) < 0) velocity.x = velocity.z = 0.0f; - if ((state == STATE_REACH || state == STATE_UP_JUMP) && (mask & ACTION) && emptyHands()) { + if ((state == STATE_REACH || state == STATE_UP_JUMP) && (input & ACTION) && emptyHands()) { if (state == STATE_REACH && velocity.y < 0.0f) return state; @@ -1071,34 +1049,30 @@ struct Lara : Controller { if (state == STATE_REACH) { return STATE_HANG; // TODO: ANIM_HANG_WALL / ANIM_HANG_NOWALL } else - return setAnimation(ANIM_HANG, -15); + return animation.setAnim(ANIM_HANG, -15); } } if (state == STATE_FORWARD_JUMP) { if (emptyHands()) { - if (mask & ACTION) return STATE_REACH; - if ((mask & (FORTH | WALK)) == (FORTH | WALK)) return STATE_SWAN_DIVE; + if (input & ACTION) return STATE_REACH; + if ((input & (FORTH | WALK)) == (FORTH | WALK)) return STATE_SWAN_DIVE; } } else if (state != STATE_SWAN_DIVE && state != STATE_REACH && state != STATE_FALL && state != STATE_UP_JUMP && state != STATE_BACK_JUMP && state != STATE_LEFT_JUMP && state != STATE_RIGHT_JUMP) - return setAnimation(ANIM_FALL); + return animation.setAnim(ANIM_FALL); return state; } - float distTo(const TR::Entity &e) { - return (pos - vec3(e.x, e.y, e.z)).length(); - } - Block* getBlock() { - int x = getEntity().x; - int y = getEntity().y; - int z = getEntity().z; + int x = (int)pos.x; + int y = (int)pos.y; + int z = (int)pos.z; for (int i = 0; i < level->entitiesCount; i++) { TR::Entity &e = level->entities[i]; - if ((e.type == TR::Entity::BLOCK_1 || e.type == TR::Entity::BLOCK_2) && e.y == y) { + if (e.isBlock() && e.y == y) { int dx = abs(e.x - x); int dz = abs(e.z - z); if ((dx <= (512 + 128) && dz <= (512 - 128)) || @@ -1119,39 +1093,39 @@ struct Lara : Controller { virtual int getStateGround() { angle.x = 0.0f; - if ((mask & ACTION) && emptyHands() && doPickUp()) + if ((input & ACTION) && emptyHands() && doPickUp()) return STATE_PICK_UP; - if ( (mask & (FORTH | ACTION)) == (FORTH | ACTION) && (animIndex == ANIM_STAND || animIndex == ANIM_STAND_NORMAL) && emptyHands()) { + if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands()) { // TODO: get rid of animation.index vec3 p = pos + getDir() * 64.0f; TR::Level::FloorInfo info; level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.z, info, true); int h = (int)pos.y - info.floor; - int aIndex = animIndex; + int aIndex = animation.index; if (info.floor == info.ceiling || h < 256 + 128) { ; // do nothing } else if (h <= 2 * 256 + 128) { aIndex = ANIM_CLIMB_2; - pos.y = info.floor + 512; + pos.y = info.floor + 512.0f; } else if (h <= 3 * 256 + 128) { aIndex = ANIM_CLIMB_3; - pos.y = info.floor + 768; + pos.y = info.floor + 768.0f; } else if (h <= 7 * 256 + 128) aIndex = ANIM_CLIMB_JUMP; - if (aIndex != animIndex) { + if (aIndex != animation.index) { alignToWall(); - return setAnimation(aIndex); + return animation.setAnim(aIndex); } } - if ( (mask & (FORTH | BACK)) == (FORTH | BACK) && (state == STATE_STOP || state == STATE_RUN) ) - return setAnimation(ANIM_STAND_ROLL_BEGIN); + if ( (input & (FORTH | BACK)) == (FORTH | BACK) && (state == STATE_STOP || state == STATE_RUN) ) + return animation.setAnim(ANIM_STAND_ROLL_BEGIN); // ready to jump if (state == STATE_COMPRESS) { - switch (mask & (RIGHT | LEFT | FORTH | BACK)) { + switch (input & (RIGHT | LEFT | FORTH | BACK)) { case RIGHT : return STATE_RIGHT_JUMP; case LEFT : return STATE_LEFT_JUMP; case FORTH : return STATE_FORWARD_JUMP; @@ -1161,30 +1135,30 @@ struct Lara : Controller { } // jump button is pressed - if (mask & JUMP) { - if ((mask & FORTH) && state == STATE_FORWARD_JUMP) + if (input & JUMP) { + if ((input & FORTH) && state == STATE_FORWARD_JUMP) return STATE_RUN; if (state == STATE_RUN) return STATE_FORWARD_JUMP; - if (animIndex == ANIM_SLIDE_BACK) + if (animation.index == ANIM_SLIDE_BACK) // TODO: animation index? %) return STATE_SLIDE_BACK; return STATE_COMPRESS; } // walk button is pressed - if (mask & WALK) { - if (mask & FORTH) return STATE_WALK; - if (mask & BACK) return STATE_BACK; - if (mask & LEFT) return STATE_STEP_LEFT; - if (mask & RIGHT) return STATE_STEP_RIGHT; + if (input & WALK) { + if (input & FORTH) return STATE_WALK; + if (input & BACK) return STATE_BACK; + if (input & LEFT) return STATE_STEP_LEFT; + if (input & RIGHT) return STATE_STEP_RIGHT; return STATE_STOP; } - if ((mask & ACTION) && emptyHands()) { - if (state == STATE_PUSH_PULL_READY && (mask & (FORTH | BACK))) { - int pushState = (mask & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK; + if ((input & ACTION) && emptyHands()) { + if (state == STATE_PUSH_PULL_READY && (input & (FORTH | BACK))) { + int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK; Block *block = getBlock(); - if (canSetState(pushState) && block->doMove(mask & FORTH)) { + if (animation.canSetState(pushState) && block->doMove((input & FORTH) != 0)) { alignToWall(128.0f); return pushState; } @@ -1195,14 +1169,14 @@ struct Lara : Controller { } // only dpad buttons pressed - if (mask & FORTH) return STATE_RUN; - if (mask & BACK) return STATE_FAST_BACK; - if (mask & (LEFT | RIGHT)) { + if (input & FORTH) return STATE_RUN; + if (input & BACK) return STATE_FAST_BACK; + if (input & (LEFT | RIGHT)) { if (state == STATE_FAST_TURN) return state; - if (mask & LEFT) return (state == STATE_TURN_LEFT && animPrev == animIndex) ? STATE_FAST_TURN : STATE_TURN_LEFT; - if (mask & RIGHT) return (state == STATE_TURN_RIGHT && animPrev == animIndex) ? STATE_FAST_TURN : STATE_TURN_RIGHT; + if (input & LEFT) return (state == STATE_TURN_LEFT && animation.prev == animation.index) ? STATE_FAST_TURN : STATE_TURN_LEFT; + if (input & RIGHT) return (state == STATE_TURN_RIGHT && animation.prev == animation.index) ? STATE_FAST_TURN : STATE_TURN_RIGHT; } return STATE_STOP; @@ -1232,37 +1206,37 @@ struct Lara : Controller { angle.y = dir; updateEntity(); - setAnimation(aIndex); + animation.setAnim(aIndex); } } virtual int getStateSlide() { - if (mask & JUMP) + if (input & JUMP) return state == STATE_SLIDE ? STATE_FORWARD_JUMP : STATE_BACK_JUMP; return state; } virtual int getStateHang() { - if (mask & LEFT) return STATE_HANG_LEFT; - if (mask & RIGHT) return STATE_HANG_RIGHT; - if (mask & FORTH) { + if (input & LEFT) return STATE_HANG_LEFT; + if (input & RIGHT) return STATE_HANG_RIGHT; + if (input & FORTH) { // possibility check TR::Level::FloorInfo info; vec3 p = pos + getDir() * 128.0f; level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.z, info, true); if (info.floor - info.ceiling >= 768) - return (mask & WALK) ? STATE_HANDSTAND : STATE_HANG_UP; + return (input & WALK) ? STATE_HANDSTAND : STATE_HANG_UP; } return STATE_HANG; } virtual int getStateUnderwater() { - if (mask == ACTION && doPickUp()) + if (input == ACTION && doPickUp()) return STATE_PICK_UP; if (state == STATE_FORWARD_JUMP || state == STATE_UP_JUMP || state == STATE_BACK_JUMP || state == STATE_LEFT_JUMP || state == STATE_RIGHT_JUMP || state == STATE_FALL || state == STATE_REACH) { - addSprite(level, TR::Entity::Type::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z); - return setAnimation(ANIM_WATER_FALL); + Sprite::add(level, TR::Entity::Type::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z); + return animation.setAnim(ANIM_WATER_FALL); // TODO: wronng animation } if (state == STATE_SWAN_DIVE) { @@ -1270,7 +1244,7 @@ struct Lara : Controller { return STATE_DIVE; } - if (mask & JUMP) return STATE_SWIM; + if (input & JUMP) return STATE_SWIM; return (state == STATE_SWIM || velocity.y > GLIDE_SPEED) ? STATE_GLIDE : STATE_TREAD; } @@ -1281,23 +1255,23 @@ struct Lara : Controller { if (state == STATE_WATER_OUT) return state; if (state != STATE_SURF_TREAD && state != STATE_SURF_LEFT && state != STATE_SURF_RIGHT && state != STATE_SURF_SWIM && state != STATE_SURF_BACK && state != STATE_STOP) - return setAnimation(ANIM_TO_ONWATER); + return animation.setAnim(ANIM_TO_ONWATER); - if (mask & FORTH) { - if (mask & JUMP) { + if (input & FORTH) { + if (input & JUMP) { angle.x = -PI * 0.25f; - return setAnimation(ANIM_TO_UNDERWATER); + return animation.setAnim(ANIM_TO_UNDERWATER); } - if ((mask & ACTION) && waterOut(state)) return state; + if ((input & ACTION) && waterOut()) return state; return STATE_SURF_SWIM; } - if (mask & BACK) return STATE_SURF_BACK; - if (mask & WALK) { - if (mask & LEFT) return STATE_SURF_LEFT; - if (mask & RIGHT) return STATE_SURF_RIGHT; + if (input & BACK) return STATE_SURF_BACK; + if (input & WALK) { + if (input & LEFT) return STATE_SURF_LEFT; + if (input & RIGHT) return STATE_SURF_RIGHT; } return STATE_SURF_TREAD; } @@ -1307,7 +1281,7 @@ struct Lara : Controller { } virtual int getStateDefault() { - if (state == STATE_DIVE || (state == STATE_RUN && (mask & JUMP)) ) return state; + if (state == STATE_DIVE || (state == STATE_RUN && (input & JUMP)) ) return state; switch (stand) { case STAND_GROUND : return STATE_STOP; case STAND_HANG : return STATE_HANG; @@ -1318,78 +1292,42 @@ struct Lara : Controller { return STATE_FALL; } - virtual int getInputMask() { - mask = 0; + virtual int getInput() { + input = 0; int &p = Input::joy.POV; - if (Input::down[ikW] || Input::down[ikUp] || p == 8 || p == 1 || p == 2) mask |= FORTH; - if (Input::down[ikD] || Input::down[ikRight] || p == 2 || p == 3 || p == 4) mask |= RIGHT; - if (Input::down[ikS] || Input::down[ikDown] || p == 4 || p == 5 || p == 6) mask |= BACK; - if (Input::down[ikA] || Input::down[ikLeft] || p == 6 || p == 7 || p == 8) mask |= LEFT; - if (Input::down[ikJoyB]) mask = FORTH | BACK; // roll - if (Input::down[ikJoyRT] || Input::down[ikX]) mask = WALK | RIGHT; // step right - if (Input::down[ikJoyLT] || Input::down[ikZ]) mask = WALK | LEFT; // step left - if (Input::down[ikSpace] || Input::down[ikJoyX]) mask |= JUMP; - if (Input::down[ikShift] || Input::down[ikJoyLB]) mask |= WALK; - if (Input::down[ikE] || Input::down[ikCtrl] || Input::down[ikJoyA]) mask |= ACTION; - if (Input::down[ikQ] || Input::down[ikAlt] || Input::down[ikJoyY]) mask |= WEAPON; - if (health <= 0) mask = DEATH; - return mask; + 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[ikS] || Input::down[ikDown] || p == 4 || p == 5 || p == 6) input |= BACK; + if (Input::down[ikA] || Input::down[ikLeft] || p == 6 || p == 7 || p == 8) input |= LEFT; + if (Input::down[ikJoyB]) input = FORTH | BACK; // roll + if (Input::down[ikJoyRT] || Input::down[ikX]) input = WALK | RIGHT; // step right + if (Input::down[ikJoyLT] || Input::down[ikZ]) input = WALK | LEFT; // step left + if (Input::down[ikSpace] || Input::down[ikJoyX]) input |= JUMP; + 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 updateState() { - doTrigger(); - - TR::Animation *anim = &level->anims[animIndex]; - - int fCount = anim->frameEnd - anim->frameStart; - int fIndex = int(animTime * 30.0f); - - float rot = 0.0f; - -#ifdef _DEBUG - // show state transitions for current animation - static bool lState = false; - if (Input::down[ikEnter]) { - if (!lState) { - lState = true; - - static int snd_id = 81; - playSound(snd_id, pos, 0); - - LOG("sound: %d\n", snd_id++); - /* - LOG("state: %d\n", anim->state); - for (int i = 0; i < anim->scCount; i++) { - auto &sc = level->states[anim->scOffset + i]; - LOG("-> %d : ", (int)sc.state); - for (int j = 0; j < sc.rangesCount; j++) { - TR::AnimRange &range = level->ranges[sc.rangesOffset + j]; - LOG("%d ", range.nextAnimation); - } - LOG("\n"); - } - */ + virtual void doCustomCommand(int curFrame, int prevFrame) { + if (state == STATE_PICK_UP) { + if (!level->entities[lastPickUp].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 } - - } else - lState = false; -#endif - // calculate turn tilt - if (state == STATE_RUN && (mask & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (mask & LEFT)) || (tilt > 0.0f && (mask & RIGHT)))) { - if (mask & LEFT) tilt -= TILT_SPEED * Core::deltaTime; - if (mask & RIGHT) tilt += TILT_SPEED * Core::deltaTime; - } else - if (fabsf(tilt) > 0.01f) - tilt -= sign(tilt) * TILT_SPEED * 4.0f * Core::deltaTime; - else - tilt = 0.0f; - tilt = clamp(tilt, -TILT_MAX, TILT_MAX); - - angle.z = tilt; + } + } - + virtual void updateAnimation(bool commands) { + Controller::updateAnimation(commands); + updateWeapon(); + } + + virtual void updateVelocity() { // get turning angle - float w = (mask & LEFT) ? -1.0f : ((mask & RIGHT) ? 1.0f : 0.0f); + float w = (input & LEFT) ? -1.0f : ((input & RIGHT) ? 1.0f : 0.0f); if (state == STATE_SWIM || state == STATE_GLIDE) w *= TURN_WATER_FAST; @@ -1411,12 +1349,12 @@ struct Lara : Controller { if (w != 0.0f) { w *= Core::deltaTime; angle.y += w; - velocity = velocity.rotateY(-w); + velocity = velocity.rotateY(-w); // in-air velocity rotation control } // pitch (underwater only) - if (stand == STAND_UNDERWATER && (mask & (FORTH | BACK)) ) { - angle.x += ((mask & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * 0.5f * Core::deltaTime; + if (stand == STAND_UNDERWATER && (input & (FORTH | BACK)) ) { + angle.x += ((input & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * 0.5f * Core::deltaTime; angle.x = clamp(angle.x, -PI * 0.5f, PI * 0.5f); } @@ -1443,26 +1381,6 @@ struct Lara : Controller { angleExt += PI * 0.5f; break; } - } - - - virtual void doCustomCommand(int curFrame, int prevFrame) { - if (state == STATE_PICK_UP) { - if (!level->entities[lastPickUp].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 - } - } - } - - virtual void updateAnimation(bool commands) { - Controller::updateAnimation(commands); - updateWeapon(); - } - - virtual void updateVelocity() { - TR::Animation *anim = &level->anims[animIndex]; switch (stand) { case STAND_AIR : @@ -1482,8 +1400,8 @@ struct Lara : Controller { speed = 15.0f; break; default : - speed = anim->speed + anim->accel * (animTime * 30.0f); - if (animIndex == ANIM_STAND_ROLL_END) + speed = animation.getSpeed(); + if (animation.index == ANIM_STAND_ROLL_END) speed = -speed; } @@ -1509,7 +1427,7 @@ struct Lara : Controller { } case STAND_UNDERWATER : { float speed = 0.0f; - if (animIndex == ANIM_TO_UNDERWATER) + if (animation.index == ANIM_TO_UNDERWATER) speed = 15.0f; if (state == STATE_SWIM) speed = 35.0f; @@ -1524,32 +1442,16 @@ struct Lara : Controller { } } - virtual void checkRoom() { - TR::Level::FloorInfo info; - TR::Entity &e = getEntity(); - level->getFloorInfo(e.room, e.x, e.z, info); + virtual void updatePosition() { // TODO: sphere / bbox collision + updateTilt(state == STATE_RUN, TILT_SPEED, TILT_MAX); - if (info.roomNext != 0xFF) - e.room = info.roomNext; + if (velocity.length() >= 0.001f) + move(); - if (info.roomBelow != 0xFF && e.y > info.floor) - e.room = info.roomBelow; - - if (info.roomAbove != 0xFF && e.y <= info.ceiling) { - if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { - stand = STAND_ONWATER; - velocity.y = 0; - pos.y = info.ceiling; - updateEntity(); - } else - if (stand != STAND_ONWATER) - e.room = info.roomAbove; - } + checkTrigger(); } - virtual void move() { // TODO: sphere / bbox collision - if (velocity.length() < 0.001f) return; - + void move() { vec3 offset = velocity * Core::deltaTime * 30.0f; vec3 p = pos; @@ -1560,11 +1462,12 @@ struct Lara : Controller { level->getFloorInfo(e.room, (int)pos.x, (int)pos.z, info, true); // get frame to get height - TR::Animation *anim = &level->anims[animIndex]; + TR::Animation *anim = animation; bool canPassGap = (info.floor - info.ceiling) >= (stand == STAND_GROUND ? 768 : 512); float f = info.floor - pos.y; float c = pos.y - info.ceiling; + /* Box eBox = Box(pos - vec3(128.0f, 0.0f, 128.0f), pos + vec3(128.0, getHeight(), 128.0f)); // getBoundingBox(); // check static meshes in the room @@ -1601,12 +1504,9 @@ struct Lara : Controller { */ if (canPassGap) switch (stand) { - case STAND_AIR : { - int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2; - TR::AnimFrame *frame = (TR::AnimFrame*)&level->frameData[((anim->frameOffset + (int(animTime * 30.0f / anim->frameRate) * fSize)) >> 1)]; - - f = info.floor - (pos.y + frame->box.maxY); - c = (pos.y + frame->box.minY) - info.ceiling; + case STAND_AIR : { + f = info.floor - (pos.y + animation.frameA->box.maxY); + c = (pos.y + animation.frameA->box.minY) - info.ceiling; canPassGap = f >= -256; if (canPassGap && c < 0) { if (c > -256) { // position correction for ceiling step (less than 256) @@ -1619,7 +1519,7 @@ struct Lara : Controller { } break; } - case STAND_GROUND : { + case STAND_GROUND : { if (state == STATE_WALK || state == STATE_BACK) canPassGap = fabsf(f) <= (256.0f + 128.0f); else @@ -1647,7 +1547,7 @@ struct Lara : Controller { default : ; } - bool isLeftFoot = (anim->frameEnd - anim->frameStart) / 2 > int(animTime * 30.0f); + bool isLeftFoot = animation.framesCount / 2 > animation.frameIndex; if (!canPassGap) { pos = p; // TODO: use smart ejection @@ -1661,7 +1561,7 @@ struct Lara : Controller { } if (velocity.x != 0.0f || velocity.z != 0.0f) { - setAnimation(ANIM_SMASH_JUMP); + animation.setAnim(ANIM_SMASH_JUMP); velocity.x = -velocity.x * 0.5f; velocity.z = -velocity.z * 0.5f; if (offset.y < 0.0f) @@ -1674,11 +1574,11 @@ struct Lara : Controller { case STAND_GROUND : case STAND_HANG : if (f <= -(256 * 3 - 128) && state == STATE_RUN) - setAnimation(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT); + animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT); else if (stand == STAND_HANG) - setAnimation(ANIM_HANG, -21); + animation.setAnim(ANIM_HANG, -21); else if (state != STATE_ROLL_1 && state != STATE_ROLL_2) - setAnimation(ANIM_STAND); + animation.setAnim(ANIM_STAND); velocity.x = velocity.z = 0.0f; break; default : ;// no smash animation @@ -1689,21 +1589,21 @@ struct Lara : Controller { if (h >= 256 && state == STATE_FAST_BACK) { stand = STAND_AIR; - setAnimation(ANIM_FALL); + animation.setAnim(ANIM_FALL); } else if (h >= 128 && (state == STATE_WALK || state == STATE_BACK)) { // descend - if (state == STATE_WALK) setAnimation(isLeftFoot ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT); - if (state == STATE_BACK) setAnimation(isLeftFoot ? ANIM_BACK_DESCEND_LEFT : ANIM_BACK_DESCEND_RIGHT); - pos.y = info.floor; + if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT); + if (state == STATE_BACK) animation.setAnim(isLeftFoot ? ANIM_BACK_DESCEND_LEFT : ANIM_BACK_DESCEND_RIGHT); + pos.y = float(info.floor); } else if (h > -1.0f) { pos.y = min((float)info.floor, pos.y += DESCENT_SPEED * Core::deltaTime); } else if (h > -128) { - pos.y = info.floor; + pos.y = float(info.floor); } else if (h >= -(256 + 128) && (state == STATE_RUN || state == STATE_WALK)) { // ascend - if (state == STATE_RUN) setAnimation(isLeftFoot ? ANIM_RUN_ASCEND_LEFT : ANIM_RUN_ASCEND_RIGHT); - if (state == STATE_WALK) setAnimation(isLeftFoot ? ANIM_WALK_ASCEND_LEFT : ANIM_WALK_ASCEND_RIGHT); - pos.y = info.floor; + if (state == STATE_RUN) animation.setAnim(isLeftFoot ? ANIM_RUN_ASCEND_LEFT : ANIM_RUN_ASCEND_RIGHT); + if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_ASCEND_LEFT : ANIM_WALK_ASCEND_RIGHT); + pos.y = float(info.floor); } else - pos.y = info.floor; + pos.y = float(info.floor); } updateEntity(); @@ -1724,11 +1624,12 @@ struct Lara : Controller { virtual void render(Frustum *frustum, MeshBuilder *mesh) { Controller::render(frustum, mesh); - chestOffset = joints[7].getPos(); + chestOffset = animation.getJoints(getMatrix(), 7).getPos(); // TODO: move to update func if (wpnCurrent != Weapon::SHOTGUN) { - renderMuzzleFlash(mesh, joints[10], vec3(-10, -50, 150), arms[0].shotTimer); - renderMuzzleFlash(mesh, joints[13], vec3( 10, -50, 150), arms[1].shotTimer); + mat4 matrix = getMatrix(); + renderMuzzleFlash(mesh, animation.getJoints(matrix, 10), vec3(-10, -50, 150), arms[0].shotTimer); + renderMuzzleFlash(mesh, animation.getJoints(matrix, 13), vec3( 10, -50, 150), arms[1].shotTimer); } } }; diff --git a/src/level.h b/src/level.h index bebcd31..a813abf 100644 --- a/src/level.h +++ b/src/level.h @@ -30,7 +30,7 @@ struct Level { float time; - Level(Stream &stream, bool demo) : level{stream, demo}, time(0.0f), lara(NULL) { + Level(const char *name, bool demo, bool home) : level(name, demo), time(0.0f), lara(NULL) { #ifdef _DEBUG Debug::init(); #endif @@ -44,8 +44,10 @@ struct Level { TR::Entity &entity = level.entities[i]; switch (entity.type) { case TR::Entity::LARA : + entity.controller = (lara = new Lara(&level, i, home)); + break; case TR::Entity::LARA_CUT : - entity.controller = (lara = new Lara(&level, i)); + entity.controller = (lara = new Lara(&level, i, false)); break; case TR::Entity::ENEMY_WOLF : entity.controller = new Wolf(&level, i); @@ -71,7 +73,7 @@ struct Level { case TR::Entity::ENEMY_CENTAUR : case TR::Entity::ENEMY_MUMMY : case TR::Entity::ENEMY_LARSON : - entity.controller = new Enemy(&level, i); + entity.controller = new Enemy(&level, i, 100); break; case TR::Entity::DOOR_1 : case TR::Entity::DOOR_2 : @@ -108,7 +110,7 @@ struct Level { if (entity.modelIndex > 0) entity.controller = new Controller(&level, i); else - entity.controller = new SpriteController(&level, i, 0); + entity.controller = new Sprite(&level, i, 0); } } @@ -232,7 +234,7 @@ struct Level { PROFILE_MARKER("ROOM"); TR::Room &room = level.rooms[roomIndex]; - vec3 offset = vec3(room.info.x, 0.0f, room.info.z); + vec3 offset = vec3(float(room.info.x), 0.0f, float(room.info.z)); Shader *sh = setRoomShader(room, 1.0f); @@ -251,9 +253,11 @@ struct Level { TR::StaticMesh *sMesh = level.getMeshByID(rMesh.meshID); ASSERT(sMesh != NULL); + if (!mesh->meshMap[sMesh->mesh]) continue; + // check visibility Box box; - vec3 offset = vec3(rMesh.x, rMesh.y, rMesh.z); + vec3 offset = vec3((float)rMesh.x, (float)rMesh.y, (float)rMesh.z); sMesh->getBox(false, rMesh.rotation, box); if (!camera->frustum->isVisible(offset + box.min, offset + box.max)) continue; @@ -343,7 +347,7 @@ struct Level { int j = room; for (int i = 0; i < level.rooms[j].lightsCount; i++) { TR::Room::Light &light = level.rooms[j].lights[i]; - float d = (pos - vec3(light.x, light.y, light.z)).length2(); + float d = (pos - vec3(float(light.x), float(light.y), float(light.z))).length2(); if (idx == -1 || d < dist) { idx = i; dist = d; @@ -360,7 +364,7 @@ struct Level { if (idx > -1) { TR::Room::Light &light = level.rooms[room].lights[idx]; float c = level.rooms[room].lights[idx].intensity / 8191.0f; - Core::lightPos[0] = vec3(light.x, light.y, light.z); + Core::lightPos[0] = vec3(float(light.x), float(light.y), float(light.z)); Core::lightColor[0] = vec4(c, c, c, (float)light.attenuation * (float)light.attenuation); } else { Core::lightPos[0] = vec3(0); @@ -389,7 +393,7 @@ struct Level { setRoomShader(room, c)->bind(); Core::active.shader->setParam(uColor, Core::color); // get light parameters for entity - getLight(vec3(entity.x, entity.y, entity.z), entity.room); + getLight(((Controller*)entity.controller)->pos, entity.room); } if (entity.modelIndex < 0) { // sprite @@ -482,7 +486,7 @@ struct Level { // Debug::Level::portals(level); // Debug::Level::meshes(level); // Debug::Level::entities(level); - Debug::Level::info(level, lara->getEntity(), (int)lara->state, lara->animIndex, int(lara->animTime * 30.0f)); + Debug::Level::info(level, lara->getEntity(), lara->animation); Debug::end(); #endif } diff --git a/src/libs/minimp3/minimp3.cpp b/src/libs/minimp3/minimp3.cpp index d76e499..cae9609 100644 --- a/src/libs/minimp3/minimp3.cpp +++ b/src/libs/minimp3/minimp3.cpp @@ -2504,16 +2504,16 @@ int mp3_decode_init() { for(i=0; i<512*16; i++){ int exponent= (i>>4); double f= libc_pow(i&15, 4.0 / 3.0) * libc_pow(2, (exponent-400)*0.25 + FRAC_BITS + 5); - expval_table[exponent][i&15]= f; + expval_table[exponent][i&15]= uint32_t(f); if((i&15)==1) - exp_table[exponent]= f; + exp_table[exponent]= uint32_t(f); } for(i=0;i<7;i++) { float f; int v; if (i != 6) { - f = tan((double)i * M_PI / 12.0); + f = float(tan((double)i * M_PI / 12.0)); v = FIXR(f / (1.0 + f)); } else { v = FIXR(1.0); @@ -2522,7 +2522,7 @@ int mp3_decode_init() { is_table[1][6 - i] = v; } for(i=7;i<16;i++) - is_table[0][i] = is_table[1][i] = 0.0; + is_table[0][i] = is_table[1][i] = 0; for(i=0;i<16;i++) { double f; @@ -2540,7 +2540,7 @@ int mp3_decode_init() { for(i=0;i<8;i++) { float ci, cs, ca; ci = ci_table[i]; - cs = 1.0 / sqrt(1.0 + ci * ci); + cs = float(1.0 / sqrt(1.0 + ci * ci)); ca = cs * ci; csa_table[i][0] = FIXHR(cs/4); csa_table[i][1] = FIXHR(ca/4); diff --git a/src/platform/web/index.html b/src/platform/web/index.html index d8cee04..6cca02b 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -3,8 +3,8 @@