diff --git a/src/camera.h b/src/camera.h index 7db21be..6f59c37 100644 --- a/src/camera.h +++ b/src/camera.h @@ -143,22 +143,19 @@ struct Camera : ICamera { if (state == STATE_CUTSCENE) { timer += Core::deltaTime * 30.0f; float t = timer - int(timer); - int indexA = int(timer) % level->cameraFramesCount; - int indexB = (indexA + 1) % level->cameraFramesCount; - TR::CameraFrame *frameA = &level->cameraFrames[indexA]; - TR::CameraFrame *frameB = &level->cameraFrames[indexB]; + int indexA = min(int(timer), level->cameraFramesCount - 1); + int indexB = min((indexA + 1), level->cameraFramesCount - 1); - if (indexB < indexA) { - indexB = indexA; - timer = 0.0f; - if (level->cutEntity != -1) { - // TODO: level end - level->initCutscene(); - game->playTrack(0, true); - } else + 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]; + const int eps = 512; if (abs(frameA->pos.x - frameB->pos.x) > eps || abs(frameA->pos.y - frameB->pos.y) > eps || abs(frameA->pos.z - frameB->pos.z) > eps) { diff --git a/src/controller.h b/src/controller.h index 21636ea..be1158c 100644 --- a/src/controller.h +++ b/src/controller.h @@ -28,6 +28,7 @@ struct ICamera { struct IGame { virtual ~IGame() {} + virtual void loadLevel(TR::LevelID id) {} virtual TR::Level* getLevel() { return NULL; } virtual MeshBuilder* getMesh() { return NULL; } virtual ICamera* getCamera() { return NULL; } diff --git a/src/game.h b/src/game.h index 9b946ce..3590e5b 100644 --- a/src/game.h +++ b/src/game.h @@ -8,7 +8,18 @@ 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); @@ -21,6 +32,8 @@ namespace Game { } void init(Stream *lvl) { + nextLevel = NULL; + Core::init(); UI::init(level); @@ -43,7 +56,7 @@ namespace Game { } void init(char *lvlName = NULL, char *sndName = NULL) { - if (!lvlName) lvlName = (char*)"level/LEVEL2.PSX"; + if (!lvlName) lvlName = (char*)"level/TITLE.PSX"; init(new Stream(lvlName)); } @@ -69,6 +82,15 @@ 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 diff --git a/src/inventory.h b/src/inventory.h index eeaf0a5..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,7 +142,8 @@ struct Inventory { } void choose() { - if (anim) anim->setAnim(0, 0, false); + if (!anim) return; + anim->setAnim(0, 0, false); } } *items[INVENTORY_MAX_ITEMS]; @@ -139,7 +156,7 @@ struct Inventory { delete stream; } - Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0) { + 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); @@ -147,9 +164,8 @@ struct Inventory { add(TR::Entity::INV_SOUND); add(TR::Entity::INV_CONTROLS); - if (id != TR::TITLE) { + if (id != TR::TITLE && id != TR::GYM) { /* - add(TR::Entity::INV_COMPASS); if (level->extra.inv.map != -1) add(TR::Entity::INV_MAP); if (level->extra.inv.gamma != -1) @@ -164,15 +180,19 @@ struct Inventory { // add(TR::Entity::INV_SCION, 1); // add(TR::Entity::INV_KEY_1, 1); // add(TR::Entity::INV_PUZZLE_1, 1); + } - for (int i = 0; i < COUNT(background); i++) - background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false); - } else { + 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; @@ -308,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; @@ -381,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]; @@ -400,18 +437,82 @@ struct Inventory { bool ready = active && phaseRing == 1.0f && phasePage == 1.0f; - if (index == targetIndex && targetPage == page && ready && !chosen) { - 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); } + enum KeyDir { NONE, LEFT, RIGHT, UP, DOWN } dir; + + 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)]; @@ -485,6 +586,9 @@ struct Inventory { toggle(); } } + + if (!isActive() && nextLevel != TR::LEVEL_MAX) + game->loadLevel(nextLevel); } void prepareBackground() { @@ -534,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 : ; } } } @@ -674,7 +823,7 @@ 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 }; if (game->getLevel()->id != TR::TITLE) UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width); @@ -690,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 2148d99..b266344 100644 --- a/src/lara.h +++ b/src/lara.h @@ -1480,10 +1480,14 @@ struct Lara : Character { case 49 : if (state != STATE_SURF_TREAD) return 0; break; case 50 : // end of GYM if (level->tracks[track].once) { - // back to title - } else + 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; @@ -1713,7 +1717,7 @@ struct Lara : Character { ((Camera*)level->cameraController)->viewTarget = (Controller*)level->entities[cmd.args].controller; break; case TR::Action::END : - LOG("END\n"); + game->loadLevel(level->id == TR::LEVEL_10C ? TR::TITLE : TR::LevelID(level->id + 1)); break; case TR::Action::SOUNDTRACK : { int track = doTutorial(cmd.args); diff --git a/src/level.h b/src/level.h index 36802c4..9007254 100644 --- a/src/level.h +++ b/src/level.h @@ -15,6 +15,8 @@ #include "debug.h" #endif +extern void loadAsync(Stream *stream, void *userData); + struct Level : IGame { TR::Level level; Inventory inventory; @@ -44,12 +46,25 @@ struct Level : IGame { int curTrack; bool lastTitle; + bool isEnded; TR::Effect effect; float effectTimer; int flickerIdx; // IGame implementation ======== + virtual void loadLevel(TR::LevelID id) { + if (isEnded) return; + + isEnded = true; + char buf[64]; + buf[0] = 0; + strcat(buf, "level/"); + strcat(buf, TR::LEVEL_INFO[id].name); + strcat(buf, ".PSX"); + new Stream(buf, loadAsync); + } + virtual TR::Level* getLevel() { return &level; } @@ -233,7 +248,7 @@ struct Level : IGame { } //============================== - Level(Stream &stream) : level(stream), inventory(this), lara(NULL) { + Level(Stream &stream) : level(stream), inventory(this), lara(NULL), isEnded(false) { params->time = 0.0f; #ifdef _DEBUG @@ -439,8 +454,18 @@ struct Level : IGame { 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 healthColor[5] = { 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F }; - static const uint32 oxygenColor[5] = { 0xFF647464, 0xFFA47848, 0xFF647464, 0xFF4C504C, 0xFF303030 }; + 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; @@ -472,20 +497,21 @@ struct Level : IGame { uvCount = 4; switch (id) { - case 0 : // white color + 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; - case 1 : // health bar - src = (TR::Color32*)&healthColor[0]; - tex = &healthTile; - mm.w = 4; // height - 1 - break; - case 2 : // oxygen bar - src = (TR::Color32*)&oxygenColor[0]; - tex = &oxygenTile; - mm.w = 4; // height - 1 - break; default : return; } @@ -493,6 +519,8 @@ struct Level : IGame { uv = tex->texCoord; uv[2].y += mm.w; uv[3].y += mm.w; + uv[1].x += mm.z; + uv[2].x += mm.z; } } @@ -583,12 +611,14 @@ struct Level : IGame { tiles->add(uv, texIdx++); } - // add white color - tiles->add(short4(2048, 2048, 2048, 2048), 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(); @@ -1390,7 +1420,7 @@ struct Level : IGame { } 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); + 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()); @@ -1400,7 +1430,7 @@ struct Level : IGame { } if (!lara->dozy && (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER)) - UI::renderBar(1, vec2(32, 32), size, oxygen); + UI::renderBar(UI::BAR_OXYGEN, vec2(32, 32), size, oxygen); } inventory.renderUI(); @@ -1414,6 +1444,14 @@ struct Level : IGame { void render() { bool title = inventory.isActive() || level.id == TR::TITLE; bool copyBg = title && lastTitle != title; + lastTitle = title; + + if (isEnded) { + UI::begin(); + UI::textOut(vec2(0, 480 - 16), STR_LOADING, UI::aCenter, UI::width); + UI::end(); + return; + } if (copyBg) { Core::defaultTarget = inventory.background[0]; @@ -1430,8 +1468,6 @@ struct Level : IGame { renderInventory(); renderUI(); - - lastTitle = title; } }; diff --git a/src/mesh.h b/src/mesh.h index 3adefd3..123c112 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -5,7 +5,7 @@ #include "format.h" -TR::ObjectTexture whiteTile, healthTile, oxygenTile; +TR::ObjectTexture whiteTile, barTile[3]; struct MeshRange { int iStart; @@ -906,7 +906,7 @@ struct MeshBuilder { 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); @@ -924,8 +924,8 @@ struct MeshBuilder { v.normal = { 0, 0, 0, 0 }; v.color = *((ubyte4*)&color); - short2 uv = type == 0 ? healthTile.texCoord[i] : oxygenTile.texCoord[i]; - + short2 uv = tile.texCoord[i]; + v.texCoord = { uv.x, uv.y, 32767, 32767 }; v.param = { 0, 0, 0, 0 }; } diff --git a/src/platform/web/build.bat b/src/platform/web/build.bat index 6f69e93..b7676b4 100644 --- a/src/platform/web/build.bat +++ b/src/platform/web/build.bat @@ -3,7 +3,7 @@ cls 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=./level/LEVEL2.PSX +set PRELOAD=./level/TITLE.PSX;./level/TITLEH.PCX echo. call em++ %SRC% %FLAGS% -o %PROJ%.js --preload-file %PRELOAD% 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 e0d0410..e02c804 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -107,7 +107,7 @@ (.PHD, .PSX)

OpenLara on github & facebook
-
last update: 12.09.2017
+
last update: 13.09.2017

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/ui.h b/src/ui.h index 2b40873..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,6 +255,10 @@ 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; @@ -204,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); } };