diff --git a/src/controller.h b/src/controller.h index 0834013..4723dde 100644 --- a/src/controller.h +++ b/src/controller.h @@ -31,7 +31,7 @@ struct Controller { DEATH = 1 << 9 }; float animTime; - int animIndex; + int animIndex, animPrev; int animPrevFrame; vec3 pos, velocity; @@ -41,9 +41,13 @@ struct Controller { 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; @@ -61,8 +65,11 @@ struct Controller { 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() { @@ -77,6 +84,10 @@ struct Controller { meshes = mCount ? new int[mCount] : NULL; for (int i = 0; i < mCount; i++) meshes[i] = model.mStart + i; + } + + void initAnimOverrides() { + TR::Model &model = getModel(); animOverrides = new quat[model.mCount]; animOverrideMask = 0; @@ -92,6 +103,11 @@ struct Controller { } } + 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); @@ -175,6 +191,36 @@ struct Controller { 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); + vec3 delta = (m.inverse() * t).normal(); + + float angleY = clampAngle(atan2(delta.x, delta.z)); + float angleX = clampAngle(asinf(delta.y)); + + if (angleX > angleRange.x && angleX <= angleRange.y && + angleY > angleRange.z && angleY <= angleRange.w) { + + quat ax(vec3(1, 0, 0), -angleX); + quat ay(vec3(0, 1, 0), angleY); + + rot = ay * ax; + if (rotAbs) + *rotAbs = m.getRot() * rot; + return true; + } + } + + if (rotAbs) + *rotAbs = rotYXZ(angle); + return false; + } + void updateEntity() { TR::Entity &e = getEntity(); e.x = int(pos.x); @@ -214,6 +260,7 @@ struct Controller { } 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; @@ -757,6 +804,10 @@ struct Controller { renderShadow(mesh, vec3(entity.x, info.floor - 16.0f, entity.z), (bmax + bmin) * 0.5f, (bmax - bmin) * 0.8f, entity.rotation); } } + + quat lerpFrames(TR::AnimFrame *frameA, TR::AnimFrame *frameB, float t, int index) { + return lerpAngle(frameA->getAngle(index), frameB->getAngle(index), t); + } }; diff --git a/src/enemy.h b/src/enemy.h index 9968fa2..ad29d4d 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -4,9 +4,15 @@ #include "controller.h" struct Enemy : Controller { - int health; + int target; + quat rotHead, rotChest; + int baseAnim; - Enemy(TR::Level *level, int entity) : Controller(level, entity), health(100) {} + 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; @@ -16,11 +22,128 @@ struct Enemy : Controller { health -= damage; }; + virtual bool activate(ActionCommand *cmd) { + Controller::activate(cmd); + + getEntity().flags.active = true; + activateNext(); + + for (int i = 0; i < level->entitiesCount; i++) + if (level->entities[i].type == TR::Entity::LARA) { + target = i; + break; + } + + return true; + } + + virtual void updateVelocity() { + TR::Animation *anim = &level->anims[animIndex]; + float speed = anim->speed + anim->accel * (animTime * 30.0f); + velocity = getDir() * speed; + } + + virtual void move() { + 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); + if (pos.y - info.floor > 1024) { + pos = p; + return; + } + if (stand == STAND_GROUND) + pos.y = 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) + 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); + } + + void lookAt(int target, int chest, int head) { + float speed = 8.0f * Core::deltaTime; + quat rot; + + if (chest > -1) { + if (aim(target, chest, vec4(-PI * 0.4f, PI * 0.4f, -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]; + } + + if (head > -1) { + if (aim(target, head, 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[head] = rotHead * animOverrides[head]; + } + } + + virtual int getInputMask() { + if (target > -1) { + vec3 v = (((Controller*)level->entities[target].controller)->pos - pos).normal(); + float d = atan2(v.x, v.z) - angle.y; + if (fabsf(d) > 0.01f) + return d < 0 ? LEFT : RIGHT; + } + return 0; + } }; +#define WOLF_TURN_FAST PI +#define WOLF_TURN_SLOW (PI / 3.0f) + struct Wolf : Enemy { + enum { + ANIM_DEATH = 20, + ANIM_DEATH_RUN = 21, + ANIM_DEATH_JUMP = 22, + }; + enum { STATE_STOP = 1, STATE_WALK = 2, @@ -31,9 +154,15 @@ struct Wolf : Enemy { STATE_SLEEP = 8, STATE_GROWL = 9, STATE_10 = 10, // WTF? + STATE_DEATH = 11, STATE_ATTACK = 12, }; + enum { + JOINT_CHEST = 2, + JOINT_HEAD = 3 + }; + Wolf(TR::Level *level, int entity) : Enemy(level, entity) {} virtual int getStateGround() { @@ -45,13 +174,67 @@ struct Wolf : Enemy { // STATE_JUMP -> STATE_RUN // STATE_GROWL -> STATE_STOP, STATE_RUN, STATE_STALKING, STATE_HOWL, STATE_ATTACK // STATE_BITING -> NULL + if (state == STATE_DEATH) return state; + + if (health <= 0) { + switch (state) { + case STATE_RUN : return setAnimation(baseAnim + ANIM_DEATH_RUN); + case STATE_JUMP : return setAnimation(baseAnim + ANIM_DEATH_JUMP); + default : return setAnimation(baseAnim + ANIM_DEATH); + } + } + + TR::Entity &e = getEntity(); + if (!e.flags.active) + return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP; + + switch (state) { + case STATE_SLEEP : return STATE_STOP; + case STATE_STOP : return STATE_HOWL; + case STATE_GROWL : return STATE_STALKING; + case STATE_STALKING : if (health < 70) return STATE_RUN; break; + } + + if (target > -1 && (state == STATE_STALKING || state == STATE_RUN)) { + vec3 v = ((Controller*)level->entities[target].controller)->pos - pos; + float d = v.length(); + if (state == STATE_STALKING && d < 512) + return STATE_ATTACK; + if (state == STATE_RUN && d > 512 && d < 1024) + return STATE_JUMP; + } + + if (state == STATE_JUMP) + return STATE_RUN; -// if (state == STATE_SLEEP) return STATE_STOP; -// if (state == STATE_STOP) return STATE_GROWL; -// if (state == STATE_GROWL) return STATE_ATTACK; -// if (state == STATE_RUN) return STATE_10; return state; } + + virtual void updateState() { + Enemy::updateState(); + float w = 0.0f; + if (state == STATE_RUN || state == STATE_STALKING) { + w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW; + if (mask & LEFT) w = -w; + + if (w != 0.0f) { + w *= Core::deltaTime; + angle.y += w; + velocity = velocity.rotateY(-w); + } + } else + velocity = vec3(0.0f); + } + + virtual void move() { + if (state == STATE_DEATH) { + animOverrideMask = 0; + return; + } + Enemy::move(); + setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD); + lookAt(target, JOINT_CHEST, JOINT_HEAD); + } }; diff --git a/src/lara.h b/src/lara.h index e080642..73f8bfd 100644 --- a/src/lara.h +++ b/src/lara.h @@ -6,13 +6,12 @@ #include "controller.h" #include "trigger.h" -#define FAST_TURN_TIME 1.0f - #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 TURN_TILT PI / 18.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 GLIDE_SPEED 50.0f @@ -195,11 +194,9 @@ struct Lara : Controller { int target; quat rotHead, rotChest; - int health; - float turnTime; - - Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), target(-1), health(100), turnTime(0.0f) { + Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), target(-1) { initMeshOverrides(); + initAnimOverrides(); for (int i = 0; i < 2; i++) { arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; arms[i].animTime = 0.0f; @@ -296,7 +293,7 @@ struct Lara : Controller { int wpnGetDamage() { switch (wpnCurrent) { case Weapon::PISTOLS : return 10; - case Weapon::SHOTGUN : return 5; + case Weapon::SHOTGUN : return 15; case Weapon::MAGNUMS : return 20; case Weapon::UZIS : return 5; default : return 0; @@ -742,35 +739,6 @@ struct Lara : Controller { } } - bool aim(int target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) { - if (target > -1) { - TR::Entity &e = level->entities[target]; - vec3 t(e.x, e.y, e.z); - - mat4 m = getJoint(joint); - vec3 delta = (m.inverse() * t).normal(); - - float angleY = clampAngle(atan2(delta.x, delta.z)); - float angleX = clampAngle(asinf(delta.y)); - - if (angleX > angleRange.x && angleX <= angleRange.y && - angleY > angleRange.z && angleY <= angleRange.w) { - - quat ax(vec3(1, 0, 0), -angleX); - quat ay(vec3(0, 1, 0), angleY); - - rot = ay * ax; - if (rotAbs) - *rotAbs = m.getRot() * rot; - return true; - } - } - - if (rotAbs) - *rotAbs = rotYXZ(angle); - return false; - } - void updateTargets() { if (emptyHands() || !wpnReady()) { target = arms[0].target = arms[1].target = -1; @@ -796,11 +764,13 @@ struct Lara : Controller { int index = -1; for (int i = 0; i < level->entitiesCount; i++) { TR::Entity &e = level->entities[i]; - if (!e.flags.rendered || !e.isEnemy()) continue; + if (!e.flags.active || !e.isEnemy()) continue; + Controller *controller = (Controller*)e.controller; + if (controller->health <= 0) continue; - vec3 p = vec3(e.x, e.y, e.z); + vec3 p = controller->pos; vec3 v = p - pos; - if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sigth -60..+60 degrees + if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees int d = v.length(); if (d < dist && checkOcclusion(pos - vec3(0, 512, 0), p, d) ) { @@ -1176,18 +1146,6 @@ struct Lara : Controller { } } - /* - // hit test - if (animIndex != ANIM_HIT_FRONT) - for (int i = 0; i < level->entitiesCount; i++) { - TR::Entity &e = level->entities[i]; - if (e.id != ENTITY_ENEMY_WOLF) continue; - vec3 v = vec3(e.x, e.y, e.z) - pos; - if (v.length2() < 128 * 128) { - return setAnimation(ANIM_HIT_FRONT); - } - } - */ if ( (mask & (FORTH | BACK)) == (FORTH | BACK) && (state == STATE_STOP || state == STATE_RUN) ) return setAnimation(ANIM_STAND_ROLL_BEGIN); @@ -1239,8 +1197,14 @@ struct Lara : Controller { // only dpad buttons pressed if (mask & FORTH) return STATE_RUN; if (mask & BACK) return STATE_FAST_BACK; - if (mask & LEFT) return turnTime < FAST_TURN_TIME ? STATE_TURN_LEFT : STATE_FAST_TURN; - if (mask & RIGHT) return turnTime < FAST_TURN_TIME ? STATE_TURN_RIGHT : STATE_FAST_TURN; + if (mask & (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; + } + return STATE_STOP; } @@ -1411,44 +1375,49 @@ struct Lara : Controller { lState = false; #endif // calculate turn tilt - if (state == STATE_RUN && (mask & (LEFT | RIGHT))) { - if (mask & LEFT) angle.z -= Core::deltaTime * TURN_TILT; - if (mask & RIGHT) angle.z += Core::deltaTime * TURN_TILT; - angle.z = clamp(angle.z, -TURN_TILT, 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 - angle.z -= angle.z * min(Core::deltaTime * 8.0f, 1.0f); - - if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_FAST_TURN) - turnTime += Core::deltaTime; - else - turnTime = 0.0f; + 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; + // get turning angle - float w = 0.0f; - + float w = (mask & LEFT) ? -1.0f : ((mask & RIGHT) ? 1.0f : 0.0f); + if (state == STATE_SWIM || state == STATE_GLIDE) - w = TURN_WATER_FAST; + w *= TURN_WATER_FAST; else if (state == STATE_TREAD || state == STATE_SURF_TREAD || state == STATE_SURF_SWIM || state == STATE_SURF_BACK) - w = TURN_WATER_SLOW; - else if (state == STATE_RUN || state == STATE_FAST_TURN) - w = TURN_FAST; // TODO: modulate angular speed by turnTime factor + w *= TURN_WATER_SLOW; + else if (state == STATE_RUN) + w *= sign(w) != sign(tilt) ? 0.0f : w * TURN_FAST * tilt / TILT_MAX; + else if (state == STATE_FAST_TURN) + w *= TURN_FAST; else if (state == STATE_FAST_BACK) - w = TURN_FAST_BACK; + w *= TURN_FAST_BACK; else if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK) - w = TURN_NORMAL; + w *= TURN_NORMAL; else if (state == STATE_FORWARD_JUMP || state == STATE_BACK) - w = TURN_SLOW; + w *= TURN_SLOW; + else + w = 0.0f; if (w != 0.0f) { w *= Core::deltaTime; - // yaw - if (mask & LEFT) { angle.y -= w; velocity = velocity.rotateY(+w); } - if (mask & RIGHT) { angle.y += w; velocity = velocity.rotateY(-w); } - // pitch (underwater only) - if (stand == STAND_UNDERWATER && (mask & (FORTH | BACK)) ) { - angle.x += ((mask & FORTH) ? -w : w) * 0.5f; - angle.x = clamp(angle.x, -PI * 0.5f, PI * 0.5f); - } + angle.y += w; + velocity = velocity.rotateY(-w); + } + + // pitch (underwater only) + if (stand == STAND_UNDERWATER && (mask & (FORTH | BACK)) ) { + angle.x += ((mask & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * 0.5f * Core::deltaTime; + angle.x = clamp(angle.x, -PI * 0.5f, PI * 0.5f); } // get animation direction @@ -1492,22 +1461,12 @@ struct Lara : Controller { updateWeapon(); } - quat lerpFrames(TR::AnimFrame *frameA, TR::AnimFrame *frameB, float t, int index) { - return lerpAngle(frameA->getAngle(index), frameB->getAngle(index), t); - } - virtual void updateVelocity() { - // calculate moving speed - float dt = Core::deltaTime * 30.0f; - TR::Animation *anim = &level->anims[animIndex]; - //if (anim->speed != 0.0f || anim->accel != 0.0f) - // LOG("speed: %f accel: %f\n", (float)anim->speed, (float)anim->accel); - switch (stand) { case STAND_AIR : - velocity.y += GRAVITY * dt; + velocity.y += GRAVITY * 30.0f * Core::deltaTime; break; case STAND_GROUND : case STAND_SLIDE : diff --git a/src/sound.h b/src/sound.h index 04b4b3c..fd37bd5 100644 --- a/src/sound.h +++ b/src/sound.h @@ -20,7 +20,7 @@ #endif #define SND_CHANNELS_MAX 32 -#define SND_FADEOFF_DIST (1024.0f * 5.0f) +#define SND_FADEOFF_DIST (1024.0f * 10.0f) namespace Sound {