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:
160
src/camera.h
160
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();
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
293
src/format.h
293
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
|
||||
|
18
src/game.h
18
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() {
|
||||
|
@@ -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);
|
||||
|
127
src/lara.h
127
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)
|
||||
|
158
src/level.h
158
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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
46
src/sound.h
46
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];
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user