From 9370a32f67f1f0460f3ab3524118226cda1471d8 Mon Sep 17 00:00:00 2001 From: Louis Moureaux Date: Sun, 7 Aug 2022 04:44:10 +0200 Subject: [PATCH] Expand the text for empire size unhappiness Improve the city dialog tooltip text that explains how many citizens are unhappy because of the number of cities. Write two paragraphs: the first describing the rules for the current player, and the second the status of the city at hand. The total number of citizens that can be made content without improvements is also shown; see #269. --- client/text.cpp | 341 +++++++++++++++++++++++++++++++++--------------- client/text.h | 2 +- common/city.cpp | 4 +- common/city.h | 1 + 4 files changed, 239 insertions(+), 109 deletions(-) diff --git a/client/text.cpp b/client/text.cpp index 00ce6c0d9d..7d542d4346 100644 --- a/client/text.cpp +++ b/client/text.cpp @@ -24,6 +24,7 @@ // common #include "calendar.h" #include "citizens.h" +#include "city.h" #include "clientutils.h" #include "combat.h" #include "culture.h" @@ -1400,128 +1401,256 @@ QString text_happiness_wonders(const struct city *pcity) return str.trimmed(); } +namespace /* anonymous */ { + /** - Describing city factors that affect happiness. + * Describes the rules of city happiness for the given player. */ -const QString text_happiness_cities(const struct city *pcity) +QString text_happiness_cities_rules(const player *pplayer, int base_content, + int basis, int step, + bool depends_on_empire_size) { - struct player *pplayer = city_owner(pcity); - int cities = city_list_size(pplayer->cities); - int content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE); - int basis = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_BASE); - int step = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_STEP); - QString str; + auto str = QString(); - if (basis + step <= 0) { - /* Special case where penalty is disabled; see - * player_content_citizens(). */ - str += QString(PL_("Cities: %1 total, but no penalty for empire size.", - "Cities: %1 total, but no penalty for empire size.", - cities)) - .arg(QString::number(cities)) - + qendl(); - // TRANS: %d is number of citizens - str += - QString(PL_("%1 content per city.", "%1 content per city.", content)) - .arg(QString::number(content)) - + qendl(); + bool next_citizens_are_angry = false; + if (base_content == 0) { + str += _("All cities start with all citizens unhappy."); + next_citizens_are_angry = game.info.angrycitizen; } else { - // Can have up to and including 'basis' cities without penalty - int excess = MAX(cities - basis, 0); - int penalty; - int unhappy, angry; - int last, next; - - if (excess > 0) { - if (step > 0) { - penalty = 1 + (excess - 1) / step; - } else { - penalty = 1; + str += QString(PL_("All cities start with %1 content citizen.", + "All cities start with %1 content citizens.", + base_content)) + .arg(base_content); + } + if (depends_on_empire_size) { + if (basis > 0) { + str += QStringLiteral(" "); + if (next_citizens_are_angry) { + str += QString(PL_("Once you have more than %1 city, a citizen " + "becomes angry.", + "Once you have more than %1 cities, a citizen " + "becomes angry.", + basis)) + .arg(basis); + } else if (base_content == 1) { + // TRANS: Comes after "All cities start with 1 content citizen." + // "it" is the citizen. + str += + QString( + PL_("Once you have more than %1 city, it becomes unhappy.", + "Once you have more than %1 cities, it becomes unhappy.", + basis)) + .arg(basis); + next_citizens_are_angry = game.info.angrycitizen; + } else if (base_content > 1) { + // TRANS: Comes after "All cities start with N content citizens." + str += QString(PL_("Once you have more than %1 city, a citizen " + "becomes unhappy.", + "Once you have more than %1 cities, a citizen " + "becomes unhappy.", + basis)) + .arg(basis); } - } else { - penalty = 0; } - unhappy = MIN(penalty, content); - angry = game.info.angrycitizen ? MAX(penalty - content, 0) : 0; - if (penalty >= 1) { - /* 'last' is when last actual malcontent appeared, will saturate - * if no angry citizens */ - last = basis + (unhappy + angry - 1) * step; - if (!game.info.angrycitizen && unhappy == content) { - // Maxed out unhappy citizens, so no more penalties - next = 0; + if (step > 0) { + str += QStringLiteral(" "); + if (next_citizens_are_angry) { + str += QString(PL_("Afterwards, for every %1 additional city, a " + "citizen becomes angry.", + "Afterwards, for every %1 additional cities, a " + "citizen becomes angry.", + step)) + .arg(step); } else { - // Angry citizens can continue appearing indefinitely - next = last + step; - } - } else { - last = 0; - next = basis; - } - // TRANS: sentence fragment, will have text appended - str += QString(PL_("Cities: %1 total:", "Cities: %1 total:", cities)) - .arg(QString::number(cities)) - + qendl(); - if (excess > 0) { - /* TRANS: appended to "Cities: %d total:"; preserve leading - * space. Pluralized in "nearest threshold of %d cities". */ - str += QString(PL_(" %1 over nearest threshold of %2 city.", - " %1 over nearest threshold of %2 cities.", last)) - .arg(QString::number(cities - last), QString::number(last)); - // TRANS: Number of content [citizen(s)] ... - str += QString(PL_("%1 content before penalty.", - "%1 content before penalty.", content)) - .arg(QString::number(content)) - + qendl(); - str += QString(PL_("%1 additional unhappy citizen.", - "%1 additional unhappy citizens.", unhappy)) - .arg(QString::number(unhappy)) - + qendl(); - if (angry > 0) { - str += QString(PL_("%1 angry citizen.", "%1 angry citizens.", angry)) - .arg(QString::number(angry)) - + qendl(); + str += QString(PL_("Afterwards, for every %1 additional city, a " + "content citizen becomes unhappy.", + "Afterwards, for every %1 additional cities, a " + "content citizen becomes unhappy.", + step)) + .arg(step); + + if (game.info.angrycitizen) { + str += QStringLiteral(" "); + str += _("If there are no more content citizens, an unhappy " + "citizen becomes angry instead."); + } } - } else { - // TRANS: appended to "Cities: %d total:"; preserve leading space. - str += - QString(PL_(" not more than %1, so no empire size penalty.", - " not more than %1, so no empire size penalty.", next)) - .arg(QString::number(next)); - str += QString(PL_("%1 content per city.", "%1 content per city.", - content)) - .arg(QString::number(content)) - + qendl(); } - if (next >= cities && penalty < content) { - str += QString(PL_("With %1 more city, another citizen will become " - "unhappy.", - "With %1 more cities, another citizen will become " + } + return str; +} + +/** + * Describes what the empire size rules imply for an empire of a given size. + */ +QString text_happiness_cities_apply_rules(int cities, int max_content) +{ + if (max_content > 0) { + // TRANS: Pluralized in "%2 content citizens" + return QString(PL_("You have %1 cities, resulting in a maximum of %2 " + "content citizen.", + "You have %1 cities, resulting in a maximum of %2 " + "content citizens.", + max_content)) + .arg(cities) + .arg(max_content); + } else if (max_content == 0) { + return _("You have %1 cities, thus all citizens are unhappy."); + } else { + // TRANS: Pluralized in "%2 angry citizens" + return QString(PL_("You have %1 cities, resulting in a maximum of " + "%2 angry citizen.", + "You have %1 cities, resulting in a maximum of " + "%2 angry citizens.", + max_content)) + .arg(cities) + .arg(-max_content); + } +} + +/** + * Describes how many citizens are content after empire size rules. + */ +QString text_happiness_cities_content(int size, int max_content) +{ + if (max_content >= size) { + // Very good + return QString(_("In this city of size %1, all citizens are " + "content.")) + .arg(size); + } else if (max_content > 0) { + // Good + // TRANS: Pluralized in "citizens are content" + return QString( + PL_("In this city of size %1, %2 citizen is content.", + "In this city of size %1, %2 citizens are " + "content.", + max_content)) + .arg(size) + .arg(max_content); + } else if (max_content == 0) { + // Still ok + return QString( + "In this city of size %1, all citizens are unhappy.") + .arg(size); + } else if (-max_content < size) { + // Somewhat bad + return QString( + PL_( + // TRANS: Pluralized in "citizens are angry" + "In this city of size %1, %2 citizen is angry.", + "In this city of size %1, %2 citizens are angry.", + -max_content)) + .arg(size) + .arg(-max_content); + } else { + // Very bad + return QString( + _("In this city of size %1, all citizens are angry.")) + .arg(size); + } +} + +/** + * Describes what would happen to a city's citizens if the empire size would + * grow further. + */ +QString text_happiness_more_cities(int base_content, int basis, int step, + int max_content, int size, int cities) +{ + // Actual content in this city (negative if there are angry citizens + // instead) + int content = std::min(max_content, size); + if (!game.info.angrycitizen && content < 0) { + content = 0; + } + // How many citizens unhappy about the empire size we'll have when the + // next unhappy appears + int unhappy_for_next_threshold = base_content - content + 1; + // How many cities we'll have when the next unhappy appears + int cities_to_next_threshold = + basis + (unhappy_for_next_threshold - 1) * step + 1; + // ...or how many more we need + cities_to_next_threshold -= cities; + + if (cities_to_next_threshold > 0) { + if (content > 0) { + return QString(PL_("With %1 more city, a content citizen would become " "unhappy.", - next + 1 - cities)) - .arg(QString::number(next + 1 - cities)) - + qendl(); - } else if (next >= cities) { - /* We maxed out the number of unhappy citizens, but they can get - * angry instead. */ - fc_assert(game.info.angrycitizen); - str += QString(PL_("With %1 more city, another citizen will become " - "angry.", - "With %1 more cities, another citizen will become " - "angry.", - next + 1 - cities)) - .arg(QString::number(next + 1 - cities)) - + qendl(); + "With %1 more cities, a content citizen would " + "become unhappy.", + cities_to_next_threshold)) + .arg(QString::number(cities_to_next_threshold)); + } else if (game.info.angrycitizen) { + // We maxed out the number of unhappy citizens, but they can get + // angry instead. + return QString( + PL_("With %1 more city, a citizen would become angry.", + "With %1 more cities, a citizen would become angry.", + cities_to_next_threshold)) + .arg(QString::number(cities_to_next_threshold)); + } + } + return _("Having more cities would not create more unhappiness."); +} +} // anonymous namespace + +/** + Describing city factors that affect happiness. + */ +QString text_happiness_cities(const struct city *pcity) +{ + struct player *pplayer = city_owner(pcity); + int cities = city_list_size(pplayer->cities); + int base_content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE); + int basis = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_BASE); + int step = get_player_bonus(pplayer, EFT_EMPIRE_SIZE_STEP); + bool depends_on_empire_size = (basis + step > 0); + + while (depends_on_empire_size && basis <= 0) { + // In this case, we get one unhappy immediately when we build the first + // city. So it's equivalent to removing one content. + base_content--; + if (step > 0) { + // The first unhappy appears at a different size. Normalize... + basis += step; } else { - /* Either no Empire_Size_Step, or we maxed out on unhappy citizens - * and ruleset doesn't allow angry ones. */ - str += QString(_("More cities will not cause more unhappy citizens.")) - + qendl(); + // This was fake! The ruleset author did something strange. + depends_on_empire_size = false; + break; } } - return str.trimmed(); + // First explain the rules -- see player_base_citizen_happiness + auto str = QStringLiteral("

