/* base/patrol.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include "core/gameserver.h" #include "base/patrol.h" #include "base/game.h" namespace game { Patrol::Patrol() : core::Entity() { // this is a server-side only entity set_serverside(true); unset_flag(core::Entity::ShowOnMap); set_flag(core::Entity::NonSolid); entity_moduletypeid = patrol_enttype; set_label("patrol"); set_radius(1.0f); patrol_leader = 0; patrol_faction = 0; patrol_profile = ProfilePatrol; patrol_waypoint_current == patrol_waypoints.end(); patrol_launch_timeout = 0; } Patrol::~Patrol() { // delete waypoints for (WayPoints::iterator it = patrol_waypoints.begin(); it != patrol_waypoints.end(); ++it) { delete (*it); (*it) = 0; } patrol_waypoints.clear(); // delete npc types for (NPCTypes::iterator it = patrol_npctypes.begin(); it != patrol_npctypes.end(); ++it) { delete(*it); (*it) = 0; } patrol_npctypes.clear(); // detach and delete remaining members for (Members::iterator it = patrol_members.begin(); it != patrol_members.end(); ++it) { NPC *member = (*it); member->set_patrol(0); member->die(); (*it) = 0; } patrol_members.clear(); } void Patrol::set_profile(const Profile profile) { patrol_profile = profile; } void Patrol::set_faction(Faction *faction) { patrol_faction = faction; if (patrol_faction) { patrol_faction->apply(this); } } void Patrol::validate() { int waypoint_counter = 1; for (WayPoints::iterator it = patrol_waypoints.begin(); it != patrol_waypoints.end(); ++it) { WayPoint *waypoint = (*it); if (waypoint->target_label().size() == 0) { con_warn << " Patrol '" << label() << "' WayPoint " << waypoint_counter << " has no target" << "'\n"; break; } std::string entitylabel; std::string zonelabel; core::Zone *targetzone = 0; size_t pos = waypoint->target_label().find(':'); if ((pos == std::string::npos) || (pos < 1) || (pos >= (waypoint->target_label().size() - 1))) { targetzone = zone(); zonelabel.assign(zone()->label()); entitylabel.assign(waypoint->target_label()); } else { zonelabel.assign(waypoint->target_label().substr(0, pos)); entitylabel.assign(waypoint->target_label().substr(pos + 1, waypoint->target_label().size() - pos)); targetzone = core::Zone::find(zonelabel); if (!targetzone) { con_warn << " Patrol '" << label() << "' waypoint " << waypoint_counter << " has invalid target zone '" << zonelabel << "'\n"; break; } } core::Entity *targetentity = targetzone->find_entity(entitylabel); if (!targetentity) { con_warn << " Patrol '" << label() << "' waypoint " << waypoint_counter << " has unknown target '" << entitylabel << "' in zone '" << zonelabel << "'\n"; break; } if (waypoint->dock() && !targetentity->has_flag(core::Entity::Dockable)) { con_warn << " Patrol '" << label() << "' waypoint " << waypoint_counter << " set to dock at non-dockable target '" << entitylabel << "' in zone '" << zonelabel << "'\n"; break; } waypoint->set_target(targetentity); } // remove invalid waypoints for (WayPoints::iterator it = patrol_waypoints.begin(); it != patrol_waypoints.end(); ) { WayPoint *waypoint = (*it); if (!waypoint->target()) { delete waypoint; (*it) = 0; patrol_waypoints.erase(it++); } else { ++it; } } if (patrol_waypoints.size() == 0) { con_warn << " Patrol '" << label() << "' without waypoints" << "\n"; die(); } else if (patrol_npctypes.size() == 0) { con_warn << " Patrol '" << label() << "' without NPC types" << "\n"; die(); } else if (!(*patrol_waypoints.begin())->target()->has_flag(core::Entity::Dockable)) { con_warn << " Patrol '" << label() << "' spawn set to non-dockable target" << "\n"; die(); } else { // spawn waypoint is dockable (*patrol_waypoints.begin())->set_dock(true); // spawn waypoint is current patrol_waypoint_current = patrol_waypoints.begin(); con_debug << " " << label() << " patrol " << patrol_waypoints.size() << " " << aux::plural("waypoint", patrol_waypoints.size()) << " " << patrol_npctypes.size() << " npc " << aux::plural("type", patrol_npctypes.size()) << std::endl; } } void Patrol::print() const { core::Entity::print(); con_print << " ^Nprofile ^B"; switch (profile()) { case(ProfileFreelancer): con_print << "freelancer"; break; case(ProfileConvoy): con_print << "convoy"; break; case(ProfilePatrol): con_print << "patrol"; break; case(ProfileGuard): con_print << "guard"; break; case(ProfileWingman): con_print << "wingman"; break; } con_print << std::endl; con_print << " ^Nwaypoints ^B" << patrol_waypoints.size() << std::endl; con_print << " ^Nnpc types ^B" << patrol_npctypes.size() << std::endl; con_print << " ^Nmembers ^B" << patrol_members.size() << std::endl; } WayPoint *Patrol::add_waypoint() { WayPoint *waypoint = new WayPoint(); patrol_waypoints.push_back(waypoint); return waypoint; } NPCType *Patrol::add_npctype() { NPCType *npctype = new NPCType(); patrol_npctypes.push_back(npctype); return npctype; } void Patrol::add_member(NPC *npc) { npc->set_patrol(this); patrol_members.push_back(npc); } void Patrol::erase_member(NPC *npc) { Members::iterator it = patrol_members.begin(); while (it != patrol_members.end()) { if ((*it) == npc) { (*it)->set_patrol(0); (*it) = 0; patrol_members.erase(it); it = patrol_members.end(); } else { ++it; } } set_leader(); } void Patrol::set_leader() { patrol_leader = ( (patrol_members.size() > 0 ) ? (*patrol_members.begin()) : 0 ); for (Members::iterator it = patrol_members.begin(); it != patrol_members.end(); ++it) { if ((*it) == patrol_leader) { (*it)->set_leader(0); } else { (*it)->set_leader(patrol_leader); } } } // return true is any of the zones of any waypoint in this patrol has players in it bool Patrol::route_alive() const { if (zone()->keepalive_run()) { return true; } for (WayPoints::const_iterator it = patrol_waypoints.begin(); it != patrol_waypoints.end(); ++it) { if ((*it)->target()->zone()->keepalive_run()) { return true; } } return false; } void Patrol::create_patrol() { if (patrol_waypoint_current == patrol_waypoints.end() || !(*patrol_waypoint_current)->dock()) { patrol_waypoint_current = patrol_waypoints.begin(); } core::Entity *spawn = waypoint()->target(); if (spawn->has_flag(core::Entity::Dockable)) { // create NPC members for every NPC type for (NPCTypes::const_iterator it = patrol_npctypes.begin(); it != patrol_npctypes.end(); ++it) { NPCType *npctype = (*it); if (!npctype->shipmodel()) { continue; } if (npctype->amount() <= 0) { continue; } const size_t nbships = 1 + math::randomi((unsigned int) npctype->amount()); for (size_t i = 0; i < nbships; i++) { // add NPC NPC *npc = new NPC(npctype->shipmodel()); npc->set_mood(NPC::MoodFormation); // set NPC name if (npctype->name().size()) { std::string str(npctype->name()); npc->set_name(str); aux::to_label(str); npc->set_label(str); } // set NPC color if (npctype->faction()) { npctype->faction()->apply(npc); } else if (faction()) { faction()->apply(npc); } // patrol ships are not dockable if (npc->has_flag(core::Entity::Dockable)) { unset_flag(core::Entity::Dockable); // delete menus for (Menus::iterator mit = npc->menus().begin(); mit != npc->menus().end(); ++mit) { delete (*mit); (*mit) = 0; } npc->menus().clear(); } // install inventory if (!npc->inventory()) { npc->add_inventory(); } // install slots if (!npc->slots()) { npc->add_slots(); slots()->load(model()); } // install weapons for (core::Slots::iterator slit = npc->slots()->begin(); slit != npc->slots()->end(); ++slit) { core::Slot *slot = (*slit); core::Item *item = 0; if (slot->type() == model::Weapon::Cannon) { if (npctype->cannon()) { item = new core::Item(npctype->cannon()); } } else if (slot->type() == model::Weapon::Turret) { if (npctype->turret()) { item = new core::Item(npctype->turret()); } } if (item) { // add item item->set_flag(core::Item::Unique); item->set_flag(core::Item::Mountable); item->set_flag(core::Item::Unrestricted); item->set_amount(1); npc->inventory()->add(item); // mount weapon slot->set_item(item); slot->set_flag(core::Slot::Active); slot->set_flag(core::Slot::Mounted); } } npc->inventory()->recalculate(); npc->calculate_weapon_range(); // dock npc at spawn npc->set_zone(spawn->zone()); npc->set_dock(spawn); // add NPC to patrol add_member(npc); } } set_leader(); patrol_launch_timeout = core::server()->timestamp() + 15000 + (unsigned long) math::randomi(15000); } } void Patrol::frame(const unsigned long elapsed) { if (patrol_waypoints.size() < 2) { return; } if (patrol_members.size() == 0) { if (route_alive()) { create_patrol(); } return; } else if (patrol_leader) { if (patrol_leader->state() == core::Entity::Docked) { if (patrol_leader->dock() == waypoint()->target()) { if (route_alive()) { // verify everyone in the patrol is docked bool group_docked = true; for (Members::iterator it = patrol_members.begin(); it != patrol_members.end(); ++it) { if ((*it)->state() != core::Entity::Docked) { group_docked = false; } } // next waypoint if (group_docked) { if (patrol_launch_timeout > 0 ) { // patrol was created, wait for it to launch if (core::server()->timestamp() > patrol_launch_timeout) { // launch everyone for (Members::iterator mit = patrol_members.begin(); mit != patrol_members.end(); ++mit) { NPC *member = (*mit); // buy cargo if requested // FIXME apply NPCType is_merchant if (waypoint()->dock() && waypoint()->cargo() && member->inventory()) { // erase all cargo from inventory for (core::Inventory::Items::iterator iit = member->inventory()->items().begin(); iit != member->inventory()->items().end(); ) { if ((*iit)->info()->type() == Cargo::infotype()) { member->inventory()->items().erase(iit++); } else { ++iit; } } member->inventory()->recalculate(); core::Item *item = new core::Item(waypoint()->cargo()); item->set_amount( (long)(member->inventory()->capacity_available() / waypoint()->cargo()->volume())); member->inventory()->add(item); member->inventory()->recalculate(); } // launch member member->launch(); } patrol_waypoint_current++; if (patrol_waypoint_current == patrol_waypoints.end()) { patrol_waypoint_current = patrol_waypoints.begin(); } patrol_leader->set_autopilot_target(waypoint()->target()); patrol_launch_timeout = 0; } } else { // reset patrol for (Members::iterator mit = patrol_members.begin(); mit != patrol_members.end(); ++mit) { NPC *member = (*mit); member->die(); } } } else { patrol_launch_timeout = 0; } } } else { patrol_leader->launch(); } } else if ((patrol_leader->state() == core::Entity::Normal) || (patrol_leader->state() == core::Entity::ImpulseInitiate) || (patrol_leader->state() == core::Entity::Impulse)) { Ship *enemy = 0; if (profile() == ProfilePatrol) { enemy = patrol_leader->target_closest_enemy(); } if (enemy) { // combat conditions if (patrol_leader->autopilot_target() != enemy) { patrol_leader->set_autopilot_target(enemy); } patrol_leader->set_autopilot_flag(Ship::AutoPilotEnabled); patrol_leader->set_autopilot_flag(Ship::AutoPilotCombat); patrol_leader->unset_autopilot_flag(Ship::AutoPilotFormation); patrol_leader->unset_autopilot_flag(Ship::AutoPilotDock); } else if (waypoint()->target()->zone() == patrol_leader->zone()) { // current waypoint is in this zone patrol_leader->set_autopilot_target(waypoint()->target()); patrol_leader->set_autopilot_flag(Ship::AutoPilotEnabled); patrol_leader->unset_autopilot_flag(Ship::AutoPilotFormation); patrol_leader->unset_autopilot_flag(Ship::AutoPilotCombat); if ( (waypoint()->dock()) && (waypoint()->target()->has_flag(core::Entity::Dockable)) ) { patrol_leader->set_autopilot_flag(Ship::AutoPilotDock); } else { patrol_leader->unset_autopilot_flag(Ship::AutoPilotDock); if ( math::distance(patrol_leader->location(), waypoint()->target()->location()) < PLANET_SAFE_DISTANCE + patrol_leader->radius() + waypoint()->target()->radius() ) { // check if other patrol memebers are near bool group_present = true; for (Members::iterator it = patrol_members.begin(); it != patrol_members.end(); ++it) { if ((*it)->zone() != patrol_leader->zone()) { group_present = false; } else if ( math::distance(patrol_leader->location(), (*it)->location()) > 4.0f * (patrol_leader->radius() + (*it)->radius()) ) { group_present = false; } } // next waypoint if (group_present) { patrol_waypoint_current++; if (patrol_waypoint_current == patrol_waypoints.end()) { patrol_waypoint_current = patrol_waypoints.begin(); } patrol_leader->set_autopilot_target(waypoint()->target()); } else { patrol_leader->set_autopilot_target(0); } } } } else { // current waypoint is in different zone patrol_leader->unset_autopilot_flag(Ship::AutoPilotCombat); // check if the leader got here through a jumpgate JumpGate *jumpgate = (waypoint()->target()->moduletype() == jumpgate_enttype ? static_cast(waypoint()->target()) : 0 ); if (jumpgate && jumpgate->target()->zone() == patrol_leader->zone()) { // check if other patrol memebers are near bool group_present = true; for (Members::iterator it = patrol_members.begin(); it != patrol_members.end(); ++it) { if ((*it)->zone() != patrol_leader->zone()) { group_present = false; } else if ( math::distance(patrol_leader->location(), (*it)->location()) > 4.0f * (patrol_leader->radius() + (*it)->radius()) ) { group_present = false; } } // next waypoint if (group_present) { patrol_waypoint_current++; if (patrol_waypoint_current == patrol_waypoints.end()) { patrol_waypoint_current = patrol_waypoints.begin(); } patrol_leader->set_autopilot_target(waypoint()->target()); } else { // cruise away from the jumpgate until patrol memebers arrive patrol_leader->set_autopilot_target(jumpgate->target()); patrol_leader->unset_autopilot_flag(Ship::AutoPilotEnabled); patrol_leader->set_target_thrust(0.25f); } } else { // we're lost: delete patrol members for (Members::iterator it = patrol_members.begin(); it != patrol_members.end(); ++it) { (*it)->die(); } } } } else { patrol_leader->set_autopilot_target(0); } } } }