diff --git a/Makefile b/Makefile index f1c8fe77..c001952b 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,9 @@ ENGINE_OBJECTS = alist.o aregion.o army.o astring.o battle.o economy.o \ edit.o faction.o game.o gamedata.o gamedefs.o gameio.o \ genrules.o i_rand.o items.o main.o market.o modify.o monthorders.o \ npc.o object.o orders.o parseorders.o production.o quests.o runorders.o \ - shields.o skills.o skillshows.o specials.o spells.o template.o unit.o \ + shields.o skills.o skillshows.o specials.o spells.o unit.o \ events.o events-battle.o events-assassination.o mapgen.o simplex.o namegen.o \ - indenter.o + indenter.o text_report_generator.o UNITTEST_SRC = unittest/main.cpp unittest/testhelper.cpp $(wildcard unittest/*_test.cpp) UNITTEST_OBJECTS = $(patsubst unittest/%.cpp,unittest/obj/%.o,$(UNITTEST_SRC)) diff --git a/aregion.cpp b/aregion.cpp index e531a5e5..13cfbf84 100644 --- a/aregion.cpp +++ b/aregion.cpp @@ -1196,29 +1196,6 @@ int ARegion::CanMakeAdv(Faction *fac, int item) return 0; } -void ARegion::WriteProducts(ostream& f, Faction *fac, int present) -{ - Production *p = get_production_for_skill(I_SILVER, S_ENTERTAINMENT); - if (p) { - f << "Entertainment available: $" - << (((Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_ENTERTAINMENT) || present) ? p->amount : 0) - << ".\n"; - } - - f << "Products: "; - bool has = false; - for (const auto& p : products) { - if (p->itemtype == I_SILVER) continue; - // Advanced items are special.. Skip unless we are allowed to see them. - if (ItemDefs[p->itemtype].type & IT_ADVANCED && !(CanMakeAdv(fac, p->itemtype) || fac->is_npc)) continue; - // For normal items, skip if we aren't allowed to see them. - if (!present && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_RESOURCES)) continue; - f << (has ? ", " : "") << p->WriteReport(); - has = true; - } - f << (has ? "." : "none.") << '\n'; -} - int ARegion::HasItem(Faction *fac, int item) { forlist(&objects) { @@ -1233,74 +1210,6 @@ int ARegion::HasItem(Faction *fac, int item) return 0; } -void ARegion::WriteMarkets(ostream &f, Faction *fac, int present) -{ - f << "Wanted: "; - int has = 0; - for (const auto& m : markets) { - if (!m->amount) continue; - if (!present && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_MARKETS)) continue; - if (m->type == M_SELL) { - if (ItemDefs[m->item].type & IT_ADVANCED) { - if (!Globals->MARKETS_SHOW_ADVANCED_ITEMS) { - if (!HasItem(fac, m->item)) { - continue; - } - } - } - f << (has ? ", " : "") << m->Report().const_str(); - has = 1; - } - } - f << (has ? "." : "none.") << '\n'; - - f << "For Sale: "; - has = 0; - for (const auto& m : markets) { - if (!m->amount) continue; - if (!present && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_MARKETS)) continue; - if (m->type == M_BUY) { - f << (has ? ", " : "") << m->Report().const_str(); - has = 1; - } - } - f << (has ? "." : "none.") << '\n'; -} - -void ARegion::WriteEconomy(ostream& f, Faction *fac, int present) -{ - f << indent::incr; - - if ((Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_WAGES) || present) { - f << "Wages: " << WagesForReport().const_str() << ".\n"; - } else { - f << "Wages: $0.\n"; - } - - WriteMarkets(f, fac, present); - - WriteProducts(f, fac, present); - - f << indent::decr << '\n'; -} - -void ARegion::WriteExits(ostream& f, ARegionList *pRegs, int *exits_seen) -{ - f << "Exits:\n"; - f << indent::incr; - int y = 0; - for (int i=0; iPrint(pRegs) << ".\n"; - y = 1; - } - } - if (!y) f << "none\n"; - f << indent::decr; - f << '\n'; -} - json ARegion::basic_json_data(ARegionList *regions) { json j; j["terrain"] = TerrainDefs[type].name; @@ -1309,6 +1218,26 @@ json ARegion::basic_json_data(ARegionList *regions) { string label = (level->strName ? level->strName->const_str() : "surface"); j["coordinates"] = { { "x", xloc }, { "y", yloc }, { "z", zloc }, { "label", label } }; + // in order to support games with different UW settings, we need to put a bit more information in the JSON to + // make the text report easier.. exact depth being hidden is really stupid, but that's the way the text report is. + if (!Globals->EASIER_UNDERWORLD) { + string z_prefix = ""; + if (zloc >= 2 && zloc < Globals->UNDERWORLD_LEVELS + 2) { + for (int i = zloc; i > 3; i--) { + z_prefix += "very "; + } + z_prefix += "deep "; + } else if ((zloc > Globals->UNDERWORLD_LEVELS + 2) && + (zloc < Globals->UNDERWORLD_LEVELS + + Globals->UNDERDEEP_LEVELS + 2)) { + for (int i = zloc; i > Globals->UNDERWORLD_LEVELS + 3; i--) { + z_prefix += "very "; + } + z_prefix += "deep "; + } + if (!z_prefix.empty()) j["coordinates"]["depth_prefix"] = z_prefix; + } + j["province"] = name->const_str(); if (town) { j["settlement"] = { { "name", town->name->const_str() }, { "size", TownString(town->TownType()).const_str() } }; @@ -1316,15 +1245,16 @@ json ARegion::basic_json_data(ARegionList *regions) { return j; } -void ARegion::write_json_report(json& j, Faction *fac, int month, ARegionList *regions) { +void ARegion::build_json_report(json& j, Faction *fac, int month, ARegionList *regions) { Farsight *farsight = GetFarsight(&farsees, fac); Farsight *passer = GetFarsight(&passers, fac); - int present = Present(fac) || fac->is_npc; + bool present = (Present(fac) == 1) || fac->is_npc; // this faction cannot see this region, why are we even here? if (!farsight && !passer && !present) return; j = basic_json_data(regions); + j["present"] = present && !fac->is_npc; if (Population() && (present || farsight || (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_PEASANTS))) { j["population"] = { { "amount", Population() } }; @@ -1353,7 +1283,7 @@ void ARegion::write_json_report(json& j, Faction *fac, int month, ARegionList *r Production *p = get_production_for_skill(I_SILVER, -1); double wages = p ? p->productivity / 10.0 : 0; auto max_wages = p ? p->amount : 0; - j["wages"] = (p && ((Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_WAGES) || present)) + j["wages"] = (p && ((Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_WAGES) || present || farsight)) ? json{ { "amount", wages }, { "max", max_wages } } : json{ { "amount", 0 } }; @@ -1361,7 +1291,7 @@ void ARegion::write_json_report(json& j, Faction *fac, int month, ARegionList *r json for_sale = json::array(); for (const auto& m : markets) { if (!m->amount) continue; - if (!present && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_MARKETS)) continue; + if (!present && !farsight && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_MARKETS)) continue; ItemType itemdef = ItemDefs[m->item]; json item = { @@ -1384,8 +1314,10 @@ void ARegion::write_json_report(json& j, Faction *fac, int month, ARegionList *r j["markets"] = { { "wanted", wanted }, { "for_sale", for_sale } }; p = get_production_for_skill(I_SILVER, S_ENTERTAINMENT); - j["entertainment"] = p && (present || (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_ENTERTAINMENT)) - ? p->amount : 0; + if (p) { + j["entertainment"] = + (present || farsight || (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_ENTERTAINMENT)) ? p->amount : 0; + } j["products"] = json::array(); for (const auto& p : products) { @@ -1394,7 +1326,7 @@ void ARegion::write_json_report(json& j, Faction *fac, int month, ARegionList *r // Advanced items have slightly different rules, so call CanMakeAdv (poorly named) to see if we can see them. if (itemdef.type & IT_ADVANCED && !(CanMakeAdv(fac, p->itemtype) || fac->is_npc)) continue; // If it's a normal resource, and we aren't here or not showing it, skip it. - if (!present && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_RESOURCES)) continue; + if (!present && !farsight && !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_RESOURCES)) continue; json item = { { "name", itemdef.name }, { "plural", itemdef.names }, { "tag", itemdef.abr }, }; @@ -1516,329 +1448,7 @@ void ARegion::write_json_report(json& j, Faction *fac, int month, ARegionList *r { forlist (&objects) { Object *o = (Object *) elem; - o->write_json_report(j, fac, obs, truesight, detfac, passobs, passtrue, passdetfac, present || farsight); - } - } -} - -void ARegion::write_text_report(ostream &f, Faction *fac, int month, ARegionList *pRegions) -{ - Farsight *farsight = GetFarsight(&farsees, fac); - Farsight *passer = GetFarsight(&passers, fac); - int present = Present(fac) || fac->is_npc; - - if (farsight || passer || present) { - AString temp = Print(pRegions); - if (Population() && - (present || farsight || - (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_PEASANTS))) { - temp += AString(", ") + Population() + " peasants"; - if (Globals->RACES_EXIST) { - temp += AString(" (") + ItemDefs[race].names + ")"; - } - if (present || farsight || - Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_REGION_MONEY) { - temp += AString(", $") + wealth; - } else { - temp += AString(", $0"); - } - } - temp += "."; - f << temp.const_str() << "\n"; - f << "------------------------------------------------------------\n"; - - f << indent::incr; - if (Globals->WEATHER_EXISTS) { - temp = "It was "; - if (clearskies) temp += "unnaturally clear "; - else { - if (weather == W_BLIZZARD) temp = "There was an unnatural "; - else if (weather == W_NORMAL) temp = "The weather was "; - temp += SeasonNames[weather]; - } - temp += " last month; "; - int nxtweather = pRegions->GetWeather(this, (month + 1) % 12); - temp += "it will be "; - temp += SeasonNames[nxtweather]; - temp += " next month."; - f << temp.const_str() << "\n"; - } - - // Freezing effect - if (weather == W_BLIZZARD && !clearskies) { - temp = "There was an unnatural freezing last month."; - f << temp.const_str() << "\n"; - } - -#if 0 - f << "\nElevation is " << elevation << ".\n"; - f << "Humidity is " << humidity << ".\n"; - f << "Temperature is " << temperature << ".\n"; -#endif - - if (type == R_NEXUS) { - f << '\n' << Globals->WORLD_NAME << " Nexus is a magical place: the entryway " - << "to the world of " << Globals->WORLD_NAME << ". Enjoy your stay; " - << "the city guards should keep you safe as long as you should choose " - << "to stay. However, rumor has it that once you have left the Nexus, " - << "you can never return.\n\n"; - } - - f << indent::decr; - - WriteEconomy(f, fac, present || farsight); - - int exits_seen[NDIRS]; - if (present || farsight || - (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_ALL_EXITS)) { - for (int i = 0; i < NDIRS; i++) - exits_seen[i] = 1; - } else { - // This is just a transit report and we're not showing all - // exits. See if we are showing used exits. - - // Show none by default. - int i; - for (i = 0; i < NDIRS; i++) - exits_seen[i] = 0; - // Now, if we should, show the ones actually used. - if (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_USED_EXITS) { - forlist(&passers) { - Farsight *p = (Farsight *)elem; - if (p->faction == fac) { - for (i = 0; i < NDIRS; i++) { - exits_seen[i] |= p->exits_used[i]; - } - } - } - } - } - - WriteExits(f, pRegions, exits_seen); - - if (Globals->GATES_EXIST && gate && gate != -1) { - int sawgate = 0; - if (fac->is_npc) - sawgate = 1; - if (Globals->IMPROVED_FARSIGHT && farsight) { - forlist(&farsees) { - Farsight *watcher = (Farsight *)elem; - if (watcher && watcher->faction == fac && watcher->unit) { - if (watcher->unit->GetSkill(S_GATE_LORE)) { - sawgate = 1; - } - } - } - } - if (Globals->TRANSIT_REPORT & GameDefs::REPORT_USE_UNIT_SKILLS) { - forlist(&passers) { - Farsight *watcher = (Farsight *)elem; - if (watcher && watcher->faction == fac && watcher->unit) { - if (watcher->unit->GetSkill(S_GATE_LORE)) { - sawgate = 1; - } - } - } - } - forlist(&objects) { - Object *o = (Object *) elem; - forlist(&o->units) { - Unit *u = (Unit *) elem; - if (!sawgate && - ((u->faction == fac) && - u->GetSkill(S_GATE_LORE))) { - sawgate = 1; - } - } - } - if (sawgate) { - if (gateopen) { - f << "There is a Gate here (Gate " << gate; - if (!Globals->DISPERSE_GATE_NUMBERS) { - f << " of " << pRegions->numberofgates; - } - f << ").\n\n"; - } else if (Globals->SHOW_CLOSED_GATES) { - f << "There is a closed Gate here.\n\n"; - } - } - } - - int obs = GetObservation(fac, 0); - int truesight = GetTrueSight(fac, 0); - int detfac = 0; - - int passobs = GetObservation(fac, 1); - int passtrue = GetTrueSight(fac, 1); - int passdetfac = detfac; - - if (fac->is_npc) { - obs = 10; - passobs = 10; - } - - forlist (&objects) { - Object *o = (Object *) elem; - forlist(&o->units) { - Unit *u = (Unit *) elem; - if (u->faction == fac && u->GetSkill(S_MIND_READING) > 1) { - detfac = 1; - } - } - } - if (Globals->IMPROVED_FARSIGHT && farsight) { - forlist(&farsees) { - Farsight *watcher = (Farsight *)elem; - if (watcher && watcher->faction == fac && watcher->unit) { - if (watcher->unit->GetSkill(S_MIND_READING) > 1) { - detfac = 1; - } - } - } - } - - if ((Globals->TRANSIT_REPORT & GameDefs::REPORT_USE_UNIT_SKILLS) && - (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_UNITS)) { - forlist(&passers) { - Farsight *watcher = (Farsight *)elem; - if (watcher && watcher->faction == fac && watcher->unit) { - if (watcher->unit->GetSkill(S_MIND_READING) > 1) { - passdetfac = 1; - } - } - } - } - - { - forlist (&objects) { - ((Object *) elem)->write_text_report(f, fac, obs, truesight, detfac, - passobs, passtrue, passdetfac, - present || farsight); - } - f << '\n'; - } - } -} - -// DK -void ARegion::WriteTemplate(ostream& f, Faction *fac, ARegionList *pRegs, int month) -{ - int header = 0, order; - AString temp, *token; - - forlist (&objects) { - Object *o = (Object *) elem; - forlist(&o->units) { - Unit *u = (Unit *) elem; - if (u->faction == fac) { - if (!header) { - // DK - if (fac->temformat == TEMPLATE_MAP) { - WriteTemplateHeader(f, fac, pRegs, month); - } else { - temp = AString("*** ") + Print(pRegs) + AString(" ***"); - f << '\n'; - f << indent::comment << temp.const_str() << '\n'; - } - header = 1; - } - - f << "\nunit " << u->num << '\n'; - // DK - if (fac->temformat == TEMPLATE_LONG || - fac->temformat == TEMPLATE_MAP) { - f << indent::comment << u->TemplateReport() << '\n'; - } - int gotMonthOrder = 0; - forlist(&(u->oldorders)) { - temp = *((AString *) elem); - temp.getat(); - token = temp.gettoken(); - if (token) { - order = Parse1Order(token); - delete token; - } else - order = NORDERS; - switch (order) { - case O_MOVE: - case O_SAIL: - case O_TEACH: - case O_STUDY: - case O_BUILD: - case O_PRODUCE: - case O_ENTERTAIN: - case O_WORK: - gotMonthOrder = 1; - break; - case O_TAX: - case O_PILLAGE: - if (Globals->TAX_PILLAGE_MONTH_LONG) - gotMonthOrder = 1; - break; - } - if (order == O_ENDTURN || order == O_ENDFORM) - f << indent::decr; - f << *((AString *) elem) << '\n'; - if (order == O_TURN || order == O_FORM) - f << indent::incr; - } - f << indent::set_indent(0); - u->oldorders.DeleteAll(); - - int firstMonthOrder = gotMonthOrder; - if (u->turnorders.First()) { - TurnOrder *tOrder; - forlist(&u->turnorders) { - tOrder = (TurnOrder *)elem; - if (firstMonthOrder) { - f << (tOrder->repeating ? "@" : "") << "TURN\n"; - f << indent::incr; - } - forlist(&tOrder->turnOrders) { - temp = *((AString *) elem); - temp.getat(); - token = temp.gettoken(); - if (token) { - order = Parse1Order(token); - delete token; - } else - order = NORDERS; - if (order == O_ENDTURN || order == O_ENDFORM) - f << indent::decr; - f << *((AString *) elem) << '\n'; - if (order == O_TURN || order == O_FORM) - f << indent::incr; - } - if (firstMonthOrder) { - f << indent::decr << "ENDTURN\n"; - } - firstMonthOrder = 1; - f << indent::set_indent(0); - } - tOrder = (TurnOrder *) u->turnorders.First(); - if (tOrder->repeating && !gotMonthOrder) { - f << "@TURN\n"; - f << indent::incr; - forlist(&tOrder->turnOrders) { - temp = *((AString *) elem); - temp.getat(); - token = temp.gettoken(); - if (token) { - order = Parse1Order(token); - delete token; - } else - order = NORDERS; - if (order == O_ENDTURN || order == O_ENDFORM) - f << indent::decr; - f << *((AString *) elem) << '\n'; - if (order == O_TURN || order == O_FORM) - f << indent::incr; - } - f << indent::set_indent(0) << "ENDTURN\n"; - } - } - u->turnorders.DeleteAll(); - } + o->build_json_report(j, fac, obs, truesight, detfac, passobs, passtrue, passdetfac, present || farsight); } } } diff --git a/aregion.h b/aregion.h index cfb244ef..003a5c4f 100644 --- a/aregion.h +++ b/aregion.h @@ -197,17 +197,7 @@ class ARegion : public AListElem int CanMakeAdv(Faction *, int); int HasItem(Faction *, int); - void WriteProducts(ostream& f, Faction *, int); - void WriteMarkets(ostream& f, Faction *, int); - void WriteEconomy(ostream& f, Faction *, int); - void WriteExits(ostream& f, ARegionList *pRegs, int *exits_seen); - void write_text_report(ostream& f, Faction *fac, int month, ARegionList *pRegions); - json basic_json_data(ARegionList *pRegions); - void write_json_report(json& j, Faction *fac, int month, ARegionList *pRegions); - // DK - void WriteTemplate(ostream& f, Faction *, ARegionList *, int); - void WriteTemplateHeader(ostream& f, Faction *, ARegionList *, int); - void GetMapLine(char *, int, ARegionList *); + void build_json_report(json& j, Faction *fac, int month, ARegionList *pRegions); AString ShortPrint(ARegionList *pRegs); AString Print(ARegionList *pRegs); @@ -385,6 +375,8 @@ class ARegion : public AListElem std::vector GetPossibleLairs(); void SetupHabitat(TerrainType* terrain); void SetupEconomy(); + + json basic_json_data(ARegionList *pRegions); }; class ARegionArray diff --git a/battle.cpp b/battle.cpp index 9cefe425..9b4b07f9 100644 --- a/battle.cpp +++ b/battle.cpp @@ -732,26 +732,17 @@ void Battle::WriteSides(ARegion * r, AddLine(""); } -void Battle::write_json_report(json& j, Faction *fac) { +void Battle::build_json_report(json& j, Faction *fac) { if(assassination == ASS_SUCC && fac != attacker) { j["type"] = "assassination"; - j["report"] = asstext; + j["report"] = json::array(); + j["report"].push_back(asstext); return; } j["type"] = "battle"; j["report"] = text; } -void Battle::write_text_report(ostream& f,Faction * fac) { - if (assassination == ASS_SUCC && fac != attacker) { - f << asstext << "\n"; - return; - } - for (const auto& line: text) { - f << line << '\n'; - } -} - void Battle::AddLine(const AString & s) { text.push_back(s.const_str()); } diff --git a/battle.h b/battle.h index 3f7bb11b..074a79b8 100644 --- a/battle.h +++ b/battle.h @@ -57,8 +57,7 @@ class Battle : public AListElem Battle(); ~Battle(); - void write_text_report(ostream& f, Faction *fac); - void write_json_report(json &j, Faction *fac); + void build_json_report(json &j, Faction *fac); void AddLine(const AString &); int Run(Events* events, ARegion *, Unit *, AList *, Unit *, AList *, int ass, diff --git a/faction.cpp b/faction.cpp index e5ae75f7..1d355a32 100644 --- a/faction.cpp +++ b/faction.cpp @@ -251,34 +251,6 @@ void Faction::SetAddress(AString &strNewAddress) address = new AString(strNewAddress); } -AString Faction::FactionTypeStr() -{ - AString temp; - if (is_npc) return AString("NPC"); - - if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_UNLIMITED) { - return (AString("Unlimited")); - } else if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_MAGE_COUNT) { - return(AString("Normal")); - } else if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) { - bool comma = false; - - for (auto &ft : *FactionTypes) { - auto value = type[ft]; - if (value) { - if (comma) { - temp += ", "; - } else { - comma = true; - } - temp += AString(ft) + " " + value; - } - } - if (!comma) return AString("none"); - } - return temp; -} - vector Faction::compute_faction_statistics(Game *game, size_t **citems) { vector stats; // To allow testing, just return an empty vector if we don't have any item arrays inbound @@ -342,50 +314,7 @@ static inline GmData collect_gm_data() { return data; } -void Faction::write_text_gm_report(ostream& f, Game *game) { - GmData data = collect_gm_data(); - - bool need_header = true; - for (auto &skillshow : data.skills) { - if(need_header) { f << "Skill reports:\n"; need_header = false; } - AString *string = skillshow.Report(this); - if (string) { - f << '\n' << string->const_str() << '\n'; - delete string; - } - } - if(!need_header) f << '\n'; - - need_header = true; - for (const auto& itemshow : data.items) { - if(need_header) { f << "Item reports:\n"; need_header = false; } - f << '\n' << itemshow << '\n'; - } - if (!need_header) f << '\n'; - - need_header = true; - for (const auto& objectshow : data.objects) { - if(need_header) { f << "Object reports:\n"; need_header = false; } - f << '\n' << objectshow << '\n'; - } - if (!need_header) f << '\n'; - - present_regions.clear(); - forlist(&(game->regions)) { - ARegion *reg = (ARegion *)elem; - present_regions.push_back(reg); - } - for (const auto& reg: present_regions) { - reg->write_text_report(f, this, game->month, &(game->regions)); - } - present_regions.clear(); - - errors.clear(); - events.clear(); - battles.clear(); -} - -void Faction::write_json_gm_report(json& j, Game *game) { +void Faction::build_gm_json_report(json& j, Game *game) { GmData data = collect_gm_data(); json skills = json::array(); @@ -410,7 +339,7 @@ void Faction::write_json_gm_report(json& j, Game *game) { json regions = json::array(); for (const auto& reg: present_regions) { json region; - reg->write_json_report(region, this, game->month, &(game->regions)); + reg->build_json_report(region, this, game->month, &(game->regions)); regions.push_back(region); } j["regions"] = regions; @@ -421,9 +350,9 @@ void Faction::write_json_gm_report(json& j, Game *game) { battles.clear(); } -void Faction::write_json_report(json& j, Game *game, size_t **citems) { +void Faction::build_json_report(json& j, Game *game, size_t **citems) { if (gets_gm_report(game)) { - write_json_gm_report(j, game); + build_gm_json_report(j, game); return; } @@ -441,18 +370,21 @@ void Faction::write_json_report(json& j, Game *game, size_t **citems) { string s = name->const_str(); j["name"] = s.substr(0, s.find(" (")); // remove the faction number from the name for json output j["number"] = num; - j["email"] = address->const_str(); - j["password"] = password->const_str(); if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) { j["type"] = json::object(); for (auto &ft : *FactionTypes) { string factype = ft; - transform(factype.begin(), factype.end(), factype.begin(), ::tolower); + std::transform(factype.begin(), factype.end(), factype.begin(), ::tolower); if (type[ft]) j["type"][factype] = type[ft]; } } - j["times_sent"] = (times != 0); - j["password_unset"] = (!password || *password == "none"); + j["administrative"]["times_sent"] = (times != 0); + bool password_unset = (!password || *password == "none"); + j["administrative"]["password_unset"] = password_unset; + j["administrative"]["email"] = address->const_str(); + j["administrative"]["show_unit_attitudes"] = (showunitattitudes != 0); + + if(!password_unset) j["administrative"]["password"] = password->const_str(); if(Globals->MAX_INACTIVE_TURNS) { int cturn = game->TurnNumber() - lastorders; if ((cturn >= (Globals->MAX_INACTIVE_TURNS - 3)) && !is_npc) { @@ -468,19 +400,24 @@ void Faction::write_json_report(json& j, Game *game, size_t **citems) { { "year", game->year } }; - if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_MAGE_COUNT) { + if ((Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_MAGE_COUNT) + || (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES)) { j["status"]["mages"] = { { "current", nummages }, { "allowed", game->AllowedMages(this) } }; if (Globals->APPRENTICES_EXIST) { + string name = Globals->APPRENTICE_NAME; + name[0] = toupper(name[0]); j["status"]["apprentices"] = { { "current", numapprentices }, - { "allowed", game->AllowedApprentices(this) } + { "allowed", game->AllowedApprentices(this) }, + { "name", name } }; } - } else if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) { + } + if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) { if (Globals->FACTION_ACTIVITY != FactionActivityRules::DEFAULT) { int currentCost = GetActivityCost(FactionActivity::TAX); int maxAllowedCost = game->AllowedMartial(this); @@ -511,16 +448,6 @@ void Faction::write_json_report(json& j, Game *game, size_t **citems) { { "allowed", game->AllowedTacticians(this) } }; } - j["status"]["mages"] = { - { "current", nummages }, - { "allowed", game->AllowedMages(this) } - }; - if (Globals->APPRENTICES_EXIST) { - j["status"]["apprentices"] = { - { "current", numapprentices }, - { "allowed", game->AllowedApprentices(this) } - }; - } } if (!errors.empty()) { @@ -534,7 +461,7 @@ void Faction::write_json_report(json& j, Game *game, size_t **citems) { for (const auto& battle: battles) { json jbattle = json::object(); // we will obviously want to make this into json-y output - battle->write_json_report(jbattle, this); + battle->build_json_report(jbattle, this); jbattles.push_back(jbattle); } j["battles"] = jbattles; @@ -548,12 +475,12 @@ void Faction::write_json_report(json& j, Game *game, size_t **citems) { /* Attitudes */ j["attitudes"] = json::object(); string defattitude = AttitudeStrs[defaultattitude]; - transform(defattitude.begin(), defattitude.end(), defattitude.begin(), ::tolower); + std::transform(defattitude.begin(), defattitude.end(), defattitude.begin(), ::tolower); j["attitudes"]["default"] = defattitude; for (int i=0; iwrite_json_report(region, this, game->month, &(game->regions)); + reg->build_json_report(region, this, game->month, &(game->regions)); regions.push_back(region); } j["regions"] = regions; } -void Faction::write_text_report(ostream& f, Game *pGame, size_t ** citems) -{ - // make the output automatically wrap at 70 characters - f << indent::wrap; - - if(gets_gm_report(pGame)) { - write_text_gm_report(f, pGame); - return; - } - - if (Globals->FACTION_STATISTICS) { - f << ";Treasury:\n"; - f << ";\n"; - f << ";Item Rank Max Total\n"; - f << ";=====================================================================\n"; - for (const auto& stat : compute_faction_statistics(pGame, citems)) { - f << ';' << stat << '\n'; - } - f << '\n'; - } - - f << "Atlantis Report For:\n"; - if ((Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_MAGE_COUNT) || - (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_UNLIMITED)) { - f << name->const_str() << '\n'; - } else if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) { - f << name->const_str() << " (" << FactionTypeStr().const_str() << ")\n"; - } - f << MonthNames[pGame->month] << ", Year " << pGame->year << "\n\n"; - - f << "Atlantis Engine Version: " << ATL_VER_STRING(CURRENT_ATL_VER) << '\n'; - f << Globals->RULESET_NAME << ", Version: " << ATL_VER_STRING(Globals->RULESET_VERSION) << "\n\n"; - - if (!times) { - f << "Note: The Times is not being sent to you.\n\n"; - } - - if (!password || (*password == "none")) { - f << "REMINDER: You have not set a password for your faction!\n\n"; - } - - if (Globals->MAX_INACTIVE_TURNS != -1) { - int cturn = pGame->TurnNumber() - lastorders; - if ((cturn >= (Globals->MAX_INACTIVE_TURNS - 3)) && !is_npc) { - cturn = Globals->MAX_INACTIVE_TURNS - cturn; - f << "WARNING: You have " << cturn - << " turns until your faction is automatically removed due to inactivity!\n\n"; - } - } - - if (!exists) { - if (quit == QUIT_AND_RESTART) { - f << "You restarted your faction this turn. This faction " - << "has been removed, and a new faction has been started " - << "for you. (Your new faction report will come in a " - << "separate message.)\n"; - } else if (quit == QUIT_GAME_OVER) { - f << "I'm sorry, the game has ended. Better luck in " - << "the next game you play!\n"; - } else if (quit == QUIT_WON_GAME) { - f << "Congratulations, you have won the game!\n"; - } else { - f << "I'm sorry, your faction has been eliminated.\n" - << "If you wish to restart, please let the " - << "Gamemaster know, and you will be restarted for " - << "the next available turn.\n"; - } - f << '\n'; - } - - f << "Faction Status:\n"; - if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_MAGE_COUNT) { - f << "Mages: " << nummages << " (" << pGame->AllowedMages(this) << ")\n"; - - if (Globals->APPRENTICES_EXIST) { - string name = Globals->APPRENTICE_NAME; - name[0] = toupper(name[0]); - f << name << "s: " << numapprentices << " (" << pGame->AllowedApprentices(this) << ")\n"; - } - } - else if (Globals->FACTION_LIMIT_TYPE == GameDefs::FACLIM_FACTION_TYPES) { - if (Globals->FACTION_ACTIVITY != FactionActivityRules::DEFAULT) { - int currentCost = GetActivityCost(FactionActivity::TAX); - int maxAllowedCost = pGame->AllowedMartial(this); - - bool isMerged = Globals->FACTION_ACTIVITY == FactionActivityRules::MARTIAL_MERGED; - f << (isMerged ? "Regions: " : "Activity: ") << currentCost << " (" << maxAllowedCost << ")\n"; - } else { - int taxRegions = GetActivityCost(FactionActivity::TAX); - int tradeRegions = GetActivityCost(FactionActivity::TRADE); - f << "Tax Regions: " << taxRegions << " (" << pGame->AllowedTaxes(this) << ")\n"; - f << "Trade Regions: " << tradeRegions << " (" << pGame->AllowedTrades(this) << ")\n"; - } - - if (Globals->TRANSPORT & GameDefs::ALLOW_TRANSPORT) { - f << "Quartermasters: " << numqms << " (" << pGame->AllowedQuarterMasters(this) << ")\n"; - } - - if (Globals->TACTICS_NEEDS_WAR) { - f << "Tacticians: " << numtacts << " (" << pGame->AllowedTacticians(this) << ")\n"; - } - - f << "Mages: " << nummages << " (" << pGame->AllowedMages(this) << ")\n"; - - if (Globals->APPRENTICES_EXIST) { - string name = Globals->APPRENTICE_NAME; - name[0] = toupper(name[0]); - f << name << "s: " << numapprentices << " (" << pGame->AllowedApprentices(this) << ")\n"; - } - } - f << '\n'; - - if (!errors.empty()) { - f << "Errors during turn:\n"; - for (const auto& error : errors) f << error << '\n'; - f << '\n'; - } - - if (!battles.empty()) { - f << "Battles during turn:\n"; - for (const auto& battle: battles) { - battle->write_text_report(f, this); - } - } - - if (!events.empty()) { - f << "Events during turn:\n"; - for (const auto& event: events) f << event << '\n'; - f << '\n'; - } - - if (!shows.empty()) { - f << "Skill reports:\n"; - for (const auto& skillshow : shows) { - AString *string = skillshow.Report(this); - if (string) { - f << '\n' << string->const_str() << '\n'; - delete string; - } - } - f << '\n'; - } - - if (!itemshows.empty()) { - f << "Item reports:\n"; - for (const auto& itemshow : itemshows) f << '\n' << itemshow << '\n'; - f << '\n'; - } - - if (!objectshows.empty()) { - f << "Object reports:\n"; - for (const auto& objectshow : objectshows) f << '\n' << objectshow << '\n'; - f << '\n'; - } - - /* Attitudes */ - f << "Declared Attitudes (default " << AttitudeStrs[defaultattitude] << "):\n"; - for (int i = 0; i < NATTITUDES; i++) { - int j = 0; - f << AttitudeStrs[i] << " : "; - for (const auto& attitude: attitudes) { - if (attitude.attitude == i) { - if (j) f << ", "; - f << GetFaction(&(pGame->factions), attitude.factionnum)->name->const_str(); - j = 1; - } - } - if (!j) f << "none"; - f << ".\n"; - } - f << '\n'; - - f << "Unclaimed silver: " << unclaimed << ".\n\n"; - - for (const auto& reg: present_regions) { - reg->write_text_report(f, this, pGame->month, &(pGame->regions)); - } - f << '\n'; -} - -// LLS - write order template -void Faction::WriteTemplate(ostream& f, Game *pGame) -{ - AString temp; - if (temformat == TEMPLATE_OFF) return; - if (is_npc) return; - - f << indent::wrap; - f << '\n'; - switch (temformat) { - case TEMPLATE_SHORT: - f << "Orders Template (Short Format):\n"; - break; - case TEMPLATE_LONG: - f << "Orders Template (Long Format):\n"; - break; - case TEMPLATE_MAP: - f << "Orders Template (Map Format):\n"; - break; - } - f << '\n'; - f << "#atlantis " << num; - if (!(*password == "none")) { - f << " \"" << *password << "\""; - } - f << '\n'; - - for (const auto& reg: present_regions) { - reg->WriteTemplate(f, this, &(pGame->regions), pGame->month); - } - - f << "\n#end\n\n"; -} - void Faction::WriteFacInfo(ostream &f) { f << "Faction: " << num << '\n'; diff --git a/faction.h b/faction.h index ccd5448c..0633f7f2 100644 --- a/faction.h +++ b/faction.h @@ -161,12 +161,8 @@ class Faction : public AListElem void error(const string &s); void event(const string &s); - AString FactionTypeStr(); - void write_text_report(ostream &f, Game *pGame, size_t **citems); - void write_json_report(json &j, Game *pGame, size_t **citems); + void build_json_report(json& j, Game *pGame, size_t **citems); - // LLS - write order template - void WriteTemplate(ostream &f, Game *pGame); void WriteFacInfo(ostream &f); void set_attitude(int faction_id, int attitude); // attitude -1 clears it @@ -256,10 +252,7 @@ class Faction : public AListElem private: vector compute_faction_statistics(Game *game, size_t **citems); void gm_report_setup(Game *game); - - // Split the gm report from the normal report to make the code more readable. - void write_text_gm_report(ostream &f, Game *game); - void write_json_gm_report(json &j, Game *game); + void build_gm_json_report(json& j, Game *game); }; Faction *GetFaction(AList *, int); diff --git a/game.cpp b/game.cpp index 70c4a1c7..825f1cdf 100644 --- a/game.cpp +++ b/game.cpp @@ -29,17 +29,18 @@ #define F_OK 0 #endif +#include +#include +#include #include -#include "game.h" -#include "unit.h" #include "astring.h" +#include "game.h" #include "gamedata.h" -#include "quests.h" #include "indenter.hpp" -#include -#include -#include +#include "text_report_generator.hpp" +#include "quests.h" +#include "unit.h" #include "external/nlohmann/json.hpp" using json = nlohmann::json; @@ -1066,10 +1067,7 @@ int Game::RunGame() Awrite("Writing the Report File..."); WriteReport(); Awrite(""); - // LLS - write order templates - Awrite("Writing order templates..."); - WriteTemplates(); - Awrite(""); + battles.clear(); EmptyHell(); @@ -1209,21 +1207,35 @@ void Game::WriteReport() forlist(&factions) { Faction *fac = (Faction *) elem; - string str = "report." + to_string(fac->num); + string report_file = "report." + to_string(fac->num); + string template_file = "template." + to_string(fac->num); if (!fac->is_npc || fac->gets_gm_report(this)) { + // Generate the report in JSON format and then write it to whatever formats we want + json json_report; + bool show_region_depth = Globals->EASIER_UNDERWORLD + && (Globals->UNDERWORLD_LEVELS + Globals->UNDERDEEP_LEVELS > 1); + fac->build_json_report(json_report, this, citems); + if (Globals->REPORT_FORMAT & GameDefs::REPORT_FORMAT_TEXT) { - ofstream f(str, ios::out|ios::ate); + TextReportGenerator text_report; + ofstream f(report_file, ios::out | ios::ate); if (f.is_open()) { - fac->write_text_report(f, this, citems); + text_report.output(f, json_report, show_region_depth); + } + if (!fac->is_npc && fac->temformat != TEMPLATE_OFF) { + // even factions which get a gm report do not get a template. + ofstream f(template_file, ios::out | ios::ate); + if (f.is_open()) { + text_report.output_template(f, json_report, fac->temformat, show_region_depth); + } } } + if (Globals->REPORT_FORMAT & GameDefs::REPORT_FORMAT_JSON) { - ofstream jsonf(str + ".json", ios::out | ios::ate); + ofstream jsonf(report_file + ".json", ios::out | ios::ate); if (jsonf.is_open()) { - json json_obj; - fac->write_json_report(json_obj, this, citems); - jsonf << json_obj.dump(2); + jsonf << json_report.dump(2); } } } @@ -1237,24 +1249,6 @@ void Game::WriteReport() } } -// LLS - write order templates for factions -void Game::WriteTemplates() -{ - forlist(&factions) { - Faction *fac = (Faction *) elem; - if (!fac->is_npc) { - string str = "template." + to_string(fac->num); - ofstream f(str.c_str(), ios::out|ios::ate); - if (f.is_open()) { - fac->WriteTemplate(f, this); - } - fac->present_regions.clear(); - } - Adot(); - } -} - - void Game::DeleteDeadFactions() { forlist(&factions) { @@ -1958,15 +1952,9 @@ void Game::CreateCityMon(ARegion *pReg, int percent, int needmage) } u->SetFlag(FLAG_HOLDING,1); u->MoveUnit(pReg->GetDummy()); - /* - Awrite(AString(*u->BattleReport(3))); - */ if ((!Globals->LEADERS_EXIST) && (pReg->type != R_NEXUS)) { u2->SetFlag(FLAG_HOLDING,1); u2->MoveUnit(pReg->GetDummy()); - /* - Awrite(AString(*u2->BattleReport(3))); - */ } if (AC && Globals->START_CITY_MAGES && needmage) { diff --git a/game.h b/game.h index ec8830c2..1b194960 100644 --- a/game.h +++ b/game.h @@ -151,13 +151,8 @@ class Game void ClearOrders(Faction *); void MakeFactionReportLists(); void CountAllSpecialists(); - // void CountAllMages(); - // void CountAllApprentices(); - // void CountAllQuarterMasters(); - // void CountAllTacticians(); + void WriteReport(); - // LLS - write order templates - void WriteTemplates(); void DeleteDeadFactions(); diff --git a/indenter.hpp b/indenter.hpp index 8e0ce292..e7271f4d 100644 --- a/indenter.hpp +++ b/indenter.hpp @@ -1,3 +1,8 @@ +#pragma once + +#ifndef __INDENTER_HPP__ +#define __INDENTER_HPP__ + #include #include #include @@ -210,3 +215,5 @@ namespace indent { return os; } } + +#endif diff --git a/object.cpp b/object.cpp index 7b34e86e..62ae1c84 100644 --- a/object.cpp +++ b/object.cpp @@ -281,7 +281,7 @@ Unit *Object::GetOwner() return(owner); } -void Object::write_json_report(json& j, Faction *fac, int obs, int truesight, +void Object::build_json_report(json& j, Faction *fac, int obs, int truesight, int detfac, int passobs, int passtrue, int passdetfac, int present) { // Exit early if no observable. @@ -302,11 +302,12 @@ void Object::write_json_report(json& j, Faction *fac, int obs, int truesight, if (describe) container["description"] = describe->const_str(); if (IsFleet()) { + container["fleet"] = true; if((GetOwner() && fac == GetOwner()->faction) || (obs > 9)){ container["load"] = FleetLoad(); container["capacity"] = FleetCapacity(); container["sailors"] = FleetSailingSkill(1); - container["fleetsize"] = GetFleetSize(); + container["fleet_size"] = GetFleetSize(); container["max_speed"] = GetFleetSpeed(1); container["damage_percent"] = incomplete; } @@ -330,8 +331,8 @@ void Object::write_json_report(json& j, Faction *fac, int obs, int truesight, } } } else { - if (incomplete) container["incomplete"] = incomplete; - container["inner_location"] = inner != -1; + if (incomplete > 0) container["incomplete"] = incomplete; + if (inner != -1) container["inner_location"] = true; if (runes) container["warding_runes"] = true; if (!(ob.flags & ObjectType::CANENTER)) container["closed"] = true; if (Globals->DECAY && !(ob.flags & ObjectType::NEVERDECAY) && incomplete < 1) { @@ -352,15 +353,15 @@ void Object::write_json_report(json& j, Faction *fac, int obs, int truesight, json unit = json::object(); int attitude = fac->get_attitude(u->faction->num); if (u->faction == fac) { - u->write_json_report(unit, -1, 1, 1, 1, attitude, fac->showunitattitudes); + u->build_json_report(unit, -1, 1, 1, 1, attitude, fac->showunitattitudes); } else { if (present) { - u->write_json_report(unit, obs, truesight, detfac, type != O_DUMMY, attitude, fac->showunitattitudes); + u->build_json_report(unit, obs, truesight, detfac, type != O_DUMMY, attitude, fac->showunitattitudes); } else { if (((type == O_DUMMY) && (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_OUTDOOR_UNITS)) || ((type != O_DUMMY) && (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_INDOOR_UNITS)) || ((u->guard == GUARD_GUARD) && (Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_GUARDS))) { - u->write_json_report(unit, passobs, passtrue, passdetfac, type != O_DUMMY, attitude, fac->showunitattitudes); + u->build_json_report(unit, passobs, passtrue, passdetfac, type != O_DUMMY, attitude, fac->showunitattitudes); } } } @@ -373,134 +374,6 @@ void Object::write_json_report(json& j, Faction *fac, int obs, int truesight, } } -void Object::write_text_report(ostream& f, Faction *fac, int obs, int truesight, - int detfac, int passobs, int passtrue, int passdetfac, int present) -{ - ObjectType *ob = &ObjectDefs[type]; - - if ((type != O_DUMMY) && !present) { - if (IsFleet() && - !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_SHIPS)) { - // This is a ship and we don't see ships in transit - return; - } - if (IsBuilding() && - !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_BUILDINGS)) { - // This is a building and we don't see buildings in transit - return; - } - if (IsRoad() && - !(Globals->TRANSIT_REPORT & GameDefs::REPORT_SHOW_ROADS)) { - // This is a road and we don't see roads in transit - return; - } - } - - /* Fleet Report */ - if (IsFleet()) { - AString temp = AString("+ ") + *name + " : " + FleetDefinition(); - /* report ships: - for (int item=0; item 0) { - if (num > 1) { - temp += AString(", ") + num + " " + ItemDefs[item].names; - } else { - temp += AString(", ") + num + " " +ItemDefs[item].name; - } - } - } - */ - if ((GetOwner() && fac == GetOwner()->faction) || (obs > 9)){ - temp += ";"; - if (incomplete > 0) { - temp += AString(" ") + incomplete + "% damaged;"; - } - temp += AString(" Load: ") + FleetLoad() + "/" + FleetCapacity() + ";"; - temp += AString(" Sailors: ") + FleetSailingSkill(1) + "/" + GetFleetSize() + ";"; - temp += AString(" MaxSpeed: ") + GetFleetSpeed(1); - } - if ((Globals->PREVENT_SAIL_THROUGH) && - (!Globals->ALLOW_TRIVIAL_PORTAGE)) { - if ((flying < 1) && - (TerrainDefs[region->type].similar_type != R_OCEAN)) { - int dir = 0; - int first = 1; - temp += AString("; Sail directions: "); - for (dir = 0; dir < NDIRS; dir++) { - if (SailThroughCheck(dir) == 1) { - if (first == 1) first = 0; - else temp += AString(", "); - - temp += DirectionAbrs[dir]; - } - } - } - } - if (describe) { - temp += AString("; ") + *describe; - } - temp += "."; - f << temp << '\n'; - f << indent::incr; - } else if (type != O_DUMMY) { - AString temp = AString("+ ") + *name + " : " + ob->name; - if (incomplete > 0) { - temp += AString(", needs ") + incomplete; - } else if (Globals->DECAY && - !(ob->flags & ObjectType::NEVERDECAY) && incomplete < 1) { - if (incomplete > (0 - ob->maxMonthlyDecay)) { - temp += ", about to decay"; - } else if (incomplete > (0 - ob->maxMaintenance/2)) { - temp += ", needs maintenance"; - } - } - if (inner != -1) { - temp += ", contains an inner location"; - } - if (runes) { - temp += ", engraved with Runes of Warding"; - } - if (describe) { - temp += AString("; ") + *describe; - } - if (!(ob->flags & ObjectType::CANENTER)) { - temp += ", closed to player units"; - } - temp += "."; - f << temp << '\n'; - f << indent::incr; - } - - forlist ((&units)) { - Unit *u = (Unit *) elem; - int attitude = fac->get_attitude(u->faction->num); - if (u->faction == fac) { - u->write_text_report(f, -1, 1, 1, 1, attitude, fac->showunitattitudes); - } else { - if (present) { - u->write_text_report(f, obs, truesight, detfac, type != O_DUMMY, attitude, fac->showunitattitudes); - } else { - if (((type == O_DUMMY) && - (Globals->TRANSIT_REPORT & - GameDefs::REPORT_SHOW_OUTDOOR_UNITS)) || - ((type != O_DUMMY) && - (Globals->TRANSIT_REPORT & - GameDefs::REPORT_SHOW_INDOOR_UNITS)) || - ((u->guard == GUARD_GUARD) && - (Globals->TRANSIT_REPORT & - GameDefs::REPORT_SHOW_GUARDS))) { - u->write_text_report(f, passobs, passtrue, passdetfac, - type != O_DUMMY, attitude, fac->showunitattitudes); - } - } - } - } - if (type != O_DUMMY) { - f << indent::decr; - } - f << '\n'; -} void Object::SetPrevDir(int newdir) { diff --git a/object.h b/object.h index c0c9938f..d426d0d3 100644 --- a/object.h +++ b/object.h @@ -91,8 +91,7 @@ class Object : public AListElem void Readin(istream& f, AList *); void Writeout(ostream& f); - void write_text_report(ostream& f, Faction *, int, int, int, int, int, int, int); - void write_json_report(json& j, Faction *, int, int, int, int, int, int, int); + void build_json_report(json& j, Faction *, int, int, int, int, int, int, int); void SetName(AString *); void SetDescribe(AString *); diff --git a/snapshot-tests/neworigins_turns/turn_0/engine-output.txt b/snapshot-tests/neworigins_turns/turn_0/engine-output.txt index 6263ce2f..38817705 100644 --- a/snapshot-tests/neworigins_turns/turn_0/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_0/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 0 regions; 96 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_1/engine-output.txt b/snapshot-tests/neworigins_turns/turn_1/engine-output.txt index 7dcc4e03..01ec5556 100644 --- a/snapshot-tests/neworigins_turns/turn_1/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_1/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 1 regions; 95 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_10/engine-output.txt b/snapshot-tests/neworigins_turns/turn_10/engine-output.txt index 97fcb9bf..6f2eb8dd 100644 --- a/snapshot-tests/neworigins_turns/turn_10/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_10/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 6 regions; 90 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_11/engine-output.txt b/snapshot-tests/neworigins_turns/turn_11/engine-output.txt index f14d78cd..6d2b7e89 100644 --- a/snapshot-tests/neworigins_turns/turn_11/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_11/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 11 regions; 85 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_12/engine-output.txt b/snapshot-tests/neworigins_turns/turn_12/engine-output.txt index f0d94dd0..c8da2512 100644 --- a/snapshot-tests/neworigins_turns/turn_12/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_12/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 20 regions; 76 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_13/engine-output.txt b/snapshot-tests/neworigins_turns/turn_13/engine-output.txt index a567354a..addf27cf 100644 --- a/snapshot-tests/neworigins_turns/turn_13/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_13/engine-output.txt @@ -52,8 +52,6 @@ Quest reward: mithril x 35. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_2/engine-output.txt b/snapshot-tests/neworigins_turns/turn_2/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_2/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_2/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_3/engine-output.txt b/snapshot-tests/neworigins_turns/turn_3/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_3/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_3/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_4/engine-output.txt b/snapshot-tests/neworigins_turns/turn_4/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_4/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_4/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_5/engine-output.txt b/snapshot-tests/neworigins_turns/turn_5/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_5/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_5/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_6/engine-output.txt b/snapshot-tests/neworigins_turns/turn_6/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_6/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_6/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_7/engine-output.txt b/snapshot-tests/neworigins_turns/turn_7/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_7/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_7/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_8/engine-output.txt b/snapshot-tests/neworigins_turns/turn_8/engine-output.txt index 8d983a7e..56c16d5f 100644 --- a/snapshot-tests/neworigins_turns/turn_8/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_8/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 2 regions; 94 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/neworigins_turns/turn_9/engine-output.txt b/snapshot-tests/neworigins_turns/turn_9/engine-output.txt index 37a6dd6c..ea699f1a 100644 --- a/snapshot-tests/neworigins_turns/turn_9/engine-output.txt +++ b/snapshot-tests/neworigins_turns/turn_9/engine-output.txt @@ -42,8 +42,6 @@ Players have visited 4 regions; 92 unvisited. Writing world events... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_0/engine-output.txt b/snapshot-tests/turns/turn_0/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_0/engine-output.txt +++ b/snapshot-tests/turns/turn_0/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_1/engine-output.txt b/snapshot-tests/turns/turn_1/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_1/engine-output.txt +++ b/snapshot-tests/turns/turn_1/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_10/engine-output.txt b/snapshot-tests/turns/turn_10/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_10/engine-output.txt +++ b/snapshot-tests/turns/turn_10/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_11/engine-output.txt b/snapshot-tests/turns/turn_11/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_11/engine-output.txt +++ b/snapshot-tests/turns/turn_11/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_12/engine-output.txt b/snapshot-tests/turns/turn_12/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_12/engine-output.txt +++ b/snapshot-tests/turns/turn_12/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_13/engine-output.txt b/snapshot-tests/turns/turn_13/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_13/engine-output.txt +++ b/snapshot-tests/turns/turn_13/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_2/engine-output.txt b/snapshot-tests/turns/turn_2/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_2/engine-output.txt +++ b/snapshot-tests/turns/turn_2/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_3/engine-output.txt b/snapshot-tests/turns/turn_3/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_3/engine-output.txt +++ b/snapshot-tests/turns/turn_3/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_4/engine-output.txt b/snapshot-tests/turns/turn_4/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_4/engine-output.txt +++ b/snapshot-tests/turns/turn_4/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_5/engine-output.txt b/snapshot-tests/turns/turn_5/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_5/engine-output.txt +++ b/snapshot-tests/turns/turn_5/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_6/engine-output.txt b/snapshot-tests/turns/turn_6/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_6/engine-output.txt +++ b/snapshot-tests/turns/turn_6/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_7/engine-output.txt b/snapshot-tests/turns/turn_7/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_7/engine-output.txt +++ b/snapshot-tests/turns/turn_7/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_8/engine-output.txt b/snapshot-tests/turns/turn_8/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_8/engine-output.txt +++ b/snapshot-tests/turns/turn_8/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/snapshot-tests/turns/turn_9/engine-output.txt b/snapshot-tests/turns/turn_9/engine-output.txt index 4d8a6cf2..7b62304e 100644 --- a/snapshot-tests/turns/turn_9/engine-output.txt +++ b/snapshot-tests/turns/turn_9/engine-output.txt @@ -39,8 +39,6 @@ Assessing Maintenance costs... Post-Turn Processing... Writing the Report File... ... -Writing order templates... -... Writing Playerinfo File... Removing Dead Factions... done diff --git a/template.cpp b/template.cpp deleted file mode 100644 index 5a1dda9c..00000000 --- a/template.cpp +++ /dev/null @@ -1,587 +0,0 @@ -// START A3HEADER -// -// This source file is part of the Atlantis PBM game program. -// Copyright (C) 1995-1999 Geoff Dunbar -// -// 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 2 -// 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, in the file license.txt. If not, write -// to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -// Boston, MA 02111-1307, USA. -// -// See the Atlantis Project web page for details: -// http://www.prankster.com/project -// -// END A3HEADER - -#include "game.h" -#include "gamedata.h" -#include "indenter.hpp" -#include -#ifdef WIN32 -#include -#endif -#include - -#define LINE_WIDTH 70 -#define MAP_WIDTH 23 -#define TMPL_MAP_WIDTH 20 -#define TMPL_MAP_OFS 1 -#define FILL_SIZE 6 -#define TEMPLATE_MAX_LINES 13 - -static char const *TemplateMap[] = { - //12345678901234567890 - " ____ ", // 1 - "nw / \\ ne", // 2 - " ____/ \\____ ", // 3 - " / \\ / \\ ", // 4 - "/ \\____/ \\", // 5 - "\\ / \\ /", // 6 - " \\____/ \\____/ ", // 7 - " / \\ / \\ ", // 8 - "/ \\____/ \\", // 9 - "\\ / \\ /", // 10 - " \\____/ \\____/ ", // 11 - " \\ / ", // 12 - "sw \\____/ se" // 13 -}; - -static int dircrd[] = { - // X Y - 8-1, 7-1, // center - 8-1, 3-1, // N - 14-1, 5-1, // NE - 14-1, 9-1, // SE - 8-1, 11-1, // S - 2-1, 9-1, // SW - 2-1, 5-1 // NW -}; - - -static char const *ter_fill[] = { - // block - " #### ", - " #### ", - - // ocean - " ~ ~ ", - " ~ ~ ", - - // plain - " ", - " ", - - // forest - " ^ ^ ", - " ^ ^ ", - - // mountain - " /\\/\\ ", - "/ \\ \\", - - // swamp - " v v ", - " v v ", - - // jungle - " @ @ ", - " @ @ ", - - // desert - " . . ", - " . . ", - - // tundra - " ' ' ", - " ' ' ", - - // cavern - " . . ", - " . . ", - - // underforest - " ^ ^ ", - " ^ ^ ", - - // tunnels - " ", - " ", - - // nexus - " !!!! ", - " !!!! ", - - // For conquest - // Island Plain - " ", - " ", - - // Island swamp - " v v ", - " v v ", - - // Island mountain - " /\\/\\ ", - "/ \\ \\", - - // For Ceran terrains - - // plain1 - " ", - " ", - // plain2 - " ", - " ", - // plain3 - " ", - " ", - - // forest1 - " ^ ^ ", - " ^ ^ ", - // forest2 - " ^ ^ ", - " ^ ^ ", - // forest3 - " ^ ^ ", - " ^ ^ ", - - // mystforest - " ` ` ", - " ` ` ", - // mystforest1 - " ` ` ", - " ` ` ", - // mystforest2 - " ` ` ", - " ` ` ", - - // mountain1 - " /\\/\\ ", - "/ \\ \\", - // mountain2 - " /\\/\\ ", - "/ \\ \\", - // mountain3 - " /\\/\\ ", - "/ \\ \\", - - // hill - " * * ", - " * * ", - // hill1 - " * * ", - " * * ", - // hill2 - " * * ", - " * * ", - - // swamp1 - " v v ", - " v v ", - // swamp2 - " v v ", - " v v ", - // swamp3 - " v v ", - " v v ", - - // jungle1 - " @ @ ", - " @ @ ", - // jungle2 - " @ @ ", - " @ @ ", - // jungle3 - " @ @ ", - " @ @ ", - - // desert1 - " . . ", - " . . ", - // desert2 - " . . ", - " . . ", - // desert3 - " . . ", - " . . ", - - // wasteland - " ; ; ", - " ; ; ", - // wasteland1 - " ; ; ", - " ; ; ", - - // lake - " ~ ~ ", - " ~ ~ ", - - // tundra1 - " ' ' ", - " ' ' ", - // tundra2 - " ' ' ", - " ' ' ", - // tundra2 - " ' ' ", - " ' ' ", - - // cavern1 - " . . ", - " . . ", - // cavern2 - " . . ", - " . . ", - // cavern3 - " . . ", - " . . ", - - // underforest1 - " ^ ^ ", - " ^ ^ ", - // underforest2 - " ^ ^ ", - " ^ ^ ", - // underforest3 - " ^ ^ ", - " ^ ^ ", - - // tunnels1 - " ", - " ", - // tunnels2 - " ", - " ", - - // grotto - " . . ", - " . . ", - // deepforest - " ^ ^ ", - " ^ ^ ", - // chasm - " ", - " ", - // grotto1 - " . . ", - " . . ", - // deepforest1 - " ^ ^ ", - " ^ ^ ", - // chasm1 - " ", - " ", - - // volcano - " /\\/\\ ", - "/ \\ \\", - - // lake - " ~ ~ ", - " ~ ~ ", -}; - -// NEW FUNCTION DK 2000.03.07, -// converted WriteReport -// -void ARegion::WriteTemplateHeader(ostream& f, Faction *fac, - ARegionList *pRegs, int month) -{ - - f << '\n' << indent::comment << "-----------------------------------------------------------\n"; - - // plain (X,Y) in Blah, contains Blah - f << indent::comment << Print(pRegs) << '\n'; - - char buffer[LINE_WIDTH+1]; - string temp; - char *data = buffer + MAP_WIDTH; - int line = 0; - - Production *prod; - int any; - - // ---------------------------------------------------------------- - GetMapLine(buffer, line, pRegs); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - - // ---------------------------------------------------------------- - - if (Globals->WEATHER_EXISTS) { - GetMapLine(buffer, line, pRegs); - - char const *nextWeather = ""; - int nxtweather = pRegs->GetWeather(this, (month + 1) % 12); - if (nxtweather == W_WINTER) - nextWeather = "winter"; - if (nxtweather == W_MONSOON) - nextWeather = "monsoon"; - if (nxtweather == W_NORMAL) - nextWeather = "clear"; - snprintf(data, LINE_WIDTH - MAP_WIDTH, "Next %s", nextWeather); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } - - // ---------------------------------------------------------------- - GetMapLine(buffer, line, pRegs); - snprintf(data, LINE_WIDTH - MAP_WIDTH, "Tax %5i", wealth); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - - // ---------------------------------------------------------------- - prod = get_production_for_skill(I_SILVER, S_ENTERTAINMENT); - if (prod) { - GetMapLine(buffer, line, pRegs); - snprintf(data, LINE_WIDTH - MAP_WIDTH, "Ente %5i", prod->amount); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } - - // ---------------------------------------------------------------- - prod = get_production_for_skill(I_SILVER, -1); - if (prod) { - GetMapLine(buffer, line, pRegs); - snprintf(data, LINE_WIDTH - MAP_WIDTH, "Wage %5i.%1i (max %i)", (prod->productivity/10), (prod->productivity%10), prod->amount); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } - - // ---------------------------------------------------------------- - any = 0; - for (const auto& m : markets) { - if (!m->amount) continue; - if (m->type == M_SELL) { - - if (ItemDefs[m->item].type & IT_ADVANCED) { - if (!HasItem(fac, m->item)) { - continue; - } - } - - if (!any) { - GetMapLine(buffer, line, pRegs); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } - - GetMapLine(buffer, line, pRegs); - - if (m->amount == -1) { - snprintf(data, LINE_WIDTH - MAP_WIDTH, "%s unlim %4s @ %3i", - (any ? " " : "Want"), - ItemDefs[m->item].abr, - m->price); - } else { - snprintf(data, LINE_WIDTH - MAP_WIDTH, "%s %5i %4s @ %3i", - (any ? " " : "Want"), - m->amount, - ItemDefs[m->item].abr, - m->price); - } - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - any = 1; - } - } - - // ---------------------------------------------------------------- - any = 0; - for (const auto& m : markets) { - if (!m->amount) continue; - if (m->type == M_BUY) { - - if (!any) { - GetMapLine(buffer, line, pRegs); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } - - GetMapLine(buffer, line, pRegs); - - if (m->amount == -1) { - snprintf(data, LINE_WIDTH - MAP_WIDTH, "%s unlim %4s @ %3i", - (any ? " " : "Sell"), - ItemDefs[m->item].abr, - m->price); - } else { - snprintf(data, LINE_WIDTH - MAP_WIDTH, "%s %5i %4s @ %3i", - (any ? " " : "Sell"), - m->amount, - ItemDefs[m->item].abr, - m->price); - } - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - any = 1; - } - } - - // ---------------------------------------------------------------- - any = 0; - for (const auto& p : products) { - if (ItemDefs[p->itemtype].type & IT_ADVANCED) { - if (!CanMakeAdv(fac, p->itemtype)) { - continue; - } - } else { - if (p->itemtype == I_SILVER) { - continue; - } - } - - if (!any) { - GetMapLine(buffer, line, pRegs); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } - - GetMapLine(buffer, line, pRegs); - - if (p->amount == -1) { - snprintf(data, LINE_WIDTH - MAP_WIDTH, "%s unlim %4s", - (any ? " " : "Prod"), - ItemDefs[p->itemtype].abr); - } else { - snprintf(data, LINE_WIDTH - MAP_WIDTH, "%s %5i %4s", - (any ? " " : "Prod"), - p->amount, - ItemDefs[p->itemtype].abr); - } - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - any = 1; - } - - // ---------------------------------------------------------------- - - if (Globals->GATES_EXIST && gate) { - int sawgate = 0; - forlist(&objects) { - Object *o = (Object *) elem; - forlist(&o->units) { - Unit *u = (Unit *) elem; - if (!sawgate && u->faction == fac && - u->GetSkill(S_GATE_LORE)) { - GetMapLine(buffer, line, pRegs); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - - GetMapLine(buffer, line, pRegs); - snprintf(data, LINE_WIDTH - MAP_WIDTH, "Gate %4i", gate); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - - sawgate = 1; - } - } - } - } - - // ---------------------------------------------------------------- - while (line < TEMPLATE_MAX_LINES) { - GetMapLine(buffer, line, pRegs); - temp = buffer; - temp.erase(temp.find_last_not_of(' ') + 1); - f << indent::comment << temp << '\n'; - line++; - } -} - - -// NEW FUNCTION DK 2000.03.07, -// converted WriteExits -// -void ARegion::GetMapLine(char *buffer, int line, ARegionList *pRegs) -{ - - for (int m=0; m= TEMPLATE_MAX_LINES) - return; - - char *dest = buffer+TMPL_MAP_OFS; - memcpy(dest, TemplateMap[line], TMPL_MAP_WIDTH); - - ARegion *r = this; - int x, y, t, i = 0; - char *name; - - t = (r->type + 1) * 2; - name = (r->town ? r->town->name->Str() : NULL); - - for (;;) { - - x = dircrd[i*2]; - y = dircrd[i*2+1]; - - if (y == line || y+1 == line) { - if (y == line) { - if (name) { - int len = strlen(name); - if (len > FILL_SIZE) len = FILL_SIZE; - memcpy(dest + x, name, len); - } else { - memcpy(dest + x, ter_fill[t], FILL_SIZE); - } - } else { - t++; - memcpy(dest + x, ter_fill[t], FILL_SIZE); - } - } - - if (i >= NDIRS) break; - - ARegion *r = neighbors[i]; - if (r) { - t = (r->type + 1) * 2; - name = (r->town ? r->town->name->Str() : NULL); - } else { - t = 0; - name = NULL; - } - - i++; - } -} diff --git a/text_report_generator.cpp b/text_report_generator.cpp new file mode 100644 index 00000000..2a22bfb8 --- /dev/null +++ b/text_report_generator.cpp @@ -0,0 +1,846 @@ +#include + +#include "astring.h" +#include "faction.h" +#include "gamedefs.h" +#include "indenter.hpp" +#include "text_report_generator.hpp" + +#include "external/nlohmann/json.hpp" +using json = nlohmann::json; + +using namespace std; + +const int TextReportGenerator::line_width = 70; +const int TextReportGenerator::map_width = 23; +const size_t TextReportGenerator::template_fill_size = 6; +const int TextReportGenerator::template_max_lines = 13; + + +// Initialize the string constants +const string TextReportGenerator::template_map[] = { +// 01234567890123456789012 + " ____ ", // 1 + " nw / \\ ne ", // 2 + " ____/ \\____ ", // 3 + " / \\ / \\ ", // 4 + " / \\____/ \\ ", // 5 + " \\ / \\ / ", // 6 + " \\____/ \\____/ ", // 7 + " / \\ / \\ ", // 8 + " / \\____/ \\ ", // 9 + " \\ / \\ / ", // 10 + " \\____/ \\____/ ", // 11 + " \\ / ", // 12 + " sw \\____/ se " // 13 +}; + +const map> TextReportGenerator::direction_offsets = { + {"center", { 8, 7-1 }}, + {"north", { 8, 3-1 }}, + {"northeast", { 14, 5-1 }}, + {"southeast", { 14, 9-1 }}, + {"south", { 8, 11-1 }}, + {"southwest", { 2, 9-1 }}, + {"northwest", { 2, 5-1 }} +}; + +const map> TextReportGenerator::terrain_fill = { + { "block", { + "####", + "####" } + }, + { "ocean", { + " ~ ~ ", + " ~ ~ " } + }, + { "plain", { + " ", + " " } + }, + { "forest", { + " ^ ^ ", + " ^ ^ " } + }, + { "mountain", { + " /\\/\\ ", + "/ \\ \\" } + }, + { "swamp", { + " v v ", + " v v " } + }, + { "jungle", { + " @ @ ", + " @ @ " } + }, + { "desert", { + " . . ", + " . . " } + }, + { "tundra", { + " ' ' ", + " ' ' " } + }, + { "cavern", { + " . . ", + " . . " } + }, + { "underforest", { + " ^ ^ ", + " ^ ^ " } + }, + { "tunnels", { + " ", + " " } + }, + { "nexus", { + " !!!! ", + " !!!! " } + }, + { "mystforest", { + " ` ` ", + " ` ` " } + }, + { "hill", { + " * * ", + " * * " } + }, + { "wasteland", { + " ; ; ", + " ; ; " } + }, + { "lake", { + " ~ ~ ", + " ~ ~ " } + }, + { "grotto", { + " . . ", + " . . " } + }, + { "deepforest", { + " ^ ^ ", + " ^ ^ " } + }, + { "chasm", { + " ", + " " } + }, + { "volcano", { + " /\\/\\ ", + "/ \\ \\" } + }, +}; + +string TextReportGenerator::to_s(const json& j) { + string s; + if (j.is_string()) return j.template get(); + // If we somehow get something that isn't a string, just dump it as json. + return j.dump(); +} + +void TextReportGenerator::output_region_header(ostream&f, const json& region, bool show_region_depth) { + f << to_s(region["terrain"]) << " (" << region["coordinates"]["x"] << "," << region["coordinates"]["y"]; + if (region["coordinates"]["label"] != "surface") { + int z = region["coordinates"]["z"]; + f << ","; + if (show_region_depth) f << z << " <"; + if (region["coordinates"].contains("depth_prefix")) { + f << to_s(region["coordinates"]["depth_prefix"]) << " "; + } + f << to_s(region["coordinates"]["label"]); + if (show_region_depth) f << ">"; + } + f << ")"; + f << " in " << to_s(region["province"]); + if (region.contains("settlement")) { + f << ", contains " << to_s(region["settlement"]["name"]) << " [" << to_s(region["settlement"]["size"]) << "]"; + } +} + +void TextReportGenerator::output_item(ostream& f, const json& item, bool assume_singular, bool show_single_amt) { + if (item.contains("unlimited")) { + f << "unlimited " << to_s(item["plural"]); + } else { + int amount = item.value("amount", 0); + if ((amount > 1) || (show_single_amt && (amount == 1))) f << amount << " "; + if (item.contains("unfinished")) f << "unfinished "; + if (assume_singular) { + f << to_s(item["name"]); + } else { + f << plural(item["amount"], item["name"], item["plural"]); + } + } + + f << " [" << to_s(item["tag"]) << "]"; + if (item.contains("needs")) f << " (needs " << item["needs"] << ")"; + if (item.contains("illusion")) f << " (illusion)"; + if (item.contains("price")) f << " at $" << item["price"]; +} + +void TextReportGenerator::output_items(ostream& f, const json& item_list, bool assume_singular, bool show_single_amt) { + if (item_list.empty()) return; + bool comma = false; + for(const auto& item : item_list) { + if (comma) f << ", "; + output_item(f, item, assume_singular, show_single_amt); + comma = true; + } +} + +void TextReportGenerator::output_item_list(ostream& f, const json& item_list, string header) { + f << header << ": "; + if (item_list.empty()) { + f << "none.\n"; + return; + } + output_items(f, item_list); + f << ".\n"; +} + +void TextReportGenerator::output_unit_summary(ostream& f, const json& unit, bool show_faction) { + f << to_s(unit["name"]) << " (" << unit["number"] << ")"; + // display guard flag *before* the faction + if (unit.contains("flags") && unit["flags"]["guard"]) f << ", on guard"; + if (unit.contains("faction") && show_faction) { + f << ", " << to_s(unit["faction"]["name"]) << " (" << unit["faction"]["number"] << ")"; + } + // and then the rest of the flags (which might or might not be present) + if (unit["flags"].contains("avoid") && unit["flags"]["avoid"]) f << ", avoiding"; + if (unit["flags"].contains("behind") && unit["flags"]["behind"]) f << ", behind"; + if (unit["flags"].contains("reveal")) { + if (unit["flags"]["reveal"] == "unit") f << ", revealing unit"; + if (unit["flags"]["reveal"] == "faction") f << ", revealing faction"; + } + if (unit["flags"].contains("holding") && unit["flags"]["holding"]) f << ", holding"; + if (unit["flags"].contains("taxing") && unit["flags"]["taxing"]) f << ", taxing"; + if (unit["flags"].contains("no_aid") && unit["flags"]["no_aid"]) f << ", receiving no aid"; + if (unit["flags"].contains("sharing") && unit["flags"]["sharing"]) f << ", sharing"; + if (unit["flags"].contains("consume")) { + if (unit["flags"]["consume"] == "unit") f << ", consuming unit's food"; + if (unit["flags"]["consume"] == "faction") f << ", consuming faction's food"; + } + if (unit["flags"].contains("no_cross_water") && unit["flags"]["no_cross_water"]) f << ", won't cross water"; + // All spoils is the default, so we don't need to output it. + if (unit["flags"].contains("spoils") && unit["flags"]["spoils"] != "all") { + f << ", " << unit["flags"]["spoils"] << " battle spoils"; + } + + f << ", "; + output_items(f, unit["items"]); + if (unit.contains("weight")) f << ". Weight: " << unit["weight"]; + if (unit.contains("capacity")) { + f << ". Capacity: " << unit["capacity"]["flying"] << "/" << unit["capacity"]["riding"] << "/" + << unit["capacity"]["walking"] << "/" << unit["capacity"]["swimming"]; + } + + if (unit.contains("skills")) { + f << ". Skills: "; + if (unit["skills"].contains("known") && !unit["skills"]["known"].empty()) { + bool comma = false; + for(const auto& skill : unit["skills"]["known"]) { + if (comma) f << ", "; + f << to_s(skill["name"]) << " [" << to_s(skill["tag"]) << "]" << " " << skill["level"] << " (" + << skill["skill_days"]; + if (skill.contains("study_rate")) { + f << "+" << skill["study_rate"]; + } + f << ")"; + comma = true; + } + } else { + f << "none"; + } + } + + if (unit.contains("combat_spell")) { + f << ". Combat spell: " << to_s(unit["combat_spell"]["name"]) + << " [" << to_s(unit["combat_spell"]["tag"]) << "]"; + } + + // Readied items + if (unit.contains("readied")) { + if (unit["readied"].contains("weapons")) { + int count = unit["readied"]["weapons"].size(); + f << ". Ready " << plural(count, "weapon", "weapons") << ": "; + output_items(f, unit["readied"]["weapons"], true); + } + if (unit["readied"].contains("armor")) { + f << ". Ready armor: "; + output_items(f, unit["readied"]["armor"], true); + } + if (unit["readied"].contains("item")) { + f << ". Ready item: "; + output_item(f, unit["readied"]["item"], true); + } + } + + // Studyable skills + if (unit.contains("skills") && unit["skills"].contains("can_study")) { + f << ". Can Study: "; + bool comma = false; + for(const auto& skill : unit["skills"]["can_study"]) { + if (comma) f << ", "; + f << to_s(skill["name"]) << " [" << to_s(skill["tag"]) << "]"; + comma = true; + } + } + + if (unit.contains("visited") && !unit["visited"].empty()) { + f << ". Has visited "; + for (auto it = unit["visited"].begin(); it != unit["visited"].end(); it++) { + if (it != unit["visited"].begin()) { + if (it == unit["visited"].end() - 1) + f << " and "; + else + f << ", "; + } + f << to_s(*it); + } + } + + if (unit.contains("description")) f << "; " << to_s(unit["description"]); + f << ".\n"; +} + +void TextReportGenerator::output_unit(ostream& f, const json& unit, bool show_unit_attitudes) { + if (unit.contains("own_unit")) { + f << "* "; + } else if (!show_unit_attitudes) { + f << "- "; + } else if (unit.contains("attitude")) { + if(unit["attitude"] == "ally") f << "= "; + if(unit["attitude"] == "friendly") f << ": "; + if(unit["attitude"] == "neutral") f << "- "; + if(unit["attitude"] == "unfriendly") f << "% "; + if(unit["attitude"] == "hostile") f << "! "; + } else { + f << "- "; + } + output_unit_summary(f, unit); +} + +void TextReportGenerator::output_structure(ostream& f, const json& structure, bool show_unit_attitudes) { + f << "+ " << to_s(structure["name"]) << " [" << structure["number"] << "] : "; + if (structure.contains("ships")) { + output_items(f, structure["ships"], false, true); + if (structure.contains("damage_percent")) f << "; " << structure["damage_percent"] << "% damaged; "; + if (structure.contains("load")) + f << "; " << "Load: " << structure["load"] << "/" << structure["capacity"] << "; "; + if (structure.contains("sailors")) + f << "; " << "Sailors: " << structure["sailors"] << "/" << structure["fleet_size"] << "; "; + if (structure.contains("max_speed")) f << "; " << "MaxSpeed: " << structure["max_speed"] << "; "; + if (structure.contains("sail_directions")) { + f << "; " << "Sail directions: "; + bool comma = false; + for(const auto& direction : structure["sail_directions"]) { + if (comma) f << ", "; + f << to_s(direction); + comma = true; + } + } + if (structure.contains("description")) f << "; " << to_s(structure["description"]); + f << ".\n"; + } else { + f << to_s(structure["type"]); + if (structure.contains("incomplete")) f << ", needs " << structure["incomplete"]; + if (structure.contains("decay")) f << ", about to decay"; + if (structure.contains("needs_maintenance")) f << ", needs maintenance"; + if (structure.contains("inner_location")) f << ", contains an inner location"; + if (structure.contains("runes")) f << ", engraved with Runes of Warding"; + if (structure.contains("description")) f << "; " << to_s(structure["description"]); + if (structure.contains("closed")) f << ", closed to player units"; + f << ".\n"; + } + f << indent::incr; + if (structure.contains("units") && !structure["units"].empty()) { + for(const auto& unit : structure["units"]) { + output_unit(f, unit, show_unit_attitudes); + } + } + f << indent::decr; + f << '\n'; +} + +void TextReportGenerator::output_region( + ostream& f, const json& region, bool show_unit_attitudes, bool show_region_depth +) { + output_region_header(f, region, show_region_depth); + if (region.contains("population")) { + f << ", " << region["population"]["amount"] << " peasants"; + if (region["population"]["race"] != "men") { + f << " (" << to_s(region["population"]["race"]) << ")"; + } + if (region.contains("tax")) { + f << ", $" << region["tax"]; + } + } + f << ".\n"; + f << "------------------------------------------------------------\n"; + + f << indent::incr; + if (region.contains("weather")) { + if (region["weather"]["current"] == "clear") { + f << "The weather was clear last month; "; + } else if (region["weather"]["current"] == "blizzard") { + f << "There was an unnatural blizzard last month; "; + } else { + f << "It was " << to_s(region["weather"]["current"]) << " last month; "; + } + f << "it will be " << to_s(region["weather"]["next"]) << " next month.\n"; + } + if (region.contains("description")) f << '\n' << to_s(region["description"]) << "\n\n"; + + if (region.contains("wages")) { + f << "Wages: $" << region["wages"]["amount"]; + if (region["wages"].contains("max")) f << " (Max: $" << region["wages"]["max"] << ")"; + f << ".\n"; + } + + if (region.contains("markets")) { + output_item_list(f, region["markets"]["wanted"], "Wanted"); + output_item_list(f, region["markets"]["for_sale"], "For Sale"); + } + + if (region.contains("entertainment")) f << "Entertainment available: $" << region["entertainment"] << ".\n"; + + output_item_list(f, region["products"], "Products"); + f << indent::decr << '\n'; + + f << "Exits:\n" << indent::incr; + if (region["exits"].empty()) { + f << "none\n"; + } else { + for (const auto& exit : region["exits"]) { + f << to_s(exit["direction"]) << " : "; + output_region_header(f, exit["region"], show_region_depth); + f << ".\n"; + } + } + f << indent::decr << '\n'; + + if (region.contains("gate")) { + if (region["gate"].contains("open")) { + f << "There is a Gate here (Gate " << region["gate"]["number"]; + if (region["gate"].contains("total")) { + f << " of " << region["gate"]["total"]; + } + f << ").\n\n"; + } else { + f << "There is a closed Gate here.\n\n"; + } + } + + if (region.contains("units") && !region["units"].empty()) { + for(const auto& unit : region["units"]) { + output_unit(f, unit, show_unit_attitudes); + } + } + f << '\n'; + + if (region.contains("structures") && !region["structures"].empty()) { + for(const auto& structure : region["structures"]) { + output_structure(f, structure, show_unit_attitudes); + } + f << '\n'; + } else { + f << '\n'; + } +} + +void TextReportGenerator::output(ostream& f, const json& report, bool show_region_depth) { + f << indent::wrap; + + if (report.contains("statistics")) { + f << ";Treasury:\n"; + f << ";\n"; + f << ";Item Rank Max Total\n"; + f << ";=====================================================================\n"; + for (const auto& stat : report["statistics"]) { + f << ';' << left << setw(42) << to_s(stat["item_name"]) << setw(6) << stat.value("rank", 0) + << setw(11) << stat.value("max", 0) << stat.value("total", 0) << right << '\n'; + } + f << '\n'; + } + + // Player reports have an extra newline. GM reports don't. This is silly, but for now we will emulate that + // behavior. + bool final_newline = false; + + if (report.contains("name")) { + // Only player reports generate the report header + final_newline = true; + + f << "Atlantis Report For:\n"; + f << to_s(report["name"]) << " (" << report["number"] << ")"; + if (report.contains("type")) { + f << " ("; + bool comma = false; + // right now there are 4 possible faction types. + // martial, war, trade, magic. + // while it's not legal to have martial along with war and trade, for now we will just check and output + // all of them. + string types[] = {"martial", "war", "trade", "magic"}; + for (const auto& type : types) { + string key = type; + key[0] = toupper(key[0]); + if (report["type"].contains(type)) { + if (comma) f << ", "; + f << key << " " << report["type"][type]; + comma = true; + } + } + f << ")"; + } + f << '\n'; + f << to_s(report["date"]["month"]) << ", Year " << report["date"]["year"] << "\n\n"; + } + + if (report.contains("engine")) { + f << "Atlantis Engine Version: " << to_s(report["engine"]["version"]) << '\n'; + f << to_s(report["engine"]["ruleset"]) << ", Version: " << to_s(report["engine"]["ruleset_version"]) << "\n\n"; + } + + bool show_unit_attitudes = false; + if (report.contains("administrative")) { + if (!report["administrative"].contains("times_sent")) + f << "Note: The Times is not being sent to you.\n\n"; + if (report["administrative"].contains("password_unset")) + f << "REMINDER: You have not set a password for your faction!\n\n"; + if (report["administrative"].contains("inactivity_deletion_turns")) { + f << "WARNING: You have " << report["administrative"]["inactivity_deletion_turns"] + << " turns until your faction is automatically removed due to inactivity!\n\n"; + } + if (report["administrative"].contains("quit")) { + string quit = to_s(report["administrative"]["quit"]); + if (quit == "quit and restart") { + f << "You restarted your faction this turn. This faction has been removed, and a new faction has " + << "been started for you. (Your new faction report will come in a separate message.)\n"; + } else if (quit == "game over") { + f << "I'm sorry, the game has ended. Better luck in the next game you play!\n"; + } else if (quit == "won game") { + f << "Congratulations, you have won the game!\n"; + } else { + f << "I'm sorry, your faction has been eliminated.\n" << "If you wish to restart, please let the " + << "Gamemaster know, and you will be restarted for the next available turn.\n"; + } + f << '\n'; + } + if (report["administrative"].contains("show_unit_attitudes")) { + show_unit_attitudes = report["administrative"]["show_unit_attitudes"]; + } + } + + if (report.contains("status")) { + auto status = report["status"]; + f << "Faction Status:\n"; + if (status.contains("regions")) { + f << "Regions: " << status["regions"]["current"] << " (" << status["regions"]["allowed"] << ")\n"; + } + if (status.contains("activity")) { + f << "Activity: " << status["activity"]["current"] << " (" << status["activity"]["allowed"] << ")\n"; + } + if (status.contains("tax_regions")) { + f << "Tax Regions: " << status["tax_regions"]["current"] << " (" + << status["tax_regions"]["allowed"] << ")\n"; + } + if (status.contains("trade_regions")) { + f << "Trade Regions: " << status["trade_regions"]["current"] << " (" + << status["trade_regions"]["allowed"] << ")\n"; + } + if (status.contains("quartermasters")) { + f << "Quartermasters: " << status["quartermasters"]["current"] << " (" + << status["quartermasters"]["allowed"] << ")\n"; + } + if (status.contains("tacticians")) { + f << "Tacticians: " << status["tacticians"]["current"] << " (" + << status["tacticians"]["allowed"] << ")\n"; + } + if (status.contains("mages")) { + f << "Mages: " << status["mages"]["current"] << " (" << status["mages"]["allowed"] << ")\n"; + } + if (status.contains("apprentices")) { + f << to_s(status["apprentices"]["name"]) << "s: " << status["apprentices"]["current"] << " (" + << status["apprentices"]["allowed"] << ")\n"; + } + f << '\n'; + } + + if (report.contains("errors") && !report["errors"].empty()) { + f << "Errors during turn:\n"; + for (const auto& error : report["errors"]) f << to_s(error) << '\n'; + f << '\n'; + } + + if (report.contains("battles") && !report["battles"].empty()) { + f << "Battles during turn:\n"; + for (const auto& battle: report["battles"]) { + for (const auto& line: battle["report"]) { + f << to_s(line) << '\n'; + } + } + } + + if (report.contains("events") && !report["events"].empty()) { + f << "Events during turn:\n"; + for (const auto& event: report["events"]) f << to_s(event) << '\n'; + f << '\n'; + } + + if(report.contains("skill_reports") && !report["skill_reports"].empty()) { + f << "Skill reports:\n"; + for (const auto& skillshow : report["skill_reports"]) f << '\n' << to_s(skillshow) << '\n'; + f << '\n'; + } + + if (report.contains("item_reports") && !report["item_reports"].empty()) { + f << "Item reports:\n"; + for (const auto& itemshow : report["item_reports"]) f << '\n' << to_s(itemshow) << '\n'; + f << '\n'; + } + + if (report.contains("object_reports") && !report["object_reports"].empty()) { + f << "Object reports:\n"; + for (const auto& objectshow : report["object_reports"]) f << '\n' << to_s(objectshow) << '\n'; + f << '\n'; + } + + if (report.contains("attitudes") && !report["attitudes"].empty()) { + string default_attitude = to_s(report["attitudes"]["default"]); + default_attitude[0] = toupper(default_attitude[0]); + f << "Declared Attitudes (default " << default_attitude << "):\n"; + string available_attitudes[] = { "hostile", "unfriendly", "neutral", "friendly", "ally" }; + for (const auto& attitude : available_attitudes) { + if (report["attitudes"].contains(attitude)) { + string attitude_name = attitude; + attitude_name[0] = toupper(attitude_name[0]); + f << attitude_name << " : "; + if (report["attitudes"][attitude].empty()) { + f << "none"; + } else { + bool comma = false; + for (const auto& faction : report["attitudes"][attitude]) { + if (comma) f << ", "; + f << to_s(faction["name"]) << " (" << faction["number"] << ")"; + comma = true; + } + } + f << ".\n"; + } + } + f << '\n'; + } + + if(report.contains("unclaimed_silver")) { + f << "Unclaimed silver: " << report["unclaimed_silver"] << ".\n\n"; + } + + if (report.contains("regions") && !report["regions"].empty()) { + for (const auto& region : report["regions"]) + output_region(f, region, show_unit_attitudes, show_region_depth); + } + if (final_newline) f << '\n'; +} + +void TextReportGenerator::output_unit_orders(ostream& f, const json& orders) { + for (const auto& order : orders) { + f << to_s(order["order"]) << '\n'; + if (order.contains("nested") && !order["nested"].empty()) { + f << indent::incr; + output_unit_orders(f, order["nested"]); + f << indent::decr; + } + } +} + +void TextReportGenerator::output_unit_template(ostream& f, const json& unit, int template_type) { + f << "\nunit " << unit["number"] << '\n'; + if (template_type == TEMPLATE_LONG || template_type == TEMPLATE_MAP) { + f << indent::comment; + output_unit_summary(f, unit, false); + } + if (unit.contains("orders") && !unit["orders"].empty()) { + output_unit_orders(f, unit["orders"]); + } +} + +string TextReportGenerator::next_map_header_line(int line, const json& region) { + if (line >= template_max_lines) return string(map_width, ' '); + + string result = template_map[line]; + + // See if there is anything special to fill on for this line. + for (const auto& offset: direction_offsets) { + int x = offset.second[0]; + int y = offset.second[1]; + + if (line == y || line == y + 1) { + if (offset.first == "center") { + string value = ( + region.contains("settlement") && line == y + ? to_s(region["settlement"]["name"]) + : terrain_fill.at(to_s(region["terrain"]))[line - y] + ); + int len = min(value.length(), template_fill_size); + result.replace(x, len, value.substr(0, len)); + } else { + if (region.contains("exits")) { + for (const auto& exit : region["exits"]) { + string lower_dir = exit["direction"]; + transform(lower_dir.begin(), lower_dir.end(), lower_dir.begin(), ::tolower); + if (lower_dir == offset.first) { + string value = ( + exit["region"].contains("settlement") && line == y + ? to_s(exit["region"]["settlement"]["name"]) + : terrain_fill.at(to_s(exit["region"]["terrain"]))[line - y] + ); + int len = min(value.length(), template_fill_size); + result.replace(x, len, value.substr(0, len)); + } + } + } + } + } + } + return result; +} + +void TextReportGenerator::output_region_map_header_line(ostream& f, string line) { + string out = line.substr(0, line_width); + out.erase(out.find_last_not_of(' ') + 1); + f << indent::comment << out << '\n'; +} + +void TextReportGenerator::output_region_map_header(ostream& f, const json& region, bool show_region_depth) { + f << '\n' << indent::comment << "-----------------------------------------------------------\n"; + f << indent::comment; + output_region_header(f, region, show_region_depth); + f << '\n'; + + int line = 0; + output_region_map_header_line(f, next_map_header_line(line++, region)); + + if (region.contains("weather")) { + string weather = "Next " + to_s(region["weather"]["next"]); + // strip off ' season' if it's there (for monsoon) + if (weather.ends_with(" season")) weather = weather.substr(0, weather.length() - 7); + output_region_map_header_line(f, next_map_header_line(line++, region) + weather); + } + + stringstream ss; + ss << "Tax " << setw(5) << right << region.value("tax", 0); + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + + if (region.contains("entertainment")) { + ss << "Ente " << setw(5) << right << region.value("entertainment", 0); + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + } + + if (region.contains("wages")) { + ss << "Wage " << setprecision(1) << fixed << setw(7) << right << region["wages"].value("amount", 0.0) + << " (max " << region["wages"]["max"] << ")"; + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + } + + bool first = true; + if (region.contains("markets")) { + for (const auto& item : region["markets"]["wanted"]) { + if (first) output_region_map_header_line(f, next_map_header_line(line++, region)); + ss << (first ? "Want " : " "); + if (item.value("unlimited", false)) ss << "unlim"; + else ss << setw(5) << right << item.value("amount", 0); + ss << " " << setw(4) << to_s(item["tag"]) << " @ " << setw(3) << right << item.value("price", 0); + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + first = false; + } + + first = true; + for (const auto& item : region["markets"]["for_sale"]) { + if (first) output_region_map_header_line(f, next_map_header_line(line++, region)); + ss << (first ? "Sell " : " "); + if (item.value("unlimited", false)) ss << "unlim"; + else ss << setw(5) << right << item.value("amount", 0); + ss << " " << setw(4) << to_s(item["tag"]) << " @ " << setw(3) << right << item.value("price", 0); + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + first = false; + } + } + + first = true; + for (const auto& item : region["products"]) { + if (first) output_region_map_header_line(f, next_map_header_line(line++, region)); + ss << (first ? "Prod " : " "); + if (item.value("unlimited", false)) ss << "unlim"; + else ss << setw(5) << right << item.value("amount", 0); + ss << " " << setw(4) << to_s(item["tag"]); + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + first = false; + } + + if (region.contains("gate")) { + if (region["gate"].contains("open")) { + ss << "Gate " << setw(4) << right << region["gate"].value("number", 0); + output_region_map_header_line(f, next_map_header_line(line++, region) + ss.str()); + stringstream().swap(ss); + } else { + output_region_map_header_line(f, next_map_header_line(line++, region) + "Gate closed"); + } + } + + while (line < template_max_lines) { + output_region_map_header_line(f, next_map_header_line(line++, region)); + } +} + +void TextReportGenerator::output_region_template( + ostream& f, const json& region, int template_type, bool show_region_depth +) { + if (template_type == TEMPLATE_MAP) output_region_map_header(f, region, show_region_depth); + else { + f << "\n" << indent::comment << "*** "; + output_region_header(f, region, show_region_depth); + f << " ***\n"; + } + + if (region.contains("units")) { + for(auto& unit: region["units"]) { + if (unit.contains("own_unit")) output_unit_template(f, unit, template_type); + } + } + + if (region.contains("structures")) { + for (auto& structure : region["structures"]) { + for (auto &unit : structure["units"]) { + if (unit.contains("own_unit")) output_unit_template(f, unit, template_type); + } + } + } +} + +void TextReportGenerator::output_template(ostream& f, const json& report, int template_type, bool show_region_depth) { + f << indent::wrap; + f << "\n"; + f << "Orders Template ("; + f << (template_type == TEMPLATE_SHORT ? "Short" : (template_type == TEMPLATE_LONG ? "Long" : "Map")); + f << " Format):\n\n"; + f << "#atlantis " << report["number"]; + if (!report["administrative"].contains("password_unset")) { + // Since the json output will quote for strings, just use it as is + f << " " << report["administrative"]["password"]; + } + f << "\n"; + + if (report.contains("regions") && !report["regions"].empty()) { + for(const auto& region : report["regions"]) { + // Only output the region template if you have actual units in the region. + if (region.contains("present")) output_region_template(f, region, template_type, show_region_depth); + } + } + f << "\n#end\n\n"; +} diff --git a/text_report_generator.hpp b/text_report_generator.hpp new file mode 100644 index 00000000..fe6a6ad3 --- /dev/null +++ b/text_report_generator.hpp @@ -0,0 +1,45 @@ +#pragma once + +#ifndef __TEXT_REPORT_GENERATOR_HPP__ +#define __TEXT_REPORT_GENERATOR_HPP__ + +#include + +#include "external/nlohmann/json.hpp" +using json = nlohmann::json; + +class TextReportGenerator { +public: + void output(std::ostream& f, const json& report, bool show_region_depth); + void output_template(std::ostream& f, const json& report, int template_type, bool show_region_depth); +private: + void output_region(std::ostream& f, const json& region, bool show_unit_attitudes, bool show_region_depth); + void output_region_header(std::ostream& f, const json& region, bool show_region_depth); + void output_item_list(std::ostream& f, const json& item_list, string header); + void output_items(std::ostream& f, const json& item_list, bool assume_singular = false, bool show_single_amt = false); + void output_item(std::ostream& f, const json& item, bool assume_singular = false, bool show_single_amt = false); + void output_structure(std::ostream& f, const json& structure, bool show_unit_attitudes); + void output_unit(std::ostream& f, const json& unit, bool show_unit_attitudes); + void output_unit_summary(std::ostream& f, const json& unit, bool show_faction = true); + void output_region_template(std::ostream& f, const json& region, int template_type, bool show_region_depth); + void output_region_map_header(std::ostream& f, const json& region, bool show_region_depth); + void output_region_map_header_line(std::ostream& f, std::string line); + std::string next_map_header_line(int line, const json& region); + void output_unit_template(std::ostream& f, const json& unit, int template_type); + void output_unit_orders(std::ostream& f, const json& orders); + + // Without this utility function for strings, we end up printing "" instead of since the + // item is a json element at that point, so we have a nice utility function here. + std::string to_s(const json& item); + + // Some private data members to help with output formatting of the map template + static const int line_width; + static const int map_width; + static const size_t template_fill_size; + static const int template_max_lines; + + static const std::string template_map[]; + static const std::map> terrain_fill; + static const std::map> direction_offsets; +}; +#endif diff --git a/unit.cpp b/unit.cpp index d0cfd730..5cef02e5 100644 --- a/unit.cpp +++ b/unit.cpp @@ -559,7 +559,7 @@ json Unit::write_json_orders() return container; } -void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int autosee, int attitude, int showattitudes) +void Unit::build_json_report(json& j, int obs, int truesight, int detfac, int autosee, int attitude, int showattitudes) { int stealth = GetAttribute("stealth"); bool my_unit = (obs == -1); @@ -609,7 +609,7 @@ void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int au j["flags"]["sharing"] = (GetFlag(FLAG_SHARING) ? true : false); if (GetFlag(FLAG_CONSUMING_UNIT)) j["flags"]["consume"] = "unit"; if (GetFlag(FLAG_CONSUMING_FACTION)) j["flags"]["consume"] = "faction"; - j["flags"]["cross_water"] = (GetFlag(FLAG_NOCROSS_WATER) ? true : false); + j["flags"]["no_cross_water"] = GetFlag(FLAG_NOCROSS_WATER) ? true : false; // Only one of these is allowed to be true at a time. if (GetFlag(FLAG_NOSPOILS)) j["flags"]["spoils"] = "weightless"; @@ -643,6 +643,7 @@ void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int au int rate = StudyRateAdjustment(man_days, exp); skill["study_rate"] = rate; } + j["skills"]["known"].push_back(skill); } for (auto i = 0; i < NSKILLS; i++) { if (SkillDefs[i].depends[0].skill != NULL) { @@ -692,6 +693,8 @@ void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int au } + j["items"] = json::array(); + // Clear any marks on the item list since we want to report items in a specific order. // Not sure this is necessary with json output, but we are going to hold it for now so that the array in json // is in the same order the items would be listed in the normal report. @@ -700,7 +703,7 @@ void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int au // now, report the items in the specific order (men, monsters, weapons, mounts, wagons, other, silver) // items will be marked when they are reported to make sure they don't get reported twice if they fit // multiple categories. - for (auto type = 0; type < 7; type++) { + for (auto output_phase = 0; output_phase < 7; output_phase++) { forlist(&items) { Item *item = (Item *)elem; if (item->checked) continue; @@ -712,13 +715,13 @@ void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int au (item->type == I_WAGON) || (item->type == I_MWAGON) || (item->type == I_SILVER) ); - if (phase == 0 && !(def.type & IT_MAN)) continue; - if (phase == 1 && !(def.type & IT_MONSTER)) continue; - if (phase == 2 && !combat_item) continue; - if (phase == 3 && !(def.type & IT_MOUNT)) continue; - if (phase == 4 && !((item->type == I_WAGON) || (item->type == I_MWAGON))) continue; - if (phase == 5 && item_reported_in_other_phase) continue; - if (phase == 6 && !(item->type == I_SILVER)) continue; + if (output_phase == 0 && !(def.type & IT_MAN)) continue; + if (output_phase == 1 && !(def.type & IT_MONSTER)) continue; + if (output_phase == 2 && !combat_item) continue; + if (output_phase == 3 && !(def.type & IT_MOUNT)) continue; + if (output_phase == 4 && !((item->type == I_WAGON) || (item->type == I_MWAGON))) continue; + if (output_phase == 5 && item_reported_in_other_phase) continue; + if (output_phase == 6 && !(item->type == I_SILVER)) continue; item->checked = 1; if (my_unit || (def.weight != 0)) { @@ -736,220 +739,6 @@ void Unit::write_json_report(json& j, int obs, int truesight, int detfac, int au } } -void Unit::write_text_report(ostream& f, int obs, int truesight, int detfac, - int autosee, int attitude, int showattitudes) -{ - int stealth = GetAttribute("stealth"); - if (obs==-1) { - /* The unit belongs to the Faction writing the report */ - obs = 2; - } else { - if (obs < stealth) { - /* The unit cannot be seen */ - if (reveal == REVEAL_FACTION) { - obs = 1; - } else { - if (guard == GUARD_GUARD || reveal == REVEAL_UNIT || autosee) { - obs = 0; - } else { - return; - } - } - } else { - if (obs == stealth) { - /* Can see unit, but not Faction */ - if (reveal == REVEAL_FACTION) { - obs = 1; - } else { - obs = 0; - } - } else { - /* Can see unit and Faction */ - obs = 1; - } - } - } - - /* Setup True Sight */ - if (obs == 2) { - truesight = 1; - } else { - if (GetSkill(S_ILLUSION) > truesight) { - truesight = 0; - } else { - truesight = 1; - } - } - - if (detfac && obs != 2) obs = 1; - - /* Write the report */ - AString temp; - if (obs == 2) { - temp += AString("* ") + *name; - } else { - if (showattitudes) { - switch (attitude) { - case A_ALLY: - temp += AString("= ") +*name; - break; - case A_FRIENDLY: - temp += AString(": ") +*name; - break; - case A_NEUTRAL: - temp += AString("- ") +*name; - break; - case A_UNFRIENDLY: - temp += AString("% ") +*name; - break; - case A_HOSTILE: - temp += AString("! ") +*name; - break; - } - } else { - temp += AString("- ") + *name; - } - } - - if (guard == GUARD_GUARD) temp += ", on guard"; - if (obs > 0) { - temp += AString(", ") + *faction->name; - if (guard == GUARD_AVOID) temp += ", avoiding"; - if (GetFlag(FLAG_BEHIND)) temp += ", behind"; - } - - if (obs == 2) { - if (reveal == REVEAL_UNIT) temp += ", revealing unit"; - if (reveal == REVEAL_FACTION) temp += ", revealing faction"; - if (GetFlag(FLAG_HOLDING)) temp += ", holding"; - if (GetFlag(FLAG_AUTOTAX)) temp += ", taxing"; - if (GetFlag(FLAG_NOAID)) temp += ", receiving no aid"; - if (GetFlag(FLAG_SHARING)) temp += ", sharing"; - if (GetFlag(FLAG_CONSUMING_UNIT)) temp += ", consuming unit's food"; - if (GetFlag(FLAG_CONSUMING_FACTION)) - temp += ", consuming faction's food"; - if (GetFlag(FLAG_NOCROSS_WATER)) temp += ", won't cross water"; - temp += SpoilsReport(); - } - - temp += items.Report(obs, truesight, 0); - - if (obs == 2) { - temp += ". Weight: "; - temp += AString(items.Weight()); - temp += ". Capacity: "; - temp += AString(FlyingCapacity()); - temp += "/"; - temp += AString(RidingCapacity()); - temp += "/"; - temp += AString(WalkingCapacity()); - temp += "/"; - temp += AString(SwimmingCapacity()); - temp += ". Skills: "; - temp += skills.Report(GetMen()); - } - - if (obs == 2 && (type == U_MAGE || type == U_GUARDMAGE)) { - temp += MageReport(); - } - - if (obs == 2) { - temp += ReadyItem(); - temp += StudyableSkills(); - if (visited.size() > 0) { - set::iterator it; - unsigned int count; - - count = 0; - temp += ". Has visited "; - for (it = visited.begin(); - it != visited.end(); - it++) { - count++; - if (count > 1) { - if (count == visited.size()) - temp += " and "; - else - temp += ", "; - } - temp += it->c_str(); - } - } - } - - if (describe) { - temp += AString("; ") + *describe; - } - temp += "."; - f << temp << '\n'; -} - -AString Unit::TemplateReport() -{ - /* Write the report */ - AString temp; - temp = *name; - - if (guard == GUARD_GUARD) temp += ", on guard"; - if (guard == GUARD_AVOID) temp += ", avoiding"; - if (GetFlag(FLAG_BEHIND)) temp += ", behind"; - if (reveal == REVEAL_UNIT) temp += ", revealing unit"; - if (reveal == REVEAL_FACTION) temp += ", revealing faction"; - if (GetFlag(FLAG_HOLDING)) temp += ", holding"; - if (GetFlag(FLAG_AUTOTAX)) temp += ", taxing"; - if (GetFlag(FLAG_NOAID)) temp += ", receiving no aid"; - if (GetFlag(FLAG_SHARING)) temp += ", sharing"; - if (GetFlag(FLAG_CONSUMING_UNIT)) temp += ", consuming unit's food"; - if (GetFlag(FLAG_CONSUMING_FACTION)) temp += ", consuming faction's food"; - if (GetFlag(FLAG_NOCROSS_WATER)) temp += ", won't cross water"; - temp += SpoilsReport(); - - temp += items.Report(2, 1, 0); - temp += ". Weight: "; - temp += AString(items.Weight()); - temp += ". Capacity: "; - temp += AString(FlyingCapacity()); - temp += "/"; - temp += AString(RidingCapacity()); - temp += "/"; - temp += AString(WalkingCapacity()); - temp += "/"; - temp += AString(SwimmingCapacity()); - temp += ". Skills: "; - temp += skills.Report(GetMen()); - - if (type == U_MAGE || type == U_GUARDMAGE) { - temp += MageReport(); - } - temp += ReadyItem(); - temp += StudyableSkills(); - if (visited.size() > 0) { - set::iterator it; - unsigned int count; - - count = 0; - temp += ". Has visited "; - for (it = visited.begin(); - it != visited.end(); - it++) { - count++; - if (count > 1) { - if (count == visited.size()) - temp += " and "; - else - temp += ", "; - } - temp += it->c_str(); - } - } - - if (describe) { - temp += AString("; ") + *describe; - } - temp += "."; - return temp; -} - AString *Unit::BattleReport(int obs) { AString *temp = new AString(""); diff --git a/unit.h b/unit.h index aa9cf5b8..53573341 100644 --- a/unit.h +++ b/unit.h @@ -133,16 +133,14 @@ class Unit : public AListElem AString SpoilsReport(void); int CanGetSpoil(Item *i); - void write_text_report(ostream& f, int obs, int truesight, int detfac, int autosee, int attitude, int showattitudes); - void write_json_report(json& j, int obs, int truesight, int detfac, int autosee, int attitude, int showattitudes); + void build_json_report(json& j, int obs, int truesight, int detfac, int autosee, int attitude, int showattitudes); json write_json_orders(); AString GetName(int); AString MageReport(); AString ReadyItem(); AString StudyableSkills(); AString * BattleReport(int); - AString TemplateReport(); - + void ClearOrders(); void ClearCastOrders(); void DefaultOrders(Object *); diff --git a/unittest/faction_test.cpp b/unittest/faction_test.cpp index 1d40111a..206f745c 100644 --- a/unittest/faction_test.cpp +++ b/unittest/faction_test.cpp @@ -51,18 +51,6 @@ ut::suite<"Faction"> faction_suite = [] expect(eq(current, expected)); }; - "FactionTypeStr returns correct string"_test = [faction] - { - // Mock up that a faction had set their types to 2 war and 3 magic. - faction->type[F_WAR] = 2; - faction->type[F_TRADE] = 0; - faction->type[F_MAGIC] = 3; - - string current = faction->FactionTypeStr().Str(); - string expected = "War 2, Magic 3"; - expect(eq(current, expected)); - }; - // No memory leaks. delete faction; diff --git a/unittest/json_report_test.cpp b/unittest/json_report_test.cpp index eecb7602..a8224cfc 100644 --- a/unittest/json_report_test.cpp +++ b/unittest/json_report_test.cpp @@ -33,7 +33,7 @@ ut::suite<"JSON Report"> json_report_suite = [] // Generate just this single factions json object. Game &game = helper.game_object(); json json_report; - faction->write_json_report(json_report, &game, nullptr); + faction->build_json_report(json_report, &game, nullptr); // pick some of the data out of the report for checking string data_name = json_report["name"]; @@ -66,7 +66,7 @@ ut::suite<"JSON Report"> json_report_suite = [] faction->error("This is an error"); json json_report; - faction->write_json_report(json_report, &helper.game_object(), nullptr); + faction->build_json_report(json_report, &helper.game_object(), nullptr); auto count = json_report["errors"].size(); expect(count == 1_ul); @@ -85,7 +85,7 @@ ut::suite<"JSON Report"> json_report_suite = [] for (auto i = 0; i < 1003; i++) faction->error("This is error #" + to_string(i+1)); json json_report; - faction->write_json_report(json_report, &helper.game_object(), nullptr); + faction->build_json_report(json_report, &helper.game_object(), nullptr); auto count = json_report["errors"].size(); expect(count == 1001_ul); @@ -109,7 +109,7 @@ ut::suite<"JSON Report"> json_report_suite = [] faction->event("This is event 2"); json json_report; - faction->write_json_report(json_report, &helper.game_object(), nullptr); + faction->build_json_report(json_report, &helper.game_object(), nullptr); auto count = json_report["events"].size(); expect(count == 2_ul); @@ -139,7 +139,7 @@ ut::suite<"JSON Report"> json_report_suite = [] ARegionList *regions = helper.get_regions(); json json_report; - region->write_json_report(json_report, faction, helper.get_month(), regions); + region->build_json_report(json_report, faction, helper.get_month(), regions); string expected_provice("Testing Wilds"); // name given in the unit test setup string province = json_report["province"]; @@ -239,8 +239,8 @@ ut::suite<"JSON Report"> json_report_suite = [] // Get a report for each region so we can verify that fleet data is correct for owners and non-owners. json json_report_1; json json_report_2; - region->write_json_report(json_report_1, faction, helper.get_month(), regions); - region->write_json_report(json_report_2, faction2, helper.get_month(), regions); + region->build_json_report(json_report_1, faction, helper.get_month(), regions); + region->build_json_report(json_report_2, faction2, helper.get_month(), regions); // Verify that owner sees additional data auto capacity = json_report_1["structures"][0]["capacity"]; @@ -282,7 +282,7 @@ ut::suite<"JSON Report"> json_report_suite = [] // Get a report for each region so we can verify that fleet data is correct for owners and non-owners. json json_unit_report; - leader->write_json_report(json_unit_report, -1, 1, 1, 1, A_ALLY, 1); + leader->build_json_report(json_unit_report, -1, 1, 1, 1, A_ALLY, 1); string name = json_unit_report["name"]; string expected_name = "My Leader"; diff --git a/unittest/testhelper.hpp b/unittest/testhelper.hpp index 1eb17e79..996e65ea 100644 --- a/unittest/testhelper.hpp +++ b/unittest/testhelper.hpp @@ -1,3 +1,7 @@ +#pragma once +#ifndef __UNIT_TEST_HELPER_HPP__ +#define __UNIT_TEST_HELPER_HPP__ + #include "game.h" #include @@ -57,3 +61,5 @@ class UnitTestHelper { streambuf *cout_streambuf; Game game; }; + +#endif