Skip to content

Commit

Permalink
Cleanup Various PR & bring some of the NO6 private branch changes int…
Browse files Browse the repository at this point in the history
…o main (#186)

* Update rules for FACTION command

Fix for text describing the FACTION command, so that it works for
the case of martial factions as well.

* Add the 'cities' map option that Artem added

This allows dumping all the surface city names to post for
the cities win condition.

* Allow the orders checker to check faction password.

* Remove the freezing code & add city counting for winner

A common New Origins win condition is percent of cities voting.
Add a global define to enable this, and put the counting and automatic
victory declaration, as well as a times message once someone has made
good progress towards it.   While this won't be used for NO7, I suspect
it will be good to have for the future.

Also, the freezing code was highly NO6 specific, so it shouldn't be
on the master branch.

* Clean up a couple of small niggles

As I was going through a few of the open PR, there were some small
changes which made for nice QoL improvements so I added them here.

* Fix compile errors, update snapshots, add tests

This is cleanup for the other changes in the previous commits.
This fixes up the compile errors, adds unit test for the order
checker changes, and updates the snapshots with the practice
event changes as well as the rules with the MARTIAL changes.

---------

Co-authored-by: nedbrek <[email protected]>
  • Loading branch information
jt-traub and nedbrek authored May 5, 2024
1 parent 29607bb commit 7571f0c
Show file tree
Hide file tree
Showing 25 changed files with 260 additions and 57 deletions.
24 changes: 21 additions & 3 deletions game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ void Game::WriteUnderworldMap(ostream& f, ARegionArray *pArr, int type)
int Game::ViewMap(const AString & typestr,const AString & mapfile)
{
int type = 0;
if (AString(typestr) == "wmon") type = 1;
if (AString(typestr) == "lair") type = 2;
if (AString(typestr) == "gate") type = 3;
if (typestr == "wmon") type = 1;
if (typestr == "lair") type = 2;
if (typestr == "gate") type = 3;
if (typestr == "cities") type = 4;

ofstream f(mapfile.const_str(), ios::out|ios::ate);
if (!f.is_open()) return(0);
Expand All @@ -250,6 +251,23 @@ int Game::ViewMap(const AString & typestr,const AString & mapfile)
case 3:
f << "Gate Map\n";
break;
case 4:
f << "Cities Map\n";
break;
}

// Cities map is a bit special since it is really just a list of all the cities in that region
if (type == 4) {
forlist(&regions) {
ARegion *reg = (ARegion *)elem;
// Ignore anything that isn't the surface
if (reg->level->levelType != ARegionArray::LEVEL_SURFACE) continue;
// Ignore anything with no city
if (!reg->town || (reg->town->TownType() != TOWN_CITY)) continue;

f << "(" << reg->xloc << "," << reg->yloc << "): " << reg->town->name << "\n";
}
return(1);
}

int i;
Expand Down
45 changes: 34 additions & 11 deletions genrules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3950,35 +3950,58 @@ int Game::GenRules(const AString &rules, const AString &css, const AString &intr
<< "EXCHANGE 3453 10 SWOR 10 LBOW\n"
<< example_end();

if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) {
if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES)
{
f << enclose(class_tag("div", "rule"), true) << '\n' << enclose("div", false);
f << anchor("faction") << '\n';

const bool martial_faction = Globals->FACTION_ACTIVITY == FactionActivityRules::MARTIAL_MERGED;
f << enclose("h4", true) << "FACTION [type] [points] ...\n" << enclose("h4", false);
f << enclose("p", true) << "Attempt to change your faction's type. In the order, you can specify up to "
<< "three faction types (WAR, TRADE, and MAGIC) and the number of faction points to assign to each type; "
f << enclose("p", true) << "Attempt to change your faction's type. In the order, you can specify up to ";
if (martial_faction) {
f << "two faction types (MARTIAL, and MAGIC)";
}
else {
f << "three faction types (WAR, TRADE, and MAGIC)";
}
f << " and the number of faction points to assign to each type; "
<< "if you are assigning points to only one or two types, you may omit the types that will not have any "
<< "points.\n"
<< enclose("p", false);

f << enclose("p", true) << "Changing the number of faction points assigned to MAGIC may be tricky. "
<< "Increasing the MAGIC points will always succeed, but if you decrease the number of points assigned "
<< "to MAGIC, you must make sure that you have only the number of magic-skilled leaders allowed by the "
<< "new number of MAGIC points BEFORE you change your point distribution. For example, if you have 3 "
<< "mages (3 points assigned to MAGIC), but want to use one of those points for WAR or TRADE (change to "
<< "mages (3 points assigned to MAGIC), but want to use one of those points for something else (change to "
<< "MAGIC 2), you must first get rid of one of your mages by either giving it to another faction or "
<< "ordering it to " << url("#forget", "FORGET") << " all its magic skills. If you have too many mages "
<< "for the number of points you try to assign to MAGIC, the FACTION order will fail.";
if (qm_exist)
f << " Similar problems could occur with TRADE points and the number of quartermasters controlled by "
<< "the faction.";

if (qm_exist) {
f << " Similar problems could occur with TRADE/MARTIAL points and the"
<< " number of quartermasters controlled by the faction.";
}
f << '\n' << enclose("p", false);

f << enclose("p", true) << "Examples:\n" << enclose("p", false);
f << example_start("Assign 2 faction points to WAR, 2 to TRADE, and 1 to MAGIC.")
<< "FACTION WAR 2 TRADE 2 MAGIC 1\n"
<< example_end();

if (martial_faction)
{
f << example_start("Assign 4 faction points to MARTIAL, and 1 to MAGIC.")
<< "FACTION MARTIAL 4 MAGIC 1\n"
<< example_end();
}
else
{
f << example_start("Assign 2 faction points to WAR, 2 to TRADE, and 1 to MAGIC.")
<< "FACTION WAR 2 TRADE 2 MAGIC 1\n"
<< example_end();
}

f << example_start("Become a pure magic faction (assign all points to magic).")
<< "FACTION MAGIC " << Globals->FACTION_POINTS << '\n'
<< example_end();
// Update this to deal with martial/martial merged!
}

if (Globals->HAVE_EMAIL_SPECIAL_COMMANDS) {
Expand Down
103 changes: 81 additions & 22 deletions neworigins/extra.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
#include "game.h"
#include "gamedata.h"
#include "quests.h"
#include <cmath>
#include <string>
#include <iterator>

using namespace std;

#define MINIMUM_ACTIVE_QUESTS 5
#define MAXIMUM_ACTIVE_QUESTS 20
#define QUEST_EXPLORATION_PERCENT 30
Expand All @@ -39,6 +42,10 @@
#define QUEST_SPAWN_CHANCE 70
#define MAX_DESTINATIONS 5

// If this is set to true, then the game will end if a faction has > 50% of all cities in the game with their id
// in the name.
#define CITY_VOTE_WIN false

int Game::SetupFaction( Faction *pFac )
{
pFac->unclaimed = Globals->START_MONEY + TurnNumber() * 300;
Expand Down Expand Up @@ -417,17 +424,17 @@ Faction *Game::CheckVictory()
string stlstr;
set<string> intersection, un;
set<string>::iterator it2;
Faction *winner = nullptr;

forlist(&quests) {
q = (Quest *) elem;
if (q->type != Quest::VISIT)
continue;
for (it2 = q->destinations.begin();
it2 != q->destinations.end();
it2++) {
for (it2 = q->destinations.begin(); it2 != q->destinations.end(); it2++) {
un.insert(*it2);
}
}

visited = 0;
unvisited = 0;
forlist_reuse(&regions) {
Expand Down Expand Up @@ -465,14 +472,14 @@ Faction *Game::CheckVictory()
if (visited >= (unvisited + visited) * QUEST_EXPLORATION_PERCENT / 100) {
// Exploration phase complete: start creating relic quests
for (i = 0; i < QUEST_SPAWN_RATE; i++) {
if (quests.Num() < MAXIMUM_ACTIVE_QUESTS &&
getrandom(100) < QUEST_SPAWN_CHANCE)
if (quests.Num() < MAXIMUM_ACTIVE_QUESTS && getrandom(100) < QUEST_SPAWN_CHANCE)
CreateQuest(&regions, monfaction);
}
while (quests.Num() < MINIMUM_ACTIVE_QUESTS) {
CreateQuest(&regions, monfaction);
}
}

if (unvisited) {
// Tell the players to get exploring :-)
if (visited > 9 * unvisited) {
Expand Down Expand Up @@ -728,30 +735,82 @@ Faction *Game::CheckVictory()
}
}

if (TurnNumber() >= 29) { // 29 is just a number to avoid breaking snapshots
// Freezing effect
forlist_reuse(&regions) {
ARegion *r = (ARegion *) elem;

// No effect for regions with clear skies
if (r->clearskies) {
continue;
// Check for victory conditions based on the current game
if (CITY_VOTE_WIN) {
std::map <int, int> votes; // track votes per faction id
int total_cities = 0; // total cities possible for vote count

forlist(&regions) {
ARegion *r = (ARegion *)elem;
// Ignore anything but the surface
if (r->level->levelType != ARegionArray::LEVEL_SURFACE) continue;
if (!r->town || (r->town->TownType() != TOWN_CITY)) continue;

total_cities++;

string name = r->town->name->const_str();
string possible_faction = name.substr(0, name.find_first_of(" \t\n"));
// The first word of the name was not all numeric, don't count for anyone
if (!all_of(
possible_faction.begin(),
possible_faction.end(),
[](unsigned char ch){ return std::isdigit(ch); }
)) continue;
// Now that we know it's all numeric, convert it to an int
int faction_id = stoi(possible_faction);

// Make sure it's a valid faction
Faction *f = GetFaction(&factions, faction_id);
if (!f || f->is_npc) continue;

auto vote = votes.find(faction_id);
if (vote == votes.end()) {
votes[faction_id] = 1;
} else {
vote->second++;
}
}

// Check if regions in freezing zome in surface
if (r->zloc == ARegionArray::LEVEL_SURFACE && (r->yloc <= 0 || r->yloc >= 71)) {
r->Pillage();
r->SetWeather(W_BLIZZARD);
printf("Freeze (%d,%d) region in %s of %s\n",
r->xloc, r->yloc, r->name->Str(), TerrainDefs[TerrainDefs[r->type].similar_type].name
);
// Set up the voting result to be reported if we are far enough in
string message = "Voting results: \n";

int max_vote = -1;
bool tie = false;
Faction *maxFaction = nullptr;
for (const auto& vote : votes) {
Faction *f = GetFaction(&factions, vote.first);
if (vote.second > max_vote) {
max_vote = vote.second;
maxFaction = f;
tie = false;
} else if (vote.second == max_vote) {
tie = true;
maxFaction = nullptr;
}
message += "Faction " + string(f->name->const_str()) + " has " + to_string(vote.second) + " votes.\n";
}

// TODO: Check if regions in freezing zome in UW
// See if we have enough votes to even report the info. Since a win requires 50% + 1, we can start reporting
// once someone has more than 25% of the cities.
if (max_vote > (total_cities / 4)) {
// Now see if we have a winner at all
if (max_vote > ((total_cities / 2) + 1)) {
winner = maxFaction;
message += "\n" + string(winner->name->const_str()) + " has enough votes and has won the game!";
} else {
int percent = floor((max_vote * 100) / total_cities);
if (tie) {
message += "\nThere is a tie for the most votes with multiple factions having ";
} else {
message += string("\n") + "The current leader is " + string(maxFaction->name->const_str()) + " with ";
}
message += to_string(max_vote) + "/" + to_string(total_cities) + " votes (" + to_string(percent) + "%).";
}
WriteTimesArticle(message);
}
}

return NULL;
return winner;
}

void Game::ModifyTablesPerRuleset(void)
Expand Down
25 changes: 20 additions & 5 deletions parseorders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ void Game::parse_error(OrdersCheck *order_check, Unit *unit, Faction *faction, c

void Game::ParseOrders(int faction, istream& f, OrdersCheck *pCheck)
{
Faction *fac = 0;
Faction *fac = nullptr;
Faction *passFac = nullptr;
Unit *unit = 0;
int indent = 0, code, i;
AString order, prefix;
Expand Down Expand Up @@ -230,6 +231,8 @@ void Game::ParseOrders(int faction, istream& f, OrdersCheck *pCheck)
if (pCheck) {
fac = &(pCheck->dummyFaction);
pCheck->numshows = 0;
// Even though we don't use the real faction for other things, we do want it for the password check.
passFac = GetFaction(&factions, token->value());
} else {
fac = GetFaction(&factions, token->value());
}
Expand All @@ -243,6 +246,18 @@ void Game::ParseOrders(int faction, istream& f, OrdersCheck *pCheck)
if (!token) {
parse_error(pCheck, 0, fac, "Warning: No password on #atlantis line.");
parse_error(pCheck, 0, fac, "If this is your first turn, ignore this error.");
} else {
// If we found their real faction above (we should have but let's not assume), then we
// can check if they gave us the correct password.
if (passFac) {
bool has_password = !(*(passFac->password) == "none");
bool wrong_password = !(*(passFac->password) == *token);
if (has_password && wrong_password) {
parse_error(pCheck, 0, fac, "Incorrect password on #atlantis line.");
fac = 0;
break;
}
}
}
} else {
if (!(*(fac->password) == "none")) {
Expand Down Expand Up @@ -1538,8 +1553,7 @@ void Game::ProcessBuildOrder(Unit *unit, AString *o, OrdersCheck *pCheck)
}
int flying = ItemDefs[st].fly;
if (!reg->IsCoastalOrLakeside() && (flying <= 0)) {
unit->error("BUILD: Can't build ship in "
"non-coastal or lakeside region.");
unit->error("BUILD: Can't build ship in non-coastal or lakeside region.");
return;
}
unit->build = -st;
Expand Down Expand Up @@ -2909,12 +2923,13 @@ void Game::ProcessTransportOrder(Unit *u, AString *o, OrdersCheck *pCheck)
return;
}
int item = ParseTransportableItem(token);
delete token;

if (item == -1) {
parse_error(pCheck, u, 0, "TRANSPORT: Invalid item.");
parse_error(pCheck, u, 0, "TRANSPORT: Invalid item" + string(token->const_str()) + ".");
delete token;
return;
}
delete token;

int except = 0;
token = o->gettoken();
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_1/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Unit (38): Claims $100.
Unit (38): Studies stealth.
Unit (38): Casts Random Gate Jump. Capacity: 10/15.
Unit (38): Jumps through a Gate to jungle (3,5) in Kleptoatres Jungle.
Unit (38): Gets 5 days of practice with pattern [PATT].

Skill reports:

Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_10/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Unit (38): Studies force.
Unit (38): Completes study to level 2 in force.
Unit (38): Casts Random Gate Jump. Capacity: 10/15.
Unit (38): Jumps through a Gate to jungle (3,5) in Kleptoatres Jungle.
Unit (38): Gets 5 days of practice with pattern [PATT].
Unit (5): Claims 10 silver for maintenance.
Unit (63): Claims 10 silver for maintenance.
Unit (64): Claims 10 silver for maintenance.
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_11/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Unit (52): Studies longbow.
Unit (38): Studies pattern.
Unit (38): Casts Random Gate Jump. Capacity: 10/15.
Unit (38): Jumps through a Gate to jungle (3,5) in Kleptoatres Jungle.
Unit (38): Gets 5 days of practice with pattern [PATT].
Unit (65): Claims 10 silver for maintenance.
Unit (67): Claims 10 silver for maintenance.
Unit (62): Claims 10 silver for maintenance.
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_12/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Unit (68): Earns 11 silver working in swamp (3,7) in Dogmoeryx
Marshes.
Unit (38): Casts Random Gate Jump. Capacity: 10/15.
Unit (38): Jumps through a Gate to jungle (3,5) in Kleptoatres Jungle.
Unit (38): Gets 5 days of practice with spirit [SPIR].
Unit (71): Claims 10 silver for maintenance.
Unit (74): Claims 10 silver for maintenance.
Unit (73): Claims 10 silver for maintenance.
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_2/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Unit (38): Studies stealth.
Unit (38): Casts Random Gate Jump. Capacity: 10/15.
Unit (38): Jumps through a Gate to swamp (13,3) in Philagotuspolisus
Marshes, contains Hymnoalotos [village].
Unit (38): Gets 5 days of practice with pattern [PATT].

Declared Attitudes (default Neutral):
Hostile : none.
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_3/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Unit (38): Unit (38) shares 688 silver [SILV] with Unit (52).
Unit (52): Buys leader [LEAD] at $688 each.
Unit (51): Withdraws 10 swords [SWOR].
Unit (38): Teaches combat to Unit (51).
Unit (38): Gets 5 days of practice with combat [COMB].
Unit (38): Unit (38) shares 100 silver [SILV] with Unit (51).
Unit (51): Studies combat and was taught for 30 days.
Unit (38): Unit (38) shares 200 silver [SILV] with Unit (52).
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_4/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Unit (57): Buys 10 gnolls [GNOL] at $39 each.
Unit (52): Withdraws longbow [LBOW].
Unit (57): Withdraws 10 swords [SWOR].
Unit (38): Teaches combat to Unit (57).
Unit (38): Gets 5 days of practice with combat [COMB].
Unit (51): Studies combat.
Unit (51): Completes study to level 2 in combat.
Unit (52): Studies longbow.
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/neworigins_turns/turn_5/report.3
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Unit (57): Claims $100.
Unit (60): Buys 10 gnolls [GNOL] at $39 each.
Unit (60): Withdraws 10 swords [SWOR].
Unit (38): Teaches combat to Unit (60).
Unit (38): Gets 5 days of practice with combat [COMB].
Unit (57): Studies combat.
Unit (57): Completes study to level 2 in combat.
Unit (60): Studies combat and was taught for 30 days.
Expand Down
Loading

0 comments on commit 7571f0c

Please sign in to comment.