/** * @brief Updates the solid's position using an Euler integration. * * Simple method * * d^2 x(t) / d t^2 = a, a = constant (acceleration) * x'(0) = v, x(0) = p * * d x(t) / d t = a*t + v, v = constant (initial velocity) * x(t) = a/2*t + v*t + p, p = constant (initial position) * * since dt isn't actually differential this gives us ERROR! * so watch out with big values for dt * */ static void solid_update_euler (Solid *obj, const double dt) { double px,py, vx,vy, ax,ay, th; double cdir, sdir; /* make sure angle doesn't flip */ obj->dir += obj->dir_vel*dt; if (obj->dir >= 2*M_PI) obj->dir -= 2*M_PI; if (obj->dir < 0.) obj->dir += 2*M_PI; /* Initial positions. */ px = obj->pos.x; py = obj->pos.y; vx = obj->vel.x; vy = obj->vel.y; th = obj->thrust; /* Save direction. */ sdir = sin(obj->dir); cdir = cos(obj->dir); /* Get acceleration. */ ax = th*cdir / obj->mass; ay = th*sdir / obj->mass; /* p = v*dt + 0.5*a*dt^2 */ px += vx*dt + 0.5*ax * dt*dt; py += vy*dt + 0.5*ay * dt*dt; /* Update position and velocity. */ vect_cset( &obj->vel, vx, vy ); vect_cset( &obj->pos, px, py ); }
/** * @brief Computes an estimation of ammo flying time * * @param w the weapon that shoot * @param parent Parent of the weapon * @param target Target of the weapon */ double pilot_weapFlyTime( Outfit *o, Pilot *parent, Vector2d *pos, Vector2d *vel) { Vector2d approach_vector, relative_location, orthoradial_vector; double speed, radial_speed, orthoradial_speed, dist, t; dist = vect_dist( &parent->solid->pos, pos ); /* Beam weapons */ if (outfit_isBeam(o)) { if (dist > o->u.bem.range) return INFINITY; return 0.; } /* A bay doesn't have range issues */ if (outfit_isFighterBay(o)) return 0.; /* Rockets use absolute velocity while bolt use relative vel */ if (outfit_isLauncher(o)) vect_cset( &approach_vector, - vel->x, - vel->y ); else vect_cset( &approach_vector, VX(parent->solid->vel) - vel->x, VY(parent->solid->vel) - vel->y ); speed = outfit_speed(o); /* Get the vector : shooter -> target */ vect_cset( &relative_location, pos->x - VX(parent->solid->pos), pos->y - VY(parent->solid->pos) ); /* Get the orthogonal vector */ vect_cset(&orthoradial_vector, VY(parent->solid->pos) - pos->y, pos->x - VX(parent->solid->pos) ); radial_speed = vect_dot( &approach_vector, &relative_location ); radial_speed = radial_speed / VMOD(relative_location); orthoradial_speed = vect_dot(&approach_vector, &orthoradial_vector); orthoradial_speed = orthoradial_speed / VMOD(relative_location); if( ((speed*speed - VMOD(approach_vector)*VMOD(approach_vector)) != 0) && (speed*speed - orthoradial_speed*orthoradial_speed) > 0) t = dist * (sqrt( speed*speed - orthoradial_speed*orthoradial_speed ) - radial_speed) / (speed*speed - VMOD(approach_vector)*VMOD(approach_vector)); else return INFINITY; /* if t < 0, try the other solution */ if (t < 0) t = - dist * (sqrt( speed*speed - orthoradial_speed*orthoradial_speed ) + radial_speed) / (speed*speed - VMOD(approach_vector)*VMOD(approach_vector)); /* if t still < 0, no solution */ if (t < 0) return INFINITY; return t; }
/** * @brief Gets the mount position of a pilot. * * Position is relative to the pilot. * * @param p Pilot to get mount position of. * @param id ID of the mount. * @param[out] v Position of the mount. * @return 0 on success. */ int pilot_getMount( const Pilot *p, const PilotOutfitSlot *w, Vector2d *v ) { double a, x, y; double cm, sm; const ShipMount *m; /* Calculate the sprite angle. */ a = (double)(p->tsy * p->ship->gfx_space->sx + p->tsx); a *= p->ship->mangle; /* 2d rotation matrix * [ x' ] [ cos sin ] [ x ] * [ y' ] = [ -sin cos ] * [ y ] * * dir is inverted so that rotation is counter-clockwise. */ m = &w->sslot->mount; cm = cos(-a); sm = sin(-a); x = m->x * cm + m->y * sm; y = m->x *-sm + m->y * cm; /* Correction for ortho perspective. */ y *= M_SQRT1_2; /* Don't forget to add height. */ y += m->h; /* Get the mount and add the player.p offset. */ vect_cset( v, x, y ); return 0; }
/** * @brief Subtracts two vectors or a vector and some cartesian coordinates. * * If x is a vector it subtracts both vectors, otherwise it subtracts cartesian * coordinates to the vector. * * @usage my_vec = my_vec - your_vec * @usage my_vec:sub( your_vec ) * @usage my_vec:sub( 5, 3 ) * * @luaparam v Vector getting stuff subtracted from. * @luaparam x X coordinate or vector to subtract. * @luaparam y Y coordinate or nil to subtract. * @luareturn The result of the vector operation. * @luafunc sub( v, x, y ) */ static int vectorL_sub( lua_State *L ) { LuaVector vout, *v1, *v2; double x, y; /* Get self. */ v1 = luaL_checkvector(L,1); /* Get rest of parameters. */ v2 = NULL; if (lua_isvector(L,2)) { v2 = lua_tovector(L,2); x = v2->vec.x; y = v2->vec.y; } else if ((lua_gettop(L) > 2) && lua_isnumber(L,2) && lua_isnumber(L,3)) { x = lua_tonumber(L,2); y = lua_tonumber(L,3); } else NLUA_INVALID_PARAMETER(L); /* Actually add it */ vect_cset( &vout.vec, v1->vec.x - x, v1->vec.y - y ); lua_pushvector( L, vout ); return 1; }
static int vectorL_sub__( lua_State *L ) { Vector2d *v1, *v2; double x, y; /* Get self. */ v1 = luaL_checkvector(L,1); /* Get rest of parameters. */ v2 = NULL; if (lua_isvector(L,2)) { v2 = lua_tovector(L,2); x = v2->x; y = v2->y; } else if ((lua_gettop(L) > 2) && lua_isnumber(L,2) && lua_isnumber(L,3)) { x = lua_tonumber(L,2); y = lua_tonumber(L,3); } else { NLUA_INVALID_PARAMETER(L); return 0; } /* Actually add it */ vect_cset( v1, v1->x - x, v1->y - y ); lua_pushvector( L, *v1 ); return 1; }
/** * @brief Starts autonav with a local position destination. */ void player_autonavPos( double x, double y ) { if (!player_autonavSetup()) return; player.autonav = AUTONAV_POS_APPROACH; player.autonavmsg = "position"; vect_cset( &player.autonav_pos, x, y ); }
/** * @brief Starts autonav with a planet destination. */ void player_autonavPnt( char *name ) { Planet *p; p = planet_get( name ); if (!player_autonavSetup()) return; player.autonav = AUTONAV_PNT_APPROACH; player.autonavmsg = p->name; vect_cset( &player.autonav_pos, p->pos.x, p->pos.y ); }
/** * @brief Sets the vector by cartesian coordinates. * * @usage my_vec:set(5, 3) -- my_vec is now (5,3) * * @luaparam v Vector to set coordinates of. * @luaparam x X coordinate to set. * @luaparam y Y coordinate to set. * @luafunc set( v, x, y ) */ static int vectorL_set( lua_State *L ) { LuaVector *v1; double x, y; /* Get parameters. */ v1 = luaL_checkvector(L,1); x = luaL_checknumber(L,2); y = luaL_checknumber(L,3); vect_cset( &v1->vec, x, y ); return 0; }
/** * @brief Divides a vector by a number. * * @usage my_vec = my_vec / 3 * @usage my_vec:div(3) * * @luaparam v Vector to divide. * @luaparam mod Amount to divide by. * @luareturn The result of the vector operation. * @luafunc div( v, mod ) */ static int vectorL_div( lua_State *L ) { LuaVector vout, *v1; double mod; /* Get parameters. */ v1 = luaL_checkvector(L,1); mod = luaL_checknumber(L,2); /* Actually add it */ vect_cset( &vout.vec, v1->vec.x / mod, v1->vec.y / mod ); lua_pushvector( L, vout ); return 1; }
static int vectorL_div__( lua_State *L ) { Vector2d *v1; double mod; /* Get parameters. */ v1 = luaL_checkvector(L,1); mod = luaL_checknumber(L,2); /* Actually add it */ vect_cset( v1, v1->x / mod, v1->y / mod ); lua_pushvector( L, *v1 ); return 1; }
/** * @brief Multiplies a vector by a number. * * @usage my_vec = my_vec * 3 * @usage my_vec:mul( 3 ) * * @luaparam v Vector to multiply. * @luaparam mod Amount to multiply by. * @luareturn The result of the vector operation. * @luafunc mul( v, mod ) */ static int vectorL_mul( lua_State *L ) { Vector2d vout, *v1; double mod; /* Get parameters. */ v1 = luaL_checkvector(L,1); mod = luaL_checknumber(L,2); /* Actually add it */ vect_cset( &vout, v1->x * mod, v1->y * mod ); lua_pushvector( L, vout ); return 1; }
/** * @brief Creates a new vector. * * @usage vec2.new( 5, 3 ) -- creates a vector at (5,3) * @usage vec2.new() -- creates a vector at (0,0) * * @luaparam x If set, the X value for the new vector. * @luaparam y If set, the Y value for the new vector. * @luareturn The new vector. * @luafunc new( x, y ) */ static int vectorL_new( lua_State *L ) { LuaVector v; double x, y; if ((lua_gettop(L) > 1) && lua_isnumber(L,1) && lua_isnumber(L,2)) { x = lua_tonumber(L,1); y = lua_tonumber(L,2); } else { x = 0.; y = 0.; } vect_cset( &v.vec, x, y ); lua_pushvector(L, v); return 1; }
/** * @brief Actually handles the shooting, how often the player.p can shoot and such. * * @param p Pilot that is shooting. * @param w Pilot's outfit to shoot. * @param time Expected flight time. * @return 0 if nothing was shot and 1 if something was shot. */ static int pilot_shootWeapon( Pilot* p, PilotOutfitSlot* w, double time ) { Vector2d vp, vv; double rate_mod, energy_mod; double energy; int j; /* Make sure weapon has outfit. */ if (w->outfit == NULL) return 0; /* Reset beam shut-off if needed. */ if (outfit_isBeam(w->outfit) && w->outfit->u.bem.min_duration) w->stimer = INFINITY; /* check to see if weapon is ready */ if (w->timer > 0.) return 0; /* Calculate rate modifier. */ pilot_getRateMod( &rate_mod, &energy_mod, p, w->outfit ); /* Get weapon mount position. */ pilot_getMount( p, w, &vp ); vp.x += p->solid->pos.x; vp.y += p->solid->pos.y; /* Modify velocity to take into account the rotation. */ vect_cset( &vv, p->solid->vel.x + vp.x*p->solid->dir_vel, p->solid->vel.y + vp.y*p->solid->dir_vel ); /* * regular bolt weapons */ if (outfit_isBolt(w->outfit)) { /* enough energy? */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target, time ); } /* * Beam weapons. */ else if (outfit_isBeam(w->outfit)) { /* Don't fire if the existing beam hasn't been destroyed yet. */ if (w->u.beamid > 0) return 0; /* Check if enough energy to last a second. */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; /** @todo Handle warmup stage. */ w->state = PILOT_OUTFIT_ON; w->u.beamid = beam_start( w->outfit, p->solid->dir, &vp, &p->solid->vel, p, p->target, w ); w->timer = w->outfit->u.bem.duration; return 1; /* Return early due to custom timer logic. */ } /* * missile launchers * * must be a secondary weapon */ else if (outfit_isLauncher(w->outfit)) { /* Shooter can't be the target - sanity check for the player.p */ if ((w->outfit->u.lau.ammo->u.amm.ai != AMMO_AI_DUMB) && (p->id==p->target)) return 0; /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* enough energy? */ if (outfit_energy(w->u.ammo.outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->u.ammo.outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target, time ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; p->solid->mass -= w->u.ammo.outfit->mass; pilot_updateMass( p ); /* If last ammo was shot, update the range */ if (w->u.ammo.quantity <= 0) { for (j=0; j<PILOT_WEAPON_SETS; j++) pilot_weapSetUpdateRange( &p->weapon_sets[j] ); } } /* * Fighter bays. */ else if (outfit_isFighterBay(w->outfit)) { /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* Create the escort. */ escort_create( p, w->u.ammo.outfit->u.fig.ship, &vp, &p->solid->vel, p->solid->dir, ESCORT_TYPE_BAY, 1 ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; w->u.ammo.deployed += 1; /* Mark as deployed. */ pilot_updateMass( p ); } else WARN(_("Shooting unknown weapon type: %s"), w->outfit->name); /* Reset timer. */ w->timer += rate_mod * outfit_delay( w->outfit ); return 1; }
/** * @brief Runge-Kutta method of updating a solid based on its acceleration. * * Runge-Kutta 4 method * * d^2 x(t) / d t^2 = a, a = constant (acceleration) * x'(0) = v, x(0) = p * x'' = f( t, x, x' ) = ( x' , a ) * * x_{n+1} = x_n + h/6 (k1 + 2*k2 + 3*k3 + k4) * h = (b-a)/2 * k1 = f(t_n, X_n ), X_n = (x_n, x'_n) * k2 = f(t_n + h/2, X_n + h/2*k1) * k3 = f(t_n + h/2, X_n + h/2*k2) * k4 = f(t_n + h, X_n + h*k3) * * x_{n+1} = x_n + h/6*(6x'_n + 3*h*a, 4*a) * * * Main advantage comes thanks to the fact that Naev is on a 2d plane. * Therefore RK chops it up in chunks and actually creates a tiny curve * instead of approximating the curve for a tiny straight line. */ #define RK4_MIN_H 0.01 /**< Minimal pass we want. */ static void solid_update_rk4 (Solid *obj, const double dt) { int i, N; /* for iteration, and pass calculation */ double h, px,py, vx,vy; /* pass, and position/velocity values */ double ix,iy, tx,ty, ax,ay, th; /* initial and temporary cartesian vector values */ double vmod, vang; int vint; int limit; /* limit speed? */ /* Initial positions and velocity. */ px = obj->pos.x; py = obj->pos.y; vx = obj->vel.x; vy = obj->vel.y; limit = (obj->speed_max >= 0.); /* Initial RK parameters. */ if (dt > RK4_MIN_H) N = (int)(dt / RK4_MIN_H); else N = 1; vmod = MOD( vx, vy ); vint = (int) vmod/100.; if (N < vint) N = vint; h = dt / (double)N; /* step */ /* Movement Quantity Theorem: m*a = \sum f */ th = obj->thrust / obj->mass; for (i=0; i < N; i++) { /* iterations */ /* Calculate acceleration for the frame. */ ax = th*cos(obj->dir); ay = th*sin(obj->dir); /* Limit the speed. */ if (limit) { vmod = MOD( vx, vy ); if (vmod > obj->speed_max) { /* We limit by applying a force against it. */ vang = ANGLE( vx, vy ) + M_PI; vmod = 3. * (vmod - obj->speed_max); /* Update accel. */ ax += vmod * cos(vang); ay += vmod * sin(vang); } } /* x component */ tx = ix = vx; tx += 2.*ix + h*tx; tx += 2.*ix + h*tx; tx += ix + h*tx; tx *= h/6.; px += tx; vx += ax * h; /* y component */ ty = iy = vy; ty += 2.*(iy + h/2.*ty); ty += 2.*(iy + h/2.*ty); ty += iy + h*ty; ty *= h/6.; py += ty; vy += ay * h; /* rotation. */ obj->dir += obj->dir_vel*h; } vect_cset( &obj->vel, vx, vy ); vect_cset( &obj->pos, px, py ); /* Sanity check. */ if (obj->dir >= 2.*M_PI) obj->dir -= 2.*M_PI; else if (obj->dir < 0.) obj->dir += 2.*M_PI; }
/** * @brief The AI of seeker missiles. * * @param w Weapon to do the thinking. * @param dt Current delta tick. */ static void think_seeker( Weapon* w, const double dt ) { double diff; double vel; Pilot *p; int effect; Vector2d v; double t; if (w->target == w->parent) return; /* no self shooting */ p = pilot_get(w->target); /* no null pilot_nstack */ if (p==NULL) { weapon_setThrust( w, 0. ); weapon_setTurn( w, 0. ); return; } /* Handle by status. */ switch (w->status) { case WEAPON_STATUS_OK: if (w->lockon < 0.) w->status = WEAPON_STATUS_LOCKEDON; break; case WEAPON_STATUS_LOCKEDON: /* Check to see if can get jammed */ if ((p->jam_range != 0.) && /* Target has jammer and weapon is in range */ (vect_dist(&w->solid->pos,&p->solid->pos) < p->jam_range)) { /* Check to see if weapon gets jammed */ if (RNGF() < p->jam_chance - w->outfit->u.amm.resist) { w->status = WEAPON_STATUS_JAMMED; /* Give it a nice random effect */ effect = RNG(0,3); switch (effect) { case 0: /* Stuck in left loop */ weapon_setTurn( w, w->outfit->u.amm.turn ); break; case 1: /* Stuck in right loop */ weapon_setTurn( w, -w->outfit->u.amm.turn ); break; default: /* Blow up. */ w->timer = -1.; break; } } else /* Can't get jammed anymore */ w->status = WEAPON_STATUS_UNJAMMED; } /* Purpose fallthrough */ case WEAPON_STATUS_UNJAMMED: /* Work as expected */ /* Smart seekers take into account ship velocity. */ if (w->outfit->u.amm.ai == 2) { /* Calculate time to reach target. */ vect_cset( &v, p->solid->pos.x - w->solid->pos.x, p->solid->pos.y - w->solid->pos.y ); t = vect_odist( &v ) / w->outfit->u.amm.speed; /* Calculate target's movement. */ vect_cset( &v, v.x + t*(p->solid->vel.x - w->solid->vel.x), v.y + t*(p->solid->vel.y - w->solid->vel.y) ); /* Get the angle now. */ diff = angle_diff(w->solid->dir, VANGLE(v) ); } /* Other seekers are stupid. */ else { diff = angle_diff(w->solid->dir, /* Get angle to target pos */ vect_angle(&w->solid->pos, &p->solid->pos)); } /* Set turn. */ weapon_setTurn( w, CLAMP( -w->outfit->u.amm.turn, w->outfit->u.amm.turn, 10 * diff * w->outfit->u.amm.turn )); break; case WEAPON_STATUS_JAMMED: /* Continue doing whatever */ /* Do nothing, dir_vel should be set already if needed */ break; default: WARN("Unknown weapon status for '%s'", w->outfit->name); break; } /* Limit speed here */ vel = MIN(w->outfit->u.amm.speed, VMOD(w->solid->vel) + w->outfit->u.amm.thrust*dt); vect_pset( &w->solid->vel, vel, w->solid->dir ); /*limit_speed( &w->solid->vel, w->outfit->u.amm.speed, dt );*/ }
/** * @brief Actually handles the shooting, how often the player.p can shoot and such. * * @param p Pilot that is shooting. * @param w Pilot's outfit to shoot. * @return 0 if nothing was shot and 1 if something was shot. */ static int pilot_shootWeapon( Pilot* p, PilotOutfitSlot* w ) { Vector2d vp, vv; double rate_mod, energy_mod; double energy; /* Make sure weapon has outfit. */ if (w->outfit == NULL) return 0; /* check to see if weapon is ready */ if (w->timer > 0.) return 0; /* Calculate rate modifier. */ pilot_getRateMod( &rate_mod, &energy_mod, p, w->outfit ); /* Get weapon mount position. */ pilot_getMount( p, w, &vp ); vp.x += p->solid->pos.x; vp.y += p->solid->pos.y; /* Modify velocity to take into account the rotation. */ vect_cset( &vv, p->solid->vel.x + vp.x*p->solid->dir_vel, p->solid->vel.y + vp.y*p->solid->dir_vel ); /* * regular bolt weapons */ if (outfit_isBolt(w->outfit)) { /* enough energy? */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target ); } /* * Beam weapons. */ else if (outfit_isBeam(w->outfit)) { /* Check if enough energy to last a second. */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; /** @todo Handle warmup stage. */ w->state = PILOT_OUTFIT_ON; w->u.beamid = beam_start( w->outfit, p->solid->dir, &vp, &p->solid->vel, p, p->target, w ); } /* * missile launchers * * must be a secondary weapon */ else if (outfit_isLauncher(w->outfit)) { /* Shooter can't be the target - sanity check for the player.p */ if ((w->outfit->u.lau.ammo->u.amm.ai > 0) && (p->id==p->target)) return 0; /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* enough energy? */ if (outfit_energy(w->u.ammo.outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->u.ammo.outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; pilot_updateMass( p ); } /* * Fighter bays. */ else if (outfit_isFighterBay(w->outfit)) { /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* Create the escort. */ escort_create( p, w->u.ammo.outfit->u.fig.ship, &vp, &p->solid->vel, p->solid->dir, ESCORT_TYPE_BAY, 1 ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; w->u.ammo.deployed += 1; /* Mark as deployed. */ pilot_updateMass( p ); } else WARN("Shooting unknown weapon type: %s", w->outfit->name); /* Reset timer. */ w->timer += rate_mod * outfit_delay( w->outfit ); return 1; }