1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-13 16:44:50 +02:00

#22 mutant eggs; #14 explode mutants, add hit sounds, fix blob shadows overlay

This commit is contained in:
XProger
2017-09-28 04:15:17 +03:00
parent 3acc52134e
commit 95fb64b2cf
8 changed files with 381 additions and 33 deletions

View File

@@ -31,7 +31,6 @@ struct Camera : ICamera {
int room;
float timer;
float shake;
Basis prevBasis;
@@ -43,7 +42,7 @@ struct Camera : ICamera {
bool firstPerson;
bool isVR;
Camera(IGame *game, Character *owner) : ICamera(), game(game), level(game->getLevel()), owner(owner), frustum(new Frustum()), timer(-1.0f), shake(0.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL), isVR(false) {
Camera(IGame *game, Character *owner) : ICamera(), game(game), level(game->getLevel()), owner(owner), frustum(new Frustum()), timer(-1.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL), isVR(false) {
changeView(false);
if (owner->getEntity().type != TR::Entity::LARA && level->cameraFrames) {
state = STATE_CUTSCENE;

View File

@@ -17,10 +17,11 @@
struct Controller;
struct ICamera {
vec4 *reflectPlane;
vec3 pos;
vec4 *reflectPlane;
vec3 pos;
float shake;
ICamera() : reflectPlane(NULL) {}
ICamera() : reflectPlane(NULL), pos(0.0f), shake(0.0f) {}
virtual void setup(bool calcMatrices) {}
virtual int getRoomIndex() const { return TR::NO_ROOM; }
@@ -52,6 +53,9 @@ struct IGame {
virtual void checkTrigger(Controller *controller, bool heavy) {}
virtual int addSprite(TR::Entity::Type type, int room, int x, int y, int z, int frame = -1, bool empty = false) { return -1; }
virtual int addEnemy(TR::Entity::Type type, int room, const vec3 &pos, float angle) { return -1; }
virtual bool invUse(TR::Entity::Type type) { return false; }
virtual void invAdd(TR::Entity::Type type, int count = 1) {}
virtual int* invCount(TR::Entity::Type type) { return NULL; }
@@ -65,7 +69,7 @@ struct IGame {
struct Controller {
static Controller *first;
Controller *next;
enum ActiveState { asNone, asActive, asInactive } activeState;
enum ActiveState { asNone, asActive, asInactive };
IGame *game;
TR::Level *level;
@@ -94,7 +98,17 @@ struct Controller {
uint32 mask;
} *layers;
Controller(IGame *game, int entity) : next(NULL), activeState(asNone), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(NULL) {
uint32 explodeMask;
struct ExplodePart {
Basis basis;
vec3 velocity;
int roomIndex;
} *explodeParts;
ActiveState activeState;
bool invertAim;
Controller(IGame *game, int entity) : next(NULL), activeState(asNone), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(0), explodeMask(0), explodeParts(0), invertAim(false) {
TR::Entity &e = getEntity();
pos = vec3(float(e.x), float(e.y), float(e.z));
angle = vec3(0.0f, e.rotation, 0.0f);
@@ -125,6 +139,7 @@ struct Controller {
virtual ~Controller() {
delete[] joints;
delete[] layers;
delete[] explodeParts;
deactivate(true);
}
@@ -195,6 +210,7 @@ struct Controller {
}
void initMeshOverrides() {
if (layers) return;
layers = new MeshLayer[MAX_LAYERS];
memset(layers, 0, sizeof(MeshLayer) * MAX_LAYERS);
layers[0].model = getEntity().modelIndex - 1;
@@ -223,6 +239,8 @@ struct Controller {
Basis b = animation.getJoints(Basis(getMatrix()), joint);
vec3 delta = (b.inverse() * t).normal();
if (invertAim)
delta = -delta;
float angleY = clampAngle(atan2f(delta.x, delta.z));
float angleX = clampAngle(asinf(delta.y));
@@ -526,8 +544,37 @@ struct Controller {
animation.framePrev = animation.frameIndex;
}
void updateExplosion() {
if (!explodeMask) return;
TR::Model *model = getModel();
for (int i = 0; i < model->mCount; i++)
if (explodeMask & (1 << i)) {
ExplodePart &part = explodeParts[i];
part.velocity.y += GRAVITY * Core::deltaTime;
quat q = quat(vec3(1, 0, 0), PI * Core::deltaTime) * quat(vec3(0, 0, 1), PI * 2.0f * Core::deltaTime);
part.basis = Basis(part.basis.rot * q, part.basis.pos + explodeParts[i].velocity * (Core::deltaTime * 30.0f));
vec3 p = part.basis.pos;
TR::Room::Sector *s = level->getSector(part.roomIndex, int(p.x), int(p.y), int(p.z));
if (s && s->floor * 256.0f < p.y) {
explodeMask &= ~(1 << i);
game->addSprite(TR::Entity::EXPLOSION, part.roomIndex, int(p.x), int(p.y), int(p.z));
game->playSound(TR::SND_EXPLOSION, pos, 0); // Sound::Flags::PAN ?
}
}
if (!explodeMask) {
delete[] explodeParts;
explodeParts = NULL;
}
}
virtual void update() {
updateAnimation(true);
updateExplosion();
}
void updateLights(bool lerp = true) {
@@ -587,6 +634,34 @@ struct Controller {
return matrix;
}
void explode(int32 mask) {
TR::Model *model = getModel();
if (!layers) initMeshOverrides();
mask &= layers[0].mask;
layers[0].mask &= ~mask;
explodeParts = new ExplodePart[model->mCount];
explodeMask = 0;
animation.getJoints(getMatrix(), -1, true, joints);
int roomIndex = getRoomIndex();
for (int i = 0; i < model->mCount; i++) {
if (!(mask & (1 << i)))
continue;
explodeMask |= (1 << i);
float angle = randf() * PI * 2.0f;
float speed = randf() * 256.0f;
ExplodePart &part = explodeParts[i];
part.basis = joints[i];
part.basis.w = 1.0f;
part.velocity = vec3(cosf(angle), (randf() - 0.5f) * 0.25f, sinf(angle)) * speed;
part.roomIndex = roomIndex;
}
}
void renderShadow(MeshBuilder *mesh) {
TR::Entity &entity = getEntity();
@@ -621,16 +696,18 @@ struct Controller {
Core::active.shader->setParam(uMaterial, vec4(vec3(0.5f * (1.0f - alpha)), alpha));
Core::active.shader->setParam(uAmbient, vec3(0.0f));
Core::setDepthWrite(false);
Core::setBlending(bmMultiply);
mesh->renderShadowBlob();
Core::setBlending(bmNone);
Core::setDepthWrite(true);
}
virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { // TODO: animation.calcJoints
mat4 matrix = getMatrix();
Box box = animation.getBoundingBox(vec3(0, 0, 0), 0);
if (frustum && !frustum->isVisible(matrix, box.min, box.max))
if (!explodeMask && frustum && !frustum->isVisible(matrix, box.min, box.max))
return;
TR::Entity &entity = getEntity();
@@ -643,16 +720,23 @@ struct Controller {
animation.getJoints(matrix, -1, true, joints);
if (layers) {
int mask = 0;
uint32 mask = 0;
for (int i = MAX_LAYERS - 1; i >= 0; i--) {
int vmask = layers[i].mask & ~mask;
uint32 vmask = (layers[i].mask & ~mask) | (!i ? explodeMask : 0);
if (!vmask) continue;
mask |= layers[i].mask;
// set meshes visibility
for (int j = 0; j < model->mCount; j++)
joints[j].w = (vmask & (1 << j)) ? 1.0f : -1.0f; // AHAHA
if (explodeMask) {
TR::Model *model = getModel();
for (int i = 0; i < model->mCount; i++)
if (explodeMask & (1 << i))
joints[i] = explodeParts[i].basis;
}
// if (entity.type == TR::Entity::LARA && Core::eye != 0)
// joints[14].w = -1.0f;
// render

View File

@@ -57,6 +57,7 @@ struct Enemy : Character {
float length; // dist from center to head (jaws)
float aggression;
int radius;
int hitSound;
Character *target;
Path *path;
@@ -85,7 +86,13 @@ struct Enemy : Character {
}
virtual void updateVelocity() {
velocity = getDir() * animation.getSpeed();
if (stand == STAND_AIR && (!flying || health <= 0.0f))
velocity.y += GRAVITY * Core::deltaTime;
else
velocity = getDir() * animation.getSpeed();
if (health <= 0.0f)
velocity.x = velocity.y = 0.0f;
}
bool checkPoint(int x, int z) {
@@ -126,7 +133,7 @@ struct Enemy : Character {
if (px != nx) pos.x = float(nx);
if (pz != nz) pos.z = float(nz);
}
}
virtual void updatePosition() {
if (!getEntity().flags.active) return;
@@ -137,7 +144,11 @@ struct Enemy : Character {
clipByBox(pos);
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info);
if (stand == STAND_AIR && !flying && info.floor < pos.y) {
stand = STAND_GROUND;
pos.y = info.floor;
}
if (info.boxIndex != 0xFFFF && zone == getZones()[info.boxIndex] && !level->boxes[info.boxIndex].overlap.block) {
switch (stand) {
@@ -221,6 +232,8 @@ struct Enemy : Character {
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
Character::hit(damage, enemy, hitType);
wound = true;
if (hitSound > -1)
game->playSound(hitSound, pos, Sound::PAN);
};
void bite(const vec3 &pos, float damage) {
@@ -486,6 +499,7 @@ struct Wolf : Enemy {
dropHeight = -1024;
jointChest = 2;
jointHead = 3;
hitSound = TR::SND_HIT_WOLF;
nextState = STATE_NONE;
animation.time = animation.timeMax;
updateAnimation(false);
@@ -605,6 +619,18 @@ struct Wolf : Enemy {
}
};
struct Lion : Enemy {
Lion(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) {
hitSound = TR::SND_HIT_LION;
}
};
struct Rat : Enemy {
Rat(IGame *game, int entity) : Enemy(game, entity, 6, 341, 375.0f, 0.25f) {
hitSound = TR::SND_HIT_RAT;
}
};
#define BEAR_DIST_EAT 768
#define BEAR_DIST_HOWL 2048
#define BEAR_DIST_BITE 1024
@@ -641,6 +667,7 @@ struct Bear : Enemy {
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 500.0f, 0.5f) {
jointChest = 13;
jointHead = 14;
hitSound = TR::SND_HIT_BEAR;
nextState = STATE_NONE;
}
@@ -814,13 +841,6 @@ struct Bat : Enemy {
return state == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
}
virtual void updateVelocity() {
if (state != STATE_DEATH)
Enemy::updateVelocity();
else
velocity = vec3(0.0f, velocity.y + GRAVITY * Core::deltaTime, 0.0f);
}
virtual void updatePosition() {
float angleY = 0.0f;
if (state == STATE_FLY || state == STATE_ATTACK)
@@ -1065,4 +1085,121 @@ struct Raptor : Enemy {
}
};
struct Mutant : Enemy {
Mutant(IGame *game, int entity) : Enemy(game, entity, 50, 341, 150.0f, 1.0f) {
TR::Entity &e = getEntity();
if (e.type != TR::Entity::ENEMY_MUTANT_1) {
initMeshOverrides();
layers[0].mask = 0xffe07fff;
aggression = 0.25f;
}
jointChest = 1;
jointHead = 2;
nextState = 0;
}
virtual void update() {
bool exploded = explodeMask != 0;
if (health <= 0.0f && !exploded) {
game->playSound(TR::SND_MUTANT_DEATH, pos, 0);
explode(0xffffffff);
}
Enemy::update();
if (exploded && !explodeMask) {
deactivate(true);
getEntity().flags.invisible = true;
}
}
virtual int getStateGround() {
if (!think(true))
return state;
return state;
}
virtual void updatePosition() {
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target);
}
};
struct GiantMutant : Enemy {
enum {
STATE_STOP = 1,
STATE_BORN = 8,
STATE_FALL = 9,
};
GiantMutant(IGame *game, int entity) : Enemy(game, entity, 500, 341, 375.0f, 1.0f) {
hitSound = TR::SND_HIT_MUTANT;
stand = STAND_AIR;
jointChest = -1;
jointHead = 3;
rangeHead = vec4(-0.5f, 0.5f, -0.5f, 0.5f) * PI;
invertAim = true;
}
virtual int getStateGround() {
if (!think(true))
return state;
return state;
}
void update() {
bool exploded = explodeMask != 0;
if (health <= 0.0f && !exploded) {
game->playSound(TR::SND_MUTANT_DEATH, pos, 0);
explode(0xffffffff);
}
switch (state) {
case STATE_BORN : animation.setState(STATE_FALL); break;
case STATE_FALL :
if (stand == STAND_GROUND) {
animation.setState(STATE_STOP);
game->getCamera()->shake = 5.0f;
}
break;
}
Enemy::update();
setOverrides(true, jointChest, jointHead);
lookAt(target);
if (exploded && !explodeMask) {
game->checkTrigger(this, true);
deactivate(true);
getEntity().flags.invisible = true;
}
}
};
struct Centaur : Enemy {
Centaur(IGame *game, int entity) : Enemy(game, entity, 20, 341, 400.0f, 0.5f) {
jointChest = 10;
jointHead = 17;
}
virtual int getStateGround() {
if (!think(true))
return state;
return state;
}
virtual void updatePosition() {
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target);
}
};
#endif

View File

@@ -254,6 +254,9 @@ namespace TR {
SND_SHOTGUN_RELOAD = 9,
SND_RICOCHET = 10,
SND_HIT_BEAR = 16,
SND_HIT_WOLF = 20,
SND_SCREAM = 30,
SND_HIT = 31,
@@ -267,6 +270,12 @@ namespace TR {
SND_FLOOD = 81,
SND_HIT_LION = 85,
SND_HIT_RAT = 95,
SND_EXPLOSION = 104,
SND_INV_SPIN = 108,
SND_INV_HOME = 109,
SND_INV_CONTROLS = 110,
@@ -278,9 +287,15 @@ namespace TR {
SND_HEALTH = 116,
SND_STAIRS2SLOPE = 119,
SND_HIT_SKATEBOY = 132,
SND_HIT_MUTANT = 142,
SND_DART = 151,
SND_EXPLOSION = 170,
SND_TNT = 170,
SND_MUTANT_DEATH = 171,
SND_SECRET = 173,
};
@@ -730,7 +745,10 @@ namespace TR {
&& type != ENEMY_REX
&& type != ENEMY_RAPTOR
&& type != ENEMY_MUTANT_1
&& type != ENEMY_MUTANT_2
&& type != ENEMY_MUTANT_3
&& type != ENEMY_CENTAUR
&& type != ENEMY_GIANT_MUTANT
&& type != ENEMY_MUMMY
&& type != ENEMY_NATLA)
opaque = true;
@@ -2016,6 +2034,9 @@ namespace TR {
}
int16 getModelIndex(Entity::Type type) const {
if (type == TR::Entity::ENEMY_MUTANT_2 || type == TR::Entity::ENEMY_MUTANT_3)
type = TR::Entity::ENEMY_MUTANT_1; // hardcoded mutant models remapping
for (int i = 0; i < modelsCount; i++)
if (type == models[i].type)
return i + 1;
@@ -2107,8 +2128,8 @@ namespace TR {
int sz = (z - room.info.z) >> 10;
if (sz <= 0 || sz >= room.xSectors - 1) {
sx = clamp(sx, 1, room.xSectors - 2);
sz = clamp(sz, 0, room.zSectors - 1);
sx = clamp(sx, 0, room.xSectors - 1);
sz = clamp(sz, 1, room.zSectors - 2);
} else
sx = clamp(sx, 0, room.xSectors - 1);

View File

@@ -180,8 +180,17 @@ struct Inventory {
// add(TR::Entity::INV_MEDIKIT_SMALL, 999);
// add(TR::Entity::INV_MEDIKIT_BIG, 999);
// add(TR::Entity::INV_SCION, 1);
// add(TR::Entity::INV_KEY_1, 1);
// add(TR::Entity::INV_PUZZLE_1, 1);
#ifdef _DEBUG
add(TR::Entity::INV_KEY_1, 3);
add(TR::Entity::INV_KEY_2, 3);
add(TR::Entity::INV_KEY_3, 3);
add(TR::Entity::INV_KEY_4, 3);
add(TR::Entity::INV_PUZZLE_1, 3);
add(TR::Entity::INV_PUZZLE_2, 3);
add(TR::Entity::INV_PUZZLE_3, 3);
add(TR::Entity::INV_PUZZLE_4, 3);
#endif
}
if (id == TR::TITLE) {

View File

@@ -209,9 +209,10 @@ struct Level : IGame {
playSound(TR::SND_STAIRS2SLOPE, vec3(), 0);
break;
case TR::Effect::EXPLOSION :
playSound(TR::SND_EXPLOSION, vec3(0), 0);
playSound(TR::SND_TNT, vec3(0), 0);
camera->shake = 1.0f;
break;
default : ;
}
}
@@ -219,6 +220,38 @@ struct Level : IGame {
lara->checkTrigger(controller, heavy);
}
virtual int addSprite(TR::Entity::Type type, int room, int x, int y, int z, int frame = -1, bool empty = false) {
return Sprite::add(this, type, room, x, y, z, frame, empty);
}
virtual int addEnemy(TR::Entity::Type type, int room, const vec3 &pos, float angle) {
int index = level.entityAdd(type, room, int(pos.x), int(pos.y), int(pos.z), TR::angle(angle), -1);
if (index > -1) {
TR::Entity &e = level.entities[index];
Enemy *enemy = NULL;
switch (type) {
case TR::Entity::ENEMY_MUTANT_1 :
case TR::Entity::ENEMY_MUTANT_2 :
case TR::Entity::ENEMY_MUTANT_3 :
enemy = new Mutant(this, index);
break;
case TR::Entity::ENEMY_CENTAUR :
enemy = new Centaur(this, index);
break;
case TR::Entity::ENEMY_GIANT_MUTANT :
enemy = new GiantMutant(this, index);
break;
default : ;
}
ASSERT(enemy);
e.controller = enemy;
e.flags.active = TR::ACTIVE;
enemy->activate();
}
return index;
}
virtual bool invUse(TR::Entity::Type type) {
if (!lara->useItem(type))
return inventory.use(type);
@@ -338,15 +371,14 @@ struct Level : IGame {
case TR::Entity::ENEMY_BAT :
entity.controller = new Bat(this, i);
break;
case TR::Entity::ENEMY_TWIN :
case TR::Entity::ENEMY_CROCODILE_LAND :
case TR::Entity::ENEMY_CROCODILE_WATER :
case TR::Entity::ENEMY_LION_MALE :
case TR::Entity::ENEMY_LION_FEMALE :
case TR::Entity::ENEMY_PUMA :
case TR::Entity::ENEMY_GORILLA :
entity.controller = new Lion(this, i);
break;
case TR::Entity::ENEMY_RAT_LAND :
case TR::Entity::ENEMY_RAT_WATER :
entity.controller = new Rat(this, i);
break;
case TR::Entity::ENEMY_REX :
entity.controller = new Rex(this, i);
break;
@@ -356,9 +388,23 @@ struct Level : IGame {
case TR::Entity::ENEMY_MUTANT_1 :
case TR::Entity::ENEMY_MUTANT_2 :
case TR::Entity::ENEMY_MUTANT_3 :
entity.controller = new Mutant(this, i);
break;
case TR::Entity::ENEMY_CENTAUR :
entity.controller = new Centaur(this, i);
break;
case TR::Entity::ENEMY_MUMMY :
case TR::Entity::ENEMY_TWIN :
case TR::Entity::ENEMY_CROCODILE_LAND :
case TR::Entity::ENEMY_CROCODILE_WATER :
case TR::Entity::ENEMY_PUMA :
case TR::Entity::ENEMY_GORILLA :
case TR::Entity::ENEMY_LARSON :
case TR::Entity::ENEMY_PIERRE :
case TR::Entity::ENEMY_SKATEBOY :
case TR::Entity::ENEMY_COWBOY :
case TR::Entity::ENEMY_MR_T :
case TR::Entity::ENEMY_NATLA :
entity.controller = new Enemy(this, i, 100, 10, 0.0f, 0.0f);
break;
case TR::Entity::DOOR_1 :
@@ -454,6 +500,10 @@ struct Level : IGame {
case TR::Entity::BOAT :
entity.controller = new Boat(this, i);
break;
case TR::Entity::MUTANT_EGG_SMALL :
case TR::Entity::MUTANT_EGG_BIG :
entity.controller = new MutantEgg(this, i);
break;
default :
if (entity.modelIndex > 0)
entity.controller = new Controller(this, i);

View File

@@ -784,6 +784,44 @@ struct Boat : Controller {
};
#define MUTANT_EGG_RANGE 4096
struct MutantEgg : Controller {
enum {
STATE_IDLE,
STATE_EXPLOSION,
};
TR::Entity::Type enemy;
MutantEgg(IGame *game, int entity) : Controller(game, entity) {
initMeshOverrides();
layers[0].mask = 0xff0001ff; // hide dynamic meshes
switch (getEntity().flags.active) {
case 1 : enemy = TR::Entity::ENEMY_MUTANT_2; break;
case 2 : enemy = TR::Entity::ENEMY_CENTAUR; break;
case 4 : enemy = TR::Entity::ENEMY_GIANT_MUTANT; break;
case 8 : enemy = TR::Entity::ENEMY_MUTANT_3; break;
default : enemy = TR::Entity::ENEMY_MUTANT_1;
}
}
virtual void update() {
if (state != STATE_EXPLOSION) {
Box box = Box(pos + vec3(-MUTANT_EGG_RANGE), pos + vec3(MUTANT_EGG_RANGE));
TR::Entity &e = getEntity();
if ( e.flags.once || e.type == TR::Entity::MUTANT_EGG_BIG || box.contains((((Controller*)level->laraController)->pos)) ) {
animation.setState(STATE_EXPLOSION);
layers[0].mask = 0xffffffff & ~(1 << 24);
explode(0x00fffe00);
game->addEnemy(enemy, getRoomIndex(), pos, angle.y);
}
}
Controller::update();
}
};
struct KeyHole : Controller {
KeyHole(IGame *game, int entity) : Controller(game, entity) {}

View File

@@ -62,11 +62,21 @@ inline const T& min(const T &a, const T &b) {
return a < b ? a : b;
}
template <typename T>
inline const T& min(const T &a, const T &b, const T &c) {
return (a < b && a < c) ? a : ((b < a && b < c) ? b : c);
}
template <class T>
inline const T& max(const T &a, const T &b) {
return a > b ? a : b;
}
template <typename T>
inline const T& max(const T &a, const T &b, const T &c) {
return (a > b && a > c) ? a : ((b > a && b > c) ? b : c);
}
template <class T>
inline const T& clamp(const T &x, const T &a, const T &b) {
return x < a ? a : (x > b ? b : x);