diff --git a/src/animation.h b/src/animation.h index 3713cdb..fbecdff 100644 --- a/src/animation.h +++ b/src/animation.h @@ -6,7 +6,7 @@ struct Animation { TR::Level *level; - TR::Model *model; + const TR::Model *model; TR::Animation *anims; int state; float time, timeMax, delta, dir; @@ -22,8 +22,8 @@ struct Animation { 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) { + Animation(TR::Level *level, const 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); } diff --git a/src/cache.h b/src/cache.h index 9227ab2..fbdefcc 100644 --- a/src/cache.h +++ b/src/cache.h @@ -383,7 +383,6 @@ struct WaterCache { } void init(IGame *game) { - TR::Level *level = game->getLevel(); TR::Room &r = level->rooms[to]; // underwater room @@ -419,7 +418,7 @@ struct WaterCache { bool hasFlow = false; if (hasWater) { TR::Level::FloorInfo info; - level->getFloorInfo(to, x + r.info.x, r.info.yBottom, z + r.info.z, info); + game->getLara()->getFloorInfo(to, vec3(float(x + r.info.x), float(r.info.yBottom), float(z + r.info.z)), info); if (info.trigCmdCount && info.trigger == TR::Level::Trigger::ACTIVATE) for (int i = 0; i < info.trigCmdCount; i++) if (info.trigCmd[i].action == TR::Action::FLOW) { diff --git a/src/camera.h b/src/camera.h index d97596e..09f6c6b 100644 --- a/src/camera.h +++ b/src/camera.h @@ -41,7 +41,7 @@ struct Camera : ICamera { 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) { changeView(false); - if (owner->getEntity().type != TR::Entity::LARA && level->cameraFrames) { + if (!owner->getEntity().isLara() && level->cameraFrames) { state = STATE_CUTSCENE; room = level->entities[level->cutEntity].room; timer = 0.0f; @@ -71,7 +71,7 @@ struct Camera : ICamera { } TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info); + owner->getFloorInfo(getRoomIndex(), pos, info); if (info.roomNext != TR::NO_ROOM) room = info.roomNext; @@ -257,7 +257,7 @@ struct Camera : ICamera { if (state != STATE_STATIC) { if (!owner->viewTarget) { - if (viewTarget && !viewTarget->getEntity().flags.invisible) { + if (viewTarget && !viewTarget->flags.invisible) { vec3 targetVec = (viewTarget->pos - owner->pos).normal(); if (targetVec.dot(owner->getDir()) > 0.5f) lookAt = viewTarget; diff --git a/src/character.h b/src/character.h index c4d28da..6e544cc 100644 --- a/src/character.h +++ b/src/character.h @@ -2,6 +2,7 @@ #define H_CHARACTER #include "controller.h" +#include "collision.h" struct Character : Controller { float health; @@ -87,16 +88,15 @@ struct Character : Controller { virtual void checkRoom() { TR::Level::FloorInfo info; - TR::Entity &e = getEntity(); - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); if (info.roomNext != TR::NO_ROOM) - e.room = info.roomNext; + roomIndex = info.roomNext; - if (info.roomBelow != TR::NO_ROOM && e.y > info.roomFloor) - e.room = info.roomBelow; + if (info.roomBelow != TR::NO_ROOM && pos.y > info.roomFloor) + roomIndex = info.roomBelow; - if (info.roomAbove != TR::NO_ROOM && e.y <= info.roomCeiling) { + if (info.roomAbove != TR::NO_ROOM && pos.y <= info.roomCeiling) { TR::Room *room = &level->rooms[info.roomAbove]; if (level->isFlipped && room->alternateRoom > -1) room = &level->rooms[room->alternateRoom]; @@ -104,11 +104,10 @@ struct Character : Controller { if (stand == STAND_UNDERWATER && !room->flags.water) { stand = STAND_ONWATER; velocity.y = 0; - pos.y = float(info.roomCeiling); - updateEntity(); + pos.y = info.roomCeiling; } else if (stand != STAND_ONWATER) - e.room = info.roomAbove; + roomIndex = info.roomAbove; } } @@ -179,7 +178,7 @@ struct Character : Controller { updateState(); Controller::update(); - if (getEntity().flags.active) { + if (flags.active) { updateVelocity(); updatePosition(); if (p != pos) { diff --git a/src/collision.h b/src/collision.h index d953844..93c6728 100644 --- a/src/collision.h +++ b/src/collision.h @@ -4,17 +4,19 @@ #include "core.h" #include "utils.h" #include "format.h" +#include "controller.h" struct Collision { enum Side { NONE, LEFT, RIGHT, FRONT, BACK, TOP, BOTTOM } side; struct Info { - int room, roomAbove, roomBelow, floor, ceiling; + int room, roomAbove, roomBelow; + float floor, ceiling; } info[4]; Collision() : side(NONE) {} - Collision(TR::Level *level, int roomIndex, vec3 &pos, const vec3 &offset, const vec3 &velocity, float radius, float angle, int minHeight, int maxHeight, int maxAscent, int maxDescent) { + Collision(Controller *controller, int roomIndex, vec3 &pos, const vec3 &offset, const vec3 &velocity, float radius, float angle, int minHeight, int maxHeight, int maxAscent, int maxDescent) { if (velocity.x > 0.0f || velocity.z > 0.0f) angle = normalizeAngle(PI2 + vec2(velocity.z, velocity.x).angle()); pos += velocity; @@ -38,16 +40,16 @@ struct Collision { int height = maxHeight - minHeight; - getFloor(level, roomIndex, vec3(pos.x, hpos.y, pos.z)); + getFloor(controller->level, roomIndex, vec3(pos.x, hpos.y, pos.z)); - if (checkHeight(level, roomIndex, hpos, vec2(0.0f), height, 0xFFFFFF, 0xFFFFFF, side = NONE)) { + if (checkHeight(controller, roomIndex, hpos, vec2(0.0f), height, 0xFFFFFF, 0xFFFFFF, side = NONE)) { pos.x -= velocity.x; pos.z -= velocity.z; side = FRONT; return; } - int hCell = info[NONE].ceiling - (int(hpos.y) - maxHeight); + float hCell = info[NONE].ceiling - (hpos.y - maxHeight); if (hCell > 0) { if (hCell > 128) { pos.x -= velocity.x; @@ -59,19 +61,19 @@ struct Collision { } } - int hFloor = info[NONE].floor - (int(hpos.y) + minHeight); + float hFloor = info[NONE].floor - (hpos.y + minHeight); if (hFloor < 0 && hFloor > -256) { pos.y = info[NONE].floor - minHeight - offset.y; side = BOTTOM; } - if (checkHeight(level, roomIndex, hpos, f, height, maxAscent, maxDescent, FRONT)) { + if (checkHeight(controller, roomIndex, hpos, f, height, maxAscent, maxDescent, FRONT)) { d = vec2(-velocity.x, -velocity.z); q ^= 1; d[q] = getOffset(p[q] + f[q], p[q]); - } else if (checkHeight(level, roomIndex, hpos, l, height, maxAscent, maxDescent, LEFT)) { + } else if (checkHeight(controller, roomIndex, hpos, l, height, maxAscent, maxDescent, LEFT)) { d[q] = getOffset(p[q] + l[q], p[q] + f[q]); - } else if (checkHeight(level, roomIndex, hpos, r, height, maxAscent, maxDescent, RIGHT)) { + } else if (checkHeight(controller, roomIndex, hpos, r, height, maxAscent, maxDescent, RIGHT)) { d[q] = getOffset(p[q] + r[q], p[q] + f[q]); } else return; @@ -79,10 +81,9 @@ struct Collision { pos += vec3(d.x, 0.0f, d.y); } - inline bool checkHeight(TR::Level *level, int roomIndex, const vec3 &pos, const vec2 &offset, int height, int maxAscent, int maxDescent, Side side) { + inline bool checkHeight(Controller *controller, int roomIndex, const vec3 &pos, const vec2 &offset, int height, int maxAscent, int maxDescent, Side side) { TR::Level::FloorInfo info; - int py = int(pos.y); - level->getFloorInfo(roomIndex, int(pos.x + offset.x), py, int(pos.z + offset.y), info); + controller->getFloorInfo(roomIndex, pos + vec3(offset.x, 0.0f, offset.y), info); Info &inf = this->info[side]; inf.room = info.roomNext != TR::NO_ROOM ? info.roomNext : roomIndex; @@ -91,7 +92,7 @@ struct Collision { inf.floor = info.floor; inf.ceiling = info.ceiling; - if ((info.ceiling == info.floor) || (info.floor - info.ceiling < height) || (py - info.floor > maxAscent) || (info.floor - py > maxDescent) || (info.ceiling > py) || + if ((info.ceiling == info.floor) || (info.floor - info.ceiling < height) || (pos.y - info.floor > maxAscent) || (info.floor - pos.y > maxDescent) || (info.ceiling > pos.y) || (maxAscent == maxDescent && (maxAscent <= 256 + 128) && (abs(info.slantX) > 2 || abs(info.slantZ) > 2))) { this->side = side; return true; diff --git a/src/controller.h b/src/controller.h index 8a59d7c..f565aba 100644 --- a/src/controller.h +++ b/src/controller.h @@ -5,7 +5,6 @@ #include "frustum.h" #include "mesh.h" #include "animation.h" -#include "collision.h" #define GRAVITY (6.0f * 30.0f) #define SPRITE_FPS 10.0f @@ -58,8 +57,7 @@ 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 Controller* addEntity(TR::Entity::Type type, int room, const vec3 &pos, float angle) { return NULL; } + virtual Controller* addEntity(TR::Entity::Type type, int room, const vec3 &pos, float angle = 0.0f) { return NULL; } virtual bool invUse(TR::Entity::Type type) { return false; } virtual void invAdd(TR::Entity::Type type, int count = 1) {} @@ -72,9 +70,38 @@ struct IGame { }; struct Controller { + + struct SaveData { + // base + int32 x, y, z; + uint16 rotation; + uint16 type; + uint16 flags; + int16 timer; + // animation + uint16 animIndex; + uint16 animFrame; + // common + uint8 room; + uint8 extraSize; + union Extra { + struct { + float velX, velY, velZ; + uint16 angleX; + uint16 health; + uint16 oxygen; + int8 curWeapon; + uint8 emptyHands; + } lara; + struct { + uint16 health; + uint16 mood; + } enemy; + } extra; + }; + static Controller *first; Controller *next; - enum ActiveState { asNone, asActive, asInactive }; IGame *game; TR::Level *level; @@ -86,6 +113,9 @@ struct Controller { vec3 pos; vec3 angle; + int16 roomIndex; + TR::Entity::Flags flags; + Basis *joints; int frameIndex; @@ -111,14 +141,17 @@ struct Controller { int roomIndex; } *explodeParts; - ActiveState activeState; bool invertAim; - Controller(IGame *game, int entity) : next(NULL), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(0), explodeMask(0), explodeParts(0), activeState(asNone), 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); - TR::Model *m = getModel(); + Controller(IGame *game, int entity) : next(NULL), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(0), explodeMask(0), explodeParts(0), invertAim(false) { + const TR::Entity &e = getEntity(); + pos = vec3(float(e.x), float(e.y), float(e.z)); + angle = vec3(0.0f, e.rotation, 0.0f); + roomIndex = e.room; + flags = e.flags; + flags.state = TR::Entity::asNone; + + const TR::Model *m = getModel(); joints = m ? new Basis[m->mCount] : NULL; frameIndex = -1; specular = 0.0f; @@ -128,14 +161,14 @@ struct Controller { updateLights(false); visibleMask = 0xFFFFFFFF; - if (e.flags.once) { - e.flags.invisible = true; - e.flags.once = false; + if (flags.once) { + flags.invisible = true; + flags.once = false; } - if (e.flags.active == TR::ACTIVE) { - e.flags.active = 0; - e.flags.reverse = true; + if (flags.active == TR::ACTIVE) { + flags.active = 0; + flags.reverse = true; activate(); } @@ -150,39 +183,292 @@ struct Controller { deactivate(true); } - bool isActive() { - TR::Entity &e = getEntity(); + void getFloorInfo(int roomIndex, const vec3 &pos, TR::Level::FloorInfo &info) const { + int x = int(pos.x); + int y = int(pos.y); + int z = int(pos.z); - if (e.flags.active != TR::ACTIVE) - return e.flags.reverse; + int dx, dz; + TR::Room::Sector &s = level->getSector(roomIndex, x, z, dx, dz); + + info.roomFloor = float(256 * s.floor); + info.roomCeiling = float(256 * s.ceiling); + info.floor = info.roomFloor; + info.ceiling = info.roomCeiling; + info.slantX = 0; + info.slantZ = 0; + info.roomNext = TR::NO_ROOM; + info.roomBelow = s.roomBelow; + info.roomAbove = s.roomAbove; + info.floorIndex = s.floorIndex; + info.boxIndex = s.boxIndex; + info.lava = false; + info.trigger = TR::Level::Trigger::ACTIVATE; + info.trigCmdCount = 0; + + if (s.floor == TR::NO_FLOOR) + return; + + TR::Room::Sector *sBelow = &s; + while (sBelow->roomBelow != TR::NO_ROOM) sBelow = &level->getSector(sBelow->roomBelow, x, z, dx, dz); + info.floor = float(256 * sBelow->floor); + + parseFloorData(info, sBelow->floorIndex, dx, dz); + + if (info.roomNext == TR::NO_ROOM) { + TR::Room::Sector *sAbove = &s; + while (sAbove->roomAbove != TR::NO_ROOM) sAbove = &level->getSector(sAbove->roomAbove, x, z, dx, dz); + if (sAbove != sBelow) { + info.ceiling = float(256 * sAbove->ceiling); + parseFloorData(info, sAbove->floorIndex, dx, dz); + } + } else { + int tmp = info.roomNext; + getFloorInfo(tmp, pos, info); + info.roomNext = tmp; + } + + // entities collide + if (info.trigCmdCount) { + int sx = x / 1024; + int sz = z / 1024; + int dx = x % 1024; + int dz = z % 1024; + + for (int i = 0; i < info.trigCmdCount; i++) { + TR::FloorData::TriggerCommand cmd = info.trigCmd[i]; + if (cmd.action == TR::Action::CAMERA_SWITCH) { + i++; + continue; + } + if (cmd.action != TR::Action::ACTIVATE) continue; + + TR::Entity &e = level->entities[cmd.args]; + Controller *controller = (Controller*)e.controller; + if (!controller) continue; // Block UpdateFloor issue while entities initialization + if (!controller->flags.collision) continue; + + switch (e.type) { + case TR::Entity::TRAP_DOOR_1 : + case TR::Entity::TRAP_DOOR_2 : { + int dirX, dirZ; + e.getAxis(dirX, dirZ); + + int ex = int(controller->pos.x) / 1024; + int ey = int(controller->pos.y); + int ez = int(controller->pos.z) / 1024; + if ((ex == sx && ez == sz) || (ex + dirX == sx && ez + dirZ == sz)) { + if (ey >= y - 128 && controller->pos.y < info.floor) { + info.floor = controller->pos.y; + info.slantX = info.slantZ = 0; + info.lava = false; + } + if (ey < y - 128 && controller->pos.y > info.ceiling) + info.ceiling = controller->pos.y + 256; + } + break; + } + case TR::Entity::TRAP_FLOOR : { + if (sx != int(controller->pos.x) / 1024 || sz != int(controller->pos.z) / 1024) + break; + int ey = int(controller->pos.y) - 512; + if (ey >= y - 128 && ey < info.floor) { + info.floor = float(ey); + info.slantX = info.slantZ = 0; + info.lava = false; + } + if (ey < y - 128 && ey > info.ceiling) + info.ceiling = float(ey); + break; + } + case TR::Entity::DRAWBRIDGE : { + if (controller->flags.active != TR::ACTIVE) continue; + int dirX, dirZ; + e.getAxis(dirX, dirZ); + int ex = int(controller->pos.x) / 1024; + int ez = int(controller->pos.z) / 1024; + + if ((ex - dirX * 1 == sx && ez - dirZ * 1 == sz) || + (ex - dirX * 2 == sx && ez - dirZ * 2 == sz)) { + int ey = int(controller->pos.y); + if (ey >= y - 128 && controller->pos.y < info.floor) { + info.floor = controller->pos.y; + info.slantX = info.slantZ = 0; + info.lava = false; + } + if (ey < y - 128 && controller->pos.y > info.ceiling) + info.ceiling = controller->pos.y + 256; + } + break; + } + case TR::Entity::HAMMER_HANDLE : { + int dirX, dirZ; + e.getAxis(dirX, dirZ); + if (abs(int(controller->pos.x) + dirX * 1024 * 3 - x) < 512 && abs(int(controller->pos.z) + dirZ * 1024 * 3 - z) < 512) + info.floor -= 1024 * 3; + break; + } + case TR::Entity::BRIDGE_0 : + case TR::Entity::BRIDGE_1 : + case TR::Entity::BRIDGE_2 : { + if (sx != int(controller->pos.x) / 1024 || sz != int(controller->pos.z) / 1024) + break; + + int s = (e.type == TR::Entity::BRIDGE_1) ? 1 : + (e.type == TR::Entity::BRIDGE_2) ? 2 : 0; + + int ey = int(controller->pos.y), sx = 0, sz = 0; + + if (s > 0) { + switch (e.rotation.value / 0x4000) { // get slantXZ by direction + case 0 : sx = s; break; + case 1 : sz = -s; break; + case 2 : sx = -s; break; + case 3 : sz = s; break; + } + + ey -= sx * (sx > 0 ? (dx - 1024) : dx) >> 2; + ey -= sz * (sz > 0 ? (dz - 1024) : dz) >> 2; + } + + if (y - 128 <= ey) { + info.floor = float(ey); + info.slantX = sx; + info.slantZ = sz; + info.lava = false; + } + if (ey < y - 128) + info.ceiling = float(ey + 64); + break; + } + + default : ; + } + } + } + } + + void parseFloorData(TR::Level::FloorInfo &info, int floorIndex, int dx, int dz) const { + if (!floorIndex) return; + + TR::FloorData *fd = &level->floors[floorIndex]; + TR::FloorData::Command cmd; + + do { + cmd = (*fd++).cmd; + + switch (cmd.func) { + + case TR::FloorData::PORTAL : + info.roomNext = (*fd++).data; + break; + + case TR::FloorData::FLOOR : // floor & ceiling + case TR::FloorData::CEILING : { + TR::FloorData::Slant slant = (*fd++).slant; + int sx = (int)slant.x; + int sz = (int)slant.z; + if (cmd.func == TR::FloorData::FLOOR) { + info.slantX = sx; + info.slantZ = sz; + info.floor -= sx * (sx > 0 ? (dx - 1024) : dx) >> 2; + info.floor -= sz * (sz > 0 ? (dz - 1024) : dz) >> 2; + } else { + info.ceiling -= sx * (sx < 0 ? (dx - 1024) : dx) >> 2; + info.ceiling += sz * (sz > 0 ? (dz - 1024) : dz) >> 2; + } + break; + } + + case TR::FloorData::TRIGGER : { + info.trigger = (TR::Level::Trigger)cmd.sub; + info.trigCmdCount = 0; + info.trigInfo = (*fd++).triggerInfo; + TR::FloorData::TriggerCommand trigCmd; + do { + ASSERT(info.trigCmdCount < MAX_TRIGGER_COMMANDS); + trigCmd = (*fd++).triggerCmd; // trigger action + info.trigCmd[info.trigCmdCount++] = trigCmd; + } while (!trigCmd.end); + break; + } + + case TR::FloorData::LAVA : + info.lava = true; + break; + + default : LOG("unknown func: %d\n", cmd.func); + } + + } while (!cmd.end); + } + + virtual void getSaveData(SaveData &data) { + const TR::Entity &e = getEntity(); + const TR::Model *m = getModel(); + // base + data.x = e.x ^ int32(pos.x); + data.y = e.y ^ int32(pos.y); + data.z = e.z ^ int32(pos.z); + data.rotation = e.rotation.value ^ TR::angle(normalizeAngle(angle.y)).value; + data.type = entity >= level->entitiesBaseCount ? int16(e.type) : 0; + data.flags = e.flags.value ^ flags.value; + data.timer = timer == 0.0f ? 0 : (timer < 0.0f ? -1 : int16(timer * 30.0f)); + // animation + data.animIndex = m ? (m->animation ^ animation.index) : 0; + data.animFrame = m ? animation.frameIndex : 0; + // common + data.room = e.room ^ roomIndex; + data.extraSize = 0; + } + + virtual void setSaveData(const SaveData &data) { + const TR::Entity &e = getEntity(); + const TR::Model *m = getModel(); + // base + pos.x = float(e.x ^ data.x); + pos.y = float(e.y ^ data.y); + pos.z = float(e.z ^ data.z); + angle.y = TR::angle(uint16(e.rotation.value ^ data.rotation)); + flags.value = e.flags.value ^ data.flags; + timer = data.timer == -1 ? -1.0f : (timer / 30.0f); + // animation + if (m) animation.setAnim(m->animation ^ data.animIndex, -data.animFrame); + roomIndex = e.room ^ data.room; + } + + bool isActive() { + if (flags.active != TR::ACTIVE) + return flags.reverse; if (timer == 0.0f) - return !e.flags.reverse; + return !flags.reverse; if (timer == -1.0f) - return e.flags.reverse; + return flags.reverse; timer = max(0.0f, timer - Core::deltaTime); if (timer == 0.0f) timer = -1.0f; - return !e.flags.reverse; + return !flags.reverse; } virtual bool activate() { - if (activeState != asNone) + if (flags.state != TR::Entity::asNone) return false; - getEntity().flags.invisible = false; - activeState = asActive; + flags.invisible = false; + flags.state = TR::Entity::asActive; next = first; first = this; return true; } virtual void deactivate(bool removeFromList = false) { - activeState = asInactive; + flags.state = TR::Entity::asInactive; if (removeFromList) { + flags.state = TR::Entity::asNone; Controller *prev = NULL; Controller *c = first; while (c) { @@ -191,7 +477,6 @@ struct Controller { prev->next = c->next; else first = c->next; - c->activeState = asNone; break; } else prev = c; @@ -204,12 +489,12 @@ struct Controller { Controller *prev = NULL; Controller *c = first; while (c) { - if (c->activeState == asInactive) { + if (c->flags.state == TR::Entity::asInactive) { if (prev) prev->next = c->next; else first = c->next; - c->activeState = asNone; + c->flags.state = TR::Entity::asNone; } else prev = c; c = c->next; @@ -270,15 +555,6 @@ struct Controller { return false; } - void updateEntity() { - TR::Entity &e = getEntity(); - e.x = int(pos.x); - e.y = int(pos.y); - e.z = int(pos.z); - angle.y = normalizeAngle(angle.y); - e.rotation = angle.y; - } - bool insideRoom(const vec3 &pos, int roomIndex) const { TR::Room &r = level->rooms[roomIndex]; vec3 min = vec3((float)r.info.x + 1024, (float)r.info.yTop, (float)r.info.z + 1024); @@ -289,23 +565,23 @@ struct Controller { pos.z >= min.z && pos.z <= max.z; } - TR::Model* getModel() const { + const TR::Model* getModel() const { int index = getEntity().modelIndex; return index > 0 ? &level->models[index - 1] : NULL; } - TR::Entity& getEntity() const { + const TR::Entity& getEntity() const { return level->entities[entity]; } - TR::Room& getRoom() const { + const TR::Room& getRoom() const { int index = getRoomIndex(); ASSERT(index >= 0 && index < level->roomsCount); return level->rooms[index]; } virtual int getRoomIndex() const { - int index = getEntity().room; + int index = roomIndex; if (level->isFlipped && level->rooms[index].alternateRoom > -1) index = level->rooms[index].alternateRoom; return index; @@ -359,7 +635,6 @@ struct Controller { } angle.y = q * (PI * 0.5f); - updateEntity(); return true; } @@ -372,7 +647,7 @@ struct Controller { } void getSpheres(Sphere *spheres, int &count) { - TR::Model *m = getModel(); + const TR::Model *m = getModel(); ASSERT(m->mCount <= MAX_SPHERES); Basis basis(getMatrix()); @@ -387,8 +662,8 @@ struct Controller { } int collide(Controller *controller, bool checkBoxes = true) { - TR::Model *a = getModel(); - TR::Model *b = controller->getModel(); + const TR::Model *a = getModel(); + const TR::Model *b = controller->getModel(); if (!a || !b) return 0; @@ -435,10 +710,10 @@ struct Controller { sz = pz / 1024 * 1024 + 512; if (lr != room || lx != sx || lz != sz) { - level->getFloorInfo(room, sx, py, sz, info); + getFloorInfo(room, vec3(float(sx), float(py), float(sz)), info); if (info.roomNext != TR::NO_ROOM) { room = info.roomNext; - level->getFloorInfo(room, sx, py, sz, info); + getFloorInfo(room, vec3(float(sx), float(py), float(sz)), info); } lr = room; lx = sx; @@ -498,7 +773,6 @@ struct Controller { virtual void cmdOffset(const vec3 &offset) { pos = pos + offset.rotateY(-angle.y); - updateEntity(); checkRoom(); } @@ -560,7 +834,7 @@ struct Controller { void updateExplosion() { if (!explodeMask) return; - TR::Model *model = getModel(); + const TR::Model *model = getModel(); for (int i = 0; i < model->mCount; i++) if (explodeMask & (1 << i)) { ExplodePart &part = explodeParts[i]; @@ -572,7 +846,7 @@ struct Controller { vec3 p = part.basis.pos; //TR::Room::Sector *s = level->getSector(part.roomIndex, int(p.x), int(p.y), int(p.z)); TR::Level::FloorInfo info; - level->getFloorInfo(part.roomIndex, int(p.x), int(p.y), int(p.z), info); + getFloorInfo(part.roomIndex, p, info); if (info.roomNext != TR::NO_ROOM) part.roomIndex = info.roomNext; @@ -601,9 +875,7 @@ struct Controller { if (explode) { 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 ? + game->addEntity(TR::Entity::EXPLOSION, part.roomIndex, p); } } @@ -620,7 +892,7 @@ struct Controller { void updateLights(bool lerp = true) { if (getModel()) { - TR::Room &room = getRoom(); + const TR::Room &room = getRoom(); vec3 center = getBoundingBox().center(); float maxAtt = 0.0f; @@ -662,8 +934,7 @@ struct Controller { mat4 getMatrix() { mat4 matrix; - TR::Entity &e = getEntity(); - if (!e.isActor()) { + if (!getEntity().isActor()) { matrix.identity(); matrix.translate(pos); if (angle.y != 0.0f) matrix.rotateY(angle.y - (animation.flip ? PI * animation.delta : 0.0f)); @@ -676,7 +947,7 @@ struct Controller { } void explode(int32 mask) { - TR::Model *model = getModel(); + const TR::Model *model = getModel(); if (!layers) initMeshOverrides(); @@ -704,12 +975,9 @@ struct Controller { } void renderShadow(MeshBuilder *mesh) { - TR::Entity &entity = getEntity(); + const TR::Entity &entity = getEntity(); - if (Core::pass != Core::passCompose || !entity.castShadow()) - return; - - if (entity.isActor()) // cutscene entities have no blob shadow + if (Core::pass != Core::passCompose || !entity.castShadow() || entity.isActor()) return; Box boxL = getBoundingBoxLocal(); @@ -718,7 +986,7 @@ struct Controller { vec3 center = boxA.center(); TR::Level::FloorInfo info; - level->getFloorInfo(entity.room, int(center.x), int(center.y), int(center.z), info); + getFloorInfo(getRoomIndex(), center, info); const vec3 size = boxL.size() * (1.0f / 1024.0f); @@ -758,11 +1026,10 @@ struct Controller { if (!explodeMask && frustum && !frustum->isVisible(matrix, box.min, box.max)) return; - TR::Entity &entity = getEntity(); - TR::Model *model = getModel(); + const TR::Model *model = getModel(); ASSERT(model); - entity.flags.rendered = true; + flags.rendered = true; if (Core::stats.frame != frameIndex) animation.getJoints(matrix, -1, true, joints); @@ -781,7 +1048,7 @@ struct Controller { if (explodeMask) { ASSERT(explodeParts); - TR::Model *model = getModel(); + const TR::Model *model = getModel(); for (int i = 0; i < model->mCount; i++) if (explodeMask & (1 << i)) joints[i] = explodeParts[i].basis; @@ -795,7 +1062,7 @@ struct Controller { } } else { Core::active.shader->setParam(uBasis, joints[0], model->mCount); - mesh->renderModel(entity.modelIndex - 1); + mesh->renderModel(getEntity().modelIndex - 1); } frameIndex = Core::stats.frame; diff --git a/src/debug.h b/src/debug.h index eee3800..5ac3132 100644 --- a/src/debug.h +++ b/src/debug.h @@ -200,7 +200,7 @@ namespace Debug { if (p.w > 0) { p.xyz = p.xyz * (1.0f / p.w); p.y = -p.y; - p.xyz = (p.xyz * 0.5f + vec3(0.5f)) * vec3(Core::width, Core::height, 1.0f); + p.xyz = (p.xyz * 0.5f + vec3(0.5f)) * vec3(float(Core::width), float(Core::height), 1.0f); text(vec2(p.x, p.y), color, str); } } @@ -208,11 +208,13 @@ namespace Debug { namespace Level { - void debugFloor(const TR::Level &level, int roomIndex, int x, int y, int z, int zone = -1) { + void debugFloor(IGame *game, int roomIndex, int x, int y, int z, int zone = -1) { + TR::Level *level = game->getLevel(); + if (zone != -1) { int dx, dz; - TR::Room::Sector &s = level.getSector(roomIndex, x, z, dx, dz); - if (zone != level.zones[0].ground1[s.boxIndex]) + TR::Room::Sector &s = level->getSector(roomIndex, x, z, dx, dz); + if (zone != level->zones[0].ground1[s.boxIndex]) return; } @@ -220,10 +222,10 @@ namespace Debug { vec3 rf[4], rc[4], f[4], c[4]; - int offsets[4][2] = { { 1, 1 }, { 1023, 1 }, { 1023, 1023 }, { 1, 1023 } }; + float offsets[4][2] = { { 1, 1 }, { 1023, 1 }, { 1023, 1023 }, { 1, 1023 } }; for (int i = 0; i < 4; i++) { - level.getFloorInfo(roomIndex, x + offsets[i][0], y, z + offsets[i][1], info); + game->getLara()->getFloorInfo(roomIndex, vec3(float(x + offsets[i][0]), float(y), float(z + offsets[i][1])), info); rf[i] = vec3( x + offsets[i][0], info.roomFloor - 4, z + offsets[i][1] ); rc[i] = vec3( x + offsets[i][0], info.roomCeiling + 4, z + offsets[i][1] ); f[i] = vec3( x + offsets[i][0], info.floor - 4, z + offsets[i][1] ); @@ -293,10 +295,10 @@ namespace Debug { void debugBox(const TR::Box &b) { glBegin(GL_QUADS); float y = b.floor - 16.0f; - glVertex3f(b.minX, y, b.maxZ); - glVertex3f(b.maxX, y, b.maxZ); - glVertex3f(b.maxX, y, b.minZ); - glVertex3f(b.minX, y, b.minZ); + glVertex3f(float(b.minX), y, float(b.maxZ)); + glVertex3f(float(b.maxX), y, float(b.maxZ)); + glVertex3f(float(b.maxX), y, float(b.minZ)); + glVertex3f(float(b.minX), y, float(b.minZ)); glEnd(); } @@ -317,7 +319,7 @@ namespace Debug { if (blockable || block) { sprintf(buf, "blocked: %s", block ? "true" : "false"); - Debug::Draw::text(vec3(r.info.x + x * 1024 + 512, floor, r.info.z + z * 1024 + 512), vec4(1, 1, 0, 1), buf); + Debug::Draw::text(vec3(float(r.info.x + x * 1024 + 512), float(floor), float(r.info.z + z * 1024 + 512)), vec4(1, 1, 0, 1), buf); } } } @@ -331,7 +333,7 @@ namespace Debug { TR::Box &b = level.boxes[boxIndex]; sprintf(str, "%d", boxIndex); - Draw::text(vec3((b.maxX + b.minX) * 0.5, b.floor, (b.maxZ + b.minZ) * 0.5), vec4(0, 1, 0, 1), str); + Draw::text(vec3((b.maxX + b.minX) * 0.5f, b.floor, (b.maxZ + b.minZ) * 0.5f), vec4(0, 1, 0, 1), str); glColor4f(0.0f, 1.0f, 0.0f, 0.25f); Core::setBlending(bmAlpha); debugBox(b); @@ -340,7 +342,7 @@ namespace Debug { do { TR::Box &b = level.boxes[o->boxIndex]; sprintf(str, "%d", o->boxIndex); - Draw::text(vec3((b.maxX + b.minX) * 0.5, b.floor, (b.maxZ + b.minZ) * 0.5), vec4(0, 0, 1, 1), str); + Draw::text(vec3((b.maxX + b.minX) * 0.5f, b.floor, (b.maxZ + b.minZ) * 0.5f), vec4(0, 0, 1, 1), str); glColor4f(0.0f, 0.0f, 1.0f, 0.25f); Core::setBlending(bmAlpha); debugBox(b); @@ -359,12 +361,12 @@ namespace Debug { Core::setDepthTest(true); } - void sectors(const TR::Level &level, int roomIndex, int y, int zone = -1) { - TR::Room &room = level.rooms[roomIndex]; + void sectors(IGame *game, int roomIndex, int y, int zone = -1) { + TR::Room &room = game->getLevel()->rooms[roomIndex]; for (int z = 0; z < room.zSectors; z++) for (int x = 0; x < room.xSectors; x++) - debugFloor(level, roomIndex, room.info.x + x * 1024, y, room.info.z + z * 1024, zone); + debugFloor(game, roomIndex, room.info.x + x * 1024, y, room.info.z + z * 1024, zone); } void rooms(const TR::Level &level, const vec3 &pos, int roomIndex) { @@ -372,7 +374,7 @@ namespace Debug { for (int i = 0; i < level.roomsCount; i++) { TR::Room &r = level.rooms[i]; - vec3 p = vec3(r.info.x, r.info.yTop, r.info.z); + vec3 p = vec3(float(r.info.x), float(r.info.yTop), float(r.info.z)); if (i == roomIndex) { //if (lara->insideRoom(Core::viewPos, i)) { @@ -399,7 +401,7 @@ namespace Debug { TR::Room::Portal &p = r.portals[j]; for (int k = 0; k < 4; k++) { TR::Vertex &v = p.vertices[k]; - glVertex3f(v.x + r.info.x, v.y, v.z + r.info.z); + glVertex3f(float(v.x + r.info.x), float(v.y), float(v.z + r.info.z)); } } } @@ -414,16 +416,18 @@ namespace Debug { for (int i = 0; i < level.entitiesCount; i++) { TR::Entity &e = level.entities[i]; + Controller *controller = (Controller*)e.controller; + if (!controller) return; sprintf(buf, "%d (%d)", (int)e.type, i); - Debug::Draw::text(vec3(e.x, e.y, e.z), e.flags.active ? vec4(0, 0, 0.8, 1) : vec4(0.8, 0, 0, 1), buf); + Debug::Draw::text(controller->pos, controller->flags.active ? vec4(0, 0, 0.8f, 1) : vec4(0.8f, 0, 0, 1), buf); } for (int i = 0; i < level.camerasCount; i++) { TR::Camera &c = level.cameras[i]; sprintf(buf, "%d (%d)", i, c.room); - Debug::Draw::text(vec3(c.x, c.y, c.z), vec4(0, 0.8, 0, 1), buf); + Debug::Draw::text(vec3(float(c.x), float(c.y), float(c.z)), vec4(0, 0.8f, 0, 1), buf); } } @@ -445,21 +449,23 @@ namespace Debug { Core::setDepthTest(true); } - void zones(const TR::Level &level, Lara *lara) { + void zones(IGame *game, Lara *lara) { + TR::Level *level = game->getLevel(); + Core::setDepthTest(false); - for (int i = 0; i < level.roomsCount; i++) - sectors(level, i, int(lara->pos.y), lara->zone); + for (int i = 0; i < level->roomsCount; i++) + sectors(game, i, int(lara->pos.y), lara->zone); Core::setDepthTest(true); char buf[64]; - for (int i = 0; i < level.entitiesCount; i++) { - TR::Entity &e = level.entities[i]; + for (int i = 0; i < level->entitiesCount; i++) { + TR::Entity &e = level->entities[i]; - if (e.type < TR::Entity::LARA || e.type > TR::Entity::ENEMY_GIANT_MUTANT) - continue; + if (!e.controller || !e.isEnemy()) continue; - sprintf(buf, "zone: %d", ((Character*)e.controller)->zone ); - Debug::Draw::text(vec3(e.x, e.y - 128, e.z), vec4(0, 1.0, 0.8, 1), buf); + Character *controller = (Character*)e.controller; + sprintf(buf, "zone: %d", controller->zone); + Debug::Draw::text(controller->pos - vec3(0, 128, 0), vec4(0, 1, 0.8f, 1), buf); } } @@ -469,14 +475,14 @@ namespace Debug { for (int j = 0; j < level.rooms[i].lightsCount; j++) { TR::Room::Light &l = level.rooms[i].lights[j]; float a = 1.0f - intensityf(l.intensity); - vec3 p = vec3(l.x, l.y, l.z); + vec3 p = vec3(float(l.x), float(l.y), float(l.z)); vec4 color = vec4(a, a, a, 1); // if (i == room) color.x = color.z = 0; Debug::Draw::point(p, color); //if (i == roomIndex && j == lightIndex) // color = vec4(0, 1, 0, 1); - Debug::Draw::sphere(p, l.radius, color); + Debug::Draw::sphere(p, float(l.radius), color); } vec4 color = vec4(lara->mainLightColor.x, 0.0f, 0.0f, 1.0f); @@ -494,7 +500,7 @@ namespace Debug { TR::StaticMesh *sm = &level.staticMeshes[m.meshIndex]; Box box; - vec3 offset = vec3(m.x, m.y, m.z); + vec3 offset = vec3(float(m.x), float(m.y), float(m.z)); sm->getBox(false, m.rotation, box); // visible box Debug::Draw::box(offset + box.min, offset + box.max, vec4(1, 1, 0, 0.25)); @@ -525,7 +531,7 @@ namespace Debug { mat4 matrix = controller->getMatrix(); Basis basis(matrix); - TR::Model *m = controller->getModel(); + const TR::Model *m = controller->getModel(); if (!m) continue; bool bboxIntersect = false; @@ -535,8 +541,9 @@ namespace Debug { int mask = 0; for (int j = 0; j < level.entitiesCount; j++) { TR::Entity &t = level.entities[j]; - if (j == i || ((!t.isEnemy() || !t.flags.active) && t.type != TR::Entity::LARA)) continue; Controller *enemy = (Controller*)t.controller; + if (!enemy) continue; + if (j == i || ((!t.isEnemy() || !enemy->flags.active) && !t.isLara())) continue; if (!enemy || !controller->getBoundingBox().intersect(enemy->getBoundingBox())) continue; bboxIntersect = true; @@ -677,7 +684,8 @@ namespace Debug { Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); TR::Level::FloorInfo info; - level.getFloorInfo(((Controller*)entity.controller)->getRoomIndex(), entity.x, entity.y, entity.z, info); + Controller *controller = (Controller*)entity.controller; + controller->getFloorInfo(controller->getRoomIndex(), controller->pos, 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, y += 16), vec4(1.0f), buf); diff --git a/src/enemy.h b/src/enemy.h index 4f2869a..a30f8da 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -77,12 +77,21 @@ struct Enemy : Character { delete path; } + virtual void getSaveData(SaveData &data) { + Character::getSaveData(data); + data.extraSize = sizeof(data.extra.enemy); + data.extra.enemy.health = uint16(health); + data.extra.enemy.mood = uint16(mood); + } + + virtual void setSaveData(const SaveData &data) { + Character::setSaveData(data); + health = float(data.extra.enemy.health); + mood = Mood(data.extra.enemy.mood); + } + virtual bool activate() { - if (health > 0.0f && Character::activate()) { - target = (Character*)game->getLara(); - return true; - } - return false; + return health > 0.0f && Character::activate(); } virtual void updateVelocity() { @@ -136,7 +145,7 @@ struct Enemy : Character { } virtual void updatePosition() { - if (!getEntity().flags.active) return; + if (!flags.active) return; vec3 p = pos; pos += velocity * (30.0f * Core::deltaTime); @@ -144,10 +153,10 @@ struct Enemy : Character { clipByBox(pos); TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); if (stand == STAND_AIR && !flying && info.floor < pos.y) { stand = STAND_GROUND; - pos.y = float(info.floor); + pos.y = info.floor; } if (info.boxIndex != 0xFFFF && zone == getZones()[info.boxIndex] && !level->boxes[info.boxIndex].overlap.block) { @@ -158,14 +167,13 @@ struct Enemy : Character { break; } case STAND_AIR : - pos.y = clamp(pos.y, float(info.ceiling), float(info.floor)); + pos.y = clamp(pos.y, info.ceiling, info.floor); break; default : ; } } else pos = p; - updateEntity(); checkRoom(); } @@ -223,7 +231,6 @@ struct Enemy : Character { speed *= Core::deltaTime; decrease(delta, pos.y, speed); if (speed != 0.0f) { - updateEntity(); return speed < 0 ? FORTH : BACK; } return 0; @@ -239,7 +246,7 @@ struct Enemy : Character { void bite(const vec3 &pos, float damage) { ASSERT(target); target->hit(damage, this); - Sprite::add(game, TR::Entity::BLOOD, target->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED); + game->addEntity(TR::Entity::BLOOD, target->getRoomIndex(), pos); } #define STALK_BOX (1024 * 3) @@ -280,6 +287,9 @@ struct Enemy : Character { } bool think(bool fixedLogic) { + if (!target) + target = (Character*)game->getLara(); + thinkTime += Core::deltaTime; if (thinkTime < 1.0f / 30.0f) return false; @@ -398,9 +408,8 @@ struct Enemy : Character { if (zone != getZones()[box]) return false; - TR::Entity &e = getEntity(); TR::Box &b = game->getLevel()->boxes[box]; - TR::Entity::Type type = e.type; + TR::Entity::Type type = getEntity().type; if (b.overlap.block) return false; @@ -412,17 +421,16 @@ struct Enemy : Character { if (b.overlap.block) return false; - return e.x < int(b.minX) || e.x > int(b.maxX) || e.z < int(b.minZ) || e.z > int(b.maxZ); + return int(pos.x) < int(b.minX) || int(pos.x) > int(b.maxX) || int(pos.z) < int(b.minZ) || int(pos.z) > int(b.maxZ); } bool isStalkBox(int box) { - TR::Entity &t = target->getEntity(); TR::Box &b = game->getLevel()->boxes[box]; - int x = (b.minX + b.maxX) / 2 - t.x; + int x = (b.minX + b.maxX) / 2 - int(target->pos.x); if (abs(x) > STALK_BOX) return false; - int z = (b.minZ + b.maxZ) / 2 - t.z; + int z = (b.minZ + b.maxZ) / 2 - int(target->pos.z); if (abs(z) > STALK_BOX) return false; // TODO: check for some quadrant shit @@ -431,17 +439,15 @@ struct Enemy : Character { } bool isEscapeBox(int box) { - TR::Entity &e = getEntity(); - TR::Entity &t = target->getEntity(); TR::Box &b = game->getLevel()->boxes[box]; - int x = (b.minX + b.maxX) / 2 - t.x; + int x = (b.minX + b.maxX) / 2 - int(target->pos.x); if (abs(x) < ESCAPE_BOX) return false; - int z = (b.minZ + b.maxZ) / 2 - t.z; + int z = (b.minZ + b.maxZ) / 2 - int(target->pos.z); if (abs(z) < ESCAPE_BOX) return false; - return !((e.x > t.x) ^ (x > 0)) || !((e.z > t.z) ^ (z > 0)); + return !((pos.x > target->pos.x) ^ (x > 0)) || !((pos.z > target->pos.z) ^ (z > 0)); } bool findPath(int ascend, int descend, bool big) { @@ -506,8 +512,7 @@ struct Wolf : Enemy { } virtual int getStateGround() { - TR::Entity &e = getEntity(); - if (!e.flags.active) + if (!flags.active) return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP; if (!think(false)) @@ -672,7 +677,7 @@ struct Bear : Enemy { } virtual int getStateGround() { - if (!getEntity().flags.active) + if (!flags.active) return state; if (!think(true)) @@ -808,7 +813,7 @@ struct Bat : Enemy { } virtual int getStateAir() { - if (!getEntity().flags.active) { + if (!flags.active) { animation.time = 0.0f; animation.dir = 0.0f; return STATE_AWAKE; @@ -854,10 +859,10 @@ struct Bat : Enemy { virtual void deactivate(bool removeFromList = false) { if (health <= 0.0f) { TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); if (info.floor > pos.y) return; - pos.y = float(info.floor); + pos.y = info.floor; } Enemy::deactivate(removeFromList); } @@ -896,7 +901,7 @@ struct Rex : Enemy { } virtual int getStateGround() { - if (!getEntity().flags.active) + if (!flags.active) return state; if (!think(true)) @@ -1015,7 +1020,7 @@ struct Raptor : Enemy { } virtual int getStateGround() { - if (!getEntity().flags.active) + if (!flags.active) return state; if (!think(true)) @@ -1098,8 +1103,7 @@ 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) { + if (getEntity().type != TR::Entity::ENEMY_MUTANT_1) { initMeshOverrides(); layers[0].mask = 0xffe07fff; aggression = 0.25f; @@ -1122,7 +1126,7 @@ struct Mutant : Enemy { if (exploded && !explodeMask) { deactivate(true); - getEntity().flags.invisible = true; + flags.invisible = true; } } @@ -1187,7 +1191,7 @@ struct GiantMutant : Enemy { if (exploded && !explodeMask) { game->checkTrigger(this, true); deactivate(true); - getEntity().flags.invisible = true; + flags.invisible = true; } } }; @@ -1273,11 +1277,10 @@ struct Doppelganger : Enemy { angle.y -= PI; } - updateEntity(); Enemy::checkRoom(); TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); if (stand != STAND_AIR && lara->stand == Character::STAND_GROUND && pos.y < info.floor - 1024) { animation = Animation(level, lara->getModel()); @@ -1289,7 +1292,7 @@ struct Doppelganger : Enemy { if (stand == STAND_AIR) { if (pos.y > info.floor) { game->checkTrigger(this, true); - getEntity().flags.invisible = true; + flags.invisible = true; deactivate(true); } else { updateAnimation(true); @@ -1327,7 +1330,7 @@ struct ScionTarget : Enemy { if (health <= 0.0f) { if (timer == 0.0f) { - getEntity().flags.invisible = true; + flags.invisible = true; game->checkTrigger(this, true); timer = 3.0f; } @@ -1338,8 +1341,7 @@ struct ScionTarget : Enemy { if (index != int(timer / 0.3f)) { vec3 p = pos + vec3((randf() * 2.0f - 1.0f) * 512.0f, (randf() * 2.0f - 1.0f) * 64.0f - 500.0f, (randf() * 2.0f - 1.0f) * 512.0f); - game->addSprite(TR::Entity::EXPLOSION, getRoomIndex(), int(p.x), int(p.y), int(p.z)); - game->playSound(TR::SND_EXPLOSION, pos, 0); + game->addEntity(TR::Entity::EXPLOSION, getRoomIndex(), p); game->getCamera()->shake = 0.5f; } diff --git a/src/format.h b/src/format.h index be0e3ac..28d5e0f 100644 --- a/src/format.h +++ b/src/format.h @@ -333,6 +333,7 @@ namespace TR { enum HitType { HIT_DEFAULT, HIT_FALL, + HIT_DART, HIT_BLADE, HIT_BOULDER, HIT_SPIKES, @@ -420,6 +421,7 @@ namespace TR { uint16 value; angle() {} + angle(uint16 value) : value(value) {} angle(float value) : value(uint16(value / (PI * 0.5f) * 16384.0f)) {} operator float() const { return value / 16384.0f * PI * 0.5f; }; }; @@ -644,15 +646,16 @@ namespace TR { }; struct Entity { + enum ActiveState : uint16 { asNone, asActive, asInactive }; + enum Type : int16 { NONE = -1, TR1_TYPES(DECL_ENUM) }; - enum Type : int16 { NONE = -1, TR1_TYPES(DECL_ENUM) } type; - + Type type; int16 room; int32 x, y, z; angle rotation; int16 intensity; union Flags { - struct { uint16 unused:6, collision:1, invisible:1, once:1, active:5, reverse:1, rendered:1; }; + struct { ActiveState state:2; uint16 unused:4, collision:1, invisible:1, once:1, active:5, reverse:1, rendered:1; }; uint16 value; } flags; // not exists in file @@ -704,6 +707,12 @@ namespace TR { return type == LARA; } + bool isSprite() const { + return type == EXPLOSION || type == WATER_SPLASH || type == BUBBLE || + type == BLOOD || type == SMOKE || type == FLAME || + type == RICOCHET || type == SPARKLES || type == LAVA_PARTICLE; + } + bool castShadow() const { return isLara() || isEnemy() || isActor() || type == DART || type == TRAP_SWORD; } @@ -1158,9 +1167,9 @@ namespace TR { }; struct FloorInfo { - int roomFloor, roomCeiling; + float roomFloor, roomCeiling; int roomNext, roomBelow, roomAbove; - int floor, ceiling; + float floor, ceiling; int slantX, slantZ; int floorIndex; int boxIndex; @@ -2096,16 +2105,16 @@ namespace TR { return 0; } - int entityAdd(Entity::Type type, int16 room, int32 x, int32 y, int32 z, angle rotation, int16 intensity) { + int entityAdd(Entity::Type type, int16 room, const vec3 &pos, float rotation, int16 intensity) { for (int i = entitiesBaseCount; i < entitiesCount; i++) if (entities[i].type == Entity::NONE) { Entity &e = entities[i]; e.type = type; e.room = room; - e.x = x; - e.y = y; - e.z = z; - e.rotation = rotation; + e.x = int(pos.x); + e.y = int(pos.y); + e.z = int(pos.z); + e.rotation = angle(normalizeAngle(rotation)); e.intensity = intensity; e.flags.value = 0; e.modelIndex = getModelIndex(e.type); @@ -2162,7 +2171,7 @@ namespace TR { return room.sectors[sectorIndex = (x * room.zSectors + z)]; } - Room::Sector* getSector(int &roomIndex, int x, int y, int z) const { + Room::Sector* getSector(int16 &roomIndex, int x, int y, int z) const { ASSERT(roomIndex >= 0 && roomIndex <= roomsCount); Room::Sector *sector = NULL; @@ -2202,223 +2211,6 @@ namespace TR { return sector; } - - void getFloorInfo(int roomIndex, int x, int y, int z, FloorInfo &info) const { - int dx, dz; - Room::Sector &s = getSector(roomIndex, x, z, dx, dz); - - info.roomFloor = 256 * s.floor; - info.roomCeiling = 256 * s.ceiling; - info.floor = info.roomFloor; - info.ceiling = info.roomCeiling; - info.slantX = 0; - info.slantZ = 0; - info.roomNext = NO_ROOM; - info.roomBelow = s.roomBelow; - info.roomAbove = s.roomAbove; - info.floorIndex = s.floorIndex; - info.boxIndex = s.boxIndex; - info.lava = false; - info.trigger = Trigger::ACTIVATE; - info.trigCmdCount = 0; - - if (s.floor == NO_FLOOR) - return; - - Room::Sector *sBelow = &s; - while (sBelow->roomBelow != NO_ROOM) sBelow = &getSector(sBelow->roomBelow, x, z, dx, dz); - info.floor = 256 * sBelow->floor; - - parseFloorData(info, sBelow->floorIndex, dx, dz); - - if (info.roomNext == NO_ROOM) { - Room::Sector *sAbove = &s; - while (sAbove->roomAbove != NO_ROOM) sAbove = &getSector(sAbove->roomAbove, x, z, dx, dz); - if (sAbove != sBelow) { - info.ceiling = 256 * sAbove->ceiling; - parseFloorData(info, sAbove->floorIndex, dx, dz); - } - } else { - int tmp = info.roomNext; - getFloorInfo(tmp, x, y, z, info); - info.roomNext = tmp; - } - - - // entities collide - if (info.trigCmdCount) { - int sx = x / 1024; - int sz = z / 1024; - int dx = x % 1024; - int dz = z % 1024; - - for (int i = 0; i < info.trigCmdCount; i++) { - FloorData::TriggerCommand cmd = info.trigCmd[i]; - if (cmd.action == Action::CAMERA_SWITCH) { - i++; - continue; - } - if (cmd.action != Action::ACTIVATE) continue; - - Entity &e = entities[cmd.args]; - if (!e.flags.collision) continue; - - switch (e.type) { - case Entity::TRAP_DOOR_1 : - case Entity::TRAP_DOOR_2 : { - int dirX, dirZ; - e.getAxis(dirX, dirZ); - - int ex = e.x / 1024; - int ez = e.z / 1024; - if ((ex == sx && ez == sz) || (ex + dirX == sx && ez + dirZ == sz)) { - if (e.y >= y - 128 && e.y < info.floor) { - info.floor = e.y; - info.slantX = info.slantZ = 0; - info.lava = false; - } - if (e.y < y - 128 && e.y > info.ceiling) - info.ceiling = e.y + 256; - } - break; - } - case Entity::TRAP_FLOOR : { - if (sx != e.x / 1024 || sz != e.z / 1024) - break; - int ey = e.y - 512; - if (ey >= y - 128 && ey < info.floor) { - info.floor = ey; - info.slantX = info.slantZ = 0; - info.lava = false; - } - if (ey < y - 128 && ey > info.ceiling) - info.ceiling = ey; - break; - } - case Entity::DRAWBRIDGE : { - if (e.flags.active != TR::ACTIVE) continue; - int dirX, dirZ; - e.getAxis(dirX, dirZ); - int ex = e.x / 1024; - int ez = e.z / 1024; - - if ((ex - dirX * 1 == sx && ez - dirZ * 1 == sz) || - (ex - dirX * 2 == sx && ez - dirZ * 2 == sz)) { - int ey = e.y; - if (ey >= y - 128 && ey < info.floor) { - info.floor = ey; - info.slantX = info.slantZ = 0; - info.lava = false; - } - if (ey < y - 128 && ey > info.ceiling) - info.ceiling = ey + 256; - } - break; - } - case Entity::HAMMER_HANDLE : { - int dirX, dirZ; - e.getAxis(dirX, dirZ); - if (abs(e.x + dirX * 1024 * 3 - x) < 512 && abs(e.z + dirZ * 1024 * 3 - z) < 512) - info.floor -= 1024 * 3; - break; - } - case Entity::BRIDGE_0 : - case Entity::BRIDGE_1 : - case Entity::BRIDGE_2 : { - if (sx != e.x / 1024 || sz != e.z / 1024) - break; - - int s = (e.type == Entity::BRIDGE_1) ? 1 : - (e.type == Entity::BRIDGE_2) ? 2 : 0; - - int ey = e.y, sx = 0, sz = 0; - - if (s > 0) { - switch (e.rotation.value / 0x4000) { // get slantXZ by direction - case 0 : sx = s; break; - case 1 : sz = -s; break; - case 2 : sx = -s; break; - case 3 : sz = s; break; - } - - ey -= sx * (sx > 0 ? (dx - 1024) : dx) >> 2; - ey -= sz * (sz > 0 ? (dz - 1024) : dz) >> 2; - } - - if (y - 128 <= ey) { - info.floor = ey; - info.slantX = sx; - info.slantZ = sz; - info.lava = false; - } - if (ey < y - 128) - info.ceiling = ey + 64; - break; - } - - default : ; - } - } - } - } - - void parseFloorData(FloorInfo &info, int floorIndex, int dx, int dz) const { - if (!floorIndex) return; - - FloorData *fd = &floors[floorIndex]; - FloorData::Command cmd; - - do { - cmd = (*fd++).cmd; - - switch (cmd.func) { - - case FloorData::PORTAL : - info.roomNext = (*fd++).data; - break; - - case FloorData::FLOOR : // floor & ceiling - case FloorData::CEILING : { - FloorData::Slant slant = (*fd++).slant; - int sx = (int)slant.x; - int sz = (int)slant.z; - if (cmd.func == FloorData::FLOOR) { - info.slantX = sx; - info.slantZ = sz; - info.floor -= sx * (sx > 0 ? (dx - 1024) : dx) >> 2; - info.floor -= sz * (sz > 0 ? (dz - 1024) : dz) >> 2; - } else { - info.ceiling -= sx * (sx < 0 ? (dx - 1024) : dx) >> 2; - info.ceiling += sz * (sz > 0 ? (dz - 1024) : dz) >> 2; - } - break; - } - - case FloorData::TRIGGER : { - info.trigger = (Trigger)cmd.sub; - info.trigCmdCount = 0; - 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); - break; - } - - case FloorData::LAVA : - info.lava = true; - break; - - default : LOG("unknown func: %d\n", cmd.func); - } - - } while (!cmd.end); - - } - - }; // struct Level } diff --git a/src/game.h b/src/game.h index 3a6923e..3b3dd3f 100644 --- a/src/game.h +++ b/src/game.h @@ -95,6 +95,16 @@ namespace Game { } } + if (Input::down[ikS]) { + level->saveGame(0); + Input::down[ikS] = false; + } + + if (Input::down[ikL]) { + level->loadGame(0); + Input::down[ikL] = false; + } + Core::deltaTime = delta = min(0.2f, delta); UI::update(); diff --git a/src/lara.h b/src/lara.h index 0cf8f8b..b830616 100644 --- a/src/lara.h +++ b/src/lara.h @@ -227,7 +227,7 @@ struct Lara : Character { TR::Entity::Type usedKey; int pickupListCount; - TR::Entity *pickupList[32]; + Controller *pickupList[32]; KeyHole *keyHole; Lightning *lightning; Texture *environment; @@ -325,7 +325,7 @@ struct Lara : Character { void collide() { TR::Level *level = lara->level; - TR::Model *model = lara->getModel(); + const TR::Model *model = lara->getModel(); #define BRAID_RADIUS 16.0f @@ -421,7 +421,7 @@ struct Lara : Character { lightning = NULL; environment = NULL; - getEntity().flags.active = 1; + flags.active = 1; initMeshOverrides(); if (level->isHomeLevel) @@ -492,7 +492,7 @@ struct Lara : Character { dbgBoxes = NULL; #endif - if (getEntity().type == TR::Entity::LARA) { + if (getEntity().isLara()) { if (getRoom().flags.water) animation.setAnim(ANIM_UNDERWATER); else @@ -507,6 +507,35 @@ struct Lara : Character { delete environment; } + virtual void getSaveData(SaveData &data) { + Character::getSaveData(data); + data.extraSize = sizeof(data.extra.lara); + data.extra.lara.velX = velocity.x; + data.extra.lara.velY = velocity.y; + data.extra.lara.velZ = velocity.z; + data.extra.lara.angleX = TR::angle(normalizeAngle(angle.x)).value; + data.extra.lara.health = uint16(health); + data.extra.lara.oxygen = uint16(oxygen); + data.extra.lara.curWeapon = int8(wpnCurrent); + data.extra.lara.emptyHands = emptyHands(); + } + + virtual void setSaveData(const SaveData &data) { + Character::setSaveData(data); + velocity = vec3(data.extra.lara.velX, data.extra.lara.velY, data.extra.lara.velZ); + angle.x = TR::angle(data.extra.lara.angleX); + health = float(data.extra.lara.health); + oxygen = float(data.extra.lara.oxygen); + + layers[1].mask = layers[2].mask = layers[3].mask = 0; + wpnState = Weapon::IS_HIDDEN; + wpnCurrent = Weapon::EMPTY; + + wpnSet(Weapon::Type(data.extra.lara.curWeapon)); + if (!data.extra.lara.emptyHands) + wpnDraw(true); + } + int getRoomByPos(const vec3 &pos) { int x = int(pos.x), y = int(pos.y), @@ -543,9 +572,9 @@ struct Lara : Character { velocity = vec3(0.0f); - getEntity().room = room; - this->pos = pos; - this->angle = vec3(0.0f, angle, 0.0f); + roomIndex = room; + this->pos = pos; + this->angle = vec3(0.0f, angle, 0.0f); if (forceStand != STAND_GROUND) { stand = forceStand; @@ -556,7 +585,6 @@ struct Lara : Character { } } - updateEntity(); updateLights(false); } @@ -732,15 +760,15 @@ struct Lara : Character { return arms[0].anim != Weapon::Anim::PREPARE && arms[0].anim != Weapon::Anim::UNHOLSTER && arms[0].anim != Weapon::Anim::HOLSTER; } - void wpnDraw() { + void wpnDraw(bool instant = false) { if (!canDrawWeapon()) return; if (wpnReady() && emptyHands()) { 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); + wpnSetAnim(arms[0], wpnState, instant ? Weapon::Anim::AIM : Weapon::Anim::PREPARE, 0.0f, 1.0f); + wpnSetAnim(arms[1], wpnState, instant ? Weapon::Anim::AIM : Weapon::Anim::PREPARE, 0.0f, 1.0f); } else - wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); + wpnSetAnim(arms[0], wpnState, instant ? Weapon::Anim::AIM : Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); } } @@ -868,10 +896,10 @@ struct Lara : Character { ((Character*)arm->target)->hit(wpnGetDamage(), this); hit -= d * 64.0f; if (type != TR::Entity::SCION_TARGET) - Sprite::add(game, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_ANIMATED); + game->addEntity(TR::Entity::BLOOD, room, hit); } else { hit -= d * 64.0f; - Sprite::add(game, TR::Entity::RICOCHET, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_RANDOM); + game->addEntity(TR::Entity::RICOCHET, room, hit); float dist = (hit - p).length(); if (dist < nearDist) { @@ -1225,7 +1253,7 @@ struct Lara : Character { Controller *c = Controller::first; do { - if (!level->entities[c->entity].isEnemy()) + if (!c->getEntity().isEnemy()) continue; Character *enemy = (Character*)c; @@ -1318,11 +1346,8 @@ struct Lara : Character { if (!count) return; game->playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN); vec3 head = animation.getJoints(getMatrix(), 14, true) * vec3(0.0f, 0.0f, 50.0f); - for (int i = 0; i < count; i++) { - int index = Sprite::add(game, TR::Entity::BUBBLE, getRoomIndex(), int(head.x), int(head.y), int(head.z), Sprite::FRAME_RANDOM, true); - if (index > -1) - level->entities[index].controller = new Bubble(game, index); - } + for (int i = 0; i < count; i++) + game->addEntity(TR::Entity::BUBBLE, getRoomIndex(), head, 0); } virtual void cmdEffect(int fx) { @@ -1342,14 +1367,14 @@ struct Lara : Character { 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; - Sprite::add(game, TR::Entity::SPARKLES, getRoomIndex(), int(sprPos.x), int(sprPos.y), int(sprPos.z), Sprite::FRAME_ANIMATED); + game->addEntity(TR::Entity::SPARKLES, getRoomIndex(), sprPos); } } void addBlood(const vec3 &sprPos, const vec3 &sprVel) { - int index = Sprite::add(game, TR::Entity::BLOOD, getRoomIndex(), int(sprPos.x), int(sprPos.y), int(sprPos.z), Sprite::FRAME_ANIMATED); - if (index > -1) - ((Sprite*)level->entities[index].controller)->velocity = 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) { @@ -1373,14 +1398,14 @@ struct Lara : Character { } void bakeEnvironment() { - getEntity().flags.invisible = true; + flags.invisible = true; if (!environment) environment = new Texture(256, 256, Texture::RGBA, true, NULL, true, true); Core::beginFrame(); game->renderEnvironment(getRoomIndex(), pos - vec3(0.0f, 384.0f, 0.0f), &environment, 0, Core::passCompose); environment->generateMipMap(); Core::endFrame(); - getEntity().flags.invisible = false; + flags.invisible = false; } virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) { @@ -1393,6 +1418,7 @@ struct Lara : Character { Character::hit(damage, enemy, hitType); switch (hitType) { + case TR::HIT_DART : addBlood(enemy->pos, vec3(0)); case TR::HIT_BLADE : addBloodBlade(); break; case TR::HIT_SPIKES : addBloodSpikes(); break; case TR::HIT_SWORD : addBloodBlade(); break; @@ -1425,7 +1451,7 @@ struct Lara : Character { if (enemy && enemy->getEntity().type == TR::Entity::TRAP_BOULDER) { angle = enemy->angle; TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); vec3 d = getDir(); v = info.getSlant(d); float dp = d.dot(v); @@ -1468,11 +1494,10 @@ struct Lara : Character { } if (hitType != TR::HIT_LAVA) { - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); - if (info.lava && info.floor == e.y) + if (info.lava && info.floor == pos.y) hitType = TR::HIT_LAVA; } @@ -1533,22 +1558,21 @@ struct Lara : Character { vec3 dst = pos + getDir() * (LARA_RADIUS + 32.0f); TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); int roomAbove = info.roomAbove; if (roomAbove == TR::NO_ROOM) return false; - level->getFloorInfo(roomAbove, int(dst.x), int(dst.y), int(dst.z), info); + getFloorInfo(roomAbove, dst, info); int h = int(pos.y - info.floor); if (h >= 0 && h <= (256 + 128) && (state == STATE_SURF_TREAD || animation.setState(STATE_SURF_TREAD)) && animation.setState(STATE_STOP)) { alignToWall(LARA_RADIUS); roomPrev = getRoomIndex(); - getEntity().room = roomAbove; - pos.y = float(info.floor); + roomIndex = roomAbove; + pos.y = info.floor; specular = LARA_WET_SPECULAR; - updateEntity(); move(); return true; } @@ -1576,12 +1600,13 @@ struct Lara : Character { continue; Controller *controller = (Controller*)entity.controller; + if (!controller) continue; - if (controller->getRoomIndex() != room || entity.flags.invisible || !canPickup(controller)) + if (controller->getRoomIndex() != room || controller->flags.invisible || !canPickup(controller)) continue; ASSERT(pickupListCount < COUNT(pickupList)); - pickupList[pickupListCount++] = &entity; + pickupList[pickupListCount++] = controller; } if (pickupListCount > 0) { @@ -1593,11 +1618,11 @@ struct Lara : Character { } bool canPickup(Controller *controller) { - TR::Entity &entity = controller->getEntity(); + TR::Entity::Type type = controller->getEntity().type; // get limits TR::Limits::Limit *limit; - switch (entity.type) { + switch (type) { case TR::Entity::SCION_PICKUP_QUALOPEC : limit = &TR::Limits::SCION; break; case TR::Entity::SCION_PICKUP_HOLDER : limit = &TR::Limits::SCION_HOLDER; break; default : limit = level->rooms[getRoomIndex()].flags.water ? &TR::Limits::PICKUP_UNDERWATER : &TR::Limits::PICKUP; @@ -1610,7 +1635,7 @@ struct Lara : Character { angle.x = -25 * DEG2RAD; // set new state - switch (entity.type) { + switch (type) { case TR::Entity::SCION_PICKUP_QUALOPEC : animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation); game->getCamera()->doCutscene(pos, angle.y); @@ -1710,11 +1735,10 @@ struct Lara : Character { } void checkTrigger(Controller *controller, bool heavy) { - TR::Entity &e = controller->getEntity(); TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), controller->pos, info); - if (e.type == TR::Entity::LARA && info.lava && info.floor == e.y) { + if (getEntity().isLara() && info.lava && info.floor == pos.y) { hit(LARA_MAX_HEALTH + 1, NULL, TR::HIT_LAVA); return; } @@ -1733,7 +1757,7 @@ struct Lara : Character { case TR::Level::Trigger::SWITCH : { Switch *controller = (Switch*)level->entities[info.trigCmd[cmdIndex++].args].controller; - if (controller->activeState == asNone) { + if (controller->flags.state == TR::Entity::asNone) { limit = state == STATE_STOP ? &TR::Limits::SWITCH : &TR::Limits::SWITCH_UNDERWATER; if (checkInteraction(controller, limit, Input::state[cAction])) { actionState = (controller->state == Switch::STATE_DOWN && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; @@ -1753,8 +1777,8 @@ struct Lara : Character { TR::Entity &entity = level->entities[info.trigCmd[cmdIndex++].args]; KeyHole *controller = (KeyHole*)entity.controller; - if (controller->activeState == asNone) { - if (entity.flags.active == TR::ACTIVE || state != STATE_STOP) + if (controller->flags.state == TR::Entity::asNone) { + if (controller->flags.active == TR::ACTIVE || state != STATE_STOP) return; actionState = entity.isPuzzleHole() ? STATE_USE_PUZZLE : STATE_USE_KEY; @@ -1782,7 +1806,7 @@ struct Lara : Character { animation.setState(actionState); } - if (controller->activeState != asInactive) + if (controller->flags.state != TR::Entity::asInactive) return; break; @@ -1799,7 +1823,7 @@ struct Lara : Character { case TR::Level::Trigger::PAD : case TR::Level::Trigger::ANTIPAD : - if (e.y != info.floor) return; + if (pos.y != info.floor) return; break; case TR::Level::Trigger::HEAVY : @@ -1822,11 +1846,13 @@ struct Lara : Character { switch (cmd.action) { case TR::Action::ACTIVATE : { TR::Entity &e = level->entities[cmd.args]; - TR::Entity::Flags &flags = e.flags; + Controller *controller = (Controller*)e.controller; + ASSERT(controller); + TR::Entity::Flags &flags = controller->flags; if (flags.once) break; - ((Controller*)e.controller)->timer = timer; + controller->timer = timer; if (info.trigger == TR::Level::Trigger::SWITCH) flags.active ^= info.trigInfo.mask; @@ -1840,7 +1866,7 @@ struct Lara : Character { flags.once |= info.trigInfo.once; - ((Controller*)e.controller)->activate(); + controller->activate(); break; } case TR::Action::CAMERA_SWITCH : { @@ -1959,7 +1985,6 @@ struct Lara : Character { animation.setAnim(ANIM_FALL_HANG); velocity = vec3(0.0f); pos.y += 128.0f; - updateEntity(); return STAND_AIR; } @@ -1981,14 +2006,12 @@ struct Lara : Character { return STAND_UNDERWATER; } - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); if ((stand == STAND_SLIDE || stand == STAND_GROUND) && (state != STATE_FORWARD_JUMP && state != STATE_BACK_JUMP)) { - if (e.y + 8 >= info.floor && (abs(info.slantX) > 2 || abs(info.slantZ) > 2)) { - pos.y = float(info.floor); - updateEntity(); + if (pos.y + 8 >= info.floor && (abs(info.slantX) > 2 || abs(info.slantZ) > 2)) { + pos.y = info.floor; if (stand == STAND_GROUND) slideStart(); @@ -1999,10 +2022,9 @@ struct Lara : Character { int extra = (stand != STAND_AIR && stand != STAND_SLIDE) ? 256 : 0; - if (e.y + extra >= info.floor && !(stand == STAND_AIR && velocity.y < 0)) { + if (pos.y + extra >= info.floor && !(stand == STAND_AIR && velocity.y < 0)) { if (stand != STAND_GROUND) { - pos.y = float(info.floor); - updateEntity(); + pos.y = info.floor; // get damage from falling if (velocity.y > 0.0f) { stopScreaming(); @@ -2052,27 +2074,25 @@ struct Lara : Character { vec3 p = vec3(pos.x, bounds.min.y, pos.z); - Collision c = Collision(level, getRoomIndex(), p, getDir() * 128.0f, vec3(0.0f), LARA_RADIUS, angleExt, 0, 0, 0, 0); + Collision c = Collision(this, getRoomIndex(), p, getDir() * 128.0f, vec3(0.0f), LARA_RADIUS, angleExt, 0, 0, 0, 0); if (c.side != Collision::FRONT) return state; - int floor = c.info[Collision::FRONT].floor; - int ceiling = c.info[Collision::FRONT].ceiling; - int hands = int(bounds.min.y); + float floor = c.info[Collision::FRONT].floor; + float ceiling = c.info[Collision::FRONT].ceiling; + float hands = bounds.min.y; if (abs(floor - hands) < 64 && floor != ceiling) { alignToWall(-LARA_RADIUS); pos.y = float(floor + LARA_HANG_OFFSET); stand = STAND_HANG; - updateEntity(); if (state == STATE_REACH) { velocity = vec3(0.0f); - vec3 p = pos + getDir() * 256.0f; TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(p.x), int(p.y), int(p.z), info); - int h = info.ceiling - floor; + getFloorInfo(getRoomIndex(), pos + getDir() * 256.0f, info); + int h = int(info.ceiling - floor); return animation.setAnim((h > 0 && h < 400) ? ANIM_HANG_SWING : ANIM_HANG); } else return animation.setAnim(ANIM_HANG, -15); @@ -2140,10 +2160,10 @@ struct Lara : Character { return state; if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands() && collision.side == Collision::FRONT) { // TODO: get rid of animation.index - int floor = collision.info[Collision::FRONT].floor; - int ceiling = collision.info[Collision::FRONT].ceiling; + float floor = collision.info[Collision::FRONT].floor; + float ceiling = collision.info[Collision::FRONT].ceiling; - int h = (int)pos.y - floor; + float h = pos.y - floor; int aIndex = animation.index; if (floor == ceiling || h < 256) @@ -2181,7 +2201,7 @@ struct Lara : Character { if (res != STATE_UP_JUMP) { vec3 p = pos; - collision = Collision(level, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 2.5f, ext, 0, LARA_HEIGHT, 256 + 128, 0xFFFFFF); + collision = Collision(this, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 2.5f, ext, 0, LARA_HEIGHT, 256 + 128, 0xFFFFFF); if (collision.side == Collision::FRONT) res = STATE_UP_JUMP; } @@ -2228,7 +2248,7 @@ struct Lara : Character { if (state == STATE_STOP && res != STATE_STOP) { vec3 p = pos; - collision = Collision(level, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 1.1f, ext, 0, LARA_HEIGHT, maxAscent, maxDescent); + collision = Collision(this, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 1.1f, ext, 0, LARA_HEIGHT, maxAscent, maxDescent); if (collision.side == Collision::FRONT) res = STATE_STOP; } @@ -2264,7 +2284,7 @@ struct Lara : Character { if (state == STATE_STOP && res != STATE_STOP) { float ext = angle.y + (res == STATE_RUN ? 0.0f : PI); vec3 p = pos; - collision = Collision(level, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 1.1f, ext, 0, LARA_HEIGHT, 256 + 128, 0xFFFFFF); + collision = Collision(this, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 1.1f, ext, 0, LARA_HEIGHT, 256 + 128, 0xFFFFFF); if (collision.side == Collision::FRONT) res = STATE_STOP; } @@ -2273,11 +2293,9 @@ struct Lara : Character { } void slideStart() { - TR::Entity &e = getEntity(); - if (state != STATE_SLIDE && state != STATE_SLIDE_BACK) { TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); int sx = abs(info.slantX), sz = abs(info.slantZ); // get direction @@ -2295,7 +2313,6 @@ struct Lara : Character { } angle.y = dir; - updateEntity(); animation.setAnim(aIndex); } } @@ -2312,8 +2329,7 @@ struct Lara : Character { if (input & FORTH) { // possibility check TR::Level::FloorInfo info; - vec3 p = pos + getDir() * (LARA_RADIUS + 2.0f); - level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.y, (int)p.z, info); + getFloorInfo(getRoomIndex(), pos + getDir() * (LARA_RADIUS + 2.0f), info); if (info.floor - info.ceiling >= LARA_HEIGHT) return (input & WALK) ? STATE_HANDSTAND : STATE_HANG_UP; } @@ -2326,7 +2342,7 @@ struct Lara : Character { 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 || state == STATE_SLIDE || state == STATE_SLIDE_BACK) { game->waterDrop(pos, 256.0f, 0.2f); - Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z); + game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), pos); pos.y += 100.0f; angle.x = -45.0f * DEG2RAD; return animation.setAnim(ANIM_WATER_FALL); // TODO: wronng animation @@ -2335,7 +2351,7 @@ struct Lara : Character { if (state == STATE_SWAN_DIVE || state == STATE_FAST_DIVE) { angle.x = -PI * 0.5f; game->waterDrop(pos, 128.0f, 0.2f); - Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z); + game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), pos); return STATE_DIVE; } @@ -2493,10 +2509,10 @@ struct Lara : Character { int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; if (animation.isFrameActive(pickupFrame)) { for (int i = 0; i < pickupListCount; i++) { - if (pickupList[i]->type == TR::Entity::SCION_PICKUP_HOLDER) + if (pickupList[i]->getEntity().type == TR::Entity::SCION_PICKUP_HOLDER) continue; pickupList[i]->flags.invisible = true; - game->invAdd(pickupList[i]->type, 1); + game->invAdd(pickupList[i]->getEntity().type, 1); } pickupListCount = 0; } @@ -2516,7 +2532,7 @@ struct Lara : Character { virtual void update() { Character::update(); - if (getEntity().type != TR::Entity::LARA) + if (!getEntity().isLara()) return; if (damageTime > 0.0f) @@ -2560,7 +2576,7 @@ struct Lara : Character { virtual void updateVelocity() { flowVelocity = vec3(0); - if (getEntity().type != TR::Entity::LARA) + if (!getEntity().isLara()) return; if (!(input & DEATH)) @@ -2650,15 +2666,14 @@ struct Lara : Character { velocity.z = cosf(angleExt) * speed; velocity.y = 0.0f; } else { - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; if (stand == STAND_HANG) { vec3 p = pos + getDir() * (LARA_RADIUS + 2.0f); - level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info); - if (info.roomAbove != TR::NO_ROOM && info.floor >= e.y - LARA_HANG_OFFSET) - level->getFloorInfo(info.roomAbove, (int)p.x, (int)p.y, (int)p.z, info); + getFloorInfo(getRoomIndex(), p, info); + if (info.roomAbove != TR::NO_ROOM && info.floor >= pos.y - LARA_HANG_OFFSET) + getFloorInfo(info.roomAbove, p, info); } else - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); vec3 v(sinf(angleExt), 0.0f, cosf(angleExt)); velocity = info.getSlant(v) * speed; @@ -2683,7 +2698,7 @@ struct Lara : Character { } virtual void updatePosition() { // TODO: sphere / bbox collision - if (getEntity().type != TR::Entity::LARA) + if (!getEntity().isLara()) return; // tilt control vec2 vTilt(LARA_TILT_SPEED * Core::deltaTime, LARA_TILT_MAX); @@ -2697,14 +2712,11 @@ struct Lara : Character { if (checkCollisions() || (velocity + flowVelocity + collisionOffset).length2() >= 1.0f) // TODO: stop & smash anim move(); - if (getEntity().type != TR::Entity::LARA) { - TR::Entity &e = getEntity(); - vec3 &p = getPos(); - e.x = int(p.x); - e.y = int(p.y); - e.z = int(p.z); + if (!getEntity().isLara()) { + vec3 p = pos; + pos = chestOffset; checkRoom(); - updateEntity(); + pos = p; } if (braid) @@ -2712,12 +2724,12 @@ struct Lara : Character { } virtual vec3& getPos() { - return getEntity().type == TR::Entity::LARA ? pos : chestOffset; + return getEntity().isLara() ? pos : chestOffset; } bool checkCollisions() { // check static objects (TODO: check linked rooms?) - TR::Room &room = getRoom(); + const TR::Room &room = getRoom(); Box box(pos - vec3(LARA_RADIUS, LARA_HEIGHT, LARA_RADIUS), pos + vec3(LARA_RADIUS, 0.0f, LARA_RADIUS)); for (int i = 0; i < room.meshesCount; i++) { @@ -2734,19 +2746,18 @@ struct Lara : Character { // check enemies & doors for (int i = 0; i < level->entitiesCount; i++) { - TR::Entity &e = level->entities[i]; + const TR::Entity &e = level->entities[i]; if (!e.isCollider()) continue; Controller *controller = (Controller*)e.controller; if (e.isEnemy()) { - if (e.type != TR::Entity::ENEMY_REX && (e.flags.active != TR::ACTIVE || ((Character*)controller)->health <= 0)) continue; + if (e.type != TR::Entity::ENEMY_REX && (controller->flags.active != TR::ACTIVE || ((Character*)controller)->health <= 0)) continue; } else { // fast distance check for object - TR::Entity &entity = getEntity(); if (e.type != TR::Entity::HAMMER_HANDLE && e.type != TR::Entity::HAMMER_BLOCK && e.type != TR::Entity::SCION_HOLDER) - if (abs(entity.x - e.x) > 1024 || abs(entity.z - e.z) > 1024 || abs(entity.y - e.y) > 2048) continue; + if (abs(pos.x - controller->pos.x) > 1024 || abs(pos.z - controller->pos.z) > 1024 || abs(pos.y - controller->pos.y) > 2048) continue; } vec3 dir = pos - vec3(0.0f, 128.0f, 0.0f) - controller->pos; @@ -2832,7 +2843,7 @@ struct Lara : Character { offset.y += LARA_HEIGHT_WATER * 0.5f; } - collision = Collision(level, room, pos, offset, vel, radius, angleExt, minHeight, maxHeight, maxAscent, maxDescent); + collision = Collision(this, room, pos, offset, vel, radius, angleExt, minHeight, maxHeight, maxAscent, maxDescent); if (!standHang && (collision.side == Collision::LEFT || collision.side == Collision::RIGHT)) { float rot = TURN_WALL_Y * Core::deltaTime; @@ -2845,7 +2856,7 @@ struct Lara : Character { maxDescent = 0xFFFFFF; maxAscent = -LARA_HANG_OFFSET; vec3 p = pos; - collision = Collision(level, room, p, offset, vec3(0.0f), radius, angleExt, minHeight, maxHeight, maxAscent, maxDescent); + collision = Collision(this, room, p, offset, vec3(0.0f), radius, angleExt, minHeight, maxHeight, maxAscent, maxDescent); if (collision.side == Collision::FRONT) pos = opos; } @@ -2869,7 +2880,7 @@ struct Lara : Character { velocity.y = 30.0f; if (collision.side == Collision::FRONT) { - int floor = collision.info[Collision::FRONT].floor; + float floor = collision.info[Collision::FRONT].floor; /* switch (angleQuadrant(angleExt - angle.y)) { case 0 : collision.side = Collision::FRONT; LOG("FRONT\n"); break; @@ -2915,8 +2926,8 @@ struct Lara : Character { } } else { if (stand == STAND_GROUND) { - int floor = collision.info[Collision::NONE].floor; - int h = int(floor - opos.y); + float floor = collision.info[Collision::NONE].floor; + float h = floor - opos.y; if (h >= 128 && (state == STATE_WALK || state == STATE_BACK)) { // descend if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT); @@ -2936,7 +2947,6 @@ struct Lara : Character { collision.side = Collision::NONE; } - updateEntity(); if (dozy) stand = STAND_GROUND; checkRoom(); if (dozy) stand = STAND_UNDERWATER; diff --git a/src/level.h b/src/level.h index e0a4226..8387af1 100644 --- a/src/level.h +++ b/src/level.h @@ -86,11 +86,51 @@ struct Level : IGame { } virtual void loadGame(int slot) { - // + Stream stream("savegame.dat"); + char *data; + stream.read(data, stream.size); + char *ptr = data; + + Controller::first = NULL; + for (int i = level.entitiesBaseCount; i < level.entitiesCount; i++) { + TR::Entity &e = level.entities[i]; + if (e.controller) { + delete (Controller*)e.controller; + e.controller = NULL; + } + } + + for (int i = 0; i < level.entitiesBaseCount; i++) { + ASSERT(int(ptr - data) < stream.size); + Controller *controller = (Controller*)level.entities[i].controller; + ASSERT(controller); + Controller::SaveData *saveData = (Controller::SaveData*)ptr; + controller->setSaveData(*saveData); + if (controller->flags.state != TR::Entity::asNone) { + controller->next = Controller::first; + Controller::first = controller; + } + ptr += (sizeof(Controller::SaveData) - sizeof(Controller::SaveData::Extra)) + saveData->extraSize; + } + delete[] data; + + // camera->room = lara->getRoomIndex(); + // camera->pos = camera->destPos = lara->pos; } virtual void saveGame(int slot) { - // + char *data = new char[sizeof(Controller::SaveData) * level.entitiesBaseCount]; + char *ptr = data; + + for (int i = 0; i < level.entitiesBaseCount; i++) { + Controller::SaveData *saveData = (Controller::SaveData*)ptr; + Controller *controller = (Controller*)level.entities[i].controller; + ASSERT(controller); + controller->getSaveData(*saveData); + ptr += (sizeof(Controller::SaveData) - sizeof(Controller::SaveData::Extra)) + saveData->extraSize; + } + Stream::write("savegame.dat", data, int(ptr - data)); + delete[] data; } virtual void applySettings(const Core::Settings &settings) { @@ -263,24 +303,28 @@ 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 Controller* addEntity(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); + int index = level.entityAdd(type, room, pos, angle, -1); if (index > -1) { TR::Entity &e = level.entities[index]; Controller *controller = initController(index); e.controller = controller; - if (e.isEnemy()) { - e.flags.active = TR::ACTIVE; + + if (e.isEnemy() || e.isSprite()) { + controller->flags.active = TR::ACTIVE; controller->activate(); } + if (e.isPickup()) e.intensity = 4096; - if (e.type == TR::Entity::LAVA_PARTICLE || e.type == TR::Entity::FLAME) - e.intensity = 0; // emissive + else + if (e.isSprite()) { + if (e.type == TR::Entity::LAVA_PARTICLE || e.type == TR::Entity::FLAME) + e.intensity = 0; // emissive + else + e.intensity = 0x1FFF - level.rooms[room].ambient; + } + return controller; } return NULL; @@ -550,6 +594,13 @@ struct Level : IGame { case TR::Entity::SCION_TARGET : return new ScionTarget(this, index); case TR::Entity::WATERFALL : return new Waterfall(this, index); case TR::Entity::TRAP_LAVA : return new TrapLava(this, index); + case TR::Entity::BUBBLE : return new Bubble(this, index); + case TR::Entity::EXPLOSION : return new Explosion(this, index); + case TR::Entity::WATER_SPLASH : + case TR::Entity::BLOOD : + case TR::Entity::SMOKE : + case TR::Entity::SPARKLES : return new Sprite(this, index, true, Sprite::FRAME_ANIMATED); + case TR::Entity::RICOCHET : return new Sprite(this, index, true, Sprite::FRAME_RANDOM); case TR::Entity::CENTAUR_STATUE : return new CentaurStatue(this, index); case TR::Entity::CABIN : return new Cabin(this, index); case TR::Entity::TRAP_FLAME_EMITTER : return new TrapFlameEmitter(this, index); @@ -898,7 +949,7 @@ struct Level : IGame { TR::Room &room = level.rooms[roomIndex]; if (!entity.isLara() && !entity.isActor()) - if (!room.flags.visible || entity.flags.invisible || entity.flags.rendered) + if (!room.flags.visible || controller->flags.invisible || controller->flags.rendered) return; int16 lum = entity.intensity == -1 ? room.ambient : entity.intensity; @@ -947,7 +998,7 @@ struct Level : IGame { TR::Entity &e = level.entities[i]; if (e.type == TR::Entity::CRYSTAL) { Crystal *crystal = (Crystal*)e.controller; - if (crystal->activeState != Controller::asActive && !level.rooms[crystal->getRoomIndex()].flags.visible) + if (crystal->flags.state != TR::Entity::asActive && !level.rooms[crystal->getRoomIndex()].flags.visible) continue; if (camera->frustum->isVisible(crystal->lightPos, CRYSTAL_LIGHT_RADIUS + 1024.0f)) { // 1024.0f because of vertex lighting @@ -1078,8 +1129,9 @@ struct Level : IGame { for (int i = 0; i < level.entitiesCount; i++) { TR::Entity &entity = level.entities[i]; - if (entity.controller && entity.flags.rendered) - ((Controller*)entity.controller)->renderShadow(mesh); + Controller *controller = (Controller*)entity.controller; + if (controller && controller->flags.rendered) + controller->renderShadow(mesh); } } @@ -1208,8 +1260,11 @@ struct Level : IGame { // clear visibility flag for rooms if (Core::pass != Core::passAmbient) - for (int i = 0; i < level.entitiesCount; i++) - level.entities[i].flags.rendered = false; + for (int i = 0; i < level.entitiesCount; i++) { + Controller *controller = (Controller*)level.entities[i].controller; + if (controller) + controller->flags.rendered = false; + } if (water) { Core::setTarget(NULL, !Core::settings.detail.stereo); // render to back buffer diff --git a/src/sprite.h b/src/sprite.h index 137e7d4..33b7cd6 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -27,18 +27,6 @@ struct Sprite : Controller { } } - static int add(IGame *game, TR::Entity::Type type, int room, int x, int y, int z, int frame = -1, bool empty = false) { - TR::Level *level = game->getLevel(); - 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 = empty ? NULL : new Sprite(game, index, true, frame); - if (level->entities[index].controller) - ((Controller*)level->entities[index].controller)->activate(); - } - return index; - } - TR::SpriteSequence& getSequence() { return level->spriteSequences[-(getEntity().modelIndex + 1)]; } diff --git a/src/trigger.h b/src/trigger.h index 01cf80e..327ce5e 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -15,10 +15,10 @@ struct Switch : Controller { Switch(IGame *game, int entity) : Controller(game, entity) {} bool setTimer(float t) { - if (activeState == asInactive) { + if (flags.state == TR::Entity::asInactive) { if (state == STATE_DOWN && t > 0.0f) { timer = t; - activeState = asActive; + flags.state = TR::Entity::asActive; } else deactivate(true); return true; @@ -36,7 +36,7 @@ struct Switch : Controller { virtual void update() { updateAnimation(true); - getEntity().flags.active = TR::ACTIVE; + flags.active = TR::ACTIVE; if (!isActive()) animation.setState(STATE_UP); } @@ -71,23 +71,17 @@ struct Dart : Controller { virtual void update() { velocity = dir * animation.getSpeed(); pos = pos + velocity * (Core::deltaTime * 30.0f); - updateEntity(); Controller *lara = (Controller*)level->laraController; if (armed && collide(lara)) { - Sprite::add(game, TR::Entity::BLOOD, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED); - lara->hit(DART_DAMAGE, this); + lara->hit(DART_DAMAGE, this, TR::HIT_DART); armed = false; } TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info); + getFloorInfo(getRoomIndex(), pos, info); if (pos.y > info.floor || pos.y < info.ceiling || !insideRoom(pos, getRoomIndex())) { - TR::Entity &e = getEntity(); - - vec3 p = pos - dir * 64.0f; // wall offset = 64 - Sprite::add(game, TR::Entity::RICOCHET, e.room, (int)p.x, (int)p.y, (int)p.z, Sprite::FRAME_RANDOM); - + game->addEntity(TR::Entity::RICOCHET, getRoomIndex(), pos - dir * 64.0f); // with wall offset level->entityRemove(entity); delete this; } @@ -109,12 +103,10 @@ struct TrapDartEmitter : Controller { ASSERT(false); if (state == STATE_FIRE && animation.framePrev == -1) { - TR::Entity &entity = getEntity(); + vec3 p = pos + vec3(0.0f, -512.0f, 256.0f).rotateY(PI - angle.y); - vec3 p = pos + vec3(0.0f, -512.0f, 256.0f).rotateY(PI - entity.rotation); - - game->addEntity(TR::Entity::DART, getRoomIndex(), p, entity.rotation); - Sprite::add(game, TR::Entity::SMOKE, entity.room, (int)p.x, (int)p.y, (int)p.z); + game->addEntity(TR::Entity::DART, getRoomIndex(), p, angle.y); + game->addEntity(TR::Entity::SMOKE, getRoomIndex(), p); game->playSound(TR::SND_DART, p, Sound::Flags::PAN); } @@ -134,7 +126,7 @@ struct Flame : Sprite { int roomIndex = controller->getRoomIndex(); vec3 pos = controller->pos; - int index = level->entityAdd(TR::Entity::FLAME, roomIndex, int(pos.x), int(pos.y), int(pos.z), 0, 0); + int index = level->entityAdd(TR::Entity::FLAME, roomIndex, pos, 0, 0); if (index > -1) { flame = new Flame(game, index, jointIndex); level->entities[index].controller = flame; @@ -228,7 +220,7 @@ struct LavaParticle : Sprite { Sprite::update(); TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); bool hit = false; if (!bounces) { @@ -243,10 +235,10 @@ struct LavaParticle : Sprite { delete this; return; } - getEntity().intensity = bounces * 2048 - 1; - - if (pos.y > info.floor) pos.y = float(info.floor); - if (pos.y < info.ceiling) pos.y = float(info.ceiling); + // getEntity().intensity = bounces * 2048 - 1; // TODO: updateEntity() + + if (pos.y > info.floor) pos.y = info.floor; + if (pos.y < info.ceiling) pos.y = info.ceiling; velocity = velocity.reflect(vec3(0, 1, 0)) * 0.5f; } @@ -288,10 +280,8 @@ struct TrapBoulder : Controller { TrapBoulder(IGame *game, int entity) : Controller(game, entity), velocity(0) {} virtual void update() { - if (activeState != asActive) return; - TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); vec3 dir = getDir(); @@ -299,7 +289,7 @@ struct TrapBoulder : Controller { if (pos.y >= info.floor - 256) { onGround = true; - pos.y = float(info.floor); + pos.y = info.floor; velocity = dir * animation.getSpeed(); if (state != STATE_ROLL) animation.setState(STATE_ROLL); @@ -314,18 +304,18 @@ struct TrapBoulder : Controller { pos += velocity * (30.0f * Core::deltaTime); if (info.roomNext != TR::NO_ROOM) - getEntity().room = info.roomNext; + roomIndex = info.roomNext; if (onGround) { game->checkTrigger(this, true); } vec3 v = pos + getDir() * 512.0f; - level->getFloorInfo(getRoomIndex(), int(v.x), int(v.y), int(v.z), info); + getFloorInfo(getRoomIndex(), v, info); if (pos.y > info.floor) { if (onGround) { pos = p; - deactivate(); + deactivate(true); return; } else { pos.x = p.x; @@ -334,7 +324,6 @@ struct TrapBoulder : Controller { } } - Character *lara = (Character*)level->laraController; if (lara->health > 0.0f && collide(lara)) { if (lara->stand == Character::STAND_GROUND) @@ -344,7 +333,6 @@ struct TrapBoulder : Controller { } updateAnimation(true); - updateEntity(); } }; @@ -362,47 +350,50 @@ struct Block : Controller { updateFloor(true); } + virtual void setSaveData(const SaveData &data) { + updateFloor(false); + Controller::setSaveData(data); + if (state == STATE_STAND) + updateFloor(true); + } + void updateFloor(bool rise) { - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); if (info.roomNext != 0xFF) - e.room = info.roomNext; + roomIndex = info.roomNext; int dx, dz; - TR::Room::Sector &s = level->getSector(e.room, e.x, e.z, dx, dz); + TR::Room::Sector &s = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz); s.floor += rise ? -4 : 4; } bool doMove(bool push) { // check floor height of next floor vec3 dir = getDir() * (push ? 1024.0f : -1024.0f); - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; - int px = e.x + (int)dir.x; - int pz = e.z + (int)dir.z; - level->getFloorInfo(e.room, px, e.y, pz, info); + vec3 p = pos + dir; + getFloorInfo(getRoomIndex(), p, info); - if ((info.slantX | info.slantZ) || info.floor != e.y || info.floor - info.ceiling < 1024) + if ((info.slantX | info.slantZ) || info.floor != pos.y || info.floor - info.ceiling < 1024) return false; // check for trapdoor - px /= 1024; - pz /= 1024; + int px = int(p.x) / 1024; + int pz = int(p.z) / 1024; for (int i = 0; i < info.trigCmdCount; i++) if (info.trigCmd[i].action == TR::Action::ACTIVATE) { - TR::Entity &obj = level->entities[info.trigCmd[i].args]; - if ((obj.type == TR::Entity::TRAP_DOOR_1 || obj.type == TR::Entity::TRAP_DOOR_2) && px == obj.x / 1024 && pz == obj.z / 1024) + TR::Entity &e = level->entities[info.trigCmd[i].args]; + vec3 objPos = ((Controller*)e.controller)->pos; + if ((e.type == TR::Entity::TRAP_DOOR_1 || e.type == TR::Entity::TRAP_DOOR_2) && px == int(objPos.x) / 1024 && pz == int(objPos.z) / 1024) return false; } // check Laras destination position if (!push) { dir = getDir() * (-2048.0f); - px = e.x + (int)dir.x; - pz = e.z + (int)dir.z; - level->getFloorInfo(e.room, px, e.y, pz, info); - if ((info.slantX | info.slantZ) || info.floor != e.y || info.floor - info.ceiling < 1024) + getFloorInfo(getRoomIndex(), pos + dir, info); + if ((info.slantX | info.slantZ) || info.floor != pos.y || info.floor - info.ceiling < 1024) return false; } @@ -414,30 +405,27 @@ struct Block : Controller { } virtual void update() { - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); if (pos.y < info.floor) { if (info.roomBelow != TR::NO_ROOM) - e.room = info.roomBelow; + roomIndex = info.roomBelow; velocity += Core::deltaTime * GRAVITY; pos.y += Core::deltaTime * velocity * 30.0f; if (pos.y >= info.floor) { velocity = 0.0f; - pos.y = float(info.floor); + pos.y = info.floor; game->setEffect(this, TR::Effect::FLOOR_SHAKE); game->playSound(TR::SND_BOULDER, pos, Sound::PAN); deactivate(true); updateFloor(true); } - updateEntity(); } else { if (state == STATE_STAND) return; updateAnimation(true); if (state == STATE_STAND) { - updateEntity(); updateFloor(true); deactivate(); game->checkTrigger(this, true); @@ -456,19 +444,17 @@ struct MovingBlock : Controller { }; MovingBlock(IGame *game, int entity) : Controller(game, entity) { - if (!getEntity().flags.invisible) + if (!flags.invisible) updateFloor(true); } void updateFloor(bool rise) { - updateEntity(); - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, e.y, e.z, info); + getFloorInfo(getRoomIndex(), pos, info); if (info.roomNext != 0xFF) - e.room = info.roomNext; + roomIndex = info.roomNext; int dx, dz; - TR::Room::Sector &s = level->getSector(e.room, e.x, e.z, dx, dz); + TR::Room::Sector &s = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz); s.floor += rise ? -8 : 8; } @@ -487,9 +473,9 @@ struct MovingBlock : Controller { } } - if (activeState == asInactive) { - if (getEntity().flags.active == TR::ACTIVE) - activeState = asActive; // stay in active items list + if (flags.state == TR::Entity::asInactive) { + if (flags.active == TR::ACTIVE) + flags.state = TR::Entity::asActive; // stay in active items list pos.x = int(pos.x / 1024.0f) * 1024.0f + 512.0f; pos.z = int(pos.z / 1024.0f) * 1024.0f + 512.0f; updateFloor(true); @@ -572,10 +558,9 @@ struct Door : Controller { } block[2]; Door(IGame *game, int entity) : Controller(game, entity) { - TR::Entity &e = getEntity(); vec3 p = pos - getDir() * 1024.0f; - block[0] = BlockInfo(level, e.room, e.x, e.z, int(p.x), int(p.z), false); - block[1] = BlockInfo(level, e.room, e.x, e.z, int(p.x), int(p.z), true); + block[0] = BlockInfo(level, getRoomIndex(), int(pos.x), int(pos.z), int(p.x), int(p.z), false); + block[1] = BlockInfo(level, getRoomIndex(), int(pos.x), int(pos.z), int(p.x), int(p.z), true); updateBlock(false); } @@ -607,7 +592,7 @@ struct TrapDoor : Controller { }; TrapDoor(IGame *game, int entity) : Controller(game, entity) { - getEntity().flags.collision = true; + flags.collision = true; } virtual void update() { @@ -615,7 +600,7 @@ struct TrapDoor : Controller { int targetState = isActive() ? STATE_OPEN : STATE_CLOSE; if (state == targetState) - getEntity().flags.collision = targetState == STATE_CLOSE; + flags.collision = targetState == STATE_CLOSE; else animation.setState(targetState); } @@ -632,14 +617,13 @@ struct TrapFloor : Controller { float speed; TrapFloor(IGame *game, int entity) : Controller(game, entity), speed(0) { - getEntity().flags.collision = true; + flags.collision = true; } virtual bool activate() { if (state != STATE_STATIC) return false; - TR::Entity &e = ((Controller*)level->laraController)->getEntity(); - int ey = getEntity().y - 512; // real floor object position - if (abs(e.y - ey) <= 8 && Controller::activate()) { + vec3 &p = ((Controller*)level->laraController)->pos; + if (abs(p.y - (pos.y - 512.0f)) <= 8 && Controller::activate()) { animation.setState(STATE_SHAKE); return true; } @@ -649,28 +633,27 @@ struct TrapFloor : Controller { virtual void update() { updateAnimation(true); if (state == STATE_FALL) { - getEntity().flags.collision = false; + flags.collision = false; speed += GRAVITY * 30 * Core::deltaTime; pos.y += speed * Core::deltaTime; TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); if (pos.y > info.roomFloor && info.roomBelow != 0xFF) - getEntity().room = info.roomBelow; + roomIndex = info.roomBelow; if (pos.y > info.floor) { pos.y = (float)info.floor; animation.setState(STATE_DOWN); } - updateEntity(); } } }; struct Bridge : Controller { Bridge(IGame *game, int entity) : Controller(game, entity) { - getEntity().flags.collision = true; + flags.collision = true; } }; @@ -681,7 +664,7 @@ struct Drawbridge : Controller { }; Drawbridge(IGame *game, int entity) : Controller(game, entity) { - getEntity().flags.collision = true; + flags.collision = true; } virtual void update() { @@ -803,16 +786,15 @@ struct TrapCeiling : Controller { pos.y += speed * Core::deltaTime; TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); if (pos.y > info.roomFloor && info.roomBelow != 0xFF) - getEntity().room = info.roomBelow; + roomIndex = info.roomBelow; if (pos.y > info.floor) { pos.y = (float)info.floor; animation.setState(STATE_DOWN); } - updateEntity(); Controller *lara = (Controller*)level->laraController; if (collide(lara)) @@ -866,7 +848,7 @@ struct TrapSword : Controller { virtual void update() { TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); Controller *lara = game->getLara(); @@ -881,12 +863,11 @@ struct TrapSword : Controller { angle.y += rot * Core::deltaTime; applyGravity(dir.y); pos += dir * (30.0f * Core::deltaTime); - if (pos.y > float(info.floor)) { - pos.y = float(info.floor); + if (pos.y > info.floor) { + pos.y = info.floor; game->playSound(TR::SND_SWORD, pos, Sound::PAN); deactivate(true); } - updateEntity(); if (collide(lara)) lara->hit(SWORD_DAMAGE * 30.0f * Core::deltaTime, this, TR::HIT_SWORD); // TODO: push lara @@ -906,13 +887,12 @@ struct ThorHammer : Controller { Controller *block; ThorHammer(IGame *game, int entity) : Controller(game, entity) { - TR::Entity &e = getEntity(); - int index = level->entityAdd(TR::Entity::HAMMER_BLOCK, getRoomIndex(), e.x, e.y, e.z, e.rotation, -1); + int index = level->entityAdd(TR::Entity::HAMMER_BLOCK, getRoomIndex(), pos, angle.y, -1); if (index > -1) { block = new Controller(game, index); level->entities[index].controller = block; } - e.flags.collision = block->getEntity().flags.collision = false; + flags.collision = block->flags.collision = false; } virtual void update() { @@ -932,7 +912,7 @@ struct ThorHammer : Controller { case STATE_DOWN : { game->checkTrigger(this, 1); if (lara->health > 0.0f) - getEntity().flags.collision = block->getEntity().flags.collision = true; + flags.collision = block->flags.collision = true; deactivate(true); break; } @@ -1153,12 +1133,10 @@ struct TrapLava : Controller { vec3 dir = getDir(); pos += dir * (25.0f * 30.0f * Core::deltaTime); - updateEntity(); - int roomIndex = getRoomIndex(); + roomIndex = getRoomIndex(); TR::Room::Sector *s = level->getSector(roomIndex, int(pos.x + dir.x * 2048.0f), int(pos.y), int(pos.z + dir.z * 2048.0f)); if (!s || s->floor * 256 != int(pos.y)) done = true; - getEntity().room = roomIndex; } }; @@ -1170,7 +1148,7 @@ struct MovingObject : Controller { }; MovingObject(IGame *game, int entity) : Controller(game, entity) { - getEntity().flags.collision = true; + flags.collision = true; } virtual void update() { @@ -1179,10 +1157,9 @@ struct MovingObject : Controller { pos += getDir() * (animation.getSpeed() * Core::deltaTime * 30.0f); TR::Level::FloorInfo info; - level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + getFloorInfo(getRoomIndex(), pos, info); if (info.roomNext != TR::NO_ROOM) - getEntity().room = info.roomNext; - updateEntity(); + roomIndex = info.roomNext; } }; @@ -1224,16 +1201,14 @@ struct Cabin : Controller { Cabin(IGame *game, int entity) : Controller(game, entity) {} virtual void update() { - TR::Entity &e = getEntity(); - - if (e.flags.active == TR::ACTIVE) { + if (flags.active == TR::ACTIVE) { if (state >= STATE_UP && state <= STATE_DOWN_2) animation.setState(state + 1); - e.flags.active = 0; + flags.active = 0; } if (state == STATE_GROUND) { - e.flags.invisible = true; + flags.invisible = true; level->flipmap[3].active = TR::ACTIVE; level->isFlipped = !level->isFlipped; deactivate(true); @@ -1257,7 +1232,7 @@ struct Boat : Controller { switch (state) { case STATE_IDLE : animation.setState(STATE_MOVE); break; case STATE_MOVE : animation.setState(STATE_STOP); break; - case STATE_STOP : deactivate(true); getEntity().flags.invisible = true; break; + case STATE_STOP : deactivate(true); flags.invisible = true; break; } updateAnimation(true); pos = pos + getDir() * (animation.getSpeed() * Core::deltaTime * 30.0f); @@ -1279,7 +1254,7 @@ struct MutantEgg : Controller { initMeshOverrides(); layers[0].mask = 0xff0001ff; // hide dynamic meshes - switch (getEntity().flags.active) { + switch (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; @@ -1291,8 +1266,7 @@ struct MutantEgg : Controller { 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)) ) { + if ( flags.once || getEntity().type == TR::Entity::MUTANT_EGG_BIG || box.contains((((Controller*)level->laraController)->pos)) ) { animation.setState(STATE_EXPLOSION); layers[0].mask = 0xffffffff & ~(1 << 24); explode(0x00fffe00); @@ -1301,6 +1275,11 @@ struct MutantEgg : Controller { } Controller::update(); } + + virtual void setSaveData(const SaveData &data) { + Controller::setSaveData(data); + visibleMask = (state == STATE_IDLE) ? 0xff0001ff : (0xffffffff & ~(1 << 24)); + } }; @@ -1309,7 +1288,7 @@ struct KeyHole : Controller { virtual bool activate() { if (!Controller::activate()) return false; - getEntity().flags.active = TR::ACTIVE; + flags.active = TR::ACTIVE; if (getEntity().isPuzzleHole()) { int doneIdx = TR::Entity::convToInv(TR::Entity::getItemForHole(getEntity().type)) - TR::Entity::INV_PUZZLE_1; meshSwap(0, level->extra.puzzleDone[doneIdx]); @@ -1373,8 +1352,7 @@ struct Waterfall : Controller { vec2 p = (vec2(randf(), randf()) * 2.0f - 1.0f) * (512.0f - dropRadius); vec3 dropPos = pos + vec3(p.x, 0.0f, p.y); game->waterDrop(dropPos, dropRadius, dropStrength); - - Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)dropPos.x, (int)dropPos.y, (int)dropPos.z); + game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), dropPos); } #undef SPLASH_TIMESTEP @@ -1386,16 +1364,15 @@ struct Bubble : Sprite { Bubble(IGame *game, int entity) : Sprite(game, entity, true, Sprite::FRAME_RANDOM) { speed = (10.0f + randf() * 6.0f) * 30.0f; // get water height => bubble life time - TR::Entity &e = getEntity(); int dx, dz; int room = getRoomIndex(); - int h = e.y; + int h = int(pos.y); while (room != TR::NO_ROOM && level->rooms[room].flags.water) { - TR::Room::Sector &s = level->getSector(room, e.x, e.z, dx, dz); + TR::Room::Sector &s = level->getSector(room, int(pos.x), int(pos.z), dx, dz); h = s.ceiling * 256; room = s.roomAbove; } - time -= (e.y - h) / speed - (1.0f / SPRITE_FPS); + time -= (pos.y - h) / speed - (1.0f / SPRITE_FPS); activate(); } @@ -1409,9 +1386,17 @@ struct Bubble : Sprite { angle.y += 30.0f * 9.0f * DEG2RAD * Core::deltaTime; pos.x += sinf(angle.y) * (11.0f * 30.0f * Core::deltaTime); pos.z += cosf(angle.x) * (8.0f * 30.0f * Core::deltaTime); - updateEntity(); Sprite::update(); } }; + +struct Explosion : Sprite { + + Explosion(IGame *game, int entity) : Sprite(game, entity, true, Sprite::FRAME_ANIMATED) { + game->playSound(TR::SND_EXPLOSION, pos, 0); + } +}; + + #endif \ No newline at end of file