diff --git a/aregion.cpp b/aregion.cpp index 5f571388..2de22987 100644 --- a/aregion.cpp +++ b/aregion.cpp @@ -428,7 +428,7 @@ void ARegion::RunDecayEvent(Object *o, ARegionList *pRegs) forlist (pFactions) { Faction *f = ((FactionPtr *) elem)->ptr; string tmp = string(GetDecayFlavor().const_str()) + " " + ObjectDefs[o->type].name + - " in " + ShortPrint(pRegs).const_str() + "."; + " in " + ShortPrint().const_str() + "."; f->event(tmp, "decay"); } } @@ -739,13 +739,13 @@ int ARegion::GetRealDirComp(int realDirection) return complementDirection; } -AString ARegion::ShortPrint(ARegionList *pRegs) +AString ARegion::ShortPrint() { AString temp = TerrainDefs[type].name; temp += AString(" (") + xloc + "," + yloc; - ARegionArray *pArr = pRegs->pRegionArrays[zloc]; + ARegionArray *pArr = this->level; if (pArr->strName) { temp += ","; if (Globals->EASIER_UNDERWORLD && @@ -779,9 +779,9 @@ AString ARegion::ShortPrint(ARegionList *pRegs) return temp; } -AString ARegion::Print(ARegionList *pRegs) +AString ARegion::Print() { - AString temp = ShortPrint(pRegs); + AString temp = ShortPrint(); if (town) { temp += AString(", contains ") + *(town->name) + " [" + TownString(town->TownType()) + "]"; @@ -1677,7 +1677,7 @@ int ARegion::NotifySpell(Unit *caster, char const *spell, ARegionList *pRegs) forlist_reuse (&flist) { FactionPtr *fp = (FactionPtr *) elem; string tmp = string(caster->name->const_str()) + " uses " + SkillStrs(sp).const_str() + - " in " + Print(pRegs).const_str() + "."; + " in " + Print().const_str() + "."; fp->ptr->event(tmp, "cast"); } return 1; diff --git a/aregion.h b/aregion.h index 1bb39d28..6b25c03e 100644 --- a/aregion.h +++ b/aregion.h @@ -201,8 +201,8 @@ class ARegion : public AListElem json basic_region_data(); void build_json_report(json& j, Faction *fac, int month, ARegionList *pRegions); - AString ShortPrint(ARegionList *pRegs); - AString Print(ARegionList *pRegs); + AString ShortPrint(); + AString Print(); void Kill(Unit *); void ClearHell(); diff --git a/battle.cpp b/battle.cpp index d91ee05c..7ceb2b7d 100644 --- a/battle.cpp +++ b/battle.cpp @@ -600,7 +600,7 @@ int Battle::Run(Events* events, assassination = ASS_SUCC; asstext = armies[1]->leader->name->const_str(); asstext += " is assassinated in "; - asstext += region->ShortPrint(pRegs).const_str(); + asstext += region->ShortPrint().const_str(); asstext += "!"; } if (armies[1]->NumAlive()) { @@ -695,10 +695,10 @@ void Battle::WriteSides(ARegion * r, { if (ass) { AddLine(*att->name + " attempts to assassinate " + *tar->name - + " in " + r->ShortPrint( pRegs ) + "!"); + + " in " + r->ShortPrint() + "!"); } else { AddLine(*att->name + " attacks " + *tar->name + " in " + - r->ShortPrint( pRegs ) + "!"); + r->ShortPrint() + "!"); } AddLine(""); diff --git a/edit.cpp b/edit.cpp index d2c347ba..cdb5fd3b 100644 --- a/edit.cpp +++ b/edit.cpp @@ -123,7 +123,7 @@ void Game::EditGameRegion(ARegion *pReg) { do { Awrite( AString("Region ") + pReg->num + ": " + - pReg->Print( ®ions ) ); + pReg->Print() ); Awrite( " 1) Edit objects..." ); Awrite( " 2) Edit terrain..." ); Awrite( " q) Return to previous menu." ); @@ -157,7 +157,7 @@ void Game::EditGameRegionObjects( ARegion *pReg ) //template copied from AtlantisDev 030730 post. Modified option a, added option h, m. { do { - Awrite( AString( "Region: " ) + pReg->ShortPrint( ®ions ) ); + Awrite( AString( "Region: " ) + pReg->ShortPrint() ); Awrite( "" ); int i = 0; AString temp = AString(""); @@ -439,7 +439,7 @@ void Game::EditGameRegionTerrain( ARegion *pReg ) { do { Awrite(""); - Awrite( AString( "Region: " ) + pReg->Print( ®ions ) ); + Awrite( AString( "Region: " ) + pReg->Print() ); Awrite( "" ); // write pop stuff Awrite( AString("") + pReg->population + " " + ItemDefs[pReg->race].names + " basepop"); @@ -737,7 +737,7 @@ void Game::EditGameRegionMarkets( ARegion *pReg ) /* This only gets called if pReg->town exists! */ do { Awrite(""); - Awrite( AString( "Region: " ) + pReg->Print( ®ions ) ); + Awrite( AString( "Region: " ) + pReg->Print() ); Awrite( "" ); // write pop stuff Awrite( AString("") + pReg->town->pop + " " + ItemDefs[pReg->race].names + " townpop"); @@ -1047,7 +1047,7 @@ void Game::EditGameUnit(Unit *pUnit) Awrite(AString("Unit: ") + *(pUnit->name)); Awrite(AString("Faction: ") + pUnit->faction->num); Awrite(AString(" in ") + - pUnit->object->region->ShortPrint(®ions)); + pUnit->object->region->ShortPrint()); Awrite(" 1) Edit items..."); Awrite(" 2) Edit skills..."); Awrite(" 3) Move unit..."); diff --git a/events.cpp b/events.cpp index 53cef248..676d6ca6 100644 --- a/events.cpp +++ b/events.cpp @@ -416,3 +416,40 @@ std::string Events::Write(std::string worldName, std::string month, int year) { return text; } + +AnnihilationFact::AnnihilationFact() { + this->message = ""; +} + +AnnihilationFact::~AnnihilationFact() { +} + +void AnnihilationFact::GetEvents(std::list &events) { + events.push_back({ + .category = EventCategory::EVENT_ANNIHILATION, + .score = 1000, + .text = this->message + }); +} + +AnomalyFact::AnomalyFact() { + this->location = nullptr; +} + +AnomalyFact::~AnomalyFact() { +} + +void AnomalyFact::GetEvents(std::list &events) { + std::ostringstream buffer; + + buffer + << "A strange anomaly was detected in regions " + << location->Print() + << "."; + + events.push_back({ + .category = EventCategory::EVENT_ANOMALY, + .score = 100, + .text = buffer.str() + }); +} diff --git a/events.h b/events.h index b1b715bb..51c3a915 100644 --- a/events.h +++ b/events.h @@ -47,6 +47,8 @@ enum EventCategory { EVENT_MONSTER_HUNT, EVENT_MONSTER_AGGRESSION, EVENT_ASSASSINATION, + EVENT_ANNIHILATION, + EVENT_ANOMALY, }; struct Event { @@ -174,4 +176,24 @@ class AssassinationFact : public FactBase { int outcome; // BATTLE_LOST, BATTLE_WON, BATTLE_DRAW }; +class AnnihilationFact : public FactBase { + public: + AnnihilationFact(); + ~AnnihilationFact(); + + void GetEvents(std::list &events); + + string message; +}; + +class AnomalyFact : public FactBase { + public: + AnomalyFact(); + ~AnomalyFact(); + + void GetEvents(std::list &events); + + ARegion *location; +}; + #endif diff --git a/gamedata.cpp b/gamedata.cpp index 3256795a..01c6c2fc 100644 --- a/gamedata.cpp +++ b/gamedata.cpp @@ -3650,7 +3650,7 @@ static ObjectType ot[] = // defencearray (ATTACK_COMBAT, ATTACK_ENERGY, ATTACK_SPIRIT, ATTACK_WEATHER, ATTACK_RIDING and ATTACK_RANGED) // {"None", - ObjectType::NEVERDECAY, + ObjectType::NEVERDECAY | ObjectType::NOANNIHILATE, 0,0,0,0, -1,0,NULL,0, -1, -1, 0, -1, 0, @@ -4968,7 +4968,7 @@ static TerrainType td[] = { 1,I_PIRATES,-1,-1, 5,{O_ISLE,-1,O_OCAVE,-1,-1,-1}}, - // Terrain for NO7 (barren) + // Terrain for NO7 (barren) {"barren", "barrens", "barren", '*', R_BARREN, TerrainType::BARREN | TerrainType::RIDINGMOUNTS | TerrainType::FLYINGMOUNTS | TerrainType::ANNIHILATED, 0,0,0,1, @@ -5391,7 +5391,7 @@ EffectType *EffectDefs = efd; int NUMEFFECTS = sizeof(efd) / sizeof(efd[0]); // Range definitions -// flags, rangeclass, rangemult, crosslevelpenalty +// key, flags, rangeclass, rangemult, crosslevelpenalty static RangeType rtd[] = { {"rng_teleport", 0, RangeType::RNG_LEVEL2, 2, 4}, {"rng_portal", 0, RangeType::RNG_LEVEL2, 2, 4}, diff --git a/havilah/extra.cpp b/havilah/extra.cpp index 6dc4976b..d52cddfa 100644 --- a/havilah/extra.cpp +++ b/havilah/extra.cpp @@ -564,7 +564,7 @@ Faction *Game::CheckVictory() if (r->Population() > 0 && !r->visited) { if (!count--) { message = "The people of the "; - message += r->ShortPrint(®ions); + message += r->ShortPrint(); switch (getrandom(4)) { case 0: message += " have not been visited by exiles."; diff --git a/monthorders.cpp b/monthorders.cpp index 651dab20..dffe76a7 100644 --- a/monthorders.cpp +++ b/monthorders.cpp @@ -296,7 +296,7 @@ Location *Game::Do1SailOrder(ARegion *reg, Object *fleet, Unit *cap) stop = 1; } else if (fleet->SailThroughCheck(x->dir) < 1) { cap->error("SAIL: Could not sail " + DirectionStrs[x->dir] + " from " + - reg->ShortPrint(®ions).const_str() + ". Cannot sail through land."); + reg->ShortPrint().const_str() + ". Cannot sail through land."); stop = 1; } @@ -319,7 +319,7 @@ Location *Game::Do1SailOrder(ARegion *reg, Object *fleet, Unit *cap) } if (!has_key) { cap->error("SAIL: Can't sail " + DirectionStrs[x->dir] + " from " + - reg->ShortPrint(®ions).const_str() + " due to mystical barrier."); + reg->ShortPrint().const_str() + " due to mystical barrier."); stop = 1; } } @@ -357,9 +357,9 @@ Location *Game::Do1SailOrder(ARegion *reg, Object *fleet, Unit *cap) Faction * f = ((FactionPtr *) elem)->ptr; string temp = fleet->name->const_str(); temp += (x->dir == MOVE_PAUSE ? " performs maneuvers in " : " sails from ") + - string(reg->ShortPrint(®ions).const_str()); + string(reg->ShortPrint().const_str()); if(x->dir != MOVE_PAUSE) { - temp += " to " + string(newreg->ShortPrint(®ions).const_str()); + temp += " to " + string(newreg->ShortPrint().const_str()); } f->event(temp, "sail"); } @@ -391,7 +391,7 @@ Location *Game::Do1SailOrder(ARegion *reg, Object *fleet, Unit *cap) reg = newreg; if (newreg->ForbiddenShip(fleet)) { string temp = string(fleet->name->const_str()) + " is stopped by guards in " + - newreg->ShortPrint(®ions).const_str() + "."; + newreg->ShortPrint().const_str() + "."; cap->faction->event(temp, "sail"); stop = 1; } @@ -720,12 +720,12 @@ void Game::RunBuildShipOrder(ARegion * r,Object * obj,Unit * u) if (unfinished == 0) { u->event("Finishes building a " + ItemDefs[ship].name + " in " + - r->ShortPrint(®ions).const_str() + ".", "build"); + r->ShortPrint().const_str() + ".", "build"); CreateShip(r, u, ship); } else { percent = 100 * output / ItemDefs[ship].pMonths; u->event("Performs construction work on a " + ItemDefs[ship].name + " (" + to_string(percent) + - "%) in " + r->ShortPrint(®ions).const_str() + ".", "build", r); + "%) in " + r->ShortPrint().const_str() + ".", "build", r); } delete u->monthorders; @@ -869,7 +869,7 @@ void Game::RunBuildHelpers(ARegion *r) int percent = 100 * output / ItemDefs[ship].pMonths; u->event("Helps " + string(target->name->const_str()) + " with construction of a " + ItemDefs[ship].name + " (" + to_string(percent) + "%) in " + - r->ShortPrint(®ions).const_str() + ".", "build", r); + r->ShortPrint().const_str() + ".", "build", r); } // no need to move unit if item-type ships // are being built. (leave this commented out) @@ -1193,7 +1193,7 @@ void Game::RunUnitProduce(ARegion *r, Unit *u) } u->items.SetNum(o->item,u->items.GetNum(o->item) + output); - u->event("Produces " + ItemString(o->item,output) + " in " + r->ShortPrint(®ions).const_str() + ".", + u->event("Produces " + ItemString(o->item,output) + " in " + r->ShortPrint().const_str() + ".", "produce", r); u->Practice(o->skill); o->target -= output; @@ -1360,13 +1360,13 @@ void Game::RunAProduction(ARegion * r, Production * p) // if (po->skill == -1) { u->event("Earns " + to_string(ubucks) + " silver working in " + - r->ShortPrint(®ions).const_str() + ".", "work", r); + r->ShortPrint().const_str() + ".", "work", r); } else { // // ENTERTAIN // u->event("Earns " + to_string(ubucks) + " silver entertaining in " + - r->ShortPrint(®ions).const_str() + ".", "entertain", r); + r->ShortPrint().const_str() + ".", "entertain", r); // If they don't have PHEN, then this will fail safely u->Practice(S_PHANTASMAL_ENTERTAINMENT); u->Practice(S_ENTERTAINMENT); @@ -1376,7 +1376,7 @@ void Game::RunAProduction(ARegion * r, Production * p) { /* Everything else */ u->event("Produces " + ItemString(po->item,ubucks) + " in " + - r->ShortPrint(®ions).const_str() + ".", "produce", r); + r->ShortPrint().const_str() + ".", "produce", r); u->Practice(po->skill); } delete u->monthorders; @@ -1863,7 +1863,7 @@ Location *Game::DoAMoveOrder(Unit *unit, ARegion *region, Object *obj) return 0; if (x->dir == MOVE_PAUSE) { - unit->event("Pauses to admire the scenery in " + string(region->ShortPrint(®ions).const_str()) + + unit->event("Pauses to admire the scenery in " + string(region->ShortPrint().const_str()) + ".", "movement"); unit->movepoints -= cost * Globals->MAX_SPEED; unit->moved += cost; @@ -1875,7 +1875,7 @@ Location *Game::DoAMoveOrder(Unit *unit, ARegion *region, Object *obj) if ((TerrainDefs[newreg->type].similar_type == R_OCEAN) && (!unit->CanSwim() || unit->GetFlag(FLAG_NOCROSS_WATER))) { - unit->event("Discovers that " + string(newreg->ShortPrint(®ions).const_str()) + " is " + + unit->event("Discovers that " + string(newreg->ShortPrint().const_str()) + " is " + TerrainDefs[newreg->type].name + ".", "movement"); goto done_moving; } @@ -1899,7 +1899,7 @@ Location *Game::DoAMoveOrder(Unit *unit, ARegion *region, Object *obj) forbid = newreg->Forbidden(unit); if (forbid && !startmove && unit->guard != GUARD_ADVANCE) { int obs = unit->GetAttribute("observation"); - unit->event("Is forbidden entry to " + string(newreg->ShortPrint(®ions).const_str()) + " by " + + unit->event("Is forbidden entry to " + string(newreg->ShortPrint().const_str()) + " by " + forbid->GetName(obs).const_str() + ".", "movement"); obs = forbid->GetAttribute("observation"); forbid->event("Forbids entry to " + string(unit->GetName(obs).const_str()) + ".", "guarding"); @@ -1937,8 +1937,8 @@ Location *Game::DoAMoveOrder(Unit *unit, ARegion *region, Object *obj) temp = "Swims "; break; } - unit->event(temp + "from " + string(region->ShortPrint(®ions).const_str()) + " to " + - newreg->ShortPrint(®ions).const_str() + ".", "movement"); + unit->event(temp + "from " + string(region->ShortPrint().const_str()) + " to " + + newreg->ShortPrint().const_str() + ".", "movement"); if (forbid) { unit->advancefrom = region; diff --git a/neworigins/extra.cpp b/neworigins/extra.cpp index 33bfba52..cd9aa17b 100644 --- a/neworigins/extra.cpp +++ b/neworigins/extra.cpp @@ -621,7 +621,7 @@ Faction *Game::CheckVictory() if (r->Population() > 0 && !r->visited) { if (!count--) { message = "The people of the "; - message += r->ShortPrint(®ions); + message += r->ShortPrint(); switch (getrandom(4)) { case 0: message += " have not been visited by exiles."; diff --git a/neworigins/map.cpp b/neworigins/map.cpp index 5b493c97..b8a2e4b5 100644 --- a/neworigins/map.cpp +++ b/neworigins/map.cpp @@ -2882,6 +2882,8 @@ void ARegionList::FinalSetup(ARegionArray *pArr) ocean_name += " Ocean"; reg->SetName(ocean_name.Str()); } + } else if (TerrainDefs[reg->type].similar_type == R_BARREN) { + reg->SetName("The Barrens"); } else { if (reg->wages == -1) reg->SetName("The Void"); diff --git a/runorders.cpp b/runorders.cpp index 94edc54c..0a441516 100644 --- a/runorders.cpp +++ b/runorders.cpp @@ -85,7 +85,7 @@ void Game::RunOrders() if (obj.flags & ObjectType::DISABLED) continue; if (!(obj.flags & ObjectType::SACRIFICE)) continue; Awrite("Running SACRIFICE Orders..."); - //RunSacrificeOrders(); + RunSacrificeOrders(); break; } /* @@ -116,7 +116,7 @@ void Game::RunOrders() if (!(SkillDefs[S_ANNIHILATION].flags & SkillType::DISABLED)) { Awrite("Running Annihilation Orders..."); - //RunAnnihilateOrders(); + RunAnnihilateOrders(); } Awrite("Assessing Maintenance costs..."); @@ -770,7 +770,7 @@ void Game::RunTaxRegion(ARegion *reg) desired -= t; u->SetMoney(u->GetMoney() + amt); u->event("Collects $" + to_string(amt) + " in taxes in " + - reg->ShortPrint(®ions).const_str() + ".", "tax", reg); + reg->ShortPrint().const_str() + ".", "tax", reg); u->taxing = TAX_NONE; } } @@ -857,7 +857,7 @@ void Game::RunPillageRegion(ARegion *reg) pillagers -= num; u->SetMoney(u->GetMoney() + temp); u->event("Pillages $" + to_string(temp) + " from " + - reg->ShortPrint(®ions).const_str() + ".", "tax", reg); + reg->ShortPrint().const_str() + ".", "tax", reg); forlist(facs) { Faction *fp = ((FactionPtr *) elem)->ptr; if (fp != u->faction) { @@ -3369,11 +3369,18 @@ void Game::RunSacrificeOrders() { // Ok this sacrifice will be valid succeeded = true; - // Okay we have a valid sacrifice. Do it. + int current = u->items.GetNum(o->item); + if (current < o->amount) { + u->error("SACRIFICE: You can only sacrifice up to " + ItemString(o->item, current) + "."); + o->amount = current; + } + int required = -sacrifice_object->incomplete; int used = min(required, o->amount); + + // Okay we have a valid sacrifice. Do it. sacrifice_object->incomplete += used; - u->items.SetNum(o->item, u->items.GetNum(o->item) - used); + u->items.SetNum(o->item, current - used); u->event("Sacrifices " + ItemString(o->item, used) + ".", "sacrifice"); // sacrifice objects store the remaining needed sacrifices as negative numbers in incomplete if (sacrifice_object->incomplete >= 0) { @@ -3384,13 +3391,13 @@ void Game::RunSacrificeOrders() { Object *ob = (Object *)elem; forlist((&ob->units)) { Unit *recip = (Unit *)elem; - if (u->faction == recip->faction && u->GetMen() != 0) { + if (recip->faction == u->faction && recip->GetMen() != 0) { done = true; recip->items.SetNum(reward_item, recip->items.GetNum(reward_item) + reward_amt); recip->event("Gains " + ItemString(reward_item, reward_amt) + " from sacrifice.", "sacrifice"); + recip->faction->DiscoverItem(reward_item, 0, 1); - // close out the quest? break; } } @@ -3403,7 +3410,7 @@ void Game::RunSacrificeOrders() { sacrifice_object->type = reward_obj; string name = string(ObjectDefs[reward_obj].name) + " [" + to_string(sacrifice_object->num) + "]"; sacrifice_object->name = new AString(name); - // Notify all factions in region and show the object + u->faction->objectshows.push_back({.obj = reward_obj}); } if (destroy) { // mark the object for destruction @@ -3421,14 +3428,13 @@ void Game::RunSacrificeOrders() { // destroy any pending objects in this region. for (auto obj: destroyed_objects) { + // This shouldn't happen, as the sacrifice objects are not enterable, but.. just in case. forlist((&obj->units)) { Unit *u = (Unit *)elem; - // ideally these objects should be empty, but, just in case. u->MoveUnit(r->GetDummy()); u->event("Moved out of a sacrificed structure.", "sacrifice"); } r->objects.Remove(obj); - // Notify all factions in region that the object is destroyed } } } @@ -3437,9 +3443,112 @@ void Game::Do1Annihilate(ARegion *reg) { // converts the type of the region to a barren type (either barrens or barren ocean). When a region is // annihilated all units, and any city/markets/production in the region are destroyed. Shafts and anomalies are // unaffected. + if (TerrainDefs[reg->type].flags & TerrainType::ANNIHILATED) return; // just a double check + + // put the annihilation in the news and tell every faction. + string message = string(reg->Print().const_str()) + " has been utterly annihilated."; + AnnihilationFact *fact = new AnnihilationFact(); + fact->message = message; + events->AddFact(fact); + + // tell all factions too + forlist((&factions)) { + Faction *f = (Faction *) elem; + f->event(message, "annihilate"); + } + + if (TerrainDefs[reg->type].similar_type == R_OCEAN) { + reg->type = R_BARRENOCEAN; + } else { + reg->type = R_BARREN; + } + // destroy all units in the region + std::vector destroyed_objects; + forlist_reuse(®->objects) { + Object *obj = (Object *)elem; + forlist(&obj->units) { + Unit *u = (Unit *)elem; + u->items.DeleteAll(); // throw away all the items + u->event("Is annihilated.", "annihilate"); + reg->Kill(u); + } + // add the object to the list to be destroyed if it is able to be annihlated + if (ObjectDefs[obj->type].flags & ObjectType::NOANNIHILATE) continue; + destroyed_objects.push_back(obj); + } + + // Okay, now we need to destroy all the destroyed objects + for (auto obj: destroyed_objects) { + reg->objects.Remove(obj); + } + + // Okay, now we clear out towns, markets, production, etc. + for (auto& p : reg->products) delete p; // Free the allocated object + for (auto& m : reg->markets) delete m; // Free the allocated object + reg->markets.clear(); + reg->products.clear(); + reg->development = 0; + reg->wages = 0; + reg->race = -1; + reg->population = 0; + reg->basepopulation = 0; + reg->maxdevelopment = 0; + reg->maxwages = 0; + reg->wealth = 0; + delete reg->town; + reg->town = nullptr; } void Game::RunAnnihilateOrders() { // Check all units for annihilate orders. A unit my only annihilate if they have access to the annihilate skill. - // Annihilate will destroy the target hex and all surrounding hexes. Barren regions cannot be annihilated again. + // Annihilate will destroy the target hex and all surrounding hexes. Already annihilated regions cannot be + // annihilated again. + + forlist((®ions)) { + ARegion *r = (ARegion *) elem; + forlist((&r->objects)) { + Object *obj = (Object *) elem; + forlist((&obj->units)) { + Unit *u = (Unit *) elem; + AnnihilateOrder *o = u->annihilateorders; + if (o == nullptr) continue; + + // Check if the unit has access to the annihilate skill + if (u->GetSkill(S_ANNIHILATION) <= 0) { + u->error("ANNIHILATE: Unit does not have access to the annihilate skill."); + continue; + } + + // Ok we have a unit doing a valid annihilate order. + ARegion *target = regions.GetRegion(o->xloc, o->yloc, o->zloc); + if (target == nullptr) { + u->error("ANNIHILATE: Target region does not exist."); + continue; + } + // Check if the target is in range + RangeType *rt = FindRange("rng_annihilate"); + // If the range for annihilation is changed, this code will need to be updated. This really should + // be made better, but not today. (right now this has a range of 1000 and a cross level penalty of 0) + int dist = regions.GetPlanarDistance(r, target, rt->crossLevelPenalty, rt->rangeMult); + if (dist > rt->rangeMult) { + u->error("ANNIHILATE: Target region is out of range."); + continue; + } + + // Make sure the target isn't already annihilated + if (TerrainDefs[target->type].flags & TerrainType::ANNIHILATED) { + u->error("ANNIHILATE: Target region is already annihilated."); + continue; + } + + // annihilate our neigbors + for (auto n = 0; n < NDIRS; n++) { + ARegion *neigh = r->neighbors[n]; + if (neigh == nullptr) continue; + Do1Annihilate(neigh); + } + Do1Annihilate(r); + } + } + } } diff --git a/spells.cpp b/spells.cpp index 6e96e028..ea4d6182 100644 --- a/spells.cpp +++ b/spells.cpp @@ -1012,7 +1012,7 @@ int Game::RunConstructGate(ARegion *r,Unit *u, int spell) return 0; } - u->event("Constructs a Gate in " + string(r->ShortPrint( ®ions).const_str()) + ".", "spell"); + u->event("Constructs a Gate in " + string(r->ShortPrint().const_str()) + ".", "spell"); regions.numberofgates++; if (Globals->DISPERSE_GATE_NUMBERS) { log10 = 0; @@ -1318,7 +1318,7 @@ int Game::RunBirdLore(ARegion *r,Unit *u) f->faction = u->faction; f->level = u->GetSkill(S_BIRD_LORE); tar->farsees.Add(f); - u->event("Sends birds to spy on " + string(tar->Print(®ions).const_str()) + ".", "spell"); + u->event("Sends birds to spy on " + string(tar->Print().const_str()) + ".", "spell"); return 1; } @@ -1559,7 +1559,7 @@ int Game::RunClearSkies(ARegion *r, Unit *u) tar = regions.GetRegion(order->xloc, order->yloc, order->zloc); val = GetRegionInRange(r, tar, u, S_CLEAR_SKIES); if (!val) return 0; - temp += " on " + string(tar->ShortPrint(®ions).const_str()); + temp += " on " + string(tar->ShortPrint().const_str()); } temp += "."; int level = u->GetSkill(S_CLEAR_SKIES); @@ -1584,7 +1584,7 @@ int Game::RunWeatherLore(ARegion *r, Unit *u) if (level >= 5) months = 12; else if (level >= 3) months = 6; - string temp = "Casts Weather Lore on " + string(tar->ShortPrint(®ions).const_str()) + ". It will be "; + string temp = "Casts Weather Lore on " + string(tar->ShortPrint().const_str()) + ". It will be "; int weather, futuremonth; for (i = 0; i <= months; i++) { futuremonth = (month + i)%12; @@ -1618,7 +1618,7 @@ int Game::RunFarsight(ARegion *r,Unit *u) f->unit = u; f->observation = u->GetAttribute("observation"); tar->farsees.Add(f); - u->event("Casts Farsight on " + string(tar->ShortPrint(®ions).const_str()) + ".", "spell"); + u->event("Casts Farsight on " + string(tar->ShortPrint().const_str()) + ".", "spell"); return 1; } @@ -1635,17 +1635,17 @@ int Game::RunDetectGates(ARegion *r,Object *o,Unit *u) int found = 0; if ((r->gate) && (!r->gateopen)) { u->event("Identified local gate number " + to_string(r->gate) + " in " + - string(r->ShortPrint(®ions).const_str()) + ".", "spell"); + string(r->ShortPrint().const_str()) + ".", "spell"); } for (int i=0; ineighbors[i]; if (tar) { if (tar->gate) { if (Globals->DETECT_GATE_NUMBERS) { - u->event(string(tar->Print(®ions).const_str()) + " contains Gate " + + u->event(string(tar->Print().const_str()) + " contains Gate " + to_string(tar->gate) + ".", "spell"); } else { - u->event(string(tar->Print(®ions).const_str()) + " contains a Gate.", "spell"); + u->event(string(tar->Print().const_str()) + " contains a Gate.", "spell"); } found = 1; } @@ -1693,11 +1693,11 @@ int Game::RunTeleport(ARegion *r,Object *o,Unit *u) // Presume they had to open the portal to see if target is ocean if (TerrainDefs[tar->type].similar_type == R_OCEAN) { - u->error(string("CAST: ") + tar->Print(®ions).const_str() + " is an ocean."); + u->error(string("CAST: ") + tar->Print().const_str() + " is an ocean."); return 1; } u->DiscardUnfinishedShips(); - u->event("Teleports to " + string(tar->Print(®ions).const_str()) + ".", "spell"); + u->event("Teleports to " + string(tar->Print().const_str()) + ".", "spell"); u->MoveUnit(tar->GetDummy()); return 1; } @@ -1848,7 +1848,7 @@ int Game::RunGateJump(ARegion *r,Object *o,Unit *u) unitlist += (comma ? ", " : "") + to_string(loc->unit->num); comma = 1; loc->unit->DiscardUnfinishedShips(); - loc->unit->event("Is teleported through a Gate to " + string(tar->Print(®ions).const_str()) + + loc->unit->event("Is teleported through a Gate to " + string(tar->Print().const_str()) + " by " + string(u->name->const_str()) + ".", "spell"); loc->unit->MoveUnit( tar->GetDummy() ); if (loc->unit != u) loc->unit->ClearCastOrders(); @@ -1860,7 +1860,7 @@ int Game::RunGateJump(ARegion *r,Object *o,Unit *u) } } u->DiscardUnfinishedShips(); - u->event("Jumps through a Gate to " + string(tar->Print(®ions).const_str()) + ".", "spell"); + u->event("Jumps through a Gate to " + string(tar->Print().const_str()) + ".", "spell"); if (comma) { u->event(unitlist + " follow through the Gate.", "spell"); } @@ -1947,7 +1947,7 @@ int Game::RunPortalLore(ARegion *r,Object *o,Unit *u) u->error("CAST: Unit is not allied."); } else { loc->unit->DiscardUnfinishedShips(); - loc->unit->event("Is teleported to " + string(tar->region->Print(®ions).const_str()) + + loc->unit->event("Is teleported to " + string(tar->region->Print().const_str()) + " by " + string(u->name->const_str()) + ".", "spell"); loc->unit->MoveUnit( tar->obj ); if (loc->unit != u) loc->unit->ClearCastOrders(); @@ -2090,7 +2090,7 @@ int Game::RunBlasphemousRitual(ARegion *r, Unit *mage) // Write article with a details message = "Vile ritual has been performed at "; - message += r->ShortPrint(®ions); + message += r->ShortPrint(); message += "!"; WriteTimesArticle(message); diff --git a/unit.cpp b/unit.cpp index 4acca086..5cf82f6a 100644 --- a/unit.cpp +++ b/unit.cpp @@ -87,6 +87,8 @@ Unit::Unit() stealorders = NULL; monthorders = NULL; castorders = NULL; + sacrificeorders = nullptr; + annihilateorders = nullptr; teleportorders = NULL; joinorders = NULL; inTurnBlock = 0; @@ -133,6 +135,8 @@ Unit::Unit(int seq, Faction *f, int a) castorders = NULL; teleportorders = NULL; joinorders = NULL; + sacrificeorders = nullptr; + annihilateorders = nullptr; inTurnBlock = 0; presentTaxing = 0; presentMonthOrders = NULL; @@ -811,6 +815,8 @@ void Unit::ClearOrders() castorders = 0; if (teleportorders) delete teleportorders; teleportorders = 0; + if (sacrificeorders) delete sacrificeorders; + sacrificeorders = 0; } void Unit::ClearCastOrders() @@ -1348,6 +1354,17 @@ int Unit::GetAvailSkill(int sk) } } + // Check for a skill granted by a structure + if (object && object->type != O_DUMMY) { + ObjectType ob = ObjectDefs[object->type]; + if (ob.flags & ObjectType::GRANTSKILL && object->GetOwner() == this) { + if (ob.granted_skill == sk) { + int grant = ob.granted_level; + if (grant > retval) retval = grant; + } + } + } + return retval; } diff --git a/unittest/n07_victory_test.cpp b/unittest/n07_victory_test.cpp index c943228a..7cd883fb 100644 --- a/unittest/n07_victory_test.cpp +++ b/unittest/n07_victory_test.cpp @@ -14,6 +14,104 @@ ut::suite<"NO7 Victory Conditions"> no7victory_suite = [] { using namespace ut; + "Unit can sacrifice to anomaly to get an entity"_test = [] + { + UnitTestHelper helper; + helper.enable(UnitTestHelper::Type::ITEM, I_IMPRISONED_ENTITY, true); + helper.enable(UnitTestHelper::Type::OBJECT, O_ENTITY_CAGE, true); + helper.initialize_game(); + helper.setup_turn(); + + string name("Test Faction"); + Faction *faction = helper.create_faction(name); + Unit *leader = helper.get_first_unit(faction); + ARegion *region = helper.get_region(0, 0, 0); + Unit *sac1 = helper.create_unit(faction, region); + AString *tmp_name= new AString("My Leader"); + leader->SetName(tmp_name); + tmp_name = new AString("My Sacrifice 1"); + sac1->SetName(tmp_name); + sac1->items.SetNum(I_LEADERS, 10); + + helper.create_building(region, nullptr, O_ENTITY_CAGE); + + stringstream ss; + ss << "#atlantis 3\n"; + ss << "unit 3\n"; + ss << "sacrifice 10 lead\n"; + helper.parse_orders(faction->num, ss); + helper.run_sacrifice(); + + helper.setup_reports(); + // Generate just this single factions json object. + Game &game = helper.game_object(); + json json_report; + faction->build_json_report(json_report, &game, nullptr); + + // verify that we got two events and that they are the expected events. + auto count = json_report["events"].size(); + expect(count == 2_ul); + json event = json_report["events"][0]; + expect(event["message"] == "Sacrifices 10 leaders [LEAD]."); + expect(event["unit"]["number"] == 3_i); + event = json_report["events"][1]; + expect(event["message"] == "Gains imprisoned entity [IENT] from sacrifice."); + expect(event["unit"]["number"] == 2_i); + + // verify that there is only object in the region now. + auto objects = json_report["regions"][0]["structures"]; + expect(objects.size() == 1_ul); + expect(objects[0]["type"] == "Shaft"); + }; + + "Unit can fulfill part of a sacrifice to anomaly and it will remain"_test = [] + { + UnitTestHelper helper; + helper.enable(UnitTestHelper::Type::ITEM, I_IMPRISONED_ENTITY, true); + helper.enable(UnitTestHelper::Type::OBJECT, O_ENTITY_CAGE, true); + helper.initialize_game(); + helper.setup_turn(); + + string name("Test Faction"); + Faction *faction = helper.create_faction(name); + Unit *leader = helper.get_first_unit(faction); + ARegion *region = helper.get_region(0, 0, 0); + Unit *sac1 = helper.create_unit(faction, region); + AString *tmp_name= new AString("My Leader"); + leader->SetName(tmp_name); + tmp_name = new AString("My Sacrifice 1"); + sac1->SetName(tmp_name); + sac1->items.SetNum(I_LEADERS, 10); + + helper.create_building(region, nullptr, O_ENTITY_CAGE); + + stringstream ss; + ss << "#atlantis 3\n"; + ss << "unit 3\n"; + ss << "sacrifice 5 lead\n"; + helper.parse_orders(faction->num, ss); + helper.run_sacrifice(); + + helper.setup_reports(); + // Generate just this single factions json object. + Game &game = helper.game_object(); + json json_report; + faction->build_json_report(json_report, &game, nullptr); + + // verify that we got two events and that they are the expected events. + auto count = json_report["events"].size(); + expect(count == 1_ul); + json event = json_report["events"][0]; + expect(event["message"] == "Sacrifices 5 leaders [LEAD]."); + expect(event["unit"]["number"] == 3_i); + + // verify that there is only object in the region now. + auto objects = json_report["regions"][0]["structures"]; + expect(objects.size() == 2_ul); + expect(objects[0]["type"] == "Shaft"); + expect(objects[1]["type"] == "Mystical Anomaly"); + }; + "Unit without entity cannot enter hex containing ritual"_test = [] { UnitTestHelper helper; diff --git a/unittest/testhelper.cpp b/unittest/testhelper.cpp index 96afb11d..06f24895 100644 --- a/unittest/testhelper.cpp +++ b/unittest/testhelper.cpp @@ -71,8 +71,14 @@ void UnitTestHelper::create_building(ARegion *region, Unit *owner, int building_ Object * obj = new Object(region); obj->type = building_type; obj->num = region->buildingseq++; - obj->SetName(new AString("Building")); + string name = "Building [" + to_string(obj->num) + "]"; + obj->name = new AString(name); region->objects.Add(obj); + ObjectType ob = ObjectDefs[building_type]; + if (ob.flags & ObjectType::SACRIFICE) { + // set up the items sacrifice needed + obj->incomplete = -(ob.sacrifice_amount); + } if (owner) owner->MoveUnit(obj); } @@ -101,6 +107,10 @@ void UnitTestHelper::move_units() { game.RunMovementOrders(); } +void UnitTestHelper::run_sacrifice() { + game.RunSacrificeOrders(); +} + void UnitTestHelper::transport_phase(TransportOrder::TransportPhase phase) { game.RunTransportPhase(phase); } diff --git a/unittest/testhelper.hpp b/unittest/testhelper.hpp index 16b5ba24..b315c262 100644 --- a/unittest/testhelper.hpp +++ b/unittest/testhelper.hpp @@ -74,6 +74,8 @@ class UnitTestHelper { int connected_distance(ARegion *reg1, ARegion *reg2, int penalty, int max); // Run the maintenance phase void maintain_units(); + // Run sacrifice orders + void run_sacrifice(); // dummy int get_seed() { return getrandom(10000); };