2016-11-20 23:27:42 +03:00
|
|
|
#ifndef H_CHARACTER
|
|
|
|
#define H_CHARACTER
|
|
|
|
|
|
|
|
#include "controller.h"
|
2017-11-07 05:54:11 +03:00
|
|
|
#include "collision.h"
|
2018-03-04 13:29:30 +03:00
|
|
|
#include "sprite.h"
|
2016-11-20 23:27:42 +03:00
|
|
|
|
|
|
|
struct Character : Controller {
|
2017-06-22 04:03:08 +03:00
|
|
|
float health;
|
2016-11-20 23:27:42 +03:00
|
|
|
float tilt;
|
|
|
|
quat rotHead, rotChest;
|
|
|
|
|
|
|
|
enum Stand {
|
2018-02-05 01:38:38 +03:00
|
|
|
STAND_AIR,
|
|
|
|
STAND_GROUND,
|
|
|
|
STAND_SLIDE,
|
|
|
|
STAND_HANG,
|
|
|
|
STAND_UNDERWATER,
|
2018-11-18 07:38:05 +03:00
|
|
|
STAND_ONWATER,
|
|
|
|
STAND_WADE
|
2016-11-20 23:27:42 +03:00
|
|
|
} stand;
|
2018-02-05 01:38:38 +03:00
|
|
|
|
2016-11-23 01:38:00 +03:00
|
|
|
int input, lastInput;
|
|
|
|
|
|
|
|
enum Key {
|
|
|
|
LEFT = 1 << 1,
|
|
|
|
RIGHT = 1 << 2,
|
|
|
|
FORTH = 1 << 3,
|
|
|
|
BACK = 1 << 4,
|
|
|
|
JUMP = 1 << 5,
|
|
|
|
WALK = 1 << 6,
|
|
|
|
ACTION = 1 << 7,
|
|
|
|
WEAPON = 1 << 8,
|
2018-02-21 02:53:48 +03:00
|
|
|
LOOK = 1 << 9,
|
|
|
|
DEATH = 1 << 10
|
2016-11-23 01:38:00 +03:00
|
|
|
};
|
2016-11-20 23:27:42 +03:00
|
|
|
|
2017-08-31 01:38:00 +03:00
|
|
|
Controller *viewTarget;
|
|
|
|
int jointChest;
|
|
|
|
int jointHead;
|
2017-09-01 03:50:12 +03:00
|
|
|
vec4 rangeChest;
|
|
|
|
vec4 rangeHead;
|
2017-08-31 01:38:00 +03:00
|
|
|
|
2016-11-20 23:27:42 +03:00
|
|
|
vec3 velocity;
|
|
|
|
float angleExt;
|
2017-02-15 04:11:26 +03:00
|
|
|
float speed;
|
2017-08-29 05:13:29 +03:00
|
|
|
int stepHeight;
|
|
|
|
int dropHeight;
|
2017-02-15 04:11:26 +03:00
|
|
|
|
2017-05-01 00:16:53 +03:00
|
|
|
int zone;
|
|
|
|
int box;
|
|
|
|
|
2018-10-13 07:18:08 +03:00
|
|
|
bool burn;
|
2017-05-01 00:16:53 +03:00
|
|
|
bool flying;
|
2018-03-04 13:29:30 +03:00
|
|
|
bool fullChestRotation;
|
2017-05-01 00:16:53 +03:00
|
|
|
|
2017-02-15 04:11:26 +03:00
|
|
|
Collision collision;
|
2016-11-20 23:27:42 +03:00
|
|
|
|
2017-09-01 08:02:08 +03:00
|
|
|
Character(IGame *game, int entity, float health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), viewTarget(NULL), jointChest(-1), jointHead(-1), velocity(0.0f), angleExt(0.0f), speed(0.0f) {
|
2017-08-29 05:13:29 +03:00
|
|
|
stepHeight = 256;
|
|
|
|
dropHeight = -256;
|
|
|
|
|
2017-09-01 03:50:12 +03:00
|
|
|
rangeChest = vec4(-0.80f, 0.80f, -0.75f, 0.75f) * PI;
|
|
|
|
rangeHead = vec4(-0.25f, 0.25f, -0.50f, 0.50f) * PI;
|
2016-11-20 23:27:42 +03:00
|
|
|
animation.initOverrides();
|
2017-11-20 10:36:30 +03:00
|
|
|
|
2016-11-20 23:27:42 +03:00
|
|
|
rotHead = rotChest = quat(0, 0, 0, 1);
|
2017-05-01 00:16:53 +03:00
|
|
|
|
2018-10-13 07:18:08 +03:00
|
|
|
burn = false;
|
2017-05-01 00:16:53 +03:00
|
|
|
flying = getEntity().type == TR::Entity::ENEMY_BAT;
|
2018-03-04 13:29:30 +03:00
|
|
|
fullChestRotation = false;
|
2017-05-01 00:16:53 +03:00
|
|
|
updateZone();
|
|
|
|
}
|
|
|
|
|
2018-06-29 03:59:33 +03:00
|
|
|
bool isActiveTarget() {
|
|
|
|
return flags.state == TR::Entity::asActive && !flags.invisible && health > 0.0f;
|
|
|
|
}
|
|
|
|
|
2017-11-25 18:45:43 +03:00
|
|
|
virtual int getRoomIndex() const {
|
|
|
|
int index = Controller::getRoomIndex();
|
2017-11-27 11:46:20 +03:00
|
|
|
|
|
|
|
if (level->isCutsceneLevel())
|
|
|
|
return index;
|
2017-11-25 18:45:43 +03:00
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
2018-03-13 10:14:52 +03:00
|
|
|
virtual TR::Room& getLightRoom() {
|
|
|
|
if (stand == STAND_ONWATER) {
|
|
|
|
int16 rIndex = getRoomIndex();
|
|
|
|
TR::Room::Sector *sector = level->getSector(rIndex, pos);
|
|
|
|
if (sector && sector->roomAbove != TR::NO_ROOM)
|
|
|
|
return level->rooms[sector->roomAbove];
|
|
|
|
}
|
|
|
|
return Controller::getLightRoom();
|
|
|
|
}
|
|
|
|
|
2017-05-10 01:39:28 +03:00
|
|
|
bool updateZone() {
|
2017-11-27 11:46:20 +03:00
|
|
|
if (level->isCutsceneLevel())
|
|
|
|
return false;
|
|
|
|
|
2017-05-01 00:16:53 +03:00
|
|
|
int dx, dz;
|
2017-05-10 01:39:28 +03:00
|
|
|
TR::Room::Sector &s = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz);
|
2018-06-22 00:37:05 +03:00
|
|
|
if (s.boxIndex == TR::NO_BOX)
|
2017-05-10 01:39:28 +03:00
|
|
|
return false;
|
|
|
|
box = s.boxIndex;
|
|
|
|
zone = getZones()[box];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16* getZones() {
|
2017-11-16 07:58:28 +03:00
|
|
|
TR::Zone &zones = level->zones[level->state.flags.flipped];
|
|
|
|
return (flying || stand == STAND_UNDERWATER || stand == STAND_ONWATER) ? zones.fly : (stepHeight == 256 ? zones.ground1 : zones.ground2);
|
2016-11-20 23:27:42 +03:00
|
|
|
}
|
|
|
|
|
2017-02-15 04:11:26 +03:00
|
|
|
void rotateY(float delta) {
|
2018-06-15 04:03:19 +03:00
|
|
|
angle.y = clampAngle(angle.y + delta);
|
2017-02-15 04:11:26 +03:00
|
|
|
velocity = velocity.rotateY(-delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rotateX(float delta) {
|
|
|
|
angle.x = clamp(angle.x + delta, -PI * 0.49f, PI * 0.49f);
|
|
|
|
}
|
|
|
|
|
2017-09-11 03:53:11 +03:00
|
|
|
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
|
2018-10-16 06:32:47 +03:00
|
|
|
if (getEntity().isEnemy() && health > 0.0f && health <= damage)
|
2018-10-22 00:42:06 +03:00
|
|
|
saveStats.kills++;
|
2017-06-22 04:03:08 +03:00
|
|
|
health = max(0.0f, health - damage);
|
2017-08-24 05:30:38 +03:00
|
|
|
}
|
2016-11-20 23:27:42 +03:00
|
|
|
|
2016-11-23 01:38:00 +03:00
|
|
|
virtual void updateVelocity() {}
|
2016-11-20 23:27:42 +03:00
|
|
|
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; }
|
2018-11-18 07:38:05 +03:00
|
|
|
virtual int getStateWade() { return state; }
|
2016-11-20 23:27:42 +03:00
|
|
|
virtual int getStateDeath() { return state; }
|
|
|
|
virtual int getStateDefault() { return state; }
|
2016-11-23 01:38:00 +03:00
|
|
|
virtual int getInput() { return health <= 0 ? DEATH : 0; }
|
2018-02-05 01:38:38 +03:00
|
|
|
virtual bool useHeadAnimation() { return false; }
|
2016-11-20 23:27:42 +03:00
|
|
|
|
2018-02-05 01:38:38 +03:00
|
|
|
int getNextState() {
|
2018-09-30 03:07:07 +03:00
|
|
|
if (input & DEATH) {
|
|
|
|
int deathState = getStateDeath();
|
2018-10-13 07:18:08 +03:00
|
|
|
if (state == deathState || animation.canSetState(deathState)) {
|
|
|
|
if (stand != STAND_AIR)
|
|
|
|
velocity = vec3(0.0f);
|
2018-09-30 03:07:07 +03:00
|
|
|
return deathState;
|
|
|
|
}
|
|
|
|
}
|
2018-02-05 01:38:38 +03:00
|
|
|
|
|
|
|
switch (stand) {
|
|
|
|
case STAND_AIR : return getStateAir();
|
|
|
|
case STAND_GROUND : return getStateGround();
|
|
|
|
case STAND_SLIDE : return getStateSlide();
|
|
|
|
case STAND_HANG : return getStateHang();
|
|
|
|
case STAND_UNDERWATER : return getStateUnderwater();
|
|
|
|
case STAND_ONWATER : return getStateOnwater();
|
2018-11-18 07:38:05 +03:00
|
|
|
case STAND_WADE : return getStateWade();
|
2018-02-05 01:38:38 +03:00
|
|
|
}
|
|
|
|
return animation.state;
|
|
|
|
}
|
2016-11-20 23:27:42 +03:00
|
|
|
|
2018-02-05 01:38:38 +03:00
|
|
|
virtual void updateState() {
|
2018-10-13 07:18:08 +03:00
|
|
|
if (stand == STAND_UNDERWATER || stand == STAND_ONWATER)
|
|
|
|
burn = false;
|
|
|
|
|
2018-02-05 01:38:38 +03:00
|
|
|
int state = getNextState();
|
2016-11-20 23:27:42 +03:00
|
|
|
// try to set new state
|
|
|
|
if (!animation.setState(state))
|
|
|
|
animation.setState(getStateDefault());
|
|
|
|
}
|
|
|
|
|
2017-05-10 01:39:28 +03:00
|
|
|
virtual void updateTilt(float value, float tiltSpeed, float tiltMax) {
|
|
|
|
value = clamp(value, -tiltMax, +tiltMax);
|
|
|
|
decrease(value - angle.z, angle.z, tiltSpeed);
|
|
|
|
}
|
|
|
|
|
2016-11-20 23:27:42 +03:00
|
|
|
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)))) {
|
2017-02-15 04:11:26 +03:00
|
|
|
if (input & LEFT) tilt -= tiltSpeed;
|
|
|
|
if (input & RIGHT) tilt += tiltSpeed;
|
|
|
|
tilt = clamp(tilt, -tiltMax, +tiltMax);
|
|
|
|
} else {
|
|
|
|
if (tilt > 0.0f) tilt = max(0.0f, tilt - tiltSpeed);
|
|
|
|
if (tilt < 0.0f) tilt = min(0.0f, tilt + tiltSpeed);
|
|
|
|
}
|
2016-11-20 23:27:42 +03:00
|
|
|
angle.z = tilt;
|
|
|
|
}
|
|
|
|
|
2016-11-23 01:38:00 +03:00
|
|
|
bool isPressed(Key key) {
|
|
|
|
return (input & key) && !(lastInput & key);
|
|
|
|
}
|
|
|
|
|
2016-11-20 23:27:42 +03:00
|
|
|
virtual void update() {
|
2018-11-18 07:38:05 +03:00
|
|
|
updateRoom();
|
|
|
|
|
2017-04-23 00:12:30 +03:00
|
|
|
vec3 p = pos;
|
2016-11-23 01:38:00 +03:00
|
|
|
lastInput = input;
|
2016-11-20 23:27:42 +03:00
|
|
|
input = getInput();
|
|
|
|
stand = getStand();
|
|
|
|
updateState();
|
|
|
|
Controller::update();
|
2017-05-10 01:39:28 +03:00
|
|
|
|
2017-11-07 05:54:11 +03:00
|
|
|
if (flags.active) {
|
2017-05-10 01:39:28 +03:00
|
|
|
updateVelocity();
|
|
|
|
updatePosition();
|
|
|
|
if (p != pos) {
|
|
|
|
if (updateZone())
|
|
|
|
updateLights();
|
|
|
|
else
|
|
|
|
pos = p;
|
|
|
|
}
|
2017-05-01 00:16:53 +03:00
|
|
|
}
|
2016-11-20 23:27:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2017-02-08 03:40:38 +03:00
|
|
|
|
2018-02-05 01:38:38 +03:00
|
|
|
vec3 getViewPoint() { // TOOD: remove this
|
2018-01-24 09:54:11 +03:00
|
|
|
return getJoint(jointChest).pos;
|
2017-08-31 01:38:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void lookAt(Controller *target) {
|
|
|
|
if (health <= 0.0f)
|
|
|
|
target = NULL;
|
|
|
|
|
2018-07-23 05:56:45 +03:00
|
|
|
vec3 t(0.0f);
|
|
|
|
if (target) {
|
|
|
|
Box box = target->getBoundingBox();
|
|
|
|
t = (box.min + box.max) * 0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
lookAtPos(target ? &t : NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void lookAtPos(const vec3 *t = NULL) {
|
2017-08-31 01:38:00 +03:00
|
|
|
float speed = 8.0f * Core::deltaTime;
|
|
|
|
quat rot;
|
|
|
|
|
|
|
|
if (jointChest > -1) {
|
2018-07-23 05:56:45 +03:00
|
|
|
if (t && aim(*t, jointChest, rangeChest, rot)) {
|
2018-03-04 13:29:30 +03:00
|
|
|
if (fullChestRotation)
|
|
|
|
rotChest = rotChest.slerp(rot, speed);
|
|
|
|
else
|
|
|
|
rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed);
|
|
|
|
} else
|
2017-08-31 01:38:00 +03:00
|
|
|
rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed);
|
|
|
|
animation.overrides[jointChest] = rotChest * animation.overrides[jointChest];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (jointHead > -1) {
|
2018-07-23 05:56:45 +03:00
|
|
|
if (t && aim(*t, jointHead, rangeHead, rot))
|
2017-08-31 01:38:00 +03:00
|
|
|
rotHead = rotHead.slerp(rot, speed);
|
|
|
|
else
|
|
|
|
rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed);
|
|
|
|
animation.overrides[jointHead] = rotHead * animation.overrides[jointHead];
|
|
|
|
}
|
|
|
|
}
|
2018-03-04 13:29:30 +03:00
|
|
|
|
|
|
|
void addSparks(uint32 mask) {
|
|
|
|
Sphere spheres[MAX_SPHERES];
|
2018-04-15 02:20:09 +03:00
|
|
|
int count = getSpheres(spheres);
|
2018-03-04 13:29:30 +03:00
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
if (mask & (1 << i)) {
|
|
|
|
vec3 sprPos = spheres[i].center + (vec3(randf(), randf(), randf()) * 2.0f - 1.0f) * spheres[i].radius;
|
|
|
|
game->addEntity(TR::Entity::SPARKLES, getRoomIndex(), sprPos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addBlood(const vec3 &sprPos, const vec3 &sprVel) {
|
|
|
|
Sprite *sprite = (Sprite*)game->addEntity(TR::Entity::BLOOD, getRoomIndex(), sprPos, 0);
|
|
|
|
if (sprite)
|
|
|
|
sprite->velocity = sprVel;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addBlood(float radius, float height, const vec3 &sprVel) {
|
|
|
|
vec3 p = pos + vec3((randf() * 2.0f - 1.0f) * radius, -randf() * height, (randf() * 2.0f - 1.0f) * radius);
|
|
|
|
addBlood(p, sprVel);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addBloodSpikes() {
|
|
|
|
float ang = randf() * PI * 2.0f;
|
|
|
|
addBlood(64.0f, 512.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * 20.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addBloodBlade() {
|
|
|
|
float ang = angle.y + (randf() - 0.5f) * 30.0f * DEG2RAD;
|
|
|
|
addBlood(64.0f, 744.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * speed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addBloodSlam(Controller *trapSlam) {
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
|
|
addBloodSpikes();
|
|
|
|
}
|
|
|
|
|
2016-11-20 23:27:42 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|