/* $Id: shot.c,v 5.29 2001/12/11 12:45:13 bertg Exp $ * * XPilot, a multiplayer gravity war game. Copyright (C) 1991-2001 by * * Bjørn Stabell * Ken Ronny Schouten * Bert Gijsbers * Dick Balaska * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #ifdef _WINDOWS # include "NT/winServer.h" #endif #define SERVER #include "version.h" #include "config.h" #include "serverconst.h" #include "global.h" #include "proto.h" #include "score.h" #include "saudio.h" #include "cannon.h" #include "objpos.h" #include "netserver.h" #include "error.h" #include "commonproto.h" char shot_version[] = VERSION; #define MISSILE_POWER_SPEED_FACT 0.25 #define MISSILE_POWER_TURNSPEED_FACT 0.75 #define MINI_TORPEDO_SPREAD_TIME 6 #define MINI_TORPEDO_SPREAD_SPEED 20 #define MINI_TORPEDO_SPREAD_ANGLE 90 #define MINI_MINE_SPREAD_TIME 18 #define MINI_MINE_SPREAD_SPEED 8 #define MINI_MISSILE_SPREAD_ANGLE 45 #define CONFUSED_UPDATE_GRANULARITY 10 #define CONFUSED_TIME 3 /*********************** * Functions for shots. */ void Place_mine(int ind) { player *pl = Players[ind]; if (pl->item[ITEM_MINE] <= 0 || (BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE) && !shieldedMining)) { return; } if (minMineSpeed > 0) { Place_moving_mine(ind); return; } Place_general_mine(ind, pl->team, 0, pl->pos.x, pl->pos.y, 0.0, 0.0, pl->mods); } void Place_moving_mine(int ind) { player *pl = Players[ind]; DFLOAT vx = pl->vel.x; DFLOAT vy = pl->vel.y; if (pl->item[ITEM_MINE] <= 0 || (BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE) && !shieldedMining)) { return; } if (minMineSpeed > 0) { if (pl->velocity < minMineSpeed) { if (pl->velocity >= 1) { vx *= (minMineSpeed / pl->velocity); vy *= (minMineSpeed / pl->velocity); } else { vx = minMineSpeed * tcos(pl->dir); vy = minMineSpeed * tsin(pl->dir); } } } Place_general_mine(ind, pl->team, GRAVITY, pl->pos.x, pl->pos.y, vx, vy, pl->mods); } void Place_general_mine(int ind, unsigned short team, long status, DFLOAT x, DFLOAT y, DFLOAT vx, DFLOAT vy, modifiers mods) { char msg[MSG_LEN]; player *pl = (ind == -1 ? NULL : Players[ind]); int used, life; long drain; DFLOAT mass; int i, minis; vector mv; if (NumObjs + mods.mini >= MAX_TOTAL_SHOTS) return; if (BIT(World.rules->mode, WRAP_PLAY)) { if (x < 0) x += World.width; else if (x >= World.width) x -= World.width; if (y < 0) y += World.height; else if (y >= World.height) y -= World.height; } if (x < 0 || x >= World.width || y < 0 || y >= World.height) { return; } if (pl && BIT(pl->status, KILLED)) { life = (int)(rfrac() * FPS); } else if (BIT(status, FROMCANNON)) { life = CANNON_SHOT_LIFE; } else { life = (mineLife ? mineLife : MINE_LIFETIME); } if (!BIT(mods.warhead, CLUSTER)) mods.velocity = 0; if (!mods.mini) mods.spread = 0; if (nukeMinSmarts <= 0) { CLR_BIT(mods.nuclear, NUCLEAR); } if (BIT(mods.nuclear, NUCLEAR)) { if (pl) { used = (BIT(mods.nuclear, FULLNUCLEAR) ? pl->item[ITEM_MINE] : nukeMinMines); if (pl->item[ITEM_MINE] < nukeMinMines) { sprintf(msg, "You need at least %d mines to %s %s!", nukeMinMines, (BIT(status, GRAVITY) ? "throw" : "drop"), Describe_shot (OBJ_MINE, status, mods, 0)); Set_player_message (pl, msg); return; } } else { used = nukeMinMines; } mass = MINE_MASS * used * NUKE_MASS_MULT; } else { mass = (BIT(status, FROMCANNON) ? MINE_MASS * 0.6 : MINE_MASS); used = 1; } if (pl) { drain = ED_MINE; if (BIT(mods.warhead, CLUSTER)) { drain += (long)(CLUSTER_MASS_DRAIN(mass)); } if (pl->fuel.sum < -drain) { sprintf(msg, "You need at least %ld fuel units to %s %s!", (-drain) >> FUEL_SCALE_BITS, (BIT(status, GRAVITY) ? "throw" : "drop"), Describe_shot(OBJ_MINE, status, mods, 0)); Set_player_message (pl, msg); return; } if (baseMineRange) { for (i = 0; i < NumPlayers; i++) { if (i != ind && !Team_immune(Players[i]->id, pl->id) && !IS_TANK_IND(i)) { int dx = (int)(x/BLOCK_SZ - World.base[Players[i]->home_base].pos.x); int dy = (int)(y/BLOCK_SZ - World.base[Players[i]->home_base].pos.y); if (sqr(dx) + sqr(dy) <= sqr(baseMineRange)) { Set_player_message(pl, "No base mining!"); return; } } } } Add_fuel(&(pl->fuel), drain); pl->item[ITEM_MINE] -= used; if (used > 1) { sprintf(msg, "%s has %s %s!", pl->name, (BIT(status, GRAVITY) ? "thrown" : "dropped"), Describe_shot(OBJ_MINE, status, mods, 0)); Set_message(msg); sound_play_all(NUKE_LAUNCH_SOUND); } else { sound_play_sensors(pl->pos.x, pl->pos.y, BIT(status, GRAVITY) ? DROP_MOVING_MINE_SOUND : DROP_MINE_SOUND); } } minis = (mods.mini + 1); SET_BIT(status, OWNERIMMUNE); for (i = 0; i < minis; i++) { mineobject *mine; if ((mine = MINE_PTR(Object_allocate())) == NULL) { break; } mine->type = OBJ_MINE; mine->color = BLUE; mine->info = mineFuseTime; mine->status = status; mine->id = (pl ? pl->id : NO_ID); mine->team = team; mine->owner = mine->id; Object_position_init_pixels(OBJ_PTR(mine), x, y); if (minis > 1) { int space = RES/minis; int dir; DFLOAT spread; spread = (DFLOAT)((unsigned)mods.spread + 1); /* * Dir gives (S is ship upwards); * * o o o * X2: o S o X3: S X4: S * o o o o */ dir = (i * space) + space/2 + (minis-2)*(RES/2) + (pl?pl->dir:0); dir += (int)((rfrac() - 0.5f) * space * 0.5f); dir = MOD2(dir, RES); mv.x = MINI_MINE_SPREAD_SPEED * tcos(dir) / spread; mv.y = MINI_MINE_SPREAD_SPEED * tsin(dir) / spread; /* * This causes the added initial velocity to reduce to * zero over the MINI_MINE_SPREAD_TIME. */ mine->spread_left = MINI_MINE_SPREAD_TIME; mine->acc.x = -mv.x / MINI_MINE_SPREAD_TIME; mine->acc.y = -mv.y / MINI_MINE_SPREAD_TIME; } else { mv.x = mv.y = mine->acc.x = mine->acc.y = 0.0; mine->spread_left = 0; } mine->vel = mv; mine->vel.x += vx * MINE_SPEED_FACT; mine->vel.y += vy * MINE_SPEED_FACT; mine->mass = mass / minis; mine->life = life / minis; mine->mods = mods; mine->pl_range = (int)(MINE_RANGE / minis); mine->pl_radius = MINE_RADIUS; Cell_add_object((object *) mine); } } /* * Up to and including 3.2.6 it was: * Cause all of the given player's dropped/thrown mines to explode. * Since this caused a slowdown when many mines detonated it * is changed into: * Cause the mine which is closest to a player and owned * by that player to detonate. */ void Detonate_mines(int ind) { player *pl = Players[ind]; int i; int closest = -1; DFLOAT dist; DFLOAT min_dist = World.hypotenuse + 1; if (BIT(pl->used, HAS_PHASING_DEVICE)) return; for (i = 0; i < NumObjs; i++) { object *mine = Obj[i]; if (! BIT(mine->type, OBJ_MINE)) continue; /* * Mines which have been ECM reprogrammed should only be detonatable * by the reprogrammer, not by the original mine placer: */ if (mine->id == pl->id) { dist = Wrap_length(pl->pos.x - mine->pos.x, pl->pos.y - mine->pos.y); if (dist < min_dist) { min_dist = dist; closest = i; } } } if (closest != -1) { Obj[closest]->life = 0; } return; } void Make_treasure_ball(int treasure) { ballobject *ball; treasure_t *t = &(World.treasures[treasure]); DFLOAT x = (t->pos.x + 0.5) * BLOCK_SZ, y = (t->pos.y * BLOCK_SZ) + 10; if (t->empty) return; if (t->have) { xpprintf("%s Failed Make_treasure_ball(treasure=%d):\n", showtime(), treasure); xpprintf("\ttreasure: destroyed = %d, team = %d, have = %d\n", t->destroyed, t->team, t->have); return; } if ((ball = BALL_PTR(Object_allocate())) == NULL) { return; } ball->length = ballConnectorLength; ball->life = LONG_MAX; ball->mass = ballMass; ball->vel.x = 0; /* make the ball stuck a little */ ball->vel.y = 0; /* longer to the ground */ ball->acc.x = 0; ball->acc.y = 0; Object_position_init_pixels(OBJ_PTR(ball), x, y); ball->id = NO_ID; ball->owner = NO_ID; ball->team = t->team; ball->type = OBJ_BALL; ball->color = WHITE; ball->count = 0; ball->pl_range = BALL_RADIUS; ball->pl_radius = BALL_RADIUS; CLEAR_MODS(ball->mods); ball->status = RECREATE; ball->treasure = treasure; Cell_add_object(OBJ_PTR(ball)); t->have = true; } /* * Describes shot of `type' which has `status' and `mods'. If `hit' is * non-zero this description is part of a collision, otherwise its part * of a launch message. */ char *Describe_shot(int type, long status, modifiers mods, int hit) { const char *name, *howmany = "a ", *plural = ""; static char msg[MSG_LEN]; switch (type) { case OBJ_MINE: if (BIT(status, GRAVITY)) name = "bomb"; else name = "mine"; break; case OBJ_SMART_SHOT: name = "smart missile"; break; case OBJ_TORPEDO: name = "torpedo"; break; case OBJ_HEAT_SHOT: name = "heatseeker"; break; case OBJ_CANNON_SHOT: if (BIT(mods.warhead, CLUSTER)) { howmany = ""; name = "flak"; } else { name = "shot"; } break; default: /* * Cluster shots are actual debris from a cluster explosion * so we describe it as "cluster debris". */ if (BIT(mods.warhead, CLUSTER)) { howmany = ""; name = "debris"; } else { name = "shot"; } break; } if (mods.mini && !hit) { howmany = "some "; plural = (type == OBJ_TORPEDO) ? "es" : "s"; } sprintf (msg, "%s%s%s%s%s%s%s%s%s", howmany, ((mods.velocity || mods.spread || mods.power) ? "modified " : ""), (mods.mini ? "mini " : ""), (BIT(mods.nuclear, FULLNUCLEAR) ? "full " : ""), (BIT(mods.nuclear, NUCLEAR) ? "nuclear " : ""), (BIT(mods.warhead, IMPLOSION) ? "imploding " : ""), (BIT(mods.warhead, CLUSTER) ? "cluster " : ""), name, plural); return msg; } void Fire_main_shot(int ind, int type, int dir) { player *pl = Players[ind]; DFLOAT x, y; if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE)) return; x = pl->pos.x + pl->ship->m_gun[pl->dir].x; y = pl->pos.y + pl->ship->m_gun[pl->dir].y; Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1); } void Fire_shot(int ind, int type, int dir) { player *pl = Players[ind]; if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE)) return; Fire_general_shot(ind, pl->team, 0, pl->pos.x, pl->pos.y, type, dir, pl->mods, -1); } void Fire_left_shot(int ind, int type, int dir, int gun) { player *pl = Players[ind]; DFLOAT x, y; if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE)) return; x = pl->pos.x + pl->ship->l_gun[gun][pl->dir].x; y = pl->pos.y + pl->ship->l_gun[gun][pl->dir].y; Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1); } void Fire_right_shot(int ind, int type, int dir, int gun) { player *pl = Players[ind]; DFLOAT x, y; if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE)) return; x = pl->pos.x + pl->ship->r_gun[gun][pl->dir].x; y = pl->pos.y + pl->ship->r_gun[gun][pl->dir].y; Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1); } void Fire_left_rshot(int ind, int type, int dir, int gun) { player *pl = Players[ind]; DFLOAT x, y; if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE)) return; x = pl->pos.x + pl->ship->l_rgun[gun][pl->dir].x; y = pl->pos.y + pl->ship->l_rgun[gun][pl->dir].y; Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1); } void Fire_right_rshot(int ind, int type, int dir, int gun) { player *pl = Players[ind]; DFLOAT x, y; if (pl->shots >= pl->shot_max || BIT(pl->used, HAS_SHIELD|HAS_PHASING_DEVICE)) return; x = pl->pos.x + pl->ship->r_rgun[gun][pl->dir].x; y = pl->pos.y + pl->ship->r_rgun[gun][pl->dir].y; Fire_general_shot(ind, pl->team, 0, x, y, type, dir, pl->mods, -1); } void Fire_general_shot(int ind, unsigned short team, bool cannon, DFLOAT x, DFLOAT y, int type, int dir, modifiers mods, int target) { char msg[MSG_LEN]; player *pl = (ind == -1 ? NULL : Players[ind]); int used, life = ShotsLife, fuse = 0, lock = 0, status = GRAVITY, i, ldir, minis, pl_range, pl_radius, rack_no = 0, racks_left = 0, r, on_this_rack = 0, side = 0, fired = 0; long drain; DFLOAT mass = ShotsMass, speed = ShotsSpeed, turnspeed = 0, max_speed = SPEED_LIMIT, angle, spread; vector mv; position shotpos; object *mini_objs[MODS_MINI_MAX + 1]; if (NumObjs >= MAX_TOTAL_SHOTS) return; if (!BIT(mods.warhead, CLUSTER)) mods.velocity = 0; if (!mods.mini) mods.spread = 0; if (cannon) { mass = CANNON_SHOT_MASS; life = CANNON_SHOT_LIFE; SET_BIT(status, FROMCANNON); } switch (type) { default: return; case OBJ_SHOT: CLEAR_MODS(mods); /* Shots can't be modified! */ /* FALLTHROUGH */ case OBJ_CANNON_SHOT: pl_range = pl_radius = 0; if (pl) { if (pl->fuel.sum < -ED_SHOT) return; Add_fuel(&(pl->fuel), (long)(ED_SHOT)); sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_SHOT_SOUND); pl->shots++; } if (!ShotsGravity) { CLR_BIT(status, GRAVITY); } break; case OBJ_SMART_SHOT: case OBJ_HEAT_SHOT: if ((type == OBJ_HEAT_SHOT) ? !allowHeatSeekers : !allowSmartMissiles) { if (allowTorpedoes) { type = OBJ_TORPEDO; } else { return; } } /* FALLTHROUGH */ case OBJ_TORPEDO: /* * Make sure there are enough object entries for the mini shots. */ if (NumObjs + mods.mini >= MAX_TOTAL_SHOTS) return; if (pl && pl->item[ITEM_MISSILE] <= 0) return; if (nukeMinSmarts <= 0) { CLR_BIT(mods.nuclear, NUCLEAR); } if (BIT(mods.nuclear, NUCLEAR)) { if (pl) { used = (BIT(mods.nuclear, FULLNUCLEAR) ? pl->item[ITEM_MISSILE] : nukeMinSmarts); if (pl->item[ITEM_MISSILE] < nukeMinSmarts) { sprintf(msg, "You need at least %d missiles to fire %s!", nukeMinSmarts, Describe_shot (type, status, mods, 0)); Set_player_message (pl, msg); return; } } else { used = nukeMinSmarts; } mass = MISSILE_MASS * used * NUKE_MASS_MULT; pl_range = (type == OBJ_TORPEDO) ? (int)NUKE_RANGE : MISSILE_RANGE; } else { mass = MISSILE_MASS; used = 1; pl_range = (type == OBJ_TORPEDO) ? (int)TORPEDO_RANGE : MISSILE_RANGE; } pl_range /= mods.mini + 1; pl_radius = MISSILE_LEN; drain = used * ED_SMART_SHOT; if (BIT(mods.warhead, CLUSTER)) { if (pl) drain += (long)(CLUSTER_MASS_DRAIN(mass)); } if (pl && BIT(pl->status, KILLED)) { life = (int)(rfrac() * FPS); } else if (!cannon) { life = (missileLife ? missileLife : MISSILE_LIFETIME); } switch (type) { case OBJ_HEAT_SHOT: #ifndef HEAT_LOCK lock = -1; #else /* HEAT_LOCK */ if (pl == NULL) { lock = target; } else { if (!BIT(pl->lock.tagged, LOCK_PLAYER) || ((pl->lock.distance > pl->sensor_range) && BIT(World.rules->mode, LIMITED_VISIBILITY))) { lock = -1; } else { lock = pl->lock.pl_id; } } #endif /* HEAT_LOCK */ if (pl) { sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_HEAT_SHOT_SOUND); } max_speed = SMART_SHOT_MAX_SPEED * HEAT_SPEED_FACT; turnspeed = SMART_TURNSPEED * HEAT_SPEED_FACT; speed *= HEAT_SPEED_FACT; break; case OBJ_SMART_SHOT: if (pl == NULL) { lock = target; } else { if (!BIT(pl->lock.tagged, LOCK_PLAYER) || ((pl->lock.distance > pl->sensor_range) && BIT(World.rules->mode, LIMITED_VISIBILITY)) || !pl->visibility[GetInd[pl->lock.pl_id]].canSee) return; lock = pl->lock.pl_id; } max_speed = SMART_SHOT_MAX_SPEED; turnspeed = SMART_TURNSPEED; break; case OBJ_TORPEDO: lock = -1; fuse = 8; break; } if (pl) { if (pl->fuel.sum < -drain) { sprintf(msg, "You need at least %ld fuel units to fire %s!", (-drain) >> FUEL_SCALE_BITS, Describe_shot(type, status, mods, 0)); Set_player_message (pl, msg); return; } Add_fuel(&(pl->fuel), drain); pl->item[ITEM_MISSILE] -= used; if (used > 1) { sprintf(msg, "%s has launched %s!", pl->name, Describe_shot(type, status, mods, 0)); Set_message(msg); sound_play_all(NUKE_LAUNCH_SOUND); } else if (type == OBJ_SMART_SHOT) { sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_SMART_SHOT_SOUND); } else if (type == OBJ_TORPEDO) { sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_TORPEDO_SOUND); } } break; } minis = (mods.mini + 1); speed *= (1 + (mods.power * MISSILE_POWER_SPEED_FACT)); max_speed *= (1 + (mods.power * MISSILE_POWER_SPEED_FACT)); turnspeed *= (1 + (mods.power * MISSILE_POWER_TURNSPEED_FACT)); spread = (DFLOAT)((unsigned)mods.spread + 1); /* * Calculate the maximum time it would take to cross one ships width, * don't fuse the shot/missile/torpedo for the owner only until that * time passes. This is a hack to stop various odd missile and shot * mounting points killing the player when they're firing. */ fuse += (int)((2.0 * (DFLOAT)SHIP_SZ) / speed + 1.0); /* * Missile Racks and Spread * ------------------------ * * A short story by H. J. Thompson * * Once upon a time, back in the "good old days" of XPilot, it was * relatively easy thing to remember the few keys needed to fly and shoot. * It was the day of Sopwith Camels biplanes, albeit triangular ones, * doing close to-the-death machine gun combat with other triangular * Red Barons, the hard vacuum of space whistling silently by as only * something that doesn't exist could do (this was later augmented by * artificial aural feedback devices on certain advanced hardware). * * Eventually the weapon designers came up with "smart" missiles, and * another key was added to the control board, causing one missile to * launch straight forwards from the front of the triangular ship. * Soon other types of missiles were added, including "heat" seekers, * and fast straight travelling "torpedoes" (hark, is that the sonorous * ping-ping-ping of sonar equipment I hear?). * * Then one day along came a certain fellow who thought, among other * things, that it would be neat to fire up to four missiles with one * key press, just so the enemy pilot would be scared witless by the * sudden appearance of four missiles hot on their tail. To make things * fair these "mini" missiles would have the same total damage of a * normal missile, but would travel at the speed of a normal missile. * * However this fellow mused that simply launching all the missiles in * the same direction and from the same point would cause the missiles * to appear on top of each other. Thus he added code to "spread" the * missiles out at various angular offsets from the ship. Indeed the * angular offsets could be controlled using a spread modifier, and yet * more keys appeared on a now crowded control desk. * * Interestingly the future would see the same fellow adding a two seater * variant of the standard single seater ship, allowing one person * to concentrate on flying the ship, while another could flick through * out-of-date manuals searching for the right key combinations on * the now huge console which would launch four full nuclear slow-cluster * imploding mini super speed close spread torpedoes at the currently * targetted enemy, and then engage emergency thrust and shields before * the ominous looking tri-winged dagger ship recoiled at high velocity * into a rocky wall half way across the other side of the universe. * * Back to our story, and this same fellow was musing at the design of * multiple "mini" missiles, and noted that the angle of launch would * also require a different launch point on the ship (actually it was * the same position as if the front of the ship was rotated to point in * the direction of missile launch, mainly because it was easier to * write the launch/guidance computer software that way). * * Later, some artistically (or sadistically) minded person decided that * triangular ships just didn't look good (even though they were very * spatially dynamic, cheap and easy to build), and wouldn't it be just * fantastic if one could have a ship shaped like a banana! Sensibly, * however, he restricted missiles and guns to the normal single frontal * launching point. * * A few weeks later, somebody else decided how visually pleasing it * would be if one could design where missiles could be fired from by * adding "missile rack" points on the ship. Up to four racks were * available, and missiles would fire from exactly these points on the * ship. Since one to four missiles could be fired in one go, the * combinations with various ship designs were numerous (16). * * What would happen if somebody fired four missiles in one go, from a * ship that only had three missile racks? How about two missiles from * one with four racks? Sadly the missile launch software hadn't been * designed to take this sort of thing into account, and incredibly the * original programmer wasn't notified until after First Customer Ship * [sic], the launch software only slightly modified by the ship * designer, who didn't know the first thing about launch acceleration * curves or electronic owner immunity fuse timers. * * Pilots found their missiles were being fired from random points and * in sometimes very odd directions, occasionally even destroying the * ship without trace, severely annoying the ship's owners and several * insurance underwriters. Not soon after several ship designers were * mysteriously killed in a freak "accident" involving a stray nuclear * cluster bomb, and the remaining ship designers became very careful * to place missile racks and extra gun turrets well away from the * ship's superstructure. * * The original programmer who invented multiple "mini" spreading * missiles quickly decided to revisit his code before any "accidents" * came his way, and spent a good few hours making sure one couldn't * shoot oneself in the "foot", and that missiles where launched in some * reasonable and sensible directions based on the position of the * missile racks. * * How It Actually Works * --------------------- * * The first obstacle is getting the right number of missiles fired * from each combination of missile rack configurations; * * * Minis 1 2 3 4 * Racks * 1 1 2 3 4 * * 2 1/- 1/1 2/1 2/2 * -/1 1/2 * * 3 1/-/- 1/1/- 1/1/1 2/1/1 * -/1/- -/1/1 1/2/1 * -/-/1 1/-/1 1/1/2 * * 4 1/-/-/- 1/1/-/- 1/1/1/- 1/1/1/1 * -/1/-/- -/1/1/- -/1/1/1 * -/-/1/- -/-/1/1 1/-/1/1 * -/-/-/1 1/-/-/1 1/1/-/1 * * To read; For example with 2 Minis and 3 Racks, the first round will * fire 1/1/-, which is one missile from left and middle racks. The * next time fired will be -/1/1; middle and right, next fire is * 1/-/1; left and right. Next time goes to the beggining state. * * Comment Point 1 * --------------- * * The *starting* rack number for each salvo cycles through the number * of missiles racks. This is stored in the player variable * `pl->missile_rack', and is only incremented after each salvo (not * after each mini missile is fired). This value is used to initialise * `rack_no', which stores the current rack that missiles are fired from. * * `on_this_rack' is computed to be the number of missiles that will be * fired from `rack_no', and `r' is used as a counter to this value. * * `racks_left' count how many unused missiles racks are left on the ship * in this mini missile salvo. * * Comment Point 2 * --------------- * * When `r' reaches `on_this_rack' all the missiles have been fired for * this rack, and the next rack should be used. `rack_no' is incremented * modulo the number of available racks, and `racks_left' is decremented. * At this point `on_this_rack' is recomputed for the next rack, and `r' * reset to zero. Thus initially these two variables are both zero, and * `rack_no' is one less, such that these variables can be computed inside * the loop to make the code simpler. * * The computation of `on_this_rack' is as follows; Given that there * are M missiles and R racks remaining; * * on_this_rack = int(M / R); (ie. round down to lowest int) * * Then; * * (M - on_this_rack) / (R - 1) < (M / R). * * That is, the number of missiles fired on the next rack will be * more precise, and trivially can be seen that when R is 1, will * give an exact number of missiles to fire on the last rack. * * In the code `M' is (minis - i), and `R' is racks_left. * * Comment Point 3 * --------------- * * In order that multiple missiles fired from one rack do not conincide, * each missile has to be "spread" based on the number of missiles * fired from this rack point. * * This is computed similar to the wide shot code; * * angle = (N - 1 - 2 * i) / (N - 1) * * Where N is the number of shots/missiles to be fired, and i is a counter * from 0 .. N-1. * * i 0 1 2 3 * N * 1 0 * 2 1 -1 * 3 1 0 -1 * 4 1 0.333 -0.333 -1 * * In this code `N' is `on_this_rack'. * * Also the position of the missile rack from the center line of the * ship (stored in `side') has a linear effect on the angle, such that * a point farthest from the center line contributes the largest angle; * * angle += (side / SHIP_SZ) * * Since the eventual `angle' value used in the code should be a * percentage of the unmodified launch angle, it should be ranged between * -1.00 and +1.00, and thus the first angle is reduced by 33% and the * second by 66%. * * Contact: harveyt@sco.com */ if (pl && type != OBJ_SHOT) { /* * Initialise missile rack spread variables. (See Comment Point 1) */ on_this_rack = 0; racks_left = pl->ship->num_m_rack; rack_no = pl->missile_rack - 1; if (++pl->missile_rack >= pl->ship->num_m_rack) pl->missile_rack = 0; } for (r = 0, i = 0; i < minis; i++, r++) { object *shot; if ((shot = Object_allocate()) == NULL) { break; } shot->life = life / minis; shot->fuselife = shot->life - fuse; shot->mass = mass / minis; shot->count = 0; shot->info = lock; shot->type = type; shot->id = (pl ? pl->id : NO_ID); shot->team = team; shot->color = (pl ? pl->color : WHITE); if (BIT(shot->type, OBJ_TORPEDO | OBJ_HEAT_SHOT | OBJ_SMART_SHOT)) { MISSILE_PTR(shot)->turnspeed = turnspeed; MISSILE_PTR(shot)->max_speed = max_speed; } shotpos.x = x; shotpos.y = y; if (pl && type != OBJ_SHOT) { if (r == on_this_rack) { /* * We've fired all the mini missiles for the current rack, * we now move onto the next one. (See Comment Point 2) */ on_this_rack = (minis - i) / racks_left--; if (on_this_rack < 1) on_this_rack = 1; if (++rack_no >= pl->ship->num_m_rack) rack_no = 0; r = 0; } shotpos.x += pl->ship->m_rack[rack_no][pl->dir].x; shotpos.y += pl->ship->m_rack[rack_no][pl->dir].y; side = (int)(pl->ship->m_rack[rack_no][0].y); } shotpos.x = WRAP_XPIXEL(shotpos.x); shotpos.y = WRAP_YPIXEL(shotpos.y); if (shotpos.x < 0 || shotpos.x >= World.width || shotpos.y < 0 || shotpos.y >= World.height) { continue; } Object_position_init_pixels(shot, shotpos.x, shotpos.y); if (type == OBJ_SHOT || !pl) { angle = 0.0; } else { /* * Calculate the percentage unmodified launch angle for missiles. * (See Comment Point 3). */ if (on_this_rack <= 1) { angle = 0.0; } else { angle = (DFLOAT)(on_this_rack - 1 - 2 * r); angle /= (3.0 * (DFLOAT)(on_this_rack - 1)); } angle += (DFLOAT)(2 * side) / (DFLOAT)(3 * SHIP_SZ); } /* * Torpedoes spread like mines, except the launch direction * is preset over the range +/- MINI_TORPEDO_SPREAD_ANGLE. * (This is not modified by the spread, the initial velocity is) * * Other missiles are just launched in a different direction * which varies over the range +/- MINI_MISSILE_SPREAD_ANGLE, * which the spread modifier varies. */ switch (type) { case OBJ_TORPEDO: angle *= (MINI_TORPEDO_SPREAD_ANGLE / 360.0) * RES; ldir = MOD2(dir + (int)angle, RES); mv.x = MINI_TORPEDO_SPREAD_SPEED * tcos(ldir) / spread; mv.y = MINI_TORPEDO_SPREAD_SPEED * tsin(ldir) / spread; /* * This causes the added initial velocity to reduce to * zero over the MINI_TORPEDO_SPREAD_TIME. * FIX: torpedoes should have the same speed * regardless of minification. */ TORP_PTR(shot)->spread_left = MINI_TORPEDO_SPREAD_TIME; shot->acc.x = -mv.x / MINI_TORPEDO_SPREAD_TIME; shot->acc.y = -mv.y / MINI_TORPEDO_SPREAD_TIME; ldir = dir; break; default: angle *= (MINI_MISSILE_SPREAD_ANGLE / 360.0) * RES / spread; ldir = MOD2(dir + (int)angle, RES); mv.x = mv.y = shot->acc.x = shot->acc.y = 0; break; } shot->vel.x = mv.x + (pl ? pl->vel.x : 0.0) + tcos(ldir) * speed; shot->vel.y = mv.y + (pl ? pl->vel.y : 0.0) + tsin(ldir) * speed; shot->status = status; shot->missile_dir = ldir; shot->mods = mods; shot->pl_range = pl_range; shot->pl_radius = pl_radius; Cell_add_object(shot); mini_objs[fired] = shot; fired++; } /* * Recoil must be done instantaneously otherwise ship moves back after * firing each mini missile. */ if (pl) { for (i = 0; i < fired; i++) { Recoil((object *)pl, mini_objs[i]); } } } void Fire_normal_shots(int ind) { player *pl = Players[ind]; int i, shot_angle; if (frame_loops < pl->shot_time + fireRepeatRate) { return; } pl->shot_time = frame_loops; shot_angle = MODS_SPREAD_MAX - pl->mods.spread; Fire_main_shot(ind, OBJ_SHOT, pl->dir); for (i = 0; i < pl->item[ITEM_WIDEANGLE]; i++) { if (pl->ship->num_l_gun > 0) { Fire_left_shot(ind, OBJ_SHOT, MOD2(pl->dir + (1 + i) * shot_angle, RES), i % pl->ship->num_l_gun); } else { Fire_main_shot(ind, OBJ_SHOT, MOD2(pl->dir + (1 + i) * shot_angle, RES)); } if (pl->ship->num_r_gun > 0) { Fire_right_shot(ind, OBJ_SHOT, MOD2(pl->dir - (1 + i) * shot_angle, RES), i % pl->ship->num_r_gun); } else { Fire_main_shot(ind, OBJ_SHOT, MOD2(pl->dir - (1 + i) * shot_angle, RES)); } } for (i = 0; i < pl->item[ITEM_REARSHOT]; i++) { if ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) < 0) { if (pl->ship->num_l_rgun > 0) { Fire_left_rshot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2, RES), (i - (pl->item[ITEM_REARSHOT] + 1) / 2) % pl->ship->num_l_rgun); } else { Fire_shot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2, RES)); } } if ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) > 0) { if (pl->ship->num_r_rgun > 0) { Fire_right_rshot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2, RES), (pl->item[ITEM_REARSHOT] / 2 - i - 1) % pl->ship->num_r_rgun); } else { Fire_shot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2, RES)); } } if ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) == 0) Fire_shot(ind, OBJ_SHOT, MOD2(pl->dir + RES/2 + ((pl->item[ITEM_REARSHOT] - 1 - 2 * i) * shot_angle) / 2, RES)); } } /* Removes shot from array */ void Delete_shot(int ind) { object *shot = Obj[ind]; /* Used when swapping places */ ballobject *ball; player *pl; int addMine = 0; int addHeat = 0; int addBall = 0; modifiers mods; long status; int i; int intensity; int type, color; DFLOAT modv, speed_modv, life_modv, num_modv; DFLOAT mass; switch (shot->type) { case OBJ_SPARK: case OBJ_DEBRIS: case OBJ_WRECKAGE: break; case OBJ_ASTEROID: Break_asteroid(ind); break; case OBJ_BALL: ball = BALL_PTR(shot); if (ball->id != NO_ID) Detach_ball(GetInd[ball->id], ind); else { /* * Maybe some player is still busy trying to connect to this ball. */ for (i = 0; i < NumPlayers; i++) { if (Players[i]->ball == ball) { Players[i]->ball = NULL; } } } if (ball->owner == NO_ID) { /* * If the ball has never been owned, the only way it could * have been destroyed is by being knocked out of the goal. * Therefore we force the ball to be recreated. */ World.treasures[ball->treasure].have = false; SET_BIT(ball->status, RECREATE); } if (BIT(ball->status, RECREATE)) { addBall = 1; if (BIT(ball->status, NOEXPLOSION)) break; sound_play_sensors(ball->pos.x, ball->pos.y, EXPLODE_BALL_SOUND); Make_debris( /* pos.x, pos.y */ ball->prevpos.x, ball->prevpos.y, /* vel.x, vel.y */ ball->vel.x, ball->vel.y, /* owner id */ ball->id, /* owner team */ ball->team, /* kind */ OBJ_DEBRIS, /* mass */ DEBRIS_MASS, /* status */ GRAVITY, /* color */ RED, /* radius */ 8, /* min,max debris */ 10, 20, /* min,max dir */ 0, RES-1, /* min,max speed */ 10, 50, /* min,max life */ 10, 2*(FPS+15) ); } break; /* Shots related to a player. */ case OBJ_MINE: case OBJ_HEAT_SHOT: case OBJ_TORPEDO: case OBJ_SMART_SHOT: case OBJ_CANNON_SHOT: if (shot->mass == 0) { break; } status = GRAVITY; if (shot->type == OBJ_MINE) { status |= COLLISIONSHOVE; } if (BIT(shot->status, FROMCANNON)) { status |= FROMCANNON; } if (BIT(shot->mods.nuclear, NUCLEAR)) { sound_play_all(NUKE_EXPLOSION_SOUND); } else if (BIT(shot->type, OBJ_MINE)) { sound_play_sensors(shot->pos.x, shot->pos.y, MINE_EXPLOSION_SOUND); } else { sound_play_sensors(shot->pos.x, shot->pos.y, OBJECT_EXPLOSION_SOUND); } if (BIT(shot->mods.warhead, CLUSTER)) { type = OBJ_SHOT; if (shot->id != NO_ID) { player *pl = Players[GetInd[shot->id]]; color = pl->color; } else { color = WHITE; } mass = ShotsMass; mass *= 3; modv = 1 << shot->mods.velocity; num_modv = 4; if (BIT(shot->mods.nuclear, NUCLEAR)) { modv *= 4.0f; num_modv = 1; } life_modv = modv * 0.20f; speed_modv = 1.0f / modv; intensity = (int)CLUSTER_MASS_SHOTS(shot->mass); } else { type = OBJ_DEBRIS; color = RED; mass = DEBRIS_MASS; modv = 1; num_modv = 1; life_modv = modv; speed_modv = modv; if (shot->type == OBJ_MINE) { intensity = 512; } else { intensity = 32; } /* * Writing it like this: * num_modv /= (shot->mods.mini + 1); * triggers a bug in HP C A.09.19. */ num_modv = num_modv / ((DFLOAT)(unsigned)shot->mods.mini + 1.0f); } if (BIT(shot->mods.nuclear, NUCLEAR)) { DFLOAT nuke_factor; if (shot->type == OBJ_MINE) { nuke_factor = NUKE_MINE_EXPL_MULT * shot->mass / MINE_MASS; } else { nuke_factor = NUKE_SMART_EXPL_MULT * shot->mass / MISSILE_MASS; } nuke_factor = (nuke_factor * (shot->mods.mini + 1)) / SHOT_MULT(shot); intensity = (int)(intensity * nuke_factor); } if (BIT(shot->mods.warhead, IMPLOSION)) { /*intensity >>= 1;*/ mass = -mass; } if (BIT(shot->type, OBJ_TORPEDO|OBJ_HEAT_SHOT|OBJ_SMART_SHOT)) { intensity /= (1 + shot->mods.power); } Make_debris( /* pos.x, pos.y */ shot->prevpos.x, shot->prevpos.y, /* vel.x, vel.y */ shot->vel.x, shot->vel.y, /* owner id */ shot->id, /* owner team */ shot->team, /* kind */ type, /* mass */ mass, /* status */ status, /* color */ color, /* radius */ 6, /* min,max debris */ (int)(0.20f * intensity * num_modv), (int)(0.30f * intensity * num_modv), /* min,max dir */ 0, RES-1, /* min,max speed */ 20 * speed_modv, (intensity >> 2) * speed_modv, /* min,max life */ (int)(8 * life_modv), (int)((intensity >> 1) * life_modv) ); break; case OBJ_SHOT: if (shot->id == NO_ID || BIT(shot->status, FROMCANNON) || BIT(shot->mods.warhead, CLUSTER)) { break; } pl = Players[GetInd[shot->id]]; if (--pl->shots <= 0) { pl->shots = 0; } break; /* Special items. */ case OBJ_ITEM: switch (shot->info) { case ITEM_MISSILE: if (shot->life == 0 && shot->color != WHITE) { shot->color = WHITE; shot->life = FPS * WARN_TIME; return; } if (shot->life == 0 && rfrac() < rogueHeatProb) { addHeat = 1; } break; case ITEM_MINE: if (!shot->life && shot->color != WHITE) { shot->color = WHITE; shot->life = FPS * WARN_TIME; return; } if (shot->life == 0 && rfrac() < rogueMineProb) { addMine = 1; } break; } World.items[shot->info].num--; break; default: xpprintf("%s Delete_shot(): Unkown shot type %d.\n", showtime(), shot->type); printf("%s Delete_shot(): Unkown shot type %d.\n", showtime(), shot->type); break; } Cell_remove_object(shot); shot->life = 0; shot->type = 0; shot->mass = 0; Object_free_ind(ind); if (addMine | addHeat) { CLEAR_MODS(mods); if (BIT(World.rules->mode, ALLOW_CLUSTERS) && (rfrac() <= 0.333f)) { SET_BIT(mods.warhead, CLUSTER); } if (BIT(World.rules->mode, ALLOW_MODIFIERS) && (rfrac() <= 0.333f)) { SET_BIT(mods.warhead, IMPLOSION); } if (BIT(World.rules->mode, ALLOW_MODIFIERS)) { mods.velocity = (int)(rfrac() * (MODS_VELOCITY_MAX + 1)); } if (BIT(World.rules->mode, ALLOW_MODIFIERS)) { mods.power = (int)(rfrac() * (MODS_POWER_MAX + 1)); } if (addMine) { long gravity_status = ((rfrac() < 0.5f) ? GRAVITY : 0); Place_general_mine(-1, TEAM_NOT_SET, gravity_status, shot->pos.x, shot->pos.y, 0.0, 0.0, mods); } else if (addHeat) { Fire_general_shot(-1, TEAM_NOT_SET, 0, shot->pos.x, shot->pos.y, OBJ_HEAT_SHOT, (int)(rfrac() * RES), mods, -1); } } else if (addBall) { ball = BALL_PTR(shot); Make_treasure_ball(ball->treasure); } } void Fire_laser(int ind) { player *pl = Players[ind]; DFLOAT x, y; if (pl->item[ITEM_LASER] > pl->num_pulses && pl->velocity < PULSE_SPEED - PULSE_SAMPLE_DISTANCE) { if (pl->fuel.sum <= -ED_LASER) { CLR_BIT(pl->used, HAS_LASER); } else { x = pl->pos.x + pl->ship->m_gun[pl->dir].x + pl->vel.x; y = pl->pos.y + pl->ship->m_gun[pl->dir].y + pl->vel.y; x = WRAP_XPIXEL(x); y = WRAP_YPIXEL(y); if (x >= 0 && x < World.width && y >= 0 && y < World.height) { Fire_general_laser(ind, pl->team, x, y, pl->dir, pl->mods); } } } } void Fire_general_laser(int ind, unsigned short team, DFLOAT x, DFLOAT y, int dir, modifiers mods) { player *pl = ((ind == -1) ? NULL : Players[ind]); pulse_t *pulse; int life; if (pl) { Add_fuel(&(pl->fuel), (long)ED_LASER); sound_play_sensors(x, y, FIRE_LASER_SOUND); life = (int)PULSE_LIFE(pl->item[ITEM_LASER]); } else { life = (int)PULSE_LIFE(CANNON_PULSES); } if (NumPulses >= MAX_TOTAL_PULSES) { return; } Pulses[NumPulses] = (pulse_t *)malloc(sizeof(pulse_t)); if (Pulses[NumPulses] == NULL) { return; } pulse = Pulses[NumPulses]; pulse->id = (pl ? pl->id : NO_ID); pulse->team = team; pulse->dir = dir; pulse->len = PULSE_LENGTH; pulse->life = life; pulse->mods = mods; pulse->refl = false; pulse->pos.x = x - PULSE_SPEED * tcos(dir); pulse->pos.y = y - PULSE_SPEED * tsin(dir); NumPulses++; if (pl) pl->num_pulses++; } void Move_ball(int ind) { #ifdef ORIGINAL_BALL /* * This is the original ball code from XPilot versions 2.0 till 3.3.1. * The `feature' which some people got dissatisfied with * is that trying to connect to a fast moving ball may result * in being launched with high speed into a wall. * Some like that feature reasoning that making everything * easy is boring. Hence keeping the old code around. * It can be enabled by adding -DORIGINAL_BALL to the compilation flags. */ ballobject *ball = BALL_IND(ind); player *pl = Players[ GetInd[ball->id] ]; vector F; const DFLOAT k = 10.0, a = 0.01, l = Wrap_length(pl->pos.x - ball->pos.x, pl->pos.y - ball->pos.y), c = k * (1.0 - ballConnectorLength / l) - a * ABS(ball->length - l) * (ball->length - l); if (l > ballConnectorLength * (1.00 + maxBallConnectorRatio) || l < ballConnectorLength * (1.00 - maxBallConnectorRatio)) { Detach_ball(GetInd[ball->id], ind); return; } F.x = WRAP_DX(pl->pos.x - ball->pos.x) * c; F.y = WRAP_DY(pl->pos.y - ball->pos.y) * c; pl->vel.x -= F.x/pl->mass; pl->vel.y -= F.y/pl->mass; ball->vel.x += F.x/ball->mass; ball->vel.y += F.y/ball->mass; ball->length = l; #else /* ORIGINAL_BALL */ /* * The new ball movement code since XPilot version 3.4.0 as made * by Bretton Wade. The code was submitted in context diff format * by Mark Boyns. Here is a an excerpt from a post in * rec.games.computer.xpilot by Bretton Wade dated 27 Jun 1995: * * If I'm not mistaken (not having looked very closely at the code * because I wasn't sure what it was trying to do), the original move_ball * routine was trying to model a Hook's law spring, but squared the * deformation term, which would lead to exagerated behavior as the spring * stretched too far. Not really a divide by zero, but effectively producing * large numbers. * * When I coded up the spring myself, I found that I could recreate the * effect by using a VERY strong spring. This can be defeated, however, by * damping. Specifically, If you compute the critical damping factor, then * you could have the cable always be the correct length. This makes me * wonder how to decide when the cable snaps. * * I chose a relatively strong spring, and a small damping factor, to make * for a nice realistic bounce when you grab at the treasure. It also gives a * fairley close approximation to the "normal" feel of the treasure. * * I modeled the cable as having zero mass, or at least insignificant mass as * compared to the ship and ball. This greatly simplifies the math, and leads * to the conclusion that there will be no change in velocity when the cable * breaks. You can check this by integrating the momentum along the cable, * and the ship or ball. * * If you assume that the cable snaps in the middle, then half of the * potential energy goes to each object attached. However, as you said, the * total momentum of the system cannot change. Because the weight of the * cable is small, the vast majority of the potential energy will become * heat. I've had two physicists verify this for me, and they both worked * really hard on the problem because they found it interesting. * * End of post. * * Changes since then: * * Comment from people was that the string snaps too soon. * Changed the value (max_spring_ratio) at which the string snaps * from 0.25 to 0.30. Not sure if that helps enough, or too much. */ ballobject *ball = BALL_IND(ind); player *pl = Players[ GetInd[ball->id] ]; vector D; DFLOAT length, force, ratio, accell, cosine; DFLOAT pl_damping, ball_damping; /* const DFLOAT k = 1500.0, b = 2.0; */ /* const DFLOAT max_spring_ratio = 0.30; */ /* compute the normalized vector between the ball and the player */ D.x = WRAP_DX(pl->pos.x - ball->pos.x); D.y = WRAP_DY(pl->pos.y - ball->pos.y); length = VECTOR_LENGTH(D); if (length > 0.0) { D.x /= length; D.y /= length; } else D.x = D.y = 0.0; /* compute the ratio for the spring action */ ratio = (ballConnectorLength - length) / ballConnectorLength; /* compute force by spring for this length */ force = ballConnectorSpringConstant * ratio; /* If we have string-style connectors then it is allowed to be * shorted than its natural length. */ if (connectorIsString && ratio > 0.0) return; /* if the tether is too long or too short, release it */ if (ABS(ratio) > maxBallConnectorRatio) { Detach_ball(GetInd[ball->id], ind); return; } ball->length = length; /* compute damping for player */ cosine = (pl->vel.x * D.x) + (pl->vel.y * D.y); pl_damping = -ballConnectorDamping * cosine; /* compute damping for ball */ cosine = (ball->vel.x * -D.x) + (ball->vel.y * -D.y); ball_damping = -ballConnectorDamping * cosine; /* compute accelleration for player, assume t = 1 */ accell = (force + pl_damping + ball_damping) / pl->mass; pl->vel.x += D.x * accell; pl->vel.y += D.y * accell; /* compute accelleration for ball, assume t = 1 */ accell = (force + ball_damping + pl_damping) / ball->mass; ball->vel.x += -D.x * accell; ball->vel.y += -D.y * accell; #endif /* ORIGINAL_BALL */ } void Move_smart_shot(int ind) { missileobject *shot = MISSILE_IND(ind); player *pl; int angle, theta; DFLOAT range = 0.0; DFLOAT acc; DFLOAT x_dif = 0.0; DFLOAT y_dif = 0.0; DFLOAT shot_speed; if (shot->type == OBJ_TORPEDO) { torpobject *torp = TORP_PTR(shot); if (BIT(torp->mods.nuclear, NUCLEAR)) { acc = (torp->info++ < NUKE_SPEED_TIME) ? NUKE_ACC : 0.0; } else { acc = (torp->info++ < TORPEDO_SPEED_TIME) ? TORPEDO_ACC : 0.0; } acc *= (1 + (torp->mods.power * MISSILE_POWER_SPEED_FACT)); if (torp->spread_left-- <= 0) { torp->acc.x = 0; torp->acc.y = 0; } torp->vel.x += acc * tcos(torp->missile_dir); torp->vel.y += acc * tsin(torp->missile_dir); return; } acc = SMART_SHOT_ACC; if (shot->type == OBJ_HEAT_SHOT) { acc = SMART_SHOT_ACC * HEAT_SPEED_FACT; if (shot->info >= 0) { /* Get player and set min to distance */ pl = Players[ GetInd[shot->info] ]; range = Wrap_length(pl->pos.x + pl->ship->engine[pl->dir].x - shot->pos.x, pl->pos.y + pl->ship->engine[pl->dir].y - shot->pos.y); } else { /* No player. Number of moves so that new target is searched */ pl = 0; shot->count = HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR; } if (pl && BIT(pl->status, THRUSTING)) { /* * Target is thrusting, * set number to moves to correct error value */ if (range < HEAT_CLOSE_RANGE) { shot->count = HEAT_CLOSE_ERROR; } else if (range < HEAT_MID_RANGE) { shot->count = HEAT_MID_ERROR; } else { shot->count = HEAT_WIDE_ERROR; } } else { shot->count++; /* Look for new target */ if ((range < HEAT_CLOSE_RANGE && shot->count > HEAT_CLOSE_TIMEOUT + HEAT_CLOSE_ERROR) || (range < HEAT_MID_RANGE && shot->count > HEAT_MID_TIMEOUT + HEAT_MID_ERROR) || shot->count > HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR) { DFLOAT l; int i; range = HEAT_RANGE * (shot->count/HEAT_CLOSE_TIMEOUT); for (i=0; istatus, THRUSTING)) continue; l = Wrap_length(p->pos.x + p->ship->engine[p->dir].x - shot->pos.x, p->pos.y + p->ship->engine[p->dir].y - shot->pos.y); /* * After burners can be detected easier; * so scale the length: */ l *= MAX_AFTERBURNER + 1 - p->item[ITEM_AFTERBURNER]; l /= MAX_AFTERBURNER + 1; if (BIT(p->have, HAS_AFTERBURNER)) l *= 16 - p->item[ITEM_AFTERBURNER]; if (l < range) { shot->info = Players[i]->id; range = l; shot->count = l < HEAT_CLOSE_RANGE ? HEAT_CLOSE_ERROR : l < HEAT_MID_RANGE ? HEAT_MID_ERROR : HEAT_WIDE_ERROR; pl = p; } } } } if (shot->info < 0) return; /* * Heat seekers cannot fly exactly, if target is far away or thrust * isn't active. So simulate the error: */ x_dif = (int)(rfrac() * 4 * shot->count); y_dif = (int)(rfrac() * 4 * shot->count); } else if (shot->type == OBJ_SMART_SHOT) { smartobject *smart = SMART_PTR(shot); if (BIT(smart->status, CONFUSED) && (!(frame_loops % CONFUSED_UPDATE_GRANULARITY) || smart->count == CONFUSED_TIME)) { if (smart->count) { smart->info = Players[(int)(rfrac() * NumPlayers)]->id; smart->count--; } else { CLR_BIT(smart->status, CONFUSED); /* range is percentage from center to periphery of ecm burst */ range = (ECM_DISTANCE - smart->ecm_range) / ECM_DISTANCE; range *= 100.0; /* * range% lock% * 100 100 * 50 75 * 0 50 */ if ((int)(rfrac() * 100) <= ((int)(range/2)+50)) { smart->info = smart->new_info; } } } pl = Players[GetInd[shot->info]]; } else { /*NOTREACHED*/ return; } /* * Use a little look ahead to fly more exact */ acc *= (1 + (shot->mods.power * MISSILE_POWER_SPEED_FACT)); if ((shot_speed = VECTOR_LENGTH(shot->vel)) < 1) shot_speed = 1; range = Wrap_length(pl->pos.x - shot->pos.x, pl->pos.y - shot->pos.y); x_dif += pl->vel.x * (range / shot_speed); y_dif += pl->vel.y * (range / shot_speed); theta = (int)Wrap_findDir(pl->pos.x + x_dif - shot->pos.x, pl->pos.y + y_dif - shot->pos.y); { DFLOAT x, y, vx, vy; int i, xi, yi, j, freemax, k, foundw; static struct { int dx, dy; } sur[8] = { {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1} }; #define BLOCK_PARTS 2 vx = shot->vel.x; vy = shot->vel.y; x = shot_speed / (BLOCK_SZ*BLOCK_PARTS); vx /= x; vy /= x; x = shot->pos.x; y = shot->pos.y; foundw = 0; for (i = SMART_SHOT_LOOK_AH; i > 0 && foundw == 0; i--) { xi = (int)((x += vx) / BLOCK_SZ); yi = (int)((y += vy) / BLOCK_SZ); if (BIT(World.rules->mode, WRAP_PLAY)) { if (xi < 0) xi += World.x; else if (xi >= World.x) xi -= World.x; if (yi < 0) yi += World.y; else if (yi >= World.y) yi -= World.y; } if (xi < 0 || xi >= World.x || yi < 0 || yi >= World.y) break; switch(World.block[xi][yi]) { case TARGET: case TREASURE: case FUEL: case FILLED: case REC_LU: case REC_RU: case REC_LD: case REC_RD: case CANNON: if (range > (SMART_SHOT_LOOK_AH-i)*(BLOCK_SZ/BLOCK_PARTS)) { if (shot_speed > SMART_SHOT_MIN_SPEED) shot_speed -= acc * (SMART_SHOT_DECFACT+1); } foundw = 1; } } i = ((int)(shot->missile_dir * 8 / RES)&7) + 8; xi = OBJ_X_IN_BLOCKS(shot); yi = OBJ_Y_IN_BLOCKS(shot); for (j=2, angle=-1, freemax=0; j>=-2; --j) { int si, xt, yt; for (si=1, k=0; si >= -1; --si) { xt = xi + sur[(i+j+si)&7].dx; yt = yi + sur[(i+j+si)&7].dy; if (xt >= 0 && xt < World.x && yt >= 0 && yt < World.y) switch (World.block[xt][yt]) { case TARGET: case TREASURE: case FUEL: case FILLED: case REC_LU: case REC_RU: case REC_LD: case REC_RD: case CANNON: if (!si) k = -32; break; default: ++k; break; } } if (k > freemax || (k == freemax && ((j == -1 && (rfrac() < 0.5f)) || j == 0 || j == 1))) { freemax = k > 2 ? 2 : k; angle = i + j; } if (k == 3 && !j) { angle = -1; break; } } if (angle >= 0) { i = angle&7; theta = (int)Wrap_findDir((yi + sur[i].dy) * BLOCK_SZ - (shot->pos.y + 2 * shot->vel.y), (xi + sur[i].dx) * BLOCK_SZ - (shot->pos.x - 2 * shot->vel.x)); #ifdef SHOT_EXTRA_SLOWDOWN if (!foundw && range > (SHOT_LOOK_AH-i) * BLOCK_SZ) { if (shot_speed > (SMART_SHOT_MIN_SPEED + SMART_SHOT_MAX_SPEED)/2) shot_speed -= SMART_SHOT_DECC+SMART_SHOT_ACC; } #endif } } angle = theta; if (angle < 0) angle += RES; angle %= RES; if (angle < shot->missile_dir) angle += RES; angle = angle - shot->missile_dir - RES/2; if (angle < 0) { shot->missile_dir += (u_byte)(((-angle < shot->turnspeed) ? -angle : shot->turnspeed)); } else { shot->missile_dir -= (u_byte)(((angle < shot->turnspeed) ? angle : shot->turnspeed)); } shot->missile_dir = MOD2(shot->missile_dir, RES); /* NOTE!!!! */ if (shot_speed < shot->max_speed) shot_speed += acc; /* shot->velocity = MIN(shot->velocity, shot->max_speed); */ shot->vel.x = tcos(shot->missile_dir) * shot_speed; shot->vel.y = tsin(shot->missile_dir) * shot_speed; } void Move_mine(int ind) { mineobject *mine = MINE_IND(ind); if (BIT(mine->status, CONFUSED) && --mine->count <= 0) CLR_BIT(mine->status, CONFUSED); if (BIT(mine->status, OWNERIMMUNE) && mine->info) { if (--mine->info <= 0) CLR_BIT(mine->status, OWNERIMMUNE); } if (mine->mods.mini && mine->spread_left-- <= 0) { mine->acc.x = 0; mine->acc.y = 0; } }