1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-28 07:20:26 +02:00

#15 TR3 level format support

This commit is contained in:
XProger
2017-11-24 12:27:28 +03:00
parent 90570a3ec3
commit 5a40006671
11 changed files with 1167 additions and 308 deletions

View File

@@ -373,7 +373,7 @@ struct Camera : ICamera {
fov = firstPerson ? 90.0f : 65.0f;
znear = firstPerson ? 8.0f : 32.0f;
zfar = 40.0f * 1024.0f;
zfar = 45.0f * 1024.0f;
}
};

View File

@@ -375,6 +375,24 @@ struct Controller {
info.climb = (*fd++).data; // climb mask
break;
case 0x07 :
case 0x08 :
case 0x09 :
case 0x0A :
case 0x0B :
case 0x0C :
case 0x0D :
case 0x0E :
case 0x0F :
case 0x10 :
case 0x11 :
case 0x12 : break; // TODO TR3 triangulation
case 0x13 : break; // TODO TR3 monkeyswing
case 0x14 :
case 0x15 : break; // TODO TR3 minecart
default : LOG("unknown func: %d\n", cmd.func);
}

View File

@@ -334,8 +334,8 @@ namespace Core {
#define MAX_CACHED_LIGHTS 3
#define MAX_RENDER_BUFFERS 32
#define MAX_CONTACTS 15
#define MAX_ANIM_TEX_RANGES 16
#define MAX_ANIM_TEX_OFFSETS 32
#define MAX_ANIM_TEX_RANGES 32
#define MAX_ANIM_TEX_OFFSETS 130
struct Shader;
struct Texture;

File diff suppressed because it is too large Load Diff

View File

@@ -155,7 +155,7 @@ struct Inventory {
if (!stream) return;
Inventory *inv = (Inventory*)userData;
inv->background[0] = Texture::LoadPCX(*stream);
inv->background[0] = Texture::Load(*stream);
delete stream;
}
@@ -207,6 +207,8 @@ struct Inventory {
new Stream("level/TITLEH.PCX", loadTitleBG, this);
if (level->version & TR::VER_TR2)
new Stream("level/2/TITLE.PCX", loadTitleBG, this);
if (level->version & TR::VER_TR3)
new Stream("level/3/TITLEUK.BMP", loadTitleBG, this);
} else {
add(TR::Entity::INV_COMPASS);
@@ -445,13 +447,22 @@ struct Inventory {
game->playSound(TR::SND_INV_PAGE);
item->value = 1;
passportSlotCount = 2;
if (level->version & TR::VER_TR1) {
passportSlots[0] = TR::LVL_TR1_1;
passportSlots[1] = TR::LVL_TR1_2;
} else {
passportSlots[0] = TR::LVL_TR2_WALL;
passportSlots[1] = TR::LVL_TR2_BOAT;
switch (level->version & TR::VER_VERSION) {
case TR::VER_TR1 :
passportSlotCount = 2;
passportSlots[0] = TR::LVL_TR1_1;
passportSlots[1] = TR::LVL_TR1_2;
break;
case TR::VER_TR2 :
passportSlotCount = 2;
passportSlots[0] = TR::LVL_TR2_WALL;
passportSlots[1] = TR::LVL_TR2_BOAT;
break;
case TR::VER_TR3 :
passportSlotCount = 1;
passportSlots[0] = TR::LVL_TR3_JUNGLE;
break;
default : ASSERT(false);
}
break;
@@ -481,8 +492,8 @@ struct Inventory {
TR::LevelID id = level->id;
switch (item->value) {
case 0 : nextLevel = passportSlots[slot]; break;
case 1 : nextLevel = id == TR::LVL_TR1_TITLE ? TR::LVL_TR1_1 : (id == TR::LVL_TR2_TITLE ? TR::LVL_TR2_WALL : id); break;
case 2 : nextLevel = level->isTitle() ? TR::LVL_MAX : level->titleId(); break;
case 1 : nextLevel = level->isTitle() ? level->getStartId() : id; break;
case 2 : nextLevel = level->isTitle() ? TR::LVL_MAX : level->getTitleId(); break;
}
if (nextLevel != TR::LVL_MAX) {
@@ -545,10 +556,7 @@ struct Inventory {
}
if (item->type == TR::Entity::INV_HOME && phaseChoose == 1.0f && key == cAction) {
if (level->version & TR::VER_TR1)
nextLevel = TR::LVL_TR1_GYM;
if (level->version & TR::VER_TR2)
nextLevel = TR::LVL_TR2_ASSAULT;
nextLevel = level->getHomeId();
toggle();
}

View File

@@ -411,6 +411,9 @@ struct Lara : Character {
} *braid;
Lara(IGame *game, int entity) : Character(game, entity, LARA_MAX_HEALTH), dozy(false), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), braid(NULL) {
if (level->extra.laraSkin > -1)
level->entities[entity].modelIndex = level->extra.laraSkin + 1;
jointChest = 7;
jointHead = 14;
rangeChest = vec4(-0.40f, 0.40f, -0.90f, 0.90f) * PI;
@@ -445,7 +448,7 @@ struct Lara : Character {
}
if (level->extra.braid > -1)
braid = new Braid(this, (level->version & TR::VER_TR2) ? vec3(0.0f, -16.0f, -48.0f) : vec3(-4.0f, 24.0f, -48.0f));
braid = new Braid(this, (level->version & (TR::VER_TR2 | TR::VER_TR3)) ? vec3(0.0f, -16.0f, -48.0f) : vec3(-4.0f, 24.0f, -48.0f));
#ifdef _DEBUG
//reset(14, vec3(40448, 3584, 60928), PI * 0.5f, STAND_ONWATER); // gym (pool)
//reset(0, vec3(74858, 3072, 20795), 0); // level 1 (dart)
@@ -1382,7 +1385,8 @@ struct Lara : Character {
case TR::Effect::MESH_SWAP_1 :
case TR::Effect::MESH_SWAP_2 :
case TR::Effect::MESH_SWAP_3 : Character::cmdEffect(fx);
case 26 : break; // TR2 TODO reset_hair
case 26 : break; // TODO TR2 reset_hair
case 32 : break; // TODO TR3 footprint
default : LOG("unknown effect command %d (anim %d)\n", fx, animation.index); ASSERT(false);
}
}
@@ -1436,7 +1440,7 @@ struct Lara : Character {
}
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
if (dozy) return;
if (dozy || level->isCutsceneLevel()) return;
if (health <= 0.0f) return;
@@ -1694,7 +1698,7 @@ struct Lara : Character {
if (level->state.tracks[track].once) {
timer += Core::deltaTime;
if (timer > 3.0f)
game->loadLevel(level->titleId());
game->loadLevel(level->getTitleId());
} else {
if (state != STATE_WATER_OUT)
return 0;
@@ -1980,7 +1984,7 @@ struct Lara : Character {
else
flags.active |= info.trigInfo.mask;
if ( (flags.active == TR::ACTIVE) || (((level->version & TR::VER_TR2)) && flags.active) ) {
if ( (flags.active == TR::ACTIVE) || (((level->version & (TR::VER_TR2 | TR::VER_TR3))) && flags.active) ) {
flags.once |= info.trigInfo.once;
game->playTrack(track);
} else
@@ -1995,7 +1999,7 @@ struct Lara : Character {
if (!(level->state.secrets & (1 << cmd.args))) {
level->state.secrets |= 1 << cmd.args;
if (!game->playSound(TR::SND_SECRET, pos))
game->playTrack(TR::TRACK_SECRET);
game->playTrack(TR::TRACK_TR1_SECRET);
}
break;
}
@@ -2379,7 +2383,8 @@ struct Lara : Character {
if (state == STATE_FORWARD_JUMP || state == STATE_UP_JUMP || state == STATE_BACK_JUMP || state == STATE_LEFT_JUMP || state == STATE_RIGHT_JUMP || state == STATE_FALL || state == STATE_REACH || state == STATE_SLIDE || state == STATE_SLIDE_BACK) {
game->waterDrop(pos, 256.0f, 0.2f);
game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), pos);
if (level->extra.waterSplash > -1)
game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), pos);
pos.y += 100.0f;
angle.x = -45.0f * DEG2RAD;
return animation.setAnim(ANIM_WATER_FALL); // TODO: wronng animation
@@ -2388,7 +2393,8 @@ struct Lara : Character {
if (state == STATE_SWAN_DIVE || state == STATE_FAST_DIVE) {
angle.x = -PI * 0.5f;
game->waterDrop(pos, 128.0f, 0.2f);
game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), pos);
if (level->extra.waterSplash > -1)
game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), pos);
return STATE_DIVE;
}

View File

@@ -64,15 +64,19 @@ struct Level : IGame {
strcat(buf, "level/");
if (level.version & TR::VER_TR2)
strcat(buf, "2/");
if (level.version & TR::VER_TR3)
strcat(buf, "3/");
strcat(buf, TR::LEVEL_INFO[id].name);
#ifdef __EMSCRIPTEN__
strcat(buf, ".PSX");
#else
switch (level.version) {
case TR::VER_TR1_PC : strcat(buf, ".PHD"); break;
case TR::VER_TR1_PSX : strcat(buf, ".PSX"); break;
case TR::VER_TR2_PC : strcat(buf, ".TR2"); break;
case TR::VER_TR2_PSX : strcat(buf, ".PSX"); break;
case TR::VER_TR2_PC :
case TR::VER_TR3_PC : strcat(buf, ".TR2"); break;
case TR::VER_TR1_PSX :
case TR::VER_TR2_PSX :
case TR::VER_TR3_PSX : strcat(buf, ".PSX"); break;
}
#endif
new Stream(buf, loadAsync);
@@ -85,7 +89,7 @@ struct Level : IGame {
return;
}
#endif
loadLevel(level.id == TR::LVL_TR1_10C || level.id == TR::LVL_TR2_HOUSE ? level.titleId() : TR::LevelID(level.id + 1));
loadLevel(level.isEnd() ? level.getTitleId() : TR::LevelID(level.id + 1));
}
virtual void saveGame(int slot) {
@@ -468,12 +472,12 @@ struct Level : IGame {
if (a == -1) return NULL;
TR::SoundInfo &b = level.soundsInfo[a];
if (b.chance == 0 || (rand() & 0x7fff) <= b.chance) {
int index = b.offset + rand() % b.flags.count;
float volume = (float)b.volume / 0x7FFF;
float pitch = b.flags.pitch ? (0.9f + randf() * 0.2f) : 1.0f;
if (b.chance == 0 || randf() <= b.chance) {
int index = b.index + rand() % b.flags.count;
float volume = b.volume;
float pitch = 1.0f + (b.flags.pitch ? ((randf() - 0.5f) * b.pitch) : 0.0f);
if (level.version == TR::VER_TR2_PSX) // fix 8 kHz VAG in PSX TR2
if (level.version == TR::VER_TR2_PSX) // fix for 8 kHz VAG in PSX TR2
pitch *= 8000.0f / 11025.0f;
if (!(flags & Sound::MUSIC)) {
@@ -541,6 +545,12 @@ struct Level : IGame {
case TR::VER_TR2_PSX :
sprintf(title, "audio/2/track_%02d.ogg", int(level.remapTrack(track)));
break;
case TR::VER_TR3_PC :
case TR::VER_TR3_PSX :
#ifndef __EMSCRIPTEN__
playAsync(Sound::openWAD(NULL, track), this);
#endif
return;
default : return;
}
@@ -588,9 +598,7 @@ struct Level : IGame {
// init sounds
//sndSoundtrack = Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), vec3(0.0f), 1, 1, Sound::Flags::LOOP);
sndUnderwater = playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP | Sound::MUSIC);
if (sndUnderwater)
sndUnderwater->volume = sndUnderwater->volumeTarget = 0.0f;
sndUnderwater = NULL;
for (int i = 0; i < level.soundSourcesCount; i++) {
TR::SoundSource &src = level.soundSources[i];
@@ -919,7 +927,7 @@ struct Level : IGame {
// repack texture tiles
Atlas *tiles = new Atlas(level.objectTexturesCount + level.spriteTexturesCount + UI::BAR_MAX, &level, fillCallback);
// add textures
int texIdx = (level.version == TR::VER_TR1_PSX || level.version == TR::VER_TR2_PSX) ? 256 : 0; // skip palette color for PSX version
int texIdx = (level.version & TR::VER_PSX) ? 256 : 0; // skip palette color for PSX version
for (int i = texIdx; i < level.objectTexturesCount; i++) {
TR::ObjectTexture &t = level.objectTextures[i];
int16 tx = (t.tile.index % 4) * 256;
@@ -1033,7 +1041,12 @@ struct Level : IGame {
//Animation anim(&level, &level.models[level.extra.sky]);
Basis b = Basis(quat(vec3(1, 0, 0), PI * 0.5f), vec3(0));
// TODO TR2 TR3 use animation frame to get skydome rotation
Basis b;
if (level.version & TR::VER_TR2)
b = Basis(quat(vec3(1, 0, 0), PI * 0.5f), vec3(0));
else
b = Basis(quat(0, 0, 0, 1), vec3(0));
Core::setDepthTest(false);
setShader(Core::pass, Shader::FLASH, false, false);
@@ -1223,7 +1236,7 @@ struct Level : IGame {
}
void update() {
if (level.isCutsceneLevel() && (lara->health > 0.0f && !sndSoundtrack))
if (level.isCutsceneLevel() && (lara->health > 0.0f && !sndSoundtrack && TR::LEVEL_INFO[level.id].ambientTrack != TR::NO_TRACK))
return;
if (Input::state[cInventory] && !level.isTitle()) {
@@ -1264,7 +1277,15 @@ struct Level : IGame {
Controller::clearInactive();
sndChanged = camera->isUnderwater() ? sndUnderwater : sndSoundtrack;
if (camera->isUnderwater()) {
if (!sndUnderwater) {
sndUnderwater = playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP | Sound::MUSIC);
if (sndUnderwater)
sndUnderwater->volume = sndUnderwater->volumeTarget = 0.0f;
}
sndChanged = sndUnderwater;
} else
sndChanged = sndSoundtrack;
}
if (sndChanged != sndCurrent) {
@@ -1450,12 +1471,11 @@ struct Level : IGame {
int roomsCount = 0;
getVisibleRooms(roomsList, roomsCount, TR::NO_ROOM, roomIndex, vec4(-1.0f, -1.0f, 1.0f, 1.0f), water);
// show all rooms
/*
for (int i = 0; i < level.roomsCount; i++)
roomsList[i] = i;
roomsCount = level.roomsCount;
*/
if (level.isCutsceneLevel()) {
for (int i = 0; i < level.roomsCount; i++)
roomsList[i] = i;
roomsCount = level.roomsCount;
}
if (water && waterCache) {
for (int i = 0; i < roomsCount; i++)

View File

@@ -303,7 +303,7 @@ struct MeshBuilder {
int y = m.y;
int z = m.z - room.info.z;
int d = m.rotation.value / 0x4000;
buildMesh(!transp, mesh, level, indices, vertices, iCount, vCount, vStartRoom, 0, x, y, z, d);
buildMesh(!transp, mesh, level, indices, vertices, iCount, vCount, vStartRoom, 0, x, y, z, d, m.color);
}
range.geometry[transp].iCount = iCount - range.geometry[transp].iStart;
}
@@ -326,6 +326,8 @@ struct MeshBuilder {
int vStartModel = vCount;
aCount++;
TR::Color32 COLOR_WHITE = { 255, 255, 255, 255 };
for (int i = 0; i < level.modelsCount; i++) {
TR::Model &model = level.models[i];
ModelRange &range = models[i];
@@ -339,9 +341,9 @@ struct MeshBuilder {
if (!index && model.mStart + j > 0) continue;
TR::Mesh &mesh = level.meshes[index];
bool opaque = buildMesh(true, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0);
bool opaque = buildMesh(true, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0, COLOR_WHITE);
if (!opaque)
buildMesh(false, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0);
buildMesh(false, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0, COLOR_WHITE);
TR::Entity::fixOpaque(model.type, opaque);
range.opaque &= opaque;
}
@@ -662,7 +664,8 @@ struct MeshBuilder {
TR::Room::Data::Vertex &v = d.vertices[f.vertices[k]];
vertices[vCount].coord = { v.vertex.x, v.vertex.y, v.vertex.z, 0 };
vertices[vCount].normal = { n.x, n.y, n.z, 0 };
vertices[vCount].color = { 255, 255, 255, intensity(v.lighting) };
TR::Color32 c = v.color;
vertices[vCount].color = { c.b, c.g, c.r, intensity(v.lighting) };
vCount++;
}
}
@@ -688,7 +691,8 @@ struct MeshBuilder {
auto &v = d.vertices[f.vertices[k]];
vertices[vCount].coord = { v.vertex.x, v.vertex.y, v.vertex.z, 0 };
vertices[vCount].normal = { n.x, n.y, n.z, 0 };
vertices[vCount].color = { 255, 255, 255, intensity(v.lighting) };
TR::Color32 c = v.color;
vertices[vCount].color = { c.b, c.g, c.r, intensity(v.lighting) };
vCount++;
}
}
@@ -696,7 +700,7 @@ struct MeshBuilder {
return isOpaque;
}
bool buildMesh(bool opaque, const TR::Mesh &mesh, const TR::Level &level, Index *indices, Vertex *vertices, int &iCount, int &vCount, int vStart, int16 joint, int x, int y, int z, int dir) {
bool buildMesh(bool opaque, const TR::Mesh &mesh, const TR::Level &level, Index *indices, Vertex *vertices, int &iCount, int &vCount, int vStart, int16 joint, int x, int y, int z, int dir, const TR::Color32 &color) {
TR::Color24 COLOR_WHITE = { 255, 255, 255 };
bool isOpaque = true;
@@ -711,6 +715,9 @@ struct MeshBuilder {
continue;
TR::Color32 c = f.flags.color ? level.getColor(f.flags.texture) : COLOR_WHITE;
c.r = int(( (c.r / 255.0f) * (color.b / 255.0f) ) * 255.0f);
c.g = int(( (c.g / 255.0f) * (color.g / 255.0f) ) * 255.0f);
c.b = int(( (c.b / 255.0f) * (color.r / 255.0f) ) * 255.0f);
addQuad(indices, iCount, vCount, vStart, vertices, &t,
mesh.vertices[f.vertices[0]].coord,
@@ -829,7 +836,7 @@ struct MeshBuilder {
v.param = { range, frame, 0, 0 };
}
if ((level->version == TR::VER_TR1_PSX || level->version == TR::VER_TR2_PSX) && !triangle)
if (((level->version & TR::VER_PSX)) && !triangle)
swap(vertices[vCount + 2].texCoord, vertices[vCount + 3].texCoord);
}

View File

@@ -2,7 +2,7 @@
#define H_SOUND
#define DECODE_VAG
//#define DECODE_ADPCM
#define DECODE_ADPCM
//#define DECODE_MP3
#define DECODE_OGG
@@ -210,7 +210,7 @@ namespace Sound {
if (seek % block == 0) {
for (int i = 0; i < channels; i++) {
char index;
uint8 index;
stream->read(index);
channel[i].c1 = coeff1[index];
channel[i].c2 = coeff2[index];
@@ -679,7 +679,7 @@ namespace Sound {
}
}
Stream *openWAD(const char *name) {
Stream *openWAD(const char *name, int index = -1) {
Stream *stream = new Stream("cdaudio.wad");
if (stream->size) {
struct Item {
@@ -690,7 +690,7 @@ namespace Sound {
for (int i = 0; i < 130; i++) {
stream->read(entity);
if (strcmp(name, entity.name) == 0) {
if ((name && strcmp(name, entity.name) == 0) || index == i) {
stream->setPos(entity.offset);
return stream;
}
@@ -701,6 +701,7 @@ namespace Sound {
}
Sample* play(Stream *stream, const vec3 &pos, float volume = 1.0f, float pitch = 0.0f, int flags = 0, int id = - 1) {
ASSERT(pitch >= 0.0f);
if (!stream) return NULL;
if (volume > 0.001f) {
if (!(flags & (FLIPPED | UNFLIPPED | MUSIC)) && (flags & PAN)) {

View File

@@ -166,15 +166,15 @@ struct Texture {
}
}
struct Color24 {
uint8 r, g, b;
};
struct Color32 {
uint8 r, g, b, a;
};
static Texture* LoadPCX(Stream &stream) {
struct Color24 {
uint8 r, g, b;
};
struct Color32 {
uint8 r, g, b, a;
};
struct PCX {
uint8 magic;
uint8 version;
@@ -240,6 +240,47 @@ struct Texture {
return tex;
}
static Texture* LoadBMP(Stream &stream) {
int32 offset, width, height;
stream.seek(10);
stream.read(offset);
stream.seek(4);
stream.read(width);
stream.read(height);
stream.seek(offset - stream.pos);
Color24 *data24 = new Color24[width * height];
Color32 *data32 = new Color32[width * height];
stream.raw(data24, width * height * sizeof(Color24));
Color32 *dst = data32;
for (int y = 0; y < height; y++) {
Color24 *src = data24 + (height - y - 1) * width;
for (int x = 0; x < width; x++) {
dst->r = src->b;
dst->g = src->g;
dst->b = src->r;
dst->a = 255;
dst++;
src++;
}
}
Texture *tex = new Texture(width, height, Texture::RGBA, false, data32);
delete[] data24;
delete[] data32;
return tex;
}
static Texture* Load(Stream &stream) {
uint16 magic;
stream.read(magic);
stream.seek(-int(sizeof(magic)));
if (magic == 0x4D42)
return LoadBMP(stream);
return LoadPCX(stream);
}
};
#define ATLAS_BORDER 8
@@ -349,7 +390,7 @@ struct Atlas {
Texture* pack() {
// TODO TR2 fix CUT2 AV
width = 2048;//nextPow2(int(sqrtf(float(size))));
width = 4096;//nextPow2(int(sqrtf(float(size))));
height = 2048;//(width * width / 2 > size) ? (width / 2) : width;
// sort
int *indices = new int[tilesCount];

View File

@@ -1358,7 +1358,8 @@ struct Waterfall : Controller {
vec2 p = (vec2(randf(), randf()) * 2.0f - 1.0f) * (512.0f - dropRadius);
vec3 dropPos = pos + vec3(p.x, 0.0f, p.y);
game->waterDrop(dropPos, dropRadius, dropStrength);
game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), dropPos);
if (level->extra.waterSplash > -1)
game->addEntity(TR::Entity::WATER_SPLASH, getRoomIndex(), dropPos);
}
#undef SPLASH_TIMESTEP