"); + str += text_happiness_cities_rules(pplayer, base_content, basis, step, + depends_on_empire_size); + + // Now add the status of this city. + str += QStringLiteral("

"); + auto max_content = player_base_citizen_happiness(city_owner(pcity)); + if (!game.info.angrycitizen) { + // No angry citizens: max_content can't go negative + max_content = CLIP(0, max_content, MAX_CITY_SIZE); + } + // If the penalty is enabled, explain it. + if (depends_on_empire_size) { + str += text_happiness_cities_apply_rules(cities, max_content); + } + + auto size = city_size_get(pcity); + str += QStringLiteral(" "); + str += text_happiness_cities_content(size, max_content); + + // Finally, add something about building more cities. + if (depends_on_empire_size) { + str += QStringLiteral(" "); + str += text_happiness_more_cities(base_content, basis, step, max_content, + size, cities); + } + + return str + QStringLiteral("

"); } /** diff --git a/client/text.h b/client/text.h index 764cc493c0..7cab783a9f 100644 --- a/client/text.h +++ b/client/text.h @@ -46,7 +46,7 @@ const QString act_sel_action_tool_tip(const struct action *paction, QString text_happiness_buildings(const struct city *pcity); const QString text_happiness_nationality(const struct city *pcity); -const QString text_happiness_cities(const struct city *pcity); +QString text_happiness_cities(const struct city *pcity); const QString text_happiness_luxuries(const struct city *pcity); const QString text_happiness_units(const struct city *pcity); QString text_happiness_wonders(const struct city *pcity); diff --git a/common/city.cpp b/common/city.cpp index 691826f0e4..ddb15c8b19 100644 --- a/common/city.cpp +++ b/common/city.cpp @@ -2055,7 +2055,7 @@ int city_granary_size(int city_size) A positive number is a number of content citizens. A negative number is a number of angry citizens (a city never starts with both). */ -static int player_base_citizen_happiness(const struct player *pplayer) +int player_base_citizen_happiness(const struct player *pplayer) { int cities = city_list_size(pplayer->cities); int content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE); @@ -2069,7 +2069,7 @@ static int player_base_citizen_happiness(const struct player *pplayer) if (cities > basis) { content--; - if (step != 0) { + if (step > 0) { /* the first penalty is at (basis + 1) cities; the next is at (basis + step + 1), _not_ (basis + step) */ content -= (cities - basis - 1) / step; diff --git a/common/city.h b/common/city.h index 2848d82e4f..07e261de46 100644 --- a/common/city.h +++ b/common/city.h @@ -532,6 +532,7 @@ void city_size_set(struct city *pcity, citizens size); citizens city_specialists(const struct city *pcity); +int player_base_citizen_happiness(const struct player *pplayer); citizens player_content_citizens(const struct player *pplayer); citizens player_angry_citizens(const struct player *pplayer);