/* * XPilot NG, a multiplayer space war game. * * Copyright (C) 2000-2004 Uoti Urpala * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "xpserver.h" struct move_parameters mp; static char msg[MSG_LEN]; /* polygon map related stuff */ /* start */ /* Maximum line length 32767-B_CLICKS, 30000 used in checks * There's a minimum map size to avoid "too much wrapping". A bit smaller * than that would cause rare errors for fast-moving things. I haven't * bothered to figure out what the limit is. 80k x 80k clicks should * be more than enough (probably...). */ #define B_SHIFT 11 #define B_CLICKS (1 << B_SHIFT) #define B_MASK (B_CLICKS - 1) #define CUTOFF (2 * BLOCK_CLICKS) /* Not sure about the optimum value */ #define MAX_MOVE 32000 #define SEPARATION_DIST 64 /* This must be increased if the ship corners are allowed to go farther * when turning! */ /* this is big enough for asteroids of size 4 */ #define MAX_SHAPE_OFFSET (52 * CLICK) #if ((-3) / 2 != -1) || ((-3) % 2 != -1) #error "This code assumes that negative numbers round upwards." #endif struct collans { int line; int point; clvec_t moved; }; struct tl2 { int base; int x; int y; }; struct bline { clvec_t start; clvec_t delta; double c; double s; short group; }; struct blockinfo { unsigned short distance; unsigned short *lines; unsigned short *points; }; struct inside_block { short *y; short *lines; struct inside_block *next; short group; char base_value; }; struct inside_block *inside_table; struct test { double distance; int inside; struct tempy *y; struct templine *lines; }; struct test *temparray; shape_t ball_wire; #define LINEY(X, Y, BASE, ARG) (((Y)*(ARG)+(BASE))/(X)) #define SIDE(X, Y, LINE) (linet[(LINE)].delta.cy * (X) - linet[(LINE)].delta.cx * (Y)) #define SIGN(X) ((X) >= 0 ? 1 : -1) struct bline *linet; #define S_LINES 100 /* stupid hack */ struct group *groups = NULL; int num_groups = 0, max_groups = 0; struct blockinfo *blockline; unsigned short *llist; unsigned short *plist; int num_lines = 0; int num_polys = 0; int mapx, mapy; static inline bool can_hit(group_t *gp, const move_t *move) { if (gp->hitmask & move->hitmask) return false; if (gp->hitfunc == NULL) return true; return gp->hitfunc(gp, move); } void Move_init(void) { LIMIT(options.maxObjectWallBounceSpeed, 0, world->hypotenuse); LIMIT(options.maxShieldedWallBounceSpeed, 0, world->hypotenuse); LIMIT(options.maxUnshieldedWallBounceSpeed, 0, world->hypotenuse); LIMIT(options.playerWallBounceBrakeFactor, 0, 1); LIMIT(options.playerWallFriction, 0, FLT_MAX); LIMIT(options.objectWallBounceBrakeFactor, 0, 1); LIMIT(options.objectWallBounceLifeFactor, 0, 1); mp.obj_bounce_mask = 0; if (options.sparksWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_SPARK_BIT); if (options.debrisWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_DEBRIS_BIT); if (options.shotsWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_SHOT_BIT|OBJ_CANNON_SHOT_BIT); if (options.itemsWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_ITEM_BIT); if (options.missilesWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_SMART_SHOT_BIT|OBJ_TORPEDO_BIT|OBJ_HEAT_SHOT_BIT); if (options.minesWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_MINE_BIT); if (options.ballsWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_BALL_BIT); if (options.asteroidsWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_ASTEROID_BIT); if (options.pulsesWallBounce) SET_BIT(mp.obj_bounce_mask, OBJ_PULSE_BIT); mp.obj_cannon_mask = (KILLING_SHOTS) | OBJ_MINE_BIT | OBJ_SHOT_BIT | OBJ_PULSE_BIT | OBJ_SMART_SHOT_BIT | OBJ_TORPEDO_BIT | OBJ_HEAT_SHOT_BIT | OBJ_ASTEROID_BIT; if (options.cannonsPickupItems) mp.obj_cannon_mask |= OBJ_ITEM_BIT; mp.obj_target_mask = mp.obj_cannon_mask | OBJ_BALL_BIT | OBJ_SPARK_BIT; mp.obj_treasure_mask = mp.obj_bounce_mask | OBJ_BALL_BIT | OBJ_PULSE_BIT; } void Object_crash(object_t *obj, int crashtype, int mapobj_ind) { switch (crashtype) { default: break; case CrashWormHole: Object_hits_wormhole(obj, mapobj_ind); break; case CrashTreasure: /* * Ball type has already been handled. */ if (obj->type == OBJ_BALL) break; obj->life = 0; break; case CrashTarget: obj->life = 0; Object_hits_target(obj, Target_by_index(mapobj_ind), -1.0); break; case CrashWall: obj->life = 0; /* add sparks ??? */ break; case CrashUniverse: obj->life = 0; break; case CrashCannon: obj->life = 0; Object_hits_cannon(obj, Cannon_by_index(mapobj_ind)); break; case CrashUnknown: obj->life = 0; break; } } void Player_crash(player_t *pl, int crashtype, int mapobj_ind, int pt) { const char *howfmt = NULL; const char *hudmsg = NULL; msg[0] = '\0'; switch (crashtype) { default: case NotACrash: warn("Unrecognized crash %d", crashtype); break; case CrashWormHole: Object_hits_wormhole(OBJ_PTR(pl), mapobj_ind); break; case CrashWall: howfmt = "%s crashed%s against a wall"; hudmsg = "[Wall]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; case CrashWallSpeed: howfmt = "%s smashed%s against a wall"; hudmsg = "[Wall]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; case CrashWallNoFuel: howfmt = "%s smacked%s against a wall"; hudmsg = "[Wall]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; case CrashWallAngle: howfmt = "%s was trashed%s against a wall"; hudmsg = "[Wall]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; case CrashTarget: howfmt = "%s smashed%s against a target"; hudmsg = "[Target]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); Object_hits_target(OBJ_PTR(pl), Target_by_index(mapobj_ind), -1.0); break; case CrashTreasure: howfmt = "%s smashed%s against a treasure"; hudmsg = "[Treasure]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; case CrashCannon: { cannon_t *cannon = Cannon_by_index(mapobj_ind); if (!Player_uses_emergency_shield(pl)) { howfmt = "%s smashed%s against a cannon"; hudmsg = "[Cannon]"; sound_play_sensors(pl->pos, PLAYER_HIT_CANNON_SOUND); } if (!BIT(cannon->used, HAS_EMERGENCY_SHIELD)) { /* pl gets points if the cannon is rammed with shields up */ if (Player_uses_emergency_shield(pl)) Cannon_dies(cannon, pl); else Cannon_dies(cannon, NULL); } } break; case CrashUniverse: howfmt = "%s left the known universe%s"; hudmsg = "[Universe]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; case CrashUnknown: howfmt = "%s slammed%s into a programming error"; hudmsg = "[Bug]"; sound_play_sensors(pl->pos, PLAYER_HIT_WALL_SOUND); break; } if (howfmt && hudmsg) { player_t *pushers[MAX_RECORDED_SHOVES]; int cnt[MAX_RECORDED_SHOVES]; int num_pushers = 0; int total_pusher_count = 0; double total_pusher_score = 0; int i, j; Player_set_state(pl, PL_STATE_KILLED); sprintf(msg, howfmt, pl->name, (!pt) ? " head first" : ""); /* get a list of who pushed me */ for (i = 0; i < MAX_RECORDED_SHOVES; i++) { shove_t *shove = &pl->shove_record[i]; if (shove->pusher_id == NO_ID) continue; if (shove->time < frame_loops - (int)((20 / timeStep) + 0.5)) continue; for (j = 0; j < num_pushers; j++) { if (shove->pusher_id == pushers[j]->id) { cnt[j]++; break; } } if (j == num_pushers) { pushers[num_pushers++] = Player_by_id(shove->pusher_id); cnt[j] = 1; } total_pusher_count++; total_pusher_score += Get_Score(pushers[j]); } if (num_pushers == 0) { Handle_Scoring(SCORE_WALL_DEATH,NULL,pl,NULL,hudmsg); strcat(msg, "."); Set_message(msg); } else { int msg_len = strlen(msg); char *msg_ptr = &msg[msg_len]; double average_pusher_score = total_pusher_score / total_pusher_count; bool was_tagged = (tagItPlayerId == pl->id), pusher_is_tagged = false; double mult; player_t dummy; for (i = 0; i < num_pushers; i++) { player_t *pusher = pushers[i]; const char *sep = (!i) ? " with help from " : (i < num_pushers - 1) ? ", " : " and "; size_t sep_len = strlen(sep); size_t name_len = strlen(pusher->name); if (msg_len + sep_len + name_len + 2 < sizeof msg) { strcpy(msg_ptr, sep); msg_len += sep_len; msg_ptr += sep_len; strcpy(msg_ptr, pusher->name); msg_len += name_len; msg_ptr += name_len; } mult = cnt[i] / total_pusher_count; if (options.tagGame) { if (tagItPlayerId == pusher->id) { mult *= options.tagItKillScoreMult; pusher_is_tagged = true; } else if (was_tagged && num_pushers == 1) { mult *= options.tagKillItScoreMult; Transfer_tag(pl, pusher); } } Handle_Scoring(SCORE_SHOVE_KILL,pusher,pl,&mult,NULL); if (i >= num_pushers - 1) Rank_add_shove_kill(pusher); } if (options.tagGame && num_pushers == 1) { if (was_tagged) mult = options.tagKillItScoreMult; else if (pusher_is_tagged) mult = options.tagItKillScoreMult; } dummy.score = average_pusher_score; Handle_Scoring(SCORE_SHOVE_DEATH,&dummy,pl,&mult,NULL); strcpy(msg_ptr, "."); Set_message(msg); /* Robots will declare war on anyone who shoves them. */ i = (int)(rfrac() * num_pushers); Robot_war(pl, pushers[i]); } } if (Player_is_killed(pl) && Get_Score(pl) < 0 && Player_is_robot(pl)) { pl->home_base = Base_by_index(0); Pick_startpos(pl); } } static void *ralloc(void *ptr, size_t size) { if (!(ptr = realloc(ptr, size))) { warn("Realloc failed."); exit(1); } return ptr; } static unsigned short *Shape_lines(shape_t *s, int dir) { int i; static unsigned short foo[100]; static shape_t *lastshape; static int lastdir; const int os = num_lines; clpos_t *pts; /* linet[i].group MUST BE INITIALIZED TO 0 */ if (s == lastshape && dir == lastdir) return foo; lastshape = s; lastdir = dir; pts = Shape_get_points((shape_t *)s, dir); for (i = 0; i < s->num_points; i++) { clpos_t pt = pts[i]; linet[i + os].start.cx = -pt.cx; linet[i + os].start.cy = -pt.cy; } for (i = 0; i < s->num_points - 1; i++) { linet[i + os].delta.cx = linet[i + os + 1].start.cx - linet[i + os].start.cx; linet[i + os].delta.cy = linet[i + os + 1].start.cy - linet[i + os].start.cy; } linet[i + os].delta.cx = linet[os].start.cx - linet[i + os].start.cx; linet[i + os].delta.cy = linet[os].start.cy - linet[i + os].start.cy; for (i = 0; i < s->num_points; i++) foo[i] = i + os; foo[i] = 65535; return foo; } static int Bounce_object(object_t *obj, move_t *move, int line, int point) { double fx, fy; double c, s, wall_brake_factor = options.objectWallBounceBrakeFactor; int group, type; int mapobj_ind; group = linet[line >= num_lines ? point : line].group; type = groups[group].type; mapobj_ind = groups[group].mapobj_ind; if (obj->collmode == 1) { fx = ABS(obj->extmove.cx) + ABS(obj->extmove.cy); /* If fx <= 64, there is practically no movement. Object collision detection can ignore the bounce. */ if (fx > 64) { obj->wall_time = 1 - (ABS(move->delta.cx) + ABS(move->delta.cy)) / fx; obj->collmode = 2; } } if (type == TREASURE) { if (obj->type == OBJ_BALL) Ball_hits_goal(BALL_PTR(obj), groupptr_by_id(group)); obj->life = 0; return 0; } if (type == TARGET) { obj->life = 0; Object_hits_target(obj, Target_by_index(mapobj_ind), -1.0); return 0; } if (type == CANNON) { Object_crash(obj, CrashCannon, mapobj_ind); return 0; } if (type == WORMHOLE) { Object_crash(obj, CrashWormHole, mapobj_ind); return 0; } if (!BIT(mp.obj_bounce_mask, OBJ_TYPEBIT(obj->type))) { obj->life = 0; return 0; } if (obj->type != OBJ_BALL && obj->type != OBJ_PULSE) { obj->life *= options.objectWallBounceLifeFactor; if (obj->life <= 0) return 0; } /* * Any bouncing sparks are no longer owner immune to give * "reactive" thrust. This is exactly like ground effect * in the real world. Very useful for stopping against walls. */ if (obj->type != OBJ_PULSE && obj->type != OBJ_SPARK && sqr(obj->vel.x) + sqr(obj->vel.y) > sqr(options.maxObjectWallBounceSpeed)) { obj->life = 0; return 0; } if (obj->type == OBJ_SPARK && sqr(obj->vel.x) + sqr(obj->vel.y) > sqr(options.maxSparkWallBounceSpeed)) { obj->life = 0; return 0; } if (obj->type == OBJ_SPARK) CLR_BIT(obj->obj_status, OWNERIMMUNE); if (line >= num_lines) { double x, y, l2; x = linet[line].delta.cx; y = linet[line].delta.cy; l2 = (x*x + y*y); c = (x*x - y*y) / l2; s = 2*x*y / l2; } else { c = linet[line].c; s = linet[line].s; } if (obj->type == OBJ_PULSE) wall_brake_factor = 1.0; fx = move->delta.cx * c + move->delta.cy * s; fy = move->delta.cx * s - move->delta.cy * c; move->delta.cx = (click_t)(fx * wall_brake_factor); move->delta.cy = (click_t)(fy * wall_brake_factor); fx = obj->vel.x * c + obj->vel.y * s; fy = obj->vel.x * s - obj->vel.y * c; obj->vel.x = fx * wall_brake_factor; obj->vel.y = fy * wall_brake_factor; if (obj->collmode == 2) obj->collmode = 3; /* find direction of pulse after bounce */ if (obj->type == OBJ_PULSE) { pulseobject_t *pulse = PULSE_PTR(obj); pulse->pulse_dir = (int)Wrap_findDir(pulse->vel.x, pulse->vel.y); pulse->pulse_len = 0; pulse->pulse_refl = true; } return 1; } static void Bounce_player(player_t *pl, move_t *move, int line, int point) { double c, s; /* cosine and sine of 2 times line angle */ double cl, sl; /* cosine and sine of line angle */ double x, y, l2, l; int group, type, mapobj_ind; bool constant_speed_subtracted = (pl->last_wall_touch == frame_loops ? true : false); x = linet[line].delta.cx; y = linet[line].delta.cy; l2 = (x*x + y*y); c = (x*x - y*y) / l2; s = 2*x*y / l2; l = sqrt(l2); cl = x / l; sl = y / l; group = linet[line >= num_lines ? point : line].group; type = groups[group].type; mapobj_ind = groups[group].mapobj_ind; if (type == TREASURE && options.treasureCollisionKills) { Player_crash(pl, CrashTreasure, NO_IND, 1); return; } if (type == WORMHOLE) { Player_crash(pl, CrashWormHole, mapobj_ind, 1); return; } if (type == CANNON) { Player_crash(pl, CrashCannon, mapobj_ind, 1); if (Player_is_killed(pl)) return; /* The player may bounce from the cannon if both have shields up. */ } pl->last_wall_touch = frame_loops; { double speed = VECTOR_LENGTH(pl->vel); double v = speed * 0.25; double m = pl->mass - pl->emptymass * 0.75; double b = 1.0 - 0.5 * options.playerWallBounceBrakeFactor; double cost = b * m * v; double max_speed = BIT(pl->used, HAS_SHIELD) ? options.maxShieldedWallBounceSpeed : options.maxUnshieldedWallBounceSpeed; if (Player_uses_emergency_shield(pl) && max_speed < 100.0) max_speed = 100.0; /* only use armor if neccessary */ if (speed > max_speed && max_speed < options.maxShieldedWallBounceSpeed && !BIT(pl->used, HAS_SHIELD) && Player_has_armor(pl)) { max_speed = options.maxShieldedWallBounceSpeed; Player_hit_armor(pl); } if (speed > max_speed) { if (type == TARGET) Player_crash(pl, CrashTarget, mapobj_ind, 1); else Player_crash(pl, CrashWallSpeed, NO_IND, 1); return; } if (!Player_uses_emergency_shield(pl)) Item_damage(pl, options.wallBounceDestroyItemProb); sound_play_sensors(pl->pos, PLAYER_BOUNCED_SOUND); if (cost && type == TARGET) Object_hits_target(OBJ_PTR(pl), Target_by_index(mapobj_ind), cost / 4.0); } /* * Remove constantSpeed before bounce. * Otherwise constantSpeed will accelerate your ship when you * slide along a wall. */ if (options.constantSpeed && !constant_speed_subtracted) { pl->vel.x -= options.constantSpeed * pl->acc.x; pl->vel.y -= options.constantSpeed * pl->acc.y; } if (options.playerWallBounceType == 0) { double fx, fy; fx = move->delta.cx * c + move->delta.cy * s; fy = move->delta.cx * s - move->delta.cy * c; move->delta.cx = (click_t)(fx * options.playerWallBounceBrakeFactor); move->delta.cy = (click_t)(fy * options.playerWallBounceBrakeFactor); fx = pl->vel.x * c + pl->vel.y * s; fy = pl->vel.x * s - pl->vel.y * c; pl->vel.x = fx * options.playerWallBounceBrakeFactor; pl->vel.y = fy * options.playerWallBounceBrakeFactor; } else { /* * Determine new velocity vector and move->delta after bounce. * The vector move->delta is the remaining amount left to move * in this frame. */ vector_t vel, vel1, mvd, mvd1; /* * i. Rotate velocity and move->delta clockwise by line angle. */ vel.x = pl->vel.x * cl + pl->vel.y * sl; vel.y = pl->vel.x * (-sl) + pl->vel.y * cl; vel1 = vel; mvd.x = move->delta.cx * cl + move->delta.cy * sl; mvd.y = move->delta.cx * (-sl) + move->delta.cy * cl; mvd1 = mvd; /* ii. Reverse direction of perpendicular component. */ vel.y = -vel.y; mvd.y = -mvd.y; /* * iii. Determine how much perpendicular and parallel components * change. */ vel.y *= options.playerWallBounceBrakeFactor; mvd.y *= options.playerWallBounceBrakeFactor; if (options.playerWallBounceType == 1) { double factor = 1.0 - options.playerWallFriction; /* * kps: simple "separate multipliers" implementation */ if (factor < 0.0) factor = 0.0; vel.x *= factor; mvd.x *= factor; } else if (options.playerWallBounceType == 2) { double vtotal1 = VECTOR_LENGTH(vel1); double vnormal1 = ABS(vel1.y); double wallfriction = options.playerWallFriction; double factor; /* Avoid division by 0 */ if (vtotal1 < 0.001) factor = 1.0 - wallfriction; else factor = 1.0 - vnormal1 / vtotal1 * wallfriction; /* * mara: * Vtangent2 = (1-Vnormal1/Vtotal1*wallfriction)*Vtangent1; */ if (factor < 0.0) factor = 0.0; vel.x *= factor; mvd.x *= factor; } else if (options.playerWallBounceType == 3) { double change; double C1 = options.playerWallFriction; double C2 = 1.0 - options.playerWallBounceBrakeFactor; double perpendicular_change, parallel_speed; /* * uau: * change the parallel one by * MIN(C1*perpendicular_change, C2*parallel_speed) * if you assume the wall has a coefficient of friction C1 */ perpendicular_change = ABS(vel1.y - vel.y); parallel_speed = ABS(vel.x); change = MIN(C1*perpendicular_change, C2*parallel_speed); if (vel.x > 0) vel.x -= change; else vel.x += change; perpendicular_change = ABS(mvd1.y - mvd.y); parallel_speed = ABS(mvd.x); change = MIN(C1*perpendicular_change, C2*parallel_speed); if (mvd.x > 0) mvd.x -= change; else mvd.x += change; } /* iv. Rotate the whole thing anti-clockwise. */ pl->vel.x = vel.x * cl + vel.y * (-sl); pl->vel.y = vel.x * sl + vel.y * cl; move->delta.cx = (click_t)(mvd.x * cl + mvd.y * (-sl)); move->delta.cy = (click_t)(mvd.x * sl + mvd.y * cl); } } /* Used internally by the movement routines to find the first line * (in the list given by *lines) that the given trajectory hits. */ static int Lines_check(int msx, int msy, int mdx, int mdy, int *mindone, const unsigned short *lines, int chx, int chy, int chxy, const move_t *move, int *minline, int *height) { int lsx, lsy, ldx, ldy, temp, mirror, start, end, i, x, sy, ey, prod; int mbase = mdy >> 1, hit = 0; while ( (i = *lines++) != 65535) { if (linet[i].group && (!can_hit(&groups[linet[i].group], move))) continue; lsx = linet[i].start.cx; lsy = linet[i].start.cy; ldx = linet[i].delta.cx; ldy = linet[i].delta.cy; if (chx) { lsx = -lsx; ldx = -ldx; } if (chy) { lsy = -lsy; ldy = -ldy; } if (chxy) { temp = ldx; ldx = ldy; ldy = temp; temp = lsx; lsx = lsy; lsy = temp; } lsx -= msx; lsy -= msy; if (chxy) { lsx = CENTER_YCLICK(lsx); lsy = CENTER_XCLICK(lsy); } else { lsx = CENTER_XCLICK(lsx); lsy = CENTER_YCLICK(lsy); } if (*height < lsy + (ldy < 0 ? ldy : 0)) continue; if (0 > lsy + (ldy < 0 ? 0 : ldy)) continue; mirror = chx ^ chy ^ chxy; if (ldx < 0) { lsx += ldx; ldx = -ldx; lsy += ldy; ldy = -ldy; mirror ^= 1; } start = MAX(0, lsx); end = MIN(*mindone + 1, lsx + ldx); if (start > end) continue; sy = LINEY(mdx, mdy, mbase, start); prod = (start - lsx) * ldy - (sy - lsy) * ldx; if (!prod) { if (!ldx && (lsy + (ldy < 0 ? ldy : 0) > sy || lsy + (ldy < 0 ? 0 : ldy) < sy)) continue; if ( (prod = -lsx * ldy + lsy * ldx) > 0 == mirror || prod == 0) continue; start--; } else { if (prod > 0 == mirror) continue; ey = LINEY(mdx, mdy, mbase, end); if ( ABS(prod) >= ldx && ABS( (prod = (end - lsx) * ldy - (ey - lsy) * ldx) ) >= ldx && prod > 0 != mirror) continue; { int schs, sche; double diff = ((double)(-mbase)/mdx-(double)(lsx)*ldy/ldx+lsy); double diff2 = (double)mdy / mdx - (double)ldy / ldx; if (ABS(diff2) < 1. / (50000.*50000)) { if (diff > 0 || diff < -1) continue; else { schs = start + 1; sche = end; } } /* Can this float->int conversion cause overflows? * If so, calculate min/max before conversion. */ else if (diff2 < 0) { schs = MAX(start + 1, (int) ((diff + 1) / diff2 + .9)); sche = MIN(end, (int) (diff / diff2 + 1.1)); } else { schs = MAX(start + 1, (int) (diff / diff2 + .9)); sche = MIN(end, (int) ((diff + 1) / diff2 + 1.1)); } for (x = schs; x <= sche; x++) if ( (prod = (x - lsx) * ldy - (LINEY(mdx, mdy, mbase, x) - lsy) * ldx) >= 0 == mirror || prod == 0) goto found; continue; found: start = x - 1; } } /* delta components can be big, so (float) to avoid overflow */ if (start < *mindone || (start == *mindone && *minline != -1 && SIDE((float)move->delta.cx, (float)move->delta.cy, i) < 0)) { hit = 1; *mindone = start; *minline = i; *height = LINEY(mdx, mdy, mbase, start); } } return hit; } /* Try to move a pointlike object along the path determined by *move. * The amount moved is returned in *answer. If the movement hits a line * the number of that line is included. The 'point' parameter in * struct collans is not relevant to this function. Even if the movement * doesn't hit a line, the amount moved can be less than the requested * amount (so that this function needs to be called again for the rest). * If the movement hits several lines at the same click, the line returned * in collans will be such that it is hit "from the outside" if possible. * Example: * X * X inside of polygon * X * *YYYYYYYYYY * 3 * 2 * 1 * Here the lines X and Y meet at click *, and the movement marked with * numbers hits the common point. The function will return line Y as the * one that was hit, because it was hit "from the outside" as opposed to * line X which was hit from the side facing the inside of the polygon. * See the comments for function 'Away' to see how the trajectory can * hit a line even though it is moving away from the line. */ /* Do not call this with no movement. */ /* May not be called with point already on top of line. * Maybe I should change that to allow lines that could be crossed. */ static void Move_point(const move_t *move, struct collans *answer) { int minline, mindone, minheight; int block; int msx = move->start.cx, msy = move->start.cy; int mdx = move->delta.cx, mdy = move->delta.cy; int mbase; int chxy = 0, chx = 0, chy = 0; int x, temp; unsigned short *lines; block = (move->start.cx >> B_SHIFT) + mapx * (move->start.cy >> B_SHIFT); x = blockline[block].distance; lines = blockline[block].lines; if (mdx < 0) { mdx = -mdx; msx = -msx; chx = 1; } if (mdy < 0) { mdy = -mdy; msy = -msy; chy = 1; } if (mdx < mdy) { temp = mdx; mdx = mdy; mdy = temp; temp = msx; msx = msy; msy = temp; chxy = 1; } /* 46341*46341 overflows signed 32-bit int */ if (mdx > 45000) { /* might overflow without float */ mdy = (int)((float)mdy * 45000 / mdx); mdx = 45000; } mindone = mdx; minheight = mdy; mdx++; mdy++; mbase = mdy >> 1; if (mindone > x) { if (x < MAX_MOVE) { /* !@# change this so that the point always moves away from the current block */ temp = msx > 0 ? B_CLICKS - (msx & B_MASK) : (-msx) & B_MASK; temp = MIN(temp, (msy > 0 ? B_CLICKS - (msy & B_MASK) : -msy & B_MASK)); x += temp; x = MIN(x, MAX_MOVE); } if (mindone > x) { mindone = x; minheight = LINEY(mdx, mdy, mbase, mindone); } } minline = -1; Lines_check(msx, msy, mdx, mdy, &mindone, lines, chx, chy, chxy, move, &minline, &minheight); answer->line = minline; if (chxy) { temp = mindone; mindone = minheight; minheight = temp; } if (chx) mindone = -mindone; if (chy) minheight = -minheight; answer->moved.cx = mindone; answer->moved.cy = minheight; return; } /* Similar to Move_point above, except that it gets the shape parameter * (and direction of that shape), and in case of collision, the 'point' * field in struct collans is used. A corner in the shape can hit a map * line, or a corner in the map can hit a shape line. The 'line' field * will contain the line, the 'point' field the corner (given as a line * whose first point is that corner - that is always possible because * polygons are closed and there is at least one line ending at and one * line starting from a given point). */ /* Do not call this with no movement. */ /* May not be called with point already on top of line. maybe I should change that to allow lines which could be crossed. */ /* This could be sped up by a lot in several ways if needed. * For example, there's no need to consider all the points * separately if the shape is not close to a wall. */ static void Shape_move(const move_t *move, shape_t *s, int dir, struct collans *answer) { int minline, mindone, minheight, minpoint; int i, block; int msx = move->start.cx, msy = move->start.cy; int mdx = move->delta.cx, mdy = move->delta.cy; int mbase; int chxy = 0, chx = 0, chy = 0; int x, temp; unsigned short *lines; unsigned short *points; clpos_t *pts; if (mdx < 0) { mdx = -mdx; chx = 1; } if (mdy < 0) { mdy = -mdy; chy = 1; } if (mdx < mdy) { temp = mdx; mdx = mdy; mdy = temp; chxy = 1; } /* 46341*46341 overflows signed 32-bit int */ if (mdx > 45000) { /* might overflow without float */ mdy = (int)((float)mdy * 45000 / mdx); mdx = 45000; } mindone = mdx; minheight = mdy; mdx++; mdy++; mbase = mdy >> 1; minline = -1; minpoint = -1; pts = Shape_get_points((shape_t *)s, dir); for (i = 0; i < s->num_points; i++) { clpos_t pt = pts[i]; msx = WRAP_XCLICK(move->start.cx + pt.cx); msy = WRAP_YCLICK(move->start.cy + pt.cy); block = (msx >> B_SHIFT) + mapx * (msy >> B_SHIFT); if (chx) msx = -msx; if (chy) msy = -msy; if (chxy) { temp = msx; msx = msy; msy = temp; } x = blockline[block].distance; lines = blockline[block].lines; if (mindone > x) { if (x < MAX_MOVE) { temp = msx > 0 ? B_CLICKS - (msx & B_MASK) : -msx & B_MASK; temp = MIN(temp, (msy > 0 ? B_CLICKS - (msy & B_MASK) : -msy & B_MASK)); x += temp; x = MIN(x, MAX_MOVE); } if (mindone > x) { mindone = x; minheight = LINEY(mdx, mdy, mbase, mindone); } } if (Lines_check(msx, msy, mdx, mdy, &mindone, lines, chx, chy, chxy, move, &minline, &minheight)) minpoint = i; } block = (move->start.cx >> B_SHIFT) + mapx * (move->start.cy >> B_SHIFT); points = blockline[block].points; lines = Shape_lines(s, dir); x = -1; while ( ( i = *points++) != 65535) { if (linet[i].group && (!can_hit(&groups[linet[i].group], move))) continue; msx = move->start.cx - linet[i].start.cx; msy = move->start.cy - linet[i].start.cy; if (chx) msx = -msx; if (chy) msy = -msy; if (chxy) { temp = msx; msx = msy; msy = temp; } if (Lines_check(msx, msy, mdx, mdy, &mindone, lines, chx, chy, chxy, move, &minline, &minheight)) minpoint = i; } answer->point = minpoint; answer->line = minline; answer->point = minpoint; if (chxy) { temp = mindone; mindone = minheight; minheight = temp; } if (chx) mindone = -mindone; if (chy) minheight = -minheight; answer->moved.cx = mindone; answer->moved.cy = minheight; return; } /* Check whether there is room at the given position (x, y) to change * from the shape shape1/dir1 to shape shape2/dir2. The shapes must have * the same number of points. The morphing of the shapes happens linearly * for each point. This should work correctly even if the intermediate * shapes are degenerate or illegal shapes (the intermediate stages are * not explicitly constructed in the algorithm). Return the number of a group * that would be hit during morphing or NO_GROUP if there is enough room. */ /* This might be useful elsewhere in the code, need not be kept static */ static int Shape_morph(shape_t *shape1, int dir1, shape_t *shape2, int dir2, hitmask_t hitmask, const object_t *obj, int x, int y, struct collans *myanswer) { struct collans answer; int i, p, xo1, xo2, yo1, yo2, xn1, xn2, yn1, yn2, xp, yp, s, t; unsigned short *points; move_t mv; /*clpos_t *pts1, *pts2;*/ int num_points; mv.hitmask = hitmask; mv.obj = obj; /*pts1 = Shape_get_points((shape_t *)shape1, dir1); pts2 = Shape_get_points((shape_t *)shape2, dir2);*/ /* kps - can this happen ?? */ if (shape1->num_points != shape2->num_points) warn("Shape_morph: shapes have different number of points!"); num_points = shape1->num_points; for (i = 0; i < num_points; i++) { clpos_t pt1, pt2; /*clpos_t ptx1, ptx2; ptx1 = pts1[i]; ptx2 = pts2[i];*/ pt1 = Ship_get_point_clpos((shipshape_t *)shape1, i, dir1); pt2 = Ship_get_point_clpos((shipshape_t *)shape2, i, dir2); /*assert(ptx1.cx == pt1.cx); assert(ptx1.cy == pt1.cy); assert(ptx2.cx == pt2.cx); assert(ptx2.cy == pt2.cy);*/ mv.start.cx = x + pt1.cx; mv.start.cy = y + pt1.cy; mv.delta.cx = x + pt2.cx - mv.start.cx; mv.delta.cy = y + pt2.cy - mv.start.cy; mv.start.cx = WRAP_XCLICK(mv.start.cx); mv.start.cy = WRAP_YCLICK(mv.start.cy); while (mv.delta.cx || mv.delta.cy) { Move_point(&mv, &answer); if (answer.line != -1){ /* report what lines/points/vectors caused the move to fail*/ myanswer->line = answer.line; myanswer->point = i; myanswer->moved.cx = mv.delta.cx; myanswer->moved.cy = mv.delta.cy; return linet[answer.line].group; } mv.start.cx = WRAP_XCLICK(mv.start.cx + answer.moved.cx); mv.start.cy = WRAP_YCLICK(mv.start.cy + answer.moved.cy); mv.delta.cx -= answer.moved.cx; mv.delta.cy -= answer.moved.cy; } } /* Convex shapes would be much easier. */ points = blockline[(x >> B_SHIFT) + mapx * (y >> B_SHIFT)].points; while ( (p = *points++) != 65535) { clpos_t pto1, ptn1; if (linet[p].group && (!can_hit(&groups[linet[p].group], &mv))) continue; xp = CENTER_XCLICK(linet[p].start.cx - x); yp = CENTER_YCLICK(linet[p].start.cy - y); /*pto1 = pts1[num_points - 1]; ptn1 = pts2[num_points - 1];*/ pto1 = Ship_get_point_clpos((shipshape_t *)shape1, num_points - 1, dir1); ptn1 = Ship_get_point_clpos((shipshape_t *)shape2, num_points - 1, dir2); xo1 = pto1.cx - xp; yo1 = pto1.cy - yp; xn1 = ptn1.cx - xp; yn1 = ptn1.cy - yp; t = 0; for (i = 0; i < num_points; i++) { clpos_t pto2, ptn2; /*pto2 = pts1[i]; ptn2 = pts2[i];*/ pto2 = Ship_get_point_clpos((shipshape_t *)shape1, i, dir1); ptn2 = Ship_get_point_clpos((shipshape_t *)shape2, i, dir2); xo2 = pto2.cx - xp; yo2 = pto2.cy - yp; xn2 = ptn2.cx - xp; yn2 = ptn2.cy - yp; #define TEMPFUNC(X1, Y1, X2, Y2) \ myanswer->line = -1; \ myanswer->point = i; \ myanswer->moved.cx = (X2) - (X1); \ myanswer->moved.cy = (Y2) - (Y1); \ if ((X1) < 0) { \ if ((X2) >= 0) { \ if ((Y1) > 0 && (Y2) >= 0) \ t++; \ else if (((Y1) >= 0 || (Y2) >= 0) && \ (s = (X1)*((Y1)-(Y2))-(Y1)*((X1)-(X2))) >= 0){ \ if (s == 0){ \ return linet[p].group; \ } \ else \ t++; \ } \ } \ } else { \ if ((X2) <= 0) { \ if ((X2) == 0) { \ if ((Y2)==0||((X1)==0 && (((Y1)<=0 && (Y2)>= 0) || \ ((Y1) >= 0 && (Y2)<=0)))){ \ return linet[p].group; \ } \ } \ else if ((Y1) > 0 && (Y2) >= 0) \ t++; \ else if (((Y1) >= 0 || (Y2) >= 0) && \ (s = (X1)*((Y1)-(Y2))-(Y1)*((X1)-(X2))) <= 0){ \ if (s == 0) \ return linet[p].group; \ else \ t++; \ } \ } \ } TEMPFUNC(xo1, yo1, xn1, yn1); TEMPFUNC(xn1, yn1, xn2, yn2); TEMPFUNC(xn2, yn2, xo2, yo2); TEMPFUNC(xo2, yo2, xo1, yo1); #undef TEMPFUNC if (t & 1){ myanswer->line = -1; /*p not a line, but point*/ myanswer->point = i; myanswer->moved.cx = p; myanswer->moved.cy = 0; return linet[p].group; } xo1 = xo2; yo1 = yo2; xn1 = xn2; yn1 = yn2; } } return NO_GROUP; } /* Try to move one click away from a line after a collision. Needed because * otherwise we could keep hitting it even though direction of movement * was away from the line. Example case: * XXX * 1*XX * 34XXX * 56 XXX * 78 XXX * Line (marked with 'X') and path of object (marked with numbers) collide * at '*' because of discreteness even though the object is moving away * from the line. * Return -1 if successful, number of another line blocking movement * otherwise (the number of the line is not used when writing this). */ static int Away(move_t *move, int line) { int dx, dy, lsx, lsy; clvec_t delta_saved; struct collans ans; lsx = linet[line].start.cx - move->start.cx; lsy = linet[line].start.cy - move->start.cy; lsx = CENTER_XCLICK(lsx); lsy = CENTER_YCLICK(lsy); if (ABS(linet[line].delta.cx) >= ABS(linet[line].delta.cy)) { dx = 0; dy = -SIGN(linet[line].delta.cx); } else { dy = 0; dx = SIGN(linet[line].delta.cy); } if ((ABS(lsx) > SEPARATION_DIST || ABS(lsy) > SEPARATION_DIST) && (ABS(lsx + linet[line].delta.cx) > SEPARATION_DIST || ABS(lsy + linet[line].delta.cy) > SEPARATION_DIST)) { move->start.cx = WRAP_XCLICK(move->start.cx + dx); move->start.cy = WRAP_YCLICK(move->start.cy + dy); return -1; } delta_saved = move->delta; move->delta.cx = dx; move->delta.cy = dy; Move_point(move, &ans); move->start.cx = WRAP_XCLICK(move->start.cx + ans.moved.cx); move->start.cy = WRAP_YCLICK(move->start.cy + ans.moved.cy); move->delta = delta_saved; return ans.line; } /* Used internally to get a point out of a tight corner where there is * no room to move it. Situation like this: * E***X * Y***XX * YY**XXX * YYY*XXXX * YYYYXXXXX * YYYY XXXXX * YYYY XXXXX * YYYY XXXXX * YYYY ! XXXXX * YYYY XXXXX * YYYY XXXXX * YYYY XXXXX * X and Y are lines meeting at E. '*' is where lines overlap. An object * can get to position !, but is hard to move out of there (and the situation * can be worse than shown here - the angles of the lines don't need to * differ so much that the gap would get 1 click bigger for each step of * the y coordinate). This function is used to move the object a bit * outwards from the corner so discreteness doesn't make moving it so hard. */ /* This function should get called only rarely, so it doesn't need to * be too efficient. */ static int Clear_corner(move_t *move, object_t *obj, int l1, int l2) { int x, y, xm, ym; int l1sx, l2sx, l1sy, l2sy; l1sx = move->start.cx - linet[l1].start.cx; l1sy = move->start.cy - linet[l1].start.cy; l1sx = CENTER_XCLICK(l1sx); l1sy = CENTER_YCLICK(l1sy); l2sx = move->start.cx - linet[l2].start.cx; l2sy = move->start.cy - linet[l2].start.cy; l2sx = CENTER_XCLICK(l2sx); l2sy = CENTER_YCLICK(l2sy); for (;;) { if (SIDE(obj->vel.x, obj->vel.y, l1) < 0) { if (!Bounce_object(obj, move, l1, 0)) return 0; } if (SIDE(obj->vel.x, obj->vel.y, l2) < 0) { if (!Bounce_object(obj, move, l2, 0)) return 0; continue; } break; } xm = SIGN(obj->vel.x); ym = SIGN(obj->vel.y); #define TMPFUNC(X, Y) (SIDE((X) + l1sx, (Y) + l1sy, l1) <= 0 || SIDE((X) + l2sx, (Y) + l2sy, l2) <= 0) if (ABS(obj->vel.x) >= ABS(obj->vel.y)) { x = xm; y = 0; for (;;) { if (TMPFUNC(x, y)) { y += ym; if (!TMPFUNC(x, y + ym)) break; else x += xm; } else { if (TMPFUNC(x, y + 1) && TMPFUNC(x, y - 1)) x += xm; else break; } } move->delta.cx -= x; move->delta.cy -= y; if ((obj->vel.x >= 0) ^ (move->delta.cx >= 0)) { move->delta.cx = 0; move->delta.cy = 0; } } else { x = 0; y = ym; for (;;) { if (TMPFUNC(x, y)) { x += xm; if (!TMPFUNC(x + xm, y)) break; else y += ym; } else { if (TMPFUNC(x + 1, y) && TMPFUNC(x - 1, y)) y += ym; else break; } } #undef TMPFUNC move->delta.cx -= x; move->delta.cy -= y; if ((obj->vel.y >= 0) ^ (move->delta.cy >= 0)) { move->delta.cx = 0; move->delta.cy = 0; } } move->start.cx = WRAP_XCLICK(move->start.cx + x); move->start.cy = WRAP_YCLICK(move->start.cy + y); return 1; } /* Move a shape away from a line after a collision. Needed for the same * reason as Away(). */ static int Shape_away(move_t *move, shape_t *s, int dir, int line, struct collans *ans) { int dx, dy; clvec_t delta_saved; if (ABS(linet[line].delta.cx) >= ABS(linet[line].delta.cy)) { dx = 0; dy = -SIGN(linet[line].delta.cx); } else { dy = 0; dx = SIGN(linet[line].delta.cy); } delta_saved = move->delta; move->delta.cx = dx; move->delta.cy = dy; Shape_move(move, s, dir, ans); move->start.cx = WRAP_XCLICK(move->start.cx + ans->moved.cx); move->start.cy = WRAP_YCLICK(move->start.cy + ans->moved.cy); move->delta = delta_saved; return ans->line == -1 && ans->point == -1; } static void store_byte(int value, unsigned char **start, int *offset, int *sz) { (*start)[(*offset)++] = value; if (*offset == *sz) { *sz *= 2; *start = (unsigned char *)ralloc(*start, *sz); } } static void store_2byte(int value, unsigned char **start, int *offset, int *sz) { store_byte(value >> 8, start, offset, sz); store_byte(value & 0xff, start, offset, sz); } static void store_4byte(int value, unsigned char **start, int *offset, int *sz) { store_2byte(value >> 16, start, offset, sz); store_2byte(value & 0xffff, start, offset, sz); } int Polys_to_client(unsigned char **start) { int i, j, startx, starty, dx, dy; int *edges; int size, offset; #define STORE1(x) store_byte(x, start, &offset, &size) #define STORE2(x) store_2byte(x, start, &offset, &size) #define STORE4(x) store_4byte(x, start, &offset, &size) *start = (unsigned char *)ralloc(NULL, 100); size = 100; offset = 0; STORE1(num_pstyles); STORE1(num_estyles); STORE1(num_bstyles); for (i = 0; i < num_pstyles; i++) { STORE4(pstyles[i].color); STORE1(pstyles[i].texture_id); STORE1(pstyles[i].defedge_id); STORE1(pstyles[i].flags); } for (i = 0; i < num_estyles; i++) { STORE1(estyles[i].width); STORE4(estyles[i].color); STORE1(estyles[i].style); } for (i = 0; i < num_bstyles; i++) { j = 0; while (1) { STORE1(bstyles[i].filename[j]); if (!bstyles[i].filename[j]) break; j++; } STORE1(bstyles[i].flags); } STORE2(num_polys); for (i = 0; i < num_polys; i++) { STORE1(pdata[i].style); j = pdata[i].num_points; STORE2(pdata[i].num_echanges); edges = estyleptr + pdata[i].estyles_start; while (*edges != INT_MAX) STORE2(*edges++); startx = pdata[i].pos.cx; starty = pdata[i].pos.cy; edges = edgeptr + pdata[i].edges; STORE2(j); STORE2(startx >> CLICK_SHIFT); STORE2(starty >> CLICK_SHIFT); dx = startx; dy = starty; for (; j > 0; j--) { dx += *edges++; dy += *edges++; if (j != 1) { STORE2((dx >> CLICK_SHIFT) - (startx>>CLICK_SHIFT)); STORE2((dy >> CLICK_SHIFT) - (starty>>CLICK_SHIFT)); } startx = dx; starty = dy; } } STORE1(Num_bases()); for (i = 0; i < Num_bases(); i++) { base_t *base = Base_by_index(i); if (base->team == TEAM_NOT_SET) STORE1(0); else STORE1(base->team); STORE2(base->pos.cx >> CLICK_SHIFT); STORE2(base->pos.cy >> CLICK_SHIFT); STORE1(base->dir); } STORE2(Num_fuels()); for (i = 0; i < Num_fuels(); i++) { fuel_t *fs = Fuel_by_index(i); STORE2(fs->pos.cx >> CLICK_SHIFT); STORE2(fs->pos.cy >> CLICK_SHIFT); } STORE1(world->NumChecks); for (i = 0; i < world->NumChecks; i++) { STORE2(world->checks[i].pos.cx >> CLICK_SHIFT); STORE2(world->checks[i].pos.cy >> CLICK_SHIFT); } return offset; } struct tempy { short y; struct tempy *next; }; struct templine { short x1, x2, y1, y2; struct templine *next; }; /* Check whether the given position (cx, cy) is such that it is inside * a polygon belonging to a group that could be hit by the given * hitmask/object. * Return the number of a group that would be hit or NO_GROUP. */ int is_inside(int cx, int cy, hitmask_t hitmask, const object_t *obj) { short *ptr; int inside, cx1, cx2, cy1, cy2, s; struct inside_block *gblock; move_t mv; #if 0 /* kps - is_inside seems to assume that cx and cy are inside the map */ clpos_t pos; pos.cx = cx; pos.cy = cy; assert(World_contains_clpos(&World, pos)); #endif mv.hitmask = hitmask; mv.obj = obj; gblock = &inside_table[(cx >> B_SHIFT) + mapx * (cy >> B_SHIFT)]; if (gblock->group == NO_GROUP) return NO_GROUP; do { if (gblock->group && (!can_hit(&groups[gblock->group], &mv))) { gblock = gblock->next; continue; } inside = gblock->base_value; if (gblock->lines == NULL) { if (inside) return gblock->group; else { gblock = gblock->next; continue; } } cx &= B_MASK; cy &= B_MASK; ptr = gblock->y; if (ptr) while (cy > *ptr++) inside++; ptr = gblock->lines; while (*ptr != 32767) { cx1 = *ptr++ - cx; cy1 = *ptr++ - cy; cx2 = *ptr++ - cx; cy2 = *ptr++ - cy; if (cy1 < 0) { if (cy2 >= 0) { if (cx1 > 0 && cx2 >= 0) inside++; else if ((cx1 >= 0 || cx2 >= 0) && (s = cy1 * (cx1 - cx2) - cx1 * (cy1 - cy2)) >= 0) { if (s == 0) return gblock->group; else inside++; } } } else if (cy2 <= 0) { if (cy2 == 0) { if (cx2 == 0 || (cy1 ==0 && ((cx1 <= 0 && cx2 >= 0) || (cx1 >= 0 && cx2 <= 0)))) return gblock->group; } else if (cx1 > 0 && cx2 >= 0) inside++; else if ((cx1 >= 0 || cx2 >= 0) && (s = cy1 * (cx1 - cx2) - cx1 * (cy1 - cy2)) <= 0) { if (s == 0) return gblock->group; else inside++; } } } if (inside & 1) return gblock->group; gblock = gblock->next; } while (gblock); return NO_GROUP; } /* Similar to the above, except check whether any part of the shape * (edge or inside) would hit the group. */ int shape_is_inside(int cx, int cy, hitmask_t hitmask, const object_t *obj, shape_t *s, int dir) { static clpos_t zeropos; static shape_t zeroshape; int i, group; struct collans ans; /* Implemented by first checking whether the middle point of the * shape is on top of something. If not, check whether it is possible * to enlarge a degenerate shape where all points are on top of each * other (at the middle point) to the given one. (So it relies on the * rule that the shape must contain the middle point. */ if ( (group = is_inside(cx, cy, hitmask, obj)) != NO_GROUP) return group; /* * kps - Ship numpoints can be > MAX_SHIP_PTS because of * SSHACK. This should somehow be fixed. */ zeroshape.num_points = s->num_points; if (zeroshape.pts[0] == NULL) { for (i = 0; i < MAX_SHIP_PTS2; i++) zeroshape.pts[i] = &zeropos; } return Shape_morph(&zeroshape, 0, s, dir, hitmask, obj, cx, cy, &ans); } static void closest_line(int bx, int by, double dist, int inside) { if (dist <= temparray[bx + mapx *by].distance) { if (dist == temparray[bx + mapx * by].distance) /* Must be joined polygons(s) if the map is legal * (the same line appears in both directions). * Both sides of this line are inside. */ /* These lines could be removed from the table as a minor * optimization. */ inside = 1; temparray[bx + mapx * by].distance = dist; temparray[bx + mapx * by].inside = inside; } } static void insert_y(int block, int y) { struct tempy *ptr; struct tempy **prev; ptr = temparray[block].y; prev = &temparray[block].y; while (ptr && ptr->y < y) { prev = &ptr->next; ptr = ptr->next; } if (ptr && ptr->y == y) { *prev = ptr->next; free(ptr); return; } *prev = (struct tempy *)ralloc(NULL, sizeof(struct tempy)); (*prev)->y = y; (*prev)->next = ptr; } static void store_inside_line(int bx, int by, int ox, int oy, int dx, int dy) { int block; struct templine *s; block = bx + mapx * by; ox = CENTER_XCLICK(ox - bx * B_CLICKS); oy = CENTER_YCLICK(oy - by * B_CLICKS); if (oy >= 0 && oy < B_CLICKS && ox >= B_CLICKS) insert_y(block, oy); if (oy + dy >= 0 && oy + dy < B_CLICKS && ox + dx >= B_CLICKS) insert_y(block, oy + dy); s = (struct templine *)ralloc(NULL, sizeof(struct templine)); s->x1 = ox; s->x2 = ox + dx; s->y1 = oy; s->y2 = oy + dy; s->next = temparray[block].lines; temparray[block].lines = s; } static void finish_inside(int block, int group) { int inside; struct inside_block *gblock; short *ptr; int cx1, cx2, cy1, cy2, s, j; struct tempy *yptr; struct templine *lptr; void *tofree; gblock = &inside_table[block]; if (gblock->group != NO_GROUP) { while (gblock->next) /* Maintain group order*/ gblock = gblock->next; gblock->next = (struct inside_block *)ralloc(NULL, sizeof(struct inside_block)); gblock = gblock->next; } gblock->group = group; gblock->next = NULL; j = 0; yptr = temparray[block].y; while (yptr) { j++; yptr = yptr->next; } if (j > 0) { ptr = (short *)ralloc(NULL, (j + 1) * sizeof(short)); gblock->y = ptr; yptr = temparray[block].y; while (yptr) { *ptr++ = yptr->y; tofree = yptr; yptr = yptr->next; free(tofree); } *ptr = 32767; } else gblock->y = NULL; j = 0; lptr = temparray[block].lines; while (lptr) { j++; lptr = lptr->next; } if (j > 0) { ptr = (short *)ralloc(NULL, (j * 4 + 1) * sizeof(short)); gblock->lines = ptr; lptr = temparray[block].lines; while (lptr) { *ptr++ = lptr->x1; *ptr++ = lptr->y1; *ptr++ = lptr->x2; *ptr++ = lptr->y2; tofree = lptr; lptr = lptr->next; free(tofree); } *ptr = 32767; } else gblock->lines = NULL; inside = temparray[block].inside; if ( (ptr = gblock->lines) != NULL) { while (*ptr != 32767) { cx1 = *ptr++ * 2 - B_CLICKS * 2 + 1; cy1 = *ptr++ * 2 + 1; cx2 = *ptr++ * 2 - B_CLICKS * 2 + 1; cy2 = *ptr++ * 2 + 1; if (cy1 < 0) { if (cy2 >= 0) { if (cx1 > 0 && cx2 >= 0) inside++; else if ((cx1 >= 0 || cx2 >= 0) && (s = cy1 * (cx1 - cx2) - cx1 * (cy1 - cy2)) > 0) inside++; } } else if (cy2 <= 0) { if (cx1 > 0 && cx2 >= 0) inside++; else if ((cx1 >= 0 || cx2 >= 0) && (s = cy1 * (cx1 - cx2) - cx1 * (cy1 - cy2)) < 0) inside++; } } } gblock->base_value = inside & 1; temparray[block].y = NULL; temparray[block].lines = NULL; temparray[block].inside = 2; temparray[block].distance = 1e20; } static void allocate_inside(void) { int i; inside_table = (struct inside_block *) ralloc(NULL, mapx * mapy * sizeof(struct inside_block)); temparray = (struct test *) ralloc(NULL, mapx * mapy * sizeof(struct test)); for (i = 0; i < mapx * mapy; i++) { temparray[i].distance = 1e20; temparray[i].inside = 2; temparray[i].y = NULL; temparray[i].lines = NULL; inside_table[i].y = NULL; inside_table[i].lines = NULL; inside_table[i].base_value = 0; inside_table[i].group = NO_GROUP; inside_table[i].next = NULL; } } /* Calculate distance of intersection from lower right corner of the * block counterclockwise along the edge. We don't return the true lengths * but values which compare the same with each other. * 'dir' is used to return whether the block is left through a horizontal * or a vertical side or a corner. */ static double edge_distance(int bx, int by, int ox, int oy, int dx, int dy, int *dir) { int last_width = (world->cwidth - 1) % B_CLICKS + 1; int last_height = (world->cheight - 1) % B_CLICKS + 1; double xdist, ydist, dist; ox = CENTER_XCLICK(ox - bx * B_CLICKS); oy = CENTER_YCLICK(oy - by * B_CLICKS); if (dx > 0) xdist = ((bx == mapx - 1) ? last_width : B_CLICKS) - .5 - ox; else if (dx < 0) xdist = ox + .5; else xdist = 1e20; /* Something big enough to be > ydist, dx */ if (dy > 0) ydist = ((by == mapy - 1) ? last_height : B_CLICKS) - .5 - oy; else if (dy < 0) ydist = oy + .5; else ydist = 1e20; if (xdist > ABS(dx) && ydist > ABS(dy)) return -1; /* Doesn't cross box boundary */ if (ABS(dy) * xdist == ABS(dx) * ydist) *dir = 3; else if (ABS(dy) * xdist < ABS(dx) * ydist) *dir = 1; else *dir = 2; if (*dir == 1) if (dx > 0) dist = oy + dy * xdist / dx; else dist = 5 * B_CLICKS - oy + dy * xdist / dx; else if (dy > 0) dist = 3 * B_CLICKS - ox - dx * ydist / dy; else dist = 6 * B_CLICKS + ox - dx * ydist / dy; return dist; } #define POSMOD(x, y) ((x) >= 0 ? (x) % (y) : ((x) + 1) % (y) + (y) - 1) static void Inside_init(void) { int dx, dy, bx, by, ox, oy, startx, starty; int i, j, num_points, minx = -1, miny = -1, poly, group; int bx2, by2, maxx = -1, maxy = -1, dir; double dist; int *edges; allocate_inside(); for (group = 0; group < num_groups; group++) { minx = -1; for (poly = 0; poly < num_polys; poly++) { if (pdata[poly].is_decor || pdata[poly].group != group) continue; num_points = pdata[poly].num_points; dx = 0; dy = 0; startx = pdata[poly].pos.cx; starty = pdata[poly].pos.cy; /* Better wrapping for bx2/by2 could be selected for speed here, * but this keeping track of min/max at all is probably * unnoticeable in practice. */ bx2 = bx = startx >> B_SHIFT; by2 = by = starty >> B_SHIFT; if (minx == -1) { minx = maxx = bx2; miny = maxy = by2; } edges = edgeptr + pdata[poly].edges; closest_line(bx, by, 1e10, 0); /* For polygons within one block */ for (j = 0; j < num_points; j++) { if (((startx >> B_SHIFT) != bx) || ((starty >> B_SHIFT) != by)) { warn("Inside_init: went into infinite loop..."); while (1); } ox = startx & B_MASK; oy = starty & B_MASK; dx = *edges++; dy = *edges++; while (1) { /* All blocks containing a part of this line */ store_inside_line(bx, by, startx, starty, dx, dy); dist = edge_distance(bx, by, WRAP_XCLICK(startx + dx), WRAP_YCLICK(starty + dy), -dx, -dy, &dir); if (dist != -1) closest_line(bx, by, dist, 1); dist = edge_distance(bx, by, startx, starty, dx, dy, &dir); if (dist == -1) break; closest_line(bx, by, dist, 0); if (dir == 1 || dir == 3) bx2 += (dx > 0) ? 1 : -1; if (bx2 > maxx) maxx = bx2; if (bx2 < minx) minx = bx2; bx = POSMOD(bx2, mapx); if (dir == 2 || dir == 3) by2 += (dy > 0) ? 1 : -1; if (by2 > maxy) maxy = by2; if (by2 < miny) miny = by2; by = POSMOD(by2, mapy); } startx = WRAP_XCLICK(startx + dx); starty = WRAP_YCLICK(starty + dy); } } if (minx == -1) continue; bx = maxx - minx + 1; if (bx > 2 * mapx) bx = 2 * mapx; by = maxy - miny + 1; if (by > mapy) by = mapy; for (i = POSMOD(miny, mapy); by-- > 0; i++) { if (i == mapy) i = 0; bx2 = bx; dir = 0; for (j = POSMOD(minx, mapx); bx2-- > 0; j++) { if (j == mapx) j = 0; if (temparray[j + mapx * i].inside < 2) { dir = temparray[j + mapx * i].distance > B_CLICKS && temparray[j + mapx * i].inside == 1; } else { if (dir) temparray[i * mapx + j].inside = 1; } if (bx2 < mapx) finish_inside(j + mapx * i, group); } } } return; } /* Include NCLLIN - 1 closest lines or all closer than CUTOFF (whichever * is less) in the line table for this block. * Include all lines closer than DICLOSE, however many there are. * LINSIZE tells the amout of temporary memory to reserve for the algorithm. * If it is not large enough to hold the required lines, print error and * exit. */ #define DICLOSE (5 * CLICK) #define LINSIZE 100 #define NCLLIN (10 + 1) static void Distance_init(void) { int cx,cy; int *lineno, *dis; int lsx, lsy, ldx, ldy, temp, dist, n, i, bx, by, j, k; int base, height2, by2, width, height; int distbound, size; unsigned short *lptr; /* max line delta 30000 */ blockline = (struct blockinfo *) ralloc(NULL, mapx * mapy * sizeof(struct blockinfo)); lineno = (int *)ralloc(NULL, mapx * mapy * LINSIZE * sizeof(int)); dis = (int *)ralloc(NULL, mapx * mapy * LINSIZE * sizeof(int)); size = 1; /* start with end marker */ for (bx = 0; bx < mapx; bx++) for (by = 0; by < mapy; by++) for (i = 0; i < LINSIZE; i++) { dis[(by * mapx + bx) * LINSIZE + i] = MAX_MOVE + B_CLICKS / 2; lineno[(by * mapx + bx) * LINSIZE +i] = 65535; } for (i = 0; i < num_lines; i++) { bx = linet[i].start.cx; by = linet[i].start.cy; width = linet[i].delta.cx; height = linet[i].delta.cy; if (width < 0) { bx += width; width = -width; bx = WRAP_XCLICK(bx); } if (height < 0) { by += height; height = -height; by = WRAP_YCLICK(by); } width = (width + 2 * MAX_MOVE) / B_CLICKS + 5; if (width >= mapx) width = mapx; height = (height + 2 * MAX_MOVE) / B_CLICKS + 5; if (height >= mapy) height = mapy; bx = (bx - MAX_MOVE) / B_CLICKS - 2; by = (by - MAX_MOVE) / B_CLICKS - 2; while (bx < 0) bx += mapx; while (by < 0) by += mapy; height2 = height; by2 = by; for (; width-- > 0; bx = bx == mapx - 1 ? 0 : bx + 1) for (by = by2, height = height2; height -- > 0; by = by == mapy - 1? 0 : by + 1) { cx = bx * B_CLICKS + B_CLICKS / 2; cy = by * B_CLICKS + B_CLICKS / 2; base = (by * mapx + bx) * LINSIZE; lsx = CENTER_XCLICK(linet[i].start.cx - cx); if (ABS(lsx) > 32767 + MAX_MOVE + B_CLICKS / 2) continue; lsy = CENTER_YCLICK(linet[i].start.cy - cy); if (ABS(lsy) > 32767 + MAX_MOVE + B_CLICKS / 2) continue; ldx = linet[i].delta.cx; ldy = linet[i].delta.cy; if (MAX(ABS(lsx), ABS(lsy)) > MAX(ABS(lsx + ldx), ABS(lsy + ldy))) { lsx += ldx; ldx = -ldx; lsy += ldy; ldy = -ldy; } if (ABS(lsx) < ABS(lsy)) { temp = lsx; lsx = lsy; lsy = temp; temp = ldx; ldx = ldy; ldy = temp; } if (lsx < 0) { lsx = -lsx; ldx = -ldx; } if (ldx >= 0) dist = lsx - 1; else { if (lsy + ldy < 0) { lsy = -lsy; ldy = -ldy; } temp = lsy - lsx; lsx += lsy; lsy = temp; temp = ldy - ldx; ldx += ldy; ldy = temp; dist = lsx - ldx * lsy / ldy; if (lsx + ldx < 0) dist = MIN(ABS(dist), ABS(lsy - ldy * lsx / ldx)); dist = dist / 2 - 3; /* 3? didn't bother to get the right value */ } if (dist < CUTOFF + B_CLICKS / 2) { if (dist < B_CLICKS / 2 + DICLOSE) distbound = LINSIZE; else distbound = NCLLIN; for (j = 1; j < distbound; j++) { if (dis[base + j] <= dist) continue; k = dis[base + j]; n = j; for (j++; j < distbound; j++) if (dis[base + j] > k) { k = dis[base + j]; n = j; } if (dis[base + 0] > dis[base + n]) dis[base + 0] = dis[base + n]; if (lineno[base + n] == 65535) { size++; /* more saved lines */ if (n == 1) size++; /* first this block, for 65535 */ } dis[base + n] = dist; lineno[base + n] = i; goto stored; } } if (dist < dis[base + 0]) dis[base + 0] = dist; if (dist < B_CLICKS / 2 + DICLOSE) { printf("Not enough space in line table. " "Fix allocation in walls.c\n"); exit(1); } stored: ; /* semicolon for ansi compatibility */ } } llist = (unsigned short *)ralloc(NULL, size * sizeof(unsigned short)); lptr = llist; *lptr++ = 65535; /* All blocks with no lines stored point to this. */ for (bx = 0; bx < mapx; bx++) for (by = 0; by < mapy; by++) { base = (by * mapx + bx) * LINSIZE; k = bx + mapx * by; blockline[k].distance = dis[base + 0] - B_CLICKS / 2; if (lineno[base + 1] == 65535) blockline[k].lines = llist; else { blockline[k].lines = lptr; for (j = 1; j < LINSIZE && lineno[base + j] != 65535; j++) *lptr++ = lineno[base + j]; *lptr++ = 65535; } } free(lineno); free(dis); } /* cut and paste from #xpilot irc channel: the current values used for creating the wall tables are unoptimal what wall tables ? corners, etc ? especially after you made the "ship size" value bigger (for asteroids IIRC?) it can be bad do i make that ship size smaller or what do you suggest ? or how should this be done if one wanted to make it right ? the server creates a table for "nearby" lines for each 32x32 pixel block where "nearby" depends on how close closest lines are and then later creates a table of corners for the same distance+shipsize is it struct blockinfo *blockline; ? if shipsize is big then the latter can include lots of corners blockline.distance is the distance for which features are listed for that block blockline.lines contains all lines that are within that distance of the block (away from the block edges, whole inside has value 0) distance is measured as MIN of x,y distance blockline.points contains all corners (identified as a line starting from that point) within distance blockline.distance+C from that block where C was some constant which depends on ship size the heuristic for choosing a suitable blockline.distance could be improved that would help how ? now it always includes some lines IIRC; it would probably be better to choose the maximum distance such that no lines are included instead if that is big enough MAX_SHAPE_OFFSET being big is a separate problem now blockline.distance is chosen based on the lines only, blockline.points is then calculated afterwards to allow shapes to be moved "compatibly" if MAX_SHAPE_OFFSET is big that means a value for blockline.distance which is good for moving objects can create inefficiently big corner lists for in blockline.points maybe it says there is not enough corner space since my xp map to polygon conversion function creates many polygons where one could have only one so there is a lot of corners close to each other the problem place is one where there is a lot of free space around, so that blockline.distance can be chosen large but then there are suddenly a lot of corners inside distance+MAX_SHAPE_OFFSET */ static void Corner_init(void) { int bx, by, cx, cy, dist, i; unsigned short *ptr, *temp; int block, size = mapx * mapy; int height, height2, width, by2; #define DISIZE 350 temp = (unsigned short *) ralloc(NULL, mapx * mapy * DISIZE * sizeof(unsigned short)); /* !@# */ for (i = 0; i < mapx * mapy; i++) temp[i * DISIZE] = 0; for (i = 0; i < num_lines; i++) { bx = linet[i].start.cx; by = linet[i].start.cy; width = height = (2 * MAX_MOVE) / B_CLICKS + 7; if (width >= mapx) width = mapx; if (height >= mapy) height = mapy; bx = (bx - MAX_MOVE) / B_CLICKS - 3; by = (by - MAX_MOVE) / B_CLICKS - 3; while (bx < 0) bx += mapx; while (by < 0) by += mapy; height2 = height; by2 = by; for (; width-- > 0; bx = bx == mapx - 1 ? 0 : bx + 1) for (by = by2, height = height2; height -- > 0; by = by == mapy - 1? 0 : by + 1) { block = bx + mapx * by; dist = blockline[block].distance + MAX_SHAPE_OFFSET + B_CLICKS / 2; cx = bx * B_CLICKS + B_CLICKS / 2; cy = by * B_CLICKS + B_CLICKS / 2; if (ABS(CENTER_XCLICK(linet[i].start.cx - cx)) > dist) continue; if (ABS(CENTER_YCLICK(linet[i].start.cy - cy)) > dist) continue; temp[++temp[DISIZE * block] + DISIZE * block] = i; size++; } } plist = (unsigned short *)ralloc(NULL, size * sizeof(unsigned short)); ptr = plist; for (block = 0; block < mapx * mapy; block++) { blockline[block].points = ptr; i = temp[block * DISIZE]; if (i > DISIZE - 1) { warn("Not enough corner space in walls.c, add more."); exit(1); } while (i > 0) { *ptr++ = temp[block * DISIZE + i]; i--; } *ptr++ = 65535; } free(temp); #undef DISIZE } void Ball_line_init(void) { int i; static clpos_t coords[MAX_SHIP_PTS]; LIMIT(options.ballRadius, 0, BALL_RADIUS); ball_wire.num_points = MAX_SHIP_PTS; for (i = 0; i < MAX_SHIP_PTS; i++) { ball_wire.pts[i] = coords + i; coords[i].cx = (click_t) (cos(i * 2 * PI / MAX_SHIP_PTS) * options.ballRadius * CLICK); coords[i].cy = (click_t) (sin(i * 2 * PI / MAX_SHIP_PTS) * options.ballRadius * CLICK); } return; } static void Poly_to_lines(void) { int i, np, j, startx, starty, dx, dy, group, *styleptr, style; int *edges; num_lines = 0; for (i = 0; i < num_polys; i++) { if (pdata[i].is_decor) continue; group = pdata[i].group; np = pdata[i].num_points; styleptr = estyleptr + pdata[i].estyles_start; style = pstyles[pdata[i].style].defedge_id; dx = 0; dy = 0; startx = pdata[i].pos.cx; starty = pdata[i].pos.cy; edges = edgeptr + pdata[i].edges; for (j = 0; j < np; j++) { if (j == *styleptr) { styleptr++; style = *styleptr++; } if (style == 0) { dx += *edges++; dy += *edges++; continue; } if (!(num_lines % 2000)) linet = (struct bline *) ralloc(linet, (num_lines + 2000) * sizeof(struct bline)); linet[num_lines].group = group; linet[num_lines].start.cx = TWRAP_XCLICK(startx + dx); linet[num_lines].start.cy = TWRAP_YCLICK(starty + dy); linet[num_lines].delta.cx = *edges; dx += *edges++; linet[num_lines++].delta.cy = *edges; dy += *edges++; } if (dx || dy) { warn("Broken map: Polygon %d (%d points) doesn't form a " "closed loop", i + 1, np); exit(1); } } linet = (struct bline *) ralloc(linet, (num_lines + S_LINES) * sizeof(struct bline)); for (i = num_lines; i < num_lines + S_LINES; i++) linet[i].group = 0; /* initialize for Shape_lines */ return; } void Walls_init(void) { double x, y, l2; int i; mapx = (world->cwidth + B_MASK) >> B_SHIFT; mapy = (world->cheight + B_MASK) >> B_SHIFT; /* Break polygons down to a list of separate lines. */ Poly_to_lines(); /* For each B_CLICKS x B_CLICKS rectangle on the map, find a list of * nearby lines that need to be checked for collision when moving * in that area. */ Distance_init(); /* Like above, except list the map corners that could be hit by the * sides of a moving polygon shape. */ Corner_init(); Ball_line_init(); /* Initialize the data structures used when determining whether a given * arbitrary point on the map is inside something. */ Inside_init(); /* Precalculate the .c and .s values used when calculating a bounce * from the line. */ for (i = 0; i < num_lines; i++) { x = linet[i].delta.cx; y = linet[i].delta.cy; l2 = (x*x + y*y); linet[i].c = (x*x - y*y) / l2; linet[i].s = 2*x*y / l2; } if (is_polygon_map) { if (options.mapData) { warn("Option mapData is not supported on polygon maps."); warn("Server automatically creates block map from polygons."); } Create_blockmap_from_polygons(); } } static void Move_asteroid(object_t *obj) { move_t mv; struct collans ans; wireobject_t *asteroid = (wireobject_t *)obj; mv.delta.cx = FLOAT_TO_CLICK(obj->vel.x * timeStep); mv.delta.cy = FLOAT_TO_CLICK(obj->vel.y * timeStep); mv.obj = obj; obj->extmove.cx = mv.delta.cx; obj->extmove.cy = mv.delta.cy; /* asteroid can't phaze */ mv.hitmask = NONBALL_BIT; /* hit everything for now */ mv.start.cx = obj->pos.cx; mv.start.cy = obj->pos.cy; while (mv.delta.cx || mv.delta.cy) { shape_t *shape = Asteroid_get_shape_by_size(asteroid->wire_size); assert(shape); Shape_move(&mv, shape, 0, &ans); mv.start.cx = WRAP_XCLICK(mv.start.cx + ans.moved.cx); mv.start.cy = WRAP_YCLICK(mv.start.cy + ans.moved.cy); mv.delta.cx -= ans.moved.cx; mv.delta.cy -= ans.moved.cy; if (ans.line != -1) { if (SIDE(obj->vel.x, obj->vel.y, ans.line) < 0) { if (!Bounce_object(obj, &mv, ans.line, ans.point)) break; } else if (!Shape_away(&mv, shape, 0, ans.line, &ans)) { if (SIDE(obj->vel.x, obj->vel.y, ans.line) < 0) { if (!Bounce_object(obj, &mv, ans.line, ans.point)) break; } else { /* This case could be handled better, * I'll write the code for that if this * happens too often. */ mv.delta.cx = 0; mv.delta.cy = 0; obj->vel.x = 0; obj->vel.y = 0; } } } } Object_position_set_clvec(obj, mv.start); Cell_add_object(obj); return; } static void Move_ball(object_t *obj) { move_t mv; struct collans ans; int owner; mv.delta.cx = FLOAT_TO_CLICK(obj->vel.x * timeStep); mv.delta.cy = FLOAT_TO_CLICK(obj->vel.y * timeStep); mv.obj = obj; obj->extmove.cx = mv.delta.cx; obj->extmove.cy = mv.delta.cy; if (obj->id != NO_ID && Player_is_phasing(Player_by_id(obj->id))) { clpos_t pos; pos.cx = obj->pos.cx + mv.delta.cx; pos.cy = obj->pos.cy + mv.delta.cy; pos = World_wrap_clpos(pos); Object_position_set_clpos(obj, pos); Cell_add_object(obj); return; } owner = BALL_PTR(obj)->ball_owner; if (owner == NO_ID) mv.hitmask = BALL_BIT | NOTEAM_BIT; else mv.hitmask = BALL_BIT | HITMASK(Player_by_id(owner)->team); mv.start.cx = obj->pos.cx; mv.start.cy = obj->pos.cy; while (mv.delta.cx || mv.delta.cy) { Shape_move(&mv, &ball_wire, 0, &ans); mv.start.cx = WRAP_XCLICK(mv.start.cx + ans.moved.cx); mv.start.cy = WRAP_YCLICK(mv.start.cy + ans.moved.cy); mv.delta.cx -= ans.moved.cx; mv.delta.cy -= ans.moved.cy; if (ans.line != -1) { if (SIDE(obj->vel.x, obj->vel.y, ans.line) < 0) { if (!Bounce_object(obj, &mv, ans.line, ans.point)) break; } else if (!Shape_away(&mv, &ball_wire, 0, ans.line, &ans)) { if (SIDE(obj->vel.x, obj->vel.y, ans.line) < 0) { if (!Bounce_object(obj, &mv, ans.line, ans.point)) break; } else { /* This case could be handled better, * I'll write the code for that if this * happens too often. */ mv.delta.cx = 0; mv.delta.cy = 0; obj->vel.x = 0; obj->vel.y = 0; } } } } Object_position_set_clvec(obj, mv.start); Cell_add_object(obj); return; } void Move_object(object_t *obj) { int t; move_t mv; struct collans ans; int trycount = 5000; int team; /* !@# should make TEAM_NOT_SET 0 */ mv.obj = obj; Object_position_remember(obj); obj->collmode = 1; if (obj->type == OBJ_ASTEROID) { Move_asteroid(obj); return; } #if 1 if (obj->type == OBJ_BALL) { Move_ball(obj); return; } #else if (obj->type == OBJ_BALL) { if (obj->owner != NO_ID) team = Player_by_id(obj->owner).team; else team = TEAM_NOT_SET; mv.hitmask = BALL_BIT; } else #endif { mv.hitmask = NONBALL_BIT; team = obj->team; } mv.hitmask |= HITMASK(team); mv.start.cx = obj->pos.cx; mv.start.cy = obj->pos.cy; mv.delta.cx = FLOAT_TO_CLICK(obj->vel.x * timeStep); mv.delta.cy = FLOAT_TO_CLICK(obj->vel.y * timeStep); obj->extmove.cx = mv.delta.cx; obj->extmove.cy = mv.delta.cy; while (mv.delta.cx || mv.delta.cy) { if (!trycount--) { sprintf(msg, "COULDN'T MOVE OBJECT!!!! Type = %s, x = %d, y = %d. " "Object was DELETED. [*DEBUG*]", Object_typename(obj), mv.start.cx, mv.start.cy); warn(msg); Set_message(msg); obj->life = 0; return; } Move_point(&mv, &ans); mv.delta.cx -= ans.moved.cx; mv.delta.cy -= ans.moved.cy; mv.start.cx = WRAP_XCLICK(mv.start.cx + ans.moved.cx); mv.start.cy = WRAP_YCLICK(mv.start.cy + ans.moved.cy); if (ans.line != -1) { if (SIDE(obj->vel.x, obj->vel.y, ans.line) < 0) { if (!Bounce_object(obj, &mv, ans.line, 0)) break; /* Object destroyed by bounce */ } else if ( (t = Away(&mv, ans.line)) != -1) { if (!Clear_corner(&mv, obj, ans.line, t)) break; /* Object destroyed by bounces */ } } } Object_position_set_clvec(obj, mv.start); Cell_add_object(obj); return; } bool in_move_player = false; void Move_player(player_t *pl) { clpos_t pos; move_t mv; struct collans ans; double fric = friction; vector_t oldv; if (!Player_is_alive(pl)) { pos.cx = pl->pos.cx + FLOAT_TO_CLICK(pl->vel.x * timeStep); pos.cy = pl->pos.cy + FLOAT_TO_CLICK(pl->vel.y * timeStep); pos.cx = WRAP_XCLICK(pos.cx); pos.cy = WRAP_YCLICK(pos.cy); if (pos.cx != pl->pos.cx || pos.cy != pl->pos.cy) { Object_position_remember(OBJ_PTR(pl)); Object_position_set_clpos(OBJ_PTR(pl), pos); } pl->velocity = VECTOR_LENGTH(pl->vel); return; } /* Figure out which friction to use. */ if (Player_is_phasing(pl)) fric = friction; else if (Num_frictionAreas() > 0) { int group, i; in_move_player = true; group = is_inside(pl->pos.cx, pl->pos.cy, NONBALL_BIT, (object_t *)pl); if (group != NO_GROUP) { for (i = 0; i < Num_frictionAreas(); i++) { friction_area_t *fa = FrictionArea_by_index(i); if (fa->group == group) { fric = fa->friction; break; } } } in_move_player = false; } /* Velocity vector might change as a result of friction and options.coriolis. */ if (options.coriolis != 0.0) { oldv = pl->vel; pl->vel.x = oldv.x * coriolisCosine + oldv.y * coriolisSine; pl->vel.y = oldv.y * coriolisCosine - oldv.x * coriolisSine; } if (fric != 0.0) { pl->vel.x *= (1.0 - fric); pl->vel.y *= (1.0 - fric); } Object_position_remember(OBJ_PTR(pl)); pl->collmode = 1; mv.obj = OBJ_PTR(pl); mv.delta.cx = FLOAT_TO_CLICK(pl->vel.x * timeStep); mv.delta.cy = FLOAT_TO_CLICK(pl->vel.y * timeStep); #if 0 pl->extmove.cx = mv.delta.cx; pl->extmove.cy = mv.delta.cy; #endif if (Player_is_phasing(pl)) { pos.cx = pl->pos.cx + mv.delta.cx; pos.cy = pl->pos.cy + mv.delta.cy; pos = World_wrap_clpos(pos); Object_position_set_clpos(OBJ_PTR(pl), pos); } else { mv.hitmask = NONBALL_BIT | HITMASK(pl->team); mv.start.cx = pl->pos.cx; mv.start.cy = pl->pos.cy; while (mv.delta.cx || mv.delta.cy) { Shape_move(&mv, (shape_t *)pl->ship, pl->dir, &ans); mv.start.cx = WRAP_XCLICK(mv.start.cx + ans.moved.cx); mv.start.cy = WRAP_YCLICK(mv.start.cy + ans.moved.cy); mv.delta.cx -= ans.moved.cx; mv.delta.cy -= ans.moved.cy; if (ans.line != -1) { if (SIDE(pl->vel.x, pl->vel.y, ans.line) < 0) { Bounce_player(pl, &mv, ans.line, ans.point); if (Player_is_killed(pl)) break; } else if (!Shape_away(&mv, (shape_t *)pl->ship, pl->dir, ans.line, &ans)) { if (SIDE(pl->vel.x, pl->vel.y, ans.line) < 0) { Bounce_player(pl, &mv, ans.line, ans.point); if (Player_is_killed(pl)) break; } else { /* This case could be handled better, * I'll write the code for that if this * happens too often. */ /* At the moment it can be caused by illegal shipshapes * too, because they're not checked */ #if 0 sprintf(msg, "%s got stuck (Illegal shape? " "Shapes aren't checked) [*Notice*]", pl->name); Set_message(msg); #endif mv.delta.cx = 0; mv.delta.cy = 0; pl->vel.x = 0; pl->vel.y = 0; } } } } Object_position_set_clvec(OBJ_PTR(pl), mv.start); } pl->velocity = VECTOR_LENGTH(pl->vel); /* !@# Better than ignoring collisions after wall touch for players, * but might cause some erroneous hits */ pl->extmove.cx = CENTER_XCLICK(pl->pos.cx - pl->prevpos.cx); pl->extmove.cy = CENTER_YCLICK(pl->pos.cy - pl->prevpos.cy); return; } void Turn_player(player_t *pl, bool push) { int new_dir = MOD2((int)(pl->float_dir + 0.5), RES); int next_dir, sign, group; hitmask_t hitmask; struct collans ans; double length, relturn; clpos_t p,p2; if (recOpt) { if (record) *playback_data++ = new_dir; else if (playback) new_dir = *playback_data++; } if (new_dir == pl->dir) return; if (!Player_is_alive(pl)) { /* kps - what is the point of this ??? */ /* virus - it prevents you from turning ship while ur dead ;) */ /* kps - why is pl->dir set to new_dir then ? */ pl->dir = new_dir; return; } if (Player_is_phasing(pl)) { pl->dir = new_dir; return; } if (new_dir > pl->dir) { if (new_dir - pl->dir <= RES + pl->dir - new_dir) { sign = 1; relturn = (new_dir - pl->dir)/(double)RES; } else { sign = -1; relturn = (RES + pl->dir - new_dir)/(double)RES; } } else { if (pl->dir - new_dir <= RES + new_dir - pl->dir) { sign = -1; relturn = (pl->dir - new_dir)/(double)RES; } else { sign = 1; relturn = (RES + new_dir - pl->dir)/(double)RES; } } hitmask = NONBALL_BIT | HITMASK(pl->team); while (pl->dir != new_dir) { next_dir = MOD2(pl->dir + sign, RES); group = Shape_morph((shape_t *)pl->ship, pl->dir, (shape_t *)pl->ship, next_dir, hitmask, OBJ_PTR(pl), pl->pos.cx, pl->pos.cy, &ans); if (group != NO_GROUP) { double /*fact, */velon, velot; double cl, sl; /* cosine and sine of line angle */ double cln, sln; /* cosine and sine of line normal */ double pc, ps; /* cosine and sine of the points */ double pdc, pds; /* cosine and sine of the points direction */ double x, y, l/*, v*/; double power = pl->power; int a = (BIT(pl->used, USES_EMERGENCY_THRUST) ? MAX_AFTERBURNER : pl->item[ITEM_AFTERBURNER]); double inert = pl->mass; double cx,cy; static move_t move; Player_set_float_dir(pl, (double)pl->dir); if (!push) break; length = 0; l = 0.0; p = Ship_get_point_clpos((shipshape_t *)pl->ship, ans.point, pl->dir); p2 = Ship_get_point_clpos((shipshape_t *)pl->ship, (ans.point + 1)%(((shape_t *)pl->ship)->num_points), pl->dir); /* x,y defines the direction of the line that prevented turning */ if (ans.line != -1) { length = Wrap_length(linet[ans.line].delta.cx, linet[ans.line].delta.cy); x = linet[ans.line].delta.cx; y = linet[ans.line].delta.cy; } else { x = p2.cx - p.cx; y = p2.cy - p.cy; } l = sqrt(x*x + y*y); if (l==0.0) { warn("A zero length line somehow prevented turning!"); break; } /* cosine and sine of the line's tangent */ cl = x / l; sl = y / l; /* cosine and sine of the line's normal */ cln = sl; sln = -cl; /* pc and ps defines cosine and sine of the point vector */ if (ans.line != -1) { l = sqrt(p.cx * p.cx + p.cy * p.cy); pc = p.cx / l; ps = p.cy / l; } else { cx = CENTER_XCLICK(linet[ans.moved.cx].start.cx - pl->pos.cx); cy = CENTER_YCLICK(linet[ans.moved.cx].start.cy - pl->pos.cy); l = sqrt(cx * cx + cy * cy); pc = cx / l; ps = cy / l; } /* * pdc pds defines cosine and sine of the points direction * (if it is a wall point, its the direction of the spot * of the shipline that hit it) */ if (sign == 1) { pdc = -ps; pds = pc; } else { pdc = ps; pds = -pc; } /* * Shipshapes can be hit from the "inside", fixing this */ if ((ans.line == -1) && ((pdc * cln + pds * sln) > 0)) { cln = -cln; sln = -sln; } /* * Push strength depends on burner power, ship mass and * constantSpeed */ if (a) { power = AFTER_BURN_POWER(power, a); } velon = (power / inert)*(1 + options.constantSpeed); /* * If we are approaching the wall that stops our turn we * need to bounce the ship. (we do this with no movement * since we just want to bounce it, not move it) */ if ((pl->vel.x*cln + pl->vel.y*sln) < 0) { move.delta.cx = 0; move.delta.cy = 0; if (ans.line != -1) Bounce_player(pl, &move, ans.line, 0); else Bounce_player(pl, &move, ans.point, 0); } velot = velon * options.playerWallFriction * ((-pdc) * cl + (-pds) * sl); /* * First store away the old speed vector, then do the turnpush move * and then restore speed */ x = pl->vel.x; y = pl->vel.y; pl->vel.x = velon * cln + options.turnGrip * velot * cl; pl->vel.y = velon * sln + options.turnGrip * velot * sl; Move_player(pl); pl->vel.x = x + options.turnPushPersistence * pl->vel.x; pl->vel.y = y + options.turnPushPersistence * pl->vel.y; break; } pl->dir = next_dir; } return; }