diff --git a/src/camera.h b/src/camera.h index 4402654..7e4d0b0 100644 --- a/src/camera.h +++ b/src/camera.h @@ -8,30 +8,46 @@ #define CAMERA_OFFSET (1024.0f + 256.0f) struct Camera : Controller { + + enum { + STATE_FOLLOW, + STATE_STATIC, + STATE_LOOK, + STATE_COMBAT, + STATE_CUTSCENE, + STATE_HEAVY + }; + Controller *owner; Frustum *frustum; float fov, znear, zfar; - vec3 target, destPos, lastDest, advAngle; + vec3 target, destPos, advAngle; float advTimer; mat4 mViewInv; int room; float timer; - int actTargetEntity, actCamera; + float shake; Basis prevBasis; vec4 *reflectPlane; - bool cutscene; + int viewIndex; + int viewIndexLast; + Controller* viewTarget; + float speed; + bool firstPerson; bool isVR; - float shake; - - Camera(IGame *game, Controller *owner) : Controller(game, owner ? owner->entity : 0), owner(owner), frustum(new Frustum()), timer(0.0f), actTargetEntity(-1), actCamera(-1), reflectPlane(NULL), isVR(false) { + Camera(IGame *game, Controller *owner) : Controller(game, owner ? owner->entity : 0), owner(owner), frustum(new Frustum()), timer(-1.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL), reflectPlane(NULL), isVR(false) { changeView(false); - cutscene = owner->getEntity().type != TR::Entity::LARA && level->cameraFrames; + if (owner->getEntity().type != TR::Entity::LARA && level->cameraFrames) { + state = STATE_CUTSCENE; + room = level->entities[level->cutEntity].room; + } else + state = STATE_FOLLOW; } virtual ~Camera() { @@ -39,14 +55,23 @@ struct Camera : Controller { } virtual int getRoomIndex() const { - return actCamera > -1 ? level->cameras[actCamera].room : room; + return viewIndex > -1 ? level->cameras[viewIndex].room : room; } virtual void checkRoom() { - TR::Level::FloorInfo info; - level->getFloorInfo(room, (int)pos.x, (int)pos.y, (int)pos.z, info); + if (state == STATE_CUTSCENE) { + for (int i = 0; i < level->roomsCount; i++) + if (insideRoom(pos, i)) { + room = i; + break; + } + return; + } - if (info.roomNext != TR::NO_ROOM) + TR::Level::FloorInfo info; + level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info); + + if (info.roomNext != TR::NO_ROOM) room = info.roomNext; if (pos.y < info.roomCeiling) { @@ -65,20 +90,7 @@ struct Camera : Controller { pos.y = (float)info.roomFloor; } } - /* - virtual bool activate(ActionCommand *cmd) { - Controller::activate(cmd); - this->timer = max(max(1.0f, this->timer), cmd->timer); - if (cmd->action == TR::Action::CAMERA_TARGET) - actTargetEntity = cmd->value; - if (cmd->action == TR::Action::CAMERA_SWITCH) { - actCamera = cmd->value; - lastDest = pos; - } - activateNext(); - return true; - } - */ + void updateListener() { Sound::listener.matrix = mViewInv; TR::Room &r = level->rooms[getRoomIndex()]; @@ -90,19 +102,34 @@ struct Camera : Controller { return level->rooms[getRoomIndex()].flags.water; } + void setView(int viewIndex, float timer, float speed) { + if (viewIndex == viewIndexLast) return; + viewIndexLast = viewIndex; + + state = STATE_STATIC; + this->viewIndex = viewIndex; + this->timer = timer; + this->speed = speed; + } + virtual void update() { if (shake > 0.0f) shake = max(0.0f, shake - Core::deltaTime); - #ifndef LEVEL_EDITOR - if (cutscene) { // cutscene - timer += Core::deltaTime * 30; + if (state == STATE_CUTSCENE) { + timer += Core::deltaTime * 15.0f; float t = timer - int(timer); int indexA = int(timer) % level->cameraFramesCount; - int indexB = min(indexA + 1, level->cameraFramesCount - 1); + int indexB = (indexA + 1) % level->cameraFramesCount; TR::CameraFrame *frameA = &level->cameraFrames[indexA]; TR::CameraFrame *frameB = &level->cameraFrames[indexB]; + if (indexB < indexA) { + level->initCutscene(); + game->playTrack(0, true); + timer = 0.0f; + } + const int eps = 512; if (abs(frameA->pos.x - frameB->pos.x) > eps || abs(frameA->pos.y - frameB->pos.y) > eps || abs(frameA->pos.z - frameB->pos.z) > eps) { @@ -118,10 +145,8 @@ struct Camera : Controller { pos = level->cutMatrix * pos; target = level->cutMatrix * target; - // TODO: frame->roll - } else - #endif - { + checkRoom(); + } else { vec3 advAngleOld = advAngle; if (Input::down[ikMouseL]) { @@ -147,13 +172,6 @@ struct Camera : Controller { if (owner->velocity != 0.0f && advTimer < 0.0f && !Input::down[ikMouseL]) advTimer = -advTimer; */ - #ifndef LEVEL_EDITOR - if (advTimer == 0.0f && advAngle != 0.0f) { - float t = 10.0f * Core::deltaTime; - advAngle.x = lerp(clampAngle(advAngle.x), 0.0f, t); - advAngle.y = lerp(clampAngle(advAngle.y), 0.0f, t); - } - #endif angle = owner->angle + advAngle; angle.z = 0.0f; /* toto @@ -162,31 +180,7 @@ struct Camera : Controller { if (owner->state == Lara::STATE_HANG || owner->state == Lara::STATE_HANG_LEFT || owner->state == Lara::STATE_HANG_RIGHT) angle.x -= 60.0f * DEG2RAD; */ - #ifdef LEVEL_EDITOR - angle = advAngle; - angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD); - - vec3 d = vec3(sinf(angle.y) * cosf(angle.x), -sinf(angle.x), cosf(angle.y) * cosf(angle.x)); - vec3 v = vec3(0); - - if (Input::down[ikW]) v = v + d; - if (Input::down[ikS]) v = v - d; - if (Input::down[ikA]) v = v + d.cross(vec3(0, 1, 0)); - if (Input::down[ikD]) v = v - d.cross(vec3(0, 1, 0)); - pos = pos + v.normal() * (Core::deltaTime * 512.0f * 10.0f); - - mViewInv.identity(); - mViewInv.translate(pos); - mViewInv.rotateY(angle.y - PI); - mViewInv.rotateX(-angle.x); - mViewInv.rotateZ(PI); - - updateListener(); - - return; - #endif - int lookAt = -1; - if (actTargetEntity > -1) lookAt = actTargetEntity; + Controller *lookAt = viewTarget; /* todo if (owner->arms[0].target > -1 && owner->arms[1].target > -1 && owner->arms[0].target != owner->arms[1].target) { // two diff targets @@ -204,17 +198,21 @@ struct Camera : Controller { if (timer > 0.0f) { timer -= Core::deltaTime; if (timer <= 0.0f) { - timer = 0.0f; - if (room != getRoomIndex()) - pos = lastDest; - actTargetEntity = actCamera = -1; + timer = -1.0f; + state = STATE_FOLLOW; + viewTarget = NULL; + viewIndex = -1; /* todo target = owner->getViewPoint(); */ } } - if (firstPerson && actCamera == -1) { + if (timer < 0.0f) { + viewTarget = NULL; + } + + if (firstPerson && viewIndex == -1) { Basis head = owner->animation.getJoints(owner->getMatrix(), 14, true); Basis eye(quat(0.0f, 0.0f, 0.0f, 1.0f), vec3(0.0f, -40.0f, 10.0f)); eye = head * eye; @@ -233,26 +231,23 @@ struct Camera : Controller { return; } - float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f; + float lerpFactor = lookAt ? 10.0f : 6.0f; vec3 dir; /* todo target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime); */ target = owner->animation.getJoints(owner->getMatrix(), 7).pos; - if (actCamera > -1) { - TR::Camera &c = level->cameras[actCamera]; - destPos = vec3(float(c.x), float(c.y), float(c.z)); + if (viewIndex > -1) { + TR::Camera &cam = level->cameras[viewIndex]; + destPos = vec3(float(cam.x), float(cam.y), float(cam.z)); if (room != getRoomIndex()) pos = destPos; - if (lookAt > -1) { - TR::Entity &e = level->entities[lookAt]; - target = ((Controller*)e.controller)->pos; - } + if (lookAt) + target = lookAt->pos; } else { - if (lookAt > -1) { - TR::Entity &e = level->entities[lookAt]; - dir = (((Controller*)e.controller)->pos - target).normal(); + if (lookAt) { + dir = (lookAt->pos - target).normal(); } else dir = getDir(); @@ -262,7 +257,6 @@ struct Camera : Controller { */ vec3 eye = target - dir * CAMERA_OFFSET; destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true); - lastDest = destPos; /* } else { vec3 eye = lastDest + dir.cross(vec3(0, 1, 0)).normal() * 2048.0f - vec3(0.0f, 512.0f, 0.0f); @@ -273,7 +267,7 @@ struct Camera : Controller { } pos = pos.lerp(destPos, Core::deltaTime * lerpFactor); - if (actCamera <= -1) + if (viewIndex == -1) checkRoom(); } diff --git a/src/controller.h b/src/controller.h index 79ad6af..9f3caa5 100644 --- a/src/controller.h +++ b/src/controller.h @@ -22,6 +22,7 @@ struct IGame { virtual MeshBuilder* getMesh() { return NULL; } virtual Controller* getCamera() { return NULL; } virtual Controller* getLara() { return NULL; } + virtual bool isCutscene() { return false; } virtual uint16 getRandomBox(uint16 zone, uint16 *zones) { return 0; } virtual uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) { return 0; } virtual void setClipParams(float clipSign, float clipHeight) {} @@ -41,6 +42,8 @@ struct IGame { virtual bool invChooseKey(TR::Entity::Type hole) { return false; } virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const { return NULL; } + virtual void playTrack(int track, bool restart = false) {} + virtual void stopTrack() {} }; struct Controller { @@ -98,6 +101,9 @@ struct Controller { e.flags.reverse = true; activate(); } + + if (e.isLara() || e.isActor()) // Lara and cutscene entities is active by default + activate(); } virtual ~Controller() { @@ -235,8 +241,8 @@ struct Controller { bool insideRoom(const vec3 &pos, int room) const { TR::Room &r = level->rooms[room]; - vec3 min = vec3((float)r.info.x, (float)r.info.yTop, (float)r.info.z); - vec3 max = min + vec3(float(r.xSectors * 1024), float(r.info.yBottom - r.info.yTop), float(r.zSectors * 1024)); + vec3 min = vec3((float)r.info.x + 1024, (float)r.info.yTop, (float)r.info.z + 1024); + vec3 max = min + vec3(float((r.xSectors - 1) * 1024), float(r.info.yBottom - r.info.yTop), float((r.zSectors - 1) * 1024)); return pos.x >= min.x && pos.x <= max.x && pos.y >= min.y && pos.y <= max.y && @@ -482,6 +488,7 @@ struct Controller { case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break; case TR::EFFECT_FLOOR_SHAKE : game->fxQuake(0.5f * max(0.0f, 1.0f - (pos - ((Controller*)level->cameraController)->pos).length2() / (15 * 1024 * 15 * 1024) )); break; case TR::EFFECT_LARA_BUBBLES : doBubbles(); break; + case TR::EFFECT_FLIP_MAP : level->isFlipped = !level->isFlipped; break; default : cmdEffect(fx); break; } } else @@ -553,7 +560,7 @@ struct Controller { mat4 matrix; TR::Entity &e = getEntity(); - if (e.type < TR::Entity::CUT_1 || e.type > TR::Entity::CUT_4) { // TODO: move to ctor + if (!e.isActor()) { matrix.identity(); matrix.translate(pos); if (angle.y != 0.0f) matrix.rotateY(angle.y - (animation.flip ? PI * animation.delta : 0.0f)); @@ -568,7 +575,10 @@ struct Controller { void renderShadow(MeshBuilder *mesh) { TR::Entity &entity = getEntity(); - if (Core::pass != Core::passCompose || !TR::castShadow(entity.type)) + if (Core::pass != Core::passCompose || !entity.castShadow()) + return; + + if (entity.isActor()) // cutscene entities have no blob shadow return; Box box = animation.getBoundingBox(pos, 0); diff --git a/src/debug.h b/src/debug.h index 460876b..9eef199 100644 --- a/src/debug.h +++ b/src/debug.h @@ -557,7 +557,7 @@ namespace Debug { sprintf(buf, "samples_PSX/%03d.wav", index); FILE *f = fopen(buf, "wb"); - if (level->version == TR::Level::VER_TR1_PSX) { + if (level->version == TR::VER_TR1_PSX) { int dataSize = level->soundSize[index] / 16 * 28 * 2 * 4; struct Header { @@ -592,7 +592,7 @@ namespace Debug { fwrite(&frames[i].L, 2, 1, f); } - if (level->version == TR::Level::VER_TR1_PC) { + if (level->version == TR::VER_TR1_PC) { uint32 *data = (uint32*)&level->soundData[level->soundOffsets[index]]; fwrite(data, data[1] + 8, 1, f); } @@ -628,7 +628,7 @@ namespace Debug { case_name(TR::Action, CAMERA_TARGET ); case_name(TR::Action, END ); case_name(TR::Action, SOUNDTRACK ); - case_name(TR::Action, HARDCODE ); + case_name(TR::Action, EFFECT ); case_name(TR::Action, SECRET ); } return "UNKNOWN"; @@ -658,7 +658,7 @@ namespace Debug { sprintf(buf, "DIP = %d, TRI = %d, SND = %d, active = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount, activeCount); Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); vec3 angle = ((Controller*)entity.controller)->angle * RAD2DEG; - sprintf(buf, "pos = (%d, %d, %d), angle = (%d, %d), room = %d", entity.x, entity.y, entity.z, (int)angle.x, (int)angle.y, entity.room); + sprintf(buf, "pos = (%d, %d, %d), angle = (%d, %d), room = %d", entity.x, entity.y, entity.z, (int)angle.x, (int)angle.y, ((Controller*)entity.controller)->getRoomIndex()); Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); int rate = anim.anims[anim.index].frameRate; sprintf(buf, "state = %d, anim = %d, next = %d, rate = %d, frame = %.2f / %d (%f)", anim.state, anim.index, anim.next, rate, anim.time * 30.0f, anim.framesCount, anim.delta); diff --git a/src/format.h b/src/format.h index 82e8752..6849f3b 100644 --- a/src/format.h +++ b/src/format.h @@ -6,6 +6,7 @@ #define MAX_RESERVED_ENTITIES 128 #define MAX_FLIPMAP_COUNT 16 #define MAX_SECRETS_COUNT 16 +#define MAX_TRACKS_COUNT 64 #define MAX_TRIGGER_COMMANDS 32 #define MAX_MESHES 512 @@ -307,6 +308,19 @@ namespace TR { SND_SECRET = 173, }; + enum { + TRACK_TITLE = 2, + TRACK_CAVES = 5, + TRACK_SECRET = 13, + TRACK_CISTERN = 57, + TRACK_EGYPT = 58, + TRACK_MINE = 59, + TRACK_CUT1 = 23, + TRACK_CUT2 = 25, + TRACK_CUT3 = 24, + TRACK_CUT4 = 22, + }; + enum { MODEL_LARA = 0, MODEL_PISTOLS = 1, @@ -326,7 +340,7 @@ namespace TR { CAMERA_TARGET , // look at item END , // end level SOUNDTRACK , // play soundtrack - HARDCODE , // special hadrdcode trigger + EFFECT , // special effect trigger SECRET , // secret found }; @@ -568,7 +582,7 @@ namespace TR { uint16 boxIndex:15, end:1; }; - struct Flipmap { + struct Flags { uint16 :8, once:1, active:5, :2; }; @@ -627,33 +641,45 @@ namespace TR { int32 modelIndex; // index of representation in models (index + 1) or spriteSequences (-(index + 1)) arrays void *controller; // Controller implementation or NULL - bool isEnemy() { - return type >= ENEMY_TWIN && type <= ENEMY_LARSON; + bool isEnemy() const { + return type >= ENEMY_TWIN && type <= ENEMY_GIANT_MUTANT; } - bool isBigEnemy() { + bool isBigEnemy() const { return type == ENEMY_REX || type == ENEMY_MUTANT_1 || type == ENEMY_CENTAUR; } - bool isDoor() { + bool isDoor() const { return (type >= DOOR_1 && type <= DOOR_6) || type == DOOR_LIFT; } - bool isItem() { + bool isItem() const { return (type >= PISTOLS && type <= AMMO_UZIS) || (type >= PUZZLE_1 && type <= PUZZLE_4) || (type >= KEY_ITEM_1 && type <= KEY_ITEM_4) || (type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == SCION_1); // TODO: recheck all items } - bool isPuzzleHole() { + bool isActor() const { + return type >= CUT_1 && type <= CUT_4; + } + + bool isPuzzleHole() const { return type >= PUZZLE_HOLE_1 && type <= PUZZLE_HOLE_2; } - bool isBlock() { + bool isBlock() const { return type >= TR::Entity::BLOCK_1 && type <= TR::Entity::BLOCK_2; } + bool isLara() const { + return type == LARA; + } + + bool castShadow() const { + return isLara() || isEnemy() || isActor(); + } + static Type convToInv(Type type) { switch (type) { case PISTOLS : return INV_PISTOLS; @@ -914,11 +940,76 @@ namespace TR { #pragma pack(pop) + enum Version : uint32 { + VER_TR1_PC = 0x00000020, + VER_TR1_PSX = 0x56414270, + }; + + enum LevelID : uint32 { + LEVEL_CUSTOM, + TITLE, + GYM, + LEVEL_1, + LEVEL_2, + LEVEL_3A, + LEVEL_3B, + CUTSCENE_1, + LEVEL_4, + LEVEL_5, + LEVEL_6, + LEVEL_7A, + LEVEL_7B, + CUTSCENE_2, + LEVEL_8A, + LEVEL_8B, + LEVEL_8C, + LEVEL_10A, + CUTSCENE_3, + LEVEL_10B, + CUTSCENE_4, + LEVEL_10C, + LEVEL_EGYPT, + LEVEL_CAT, + LEVEL_END, + LEVEL_END2, + LEVEL_MAX, + }; + + struct { + const char *name; + int ambientTrack; + } LEVEL_INFO[LEVEL_MAX] = { + { "" , TRACK_CAVES }, + { "TITLE" , TRACK_TITLE }, + { "GYM" , 0 }, + { "LEVEL1" , TRACK_CAVES }, + { "LEVEL2" , TRACK_CAVES }, + { "LEVEL3A" , TRACK_CAVES }, + { "LEVEL3B" , TRACK_CAVES }, + { "CUT1" , TRACK_CUT1 }, + { "LEVEL4" , TRACK_CAVES }, + { "LEVEL5" , TRACK_CAVES }, + { "LEVEL6" , TRACK_CAVES }, + { "LEVEL7A" , TRACK_CISTERN }, + { "LEVEL7B" , TRACK_CISTERN }, + { "CUT2" , TRACK_CUT2 }, + { "LEVEL8A" , TRACK_EGYPT }, + { "LEVEL8B" , TRACK_EGYPT }, + { "LEVEL8C" , TRACK_EGYPT }, + { "LEVEL10A" , TRACK_MINE }, + { "CUT3" , TRACK_CUT3 }, + { "LEVEL10B" , TRACK_MINE }, + { "CUT4" , TRACK_CUT4 }, + { "LEVEL10C" , TRACK_MINE }, + { "EGYPT" , TRACK_EGYPT }, + { "CAT" , TRACK_EGYPT }, + { "END" , TRACK_EGYPT }, + { "END2" , TRACK_EGYPT }, + }; + struct Level { - enum : uint32 { - VER_TR1_PC = 0x00000020, - VER_TR1_PSX = 0x56414270, - } version; + Version version; + LevelID id; int32 tilesCount; Tile32 *tiles; @@ -1051,13 +1142,17 @@ namespace TR { } }; - Flipmap flipmap[MAX_FLIPMAP_COUNT]; + Flags flipmap[MAX_FLIPMAP_COUNT]; bool secrets[MAX_SECRETS_COUNT]; + Flags tracks[MAX_TRACKS_COUNT]; + void *cameraController; void *laraController; int cutEntity; mat4 cutMatrix; + bool isDemoLevel; + bool isHomeLevel; bool isFlipped; struct { @@ -1090,7 +1185,7 @@ namespace TR { int16 glyphSeq; } extra; - Level(Stream &stream, bool demo) { + Level(Stream &stream) { int startPos = stream.pos; memset(this, 0, sizeof(*this)); cutEntity = -1; @@ -1111,6 +1206,8 @@ namespace TR { return; } + id = getLevelID(stream.size); + if (version == VER_TR1_PSX) { uint32 offsetTexTiles; stream.seek(8); @@ -1246,7 +1343,7 @@ namespace TR { readObjectTex(stream); readSpriteTex(stream); // palette for demo levels - if (version == VER_TR1_PC && demo) stream.read(palette, 256); + if (version == VER_TR1_PC && isDemoLevel) stream.read(palette, 256); // cameras stream.read(cameras, stream.read(camerasCount)); // sound sources @@ -1291,7 +1388,7 @@ namespace TR { if (version == VER_TR1_PC) { stream.seek(32 * 256); // palette for release levels - if (!demo) + if (!isDemoLevel) stream.read(palette, 256); // cinematic frames for cameras (PC) stream.read(cameraFrames, stream.read(cameraFramesCount)); @@ -1319,10 +1416,13 @@ namespace TR { delete[] tiles8; tiles8 = NULL; // init flipmap states - memset(flipmap, 0, MAX_FLIPMAP_COUNT * sizeof(flipmap[0])); isFlipped = false; + memset(flipmap, 0, MAX_FLIPMAP_COUNT * sizeof(flipmap[0])); // init secrets states memset(secrets, 0, MAX_SECRETS_COUNT * sizeof(secrets[0])); + // init soundtracks states + memset(tracks, 0, MAX_TRACKS_COUNT * sizeof(tracks[0])); + // get special models indices memset(&extra, 0xFF, sizeof(extra)); @@ -1385,26 +1485,7 @@ namespace TR { } ASSERT(extra.glyphSeq != -1); - // init cutscene transform - cutMatrix.identity(); - if (cutEntity > -1) { - Entity &e = entities[cutEntity]; - switch (cameraFramesCount) { // HACK to detect cutscene level number - case 1600 : // CUT1 - cutMatrix.translate(vec3(36668, float(e.y), 63180)); - cutMatrix.rotateY(-23312.0f / float(0x4000) * PI * 0.5f); - break; - case 1000 : // CUT2 - cutMatrix.translate(vec3(51962, float(e.y), 53760)); - cutMatrix.rotateY(16380.0f / float(0x4000) * PI * 0.5f); - break; - case 400 : // CUT3 - case 1890 : // CUT4 - cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z))); - cutMatrix.rotateY(PI * 0.5f); - break; - } - } + initCutscene(); } ~Level() { @@ -1458,6 +1539,86 @@ namespace TR { delete[] soundSize; } + LevelID getLevelID(int size) { + isDemoLevel = false; + isHomeLevel = false; + switch (size) { + case 508614 : + case 316460 : return TITLE; + case 1074234 : + case 3237128 : isHomeLevel = true; return GYM; + case 1448896 : + case 2533634 : return LEVEL_1; + case 2873406 : isDemoLevel = true; return LEVEL_2; + case 1535734 : + case 2873450 : return LEVEL_2; + case 1630560 : + case 2934730 : return LEVEL_3A; + case 1506614 : + case 2738258 : return LEVEL_3B; + case 722402 : + case 599840 : return CUTSCENE_1; + case 1621970 : + case 3030872 : return LEVEL_4; + case 1585942 : + case 2718540 : return LEVEL_5; + case 1708464 : + case 3074376 : return LEVEL_6; + case 1696664 : + case 2817612 : return LEVEL_7A; + case 1733274 : + case 3389096 : return LEVEL_7B; + case 542960 : + case 354320 : return CUTSCENE_2; + case 1563356 : + case 2880564 : return LEVEL_8A; + case 1565630 : + case 2886756 : return LEVEL_8B; + case 1619360 : + case 3105450 : return LEVEL_8C; + case 1678018 : + case 3224138 : return LEVEL_10A; + case 636660 : + case 512104 : return CUTSCENE_3; + case 1686748 : + case 3094020 : return LEVEL_10B; + case 940398 : + case 879582 : return CUTSCENE_4; + case 1814278 : + case 3532024 : return LEVEL_10C; + case 3279242 : return LEVEL_EGYPT; + case 3270998 : return LEVEL_CAT; + case 3208018 : return LEVEL_END; + case 3153300 : return LEVEL_END2; + } + return LEVEL_CUSTOM; + } + + void initCutscene() { + // init cutscene transform + cutMatrix.identity(); + if (cutEntity > -1) { + Entity &e = entities[cutEntity]; + switch (id) { // HACK to detect cutscene level number + case CUTSCENE_1 : + cutMatrix.translate(vec3(36668, float(e.y), 63180)); + cutMatrix.rotateY(-23312.0f / float(0x4000) * PI * 0.5f); + break; + case CUTSCENE_2 : + cutMatrix.translate(vec3(51962, float(e.y), 53760)); + cutMatrix.rotateY(16380.0f / float(0x4000) * PI * 0.5f); + break; + case CUTSCENE_3 : + isFlipped = true; + case CUTSCENE_4 : + cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z))); + cutMatrix.rotateY(PI * 0.5f); + break; + default : ; + } + } + } + void readMeshes(Stream &stream) { uint32 meshDataSize; stream.read(meshDataSize); @@ -1835,6 +1996,7 @@ namespace TR { } Stream* getSampleStream(int index) const { + if (!soundOffsets) return NULL; uint8 *data = &soundData[soundOffsets[index]]; uint32 size = 0; switch (version) { @@ -1889,6 +2051,16 @@ namespace TR { entities[entityIndex].controller = NULL; } + int getNextRoom(int floorIndex) const { + if (!floorIndex) return NO_ROOM; + FloorData *fd = &floors[floorIndex]; + // floor data always in this order and can't be less than uint16 x 3 + if (fd->cmd.func == FloorData::FLOOR) fd += 2; // skip floor slant info + if (fd->cmd.func == FloorData::CEILING) fd += 2; // skip ceiling slant info + if (fd->cmd.func == FloorData::PORTAL) return (++fd)->data; + return NO_ROOM; + } + Room::Sector& getSector(int roomIndex, int x, int z, int &dx, int &dz) const { ASSERT(roomIndex >= 0 && roomIndex < roomsCount); @@ -1911,6 +2083,47 @@ namespace TR { return room.sectors[sx * room.zSectors + sz]; } + Room::Sector* getSector(int &roomIndex, int x, int y, int z) const { + ASSERT(roomIndex >= 0 && roomIndex <= roomsCount); + + Room::Sector *sector = NULL; + + // check horizontal + while (1) { // Let's Rock! + TR::Room &room = rooms[roomIndex]; + + int sx = (x - room.info.x) >> 10; + int sz = (z - room.info.z) >> 10; + + if (sz <= 0 || sz >= room.xSectors - 1) { + sx = clamp(sx, 1, room.xSectors - 2); + sz = clamp(sz, 0, room.zSectors - 1); + } else + sx = clamp(sx, 0, room.xSectors - 1); + + sector = room.sectors + sx * room.zSectors + sz; + + int nextRoom = getNextRoom(sector->floorIndex); + if (nextRoom == NO_ROOM) + break; + + roomIndex = nextRoom; + }; + + // check vertical + while (sector->roomAbove != NO_ROOM && y < sector->ceiling * 256) { + TR::Room &room = rooms[roomIndex = sector->roomAbove]; + sector = room.sectors + (x - room.info.x) / 1024 * room.zSectors + (z - room.info.z) / 1024; + } + + while (sector->roomBelow != NO_ROOM && y >= sector->floor * 256) { + TR::Room &room = rooms[roomIndex = sector->roomBelow]; + sector = room.sectors + (x - room.info.x) / 1024 * room.zSectors + (z - room.info.z) / 1024; + } + + 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); @@ -2074,10 +2287,6 @@ namespace TR { }; // struct Level - - bool castShadow(Entity::Type type) { - return (type >= Entity::ENEMY_TWIN && type <= Entity::ENEMY_GIANT_MUTANT) || type == Entity::LARA || (type >= Entity::CUT_1 && type <= Entity::CUT_4); - } } #endif diff --git a/src/game.h b/src/game.h index 080c626..de28d63 100644 --- a/src/game.h +++ b/src/game.h @@ -9,15 +9,20 @@ namespace Game { Level *level; - void startLevel(Stream *lvl, Stream *snd, bool demo, bool home) { + void startLevel(Stream *lvl) { delete level; - level = new Level(*lvl, snd, demo, home); + level = new Level(*lvl); UI::init(level); delete lvl; } - void init(Stream *lvl, Stream *snd) { + void stopChannel(Sound::Sample *channel) { + if (level) level->stopChannel(channel); + } + + void init(Stream *lvl) { Core::init(); + Sound::callback = stopChannel; Core::settings.detail.ambient = true; Core::settings.detail.lighting = true; @@ -33,15 +38,12 @@ namespace Game { Core::settings.controls.retarget = true; level = NULL; - startLevel(lvl, snd, false, false); + startLevel(lvl); } void init(char *lvlName = NULL, char *sndName = NULL) { if (!lvlName) lvlName = (char*)"LEVEL2.PSX"; - #ifndef __EMSCRIPTEN__ - if (!sndName) sndName = (char*)"05.ogg"; - #endif - init(new Stream(lvlName), sndName ? new Stream(sndName) : NULL); + init(new Stream(lvlName)); } void free() { diff --git a/src/inventory.h b/src/inventory.h index 82cb466..c531813 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -132,38 +132,42 @@ struct Inventory { } *items[INVENTORY_MAX_ITEMS]; Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0) { + TR::LevelID id = game->getLevel()->id; + add(TR::Entity::INV_PASSPORT); add(TR::Entity::INV_DETAIL); add(TR::Entity::INV_SOUND); add(TR::Entity::INV_CONTROLS); + + if (id != TR::TITLE) { /* - add(TR::Entity::INV_PUZZLE_1, 3); - add(TR::Entity::INV_KEY_1, 3); + add(TR::Entity::INV_COMPASS); + if (level->extra.inv.map != -1) + add(TR::Entity::INV_MAP); + if (level->extra.inv.gamma != -1) + add(TR::Entity::INV_GAMMA); */ + add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO); + add(TR::Entity::INV_SHOTGUN, 10); + add(TR::Entity::INV_MAGNUMS, 10); + add(TR::Entity::INV_UZIS, 50); +// add(TR::Entity::INV_MEDIKIT_SMALL, 999); +// add(TR::Entity::INV_MEDIKIT_BIG, 999); +// add(TR::Entity::INV_SCION, 1); +// add(TR::Entity::INV_KEY_1, 1); +// add(TR::Entity::INV_PUZZLE_1, 1); - /* - add(TR::Entity::INV_COMPASS); - if (level->extra.inv.map != -1) - add(TR::Entity::INV_MAP); - if (level->extra.inv.gamma != -1) - add(TR::Entity::INV_GAMMA); - */ - add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO); - add(TR::Entity::INV_SHOTGUN, 10); - add(TR::Entity::INV_MAGNUMS, 10); - add(TR::Entity::INV_UZIS, 50); -// add(TR::Entity::INV_MEDIKIT_SMALL, 999); -// add(TR::Entity::INV_MEDIKIT_BIG, 999); + for (int i = 0; i < COUNT(background); i++) + background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false); + } else { + add(TR::Entity::INV_HOME); -// add(TR::Entity::INV_SCION, 1); -// add(TR::Entity::INV_KEY_1, 1); -// add(TR::Entity::INV_PUZZLE_1, 1); + memset(background, 0, sizeof(background)); + background[0] = Texture::LoadPCX("data/TITLEH.PCX"); + } phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f; memset(pageItemIndex, 0, sizeof(pageItemIndex)); - - for (int i = 0; i < COUNT(background); i++) - background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false); } ~Inventory() { @@ -592,15 +596,26 @@ struct Inventory { // background Core::setDepthTest(false); - game->setShader(Core::passFilter, Shader::FILTER_MIXER, false, false); - Core::active.shader->setParam(uParam, vec4(phaseRing, 1.0f - phaseRing * 0.4f, 0, 0));; background[0]->bind(sDiffuse); // orignal image - background[1]->bind(sNormal); // blured grayscale image + if (background[1]) { + game->setShader(Core::passFilter, Shader::FILTER_MIXER, false, false); + Core::active.shader->setParam(uParam, vec4(phaseRing, 1.0f - phaseRing * 0.4f, 0, 0));; + background[1]->bind(sNormal); // blured grayscale image + } else { + game->setShader(Core::passFilter, Shader::DEFAULT, false, false); + + float aspect1 = float(background[0]->width) / float(background[0]->height); + float aspect2 = float(Core::width) / float(Core::height); + Core::active.shader->setParam(uParam, vec4(aspect2 / aspect1, -1.0f, 0, 0));; + } game->getMesh()->renderQuad(); Core::setDepthTest(true); Core::setBlending(bmAlpha); + if (game->isCutscene()) + return; + // items game->setupBinding(); @@ -645,7 +660,8 @@ struct Inventory { static const char* pageTitle[PAGE_MAX] = { "OPTION", "INVENTORY", "ITEMS" }; - UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width); + if (game->getLevel()->id != TR::TITLE) + UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width); if (page < PAGE_ITEMS && getItemsCount(page + 1)) { UI::textOut(vec2(16, 32), "[", UI::aLeft, UI::width); diff --git a/src/lara.h b/src/lara.h index ebc5f6b..8acef66 100644 --- a/src/lara.h +++ b/src/lara.h @@ -196,7 +196,6 @@ struct Lara : Character { BODY_BRAID_MASK = BODY_HEAD | BODY_CHEST | BODY_ARM_L1 | BODY_ARM_L2 | BODY_ARM_R1 | BODY_ARM_R2, }; - bool home; bool dozy; struct Weapon { @@ -395,7 +394,7 @@ struct Lara : Character { } *braid; - Lara(IGame *game, int entity, bool home) : Character(game, entity, LARA_MAX_HEALTH), home(home), dozy(false), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), viewTarget(-1), braid(NULL) { + Lara(IGame *game, int entity) : Character(game, entity, LARA_MAX_HEALTH), dozy(false), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), viewTarget(-1), braid(NULL) { if (getEntity().type == TR::Entity::LARA) { if (getRoom().flags.water) @@ -412,10 +411,10 @@ struct Lara : Character { getEntity().flags.active = 1; initMeshOverrides(); - if (!home) - wpnSet(Weapon::PISTOLS); - else + if (level->isHomeLevel) meshSwap(1, TR::MODEL_LARA_SPEC, BODY_UPPER | BODY_LOWER); + else + wpnSet(Weapon::PISTOLS); for (int i = 0; i < 2; i++) { arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; @@ -434,6 +433,7 @@ struct Lara : Character { #ifdef _DEBUG //reset(14, vec3(40448, 3584, 60928), PI * 0.5f, STAND_ONWATER); // gym (pool) //reset(14, vec3(20215, 6656, 52942), PI); // level 1 (bridge) + //reset(33, vec3(48229, 4608, 78420), 270 * DEG2RAD); // level 1 (end) //reset(15, vec3(70067, -256, 29104), -0.68f); // level 2 (pool) //reset(26, vec3(71980, 1546, 19000), 270 * DEG2RAD); // level 2 (underwater switch) //reset(61, vec3(27221, -1024, 29205), PI * 0.5f); // level 2 (blade) @@ -702,7 +702,7 @@ struct Lara : Character { } void wpnChange(Weapon::Type wType) { - if (wpnCurrent == wType || home) { + if (wpnCurrent == wType || level->isHomeLevel) { if (emptyHands()) wpnDraw(); return; @@ -1265,13 +1265,11 @@ struct Lara : Character { } virtual void cmdEffect(int fx) { - switch (fx) { - case TR::EFFECT_FLIP_MAP : break; // TODO case TR::EFFECT_LARA_HANDSFREE : break;//meshSwap(1, level->extra.weapons[wpnCurrent], BODY_LEG_L1 | BODY_LEG_R1); break; case TR::EFFECT_DRAW_RIGHTGUN : case TR::EFFECT_DRAW_LEFTGUN : drawGun(fx == TR::EFFECT_DRAW_RIGHTGUN); break; - default : LOG("unknown effect command %d (anim %d)\n", fx, animation.index); + default : LOG("unknown effect command %d (anim %d)\n", fx, animation.index); ASSERT(false); } } @@ -1388,6 +1386,25 @@ struct Lara : Character { return false; } + int doTutorial(int track) { + switch (track) { // GYM tutorial routine + case 28 : if (level->tracks[track].once && state == STATE_UP_JUMP) track = 29; break; + case 37 : + case 41 : if (state != STATE_HANG) return 0; break; + case 42 : if (level->tracks[track].once && state == STATE_HANG) track = 43; break; + case 49 : if (state != STATE_SURF_TREAD) return 0; break; + case 50 : // end of GYM + if (level->tracks[track].once) { + // back to title + } else + if (state != STATE_WATER_OUT) + return 0; + break; + } + return track; + } + + bool checkInteraction(Controller *controller, const TR::Limits::Limit &limit, bool action) { if ((state != STATE_STOP && state != STATE_TREAD && state != STATE_PUSH_PULL_READY) || !action || !emptyHands()) return false; @@ -1444,16 +1461,15 @@ struct Lara : Character { TR::Limits::Limit *limit = NULL; bool switchIsDown = false; - bool skipFirst = false; float timer = info.trigInfo.timer == 1 ? EPS : float(info.trigInfo.timer); - + int cmdIndex = 0; int actionState = state; + switch (info.trigger) { case TR::Level::Trigger::ACTIVATE : break; case TR::Level::Trigger::SWITCH : { - skipFirst = true; - Switch *controller = (Switch*)level->entities[info.trigCmd[0].args].controller; + Switch *controller = (Switch*)level->entities[info.trigCmd[cmdIndex++].args].controller; if (controller->activeState == asNone) { limit = state == STATE_STOP ? &TR::Limits::SWITCH : &TR::Limits::SWITCH_UNDERWATER; @@ -1472,9 +1488,7 @@ struct Lara : Character { } case TR::Level::Trigger::KEY : { - skipFirst = true; - - TR::Entity &entity = level->entities[info.trigCmd[0].args]; + TR::Entity &entity = level->entities[info.trigCmd[cmdIndex++].args]; KeyHole *controller = (KeyHole*)entity.controller; if (controller->activeState == asNone) { @@ -1512,9 +1526,7 @@ struct Lara : Character { break; } case TR::Level::Trigger::PICKUP : - skipFirst = true; - - if (!level->entities[info.trigCmd[0].args].flags.invisible) + if (!level->entities[info.trigCmd[cmdIndex++].args].flags.invisible) return; break; @@ -1529,13 +1541,14 @@ struct Lara : Character { break; case TR::Level::Trigger::HEAVY : + break; case TR::Level::Trigger::DUMMY : return; } bool needFlip = false; - for (int i = skipFirst; i < info.trigCmdCount; i++) { - TR::FloorData::TriggerCommand &cmd = info.trigCmd[i]; + while (cmdIndex < info.trigCmdCount) { + TR::FloorData::TriggerCommand &cmd = info.trigCmd[cmdIndex++]; switch (cmd.action) { case TR::Action::ACTIVATE : { @@ -1564,36 +1577,26 @@ struct Lara : Character { case TR::Action::CAMERA_SWITCH : { Camera *camera = (Camera*)level->cameraController; - TR::FloorData::TriggerCommand &cam = info.trigCmd[++i]; + TR::FloorData::TriggerCommand &cam = info.trigCmd[cmdIndex++]; if (level->cameras[cmd.args].flags.once) break; - /* - camera->current = level->entity[cmd.args].controller; if (info.trigger == TR::Level::Trigger::COMBAT) break; if (info.trigger == TR::Level::Trigger::SWITCH && info.trigInfo.timer && switchIsDown) break; - - if (info.trigger == TR::Level::Trigger::SWITCH || camera->curIndex != camera->prevIndex) { - - controller->timer = cam.timer == 1 ? EPS : float(cam.timer); - - if (cam.once) - level->cameras[cmd.args].flags.once = true; - - camera->speed = cam.speed * 8; + + if (info.trigger == TR::Level::Trigger::SWITCH || cmd.args != camera->viewIndexLast) { + level->cameras[cmd.args].flags.once |= cam.once; + camera->setView(cmd.args, cam.timer == 1 ? EPS : float(cam.timer), cam.speed * 8.0f); } - */ + break; } - case TR::Action::CAMERA_TARGET : - // camera->target = level->entity[cmd.args].controller; - break; case TR::Action::FLOW : break; case TR::Action::FLIP_MAP : { - TR::Flipmap &flip = level->flipmap[cmd.args]; + TR::Flags &flip = level->flipmap[cmd.args]; if (flip.once) break; @@ -1609,32 +1612,56 @@ struct Lara : Character { if ((flip.active == 0x1F) ^ level->isFlipped) needFlip = true; - LOG("FLIP_MAP\n"); break; } case TR::Action::FLIP_ON : if (level->flipmap[cmd.args].active == 0x1F && !level->isFlipped) needFlip = true; - LOG("FLIP_ON\n"); break; case TR::Action::FLIP_OFF : if (level->flipmap[cmd.args].active == 0x1F && level->isFlipped) needFlip = true; - LOG("FLIP_OFF\n"); + break; + case TR::Action::CAMERA_TARGET : + ((Camera*)level->cameraController)->viewTarget = (Controller*)level->entities[cmd.args].controller; break; case TR::Action::END : LOG("END\n"); break; - case TR::Action::SOUNDTRACK : - LOG("SOUNDTRACK\n"); + case TR::Action::SOUNDTRACK : { + int track = doTutorial(cmd.args); + + if (track == 0) break; + + // check trigger + TR::Flags &flags = level->tracks[track]; + + if (flags.once) + break; + + if (info.trigger == TR::Level::Trigger::SWITCH) + flags.active ^= info.trigInfo.mask; + else if (info.trigger == TR::Level::Trigger::ANTIPAD) + flags.active &= ~info.trigInfo.mask; + else + flags.active |= info.trigInfo.mask; + + if (flags.active == 0x1F) { + flags.once |= info.trigInfo.once; + game->playTrack(track); + } else + game->stopTrack(); + break; - case TR::Action::HARDCODE : - LOG("HARDCODE\n"); + } + case TR::Action::EFFECT : + LOG("EFFECT\n"); break; case TR::Action::SECRET : if (!level->secrets[cmd.args]) { level->secrets[cmd.args] = true; - playSound(TR::SND_SECRET, pos, 0); + if (!playSound(TR::SND_SECRET, pos, 0)) + game->playTrack(TR::TRACK_SECRET); } break; } @@ -2176,6 +2203,9 @@ struct Lara : Character { virtual void update() { Character::update(); + + if (getEntity().type != TR::Entity::LARA) + return; if (damageTime > 0.0f) damageTime = max(0.0f, damageTime - Core::deltaTime); @@ -2203,6 +2233,9 @@ struct Lara : Character { } virtual void updateVelocity() { + if (getEntity().type != TR::Entity::LARA) + return; + if (!(input & DEATH)) checkTrigger(); @@ -2315,6 +2348,8 @@ struct Lara : Character { } virtual void updatePosition() { // TODO: sphere / bbox collision + if (getEntity().type != TR::Entity::LARA) + return; // tilt control vec2 vTilt(LARA_TILT_SPEED * Core::deltaTime, LARA_TILT_MAX); if (stand == STAND_UNDERWATER) diff --git a/src/level.h b/src/level.h index 7af9520..956df04 100644 --- a/src/level.h +++ b/src/level.h @@ -42,6 +42,7 @@ struct Level : IGame { Sound::Sample *sndUnderwater; Sound::Sample *sndCurrent; + int curTrack; bool lastTitle; // IGame implementation ======== @@ -61,6 +62,11 @@ struct Level : IGame { return lara; } + virtual bool isCutscene() { + if (level.id == TR::TITLE) return false; + return camera->state == Camera::STATE_CUTSCENE; + } + virtual uint16 getRandomBox(uint16 zone, uint16 *zones) { ZoneCache::Item *item = zoneCache->getBoxes(zone, zones); return item->boxes[int(randf() * item->count)]; @@ -101,7 +107,6 @@ struct Level : IGame { Core::basis.identity(); } - virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) { PROFILE_MARKER("ENVIRONMENT"); Core::eye = 0.0f; @@ -140,7 +145,7 @@ struct Level : IGame { } virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const { - if (level.version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET) + if (level.version == TR::VER_TR1_PSX && id == TR::SND_SECRET) return NULL; int16 a = level.soundsMap[id]; @@ -160,9 +165,52 @@ struct Level : IGame { } return NULL; } + + void stopChannel(Sound::Sample *channel) { + if (channel == sndSoundtrack) { + if (sndCurrent == sndSoundtrack) + sndCurrent = NULL; + sndSoundtrack = NULL; + playTrack(0); + } + } + + virtual void playTrack(int track, bool restart = false) { + if (track == 0) + track = TR::LEVEL_INFO[level.id].ambientTrack; + + if (curTrack == track) { + if (restart && sndSoundtrack) { + sndSoundtrack->replay(); + sndSoundtrack->setVolume(1.0f, 0.2f); + } + return; + } + curTrack = track; + + if (track == 0) return; + + if (sndSoundtrack) { + sndSoundtrack->setVolume(-1.0f, 0.2f); + if (sndCurrent == sndSoundtrack) + sndCurrent = NULL; + sndSoundtrack = NULL; + } + + char title[32]; + sprintf(title, "audio/track_%02d.ogg", track); + + sndSoundtrack = Sound::play(new Stream(title), vec3(0.0f), 0.01f, 1.0f, track == TR::LEVEL_INFO[level.id].ambientTrack ? Sound::Flags::LOOP : 0); + if (sndSoundtrack) + sndSoundtrack->setVolume(1.0f, 0.2f); + } + + virtual void stopTrack() { + playTrack(0); + } //============================== - Level(Stream &stream, Stream *snd, bool demo, bool home) : level(stream, demo), inventory(this), lara(NULL) { + Level(Stream &stream) : level(stream), inventory(this), lara(NULL) { params->time = 0.0f; #ifdef _DEBUG @@ -180,7 +228,7 @@ struct Level : IGame { switch (entity.type) { case TR::Entity::LARA : case TR::Entity::CUT_1 : - entity.controller = (lara = new Lara(this, i, home)); + entity.controller = (lara = new Lara(this, i)); break; case TR::Entity::ENEMY_WOLF : entity.controller = new Wolf(this, i); @@ -297,9 +345,7 @@ struct Level : IGame { } } - lastTitle = false; - - if (!isTitle()) { + if (level.id != TR::TITLE) { ASSERT(lara != NULL); camera = new Camera(this, lara); @@ -313,22 +359,19 @@ struct Level : IGame { initReflections(); - // init sounds + // init sounds //sndSoundtrack = Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), vec3(0.0f), 1, 1, Sound::Flags::LOOP); - sndSoundtrack = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP); sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP); if (sndUnderwater) sndUnderwater->volume = sndUnderwater->volumeTarget = 0.0f; - sndCurrent = sndSoundtrack; - for (int i = 0; i < level.soundSourcesCount; i++) { TR::SoundSource &src = level.soundSources[i]; lara->playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), Sound::PAN | Sound::LOOP | Sound::STATIC); } - lara->activate(); + lastTitle = false; } else { camera = NULL; ambientCache = NULL; @@ -338,7 +381,13 @@ struct Level : IGame { sndSoundtrack = NULL; sndUnderwater = NULL; sndCurrent = NULL; + lastTitle = true; + inventory.toggle(Inventory::PAGE_OPTION); } + + sndSoundtrack = NULL; + playTrack(curTrack = 0); + sndCurrent = sndSoundtrack; } virtual ~Level() { @@ -363,10 +412,6 @@ struct Level : IGame { Sound::stopAll(); } - bool isTitle() { - return lara == NULL || inventory.isActive(); - } - static void fillCallback(int id, int width, int height, int tileX, int tileY, void *userData, void *data) { static const uint32 whiteColor = 0xFFFFFFFF; static const uint32 healthColor[5] = { 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F }; @@ -485,7 +530,7 @@ struct Level : IGame { // repack texture tiles Atlas *tiles = new Atlas(level.objectTexturesCount + level.spriteTexturesCount + 3, &level, fillCallback); // add textures - int startIdx = level.version == TR::Level::VER_TR1_PSX ? 256 : 0; // skip palette color for PSX version + int startIdx = level.version == TR::VER_TR1_PSX ? 256 : 0; // skip palette color for PSX version for (int i = startIdx; i < level.objectTexturesCount; i++) { TR::ObjectTexture &t = level.objectTextures[i]; int16 tx = (t.tile.index % 4) * 256; @@ -699,7 +744,7 @@ struct Level : IGame { void renderEntity(const TR::Entity &entity) { //if (entity.room != lara->getRoomIndex()) return; if (entity.type == TR::Entity::NONE || !entity.modelIndex) return; - if (Core::pass == Core::passShadow && !TR::castShadow(entity.type)) return; + if (Core::pass == Core::passShadow && !entity.castShadow()) return; ASSERT(entity.controller); @@ -710,7 +755,7 @@ struct Level : IGame { int roomIndex = controller->getRoomIndex(); TR::Room &room = level.rooms[roomIndex]; - if (entity.type != TR::Entity::LARA) // TODO: remove this hack (collect conjugate room entities) + if (!entity.isLara() && !entity.isActor()) if (!room.flags.visible || entity.flags.invisible || entity.flags.rendered) return; @@ -720,7 +765,6 @@ struct Level : IGame { if (entity.type == TR::Entity::CRYSTAL) type = Shader::MIRROR; - setRoomParams(roomIndex, type, 1.0f, intensityf(lum), controller->specular, 1.0f, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true); if (isModel) { // model @@ -748,19 +792,13 @@ struct Level : IGame { } void update() { - #ifdef LEVEL_EDITOR - if (Input::down[ik0]) { - Input::down[ik0] = false; - lara->reset(TR::NO_ROOM, camera->pos, camera->angle.y, false); - } - #endif Sound::Sample *sndChanged = sndCurrent; - if (isTitle()) { + if (inventory.isActive() || level.id == TR::TITLE) { Sound::reverb.setRoomSize(vec3(1.0f)); inventory.update(); - - sndChanged = NULL; + if (level.id != TR::TITLE) + sndChanged = NULL; } else { params->time += Core::deltaTime; @@ -882,13 +920,6 @@ struct Level : IGame { } void getVisibleRooms(int *roomsList, int &roomsCount, int from, int to, const vec4 &viewPort, bool water, int count = 0) { - if (camera->cutscene) { - roomsCount = level.roomsCount; - for (int i = 0; i < roomsCount; i++) - roomsList[i] = i; - return; - } - if (count > 16) { //ASSERT(false); return; @@ -911,10 +942,8 @@ struct Level : IGame { for (int i = 0; i < room.portalsCount; i++) { TR::Room::Portal &p = room.portals[i]; - if (from == room.portals[i].roomIndex || !checkPortal(room, p, viewPort, clipPort)) - continue; - - getVisibleRooms(roomsList, roomsCount, to, p.roomIndex, clipPort, water, count + 1); + if (from != room.portals[i].roomIndex && checkPortal(room, p, viewPort, clipPort)) + getVisibleRooms(roomsList, roomsCount, to, p.roomIndex, clipPort, water, count + 1); } } @@ -1023,6 +1052,8 @@ struct Level : IGame { #ifdef _DEBUG void renderDebug() { + if (level.id == TR::TITLE) return; + // Core::mViewInv = camera->mViewInv; // Core::mView = Core::mViewInv.inverse(); Core::setViewport(0, 0, Core::width, Core::height); @@ -1277,41 +1308,46 @@ struct Level : IGame { } void renderUI() { + if (isCutscene()) return; + UI::begin(); - // render health & oxygen bars - vec2 size = vec2(180, 10); + if (level.id != TR::TITLE) { + // render health & oxygen bars + vec2 size = vec2(180, 10); - float health = lara->health / float(LARA_MAX_HEALTH); - float oxygen = lara->oxygen / float(LARA_MAX_OXYGEN); + float health = lara->health / float(LARA_MAX_HEALTH); + float oxygen = lara->oxygen / float(LARA_MAX_OXYGEN); - if ((params->time - int(params->time)) < 0.5f) { // blinking - if (health <= 0.2f) health = 0.0f; - if (oxygen <= 0.2f) oxygen = 0.0f; - } - - if (inventory.showHealthBar() || (!inventory.active && (!lara->emptyHands() || lara->damageTime > 0.0f || health <= 0.2f))) { - UI::renderBar(0, vec2(UI::width - 32 - size.x, 32), size, health); - - if (!inventory.active && !lara->emptyHands()) { // ammo - int index = inventory.contains(lara->getCurrentWeaponInv()); - if (index > -1) - inventory.renderItemCount(inventory.items[index], vec2(UI::width - 32 - size.x, 64), size.x); + if ((params->time - int(params->time)) < 0.5f) { // blinking + if (health <= 0.2f) health = 0.0f; + if (oxygen <= 0.2f) oxygen = 0.0f; } - } - if (!lara->dozy && (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER)) - UI::renderBar(1, vec2(32, 32), size, oxygen); + if (inventory.showHealthBar() || (!inventory.active && (!lara->emptyHands() || lara->damageTime > 0.0f || health <= 0.2f))) { + UI::renderBar(0, vec2(UI::width - 32 - size.x, 32), size, health); + + if (!inventory.active && !lara->emptyHands()) { // ammo + int index = inventory.contains(lara->getCurrentWeaponInv()); + if (index > -1) + inventory.renderItemCount(inventory.items[index], vec2(UI::width - 32 - size.x, 64), size.x); + } + } + + if (!lara->dozy && (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER)) + UI::renderBar(1, vec2(32, 32), size, oxygen); + } inventory.renderUI(); - UI::renderHelp(); + if (level.id != TR::TITLE) + UI::renderHelp(); UI::end(); } void render() { - bool title = isTitle(); + bool title = inventory.isActive() || level.id == TR::TITLE; bool copyBg = title && lastTitle != title; if (copyBg) { diff --git a/src/mesh.h b/src/mesh.h index 6aaa87f..3adefd3 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -803,7 +803,7 @@ struct MeshBuilder { v.param = { range, frame, 0, 0 }; } - if (level->version == TR::Level::VER_TR1_PSX && !triangle) + if (level->version == TR::VER_TR1_PSX && !triangle) swap(vertices[vCount + 2].texCoord, vertices[vCount + 3].texCoord); } diff --git a/src/shaders/filter.glsl b/src/shaders/filter.glsl index 5b455d7..08aba8a 100644 --- a/src/shaders/filter.glsl +++ b/src/shaders/filter.glsl @@ -6,21 +6,22 @@ R"====( varying vec2 vTexCoord; -uniform int uType; +uniform vec4 uParam; #ifdef VERTEX attribute vec4 aCoord; void main() { vTexCoord = aCoord.zw; + #ifdef FILTER_DEFAULT + vTexCoord = ((vTexCoord * 2.0 - 1.0) * uParam.xy) * 0.5 + 0.5; + #endif gl_Position = vec4(aCoord.xy, 0.0, 1.0); } #else uniform sampler2D sDiffuse; uniform sampler2D sNormal; - uniform vec4 uParam; - vec4 downsample() { // uParam (textureSize, unused, unused, unused) float k = 1.0 / uParam.x; // inverted texture size @@ -29,7 +30,7 @@ uniform int uType; for (float x = -1.5; x < 2.0; x++) { vec4 p; p.xyz = texture2D(sDiffuse, vTexCoord + vec2(x, y) * k).xyz; - p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114)); + p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114)); p.xyz *= p.w; color += p; } diff --git a/src/sound.h b/src/sound.h index 316a2db..f1ed826 100644 --- a/src/sound.h +++ b/src/sound.h @@ -424,6 +424,7 @@ namespace Sound { int flags; int id; bool isPlaying; + bool stopAfterFade; Sample(Stream *stream, const vec3 &pos, float volume, float pitch, int flags, int id) : decoder(NULL), pos(pos), volume(volume), volumeTarget(volume), volumeDelta(0.0f), pitch(pitch), flags(flags), id(id) { uint32 fourcc; @@ -484,15 +485,21 @@ namespace Sound { } void setVolume(float value, float time) { + if (value < 0.0f) { + stopAfterFade = true; + value = 0.0f; + } else + stopAfterFade = false; + volumeTarget = value; volumeDelta = volumeTarget - volume; if (time > 0.0f) volumeDelta /= 44100.0f * time; } - vec3 getPan() { + vec2 getPan() { if (!(flags & PAN)) - return vec3(1.0f); + return vec2(1.0f); mat4 m = Sound::listener.matrix; vec3 v = pos - m.offset.xyz; @@ -502,11 +509,11 @@ namespace Sound { float l = min(1.0f, 1.0f - pan); float r = min(1.0f, 1.0f + pan); - return vec3(l, r, 1.0f) * dist; + return vec2(l, r) * dist; } bool render(Frame *frames, int count) { - if (!isPlaying) return 0; + if (!isPlaying) return false; // decode int i = 0; while (i < count) { @@ -520,14 +527,9 @@ namespace Sound { } i += res; } - // apply pan - vec3 pan = getPan(); - if (pan.x < 1.0f || pan.y < 1.0f) - for (int j = 0; j < i; j++) { - frames[j].L = int(frames[j].L * pan.x); - frames[j].R = int(frames[j].R * pan.y); - } // apply volume + vec2 pan = getPan(); + vec2 vol = vec2(1.0f);//pan * volume; for (int j = 0; j < i; j++) { if (volumeDelta != 0.0f) { // increase / decrease channel volume volume += volumeDelta; @@ -535,20 +537,35 @@ namespace Sound { (volumeDelta > 0.0f && volume > volumeTarget)) { volume = volumeTarget; volumeDelta = 0.0f; + if (stopAfterFade) + isPlaying = false; } + vol = pan * volume; } - frames[j].L *= volume; - frames[j].R *= volume; + frames[j].L = int(frames[j].L * vol.x); + frames[j].R = int(frames[j].R * vol.y); } return true; } + + void stop() { + isPlaying = false; + } + + void replay() { + decoder->replay(); + } } *channels[SND_CHANNELS_MAX]; int channelsCount; + typedef void (Callback)(Sample *channel); + Callback *callback; + Filter::Reverberation reverb; void init() { channelsCount = 0; + callback = NULL; #ifdef DECODE_MP3 mp3_decode_init(); #endif @@ -621,6 +638,7 @@ namespace Sound { for (int i = 0; i < channelsCount; i++) if (!channels[i]->isPlaying) { + if (callback) callback(channels[i]); delete channels[i]; channels[i] = channels[--channelsCount]; i--; @@ -657,7 +675,7 @@ namespace Sound { channels[i]->pos = pos; channels[i]->pitch = pitch; if (flags & (REPLAY | UNIQUE)) - channels[i]->decoder->replay(); + channels[i]->replay(); delete stream; return channels[i]; } diff --git a/src/texture.h b/src/texture.h index b6c8717..c4f2068 100644 --- a/src/texture.h +++ b/src/texture.h @@ -125,6 +125,71 @@ struct Texture { glBindTexture(cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, 0); } } + + static Texture* LoadPCX(const char *fileName) { + Stream stream(fileName); + + struct Color24 { + uint8 r, g, b; + }; + + struct Color32 { + uint8 r, g, b, a; + }; + + struct PCX { + uint8 magic; + uint8 version; + uint8 compression; + uint8 bpp; + uint16 rect[4]; + uint16 width; + uint16 height; + uint8 other[48 + 64]; + } pcx; + + stream.raw(&pcx, sizeof(PCX)); + + ASSERT(pcx.bpp == 8); + ASSERT(pcx.compression == 1); + + int i = 0; + int size = pcx.width * pcx.height; + uint8 *buffer = new uint8[size + 256 * 3 + size * 4]; + + while (i < size) { + uint8 n; + stream.read(n); + if ((n & 0xC0) == 0xC0) { + uint8 count = n & 0x3F; + stream.read(n); + memset(&buffer[i], n, count); + i += count; + } else + buffer[i++] = n; + } + + uint8 flag; + stream.read(flag); + ASSERT(flag == 0x0C); + + Color24 *palette = (Color24*)&buffer[size]; + stream.raw(palette, 256 * 3); + + Color32 *data = (Color32*)&palette[256]; + for (i = 0; i < size; i++) { + Color24 &c = palette[buffer[i]]; + data[i].r = c.r; + data[i].g = c.g; + data[i].b = c.b; + data[i].a = 255; + } + + Texture *tex = new Texture(pcx.width, pcx.height, Texture::RGBA, false, data); + delete[] buffer; + + return tex; + } }; #define ATLAS_BORDER 8