/* base/npc.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/range.h" #include "base/npc.h" #include "base/game.h" #include "base/patrol.h" #include namespace game { const float NPC_REPAIR_WIMPY = 20.0f; // the health percentage at which the NPC willtry to repair itself const float NPC_REPAIR_IDLE = 70.0f; //the health percentage at which the NPC willtry to repair itself when not in combat const float NPC_REPAIR_RATE = 25.0f; // repair rate in units of armor per second // NPC Wingman factory function NPC *NPC::add_wingman(Ship *leader) { if (!leader) { return 0; } if (leader->state() != Entity::Normal) { return 0; } NPC *npc = new NPC(leader->shipmodel()); npc->set_leader(leader); //npc->set_owner(leader->owner()); npc->set_name("Wingman"); npc->set_mood(MoodFormation); npc->set_color(npc->leader()->color()); npc->set_color_second(npc->leader()->color_second()); npc->set_location(leader->location() - leader->axis().forward() * 2.0f * (leader->radius() + npc->radius())); npc->set_axis(leader->axis()); npc->set_zone(leader->zone()); npc->nudge(true); // copy weapon layout if (!npc->inventory()) { npc->add_inventory(); } // TODO ship models should be identical, otherwise the slot loeading can fail in horrible ways assert (npc->shipmodel() == leader->shipmodel()); for (size_t i = 0; i < leader->slots()->size(); ++i) { core::Slot *slot = leader->slots()->operator[](i); if (slot->item() && slot->has_flag(core::Slot::Mounted)) { core::Item *npc_item = new core::Item(slot->item()->info()); npc_item->set_amount(slot->item()->amount()); npc_item->set_flags(slot->item()->flags()); npc_item->set_flag(core::Item::Mounted); npc->inventory()->add(npc_item); core::Slot *npc_slot = npc->slots()->operator[](i); npc_slot->set_item(npc_item); npc_slot->set_flag(core::Slot::Active); npc_slot->set_flag(core::Slot::Mounted); } } npc->calculate_weapon_range(); npc->reset(); return npc; } NPC::NPC(const ShipModel *shipmodel) : Ship(0, shipmodel) { npc_mood = MoodWander; npc_destroyed_timestamp = 0; npc_repair_timestamp = 0; npc_patrol = 0; npc_leader = 0; npc_commander = 0; npc_weapon_range = 0.0f; } NPC::~NPC() { if (npc_patrol) { npc_patrol->erase_member(this); } } void NPC::calculate_weapon_range() { npc_weapon_range = 0.0f; if (!slots()) { return; } for (core::Slots::const_iterator it = slots()->begin(); it != slots()->end(); it++) { core::Slot *slot = (*it); if (!slot->has_flag(core::Slot::Mounted)) { continue; } else if (!slot->item() || (slot->item()->info()->type() != Weapon::infotype())) { continue; } else { const Weapon *weapon = static_cast(slot->item()->info()); if ((weapon->subtype() != Weapon::Cannon) && (weapon->subtype() != Weapon::Turret) ) { continue; } const float r = weapon->projectile_range(); if (r > npc_weapon_range) { npc_weapon_range = r; } } } } void NPC::set_mood(const Mood mood) { npc_mood = mood; } void NPC::set_leader(Ship *leader) { npc_leader = leader; } void NPC::set_patrol(Patrol *patrol) { npc_patrol = patrol; } void NPC::set_commander(core::Player *commander) { npc_commander = commander; } Ship *NPC::target_closest_enemy() { // scan for enemies Ship *current_enemy = 0; float current_distance = 0.0f; if (!npc_weapon_range) { return 0; } for (core::Zone::Content::iterator zit = zone()->content().begin(); zit != zone()->content().end(); ++zit) { if (((*zit)->moduletype() == ship_enttype) && ((*zit) != this)) { Ship *other_ship = static_cast((*zit)); if ((other_ship->state() != Normal) && (other_ship->state() != ImpulseInitiate) && (other_ship->state() != Impulse)) { continue; } const float d = math::distance(location(), other_ship->location()); const float r = radius() + other_ship->radius(); // too far if (d > r + core::range::fxdistance) { continue; } // further than current target if ((current_distance > 0.0f) && (d > current_distance)) { continue; } float reputation = 0.0f; if (other_ship->owner()) { // check owner reputation for the faction this NPC belongs to reputation = other_ship->owner()->reputation(faction()); } else if (other_ship->faction()) { // check other ships's faction for the faction this NPC belongs to assert(other_ship->faction()->type() == Faction::infotype()); reputation = static_cast(other_ship->faction())->reputation(faction()); } // reputation threshold to get attacked if (reputation > core::range::reputation_hostile) { continue; } current_enemy = other_ship; current_distance = d; } } // set aim if ((state() == Normal) && current_enemy && (current_distance <= radius() + npc_weapon_range)) { // activate weapons set_target_controlflag(core::EntityControlable::ControlFlagFire); // rudimentary aim currention target_aim.assign(current_enemy->location() + current_enemy->axis().forward() * (current_enemy->radius() * 0.25f) ); } else { // deactivate weapons unset_target_controlflag(core::EntityControlable::ControlFlagFire); } return current_enemy; } void NPC::frame(const unsigned long elapsed) { if (state() == core::Entity::Destroyed) { if (!npc_destroyed_timestamp) { npc_destroyed_timestamp = core::game()->time(); } else if (npc_destroyed_timestamp + 10.0f < core::game()->time()) { // stay alive for 10 more seconds while explosion particles are drawn die(); } } else { // TODO pilot magic and mood witchcraft if (leader()) { // verify leader still exists if (!core::Entity::find(leader())) { set_leader(0); // verify the commander hasn't left the game if (commander()) { set_commander(core::game()->find_player(commander())); if (commander() && commander()->control() && (commander()->control()->moduletype() == ship_enttype)) { set_leader(static_cast(commander()->control())); } } } // if the leader has dissapeared, the NPC explodes if (!leader()) { explode(); npc_destroyed_timestamp = core::game()->time(); } else if (leader()->zone() == zone()) { // leader is in this zone if (leader()->state() == Docked) { if (state() != core::Entity::Docked) { set_autopilot_target(leader()->dock()); set_autopilot_flag(Ship::AutoPilotEnabled); set_autopilot_flag(Ship::AutoPilotDock); unset_autopilot_flag(Ship::AutoPilotFormation); unset_autopilot_flag(Ship::AutoPilotCombat); } else { unset_autopilot_flag(Ship::AutoPilotEnabled); } } else if ((leader()->state() == JumpInitiate) || (leader()->state() == Jump)) { // goto wherever the leader is jumping set_autopilot_target(leader()->jumpdepart()); set_autopilot_flag(Ship::AutoPilotEnabled); set_autopilot_flag(Ship::AutoPilotDock); unset_autopilot_flag(Ship::AutoPilotFormation); unset_autopilot_flag(Ship::AutoPilotCombat); } else { if (state() == core::Entity::Docked) { if (!core::Entity::find(dock())) { // dock doesn't exist any more set_dock(0); npc_repair_timestamp = 0; nudge(true); set_state(Entity::Normal); set_dirty(); } else { // check if our dock might be noving if (dock()->moduletype() == ship_enttype) { Ship *carrier = static_cast(dock()); set_location(carrier->location()); set_axis(carrier->axis()); if (zone() != carrier->zone()) { set_zone(carrier->zone()); } if (carrier->state() == core::Entity::Destroyed) { explode(); } else if ((carrier == leader()) && (armor() < maxarmor())) { // repair const unsigned long now = core::game()->timestamp(); if (npc_repair_timestamp == 0) { npc_repair_timestamp = now; } else { while (npc_repair_timestamp + 1000 < now) { set_armor(armor() + NPC_REPAIR_RATE); npc_repair_timestamp += 1000; } } } else { npc_repair_timestamp = 0; if ( (carrier->state() == core::Entity::Normal) && ((carrier != leader()) || !leader()->has_autopilot_flag(Ship::AutoPilotRecall)) ) { launch(); } } } else { launch(); } } } else { if (leader()->has_flag(core::Entity::Dockable) && ((health() < NPC_REPAIR_WIMPY) || leader()->has_autopilot_flag(Ship::AutoPilotRecall)) ) { set_autopilot_target(leader()); set_autopilot_flag(Ship::AutoPilotEnabled); unset_autopilot_flag(Ship::AutoPilotCombat); set_autopilot_flag(Ship::AutoPilotDock); unset_autopilot_flag(Ship::AutoPilotFormation); } else { Ship *enemy = target_closest_enemy(); if (enemy && leader()->has_autopilot_flag(Ship::AutoPilotCombat)) { set_autopilot_target(enemy); set_autopilot_flag(Ship::AutoPilotEnabled); set_autopilot_flag(Ship::AutoPilotCombat); unset_autopilot_flag(Ship::AutoPilotDock); unset_autopilot_flag(Ship::AutoPilotFormation); } else { set_autopilot_target(leader()); set_autopilot_flag(Ship::AutoPilotEnabled); unset_autopilot_flag(Ship::AutoPilotCombat); if (leader()->has_flag(core::Entity::Dockable) && (health() < NPC_REPAIR_IDLE)) { set_autopilot_flag(Ship::AutoPilotDock); unset_autopilot_flag(Ship::AutoPilotFormation); } else { unset_autopilot_flag(Ship::AutoPilotDock); set_autopilot_flag(Ship::AutoPilotFormation); } } } } } } else { // leader is not in this zone if ((state() == core::Entity::Docked) && (dock()) && (dock()->moduletype() == ship_enttype)) { Ship *carrier = static_cast(dock()); set_location(carrier->location()); set_axis(carrier->axis()); if (zone() != carrier->zone()) { set_zone(carrier->zone()); } } else if (!autopilot_target()) { target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_afterburner = 0.0f; target_thrust = 0; if (state() == core::Entity::Impulse) { func_impulse(); } } else { set_autopilot_flag(Ship::AutoPilotEnabled); set_autopilot_flag(Ship::AutoPilotDock); unset_autopilot_flag(Ship::AutoPilotFormation); unset_autopilot_flag(Ship::AutoPilotCombat); } } } else if (patrol()) { // patrol leader behaviour // autopilot_target() is set by patrol if (!autopilot_target()) { target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_afterburner = 0.0f; target_thrust = 0; if (state() == core::Entity::Impulse) { func_impulse(); } } } else { die(); } } // run a ship game frame Ship::frame(elapsed); } } // namespace game