diff --git a/src/character.h b/src/character.h index 26efff3..ec6a72f 100644 --- a/src/character.h +++ b/src/character.h @@ -128,7 +128,7 @@ struct Character : Controller { virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) { if (getEntity().isEnemy() && health > 0.0f && health <= damage) - level->levelStats.kills++; + level->stats.kills++; health = max(0.0f, health - damage); } diff --git a/src/debug.h b/src/debug.h index 5f6a266..015a711 100644 --- a/src/debug.h +++ b/src/debug.h @@ -711,7 +711,7 @@ namespace Debug { sprintf(buf, "floor = %d, roomBelow = %d, roomAbove = %d, roomNext = %d, height = %d", info.floorIndex, info.roomBelow, info.roomAbove, info.roomNext, int(info.floor - info.ceiling)); Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf); - const SaveProgress &stats = game->getLevel()->levelStats; + const SaveProgress &stats = game->getLevel()->stats; sprintf(buf, "stats: time = %d, distance = %d, secrets = %c%c%c, pickups = %d, mediUsed = %d, ammoUsed = %d, kills = %d", stats.time, stats.distance, (stats.secrets & 4) ? '1' : '0', (stats.secrets & 2) ? '1' : '0', diff --git a/src/format.h b/src/format.h index 7960258..a1febe6 100644 --- a/src/format.h +++ b/src/format.h @@ -2344,8 +2344,7 @@ namespace TR { } }; - SaveProgress gameStats; - SaveProgress levelStats; + SaveProgress stats; SaveState state; int cutEntity; @@ -2839,9 +2838,8 @@ namespace TR { initRoomMeshes(); initAnimTex(); - memset(&gameStats, 0, sizeof(gameStats)); - memset(&levelStats, 0, sizeof(levelStats)); - memset(&state, 0, sizeof(state)); + memset(&stats, 0, sizeof(stats)); + memset(&state, 0, sizeof(state)); initExtra(); initCutscene(); diff --git a/src/game.h b/src/game.h index 4189a00..4a0988a 100644 --- a/src/game.h +++ b/src/game.h @@ -186,6 +186,11 @@ namespace Game { if (level->isEnded) return true; + if (Input::down[ik0] && !level->inventory->isActive()) { + level->inventory->toggle(0, Inventory::PAGE_LEVEL_STATS); + Input::down[ik0] = false; + } + if (Input::down[ik5] && !level->inventory->isActive()) { if (level->players[0]->canSaveGame()) level->saveGame(true, false); diff --git a/src/inventory.h b/src/inventory.h index f47f607..eff5f13 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -75,8 +75,8 @@ struct OptionItem { UI::textOut(vec2(x, y), vStr, UI::aCenter, w, alpha, UI::SHADE_GRAY); // color as StringID if (type == TYPE_PARAM && active) { - float maxWidth = UI::getTextSize(STR[color + value]).x; - maxWidth = maxWidth * 0.5f + 8.0f; + int maxWidth = UI::getTextSize(STR[color + value]).x; + maxWidth = maxWidth / 2 + 8; x += w * 0.5f; if (checkValue(value - 1)) UI::specOut(vec2(x - maxWidth - 16.0f, y), 108); if (checkValue(value + 1)) UI::specOut(vec2(x + maxWidth, y), 109); @@ -191,6 +191,7 @@ struct Inventory { PAGE_INVENTORY, PAGE_ITEMS, PAGE_SAVEGAME, + PAGE_LEVEL_STATS, PAGE_MAX }; @@ -787,7 +788,8 @@ struct Inventory { phaseRing = active ? 1.0f : 0.0f; slot = 1; } else { - game->playSound(active ? TR::SND_INV_SHOW : TR::SND_INV_HIDE, p); + if (curPage != PAGE_LEVEL_STATS) + game->playSound(active ? TR::SND_INV_SHOW : TR::SND_INV_HIDE, p); } chosen = false; @@ -1085,7 +1087,11 @@ struct Inventory { Item *item = items[getGlobalIndex(page, index)]; - if (page == PAGE_SAVEGAME) { + if (page == PAGE_LEVEL_STATS) { + if (Input::lastState[playerIndex] != cMAX) { + toggle(playerIndex, targetPage); + } + } else if (page == PAGE_SAVEGAME) { if (Input::lastState[playerIndex] == cLeft || Input::lastState[playerIndex] == cRight) slot ^= 1; @@ -1345,7 +1351,7 @@ struct Inventory { } UI::textOut(vec2(0, 480 - 32), str, UI::aCenter, UI::width); - float tw = UI::getTextSize(STR[str]).x; + int tw = UI::getTextSize(STR[str]).x; if (item->value > 0) UI::specOut(vec2((UI::width - tw) * 0.5f - 32.0f, 480 - 32), 108); if (item->value < 2) UI::specOut(vec2((UI::width + tw) * 0.5f + 16.0f, 480 - 32), 109); @@ -1716,10 +1722,39 @@ struct Inventory { renderPage(targetPage); } + void showLevelStats(const vec2 &pos) { + char buf[256]; + char time[16]; + TR::Level *level = game->getLevel(); + SaveProgress &stats = level->stats; + + int secretsMax = 3; + int secrets = ((stats.secrets & 1) != 0) + + ((stats.secrets & 2) != 0) + + ((stats.secrets & 4) != 0); + + int s = stats.time % 60; + int m = stats.time / 60 % 60; + int h = stats.time / 3600; + + if (h) + sprintf(time, "%d:%02d:%02d", h, m, s); + else + sprintf(time, "%d:%02d", m, s); + + sprintf(buf, STR[STR_LEVEL_STATS], + TR::LEVEL_INFO[level->id].title, + stats.kills, + stats.pickups, + secrets, secretsMax, time); + + UI::textOut(pos, buf, UI::aCenter, UI::width); + } + void renderUI() { if (!active || phaseRing < 1.0f) return; - static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_ITEMS, STR_SAVEGAME }; + static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_ITEMS, STR_SAVEGAME, STR_LEVEL_STATS }; float eye = UI::width * Core::eye * 0.01f; @@ -1738,6 +1773,11 @@ struct Inventory { return; } + if (page == PAGE_LEVEL_STATS) { + showLevelStats(vec2(-eye, 180)); + 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 f3e5a52..1cd0ccb 100644 --- a/src/lara.h +++ b/src/lara.h @@ -1020,7 +1020,7 @@ struct Lara : Character { } if (shots) { - level->levelStats.ammoUsed += ((wpnCurrent == TR::Entity::SHOTGUN) ? 1 : 2); + level->stats.ammoUsed += ((wpnCurrent == TR::Entity::SHOTGUN) ? 1 : 2); game->playSound(wpnGetSound(), pos, Sound::PAN); game->playSound(TR::SND_RICOCHET, nearPos, Sound::PAN); @@ -1635,7 +1635,7 @@ struct Lara : Character { case TR::Entity::INV_UZIS : wpnChange(TR::Entity::UZIS); break; case TR::Entity::INV_MEDIKIT_SMALL : case TR::Entity::INV_MEDIKIT_BIG : - level->levelStats.mediUsed += (item == TR::Entity::INV_MEDIKIT_SMALL) ? 1 : 2; + level->stats.mediUsed += (item == TR::Entity::INV_MEDIKIT_SMALL) ? 1 : 2; damageTime = LARA_DAMAGE_TIME; health = min(LARA_MAX_HEALTH, health + (item == TR::Entity::INV_MEDIKIT_SMALL ? LARA_MAX_HEALTH / 2 : LARA_MAX_HEALTH)); game->playSound(TR::SND_HEALTH, pos, Sound::PAN); @@ -2107,8 +2107,8 @@ struct Lara : Character { effect = TR::Effect::Type(cmd.args); break; case TR::Action::SECRET : - if (!(level->levelStats.secrets & (1 << cmd.args))) { - level->levelStats.secrets |= 1 << cmd.args; + if (!(level->stats.secrets & (1 << cmd.args))) { + level->stats.secrets |= 1 << cmd.args; if (!game->playSound(TR::SND_SECRET, pos)) game->playTrack(TR::TRACK_TR1_SECRET, true); } @@ -2839,7 +2839,7 @@ struct Lara : Character { pickupList[i]->deactivate(); pickupList[i]->flags.invisible = true; game->invAdd(pickupList[i]->getEntity().type, 1); - level->levelStats.pickups++; + level->stats.pickups++; } pickupListCount = 0; } @@ -3123,7 +3123,7 @@ struct Lara : Character { statsDistDelta += (pos - oldPos).length(); while (statsDistDelta >= UNITS_PER_METER) { statsDistDelta -= UNITS_PER_METER; - level->levelStats.distance++; + level->stats.distance++; } } } diff --git a/src/level.h b/src/level.h index 913f6f1..b47f945 100644 --- a/src/level.h +++ b/src/level.h @@ -82,7 +82,7 @@ struct Level : IGame { // save next level level.id = TR::getNextSaveLevel(level.id); // get next not cutscene level if (level.id != TR::LVL_MAX && !level.isTitle()) { - memset(&level.levelStats, 0, sizeof(level.levelStats)); + memset(&level.stats, 0, sizeof(level.stats)); saveGame(false, false); loadSlot = getSaveSlot(level.id, false); } @@ -123,7 +123,7 @@ struct Level : IGame { if (dummy) memset(levelStats, 0, sizeof(*levelStats)); else - *levelStats = level.levelStats; + *levelStats = level.stats; // inventory items int32 *itemsCount = (int32*)ptr; @@ -191,8 +191,8 @@ struct Level : IGame { uint8 *ptr = data; // level progress stats - level.levelStats = *(SaveProgress*)ptr; - ptr += sizeof(level.levelStats); + level.stats = *(SaveProgress*)ptr; + ptr += sizeof(level.stats); // inventory items int32 itemsCount = *(int32*)ptr; @@ -247,7 +247,7 @@ struct Level : IGame { level.state.flags.track = 0; playTrack(track); } else - memset(&level.levelStats, 0, sizeof(level.levelStats)); + memset(&level.stats, 0, sizeof(level.stats)); statsTimeDelta = 0.0f; } @@ -284,7 +284,7 @@ struct Level : IGame { } else slot = saveSlots[index]; SaveProgress *levelStats = (SaveProgress*)slot.data; - *levelStats = level.levelStats; + *levelStats = level.stats; } else { removeSaveSlot(id, checkpoint); // remove checkpoints and level saves saveSlots.push(createSaveSlot(id, checkpoint)); @@ -1771,7 +1771,7 @@ struct Level : IGame { statsTimeDelta += Core::deltaTime; while (statsTimeDelta >= 1.0f) { statsTimeDelta -= 1.0f; - level.levelStats.time++; + level.stats.time++; } params->time += Core::deltaTime; diff --git a/src/ui.h b/src/ui.h index 113f5ef..10154fd 100644 --- a/src/ui.h +++ b/src/ui.h @@ -11,6 +11,7 @@ enum StringID { , STR_LOADING , STR_HELP_PRESS , STR_HELP_TEXT + , STR_LEVEL_STATS , STR_HINT_SAVING , STR_HINT_SAVING_DONE , STR_HINT_SAVING_ERROR @@ -122,6 +123,12 @@ const char *helpText = "DOZY on - Look + Duck + Action + Jump@" "DOZY off - Walk"; +const char *levelStats = + "%s@@@" + "KILLS %d@@" + "PICKUPS %d@@" + "SECRETS %d of %d@@" + "TIME TAKEN %s"; const char *STR[STR_MAX] = { "Not implemented yet!" @@ -129,6 +136,7 @@ const char *STR[STR_MAX] = { , "Loading..." , "Press H for help" , helpText + , levelStats , "Saving game..." , "Saving done!" , "SAVING ERROR!" @@ -256,7 +264,21 @@ namespace UI { return char_map[c - 32]; } - vec2 getTextSize(const char *text) { + short2 getLineSize(const char *text) { + int x = 0; + + while (char c = *text++) { + if (c == ' ' || c == '_') { + x += 6; + } else if (c == '@') { + break; + } else + x += char_width[charRemap(c)] + 1; + } + return short2(x, 16); + } + + short2 getTextSize(const char *text) { int x = 0, w = 0, h = 16; while (char c = *text++) { @@ -271,7 +293,7 @@ namespace UI { } w = max(w, x); - return vec2(float(w), float(h)); + return short2(w, h); } #define MAX_CHARS DYN_MESH_QUADS @@ -347,6 +369,19 @@ namespace UI { SHADE_GRAY = 2, }; + int getLeftOffset(const char *text, Align align, int width) { + if (align != aLeft) { + int lineWidth = getLineSize(text).x; + + if (align == aCenter) + return (width - lineWidth) / 2; + + if (align == aRight) + return width - lineWidth; + } + return 0; + } + void textOut(const vec2 &pos, const char *text, Align align = aLeft, float width = 0, uint8 alpha = 255, ShadeType shade = SHADE_ORANGE, bool isShadow = false) { if (!text) return; @@ -358,26 +393,19 @@ namespace UI { MeshBuilder *mesh = game->getMesh(); int seq = level->extra.glyphs; - int x = int(pos.x); + int x = int(pos.x) + getLeftOffset(text, align, width); int y = int(pos.y); - if (align == aCenter) - x += int((width - getTextSize(text).x) / 2); - - if (align == aRight) - x += int(width - getTextSize(text).x); - - int left = x; - while (char c = *text++) { - if (c == ' ' || c == '_') { - x += 6; + + if (c == '@') { + x = int(pos.x) + getLeftOffset(text, align, width); + y += 16; continue; } - if (c == '@') { - x = left; - y += 16; + if (c == ' ' || c == '_') { + x += 6; continue; }