void AutoResolveCombat(CombatInfo& combat_info) { if (combat_info.objects.Empty()) return; const System* system = combat_info.objects.Object<System>(combat_info.system_id); if (!system) Logger().errorStream() << "AutoResolveCombat couldn't get system with id " << combat_info.system_id; else Logger().debugStream() << "AutoResolveCombat at " << system->Name(); if (GetOptionsDB().Get<bool>("verbose-logging")) { Logger().debugStream() << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"; Logger().debugStream() << "AutoResolveCombat objects before resolution: " << combat_info.objects.Dump(); } // reasonably unpredictable but reproducible random seeding const int base_seed = combat_info.objects.begin()->first + CurrentTurn(); // compile list of valid objects to attack or be attacked in this combat std::set<int> valid_target_object_ids; // all objects that can be attacked std::set<int> valid_attacker_object_ids; // all objects that can attack std::map<int, std::set<int> > empire_valid_attacker_object_ids; // objects that can attack that each empire owns float monster_detection = 0.0; for (ObjectMap::iterator it = combat_info.objects.begin(); it != combat_info.objects.end(); ++it) { const UniverseObject* obj = it->second; //Logger().debugStream() << "Considerting object " << obj->Name() << " owned by " << obj->Owner(); if (ObjectCanAttack(obj)) { //Logger().debugStream() << "... can attack"; valid_attacker_object_ids.insert(it->first); empire_valid_attacker_object_ids[obj->Owner()].insert(it->first); } if (ObjectCanBeAttacked(obj)) { //Logger().debugStream() << "... can be attacked"; valid_target_object_ids.insert(it->first); } if (obj->Unowned() && obj->ObjectType() == OBJ_SHIP) monster_detection = std::max(monster_detection, obj->CurrentMeterValue(METER_DETECTION)); } // map from empire to set of IDs of objects that empire's objects // could potentially target. std::map<int, std::set<int> > empire_valid_target_object_ids; // objects that each empire can attack for (std::set<int>::const_iterator target_it = valid_target_object_ids.begin(); target_it != valid_target_object_ids.end(); ++target_it) { int object_id = *target_it; const UniverseObject* obj = combat_info.objects.Object(object_id); //Logger().debugStream() << "Considering attackability of object " << obj->Name() << " owned by " << obj->Owner(); // for all empires, can they attack this object? for (std::set<int>::const_iterator empire_it = combat_info.empire_ids.begin(); empire_it != combat_info.empire_ids.end(); ++empire_it) { int attacking_empire_id = *empire_it; if (attacking_empire_id == ALL_EMPIRES) { if (ObjectAttackableByMonsters(obj, monster_detection)) { //Logger().debugStream() << "object: " << obj->Name() << " attackable by monsters"; empire_valid_target_object_ids[ALL_EMPIRES].insert(object_id); } } else { // call function to find if empires can attack objects... if (ObjectAttackableByEmpire(obj, attacking_empire_id)) { //Logger().debugStream() << "object: " << obj->Name() << " attackable by empire " << attacking_empire_id; empire_valid_target_object_ids[attacking_empire_id].insert(object_id); } } } } // Each combat "round" a randomly-selected object in the battle attacks // something, if it is able to do so. The number of rounds scales with the // number of objects, so the total actions per object is independent of // number of objects in the battle const int NUM_COMBAT_ROUNDS = 3*valid_attacker_object_ids.size(); for (int round = 1; round <= NUM_COMBAT_ROUNDS; ++round) { Seed(base_seed + round); // ensure each combat round produces different results // ensure something can attack and something can be attacked if (valid_attacker_object_ids.empty()) { Logger().debugStream() << "Nothing left can attack; combat over"; break; } if (empire_valid_target_object_ids.empty()) { Logger().debugStream() << "Nothing left can be attacked; combat over"; break; } // empires may have valid targets, but nothing to attack with. If all // empires have no attackers or no valid targers, combat is over bool someone_can_attack_something = false; for (std::map<int, std::set<int> >::const_iterator attacker_it = empire_valid_attacker_object_ids.begin(); attacker_it != empire_valid_attacker_object_ids.end(); ++attacker_it) { if (empire_valid_target_object_ids.find(attacker_it->first) != empire_valid_target_object_ids.end()) { someone_can_attack_something = true; break; } } if (!someone_can_attack_something) { Logger().debugStream() << "No empire has valid targets and something to attack with; combat over."; break; } Logger().debugStream() << "Combat at " << system->Name() << " (" << combat_info.system_id << ") Round " << round; // select attacking object in battle SmallIntDistType attacker_id_num_dist = SmallIntDist(0, valid_attacker_object_ids.size() - 1); std::set<int>::const_iterator attacker_it = valid_attacker_object_ids.begin(); std::advance(attacker_it, attacker_id_num_dist()); assert(attacker_it != valid_attacker_object_ids.end()); int attacker_id = *attacker_it; UniverseObject* attacker = combat_info.objects.Object(attacker_id); if (!attacker) { Logger().errorStream() << "AutoResolveCombat couldn't get object with id " << attacker_id; continue; } Logger().debugStream() << "Attacker: " << attacker->Name(); Ship* attack_ship = universe_object_cast<Ship*>(attacker); Planet* attack_planet = universe_object_cast<Planet*>(attacker); // loop over weapons of attacking object. each gets a shot at a // randomly selected target object std::vector<PartAttackInfo> weapons; if (attack_ship) { weapons = ShipWeaponsStrengths(attack_ship); for (std::vector<PartAttackInfo>::const_iterator part_it = weapons.begin(); part_it != weapons.end(); ++part_it) { Logger().debugStream() << "weapon: " << part_it->part_type_name << " attack: " << part_it->part_attack; } } else if (attack_planet) { // treat planet defenses as short range weapons.push_back(PartAttackInfo(PC_SHORT_RANGE, "", attack_planet->CurrentMeterValue(METER_DEFENSE))); } if (weapons.empty()) { Logger().debugStream() << "no weapons' can't attack"; continue; // no ability to attack! } for (std::vector<PartAttackInfo>::const_iterator weapon_it = weapons.begin(); weapon_it != weapons.end(); ++weapon_it) { // select object from valid targets for this object's owner TODO: with this weapon... Logger().debugStream() << "Attacking with weapon " << weapon_it->part_type_name << " with power " << weapon_it->part_attack; // get valid targets set for attacker owner. need to do this for // each weapon that is attacking, as the previous shot might have // destroyed something int attacker_owner_id = attacker->Owner(); std::map<int, std::set<int> >::iterator target_vec_it = empire_valid_target_object_ids.find(attacker_owner_id); if (target_vec_it == empire_valid_target_object_ids.end()) { Logger().debugStream() << "No targets for attacker with id: " << attacker_owner_id; break; } const std::set<int>& valid_target_ids = target_vec_it->second; if (valid_target_ids.empty()) break; // should be redundant with this entry being erased when emptied // select target object SmallIntDistType target_id_num_dist = SmallIntDist(0, valid_target_ids.size() - 1); std::set<int>::const_iterator target_it = valid_target_ids.begin(); std::advance(target_it, target_id_num_dist()); assert(target_it != valid_target_ids.end()); int target_id = *target_it; UniverseObject* target = combat_info.objects.Object(target_id); if (!target) { Logger().errorStream() << "AutoResolveCombat couldn't get target object with id " << target_id; continue; } Logger().debugStream() << "Target: " << target->Name(); // do actual attacks, and mark attackers as valid targets for attacked object's owners if (attack_ship) { if (Ship* target_ship = universe_object_cast<Ship*>(target)) { AttackShipShip(attack_ship, weapon_it->part_attack, target_ship, combat_info.damaged_object_ids); empire_valid_target_object_ids[target_ship->Owner()].insert(attacker_id); } else if (Planet* target_planet = universe_object_cast<Planet*>(target)) { AttackShipPlanet(attack_ship, weapon_it->part_attack, target_planet, combat_info.damaged_object_ids); empire_valid_target_object_ids[target_planet->Owner()].insert(attacker_id); } } else if (attack_planet) { if (Ship* target_ship = universe_object_cast<Ship*>(target)) { AttackPlanetShip(attack_planet, target_ship, combat_info.damaged_object_ids); empire_valid_target_object_ids[target_ship->Owner()].insert(attacker_id); } else if (Planet* target_planet = universe_object_cast<Planet*>(target)) { AttackPlanetPlanet(attack_planet, target_planet, combat_info.damaged_object_ids); empire_valid_target_object_ids[target_planet->Owner()].insert(attacker_id); } } // check for destruction of target object if (target->ObjectType() == OBJ_SHIP) { if (target->CurrentMeterValue(METER_STRUCTURE) <= 0.0) { Logger().debugStream() << "!! Target Ship is destroyed!"; // object id destroyed combat_info.destroyed_object_ids.insert(target_id); // all empires in battle know object was destroyed for (std::set<int>::const_iterator it = combat_info.empire_ids.begin(); it != combat_info.empire_ids.end(); ++it) { int empire_id = *it; if (empire_id != ALL_EMPIRES) combat_info.destroyed_object_knowers[empire_id].insert(target_id); } // remove destroyed ship's ID from lists of valid attackers and targets valid_attacker_object_ids.erase(target_id); valid_target_object_ids.erase(target_id); // probably not necessary as this set isn't used in this loop for (target_vec_it = empire_valid_target_object_ids.begin(); target_vec_it != empire_valid_target_object_ids.end(); ++target_vec_it) { target_vec_it->second.erase(target_id); } for (target_vec_it = empire_valid_attacker_object_ids.begin(); target_vec_it != empire_valid_attacker_object_ids.end(); ++target_vec_it) { target_vec_it->second.erase(target_id); } // TODO: only erase from owner's entry in this list } } else if (target->ObjectType() == OBJ_PLANET) { if (target->CurrentMeterValue(METER_SHIELD) <= 0.0 && target->CurrentMeterValue(METER_DEFENSE) <= 0.0 && target->CurrentMeterValue(METER_CONSTRUCTION) <= 0.0) { Logger().debugStream() << "!! Target Planet is knocked out of battle"; // remove disabled planet's ID from lists of valid attackers and targets valid_attacker_object_ids.erase(target_id); valid_target_object_ids.erase(target_id); // probably not necessary as this set isn't used in this loop for (target_vec_it = empire_valid_target_object_ids.begin(); target_vec_it != empire_valid_target_object_ids.end(); ++target_vec_it) { target_vec_it->second.erase(target_id); } for (target_vec_it = empire_valid_attacker_object_ids.begin(); target_vec_it != empire_valid_attacker_object_ids.end(); ++target_vec_it) { target_vec_it->second.erase(target_id); } // TODO: only erase from owner's entry in this list } } // check if any empire has no remaining target or attacker objects. // If so, remove that empire's entry std::map<int, std::set<int> > temp = empire_valid_target_object_ids; for (target_vec_it = empire_valid_target_object_ids.begin(); target_vec_it != empire_valid_target_object_ids.end(); ++target_vec_it) { if (target_vec_it->second.empty()) { temp.erase(target_vec_it->first); Logger().debugStream() << "No valid targets left for empire with id: " << target_vec_it->first; } } empire_valid_target_object_ids = temp; temp = empire_valid_attacker_object_ids; for (target_vec_it = empire_valid_attacker_object_ids.begin(); target_vec_it != empire_valid_attacker_object_ids.end(); ++target_vec_it) { if (target_vec_it->second.empty()) { temp.erase(target_vec_it->first); Logger().debugStream() << "No valid attacking objects left for empire with id: " << target_vec_it->first; } } empire_valid_attacker_object_ids = temp; } // end for over weapons } // end for over combat arounds // ensure every participant knows what happened. // TODO: assemble list of objects to copy for each empire. this should // include objects the empire already knows about with standard // visibility system, and also any objects the empire knows are // destroyed or for (std::map<int, ObjectMap>::iterator it = combat_info.empire_known_objects.begin(); it != combat_info.empire_known_objects.end(); ++it) { it->second.Copy(combat_info.objects); } if (GetOptionsDB().Get<bool>("verbose-logging")) Logger().debugStream() << "AutoResolveCombat objects after resolution: " << combat_info.objects.Dump(); }
int RandSmallInt(int min, int max) { return (min == max ? min : SmallIntDist(min,max)()); }