/* 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 "auxiliary/functions.h" #include "core/gameserver.h" #include "core/entity.h" #include "base/game.h" #include "base/ship.h" #include "math/functions.h" using math::degrees360f; using math::degrees180f; namespace game { const float MIN_DELTA = 0.000001f; Ship::Ship(core::Player *owner, ShipModel *shipmodel) : core::EntityControlable(owner, ship_enttype), PhysicsBody(this) { set_modelname(shipmodel->modelname()); set_name(shipmodel->name()); std::string str(aux::text_strip(owner->name())); aux::to_label(str); set_label(str); entity_moduletypeid = ship_enttype; get_color().assign(owner->color()); get_color_second().assign(owner->color_second()); ship_shipmodel = shipmodel; ship_jumpdrive = shipmodel->shipmodel_jumpdrive; ship_impulsedrive_timer = 0; ship_jumpdrive_timer = 0; ship_jumpdepart = 0; reset(); } Ship::~Ship() { shutdown_physics(); } 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_afterburner = 0.0f; } void Ship::func_impulse() { if (entity_state == core::Entity::Impulse) { entity_state = core::Entity::Normal; target_thrust = 1.0f; entity_thrust = 1.0f; } else if (entity_state == core::Entity::ImpulseInitiate) { entity_state = core::Entity::Normal; } else if (entity_state != core::Entity::Normal) { return; } else { if (entity_state == core::Entity::JumpInitiate) { ship_jumpdrive_timer = 0; entity_timer = 0; } entity_state = core::Entity::ImpulseInitiate; if (Game::g_devel->value()) { entity_timer = 0; } else { entity_timer = 3; } ship_impulsedrive_timer = core::server()->time(); } set_dirty(); } void Ship::initiate_jump(JumpPoint *depart) { ship_jumpdepart = 0; if (!depart) return; if (!depart->target()) return; ship_jumpdepart = depart; entity_state = core::Entity::JumpInitiate; if (Game::g_devel->value()) { entity_timer = 0; } else { entity_timer = 8; } 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()); } owner()->send("Usage: jump [^B" + helpstr + "^N]"); return; } if (jumptargetzone == zone()) { owner()->send("Already in the " + jumptargetzone->name()); return; } owner()->send("Jumping to the " + jumptargetzone->name()); set_zone(jumptargetzone); ship_jumpdrive_timer = 0; entity_timer = 0; set_state(core::Entity::Jump); set_dirty(); return; } else { if (!jumpdrive() && !Game::g_devel->value()) { 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) { 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) { owner()->send("Jumppoint out of range!"); return 0; } else { owner()->send("Jumping to the " + jumppoint->target()->zone()->name()); return jumppoint; } } else { owner()->send("No jumppoints found!"); return 0; } return 0; } void Ship::explode() { set_state(core::Entity::Destroyed); target_thrust = 0; target_pitch = 0; target_roll = 0; target_direction = 0; target_afterburner = 0.0f; target_thrust = 0; entity_thrust = 0; if (owner()) { if (owner()->control() == this) owner()->set_view(this); } }; void Ship::set_zone(core::Zone *zone) { shutdown_physics(); core::EntityControlable::set_zone(zone); if (owner() && (owner()->control() == (EntityControlable*) this)) owner()->set_zone(zone); init_physics(radius()); } void Ship::frame(float seconds) { float actual_maxspeed = ship_shipmodel->maxspeed(); float actual_turnspeed = ship_shipmodel->turnspeed(); float actual_acceleration = ship_shipmodel->acceleration(); float actual_thrust = 0; #ifndef HAVE_BULLET const float direction_reaction = 2.0f; // direction controls reaction time factor const float thrust_reaction = 0.5f; // thrust control reaction time factor const float strafe_reaction = 1.5f; const float afterburner_reaction = 1.0f; // a fterburner control reaction time float cosangle; // cosine of an angle float angle; // angle in radians math::Vector3f n; // normal of a plane math::Axis target_axis(axis()); // target axis #endif entity_movement = 0; /* -- update state ----------------------------------------- */ // speed might be set to 0 on this update if (entity_speed != 0.0f) set_dirty(); if (entity_state == core::Entity::Docked) { target_thrust = 0; target_pitch = 0; target_roll = 0; target_direction = 0; target_afterburner = 0.0f; target_thrust = 0; entity_speed = 0; entity_thrust = 0; } 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(); if (ship_jumpdepart->moduletype() == jumpgate_enttype) { get_axis().assign(ship_jumpdepart->target()->axis()); get_location().assign(ship_jumpdepart->target()->location()); //entity_location += entity_axis.forward() * radius(); } else { get_location().assign(ship_jumpdepart->target()->location() + location() - ship_jumpdepart->location()); } set_zone(ship_jumpdepart->target()->zone()); owner()->send("^BJumping to the " + ship_jumpdepart->target()->zone()->name()); } else { set_state(core::Entity::Normal); } ship_jumpdrive_timer = 0; entity_timer = 0; set_dirty(); return; } else { ship_jumpdrive_timer = core::server()->time(); set_dirty(); } } // control is disabled while the jumpdrive is activated target_thrust = 0; target_pitch = 0; target_roll = 0; target_direction = 0; target_afterburner = 0.0f; target_thrust = 0.1; } else if (entity_state == core::Entity::Jump) { // control is disabled while the jumpdrive is activated target_thrust = 0; target_pitch = 0; target_roll = 0; target_direction = 0; target_afterburner = 0.0f; target_thrust = 0; // FIXME 5 second cooldown entity_state = core::Entity::Normal; set_dirty(); if (owner() && owner()->view() && owner()->control() == (EntityControlable*) this) owner()->set_view(0); 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) { actual_maxspeed = Game::g_impulsespeed->value(); actual_acceleration = Game::g_impulseacceleration->value(); 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; 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); actual_turnspeed *= 0.5; } } 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 { actual_maxspeed = Game::g_impulsespeed->value(); actual_acceleration = Game::g_impulseacceleration->value(); actual_turnspeed *= 0.5; 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); if (speed() > actual_maxspeed * 1.15f) { actual_acceleration = Game::g_impulseacceleration->value(); actual_turnspeed *= 0.5; } } else if (entity_state == core::Entity::Destroyed) { target_thrust = 0; target_pitch = 0; target_roll = 0; target_direction = 0; target_afterburner = 0.0f; target_thrust = 0; entity_thrust = 0; } // update afterburner control target if (current_target_afterburner < target_afterburner) { current_target_afterburner += afterburner_reaction * seconds; if (current_target_afterburner > target_afterburner) current_target_afterburner = target_afterburner; } else if (current_target_afterburner > target_afterburner) { current_target_afterburner -= afterburner_reaction * seconds; if (current_target_afterburner < target_afterburner) current_target_afterburner = target_afterburner; } // update thrust control target if (current_target_afterburner < 0.0f) { target_thrust = 0; } if (entity_thrust < target_thrust) { entity_thrust += thrust_reaction * seconds; if (entity_thrust > target_thrust) entity_thrust = target_thrust; } else if (entity_thrust > target_thrust) { entity_thrust -= thrust_reaction * seconds; if (entity_thrust < target_thrust) entity_thrust = target_thrust; } math::clamp(entity_thrust, 0.0f, 1.0f); actual_thrust = entity_thrust + current_target_afterburner * 0.15f; if ((entity_state == core::Entity::ImpulseInitiate) || (entity_state == core::Entity::Impulse)) { actual_thrust = 1.0f; } // update strafe control target if (current_target_strafe < target_strafe) { current_target_strafe += strafe_reaction * seconds; if (current_target_strafe > target_strafe) current_target_strafe = target_strafe; } else if (current_target_strafe > target_strafe) { current_target_strafe -= strafe_reaction * seconds; if (current_target_strafe < target_strafe) current_target_strafe = target_strafe; } #ifndef HAVE_BULLET /* -- original frame --------------------------------------- */ // update roll control target if (current_target_roll < target_roll) { current_target_roll += direction_reaction * seconds; if (current_target_roll > target_roll) current_target_roll = target_roll; } else if (current_target_roll > target_roll) { current_target_roll -= direction_reaction * seconds; if (current_target_roll < target_roll) current_target_roll = target_roll; } math::clamp(current_target_roll, -1.0f, 1.0f); if (fabs(current_target_roll) > MIN_DELTA) { float roll_offset = seconds * current_target_roll; get_axis().change_roll(actual_turnspeed * roll_offset); } else { current_target_roll = 0.0f; } // update direction control target if (current_target_direction < target_direction) { current_target_direction += direction_reaction * seconds; if (current_target_direction > target_direction) { current_target_direction = target_direction; } } else if (current_target_direction > target_direction) { current_target_direction -= direction_reaction * seconds; if (current_target_direction < target_direction) { current_target_direction = target_direction; } } if (fabs(current_target_direction) > MIN_DELTA) { math::clamp(current_target_direction, -1.0f, 1.0f); target_axis.change_direction(actual_turnspeed * current_target_direction); } else { current_target_direction = 0.0f; } // update pitch control target if (current_target_pitch < target_pitch) { current_target_pitch += direction_reaction * seconds; if (current_target_pitch > target_pitch) current_target_pitch = target_pitch; } else if (current_target_pitch > target_pitch) { current_target_pitch -= direction_reaction * seconds; if (current_target_pitch < target_pitch) current_target_pitch = target_pitch; } if (fabs(current_target_pitch) > MIN_DELTA) { math::clamp(current_target_pitch, -1.0f, 1.0f); target_axis.change_pitch(actual_turnspeed * current_target_pitch); } else { current_target_pitch = 0.0f; } // update axis n.assign(math::crossproduct(axis().forward(), target_axis.forward())); if (!(n.length() < MIN_DELTA)) { n.normalize(); cosangle = math::dotproduct(axis().forward(), target_axis.forward()); angle = acos(cosangle) * seconds; // * 180.0f / M_PI; if (angle > MIN_DELTA) get_axis().rotate(n, -angle); } // update speed const float max = actual_thrust * actual_maxspeed; if (entity_speed < max) { entity_speed += actual_acceleration * seconds; if (entity_speed > max) { entity_speed = max; } } else if (entity_speed > max) { entity_speed -= actual_acceleration * seconds; if (entity_speed < max) { entity_speed = max; } } if (fabs(current_target_strafe) > MIN_DELTA) { get_location() += axis().left() * (current_target_strafe * 0.15f * actual_maxspeed) * seconds; } if (fabs(speed()) > MIN_DELTA) { get_location() += axis().forward() * speed() * seconds; } #else /* #ifndef HAVE_BULLET */ /* -- bullet frame ----------------------------------------- */ // get entity speed from physics body btVector3 velocity(body()->getInterpolationLinearVelocity()); entity_speed = velocity.length(); // get physics body world transformation btTransform t; if (body()->getMotionState()) body()->getMotionState()->getWorldTransform(t); else t = body()->getWorldTransform(); // get entity location from physics body btVector3 v(t.getOrigin()); entity_location.assign(v[0], v[1], v[2]); // apply engine trust to the body body()->applyCentralImpulse(to_btVector3(axis().forward() * actual_thrust * actual_acceleration * seconds * 20.0f)); #endif /* else #ifndef HAVE_BULLET */ entity_movement = target_thrust; entity_movement = math::max(entity_movement, fabs(current_target_pitch)); entity_movement = math::max(entity_movement, fabs(current_target_direction)); entity_movement = math::max(entity_movement, fabs(current_target_roll)); entity_movement = math::max(entity_movement, fabs(current_target_afterburner)); entity_movement = math::max(entity_movement, fabs(current_target_strafe)); if ((entity_movement > 0) || (entity_speed > 0)) { set_dirty(); } } } // namespace game