diff --git a/src/controller.h b/src/controller.h index 6f29570..4724878 100644 --- a/src/controller.h +++ b/src/controller.h @@ -51,8 +51,8 @@ struct IGame { virtual ~IGame() {} virtual void loadLevel(TR::LevelID id) {} virtual void loadNextLevel() {} - virtual void loadGame(int slot) {} - virtual void saveGame(int slot) {} + virtual void loadGame() {} + virtual void saveGame(int entityIndex) {} virtual void applySettings(const Core::Settings &settings) {} virtual TR::Level* getLevel() { return NULL; } @@ -86,6 +86,7 @@ struct IGame { virtual void addMuzzleFlash(Controller *owner, int joint, const vec3 &offset, int lightIndex) {} + virtual void invShow(int playerIndex, int page, int itemIndex = -1) {} virtual bool invUse(int playerIndex, TR::Entity::Type type) { return false; } virtual void invAdd(TR::Entity::Type type, int count = 1) {} virtual int* invCount(TR::Entity::Type type) { return NULL; } @@ -402,6 +403,7 @@ struct Controller { } case TR::FloorData::TRIGGER : { + if (info.trigCmdCount) break; info.trigger = (TR::Level::Trigger::Type)cmd.sub; info.trigCmdCount = 0; info.trigInfo = (*fd++).triggerInfo; diff --git a/src/format.h b/src/format.h index 89b0941..e19e549 100644 --- a/src/format.h +++ b/src/format.h @@ -1803,13 +1803,14 @@ namespace TR { return type >= DOOR_1 && type <= DOOR_8; } - bool isCollider() const { + bool isCollider(TR::Entity::Flags flags) const { return isEnemy() || isVehicle() || isDoor() || + (type == CRYSTAL && flags.collision) || (type == DRAWBRIDGE && flags.active != ACTIVE) || ((type == HAMMER_HANDLE || type == HAMMER_BLOCK) && flags.collision) || - type == CRYSTAL || type == MOVING_OBJECT || type == SCION_HOLDER; + type == MOVING_OBJECT || type == SCION_HOLDER; } static bool isPickup(Type type) { @@ -1818,6 +1819,7 @@ namespace TR { (type >= KEY_ITEM_1 && type <= KEY_ITEM_4) || (type == MEDIKIT_SMALL || type == MEDIKIT_BIG) || (type == SCION_PICKUP_QUALOPEC || type == SCION_PICKUP_DROP || type == SCION_PICKUP_HOLDER || type == LEADBAR) || + (type == CRYSTAL) || (type >= SECRET_1 && type <= SECRET_3) || (type == M16 || type == AMMO_M16) || (type == MP5 || type == AMMO_MP5) || @@ -3977,6 +3979,25 @@ namespace TR { stream.read(s.sStart); s.sCount = -s.sCount; } + + // remove unavailable sprites (check EGYPT.PHD) + for (int roomIndex = 0; roomIndex < roomsCount; roomIndex++) { + Room::Data &data = rooms[roomIndex].data; + + int i = 0; + while (i < data.sCount) + if (data.sprites[i].vertex >= data.vCount || data.sprites[i].texture >= spriteTexturesCount) { + LOG("! room %d has wrong sprite %d (v:%d/%d t:%d/%d)\n", roomIndex, i, data.sprites[i].vertex, data.vCount, data.sprites[i].texture, spriteTexturesCount); + ASSERT(false); + data.sprites[i] = data.sprites[--data.sCount]; + } else + i++; + + if (!data.sCount && data.sprites) { + delete[] data.sprites; + data.sprites = NULL; + } + } } void readAnimTex(Stream &stream) { diff --git a/src/game.h b/src/game.h index 52d78a5..7d1ea7e 100644 --- a/src/game.h +++ b/src/game.h @@ -112,6 +112,13 @@ namespace Game { } void updateTick() { + Input::update(); + + if (!level->level.isTitle()) { + if (Input::lastState[0] == cStart) level->addPlayer(0); + if (Input::lastState[1] == cStart) level->addPlayer(1); + } + float dt = Core::deltaTime; if (Input::down[ikR]) // slow motion (for animation debugging) Core::deltaTime /= 10.0f; @@ -144,8 +151,6 @@ namespace Game { if (level->isEnded) return true; - - Input::update(); /* if (level->camera) { if (Input::down[ikV]) { // third <-> first person view @@ -165,10 +170,6 @@ namespace Game { Input::down[ikL] = false; } */ - if (!level->level.isTitle()) { - if (Input::lastState[0] == cStart) level->addPlayer(0); - if (Input::lastState[1] == cStart) level->addPlayer(1); - } if (!level->level.isCutsceneLevel()) delta = min(0.2f, delta); diff --git a/src/inventory.h b/src/inventory.h index adcc986..10955f3 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -207,6 +207,7 @@ struct Inventory { PAGE_OPTION, PAGE_INVENTORY, PAGE_ITEMS, + PAGE_SAVEGAME, PAGE_MAX }; @@ -701,7 +702,14 @@ struct Inventory { if (phaseRing == 0.0f || phaseRing == 1.0f) { active = !active; vec3 p; - game->playSound(active ? TR::SND_INV_SHOW : TR::SND_INV_HIDE, p); + + if (curPage == PAGE_SAVEGAME) { + phaseRing = active ? 1.0f : 0.0f; + slot = 1; + } else { + game->playSound(active ? TR::SND_INV_SHOW : TR::SND_INV_HIDE, p); + } + chosen = false; if (active) { @@ -1031,7 +1039,20 @@ struct Inventory { Item *item = items[getGlobalIndex(page, index)]; - if (index == targetIndex && targetPage == page && ready) { + if (page == PAGE_SAVEGAME) { + if (Input::lastState[playerIndex] == cLeft || Input::lastState[playerIndex] == cRight) + slot ^= 1; + + if (Input::lastState[playerIndex] == cAction) { + if (slot == 1) { + TR::Entity &e = game->getLevel()->entities[index]; + Controller *controller = (Controller*)e.controller; + controller->deactivate(true); + } + toggle(playerIndex, targetPage); + } + + } else if (index == targetIndex && targetPage == page && ready) { if (!chosen) { if ((key == cUp && !canFlipPage(-1)) || (key == cDown && !canFlipPage( 1))) key = cMAX; @@ -1092,7 +1113,7 @@ struct Inventory { } } else if (!game->getLevel()->isTitle()) - toggle(); + toggle(playerIndex, targetPage); } } lastKey = key; @@ -1233,7 +1254,7 @@ struct Inventory { // grayscale Core::setTarget(background[1], RT_STORE_COLOR); game->setShader(Core::passFilter, Shader::FILTER_GRAYSCALE, false, false); - Core::active.shader->setParam(uParam, vec4(1, 0, 0, 0)); + Core::active.shader->setParam(uParam, vec4(0.75f, 0.75f, 1.0f, 1.0f)); background[0]->bind(sDiffuse); game->getMesh()->renderQuad(); @@ -1646,7 +1667,7 @@ struct Inventory { void renderUI() { if (!active || phaseRing < 1.0f) return; - static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_ITEMS }; + static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_ITEMS, STR_SAVEGAME }; float eye = UI::width * Core::eye * 0.01f; @@ -1656,6 +1677,15 @@ struct Inventory { eye = 0.0f; } + if (page == PAGE_SAVEGAME) { + UI::renderBar(UI::BAR_OPTION, vec2(-eye + UI::width / 2 - 120, 240 - 14), vec2(240, LINE_HEIGHT - 6), 1.0f, 0x802288FF, 0, 0, 0); + UI::textOut(vec2(-eye, 240), pageTitle[page], UI::aCenter, UI::width); + UI::renderBar(UI::BAR_OPTION, vec2(-eye - 48 * slot + UI::width / 2, 240 + 24 - 16), vec2(48, 18), 1.0f, 0xFFD8377C, 0); + UI::textOut(vec2(-eye - 48 + UI::width / 2, 240 + 24), STR_YES, UI::aCenter, 48); + UI::textOut(vec2(-eye + UI::width / 2, 240 + 24), STR_NO, UI::aCenter, 48); + return; + } + if (!game->getLevel()->isTitle()) UI::textOut(vec2(-eye, 32), pageTitle[page], UI::aCenter, UI::width); diff --git a/src/lara.h b/src/lara.h index 3aa399f..64c1e9a 100644 --- a/src/lara.h +++ b/src/lara.h @@ -7,6 +7,7 @@ #include "trigger.h" #include "sprite.h" #include "enemy.h" +#include "inventory.h" // TODO: slide to slide in WALL // TODO: static sounds in LEVEL3A @@ -1671,11 +1672,25 @@ struct Lara : Character { Controller *controller = (Controller*)entity.controller; - if (controller->getRoomIndex() != room || controller->flags.invisible || !canPickup(controller)) + if (controller->getRoomIndex() != room || controller->flags.invisible) continue; - ASSERT(pickupListCount < COUNT(pickupList)); - pickupList[pickupListCount++] = controller; + if (entity.type == TR::Entity::CRYSTAL) { + if (Input::lastState[camera->cameraIndex] == cAction) { + vec3 dir = controller->pos - pos; + if (dir.length2() < SQR(350.0f) && getDir().dot(dir.normal()) > COS30) { + pickupListCount = 0; + game->invShow(camera->cameraIndex, Inventory::PAGE_SAVEGAME, i); + return true; + } + } + } else { + if (!canPickup(controller)) + continue; + + ASSERT(pickupListCount < COUNT(pickupList)); + pickupList[pickupListCount++] = controller; + } } if (pickupListCount > 0) { @@ -3068,10 +3083,10 @@ struct Lara : Character { for (int i = 0; i < level->entitiesCount; i++) { const TR::Entity &e = level->entities[i]; - if (!e.controller || !e.isCollider()) continue; - Controller *controller = (Controller*)e.controller; + if (!controller || !e.isCollider(controller->flags)) continue; + if (e.isEnemy()) { if (e.type != TR::Entity::ENEMY_REX && (controller->flags.active != TR::ACTIVE || ((Character*)controller)->health <= 0)) continue; } else { diff --git a/src/level.h b/src/level.h index 0b89049..7eeaa01 100644 --- a/src/level.h +++ b/src/level.h @@ -84,7 +84,13 @@ struct Level : IGame { loadLevel((level.isEnd() || level.isHome()) ? level.getTitleId() : TR::LevelID(level.id + 1)); } - virtual void saveGame(int slot) { + virtual void invShow(int playerIndex, int page, int itemIndex = -1) { + if (itemIndex != -1) + inventory->pageItemIndex[page] = itemIndex; + inventory->toggle(playerIndex, Inventory::Page(page)); + } + + virtual void saveGame(int entityIndex) { LOG("Save Game... "); char *data = new char[sizeof(TR::SaveGame) + sizeof(TR::SaveGame::Item) * inventory->itemsCount + sizeof(TR::SaveGame::CurrentState) + sizeof(TR::SaveGame::Entity) * level.entitiesCount]; // oversized @@ -141,7 +147,7 @@ struct Level : IGame { LOG("Ok\n"); } - virtual void loadGame(int slot) { + virtual void loadGame() { LOG("Load Game... "); Stream *stream = NULL;//osLoadGame(); @@ -1199,22 +1205,41 @@ struct Level : IGame { #ifdef _OS_PSP atlas = new Texture(level.tiles4, level.tilesCount, level.cluts, level.clutsCount); #else - TR::Tile32 *tiles = new TR::Tile32[level.tilesCount]; + Texture::Tile *tiles = new Texture::Tile[level.tilesCount]; + for (int i = 0; i < level.tilesCount; i++) { + tiles[i].width = tiles[i].height = 256; + tiles[i].data = new uint32[256 * 256]; + } for (int i = 0; i < level.objectTexturesCount; i++) { TR::ObjectTexture &t = level.objectTextures[i]; short4 uv = t.getMinMax(); - level.fillObjectTexture(&tiles[t.tile.index], uv, t.tile.index, t.clut); + uv.z++; + uv.w++; + level.fillObjectTexture((TR::Tile32*)tiles[t.tile.index].data, uv, t.tile.index, t.clut); } for (int i = 0; i < level.spriteTexturesCount; i++) { TR::SpriteTexture &t = level.spriteTextures[i]; short4 uv = t.getMinMax(); - level.fillObjectTexture(&tiles[t.tile], uv, t.tile, t.clut); + uv.z++; + uv.w++; + level.fillObjectTexture((TR::Tile32*)tiles[t.tile].data, uv, t.tile, t.clut); + } + + for (int i = 0; i < level.tilesCount; i++) { + char buf[256]; + sprintf(buf, "texture/%s_%d.png", TR::LEVEL_INFO[level.id].name, i); + if (Stream::exists(buf)) { + delete[] tiles[i].data; + tiles[i].data = (uint32*)Texture::LoadPNG(Stream(buf), tiles[i].width, tiles[i].height); + } } atlas = new Texture(tiles, level.tilesCount); + for (int i = 0; i < level.tilesCount; i++) + delete[] tiles[i].data; delete[] tiles; #endif @@ -1582,6 +1607,8 @@ struct Level : IGame { inventory->toggle(playerIndex); } + bool invActive = inventory->isActive(); + inventory->update(); if (inventory->titleTimer > 1.0f) @@ -1591,7 +1618,7 @@ struct Level : IGame { float volWater, volTrack; - if (inventory->isActive() || level.isTitle()) { + if (invActive || level.isTitle()) { Sound::reverb.setRoomSize(vec3(1.0f)); volWater = 0.0f; volTrack = level.isTitle() ? 0.9f : 0.0f; diff --git a/src/texture.h b/src/texture.h index c3feb28..8bd3ebf 100644 --- a/src/texture.h +++ b/src/texture.h @@ -31,12 +31,18 @@ struct Texture : GAPI::Texture { #else Texture *tiles[32]; - Texture(TR::Tile32 *tiles, int tilesCount) : GAPI::Texture(256, 256, OPT_PROXY) { + struct Tile { + uint32 width; + uint32 height; + uint32 *data; + }; + + Texture(Tile *tiles, int tilesCount) : GAPI::Texture(256, 256, OPT_PROXY) { memset(this->tiles, 0, sizeof(this->tiles)); ASSERT(tilesCount < COUNT(this->tiles)); for (int i = 0; i < tilesCount; i++) - this->tiles[i] = new Texture(width, height, FMT_RGBA, 0, tiles + i); + this->tiles[i] = new Texture(tiles[i].width, tiles[i].height, FMT_RGBA, OPT_MIPMAPS, tiles[i].data); } #endif diff --git a/src/trigger.h b/src/trigger.h index 6c173f2..20057a1 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -778,6 +778,7 @@ struct Crystal : Controller { Crystal(IGame *game, int entity) : Controller(game, entity) { environment = new Texture(64, 64, FMT_RGBA, OPT_CUBEMAP | OPT_MIPMAPS | OPT_TARGET); + flags.collision = true; activate(); } @@ -786,6 +787,8 @@ struct Crystal : Controller { } virtual void deactivate(bool removeFromList = false) { + flags.invisible = true; + flags.collision = false; Controller::deactivate(removeFromList); getRoom().removeDynLight(entity); } diff --git a/src/ui.h b/src/ui.h index 1c6f32e..9c2ab3c 100644 --- a/src/ui.h +++ b/src/ui.h @@ -13,6 +13,8 @@ enum StringID { , STR_HELP_TEXT , STR_OFF , STR_ON + , STR_YES + , STR_NO , STR_SPLIT , STR_VR , STR_QUALITY_LOW @@ -33,6 +35,8 @@ enum StringID { , STR_OPTION , STR_INVENTORY , STR_ITEMS +// save game page + , STR_SAVEGAME // inventory option , STR_GAME , STR_MAP @@ -123,6 +127,8 @@ const char *STR[STR_MAX] = { , helpText , "Off" , "On" + , "YES" + , "NO" , "Split Screen" , "VR" , "Low" @@ -143,6 +149,8 @@ const char *STR[STR_MAX] = { , "OPTION" , "INVENTORY" , "ITEMS" +// save game page + , "Save Game?" // inventory option , "Game" , "Map" diff --git a/src/utils.h b/src/utils.h index e4ba30a..2bdde17 100644 --- a/src/utils.h +++ b/src/utils.h @@ -63,6 +63,11 @@ #define PI2 (PI * 2.0f) #define DEG2RAD (PI / 180.0f) #define RAD2DEG (180.0f / PI) + +#define COS30 0.86602540378f +#define COS45 0.70710678118f +#define COS60 0.50000000000f + #define SQR(x) ((x)*(x)) #define randf() ((float)rand()/RAND_MAX)