diff --git a/README.md b/README.md index 82fda24..d8ce036 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,9 @@ inspired by OpenTomb project http://opentomb.github.io/ ## Links * [Discord channel](https://discord.gg/EF8JaQB) * [Tomb Raider Forums thread](http://www.tombraiderforums.com/showthread.php?t=216618) + +## Screenshots +![Waterfall](http://xproger.info/projects/OpenLara/shots/waterfall.jpg) +![Double-aim](http://xproger.info/projects/OpenLara/shots/multi-aim.jpg) +![Cutscene](http://xproger.info/projects/OpenLara/shots/cut1.jpg) +![Cistern](http://xproger.info/projects/OpenLara/shots/flipmap.jpg) diff --git a/bin/008.ogg b/bin/008.ogg deleted file mode 100644 index 7151a74..0000000 Binary files a/bin/008.ogg and /dev/null differ diff --git a/bin/CUT1.PHD b/bin/CUT1.PHD deleted file mode 100644 index d77b4f1..0000000 Binary files a/bin/CUT1.PHD and /dev/null differ diff --git a/bin/CUT1.bat b/bin/CUT1.bat deleted file mode 100644 index 2272f85..0000000 --- a/bin/CUT1.bat +++ /dev/null @@ -1 +0,0 @@ -OpenLara CUT1.PHD 008.ogg \ No newline at end of file diff --git a/bin/OpenLara b/bin/OpenLara index 3bacdeb..632c07c 100755 Binary files a/bin/OpenLara and b/bin/OpenLara differ diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index 6ae96e0..e7bd3af 100644 Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ diff --git a/bin/audio/track_02.ogg b/bin/audio/track_02.ogg new file mode 100644 index 0000000..fe3d091 Binary files /dev/null and b/bin/audio/track_02.ogg differ diff --git a/bin/audio/track_03.ogg b/bin/audio/track_03.ogg new file mode 100644 index 0000000..6aef2cf Binary files /dev/null and b/bin/audio/track_03.ogg differ diff --git a/bin/audio/track_04.ogg b/bin/audio/track_04.ogg new file mode 100644 index 0000000..13fb829 Binary files /dev/null and b/bin/audio/track_04.ogg differ diff --git a/bin/05.ogg b/bin/audio/track_05.ogg similarity index 100% rename from bin/05.ogg rename to bin/audio/track_05.ogg diff --git a/bin/audio/track_09.ogg b/bin/audio/track_09.ogg new file mode 100644 index 0000000..249958b Binary files /dev/null and b/bin/audio/track_09.ogg differ diff --git a/bin/audio/track_12.ogg b/bin/audio/track_12.ogg new file mode 100644 index 0000000..54aa642 Binary files /dev/null and b/bin/audio/track_12.ogg differ diff --git a/bin/audio/track_13.ogg b/bin/audio/track_13.ogg new file mode 100644 index 0000000..18636a1 Binary files /dev/null and b/bin/audio/track_13.ogg differ diff --git a/bin/LEVEL2.PSX b/bin/level/LEVEL2.PSX similarity index 100% rename from bin/LEVEL2.PSX rename to bin/level/LEVEL2.PSX diff --git a/bin/level/TITLE.PSX b/bin/level/TITLE.PSX new file mode 100644 index 0000000..51d78cc Binary files /dev/null and b/bin/level/TITLE.PSX differ diff --git a/bin/level/TITLEH.PCX b/bin/level/TITLEH.PCX new file mode 100644 index 0000000..e0274db Binary files /dev/null and b/bin/level/TITLEH.PCX differ diff --git a/src/animation.h b/src/animation.h index 4cfb3bf..3713cdb 100644 --- a/src/animation.h +++ b/src/animation.h @@ -194,7 +194,7 @@ struct Animation { if (flip) { int frame = (*ptr++) - anim->frameStart; int fx = (*ptr++) & 0x3FFF; - *flip = fx == TR::EFFECT_ROTATE_180 && frame == frameIndex; + *flip = fx == TR::Effect::ROTATE_180 && frame == frameIndex; } else ptr += 2; break; diff --git a/src/cache.h b/src/cache.h index 466cb56..285abdb 100644 --- a/src/cache.h +++ b/src/cache.h @@ -34,10 +34,9 @@ const char GUI[] = struct ShaderCache { enum Effect { FX_NONE = 0, FX_UNDERWATER = 1, FX_ALPHA_TEST = 2, FX_CLIP_PLANE = 4 }; - IGame *game; Shader *shaders[Core::passMAX][Shader::MAX][(FX_UNDERWATER | FX_ALPHA_TEST | FX_CLIP_PLANE) + 1]; - ShaderCache(IGame *game) : game(game) { + ShaderCache() { memset(shaders, 0, sizeof(shaders)); LOG("shader: cache warm up...\n"); @@ -97,7 +96,7 @@ struct ShaderCache { ~ShaderCache() { for (int pass = 0; pass < Core::passMAX; pass++) for (int type = 0; type < Shader::MAX; type++) - for (int fx = 0; fx < sizeof(shaders[Core::passMAX][Shader::MAX]) / sizeof(shaders[Core::passMAX][Shader::MAX][FX_NONE]); fx++) + for (int fx = 0; fx < sizeof(shaders[pass][Shader::MAX]) / sizeof(shaders[pass][Shader::MAX][FX_NONE]); fx++) delete shaders[pass][type][fx]; } @@ -118,7 +117,7 @@ struct ShaderCache { } } - const char *passNames[] = { "COMPOSE", "SHADOW", "AMBIENT", "WATER", "FILTER", "VOLUME", "GUI" }; + const char *passNames[] = { "COMPOSE", "SHADOW", "AMBIENT", "WATER", "FILTER", "GUI" }; const char *src = NULL; const char *typ = NULL; switch (pass) { @@ -128,10 +127,8 @@ struct ShaderCache { static const char *typeNames[] = { "SPRITE", "FLASH", "ROOM", "ENTITY", "MIRROR" }; src = SHADER; - typ = typeNames[type]; - int animTexRangesCount = game->getMesh()->animTexRangesCount; - int animTexOffsetsCount = game->getMesh()->animTexOffsetsCount; - sprintf(def, "%s#define PASS_%s\n#define TYPE_%s\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_CONTACTS %d\n#define FOG_DIST (1.0/%d.0)\n#define WATER_FOG_DIST (1.0/%d.0)\n#define SHADOW_TEX_SIZE %d.0\n", ext, passNames[pass], typ, MAX_LIGHTS, animTexRangesCount, animTexOffsetsCount, MAX_CONTACTS, FOG_DIST, WATER_FOG_DIST, SHADOW_TEX_SIZE); + typ = typeNames[type]; + sprintf(def, "%s#define PASS_%s\n#define TYPE_%s\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_CONTACTS %d\n#define FOG_DIST (1.0/%d.0)\n#define WATER_FOG_DIST (1.0/%d.0)\n#define SHADOW_TEX_SIZE %d.0\n", ext, passNames[pass], typ, MAX_LIGHTS, MAX_ANIM_TEX_RANGES, MAX_ANIM_TEX_OFFSETS, MAX_CONTACTS, FOG_DIST, WATER_FOG_DIST, SHADOW_TEX_SIZE); if (fx & FX_UNDERWATER) strcat(def, "#define UNDERWATER\n" UNDERWATER_COLOR); if (fx & FX_ALPHA_TEST) strcat(def, "#define ALPHA_TEST\n"); if (fx & FX_CLIP_PLANE) strcat(def, "#define CLIP_PLANE\n"); @@ -172,7 +169,7 @@ struct ShaderCache { return shaders[pass][type][fx] = new Shader(src, def); } - void bind(Core::Pass pass, Shader::Type type, int fx) { + void bind(Core::Pass pass, Shader::Type type, int fx, IGame *game) { Core::pass = pass; Shader *shader = shaders[pass][type][fx]; if (!shader) @@ -185,6 +182,8 @@ struct ShaderCache { shader->setParam(uViewPos, Core::viewPos); shader->setParam(uParam, Core::params); MeshBuilder *mesh = game->getMesh(); + ASSERT(mesh->animTexRangesCount <= MAX_ANIM_TEX_RANGES); + ASSERT(mesh->animTexOffsetsCount <= MAX_ANIM_TEX_OFFSETS); shader->setParam(uAnimTexRanges, mesh->animTexRanges[0], mesh->animTexRangesCount); shader->setParam(uAnimTexOffsets, mesh->animTexOffsets[0], mesh->animTexOffsetsCount); } @@ -205,6 +204,7 @@ struct AmbientCache { struct Task { int room; + int flip; int sector; Cube *cube; } tasks[32]; @@ -242,6 +242,7 @@ struct AmbientCache { Task &task = tasks[tasksCount++]; task.room = room; + task.flip = level->isFlipped; task.sector = sector; task.cube = &items[offsets[room] + sector]; task.cube->status = Cube::WAIT; @@ -288,11 +289,16 @@ struct AmbientCache { Core::setDepthTest(true); } - void precessQueue() { + void processQueue() { game->setupBinding(); for (int i = 0; i < tasksCount; i++) { Task &task = tasks[i]; + + bool oldFlip = level->isFlipped; + level->isFlipped = task.flip != 0; renderAmbient(task.room, task.sector, &task.cube->colors[0]); + level->isFlipped = oldFlip; + task.cube->status = Cube::READY; } tasksCount = 0; @@ -321,7 +327,7 @@ struct AmbientCache { }; struct WaterCache { - #define MAX_SURFACES 8 + #define MAX_SURFACES 16 #define MAX_INVISIBLE_TIME 5.0f #define SIMULATE_TIMESTEP (1.0f / 40.0f) #define DETAIL (64.0f / 1024.0f) @@ -355,7 +361,7 @@ struct WaterCache { TR::Level *level = game->getLevel(); TR::Room &r = level->rooms[to]; // underwater room - int minX = r.xSectors, minZ = r.zSectors, maxX = 0, maxZ = 0, posY; + int minX = r.xSectors, minZ = r.zSectors, maxX = 0, maxZ = 0, posY = 0; for (int z = 0; z < r.zSectors; z++) for (int x = 0; x < r.xSectors; x++) { @@ -402,9 +408,9 @@ struct WaterCache { size = vec3(float((maxX - minX) * 512), 1.0f, float((maxZ - minZ) * 512)); // half size pos = vec3(r.info.x + minX * 1024 + size.x, float(posY), r.info.z + minZ * 1024 + size.z); - data[0] = new Texture(w * 64, h * 64, Texture::RGBA_HALF, false); - data[1] = new Texture(w * 64, h * 64, Texture::RGBA_HALF, false); - caustics = new Texture(512, 512, Texture::RGB16, false); + data[0] = new Texture(w * 64, h * 64, Texture::RGBA_HALF); + data[1] = new Texture(w * 64, h * 64, Texture::RGBA_HALF); + caustics = new Texture(512, 512, Texture::RGB16); mask = new Texture(w, h, Texture::RGB16, false, m, false); delete[] m; @@ -413,6 +419,7 @@ struct WaterCache { // texture may be initialized with trash, so... Core::setTarget(data[0], true); Core::validateRenderState(); // immediate clear + Core::invalidateTarget(false, true); } void free() { @@ -592,6 +599,8 @@ struct WaterCache { } void renderMask() { + if (!visible) return; + PROFILE_MARKER("WATER_RENDER_MASK"); // mask underwater geometry by zero alpha game->setShader(Core::passWater, Shader::WATER_MASK); Core::active.shader->setParam(uTexParam, vec4(1.0f)); @@ -614,21 +623,38 @@ struct WaterCache { Core::setCulling(cfFront); } - void getRefract() { - int w = int(Core::viewportDef.z); - int h = int(Core::viewportDef.w); + void getTargetSize(int &w, int &h) { + if (Core::active.target != NULL) { + w = Core::active.target->width; + h = Core::active.target->height; + } else { + w = int(Core::viewportDef.z); + h = int(Core::viewportDef.w); + } + } + void getRefract() { + if (!visible) return; + PROFILE_MARKER("WATER_REFRACT"); + int w, h; + getTargetSize(w, h); // get refraction texture if (!refract || w != refract->width || h != refract->height) { delete refract; refract = new Texture(w, h, Texture::RGBA, false); + Core::setTarget(refract, true); + Core::validateRenderState(); // immediate clear + Core::invalidateTarget(false, true); + Core::setTarget(NULL); } - Core::copyTarget(refract, 0, 0, int(Core::viewportDef.x), int(Core::viewportDef.y), w, h); // copy framebuffer into refraction texture + Core::copyTarget(refract, 0, 0, 0, 0, w, h); // copy framebuffer into refraction texture } void simulate() { + PROFILE_MARKER("WATER_SIMULATE"); // simulate water Core::setDepthTest(false); + Core::setBlending(bmNone); for (int i = 0; i < count; i++) { Item &item = items[i]; if (!item.visible) continue; @@ -646,6 +672,7 @@ struct WaterCache { void renderReflect() { if (!visible) return; + PROFILE_MARKER("WATER_REFLECT"); for (int i = 0; i < count; i++) { Item &item = items[i]; @@ -687,22 +714,20 @@ struct WaterCache { } void render() { + if (!visible) return; + PROFILE_MARKER("WATER_RENDER"); for (int i = 0; i < count; i++) { Item &item = items[i]; if (!item.visible) continue; // render water plane - if (level->rooms[item.from].lightsCount) { - TR::Room::Light &light = level->rooms[item.from].lights[0]; - Core::lightPos[0] = vec3(float(light.x), float(light.y), float(light.z)); - float lum = intensityf(light.intensity); - Core::lightColor[0] = vec4(lum, lum, lum, float(light.radius) * float(light.radius)); - } - game->setShader(Core::passWater, Shader::WATER_COMPOSE); Core::active.shader->setParam(uLightPos, Core::lightPos[0], 1); Core::active.shader->setParam(uLightColor, Core::lightColor[0], 1); - Core::active.shader->setParam(uParam, vec4(Core::viewportDef.z / refract->width, Core::viewportDef.w / refract->height, 0.05f, 0.02f)); + + int w, h; + getTargetSize(w, h); + Core::active.shader->setParam(uParam, vec4(float(w) / refract->width, float(h) / refract->height, 0.05f, 0.02f)); float sx = item.size.x * DETAIL / (item.data[0]->width / 2); float sz = item.size.z * DETAIL / (item.data[0]->height / 2); diff --git a/src/camera.h b/src/camera.h index 94c6df8..6f59c37 100644 --- a/src/camera.h +++ b/src/camera.h @@ -4,35 +4,53 @@ #include "core.h" #include "frustum.h" #include "controller.h" -#include "lara.h" +#include "character.h" #define CAMERA_OFFSET (1024.0f + 256.0f) -struct Camera : Controller { - Lara *owner; - Frustum *frustum; +struct Camera : ICamera { + + enum { + STATE_FOLLOW, + STATE_STATIC, + STATE_LOOK, + STATE_COMBAT, + STATE_CUTSCENE, + STATE_HEAVY + } state; + + IGame *game; + TR::Level *level; + Character *owner; + Frustum *frustum; float fov, znear, zfar; - vec3 target, destPos, lastDest, advAngle; + vec3 target, destPos, lastDest, angle, 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, Lara *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, Character *owner) : ICamera(), game(game), level(game->getLevel()), owner(owner), frustum(new Frustum()), timer(-1.0f), shake(0.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(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; + advTimer = -1.0f; } virtual ~Camera() { @@ -40,14 +58,23 @@ struct Camera : Controller { } virtual int getRoomIndex() const { - return actCamera > -1 ? level->cameras[actCamera].room : room; + return 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 (owner->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) { @@ -67,19 +94,6 @@ struct Camera : Controller { } } - 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()]; @@ -91,16 +105,54 @@ 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; + lastDest = pos; + + if (viewIndex > -1) + room = level->cameras[viewIndex].room; + } + + vec3 getViewPoint() { + vec3 p = owner->getViewPoint(); + if (owner->stand != Character::STAND_UNDERWATER) + p.y -= 256.0f; + if (state == STATE_COMBAT) + p.y -= 256.0f; + return p; + } + + void resetTarget(const vec3 &viewPoint) { + timer = -1.0f; + state = STATE_FOLLOW; + viewTarget = NULL; + viewIndex = -1; + target = viewPoint; + } + 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 * 30.0f; float t = timer - int(timer); - int indexA = int(timer) % level->cameraFramesCount; - int indexB = min(indexA + 1, level->cameraFramesCount - 1); + int indexA = min(int(timer), level->cameraFramesCount - 1); + int indexB = min((indexA + 1), level->cameraFramesCount - 1); + + if (indexA == level->cameraFramesCount - 1) { + if (level->cutEntity != -1) + game->loadLevel(TR::LevelID(level->id + 1)); + else + state = STATE_FOLLOW; + } + TR::CameraFrame *frameA = &level->cameraFrames[indexA]; TR::CameraFrame *frameB = &level->cameraFrames[indexB]; @@ -119,10 +171,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]) { @@ -137,9 +187,7 @@ struct Camera : Controller { if (advAngleOld == advAngle) { if (advTimer > 0.0f) { - advTimer -= Core::deltaTime; - if (advTimer <= 0.0f) - advTimer = 0.0f; + advTimer = max(0.0f, advTimer - Core::deltaTime); } } else advTimer = -1.0f; @@ -147,72 +195,34 @@ 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 - if (owner->health > 0) - angle = owner->angle + advAngle; + + angle = owner->angle + advAngle; angle.z = 0.0f; - if (owner->stand == Lara::STAND_ONWATER) + if (owner->stand == Character::STAND_ONWATER) angle.x -= 22.0f * DEG2RAD; - if (owner->state == Lara::STATE_HANG || owner->state == Lara::STATE_HANG_LEFT || owner->state == Lara::STATE_HANG_RIGHT) + if (owner->stand == Character::STAND_HANG) 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); + Controller *lookAt = viewTarget; + + if (state != STATE_STATIC) { + if (owner->viewTarget) + owner->lookAt(lookAt = owner->viewTarget); + else + owner->lookAt(lookAt = viewTarget); + } else + owner->lookAt(NULL); - 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); + vec3 viewPoint = getViewPoint(); - 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; - if (owner->arms[0].target > -1 && owner->arms[1].target > -1 && owner->arms[0].target != owner->arms[1].target) { - // two diff targets - } else if (owner->arms[0].target > -1) - lookAt = owner->arms[0].target; - else if (owner->arms[1].target > -1) - lookAt = owner->arms[1].target; - else if (owner->arms[0].tracking > -1) - lookAt = owner->arms[0].tracking; - else if (owner->arms[1].tracking > -1) - lookAt = owner->arms[1].tracking; - - owner->viewTarget = lookAt; - - if (timer > 0.0f) { - timer -= Core::deltaTime; - if (timer <= 0.0f) { - timer = 0.0f; - if (room != getRoomIndex()) - pos = lastDest; - actTargetEntity = actCamera = -1; - target = owner->getViewPoint(); - } - } - - if (firstPerson && actCamera == -1) { + 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; @@ -231,40 +241,46 @@ struct Camera : Controller { return; } - float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f; + float lerpFactor = lookAt ? 10.0f : 6.0f; vec3 dir; - target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime); - if (actCamera > -1) { - TR::Camera &c = level->cameras[actCamera]; - destPos = vec3(float(c.x), float(c.y), float(c.z)); + target = target.lerp(viewPoint, lerpFactor * Core::deltaTime); + + 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(); + dir = vec3(angle.x, angle.y); int destRoom; - if ((!owner->emptyHands() || owner->state != Lara::STATE_BACK_JUMP) || lookAt > -1) { + if ((state == STATE_COMBAT || owner->state != 25) || lookAt) { // TODO: FUUU! 25 == Lara::STATE_BACK_JUMP vec3 eye = target - dir * CAMERA_OFFSET; - destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true); + destPos = owner->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); - destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true); - } + destPos = owner->trace(owner->getRoomIndex(), target, eye, destRoom, true); + } + room = destRoom; } pos = pos.lerp(destPos, Core::deltaTime * lerpFactor); - if (actCamera <= -1) + if (timer > 0.0f) { + timer -= Core::deltaTime; + if (timer <= 0.0f) + resetTarget(viewPoint); + } else + resetTarget(target); + + if (viewIndex == -1) checkRoom(); } @@ -314,7 +330,7 @@ struct Camera : Controller { room = owner->getRoomIndex(); pos = owner->pos - owner->getDir() * 1024.0f; - target = owner->getViewPoint(); + target = getViewPoint(); advAngle = vec3(0.0f); advTimer = 0.0f; diff --git a/src/character.h b/src/character.h index 45635ed..67ac1e3 100644 --- a/src/character.h +++ b/src/character.h @@ -2,7 +2,6 @@ #define H_CHARACTER #include "controller.h" -#include "trigger.h" struct Character : Controller { float health; @@ -26,9 +25,17 @@ struct Character : Controller { DEATH = 1 << 9 }; + Controller *viewTarget; + int jointChest; + int jointHead; + vec4 rangeChest; + vec4 rangeHead; + vec3 velocity; float angleExt; float speed; + int stepHeight; + int dropHeight; int zone; int box; @@ -37,7 +44,13 @@ struct Character : Controller { Collision collision; - Character(IGame *game, int entity, float health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), velocity(0.0f), angleExt(0.0f) { + Character(IGame *game, int entity, float health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), viewTarget(NULL), jointChest(-1), jointHead(-1), velocity(0.0f), angleExt(0.0f), speed(0.0f) { + stepHeight = 256; + dropHeight = -256; + + rangeChest = vec4(-0.80f, 0.80f, -0.75f, 0.75f) * PI; + rangeHead = vec4(-0.25f, 0.25f, -0.50f, 0.50f) * PI; + animation.initOverrides(); rotHead = rotChest = quat(0, 0, 0, 1); @@ -56,7 +69,7 @@ struct Character : Controller { } uint16* getZones() { - return flying ? level->zones[0].fly : level->zones[0].ground1; + return flying ? level->zones[level->isFlipped].fly : (stepHeight == 256 ? level->zones[level->isFlipped].ground1 : level->zones[level->isFlipped].ground2); } void rotateY(float delta) { @@ -68,9 +81,9 @@ struct Character : Controller { angle.x = clamp(angle.x + delta, -PI * 0.49f, PI * 0.49f); } - virtual void hit(float damage, Controller *enemy = NULL) { + virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) { health = max(0.0f, health - damage); - }; + } virtual void checkRoom() { TR::Level::FloorInfo info; @@ -95,10 +108,6 @@ struct Character : Controller { } } - virtual void cmdKill() { - health = 0; - } - virtual void updateVelocity() {} virtual void updatePosition() {} virtual Stand getStand() { return stand; } @@ -185,15 +194,31 @@ struct Character : Controller { stand = STAND_AIR; } - virtual void doBubbles() { - int count = rand() % 3; - if (!count) return; - playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN); - vec3 head = animation.getJoints(getMatrix(), 14, true) * vec3(0.0f, 0.0f, 50.0f); - for (int i = 0; i < count; i++) { - int index = Sprite::add(game, TR::Entity::BUBBLE, getRoomIndex(), int(head.x), int(head.y), int(head.z), Sprite::FRAME_RANDOM, true); - if (index > -1) - level->entities[index].controller = new Bubble(game, index); + vec3 getViewPoint() { + return animation.getJoints(getMatrix(), jointChest).pos; + } + + virtual void lookAt(Controller *target) { + if (health <= 0.0f) + target = NULL; + + float speed = 8.0f * Core::deltaTime; + quat rot; + + if (jointChest > -1) { + if (aim(target, jointChest, rangeChest, rot)) + rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); + else + rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); + animation.overrides[jointChest] = rotChest * animation.overrides[jointChest]; + } + + if (jointHead > -1) { + if (aim(target, jointHead, rangeHead, rot)) + rotHead = rotHead.slerp(rot, speed); + else + rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); + animation.overrides[jointHead] = rotHead * animation.overrides[jointHead]; } } }; diff --git a/src/collision.h b/src/collision.h index 378cf78..d953844 100644 --- a/src/collision.h +++ b/src/collision.h @@ -41,17 +41,26 @@ struct Collision { getFloor(level, roomIndex, vec3(pos.x, hpos.y, pos.z)); if (checkHeight(level, roomIndex, hpos, vec2(0.0f), height, 0xFFFFFF, 0xFFFFFF, side = NONE)) { - pos -= velocity; + pos.x -= velocity.x; + pos.z -= velocity.z; side = FRONT; return; } - if (info[NONE].ceiling > hpos.y - maxHeight) { - pos.y = info[NONE].ceiling + maxHeight - offset.y; - side = TOP; + int hCell = info[NONE].ceiling - (int(hpos.y) - maxHeight); + if (hCell > 0) { + if (hCell > 128) { + pos.x -= velocity.x; + pos.z -= velocity.z; + side = FRONT; + } else { + pos.y = info[NONE].ceiling + maxHeight - offset.y; + side = TOP; + } } - if (info[NONE].floor < hpos.y + minHeight) { + int hFloor = info[NONE].floor - (int(hpos.y) + minHeight); + if (hFloor < 0 && hFloor > -256) { pos.y = info[NONE].floor - minHeight - offset.y; side = BOTTOM; } diff --git a/src/controller.h b/src/controller.h index cc2facc..be1158c 100644 --- a/src/controller.h +++ b/src/controller.h @@ -16,11 +16,24 @@ struct Controller; +struct ICamera { + vec4 *reflectPlane; + vec3 pos; + + ICamera() : reflectPlane(NULL) {} + + virtual void setup(bool calcMatrices) {} + virtual int getRoomIndex() const { return TR::NO_ROOM; } +}; + struct IGame { virtual ~IGame() {} + virtual void loadLevel(TR::LevelID id) {} virtual TR::Level* getLevel() { return NULL; } virtual MeshBuilder* getMesh() { return NULL; } - virtual Controller* getCamera() { return NULL; } + virtual ICamera* 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) {} @@ -32,7 +45,7 @@ struct IGame { virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {} virtual void renderCompose(int roomIndex) {} virtual void renderView(int roomIndex, bool water) {} - virtual void fxQuake(float time) {} + virtual void setEffect(TR::Effect effect, float param) {} virtual bool invUse(TR::Entity::Type type) { return false; } virtual void invAdd(TR::Entity::Type type, int count = 1) {} @@ -40,9 +53,15 @@ 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 { + static Controller *first; + Controller *next; + enum ActiveState { asNone, asActive, asInactive } activeState; + IGame *game; TR::Level *level; int entity; @@ -59,6 +78,8 @@ struct Controller { vec3 ambient[6]; float specular; + float timer; + TR::Room::Light *targetLight; vec3 mainLightPos; vec4 mainLightColor; @@ -68,18 +89,7 @@ struct Controller { uint32 mask; } *layers; - struct ActionCommand { - int emitter; - TR::Action action; - int value; - float timer; - ActionCommand *next; - - ActionCommand() {} - ActionCommand(int emitter, TR::Action action, int value, float timer, ActionCommand *next = NULL) : emitter(emitter), action(action), value(value), timer(timer), next(next) {} - } *actionCommand; - - Controller(IGame *game, int entity) : game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(NULL), actionCommand(NULL) { + Controller(IGame *game, int entity) : next(NULL), activeState(asNone), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(NULL) { TR::Entity &e = getEntity(); pos = vec3(float(e.x), float(e.y), float(e.z)); angle = vec3(0.0f, e.rotation, 0.0f); @@ -87,14 +97,96 @@ struct Controller { joints = m ? new Basis[m->mCount] : NULL; frameIndex = -1; specular = 0.0f; + timer = 0.0f; ambient[0] = ambient[1] = ambient[2] = ambient[3] = ambient[4] = ambient[5] = vec3(intensityf(getRoom().ambient)); targetLight = NULL; updateLights(false); + + if (e.flags.once) { + e.flags.invisible = true; + e.flags.once = false; + } + + if (e.flags.active == TR::ACTIVE) { + e.flags.active = 0; + e.flags.reverse = true; + activate(); + } + + if (e.isLara() || e.isActor()) // Lara and cutscene entities is active by default + activate(); } virtual ~Controller() { delete[] joints; delete[] layers; + deactivate(true); + } + + bool isActive() { + TR::Entity &e = getEntity(); + + if (e.flags.active != TR::ACTIVE) + return e.flags.reverse; + + if (timer == 0.0f) + return !e.flags.reverse; + + if (timer == -1.0f) + return e.flags.reverse; + + timer = max(0.0f, timer - Core::deltaTime); + + if (timer == 0.0f) + timer = -1.0f; + + return !e.flags.reverse; + } + + virtual bool activate() { + if (activeState != asNone) + return false; + getEntity().flags.invisible = false; + activeState = asActive; + next = first; + first = this; + return true; + } + + virtual void deactivate(bool removeFromList = false) { + activeState = asInactive; + if (removeFromList) { + Controller *prev = NULL; + Controller *c = first; + while (c) { + if (c == this) { + if (prev) + prev->next = c->next; + else + first = c->next; + c->activeState = asNone; + break; + } else + prev = c; + c = c->next; + } + } + } + + static void clearInactive() { + Controller *prev = NULL; + Controller *c = first; + while (c) { + if (c->activeState == asInactive) { + if (prev) + prev->next = c->next; + else + first = c->next; + c->activeState = asNone; + } else + prev = c; + c = c->next; + } } void initMeshOverrides() { @@ -119,10 +211,9 @@ struct Controller { layers[layer].mask = mask; } - bool aim(int target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) { - if (target > -1) { - TR::Entity &e = level->entities[target]; - Box box = ((Controller*)e.controller)->getBoundingBox(); + bool aim(Controller *target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) { + if (target) { + Box box = target->getBoundingBox(); vec3 t = (box.min + box.max) * 0.5f; Basis b = animation.getJoints(Basis(getMatrix()), joint); @@ -158,10 +249,10 @@ struct Controller { e.rotation = angle.y; } - 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)); + bool insideRoom(const vec3 &pos, int roomIndex) const { + TR::Room &r = level->rooms[roomIndex]; + vec3 min = vec3((float)r.info.x + 1024, (float)r.info.yTop, (float)r.info.z + 1024); + 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 && @@ -184,7 +275,10 @@ struct Controller { } virtual int getRoomIndex() const { - return getEntity().room; + int index = getEntity().room; + if (level->isFlipped && level->rooms[index].alternateRoom > -1) + index = level->rooms[index].alternateRoom; + return index; } virtual vec3& getPos() { @@ -355,62 +449,13 @@ struct Controller { return pos; } - virtual void doBubbles() { - // + bool checkRange(Controller *target, float range) { + vec3 d = target->pos - pos; + return fabsf(d.x) < range && fabsf(d.z) < range && fabsf(d.y) < range; } - void activateNext() { // activate next entity (for triggers) - if (!actionCommand || !actionCommand->next) { - actionCommand = NULL; - return; - } - ActionCommand *next = actionCommand->next; + virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {} - Controller *controller = NULL; - switch (next->action) { - case TR::Action::ACTIVATE : - controller = (Controller*)level->entities[next->value].controller; - break; - case TR::Action::CAMERA_SWITCH : - case TR::Action::CAMERA_TARGET : - controller = (Controller*)level->cameraController; - break; - case TR::Action::SECRET : - if (!level->secrets[next->value]) { - level->secrets[next->value] = true; - playSound(TR::SND_SECRET, pos, 0); - } - actionCommand = next; - activateNext(); - return; - case TR::Action::FLOW : - applyFlow(level->cameras[next->value]); - actionCommand = next; - activateNext(); - break; - case TR::Action::FLIP_MAP : - case TR::Action::FLIP_ON : - case TR::Action::FLIP_OFF : - case TR::Action::END : - case TR::Action::SOUNDTRACK : - case TR::Action::HARDCODE : - case TR::Action::CLEAR : - case TR::Action::CAMERA_FLYBY : - case TR::Action::CUTSCENE : - //LOG("! action is not implemented\n"); - actionCommand = next; - activateNext(); - break; - } - - if (controller) { - if (controller->activate(next)) - actionCommand = NULL; - } else - actionCommand = NULL; - } - - virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; } virtual void doCustomCommand (int curFrame, int prevFrame) {} virtual void checkRoom() {} virtual void applyFlow(TR::Camera &sink) {} @@ -422,7 +467,6 @@ struct Controller { } virtual void cmdJump(const vec3 &vel) {} - virtual void cmdKill() {} virtual void cmdEmpty() {} virtual void cmdEffect(int fx) { ASSERT(false); } // not implemented @@ -441,7 +485,10 @@ struct Controller { case TR::ANIM_CMD_OFFSET : ptr += 3; break; case TR::ANIM_CMD_JUMP : ptr += 2; break; case TR::ANIM_CMD_EMPTY : cmdEmpty(); break; - case TR::ANIM_CMD_KILL : cmdKill(); break; + case TR::ANIM_CMD_KILL : + if (animation.isEnded) + deactivate(); + break; case TR::ANIM_CMD_SOUND : case TR::ANIM_CMD_EFFECT : { int frame = (*ptr++) - anim->frameStart; @@ -449,10 +496,10 @@ struct Controller { if (animation.isFrameActive(frame)) { if (cmd == TR::ANIM_CMD_EFFECT) { switch (fx) { - 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; - default : cmdEffect(fx); break; + case TR::Effect::ROTATE_180 : angle.y = angle.y + PI; break; + case TR::Effect::FLOOR_SHAKE : game->setEffect(TR::Effect(fx), 0.5f * max(0.0f, 1.0f - (pos - ((ICamera*)level->cameraController)->pos).length2() / (15 * 1024 * 15 * 1024) )); break; + case TR::Effect::FLIP_MAP : level->isFlipped = !level->isFlipped; break; + default : cmdEffect(fx); break; } } else playSound(fx, pos, Sound::Flags::PAN); @@ -470,7 +517,6 @@ struct Controller { if (animation.offset != 0.0f) cmdOffset(animation.offset); if (animation.jump != 0.0f) cmdJump(animation.jump); animation.playNext(); - activateNext(); } else animation.framePrev = animation.frameIndex; } @@ -524,7 +570,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)); @@ -539,7 +585,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); @@ -570,8 +619,6 @@ struct Controller { Core::setBlending(bmMultiply); mesh->renderShadowBlob(); Core::setBlending(bmNone); - - Core::active.shader->setParam(uViewProj, Core::mViewProj); } virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { // TODO: animation.calcJoints @@ -616,4 +663,6 @@ struct Controller { } }; +Controller *Controller::first = NULL; + #endif \ No newline at end of file diff --git a/src/core.h b/src/core.h index aa10ddd..5f19a81 100644 --- a/src/core.h +++ b/src/core.h @@ -15,20 +15,16 @@ #define GL_CLAMP_TO_BORDER 0x812D #define GL_TEXTURE_BORDER_COLOR 0x1004 - #define GL_TEXTURE_COMPARE_MODE 0x884C - #define GL_TEXTURE_COMPARE_FUNC 0x884D - #define GL_COMPARE_REF_TO_TEXTURE 0x884E + #define GL_TEXTURE_COMPARE_MODE 0x884C + #define GL_TEXTURE_COMPARE_FUNC 0x884D + #define GL_COMPARE_REF_TO_TEXTURE 0x884E - #undef GL_RGBA32F - #undef GL_RGBA16F - #undef GL_HALF_FLOAT + #define GL_RGBA16F 0x881A + #define GL_RGBA32F 0x8814 + #define GL_HALF_FLOAT 0x140B - #define GL_RGBA32F GL_RGBA - #define GL_RGBA16F GL_RGBA - #define GL_HALF_FLOAT GL_HALF_FLOAT_OES - - #define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES - #define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES + #define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES + #define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES #define PFNGLGENVERTEXARRAYSPROC PFNGLGENVERTEXARRAYSOESPROC #define PFNGLDELETEVERTEXARRAYSPROC PFNGLDELETEVERTEXARRAYSOESPROC @@ -43,6 +39,38 @@ #define glProgramBinary glProgramBinaryOES #define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES +#elif __RPI__ + #define MOBILE + #include + #include + #include + #include + + #define GL_CLAMP_TO_BORDER 0x812D + #define GL_TEXTURE_BORDER_COLOR 0x1004 + + #define GL_TEXTURE_COMPARE_MODE 0x884C + #define GL_TEXTURE_COMPARE_FUNC 0x884D + #define GL_COMPARE_REF_TO_TEXTURE 0x884E + + #undef GL_RGBA32F + #undef GL_RGBA16F + #undef GL_HALF_FLOAT + + #define GL_RGBA32F GL_RGBA + #define GL_RGBA16F GL_RGBA + #define GL_HALF_FLOAT GL_HALF_FLOAT_OES + + #define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES + #define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES + + #define glGenVertexArrays(...) + #define glDeleteVertexArrays(...) + #define glBindVertexArray(...) + + #define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES + #define glGetProgramBinary(...) + #define glProgramBinary(...) #elif __linux__ #define LINUX 1 #include @@ -64,12 +92,12 @@ #define glDeleteVertexArrays glDeleteVertexArraysOES #define glBindVertexArray glBindVertexArrayOES - #define GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_EXT - #define GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_EXT + #define GL_CLAMP_TO_BORDER 0x812D + #define GL_TEXTURE_BORDER_COLOR 0x1004 - #define GL_TEXTURE_COMPARE_MODE GL_TEXTURE_COMPARE_MODE_EXT - #define GL_TEXTURE_COMPARE_FUNC GL_TEXTURE_COMPARE_FUNC_EXT - #define GL_COMPARE_REF_TO_TEXTURE GL_COMPARE_REF_TO_TEXTURE_EXT + #define GL_TEXTURE_COMPARE_MODE GL_TEXTURE_COMPARE_MODE_EXT + #define GL_TEXTURE_COMPARE_FUNC GL_TEXTURE_COMPARE_FUNC_EXT + #define GL_COMPARE_REF_TO_TEXTURE GL_COMPARE_REF_TO_TEXTURE_EXT #else #include #include @@ -78,13 +106,14 @@ #include #include - #define GL_RGBA32F GL_RGBA - #define GL_RGBA16F GL_RGBA + #define GL_RGBA16F 0x881A + #define GL_RGBA32F 0x8814 + #define GL_HALF_FLOAT 0x140B #define GL_RGB565 GL_RGBA - #define GL_TEXTURE_COMPARE_MODE 0x884C - #define GL_TEXTURE_COMPARE_FUNC 0x884D - #define GL_COMPARE_REF_TO_TEXTURE 0x884E + #define GL_TEXTURE_COMPARE_MODE 0x884C + #define GL_TEXTURE_COMPARE_FUNC 0x884D + #define GL_COMPARE_REF_TO_TEXTURE 0x884E #define glGenVertexArrays glGenVertexArraysAPPLE #define glDeleteVertexArrays glDeleteVertexArraysAPPLE @@ -96,18 +125,10 @@ #endif #elif __EMSCRIPTEN__ #define MOBILE - #include - #include + #include + #include #include #include - - #undef GL_RGBA32F - #undef GL_RGBA16F - #undef GL_HALF_FLOAT - - #define GL_RGBA32F GL_RGBA - #define GL_RGBA16F GL_RGBA - #define GL_HALF_FLOAT GL_HALF_FLOAT_OES #define GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_EXT #define GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_EXT @@ -119,13 +140,32 @@ namespace Core { float deltaTime; int width, height; + + struct { + struct { + bool ambient; + bool lighting; + bool shadows; + bool water; + bool contact; + } detail; + + struct { + bool retarget; + } controls; + + struct { + bool reverb; + } audio; + } settings; } #include "utils.h" #include "input.h" #include "sound.h" -#if defined(WIN32) || defined(LINUX) || defined(ANDROID) + +#if defined(WIN32) || (defined(LINUX) && !defined(__RPI__)) || defined(ANDROID) #ifdef ANDROID #define GetProc(x) dlsym(libGL, x); @@ -133,6 +173,8 @@ namespace Core { void* GetProc(const char *name) { #ifdef WIN32 return (void*)wglGetProcAddress(name); + #elif __RPI__ + return (void*)eglGetProcAddress(name); #elif LINUX return (void*)glXGetProcAddress((GLubyte*)name); #endif @@ -147,6 +189,7 @@ namespace Core { #endif #if defined(WIN32) || defined(LINUX) + PFNGLGENERATEMIPMAPPROC glGenerateMipmap; // Profiling #ifdef PROFILE PFNGLOBJECTLABELPROC glObjectLabel; @@ -210,10 +253,12 @@ namespace Core { PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; #endif -#define MAX_LIGHTS 4 -#define MAX_CACHED_LIGHTS 3 -#define MAX_RENDER_BUFFERS 32 -#define MAX_CONTACTS 15 +#define MAX_LIGHTS 4 +#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 struct Shader; struct Texture; @@ -242,7 +287,7 @@ typedef unsigned short Index; struct Vertex { short4 coord; // xyz - position, w - joint index (for entities only) - short4 normal; // xyz - vertex normalá w - unused + short4 normal; // xyz - vertex normal, w - unused short4 texCoord; // xy - texture coordinates, zw - trapezoid warping ubyte4 param; // xy - anim tex range and frame index, zw - unused ubyte4 color; // xyz - color, w - intensity @@ -258,8 +303,9 @@ namespace Core { bool texNPOT; bool texRG; bool texBorder; - bool texFloat, texFloatLinear; - bool texHalf, texHalfLinear; + int8 texAniso; + bool colorFloat, texFloat, texFloatLinear; + bool colorHalf, texHalf, texHalfLinear; #ifdef PROFILE bool profMarker; bool profTiming; @@ -268,13 +314,41 @@ namespace Core { } #ifdef PROFILE + #define USE_CV_MARKERS + + #ifdef USE_CV_MARKERS + #include + using namespace Concurrency::diagnostic; + + marker_series *series[256]; + int seriesIndex; + #endif + struct Marker { + #ifdef USE_CV_MARKERS + span *cvSpan; + #endif + Marker(const char *title) { if (Core::support.profMarker) glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, title); + #ifdef USE_CV_MARKERS + marker_series *&s = series[seriesIndex]; + if (s == NULL) { + char seriesTitle[64]; + sprintf(seriesTitle, "events - %d", seriesIndex); + s = new marker_series(seriesTitle); + } + cvSpan = new span(*s, normal_importance, _T(title)); + seriesIndex++; + #endif } ~Marker() { if (Core::support.profMarker) glPopDebugGroup(); + #ifdef USE_CV_MARKERS + delete cvSpan; + seriesIndex--; + #endif } static void setLabel(GLenum id, GLuint name, const char *label) { @@ -386,20 +460,6 @@ namespace Core { frame++; } } stats; - - struct { - struct { - bool ambient; - bool lighting; - bool shadows; - bool water; - bool contact; - } detail; - - struct { - bool retarget; - } controls; - } settings; } #include "texture.h" @@ -417,12 +477,14 @@ namespace Core { void *libGL = dlopen("libGLESv2.so", RTLD_LAZY); #endif - #if defined(WIN32) || defined(LINUX) || defined(ANDROID) + #if defined(WIN32) || (defined(LINUX) && !defined(__RPI__)) || defined(ANDROID) #ifdef WIN32 GetProcOGL(glActiveTexture); #endif #if defined(WIN32) || defined(LINUX) + GetProcOGL(glGenerateMipmap); + #ifdef PROFILE GetProcOGL(glObjectLabel); GetProcOGL(glPushDebugGroup); @@ -485,9 +547,22 @@ namespace Core { GetProcOGL(glProgramBinary); #endif - char *ext = (char*)glGetString(GL_EXTENSIONS); - //LOG("%s\n", ext); + char *ext = (char*)glGetString(GL_EXTENSIONS); +/* + if (ext != NULL) { + char buf[255]; + int len = strlen(ext); + int start = 0; + for (int i = 0; i < len; i++) + if (ext[i] == ' ' || (i == len - 1)) { + memcpy(buf, &ext[start], i - start); + buf[i - start] = 0; + LOG("%s\n", buf); + start = i + 1; + } + } +*/ support.shaderBinary = extSupport(ext, "_program_binary"); support.VAO = extSupport(ext, "_vertex_array_object"); support.depthTexture = extSupport(ext, "_depth_texture"); @@ -496,16 +571,24 @@ namespace Core { support.texNPOT = extSupport(ext, "_texture_npot") || extSupport(ext, "_texture_non_power_of_two"); support.texRG = extSupport(ext, "_texture_rg "); // hope that isn't last extension in string ;) support.texBorder = extSupport(ext, "_texture_border_clamp"); - support.texFloatLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_float_linear"); + support.texAniso = extSupport(ext, "_texture_filter_anisotropic"); + support.colorFloat = extSupport(ext, "_color_buffer_float"); + support.colorHalf = extSupport(ext, "_color_buffer_half_float") || extSupport(ext, "GL_ARB_half_float_pixel"); + support.texFloatLinear = support.colorFloat || extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_float_linear"); support.texFloat = support.texFloatLinear || extSupport(ext, "_texture_float"); - support.texHalfLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_half_float_linear"); + support.texHalfLinear = support.colorHalf || extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_half_float_linear") || extSupport(ext, "_color_buffer_half_float"); support.texHalf = support.texHalfLinear || extSupport(ext, "_texture_half_float"); + if (support.texAniso) { + int maxAniso; + glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); + support.texAniso = maxAniso; + } + #ifdef PROFILE support.profMarker = extSupport(ext, "_KHR_debug"); support.profTiming = extSupport(ext, "_timer_query"); #endif - char *vendor = (char*)glGetString(GL_VENDOR); LOG("Vendor : %s\n", vendor); LOG("Renderer : %s\n", glGetString(GL_RENDERER)); @@ -520,13 +603,15 @@ namespace Core { LOG(" NPOT textures : %s\n", support.texNPOT ? "true" : "false"); LOG(" RG textures : %s\n", support.texRG ? "true" : "false"); LOG(" border color : %s\n", support.texBorder ? "true" : "false"); + LOG(" anisotropic : %d\n", support.texAniso); LOG(" float textures : float = %s, half = %s\n", - support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false", - support.texHalf ? (support.texHalfLinear ? "linear" : "nearest") : "false"); + support.colorFloat ? "full" : (support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false"), + support.colorHalf ? "full" : (support.texHalf ? (support.texHalfLinear ? "linear" : "nearest") : "false")); LOG("\n"); glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&defaultFBO); glGenFramebuffers(1, &FBO); + memset(rtCache, 0, sizeof(rtCache)); defaultTarget = NULL; @@ -541,7 +626,7 @@ namespace Core { uint32 data = 0x00000000; blackTex = new Texture(1, 1, Texture::RGBA, false, &data, false); data = 0xFFFFFFFF; - whiteTex = new Texture(1, 1, Texture::RGBA, false, &data, false); + whiteTex = new Texture(1, 1, Texture::RGBA, false, &data, false); } void free() { @@ -752,7 +837,7 @@ namespace Core { void copyTarget(Texture *dst, int xOffset, int yOffset, int x, int y, int width, int height) { validateRenderState(); dst->bind(sDiffuse); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, x, y, width, height); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, x, y, width, height); // TODO: too bad for iOS devices! } vec4 copyPixel(int x, int y) { // GPU sync! @@ -776,6 +861,12 @@ namespace Core { } void endFrame() { + #ifdef __EMSCRIPTEN__ + glColorMask(false, false, false, true); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glColorMask(true, true, true, true); + #endif Core::stats.stop(); } diff --git a/src/debug.h b/src/debug.h index 18eef15..3e3216c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -382,7 +382,6 @@ namespace Debug { glBegin(GL_QUADS); for (int i = 0; i < level.roomsCount; i++) { - // if (level.entities[91].room != i) continue; TR::Room &r = level.rooms[i]; for (int j = 0; j < r.portalsCount; j++) { TR::Room::Portal &p = r.portals[j]; @@ -453,8 +452,6 @@ namespace Debug { } void lights(const TR::Level &level, int room, Controller *lara) { - // int roomIndex = level.entities[lara->entity].room; - // int lightIndex = getLightIndex(lara->pos, roomIndex); glPointSize(8); for (int i = 0; i < level.roomsCount; i++) for (int j = 0; j < level.rooms[i].lightsCount; j++) { @@ -560,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 { @@ -595,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); } @@ -616,7 +613,6 @@ namespace Debug { case_name(TR::Level::Trigger, ANTIPAD ); case_name(TR::Level::Trigger, COMBAT ); case_name(TR::Level::Trigger, DUMMY ); - case_name(TR::Level::Trigger, ANTI ); } return "UNKNOWN"; } @@ -626,17 +622,14 @@ namespace Debug { case_name(TR::Action, ACTIVATE ); case_name(TR::Action, CAMERA_SWITCH ); case_name(TR::Action, FLOW ); - case_name(TR::Action, FLIP_MAP ); + case_name(TR::Action, FLIP ); case_name(TR::Action, FLIP_ON ); case_name(TR::Action, FLIP_OFF ); 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 ); - case_name(TR::Action, CLEAR ); - case_name(TR::Action, CAMERA_FLYBY ); - case_name(TR::Action, CUTSCENE ); } return "UNKNOWN"; } @@ -654,24 +647,31 @@ namespace Debug { void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) { float y = 0.0f; + int activeCount = 0; + Controller *c = Controller::first; + while (c) { + activeCount++; + c = c->next; + } + char buf[255]; - sprintf(buf, "DIP = %d, TRI = %d, SND = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount); + 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 (camera: %d)", entity.x, entity.y, entity.z, (int)angle.x, (int)angle.y, ((Controller*)entity.controller)->getRoomIndex(), ((ICamera*)level.cameraController)->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); Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); TR::Level::FloorInfo info; - level.getFloorInfo(entity.room, entity.x, entity.y, entity.z, info); + level.getFloorInfo(((Controller*)entity.controller)->getRoomIndex(), entity.x, entity.y, entity.z, info); sprintf(buf, "floor = %d, roomBelow = %d, roomAbove = %d, height = %d", info.floorIndex, info.roomBelow, info.roomAbove, info.floor - info.ceiling); Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); if (info.trigCmdCount > 0) { y += 16; - sprintf(buf, "trigger: %s%s mask: %d", getTriggerType(level, info.trigger), info.trigInfo.once ? " (once)" : "", info.trigInfo.mask); + sprintf(buf, "trigger: %s%s mask: %d timer: %d", getTriggerType(level, info.trigger), info.trigInfo.once ? " (once)" : "", info.trigInfo.mask, info.trigInfo.timer); Debug::Draw::text(vec2(16, y += 16), vec4(0.5f, 0.8f, 0.5f, 1.0f), buf); for (int i = 0; i < info.trigCmdCount; i++) { @@ -679,6 +679,11 @@ namespace Debug { const char *ent = (cmd.action == TR::Action::ACTIVATE || cmd.action == TR::Action::CAMERA_TARGET) ? getEntityName(level, level.entities[cmd.args]) : ""; sprintf(buf, "%s -> %s (%d)", getTriggerAction(level, cmd.action), ent, cmd.args); + if (cmd.action == TR::Action::CAMERA_SWITCH) { + i++; + sprintf(buf, "%s delay: %d speed: %d", buf, int(info.trigCmd[i].timer), int(info.trigCmd[i].speed) * 8 + 1); + } + Debug::Draw::text(vec2(16, y += 16), vec4(0.1f, 0.6f, 0.1f, 1.0f), buf); } } diff --git a/src/enemy.h b/src/enemy.h index 7505cf8..e1e3bb9 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -57,15 +57,10 @@ struct Enemy : Character { float length; // dist from center to head (jaws) float aggression; int radius; - int stepHeight; - int dropHeight; Character *target; Path *path; - int jointChest; - int jointHead; - float targetDist; bool targetDead; bool targetInView; // target in enemy view zone @@ -73,11 +68,6 @@ struct Enemy : Character { bool targetCanAttack; Enemy(IGame *game, int entity, float health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), target(NULL), path(NULL) { - stepHeight = 256; - dropHeight = -256; - - jointChest = jointHead = -1; - targetDist = +INF; targetInView = targetFromView = targetCanAttack = false; } @@ -86,24 +76,12 @@ struct Enemy : Character { delete path; } - virtual bool activate(ActionCommand *cmd) { - #ifdef LEVEL_EDITOR - return true; - #endif - - Controller::activate(cmd); - - getEntity().flags.active = true; - activateNext(); - - for (int i = 0; i < level->entitiesCount; i++) - if (level->entities[i].type == TR::Entity::LARA) { - target = (Character*)level->entities[i].controller; - break; - } - ASSERT(target); - - return true; + virtual bool activate() { + if (Character::activate()) { + target = (Character*)game->getLara(); + return true; + } + return false; } virtual void updateVelocity() { @@ -194,27 +172,6 @@ struct Enemy : Character { animation.overrideMask &= ~(1 << chest); } - void lookAt(int target, int chest, int head, bool rotate = false) { - float speed = 8.0f * Core::deltaTime; - quat rot; - - if (chest > -1) { - if (rotate && aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot)) - rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); - else - rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); - animation.overrides[chest] = rotChest * animation.overrides[chest]; - } - - if (head > -1) { - if (rotate && aim(target, head, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot)) - rotHead = rotHead.slerp(rot, speed); - else - rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); - animation.overrides[head] = rotHead * animation.overrides[head]; - } - } - bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) { vec3 p = waypoint; p.y -= height; @@ -232,6 +189,10 @@ struct Enemy : Character { return true; } + virtual void lookAt(Controller *target) { + Character::lookAt(targetInView ? target : NULL); + } + int turn(float delta, float speed) { float w = speed * Core::deltaTime; @@ -257,8 +218,8 @@ struct Enemy : Character { return 0; } - virtual void hit(float damage, Controller *enemy = NULL) { - Character::hit(damage, enemy); + virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) { + Character::hit(damage, enemy, hitType); wound = true; }; @@ -638,7 +599,7 @@ struct Wolf : Enemy { Enemy::updatePosition(); setOverrides(state != STATE_DEATH, jointChest, jointHead); - lookAt(target ? target->entity : -1, jointChest, jointHead); + lookAt(target); } }; @@ -787,7 +748,7 @@ struct Bear : Enemy { Enemy::updatePosition(); setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_HIND, jointChest, jointHead); - lookAt(target ? target->entity : -1, jointChest, jointHead); + lookAt(target); } }; @@ -875,6 +836,7 @@ struct Bat : Enemy { #define REX_DIST_WALK 5120 #define REX_TURN_FAST (DEG2RAD * 120) #define REX_TURN_SLOW (DEG2RAD * 60) +#define REX_DAMAGE 1000 struct Rex : Enemy { @@ -950,7 +912,7 @@ struct Rex : Enemy { break; case STATE_BITE : if (mask & HIT_MASK) { - target->hit(10000, this); + target->hit(REX_DAMAGE, this, TR::HIT_REX); return STATE_FATAL; } nextState = STATE_WALK; @@ -979,7 +941,7 @@ struct Rex : Enemy { Enemy::updatePosition(); setOverrides(true, jointChest, jointHead); - lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH && state != STATE_FATAL); + lookAt(target); } }; @@ -1097,7 +1059,7 @@ struct Raptor : Enemy { Enemy::updatePosition(); setOverrides(true, jointChest, jointHead); - lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH); + lookAt(target); } }; diff --git a/src/format.h b/src/format.h index 3bfd48c..691a13f 100644 --- a/src/format.h +++ b/src/format.h @@ -4,7 +4,9 @@ #include "utils.h" #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 @@ -52,7 +54,7 @@ E( TRAP_DARTGUN ) \ E( DOOR_LIFT ) \ E( TRAP_SLAM ) \ - E( FALLING_SWORD ) \ + E( TRAP_SWORD ) \ E( HAMMER_HANDLE ) \ E( HAMMER_BLOCK ) \ E( LIGHTNING_BALL ) \ @@ -62,8 +64,8 @@ E( BLOCK_3 ) \ E( BLOCK_4 ) \ E( MOVING_BLOCK ) \ - E( FALLING_CEILING_1 ) \ - E( FALLING_CEILING_2 ) \ + E( TRAP_CEILING_1 ) \ + E( TRAP_CEILING_2 ) \ E( SWITCH ) \ E( SWITCH_WATER ) \ E( DOOR_1 ) \ @@ -138,10 +140,10 @@ E( LEADBAR ) \ E( INV_LEADBAR ) \ E( MIDAS_TOUCH ) \ - E( KEY_1 ) \ - E( KEY_2 ) \ - E( KEY_3 ) \ - E( KEY_4 ) \ + E( KEY_ITEM_1 ) \ + E( KEY_ITEM_2 ) \ + E( KEY_ITEM_3 ) \ + E( KEY_ITEM_4 ) \ E( INV_KEY_1 ) \ E( INV_KEY_2 ) \ E( INV_KEY_3 ) \ @@ -204,8 +206,10 @@ namespace TR { enum { - FLOOR_BLOCK = -127, - NO_ROOM = 0xFF, + NO_FLOOR = -127, + NO_ROOM = 0xFF, + NO_BOX = 0xFFFF, + ACTIVE = 0x1F, }; enum { @@ -218,56 +222,26 @@ namespace TR { ANIM_CMD_EFFECT , }; - // https://dl.dropboxusercontent.com/u/62482708/Secret/TR4%26TR5%20PSX%20Stuff.zip - enum { - EFFECT_ROTATE_180 , - EFFECT_FLOOR_SHAKE , - EFFECT_LARA_NORMAL , - EFFECT_LARA_BUBBLES , - EFFECT_FINISH_LEVEL , - EFFECT_ACTIVATE_CAMERA , - EFFECT_ACTIVATE_KEY , - EFFECT_RUBBLEFX , - EFFECT_CROWBAR , - EFFECT_CURTAINFX , - EFFECT_SETCHANGEFX , - EFFECT_EXPLOSION_FX , - EFFECT_LARA_HANDSFREE , - EFFECT_FLIP_MAP , - EFFECT_DRAW_RIGHTGUN , - EFFECT_DRAW_LEFTGUN , - EFFECT_SHOOT_RIGHTGUN , - EFFECT_SHOOT_LEFTGUN , - EFFECT_MESH_SWAP1 , - EFFECT_MESH_SWAP2 , - EFFECT_MESH_SWAP3 , - EFFECT_INV_ON , - EFFECT_INV_OFF , - EFFECT_DYN_ON , - EFFECT_DYN_OFF , - EFFECT_STATUEFX , - EFFECT_RESET_HAIR , - EFFECT_BOILERFX , - EFFECT_SETFOG , - EFFECT_GHOSTTRAP , - EFFECT_LARALOCATION , - EFFECT_CLEARSCARABS , - EFFECT_FOOTPRINT_FX , - EFFECT_FLIP_MAP0 , - EFFECT_FLIP_MAP1 , - EFFECT_FLIP_MAP2 , - EFFECT_FLIP_MAP3 , - EFFECT_FLIP_MAP4 , - EFFECT_FLIP_MAP5 , - EFFECT_FLIP_MAP6 , - EFFECT_FLIP_MAP7 , - EFFECT_FLIP_MAP8 , - EFFECT_FLIP_MAP9 , - EFFECT_POURSWAP1 , - EFFECT_POURSWAP2 , - EFFECT_LARALOCATIONPAD , - EFFECT_KILLACTIVEBADDIES, - }; + enum Effect : int32 { + NONE = -1, + ROTATE_180 , + FLOOR_SHAKE , + LARA_NORMAL , + LARA_BUBBLES , + FINISH_LEVEL , + EARTHQUAKE , + FLOOD , + UNK1 , + UNK2 , + UNK3 , + UNK4 , + EXPLOSION , + LARA_HANDSFREE , + FLIP_MAP , + DRAW_RIGHTGUN , + UNK5 , + FLICKER , + }; enum { SND_NO = 2, @@ -290,6 +264,8 @@ namespace TR { SND_SHOTGUN_SHOT = 45, SND_UNDERWATER = 60, + + SND_FLOOD = 81, SND_INV_SPIN = 108, SND_INV_HOME = 109, @@ -306,6 +282,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, @@ -315,21 +304,26 @@ namespace TR { MODEL_LARA_SPEC = 5, }; + enum HitType { + HIT_DEFAULT, + HIT_BLADE, + HIT_BOULDER, + HIT_SPIKES, + HIT_REX, + }; + enum Action : uint16 { ACTIVATE , // activate item CAMERA_SWITCH , // switch to camera FLOW , // underwater flow - FLIP_MAP , // flip map + FLIP , // flip map FLIP_ON , // flip on FLIP_OFF , // flip off CAMERA_TARGET , // look at item END , // end level SOUNDTRACK , // play soundtrack - HARDCODE , // special hadrdcode trigger + EFFECT , // special effect trigger SECRET , // secret found - CLEAR , // clear bodies - CAMERA_FLYBY , // flyby camera sequence - CUTSCENE , // play cutscene }; namespace Limits { @@ -337,39 +331,43 @@ namespace TR { struct Limit { float dy, dz, ay; ::Box box; + bool alignAngle; + bool alignHoriz; }; Limit SWITCH = { - 0, 376, 30, {{-200, 0, 312}, {200, 0, 512}} + 0, 376, 30, {{-200, 0, 312}, {200, 0, 512}}, true, false }; Limit SWITCH_UNDERWATER = { - 0, 100, 80, {{-1024, -1024, -1024}, {1024, 1024, 512}} + 0, 100, 80, {{-1024, -1024, -1024}, {1024, 1024, 512}}, true, true }; Limit PICKUP = { - 0, -100, 180, {{-256, -100, -256}, {256, 100, 100}} + 0, -100, 180, {{-256, -100, -256}, {256, 100, 100}}, false, true }; Limit PICKUP_UNDERWATER = { - -200, -350, 45, {{-512, -512, -512}, {512, 512, 512}} + -200, -350, 45, {{-512, -512, -512}, {512, 512, 512}}, false, true }; Limit KEY_HOLE = { - 0, 362, 30, {{-200, 0, 312}, {200, 0, 512}} + 0, 362, 30, {{-200, 0, 312}, {200, 0, 512}}, true, true }; Limit PUZZLE_HOLE = { - 0, 327, 30, {{-200, 0, 312}, {200, 0, 512}} + 0, 327, 30, {{-200, 0, 312}, {200, 0, 512}}, true, true }; Limit BLOCK = { - 0, -612, 30, {{-300, 0, -692}, {300, 0, -512}} + 0, -612, 30, {{-300, 0, -692}, {300, 0, -512}}, true, false + }; + + Limit SCION = { + 640, -202, 30, {{-256, 540, -350}, {256, 740, -200}}, false, false }; } - #pragma pack(push, 1) - struct fixed { uint16 L; int16 H; @@ -418,12 +416,12 @@ namespace TR { struct Rectangle { uint16 vertices[4]; - uint16 texture; + uint16 texture:15, color:1; }; struct Triangle { uint16 vertices[3]; - uint16 texture; + uint16 texture:15, color:1; }; struct Tile4 { @@ -437,7 +435,7 @@ namespace TR { }; struct Tile32 { - Color32 color[256 * 256]; + Color32 color[256 * 256]; // + 128 for mips data }; struct CLUT { @@ -550,7 +548,7 @@ namespace TR { uint16 end:1; }; struct { - uint16 delay:8, once:1, timer:7; + uint16 timer:8, once:1, speed:5, :2; }; } triggerCmd; @@ -568,10 +566,9 @@ namespace TR { uint16 boxIndex:15, end:1; }; - //struct Collider { - // uint16 radius:10, info:6; - // uint16 flags:16; - //}; + struct Flags { + uint16 :8, once:1, active:5, :2; + }; // internal mesh structure struct Mesh { @@ -614,42 +611,54 @@ namespace TR { int32 x, y, z; angle rotation; int16 intensity; - union { - struct { uint16 unused:7, clear:1, invisible:1, active:5, collision:1, rendered:1; }; + union Flags { + struct { uint16 unused:6, collision:1, invisible:1, once:1, active:5, reverse:1, rendered:1; }; uint16 value; } flags; // not exists in file uint16 align; int32 modelIndex; // index of representation in models (index + 1) or spriteSequences (-(index + 1)) arrays - void *controller; // Controller implementation or NULL + 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_1 && type <= KEY_4) || + (type >= KEY_ITEM_1 && type <= KEY_ITEM_4) || (type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == SCION_1); // TODO: recheck all items } - bool isKeyHole() { - return type >= KEY_HOLE_1 && type <= KEY_HOLE_2; + bool isActor() const { + return type >= CUT_1 && type <= CUT_4; } - bool isBlock() { + bool isPuzzleHole() const { + return type >= PUZZLE_HOLE_1 && type <= PUZZLE_HOLE_2; + } + + 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; @@ -670,10 +679,10 @@ namespace TR { case PUZZLE_3 : return INV_PUZZLE_3; case PUZZLE_4 : return INV_PUZZLE_4; - case KEY_1 : return INV_KEY_1; - case KEY_2 : return INV_KEY_2; - case KEY_3 : return INV_KEY_3; - case KEY_4 : return INV_KEY_4; + case KEY_ITEM_1 : return INV_KEY_1; + case KEY_ITEM_2 : return INV_KEY_2; + case KEY_ITEM_3 : return INV_KEY_3; + case KEY_ITEM_4 : return INV_KEY_4; case LEADBAR : return INV_LEADBAR; //case TR::Entity::SCION : return TR::Entity::INV_SCION; @@ -681,21 +690,21 @@ namespace TR { } } - static Type getKeyForHole(Type hole) { + static Type getItemForHole(Type hole) { switch (hole) { - case PUZZLE_HOLE_1 : return PUZZLE_1; break; - case PUZZLE_HOLE_2 : return PUZZLE_2; break; - case PUZZLE_HOLE_3 : return PUZZLE_3; break; - case PUZZLE_HOLE_4 : return PUZZLE_4; break; - case KEY_HOLE_1 : return KEY_1; break; - case KEY_HOLE_2 : return KEY_2; break; - case KEY_HOLE_3 : return KEY_3; break; - case KEY_HOLE_4 : return KEY_4; break; + case PUZZLE_HOLE_1 : return PUZZLE_1; break; + case PUZZLE_HOLE_2 : return PUZZLE_2; break; + case PUZZLE_HOLE_3 : return PUZZLE_3; break; + case PUZZLE_HOLE_4 : return PUZZLE_4; break; + case KEY_HOLE_1 : return KEY_ITEM_1; break; + case KEY_HOLE_2 : return KEY_ITEM_2; break; + case KEY_HOLE_3 : return KEY_ITEM_3; break; + case KEY_HOLE_4 : return KEY_ITEM_4; break; default : return NONE; } } - static void fixOpaque(Type type, bool &opaque) { + static void fixOpaque(Type type, bool &opaque) { // to boost performance on mobile devices if (type >= LARA && type <= ENEMY_GIANT_MUTANT && type != ENEMY_REX && type != ENEMY_RAPTOR @@ -706,6 +715,8 @@ namespace TR { opaque = true; if (type == SWITCH || type == SWITCH_WATER) opaque = true; + if (type == PUZZLE_HOLE_1) // LEVEL3A cogs + opaque = false; } }; @@ -818,16 +829,29 @@ namespace TR { struct ObjectTexture { uint16 clut; - Tile tile; // tile or palette index - uint16 attribute; // 0 - opaque, 1 - transparent, 2 - blend additive - ubyte2 texCoord[4]; + Tile tile; // tile or palette index + uint16 attribute:15, repeat:1; // 0 - opaque, 1 - transparent, 2 - blend additive, + short2 texCoord[4]; + + short4 getMinMax() const { + return { + min(min(texCoord[0].x, texCoord[1].x), texCoord[2].x), + min(min(texCoord[0].y, texCoord[1].y), texCoord[2].y), + max(max(texCoord[0].x, texCoord[1].x), texCoord[2].x), + max(max(texCoord[0].y, texCoord[1].y), texCoord[2].y), + }; + } }; struct SpriteTexture { uint16 clut; uint16 tile; int16 l, t, r, b; - ubyte2 texCoord[2]; + short2 texCoord[2]; + + short4 getMinMax() const { + return { texCoord[0].x, texCoord[0].y, texCoord[1].x, texCoord[1].y }; + } }; struct SpriteSequence { @@ -843,7 +867,9 @@ namespace TR { int16 room; // for camera int16 speed; // for sink (underwater current) }; - uint16 flags; + struct { + uint16 :8, once:1, :5, :2; + } flags; }; struct CameraFrame { @@ -891,13 +917,77 @@ namespace TR { } flags; }; - #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; + const char *title; + int ambientTrack; + } LEVEL_INFO[LEVEL_MAX] = { + { "" , "Custom Level", TRACK_CAVES }, + { "TITLE" , "", TRACK_TITLE }, + { "GYM" , "Lara's Home", 0 }, + { "LEVEL1" , "Caves", TRACK_CAVES }, + { "LEVEL2" , "City of Vilcabamba", TRACK_CAVES }, + { "LEVEL3A" , "Lost Valley", TRACK_CAVES }, + { "LEVEL3B" , "Tomb of Qualopec", TRACK_CAVES }, + { "CUT1" , "", TRACK_CUT1 }, + { "LEVEL4" , "St. Francis' Folly", TRACK_CAVES }, + { "LEVEL5" , "Colosseum", TRACK_CAVES }, + { "LEVEL6" , "Palace Midas", TRACK_CAVES }, + { "LEVEL7A" , "The Cistern", TRACK_CISTERN }, + { "LEVEL7B" , "Tomb of Tihocan", TRACK_CISTERN }, + { "CUT2" , "", TRACK_CUT2 }, + { "LEVEL8A" , "City of Khamoon", TRACK_EGYPT }, + { "LEVEL8B" , "Obelisk of Khamoon", TRACK_EGYPT }, + { "LEVEL8C" , "Sanctuary of the Scion", TRACK_EGYPT }, + { "LEVEL10A" , "Natla's Mines", TRACK_MINE }, + { "CUT3" , "", TRACK_CUT3 }, + { "LEVEL10B" , "Atlantis", TRACK_MINE }, + { "CUT4" , "", TRACK_CUT4 }, + { "LEVEL10C" , "The Great Pyramid", TRACK_MINE }, + { "EGYPT" , "Return to Egypt", TRACK_EGYPT }, + { "CAT" , "Temple of the Cat", TRACK_EGYPT }, + { "END" , "Atlantean Stronghold", TRACK_EGYPT }, + { "END2" , "The Hive", TRACK_EGYPT }, + }; struct Level { - enum : uint32 { - VER_TR1_PC = 0x00000020, - VER_TR1_PSX = 0x56414270, - } version; + Version version; + LevelID id; int32 tilesCount; Tile32 *tiles; @@ -1001,7 +1091,6 @@ namespace TR { ANTIPAD , COMBAT , DUMMY , - ANTI , }; struct FloorInfo { @@ -1031,11 +1120,18 @@ namespace TR { } }; + 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 { int16 muzzleFlash; @@ -1067,7 +1163,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; @@ -1088,6 +1184,8 @@ namespace TR { return; } + id = getLevelID(stream.size); + if (version == VER_TR1_PSX) { uint32 offsetTexTiles; stream.seek(8); @@ -1206,14 +1304,24 @@ namespace TR { // models stream.read(modelsCount); models = modelsCount ? new Model[modelsCount] : NULL; - for (int i = 0; i < modelsCount; i++) - stream.raw(&models[i], sizeof(models[i]) - (version == VER_TR1_PC ? sizeof(models[i].align) : 0)); + for (int i = 0; i < modelsCount; i++) { + Model &m = models[i]; + stream.read(m.type); + stream.read(m.unused); + stream.read(m.mCount); + stream.read(m.mStart); + stream.read(m.node); + stream.read(m.frame); + stream.read(m.animation); + if (version == VER_TR1_PSX) + stream.seek(sizeof(m.align)); + } stream.read(staticMeshes, stream.read(staticMeshesCount)); // textures & UV 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 @@ -1234,10 +1342,19 @@ namespace TR { entities = new Entity[entitiesCount]; for (int i = 0; i < entitiesBaseCount; i++) { Entity &e = entities[i]; - stream.raw(&e, sizeof(e) - sizeof(e.align) - sizeof(e.controller) - sizeof(e.modelIndex)); + stream.read(e.type); + stream.read(e.room); + stream.read(e.x); + stream.read(e.y); + stream.read(e.z); + stream.read(e.rotation); + stream.read(e.intensity); + stream.read(e.flags); + e.align = 0; e.controller = NULL; e.modelIndex = getModelIndex(e.type); + if (e.type == Entity::CUT_1) cutEntity = i; } @@ -1249,7 +1366,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)); @@ -1273,11 +1390,19 @@ namespace TR { initRoomMeshes(); initTiles(tiles4, tiles8, palette, cluts); - //delete[] tiles4; tiles4 = NULL; - delete[] tiles8; tiles8 = NULL; + //delete[] tiles4; + //tiles4 = NULL; + delete[] tiles8; + tiles8 = NULL; + // init flipmap states + 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)); @@ -1340,26 +1465,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() { @@ -1413,6 +1519,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); @@ -1492,8 +1678,8 @@ namespace TR { stream.seek(sizeof(uint16)); stream.raw(&mesh.rectangles[rCount], crCount * sizeof(Rectangle)); stream.seek(sizeof(uint16)); stream.raw(&mesh.triangles[tCount], ctCount * sizeof(Triangle)); // add "use palette color" flags - for (int i = rCount; i < mesh.rCount; i++) mesh.rectangles[i].texture |= 0x8000; - for (int i = tCount; i < mesh.tCount; i++) mesh.triangles[i].texture |= 0x8000; + for (int i = rCount; i < mesh.rCount; i++) mesh.rectangles[i].color = true; + for (int i = tCount; i < mesh.tCount; i++) mesh.triangles[i].color = true; break; } case VER_TR1_PSX : { @@ -1531,8 +1717,8 @@ namespace TR { stream.read(mesh.rectangles, stream.read(mesh.rCount)); stream.read(mesh.triangles, stream.read(mesh.tCount)); - for (int i = 0; i < mesh.rCount; i++) if (mesh.rectangles[i].texture < 300) mesh.rectangles[i].texture |= 0x8000; - for (int i = 0; i < mesh.tCount; i++) if (mesh.triangles[i].texture < 300) mesh.triangles[i].texture |= 0x8000; + for (int i = 0; i < mesh.rCount; i++) if (mesh.rectangles[i].texture < 256) mesh.rectangles[i].color = true; + for (int i = 0; i < mesh.tCount; i++) if (mesh.triangles[i].texture < 256) mesh.triangles[i].color = true; break; } } @@ -1627,7 +1813,7 @@ namespace TR { uint8 x0, y0; uint16 clut; uint8 x1, y1; - Tile tile; + Tile tile; uint8 x2, y2; uint16 unknown; uint8 x3, y3; @@ -1658,7 +1844,7 @@ namespace TR { for (int i = 0; i < spriteTexturesCount; i++) { SpriteTexture &t = spriteTextures[i]; switch (version) { - case VER_TR1_PC : { + case VER_TR1_PC : { struct { uint16 tile; uint8 u, v; @@ -1705,7 +1891,7 @@ namespace TR { void initTiles(Tile4 *tiles4, Tile8 *tiles8, Color24 *palette, CLUT *cluts) { tiles = new Tile32[tilesCount]; - + // convert to RGBA switch (version) { case VER_TR1_PC : { ASSERT(tiles8); @@ -1750,7 +1936,7 @@ namespace TR { int maxY = max(max(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y); for (int y = minY; y <= maxY; y++) - for (int x = minX; x <= maxX; x++) + for (int x = minX; x <= maxX; x++) dst.color[y * 256 + x] = clut.color[(x % 2) ? src.index[(y * 256 + x) / 2].b : src.index[(y * 256 + x) / 2].a]; } @@ -1760,8 +1946,8 @@ namespace TR { Tile32 &dst = tiles[t.tile]; Tile4 &src = tiles4[t.tile]; - for (int y = t.texCoord[0].y; y < t.texCoord[1].y; y++) - for (int x = t.texCoord[0].x; x < t.texCoord[1].x; x += 2) { + for (int y = t.texCoord[0].y; y <= t.texCoord[1].y; y++) + for (int x = t.texCoord[0].x; x <= t.texCoord[1].x; x += 2) { dst.color[y * 256 + x + 0] = clut.color[src.index[(y * 256 + x) / 2].a]; dst.color[y * 256 + x + 1] = clut.color[src.index[(y * 256 + x) / 2].b]; } @@ -1777,6 +1963,7 @@ namespace TR { switch (version) { case VER_TR1_PC : return palette[texture & 0xFF]; case VER_TR1_PSX : { + ASSERT((texture & 0x7FFF) < 256); ObjectTexture &t = objectTextures[texture & 0x7FFF]; int idx = (t.texCoord[0].y * 256 + t.texCoord[0].x) / 2; int part = t.texCoord[0].x % 2; @@ -1789,6 +1976,7 @@ namespace TR { } Stream* getSampleStream(int index) const { + if (!soundOffsets) return NULL; uint8 *data = &soundData[soundOffsets[index]]; uint32 size = 0; switch (version) { @@ -1843,8 +2031,22 @@ 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); + + if (isFlipped && rooms[roomIndex].alternateRoom > -1) + roomIndex = rooms[roomIndex].alternateRoom; + Room &room = rooms[roomIndex]; int sx = x - room.info.x; @@ -1861,6 +2063,57 @@ namespace TR { return room.sectors[sx * room.zSectors + sz]; } + Room::Sector& getSector(int roomIndex, int x, int z, int §orIndex) { + ASSERT(roomIndex >= 0 && roomIndex < roomsCount); + Room &room = rooms[roomIndex]; + x -= room.info.x; + z -= room.info.z; + x /= 1024; + z /= 1024; + return room.sectors[sectorIndex = (x * room.zSectors + z)]; + } + + 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); @@ -1880,7 +2133,7 @@ namespace TR { info.trigger = Trigger::ACTIVATE; info.trigCmdCount = 0; - if (s.floor == -127) + if (s.floor == NO_FLOOR) return; Room::Sector *sBelow = &s; @@ -1912,6 +2165,10 @@ namespace TR { for (int i = 0; i < info.trigCmdCount; i++) { FloorData::TriggerCommand cmd = info.trigCmd[i]; + if (cmd.action == Action::CAMERA_SWITCH) { + i++; + continue; + } if (cmd.action != Action::ACTIVATE) continue; Entity &e = entities[cmd.args]; @@ -2024,10 +2281,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 \ No newline at end of file +#endif diff --git a/src/game.h b/src/game.h index 0fd2ef9..124fcdc 100644 --- a/src/game.h +++ b/src/game.h @@ -3,20 +3,40 @@ #include "core.h" #include "format.h" +#include "cache.h" #include "level.h" #include "ui.h" -namespace Game { - Level *level; +ShaderCache *shaderCache; - void startLevel(Stream *lvl, Stream *snd, bool demo, bool home) { +namespace Game { + Level *level; + Stream *nextLevel; +} + +void loadAsync(Stream *stream, void *userData) { + if (!stream) { + if (Game::level) Game::level->isEnded = false; + return; + } + Game::nextLevel = stream; +} + +namespace Game { + void startLevel(Stream *lvl) { delete level; - level = new Level(*lvl, snd, demo, home); - UI::init(level); + level = new Level(*lvl); + UI::game = level; delete lvl; } - void init(Stream *lvl, Stream *snd) { + void stopChannel(Sound::Sample *channel) { + if (level) level->stopChannel(channel); + } + + void init(Stream *lvl) { + nextLevel = NULL; + Core::init(); Core::settings.detail.ambient = true; @@ -24,30 +44,36 @@ namespace Game { Core::settings.detail.shadows = true; Core::settings.detail.water = Core::support.texFloat || Core::support.texHalf; Core::settings.detail.contact = false; - +#ifdef __RPI__ + Core::settings.detail.ambient = false; + Core::settings.detail.shadows = false; +#endif Core::settings.controls.retarget = true; + Core::settings.audio.reverb = true; + + shaderCache = new ShaderCache(); + + UI::init(level); + + Sound::callback = stopChannel; 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); + if (!lvlName) lvlName = (char*)"level/TITLE.PSX"; + init(new Stream(lvlName)); } void free() { delete level; + UI::free(); + delete shaderCache; Core::free(); } void updateTick() { - if (Input::state[cInventory]) - level->inventory.toggle(); - float dt = Core::deltaTime; if (Input::down[ikR]) // slow motion (for animation debugging) Core::deltaTime /= 10.0f; @@ -62,6 +88,16 @@ namespace Game { } void update(float delta) { + PROFILE_MARKER("UPDATE"); + + if (nextLevel) { + startLevel(nextLevel); + nextLevel = NULL; + } + + if (level->isEnded) + return; + Input::update(); if (Input::down[ikV]) { // third <-> first person view @@ -80,6 +116,7 @@ namespace Game { } void render() { + PROFILE_MARKER("RENDER"); PROFILE_TIMING(Core::stats.tFrame); Core::beginFrame(); level->render(); @@ -93,4 +130,4 @@ namespace Game { } } -#endif \ No newline at end of file +#endif diff --git a/src/inventory.h b/src/inventory.h index 7926f0f..b8fe9ce 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -29,58 +29,62 @@ struct Inventory { Page page, targetPage; int itemsCount; + TR::LevelID nextLevel; // toggle result + struct Item { TR::Entity::Type type; int count; float angle; Animation *anim; + int value; + struct Desc { - const char *name; + StringID str; Page page; int model; } desc; Item() : anim(NULL) {} - Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f) { + Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f), value(0) { switch (type) { - case TR::Entity::INV_PASSPORT : desc = { "Game", PAGE_OPTION, level->extra.inv.passport }; break; - case TR::Entity::INV_PASSPORT_CLOSED : desc = { "Game", PAGE_OPTION, level->extra.inv.passport_closed }; break; - case TR::Entity::INV_MAP : desc = { "Map", PAGE_INVENTORY, level->extra.inv.map }; break; - case TR::Entity::INV_COMPASS : desc = { "Compass", PAGE_INVENTORY, level->extra.inv.compass }; break; - case TR::Entity::INV_HOME : desc = { "Lara's Home", PAGE_OPTION, level->extra.inv.home }; break; - case TR::Entity::INV_DETAIL : desc = { "Detail Levels", PAGE_OPTION, level->extra.inv.detail }; break; - case TR::Entity::INV_SOUND : desc = { "Sound", PAGE_OPTION, level->extra.inv.sound }; break; - case TR::Entity::INV_CONTROLS : desc = { "Controls", PAGE_OPTION, level->extra.inv.controls }; break; - case TR::Entity::INV_GAMMA : desc = { "Gamma", PAGE_OPTION, level->extra.inv.gamma }; break; + case TR::Entity::INV_PASSPORT : desc = { STR_GAME, PAGE_OPTION, level->extra.inv.passport }; break; + case TR::Entity::INV_PASSPORT_CLOSED : desc = { STR_GAME, PAGE_OPTION, level->extra.inv.passport_closed }; break; + case TR::Entity::INV_MAP : desc = { STR_MAP, PAGE_INVENTORY, level->extra.inv.map }; break; + case TR::Entity::INV_COMPASS : desc = { STR_COMPASS, PAGE_INVENTORY, level->extra.inv.compass }; break; + case TR::Entity::INV_HOME : desc = { STR_HOME, PAGE_OPTION, level->extra.inv.home }; break; + case TR::Entity::INV_DETAIL : desc = { STR_DETAIL, PAGE_OPTION, level->extra.inv.detail }; break; + case TR::Entity::INV_SOUND : desc = { STR_SOUND, PAGE_OPTION, level->extra.inv.sound }; break; + case TR::Entity::INV_CONTROLS : desc = { STR_CONTROLS, PAGE_OPTION, level->extra.inv.controls }; break; + case TR::Entity::INV_GAMMA : desc = { STR_GAMMA, PAGE_OPTION, level->extra.inv.gamma }; break; - case TR::Entity::INV_PISTOLS : desc = { "Pistols", PAGE_INVENTORY, level->extra.inv.weapon[0] }; break; - case TR::Entity::INV_SHOTGUN : desc = { "Shotgun", PAGE_INVENTORY, level->extra.inv.weapon[1] }; break; - case TR::Entity::INV_MAGNUMS : desc = { "Magnums", PAGE_INVENTORY, level->extra.inv.weapon[2] }; break; - case TR::Entity::INV_UZIS : desc = { "Uzis", PAGE_INVENTORY, level->extra.inv.weapon[3] }; break; + case TR::Entity::INV_PISTOLS : desc = { STR_PISTOLS, PAGE_INVENTORY, level->extra.inv.weapon[0] }; break; + case TR::Entity::INV_SHOTGUN : desc = { STR_SHOTGUN, PAGE_INVENTORY, level->extra.inv.weapon[1] }; break; + case TR::Entity::INV_MAGNUMS : desc = { STR_MAGNUMS, PAGE_INVENTORY, level->extra.inv.weapon[2] }; break; + case TR::Entity::INV_UZIS : desc = { STR_UZIS, PAGE_INVENTORY, level->extra.inv.weapon[3] }; break; - case TR::Entity::INV_AMMO_PISTOLS : desc = { "Pistol Clips", PAGE_INVENTORY, level->extra.inv.ammo[0] }; break; - case TR::Entity::INV_AMMO_SHOTGUN : desc = { "Shotgun Shells", PAGE_INVENTORY, level->extra.inv.ammo[1] }; break; - case TR::Entity::INV_AMMO_MAGNUMS : desc = { "Magnum Clips", PAGE_INVENTORY, level->extra.inv.ammo[2] }; break; - case TR::Entity::INV_AMMO_UZIS : desc = { "Uzi Clips", PAGE_INVENTORY, level->extra.inv.ammo[3] }; break; + case TR::Entity::INV_AMMO_PISTOLS : desc = { STR_AMMO_PISTOLS, PAGE_INVENTORY, level->extra.inv.ammo[0] }; break; + case TR::Entity::INV_AMMO_SHOTGUN : desc = { STR_AMMO_SHOTGUN, PAGE_INVENTORY, level->extra.inv.ammo[1] }; break; + case TR::Entity::INV_AMMO_MAGNUMS : desc = { STR_AMMO_MAGNUMS, PAGE_INVENTORY, level->extra.inv.ammo[2] }; break; + case TR::Entity::INV_AMMO_UZIS : desc = { STR_AMMO_UZIS, PAGE_INVENTORY, level->extra.inv.ammo[3] }; break; - case TR::Entity::INV_MEDIKIT_SMALL : desc = { "Small Medi Pack", PAGE_INVENTORY, level->extra.inv.medikit[0] }; break; - case TR::Entity::INV_MEDIKIT_BIG : desc = { "Large Medi Pack", PAGE_INVENTORY, level->extra.inv.medikit[1] }; break; + case TR::Entity::INV_MEDIKIT_SMALL : desc = { STR_MEDI_SMALL, PAGE_INVENTORY, level->extra.inv.medikit[0] }; break; + case TR::Entity::INV_MEDIKIT_BIG : desc = { STR_MEDI_BIG, PAGE_INVENTORY, level->extra.inv.medikit[1] }; break; - case TR::Entity::INV_PUZZLE_1 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[0] }; break; - case TR::Entity::INV_PUZZLE_2 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[1] }; break; - case TR::Entity::INV_PUZZLE_3 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[2] }; break; - case TR::Entity::INV_PUZZLE_4 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[3] }; break; + case TR::Entity::INV_PUZZLE_1 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[0] }; break; + case TR::Entity::INV_PUZZLE_2 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[1] }; break; + case TR::Entity::INV_PUZZLE_3 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[2] }; break; + case TR::Entity::INV_PUZZLE_4 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[3] }; break; - case TR::Entity::INV_KEY_1 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[0] }; break; - case TR::Entity::INV_KEY_2 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[1] }; break; - case TR::Entity::INV_KEY_3 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[2] }; break; - case TR::Entity::INV_KEY_4 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[3] }; break; + case TR::Entity::INV_KEY_1 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[0] }; break; + case TR::Entity::INV_KEY_2 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[1] }; break; + case TR::Entity::INV_KEY_3 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[2] }; break; + case TR::Entity::INV_KEY_4 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[3] }; break; - case TR::Entity::INV_LEADBAR : desc = { "Lead Bar", PAGE_ITEMS, level->extra.inv.leadbar }; break; - case TR::Entity::INV_SCION : desc = { "Scion", PAGE_ITEMS, level->extra.inv.scion }; break; - default : desc = { "unknown", PAGE_ITEMS, -1 }; break; + case TR::Entity::INV_LEADBAR : desc = { STR_LEAD_BAR, PAGE_ITEMS, level->extra.inv.leadbar }; break; + case TR::Entity::INV_SCION : desc = { STR_SCION, PAGE_ITEMS, level->extra.inv.scion }; break; + default : desc = { STR_UNKNOWN, PAGE_ITEMS, -1 }; break; } if (desc.model > -1) { @@ -108,7 +112,19 @@ struct Inventory { } void update() { - if (anim) anim->update(); + if (!anim) return; + anim->update(); + + if (type == TR::Entity::INV_PASSPORT) { + float t = (14 + value * 5) / 30.0f; + + if ( (anim->dir > 0.0f && anim->time > t) || + (anim->dir < 0.0f && anim->time < t)) { + anim->dir = 0.0f; + anim->time = t; + anim->updateInfo(); + } + } } void render(IGame *game, const Basis &basis) { @@ -126,39 +142,61 @@ struct Inventory { } void choose() { - if (anim) anim->setAnim(0, 0, false); + if (!anim) return; + anim->setAnim(0, 0, false); } } *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) { + static void loadTitleBG(Stream *stream, void *userData) { + if (!stream) return; + Inventory *inv = (Inventory*)userData; + + inv->background[0] = Texture::LoadPCX(*stream); + delete stream; + } + + Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0), nextLevel(TR::LEVEL_MAX) { + 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); - /* - 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); + if (id != TR::TITLE && id != TR::GYM) { +/* + 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); + } + + if (id == TR::TITLE) { + add(TR::Entity::INV_HOME); + + memset(background, 0, sizeof(background)); + + new Stream("level/TITLEH.PCX", loadTitleBG, this); + } else { + add(TR::Entity::INV_COMPASS); + + for (int i = 0; i < COUNT(background); i++) + background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false); + } 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() { @@ -261,7 +299,7 @@ struct Inventory { } bool chooseKey(TR::Entity::Type hole) { - TR::Entity::Type type = TR::Entity::getKeyForHole(hole); + TR::Entity::Type type = TR::Entity::getItemForHole(hole); if (type == TR::Entity::NONE) return false; int index = contains(type); @@ -290,6 +328,7 @@ struct Inventory { for (int i = 0; i < itemsCount; i++) items[i]->reset(); + nextLevel = TR::LEVEL_MAX; phasePage = 1.0f; phaseSelect = 1.0f; page = targetPage = curPage; @@ -363,13 +402,29 @@ struct Inventory { return active && phaseRing == 1.0f && index == targetIndex && phasePage == 1.0f && (type == TR::Entity::INV_MEDIKIT_SMALL || type == TR::Entity::INV_MEDIKIT_BIG); } + void onChoose(Item *item) { + if (item->type == TR::Entity::INV_PASSPORT) { + game->playSound(TR::SND_INV_PAGE, vec3(), 0, 0); + item->value = 1; + passportSlot = 0; + passportSlotCount = 2; + passportSlots[0] = TR::LEVEL_1; + passportSlots[1] = TR::LEVEL_2; + } + } + void update() { + float lastChoose = phaseChoose; + if (phaseChoose == 0.0f) doPhase(active, 2.0f, phaseRing); doPhase(true, 1.6f, phasePage); doPhase(chosen, 4.0f, phaseChoose); doPhase(true, 2.5f, phaseSelect); + if (phaseChoose == 1.0f && lastChoose != 1.0f) + onChoose(items[getGlobalIndex(page, index)]); + if (page != targetPage && phasePage == 1.0f) { page = targetPage; index = targetIndex = pageItemIndex[page]; @@ -382,20 +437,82 @@ struct Inventory { bool ready = active && phaseRing == 1.0f && phasePage == 1.0f; - if (index == targetIndex && targetPage == page && ready && !chosen) { - float s = Input::touchTimerVis > 0.0f ? -1.0f : 1.0f; + enum KeyDir { NONE, LEFT, RIGHT, UP, DOWN } dir; - if (Input::state[cLeft] || Input::joy.L.x < -0.5f || Input::joy.R.x > 0.5f) { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; } - if (Input::state[cRight] || Input::joy.L.x > 0.5f || Input::joy.R.x < -0.5f) { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; } - if ((Input::state[cUp] || Input::joy.L.y < -0.5f || Input::joy.R.y > 0.5f) && page < PAGE_ITEMS && getItemsCount(page + 1)) { phasePage = 0.0f; targetPage = Page(page + 1); } - if ((Input::state[cDown] || Input::joy.L.y > 0.5f || Input::joy.R.y < -0.5f) && page > PAGE_OPTION && getItemsCount(page - 1)) { phasePage = 0.0f; targetPage = Page(page - 1); } + if (Input::state[cLeft] || Input::joy.L.x < -0.5f || Input::joy.R.x > 0.5f) + dir = LEFT; + else if (Input::state[cRight] || Input::joy.L.x > 0.5f || Input::joy.R.x < -0.5f) + dir = RIGHT; + else if (Input::state[cUp] || Input::joy.L.y < -0.5f || Input::joy.R.y > 0.5f) + dir = UP; + else if (Input::state[cDown] || Input::joy.L.y > 0.5f || Input::joy.R.y < -0.5f) + dir = DOWN; + else + dir = NONE; + + static KeyDir lastDir = NONE; + + if (index == targetIndex && targetPage == page && ready) { + if (!chosen) { + if (dir == UP && !(page < PAGE_ITEMS && getItemsCount(page + 1))) dir = NONE; + if (dir == DOWN && !(page > PAGE_OPTION && getItemsCount(page - 1))) dir = NONE; + + switch (dir) { + case LEFT : { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; } break; + case RIGHT : { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; } break; + case UP : { phasePage = 0.0f; targetPage = Page(page + 1); } break; + case DOWN : { phasePage = 0.0f; targetPage = Page(page - 1); } break; + default : ; + } + + if (index != targetIndex) { + vec3 p; + game->playSound(TR::SND_INV_SPIN, p, 0, 0); + } + } else { + Item *item = items[getGlobalIndex(page, index)]; + + if (item->type == TR::Entity::INV_PASSPORT && passportSlotCount) { + if (lastDir != dir) { + // passport slots + if (item->value == 0 && item->anim->dir == 0.0f) { // slot select + if (dir == UP) { passportSlot = (passportSlot - 1 + passportSlotCount) % passportSlotCount; }; + if (dir == DOWN) { passportSlot = (passportSlot + 1) % passportSlotCount; }; + } + // passport pages + if (dir == LEFT && item->value > 0) { item->value--; item->anim->dir = -1.0f; game->playSound(TR::SND_INV_PAGE, vec3(), 0, 0); } + if (dir == RIGHT && item->value < 2) { item->value++; item->anim->dir = 1.0f; game->playSound(TR::SND_INV_PAGE, vec3(), 0, 0); } + lastDir = dir; + } + + if (Input::state[cAction] && phaseChoose == 1.0f) { + TR::LevelID id = game->getLevel()->id; + switch (item->value) { + case 0 : nextLevel = passportSlots[passportSlot]; break; + case 1 : nextLevel = (id == TR::TITLE) ? TR::LEVEL_1 : game->getLevel()->id; break; + case 2 : nextLevel = (id == TR::TITLE) ? TR::LEVEL_MAX : TR::TITLE; break; + } + + if (nextLevel != TR::LEVEL_MAX) { + item->anim->dir = -1.0f; + item->value = -100; + toggle(); + } + } + } + + if (item->type == TR::Entity::INV_HOME) { + if (Input::state[cAction] && phaseChoose == 1.0f) { + nextLevel = TR::GYM; + toggle(); + } + } - if (index != targetIndex) { - vec3 p; - game->playSound(TR::SND_INV_SPIN, p, 0, 0); } } + ready = active && phaseRing == 1.0f && phasePage == 1.0f; + vec3 p; Item *item = items[getGlobalIndex(page, index)]; @@ -469,6 +586,9 @@ struct Inventory { toggle(); } } + + if (!isActive() && nextLevel != TR::LEVEL_MAX) + game->loadLevel(nextLevel); } void prepareBackground() { @@ -518,21 +638,66 @@ struct Inventory { } } - void renderItemText(const Item *item, float width) { - UI::textOut(vec2(0, 480 - 16), item->desc.name, UI::aCenter, width); - renderItemCount(item, vec2(width / 2 - 160, 480 - 96), 320); + int passportSlot, passportSlotCount; + TR::LevelID passportSlots[32]; + + void renderPassport(Item *item) { + if (item->value != 0 || item->anim->dir != 0.0f) return; // check for "Load Game" page + + float y = 120.0f; + float h = 20.0f; + float w = 320.0f; + + // background + UI::renderBar(UI::BAR_OPTION, vec2((UI::width - w - 16.0f) * 0.5f, y - 16.0f), vec2(w + 16.0f, h * 16.0f), 0.0f, 0, 0xC0000000); + // title + UI::renderBar(UI::BAR_OPTION, vec2((UI::width - w) * 0.5f, y - h + 6), vec2(w, h - 6), 1.0f, 0x802288FF, 0, 0, 0); + UI::textOut(vec2(0, y), STR_SELECT_LEVEL, UI::aCenter, UI::width); + + y += h * 2; + UI::renderBar(UI::BAR_OPTION, vec2((UI::width - w) * 0.5f, y + passportSlot * h + 6 - h), vec2(w, h - 6), 1.0f, 0xFFD8377C, 0); + + for (int i = 0; i < passportSlotCount; i++) + if (passportSlots[i] == TR::LEVEL_MAX) + UI::textOut(vec2(0, y + i * h), STR_AUTOSAVE, UI::aCenter, UI::width); + else + UI::textOut(vec2(0, y + i * h), TR::LEVEL_INFO[passportSlots[i]].title, UI::aCenter, UI::width); + } + + void renderItemText(Item *item) { + if (item->type == TR::Entity::INV_PASSPORT && phaseChoose == 1.0f) { + StringID str = STR_LOAD_GAME; + + if (game->getLevel()->id == TR::TITLE) { + if (item->value == 1) str = STR_START_GAME; + if (item->value == 2) str = STR_EXIT_GAME; + } else { + if (item->value == 1) str = STR_RESTART_LEVEL; + if (item->value == 2) str = STR_EXIT_TO_TITLE; + } + + UI::textOut(vec2(0, 480 - 16), str, UI::aCenter, UI::width); + } else + UI::textOut(vec2(0, 480 - 16), item->desc.str, UI::aCenter, UI::width); + + renderItemCount(item, vec2(UI::width / 2 - 160, 480 - 96), 320); if (phaseChoose == 1.0f) { - if (item->type == TR::Entity::INV_PASSPORT || - item->type == TR::Entity::INV_MAP || - item->type == TR::Entity::INV_COMPASS || - item->type == TR::Entity::INV_HOME || - item->type == TR::Entity::INV_DETAIL || - item->type == TR::Entity::INV_SOUND || - item->type == TR::Entity::INV_CONTROLS || - item->type == TR::Entity::INV_GAMMA) - { - UI::textOut(vec2(0, 240), "Not implemented yet!", UI::aCenter, width); + switch (item->type) { + case TR::Entity::INV_PASSPORT : + renderPassport(item); + break; + case TR::Entity::INV_HOME : + break; + case TR::Entity::INV_COMPASS : + case TR::Entity::INV_MAP : + case TR::Entity::INV_DETAIL : + case TR::Entity::INV_SOUND : + case TR::Entity::INV_CONTROLS : + case TR::Entity::INV_GAMMA : + UI::textOut(vec2(0, 240), STR_NOT_IMPLEMENTED, UI::aCenter, UI::width); + break; + default : ; } } } @@ -588,16 +753,34 @@ struct Inventory { void render() { // background Core::setDepthTest(false); + Core::setBlending(bmNone); - 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 - game->getMesh()->renderQuad(); + if (background[0]) { + background[0]->bind(sDiffuse); // orignal 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 aspectSrc = float(640.0f) / float(480.0f); + float aspectDst = float(Core::width) / float(Core::height); + float aspectImg = aspectDst / aspectSrc; + float ax = 640.0f / float(background[0]->width); + float ay = 480.0f / float(background[0]->height); + + Core::active.shader->setParam(uParam, vec4(ax * aspectImg, -ay, (0.5f - aspectImg * 0.5f) * ax, ay)); + } + game->getMesh()->renderQuad(); + } Core::setDepthTest(true); Core::setBlending(bmAlpha); + if (game->isCutscene()) + return; + // items game->setupBinding(); @@ -640,9 +823,10 @@ struct Inventory { void renderUI() { if (!active || phaseRing < 1.0f) return; - static const char* pageTitle[PAGE_MAX] = { "OPTION", "INVENTORY", "ITEMS" }; + static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_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); @@ -655,7 +839,7 @@ struct Inventory { } if (index == targetIndex) - renderItemText(items[getGlobalIndex(page, index)], UI::width); + renderItemText(items[getGlobalIndex(page, index)]); } }; diff --git a/src/lara.h b/src/lara.h index 9d98126..b266344 100644 --- a/src/lara.h +++ b/src/lara.h @@ -6,6 +6,7 @@ #include "character.h" #include "trigger.h" #include "sprite.h" +#include "enemy.h" #define TURN_FAST PI #define TURN_FAST_BACK PI * 3.0f / 4.0f @@ -42,6 +43,7 @@ #define PICKUP_FRAME_GROUND 40 #define PICKUP_FRAME_UNDERWATER 18 #define PUZZLE_FRAME 80 +#define KEY_FRAME 110 #define MAX_TRIGGER_ACTIONS 64 @@ -106,6 +108,7 @@ struct Lara : Character { ANIM_STAND_ROLL_BEGIN = 146, ANIM_STAND_ROLL_END = 147, + ANIM_DEATH_SPIKES = 149, ANIM_HANG_SWING = 150, }; @@ -194,7 +197,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 { @@ -210,21 +212,20 @@ struct Lara : Character { vec3 chestOffset; struct Arm { - int tracking; // tracking target (main target) - int target; // target for shooting + Controller *tracking; // tracking target (main target) + Controller *target; // target for shooting float shotTimer; quat rot, rotAbs; Weapon::Anim anim; Animation animation; + + Arm() : tracking(NULL), target(NULL) {} } arms[2]; - ActionCommand actionList[MAX_TRIGGER_ACTIONS]; - TR::Entity::Type usedKey; - TR::Entity *puzzleEntity; TR::Entity *pickupEntity; + KeyHole *keyHole; - int viewTarget; int roomPrev; // water out from room vec2 rotFactor; @@ -395,7 +396,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), braid(NULL) { if (getEntity().type == TR::Entity::LARA) { if (getRoom().flags.water) @@ -404,6 +405,11 @@ struct Lara : Character { animation.setAnim(ANIM_STAND); } + jointChest = 7; + jointHead = 14; + rangeChest = vec4(-0.40f, 0.40f, -0.90f, 0.90f) * PI; + rangeHead = vec4(-0.25f, 0.25f, -0.50f, 0.50f) * PI; + oxygen = LARA_MAX_OXYGEN; hitDir = -1; damageTime = LARA_DAMAGE_TIME; @@ -412,10 +418,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; @@ -425,11 +431,19 @@ struct Lara : Character { if (level->extra.braid > -1) braid = new Braid(this, vec3(-4.0f, 24.0f, -48.0f)); + //reset(19, vec3(41418, -3707, 58863), 270 * DEG2RAD); // level 5 (triangle) + //reset(9, vec3(63008, 0, 37787), 0); // level 2 (switch) + //reset(5, vec3(38643, -3072, 92370), PI * 0.5f); // level 3a (gears) + //reset(15, vec3(70067, -256, 29104), -0.68f); // level 2 (pool) + //reset(26, vec3(24475, 6912, 83505), 90 * DEG2RAD); // level 1 (switch timer) #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) //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(61, vec3(27221, -1024, 29205), PI * 0.5f); // level 2 (blade) + //reset(26, vec3(71980, 1546, 19000), 270 * DEG2RAD); // level 2 (underwater switch) + //reset(61, vec3(27221, -1024, 29205), -PI * 0.5f); // level 2 (blade) //reset(43, vec3(31400, -2560, 25200), PI); // level 2 (reach) //reset(16, vec3(60907, 0, 39642), PI * 3 / 2); // level 2 (hang & climb) //reset(19, vec3(60843, 1024, 30557), PI); // level 2 (block) @@ -441,6 +455,10 @@ struct Lara : Character { //reset(51, vec3(41015, 3584, 34494), -PI); // level 3a (t-rex) //reset(5, vec3(38643, -3072, 92370), PI * 0.5f); // level 3a (gears) //reset(43, vec3(64037, 6656, 48229), PI); // level 3b (movingblock) + //reset(27, vec3(72372, 8704, 46547), PI * 0.5f); // level 3b (spikes) + //reset(5, vec3(73394, 3840, 60758), 0); // level 3b (scion) + //reset(20, vec3(57724, 6656, 61941), 90 * DEG2RAD); // level 3b (boulder) + //reset(99, vec3(45562, -3328, 63366), 225 * DEG2RAD); // level 7a (flipmap) //reset(0, vec3(40913, -1012, 42252), PI); // level 8c //reset(10, vec3(90443, 11264 - 256, 114614), PI, STAND_ONWATER); // villa mortal 2 #endif @@ -694,7 +712,7 @@ struct Lara : Character { } void wpnChange(Weapon::Type wType) { - if (wpnCurrent == wType || home) { + if (wpnCurrent == wType || level->isHomeLevel) { if (emptyHands()) wpnDraw(); return; @@ -746,10 +764,10 @@ struct Lara : Character { //int realFrameIndex = int(arms[i].animation.time * 30.0f / anim->frameRate) % ((anim->frameEnd - anim->frameStart) / anim->frameRate + 1); if (anim.frameIndex != anim.framePrev) { if (anim.frameIndex == 0) { //realFrameIndex < arms[i].animation.framePrev) { - if ((input & ACTION) && (arm.tracking == -1 || arm.target > -1)) { + if ((input & ACTION) && (!arm.tracking || arm.target)) armShot[i] = true; - } else - wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, arm.target == -1); + else + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, arm.target == NULL); } // shotgun reload sound if (wpnCurrent == Weapon::SHOTGUN) { @@ -769,7 +787,6 @@ struct Lara : Character { void doShot(bool rightHand, bool leftHand) { int count = wpnCurrent == Weapon::SHOTGUN ? 6 : 2; - float nearDist = 32.0f * 1024.0f; vec3 nearPos; int shots = 0; @@ -803,8 +820,8 @@ struct Lara : Character { int room; vec3 hit = trace(getRoomIndex(), p, t, room, false); - if (arm->target > -1 && checkHit(arm->target, p, hit, hit)) { - ((Character*)level->entities[arm->target].controller)->hit(wpnGetDamage()); + if (arm->target && checkHit(arm->target, p, hit, hit)) { + ((Character*)arm->target)->hit(wpnGetDamage()); hit -= d * 64.0f; Sprite::add(game, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_ANIMATED); } else { @@ -840,15 +857,12 @@ struct Lara : Character { if (input & DEATH) { arms[0].shotTimer = arms[1].shotTimer = MUZZLE_FLASH_TIME + 1.0f; - arms[0].tracking = arms[1].tracking = -1; - arms[0].target = arms[1].target = -1; + arms[0].tracking = arms[1].tracking = NULL; + arms[0].target = arms[1].target = NULL; animation.overrideMask = 0; return; } - updateTargets(); - updateOverrides(); - if (wpnNext != Weapon::EMPTY && emptyHands()) { wpnSet(wpnNext); wpnDraw(); @@ -869,7 +883,7 @@ struct Lara : Character { for (int i = 0; i < 2; i++) { Arm &arm = arms[i]; - if (arm.target > -1 || ((input & ACTION) && arm.tracking == -1)) { + if (arm.target || ((input & ACTION) && !arm.tracking)) { if (arm.anim == Weapon::Anim::HOLD) wpnSetAnim(arm, wpnState, Weapon::Anim::AIM, 0.0f, 1.0f); } else @@ -1017,40 +1031,28 @@ struct Lara : Character { } animation.overrideMask = overrideMask; - - lookAt(viewTarget); - - if (wpnCurrent == Weapon::SHOTGUN) - aimShotgun(); - else - aimPistols(); } - void lookAt(int target) { // TODO: character lookAt - float speed = 8.0f * Core::deltaTime; - quat rot; + virtual void lookAt(Controller *target) { + updateOverrides(); - bool can = canLookAt(); - // chest - if (can && aim(target, 7, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.9f, PI * 0.9f), rot)) - rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); - else - rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); - animation.overrides[7] = rotChest * animation.overrides[7]; + Character::lookAt(canLookAt() ? target : NULL); - // head - if (can && aim(target, 14, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot)) - rotHead = rotHead.slerp(rot, speed); - else - rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); - animation.overrides[14] = rotHead * animation.overrides[14]; + if (!emptyHands()) { + updateTargets(); + + if (wpnCurrent == Weapon::SHOTGUN) + aimShotgun(); + else + aimPistols(); + } } void aimShotgun() { quat rot; Arm &arm = arms[0]; - arm.target = aim(arm.target, 14, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.25f, PI * 0.25f), rot, &arm.rotAbs) ? arm.target : -1; + arm.target = aim(arm.target, 14, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.25f, PI * 0.25f), rot, &arm.rotAbs) ? arm.target : NULL; } void aimPistols() { @@ -1072,7 +1074,7 @@ struct Lara : Character { arm.target = arms[i^1].target; if (!aim(arm.target, j, ranges[i], rot, &arm.rotAbs)) { rot = quat(0, 0, 0, 1); - arm.target = -1; + arm.target = NULL; } } @@ -1090,10 +1092,11 @@ struct Lara : Character { } void updateTargets() { - arms[0].target = arms[1].target = -1; + arms[0].target = arms[1].target = NULL; + viewTarget = NULL; if (emptyHands() || !wpnReady()) { - arms[0].tracking = arms[1].tracking = -1; + arms[0].tracking = arms[1].tracking = NULL; return; } @@ -1101,7 +1104,7 @@ struct Lara : Character { bool retarget = false; if (Core::settings.controls.retarget) { for (int i = 0; i < 2; i++) - if (arms[i].tracking == -1 || ((Character*)level->entities[arms[i].tracking].controller)->health <= 0.0f) { + if (!arms[i].tracking || ((Character*)arms[i].tracking)->health <= 0.0f) { retarget = true; break; } @@ -1111,15 +1114,15 @@ struct Lara : Character { if (!(input & ACTION) || retarget) { getTargets(arms[0].tracking, arms[1].tracking); if (count == 1) - arms[1].tracking = -1; - else if (arms[0].tracking == -1 && arms[1].tracking != -1) + arms[1].tracking = NULL; + else if (!arms[0].tracking && arms[1].tracking) arms[0].tracking = arms[1].tracking; - else if (arms[1].tracking == -1 && arms[0].tracking != -1) + else if (!arms[1].tracking && arms[0].tracking) arms[1].tracking = arms[0].tracking; arms[0].target = arms[0].tracking; arms[1].target = arms[1].tracking; } else { - if (arms[0].tracking == -1 && arms[1].tracking == -1) + if (!arms[0].tracking && !arms[1].tracking) return; // flip left and right by relative target direction @@ -1129,8 +1132,8 @@ struct Lara : Character { dir.y = 0.0f; for (int i = 0; i < count; i++) - if (arms[i].tracking != -1) { - vec3 v = ((Controller*)level->entities[arms[i].tracking].controller)->pos - pos; + if (arms[i].tracking) { + vec3 v = arms[i].tracking->pos - pos; v.y = 0; side[i] = sign(v.cross(dir).y); } @@ -1141,47 +1144,61 @@ struct Lara : Character { // check occlusion for tracking targets for (int i = 0; i < count; i++) - if (arms[i].tracking > -1) { - TR::Entity &e = level->entities[arms[i].tracking]; - Controller *enemy = (Controller*)e.controller; + if (arms[i].tracking) { + Controller *enemy = (Controller*)arms[i].tracking; Box box = enemy->getBoundingBox(); vec3 to = box.center(); to.y = box.min.y + (box.max.y - box.min.y) / 3.0f; vec3 from = pos - vec3(0, 650, 0); - arms[i].target = checkOcclusion(from, to, (to - from).length()) ? arms[i].tracking : -1; + arms[i].target = checkOcclusion(from, to, (to - from).length()) ? arms[i].tracking : NULL; } if (count == 1) - arms[1].target = -1; - else if (arms[0].target == -1 && arms[1].target != -1) + arms[1].target = NULL; + else if (!arms[0].target && arms[1].target) arms[0].target = arms[1].target; - else if (arms[1].target == -1 && arms[0].target != -1) + else if (!arms[1].target && arms[0].target) arms[1].target = arms[0].target; } + + if (arms[0].target && arms[1].target && arms[0].target != arms[1].target) { + viewTarget = NULL; //arms[0].target; + } else if (arms[0].target) + viewTarget = arms[0].target; + else if (arms[1].target) + viewTarget = arms[1].target; + else if (arms[0].tracking) + viewTarget = arms[0].tracking; + else if (arms[1].tracking) + viewTarget = arms[1].tracking; } - void getTargets(int &target1, int &target2) { + void getTargets(Controller *&target1, Controller *&target2) { vec3 dir = getDir().normal(); float dist[2] = { TARGET_MAX_DIST, TARGET_MAX_DIST }; - target1 = target2 = -1; + target1 = target2 = NULL; vec3 from = pos - vec3(0, 650, 0); - for (int i = 0; i < level->entitiesCount; i++) { - TR::Entity &e = level->entities[i]; - if (!e.flags.active || !e.isEnemy()) continue; - Character *enemy = (Character*)e.controller; - if (enemy->health <= 0) continue; + Controller *c = Controller::first; + do { + if (!level->entities[c->entity].isEnemy()) + continue; + + Character *enemy = (Character*)c; + if (enemy->health <= 0) + continue; Box box = enemy->getBoundingBox(); vec3 p = box.center(); p.y = box.min.y + (box.max.y - box.min.y) / 3.0f; vec3 v = p - pos; - if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees + if (dir.dot(v.normal()) <= 0.5f) + continue; // target is out of view range -60..+60 degrees float d = v.length(); @@ -1191,15 +1208,15 @@ struct Lara : Character { if (d < dist[0]) { target2 = target1; dist[1] = dist[0]; - target1 = i; + target1 = enemy; dist[0] = d; } else if (d < dist[1]) { - target2 = i; + target2 = enemy; dist[1] = d; } - } + } while ((c = c->next)); - if (target2 == -1 || dist[1] > dist[0] * 4) + if (!target2 || dist[1] > dist[0] * 4) target2 = target1; } @@ -1209,12 +1226,9 @@ struct Lara : Character { return ((d - from).length() > (dist - 512.0f)); } - bool checkHit(int target, const vec3 &from, const vec3 &to, vec3 &point) { - TR::Entity &e = level->entities[target]; - Controller *controller = (Controller*)e.controller; - - Box box = controller->getBoundingBoxLocal(); - mat4 m = controller->getMatrix(); + bool checkHit(Controller *target, const vec3 &from, const vec3 &to, vec3 &point) { + Box box = target->getBoundingBoxLocal(); + mat4 m = target->getMatrix(); float t; vec3 v = to - from; @@ -1223,7 +1237,7 @@ struct Lara : Character { v = v.normal(); Sphere spheres[34]; int count; - controller->getSpheres(spheres, count); + target->getSpheres(spheres, count); for (int i = 0; i < count; i++) if (spheres[i].intersect(from, v, t)) { point = from + v * t; @@ -1256,36 +1270,100 @@ struct Lara : Character { meshSwap(1, level->extra.weapons[wpnCurrent], mask); } - 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); + void doBubbles() { + int count = rand() % 3; + if (!count) return; + playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN); + vec3 head = animation.getJoints(getMatrix(), 14, true) * vec3(0.0f, 0.0f, 50.0f); + for (int i = 0; i < count; i++) { + int index = Sprite::add(game, TR::Entity::BUBBLE, getRoomIndex(), int(head.x), int(head.y), int(head.z), Sprite::FRAME_RANDOM, true); + if (index > -1) + level->entities[index].controller = new Bubble(game, index); } } - virtual void hit(float damage, Controller *enemy = NULL) { + virtual void cmdEffect(int fx) { + switch (fx) { + case TR::Effect::LARA_NORMAL : animation.setAnim(ANIM_STAND); break; + case TR::Effect::LARA_BUBBLES : doBubbles(); break; + case TR::Effect::LARA_HANDSFREE : break;//meshSwap(1, level->extra.weapons[wpnCurrent], BODY_LEG_L1 | BODY_LEG_R1); break; + case TR::Effect::DRAW_RIGHTGUN : drawGun(true); break; + default : LOG("unknown effect command %d (anim %d)\n", fx, animation.index); ASSERT(false); + } + } + + void addBlood(float radius, float height, const vec3 &spriteVelocity) { + vec3 p = pos + vec3((randf() * 2.0f - 1.0f) * radius, -randf() * height, (randf() * 2.0f - 1.0f) * radius); + int index = Sprite::add(game, TR::Entity::BLOOD, getRoomIndex(), int(p.x), int(p.y), int(p.z), Sprite::FRAME_ANIMATED); + if (index > -1) + ((Sprite*)level->entities[index].controller)->velocity = spriteVelocity; + } + + void addBloodSpikes() { + float ang = randf() * PI * 2.0f; + addBlood(64.0f, 512.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * 20.0f); + } + + void addBloodBlade() { + float ang = angle.y + (randf() - 0.5f) * 30.0f * DEG2RAD; + addBlood(64.0f, 744.0f, vec3(sinf(ang), 0.0f, cosf(ang)) * speed); + } + + + virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) { if (dozy) return; + if (health <= 0.0f) return; + damageTime = LARA_DAMAGE_TIME; - Character::hit(damage, enemy); - if (damage == 10000) { // T-Rex attack (fatal) - pos = enemy->pos; - angle = enemy->angle; + Character::hit(damage, enemy, hitType); - meshSwap(1, TR::MODEL_LARA_SPEC, BODY_UPPER | BODY_LOWER); - meshSwap(2, level->extra.weapons[Weapon::SHOTGUN], 0); - meshSwap(3, level->extra.weapons[Weapon::UZIS], 0); + if (hitType == TR::HIT_BLADE) + addBloodBlade(); - animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 1); + if (hitType == TR::HIT_SPIKES) + addBloodSpikes(); + + if (health > 0.0f) + return; + + switch (hitType) { + case TR::HIT_BOULDER : { + animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 2); + angle = enemy->angle; + TR::Level::FloorInfo info; + level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + vec3 d = getDir(); + vec3 v = info.getSlant(d); + angle.x = -acos(d.dot(v)); + v = ((TrapBoulder*)enemy)->velocity * 2.0f; + for (int i = 0; i < 15; i++) + addBlood(256.0f, 512.0f, v); + break; + } + case TR::HIT_SPIKES : { + pos.y = enemy->pos.y; + animation.setAnim(ANIM_DEATH_SPIKES); + for (int i = 0; i < 19; i++) + addBloodSpikes(); + break; + } + case TR::HIT_REX : { + pos = enemy->pos; + angle = enemy->angle; + + meshSwap(1, TR::MODEL_LARA_SPEC, BODY_UPPER | BODY_LOWER); + meshSwap(2, level->extra.weapons[Weapon::SHOTGUN], 0); + meshSwap(3, level->extra.weapons[Weapon::UZIS], 0); + + animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 1); + break; + } + default : ; } - if (health <= 0) - Core::lightColor[1 + 0] = Core::lightColor[1 + 1] = vec4(0, 0, 0, 1); + Core::lightColor[1 + 0] = Core::lightColor[1 + 1] = vec4(0, 0, 0, 1); }; bool useItem(TR::Entity::Type item) { @@ -1358,186 +1436,332 @@ struct Lara : Character { for (int i = 0; i < level->entitiesCount; i++) { TR::Entity &item = level->entities[i]; - if (item.room == room && !item.flags.invisible) { - if (!item.isItem()) - continue; - - Controller *controller = (Controller*)item.controller; - + if (!item.isItem()) + continue; + + Controller *controller = (Controller*)item.controller; + if (controller->getRoomIndex() == room && !item.flags.invisible) { if (stand == STAND_UNDERWATER) controller->angle.x = -25 * DEG2RAD; controller->angle.y = angle.y; + if (item.type == TR::Entity::SCION_1) + limit = TR::Limits::SCION; + if (!checkInteraction(controller, limit, (input & ACTION) != 0)) continue; - alignByItem(controller, limit, true, false); - if (stand == STAND_UNDERWATER) angle.x = -25 * DEG2RAD; pickupEntity = &item; + + if (item.type == TR::Entity::SCION_1) { + animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation); + ((Camera*)level->cameraController)->state = Camera::STATE_CUTSCENE; + level->cutMatrix.identity(); + level->cutMatrix.rotateY(angle.y); + level->cutMatrix.setPos(pos); + } else + state = STATE_PICK_UP; + return true; } } return false; } - void alignByItem(Controller *item, const TR::Limits::Limit &limit, bool dx, bool ay) { - if (ay) - angle = item->angle; - else - angle.x = angle.z = 0.0f; - - mat4 m = item->getMatrix(); - - float fx = 0.0f; - if (!dx) - fx = (m.transpose() * vec4(pos - item->pos, 0.0f)).x; - - pos = item->pos + (m * vec4(fx, limit.dy, limit.dz, 0.0f)).xyz; - velocity = vec3(0.0f); - speed = 0.0f; - updateEntity(); + 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) { + timer += Core::deltaTime; + if (timer > 3.0f) + game->loadLevel(TR::TITLE); + } else { + if (state != STATE_WATER_OUT) + return 0; + timer = 0.0f; + } + 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; - vec3 delta = (controller->getMatrix().transpose() * vec4(pos - controller->pos, 0.0f)).xyz; // inverse transform + mat4 m = controller->getMatrix(); + + float fx = 0.0f; + if (!limit.alignHoriz) + fx = (m.transpose() * vec4(pos - controller->pos, 0.0f)).x; + + vec3 targetPos = controller->pos + (m * vec4(fx, limit.dy, limit.dz, 0.0f)).xyz; + + vec3 deltaAbs = pos - targetPos; + vec3 deltaRel = (controller->getMatrix().transpose() * vec4(pos - controller->pos, 0.0f)).xyz; // inverse transform - return limit.box.contains(delta) && fabsf(shortAngle(angle.y, controller->angle.y)) <= limit.ay * DEG2RAD; + if (limit.box.contains(deltaRel)) { + float deltaAngY = shortAngle(angle.y, controller->angle.y); + + if (stand == STAND_UNDERWATER) { + float deltaAngX = shortAngle(angle.x, controller->angle.x); + + if (deltaAbs.length() > 64.0f || max(fabs(deltaAngX), fabs(deltaAngY)) > (10.0f * DEG2RAD)) { + pos -= deltaAbs.normal() * min(deltaAbs.length(), Core::deltaTime * 512.0f); + angle.x += sign(deltaAngX) * min(fabsf(deltaAngX), Core::deltaTime * (90.0f * DEG2RAD)); + angle.y += sign(deltaAngY) * min(fabsf(deltaAngY), Core::deltaTime * (90.0f * DEG2RAD)); + return false; + } + } + + if (fabsf(deltaAngY) <= limit.ay * DEG2RAD) { + // align + if (limit.alignAngle) + angle = controller->angle; + else + angle.x = angle.z = 0.0f; + + pos = targetPos; + velocity = vec3(0.0f); + speed = 0.0f; + + return true; + } + } + + return false; } void checkTrigger() { - if (actionCommand) return; - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; level->getFloorInfo(e.room, e.x, e.y, e.z, info); if (!info.trigCmdCount) return; // has no trigger - TR::FloorData::TriggerCommand &cmd = info.trigCmd[0]; - bool isActive = false; - switch (cmd.action) { - case TR::Action::SECRET : isActive = level->secrets[cmd.args]; break; - case TR::Action::ACTIVATE : isActive = level->entities[cmd.args].flags.active != 0; break; - default : isActive = false; - } - - if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated - TR::Limits::Limit *limit = NULL; - + bool switchIsDown = 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 : - if (isActive) return; - break; - case TR::Level::Trigger::PAD : - if (stand != STAND_GROUND || isActive) return; - break; - case TR::Level::Trigger::SWITCH : - actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; - if (!animation.canSetState(actionState)) + case TR::Level::Trigger::ACTIVATE : break; + + case TR::Level::Trigger::SWITCH : { + 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; + if (checkInteraction(controller, *limit, Input::state[cAction])) { + actionState = (controller->state == Switch::STATE_DOWN && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; + if (animation.setState(actionState)) + controller->activate(); + } + } + + if (!controller->setTimer(timer)) return; - limit = state == STATE_STOP ? &TR::Limits::SWITCH : &TR::Limits::SWITCH_UNDERWATER; - { - Trigger *controller = (Trigger*)level->entities[info.trigCmd[0].args].controller; - if (!controller->inState() || !checkInteraction(controller, *limit, isPressed(ACTION))) + + switchIsDown = controller->state == Switch::STATE_DOWN; + break; + } + + case TR::Level::Trigger::KEY : { + TR::Entity &entity = level->entities[info.trigCmd[cmdIndex++].args]; + KeyHole *controller = (KeyHole*)entity.controller; + + if (controller->activeState == asNone) { + if (entity.flags.active == TR::ACTIVE || state != STATE_STOP) return; + + actionState = entity.isPuzzleHole() ? STATE_USE_PUZZLE : STATE_USE_KEY; + if (!animation.canSetState(actionState)) + return; + + limit = actionState == STATE_USE_PUZZLE ? &TR::Limits::PUZZLE_HOLE : &TR::Limits::KEY_HOLE; + if (!checkInteraction(controller, *limit, isPressed(ACTION) || usedKey != TR::Entity::NONE)) + return; + + if (usedKey == TR::Entity::NONE) { + if (isPressed(ACTION) && !game->invChooseKey(entity.type)) + playSound(TR::SND_NO, pos, Sound::PAN); // no compatible items in inventory + return; + } + + if (TR::Entity::convToInv(TR::Entity::getItemForHole(entity.type)) != usedKey) { // check compatibility if user select other + playSound(TR::SND_NO, pos, Sound::PAN); // uncompatible item + return; + } + + keyHole = controller; + game->invUse(usedKey); + + animation.setState(actionState); } + + if (controller->activeState != asInactive) + return; + break; - case TR::Level::Trigger::KEY : - if (level->entities[info.trigCmd[0].args].flags.active || state != STATE_STOP) - return; - - actionState = level->entities[info.trigCmd[0].args].isKeyHole() ? STATE_USE_KEY : STATE_USE_PUZZLE; - if (!animation.canSetState(actionState)) - return; - - limit = actionState == STATE_USE_PUZZLE ? &TR::Limits::PUZZLE_HOLE : &TR::Limits::KEY_HOLE; - if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION) || usedKey != TR::Entity::NONE)) - return; - - if (!animation.canSetState(actionState)) - return; - - if (usedKey == TR::Entity::NONE) { - if (isPressed(ACTION) && !game->invChooseKey(level->entities[info.trigCmd[0].args].type)) - playSound(TR::SND_NO, pos, Sound::PAN); // no compatible items in inventory - return; - } - - if (TR::Entity::convToInv(TR::Entity::getKeyForHole(level->entities[info.trigCmd[0].args].type)) != usedKey) { // check compatibility if user select other - playSound(TR::SND_NO, pos, Sound::PAN); // uncompatible item - return; - } - - puzzleEntity = actionState == STATE_USE_PUZZLE ? &level->entities[info.trigCmd[0].args] : NULL; - game->invUse(usedKey); - break; + } case TR::Level::Trigger::PICKUP : - if (!isActive) // check if item is not picked up + if (!level->entities[info.trigCmd[cmdIndex++].args].flags.invisible) return; break; - default : - LOG("unsupported trigger type %d\n", info.trigger); + + case TR::Level::Trigger::COMBAT : + if (emptyHands()) + return; + break; + + case TR::Level::Trigger::PAD : + case TR::Level::Trigger::ANTIPAD : + if (e.y != info.floor) return; + break; + + case TR::Level::Trigger::HEAVY : + break; + case TR::Level::Trigger::DUMMY : return; } - // try to activate Lara state - if (!animation.setState(actionState)) return; + bool needFlip = false; + TR::Effect effect = TR::Effect::NONE; - if (info.trigger == TR::Level::Trigger::KEY) - level->entities[info.trigCmd[0].args].flags.active = true; + while (cmdIndex < info.trigCmdCount) { + TR::FloorData::TriggerCommand &cmd = info.trigCmd[cmdIndex++]; - if (limit) - alignByItem((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, stand != STAND_GROUND || info.trigger != TR::Level::Trigger::SWITCH, true); - - // build trigger activation chain - ActionCommand *actionItem = &actionList[1]; - - Controller *controller = this; - for (int i = 0; i < info.trigCmdCount; i++) { - if (!controller) { - LOG("! next activation entity %d has no controller\n", level->entities[info.trigCmd[i].args].type); - playSound(TR::SND_NO, pos, 0); - return; - } - - if (info.trigger == TR::Level::Trigger::KEY && i == 0) continue; // skip key ůę puzzle hole - - TR::FloorData::TriggerCommand &cmd = info.trigCmd[i]; switch (cmd.action) { - case TR::Action::CAMERA_SWITCH : - *actionItem = ActionCommand(entity, cmd.action, cmd.args, (float)info.trigCmd[++i].delay); // camera switch uses next command for delay timer - break; - default : - *actionItem = ActionCommand(entity, cmd.action, cmd.args, info.trigInfo.timer); - } + case TR::Action::ACTIVATE : { + TR::Entity &e = level->entities[cmd.args]; + TR::Entity::Flags &flags = e.flags; - actionItem->next = (i < info.trigCmdCount - 1) ? actionItem + 1 : NULL; - actionItem++; + if (flags.once) + break; + ((Controller*)e.controller)->timer = timer; + + 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 != TR::ACTIVE) + break; + + flags.once |= info.trigInfo.once; + + ((Controller*)e.controller)->activate(); + break; + } + case TR::Action::CAMERA_SWITCH : { + Camera *camera = (Camera*)level->cameraController; + + TR::FloorData::TriggerCommand &cam = info.trigCmd[cmdIndex++]; + if (level->cameras[cmd.args].flags.once) + break; + + 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 || 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::FLOW : + break; + case TR::Action::FLIP : { + TR::Flags &flip = level->flipmap[cmd.args]; + + if (flip.once) + break; + + if (info.trigger == TR::Level::Trigger::SWITCH) + flip.active ^= info.trigInfo.mask; + else + flip.active |= info.trigInfo.mask; + + if (flip.active == TR::ACTIVE) + flip.once |= info.trigInfo.once; + + if ((flip.active == TR::ACTIVE) ^ level->isFlipped) + needFlip = true; + + break; + } + case TR::Action::FLIP_ON : + if (level->flipmap[cmd.args].active == TR::ACTIVE && !level->isFlipped) + needFlip = true; + break; + case TR::Action::FLIP_OFF : + if (level->flipmap[cmd.args].active == TR::ACTIVE && level->isFlipped) + needFlip = true; + break; + case TR::Action::CAMERA_TARGET : + ((Camera*)level->cameraController)->viewTarget = (Controller*)level->entities[cmd.args].controller; + break; + case TR::Action::END : + game->loadLevel(level->id == TR::LEVEL_10C ? TR::TITLE : TR::LevelID(level->id + 1)); + break; + 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 == TR::ACTIVE) { + flags.once |= info.trigInfo.once; + game->playTrack(track); + } else + game->stopTrack(); + + break; + } + case TR::Action::EFFECT : + effect = TR::Effect(cmd.args); + break; + case TR::Action::SECRET : + if (!level->secrets[cmd.args]) { + level->secrets[cmd.args] = true; + if (!playSound(TR::SND_SECRET, pos, 0)) + game->playTrack(TR::TRACK_SECRET); + } + break; + } } - actionList[0].next = &actionList[1]; - actionCommand = &actionList[0]; - - if (info.trigger != TR::Level::Trigger::KEY) - activateNext(); - } - - vec3 getViewPoint() { - vec3 offset = chestOffset = animation.getJoints(getMatrix(), 7).pos; - if (stand != STAND_UNDERWATER) - offset.y -= 256.0f; - if (!emptyHands()) - offset.y -= 256.0f; - - return offset; + if (needFlip) { + level->isFlipped = !level->isFlipped; + game->setEffect(effect, 0); + } } virtual Stand getStand() { @@ -1553,7 +1777,7 @@ struct Lara : Character { return STAND_AIR; } - if (state == STATE_HANDSTAND || state == STATE_HANG_UP) + if (state == STATE_HANDSTAND || (state == STATE_HANG_UP && animation.index != ANIM_CLIMB_JUMP)) return STAND_HANG; if (stand == STAND_ONWATER && state != STATE_STOP) { @@ -1621,7 +1845,7 @@ struct Lara : Character { vec3 p = vec3(pos.x, bounds.min.y, pos.z); - Collision c = Collision(level, getRoomIndex(), p, getDir() * 32.0f, vec3(0.0f), LARA_RADIUS, angleExt, 0, 0, 0, 0); + Collision c = Collision(level, getRoomIndex(), p, getDir() * 128.0f, vec3(0.0f), LARA_RADIUS, angleExt, 0, 0, 0, 0); if (c.side != Collision::FRONT) return state; @@ -1637,6 +1861,7 @@ struct Lara : Character { updateEntity(); if (state == STATE_REACH) { + velocity = vec3(0.0f); vec3 p = pos + getDir() * 256.0f; TR::Level::FloorInfo info; level->getFloorInfo(getRoomIndex(), int(p.x), int(p.y), int(p.z), info); @@ -1647,14 +1872,14 @@ struct Lara : Character { } } - if (state == STATE_FORWARD_JUMP) { + if (state == STATE_FORWARD_JUMP || state == STATE_FALL_BACK) { if (emptyHands()) { if (input & ACTION) return STATE_REACH; if ((input & (JUMP | FORTH | WALK)) == (JUMP | FORTH | WALK)) return STATE_SWAN_DIVE; } } else if (state != STATE_FALL && state != STATE_FALL_BACK && state != STATE_SWAN_DIVE && state != STATE_FAST_DIVE && state != STATE_REACH && state != STATE_UP_JUMP && state != STATE_BACK_JUMP && state != STATE_LEFT_JUMP && state != STATE_RIGHT_JUMP) - return animation.setAnim( (state == STATE_FAST_BACK || state == STATE_SLIDE_BACK || state == STATE_ROLL_2) ? ANIM_FALL_BACK : ANIM_FALL_FORTH); + return animation.setAnim(ANIM_FALL_FORTH);// (state == STATE_FAST_BACK || state == STATE_SLIDE_BACK || state == STATE_ROLL_2) ? ANIM_FALL_BACK : ANIM_FALL_FORTH); if (state == STATE_SWAN_DIVE) return STATE_FAST_DIVE; @@ -1695,8 +1920,6 @@ struct Lara : Character { continue; } - alignByItem(block, TR::Limits::BLOCK, false, true); - return block; } return NULL; @@ -1707,7 +1930,7 @@ struct Lara : Character { angle.x = 0.0f; if ((state == STATE_STOP || state == STATE_TREAD) && (input & ACTION) && emptyHands() && doPickUp()) - return STATE_PICK_UP; + return state; if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands() && collision.side == Collision::FRONT) { // TODO: get rid of animation.index int floor = collision.info[Collision::FRONT].floor; @@ -1892,7 +2115,7 @@ struct Lara : Character { virtual int getStateUnderwater() { if (input == ACTION && doPickUp()) - return STATE_PICK_UP; + return state; 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); @@ -2051,11 +2274,11 @@ struct Lara : Character { } break; } + case STATE_USE_KEY : case STATE_USE_PUZZLE : { - if (puzzleEntity && animation.isFrameActive(PUZZLE_FRAME)) { - int doneIdx = TR::Entity::convToInv(TR::Entity::getKeyForHole(puzzleEntity->type)) - TR::Entity::INV_PUZZLE_1; - ((Controller*)puzzleEntity->controller)->meshSwap(0, level->extra.puzzleDone[doneIdx]); - puzzleEntity = NULL; + if (keyHole && animation.isFrameActive(state == STATE_USE_PUZZLE ? PUZZLE_FRAME : KEY_FRAME)) { + keyHole->activate(); + keyHole = NULL; } break; } @@ -2064,6 +2287,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); @@ -2091,7 +2317,11 @@ struct Lara : Character { } virtual void updateVelocity() { - checkTrigger(); + if (getEntity().type != TR::Entity::LARA) + return; + + if (!(input & DEATH)) + checkTrigger(); // get turning angle float w = (input & LEFT) ? -1.0f : ((input & RIGHT) ? 1.0f : 0.0f); @@ -2202,6 +2432,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) @@ -2255,12 +2487,12 @@ struct Lara : Character { } // check enemies & doors - for (int i = 0; i < level->entitiesCount; i++) { - TR::Entity &e = level->entities[i]; - Controller *controller = (Controller*)e.controller; + Controller *controller = Controller::first; + do { + TR::Entity &e = controller->getEntity(); if (e.isEnemy()) { - if (e.type != TR::Entity::ENEMY_REX && (!e.flags.active || ((Character*)e.controller)->health <= 0)) continue; + if (e.type != TR::Entity::ENEMY_REX && (!e.flags.active || ((Character*)controller)->health <= 0)) continue; } else if (!e.isDoor()) continue; @@ -2281,7 +2513,7 @@ struct Lara : Character { collisionOffset += vec3(p.x, 0.0f, p.z); } - if (e.type == TR::Entity::ENEMY_REX && ((Character*)e.controller)->health <= 0) + if (e.type == TR::Entity::ENEMY_REX && ((Character*)controller)->health <= 0) return true; if (e.isDoor()) return true; @@ -2295,7 +2527,7 @@ struct Lara : Character { hitDir = angleQuadrant(dir.rotateY(angle.y + PI * 0.5f).angleY()); return true; - } + } while ((controller = controller->next)); hitDir = -1; return false; @@ -2387,7 +2619,7 @@ struct Lara : Character { // hit the wall switch (stand) { case STAND_AIR : - if (state == STATE_UP_JUMP || state == STATE_REACH) + if (state == STATE_UP_JUMP || state == STATE_REACH || state == STATE_FALL_BACK) velocity.x = velocity.z = 0.0f; if (velocity.x != 0.0f || velocity.z != 0.0f) { diff --git a/src/level.h b/src/level.h index 8ed8c19..02f519b 100644 --- a/src/level.h +++ b/src/level.h @@ -15,6 +15,9 @@ #include "debug.h" #endif +extern ShaderCache *shaderCache; +extern void loadAsync(Stream *stream, void *userData); + struct Level : IGame { TR::Level level; Inventory inventory; @@ -33,7 +36,6 @@ struct Level : IGame { float clipHeight; } *params = (Params*)&Core::params; - ShaderCache *shaderCache; AmbientCache *ambientCache; WaterCache *waterCache; ZoneCache *zoneCache; @@ -42,19 +44,50 @@ struct Level : IGame { Sound::Sample *sndUnderwater; Sound::Sample *sndCurrent; + int curTrack; bool lastTitle; + bool isEnded; + + TR::Effect effect; + float effectTimer; + int flickerIdx; // IGame implementation ======== + virtual void loadLevel(TR::LevelID id) { + if (isEnded) return; + Sound::stopAll(); + + isEnded = true; + char buf[64]; + buf[0] = 0; + strcat(buf, "level/"); + strcat(buf, TR::LEVEL_INFO[id].name); + switch (level.version) { + case TR::VER_TR1_PC : strcat(buf, ".PHD"); break; + case TR::VER_TR1_PSX : strcat(buf, ".PSX"); break; + } + new Stream(buf, loadAsync); + } + virtual TR::Level* getLevel() { return &level; } - virtual MeshBuilder* getMesh() { + virtual MeshBuilder* getMesh() { return mesh; } - virtual Controller* getCamera() { - return camera; + virtual ICamera* getCamera() { + return camera; + } + + virtual Controller* getLara() { + 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) { @@ -85,7 +118,7 @@ struct Level : IGame { } virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) { - shaderCache->bind(pass, type, (underwater ? ShaderCache::FX_UNDERWATER : 0) | (alphaTest ? ShaderCache::FX_ALPHA_TEST : 0) | ((params->clipHeight != NO_CLIP_PLANE && pass == Core::passCompose) ? ShaderCache::FX_CLIP_PLANE : 0)); + shaderCache->bind(pass, type, (underwater ? ShaderCache::FX_UNDERWATER : 0) | (alphaTest ? ShaderCache::FX_ALPHA_TEST : 0) | ((params->clipHeight != NO_CLIP_PLANE && pass == Core::passCompose) ? ShaderCache::FX_CLIP_PLANE : 0), this); } virtual void setupBinding() { @@ -97,7 +130,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; @@ -113,8 +145,26 @@ struct Level : IGame { } } - virtual void fxQuake(float time) { - camera->shake = time; + virtual void setEffect(TR::Effect effect, float param) { + if (effect == TR::Effect::NONE) + return; + + if (effect == TR::Effect::FLOOR_SHAKE) { + camera->shake = param; + return; + } + + if (effect == TR::Effect::FLICKER) + flickerIdx = 0; + + if (effect == TR::Effect::FLOOD) { + Sound::Sample *sample = playSound(TR::SND_FLOOD, vec3(), 0); + if (sample) + sample->setVolume(0.0f, 4.0f); + } + + this->effect = effect; + this->effectTimer = 0.0f; } virtual bool invUse(TR::Entity::Type type) { @@ -136,7 +186,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]; @@ -156,18 +206,68 @@ struct Level : IGame { } return NULL; } + + void stopChannel(Sound::Sample *channel) { + if (channel == sndSoundtrack) { + if (sndCurrent == sndSoundtrack) + sndCurrent = NULL; + sndSoundtrack = NULL; + playTrack(0); + } + } + + static void playAsync(Stream *stream, void *userData) { + if (!stream) return; + Level *level = (Level*)userData; + + level->sndSoundtrack = Sound::play(stream, vec3(0.0f), 0.01f, 1.0f, Sound::MUSIC); + if (level->sndSoundtrack) + level->sndSoundtrack->setVolume(1.0f, 0.2f); + } + + 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); + + new Stream(title, playAsync, this); + } + + 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), isEnded(false) { params->time = 0.0f; #ifdef _DEBUG Debug::init(); #endif - mesh = new MeshBuilder(level); initTextures(); - shaderCache = new ShaderCache(this); + mesh = new MeshBuilder(level); + initOverrides(); for (int i = 0; i < level.entitiesBaseCount; i++) { @@ -175,7 +275,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); @@ -231,7 +331,7 @@ struct Level : IGame { case TR::Entity::GEARS_1 : case TR::Entity::GEARS_2 : case TR::Entity::GEARS_3 : - entity.controller = new Boulder(this, i); + entity.controller = new Gear(this, i); break; case TR::Entity::TRAP_FLOOR : entity.controller = new TrapFloor(this, i); @@ -240,14 +340,16 @@ struct Level : IGame { entity.controller = new Crystal(this, i); break; case TR::Entity::TRAP_BLADE : + entity.controller = new TrapBlade(this, i); + break; case TR::Entity::TRAP_SPIKES : - entity.controller = new Trigger(this, i, true); + entity.controller = new TrapSpikes(this, i); break; case TR::Entity::TRAP_BOULDER : - entity.controller = new Boulder(this, i); + entity.controller = new TrapBoulder(this, i); break; case TR::Entity::TRAP_DARTGUN : - entity.controller = new Dartgun(this, i); + entity.controller = new TrapDartgun(this, i); break; case TR::Entity::BLOCK_1 : case TR::Entity::BLOCK_2 : @@ -258,13 +360,17 @@ struct Level : IGame { case TR::Entity::MOVING_BLOCK : entity.controller = new MovingBlock(this, i); break; - case TR::Entity::FALLING_CEILING_1 : - case TR::Entity::FALLING_CEILING_2 : - case TR::Entity::FALLING_SWORD : - entity.controller = new Trigger(this, i, true); + case TR::Entity::TRAP_CEILING_1 : + case TR::Entity::TRAP_CEILING_2 : + entity.controller = new TrapCeiling(this, i); + break; + case TR::Entity::TRAP_SWORD : + entity.controller = new TrapSword(this, i); break; case TR::Entity::SWITCH : case TR::Entity::SWITCH_WATER : + entity.controller = new Switch(this, i); + break; case TR::Entity::PUZZLE_HOLE_1 : case TR::Entity::PUZZLE_HOLE_2 : case TR::Entity::PUZZLE_HOLE_3 : @@ -273,7 +379,7 @@ struct Level : IGame { case TR::Entity::KEY_HOLE_2 : case TR::Entity::KEY_HOLE_3 : case TR::Entity::KEY_HOLE_4 : - entity.controller = new Trigger(this, i, false); + entity.controller = new KeyHole(this, i); break; case TR::Entity::WATERFALL : entity.controller = new Waterfall(this, i); @@ -286,13 +392,12 @@ struct Level : IGame { } } - lastTitle = false; - - if (!isTitle()) { + if (level.id != TR::TITLE) { ASSERT(lara != NULL); camera = new Camera(this, lara); level.cameraController = camera; + level.laraController = lara; ambientCache = Core::settings.detail.ambient ? new AmbientCache(this) : NULL; waterCache = Core::settings.detail.water ? new WaterCache(this) : NULL; @@ -301,20 +406,19 @@ struct Level : IGame { initReflections(); - // init sounds - //Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0); - sndSoundtrack = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP); + // init sounds + //sndSoundtrack = Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), vec3(0.0f), 1, 1, Sound::Flags::LOOP); - sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP); + sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP | Sound::MUSIC); if (sndUnderwater) - sndUnderwater->volume = 0.0f; - - sndCurrent = sndSoundtrack; + sndUnderwater->volume = sndUnderwater->volumeTarget = 0.0f; 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); } + + lastTitle = false; } else { camera = NULL; ambientCache = NULL; @@ -324,7 +428,15 @@ struct Level : IGame { sndSoundtrack = NULL; sndUnderwater = NULL; sndCurrent = NULL; + lastTitle = true; + inventory.toggle(Inventory::PAGE_OPTION); } + + sndSoundtrack = NULL; + playTrack(curTrack = 0); + sndCurrent = sndSoundtrack; + + effect = TR::Effect::NONE; } virtual ~Level() { @@ -334,8 +446,6 @@ struct Level : IGame { for (int i = 0; i < level.entitiesCount; i++) delete (Controller*)level.entities[i].controller; - delete shaderCache; - delete shadow; delete ambientCache; delete waterCache; @@ -349,8 +459,126 @@ 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 barColor[UI::BAR_MAX][25] = { + // health bar + { 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F }, + // oxygen bar + { 0xFF647464, 0xFFA47848, 0xFF647464, 0xFF4C504C, 0xFF303030 }, + // option bar + { 0x00FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x00FFFFFF, + 0x00FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x00FFFFFF, + 0x00FFFFFF, 0x80FFFFFF, 0x80FFFFFF, 0x80FFFFFF, 0x00FFFFFF, + 0x00FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x00FFFFFF, + 0x00FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x00FFFFFF }, + }; + + int stride = 256, uvCount; + short2 *uv = NULL; + + TR::Level *level = (TR::Level*)userData; + TR::Color32 *src, *dst = (TR::Color32*)data; + short4 mm; + + if (id < level->objectTexturesCount) { // textures + TR::ObjectTexture &t = level->objectTextures[id]; + mm = t.getMinMax(); + src = level->tiles[t.tile.index].color; + uv = t.texCoord; + uvCount = 4; + } else { + id -= level->objectTexturesCount; + + if (id < level->spriteTexturesCount) { // sprites + TR::SpriteTexture &t = level->spriteTextures[id]; + mm = t.getMinMax(); + src = level->tiles[t.tile].color; + uv = t.texCoord; + uvCount = 2; + } else { // common (generated) textures + id -= level->spriteTexturesCount; + TR::ObjectTexture *tex; + mm.x = mm.y = mm.z = mm.w = 0; + stride = 1; + uvCount = 4; + + switch (id) { + case UI::BAR_HEALTH : + case UI::BAR_OXYGEN : + case UI::BAR_OPTION : + src = (TR::Color32*)&barColor[id][0]; + tex = &barTile[id]; + mm.w = 4; // height - 1 + if (id == UI::BAR_OPTION) { + stride = 5; + mm.z = 4; + } + break; + case 3 : // white color + src = (TR::Color32*)&whiteColor; + tex = &whiteTile; + break; + default : return; + } + + memset(tex, 0, sizeof(*tex)); + uv = tex->texCoord; + uv[2].y += mm.w; + uv[3].y += mm.w; + uv[1].x += mm.z; + uv[2].x += mm.z; + } + } + + int cx = -mm.x, cy = -mm.y; + + if (data) { + int w = mm.z - mm.x + 1; + int h = mm.w - mm.y + 1; + int dstIndex = tileY * width + tileX; + for (int y = -ATLAS_BORDER; y < h + ATLAS_BORDER; y++) { + for (int x = -ATLAS_BORDER; x < w + ATLAS_BORDER; x++) { + TR::Color32 *p = &src[mm.y * stride + mm.x]; + p += clamp(x, 0, w - 1); + p += clamp(y, 0, h - 1) * stride; + dst[dstIndex++] = *p; + } + dstIndex += width - ATLAS_BORDER * 2 - w; + } + + cx += tileX + ATLAS_BORDER; + cy += tileY + ATLAS_BORDER; + } + + for (int i = 0; i < uvCount; i++) { + if (uv[i].x == mm.z) uv[i].x++; + if (uv[i].y == mm.w) uv[i].y++; + uv[i].x += cx; + uv[i].y += cy; + + uv[i].x = int32(uv[i].x) * 32767 / width; + uv[i].y = int32(uv[i].y) * 32767 / height; + } + + // apply ref for instanced tile + if (data) return; + + int ref = tileX; + if (ref < level->objectTexturesCount) { // textures + mm = level->objectTextures[ref].getMinMax(); + } else { + ref -= level->objectTexturesCount; + if (ref < level->spriteTexturesCount) // sprites + mm = level->spriteTextures[ref].getMinMax(); + else + ASSERT(false); // only object textures and sprites may be instanced + } + + for (int i = 0; i < uvCount; i++) { + uv[i].x += mm.x; + uv[i].y += mm.y; + } } void initTextures() { @@ -359,62 +587,56 @@ struct Level : IGame { return; } - // merge all tiles into one 1024x1024 32bpp - TR::Color32 *data = new TR::Color32[1024 * 1024]; - for (int i = 0; i < level.tilesCount; i++) { - int tx = (i % 4) * 256; - int ty = (i / 4) * 256; + // repack texture tiles + Atlas *tiles = new Atlas(level.objectTexturesCount + level.spriteTexturesCount + 4, &level, fillCallback); + // add textures + int texIdx = level.version == TR::VER_TR1_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; + int16 ty = (t.tile.index / 4) * 256; - TR::Color32 *ptr = &data[ty * 1024 + tx]; - for (int y = 0; y < 256; y++) { - memcpy(ptr, &level.tiles[i].color[y * 256], 256 * sizeof(TR::Color32)); - ptr += 1024; - } + short4 uv; + uv.x = tx + min(min(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x); + uv.y = ty + min(min(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y); + uv.z = tx + max(max(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x) + 1; + uv.w = ty + max(max(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y) + 1; + + tiles->add(uv, texIdx++); } + // add sprites + for (int i = 0; i < level.spriteTexturesCount; i++) { + TR::SpriteTexture &t = level.spriteTextures[i]; + int16 tx = (t.tile % 4) * 256; + int16 ty = (t.tile / 4) * 256; - // white texture - for (int y = 1020; y < 1024; y++) - for (int x = 1020; x < 1024; x++) { - int i = y * 1024 + x; - data[i].r = data[i].g = data[i].b = data[i].a = 255; // white texel for colored triangles - } + short4 uv; + uv.x = tx + t.texCoord[0].x; + uv.y = ty + t.texCoord[0].y; + uv.z = tx + t.texCoord[1].x + 1; + uv.w = ty + t.texCoord[1].y + 1; -// uint32 healthBar[1+5+1] = { 0xFF2C5C70, 0xFF2C5C70, 0xFF4878A4, 0xFF2C5C70, 0xFF004458, 0xFF143050, 0xFF143050 }; - uint32 healthBar[1+5+1] = { 0xFF2C5D71, 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F, 0xFF16304F }; + tiles->add(uv, texIdx++); + } + // add health bar + tiles->add(short4(2048, 2048, 2048, 2048 + 4), texIdx++); + // add oxygen bar + tiles->add(short4(4096, 4096, 4096, 4096 + 4), texIdx++); + // add option bar + tiles->add(short4(8192, 8192, 8192 + 4, 8192 + 4), texIdx++); + // add white color + tiles->add(short4(2048, 2048, 2048, 2048), texIdx++); + // get result texture + atlas = tiles->pack(); - for (int y = 0; y < COUNT(healthBar); y++) - for (int x = 0; x < 2; x++) { - int i = (TEX_HEALTH_BAR_Y + y) * 1024 + (TEX_HEALTH_BAR_X + x); - *((uint32*)&data[i]) = healthBar[y]; - } + delete tiles; - uint32 oxygenBar[1+5+1] = { 0xFF647464, 0xFF647464, 0xFFA47848, 0xFF647464, 0xFF4C504C, 0xFF303030, 0xFF303030 }; - for (int y = 0; y < COUNT(oxygenBar); y++) - for (int x = 0; x < 2; x++) { - int i = (TEX_OXYGEN_BAR_Y + y) * 1024 + (TEX_OXYGEN_BAR_X + x); - *((uint32*)&data[i]) = oxygenBar[y]; - } - - - /* - FILE *f = fopen("atlas.raw", "wb"); - fwrite(data, 1024 * 1024 * 4, 1, f); - fclose(f); - */ - /* - memset(data, 255, 4 * 1024 * 1024); - for (int i = 0; i < 1024; i++) - for (int j = 0; j < 1024; j++) - data[i * 1024 + j].b = data[i * 1024 + j].g = ((i % 8 == 0) || (j % 8 == 0)) ? 0 : 255; - */ - - atlas = new Texture(1024, 1024, Texture::RGBA, false, data, true, false); // TODO: generate mips + LOG("atlas: %d x %d\n", atlas->width, atlas->height); PROFILE_LABEL(TEXTURE, atlas->ID, "atlas"); uint32 whitePix = 0xFFFFFFFF; cube = new Texture(1, 1, Texture::RGBA, true, &whitePix); - delete[] data; delete[] level.tiles; level.tiles = NULL; } @@ -584,7 +806,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); @@ -592,8 +814,10 @@ struct Level : IGame { Controller *controller = (Controller*)entity.controller; - TR::Room &room = level.rooms[entity.room]; - if (entity.type != TR::Entity::LARA) // TODO: remove this hack (collect conjugate room entities) + int roomIndex = controller->getRoomIndex(); + TR::Room &room = level.rooms[roomIndex]; + + if (!entity.isLara() && !entity.isActor()) if (!room.flags.visible || entity.flags.invisible || entity.flags.rendered) return; @@ -603,16 +827,18 @@ struct Level : IGame { if (entity.type == TR::Entity::CRYSTAL) type = Shader::MIRROR; - int roomIndex = entity.room; - - setRoomParams(roomIndex, type, 1.0f, intensityf(lum), controller->specular, 1.0f, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true); + if (type == Shader::SPRITE) { + float alpha = (entity.type == TR::Entity::SMOKE || entity.type == TR::Entity::WATER_SPLASH) ? 0.75f : 1.0f; + setRoomParams(roomIndex, type, 0.5f, intensityf(lum), controller->specular, alpha, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true); + } else + setRoomParams(roomIndex, type, 1.0f, intensityf(lum), controller->specular, 1.0f, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true); if (isModel) { // model vec3 pos = controller->getPos(); if (Core::settings.detail.ambient) { AmbientCache::Cube cube; if (Core::stats.frame != controller->frameIndex) { - ambientCache->getAmbient(entity.room, pos, cube); + ambientCache->getAmbient(roomIndex, pos, cube); if (cube.status == AmbientCache::Cube::READY) memcpy(controller->ambient, cube.colors, sizeof(cube.colors)); // store last calculated ambient into controller } @@ -632,46 +858,73 @@ 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 - if (isTitle()) { - sndCurrent->volume = 0.0f; + if (isCutscene() && !sndSoundtrack) + return; + + if (Input::state[cInventory] && level.id != TR::TITLE) + inventory.toggle(); + + Sound::Sample *sndChanged = sndCurrent; + + if (inventory.isActive() || level.id == TR::TITLE) { Sound::reverb.setRoomSize(vec3(1.0f)); inventory.update(); - return; - } + if (level.id != TR::TITLE) + sndChanged = NULL; + } else { + params->time += Core::deltaTime; - params->time += Core::deltaTime; + updateEffect(); - for (int i = 0; i < level.entitiesCount; i++) { - TR::Entity &e = level.entities[i]; - if (e.type != TR::Entity::NONE) { - Controller *controller = (Controller*)e.controller; - if (controller) { - controller->update(); - - if (waterCache && e.type == TR::Entity::WATERFALL && ((Waterfall*)controller)->drop) { // add water drops for waterfalls - Waterfall *w = (Waterfall*)controller; - waterCache->addDrop(w->dropPos, w->dropRadius, w->dropStrength); - } - } + Controller *c = Controller::first; + while (c) { + Controller *next = c->next; + c->update(); + c = next; } + + if (!isCutscene() && camera->state != Camera::STATE_STATIC) + camera->state = lara->emptyHands() ? Camera::STATE_FOLLOW : Camera::STATE_COMBAT; + + camera->update(); + + if (waterCache) + waterCache->update(); + + Controller::clearInactive(); + + sndChanged = camera->isUnderwater() ? sndUnderwater : sndSoundtrack; } - camera->update(); + if (sndChanged != sndCurrent) { + if (sndCurrent) sndCurrent->setVolume(0.0f, 0.2f); + if (sndChanged) sndChanged->setVolume(1.0f, 0.2f); + sndCurrent = sndChanged; + } + } - sndCurrent = camera->isUnderwater() ? sndUnderwater : sndSoundtrack; + void updateEffect() { + if (effect == TR::Effect::NONE) + return; - if (sndSoundtrack && sndCurrent != sndSoundtrack) sndSoundtrack->volume = 0.0f; - if (sndUnderwater && sndCurrent != sndUnderwater) sndUnderwater->volume = 0.0f; - if (sndCurrent) sndCurrent->volume = 1.0f; + effectTimer += Core::deltaTime; - if (waterCache) - waterCache->update(); + switch (effect) { + case TR::Effect::FLICKER : { + int idx = flickerIdx; + switch (flickerIdx) { + case 0 : if (effectTimer > 3.0f) flickerIdx++; break; + case 1 : if (effectTimer > 3.1f) flickerIdx++; break; + case 2 : if (effectTimer > 3.5f) flickerIdx++; break; + case 3 : if (effectTimer > 3.6f) flickerIdx++; break; + case 4 : if (effectTimer > 4.1f) { flickerIdx++; effect = TR::Effect::NONE; } break; + } + if (idx != flickerIdx) + level.isFlipped = !level.isFlipped; + break; + } + default : return; + } } void setup() { @@ -689,7 +942,7 @@ struct Level : IGame { for (int i = 0; i < level.entitiesCount; i++) { TR::Entity &entity = level.entities[i]; - if (entity.flags.rendered) + if (entity.controller && entity.flags.rendered) ((Controller*)entity.controller)->renderShadow(mesh); } } @@ -769,17 +1022,13 @@ 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; + if (count > 16) { + //ASSERT(false); return; } - if (count > 16) { - ASSERT(false); - return; - } + if (level.rooms[to].alternateRoom > -1 && level.isFlipped) + to = level.rooms[to].alternateRoom; TR::Room &room = level.rooms[to]; @@ -795,10 +1044,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); } } @@ -847,6 +1094,7 @@ struct Level : IGame { if (water && waterCache && waterCache->visible) { waterCache->renderMask(); waterCache->getRefract(); + setMainLight(lara); waterCache->render(); } } @@ -906,11 +1154,19 @@ 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); camera->setup(true); + + if (Input::down[ikF]) { + level.isFlipped = !level.isFlipped; + Input::down[ikF] = false; + } + /* static int snd_index = 0; if (Input::down[ikG]) { snd_index = (snd_index + 1) % level.soundsInfoCount; @@ -918,7 +1174,7 @@ struct Level : IGame { lara->playSound(snd_index, lara->pos, 0); Input::down[ikG] = false; } - /* + static int modelIndex = 0; static bool lastStateK = false; static int lastEntity = -1; @@ -1041,9 +1297,88 @@ struct Level : IGame { // Debug::Level::blocks(level); // Debug::Level::path(level, (Enemy*)level.entities[86].controller); // Debug::Level::debugOverlaps(level, lara->box); - // Core::setBlending(bmNone); - + + /* + static int dbg_ambient = 0; + dbg_ambient = int(params->time * 2) % 4; + + shadow->unbind(sShadow); + cube->unbind(sEnvironment); + + glActiveTexture(GL_TEXTURE0); + glEnable(GL_TEXTURE_2D); + glDisable(GL_CULL_FACE); + glColor3f(1, 1, 1); + for (int j = 0; j < 6; j++) { + glPushMatrix(); + glTranslatef(lara->pos.x, lara->pos.y - 1024, lara->pos.z); + switch (j) { + case 0 : glRotatef( 90, 0, 1, 0); break; + case 1 : glRotatef(-90, 0, 1, 0); break; + case 2 : glRotatef(-90, 1, 0, 0); break; + case 3 : glRotatef( 90, 1, 0, 0); break; + case 4 : glRotatef( 0, 0, 1, 0); break; + case 5 : glRotatef(180, 0, 1, 0); break; + } + glTranslatef(0, 0, 256); + + ambientCache->textures[j * 4 + dbg_ambient]->bind(sDiffuse); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex3f(-256, 256, 0); + glTexCoord2f(1, 0); glVertex3f( 256, 256, 0); + glTexCoord2f(1, 1); glVertex3f( 256, -256, 0); + glTexCoord2f(0, 1); glVertex3f(-256, -256, 0); + glEnd(); + glPopMatrix(); + } + glEnable(GL_CULL_FACE); + glDisable(GL_TEXTURE_2D); + + + glLineWidth(4); + glBegin(GL_LINES); + float S = 64.0f; + for (int i = 0; i < level.roomsCount; i++) { + TR::Room &r = level.rooms[i]; + for (int j = 0; j < r.xSectors * r.zSectors; j++) { + TR::Room::Sector &s = r.sectors[j]; + vec3 p = vec3(float((j / r.zSectors) * 1024 + 512 + r.info.x), + float(max((s.floor - 2) * 256, (s.floor + s.ceiling) * 256 / 2)), + float((j % r.zSectors) * 1024 + 512 + r.info.z)); + + AmbientCache::Cube &cube = ambientCache->items[ambientCache->offsets[i] + j]; + if (cube.status == AmbientCache::Cube::READY) { + glColor3fv((GLfloat*)&cube.colors[0]); + glVertex3f(p.x + 0, p.y + 0, p.z + 0); + glVertex3f(p.x + S, p.y + 0, p.z + 0); + + glColor3fv((GLfloat*)&cube.colors[1]); + glVertex3f(p.x + 0, p.y + 0, p.z + 0); + glVertex3f(p.x - S, p.y + 0, p.z + 0); + + glColor3fv((GLfloat*)&cube.colors[2]); + glVertex3f(p.x + 0, p.y + 0, p.z + 0); + glVertex3f(p.x + 0, p.y + S, p.z + 0); + + glColor3fv((GLfloat*)&cube.colors[3]); + glVertex3f(p.x + 0, p.y + 0, p.z + 0); + glVertex3f(p.x + 0, p.y - S, p.z + 0); + + glColor3fv((GLfloat*)&cube.colors[4]); + glVertex3f(p.x + 0, p.y + 0, p.z + 0); + glVertex3f(p.x + 0, p.y + 0, p.z + S); + + glColor3fv((GLfloat*)&cube.colors[5]); + glVertex3f(p.x + 0, p.y + 0, p.z + 0); + glVertex3f(p.x + 0, p.y + 0, p.z - S); + } + } + } + glEnd(); + glLineWidth(1); + */ + Debug::Level::info(level, lara->getEntity(), lara->animation); @@ -1058,7 +1393,7 @@ struct Level : IGame { params->waterHeight = params->clipHeight; if (ambientCache) - ambientCache->precessQueue(); + ambientCache->processQueue(); if (shadow) renderShadows(lara->getRoomIndex()); @@ -1075,42 +1410,56 @@ 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(UI::BAR_HEALTH, 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(UI::BAR_OXYGEN, 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; + lastTitle = title; + + if (isEnded) { + Core::setTarget(NULL, true); + UI::begin(); + UI::textOut(vec2(0, 480 - 16), STR_LOADING, UI::aCenter, UI::width); + UI::end(); + return; + } if (copyBg) { Core::defaultTarget = inventory.background[0]; @@ -1127,8 +1476,6 @@ struct Level : IGame { renderInventory(); renderUI(); - - lastTitle = title; } }; diff --git a/src/mesh.h b/src/mesh.h index 14fadc2..123c112 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -4,11 +4,8 @@ #include "core.h" #include "format.h" -#define TEX_HEALTH_BAR_X 1000 -#define TEX_HEALTH_BAR_Y 1000 -#define TEX_OXYGEN_BAR_X 1002 -#define TEX_OXYGEN_BAR_Y 1000 +TR::ObjectTexture whiteTile, barTile[3]; struct MeshRange { int iStart; @@ -16,7 +13,7 @@ struct MeshRange { int vStart; int aIndex; - MeshRange() : aIndex(-1) {} + MeshRange() : iStart(0), iCount(0), vStart(0), aIndex(-1) {} void setup() const { glEnableVertexAttribArray(aCoord); @@ -179,7 +176,6 @@ struct MeshBuilder { int animTexOffsetsCount; TR::Level *level; - TR::ObjectTexture whiteTile; MeshBuilder(TR::Level &level) : level(&level) { dynMesh = new Mesh(NULL, DYN_MESH_QUADS * 6, NULL, DYN_MESH_QUADS * 4, 1); @@ -189,15 +185,6 @@ struct MeshBuilder { initAnimTextures(level); - // create dummy white object textures for non-textured (colored) geometry - whiteTile.attribute = 0; - whiteTile.tile.index = 15; - whiteTile.tile.triangle = 0; - whiteTile.texCoord[0] = - whiteTile.texCoord[1] = - whiteTile.texCoord[2] = - whiteTile.texCoord[3] = { 253, 253 }; - // allocate room geometry ranges rooms = new RoomRange[level.roomsCount]; @@ -224,14 +211,8 @@ struct MeshBuilder { vCount += mesh.rCount * 4 + mesh.tCount * 3; } - RoomRange &range = rooms[i]; - range.sprites.vStart = vCount; - range.sprites.iStart = iCount; iCount += d.sCount * 6; vCount += d.sCount * 4; - range.sprites.iCount = iCount - range.sprites.iStart; - - ASSERT(vCount - range.sprites.vStart < 0xFFFF); } // get models info @@ -251,39 +232,24 @@ struct MeshBuilder { // get size of mesh for sprite sequences sequences = new MeshRange[level.spriteSequencesCount]; for (int i = 0; i < level.spriteSequencesCount; i++) { - sequences[i].vStart = vCount; - sequences[i].iStart = iCount; - sequences[i].iCount = level.spriteSequences[i].sCount * 6; iCount += level.spriteSequences[i].sCount * 6; vCount += level.spriteSequences[i].sCount * 4; } // shadow blob mesh (8 triangles, 8 vertices) - shadowBlob.vStart = vCount; - shadowBlob.iStart = iCount; - shadowBlob.iCount = 8 * 3 * 3; - iCount += shadowBlob.iCount; + iCount += 8 * 3 * 3; vCount += 8 * 2 + 1; // quad (post effect filter) - quad.vStart = vCount; - quad.iStart = iCount; - quad.iCount = 2 * 3; - iCount += quad.iCount; + iCount += 2 * 3; vCount += 4; // circle - circle.vStart = vCount; - circle.iStart = iCount; - circle.iCount = CIRCLE_SEGS * 3; - iCount += circle.iCount; + iCount += CIRCLE_SEGS * 3; vCount += CIRCLE_SEGS + 1; // detailed plane - plane.vStart = vCount; - plane.iStart = iCount; - plane.iCount = PLANE_DETAIL * 2 * PLANE_DETAIL * 2 * (2 * 3); - iCount += plane.iCount; + iCount += PLANE_DETAIL * 2 * PLANE_DETAIL * 2 * (2 * 3); vCount += (PLANE_DETAIL * 2 + 1) * (PLANE_DETAIL * 2 + 1); // make meshes buffer (single vertex buffer object for all geometry & sprites on level) @@ -293,20 +259,20 @@ struct MeshBuilder { int aCount = 0; // build rooms + int vStartRoom = vCount; + aCount++; + for (int i = 0; i < level.roomsCount; i++) { TR::Room &room = level.rooms[i]; TR::Room::Data &d = room.data; RoomRange &range = rooms[i]; - int vStart; - for (int transp = 0; transp < 2; transp++) { // opaque, opacity - range.geometry[transp].vStart = vCount; + range.geometry[transp].vStart = vStartRoom; range.geometry[transp].iStart = iCount; - vStart = vCount; // rooms geometry - buildRoom(!transp, room, level, indices, vertices, iCount, vCount, vStart); + buildRoom(!transp, room, level, indices, vertices, iCount, vCount, vStartRoom); // static meshes for (int j = 0; j < room.meshesCount; j++) { @@ -319,44 +285,44 @@ 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, vStart, 0, x, y, z, d); + buildMesh(!transp, mesh, level, indices, vertices, iCount, vCount, vStartRoom, 0, x, y, z, d); } range.geometry[transp].iCount = iCount - range.geometry[transp].iStart; - - if (range.geometry[transp].iCount) - aCount++; } // rooms sprites - vStart = vCount; + range.sprites.vStart = vStartRoom; + range.sprites.iStart = iCount; for (int j = 0; j < d.sCount; j++) { TR::Room::Data::Sprite &f = d.sprites[j]; TR::Room::Data::Vertex &v = d.vertices[f.vertex]; TR::SpriteTexture &sprite = level.spriteTextures[f.texture]; - addSprite(indices, vertices, iCount, vCount, vStart, v.vertex.x, v.vertex.y, v.vertex.z, sprite, intensity(v.lighting)); + addSprite(indices, vertices, iCount, vCount, vStartRoom, v.vertex.x, v.vertex.y, v.vertex.z, sprite, intensity(v.lighting)); } - - if (d.sCount) aCount++; + range.sprites.iCount = iCount - range.sprites.iStart; } // build models geometry + int vStartModel = vCount; + aCount++; + for (int i = 0; i < level.modelsCount; i++) { TR::Model &model = level.models[i]; ModelRange &range = models[i]; - int vStart = vCount; - range.geometry.vStart = vStart; + + range.geometry.vStart = vStartModel; range.geometry.iStart = iCount; range.opaque = true; for (int j = 0; j < model.mCount; j++) { int index = level.meshOffsets[model.mStart + j]; if (!index && model.mStart + j > 0) continue; - aCount++; + TR::Mesh &mesh = level.meshes[index]; - bool opaque = buildMesh(true, mesh, level, indices, vertices, iCount, vCount, vStart, j, 0, 0, 0, 0); + bool opaque = buildMesh(true, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0); if (!opaque) - buildMesh(false, mesh, level, indices, vertices, iCount, vCount, vStart, j, 0, 0, 0, 0); + buildMesh(false, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0); TR::Entity::fixOpaque(model.type, opaque); range.opaque &= opaque; } @@ -364,18 +330,31 @@ struct MeshBuilder { } // build sprite sequences - for (int i = 0; i < level.spriteSequencesCount; i++) + int vStartSprite = vCount; + aCount++; + + for (int i = 0; i < level.spriteSequencesCount; i++) { + MeshRange &range = sequences[i]; + range.vStart = vStartSprite; + range.iStart = iCount; for (int j = 0; j < level.spriteSequences[i].sCount; j++) { TR::SpriteTexture &sprite = level.spriteTextures[level.spriteSequences[i].sStart + j]; - addSprite(indices, vertices, iCount, vCount, sequences[i].vStart, 0, 0, 0, sprite, 255); + addSprite(indices, vertices, iCount, vCount, vStartSprite, 0, 0, 0, sprite, 255); } - aCount += level.spriteSequencesCount; + range.iCount = iCount - range.iStart; + } - // build shadow blob + // build common primitives + int vStartCommon = vCount; + aCount++; + + shadowBlob.vStart = vStartCommon; + shadowBlob.iStart = iCount; + shadowBlob.iCount = 8 * 3 * 3; for (int i = 0; i < 9; i++) { Vertex &v0 = vertices[vCount + i * 2 + 0]; v0.normal = { 0, -1, 0, 0 }; - v0.texCoord = { 32688, 32688, 32767, 32767 }; + v0.texCoord = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 }; v0.param = { 0, 0, 0, 0 }; v0.color = { 0, 0, 0, 0 }; @@ -414,10 +393,13 @@ struct MeshBuilder { } vCount += 8 * 2 + 1; iCount += shadowBlob.iCount; - aCount++; // quad - addQuad(indices, iCount, vCount, quad.vStart, vertices, &whiteTile); + quad.vStart = vStartCommon; + quad.iStart = iCount; + quad.iCount = 2 * 3; + + addQuad(indices, iCount, vCount, vStartCommon, vertices, &whiteTile); vertices[vCount + 3].coord = { -1, -1, 0, 0 }; vertices[vCount + 2].coord = { 1, -1, 1, 0 }; vertices[vCount + 1].coord = { 1, 1, 1, 1 }; @@ -427,41 +409,48 @@ struct MeshBuilder { Vertex &v = vertices[vCount + i]; v.normal = { 0, 0, 0, 0 }; v.color = { 255, 255, 255, 255 }; - v.texCoord = { 32688, 32688, 32767, 32767 }; + v.texCoord = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 }; v.param = { 0, 0, 0, 0 }; } vCount += 4; - aCount++; // circle + circle.vStart = vStartCommon; + circle.iStart = iCount; + circle.iCount = CIRCLE_SEGS * 3; + vec2 pos(32767.0f, 0.0f); vec2 cs(cosf(PI2 / CIRCLE_SEGS), sinf(PI2 / CIRCLE_SEGS)); + int baseIdx = vCount - vStartCommon; for (int i = 0; i < CIRCLE_SEGS; i++) { Vertex &v = vertices[vCount + i]; pos.rotate(cs); v.coord = { short(pos.x), short(pos.y), 0, 0 }; v.normal = { 0, 0, 0, 0 }; v.color = { 255, 255, 255, 255 }; - v.texCoord = { 32688, 32688, 32767, 32767 }; + v.texCoord = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 }; v.param = { 0, 0, 0, 0 }; - indices[iCount++] = i; - indices[iCount++] = (i + 1) % CIRCLE_SEGS; - indices[iCount++] = CIRCLE_SEGS; + indices[iCount++] = baseIdx + i; + indices[iCount++] = baseIdx + (i + 1) % CIRCLE_SEGS; + indices[iCount++] = baseIdx + CIRCLE_SEGS; } vertices[vCount + CIRCLE_SEGS] = vertices[vCount]; vertices[vCount + CIRCLE_SEGS].coord = { 0, 0, 0, 0 }; - vCount += CIRCLE_SEGS + 1; - aCount++; // plane + plane.vStart = vStartCommon; + plane.iStart = iCount; + plane.iCount = PLANE_DETAIL * 2 * PLANE_DETAIL * 2 * (2 * 3); + + baseIdx = vCount - vStartCommon; for (int16 j = -PLANE_DETAIL; j <= PLANE_DETAIL; j++) for (int16 i = -PLANE_DETAIL; i <= PLANE_DETAIL; i++) { vertices[vCount++].coord = { i, j, 0, 0 }; if (j < PLANE_DETAIL && i < PLANE_DETAIL) { - int idx = (j + PLANE_DETAIL) * (PLANE_DETAIL * 2 + 1) + i + PLANE_DETAIL; + int idx = baseIdx + (j + PLANE_DETAIL) * (PLANE_DETAIL * 2 + 1) + i + PLANE_DETAIL; indices[iCount + 0] = idx + PLANE_DETAIL * 2 + 1; indices[iCount + 1] = idx + 1; indices[iCount + 2] = idx; @@ -471,7 +460,7 @@ struct MeshBuilder { iCount += 6; } } - aCount++; + LOG("MegaMesh: %d %d %d\n", iCount, vCount, aCount); // compile buffer and ranges @@ -483,24 +472,35 @@ struct MeshBuilder { PROFILE_LABEL(BUFFER, mesh->ID[1], "Geometry vertices"); // initialize Vertex Arrays + MeshRange rangeRoom; + rangeRoom.vStart = vStartRoom; + mesh->initRange(rangeRoom); for (int i = 0; i < level.roomsCount; i++) { RoomRange &r = rooms[i]; - if (r.geometry[0].iCount) - mesh->initRange(r.geometry[0]); - if (r.geometry[1].iCount) - mesh->initRange(r.geometry[1]); - if (r.sprites.iCount) - mesh->initRange(r.sprites); + r.geometry[0].aIndex = rangeRoom.aIndex; + r.geometry[1].aIndex = rangeRoom.aIndex; + r.sprites.aIndex = rangeRoom.aIndex; } - for (int i = 0; i < level.spriteSequencesCount; i++) - mesh->initRange(sequences[i]); + MeshRange rangeModel; + rangeModel.vStart = vStartModel; + mesh->initRange(rangeModel); for (int i = 0; i < level.modelsCount; i++) - mesh->initRange(models[i].geometry); - mesh->initRange(shadowBlob); - mesh->initRange(quad); - mesh->initRange(circle); - mesh->initRange(plane); + models[i].geometry.aIndex = rangeModel.aIndex; + + MeshRange rangeSprite; + rangeSprite.vStart = vStartSprite; + mesh->initRange(rangeSprite); + for (int i = 0; i < level.spriteSequencesCount; i++) + sequences[i].aIndex = rangeSprite.aIndex; + + MeshRange rangeCommon; + rangeCommon.vStart = vStartCommon; + mesh->initRange(rangeCommon); + shadowBlob.aIndex = rangeCommon.aIndex; + quad.aIndex = rangeCommon.aIndex; + circle.aIndex = rangeCommon.aIndex; + plane.aIndex = rangeCommon.aIndex; } ~MeshBuilder() { @@ -535,16 +535,18 @@ struct MeshBuilder { return res; } - bool roomCheckWaterPortal(TR::Room room) { - for (int i = 0; i < room.portalsCount; i++) - if (room.flags.water ^ level->rooms[room.portals[i].roomIndex].flags.water) + bool isWaterSurface(int delta, int roomIndex, bool fromWater) { + if (roomIndex != TR::NO_ROOM && delta == 0) { + TR::Room &r = level->rooms[roomIndex]; + if (r.flags.water ^ fromWater) return true; + if (r.alternateRoom > -1 && level->rooms[r.alternateRoom].flags.water ^ fromWater) + return true; + } return false; } void roomRemoveWaterSurfaces(TR::Room &room, int &iCount, int &vCount) { - if (!roomCheckWaterPortal(room)) return; - // remove animated water polygons from room geometry for (int i = 0; i < room.data.rCount; i++) { TR::Rectangle &f = room.data.rectangles[i]; @@ -568,8 +570,8 @@ struct MeshBuilder { if (yt > 0 && yb > 0) continue; - if ((yt == 0 && s.roomAbove != TR::NO_ROOM && (level->rooms[s.roomAbove].flags.water ^ room.flags.water)) || - (yb == 0 && s.roomBelow != TR::NO_ROOM && (level->rooms[s.roomBelow].flags.water ^ room.flags.water))) { + if (isWaterSurface(yt, s.roomAbove, room.flags.water) || + isWaterSurface(yb, s.roomBelow, room.flags.water)) { f.vertices[0] = 0xFFFF; // mark as unused iCount -= 6; vCount -= 4; @@ -597,8 +599,8 @@ struct MeshBuilder { if (yt > 0 && yb > 0) continue; - if ((yt <= 1 && s.roomAbove != TR::NO_ROOM && (level->rooms[s.roomAbove].flags.water ^ room.flags.water)) || - (yb <= 1 && s.roomBelow != TR::NO_ROOM && (level->rooms[s.roomBelow].flags.water ^ room.flags.water))) { + if (isWaterSurface(yt, s.roomAbove, room.flags.water) || + isWaterSurface(yb, s.roomBelow, room.flags.water)) { f.vertices[0] = 0xFFFF; // mark as unused iCount -= 3; vCount -= 3; @@ -674,8 +676,7 @@ struct MeshBuilder { for (int j = 0; j < mesh.rCount; j++) { TR::Rectangle &f = mesh.rectangles[j]; - bool textured = !(f.texture & 0x8000); - TR::ObjectTexture &t = textured ? level.objectTextures[f.texture] : whiteTile; + TR::ObjectTexture &t = f.color ? whiteTile : level.objectTextures[f.texture]; if (t.attribute != 0) isOpaque = false; @@ -683,7 +684,7 @@ struct MeshBuilder { if (opaque != (t.attribute == 0)) continue; - TR::Color24 c = textured ? COLOR_WHITE : level.getColor(f.texture); + TR::Color24 c = f.color ? level.getColor(f.texture) : COLOR_WHITE; addQuad(indices, iCount, vCount, vStart, vertices, &t, mesh.vertices[f.vertices[0]].coord, @@ -704,8 +705,7 @@ struct MeshBuilder { for (int j = 0; j < mesh.tCount; j++) { TR::Triangle &f = mesh.triangles[j]; - bool textured = !(f.texture & 0x8000); - TR::ObjectTexture &t = textured ? level.objectTextures[f.texture] : whiteTile; + TR::ObjectTexture &t = f.color ? whiteTile : level.objectTextures[f.texture]; if (t.attribute != 0) isOpaque = false; @@ -713,7 +713,7 @@ struct MeshBuilder { if (opaque != (t.attribute == 0)) continue; - TR::Color24 c = textured ? COLOR_WHITE : level.getColor(f.texture); + TR::Color24 c = f.color ? level.getColor(f.texture) : COLOR_WHITE; addTriangle(indices, iCount, vCount, vStart, vertices, &t); @@ -732,11 +732,7 @@ struct MeshBuilder { } vec2 getTexCoord(const TR::ObjectTexture &tex) { - int tile = tex.tile.index; - int tx = (tile % 4) * 256; - int ty = (tile / 4) * 256; - return vec2( (float)(((tx + tex.texCoord[0].x) << 5) + 16), - (float)(((ty + tex.texCoord[0].y) << 5) + 16) ) * (1.0f / 32767.0f); + return vec2(tex.texCoord[0].x / 32767.0f, tex.texCoord[0].y / 32767.0f); } void initAnimTextures(TR::Level &level) { @@ -800,34 +796,15 @@ struct MeshBuilder { uint8 range, frame; tex = getAnimTexture(tex, range, frame); - int tile = tex->tile.index; - int tx = (tile % 4) * 256; - int ty = (tile / 4) * 256; - - int count = triangle ? 3 : 4; for (int i = 0; i < count; i++) { Vertex &v = vertices[vCount + i]; - v.texCoord.x = ((tx + tex->texCoord[i].x) << 5) + 16; - v.texCoord.y = ((ty + tex->texCoord[i].y) << 5) + 16; - v.texCoord.z = 32767; - v.texCoord.w = 32767; - v.param = { range, frame, 0, 0 }; + v.texCoord = { tex->texCoord[i].x, tex->texCoord[i].y, 32767, 32767 }; + 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); - /* - short2 uv[4] = { - {0, 0}, {32767, 0}, {32767, 32767}, {0, 32767} - }; - - for (int i = 0; i < count; i++) { - Vertex &v = vertices[vCount + i]; - v.texCoord.x = uv[i].x; - v.texCoord.y = uv[i].y; - } - */ } void addTriangle(Index *indices, int &iCount, int vCount, int vStart, Vertex *vertices, TR::ObjectTexture *tex) { @@ -921,23 +898,15 @@ struct MeshBuilder { quad[0].color = quad[1].color = quad[2].color = quad[3].color = { 255, 255, 255, intensity }; quad[0].param = quad[1].param = quad[2].param = quad[3].param = { 0, 0, 0, 0 }; - int tx = (sprite.tile % 4) * 256; - int ty = (sprite.tile / 4) * 256; - - int16 u0 = (((tx + sprite.texCoord[0].x) << 5)); - int16 v0 = (((ty + sprite.texCoord[0].y) << 5)); - int16 u1 = (((tx + sprite.texCoord[1].x) << 5)); - int16 v1 = (((ty + sprite.texCoord[1].y) << 5)); - - quad[0].texCoord = { u0, v0, sprite.l, sprite.t }; - quad[1].texCoord = { u1, v0, sprite.r, sprite.t }; - quad[2].texCoord = { u1, v1, sprite.r, sprite.b }; - quad[3].texCoord = { u0, v1, sprite.l, sprite.b }; + quad[0].texCoord = { sprite.texCoord[0].x, sprite.texCoord[0].y, sprite.l, sprite.t }; + quad[1].texCoord = { sprite.texCoord[1].x, sprite.texCoord[0].y, sprite.r, sprite.t }; + quad[2].texCoord = { sprite.texCoord[1].x, sprite.texCoord[1].y, sprite.r, sprite.b }; + quad[3].texCoord = { sprite.texCoord[0].x, sprite.texCoord[1].y, sprite.l, sprite.b }; vCount += 4; } - void addBar(Index *indices, Vertex *vertices, int &iCount, int &vCount, int type, const vec2 &pos, const vec2 &size, uint32 color) { + void addBar(Index *indices, Vertex *vertices, int &iCount, int &vCount, const TR::ObjectTexture &tile, const vec2 &pos, const vec2 &size, uint32 color) { addQuad(indices, iCount, vCount, 0, vertices, NULL); int16 minX = int16(pos.x); @@ -955,22 +924,9 @@ struct MeshBuilder { v.normal = { 0, 0, 0, 0 }; v.color = *((ubyte4*)&color); - int16 s, t; + short2 uv = tile.texCoord[i]; - if (type == 0) { // health bar - s = TEX_HEALTH_BAR_X + 1; - t = TEX_HEALTH_BAR_Y + 1; - } else { // oxygen bar - s = TEX_OXYGEN_BAR_X + 1; - t = TEX_OXYGEN_BAR_Y + 1; - } - - if (i > 1) t += 5; - - s = int(s) * 32767 / 1024; - t = int(t) * 32767 / 1024; - - v.texCoord = { s, t, 32767, 32767 }; + v.texCoord = { uv.x, uv.y, 32767, 32767 }; v.param = { 0, 0, 0, 0 }; } @@ -978,6 +934,8 @@ struct MeshBuilder { } void addFrame(Index *indices, Vertex *vertices, int &iCount, int &vCount, const vec2 &pos, const vec2 &size, uint32 color1, uint32 color2) { + short4 uv = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 }; + int16 minX = int16(pos.x); int16 minY = int16(pos.y); int16 maxX = int16(size.x) + minX; @@ -997,7 +955,7 @@ struct MeshBuilder { Vertex &v = vertices[vCount + i]; v.normal = { 0, 0, 0, 0 }; v.color = *((ubyte4*)&color1); - v.texCoord = { 32688, 32688, 32767, 32767 }; + v.texCoord = uv; } addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; @@ -1017,14 +975,13 @@ struct MeshBuilder { Vertex &v = vertices[vCount + i]; v.normal = { 0, 0, 0, 0 }; v.color = *((ubyte4*)&color2); - v.texCoord = { 32688, 32688, 32767, 32767 }; + v.texCoord = uv; } addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4; } - void bind() { mesh->bind(); } diff --git a/src/platform/android/app/build.gradle b/src/platform/android/app/build.gradle index 4b87889..b75219d 100644 --- a/src/platform/android/app/build.gradle +++ b/src/platform/android/app/build.gradle @@ -10,7 +10,7 @@ android { versionCode 1 versionName "0.1" ndk { - abiFilters 'armeabi-v7a','x86','x86_64' + abiFilters 'armeabi-v7a', 'x86', 'x86_64' } externalNativeBuild { cmake { @@ -29,6 +29,9 @@ android { path "CMakeLists.txt" } } + aaptOptions { + noCompress 'psx', 'phd', 'pcx', 'mp3', 'ogg' + } } dependencies { diff --git a/src/platform/android/app/src/main/AndroidManifest.xml b/src/platform/android/app/src/main/AndroidManifest.xml index 0aea464..6900fb7 100644 --- a/src/platform/android/app/src/main/AndroidManifest.xml +++ b/src/platform/android/app/src/main/AndroidManifest.xml @@ -9,9 +9,10 @@ + + --> + + GetStringUTFChars(packName, NULL); + Stream *level = new Stream(str); + env->ReleaseStringUTFChars(packName, str); + level->seek(levelOffset); + + str = env->GetStringUTFChars(contentDir, NULL); + strcat(Stream::contentDir, str); + env->ReleaseStringUTFChars(contentDir, str); + str = env->GetStringUTFChars(cacheDir, NULL); strcat(Stream::cacheDir, str); env->ReleaseStringUTFChars(cacheDir, str); - str = env->GetStringUTFChars(packName, NULL); - Stream *level = new Stream(str); - Stream *music = new Stream(str); - env->ReleaseStringUTFChars(packName, str); + Game::init(level); - level->seek(levelOffset); - music->seek(musicOffset); - - Game::init(level, music); - - lastTime = getTime(); + lastTime = getTime(); } JNI_METHOD(void, nativeFree)(JNIEnv* env) { diff --git a/src/platform/android/app/src/main/java/org/xproger/openlara/MainActivity.java b/src/platform/android/app/src/main/java/org/xproger/openlara/MainActivity.java index 8950c34..40efbf7 100644 --- a/src/platform/android/app/src/main/java/org/xproger/openlara/MainActivity.java +++ b/src/platform/android/app/src/main/java/org/xproger/openlara/MainActivity.java @@ -66,9 +66,8 @@ public class MainActivity extends Activity implements OnTouchListener, OnKeyList String packName = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES).applicationInfo.sourceDir; // hardcoded demo level and music AssetFileDescriptor fLevel = this.getResources().openRawResourceFd(R.raw.level2); - AssetFileDescriptor fMusic = this.getResources().openRawResourceFd(R.raw.music); - wrapper.onCreate(packName, getCacheDir().getAbsolutePath() + "/", (int)fLevel.getStartOffset(), (int)fMusic.getStartOffset()); + wrapper.onCreate(System.getenv("EXTERNAL_STORAGE") + "/OpenLara/", getCacheDir().getAbsolutePath() + "/", packName, (int)fLevel.getStartOffset()); } catch (Exception e) { e.printStackTrace(); finish(); @@ -215,9 +214,9 @@ class Sound { public void run() { while ( audioTrack.getPlayState() != AudioTrack.PLAYSTATE_STOPPED ) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING && wrapper.ready) { - synchronized (wrapper) { + //synchronized (wrapper) { Wrapper.nativeSoundFill(buffer); - } + //} audioTrack.write(buffer, 0, buffer.length); audioTrack.flush(); } else @@ -270,7 +269,7 @@ class Touch { } class Wrapper implements Renderer { - public static native void nativeInit(String packName, String cacheDir, int levelOffset, int musicOffset); + public static native void nativeInit(String contentDir, String cacheDir, String packName, int levelOffset); public static native void nativeFree(); public static native void nativeReset(); public static native void nativeResize(int w, int h); @@ -280,18 +279,18 @@ class Wrapper implements Renderer { public static native void nativeSoundFill(short buffer[]); Boolean ready = false; - private String packName; + private String contentDir; private String cacheDir; + private String packName; private int levelOffset; - private int musicOffset; private ArrayList touch = new ArrayList<>(); private Sound sound; - void onCreate(String packName, String cacheDir, int levelOffset, int musicOffset) { - this.packName = packName; - this.cacheDir = cacheDir; + void onCreate(String contentDir, String cacheDir, String packName, int levelOffset) { + this.contentDir = contentDir; + this.cacheDir = cacheDir; + this.packName = packName; this.levelOffset = levelOffset; - this.musicOffset = musicOffset; sound = new Sound(); sound.start(this); @@ -325,8 +324,8 @@ class Wrapper implements Renderer { nativeTouch(t.id, t.state, t.x, t.y); } touch.clear(); - nativeUpdate(); } + nativeUpdate(); nativeRender(); } @@ -338,7 +337,7 @@ class Wrapper implements Renderer { @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { if (!ready) { - nativeInit(packName, cacheDir, levelOffset, musicOffset); + nativeInit(contentDir, cacheDir, packName, levelOffset); sound.play(); ready = true; } diff --git a/src/platform/android/app/src/main/res/raw/level2.jet b/src/platform/android/app/src/main/res/raw/level2.psx similarity index 100% rename from src/platform/android/app/src/main/res/raw/level2.jet rename to src/platform/android/app/src/main/res/raw/level2.psx diff --git a/src/platform/android/app/src/main/res/raw/music.jet b/src/platform/android/app/src/main/res/raw/music.jet deleted file mode 100644 index 2ff42d4..0000000 Binary files a/src/platform/android/app/src/main/res/raw/music.jet and /dev/null differ diff --git a/src/platform/nix/main.cpp b/src/platform/nix/main.cpp index 1f723b6..a212dfa 100644 --- a/src/platform/nix/main.cpp +++ b/src/platform/nix/main.cpp @@ -11,6 +11,16 @@ #define WND_TITLE "OpenLara" +// Time +unsigned int startTime; + +int getTime() { + timeval t; + gettimeofday(&t, NULL); + return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000); +} + +// Sound #define SND_FRAME_SIZE 4 #define SND_DATA_SIZE (1024 * SND_FRAME_SIZE) @@ -70,12 +80,7 @@ void sndFree() { pthread_mutex_destroy(&sndMutex); } -int getTime() { - timeval t; - gettimeofday(&t, NULL); - return (t.tv_sec * 1000 + t.tv_usec / 1000); -} - +// Input InputKey keyToInputKey(int code) { int codes[] = { 113, 114, 111, 116, 65, 23, 36, 9, 50, 37, 64, @@ -147,7 +152,7 @@ void WndProc(const XEvent &e,Display*dpy,Window wnd) { char Stream::cacheDir[255]; char Stream::contentDir[255]; -int main() { +int main(int argc, char **argv) { Stream::contentDir[0] = Stream::cacheDir[0] = 0; const char *home; @@ -190,8 +195,12 @@ int main() { Atom WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", 0); XSetWMProtocols(dpy, wnd, &WM_DELETE_WINDOW, 1); + timeval t; + gettimeofday(&t, NULL); + startTime = t.tv_sec; + sndInit(); - Game::init(); + Game::init(argc > 1 ? argv[1] : NULL); int lastTime = getTime(); diff --git a/src/platform/osx/OpenLara.xcodeproj/project.pbxproj b/src/platform/osx/OpenLara.xcodeproj/project.pbxproj index cf016bc..3c196c3 100644 --- a/src/platform/osx/OpenLara.xcodeproj/project.pbxproj +++ b/src/platform/osx/OpenLara.xcodeproj/project.pbxproj @@ -7,10 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 995C97F01E91A857003825B2 /* shaders in Resources */ = {isa = PBXBuildFile; fileRef = 995C97EF1E91A857003825B2 /* shaders */; }; - 9971B72F1E7C239900F25790 /* LEVEL2.PSX in Resources */ = {isa = PBXBuildFile; fileRef = 9971B72E1E7C239900F25790 /* LEVEL2.PSX */; }; + 99714F651F621E1000960AA7 /* level in Resources */ = {isa = PBXBuildFile; fileRef = 99714F641F621E1000960AA7 /* level */; }; + 99714F671F621E5E00960AA7 /* audio in Resources */ = {isa = PBXBuildFile; fileRef = 99714F661F621E5E00960AA7 /* audio */; }; 99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A11DCA7F5300E2E997 /* main.cpp */; }; - 99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A41DCA872D00E2E997 /* 05.ogg */; }; 99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */; }; 99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */; }; 99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0951796A9730032DE85 /* AGL.framework */; }; @@ -21,7 +20,8 @@ /* Begin PBXFileReference section */ 995C97EF1E91A857003825B2 /* shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; name = shaders; path = ../../shaders; sourceTree = ""; }; - 9971B72E1E7C239900F25790 /* LEVEL2.PSX */ = {isa = PBXFileReference; lastKnownFileType = file; name = LEVEL2.PSX; path = ../../../bin/LEVEL2.PSX; sourceTree = SOURCE_ROOT; }; + 99714F641F621E1000960AA7 /* level */ = {isa = PBXFileReference; lastKnownFileType = folder; name = level; path = ../../../../bin/level; sourceTree = ""; }; + 99714F661F621E5E00960AA7 /* audio */ = {isa = PBXFileReference; lastKnownFileType = folder; name = audio; path = ../../../../bin/audio; sourceTree = ""; }; 99BFB68D1DCA7F1700E2E997 /* lara.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lara.h; path = ../../lara.h; sourceTree = ""; }; 99BFB68E1DCA7F1700E2E997 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../format.h; sourceTree = ""; }; 99BFB68F1DCA7F1700E2E997 /* level.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = level.h; path = ../../level.h; sourceTree = ""; }; @@ -40,7 +40,6 @@ 99BFB69D1DCA7F1700E2E997 /* texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = texture.h; path = ../../texture.h; sourceTree = ""; }; 99BFB69E1DCA7F1700E2E997 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libs; path = ../../libs; sourceTree = ""; }; 99BFB6A11DCA7F5300E2E997 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; - 99BFB6A41DCA872D00E2E997 /* 05.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; name = 05.ogg; path = ../../../bin/05.ogg; sourceTree = SOURCE_ROOT; }; 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = ""; }; 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = minimp3.cpp; path = ../../libs/minimp3/minimp3.cpp; sourceTree = ""; }; 99C4C0931796A96F0032DE85 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; @@ -122,8 +121,8 @@ 99C4C0A31796AACF0032DE85 /* Supporting Files */ = { isa = PBXGroup; children = ( - 9971B72E1E7C239900F25790 /* LEVEL2.PSX */, - 99BFB6A41DCA872D00E2E997 /* 05.ogg */, + 99714F661F621E5E00960AA7 /* audio */, + 99714F641F621E1000960AA7 /* level */, 99C4C0A41796AACF0032DE85 /* OpenLara-Info.plist */, ); name = "Supporting Files"; @@ -181,9 +180,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 995C97F01E91A857003825B2 /* shaders in Resources */, - 99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */, - 9971B72F1E7C239900F25790 /* LEVEL2.PSX in Resources */, + 99714F651F621E1000960AA7 /* level in Resources */, + 99714F671F621E5E00960AA7 /* audio in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/platform/rpi/Makefile b/src/platform/rpi/Makefile new file mode 100644 index 0000000..86896ae --- /dev/null +++ b/src/platform/rpi/Makefile @@ -0,0 +1,28 @@ +OBJS=OpenLara.o +BIN=../../../bin/OpenLara.bin + +CFLAGS+=-D__RPI__ +LDFLAGS+=-L$(SDKSTAGE)/opt/vc/lib/ -lbrcmGLESv2 -lbrcmEGL -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt -lm -L$(SDKSTAGE)/opt/vc/src/hello_pi/libs/vgfont + +INCLUDES+=-I$(SDKSTAGE)/opt/vc/include/ -I$(SDKSTAGE)/opt/vc/include/interface/vcos/pthreads -I$(SDKSTAGE)/opt/vc/include/interface/vmcs_host/linux -I./ + +all: $(BIN) $(LIB) + +%.o: %.c + @rm -f $@ + $(CC) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations + +%.o: %.cpp + @rm -f $@ + $(CXX) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations + +%.bin: $(OBJS) + $(CC) -o $@ -Wl,--whole-archive $(OBJS) $(LDFLAGS) -Wl,--no-whole-archive -rdynamic + +%.a: $(OBJS) + $(AR) r $@ $^ + +clean: + for i in $(OBJS); do (if test -e "$$i"; then ( rm $$i ); fi ); done + @rm -f $(BIN) $(LIB) + diff --git a/src/platform/rpi/build.sh b/src/platform/rpi/build.sh new file mode 100755 index 0000000..75c582f --- /dev/null +++ b/src/platform/rpi/build.sh @@ -0,0 +1,3 @@ +set -e +clang++ -std=c++11 -Os -s -g -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections -DNDEBUG -D__RPI__ main.cpp ../../libs/stb_vorbis/stb_vorbis.c -I/opt/vc/include -I../../ -o../../../bin/OpenLara -L/opt/vc/lib/ -lbrcmGLESv2 -lbrcmEGL -lX11 -lm -lrt -lpthread -lasound -lbcm_host -ludev +strip ../../../bin/OpenLara --strip-all --remove-section=.comment --remove-section=.note diff --git a/src/platform/rpi/main.cpp b/src/platform/rpi/main.cpp new file mode 100644 index 0000000..35b55d6 --- /dev/null +++ b/src/platform/rpi/main.cpp @@ -0,0 +1,495 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "game.h" + +#define WND_TITLE "OpenLara" + +// Time +unsigned int startTime; + +int getTime() { + timeval t; + gettimeofday(&t, NULL); + return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000); +} + +// Sound +snd_pcm_uframes_t SND_FRAMES = 512; +snd_pcm_t *sndOut; +Sound::Frame *sndData; +pthread_t sndThread; +pthread_mutex_t sndMutex; + +void* sndFill(void *arg) { + while (sndOut) { + pthread_mutex_lock(&sndMutex); + Sound::fill(sndData, SND_FRAMES); + pthread_mutex_unlock(&sndMutex); + + int err = snd_pcm_writei(sndOut, sndData, SND_FRAMES); + if (err < 0) { + LOG("! sound: write %s\n", snd_strerror(err));; + if (err != -EPIPE) + break; + + err = snd_pcm_recover(sndOut, err, 0); + if (err < 0) { + LOG("! sound: failed to recover\n"); + break; + } + snd_pcm_prepare(sndOut); + } + } + return NULL; +} + +bool sndInit() { + unsigned int freq = 44100; + + pthread_mutex_init(&sndMutex, NULL); + + int err; + if ((err = snd_pcm_open(&sndOut, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + LOG("! sound: open %s\n", snd_strerror(err));\ + sndOut = NULL; + return false; + } + + snd_pcm_hw_params_t *params; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(sndOut, params); + snd_pcm_hw_params_set_access(sndOut, params, SND_PCM_ACCESS_RW_INTERLEAVED); + + snd_pcm_hw_params_set_channels(sndOut, params, 2); + snd_pcm_hw_params_set_format(sndOut, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(sndOut, params, &freq, NULL); + + snd_pcm_hw_params_set_periods(sndOut, params, 4, 0); + snd_pcm_hw_params_set_period_size_near(sndOut, params, &SND_FRAMES, NULL); + snd_pcm_hw_params_get_period_size(params, &SND_FRAMES, 0); + + snd_pcm_hw_params(sndOut, params); + snd_pcm_prepare(sndOut); + + sndData = new Sound::Frame[SND_FRAMES]; + memset(sndData, 0, SND_FRAMES * sizeof(Sound::Frame)); + if ((err = snd_pcm_writei(sndOut, sndData, SND_FRAMES)) < 0) { + LOG("! sound: write %s\n", snd_strerror(err));\ + sndOut = NULL; + } + + snd_pcm_start(sndOut); + pthread_create(&sndThread, NULL, sndFill, NULL); + + return true; +} + +void sndFree() { + pthread_cancel(sndThread); + pthread_mutex_lock(&sndMutex); + snd_pcm_drop(sndOut); + snd_pcm_drain(sndOut); + snd_pcm_close(sndOut); + pthread_mutex_unlock(&sndMutex); + pthread_mutex_destroy(&sndMutex); + delete[] sndData; +} + +// Window +bool wndInit(DISPMANX_DISPLAY_HANDLE_T &display, EGL_DISPMANX_WINDOW_T &window) { + if (graphics_get_display_size(0, (uint32_t*)&window.width, (uint32_t*)&window.height) < 0) { + LOG("! can't get display size\n"); + return false; + } + + int scale = 1; + window.width /= scale; + window.height /= scale; + + VC_RECT_T dstRect, srcRect; + vc_dispmanx_rect_set(&dstRect, 0, 0, window.width, window.height); + vc_dispmanx_rect_set(&srcRect, 0, 0, window.width << 16, window.height << 16); + VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 0xFF, 0 }; + + display = vc_dispmanx_display_open(0); + + DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); + + window.element = vc_dispmanx_element_add( + update, display, 0, &dstRect, 0, &srcRect, + DISPMANX_PROTECTION_NONE, &alpha, NULL, DISPMANX_NO_ROTATE); + + vc_dispmanx_update_submit_sync(update); + + return true; +} + +void wndFree(DISPMANX_DISPLAY_HANDLE_T &display, EGL_DISPMANX_WINDOW_T &window) { + DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); + vc_dispmanx_element_remove(update, window.element); + vc_dispmanx_update_submit_sync(update); + vc_dispmanx_display_close(display); +} + +bool eglInit(EGL_DISPMANX_WINDOW_T &window, EGLDisplay &display, EGLSurface &surface, EGLContext &context) { + static const EGLint eglAttr[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_SAMPLES, 0, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + + static const EGLint ctxAttr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) + return false; + + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) + return false; + + EGLConfig config; + EGLint configCount; + + if (eglChooseConfig(display, eglAttr, &config, 1, &configCount) == EGL_FALSE) + return false; + + context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr); + if (context == EGL_NO_CONTEXT) + return false; + + surface = eglCreateWindowSurface(display, config, &window, NULL); + if (surface == EGL_NO_SURFACE) + return false; + + if (eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) == EGL_FALSE) + return false; + + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) + return false; + + //eglSwapInterval(display, 0); // turn off vsync + + return true; +} + +void eglFree(EGLDisplay display, EGLSurface surface, EGLContext context) { + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(display, surface); + eglDestroyContext(display, context); + eglTerminate(display); +} + +// Input +#define MAX_INPUT_DEVICES 16 +int inputDevices[MAX_INPUT_DEVICES]; + +udev *udevObj; +udev_monitor *udevMon; +int udevMon_fd; + +vec2 joyL, joyR; + +InputKey codeToInputKey(int code) { + switch (code) { + // keyboard + case KEY_LEFT : return ikLeft; + case KEY_RIGHT : return ikRight; + case KEY_UP : return ikUp; + case KEY_DOWN : return ikDown; + case KEY_SPACE : return ikSpace; + case KEY_TAB : return ikTab; + case KEY_ENTER : return ikEnter; + case KEY_ESC : return ikEscape; + case KEY_LEFTSHIFT : + case KEY_RIGHTSHIFT : return ikShift; + case KEY_LEFTCTRL : + case KEY_RIGHTCTRL : return ikCtrl; + case KEY_LEFTALT : + case KEY_RIGHTALT : return ikAlt; + case KEY_0 : return ik0; + case KEY_1 : return ik1; + case KEY_2 : return ik2; + case KEY_3 : return ik3; + case KEY_4 : return ik4; + case KEY_5 : return ik5; + case KEY_6 : return ik6; + case KEY_7 : return ik7; + case KEY_8 : return ik8; + case KEY_9 : return ik9; + case KEY_A : return ikA; + case KEY_B : return ikB; + case KEY_C : return ikC; + case KEY_D : return ikD; + case KEY_E : return ikE; + case KEY_F : return ikF; + case KEY_G : return ikG; + case KEY_H : return ikH; + case KEY_I : return ikI; + case KEY_J : return ikJ; + case KEY_K : return ikK; + case KEY_L : return ikL; + case KEY_M : return ikM; + case KEY_N : return ikN; + case KEY_O : return ikO; + case KEY_P : return ikP; + case KEY_Q : return ikQ; + case KEY_R : return ikR; + case KEY_S : return ikS; + case KEY_T : return ikT; + case KEY_U : return ikU; + case KEY_V : return ikV; + case KEY_W : return ikW; + case KEY_X : return ikX; + case KEY_Y : return ikY; + case KEY_Z : return ikZ; + // mouse + case BTN_LEFT : return ikMouseL; + case BTN_RIGHT : return ikMouseR; + case BTN_MIDDLE : return ikMouseM; + // gamepad + case KEY_HOMEPAGE : return ikEscape; + case BTN_A : return ikJoyA; + case BTN_B : return ikJoyB; + case BTN_X : return ikJoyX; + case BTN_Y : return ikJoyY; + case BTN_TL : return ikJoyLB; + case BTN_TR : return ikJoyRB; + case BTN_SELECT : return ikJoySelect; + case BTN_START : return ikJoyStart; + case BTN_THUMBL : return ikJoyL; + case BTN_THUMBR : return ikJoyR; + case BTN_TL2 : return ikJoyLT; + case BTN_TR2 : return ikJoyRT; + } + return ikNone; +} + +int inputDevIndex(const char *node) { + const char *str = strstr(node, "/event"); + if (str) + return atoi(str + 6); + return -1; +} + +void inputDevAdd(const char *node) { + int index = inputDevIndex(node); + if (index != -1) { + inputDevices[index] = open(node, O_RDONLY | O_NONBLOCK); + ioctl(inputDevices[index], EVIOCGRAB, 1); + //LOG("input: add %s\n", node); + } +} + +void inputDevRemove(const char *node) { + int index = inputDevIndex(node); + if (index != -1 && inputDevices[index] != -1) { + close(inputDevices[index]); + //LOG("input: remove %s\n", node); + } +} + +bool inputInit() { + joyL = joyR = vec2(0); + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) + inputDevices[i] = -1; + + udevObj = udev_new(); + if (!udevObj) + return false; + + udevMon = udev_monitor_new_from_netlink(udevObj, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(udevMon, "input", NULL); + udev_monitor_enable_receiving(udevMon); + udevMon_fd = udev_monitor_get_fd(udevMon); + + udev_enumerate *e = udev_enumerate_new(udevObj); + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + udev_list_entry *devices = udev_enumerate_get_list_entry(e); + + udev_list_entry *entry; + udev_list_entry_foreach(entry, devices) { + const char *path, *node; + udev_device *device; + + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(udevObj, path); + node = udev_device_get_devnode(device); + + if (node) + inputDevAdd(node); + } + udev_enumerate_unref(e); + + return true; +} + +void inputFree() { + for (int i = 0; i < MAX_INPUT_DEVICES; i++) + if (inputDevices[i] != -1) + close(inputDevices[i]); + udev_monitor_unref(udevMon); + udev_unref(udevObj); +} + +void inputUpdate() { +// get input events + input_event events[16]; + + for (int i = 0; i < MAX_INPUT_DEVICES; i++) { + if (inputDevices[i] == -1) continue; + int rb = read(inputDevices[i], events, sizeof(events)); + + input_event *e = events; + while (rb > 0) { + switch (e->type) { + case EV_KEY : { + InputKey key = codeToInputKey(e->code); + if (key == ikMouseL || key == ikMouseR || key == ikMouseM) + Input::setPos(key, Input::mouse.pos); + Input::setDown(key, e->value != 0); + break; + } + case EV_REL : { + vec2 delta(0); + delta[e->code] = float(e->value); + Input::setPos(ikMouseL, Input::mouse.pos + delta); + break; + } + case EV_ABS : { + float v = float(e->value) / 128.0f - 1.0f; + switch (e->code) { + case ABS_X : joyL.x = v; break; + case ABS_Y : joyL.y = v; break; + case ABS_Z : joyR.x = v; break; + case ABS_RZ : joyR.y = v; break; + } + } + } + //LOG("input: type = %d, code = %d, value = %d\n", int(e->type), int(e->code), int(e->value)); + e++; + rb -= sizeof(events[0]); + } + } + Input::setPos(ikJoyL, joyL); + Input::setPos(ikJoyR, joyR); +// monitoring plug and unplug input devices + fd_set fds; + FD_ZERO(&fds); + FD_SET(udevMon_fd, &fds); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + if (select(udevMon_fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(udevMon_fd, &fds)) { + udev_device *device = udev_monitor_receive_device(udevMon); + if (device) { + const char *node = udev_device_get_devnode(device); + if (node) { + const char *action = udev_device_get_action(device); + if (!strcmp(action, "add")) + inputDevAdd(node); + if (!strcmp(action, "remove")) + inputDevRemove(node); + } + udev_device_unref(device); + } else + LOG("! input: receive_device\n"); + } +} + +char Stream::cacheDir[255]; +char Stream::contentDir[255]; + +int main(int argc, char **argv) { + bcm_host_init(); + + DISPMANX_DISPLAY_HANDLE_T dmDisplay; + EGL_DISPMANX_WINDOW_T dmWindow; + if (!wndInit(dmDisplay, dmWindow)) + return 1; + Core::width = dmWindow.width; + Core::height = dmWindow.height; + + EGLDisplay display; + EGLSurface surface; + EGLContext context; + if (!eglInit(dmWindow, display, context, surface)) { + LOG("! can't initialize EGL context\n"); + return 1; + } + + Stream::contentDir[0] = Stream::cacheDir[0] = 0; + + const char *home; + if (!(home = getenv("HOME"))) + home = getpwuid(getuid())->pw_dir; + strcat(Stream::cacheDir, home); + strcat(Stream::cacheDir, "/.OpenLara/"); + + struct stat st = {0}; + if (stat(Stream::cacheDir, &st) == -1 && mkdir(Stream::cacheDir, 0777) == -1) + Stream::cacheDir[0] = 0; + + timeval t; + gettimeofday(&t, NULL); + startTime = t.tv_sec; + + sndInit(); + + char *lvlName = argc > 1 ? argv[1] : NULL; + char *sndName = argc > 2 ? argv[2] : NULL; + + Game::init(lvlName, sndName); + + inputInit(); // initialize and grab input devices + + int lastTime = getTime(); + + while (!Input::down[ikEscape]) { + inputUpdate(); + + int time = getTime(); + if (time <= lastTime) + continue; + + pthread_mutex_lock(&sndMutex); + Game::update((time - lastTime) * 0.001f); + pthread_mutex_unlock(&sndMutex); + lastTime = time; + + Game::render(); + eglSwapBuffers(display, surface); + }; + + sndFree(); + Game::free(); + + inputFree(); + eglFree(display, surface, context); + wndFree(dmDisplay, dmWindow); + + return 0; +} diff --git a/src/platform/web/audio/dummy b/src/platform/web/audio/dummy new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/web/build.bat b/src/platform/web/build.bat index 91ab70e..b44549c 100644 --- a/src/platform/web/build.bat +++ b/src/platform/web/build.bat @@ -1,9 +1,8 @@ @echo off cls -set SRC=main.cpp +set SRC=main.cpp ../../libs/stb_vorbis/stb_vorbis.c set PROJ=OpenLara set FLAGS=-O3 -Wno-deprecated-register --llvm-opts 2 -fmax-type-align=2 -std=c++11 -Wall -I../../ -set PRELOAD=./LEVEL2.PSX echo. -call em++ %SRC% %FLAGS% -o %PROJ%.js --preload-file %PRELOAD% +call em++ %SRC% %FLAGS% -o %PROJ%.js --preload-file ./level/TITLE.PSX --preload-file ./level/TITLEH.PCX --preload-file ./audio/dummy gzip.exe -9 -f %PROJ%.data %PROJ%.js %PROJ%.js.mem \ No newline at end of file diff --git a/src/platform/web/index.html b/src/platform/web/index.html index 4c29612..6009086 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -23,7 +23,6 @@ -
@@ -89,7 +88,7 @@ proc.connect(ctx.destination); } - var gl = canvasElement.getContext("webgl", {antialias:false}) || canvasElement.getContext("experimental-webgl", {antialias:false}); + var gl = canvasElement.getContext("webgl", {antialias:false, premultipliedAlpha: false}) || canvasElement.getContext("experimental-webgl", {antialias:false, premultipliedAlpha: false}); Module.setStatus('Downloading...'); window.onerror = function(event) { @@ -101,6 +100,16 @@ }; }; + + + + (.PHD, .PSX) +

