Skip to content

Commit

Permalink
Expand the text for empire size unhappiness
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lmoureaux committed Aug 7, 2022
1 parent b1f1881 commit 8ae4127
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 102 deletions.
286 changes: 187 additions & 99 deletions client/text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// common
#include "calendar.h"
#include "citizens.h"
#include "city.h"
#include "clientutils.h"
#include "combat.h"
#include "culture.h"
Expand Down Expand Up @@ -1403,125 +1404,212 @@ QString text_happiness_wonders(const struct city *pcity)
/**
Describing city factors that affect happiness.
*/
const QString text_happiness_cities(const struct city *pcity)
QString text_happiness_cities(const struct city *pcity)
{
struct player *pplayer = city_owner(pcity);
int cities = city_list_size(pplayer->cities);
int content = get_player_bonus(pplayer, EFT_CITY_UNHAPPY_SIZE);
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);
QString str;
bool depends_on_empire_size = (basis + step > 0);

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();
auto str = QStringLiteral("<p>");

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 {
// This was fake!
depends_on_empire_size = false;
break;
}
}

// First explain the rules -- see player_base_citizen_happiness
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;
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 content citizens, an "
"unhappy citizen becomes angry instead.");
}
}
} 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();
}
}

// Now add the status of this city.
auto max_content = player_base_citizen_happiness(city_owner(pcity));
if (!game.info.angrycitizen) {
// No angry citizens
max_content = CLIP(0, max_content, MAX_CITY_SIZE);
;
}
str += QStringLiteral("</p><p>");
if (depends_on_empire_size) {
// Only mention the number of cities if the penalty is disabled.
if (max_content > 0) {
// TRANS: Pluralized in "%2 content citizens"
str += 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) {
str += _("You have %1 cities, thus all citizens are unhappy.");
} 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();
// TRANS: Pluralized in "%2 angry citizens"
str += 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);
}
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 "
}

auto size = city_size_get(pcity);
str += QStringLiteral(" ");
if (max_content >= size) {
// Very good
str +=
QString(
_("In this city of size %1, <b>all citizens are content.</b>"))
.arg(size);
} else if (max_content > 0) {
// Good
// TRANS: Pluralized in "citizens are content"
str +=
QString(
PL_("In this city of size %1, <b>%2 citizen is content.</b>",
"In this city of size %1, <b>%2 citizens are content.</b>",
max_content))
.arg(size)
.arg(max_content);
} else if (max_content == 0) {
// Still ok
str +=
QString("In this city of size %1, <b>all citizens are unhappy.</b>")
.arg(size);
} else if (-max_content < size) {
// Somewhat bad
str +=
QString(PL_(
// TRANS: Pluralized in "citizens are angry"
"In this city of size %1, <b>%2 citizen is angry.</b>",
"In this city of size %1, <b>%2 citizens are angry.</b>",
-max_content))
.arg(size)
.arg(-max_content);
} else {
// Very bad
str +=
QString(_("In this city of size %1, <b>all citizens are angry.</b>"))
.arg(size);
}

// Finally, add something about building more cities.
if (depends_on_empire_size) {
// Actual content in this city (negative if there are angry citizens
// instead)
int content = std::min<int>(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;

str += QStringLiteral(" ");
if (cities_to_next_threshold <= 0) {
} else if (content > 0) {
str += 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.
str +=
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));
} 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();
str += _("Having more cities would not create more unhappiness.");
}
}

return str.trimmed();
return str + QStringLiteral("</p>");
}

/**
Expand Down
2 changes: 1 addition & 1 deletion client/text.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions common/city.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions common/city.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 8ae4127

Please sign in to comment.