/* base/ship.cc This file is part of the Osirion project and is distributed under the terms and conditions of the GNU General Public License version 2 */ #include #include #include #include "auxiliary/functions.h" #include "math/functions.h" #include "core/gameserver.h" #include "core/entity.h" #include "core/entityprojectile.h" #include "base/game.h" #include "base/ship.h" #include "base/spacemine.h" #include "base/jumppoint.h" #include "base/racetrack.h" #include "base/cargo.h" #include "base/cargopod.h" #include "base/faction.h" using math::degrees360f; using math::degrees180f; namespace game { // powersteering factor const float POWERSTEERING = 4.0f; Ship::Ship(core::Player *owner, const ShipModel *shipmodel) : core::EntityControlable() { assert(shipmodel); entity_moduletypeid = ship_enttype; set_radius(0); ship_shipmodel = shipmodel; ship_impulsedrive_timer = 0; ship_jumpdrive_timer = 0; ship_jumpdepart = 0; ship_autopilot_target = 0; ship_autopilot_flags = AutoPilotDisabled; ship_dock = 0; ship_spawn = 0; ship_maxarmor = 100.0f; ship_armor = 0.0f; ship_maxshield = 0.0f; ship_shield = 0.0f; current_impulse = false; // apply ship type settings ship_shipmodel->apply(this); // radius fallback values if (!radius()) { if (model()) { set_radius(model()->radius()); } } if (!radius()) { set_radius(0.5f); } // mass fallback value if (!mass()) { set_mass(radius() * 100.0f); } if (owner) { // this ship is owned by a player, // player colors override template colors set_owner(owner); get_color().assign(owner->color()); get_color_second().assign(owner->color_second()); std::string str(aux::text_strip(owner->name())); aux::to_label(str); set_label(str); str.assign(aux::text_strip(owner->name())); aux::text_strip(str); set_name(str); } // add an inventory add_inventory(); inventory()->set_capacity(ship_shipmodel->maxcargo()); if (model()) { const float modelscale = radius() / model()->radius(); add_slots(); slots()->load(model(), modelscale); } // menus for docked players if (ship_shipmodel->dockable()) { using core::MenuDescription; using core::ButtonDescription; MenuDescription *menu_main = new MenuDescription(); menu_main->set_label("main"); menu_main->set_text("Launch area"); add_menu(menu_main); ButtonDescription *button = new ButtonDescription(); button->set_text("Repair"); button->set_command("repair confirm", ButtonDescription::CommandGame); button->set_alignment(ButtonDescription::Center); menu_main->add_button(button); button = new ButtonDescription(); button->set_text("Launch"); button->set_command("launch", ButtonDescription::CommandGame); button->set_alignment(ButtonDescription::Center); menu_main->add_button(button); set_flag(core::Entity::Dockable); } set_armor(maxarmor()); } Ship::~Ship() { } void Ship::print() const { core::EntityControlable::print(); con_print << " ^Narmor ^B" << armor() << "^N/^B" << maxarmor() << std::endl; } void Ship::func_impulse() { switch (entity_state) { case core::Entity::Impulse: entity_state = core::Entity::Normal; //target_thrust = 1.0f; //entity_thrust = 0.0f; set_dirty(); break; case core::Entity::ImpulseInitiate: entity_state = core::Entity::Normal; set_dirty(); break; case core::Entity::JumpInitiate: entity_state = core::Entity::Normal; ship_jumpdrive_timer = 0; entity_timer = 0; set_dirty(); break; case core::Entity::Normal: entity_state = core::Entity::ImpulseInitiate; entity_timer = impulse_timer_delay; ship_impulsedrive_timer = core::server()->time(); break; default: break; } } void Ship::initiate_jump(JumpPoint *depart) { ship_jumpdepart = 0; if (!depart) return; if (!depart->target()) return; ship_jumpdepart = depart; entity_state = core::Entity::JumpInitiate; entity_timer = jump_timer_delay; ship_jumpdrive_timer = core::server()->time(); set_dirty(); } void Ship::func_jump(std::string const &args) { if (entity_state == core::Entity::Docked) { return; } // devel mode provides instant jump access to arbitrary systems if (Game::g_devel->value() && (args.size())) { core::Zone *jumptargetzone = core::Zone::search(args); if (!jumptargetzone) { std::string helpstr; for (core::Zone::Registry::iterator it = core::Zone::registry().begin(); it != core::Zone::registry().end(); it++) { if (helpstr.size()) helpstr.append("^N|^B"); helpstr.append((*it).second->label()); } if (owner()) { owner()->send("Usage: jump [^B" + helpstr + "^N]"); } return; } if (jumptargetzone == zone()) { if (owner()) { owner()->send("Already in the " + jumptargetzone->name()); } return; } if (owner()) { owner()->send("Jumping to the " + jumptargetzone->name()); } set_zone(jumptargetzone); //ship_jumpdrive_timer = 0; //entity_timer = 0; // setting the cooldown timer even with cheats on will allow some time for the jump completed sound to play ship_jumpdrive_timer = core::server()->time(); entity_timer = jump_cooldown_delay; set_state(core::Entity::Jump); set_dirty(); return; } else { if (!jumpdrive() && !Game::g_devel->value()) { if (owner()) { owner()->send("This ship is not equiped with a hyperspace drive!"); } return; } else if (entity_state == core::Entity::Jump) { return; } else if (entity_state == core::Entity::JumpInitiate) { if (owner()) { owner()->send("Jump aborted, hyperspace drive deactivated"); } ship_jumpdrive_timer = 0; entity_timer = 0; entity_state = core::Entity::Normal; return; } initiate_jump(find_closest_jumppoint()); } } JumpPoint * Ship::find_closest_jumppoint() { // find closest jumpgate or jumppoint float d = -1; JumpPoint *jumppoint = 0; for (core::Zone::Content::iterator it = zone()->content().begin(); it != zone()->content().end(); it++) { core::Entity *entity = (*it); if ((entity->moduletype() == jumppoint_enttype) || (entity->moduletype() == jumpgate_enttype)) { JumpPoint *te = static_cast(entity); float d1 = math::distance(location(), te->location()); if ((d < 0) || (d1 < d)) { d = d1; jumppoint = te; } } } if (jumppoint && jumppoint->target()) { if (Game::g_jumppointrange->value() < d) { if (owner()) { owner()->send("Jumppoint out of range!"); } return 0; } else { if (owner()) { owner()->send("Jumping to the " + jumppoint->target()->zone()->name()); } return jumppoint; } } else { if (owner()) { owner()->send("No jumppoints found!"); } return 0; } return 0; } void Ship::eject(core::Item *item, const long eject_amount, const bool eject_message) { // verify whe have the item in our inventory if (!inventory()->find(item)) { return; } // clamp amount to what's available const long amount = math::min(eject_amount, item->amount()); if (amount < 1) { return; } // remove the requested amount item->dec_amount(amount); if (state() == Docked) { if (eject_message && owner()) { std::stringstream msgstr; msgstr << "^BDestroyed " << amount << " " << aux::plural("unit", amount) << " of " << item->info()->name(); owner()->send(msgstr.str()); owner()->sound("game/eject"); } } else { // create cargo pod CargoPod *pod = new CargoPod(); if (item->info()->modelname().size()) { pod->set_name(item->info()->name()); /* // setting item models looks silly, but can be uncommented server-side //pod->set_modelname(item->info()->modelname()); */ } // randomize location math::Axis pod_axis; pod_axis.change_direction(math::randomf(360.0f)); pod_axis.change_pitch(math::randomf(180.0f)); pod_axis.change_roll(math::randomf(360.0f)); pod->set_color(color()); pod->set_color_second(color_second()); pod->set_location(location() + pod_axis.up() * (radius() + pod->radius())); pod->set_axis(pod_axis); pod->set_zone(zone()); pod->set_info(item->info()); // add loot to inventory pod->add_inventory(); pod->inventory()->set_capacity(item->info()->volume() * amount); core::Item *loot = new core::Item(item->info()); loot->set_amount(amount); loot->set_flags(item->flags()); loot->unset_flag(core::Item::Mounted); pod->inventory()->add(loot); pod->inventory()->set_dirty(); if (eject_message && owner()) { std::stringstream msgstr; if (item->unique()) { msgstr << "^BEjected " << item->info()->name(); } else { msgstr << "^BEjected " << amount << " " << aux::plural("unit", amount) << " of " << item->info()->name(); } owner()->send(msgstr.str()); owner()->sound("game/eject"); } // reset cargopod physics pod->nudge(); pod->reset(); assert(pod->body()); const float CARGOPOD_SPEED = 0.5f; pod->set_speed(CARGOPOD_SPEED); pod->body()->setLinearVelocity(math::to_btVector3(pod_axis.forward() * CARGOPOD_SPEED)); } if (item->amount() == 0) { if (item->has_flag(core::Item::Mounted)) { // unmount core::Slot *slot = 0; for(core::Slots::iterator it = slots()->begin(); (!slot) && (it != slots()->end()); ++it) { if ((*it)->item() == item) { slot = (*it); } } if (slot) { slot->set_item(0); slot->unset_flag(core::Slot::Active); slot->unset_flag(core::Slot::Mounted); item->unset_flag(core::Item::Mounted); } } inventory()->erase(item->id()); } inventory()->set_dirty(); } void Ship::explode() { target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_afterburner = 0.0f; target_thrust = 0; entity_thrust = 0; set_armor(0); set_state(core::Entity::Destroyed); // 100 % cargo loss by default float percentage = 100.0f; if (owner()) { // make the player watch his death if (owner()->control() == this) { owner()->set_view(this); owner()->messagebox("Your ship has been destroyed.", "Respawn", "respawn"); } // cargo loss % is set by the g_cargoloss CVar percentage = Game::g_cargoloss->value(); percentage = floorf(percentage); math::clamp(percentage, 0.0f, 100.0f); } if (percentage > 0) { assert(inventory()); core::Inventory::Items::iterator it = inventory()->items().begin(); while (it != inventory()->items().end()) { if ((*it)->info()->type() == Cargo::infotype()) { long loss = (*it)->amount() * ((long) percentage) / 100l; if (loss > 0) { // eject potentially deletes the item the iterator is pointing to eject((*it++), loss, false); continue; } } ++it; } } if (!owner()) { assert(inventory()); core::Inventory::Items::iterator it = inventory()->items().begin(); while (it != inventory()->items().end()) { if ((*it)->info()->type() == Weapon::infotype()) { if (math::randomf(100.0f) < 10.0f) { // eject potentially deletes the item the iterator is pointing to float amount = (*it)->amount(); eject((*it++), amount, false); continue; } } ++it; } } }; void Ship::reset() { current_target_direction = 0.0f; current_target_pitch = 0.0f;; current_target_roll = 0.0f; current_target_strafe = 0.0f; current_target_vstrafe = 0.0f; current_target_afterburner = 0.0f; EntityControlable::reset(); if (body()) { body()->setDamping(ship_shipmodel->linear_damping(), ship_shipmodel->angular_damping()); } } void Ship::set_zone(core::Zone *zone) { core::EntityControlable::set_zone(zone); if (owner() && (owner()->control() == (EntityControlable*) this)) owner()->set_zone(zone); } void Ship::set_autopilot_target(core::Entity *target) { if (ship_autopilot_target != target) { ship_autopilot_target = target; } } void Ship::set_dock(core::Entity *dock) { if (!dock) { ship_dock = 0; return; } ship_autopilot_flags = AutoPilotDisabled; ship_autopilot_target = 0; get_location().assign(dock->location()); get_axis().assign(dock->axis()); ship_dock = dock; if (dock->zone() != zone()) { set_zone(dock->zone()); } set_state(core::Entity::Docked); reset(); // if the dock is not owned by a player. set it as spawn const core::Player *owner = (dock->type() == core::Entity::Controlable ? static_cast(dock)->owner() : 0 ); if (!owner) { set_spawn(dock); } } void Ship::launch() { if (!ship_dock) return; if (ship_dock->type() == core::Entity::Globe) { get_axis().assign(ship_dock->axis()); get_location().assign(ship_dock->location() + (ship_dock->axis().forward() * (PLANET_SAFE_DISTANCE + this->radius() + ship_dock->radius()))); } else { int nbdocks = 0; if (ship_dock->slots()) { for (core::Slots::iterator slit = ship_dock->slots()->begin(); slit != ship_dock->slots()->end(); ++slit) { if ((*slit)->type() == model::Slot::Dock) { ++nbdocks; } } } if (nbdocks) { // choose a random dock to launch from int launchbay_index = math::randomi(nbdocks); int i = 0; core::Slot *launchbay = 0; for (core::Slots::iterator slit = ship_dock->slots()->begin(); slit != ship_dock->slots()->end(); ++slit) { if ((*slit)->type() == model::Slot::Dock) { if (i == launchbay_index) { launchbay = (*slit); break; } i++; } } get_axis().assign(ship_dock->axis() * launchbay->axis()); get_location().assign(ship_dock->location() + ship_dock->axis() * ( launchbay->location() + launchbay->axis().forward() * radius())); } else { const float r = radius() + ship_dock->radius(); get_axis().assign(ship_dock->axis()); get_location().assign(ship_dock->location() + (ship_dock->axis().forward() * r ) ); } } nudge(false); ship_dock = 0; set_state(core::Entity::Normal); ship_autopilot_flags = AutoPilotDisabled; ship_autopilot_target = 0; reset(); } void Ship::set_spawn(core::Entity *spawn) { ship_spawn = spawn; } void Ship::set_armor(const float armor) { ship_armor = armor; math::clamp(ship_armor, 0.0f, maxarmor()); entity_health = ship_armor * 100.0f / maxarmor(); } void Ship::action (btScalar seconds) { float engine_force = 0; float turn_force = ship_turn_force; float roll_force = ship_roll_force; btTransform t; switch (entity_state) { case core::Entity::Normal: engine_force = ship_thrust_force * entity_thrust; break; case core::Entity::ImpulseInitiate: engine_force = ship_thrust_force * entity_thrust; break; case core::Entity::Impulse: engine_force = ship_impulse_force; turn_force *= .5f; roll_force *= .5f; break; case core::Entity::JumpInitiate: case core::Entity::Jump: engine_force = 0.0f; break; case core::Entity::Docked: return; break; default: engine_force = 0.0f; break; } t.setIdentity(); entity_motionstate->getWorldTransform(t); get_location().assign(t.getOrigin()); get_axis().assign(t.getBasis()); const float torque_scale = 0.001f; // apply strafe body()->applyCentralImpulse(math::to_btVector3(axis().up() * current_target_vstrafe * ship_strafe_force)); body()->applyCentralImpulse(math::to_btVector3(axis().left() * current_target_strafe * ship_strafe_force)); // apply afterburner body()->applyCentralImpulse(math::to_btVector3(axis().forward() * current_target_afterburner * ship_strafe_force)); // apply main thruster // note: negative afterburner puts the ship in reverse and disables the main thruster if (current_target_afterburner >= 0) { body()->applyCentralImpulse(math::to_btVector3(axis().forward() * engine_force)); } // apply direction body()->applyTorqueImpulse(math::to_btVector3(axis().up() * current_target_direction * turn_force * torque_scale)); // apply pitch body()->applyTorqueImpulse(math::to_btVector3(axis().left() * -current_target_pitch * turn_force * torque_scale)); // apply roll body()->applyTorqueImpulse(math::to_btVector3(axis().forward() * -current_target_roll * roll_force* torque_scale)); // limit speed entity_speed = (float) entity_body->getLinearVelocity().length(); if (entity_speed > Game::g_impulsespeed->value() * 0.01f) { entity_speed = Game::g_impulsespeed->value() * 0.01f; body()->setLinearVelocity(math::to_btVector3(axis().forward() * entity_speed)); } } void Ship::hit(core::Entity *other) { if (state() == core::Entity::Destroyed) { return; } if (state() == core::Entity::Docked) { return; } if (other->moduletype() == spacemine_enttype) { // hit by a mine SpaceMine * spacemine = static_cast(other); if (spacemine->state() != core::Entity::Destroyed) { set_armor(armor() - spacemine->damage()); } // destroyed if (armor() <= 0) { explode(); core::Player *assassin = 0; if (spacemine->owner_id()) { assassin = core::server()->find_player(spacemine->owner_id()); } if (owner()) { // broadcast death message if the owner is controlling this ship if (owner()->control() == this) { std::string message("^B"); message.append(owner()->name()); if (assassin) { if (assassin == owner()) { message.append(" ^Bran into his own mine."); } else { message.append(" ^Bran into "); message.append(assassin->name()); message.append(" ^B's mine."); // asssissin killed a player assassin->set_pvpkills(assassin->pvpkills() + 1); assassin->set_dirty(); } } else { message.append(" ^Bwent boom."); } core::server()->broadcast(message); } } else { if (assassin) { // assissin killed an NPC assassin->set_npckills(assassin->npckills() + 1); assassin->set_dirty(); // faction ship got killed assert (faction()->type() == Faction::infotype()); static_cast(faction())->apply_kill(assassin); } } } } else if (other->type() == core::Entity::Projectile) { // hit by projectile core::EntityProjectile *projectile = static_cast(other); // don't hit self if ((projectile->state() != core::Entity::Destroyed) && (projectile->spawn_id() != id())) { set_armor(armor() - projectile->damage()); core::Player *assassin = 0; if (projectile->owner_id()) { assassin = core::server()->find_player(projectile->owner_id()); } // destroyed if (armor() <= 0) { explode(); if (owner()) { // broadcast death message if the owner is controlling this ship if (owner()->control() == this) { // broadcast death message std::string message("^B"); message.append(owner()->name()); if (assassin) { if (assassin == owner()) { message.append(" ^Bate his own bullet."); } else { message.append(" ^Bwas blown to bits by "); message.append(assassin->name()); } } else { message.append(" ^Bwas blown to bits."); } core::server()->broadcast(message); } } else if (faction()) { if (assassin) { // faction ship got killed assert (faction()->type() == Faction::infotype()); static_cast(faction())->apply_kill(assassin); } } if (assassin) { // send kill sound to assassin assassin->sound("game/target_kill"); // update assassin stats if (owner()) { // asssissin killed a player assassin->set_pvpkills(assassin->pvpkills() + 1); } else { // assissin killed an NPC assassin->set_npckills(assassin->npckills() + 1); } assassin->set_dirty(); } } else { // send hit sound to assassin if (assassin) { assassin->sound("game/target_hit"); } // send hit sound if the owner is controlling this ship if (owner() && (owner()->control() == this)) { const float chance = math::randomf(100.0f); if (chance < 33.0f) { owner()->sound("game/hit01"); } else if (chance < 66.0f) { owner()->sound("game/hit02"); } else { owner()->sound("game/hit03"); } } } } } } void Ship::collision(core::Entity *other) { if (state() == core::Entity::Destroyed) { return; } if (state() == core::Entity::Docked) { return; } // do not fly into planets if (other->moduletype() == planet_enttype) { // ran into a planet explode(); if (owner()) { std::string message("^B"); message.append(owner()->name()); message.append(" ^Bran into "); message.append(other->name()); message.append("."); core::server()->broadcast(message); } else { die(); } } else if (other->moduletype() == star_enttype) { // flew into a star explode(); if (owner()) { std::string message("^B"); message.append(owner()->name()); message.append(" ^Bflew into a star."); core::server()->broadcast(message); } else { die(); } } } void Ship::frame(const unsigned long elapsed) { //const float direction_reaction = 2.0f; // directional control reaction time //const float thrust_reaction = 0.5f; // thrust control reaction time //const float strafe_reaction = 1.5f; // strafe control reaction time //const float afterburner_reaction = 1.0f; // afterburner control reaction time math::Vector3f n; // normal of a plane math::Axis target_axis(axis()); // target axis // speed might be set to 0 on this update if (entity_speed != 0.0f) { set_dirty(); } /* -- autopilot -------------------------------------------- */ if (has_autopilot_flag(Ship::AutoPilotEnabled)) { if ((state() == core::Entity::Normal) || (state() == core::Entity::Impulse) || (state() == core::Entity::ImpulseInitiate)) { frame_autopilot(elapsed); } } /* -- update state ----------------------------------------- */ if (entity_state == core::Entity::Docked) { target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_controlflags = 0; target_afterburner = 0.0f; target_thrust = 0; entity_thrust = 0; entity_speed = 0.0f; if (body()) { reset(); } } else if (entity_state == core::Entity::JumpInitiate) { if (ship_jumpdrive_timer + 1.0f <= core::server()->time()) { entity_timer -= 1.0f; if (entity_timer <= 0) { if (ship_jumpdepart && ship_jumpdepart->target()) { set_state(core::Entity::Jump); entity_speed = Game::g_impulsespeed->value() * 0.01f; if (ship_jumpdepart->moduletype() == jumpgate_enttype) { get_axis().assign(ship_jumpdepart->target()->axis()); get_location().assign(ship_jumpdepart->target()->location()); get_location() += axis().forward() * (radius() + ship_jumpdepart->target()->radius()); } else { get_location().assign(ship_jumpdepart->target()->location() + location() - ship_jumpdepart->location()); } set_zone(ship_jumpdepart->target()->zone()); if (owner()) { if (owner()->view() && (owner()->view()->zone() != ship_jumpdepart->target()->zone())) { owner()->set_view(0); } owner()->send("^BJumping to the " + ship_jumpdepart->target()->zone()->name()); } } else { set_state(core::Entity::Normal); } ship_jumpdrive_timer = core::server()->time(); entity_timer = jump_cooldown_delay; set_dirty(); return; } else { ship_jumpdrive_timer = core::server()->time(); set_dirty(); } } // control is disabled while the jumpdrive is activated target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_afterburner = 0.0f; target_thrust = 0.1f; target_controlflags = 0; } else if (entity_state == core::Entity::Jump) { // control is disabled while the jumpdrive is activated target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_afterburner = 0.0f; target_thrust = 0.0f; target_controlflags = 0; // apply jump drive cooldown if (ship_jumpdrive_timer + 1.0f <= core::server()->time()) { entity_timer -= 1.0f; if (entity_timer <= 0) { ship_jumpdrive_timer = 0; set_state(core::Entity::Normal); set_dirty(); if (owner() && owner()->view() && owner()->control() == (EntityControlable*) this) owner()->set_view(0); } else { ship_jumpdrive_timer = core::server()->time(); set_dirty(); } } return; } else if (entity_state == core::Entity::ImpulseInitiate) { // cancel impulse drive if afterburner goes reverse if (target_afterburner < 0.0f) { set_state(core::Entity::Normal); set_dirty(); entity_timer = 0; } else { if (ship_impulsedrive_timer + 1.0f <= core::server()->time()) { entity_timer -= 1.0f; if (entity_timer <= 0) { entity_state = core::Entity::Impulse; entity_timer = 0; set_dirty(); } else { ship_impulsedrive_timer = core::server()->time(); set_dirty(); } } // clamp input values //target_thrust = 0.0f; //target_afterburner = 0.0f; math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_roll, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); } } else if (entity_state == core::Entity::Impulse) { // clamp input values math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_roll, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); // cancel impulse drive if afterburner goes reverse if (target_afterburner < 0.0f) { set_state(core::Entity::Normal); set_dirty(); entity_timer = 0; //target_thrust = 1.0f; //entity_thrust = 1.0f; } else { //target_afterburner = 0.0f; //target_thrust = 0.0f; } } else if (entity_state == core::Entity::Normal) { // clamp input values math::clamp(target_thrust, 0.0f, 1.0f); math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_roll, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); math::clamp(target_afterburner, -1.0f, 1.0f); } else if (entity_state == core::Entity::Destroyed) { target_direction = 0; target_pitch = 0; target_roll = 0; target_strafe = 0.0f; target_vstrafe = 0.0f; target_afterburner = 0.0f; target_thrust = 0; target_controlflags = 0; entity_thrust = 0; if (body()) { reset(); } } // set steering controls current_target_afterburner = target_afterburner; if (current_target_afterburner < 0.0f) { target_thrust = 0; } entity_thrust = target_thrust; current_target_strafe = target_strafe; current_target_vstrafe = target_vstrafe; if (has_target_controlflag(core::EntityControlable::ControlFlagOverride)) { // use mouse aim location to set steering controls math::Vector3f direction(target_aim - location()); direction.normalize(); // transform direction from world coordinates to local entity coordinates direction = axis().transpose() * direction; if (direction.x() < 0) { // aim is behind the ship target_pitch = direction.z() * POWERSTEERING; target_direction = direction.y() * POWERSTEERING; } else { // target is in front of the ship target_pitch = direction.z() * POWERSTEERING; target_direction = direction.y() * POWERSTEERING; } math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); } current_target_roll = target_roll; current_target_pitch = target_pitch; current_target_direction = target_direction; math::clamp(entity_thrust, 0.0f, 1.0f); EntityControlable::frame(elapsed); // fire weapons if (slots() && (state() == core::Entity::Normal) && has_target_controlflag(core::EntityControlable::ControlFlagFire)) { for (core::Slots::iterator it = slots()->begin(); it != slots()->end(); it++) { core::Slot *slot = (*it); // create projectiles 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; } else if ((weapon->projectile_interval() > 0) && (slot->last_fired() + weapon->projectile_interval() <= core::server()->timestamp())) { // aim math::Axis projectile_axis(axis() * slot->axis()); math::Vector3f projectile_location(location() + axis() * slot->location()); math::Vector3f projectile_direction(target_aim - projectile_location); projectile_direction.normalize(); float cosa = math::dotproduct(projectile_direction, projectile_axis.forward()); // fire a projectile if the angle between the aim direction and the slot's forward direction is small enough // we only need half the cone angle for the cosine calculation float conecos = cosf(slot->cone() * 0.5f); if (cosa >= conecos) { // aim math::Vector3f normal(math::crossproduct(projectile_direction, projectile_axis.forward())); if (normal.length() > MIN_DELTA) { float sina = sqrt(1.0f - cosa * cosa); normal.normalize(); projectile_axis.rotate(normal, cosa, sina); } // spawn a new projectile core::EntityProjectile *projectile = new core::EntityProjectile(this); projectile->set_damage(weapon->damage()); projectile->set_lifespan(weapon->projectile_lifespan()); projectile->set_projectile_modelname(weapon->projectile_modelname()); projectile->set_projectile_soundname(weapon->projectile_soundname()); projectile->set_axis(projectile_axis); projectile->set_location(projectile_location); projectile->set_speed(weapon->projectile_speed()); projectile->reset(); slot->set_last_fired(core::server()->timestamp()); } } } } } } void Ship::frame_autopilot(const unsigned long elapsed) { if (!autopilot_target()) { return; } if (!zone()->find_entity(ship_autopilot_target)) { set_autopilot_target(0); return; } // reference radius used in calculations const float r = radius() + autopilot_target()->radius(); const float distance = math::distance(location(), autopilot_target()->location()); const float dock_distance = (autopilot_target()->moduletype() == planet_enttype ? r + PLANET_SAFE_DISTANCE : r); if (!has_target_controlflag(core::EntityControlable::ControlFlagOverride)) { // thruster and speed control if (has_autopilot_flag(AutoPilotCombat) && (distance < r + COMBAT_DISTANCE)) { frame_autopilot_combat(elapsed, autopilot_target()); } else if (has_autopilot_flag(AutoPilotFormation) && (distance < 4.0f * r)) { frame_autopilot_formation(elapsed, autopilot_target()); } else if (has_autopilot_flag(AutoPilotDock) && (distance < dock_distance)) { frame_autopilot_dock(elapsed, autopilot_target()); } else { frame_autopilot_goto(elapsed, autopilot_target()); } } else { // docking has to work even if ControlFlagOverride is set if (has_autopilot_flag(AutoPilotDock) && (distance < dock_distance)) { frame_autopilot_dock(elapsed, autopilot_target()); } } } void Ship::frame_autopilot_goto(const unsigned long elapsed, core::Entity *target) { // reference radius used in calculations const float r = radius() + target->radius(); // desired direction math::Vector3f direction(target->location() - location()); const float distance = direction.length(); direction.normalize(); // transform direction from world coordinates to local entity coordinates direction = axis().transpose() * direction; if (direction.x() < 0) { // target is behind the ship target_direction = (direction.y() > 0 ? 1.0f : -1.0f); target_pitch = direction.z(); } else if (direction.x() + MIN_DELTA < 1.0f) { // target is in front of the ship target_direction = direction.y(); target_pitch = direction.z(); } else { target_direction = 0.0f; target_pitch = 0.0f; } target_direction *= POWERSTEERING; current_target_pitch *= POWERSTEERING; math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); target_roll = 0.0f; // impulse or thruster if (distance > r + 2.0f * PLANET_SAFE_DISTANCE) { if (state() == core::Entity::Normal) { // far away, enable impulse drive func_impulse(); } } else if (distance > r + PLANET_SAFE_DISTANCE) { if (autopilot_target()->moduletype() == planet_enttype) { if ((state() == core::Entity::Impulse) || (state() == core::Entity::ImpulseInitiate)) { // near planet, disable impulse drive func_impulse(); } } else { if (state() == core::Entity::Normal) { // still far enough, enable impulse drive func_impulse(); } } } else { if (target->type() == core::Entity::Controlable) { const core::EntityControlable *controlable = static_cast(target); if (state() == core::Entity::Impulse) { if (controlable->state() == core::Entity::Normal) { // disable impulse drive func_impulse(); } } else if (state() == core::Entity::Normal) { if ((controlable->state() == core::Entity::Impulse) || (controlable->state() == core::Entity::ImpulseInitiate)) { // enable impulse drive func_impulse(); } } } else { if ((state() == core::Entity::Impulse) || (state() == core::Entity::ImpulseInitiate)){ func_impulse(); } } } if (autopilot_target()->moduletype() == planet_enttype) { // thrust rules for planet approaches if (distance > r + 2.0f * PLANET_SAFE_DISTANCE) { target_thrust = 1.0f; } else if (distance > r + PLANET_SAFE_DISTANCE) { target_thrust = math::max(0.1f, (distance - r - PLANET_SAFE_DISTANCE) / PLANET_SAFE_DISTANCE); } else { target_thrust = 0.0f; // TODO autolevel } } else { // thruster if (distance > 4.0f * r) { target_thrust = 1.0f; } else if (distance > r ) { target_thrust = math::max(0.1f, (distance - r ) / (3.0f * r)); } else { if (target->has_flag(core::Entity::Dockable) && autopilot_target() && has_autopilot_flag(AutoPilotDock)) { target_thrust = 0.1f; } else { target_thrust = 0.0f; } } } } void Ship::frame_autopilot_combat(const unsigned long elapsed, core::Entity *target) { if ((state() == core::Entity::Impulse) || (state() == core::Entity::ImpulseInitiate)) { func_impulse(); } // desired direction math::Vector3f direction(target->location() - location()); const float distance = direction.length(); // normalize direction /= distance; // reference radius used in calculations const float r = radius() + target->radius(); // transform direction from world coordinates to local entity coordinates direction = axis().transpose() * direction; if (direction.x() < 0) { // target is behind the ship if (distance > 5.0f * radius() + target->radius()) { // turn at a distance target_direction = (direction.y() > 0.0f ? 1.0f : -1.0f); target_pitch = direction.z(); } else { // fly-by target_direction = -direction.y(); target_pitch = -direction.z(); } } else if (direction.x() + MIN_DELTA < 1.0f) { // target is in front of the ship target_direction = direction.y(); target_pitch = direction.z(); } else { target_direction = 0.0f; target_pitch = 0.0f; } target_direction *= POWERSTEERING; current_target_pitch *= POWERSTEERING; math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); // transform the target's axis into local coordinates const math::Axis reference(axis().transpose() * target->axis()); if (reference.up().z() < 0.0f) { // upward-down target_roll = (reference.up().y() > 0.0f ? 1.0f : 0.0f); } else if (reference.up().z() + MIN_DELTA < 1.0f) { target_roll = reference.up().y(); } else { target_roll = 0.0f; } // thruster if (distance > 2.0f * r) { target_thrust = 1.0f; } else if (distance > r ) { target_thrust = math::max(0.1f, (distance - r ) / r); } else { target_thrust = 0.0f; } } void Ship::frame_autopilot_formation(const unsigned long elapsed, core::Entity *target) { // transform the target's axis into local coordinates const math::Axis reference(axis().transpose() * target->axis()); //const float distance = math::distance(location(), target->location()); //const float r = radius() + target->radius(); if (reference.forward().x() < 0) { // target is behind the ship target_direction = (reference.forward().y() > 0.0f ? 1.0f : -1.0f); target_pitch = reference.forward().z(); } else if (reference.forward().x() + MIN_DELTA < 1.0f) { // target is in front of the ship target_direction = reference.forward().y(); target_pitch = reference.forward().z(); } else { target_direction = 0.0f; target_pitch = 0.0f; } target_direction *= POWERSTEERING; current_target_pitch *= POWERSTEERING; math::clamp(target_pitch, -1.0f, 1.0f); math::clamp(target_direction, -1.0f, 1.0f); if (reference.up().z() < 0.0f) { // upward-down target_roll = (reference.up().y() > 0.0f ? 1.0f : 0.0f); } else if (reference.up().z() + MIN_DELTA < 1.0f) { target_roll = reference.up().y(); } else { target_roll = 0.0f; } if (target->type() == core::Entity::Controlable) { const core::EntityControlable *controlable = static_cast(target); target_thrust = controlable->thrust(); if (state() == core::Entity::Impulse) { if (controlable->state() != core::Entity::Impulse) { // disbable impulse drive func_impulse(); } } else if (state() == core::Entity::Normal) { if ((controlable->state() == core::Entity::Impulse) || (controlable->state() == core::Entity::ImpulseInitiate)) { // enable impulse drive func_impulse(); } } } else { if (state() == core::Entity::Impulse) { // disable impulse drive func_impulse(); } } } void Ship::frame_autopilot_dock(const unsigned long elapsed, core::Entity *target) { // reference radius used in calculations //const float r = radius() + target->radius(); //const float dock_distance = (target->moduletype() == planet_enttype ? r + PLANET_SAFE_DISTANCE : r); if ((state() == core::Entity::Impulse) || (state() == core::Entity::ImpulseInitiate)) { func_impulse(); } else if (state() != core::Entity::Normal) { return; } if (target->moduletype() == jumpgate_enttype) { // jumpgates have their own docking function JumpGate *jumpgate = static_cast(target); jumpgate->func_dock(this); unset_autopilot_flag(AutoPilotDock); return; } else if (target->moduletype() == race_enttype) { if (owner()) { RaceTrack *race = static_cast(target); race->func_dock(this); } unset_autopilot_flag(AutoPilotEnabled); unset_autopilot_flag(AutoPilotDock); set_autopilot_target(0); return; } else { set_dock(target); if (owner()) { if (owner()->control() == this) { owner()->set_view(target); } core::Player *dock_owner = (target->type() == core::Entity::Controlable ? static_cast(target)->owner() : 0 ); if (dock_owner) { owner()->send("^BDocking at " + dock_owner->name() + "^B's " + target->name()); } else { owner()->send("^BDocking at " + target->name()); } // force save core::server()->module()->player_save(owner()); } } } } // namespace game