Skip to content

Commit

Permalink
MenuCharacter: Organize stats by category
Browse files Browse the repository at this point in the history
Stats are now displayed in 1 of 4 categories:

- Core (e.g. HP/MP)
- Offenese (e.g. Damage, Accuracy)
- Defense (e.g. Absorb, Avoidance, elemental resists)
- Misc. (e.g. bonus XP/gold)
  • Loading branch information
dorkster committed Dec 27, 2024
1 parent b87129a commit 401482c
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 45 deletions.
1 change: 1 addition & 0 deletions RELEASE_NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Engine features:
* Added 'preview_enabled' and 'preview_pos' to menus/inventory.txt config.
* Added 'font' property to engine/combat_text.txt config.
* New mouse movment behavior. Click once to move the player to a target and, if applicable, interact with it.
* Stats on the character page are now organized by categories: Core, Offense, Defense, and Misc.
* Android: Enabled use of external input devices (gamepads, keyboards, mice)

Engine fixes:
Expand Down
146 changes: 105 additions & 41 deletions src/MenuCharacter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,77 +303,141 @@ void MenuCharacter::refreshStats() {
unsigned stat_index = 0;
size_t resource_offset_index = Stats::COUNT + eset->damage_types.count + eset->elements.list.size();

ss.str("");
ss << msg->get("Core Stats");
statList->set(stat_index, ss.str(), "");
statList->setRowHighlight(stat_index, true);
stat_index++;

for (int i=0; i<Stats::COUNT; ++i) {
if (Stats::CATEGORY[i] != Stats::CATEGORY_CORE)
continue;

if (!show_stat[i]) continue;

// Stats::ABS_MIN handles both min and max
if (i == Stats::ABS_MAX) continue;

// insert resource stats (execpt stealing) before accuracy
if (i == Stats::ACCURACY) {
for (size_t j = 0; j < eset->resource_stats.list.size(); ++j) {
for (size_t k = 0; k < EngineSettings::ResourceStats::STAT_STEAL; ++k) {
if (show_stat[resource_offset_index + (j * EngineSettings::ResourceStats::STAT_COUNT) + k]) {
ss.str("");
ss << eset->resource_stats.list[j].text[k] << ": " << Utils::floatToString(pc->stats.getResourceStat(j, k), eset->number_format.character_menu);
statList->set(stat_index, ss.str(), resourceStatTooltip(j, k));
stat_index++;
}
}
}
}

// insert damage stats before absorb min
if (i == Stats::ABS_MIN) {
for (size_t j = 0; j < eset->damage_types.list.size(); ++j) {
if (show_stat[Stats::COUNT + (j*2)] || show_stat[Stats::COUNT + (j*2) + 1]) {
float min_dmg = pc->stats.getDamageMin(j);
float max_dmg = pc->stats.getDamageMax(j);

ss.str("");
ss << eset->damage_types.list[j].name << ": " << Utils::createMinMaxString(min_dmg, max_dmg, eset->number_format.character_menu);
statList->set(stat_index, ss.str(), damageTooltip(j));
stat_index++;
}
}

ss.str("");
ss << msg->get("Absorb") << ": " << Utils::createMinMaxString(pc->stats.get(Stats::ABS_MIN), pc->stats.get(Stats::ABS_MAX), eset->number_format.character_menu);
}
else {
ss.str("");
ss << Stats::NAME[i] << ": " << Utils::floatToString(pc->stats.get(static_cast<Stats::STAT>(i)), 2);
if (Stats::PERCENT[i]) ss << "%";
}

ss.str("");
ss << " " << Stats::NAME[i] << ": " << Utils::floatToString(pc->stats.get(static_cast<Stats::STAT>(i)), 2);
if (Stats::PERCENT[i]) ss << "%";
statList->set(stat_index, ss.str(), statTooltip(i));
stat_index++;
}

// insert resource stealing stats after HP/MP steal
// insert resource stats (execpt stealing)
for (size_t j = 0; j < eset->resource_stats.list.size(); ++j) {
for (size_t k = EngineSettings::ResourceStats::STAT_STEAL; k < EngineSettings::ResourceStats::STAT_COUNT; ++k) {
for (size_t k = 0; k < EngineSettings::ResourceStats::STAT_STEAL; ++k) {
if (show_stat[resource_offset_index + (j * EngineSettings::ResourceStats::STAT_COUNT) + k]) {
ss.str("");
ss << eset->resource_stats.list[j].text[k] << ": " << Utils::floatToString(pc->stats.getResourceStat(j, k), eset->number_format.character_menu) << "%";
ss << " " << eset->resource_stats.list[j].text[k] << ": " << Utils::floatToString(pc->stats.getResourceStat(j, k), eset->number_format.character_menu);
statList->set(stat_index, ss.str(), resourceStatTooltip(j, k));
stat_index++;
}
}
}

ss.str("");
ss << msg->get("Offensive Stats");
statList->set(stat_index, ss.str(), "");
statList->setRowHighlight(stat_index, true);
stat_index++;

// insert damage stats
for (size_t j = 0; j < eset->damage_types.list.size(); ++j) {
if (show_stat[Stats::COUNT + (j*2)] || show_stat[Stats::COUNT + (j*2) + 1]) {
float min_dmg = pc->stats.getDamageMin(j);
float max_dmg = pc->stats.getDamageMax(j);

ss.str("");
ss << " " << eset->damage_types.list[j].name << ": " << Utils::createMinMaxString(min_dmg, max_dmg, eset->number_format.character_menu);
statList->set(stat_index, ss.str(), damageTooltip(j));
stat_index++;
}
}
for (int i=0; i<Stats::COUNT; ++i) {
if (Stats::CATEGORY[i] != Stats::CATEGORY_OFFENSE)
continue;

if (!show_stat[i]) continue;

ss.str("");
ss << " " << Stats::NAME[i] << ": " << Utils::floatToString(pc->stats.get(static_cast<Stats::STAT>(i)), 2);
if (Stats::PERCENT[i]) ss << "%";
statList->set(stat_index, ss.str(), statTooltip(i));
stat_index++;
}

ss.str("");
ss << msg->get("Defensive Stats");
statList->set(stat_index, ss.str(), "");
statList->setRowHighlight(stat_index, true);
stat_index++;

ss.str("");
ss << " " << msg->get("Absorb") << ": " << Utils::createMinMaxString(pc->stats.get(Stats::ABS_MIN), pc->stats.get(Stats::ABS_MAX), eset->number_format.character_menu);
statList->set(stat_index, ss.str(), statTooltip(Stats::ABS_MIN));
stat_index++;

for (int i=0; i<Stats::COUNT; ++i) {
if (Stats::CATEGORY[i] != Stats::CATEGORY_DEFENSE)
continue;

if (!show_stat[i]) continue;

// absorb was already added to the list
if (i == Stats::ABS_MIN || i == Stats::ABS_MAX) continue;

ss.str("");
ss << " " << Stats::NAME[i] << ": " << Utils::floatToString(pc->stats.get(static_cast<Stats::STAT>(i)), 2);
if (Stats::PERCENT[i]) ss << "%";
statList->set(stat_index, ss.str(), statTooltip(i));
stat_index++;
}

if (show_resists) {
for (size_t i=0; i<eset->elements.list.size(); ++i) {
if (!show_stat[Stats::COUNT + eset->damage_types.count + i])
continue;

ss.str("");
ss << msg->getv("Resistance (%s)", eset->elements.list[i].name.c_str()) << ": " << Utils::floatToString(pc->stats.getResist(i), eset->number_format.character_menu) << "%";
ss << " " << msg->getv("Resistance (%s)", eset->elements.list[i].name.c_str()) << ": " << Utils::floatToString(pc->stats.getResist(i), eset->number_format.character_menu) << "%";
statList->set(stat_index, ss.str(), resistTooltip(i));
stat_index++;
}
}

ss.str("");
ss << msg->get("Miscellaneous Stats");
statList->set(stat_index, ss.str(), "");
statList->setRowHighlight(stat_index, true);
stat_index++;

for (int i=0; i<Stats::COUNT; ++i) {
if (Stats::CATEGORY[i] != Stats::CATEGORY_MISC)
continue;

if (!show_stat[i]) continue;

ss.str("");
ss << " " << Stats::NAME[i] << ": " << Utils::floatToString(pc->stats.get(static_cast<Stats::STAT>(i)), 2);
if (Stats::PERCENT[i]) ss << "%";
statList->set(stat_index, ss.str(), statTooltip(i));
stat_index++;
}

// insert resource stealing stats after HP/MP steal
for (size_t j = 0; j < eset->resource_stats.list.size(); ++j) {
for (size_t k = EngineSettings::ResourceStats::STAT_STEAL; k < EngineSettings::ResourceStats::STAT_COUNT; ++k) {
if (show_stat[resource_offset_index + (j * EngineSettings::ResourceStats::STAT_COUNT) + k]) {
ss.str("");
ss << " " << eset->resource_stats.list[j].text[k] << ": " << Utils::floatToString(pc->stats.getResourceStat(j, k), eset->number_format.character_menu) << "%";
statList->set(stat_index, ss.str(), resourceStatTooltip(j, k));
stat_index++;
}
}
}

// update tool tips
cstat[CSTAT_NAME].tip.clear();
cstat[CSTAT_NAME].tip.addText(pc->stats.name);
Expand Down
27 changes: 27 additions & 0 deletions src/Stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace Stats {
std::string NAME[COUNT];
std::string DESC[COUNT];
bool PERCENT[COUNT];
short CATEGORY[COUNT];

// KEY values aren't visible in-game, but they are used for parsing config files like engine/stats.txt
// NAME values are the translated strings visible in the Character menu and item tooltips
Expand All @@ -38,155 +39,181 @@ namespace Stats {
NAME[HP_MAX] = msg->get("Max HP");
DESC[HP_MAX] = msg->get("Total amount of HP.");
PERCENT[HP_MAX] = false;
CATEGORY[HP_MAX] = CATEGORY_CORE;

// @TYPE hp_regen|HP restored per minute
KEY[HP_REGEN] = "hp_regen";
NAME[HP_REGEN] = msg->get("HP Regen");
DESC[HP_REGEN] = msg->get("Ticks of HP regen per minute.");
PERCENT[HP_REGEN] = false;
CATEGORY[HP_REGEN] = CATEGORY_CORE;

// @TYPE mp|Magic points
KEY[MP_MAX] = "mp";
NAME[MP_MAX] = msg->get("Max MP");
DESC[MP_MAX] = msg->get("Total amount of MP.");
PERCENT[MP_MAX] = false;
CATEGORY[MP_MAX] = CATEGORY_CORE;

// @TYPE mp_regen|MP restored per minute
KEY[MP_REGEN] = "mp_regen";
NAME[MP_REGEN] = msg->get("MP Regen");
DESC[MP_REGEN] = msg->get("Ticks of MP regen per minute.");
PERCENT[MP_REGEN] = false;
CATEGORY[MP_REGEN] = CATEGORY_CORE;

// @TYPE accuracy|Accuracy %. Higher values mean less likely to miss.
KEY[ACCURACY] = "accuracy";
NAME[ACCURACY] = msg->get("Accuracy");
DESC[ACCURACY] = msg->get("Accuracy rating. The enemy's Avoidance rating is subtracted from this value to calculate your likeliness to land a direct hit.");
PERCENT[ACCURACY] = true;
CATEGORY[ACCURACY] = CATEGORY_OFFENSE;

// @TYPE avoidance|Avoidance %. Higher values means more likely to not get hit.
KEY[AVOIDANCE] = "avoidance";
NAME[AVOIDANCE] = msg->get("Avoidance");
DESC[AVOIDANCE] = msg->get("Avoidance rating. This value is subtracted from the enemy's Accuracy rating to calculate their likeliness to land a direct hit.");
PERCENT[AVOIDANCE] = true;
CATEGORY[AVOIDANCE] = CATEGORY_DEFENSE;

// @TYPE absorb_min|Minimum damage absorption
KEY[ABS_MIN] = "absorb_min";
NAME[ABS_MIN] = msg->get("Absorb Min");
DESC[ABS_MIN] = msg->get("Reduces the amount of damage taken.");
PERCENT[ABS_MIN] = false;
CATEGORY[ABS_MIN] = CATEGORY_DEFENSE;

// @TYPE absorb_max|Maximum damage absorption
KEY[ABS_MAX] = "absorb_max";
NAME[ABS_MAX] = msg->get("Absorb Max");
DESC[ABS_MAX] = msg->get("Reduces the amount of damage taken.");
PERCENT[ABS_MAX] = false;
CATEGORY[ABS_MAX] = CATEGORY_DEFENSE;

// @TYPE crit|Critical hit chance %
KEY[CRIT] = "crit";
NAME[CRIT] = msg->get("Critical Hit Chance");
DESC[CRIT] = msg->get("Chance for an attack to do extra damage.");
PERCENT[CRIT] = true;
CATEGORY[CRIT] = CATEGORY_OFFENSE;

// @TYPE xp_gain|Percentage boost to the amount of experience points gained per kill.
KEY[XP_GAIN] = "xp_gain";
NAME[XP_GAIN] = msg->get("Bonus XP");
DESC[XP_GAIN] = msg->get("Increases the XP gained per kill.");
PERCENT[XP_GAIN] = true;
CATEGORY[XP_GAIN] = CATEGORY_MISC;

// @TYPE currency_find|Percentage boost to the amount of gold dropped per loot event.
KEY[CURRENCY_FIND] = "currency_find";
NAME[CURRENCY_FIND] = msg->getv("Bonus %s", eset->loot.currency.c_str());
DESC[CURRENCY_FIND] = msg->getv("Increases the %s found per drop.", eset->loot.currency.c_str());
PERCENT[CURRENCY_FIND] = true;
CATEGORY[CURRENCY_FIND] = CATEGORY_MISC;

// @TYPE item_find|Increases the chance of finding items in loot.
KEY[ITEM_FIND] = "item_find";
NAME[ITEM_FIND] = msg->get("Item Find Chance");
DESC[ITEM_FIND] = msg->get("Increases the chance that an enemy will drop an item.");
PERCENT[ITEM_FIND] = true;
CATEGORY[ITEM_FIND] = CATEGORY_MISC;

// @TYPE stealth|Decrease the distance required to alert enemies by %
KEY[STEALTH] = "stealth";
NAME[STEALTH] = msg->get("Stealth");
DESC[STEALTH] = msg->get("Increases your ability to move undetected.");
PERCENT[STEALTH] = true;
CATEGORY[STEALTH] = CATEGORY_DEFENSE;

// @TYPE poise|Reduced % chance of entering "hit" animation when damaged
KEY[POISE] = "poise";
NAME[POISE] = msg->get("Poise");
DESC[POISE] = msg->get("Reduces your chance of stumbling when hit.");
PERCENT[POISE] = true;
CATEGORY[POISE] = CATEGORY_DEFENSE;

// @TYPE reflect_chance|Percentage chance to reflect missiles
KEY[REFLECT] = "reflect_chance";
NAME[REFLECT] = msg->get("Missile Reflect Chance");
DESC[REFLECT] = msg->get("Increases your chance of reflecting missiles back at enemies.");
PERCENT[REFLECT] = true;
CATEGORY[REFLECT] = CATEGORY_DEFENSE;

// @TYPE return_damage|Deals a percentage of the damage taken back to the attacker
KEY[RETURN_DAMAGE] = "return_damage";
NAME[RETURN_DAMAGE] = msg->get("Damage Reflection");
DESC[RETURN_DAMAGE] = msg->get("Deals a percentage of damage taken back to the attacker.");
PERCENT[RETURN_DAMAGE] = true;
CATEGORY[RETURN_DAMAGE] = CATEGORY_OFFENSE;

// @TYPE hp_steal|Percentage of HP stolen when damaging a target
KEY[HP_STEAL] = "hp_steal";
NAME[HP_STEAL] = msg->get("HP Steal");
DESC[HP_STEAL] = msg->get("Percentage of HP stolen per hit.");
PERCENT[HP_STEAL] = true;
CATEGORY[HP_STEAL] = CATEGORY_MISC;

// @TYPE mp_steal|Percentage of MP stolen when damaging a target
KEY[MP_STEAL] = "mp_steal";
NAME[MP_STEAL] = msg->get("MP Steal");
DESC[MP_STEAL] = msg->get("Percentage of MP stolen per hit.");
PERCENT[MP_STEAL] = true;
CATEGORY[MP_STEAL] = CATEGORY_MISC;

// @TYPE resist_damage_over_time|Percentage chance that damage-over-time effects will be negated
KEY[RESIST_DAMAGE_OVER_TIME] = "resist_damage_over_time";
NAME[RESIST_DAMAGE_OVER_TIME] = msg->get("Resistance to damage-over-time");
DESC[RESIST_DAMAGE_OVER_TIME] = msg->get("Percentage chance that damage-over-time effects will be negated.");
PERCENT[RESIST_DAMAGE_OVER_TIME] = true;
CATEGORY[RESIST_DAMAGE_OVER_TIME] = CATEGORY_DEFENSE;

// @TYPE resist_slow|Percentage chance that slow effects will be negated
KEY[RESIST_SLOW] = "resist_slow";
NAME[RESIST_SLOW] = msg->get("Resistance to slow");
DESC[RESIST_SLOW] = msg->get("Percentage chance that slow effects will be negated.");
PERCENT[RESIST_SLOW] = true;
CATEGORY[RESIST_SLOW] = CATEGORY_DEFENSE;

// @TYPE resist_stun|Percentage chance that stun effects will be negated
KEY[RESIST_STUN] = "resist_stun";
NAME[RESIST_STUN] = msg->get("Resistance to stun");
DESC[RESIST_STUN] = msg->get("Percentage chance that stun effects will be negated.");
PERCENT[RESIST_STUN] = true;
CATEGORY[RESIST_STUN] = CATEGORY_DEFENSE;

// @TYPE resist_knockback|Percentage chance that knockback effects will be negated
KEY[RESIST_KNOCKBACK] = "resist_knockback";
NAME[RESIST_KNOCKBACK] = msg->get("Resistance to knockback");
DESC[RESIST_KNOCKBACK] = msg->get("Percentage chance that knockback effects will be negated.");
PERCENT[RESIST_KNOCKBACK] = true;
CATEGORY[RESIST_KNOCKBACK] = CATEGORY_DEFENSE;

// @TYPE resist_stat_debuff|Percentage chance that stat debuff effects will be negated
KEY[RESIST_STAT_DEBUFF] = "resist_stat_debuff";
NAME[RESIST_STAT_DEBUFF] = msg->get("Resistance to stat debuffs");
DESC[RESIST_STAT_DEBUFF] = msg->get("Percentage chance that stat debuff effects will be negated.");
PERCENT[RESIST_STAT_DEBUFF] = true;
CATEGORY[RESIST_STAT_DEBUFF] = CATEGORY_DEFENSE;

// @TYPE resist_damage_reflect|Percentage chance that damage reflection will be negated
KEY[RESIST_DAMAGE_REFLECT] = "resist_damage_reflect";
NAME[RESIST_DAMAGE_REFLECT] = msg->get("Resistance to damage reflection");
DESC[RESIST_DAMAGE_REFLECT] = msg->get("Percentage chance that damage reflection will be negated.");
PERCENT[RESIST_DAMAGE_REFLECT] = true;
CATEGORY[RESIST_DAMAGE_REFLECT] = CATEGORY_DEFENSE;

// @TYPE resist_hp_steal|Percentage chance that HP steal will be negated
KEY[RESIST_HP_STEAL] = "resist_hp_steal";
NAME[RESIST_HP_STEAL] = msg->get("Resistance to HP steal");
DESC[RESIST_HP_STEAL] = msg->get("Percentage chance that HP steal will be negated.");
PERCENT[RESIST_HP_STEAL] = true;
CATEGORY[RESIST_HP_STEAL] = CATEGORY_DEFENSE;

// @TYPE resist_mp_steal|Percentage chance that MP steal will be negated
KEY[RESIST_MP_STEAL] = "resist_mp_steal";
NAME[RESIST_MP_STEAL] = msg->get("Resistance to MP steal");
DESC[RESIST_MP_STEAL] = msg->get("Percentage chance that MP steal will be negated.");
PERCENT[RESIST_MP_STEAL] = true;
CATEGORY[RESIST_MP_STEAL] = CATEGORY_DEFENSE;
}
}
8 changes: 8 additions & 0 deletions src/Stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,18 @@ namespace Stats {
COUNT
};

enum STAT_CATEGORY {
CATEGORY_CORE = 0,
CATEGORY_OFFENSE,
CATEGORY_DEFENSE,
CATEGORY_MISC,
};

extern std::string KEY[COUNT];
extern std::string NAME[COUNT];
extern std::string DESC[COUNT];
extern bool PERCENT[COUNT];
extern short CATEGORY[COUNT];
void init();
}

Expand Down
Loading

2 comments on commit 401482c

@Blueberryy
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add category names in translation file?

@dorkster
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add category names in translation file?

Localization files are now updated with the new strings.

Please sign in to comment.