/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ultima/ultima4/ultima4.h" #include "ultima/ultima4/core/config.h" #include "ultima/ultima4/core/utils.h" #include "ultima/ultima4/views/stats.h" #include "ultima/ultima4/game/armor.h" #include "ultima/ultima4/game/context.h" #include "ultima/ultima4/views/menu.h" #include "ultima/ultima4/game/names.h" #include "ultima/ultima4/game/player.h" #include "ultima/ultima4/filesys/savegame.h" #include "ultima/ultima4/game/spell.h" #include "ultima/ultima4/map/tile.h" #include "ultima/ultima4/game/weapon.h" namespace Ultima { namespace Ultima4 { /** * StatsArea class implementation */ StatsArea::StatsArea() : _title(STATS_AREA_X * CHAR_WIDTH, 0 * CHAR_HEIGHT, STATS_AREA_WIDTH, 1), _mainArea(STATS_AREA_X * CHAR_WIDTH, STATS_AREA_Y * CHAR_HEIGHT, STATS_AREA_WIDTH, STATS_AREA_HEIGHT), _summary(STATS_AREA_X * CHAR_WIDTH, (STATS_AREA_Y + STATS_AREA_HEIGHT + 1) * CHAR_HEIGHT, STATS_AREA_WIDTH, 1), _view(STATS_PARTY_OVERVIEW) { // Generate a formatted Common::String for each menu item, // and then add the item to the menu. The Y value // for each menu item will be filled in later. for (int count = 0; count < 8; count++) { char outputBuffer[16]; snprintf(outputBuffer, sizeof(outputBuffer), "-%-11s%%s", getReagentName((Reagent)count)); _reagentsMixMenu.add(count, new IntMenuItem(outputBuffer, 1, 0, -1, (int *)g_context->_party->getReagentPtr((Reagent)count), 0, 99, 1, MENU_OUTPUT_REAGENT)); } _reagentsMixMenu.addObserver(this); } void StatsArea::setView(StatsView view) { this->_view = view; update(); } void StatsArea::prevItem() { _view = (StatsView)(_view - 1); if (_view < STATS_CHAR1) _view = STATS_MIXTURES; if (_view <= STATS_CHAR8 && (_view - STATS_CHAR1 + 1) > g_context->_party->size()) _view = (StatsView)(STATS_CHAR1 - 1 + g_context->_party->size()); update(); } void StatsArea::nextItem() { _view = (StatsView)(_view + 1); if (_view > STATS_MIXTURES) _view = STATS_CHAR1; if (_view <= STATS_CHAR8 && (_view - STATS_CHAR1 + 1) > g_context->_party->size()) _view = STATS_WEAPONS; update(); } void StatsArea::update(bool avatarOnly) { clear(); /* * update the upper stats box */ switch (_view) { case STATS_PARTY_OVERVIEW: showPartyView(avatarOnly); break; case STATS_CHAR1: case STATS_CHAR2: case STATS_CHAR3: case STATS_CHAR4: case STATS_CHAR5: case STATS_CHAR6: case STATS_CHAR7: case STATS_CHAR8: showPlayerDetails(); break; case STATS_WEAPONS: showWeapons(); break; case STATS_ARMOR: showArmor(); break; case STATS_EQUIPMENT: showEquipment(); break; case STATS_ITEMS: showItems(); break; case STATS_REAGENTS: showReagents(); break; case STATS_MIXTURES: showMixtures(); break; case MIX_REAGENTS: showReagents(true); break; } /* * update the lower stats box (food, gold, etc.) */ if (g_context->_transportContext == TRANSPORT_SHIP) _summary.textAt(0, 0, "F:%04d SHP:%02d", g_ultima->_saveGame->_food / 100, g_ultima->_saveGame->_shipHull); else _summary.textAt(0, 0, "F:%04d G:%04d", g_ultima->_saveGame->_food / 100, g_ultima->_saveGame->_gold); update(g_context->_aura); redraw(); } void StatsArea::update(Aura *observable, NoArg *arg) { Observer::update(observable, arg); } void StatsArea::update(Aura *aura) { byte mask = 0xff; for (int i = 0; i < VIRT_MAX; i++) { if (g_ultima->_saveGame->_karma[i] == 0) mask &= ~(1 << i); } switch (aura->getType()) { case Aura::NONE: _summary.drawCharMasked(0, STATS_AREA_WIDTH / 2, 0, mask); break; case Aura::HORN: _summary.drawChar(CHARSET_REDDOT, STATS_AREA_WIDTH / 2, 0); break; case Aura::JINX: _summary.drawChar('J', STATS_AREA_WIDTH / 2, 0); break; case Aura::NEGATE: _summary.drawChar('N', STATS_AREA_WIDTH / 2, 0); break; case Aura::PROTECTION: _summary.drawChar('P', STATS_AREA_WIDTH / 2, 0); break; case Aura::QUICKNESS: _summary.drawChar('Q', STATS_AREA_WIDTH / 2, 0); break; } _summary.update(); } void StatsArea::update(Party *party, PartyEvent &event) { update(); // Do a full update } void StatsArea::update(Menu *menu, MenuEvent &event) { update(); // Do a full update } void StatsArea::highlightPlayer(int player) { assertMsg(player < g_context->_party->size(), "player number out of range: %d", player); _mainArea.highlight(0, player * CHAR_HEIGHT, STATS_AREA_WIDTH * CHAR_WIDTH, CHAR_HEIGHT); #ifdef IOS_ULTIMA4 U4IOS::updateActivePartyMember(player); #endif } void StatsArea::clear() { for (int i = 0; i < STATS_AREA_WIDTH; i++) _title.drawChar(CHARSET_HORIZBAR, i, 0); _mainArea.clear(); _summary.clear(); } void StatsArea::redraw() { _title.update(); _mainArea.update(); _summary.update(); } void StatsArea::setTitle(const Common::String &s) { int titleStart = (STATS_AREA_WIDTH / 2) - ((s.size() + 2) / 2); _title.textAt(titleStart, 0, "%c%s%c", 16, s.c_str(), 17); } void StatsArea::showPartyView(bool avatarOnly) { const char *format = "%d%c%-9.8s%3d%s"; PartyMember *p = nullptr; int activePlayer = g_context->_party->getActivePlayer(); assertMsg(g_context->_party->size() <= 8, "party members out of range: %d", g_context->_party->size()); if (!avatarOnly) { for (int i = 0; i < g_context->_party->size(); i++) { p = g_context->_party->member(i); _mainArea.textAt(0, i, format, i + 1, (i == activePlayer) ? CHARSET_BULLET : '-', p->getName().c_str(), p->getHp(), _mainArea.colorizeStatus(p->getStatus()).c_str()); } } else { p = g_context->_party->member(0); _mainArea.textAt(0, 0, format, 1, (activePlayer == 0) ? CHARSET_BULLET : '-', p->getName().c_str(), p->getHp(), _mainArea.colorizeStatus(p->getStatus()).c_str()); } } void StatsArea::showPlayerDetails() { int player = _view - STATS_CHAR1; assertMsg(player < 8, "character number out of range: %d", player); PartyMember *p = g_context->_party->member(player); setTitle(p->getName()); _mainArea.textAt(0, 0, "%c %c", p->getSex(), p->getStatus()); Common::String classStr = getClassName(p->getClass()); int classStart = (STATS_AREA_WIDTH / 2) - (classStr.size() / 2); _mainArea.textAt(classStart, 0, "%s", classStr.c_str()); _mainArea.textAt(0, 2, " MP:%02d LV:%d", p->getMp(), p->getRealLevel()); _mainArea.textAt(0, 3, "STR:%02d HP:%04d", p->getStr(), p->getHp()); _mainArea.textAt(0, 4, "DEX:%02d HM:%04d", p->getDex(), p->getMaxHp()); _mainArea.textAt(0, 5, "INT:%02d EX:%04d", p->getInt(), p->getExp()); _mainArea.textAt(0, 6, "W:%s", p->getWeapon()->getName().c_str()); _mainArea.textAt(0, 7, "A:%s", p->getArmor()->getName().c_str()); } void StatsArea::showWeapons() { setTitle("Weapons"); int line = 0; int col = 0; _mainArea.textAt(0, line++, "A-%s", g_weapons->get(WEAP_HANDS)->getName().c_str()); for (int w = WEAP_HANDS + 1; w < WEAP_MAX; w++) { int n = g_ultima->_saveGame->_weapons[w]; if (n >= 100) n = 99; if (n >= 1) { const char *format = (n >= 10) ? "%c%d-%s" : "%c-%d-%s"; _mainArea.textAt(col, line++, format, w - WEAP_HANDS + 'A', n, g_weapons->get((WeaponType) w)->getAbbrev().c_str()); if (line >= (STATS_AREA_HEIGHT)) { line = 0; col += 8; } } } } void StatsArea::showArmor() { setTitle("Armour"); int line = 0; _mainArea.textAt(0, line++, "A -No Armour"); for (int a = ARMR_NONE + 1; a < ARMR_MAX; a++) { if (g_ultima->_saveGame->_armor[a] > 0) { const char *format = (g_ultima->_saveGame->_armor[a] >= 10) ? "%c%d-%s" : "%c-%d-%s"; _mainArea.textAt(0, line++, format, a - ARMR_NONE + 'A', g_ultima->_saveGame->_armor[a], g_armors->get((ArmorType) a)->getName().c_str()); } } } void StatsArea::showEquipment() { setTitle("Equipment"); int line = 0; _mainArea.textAt(0, line++, "%2d Torches", g_ultima->_saveGame->_torches); _mainArea.textAt(0, line++, "%2d Gems", g_ultima->_saveGame->_gems); _mainArea.textAt(0, line++, "%2d Keys", g_ultima->_saveGame->_keys); if (g_ultima->_saveGame->_sextants > 0) _mainArea.textAt(0, line++, "%2d Sextants", g_ultima->_saveGame->_sextants); } void StatsArea::showItems() { int i, j; char buffer[17]; setTitle("Items"); int line = 0; if (g_ultima->_saveGame->_stones != 0) { j = 0; for (i = 0; i < 8; i++) { if (g_ultima->_saveGame->_stones & (1 << i)) buffer[j++] = getStoneName((Virtue) i)[0]; } buffer[j] = '\0'; _mainArea.textAt(0, line++, "Stones:%s", buffer); } if (g_ultima->_saveGame->_runes != 0) { j = 0; for (i = 0; i < 8; i++) { if (g_ultima->_saveGame->_runes & (1 << i)) buffer[j++] = getVirtueName((Virtue) i)[0]; } buffer[j] = '\0'; _mainArea.textAt(0, line++, "Runes:%s", buffer); } if (g_ultima->_saveGame->_items & (ITEM_CANDLE | ITEM_BOOK | ITEM_BELL)) { buffer[0] = '\0'; if (g_ultima->_saveGame->_items & ITEM_BELL) { Common::strcat_s(buffer, getItemName(ITEM_BELL)); Common::strcat_s(buffer, " "); } if (g_ultima->_saveGame->_items & ITEM_BOOK) { Common::strcat_s(buffer, getItemName(ITEM_BOOK)); Common::strcat_s(buffer, " "); } if (g_ultima->_saveGame->_items & ITEM_CANDLE) { Common::strcat_s(buffer, getItemName(ITEM_CANDLE)); buffer[15] = '\0'; } _mainArea.textAt(0, line++, "%s", buffer); } if (g_ultima->_saveGame->_items & (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T)) { j = 0; if (g_ultima->_saveGame->_items & ITEM_KEY_T) buffer[j++] = getItemName(ITEM_KEY_T)[0]; if (g_ultima->_saveGame->_items & ITEM_KEY_L) buffer[j++] = getItemName(ITEM_KEY_L)[0]; if (g_ultima->_saveGame->_items & ITEM_KEY_C) buffer[j++] = getItemName(ITEM_KEY_C)[0]; buffer[j] = '\0'; _mainArea.textAt(0, line++, "3 Part Key:%s", buffer); } if (g_ultima->_saveGame->_items & ITEM_HORN) _mainArea.textAt(0, line++, "%s", getItemName(ITEM_HORN)); if (g_ultima->_saveGame->_items & ITEM_WHEEL) _mainArea.textAt(0, line++, "%s", getItemName(ITEM_WHEEL)); if (g_ultima->_saveGame->_items & ITEM_SKULL) _mainArea.textAt(0, line++, "%s", getItemName(ITEM_SKULL)); } void StatsArea::showReagents(bool active) { setTitle("Reagents"); int line = 0, r = REAG_ASH; Common::String shortcut("A"); _reagentsMixMenu.show(&_mainArea); for (const auto *item : _reagentsMixMenu) { if (item->isVisible()) { // Insert the reagent menu item shortcut character shortcut.setChar('A' + r, 0); if (active) _mainArea.textAt(0, line++, "%s", _mainArea.colorizeString(shortcut, FG_YELLOW, 0, 1).c_str()); else _mainArea.textAt(0, line++, "%s", shortcut.c_str()); } r++; } } void StatsArea::showMixtures() { setTitle("Mixtures"); int line = 0; int col = 0; for (int s = 0; s < SPELL_MAX; s++) { int n = g_ultima->_saveGame->_mixtures[s]; if (n >= 100) n = 99; if (n >= 1) { _mainArea.textAt(col, line++, "%c-%02d", s + 'A', n); if (line >= (STATS_AREA_HEIGHT)) { if (col >= 10) break; line = 0; col += 5; } } } } void StatsArea::resetReagentsMenu() { int i = 0, row = 0; for (auto *item : _reagentsMixMenu) { if (g_ultima->_saveGame->_reagents[i++] > 0) { item->setVisible(true); item->setY(row++); } else { item->setVisible(false); } } _reagentsMixMenu.reset(false); } } // End of namespace Ultima4 } // End of namespace Ultima