diff --git a/dat/outfit.xml b/dat/outfit.xml index d960bf5..81ac7f0 100644 --- a/dat/outfit.xml +++ b/dat/outfit.xml @@ -555,6 +555,40 @@ 35 + + + 2 + 7 + 17 + 31000 + empgrenadelauncher + This launcher can fire EMP grenades in any direction using a unique internal guidance system. These are favored by Traders for escaping from pirates. + + + EMP Grenade + 2000 + + + + + 30 + 6 + 1 + 600 + empgrenade + This is a grenade that can be launched to unleash EMP blasts rendering most electronics in the area useless. Good for taking ships alive instead of killing them as the EMP blasts rarely destroy the ship instead leaving it intact with non-working electronics. + + + empgrenade + missile + EmpS + EmpM + 1.3 + 0 + 400 + 40 + + 1 diff --git a/gfx/outfit/space/empgrenade.png b/gfx/outfit/space/empgrenade.png new file mode 100644 index 0000000..5600523 Binary files /dev/null and b/gfx/outfit/space/empgrenade.png differ diff --git a/gfx/outfit/store/empgrenade.png b/gfx/outfit/store/empgrenade.png new file mode 100644 index 0000000..a4370ce Binary files /dev/null and b/gfx/outfit/store/empgrenade.png differ diff --git a/gfx/outfit/store/empgrenadelauncher.png b/gfx/outfit/store/empgrenadelauncher.png new file mode 100644 index 0000000..7caa71d Binary files /dev/null and b/gfx/outfit/store/empgrenadelauncher.png differ diff --git a/gfx/spfx/empm.png b/gfx/spfx/empm.png new file mode 100644 index 0000000..57be2ea Binary files /dev/null and b/gfx/spfx/empm.png differ diff --git a/gfx/spfx/emps.png b/gfx/spfx/emps.png new file mode 100644 index 0000000..c215a72 Binary files /dev/null and b/gfx/spfx/emps.png differ diff --git a/scripts/ai/tpl/merchant.lua b/scripts/ai/tpl/merchant.lua index b4a4beb..7f7c609 100644 --- a/scripts/ai/tpl/merchant.lua +++ b/scripts/ai/tpl/merchant.lua @@ -21,7 +21,22 @@ function control() -- Try to jump when far enough away. elseif task == "runaway" then - if ai.dist(ai.pos(ai.target())) > 400 then + target = ai.target() + + -- Check if should still run. + if not ai.exists(target) then + ai.poptask() + return + end + + -- See if another enemy is closer. + if enemy ~= target then + ai.poptask() + ai.pushtask(0, "runaway", enemy) + end + + -- Try to jump. + if ai.dist(target) > 400 then ai.hyperspace() end diff --git a/src/outfit.c b/src/outfit.c index 7ba5884..a376e43 100644 --- a/src/outfit.c +++ b/src/outfit.c @@ -142,6 +142,11 @@ void outfit_calcDamage(double* dshield, double* darmour, double* knockback, ds = dmg*0.15; /* Still take damage, just very little. */ da = dmg; kn = 0.8; + case DAMAGE_TYPE_EMP: + ds = dmg*0.6; + da = dmg*1.3; + kn = 0.; + break; default: WARN("Unknown damage type: %d!", dtype); ds = da = kn = 0.; @@ -188,6 +193,7 @@ int outfit_isBeam(const Outfit* o) { int outfit_isLauncher(const Outfit* o) { return((o->type == OUTFIT_TYPE_MISSILE_DUMB) || + (o->type == OUTFIT_TYPE_TURRET_DUMB) || (o->type == OUTFIT_TYPE_MISSILE_SEEK) || (o->type == OUTFIT_TYPE_MISSILE_SEEK_SMART) || (o->type == OUTFIT_TYPE_MISSILE_SWARM) || @@ -203,6 +209,7 @@ int outfit_isLauncher(const Outfit* o) { */ int outfit_isAmmo(const Outfit* o) { return((o->type == OUTFIT_TYPE_MISSILE_DUMB_AMMO) || + (o->type == OUTFIT_TYPE_TURRET_DUMB_AMMO) || (o->type == OUTFIT_TYPE_MISSILE_SEEK_AMMO) || (o->type == OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO) || (o->type == OUTFIT_TYPE_MISSILE_SWARM_AMMO) || @@ -234,8 +241,9 @@ int outfit_isSeeker(const Outfit* o) { * @return 1 if o is a turret class weapon. */ int outfit_isTurret(const Outfit* o) { - return ((o->type == OUTFIT_TYPE_TURRET_BOLT) || - (o->type == OUTFIT_TYPE_TURRET_BEAM)); + return ((o->type==OUTFIT_TYPE_TURRET_BOLT) || + (o->type==OUTFIT_TYPE_TURRET_BEAM) || + (o->type==OUTFIT_TYPE_TURRET_DUMB)); } /** @@ -437,6 +445,16 @@ double outfit_speed(const Outfit* o) { return -1.; } +/** + * @brief Get the outfit animation spin. + * @param o Outfit to get information from. + */ +double outfit_spin(const Outfit* o) { + if(outfit_isBolt(o)) return o->u.blt.spin; + else if(outfit_isAmmo(o)) return o->u.amm.spin; + return -1; +} + /** * @fn const char* outfit_getType(const Outfit* o) * @@ -453,6 +471,8 @@ const char* outfit_getType(const Outfit* o) { "Beam Turret", "Dumb Missile", "Dumb Missile Ammunition", + "Projectile Turret", + "Projectile Turret Ammunition", "Seeker Missile", "Seeker Missile Ammunition", "Smart Seeker Missile", @@ -502,6 +522,7 @@ static DamageType outfit_strToDamageType(char* buf) { else if(strcmp(buf, "kinetic")==0) return DAMAGE_TYPE_KINETIC; else if(strcmp(buf, "ion")==0) return DAMAGE_TYPE_ION; else if(strcmp(buf, "radiation")==0) return DAMAGE_TYPE_RADIATION; + else if(strcmp(buf, "emp")==0) return DAMAGE_TYPE_EMP; WARN("Invalid damage type: '%s'", buf); return DAMAGE_TYPE_NULL; @@ -517,6 +538,8 @@ static OutfitType outfit_strToOutfitType(char* buf) { O_CMP("turret beam", OUTFIT_TYPE_TURRET_BEAM); O_CMP("missile dumb", OUTFIT_TYPE_MISSILE_DUMB); O_CMP("missile dumb ammo", OUTFIT_TYPE_MISSILE_DUMB_AMMO); + O_CMP("turret dumb", OUTFIT_TYPE_TURRET_DUMB); + O_CMP("turret dumb ammo", OUTFIT_TYPE_TURRET_DUMB_AMMO); O_CMP("missile seek", OUTFIT_TYPE_MISSILE_SEEK); O_CMP("missile seek ammo", OUTFIT_TYPE_MISSILE_SEEK_AMMO); O_CMP("missile smart", OUTFIT_TYPE_MISSILE_SEEK_SMART); @@ -562,7 +585,7 @@ static int outfit_parseDamage(DamageType* dtype, double* dmg, xmlNodePtr node) { /* Parses the specific area for a weapon and loads it into outfit. */ static void outfit_parseSBolt(Outfit* tmp, const xmlNodePtr parent) { xmlNodePtr node; - char str[PATH_MAX] = "\0"; + char* buf; /* Defaults. */ tmp->u.blt.spfx_armour = -1; @@ -579,8 +602,14 @@ static void outfit_parseSBolt(Outfit* tmp, const xmlNodePtr parent) { xmlr_float(node, "energy", tmp->u.blt.energy); if(xml_isNode(node, "gfx")) { - snprintf(str, PATH_MAX, OUTFIT_GFX"space/%s.png", xml_get(node)); - tmp->u.blt.gfx_space = gl_newSprite(str, 6, 6); + tmp->u.blt.gfx_space = xml_parseTexture(node, + OUTFIT_GFX"space/%s.png", 6, 6); + xmlr_attr(node, "spin", buf); + if(buf != NULL) { + outfit_setProp(tmp, OUTFIT_PROP_WEAP_SPIN); + tmp->u.blt.spin = atof(buf); + free(buf); + } continue; } if(xml_isNode(node, "spfx_shield")) { @@ -624,7 +653,6 @@ static void outfit_parseSBolt(Outfit* tmp, const xmlNodePtr parent) { */ static void outfit_parseSBeam(Outfit* tmp, const xmlNodePtr parent) { xmlNodePtr node; - char str[PATH_MAX] = "\0"; /* Defaults. */ tmp->u.bem.spfx_armour = -1; @@ -649,8 +677,8 @@ static void outfit_parseSBeam(Outfit* tmp, const xmlNodePtr parent) { /* Graphics stuff. */ if(xml_isNode(node, "gfx")) { - snprintf(str, PATH_MAX, OUTFIT_GFX"space/%s.png", xml_get(node)); - tmp->u.bem.gfx = gl_newSprite(str, 1, 1); + tmp->u.bem.gfx = xml_parseTexture(node, + OUTFIT_GFX"space/%s.png", 1, 1); continue; } @@ -719,9 +747,9 @@ static void outfit_parseSLauncher(Outfit* tmp, const xmlNodePtr parent) { /* Parse the specific area for a weapon and load it into Outfit. */ static void outfit_parseSAmmo(Outfit* tmp, const xmlNodePtr parent) { xmlNodePtr node; - node = parent->xmlChildrenNode; + char* buf; - char str[PATH_MAX] = "\0"; + node = parent->xmlChildrenNode; /* Defaults. */ tmp->u.amm.spfx_armour = -1; @@ -739,8 +767,14 @@ static void outfit_parseSAmmo(Outfit* tmp, const xmlNodePtr parent) { xmlr_float(node, "speed", tmp->u.amm.speed); xmlr_float(node, "energy", tmp->u.amm.energy); if(xml_isNode(node, "gfx")) { - snprintf(str, PATH_MAX, OUTFIT_GFX"space/%s.png", xml_get(node)); - tmp->u.amm.gfx_space = gl_newSprite(str, 6, 6); + tmp->u.amm.gfx_space = xml_parseTexture(node, + OUTFIT_GFX"space/%s.png", 6, 6); + xmlr_attr(node, "spin", buf); + if(buf != NULL) { + outfit_setProp(tmp, OUTFIT_PROP_WEAP_SPIN); + tmp->u.blt.spin = atof(buf); + free(buf); + } continue; } else if(xml_isNode(node, "spfx_armour")) @@ -761,9 +795,9 @@ static void outfit_parseSAmmo(Outfit* tmp, const xmlNodePtr parent) { MELEMENT(tmp->u.amm.spfx_shield==-1, "spfx_shield"); MELEMENT(tmp->u.amm.spfx_armour==-1, "spfx_armour"); MELEMENT((sound_disabled != 0) && (tmp->u.amm.sound < 0), "sound"); - MELEMENT(tmp->u.amm.thrust==0, "thrust"); + /*MELEMENT(tmp->u.amm.thrust==0, "thrust");*/ /* Dumb missiles don't need everything. */ - if(tmp->type != OUTFIT_TYPE_MISSILE_DUMB_AMMO) { + if(outfit_isSeeker(tmp)) { MELEMENT(tmp->u.amm.turn==0, "turn"); MELEMENT(tmp->u.amm.lockon==0, "lockon"); } diff --git a/src/outfit.h b/src/outfit.h index cdeb1e1..191466f 100644 --- a/src/outfit.h +++ b/src/outfit.h @@ -5,6 +5,7 @@ #define outfit_isProp(o,p) ((o)->properties & p) /**< Check an outfit for property. */ /* Property flags. */ #define OUTFIT_PROP_WEAP_SECONDARY (1<<0) /**< Is a secondary weapon? */ +#define OUTFIT_PROP_WEAP_SPIN (1<<1) /**< Should weapon spin around? */ struct Outfit_; @@ -23,6 +24,8 @@ typedef enum OutfitType_ { OUTFIT_TYPE_TURRET_BEAM, /**< Rotary bolt turret. */ OUTFIT_TYPE_MISSILE_DUMB, /**< Dumb missile launcher. */ OUTFIT_TYPE_MISSILE_DUMB_AMMO, /**< Dumb missile ammo. */ + OUTFIT_TYPE_TURRET_DUMB, /**< Dumb missile turret launcher. */ + OUTFIT_TYPE_TURRET_DUMB_AMMO, /**< Dumb missile turret ammo. */ OUTFIT_TYPE_MISSILE_SEEK, /**< Seeker missile launcher. */ OUTFIT_TYPE_MISSILE_SEEK_AMMO, /**< Seeker missile ammo. */ OUTFIT_TYPE_MISSILE_SEEK_SMART, /**< ATM equivalent to SEEK. */ @@ -51,7 +54,8 @@ typedef enum DamageType_ { DAMAGE_TYPE_ENERGY, /**< Energy-based weapons. */ DAMAGE_TYPE_KINETIC, /**< Physic impact weapons. */ DAMAGE_TYPE_ION, /**< Ion-based weapons. */ - DAMAGE_TYPE_RADIATION /**< Radioactive weapons. */ + DAMAGE_TYPE_RADIATION, /**< Radioactive weapons. */ + DAMAGE_TYPE_EMP /**< Electromagnetic pulse weapons. */ } DamageType; /** @@ -70,6 +74,7 @@ typedef struct OutfitBoltData_ { /* Sound and graphics. */ glTexture* gfx_space; /**< Graphic. */ + double spin; /**< Graphic spin rate. */ int sound; /**< Sound to play. */ int spfx_armour; /**< Special effect on hit. */ int spfx_shield; /**< Special effect on hit. */ @@ -134,6 +139,7 @@ typedef struct OutfitAmmoData_ { double damage; /**< Damage. */ glTexture* gfx_space; /**< Graphic. */ + double spin; /**< Graphic spin rate. */ int sound; /**< Sound to play. */ int spfx_armour; /**< Special effect on hit. */ int spfx_shield; /**< Special effect on hit. */ @@ -275,6 +281,7 @@ int outfit_isBolt(const Outfit* o); int outfit_isBeam(const Outfit* o); int outfit_isLauncher(const Outfit* o); int outfit_isAmmo(const Outfit* o); +int outfit_isSeeker(const Outfit* o); int outfit_isTurret(const Outfit* o); int outfit_isMod(const Outfit* o); int outfit_isAfterburner(const Outfit* o); @@ -288,7 +295,8 @@ const char* outfit_getTypeBroad(const Outfit* o); /* Get data from outfit. */ glTexture* outfit_gfx(const Outfit* o); -int outfit_spfx(const Outfit* o); +int outfit_spfxArmour(const Outfit* o); +int outfit_spfxShield(const Outfit* o); double outfit_damage(const Outfit* o); DamageType outfit_damageType(const Outfit* o); int outfit_delay(const Outfit* o); @@ -296,7 +304,7 @@ Outfit* outfit_ammo(const Outfit* o); double outfit_energy(const Outfit* o); double outfit_range(const Outfit* o); double outfit_speed(const Outfit* o); -int outfit_isSeeker(const Outfit* o); +double outfit_spin(const Outfit* o); /* Load/free outfit stack. */ int outfit_load(void); diff --git a/src/pilot.c b/src/pilot.c index b93ac5f..1897300 100644 --- a/src/pilot.c +++ b/src/pilot.c @@ -440,28 +440,35 @@ void pilot_hit(Pilot* p, const Solid* w, const unsigned int shooter, p->shield = 0.; dam_mod = (damage_shield+damage_armour) / (p->shield_max + p->armour_max); } - else if(p->armour-damage_armour > 0.) { + else if(p->armour > 0.) { p->armour -= damage_armour; - dam_mod = damage_armour/p->armour_max; - /* Shake us up a bit. */ - if(p->id == PLAYER_ID) - spfx_shake(dam_mod*100.); - } - else { - /* We are officially dead. */ - p->armour = 0.; - dam_mod = 0.; + /* EMP don't kill. */ + if((dtype == DAMAGE_TYPE_EMP) && + (p->armour < PILOT_DISABLED_ARMOUR * p->armour_max)) + p->armour = PILOT_DISABLED_ARMOUR * p->armour_max - 1.; - if(!pilot_isFlag(p, PILOT_DEAD)) { - pilot_dead(p); - /* Adjust the combat rating based on pilot mass and ditto faction. */ - pshooter = pilot_get(shooter); - if((pshooter != NULL) && (pshooter->faction == FACTION_PLAYER)) { - mod = sqrt(p->ship->mass) / 5; - player_crating += 2*mod; /* Crating chanes faster. */ - faction_modPlayer(p->faction, -mod); + /* Officially dead. */ + if(p->armour <= 0.) { + p->armour = 0.; + dam_mod = 0.; + + if(!pilot_isFlag(p, PILOT_DEAD)) { + pilot_dead(p); + + /* Adjust the combat rating based on pilot mass and ditto faction. */ + pshooter = pilot_get(shooter); + if((pshooter != NULL) && (pshooter->faction == FACTION_PLAYER)) { + mod = sqrt(p->ship->mass) / 5; + player_crating += 2*mod; /* Crating changes faster. */ + faction_modPlayer(p->faction, -mod); + } } + } else { /* Some minor effects and stuff. */ + dam_mod = damage_armour / p->armour_max; + + if(p->id == PLAYER_ID) /* Shake us up a bit. */ + spfx_shake(dam_mod*100.); } } diff --git a/src/spfx.c b/src/spfx.c index 2f42b7f..f7faa81 100644 --- a/src/spfx.c +++ b/src/spfx.c @@ -143,6 +143,8 @@ int spfx_load(void) { spfx_base_load("ExpM", 450, 450, "expm.png", 6, 5); spfx_base_load("ExpL", 500, 500, "expl.png", 6, 5); spfx_base_load("cargo", 15000, 5000, "cargo.png", 6, 6); + spfx_base_load("EmpS", 400, 400, "emps.png", 6, 5); + spfx_base_load("EmpM", 450, 450, "empm.png", 6, 5); return 0; } diff --git a/src/weapon.c b/src/weapon.c index 40df881..0dd086b 100644 --- a/src/weapon.c +++ b/src/weapon.c @@ -62,7 +62,8 @@ typedef struct Weapon_ { int voice; /**< Weapons voice. */ double lockon; /**< Some weapons have a lockon delay. */ double timer; /**< Mainly used to see when the weapon was fired. */ - double anim; /**< Used for beam weapon graphics. */ + double anim; /**< Used for beam weapon graphics and others. */ + int sprite; /**< USed for spinning outfits. */ /* Update position and render. */ void(*update)(struct Weapon_*, const double, WeaponLayer); /**< Update the weapon. */ @@ -345,6 +346,7 @@ static void weapons_updateLayer(const double dt, const WeaponLayer layer) { /* Purpose fallthrough. */ /* Bolts too. */ + case OUTFIT_TYPE_TURRET_DUMB_AMMO: case OUTFIT_TYPE_MISSILE_DUMB_AMMO: /* Dumb missiles are like bolts. */ limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt); case OUTFIT_TYPE_BOLT: @@ -439,10 +441,29 @@ static void weapon_render(Weapon* w, const double dt) { case OUTFIT_TYPE_BOLT: case OUTFIT_TYPE_TURRET_BOLT: case OUTFIT_TYPE_MISSILE_DUMB_AMMO: + case OUTFIT_TYPE_TURRET_DUMB_AMMO: gfx = outfit_gfx(w->outfit); - /* Get the sprite corresponding to the direction facing. */ - gl_getSpriteFromDir(&sx, &sy, gfx, w->solid->dir); - gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, sx, sy, NULL); + + /* Outfit spins around. */ + if(outfit_isProp(w->outfit, OUTFIT_PROP_WEAP_SPIN)) { + /* Check timer. */ + w->anim -= dt; + if(w->anim < 0.) { + w->anim = outfit_spin(w->outfit); + + /* Increment sprite. */ + w->sprite++; + if(w->sprite >= gfx->sx*gfx->sy) + w->sprite = 0; + } + + /* Render. */ + gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, + w->sprite % (int)gfx->sx, w->sprite / (int)gfx->sx, NULL); + } else { /* Outfit faces direction. */ + gl_getSpriteFromDir(&sx, &sy, gfx, w->solid->dir); + gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, sx, sy, NULL); + } break; /* Beam weapons. */ @@ -723,7 +744,7 @@ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2* Weapon* w; /* Create basic features. */ - w = MALLOC_L(Weapon); + w = CALLOC_L(Weapon); w->faction = pilot_get(parent)->faction; /*Non-Changeable. */ w->parent = parent; /* Non-Changeable. */ w->target = target; /* Non-Changeable. */ @@ -766,8 +787,7 @@ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2* } /* Set angle to face. */ - vect_cset(&v, x, y); - rdir = VANGLE(v); + rdir = ANGLE(x, y); } } else /* Fire straight. */ rdir = dir; @@ -830,6 +850,52 @@ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2* w->solid->pos.y + w->solid->vel.y); break; + /* Turrets are a special case. */ + case OUTFIT_TYPE_TURRET_DUMB_AMMO: + pilot_target = pilot_get(w->target); + if(pilot_target == NULL) + rdir = dir; + + else { + /* Get the distance. */ + dist = vect_dist(pos, &pilot_target->solid->pos); + + /* Aim. */ + if(dist > outfit->u.blt.range * 1.2) { + x = pilot_target->solid->pos.x - pos->x; + y = pilot_target->solid->pos.y - pos->y; + } else { + /* Try to predict where the enemy will be. */ + /* Time for shots to reach that distance. */ + t = dist / w->outfit->u.amm.speed; + + /* Position is calculated on where it should be. */ + x = (pilot_target->solid->pos.x + pilot_target->solid->vel.x*t) + - (pos->x + vel->x*t); + y = (pilot_target->solid->pos.y + pilot_target->solid->vel.y*t) + - (pos->y + vel->y*t); + } + /* Set angle to face. */ + rdir = ANGLE(x, y); + } + + /* If thrust is 0. We assume it starts out at speed. */ + vectcpy(&v, vel); + if(outfit->u.amm.thrust == 0.) + vect_cadd(&v, cos(rdir) * w->outfit->u.amm.speed, + sin(rdir) * w->outfit->u.amm.speed); + + mass = w->outfit->mass; + w->timer = outfit->u.amm.duration; + w->solid = solid_create(mass, rdir, pos, &v); + if(w->outfit->u.amm.thrust != 0.) + vect_pset(&w->solid->force, w->outfit->u.amm.thrust, rdir); + w->think = NULL; /* No AI. */ + w->voice = sound_playPos(w->outfit->u.amm.sound, + w->solid->pos.x + w->solid->vel.x, + w->solid->pos.y + w->solid->vel.y); + break; + /* Dumb missiles are a mix of missile and bolt. */ case OUTFIT_TYPE_MISSILE_DUMB_AMMO: mass = w->outfit->mass;