1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-14 00:54:05 +02:00

! CAMERA STILL BROKEN, PLAYABLE ONLY FOR WINDOWS WITH ORIGINAL AUDIO RESOURCE FILES ! fix for other platforms will be soon.

#8 part of camera's trigger logic; #3 animation flipmap command; turn off blob shadows for cutscene actors; #22 soundtrack trigger support (audio/track_*.ogg); #11 title screen support, turn off inventory for cutscenes; #23 level id detector and no more ambient sound, home and demo flags needed; GYM tutorial, PCX texture loader for title screen;
This commit is contained in:
XProger
2017-08-29 05:25:29 +03:00
parent 66bee62a25
commit ec6ab322bb
12 changed files with 678 additions and 292 deletions

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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];
}

View File

@@ -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