diff --git a/src/camera.h b/src/camera.h index 6ab658b..23d8b92 100644 --- a/src/camera.h +++ b/src/camera.h @@ -60,7 +60,7 @@ struct Camera : ICamera { } virtual int getRoomIndex() const { - return room; + return (level->isFlipped && level->rooms[room].alternateRoom > -1) ? level->rooms[room].alternateRoom : room; } virtual void checkRoom() { diff --git a/src/format.h b/src/format.h index afd8886..4ab8246 100644 --- a/src/format.h +++ b/src/format.h @@ -764,7 +764,7 @@ namespace TR { opaque = true; if (type == SWITCH || type == SWITCH_WATER) opaque = true; - if (type == PUZZLE_HOLE_1) // LEVEL3A cogs + if (type == PUZZLE_HOLE_1 || type == LIGHTNING) // LEVEL3A cogs opaque = false; } }; diff --git a/src/lara.h b/src/lara.h index 72e4c4d..eb6e97e 100644 --- a/src/lara.h +++ b/src/lara.h @@ -228,6 +228,7 @@ struct Lara : Character { TR::Entity::Type usedKey; TR::Entity *pickupEntity; KeyHole *keyHole; + Lightning *lightning; int roomPrev; // water out from room vec2 rotFactor; @@ -410,6 +411,9 @@ struct Lara : Character { damageTime = LARA_DAMAGE_TIME; hitTime = 0.0f; + keyHole = NULL; + lightning = NULL; + getEntity().flags.active = 1; initMeshOverrides(); @@ -1341,10 +1345,11 @@ struct Lara : Character { Character::hit(damage, enemy, hitType); switch (hitType) { - case TR::HIT_BLADE : addBloodBlade(); break; - case TR::HIT_SPIKES : addBloodSpikes(); break; - case TR::HIT_SLAM : addBloodSlam(enemy); break; - default : ; + case TR::HIT_BLADE : addBloodBlade(); break; + case TR::HIT_SPIKES : addBloodSpikes(); break; + case TR::HIT_SLAM : addBloodSlam(enemy); break; + case TR::HIT_LIGHTNING : lightning = (Lightning*)enemy; break; + default : ; } if (health > 0.0f) @@ -2321,8 +2326,17 @@ struct Lara : Character { if (Input::state[cWeapon]) input |= WEAPON; // scion debug (TODO: remove) - if (Input::down[ikP] && level->id == TR::LEVEL_3B) - reset(5, vec3(73394, 3840, 60758), 0); // level 3b (scion) + if (Input::down[ikP]) { + switch (level->id) { + case TR::LEVEL_3B : + reset(5, vec3(73394, 3840, 60758), 0); // level 3b (scion) + break; + case TR::LEVEL_4 : + reset(18, vec3(34914, 11008, 41315), 90 * DEG2RAD); // main hall + break; + default : game->playSound(TR::SND_NO, pos, Sound::PAN); + } + } // analog control rotFactor = vec2(1.0f); @@ -2470,7 +2484,7 @@ struct Lara : Character { switch (stand) { case STAND_AIR : velocity.y += (velocity.y >= 128.0f ? 30.0f : GRAVITY) * Core::deltaTime; - if (velocity.y >= 154.0f) + if (velocity.y >= 154.0f && state == STATE_FALL) game->playSound(TR::SND_SCREAM, pos, Sound::PAN); /* if (state == STATE_FALL || state == STATE_FAST_DIVE) { @@ -2636,7 +2650,14 @@ struct Lara : Character { } }; - hitDir = -1; + if (lightning && lightning->flash && !lightning->armed) { + if (hitDir == -1) + hitTime = 0.0f; + hitDir = int(randf() * 4); + } else { + hitDir = -1; + lightning = NULL; + } return false; } diff --git a/src/level.h b/src/level.h index 9e07464..eab4d9b 100644 --- a/src/level.h +++ b/src/level.h @@ -504,6 +504,8 @@ struct Level : IGame { static void fillCallback(int id, int width, int height, int tileX, int tileY, void *userData, void *data) { static const uint32 barColor[UI::BAR_MAX][25] = { + // flash bar + { 0x00000000, 0xFFA20058, 0xFFFFFFFF, 0xFFA20058, 0x00000000 }, // health bar { 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F }, // oxygen bar @@ -548,6 +550,7 @@ struct Level : IGame { uvCount = 4; switch (id) { + case UI::BAR_FLASH : case UI::BAR_HEALTH : case UI::BAR_OXYGEN : case UI::BAR_OPTION : @@ -661,14 +664,10 @@ struct Level : IGame { 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++); + // add common textures + const short2 bar[UI::BAR_MAX] = { {0, 4}, {0, 4}, {0, 4}, {4, 4}, {0, 0} }; + for (int i = 0; i < UI::BAR_MAX; i++) + tiles->add(short4(i * 32, 4096, i * 32 + bar[i].x, 4096 + bar[i].y), texIdx++); // get result texture atlas = tiles->pack(); @@ -993,10 +992,19 @@ struct Level : IGame { setupBinding(); } + // TODO: opqque/transparent pass for rooms and entities + void renderEntities(bool opaque) { + for (int i = 0; i < level.entitiesCount; i++) { + int modelIndex = level.entities[i].modelIndex; + if ((modelIndex < 0 && !opaque) || (modelIndex > 0 && mesh->models[modelIndex - 1].opaque == opaque)) + renderEntity(level.entities[i]); + } + } + void renderEntities() { PROFILE_MARKER("ENTITIES"); - for (int i = 0; i < level.entitiesCount; i++) - renderEntity(level.entities[i]); + renderEntities(true); + renderEntities(false); for (int i = 0; i < level.entitiesCount; i++) { TR::Entity &entity = level.entities[i]; diff --git a/src/mesh.h b/src/mesh.h index b07f7e9..b905767 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -5,8 +5,8 @@ #include "format.h" -TR::ObjectTexture barTile[4 /* UI::BAR_MAX */]; -TR::ObjectTexture &whiteTile = barTile[3]; +TR::ObjectTexture barTile[5 /* UI::BAR_MAX */]; +TR::ObjectTexture &whiteTile = barTile[4]; // BAR_WHITE struct MeshRange { int iStart; diff --git a/src/trigger.h b/src/trigger.h index 987bd14..2130d2d 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -353,7 +353,7 @@ struct Block : Controller { pos.y += Core::deltaTime * velocity * 30.0f; if (pos.y >= info.floor) { velocity = 0.0f; - pos.y = info.floor; + pos.y = float(info.floor); game->setEffect(this, TR::Effect::FLOOR_SHAKE); game->playSound(TR::SND_BOULDER, pos, Sound::PAN); deactivate(true); @@ -839,7 +839,7 @@ struct ThorHammer : Controller { #define LIGHTNING_DAMAGE 400 struct Lightning : Controller { - Basis target; + vec3 target; float timer; bool flash; bool armed; @@ -850,6 +850,8 @@ struct Lightning : Controller { if (isActive()) { timer -= Core::deltaTime; + Character *lara = (Character*)level->laraController; + if (timer <= 0.0f) { if (flash) { level->isFlipped = false; @@ -861,17 +863,15 @@ struct Lightning : Controller { flash = true; timer = 20.0f / 30.0f; - Character *lara = (Character*)level->laraController; - bool hasTargets = getModel()->mCount > 1; // LEVEL4 has, LEVEL10C not if ((lara->pos - pos).length() < (hasTargets ? 2560.0f : 1024.0f)) { lara->hit(LIGHTNING_DAMAGE, this, TR::HIT_LIGHTNING); armed = false; } else if (!hasTargets) { - // + target = pos + vec3(0.0f, 1024.0f, 0.0f); } else - animation.getJoints(getMatrix(), int(randf() * 5), false, &target); + target = animation.getJoints(getMatrix(), 1 + int(randf() * 5)).pos; } game->playSound(TR::SND_LIGHTNING, pos, Sound::PAN); } @@ -883,10 +883,105 @@ struct Lightning : Controller { } } + void divide(vec3 *points, int L, int R, float spread) { + int M = (L + R) / 2; + if (M == L || M == R) return; + points[M] = (points[L] + points[R]) * 0.5f + (vec3(randf(), randf(), randf()) - 0.5f) * spread; + spread *= 0.5f; + divide(points, L, M, spread); + divide(points, M, R, spread); + } + + short4 toCoord(const vec3 &v, int16 joint) { + return short4(int16(v.x), int16(v.y), int16(v.z), joint); + } + + void setVertex(Vertex &v, const vec3 &coord, int16 joint, int idx) { + v.coord = toCoord(coord, joint); + v.normal = { 0, -1, 0, 0 }; + v.texCoord = { barTile[0].texCoord[idx].x, barTile[0].texCoord[idx].y, 32767, 32767 }; + v.param = { 0, 0, 0, 0 }; + v.color = { 255, 255, 255, 255 }; + } + + void renderPolyline(const vec3 &start, const vec3 &end, float width, float spread, int depth) { + vec3 points[9]; + points[0] = start; + points[8] = end; + divide(points, 0, 8, spread); + + Index indices[(COUNT(points) - 1) * 6]; + Vertex vertices[COUNT(points) * 2]; + + int iCount = 0; + int vCount = 0; + int count = COUNT(points); + // build indices + for (int i = 0; i < count - 1; i++) { + indices[iCount++] = vCount; + indices[iCount++] = vCount + 1; + indices[iCount++] = vCount + 2; + indices[iCount++] = vCount + 1; + indices[iCount++] = vCount + 3; + indices[iCount++] = vCount + 2; + vCount += 2; + } + vCount += 2; + ASSERT(iCount == (count - 1) * 6); + ASSERT(vCount == count * 2); + + // build vertices + vec3 dir = Core::mViewInv.dir.xyz; + + vCount = 0; + vec3 n; + for (int i = 0; i < count; i++) { + if (i < count - 1) + n = dir.cross(points[i + 1] - points[i]).normal() * width; + setVertex(vertices[vCount++], points[i] - n, 0, 0); + setVertex(vertices[vCount++], points[i] + n, 0, 3); + } + ASSERT(vCount == count * 2); + + game->getMesh()->renderBuffer(indices, iCount, vertices, vCount); + + if (depth > 0) { + for (int i = 0; i < 2; i++) { + vec3 a = points[int(randf() * (count - 1))]; + vec3 b = a; + b.x += (randf() - 0.5f) * spread; + b.y = points[count - 1].y; + b.z += (randf() - 0.5f) * spread; + + renderPolyline(a, b, width * 0.75f, spread * 0.5f, depth - 1); + } + } + } + + virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { Controller::render(frustum, mesh, type, caustics); if (!flash) return; - // TODO + + if (!armed) + target = game->getLara()->pos; + + Basis b = animation.getJoints(getMatrix(), 0); + b.rot = quat(0, 0, 0, 1); + + game->setShader(Core::pass, Shader::FLASH, false, false); + Core::active.shader->setParam(uMaterial, vec4(0.0f, 0.0f, 0.0f, 1.0f)); + Core::active.shader->setParam(uBasis, b); + + Core::setCulling(cfNone); + Core::setBlending(bmAdd); + Core::setDepthWrite(false); + + renderPolyline(vec3(0.0f), target - b.pos, 64.0f, 512.0f, 1); + + Core::setDepthWrite(true); + Core::setBlending(bmNone); + Core::setCulling(cfFront); } }; diff --git a/src/ui.h b/src/ui.h index 8e743c3..a0fc80f 100644 --- a/src/ui.h +++ b/src/ui.h @@ -204,6 +204,7 @@ namespace UI { #define MAX_CHARS DYN_MESH_QUADS enum BarType { + BAR_FLASH, BAR_HEALTH, BAR_OXYGEN, BAR_OPTION, diff --git a/src/utils.h b/src/utils.h index 4191d83..190ddad 100644 --- a/src/utils.h +++ b/src/utils.h @@ -204,8 +204,8 @@ struct vec2 { struct vec3 { union { - struct { vec2 xy; }; struct { float x, y, z; }; + struct { vec2 xy; }; }; vec3() {} @@ -271,9 +271,9 @@ struct vec3 { struct vec4 { union { + struct { float x, y, z, w; }; struct { vec2 xy, zw; }; struct { vec3 xyz; }; - struct { float x, y, z, w; }; }; vec4() {}