1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-14 00:54:05 +02:00

#22 end_of_level trigger; #11 title menu at start, passport, global UI string list

This commit is contained in:
XProger
2017-09-13 04:34:36 +03:00
parent 4c99970b9d
commit f71270c659
11 changed files with 454 additions and 140 deletions

View File

@@ -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) {

View File

@@ -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; }

View File

@@ -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

View File

@@ -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)]);
}
};

View File

@@ -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);

View File

@@ -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;
}
};

View File

@@ -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,7 +924,7 @@ 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 };

View File

@@ -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

View File

@@ -107,7 +107,7 @@
<input type="button" value="Browse Level" onclick="document.getElementById('browseFile').click();" /> (.PHD, .PSX)
<p style="margin:8px">
OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br>
<br><i>last update: 12.09.2017</i><br>
<br><i>last update: 13.09.2017</i><br>
</p>
</span>

View File

@@ -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

173
src/ui.h
View File

@@ -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);
}
};