From 6bee3d4edab35e89faa92baaf04e1830310642f2 Mon Sep 17 00:00:00 2001 From: XProger Date: Mon, 9 Oct 2017 08:18:09 +0300 Subject: [PATCH] #22 add midas hand logic and death; #3 multiple pickups, fix surfacing for flipmaps; #11 fix inventory after death; #23 mipmap generation for cubemap textures --- src/camera.h | 7 +- src/character.h | 6 +- src/controller.h | 6 ++ src/core.h | 1 + src/format.h | 22 +++-- src/inventory.h | 58 ++++++++----- src/lara.h | 186 +++++++++++++++++++++++++++++----------- src/level.h | 71 ++++++++------- src/shaders/shader.glsl | 6 +- src/texture.h | 23 +++-- src/trigger.h | 49 +++++++++-- 11 files changed, 312 insertions(+), 123 deletions(-) diff --git a/src/camera.h b/src/camera.h index 23d8b92..fda4f61 100644 --- a/src/camera.h +++ b/src/camera.h @@ -166,8 +166,11 @@ struct Camera : ICamera { if (indexA == level->cameraFramesCount - 1) { if (level->cutEntity != -1) game->loadLevel(TR::LevelID(level->id + 1)); - else - state = STATE_FOLLOW; + else { + Character *lara = (Character*)game->getLara(); + if (lara->health > 0.0f) + state = STATE_FOLLOW; + } } TR::CameraFrame *frameA = &level->cameraFrames[indexA]; diff --git a/src/character.h b/src/character.h index 67ac1e3..9ffb96b 100644 --- a/src/character.h +++ b/src/character.h @@ -97,7 +97,11 @@ struct Character : Controller { e.room = info.roomBelow; if (info.roomAbove != TR::NO_ROOM && e.y <= info.roomCeiling) { - if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) { + TR::Room *room = &level->rooms[info.roomAbove]; + if (level->isFlipped && room->alternateRoom > -1) + room = &level->rooms[room->alternateRoom]; + + if (stand == STAND_UNDERWATER && !room->flags.water) { stand = STAND_ONWATER; velocity.y = 0; pos.y = float(info.roomCeiling); diff --git a/src/controller.h b/src/controller.h index a9ab677..9ba8393 100644 --- a/src/controller.h +++ b/src/controller.h @@ -25,6 +25,7 @@ struct ICamera { virtual void setup(bool calcMatrices) {} virtual int getRoomIndex() const { return TR::NO_ROOM; } + virtual void doCutscene(const vec3 &pos, float rotation) {} }; struct IGame { @@ -45,6 +46,7 @@ struct IGame { virtual void setWaterParams(float height) {} virtual void waterDrop(const vec3 &pos, float radius, float strength) {} virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {} + virtual void setRoomParams(int roomIndex, Shader::Type type, float diffuse, float ambient, float specular, float alpha, bool alphaTest = false) {} virtual void setupBinding() {} virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {} virtual void renderCompose(int roomIndex) {} @@ -98,6 +100,7 @@ struct Controller { uint32 mask; } *layers; + uint32 visibleMask; uint32 explodeMask; struct ExplodePart { Basis basis; @@ -120,6 +123,7 @@ struct Controller { ambient[0] = ambient[1] = ambient[2] = ambient[3] = ambient[4] = ambient[5] = vec3(intensityf(getRoom().ambient)); targetLight = NULL; updateLights(false); + visibleMask = 0xFFFFFFFF; if (e.flags.once) { e.flags.invisible = true; @@ -762,6 +766,7 @@ struct Controller { for (int i = MAX_LAYERS - 1; i >= 0; i--) { uint32 vmask = (layers[i].mask & ~mask) | (!i ? explodeMask : 0); + vmask &= visibleMask; if (!vmask) continue; mask |= layers[i].mask; // set meshes visibility @@ -769,6 +774,7 @@ struct Controller { joints[j].w = (vmask & (1 << j)) ? 1.0f : -1.0f; // AHAHA if (explodeMask) { + ASSERT(explodeParts); TR::Model *model = getModel(); for (int i = 0; i < model->mCount; i++) if (explodeMask & (1 << i)) diff --git a/src/core.h b/src/core.h index cb191a7..03c2dd2 100644 --- a/src/core.h +++ b/src/core.h @@ -668,6 +668,7 @@ namespace Core { memset(rtCache, 0, sizeof(rtCache)); defaultTarget = NULL; + glDepthFunc(GL_LEQUAL); Sound::init(); diff --git a/src/format.h b/src/format.h index 39a1841..49803ec 100644 --- a/src/format.h +++ b/src/format.h @@ -139,7 +139,7 @@ E( PUZZLE_DONE_4 ) \ E( LEADBAR ) \ E( INV_LEADBAR ) \ - E( MIDAS_TOUCH ) \ + E( MIDAS_HAND ) \ E( KEY_ITEM_1 ) \ E( KEY_ITEM_2 ) \ E( KEY_ITEM_3 ) \ @@ -340,6 +340,7 @@ namespace TR { HIT_SLAM, HIT_REX, HIT_LIGHTNING, + HIT_MIDAS, }; enum Action : uint16 { @@ -393,8 +394,12 @@ namespace TR { 0, -612, 30, {{-300, 0, -692}, {300, 0, -512}}, true, false }; + Limit MIDAS = { + 512, -612, 30, {{-700, 284, -700}, {700, 996, 700}}, true, false + }; + Limit SCION = { - 640, -202, 30, {{-256, 540, -350}, {256, 740, -200}}, false, false + 640, -202, 30, {{-256, 540, -350}, {256, 740, -200}}, false, false }; } @@ -667,14 +672,16 @@ namespace TR { isDoor() || (type == DRAWBRIDGE && flags.active != ACTIVE) || (type == SCION_HOLDER) || - ((type == HAMMER_HANDLE || type == HAMMER_BLOCK) && flags.collision); + ((type == HAMMER_HANDLE || type == HAMMER_BLOCK) && flags.collision) || + (type == CRYSTAL); } - bool isItem() const { + bool isPickup() const { return (type >= PISTOLS && type <= AMMO_UZIS) || (type >= PUZZLE_1 && type <= PUZZLE_4) || (type >= KEY_ITEM_1 && type <= KEY_ITEM_4) || - (type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == SCION_QUALOPEC || type == SCION_DROP || type == SCION_NATLA || type == LEADBAR); // TODO: recheck all items + (type == MEDIKIT_SMALL || type == MEDIKIT_BIG) || + (type == SCION_QUALOPEC || type == SCION_DROP || type == SCION_NATLA || type == LEADBAR); // TODO: recheck all items } bool isActor() const { @@ -748,6 +755,7 @@ namespace TR { 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; + case MIDAS_HAND : return LEADBAR; break; default : return NONE; } } @@ -1668,6 +1676,10 @@ namespace TR { } } + bool isCutsceneLevel() { + return cutEntity > -1; + } + void readMeshes(Stream &stream) { uint32 meshDataSize; stream.read(meshDataSize); diff --git a/src/inventory.h b/src/inventory.h index 35f31ab..56cdb84 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -190,6 +190,8 @@ struct Inventory { add(TR::Entity::INV_PUZZLE_2, 3); add(TR::Entity::INV_PUZZLE_3, 3); add(TR::Entity::INV_PUZZLE_4, 3); + + add(TR::Entity::INV_LEADBAR, 3); #endif } @@ -351,6 +353,9 @@ struct Inventory { } index = targetIndex = pageItemIndex[page]; + + //if (type == TR::Entity::INV_PASSPORT) // toggle after death + // chooseItem(); } } return active; @@ -563,8 +568,8 @@ struct Inventory { if (index == targetIndex && targetPage == page && ready) { if (!chosen) { - if (key == cUp && !(page < PAGE_ITEMS && getItemsCount(page + 1))) key = cMAX; - if (key == cDown && !(page > PAGE_OPTION && getItemsCount(page - 1))) key = cMAX; + if ((key == cUp && !canFlipPage(-1)) || (key == cDown && !canFlipPage( 1))) + key = cMAX; switch (key) { case cLeft : { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; } break; @@ -577,21 +582,8 @@ struct Inventory { if (index != targetIndex) game->playSound(TR::SND_INV_SPIN); - if (lastKey != key && key == cAction && phaseChoose == 0.0f) { - vec3 p; - chosen = true; - switch (item->type) { - case TR::Entity::INV_COMPASS : game->playSound(TR::SND_INV_COMPASS); break; - case TR::Entity::INV_HOME : game->playSound(TR::SND_INV_HOME); break; - case TR::Entity::INV_CONTROLS : game->playSound(TR::SND_INV_CONTROLS); break; - case TR::Entity::INV_PISTOLS : - case TR::Entity::INV_SHOTGUN : - case TR::Entity::INV_MAGNUMS : - case TR::Entity::INV_UZIS : game->playSound(TR::SND_INV_WEAPON); break; - default : game->playSound(TR::SND_INV_SHOW); break; - } - item->choose(); - } + if (lastKey != key && key == cAction && phaseChoose == 0.0f) + chooseItem(); } else { if (changeTimer > 0.0f) { changeTimer -= Core::deltaTime; @@ -662,6 +654,32 @@ struct Inventory { game->loadLevel(nextLevel); } + void chooseItem() { + Item *item = items[getGlobalIndex(page, index)]; + + vec3 p; + chosen = true; + switch (item->type) { + case TR::Entity::INV_COMPASS : game->playSound(TR::SND_INV_COMPASS); break; + case TR::Entity::INV_HOME : game->playSound(TR::SND_INV_HOME); break; + case TR::Entity::INV_CONTROLS : game->playSound(TR::SND_INV_CONTROLS); break; + case TR::Entity::INV_PISTOLS : + case TR::Entity::INV_SHOTGUN : + case TR::Entity::INV_MAGNUMS : + case TR::Entity::INV_UZIS : game->playSound(TR::SND_INV_WEAPON); break; + default : game->playSound(TR::SND_INV_SHOW); break; + } + item->choose(); + } + + bool canFlipPage(int dir) { + if (((Character*)game->getLara())->health <= 0.0f) + return false; + if (dir == -1) return page < PAGE_ITEMS && getItemsCount(page + 1); + if (dir == 1) return page > PAGE_OPTION && getItemsCount(page - 1); + return false; + } + void prepareBackground() { Core::setDepthTest(false); @@ -952,7 +970,7 @@ struct Inventory { Core::setDepthTest(true); Core::setBlending(bmAlpha); - if (game->isCutscene()) + if (game->getLevel()->isCutsceneLevel()) return; // items @@ -1002,12 +1020,12 @@ struct Inventory { if (game->getLevel()->id != TR::TITLE) UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width); - if (page < PAGE_ITEMS && getItemsCount(page + 1)) { + if (canFlipPage(-1)) { UI::textOut(vec2(16, 32), "[", UI::aLeft, UI::width); UI::textOut(vec2( 0, 32), "[", UI::aRight, UI::width - 20); } - if (page > PAGE_OPTION && getItemsCount(page - 1)) { + if (canFlipPage(1)) { UI::textOut(vec2(16, 480 - 16), "]", UI::aLeft, UI::width); UI::textOut(vec2(0, 480 - 16), "]", UI::aRight, UI::width - 20); } diff --git a/src/lara.h b/src/lara.h index 8a8dce1..bed2787 100644 --- a/src/lara.h +++ b/src/lara.h @@ -226,9 +226,11 @@ struct Lara : Character { } arms[2]; TR::Entity::Type usedKey; - TR::Entity *pickupEntity; + int pickupListCount; + TR::Entity *pickupList[32]; KeyHole *keyHole; Lightning *lightning; + Texture *environment; int roomPrev; // water out from room vec2 rotFactor; @@ -411,8 +413,9 @@ struct Lara : Character { damageTime = LARA_DAMAGE_TIME; hitTime = 0.0f; - keyHole = NULL; - lightning = NULL; + keyHole = NULL; + lightning = NULL; + environment = NULL; getEntity().flags.active = 1; initMeshOverrides(); @@ -460,11 +463,12 @@ struct Lara : Character { //reset(24, vec3(45609, 18176, 41500), 90 * DEG2RAD); // level 4 (thor) //reset(19, vec3(41418, -3707, 58863), 270 * DEG2RAD); // level 5 (triangle) //reset(21, vec3(24106, -4352, 52089), 0); // level 6 (flame traps) - //reset(73, vec3(73372, -122, 51687), PI * 0.5f); // level 6 (midas hand) + //reset(73, vec3(73372, 122, 51687), PI * 0.5f); // level 6 (midas hand) //reset(64, vec3(36839, -2560, 48769), 270 * DEG2RAD); // level 6 (flipmap effect) //reset(99, vec3(45562, -3328, 63366), 225 * DEG2RAD); // level 7a (flipmap) //reset(90, vec3(19438, 3840, 78341), 90 * DEG2RAD); // level 7a (statues) - //reset(57, vec3(54844, -3328, 53145), 0); // level 8b (bridge switch) + //reset(77, vec3(36943, -4096, 62821), 270 * DEG2RAD); // level 7b (heavy trigger) + //reset(57, vec3(54844, -3328, 53145), 0); // level 8b (bridge switch //reset(12, vec3(34236, -2415, 14974), 0); // level 8b (sphinx) //reset(0, vec3(40913, -1012, 42252), PI); // level 8c //reset(30, vec3(69689, -8448, 34922), 330 * DEG2RAD); // Level 10a (cabin) @@ -488,6 +492,7 @@ struct Lara : Character { virtual ~Lara() { delete braid; + delete environment; } int getRoomByPos(const vec3 &pos) { @@ -517,6 +522,9 @@ struct Lara : Character { if (level->rooms[room].flags.water) { stand = STAND_UNDERWATER; animation.setAnim(ANIM_UNDERWATER); + } else { + stand = STAND_GROUND; + animation.setAnim(ANIM_STAND); } velocity = vec3(0.0f); @@ -1408,6 +1416,19 @@ struct Lara : Character { animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 1); break; } + case TR::HIT_MIDAS : { + // generate environment map for reflections + if (!environment) + environment = new Texture(256, 256, Texture::RGBA, true, NULL, true, true); + Core::beginFrame(); + game->renderEnvironment(73, pos - vec3(0.0f, 384.0f, 0.0f), &environment); + environment->generateMipMap(); + Core::endFrame(); + // set death animation + animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation + 1); + game->getCamera()->doCutscene(pos, angle.y); + break; + } default : ; } @@ -1427,6 +1448,8 @@ struct Lara : Character { }; bool useItem(TR::Entity::Type item) { + if (game->isCutscene()) return false; + switch (item) { case TR::Entity::INV_PISTOLS : wpnChange(Lara::Weapon::PISTOLS); break; case TR::Entity::INV_SHOTGUN : wpnChange(Lara::Weapon::SHOTGUN); break; @@ -1451,6 +1474,17 @@ struct Lara : Character { return false; usedKey = item; break; + case TR::Entity::INV_LEADBAR : + for (int i = 0; i < level->entitiesCount; i++) + if (level->entities[i].type == TR::Entity::MIDAS_HAND) { + MidasHand *controller = (MidasHand*)level->entities[i].controller; + if (controller->interaction) { + controller->invItem = item; + return false; // remove item from inventory + } + return true; + } + return false; default : return false; } return true; @@ -1488,46 +1522,68 @@ struct Lara : Character { } bool doPickUp() { - if ((state != STATE_STOP && state != STATE_TREAD) || !animation.canSetState(STATE_PICK_UP)) + if (!animation.canSetState(STATE_PICK_UP)) return false; int room = getRoomIndex(); - TR::Limits::Limit limit = state == STATE_STOP ? TR::Limits::PICKUP : TR::Limits::PICKUP_UNDERWATER; + + pickupListCount = 0; for (int i = 0; i < level->entitiesCount; i++) { - TR::Entity &item = level->entities[i]; - if (!item.isItem()) + TR::Entity &entity = level->entities[i]; + if (!entity.isPickup()) 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; + Controller *controller = (Controller*)entity.controller; - if (item.type == TR::Entity::SCION_QUALOPEC) - limit = TR::Limits::SCION; + // set item orientation to hack limits check + if (stand == STAND_UNDERWATER) + controller->angle.x = -25 * DEG2RAD; + controller->angle.y = angle.y; - if (!checkInteraction(controller, limit, (input & ACTION) != 0)) - continue; + if (controller->getRoomIndex() != room || entity.flags.invisible || !canPickup(controller)) + continue; - if (stand == STAND_UNDERWATER) - angle.x = -25 * DEG2RAD; - - pickupEntity = &item; - - if (item.type == TR::Entity::SCION_QUALOPEC) { - animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation); - ((Camera*)level->cameraController)->doCutscene(pos, angle.y); - } else - state = STATE_PICK_UP; - - return true; - } + ASSERT(pickupListCount < COUNT(pickupList)); + pickupList[pickupListCount++] = &entity; } + + if (pickupListCount > 0) { + state = STATE_PICK_UP; + return true; + } + return false; } + bool canPickup(Controller *controller) { + TR::Entity &entity = controller->getEntity(); + + // get limits + TR::Limits::Limit *limit; + switch (entity.type) { + case TR::Entity::SCION_QUALOPEC : limit = &TR::Limits::SCION; break; + default : limit = level->rooms[getRoomIndex()].flags.water ? &TR::Limits::PICKUP_UNDERWATER : &TR::Limits::PICKUP; + } + + if (!checkInteraction(controller, limit, true)) + return false; + + if (stand == Character::STAND_UNDERWATER) + angle.x = -25 * DEG2RAD; + + // set new state + switch (entity.type) { + case TR::Entity::SCION_QUALOPEC : + animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation); + game->getCamera()->doCutscene(pos, angle.y); + break; + default : ; + } + + return true; + } + int doTutorial(int track) { switch (track) { // GYM tutorial routine case 28 : if (level->tracks[track].once && state == STATE_UP_JUMP) track = 29; break; @@ -1551,22 +1607,22 @@ struct Lara : Character { } - bool checkInteraction(Controller *controller, const TR::Limits::Limit &limit, bool action) { + 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; mat4 m = controller->getMatrix(); float fx = 0.0f; - if (!limit.alignHoriz) + 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 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 - if (limit.box.contains(deltaRel)) { + if (limit->box.contains(deltaRel)) { float deltaAngY = shortAngle(angle.y, controller->angle.y); if (stand == STAND_UNDERWATER) { @@ -1580,9 +1636,9 @@ struct Lara : Character { } } - if (fabsf(deltaAngY) <= limit.ay * DEG2RAD) { + if (fabsf(deltaAngY) <= limit->ay * DEG2RAD) { // align - if (limit.alignAngle) + if (limit->alignAngle) angle = controller->angle; else angle.x = angle.z = 0.0f; @@ -1624,7 +1680,7 @@ struct Lara : Character { if (controller->activeState == asNone) { limit = state == STATE_STOP ? &TR::Limits::SWITCH : &TR::Limits::SWITCH_UNDERWATER; - if (checkInteraction(controller, *limit, Input::state[cAction])) { + 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(); @@ -1651,7 +1707,7 @@ struct Lara : Character { return; limit = actionState == STATE_USE_PUZZLE ? &TR::Limits::PUZZLE_HOLE : &TR::Limits::KEY_HOLE; - if (!checkInteraction(controller, *limit, isPressed(ACTION) || usedKey != TR::Entity::NONE)) + if (!checkInteraction(controller, limit, isPressed(ACTION) || usedKey != TR::Entity::NONE)) return; if (usedKey == TR::Entity::NONE) { @@ -2010,7 +2066,7 @@ struct Lara : Character { float oldAngle = block->angle.y; block->angle.y = angleQuadrant(angle.y) * (PI * 0.5f); - if (!checkInteraction(block, TR::Limits::BLOCK, (input & ACTION) != 0)) { + if (!checkInteraction(block, &TR::Limits::BLOCK, (input & ACTION) != 0)) { block->angle.y = oldAngle; continue; } @@ -2024,7 +2080,7 @@ struct Lara : Character { int res = STATE_STOP; angle.x = 0.0f; - if ((state == STATE_STOP || state == STATE_TREAD) && (input & ACTION) && emptyHands() && doPickUp()) + if ((input == ACTION) && (state == STATE_STOP) && emptyHands() && doPickUp()) 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 @@ -2209,7 +2265,7 @@ struct Lara : Character { } virtual int getStateUnderwater() { - if (input == ACTION && doPickUp()) + if ((input == ACTION) && (state == STATE_TREAD) && emptyHands() && doPickUp()) 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) { @@ -2335,6 +2391,12 @@ struct Lara : Character { case TR::LEVEL_4 : reset(18, vec3(34914, 11008, 41315), 90 * DEG2RAD); // main hall break; + case TR::LEVEL_6 : + reset(73, vec3(73372, 122, 51687), PI * 0.5f); // level 6 (midas hand) + break; + case TR::LEVEL_7B : + reset(77, vec3(36943, -4096, 62821), 270 * DEG2RAD); // level 7b (heavy trigger) + break; default : game->playSound(TR::SND_NO, pos, Sound::PAN); } } @@ -2372,13 +2434,13 @@ struct Lara : Character { virtual void doCustomCommand(int curFrame, int prevFrame) { switch (state) { case STATE_PICK_UP : { - if (pickupEntity && !pickupEntity->flags.invisible) { - int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; - if (animation.isFrameActive(pickupFrame)) { - pickupEntity->flags.invisible = true; - game->invAdd(pickupEntity->type, 1); - pickupEntity = NULL; + int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; + if (animation.isFrameActive(pickupFrame)) { + for (int i = 0; i < pickupListCount; i++) { + pickupList[i]->flags.invisible = true; + game->invAdd(pickupList[i]->type, 1); } + pickupListCount = 0; } break; } @@ -2814,6 +2876,24 @@ struct Lara : Character { velocity = v * (sink.speed * 8.0f); } + uint32 getMidasDeathMask() { + uint32 mask = 0; + int frame = animation.frameIndex; + if (frame > 4 ) mask |= BODY_LEG_L3 | BODY_LEG_R3; + if (frame > 69 ) mask |= BODY_LEG_L2; + if (frame > 79 ) mask |= BODY_LEG_L1; + if (frame > 99 ) mask |= BODY_LEG_R2; + if (frame > 119) mask |= BODY_LEG_R1 | BODY_HIP; + if (frame > 134) mask |= BODY_CHEST; + if (frame > 149) mask |= BODY_ARM_L1; + if (frame > 162) mask |= BODY_ARM_L2; + if (frame > 173) mask |= BODY_ARM_L3; + if (frame > 185) mask |= BODY_ARM_R1; + if (frame > 194) mask |= BODY_ARM_R2; + if (frame > 217) mask |= BODY_ARM_R3; + if (frame > 224) mask |= BODY_HEAD; + return mask; + } void renderMuzzleFlash(MeshBuilder *mesh, const Basis &basis, const vec3 &offset, float time) { ASSERT(level->extra.muzzleFlash); @@ -2843,6 +2923,16 @@ struct Lara : Character { renderMuzzleFlash(mesh, animation.getJoints(matrix, 13, true), vec3( 10, -50, 150), arms[1].shotTimer); Core::setBlending(bmNone); } + + if (state == STATE_DIE_MIDAS) { + game->setRoomParams(getRoomIndex(), Shader::MIRROR, 1.2f, 1.0f, 0.2f, 1.0f, false); + environment->bind(sEnvironment); + Core::setBlending(bmAlpha); + visibleMask = getMidasDeathMask(); + Controller::render(frustum, mesh, type, caustics); + visibleMask = 0xFFFFFFFF; + Core::setBlending(bmNone); + } } }; diff --git a/src/level.h b/src/level.h index eab4d9b..009a3b8 100644 --- a/src/level.h +++ b/src/level.h @@ -163,6 +163,34 @@ struct Level : IGame { 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 setRoomParams(int roomIndex, Shader::Type type, float diffuse, float ambient, float specular, float alpha, bool alphaTest = false) { + if (Core::pass == Core::passShadow) { + setShader(Core::pass, type); + return; + } + + TR::Room &room = level.rooms[roomIndex]; + + if (room.flags.water) + setWaterParams(float(room.info.yTop)); + else + setWaterParams(NO_CLIP_PLANE); + + setShader(Core::pass, type, room.flags.water, alphaTest); + + if (room.flags.water) { + if (waterCache) + waterCache->bindCaustics(roomIndex); + setWaterParams(float(room.info.yTop)); + } + + Core::active.shader->setParam(uParam, Core::params); + Core::active.shader->setParam(uMaterial, vec4(diffuse, ambient, specular, alpha)); + + if (Core::settings.detail.shadows > Core::Settings::MEDIUM) + Core::active.shader->setParam(uContacts, Core::contacts[0], MAX_CONTACTS); + } + virtual void setupBinding() { atlas->bind(sDiffuse); Core::whiteTex->bind(sNormal); @@ -489,6 +517,7 @@ struct Level : IGame { case TR::Entity::KEY_HOLE_2 : case TR::Entity::KEY_HOLE_3 : case TR::Entity::KEY_HOLE_4 : return new KeyHole(this, index); + case TR::Entity::MIDAS_HAND : return new MidasHand(this, index); case TR::Entity::SCION_TARGET : return new ScionTarget(this, index); case TR::Entity::WATERFALL : return new Waterfall(this, index); case TR::Entity::TRAP_LAVA : return new TrapLava(this, index); @@ -729,38 +758,12 @@ struct Level : IGame { if (e.type == TR::Entity::CRYSTAL) { Crystal *c = (Crystal*)e.controller; renderEnvironment(c->getRoomIndex(), c->pos - vec3(0, 512, 0), &c->environment); + c->environment->generateMipMap(); } } Core::endFrame(); } - void setRoomParams(int roomIndex, Shader::Type type, float diffuse, float ambient, float specular, float alpha, bool alphaTest = false) { - if (Core::pass == Core::passShadow) { - setShader(Core::pass, type); - return; - } - - TR::Room &room = level.rooms[roomIndex]; - - if (room.flags.water) - setWaterParams(float(room.info.yTop)); - else - setWaterParams(NO_CLIP_PLANE); - - setShader(Core::pass, type, room.flags.water, alphaTest); - - if (room.flags.water) { - if (waterCache) - waterCache->bindCaustics(roomIndex); - setWaterParams(float(room.info.yTop)); - } - - Core::active.shader->setParam(uParam, Core::params); - Core::active.shader->setParam(uMaterial, vec4(diffuse, ambient, specular, alpha)); - if (Core::settings.detail.shadows > Core::Settings::MEDIUM) - Core::active.shader->setParam(uContacts, Core::contacts[0], MAX_CONTACTS); - } - void setMainLight(Controller *controller) { Core::lightPos[0] = controller->mainLightPos; Core::lightColor[0] = vec4(controller->mainLightColor.xyz, 1.0f / controller->mainLightColor.w); @@ -874,7 +877,7 @@ struct Level : IGame { if (type == Shader::SPRITE) { float alpha = (entity.type == TR::Entity::SMOKE || entity.type == TR::Entity::WATER_SPLASH) ? 0.75f : 1.0f; - float diffuse = entity.isItem() ? 1.0f : 0.5f; + float diffuse = entity.isPickup() ? 1.0f : 0.5f; setRoomParams(roomIndex, type, diffuse, 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); @@ -904,11 +907,15 @@ struct Level : IGame { } void update() { - if (isCutscene() && !sndSoundtrack) + if (isCutscene() && (lara->health > 0.0f && !sndSoundtrack)) return; - if (Input::state[cInventory] && level.id != TR::TITLE) - inventory.toggle(); + if (Input::state[cInventory] && level.id != TR::TITLE) { + if (lara->health <= 0.0f) + inventory.toggle(Inventory::PAGE_OPTION, TR::Entity::INV_PASSPORT); + else + inventory.toggle(); + } Sound::Sample *sndChanged = sndCurrent; @@ -1477,7 +1484,7 @@ struct Level : IGame { } void renderUI() { - if (isCutscene()) return; + if (level.isCutsceneLevel()) return; UI::begin(); diff --git a/src/shaders/shader.glsl b/src/shaders/shader.glsl index cced98d..57b57f0 100644 --- a/src/shaders/shader.glsl +++ b/src/shaders/shader.glsl @@ -45,7 +45,7 @@ uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha #ifdef VERTEX - #ifdef TYPE_ENTITY + #if defined(TYPE_ENTITY) || defined(TYPE_MIRROR) uniform vec4 uBasis[32 * 2]; #else uniform vec4 uBasis[2]; @@ -89,7 +89,7 @@ uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha } vec4 _transform() { - #ifdef TYPE_ENTITY + #if defined(TYPE_ENTITY) || defined(TYPE_MIRROR) int index = int(aCoord.w * 2.0); vec4 rBasisRot = uBasis[index]; vec4 rBasisPos = uBasis[index + 1]; @@ -147,7 +147,7 @@ uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha #endif #ifdef TYPE_MIRROR - vDiffuse.xyz = vec3(0.5, 0.5, 2.0); // blue color dodge for crystal + vDiffuse.xyz = uMaterial.xyz; #endif #ifdef TYPE_FLASH diff --git a/src/texture.h b/src/texture.h index 3b2be22..a384dd1 100644 --- a/src/texture.h +++ b/src/texture.h @@ -10,8 +10,9 @@ struct Texture { int width, height; Format format; bool cube; + bool filter; - Texture(int width, int height, Format format, bool cube = false, 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), filter(filter) { if (!Core::support.texNPOT) { width = nextPow2(width); height = nextPow2(height); @@ -117,12 +118,20 @@ struct Texture { if (!cube) break; } - if (mips) { - glGenerateMipmap(target); - if (!cube && filter && (Core::support.maxAniso > 0)) - glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(int(Core::support.maxAniso), 8)); - //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); - } + if (mips) + generateMipMap(); + + this->filter = filter; + } + + void generateMipMap() { + bind(0); + GLenum target = cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; + + glGenerateMipmap(target); + if (!cube && filter && (Core::support.maxAniso > 0)) + glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(int(Core::support.maxAniso), 8)); + //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); } virtual ~Texture() { diff --git a/src/trigger.h b/src/trigger.h index f083d5a..ac38f29 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -621,7 +621,7 @@ struct Crystal : Controller { Texture *environment; Crystal(IGame *game, int entity) : Controller(game, entity) { - environment = new Texture(64, 64, Texture::RGBA, true); + environment = new Texture(64, 64, Texture::RGBA, true, NULL, true, true); activate(); } @@ -634,8 +634,7 @@ struct Crystal : Controller { } virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { - Shader *sh = Core::active.shader; - sh->setParam(uMaterial, vec4(1.0f)); + Core::active.shader->setParam(uMaterial, vec4(0.5, 0.5, 2.0, 1.0f)); // blue color dodge for crystal environment->bind(sEnvironment); Controller::render(frustum, mesh, type, caustics); } @@ -803,8 +802,8 @@ struct TrapSword : Controller { angle.y += rot * Core::deltaTime; applyGravity(dir.y); pos += dir * (30.0f * Core::deltaTime); - if (pos.y > info.floor) { - pos.y = info.floor; + if (pos.y > float(info.floor)) { + pos.y = float(info.floor); game->playSound(TR::SND_SWORD, pos, Sound::PAN); deactivate(true); } @@ -1017,6 +1016,46 @@ struct Lightning : Controller { } }; +struct MidasHand : Controller { + TR::Entity::Type invItem; + bool interaction; + + MidasHand(IGame *game, int entity) : Controller(game, entity), invItem(TR::Entity::NONE), interaction(false) { + activate(); + } + + virtual void update() { + Character *lara = (Character*)level->laraController; + + if (lara->health <= 0.0f || lara->stand != Character::STAND_GROUND || lara->getRoomIndex() != getRoomIndex()) + return; + + vec3 d = (pos - lara->pos).abs(); + + if (d.x < 512.0f && d.z < 512.0f) { // check for same sector + lara->hit(1001.0f, this, TR::HIT_MIDAS); + deactivate(true); + return; + } + + interaction = (d.x < 700.0f && d.z < 700.0f) && lara->state == 2; // 2 = Lara::STATE_STOP + + if (interaction) { + if (invItem != TR::Entity::NONE) { + if (invItem == TR::Entity::INV_LEADBAR) { + lara->angle.y = PI * 0.5f; + lara->pos.x = pos.x - 612.0f; + lara->animation.setAnim(level->models[TR::MODEL_LARA_SPEC].animation); + game->invAdd(TR::Entity::PUZZLE_1); + } else + game->playSound(TR::SND_NO, pos, Sound::PAN); // uncompatible item + invItem = TR::Entity::NONE; + } else if (Input::state[cAction] && !game->invChooseKey(getEntity().type)) // TODO: add callback for useItem + game->playSound(TR::SND_NO, pos, Sound::PAN); // no compatible items in inventory + } + } +}; + struct TrapLava : Controller { bool done;