+ OpenLara on github & facebook
+
last update: 14.09.2017
+

+
+ - - - - - - (.PHD, .PSX) - -

- OpenLara on github & facebook
-

-
- diff --git a/src/platform/web/level/dummy b/src/platform/web/level/dummy new file mode 100644 index 0000000..e69de29 diff --git a/src/platform/web/main.cpp b/src/platform/web/main.cpp index 8d880db..6dd45d6 100644 --- a/src/platform/web/main.cpp +++ b/src/platform/web/main.cpp @@ -17,8 +17,8 @@ extern "C" { Sound::fill(frames, count); } - void EMSCRIPTEN_KEEPALIVE game_level_load(char *data, int size, int home) { - Game::startLevel(new Stream(data, size), NULL, false, home); + void EMSCRIPTEN_KEEPALIVE game_level_load(char *data, int size) { + Game::startLevel(new Stream(data, size)); } } @@ -233,6 +233,39 @@ EM_BOOL mouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userDa return 1; } +const char *IDB = "db"; + +void onError(void *str) { + LOG("! IDB error: %s\n", str); +} + +void onLoad(void *arg, void *data, int size) { + Stream *stream = (Stream*)arg; + + FILE *f = fopen(stream->name, "wb"); + fwrite(data, size, 1, f); + fclose(f); + + stream->callback(new Stream(stream->name), stream->userData); + delete stream; +} + +void onLoadAndStore(void *arg, void *data, int size) { + emscripten_idb_async_store(IDB, ((Stream*)arg)->name, data, size, NULL, NULL, onError); + onLoad(arg, data, size); +} + +void onExists(void *arg, int exists) { + if (exists) + emscripten_idb_async_load(IDB, ((Stream*)arg)->name, arg, onLoad, onError); + else + emscripten_async_wget_data(((Stream*)arg)->name, arg, onLoadAndStore, onError); +} + +void osDownload(Stream *stream) { + emscripten_idb_async_exists(IDB, stream->name, stream, onExists, onError); +} + char Stream::cacheDir[255]; char Stream::contentDir[255]; diff --git a/src/platform/win/OpenLara.sln b/src/platform/win/OpenLara.sln index 3751473..c49f444 100644 --- a/src/platform/win/OpenLara.sln +++ b/src/platform/win/OpenLara.sln @@ -8,15 +8,12 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 - Editor|Win32 = Editor|Win32 Profile|Win32 = Profile|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6935E070-59B8-418A-9241-70BACB4217B5}.Debug|Win32.ActiveCfg = Debug|Win32 {6935E070-59B8-418A-9241-70BACB4217B5}.Debug|Win32.Build.0 = Debug|Win32 - {6935E070-59B8-418A-9241-70BACB4217B5}.Editor|Win32.ActiveCfg = Editor|Win32 - {6935E070-59B8-418A-9241-70BACB4217B5}.Editor|Win32.Build.0 = Editor|Win32 {6935E070-59B8-418A-9241-70BACB4217B5}.Profile|Win32.ActiveCfg = Profile|Win32 {6935E070-59B8-418A-9241-70BACB4217B5}.Profile|Win32.Build.0 = Profile|Win32 {6935E070-59B8-418A-9241-70BACB4217B5}.Release|Win32.ActiveCfg = Release|Win32 diff --git a/src/platform/win/OpenLara.vcxproj.user b/src/platform/win/OpenLara.vcxproj.user index a2075f5..37c9869 100644 --- a/src/platform/win/OpenLara.vcxproj.user +++ b/src/platform/win/OpenLara.vcxproj.user @@ -1,14 +1,10 @@  - - ../../../bin - WindowsLocalDebugger - data/LEVEL3A.PSX - ../../../bin WindowsLocalDebugger - data/LEVEL2.PHD + + ../../../bin @@ -17,6 +13,7 @@ ../../../bin WindowsLocalDebugger - data/LEVEL2.PSX + + \ No newline at end of file diff --git a/src/platform/win/main.cpp b/src/platform/win/main.cpp index f60d9e5..af7e375 100644 --- a/src/platform/win/main.cpp +++ b/src/platform/win/main.cpp @@ -372,8 +372,8 @@ int main(int argc, char** argv) { joyUpdate(); DWORD time = getTime(); - if (time <= lastTime) - continue; + //if (time <= lastTime) + // continue; EnterCriticalSection(&sndCS); Game::update((time - lastTime) * 0.001f); diff --git a/src/shaders/filter.glsl b/src/shaders/filter.glsl index 5b455d7..8f7add5 100644 --- a/src/shaders/filter.glsl +++ b/src/shaders/filter.glsl @@ -6,21 +6,23 @@ R"====( varying vec2 vTexCoord; -uniform int uType; +uniform vec4 uParam; #ifdef VERTEX attribute vec4 aCoord; void main() { - vTexCoord = aCoord.zw; + #ifdef FILTER_DEFAULT + vTexCoord = aCoord.zw * uParam.xy + uParam.zw; + #else + vTexCoord = aCoord.zw; + #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 +31,7 @@ uniform int uType; for (float x = -1.5; x < 2.0; x++) { vec4 p; p.xyz = texture2D(sDiffuse, vTexCoord + vec2(x, y) * k).xyz; - p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114)); + p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114)); p.xyz *= p.w; color += p; } diff --git a/src/shaders/gui.glsl b/src/shaders/gui.glsl index 5988d58..f3fa577 100644 --- a/src/shaders/gui.glsl +++ b/src/shaders/gui.glsl @@ -18,7 +18,7 @@ varying vec4 vColor; void main() { vTexCoord = aTexCoord.xy; - vColor = aColor * uMaterial; + vColor = aColor; gl_Position = uViewProj * vec4(aCoord.xy * uPosScale.zw + uPosScale.xy, 0.0, 1.0); } #else diff --git a/src/shaders/shader.glsl b/src/shaders/shader.glsl index db9b99f..d12b0dd 100644 --- a/src/shaders/shader.glsl +++ b/src/shaders/shader.glsl @@ -14,10 +14,6 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction varying vec2 vCausticsCoord; // - xy caustics texture coord #endif -//uniform vec4 data[MAX_RANGES + MAX_OFFSETS + 4 + 4 + 1 + 1 + MAX_LIGHTS + MAX_LIGHTS + 1 + 6 + 1 + 32 * 2]; - -uniform vec2 uAnimTexRanges[MAX_RANGES]; -uniform vec2 uAnimTexOffsets[MAX_OFFSETS]; uniform mat4 uLightProj; uniform mat4 uViewProj; uniform vec3 uViewPos; @@ -25,9 +21,8 @@ uniform vec4 uParam; // x - time, y - water height, z - clip plane sign, w - cli uniform vec3 uLightPos[MAX_LIGHTS]; uniform vec4 uLightColor[MAX_LIGHTS]; // xyz - color, w - radius * intensity uniform vec4 uRoomSize; // xy - minXZ, zw - maxXZ -uniform vec3 uAmbient[6]; + uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha -uniform vec4 uBasis[32 * 2]; #ifndef PASS_SHADOW varying vec4 vViewVec; // xyz - dir * dist, w - coord.y @@ -45,7 +40,21 @@ uniform vec4 uBasis[32 * 2]; #endif varying vec4 vLight; // lights intensity (MAX_LIGHTS == 4) + #endif +#endif +#ifdef VERTEX + +#ifdef TYPE_ENTITY + #if defined(OPT_AMBIENT) + uniform vec3 uAmbient[6]; + #endif + uniform vec4 uBasis[32 * 2]; +#else + uniform vec4 uBasis[2]; +#endif + +#ifndef PASS_SHADOW #if defined(OPT_AMBIENT) && defined(TYPE_ENTITY) vec3 calcAmbient(vec3 n) { vec3 sqr = n * n; @@ -55,10 +64,13 @@ uniform vec4 uBasis[32 * 2]; sqr.z * mix(uAmbient[5], uAmbient[4], pos.z); } #endif - #endif #endif -#ifdef VERTEX +#if defined(PASS_COMPOSE) && defined(TYPE_ROOM) + uniform vec2 uAnimTexRanges[MAX_RANGES]; + uniform vec2 uAnimTexOffsets[MAX_OFFSETS]; +#endif + attribute vec4 aCoord; attribute vec4 aTexCoord; attribute vec4 aParam; @@ -75,8 +87,8 @@ uniform vec4 uBasis[32 * 2]; return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w); } - vec3 mulBasis(vec4 rot, vec4 pos, vec3 v) { - return mulQuat(rot, v) + pos.xyz; + vec3 mulBasis(vec4 rot, vec3 pos, vec3 v) { + return mulQuat(rot, v) + pos; } vec4 _transform() { @@ -92,9 +104,9 @@ uniform vec4 uBasis[32 * 2]; vec4 coord; coord.w = rBasisPos.w; // visible flag #ifdef TYPE_SPRITE - coord.xyz = mulBasis(rBasisRot, rBasisPos + aCoord.xyz, vec3(aTexCoord.z, -aTexCoord.w, 0.0) * 32767.0); + coord.xyz = mulBasis(rBasisRot, rBasisPos.xyz + aCoord.xyz, vec3(aTexCoord.z, -aTexCoord.w, 0.0) * 32767.0); #else - coord.xyz = mulBasis(rBasisRot, rBasisPos, aCoord.xyz); + coord.xyz = mulBasis(rBasisRot, rBasisPos.xyz, aCoord.xyz); #endif #ifndef PASS_SHADOW @@ -138,7 +150,7 @@ uniform vec4 uBasis[32 * 2]; #endif #ifdef TYPE_MIRROR - vDiffuse.xyz *= vec3(0.3, 0.3, 2.0); // blue color dodge for crystal + vDiffuse.xyz = vec3(0.5, 0.5, 2.0); // blue color dodge for crystal #endif #ifdef TYPE_FLASH @@ -217,11 +229,13 @@ uniform vec4 uBasis[32 * 2]; void _uv(vec3 coord) { vTexCoord = aTexCoord; #if defined(PASS_COMPOSE) && !defined(TYPE_SPRITE) - // animated texture coordinates - vec2 range = uAnimTexRanges[int(aParam.x)]; // x - start index, y - count - float frame = fract((aParam.y + uParam.x * 4.0 - range.x) / range.y) * range.y; - vec2 offset = uAnimTexOffsets[int(range.x + frame)]; // texCoord offset from first frame - vTexCoord.xy += offset; + #ifdef TYPE_ROOM + // animated texture coordinates + vec2 range = uAnimTexRanges[int(aParam.x)]; // x - start index, y - count + float frame = fract((aParam.y + uParam.x * 4.0 - range.x) / range.y) * range.y; + vec2 offset = uAnimTexOffsets[int(range.x + frame)]; // texCoord offset from first frame + vTexCoord.xy += offset; + #endif vTexCoord.xy *= vTexCoord.zw; #endif @@ -464,7 +478,7 @@ uniform vec4 uBasis[32 * 2]; #endif #ifdef TYPE_ENTITY - color.xyz += calcSpecular(n, vViewVec.xyz, vLightVec.xyz, uLightColor[0], uMaterial.z * rShadow + 0.03); + color.xyz += calcSpecular(n, vViewVec.xyz, vLightVec.xyz, uLightColor[0], (uMaterial.z + 0.03) * rShadow); #endif #endif diff --git a/src/sound.h b/src/sound.h index 05e6eab..5994d1f 100644 --- a/src/sound.h +++ b/src/sound.h @@ -8,7 +8,6 @@ #ifdef __EMSCRIPTEN__ // TODO: http streaming #undef DECODE_MP3 - #undef DECODE_OGG #endif #include "utils.h" @@ -29,11 +28,15 @@ namespace Sound { short L, R; }; - namespace Filter { - #define MAX_FDN 32 - #define MAX_DELAY 2048 + struct FrameHI { + int L, R; + }; - static const short FDN[MAX_FDN] = {281,331,373,419,461,503,547,593,641,683,727,769,811,853,907,953,997,1039,1087,1129,1171,1213,1259,1301,1361,1409,1451,1493,1543,1597,1657,1699}; + namespace Filter { + #define MAX_FDN 16 + #define MAX_DELAY 1024 + + static const short FDN[MAX_FDN] = {281,331,373,419,461,503,547,593,641,683,727,769,811,853,907,953}; struct Delay { int index; @@ -83,7 +86,7 @@ namespace Sound { void setRoomSize(const vec3 &size) { float S = (size.x * size.z + size.x * size.y + size.z * size.y) * 2; float V = size.x * size.y * size.z; - float f = 0.07f; // brick absorption + float f = 0.1f; // absorption factor float rt60 = 0.161f * (V / (S * f)); float k = -10.0f / (44100.0f * rt60); @@ -93,22 +96,23 @@ namespace Sound { } }; - void process(Frame *frames, int count) { + void process(FrameHI *frames, int count) { float buffer[MAX_FDN]; for (int i = 0; i < count; i++) { - Frame &frame = frames[i]; - float L = float(frame.L); - float R = float(frame.R); + FrameHI &frame = frames[i]; + float L = frame.L * (1.0f / 32768.0f); + float R = frame.R * (1.0f / 32768.0f); float in = (L + R) * 0.5f; float out = 0.0f; // apply delay & absorption filters for (int j = 0; j < MAX_FDN; j++) { - buffer[j] = in + output[j]; - buffer[j] = df[j].process(buffer[j], FDN[j]); - buffer[j] = af[j].process(buffer[j], absCoeff[j][0], absCoeff[j][1]); - out += buffer[j] * (2.0f / MAX_FDN); + float k = in + output[j]; + k = df[j].process(k, FDN[j]); + k = af[j].process(k, absCoeff[j][0], absCoeff[j][1]); + out += k * (2.0f / MAX_FDN); + buffer[j] = k; } // apply pan @@ -118,8 +122,8 @@ namespace Sound { R += buffer[j] * panCoeff[j][1]; } - frame.L = clamp(int(L), -32768, 32767); - frame.R = clamp(int(R), -32768, 32767); + frame.L = int(L * 32768.0f); + frame.R = int(R * 32768.0f); } } }; @@ -259,16 +263,16 @@ namespace Sound { int s = (s1 * inc[pred] + s2 * dec[pred]) >> 6; s = clamp((value >> shift) + s, -32768, 32767); - s2 = s1; + s2 = s1; s1 = s; } void resample(Frame *frames, short value) { predicate(value); - frames[0].L = frames[0].R = s2 + (s1 - s2) / 4; // 0.25 - frames[1].L = frames[1].R = s2 + (s1 - s2) / 2; // 0.50 - frames[2].L = frames[2].R = s2 + (s1 - s2) * 3 / 4; // 0.75 - frames[3].L = frames[3].R = s1; // 1.00 + frames[0].L = frames[0].R = s2 + (s1 - s2) / 4; // 0.25 + frames[1].L = frames[1].R = s2 + (s1 - s2) / 2; // 0.50 + frames[2].L = frames[2].R = s2 + (s1 - s2) * 3 / 4; // 0.75 + frames[3].L = frames[3].R = s1; // 1.00 } int processBlock() { @@ -401,7 +405,6 @@ namespace Sound { struct Listener { mat4 matrix; - // vec3 velocity; } listener; enum Flags { @@ -411,19 +414,22 @@ namespace Sound { REPLAY = 8, SYNC = 16, STATIC = 32, + MUSIC = 64, }; struct Sample { Decoder *decoder; vec3 pos; - vec3 velocity; float volume; + float volumeTarget; + float volumeDelta; float pitch; 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), pitch(pitch), flags(flags), id(id) { + 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; stream->read(fourcc); if (fourcc == FOURCC("RIFF")) { // wav @@ -472,7 +478,7 @@ namespace Sound { decoder = new VAG(stream); } #endif - + isPlaying = decoder != NULL; ASSERT(isPlaying); } @@ -481,9 +487,22 @@ namespace Sound { delete decoder; } - vec3 getPan() { + 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; + } + + vec2 getPan() { if (!(flags & PAN)) - return vec3(1.0f); + return vec2(1.0f); mat4 m = Sound::listener.matrix; vec3 v = pos - m.offset.xyz; @@ -493,11 +512,12 @@ 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) { int res = decoder->decode(&frames[i], count - i); @@ -510,23 +530,49 @@ namespace Sound { } i += res; } - - vec3 pan = getPan() * volume; - - 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 = pan * volume; + for (int j = 0; j < i; j++) { + if (volumeDelta != 0.0f) { // increase / decrease channel volume + volume += volumeDelta; + if ((volumeDelta < 0.0f && volume < volumeTarget) || + (volumeDelta > 0.0f && volume > volumeTarget)) { + volume = volumeTarget; + volumeDelta = 0.0f; + if (stopAfterFade) + isPlaying = false; + } + vol = pan * 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; + + FrameHI *result; + Frame *buffer; Filter::Reverberation reverb; void init() { channelsCount = 0; + callback = NULL; + buffer = NULL; + result = NULL; #ifdef DECODE_MP3 mp3_decode_init(); #endif @@ -538,33 +584,25 @@ namespace Sound { #ifdef DECODE_MP3 mp3_decode_free(); #endif + delete[] buffer; + delete[] result; } - void fill(Frame *frames, int count) { - if (!channelsCount) { - memset(frames, 0, sizeof(frames[0]) * count); - reverb.process(frames, count); - return; - } - - struct FrameHI { - int L, R; - }; - - FrameHI *result = new FrameHI[count]; - memset(result, 0, sizeof(FrameHI) * count); - + void renderChannels(FrameHI *result, int count, bool music) { int bufSize = count + count / 2; - Frame *buffer = new Frame[bufSize]; // + 50% for pitch + if (!buffer) buffer = new Frame[bufSize]; // + 50% for pitch for (int i = 0; i < channelsCount; i++) { + if (music != ((channels[i]->flags & MUSIC) != 0)) + continue; + if (channels[i]->flags & STATIC) { vec3 d = channels[i]->pos - listener.matrix.getPos(); if (fabsf(d.x) > SND_FADEOFF_DIST || fabsf(d.y) > SND_FADEOFF_DIST || fabsf(d.z) > SND_FADEOFF_DIST) continue; } - if ((channels[i]->flags & LOOP) && channels[i]->volume < EPS) + if ((channels[i]->flags & LOOP) && channels[i]->volume < EPS && channels[i]->volumeTarget < EPS) continue; memset(buffer, 0, sizeof(Frame) * bufSize); @@ -586,19 +624,34 @@ namespace Sound { } } } + } + + void fill(Frame *frames, int count) { + if (!channelsCount) { + memset(frames, 0, sizeof(frames[0]) * count); + //if (Core::settings.audio.reverb) + // reverb.process(frames, count); + return; + } + + if (!result) result = new FrameHI[count]; + memset(result, 0, sizeof(FrameHI) * count); + + renderChannels(result, count, false); + + if (Core::settings.audio.reverb) + reverb.process(result, count); + + renderChannels(result, count, true); for (int i = 0; i < count; i++) { frames[i].L = clamp(result[i].L, -32768, 32767); frames[i].R = clamp(result[i].R, -32768, 32767); } - reverb.process(frames, count); - - delete[] buffer; - delete[] result; - for (int i = 0; i < channelsCount; i++) if (!channels[i]->isPlaying) { + if (callback) callback(channels[i]); delete channels[i]; channels[i] = channels[--channelsCount]; i--; @@ -635,7 +688,7 @@ namespace Sound { channels[i]->pos = pos; channels[i]->pitch = pitch; if (flags & (REPLAY | UNIQUE)) - channels[i]->decoder->replay(); + channels[i]->replay(); delete stream; return channels[i]; } diff --git a/src/sprite.h b/src/sprite.h index 3bdf41e..9b8b0fc 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -13,8 +13,9 @@ struct Sprite : Controller { bool instant; int frame, flag; float time; + vec3 velocity; - Sprite(IGame *game, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(game, entity), instant(instant), flag(frame), time(0.0f) { + Sprite(IGame *game, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(game, entity), instant(instant), flag(frame), time(0), velocity(0) { if (frame >= 0) { // specific frame this->frame = frame; } else if (frame == FRAME_RANDOM) { // random frame @@ -30,6 +31,8 @@ struct Sprite : Controller { if (index > -1) { level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient; level->entities[index].controller = empty ? NULL : new Sprite(game, index, true, frame); + if (level->entities[index].controller) + ((Controller*)level->entities[index].controller)->activate(); } return index; } @@ -55,6 +58,8 @@ struct Sprite : Controller { if (instant && time >= (1.0f / SPRITE_FPS)) remove = true; + pos += velocity * (30.0f * Core::deltaTime); + if (remove) { level->entityRemove(entity); delete this; @@ -62,8 +67,10 @@ struct Sprite : Controller { } virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { + Core::setBlending(bmAlpha); Core::active.shader->setParam(uBasis, Basis(Core::mViewInv.getRot(), pos)); mesh->renderSprite(-(getEntity().modelIndex + 1), frame); + Core::setBlending(bmNone); } }; diff --git a/src/texture.h b/src/texture.h index 0183f05..8f3762a 100644 --- a/src/texture.h +++ b/src/texture.h @@ -11,7 +11,7 @@ struct Texture { Format format; bool cube; - Texture(int width, int height, Format format, bool cube, void *data = NULL, bool filter = true, bool mips = false) : cube(cube) { + Texture(int width, int height, Format format, bool cube = false, void *data = NULL, bool filter = true, bool mips = false) : cube(cube) { if (!Core::support.texNPOT) { width = nextPow2(width); height = nextPow2(height); @@ -84,12 +84,45 @@ struct Texture { { GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }, // SHADOW }; - FormatDesc &desc = formats[format]; - + FormatDesc desc = formats[format]; + +#ifdef __EMSCRIPTEN__ // fucking firefox! + if (format == RGBA_FLOAT) { + if (Core::support.texFloat) { + desc.ifmt = GL_RGBA; + desc.type = GL_FLOAT; + } + } + + if (format == RGBA_HALF) { + if (Core::support.texHalf) { + desc.ifmt = GL_RGBA; + #ifdef MOBILE + desc.type = GL_HALF_FLOAT_OES; + #endif + } + } +#else + if ((format == RGBA_FLOAT && !Core::support.colorFloat) || (format == RGBA_HALF && !Core::support.colorHalf)) { + desc.ifmt = GL_RGBA; + #ifdef MOBILE + if (format == RGBA_HALF) + desc.type = GL_HALF_FLOAT_OES; + #endif + } +#endif + for (int i = 0; i < 6; i++) { glTexImage2D(cube ? (GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) : GL_TEXTURE_2D, 0, desc.ifmt, width, height, 0, desc.fmt, desc.type, data); if (!cube) break; } + + if (mips) { + glGenerateMipmap(target); + if (!cube && filter && Core::support.texAniso) + glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(int(Core::support.texAniso), 8)); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); + } } virtual ~Texture() { @@ -111,6 +144,275 @@ struct Texture { glBindTexture(cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, 0); } } + + static Texture* LoadPCX(Stream &stream) { + 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; + int dw = Core::support.texNPOT ? pcx.width : nextPow2(pcx.width); + int dh = Core::support.texNPOT ? pcx.height : nextPow2(pcx.height); + + uint8 *buffer = new uint8[size + 256 * 3 + dw * dh * 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]; + memset(data, 0, dw * dh * 4); + + // TODO: color bleeding + + Color32 *ptr = data; + i = 0; + for (int y = 0; y < pcx.height; y++) { + for (int x = 0; x < pcx.width; x++) { + Color24 &c = palette[buffer[i++]]; + ptr[x].r = c.r; + ptr[x].g = c.g; + ptr[x].b = c.b; + ptr[x].a = 255; + } + ptr += dw; + } + + Texture *tex = new Texture(dw, dh, Texture::RGBA, false, data); + delete[] buffer; + + return tex; + } +}; + +#define ATLAS_BORDER 8 + +struct Atlas { + typedef void (Callback)(int id, int width, int height, int x, int y, void *userData, void *data); + + struct Node { + Node *child[2]; + short4 rect; + int id; + + Node(short l, short t, short r, short b) : rect(l, t, r, b), id(-1) { + child[0] = child[1] = NULL; + } + + ~Node() { + delete child[0]; + delete child[1]; + } + + Node* insert(const short4 &tile, int id) { + ASSERT(tile.x != 0x7FFF); + + if (child[0] != NULL && child[1] != NULL) { + Node *node = child[0]->insert(tile, id); + if (node != NULL) return node; + return child[1]->insert(tile, id); + } else { + if (this->id != -1) + return NULL; + + int16 w = rect.z - rect.x; + int16 h = rect.w - rect.y; + int16 tx = (tile.z - tile.x) + ATLAS_BORDER * 2; + int16 ty = (tile.w - tile.y) + ATLAS_BORDER * 2; + + if (w < tx || h < ty) + return NULL; + + if (w == tx && h == ty) { + this->id = id; + return this; + } + + int16 dx = w - tx; + int16 dy = h - ty; + + if (dx > dy) { + child[0] = new Node(rect.x, rect.y, rect.x + tx, rect.w); + child[1] = new Node(rect.x + tx, rect.y, rect.z, rect.w); + } else { + child[0] = new Node(rect.x, rect.y, rect.z, rect.y + ty); + child[1] = new Node(rect.x, rect.y + ty, rect.z, rect.w); + } + + return child[0]->insert(tile, id); + } + } + } *root; + + struct Tile { + int id; + short4 uv; + } *tiles; + + int tilesCount; + int size; + int width, height; + void *userData; + Callback *callback; + + Atlas(int maxTiles, void *userData, Callback *callback) : root(NULL), tilesCount(0), size(0), userData(userData), callback(callback) { + tiles = new Tile[maxTiles]; + } + + ~Atlas() { + delete root; + delete[] tiles; + } + + void add(short4 uv, int id) { + for (int i = 0; i < tilesCount; i++) + if (tiles[i].uv == uv) { + uv.x = 0x7FFF; + uv.y = tiles[i].id; + uv.z = uv.w = 0; + break; + } + + tiles[tilesCount].id = id; + tiles[tilesCount].uv = uv; + tilesCount++; + + if (uv.x != 0x7FFF) + size += (uv.z - uv.x + ATLAS_BORDER * 2) * (uv.w - uv.y + ATLAS_BORDER * 2); + } + + bool insertAll(int *indices) { + for (int i = 0; i < tilesCount; i++) { + int idx = indices[i]; + if (tiles[idx].uv.x != 0x7FFF && !root->insert(tiles[idx].uv, tiles[idx].id)) + return false; + } + return true; + } + + Texture* pack() { + width = nextPow2(int(sqrtf(float(size)))); + height = (width * width / 2 > size) ? (width / 2) : width; + // sort + int *indices = new int[tilesCount]; + for (int i = 0; i < tilesCount; i++) + indices[i] = i; + + int n = tilesCount; + bool swapped; + do { + swapped = false; + for (int i = 1; i < n; i++) { + short4 &a = tiles[indices[i - 1]].uv; + short4 &b = tiles[indices[i]].uv; + //if ((a.z - a.x + ATLAS_BORDER * 2) * (a.w - a.y + ATLAS_BORDER * 2) < (b.z - b.x + ATLAS_BORDER * 2) * (b.w - b.y + ATLAS_BORDER * 2)) { + if ((a.z - a.x + ATLAS_BORDER * 2) < (b.z - b.x + ATLAS_BORDER * 2)) { + swap(indices[i - 1], indices[i]); + swapped = true; + } + } + n--; + } while (swapped); + // pack + while (1) { + delete root; + root = new Node(0, 0, width, height); + + if (insertAll(indices)) + break; + + if (width < height) + width *= 2; + else + height *= 2; + } + + delete[] indices; + + uint32 *data = new uint32[width * height]; + memset(data, 0, width * height * sizeof(data[0])); + fill(root, data); + fillInstances(); + + Texture *atlas = new Texture(width, height, Texture::RGBA, false, data, true, true); + + delete[] data; + return atlas; + }; + + void fill(Node *node, void *data) { + if (!node) return; + + if (node->id == -1) { + fill(node->child[0], data); + fill(node->child[1], data); + } else + callback(node->id, width, height, node->rect.x, node->rect.y, userData, data); + } + + void fillInstances() { + for (int i = 0; i < tilesCount; i++) + if (tiles[i].uv.x == 0x7FFF) { + callback(tiles[i].id, width, height, tiles[i].uv.y, 0, userData, NULL); + /* + TR::ObjectTexture &r = level.objectTextures[ref]; + int minXr = min(min(r.texCoord[0].x, r.texCoord[1].x), r.texCoord[2].x); + int minYr = min(min(r.texCoord[0].y, r.texCoord[1].y), r.texCoord[2].y); + + TR::ObjectTexture &t = level.objectTextures[tiles[i].id]; + int minX = min(min(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x); + int maxX = max(max(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x); + int minY = min(min(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y); + int maxY = max(max(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y); + + int cx = minXr - minX; + int cy = minYr - minY; + + for (int i = 0; i < 4; i++) { + if (t.texCoord[i].x == maxX) t.texCoord[i].x++; + if (t.texCoord[i].y == maxY) t.texCoord[i].y++; + t.texCoord[i].x += cx; + t.texCoord[i].y += cy; + } + */ + } + } }; #endif \ No newline at end of file diff --git a/src/trigger.h b/src/trigger.h index 75db608..1c68142 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -3,60 +3,56 @@ #include "core.h" #include "controller.h" +#include "character.h" #include "sprite.h" -struct Trigger : Controller { +struct Switch : Controller { + enum { + STATE_DOWN, + STATE_UP, + }; - bool immediate; - float timer; - int baseState; - - Trigger(IGame *game, int entity, bool immediate) : Controller(game, entity), immediate(immediate), timer(0.0f) { - baseState = state; - getEntity().flags.collision = false; + Switch(IGame *game, int entity) : Controller(game, entity) {} + + bool setTimer(float t) { + if (activeState == asInactive) { + if (state == STATE_DOWN && t > 0.0f) { + timer = t; + activeState = asActive; + } else + deactivate(true); + return true; + } + return false; } - - bool inState() { - return (state != baseState) == (getEntity().flags.active != 0); - } - - virtual bool activate(ActionCommand *cmd) { - if (this->timer != 0.0f || !inState() || actionCommand) return false; - Controller::activate(cmd); - this->timer = cmd->timer; - - getEntity().flags.active ^= 0x1F; - - if (immediate) - activateNext(); - - return true; + + virtual bool activate() { + if (Controller::activate()) { + animation.setState(state == STATE_UP ? STATE_DOWN : STATE_UP); + return true; + } + return false; } virtual void update() { - TR::Entity &entity = getEntity(); - - if (timer > 0.0f) { - timer -= Core::deltaTime; - if (timer <= 0.0f) { - timer = 0.0f; - entity.flags.active ^= 0x1F; - } - } - - if (timer < 0.0f) { - timer += Core::deltaTime; - if (timer >= 0.0f) { - timer = 0.0f; - entity.flags.active ^= 0x1F; - } - } - - if (!inState() && entity.type != TR::Entity::KEY_HOLE_1 && entity.type != TR::Entity::PUZZLE_HOLE_1) - animation.setState(state != baseState ? baseState : (entity.type == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1))); - updateAnimation(true); - updateEntity(); + getEntity().flags.active = TR::ACTIVE; + if (!isActive()) + animation.setState(STATE_UP); + } +}; + +struct Gear : Controller { + enum { + STATE_STATIC, + STATE_ROTATE, + }; + + Gear(IGame *game, int entity) : Controller(game, entity) {} + + virtual void update() { + updateAnimation(true); + animation.setState(isActive() ? STATE_ROTATE : STATE_STATIC); } }; @@ -64,8 +60,9 @@ struct Dart : Controller { vec3 velocity; vec3 dir; bool inWall; // dart starts from wall + bool armed; - Dart(IGame *game, int entity) : Controller(game, entity), inWall(true) { + Dart(IGame *game, int entity) : Controller(game, entity), inWall(true), armed(true) { dir = vec3(sinf(angle.y), 0, cosf(angle.y)); } @@ -73,6 +70,14 @@ struct Dart : Controller { velocity = dir * animation.getSpeed(); pos = pos + velocity * (Core::deltaTime * 30.0f); updateEntity(); + + Controller *lara = (Controller*)level->laraController; + if (armed && collide(lara)) { + Sprite::add(game, TR::Entity::BLOOD, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED); + lara->hit(50.0f, this); + armed = false; + } + TR::Level::FloorInfo info; level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info); if (pos.y > info.floor || pos.y < info.ceiling || !insideRoom(pos, getRoomIndex())) { @@ -90,23 +95,31 @@ struct Dart : Controller { } }; -struct Dartgun : Trigger { - vec3 origin; +struct TrapDartgun : Controller { + enum { + STATE_IDLE, + STATE_FIRE + }; - Dartgun(IGame *game, int entity) : Trigger(game, entity, true), origin(pos) {} - - virtual bool activate(ActionCommand *cmd) { - if (!Trigger::activate(cmd)) + TrapDartgun(IGame *game, int entity) : Controller(game, entity) {} + + virtual bool activate() { + if (!Controller::activate()) return false; + animation.setState(STATE_FIRE); + // add dart (bullet) TR::Entity &entity = getEntity(); vec3 p = pos + vec3(0.0f, -512.0f, 256.0f).rotateY(PI - entity.rotation); int dartIndex = level->entityAdd(TR::Entity::TRAP_DART, entity.room, (int)p.x, (int)p.y, (int)p.z, entity.rotation, entity.intensity); - if (dartIndex > -1) - level->entities[dartIndex].controller = new Dart(game, dartIndex); + if (dartIndex > -1) { + Dart *dart = new Dart(game, dartIndex); + dart->activate(); + level->entities[dartIndex].controller = dart; + } Sprite::add(game, TR::Entity::SMOKE, entity.room, (int)p.x, (int)p.y, (int)p.z); @@ -115,23 +128,77 @@ struct Dartgun : Trigger { return true; } + void virtual update() { + updateAnimation(true); + if (animation.canSetState(STATE_IDLE)) { + animation.setState(STATE_IDLE); + deactivate(); + } + } }; -struct Boulder : Trigger { +#define BOULDER_DAMAGE_GROUND 1000 +#define BOULDER_DAMAGE_AIR 100 - Boulder(IGame *game, int entity) : Trigger(game, entity, true) {} +struct TrapBoulder : Controller { + enum { + STATE_FALL, + STATE_ROLL, + }; + + vec3 velocity; + + TrapBoulder(IGame *game, int entity) : Controller(game, entity), velocity(0) {} virtual void update() { - if (getEntity().flags.active) { - updateAnimation(true); - updateEntity(); + if (activeState != asActive) return; + + TR::Level::FloorInfo info; + level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + + vec3 dir = getDir(); + + if (pos.y >= info.floor - 256) { + pos.y = float(info.floor); + velocity = dir * animation.getSpeed(); + if (state != STATE_ROLL) + animation.setState(STATE_ROLL); + } else { + if (velocity.y == 0.0f) + velocity.y = 10.0f; + velocity.y += GRAVITY * Core::deltaTime; + animation.setState(STATE_FALL); } + + vec3 p = pos; + pos += velocity * (30.0f * Core::deltaTime); + + if (info.roomNext != TR::NO_ROOM) + getEntity().room = info.roomNext; + + vec3 v = pos + getDir() * 512.0f; + level->getFloorInfo(getRoomIndex(), int(v.x), int(v.y), int(v.z), info); + if (pos.y > info.floor) { + pos = p; + deactivate(); + return; + } + + Character *lara = (Character*)level->laraController; + if (lara->health > 0.0f && collide(lara)) { + if (lara->stand == Character::STAND_GROUND) + lara->hit(BOULDER_DAMAGE_GROUND, this, TR::HIT_BOULDER); + if (lara->stand == Character::STAND_AIR) + lara->hit(BOULDER_DAMAGE_AIR * 30.0f * Core::deltaTime, this); + } + + updateAnimation(true); + updateEntity(); } }; // not a trigger struct Block : Controller { - enum { STATE_STAND = 1, STATE_PUSH, @@ -189,6 +256,7 @@ struct Block : Controller { if (!animation.setState(push ? STATE_PUSH : STATE_PULL)) return false; updateFloor(false); + activate(); return true; } @@ -198,21 +266,27 @@ struct Block : Controller { if (state == STATE_STAND) { updateEntity(); updateFloor(true); + deactivate(); } updateLights(); } }; -struct MovingBlock : Trigger { - int lastState; +struct MovingBlock : Controller { + enum { + STATE_BEGIN, + STATE_END, + STATE_MOVE, + }; - MovingBlock(IGame *game, int entity) : Trigger(game, entity, true) { - lastState = state; - updateFloor(true); + MovingBlock(IGame *game, int entity) : Controller(game, entity) { + if (!getEntity().flags.invisible) + updateFloor(true); } void updateFloor(bool rise) { + updateEntity(); TR::Entity &e = getEntity(); TR::Level::FloorInfo info; level->getFloorInfo(e.room, e.x, e.y, e.z, info); @@ -222,123 +296,191 @@ struct MovingBlock : Trigger { TR::Room::Sector &s = level->getSector(e.room, e.x, e.z, dx, dz); s.floor += rise ? -8 : 8; } - - virtual void updateAnimation(bool commands) { - Trigger::updateAnimation(commands); - if (state != lastState) { - switch (lastState = state) { - case 0 : - case 1 : updateFloor(true); break; - case 2 : updateFloor(false); break; + virtual void update() { + updateAnimation(true); + + if (isActive()) { + if (state == STATE_BEGIN) { + updateFloor(false); + animation.setState(STATE_END); } + } else { + if (state == STATE_END) { + updateFloor(false); + animation.setState(STATE_BEGIN); + } + } + + if (activeState == asInactive) { + if (getEntity().flags.active == TR::ACTIVE) + activeState = asActive; // stay in active items list + pos.x = int(pos.x / 1024.0f) * 1024.0f + 512.0f; + pos.z = int(pos.z / 1024.0f) * 1024.0f + 512.0f; + updateFloor(true); + return; } pos += getDir() * (animation.getSpeed() * Core::deltaTime * 30.0f); } }; -struct Door : Trigger { - int8 *floor[2], orig[2]; - uint16 box; - Door(IGame *game, int entity) : Trigger(game, entity, true) { - TR::Entity &e = getEntity(); - TR::Level::FloorInfo info; - vec3 p = pos - getDir() * 1024.0f; +struct Door : Controller { + enum { + STATE_CLOSE, + STATE_OPEN, + }; - level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info); - box = info.boxIndex; + struct BlockInfo { + int roomIndex[2]; + int sectorIndex[2]; + TR::Room::Sector sectors[2]; - int dx, dz; - TR::Room::Sector *s = &level->getSector(e.room, (int)p.x, (int)p.z, dx, dz); + BlockInfo() {} + BlockInfo(TR::Level *level, int room, int nx, int nz, int x, int z, bool flip) { + // front + roomIndex[0] = room; + roomIndex[1] = TR::NO_ROOM; - orig[0] = *(floor[0] = &s->floor); + if (roomIndex[0] == TR::NO_ROOM) + return; + if (flip && level->rooms[roomIndex[0]].alternateRoom != -1) + roomIndex[0] = level->rooms[roomIndex[0]].alternateRoom; - if (info.roomNext != 0xFF) { - s = &level->getSector(info.roomNext, e.x, e.z, dx, dz); - orig[1] = *(floor[1] = &s->floor); - } else - floor[1] = NULL; + sectors[0] = level->getSector(roomIndex[0], x, z, sectorIndex[0]); - updateBlock(); - } + // behind + roomIndex[1] = level->getNextRoom(sectors[0].floorIndex); - void updateBlock() { - int8 v[2]; - if (getEntity().flags.active) { - v[0] = orig[0]; - v[1] = orig[1]; - } else - v[0] = v[1] = TR::FLOOR_BLOCK; + if (roomIndex[1] == TR::NO_ROOM) + return; + if (flip && level->rooms[roomIndex[1]].alternateRoom != -1) + roomIndex[1] = level->rooms[roomIndex[1]].alternateRoom; - if (box != 0xFFFF) { - TR::Box &b = level->boxes[box]; - if (b.overlap.blockable) - b.overlap.block = !getEntity().flags.active; + sectors[1] = level->getSector(roomIndex[1], nx, nz, sectorIndex[1]); } - if (floor[0]) *floor[0] = v[0]; - if (floor[1]) *floor[1] = v[1]; + void set(TR::Level *level) { + for (int i = 0; i < 2; i++) + if (roomIndex[i] != TR::NO_ROOM) { + TR::Room::Sector &s = level->rooms[roomIndex[i]].sectors[sectorIndex[i]]; + s.floorIndex = 0; + s.boxIndex = TR::NO_BOX; + s.roomBelow = TR::NO_ROOM; + s.floor = TR::NO_FLOOR; + s.roomAbove = TR::NO_ROOM; + s.ceiling = TR::NO_FLOOR; + + if (sectors[i].boxIndex != TR::NO_BOX) { + TR::Box &box = level->boxes[sectors[i].boxIndex]; + if (box.overlap.blockable) + box.overlap.block = true; + } + } + } + + void reset(TR::Level *level) { + for (int i = 0; i < 2; i++) + if (roomIndex[i] != TR::NO_ROOM) { + level->rooms[roomIndex[i]].sectors[sectorIndex[i]] = sectors[i]; + if (sectors[i].boxIndex != TR::NO_BOX) { + TR::Box &box = level->boxes[sectors[i].boxIndex]; + if (box.overlap.blockable) + box.overlap.block = false; + } + } + } + + } block[2]; + + Door(IGame *game, int entity) : Controller(game, entity) { + TR::Entity &e = getEntity(); + vec3 p = pos - getDir() * 1024.0f; + block[0] = BlockInfo(level, e.room, e.x, e.z, int(p.x), int(p.z), false); + block[1] = BlockInfo(level, e.room, e.x, e.z, int(p.x), int(p.z), true); + updateBlock(false); } - virtual bool activate(ActionCommand *cmd) { - bool res = Trigger::activate(cmd); - updateBlock(); - return res; + void updateBlock(bool open) { + if (open) { + block[0].reset(level); + block[1].reset(level); + } else { + block[0].set(level); + block[1].set(level); + } + } + + virtual void update() { + updateAnimation(true); + int targetState = isActive() ? STATE_OPEN : STATE_CLOSE; + + if (state == targetState) + updateBlock(targetState == STATE_OPEN); + else + animation.setState(targetState); } }; -struct TrapDoor : Trigger { +struct TrapDoor : Controller { + enum { + STATE_CLOSE, + STATE_OPEN, + }; - TrapDoor(IGame *game, int entity) : Trigger(game, entity, true) { + TrapDoor(IGame *game, int entity) : Controller(game, entity) { getEntity().flags.collision = true; } + + virtual void update() { + updateAnimation(true); + int targetState = isActive() ? STATE_OPEN : STATE_CLOSE; - virtual bool activate(ActionCommand *cmd) { - bool res = Trigger::activate(cmd); - getEntity().flags.collision = !getEntity().flags.active; - return res; + if (state == targetState) + getEntity().flags.collision = targetState == STATE_CLOSE; + else + animation.setState(targetState); } - }; -struct TrapFloor : Trigger { - +struct TrapFloor : Controller { enum { STATE_STATIC, STATE_SHAKE, STATE_FALL, STATE_DOWN, }; - float velocity; + float speed; - TrapFloor(IGame *game, int entity) : Trigger(game, entity, true), velocity(0) { - TR::Entity &e = getEntity(); - e.flags.collision = true; + TrapFloor(IGame *game, int entity) : Controller(game, entity), speed(0) { + getEntity().flags.collision = true; } - virtual bool activate(ActionCommand *cmd) { - TR::Entity &e = level->entities[cmd->emitter]; - if (e.type != TR::Entity::LARA) return true; - int ey = (int)pos.y - 512; // real floor object position - return (abs(e.y - ey) <= 8) ? Trigger::activate(cmd) : true; + virtual bool activate() { + if (state != STATE_STATIC) return false; + TR::Entity &e = ((Controller*)level->laraController)->getEntity(); + int ey = getEntity().y - 512; // real floor object position + if (abs(e.y - ey) <= 8 && Controller::activate()) { + animation.setState(STATE_SHAKE); + return true; + } + return false; } virtual void update() { - Trigger::update(); + updateAnimation(true); if (state == STATE_FALL) { - TR::Entity &e = getEntity(); - e.flags.collision = false; - velocity += GRAVITY * 30 * Core::deltaTime; - pos.y += velocity * Core::deltaTime; + getEntity().flags.collision = false; + speed += GRAVITY * 30 * Core::deltaTime; + pos.y += speed * Core::deltaTime; TR::Level::FloorInfo info; - level->getFloorInfo(e.room, e.x, (int)pos.y, e.z, info); + level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); if (pos.y > info.roomFloor && info.roomBelow != 0xFF) - e.room = info.roomBelow; + getEntity().room = info.roomBelow; if (pos.y > info.floor) { pos.y = (float)info.floor; @@ -349,10 +491,8 @@ struct TrapFloor : Trigger { } }; - -struct Bridge : Trigger { - - Bridge(IGame *game, int entity) : Trigger(game, entity, true) { +struct Bridge : Controller { + Bridge(IGame *game, int entity) : Controller(game, entity) { getEntity().flags.collision = true; } }; @@ -362,12 +502,17 @@ struct Crystal : Controller { Crystal(IGame *game, int entity) : Controller(game, entity) { environment = new Texture(64, 64, Texture::RGBA, true); + activate(); } virtual ~Crystal() { delete environment; } + virtual void update() { + updateAnimation(false); + } + virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { Shader *sh = Core::active.shader; sh->setParam(uMaterial, vec4(1.0f)); @@ -376,23 +521,145 @@ struct Crystal : Controller { } }; -struct Waterfall : Trigger { +#define BLADE_DAMAGE 100 +#define BLADE_RANGE 1024 + +struct TrapBlade : Controller { + enum { + STATE_STATIC = 0, + STATE_SWING = 2, + }; + + TrapBlade(IGame *game, int entity) : Controller(game, entity) {} + + virtual void update() { + updateAnimation(true); + + if (isActive()) { + if (state == STATE_STATIC) + animation.setState(STATE_SWING); + } else { + if (state == STATE_SWING) + animation.setState(STATE_STATIC); + } + + if (state != STATE_SWING) + return; + + int f = animation.frameIndex; + if ((f <= 8 || f >= 20) && (f <= 42 || f >= 57)) + return; + + Character* lara = (Character*)level->laraController; + if (!checkRange(lara, BLADE_RANGE) || !collide(lara)) + return; + + lara->hit(BLADE_DAMAGE * 30.0f * Core::deltaTime, this, TR::HIT_BLADE); + } +}; + +#define SPIKES_DAMAGE_FALL 1000 +#define SPIKES_DAMAGE_RUN 15 +#define SPIKES_RANGE 1024 + +struct TrapSpikes : Controller { + TrapSpikes(IGame *game, int entity) : Controller(game, entity) { + activate(); + } + + virtual void update() { + Character *lara = (Character*)level->laraController; + if (lara->health <= 0.0f) return; + + if (!checkRange(lara, SPIKES_RANGE) || !collide(lara)) + return; + + if (lara->stand != Character::STAND_AIR || lara->velocity.y <= 0.0f || (pos.y - lara->pos.y) > 256.0f) { + if (lara->speed < 30.0f) + return; + lara->hit(SPIKES_DAMAGE_RUN * 30.0f * Core::deltaTime, this, TR::HIT_SPIKES); + } else + lara->hit(SPIKES_DAMAGE_FALL, this, TR::HIT_SPIKES); + } +}; + +struct TrapCeiling : Controller { + enum { + STATE_STATIC, + STATE_FALL, + STATE_DOWN, + }; + + float speed; + + TrapCeiling(IGame *game, int entity) : Controller(game, entity), speed(0) {} + + virtual void update() { + updateAnimation(true); + + if (state == STATE_STATIC) + animation.setState(STATE_FALL); + + if (state == STATE_FALL) { + speed += GRAVITY * 30 * Core::deltaTime; + pos.y += speed * Core::deltaTime; + + TR::Level::FloorInfo info; + level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info); + + if (pos.y > info.roomFloor && info.roomBelow != 0xFF) + getEntity().room = info.roomBelow; + + if (pos.y > info.floor) { + pos.y = (float)info.floor; + animation.setState(STATE_DOWN); + } + updateEntity(); + + Controller *lara = (Controller*)level->laraController; + if (collide(lara)) + lara->hit(1000); + } + } +}; + + +struct TrapSword : Controller { + TrapSword(IGame *game, int entity) : Controller(game, entity) {} + + virtual void update() { + updateAnimation(true); + } +}; + +struct KeyHole : Controller { + KeyHole(IGame *game, int entity) : Controller(game, entity) {} + + virtual bool activate() { + if (!Controller::activate()) return false; + getEntity().flags.active = TR::ACTIVE; + if (getEntity().isPuzzleHole()) { + int doneIdx = TR::Entity::convToInv(TR::Entity::getItemForHole(getEntity().type)) - TR::Entity::INV_PUZZLE_1; + meshSwap(0, level->extra.puzzleDone[doneIdx]); + } + deactivate(); + return true; + } + + virtual void update() {} +}; + +struct Waterfall : Controller { #define SPLASH_TIMESTEP (1.0f / 30.0f) float timer; - bool drop; - float dropRadius; - float dropStrength; - vec3 dropPos; - Waterfall(IGame *game, int entity) : Trigger(game, entity, true), timer(0.0f) {} + Waterfall(IGame *game, int entity) : Controller(game, entity), timer(0.0f) {} virtual void update() { - drop = false; - Trigger::update(); - if (!getEntity().flags.active) return; + updateAnimation(true); - vec3 delta = (((Controller*)level->cameraController)->pos - pos) * (1.0f / 1024.0f); + vec3 delta = (((ICamera*)level->cameraController)->pos - pos) * (1.0f / 1024.0f); if (delta.length2() > 100.0f) return; @@ -400,12 +667,12 @@ struct Waterfall : Trigger { if (timer > 0.0f) return; timer += SPLASH_TIMESTEP * (1.0f + randf() * 0.25f); - drop = true; - dropRadius = randf() * 128.0f + 128.0f; - dropStrength = randf() * 0.1f + 0.05f; + float dropRadius = randf() * 128.0f + 128.0f; + float dropStrength = randf() * 0.1f + 0.05f; vec2 p = (vec2(randf(), randf()) * 2.0f - 1.0f) * (512.0f - dropRadius); - dropPos = pos + vec3(p.x, 0.0f, p.y); + vec3 dropPos = pos + vec3(p.x, 0.0f, p.y); + game->waterDrop(dropPos, dropRadius, dropStrength); Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)dropPos.x, (int)dropPos.y, (int)dropPos.z); } @@ -429,6 +696,7 @@ struct Bubble : Sprite { room = s.roomAbove; } time -= (e.y - h) / speed - (1.0f / SPRITE_FPS); + activate(); } virtual ~Bubble() { @@ -439,8 +707,8 @@ struct Bubble : Sprite { pos.y -= speed * Core::deltaTime; angle.x += 30.0f * 13.0f * DEG2RAD * Core::deltaTime; angle.y += 30.0f * 9.0f * DEG2RAD * Core::deltaTime; - pos.x += sinf(angle.y) * 11.0f * 30.0f * Core::deltaTime; - pos.z += cosf(angle.x) * 8.0f * 30.0f * Core::deltaTime; + pos.x += sinf(angle.y) * (11.0f * 30.0f * Core::deltaTime); + pos.z += cosf(angle.x) * (8.0f * 30.0f * Core::deltaTime); updateEntity(); Sprite::update(); } diff --git a/src/ui.h b/src/ui.h index 5b78fcb..7f8a070 100644 --- a/src/ui.h +++ b/src/ui.h @@ -4,6 +4,125 @@ #include "core.h" #include "controller.h" +enum StringID { + STR_NOT_IMPLEMENTED +// help + , STR_LOADING + , STR_HELP_PRESS + , STR_HELP_TEXT +// inventory pages + , STR_OPTION + , STR_INVENTORY + , STR_ITEMS +// inventory option + , STR_GAME + , STR_MAP + , STR_COMPASS + , STR_HOME + , STR_DETAIL + , STR_SOUND + , STR_CONTROLS + , STR_GAMMA +// passport menu + , STR_AUTOSAVE + , STR_LOAD_GAME + , STR_START_GAME + , STR_RESTART_LEVEL + , STR_EXIT_TO_TITLE + , STR_EXIT_GAME + , STR_SELECT_LEVEL +// inventory items + , STR_UNKNOWN + , STR_PISTOLS + , STR_SHOTGUN + , STR_MAGNUMS + , STR_UZIS + , STR_AMMO_PISTOLS + , STR_AMMO_SHOTGUN + , STR_AMMO_MAGNUMS + , STR_AMMO_UZIS + , STR_MEDI_SMALL + , STR_MEDI_BIG + , STR_PUZZLE + , STR_KEY + , STR_LEAD_BAR + , STR_SCION + , STR_MAX +}; + +const char *helpText = + "Controls gamepad, touch and keyboard:@" + " H - Show or hide this help@" + " TAB - Inventory@" + " LEFT - Left@" + " RIGHT - Right@" + " UP - Run@" + " DOWN - Back@" + " SHIFT - Walk@" + " SPACE - Draw Weapon@" + " CTRL - Action@" + " D - Jump@" + " Z - Step Left@" + " X - Step Right@" + " A - Roll@" + " C - Look # not implemented #@" + " V - First Person View@" + " R - slow motion@" + " T - fast motion@" + " ALT + ENTER - Fullscreen@@" + "Actions:@" + " Out of water - Run + Action@" + " Handstand - Run + Walk@" + " Swan dive - Run + Walk + jump@" + " DOZY on - Look + Step Right + Action + Jump@" + " DOZY off - Walk@"; + + +const char *STR[STR_MAX] = { + "Not implemented yet!" +// help + , "Loading..." + , "Press H for help" + , helpText +// inventory pages + , "OPTION" + , "INVENTORY" + , "ITEMS" +// inventory option + , "Game" + , "Map" + , "Compass" + , "Lara's Home" + , "Detail Levels" + , "Sound" + , "Controls" + , "Gamma" +// passport options + , "Autosave" + , "Load Game" + , "Start Game" + , "Restart Level" + , "Exit to Title" + , "Exit Game" + , "Select Level" +// inventory items + , "Unknown" + , "Pistols" + , "Shotgun" + , "Magnums" + , "Uzis" + , "Pistol Clips" + , "Shotgun Shells" + , "Magnum Clips" + , "Uzi Clips" + , "Small Medi Pack" + , "Large Medi Pack" + , "Puzzle" + , "Key" + , "Lead Bar" + , "Scion" +}; + namespace UI { IGame *game; float width; @@ -48,6 +167,13 @@ namespace UI { #define MAX_CHARS DYN_MESH_QUADS + enum BarType { + BAR_HEALTH, + BAR_OXYGEN, + BAR_OPTION, + BAR_MAX, + }; + struct { Vertex vertices[MAX_CHARS * 4]; Index indices[MAX_CHARS * 6]; @@ -129,13 +255,36 @@ namespace UI { } } + void textOut(const vec2 &pos, StringID str, Align align = aLeft, float width = 0) { + textOut(pos, STR[str], align, width); + } + #undef MAX_CHARS +/* + Texture *texInv, *texAction; - + Texture* loadRAW(int width, int height, const char *name) { + FILE *f = fopen(name, "rb"); + ASSERT(f); + uint8 *data = new uint8[width * height * 4]; + fread(data, 1, width * height * 4, f); + fclose(f); + Texture *tex = new Texture(width, height, Texture::RGBA, false, data); + delete[] data; + return tex; + } +*/ void init(IGame *game) { UI::game = game; showHelp = false; helpTipTime = 5.0f; +// texInv = loadRAW(64, 64, "btn_inv.raw"); +// texAction = loadRAW(64, 64, "btn_action.raw"); + } + + void free() { +// delete texInv; +// delete texAction; } void update() { @@ -185,48 +334,23 @@ namespace UI { Core::setDepthTest(true); } - void renderBar(int type, const vec2 &pos, const vec2 &size, float value) { + void renderBar(BarType type, const vec2 &pos, const vec2 &size, float value, uint32 fgColor = 0xFFFFFFFF, uint32 bgColor = 0x80000000, uint32 brColor1 = 0xFF4C504C, uint32 brColor2 = 0xFF748474) { MeshBuilder *mesh = game->getMesh(); - mesh->addFrame(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, pos - 2.0f, size + 4.0f, 0xFF4C504C, 0xFF748474); - mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, type, pos - 1.0f, size + 2.0f, 0x80000000); - if (value > 0.0f) - mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, type, pos, vec2(size.x * value, size.y), 0xFFFFFFFF); + if (brColor1 != 0 || brColor2 != 0) + mesh->addFrame(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, pos - 2.0f, size + 4.0f, brColor1, brColor2); + if (bgColor != 0) + mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, whiteTile, pos - 1.0f, size + 2.0f, bgColor); + if (fgColor != 0 && value > 0.0f) + mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, barTile[type], pos, vec2(size.x * value, size.y), fgColor); } - const char *helpText = - "Controls gamepad, touch and keyboard:@" - " H - Show or hide this help@" - " TAB - Inventory@" - " LEFT - Left@" - " RIGHT - Right@" - " UP - Run@" - " DOWN - Back@" - " SHIFT - Walk@" - " SPACE - Draw Weapon@" - " CTRL - Action@" - " D - Jump@" - " Z - Step Left@" - " X - Step Right@" - " A - Roll@" - " C - Look # not implemented #@" - " V - First Person View@" - " R - slow motion@" - " T - fast motion@" - " ALT + ENTER - Fullscreen@@" - "Actions:@" - " Out of water - Run + Action@" - " Handstand - Run + Walk@" - " Swan dive - Run + Walk + jump@" - " DOZY on - Look + Step Right + Action + Jump@" - " DOZY off - Walk@"; - void renderHelp() { if (showHelp) - textOut(vec2(0, 64), helpText, aRight, width - 32); + textOut(vec2(0, 64), STR_HELP_TEXT, aRight, width - 32); else if (helpTipTime > 0.0f) - textOut(vec2(0, 480 - 32), "Press H for help", aCenter, width); + textOut(vec2(0, 480 - 32), STR_HELP_PRESS, aCenter, width); } }; diff --git a/src/utils.h b/src/utils.h index a8ce1cd..0012fcf 100644 --- a/src/utils.h +++ b/src/utils.h @@ -13,7 +13,7 @@ #define debugBreak() _asm { int 3 } #endif - #define ASSERT(expr) if (expr) {} else { LOG("ASSERT %s in %s:%d\n", #expr, __FILE__, __LINE__); debugBreak(); } + #define ASSERT(expr) if (expr) {} else { LOG("ASSERT:\n %s:%d\n %s => %s\n", __FILE__, __LINE__, __FUNCTION__, #expr); debugBreak(); } #ifndef ANDROID #define LOG(...) printf(__VA_ARGS__) @@ -21,11 +21,12 @@ #else #define ASSERT(expr) -// #ifdef PROFILE + #ifdef LINUX + #define LOG(...) printf(__VA_ARGS__); fflush(stdout) + #else #define LOG(...) printf(__VA_ARGS__) -// #else -// #define LOG(...) 0 -// #endif + // #define LOG(...) 0 + #endif #endif #ifdef ANDROID @@ -727,9 +728,15 @@ struct short3 { struct short4 { int16 x, y, z, w; + short4() {} + short4(int16 x, int16 y, int16 z, int16 w) : x(x), y(y), z(z), w(w) {} + operator vec3() const { return vec3((float)x, (float)y, (float)z); }; operator short3() const { return *((short3*)this); } + inline bool operator == (const short4 &v) const { return x == v.x && y == v.y && z == v.z && w == v.w; } + inline bool operator != (const short4 &v) const { return !(*this == v); } + inline int16& operator [] (int index) const { ASSERT(index >= 0 && index <= 3); return ((int16*)this)[index]; } }; @@ -960,14 +967,19 @@ struct Stream { static char cacheDir[255]; static char contentDir[255]; + typedef void (Callback)(Stream *stream, void *userData); + Callback *callback; + void *userData; + FILE *f; - const char *data; + char *data; int size, pos; + char *name; - Stream(const void *data, int size) : f(NULL), data((char*)data), size(size), pos(0) {} + Stream(const void *data, int size) : callback(NULL), userData(NULL), f(NULL), data((char*)data), size(size), pos(0), name(NULL) {} - Stream(const char *name) : data(NULL), size(-1), pos(0) { - if (contentDir[0]) { + Stream(const char *name, Callback *callback = NULL, void *userData = NULL) : callback(callback), userData(userData), data(NULL), size(-1), pos(0), name(NULL) { + if (contentDir[0] && (!cacheDir[0] || !strstr(name, cacheDir))) { char path[255]; path[0] = 0; strcat(path, contentDir); @@ -976,15 +988,35 @@ struct Stream { } else f = fopen(name, "rb"); - if (!f) LOG("error loading file \"%s\"\n", name); - ASSERT(f != NULL); + if (!f) { + #ifdef __EMSCRIPTEN__ + this->name = new char[64]; + strcpy(this->name, name); - fseek(f, 0, SEEK_END); - size = ftell(f); - fseek(f, 0, SEEK_SET); + extern void osDownload(Stream *stream); + osDownload(this); + return; + #else + LOG("error loading file \"%s\"\n", name); + if (callback) { + callback(NULL, userData); + return; + } else { + ASSERT(false); + } + #endif + } else { + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (callback) + callback(this, userData); + } } ~Stream() { + delete[] name; if (f) fclose(f); }