/* PROJECTILES.C Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. 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. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html Friday, May 27, 1994 10:54:44 AM Friday, July 15, 1994 12:28:36 PM added maximum range. Monday, February 6, 1995 2:46:08 AM (Jason') persistent/virulent projectiles; media detonation effects. Tuesday, June 13, 1995 12:07:00 PM (Jason) non-melee projectiles must start above media. Monday, June 26, 1995 8:52:32 AM (Jason) bouncing projectiles Tuesday, August 1, 1995 3:31:08 PM (Jason) guided projectiles bite on low levels Thursday, August 17, 1995 9:35:13 AM (Jason) wandering projectiles Thursday, October 5, 1995 10:19:48 AM (Jason) until we fix it, calling translate_projectile() is too time consuming on high levels. Friday, October 6, 1995 8:35:04 AM (Jason) simpler guided projectile model. Feb 4, 2000 (Loren Petrich): Added effects of "penetrates media boundary" flag; assuming it to be like "penetrates media" flag until I can figure out the difference between the two. Changed halt() to assert(false) for better debugging Determined that "penetrates media boundary" means making a splash but nevertheless continuing Feb 6, 2000 (Loren Petrich): Added access to size of projectile-definition structure. Feb 9, 2000 (Loren Petrich): Put in handling of "penetrates media boundary" flag Feb 13, 2000 (Loren Petrich): Fixed bug in setting will_go_through when hitting a media boundary; this banishes the floating-mine effect. Feb 14, 2000 (Loren Petrich): Added workaround for Pfhorte bug: if there is no polygon on the other side of a non-solid line, then treat the line as if it was solid. Feb 16, 2000 (Loren Petrich): Improved the handling of "penetrates media boundary" -- if the rocket has that, it will explode on the surface, and then afterward on something it hits. Feb 17, 2000 (Loren Petrich): Fixed stuff near GUESS_HYPOTENUSE() to be long-distance-friendly Feb 19, 2000 (Loren Petrich): Added growable lists of indices of objects to be checked for collisions Jul 1, 2000 (Loren Petrich): Added Benad's changes Aug 30, 2000 (Loren Petrich): Added stuff for unpacking and packing Oct 13, 2000 (Loren Petrich) Converted the intersected-objects list into a Standard Template Library vector */ #include "cseries.h" #include "map.h" #include "interface.h" #include "effects.h" #include "monsters.h" #include "projectiles.h" #include "player.h" #include "scenery.h" #include "media.h" #include "SoundManager.h" #include "items.h" // LP additions #include "dynamic_limits.h" #include "Packing.h" #include "lua_script.h" /* //translate_projectile() must set _projectile_hit_landscape bit */ #ifdef env68k #pragma segment objects #endif /* ---------- constants */ enum { GRAVITATIONAL_ACCELERATION= 1, // per tick WANDER_MAGNITUDE= WORLD_ONE/TICKS_PER_SECOND, MINIMUM_REBOUND_VELOCITY= GRAVITATIONAL_ACCELERATION*TICKS_PER_SECOND/3 }; enum /* translate_projectile() flags */ { _flyby_of_current_player= 0x0001, _projectile_hit= 0x0002, _projectile_hit_monster= 0x0004, // monster_index in *obstruction_index _projectile_hit_floor= 0x0008, // polygon_index in *obstruction_index _projectile_hit_media= 0x0010, // polygon_index in *obstruction_index _projectile_hit_landscape= 0x0020, _projectile_hit_scenery= 0x0040 }; enum /* things the projectile can hit in detonate_projectile() */ { _hit_nothing, _hit_floor, _hit_media, _hit_ceiling, _hit_wall, _hit_monster, _hit_scenery }; #define MAXIMUM_PROJECTILE_ELEVATION (QUARTER_CIRCLE/2) /* ---------- structures */ /* ---------- private prototypes */ /* ---------- globals */ /* import projectile definition structures, constants and globals */ #include "projectile_definitions.h" /* if copy-protection fails, these are replaced externally with the rocket and the rifle bullet, respectively */ short alien_projectile_override= NONE; short human_projectile_override= NONE; // LP addition: growable list of intersected objects static vector IntersectedObjects; /* ---------- private prototypes */ static short adjust_projectile_type(world_point3d *origin, short polygon_index, short type, short owner_index, short owner_type, short intended_target_index, _fixed damage_scale); static void update_guided_projectile(short projectile_index); // LP change: added a location of hitting something; // it may be different from the new location, // as may happen for a "penetrates media boundary" projectile. static uint16 translate_projectile(short type, world_point3d *old_location, short old_polygon_index, world_point3d *new_location, short *new_polygon_index, short owner_index, short *obstruction_index); /*static*/ projectile_definition *get_projectile_definition( short type); /* ---------- code */ projectile_data *get_projectile_data( const short projectile_index) { struct projectile_data *projectile = GetMemberWithBounds(projectiles,projectile_index,MAXIMUM_PROJECTILES_PER_MAP); vassert(projectile, csprintf(temporary, "projectile index #%d is out of range", projectile_index)); vassert(SLOT_IS_USED(projectile), csprintf(temporary, "projectile index #%d (%p) is unused", projectile_index, projectile)); return projectile; } // LP change: moved down here to use the projectile definitions projectile_definition *get_projectile_definition( short type) { projectile_definition *definition = GetMemberWithBounds(projectile_definitions,type,NUMBER_OF_PROJECTILE_TYPES); vassert(definition, csprintf(temporary, "projectile type #%d is out of range", type)); return definition; } /* false means donÕt fire this (itÕs in a floor or ceiling or outside of the map), otherwise the monster that was intersected first (or NONE) is returned in target_index */ bool preflight_projectile( world_point3d *origin, short origin_polygon_index, world_point3d *destination, angle delta_theta, short type, short owner, short owner_type, short *obstruction_index) { bool legal_projectile= false; struct projectile_definition *definition= get_projectile_definition(type); (void) (delta_theta); /* will be used when we truly preflight projectiles */ (void) (owner_type); if (origin_polygon_index!=NONE) { world_distance dx= destination->x-origin->x, dy= destination->y-origin->y; angle elevation= arctangent(isqrt(dx*dx + dy*dy), destination->z-origin->z); if (elevationFULL_CIRCLE-MAXIMUM_PROJECTILE_ELEVATION) { struct polygon_data *origin_polygon= get_polygon_data(origin_polygon_index); // LP note: "penetrates media boundary" means "hit media surface and continue"; // it will act like "penetrates_media" here // Added idiot-proofing to media data media_data *media = get_media_data(origin_polygon->media_index); if (origin->z>origin_polygon->floor_height && origin->zceiling_height && (origin_polygon->media_index==NONE || definition->flags&(_penetrates_media) || (media ? origin->z>media->height : true))) { /* make sure it hits something */ uint16 flags= translate_projectile(type, origin, origin_polygon_index, destination, (short *) NULL, owner, obstruction_index); *obstruction_index= (flags&_projectile_hit_monster) ? get_object_data(*obstruction_index)->permutation : NONE; legal_projectile= true; } } } return legal_projectile; } // pointless if not an area-of-effect weapon void detonate_projectile( world_point3d *origin, short polygon_index, short type, short owner_index, short owner_type, _fixed damage_scale) { struct projectile_definition *definition= get_projectile_definition(type); struct damage_definition *damage= &definition->damage; damage->scale= damage_scale; damage_monsters_in_radius(NONE, owner_index, owner_type, origin, polygon_index, definition->area_of_effect, damage, NONE); if (definition->detonation_effect!=NONE) new_effect(origin, polygon_index, definition->detonation_effect, 0); L_Call_Projectile_Detonated(type, owner_index, polygon_index, *origin); } short new_projectile( world_point3d *origin, short polygon_index, world_point3d *_vector, angle delta_theta, /* ±¶theta is added (in a circle) to the angle before firing */ short type, short owner_index, short owner_type, short intended_target_index, /* can be NONE */ _fixed damage_scale) { struct projectile_definition *definition; struct projectile_data *projectile; short projectile_index; type= adjust_projectile_type(origin, polygon_index, type, owner_index, owner_type, intended_target_index, damage_scale); definition= get_projectile_definition(type); for (projectile_index= 0, projectile= projectiles; projectile_indexx, _vector->y); elevation= arctangent(isqrt(_vector->x*_vector->x+_vector->y*_vector->y), _vector->z); if (delta_theta) { if (!(definition->flags&_no_horizontal_error)) facing= normalize_angle(facing+global_random()%(2*delta_theta)-delta_theta); if (!(definition->flags&_no_vertical_error)) elevation= (definition->flags&_positive_vertical_error) ? normalize_angle(elevation+global_random()%delta_theta) : normalize_angle(elevation+global_random()%(2*delta_theta)-delta_theta); } object_index= new_map_object3d(origin, polygon_index, definition->collection==NONE ? NONE : BUILD_DESCRIPTOR(definition->collection, definition->shape), facing); if (object_index!=NONE) { object= get_object_data(object_index); projectile->type= (definition->flags&_alien_projectile) ? (alien_projectile_override==NONE ? type : alien_projectile_override) : (human_projectile_override==NONE ? type : human_projectile_override); projectile->object_index= object_index; projectile->owner_index= owner_index; projectile->target_index= intended_target_index; projectile->owner_type= owner_type; projectile->flags= 0; projectile->gravity= 0; projectile->ticks_since_last_contrail= projectile->contrail_count= 0; projectile->elevation= elevation; projectile->distance_travelled= 0; projectile->damage_scale= damage_scale; MARK_SLOT_AS_USED(projectile); SET_OBJECT_OWNER(object, _object_is_projectile); object->sound_pitch= definition->sound_pitch; } else { projectile_index= NONE; } break; } } if (projectile_index==MAXIMUM_PROJECTILES_PER_MAP) projectile_index= NONE; return projectile_index; } /* assumes ¶t==1 tick */ void move_projectiles( void) { struct projectile_data *projectile; short projectile_index; for (projectile_index=0,projectile=projectiles;projectile_indexobject_index); // if (!OBJECT_IS_INVISIBLE(object)) { struct projectile_definition *definition= get_projectile_definition(projectile->type); short old_polygon_index= object->polygon; world_point3d new_location, old_location; short obstruction_index, new_polygon_index; new_location= old_location= object->location; /* update our objectÕs animation */ animate_object(projectile->object_index); /* if weÕre supposed to end when our animation loops, check this condition */ if ((definition->flags&_stop_when_animation_loops) && (GET_OBJECT_ANIMATION_FLAGS(object)&_obj_last_frame_animated)) { remove_projectile(projectile_index); } else { world_distance speed= definition->speed; uint16 flags; /* base alien projectile speed on difficulty level */ if (definition->flags&_alien_projectile) { switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: speed-= speed>>3; break; case _easy_level: speed-= speed>>4; break; case _major_damage_level: speed+= speed>>3; break; case _total_carnage_level: speed+= speed>>2; break; } } /* if this is a guided projectile with a valid target, update guidance system */ if ((definition->flags&_guided) && projectile->target_index!=NONE && (dynamic_world->tick_count&1)) update_guided_projectile(projectile_index); /* move the projectile and check for collisions; if we didnÕt detonate move the projectile and check to see if we need to leave a contrail */ if ((definition->flags&_affected_by_half_gravity) && (dynamic_world->tick_count&1)) projectile->gravity-= GRAVITATIONAL_ACCELERATION; if (definition->flags&_affected_by_gravity) projectile->gravity-= GRAVITATIONAL_ACCELERATION; if (definition->flags&_doubly_affected_by_gravity) projectile->gravity-= 2*GRAVITATIONAL_ACCELERATION; new_location.z+= projectile->gravity; translate_point3d(&new_location, speed, object->facing, projectile->elevation); if (definition->flags&_vertical_wander) new_location.z+= (global_random()&1) ? WANDER_MAGNITUDE : -WANDER_MAGNITUDE; if (definition->flags&_horizontal_wander) translate_point3d(&new_location, (global_random()&1) ? WANDER_MAGNITUDE : -WANDER_MAGNITUDE, NORMALIZE_ANGLE(object->facing+QUARTER_CIRCLE), 0); flags= translate_projectile(projectile->type, &old_location, object->polygon, &new_location, &new_polygon_index, projectile->owner_index, &obstruction_index); // LP change: set up for penetrating media boundary bool will_go_through = false; if (flags&_projectile_hit) { if ((flags&_projectile_hit_floor) && (definition->flags&_rebounds_from_floor) && projectile->gravity<-MINIMUM_REBOUND_VELOCITY) { play_object_sound(projectile->object_index, definition->rebound_sound); projectile->gravity= - projectile->gravity + (projectile->gravity>>2); /* 0.75 */ } else { short monster_obstruction_index= (flags&_projectile_hit_monster) ? get_object_data(obstruction_index)->permutation : NONE; bool destroy_persistent_projectile= false; if (flags&_projectile_hit_scenery) damage_scenery(obstruction_index); /* cause damage, if we can */ if (!PROJECTILE_HAS_CAUSED_DAMAGE(projectile)) { struct damage_definition *damage= &definition->damage; damage->scale= projectile->damage_scale; if (definition->flags&_becomes_item_on_detonation) { if (monster_obstruction_index==NONE) { struct object_location location; location.p= object->location, location.p.z= 0; location.polygon_index= object->polygon; location.yaw= location.pitch= 0; location.flags= 0; // START Benad // Found it! // With new_item(), current_item_count[item] increases, but not // with try_and_add_player_item(). So reverse the effect of new_item in advance. dynamic_world->current_item_count[projectile->permutation]--; // END Benad new_item(&location, projectile->permutation); destroy_persistent_projectile= true; } else { if(MONSTER_IS_PLAYER(get_monster_data(monster_obstruction_index))) { short player_obstruction_index= monster_index_to_player_index(monster_obstruction_index); destroy_persistent_projectile= try_and_add_player_item(player_obstruction_index, projectile->permutation); } } } else { if (definition->area_of_effect) { damage_monsters_in_radius(monster_obstruction_index, projectile->owner_index, projectile->owner_type, &old_location, object->polygon, definition->area_of_effect, damage, projectile_index); } else { if (monster_obstruction_index!=NONE) damage_monster(monster_obstruction_index, projectile->owner_index, projectile->owner_type, &old_location, damage, projectile_index); } } } if ((definition->flags&_persistent) && !destroy_persistent_projectile) { SET_PROJECTILE_DAMAGE_STATUS(projectile, true); } else { short detonation_effect= definition->detonation_effect; if (monster_obstruction_index!=NONE) { if (definition->flags&_bleeding_projectile) { detonation_effect= get_monster_impact_effect(monster_obstruction_index); } if (definition->flags&_melee_projectile) { short new_detonation_effect= get_monster_melee_impact_effect(monster_obstruction_index); if (new_detonation_effect!=NONE) detonation_effect= new_detonation_effect; } } // LP addition: check if projectile will hit media and continue (PMB flag) // set will_go_through for later processing if (flags&_projectile_hit_media) { get_media_detonation_effect(get_polygon_data(obstruction_index)->media_index, definition->media_detonation_effect, &detonation_effect); // Be careful about parentheses here! will_go_through = (definition->flags&_penetrates_media_boundary) != 0; // Push the projectile upward or downward, if necessary if (will_go_through) { if (projectile->elevation == 0) {} else if (projectile->elevation < HALF_CIRCLE) new_location.z++; else if (projectile->elevation > HALF_CIRCLE) new_location.z--; } } // LP addition: don't detonate if going through media // if PMB is set; otherwise, detonate if doing so. // Some of the later routines may set both "hit landscape" and "hit media", // so be careful. if (flags&_projectile_hit_landscape && !(flags&_projectile_hit_media)) detonation_effect= NONE; if (detonation_effect!=NONE) new_effect(&new_location, new_polygon_index, detonation_effect, object->facing); L_Call_Projectile_Detonated(projectile->type, projectile->owner_index, new_polygon_index, new_location); if ((definition->flags&_persistent_and_virulent) && !destroy_persistent_projectile && monster_obstruction_index!=NONE) { projectile->owner_index= monster_obstruction_index; /* keep going, but donÕt hit this target again */ } // LP addition: don't remove a projectile that will hit media and continue (PMB flag) else if (!will_go_through) { remove_projectile(projectile_index); } } } } // Move the projectile if it hit nothing or it will go through media surface if (!(flags&_projectile_hit) || will_go_through) // else { /* move to the new_polygon_index */ translate_map_object(projectile->object_index, &new_location, new_polygon_index); /* should we leave a contrail at our old location? */ if ((projectile->ticks_since_last_contrail+=1)>=definition->ticks_between_contrails) { if (definition->maximum_contrails==NONE || projectile->contrail_countmaximum_contrails) { projectile->contrail_count+= 1; projectile->ticks_since_last_contrail= 0; if (definition->contrail_effect!=NONE) new_effect(&old_location, old_polygon_index, definition->contrail_effect, object->facing); } } if ((flags&_flyby_of_current_player) && !PROJECTILE_HAS_MADE_A_FLYBY(projectile)) { SET_PROJECTILE_FLYBY_STATUS(projectile, true); play_object_sound(projectile->object_index, definition->flyby_sound); } /* if we have a maximum range and we have exceeded it then remove the projectile */ if (definition->maximum_range!=NONE) { if ((projectile->distance_travelled+= speed)>=definition->maximum_range) { remove_projectile(projectile_index); } } } } } } } } void remove_projectile( short projectile_index) { struct projectile_data *projectile= get_projectile_data(projectile_index); remove_map_object(projectile->object_index); MARK_SLOT_AS_FREE(projectile); } void remove_all_projectiles( void) { struct projectile_data *projectile; short projectile_index; for (projectile_index=0,projectile=projectiles;projectile_indexowner_index==monster_index) projectile->owner_index= NONE; if (projectile->target_index==monster_index) projectile->target_index= NONE; } } void load_projectile_sounds( short projectile_type) { if (projectile_type!=NONE) { struct projectile_definition *definition= get_projectile_definition(projectile_type); SoundManager::instance()->LoadSound(definition->flyby_sound); SoundManager::instance()->LoadSound(definition->rebound_sound); } } void mark_projectile_collections( short projectile_type, bool loading) { if (projectile_type!=NONE) { struct projectile_definition *definition= get_projectile_definition(projectile_type); /* If the projectile is not invisible */ if (definition->collection!=NONE) { /* mark the projectile collection */ loading ? mark_collection_for_loading(definition->collection) : mark_collection_for_unloading(definition->collection); } /* mark the projectileÕs effectÕs collection */ mark_effect_collections(definition->detonation_effect, loading); mark_effect_collections(definition->contrail_effect, loading); } } void drop_the_ball( world_point3d *origin, short polygon_index, short owner_index, short owner_type, short item_type) { struct world_point3d _vector; short projectile_index; _vector.x= _vector.y= _vector.z= 0; projectile_index= new_projectile(origin, polygon_index, &_vector, 0, _projectile_ball, owner_index, owner_type, NONE, FIXED_ONE); if (projectile_index!=NONE) { struct projectile_data *projectile= get_projectile_data(projectile_index); struct object_data *object= get_object_data(projectile->object_index); projectile->permutation= item_type; object->shape= get_item_shape(item_type); } } /* ---------- private code */ static short adjust_projectile_type( world_point3d *origin, short polygon_index, short type, short owner_index, short owner_type, short intended_target_index, _fixed damage_scale) { struct projectile_definition *definition= get_projectile_definition(type); short media_index= get_polygon_data(polygon_index)->media_index; (void) (owner_index); (void) (owner_type); (void) (intended_target_index); (void) (damage_scale); if (media_index!=NONE) { // LP change: idiot-proofing media_data *media = get_media_data(media_index); if (media) { if (media->height>origin->z) { if (definition->media_projectile_promotion!=NONE) type= definition->media_projectile_promotion; } } } return type; } #define MAXIMUM_GUIDED_DELTA_YAW 8 #define MAXIMUM_GUIDED_DELTA_PITCH 6 /* changes are at a rate of ±1 angular unit per tick */ static void update_guided_projectile( short projectile_index) { struct projectile_data *projectile= get_projectile_data(projectile_index); struct monster_data *target= get_monster_data(projectile->target_index); struct object_data *projectile_object= get_object_data(projectile->object_index); struct object_data *target_object= get_object_data(target->object_index); world_distance target_radius, target_height; world_point3d target_location; get_monster_dimensions(projectile->target_index, &target_radius, &target_height); target_location= target_object->location; target_location.z+= target_height>>1; switch (target_object->transfer_mode) { case _xfer_invisibility: case _xfer_subtle_invisibility: /* canÕt hold lock on invisible targets unless on _total_carnage_level */ if (dynamic_world->game_information.difficulty_level!=_total_carnage_level) break; default: { // LP change: made this long-distance-friendly long dx= long(target_location.x) - long(projectile_object->location.x); long dy= long(target_location.y) - long(projectile_object->location.y); world_distance dz= target_location.z - projectile_object->location.z; short delta_yaw= MAXIMUM_GUIDED_DELTA_YAW+_normal_level-dynamic_world->game_information.difficulty_level; short delta_pitch= MAXIMUM_GUIDED_DELTA_PITCH+_normal_level-dynamic_world->game_information.difficulty_level; if (dx*sine_table[projectile_object->facing] - dy*cosine_table[projectile_object->facing] > 0) { // turn left delta_yaw= -delta_yaw; } dx= ABS(dx), dy= ABS(dy); if (GUESS_HYPOTENUSE(dx, dy)*sine_table[projectile->elevation] - dz*cosine_table[projectile->elevation] > 0) { // turn down delta_pitch= -delta_pitch; } projectile_object->facing= NORMALIZE_ANGLE(projectile_object->facing+delta_yaw); projectile->elevation= NORMALIZE_ANGLE(projectile->elevation+delta_pitch); #if 0 angle delta_pitch= HALF_CIRCLE - NORMALIZE_ANGLE(arctangent(guess_distance2d((world_point2d *)&target_location, (world_point2d *)&projectile_object->location), dz) - projectile->elevation); angle delta_yaw= HALF_CIRCLE - NORMALIZE_ANGLE(arctangent(dx, dy) - projectile_object->facing); short obstruction_index; uint16 flags; switch (dynamic_world->game_information.difficulty_level) { case _wuss_level: if (dynamic_world->tick_count&7) return; break; case _easy_level: if (dynamic_world->tick_count&3) return; break; } switch (dynamic_world->game_information.difficulty_level) { case _major_damage_level: case _total_carnage_level: flags= translate_projectile(projectile->type, &projectile_object->location, projectile_object->polygon, &target_location, (short *) NULL, projectile->owner_index, &obstruction_index); if (!(flags&_projectile_hit_monster)) break; /* if weÕre headed for a wall, donÕt steer */ default: projectile_object->facing= NORMALIZE_ANGLE(projectile_object->facing+PIN(delta_yaw, -maximum_delta_yaw, maximum_delta_yaw)); projectile->elevation= NORMALIZE_ANGLE(projectile->elevation+PIN(delta_pitch, -maximum_delta_pitch, maximum_delta_pitch)); } #endif } } } /* new_polygon_index==NULL means weÕre preflighting */ uint16 translate_projectile( short type, world_point3d *old_location, short old_polygon_index, world_point3d *new_location, short *new_polygon_index, short owner_index, short *obstruction_index) { struct projectile_definition *definition= get_projectile_definition(type); struct polygon_data *old_polygon; world_point3d intersection; world_distance media_height; short line_index; size_t intersected_object_count; short contact; uint16 flags= 0; *obstruction_index= NONE; contact= _hit_nothing; IntersectedObjects.clear(); old_polygon= get_polygon_data(old_polygon_index); if (new_polygon_index) *new_polygon_index= old_polygon_index; do { // LP note: "penetrates media boundary" means "hit media surface and continue"; // create a hack for enabling a projectile with this flag to continue // Idiot-proofing of media handling media_data *media = get_media_data(old_polygon->media_index); media_height= (old_polygon->media_index==NONE || definition->flags&_penetrates_media || !media) ? INT16_MIN : media->height; // This flag says it can bool traveled_underneath = (definition->flags&_penetrates_media_boundary) && (old_location->z <= media_height); /* add this polygonÕs monsters to our non-redundant list of possible intersections */ possible_intersecting_monsters(&IntersectedObjects, GLOBAL_INTERSECTING_MONSTER_BUFFER_SIZE, old_polygon_index, true); intersected_object_count = IntersectedObjects.size(); line_index= find_line_crossed_leaving_polygon(old_polygon_index, (world_point2d *)old_location, (world_point2d *)new_location); if (line_index!=NONE) { /* we crossed a line: if the line is solid, we detonate immediately on the wall, otherwise we calculate our Z at the line and compare it to the ceiling and floor heights of the old and new polygon to see if we hit a wall between the polygons, or the floor or ceiling somewhere in the old polygon */ struct line_data *line= get_line_data(line_index); find_line_intersection(&get_endpoint_data(line->endpoint_indexes[0])->vertex, &get_endpoint_data(line->endpoint_indexes[1])->vertex, old_location, new_location, &intersection); // LP change: workaround for Pfhorte map bug: check to see if there is a polygon // on the other side short adjacent_polygon_index= find_adjacent_polygon(old_polygon_index, line_index); if ((!LINE_IS_SOLID(line) || LINE_HAS_TRANSPARENT_SIDE(line)) && (adjacent_polygon_index != NONE)) { // short adjacent_polygon_index= find_adjacent_polygon(old_polygon_index, line_index); struct polygon_data *adjacent_polygon= get_polygon_data(adjacent_polygon_index); // LP change: if PMB is set, then a projectile can travel as if the liquid did not exist, // but it will be able to run into a media surface. // Here, test for whether the projectile is above the floor or the media surface; // ignore the latter test if PMB is set. if ((traveled_underneath || intersection.z>media_height) && intersection.z>old_polygon->floor_height) { // If PMB was set, check to see if the projectile hit the media surface. if ((!traveled_underneath || intersection.zceiling_height) { if (intersection.z>adjacent_polygon->floor_height&&intersection.zceiling_height) { if (!LINE_HAS_TRANSPARENT_SIDE(line) || (!new_polygon_index && (definition->flags&(_usually_pass_transparent_side|_sometimes_pass_transparent_side))) || ((definition->flags&_usually_pass_transparent_side) && (global_random()&3)) || ((definition->flags&_sometimes_pass_transparent_side) && !(global_random()&3))) { /* no intersections, successfully entered new polygon */ if (new_polygon_index) *new_polygon_index= adjacent_polygon_index; old_polygon_index= adjacent_polygon_index; old_polygon= adjacent_polygon; } else { /* hit and could not pass transparent texture */ contact= _hit_wall; } } else { /* hit wall created by ceiling or floor of new polygon; test to see if we toggle a control panel */ if (new_polygon_index && (definition->flags&_can_toggle_control_panels)) try_and_toggle_control_panel(old_polygon_index, line_index); contact= _hit_wall; } } else { /* hit ceiling of old polygon */ *obstruction_index= old_polygon_index; if (adjacent_polygon->ceiling_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape; // LP change: if PMB was set, check to see if hit media contact= (!traveled_underneath || old_polygon->ceiling_heightfloor_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape; // LP change: suppress media-hit test only if PMB was set and the projectile was underneath the media contact= (traveled_underneath || old_polygon->floor_height>media_height) ? _hit_floor : _hit_media; } } else { /* hit wall created by solid line; test to see if we toggle a control panel */ if (new_polygon_index && (definition->flags&_can_toggle_control_panels)) try_and_toggle_control_panel(old_polygon_index, line_index); if (line_is_landscaped(old_polygon_index, line_index, intersection.z)) flags|= _projectile_hit_landscape; contact= _hit_wall; } } else { /* make sure we didnÕt hit the ceiling or floor in this polygon */ // LP change: if PMB is set, then a projectile can travel as if the liquid did not exist, // but it will be able to run into a media surface. // Here, test for whether the projectile is above the floor or the media surface; // ignore the latter test if PMB is set. if ((traveled_underneath || new_location->z>media_height) && new_location->z>old_polygon->floor_height) { // If PMB was set, check to see if the projectile hit the media surface. if ((!traveled_underneath || new_location->zzceiling_height) { /* weÕre staying in this polygon and weÕre finally done screwing around; the caller can look in *new_polygon_index to find out where we ended up */ } else { /* hit ceiling of current polygon */ *obstruction_index= old_polygon_index; if (old_polygon->ceiling_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape; // LP change: if PMB was set, check to see if hit media contact= (!traveled_underneath || old_polygon->ceiling_heightfloor_transfer_mode==_xfer_landscape) flags|= _projectile_hit_landscape; // LP change: suppress media-hit test only if PMB was set and the projectile was underneath the media contact= (traveled_underneath || old_polygon->floor_height>media_height) ? _hit_floor : _hit_media; } } } while (line_index!=NONE&&contact==_hit_nothing); /* ceilings and floor intersections still donÕt have accurate intersection points, so calculate them */ if (contact!=_hit_nothing) { switch (contact) { case _hit_media: find_floor_or_ceiling_intersection(media_height, old_location, new_location, &intersection); break; case _hit_floor: find_floor_or_ceiling_intersection(old_polygon->floor_height, old_location, new_location, &intersection); break; case _hit_ceiling: find_floor_or_ceiling_intersection(old_polygon->ceiling_height, old_location, new_location, &intersection); break; } /* change new_location to the point of intersection with the ceiling, floor, or wall */ *new_location= intersection; } /* check our object list and find the best intersection ... if we find an intersection at all, then we hit this before we hit the wall, because the object list is checked against the clipped new_location. */ { world_distance best_intersection_distance = 0; world_distance distance_traveled; world_distance best_radius = 0; short best_intersection_object = NONE; distance_traveled= distance2d((world_point2d *)old_location, (world_point2d *)new_location); for (size_t i=0;ilocation, (world_point2d *)old_location, (world_point2d *)new_location); world_distance radius, height; if (object->permutation!=owner_index) /* donÕt hit ourselves */ { long radius_squared; switch (GET_OBJECT_OWNER(object)) { case _object_is_monster: get_monster_dimensions(object->permutation, &radius, &height); break; case _object_is_scenery: get_scenery_dimensions(object->permutation, &radius, &height); break; default: assert(false); break; } radius_squared= (radius+definition->radius)*(radius+definition->radius); if (separationlocation); world_distance projectile_z= distance_traveled ? old_location->z + (distance*(new_location->z-old_location->z))/distance_traveled : old_location->z; if ((height>0 && projectile_z>=object->location.z && projectile_z<=object->location.z+height) || (height<0 && projectile_z>=object->location.z+height && projectile_z<=object->location.z)) { if (best_intersection_object==NONE || distancepermutation)) && monster_index_to_player_index(object->permutation)==current_player_index) { flags|= _flyby_of_current_player; } } } } } if (best_intersection_object!=NONE) /* if we hit something, take it */ { struct object_data *object= get_object_data(best_intersection_object); *obstruction_index= best_intersection_object; if (distance_traveled) { world_distance actual_distance_to_hit; actual_distance_to_hit= distance2d((world_point2d *)old_location, (world_point2d *) &object->location); actual_distance_to_hit-= best_radius; new_location->x= old_location->x + (actual_distance_to_hit*(new_location->x-old_location->x))/distance_traveled; new_location->y= old_location->y + (actual_distance_to_hit*(new_location->y-old_location->y))/distance_traveled; new_location->z= old_location->z + (actual_distance_to_hit*(new_location->z-old_location->z))/distance_traveled; if (new_polygon_index) *new_polygon_index= find_new_object_polygon((world_point2d *) &object->location, (world_point2d *) new_location, object->polygon); } else { *new_location= *old_location; } } } switch (contact) { case _hit_monster: flags|= _projectile_hit|_projectile_hit_monster; break; case _hit_floor: flags|= _projectile_hit|_projectile_hit_floor; break; case _hit_media: flags|= _projectile_hit|_projectile_hit_media; break; case _hit_scenery: flags|= _projectile_hit|_projectile_hit_scenery; break; case _hit_nothing: break; default: flags|= _projectile_hit; break; } /* returns true if we hit something, false otherwise */ return flags; } // Indicates this feature of some type of projectile bool ProjectileIsGuided(short Type) { projectile_definition *definition = get_projectile_definition(Type); return ((definition->flags&_guided) != 0); } uint8 *unpack_projectile_data(uint8 *Stream, projectile_data* Objects, size_t Count) { uint8* S = Stream; projectile_data* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { StreamToValue(S,ObjPtr->type); StreamToValue(S,ObjPtr->object_index); StreamToValue(S,ObjPtr->target_index); StreamToValue(S,ObjPtr->elevation); StreamToValue(S,ObjPtr->owner_index); StreamToValue(S,ObjPtr->owner_type); StreamToValue(S,ObjPtr->flags); StreamToValue(S,ObjPtr->ticks_since_last_contrail); StreamToValue(S,ObjPtr->contrail_count); StreamToValue(S,ObjPtr->distance_travelled); StreamToValue(S,ObjPtr->gravity); StreamToValue(S,ObjPtr->damage_scale); StreamToValue(S,ObjPtr->permutation); S += 2*2; } assert((S - Stream) == static_cast(Count*SIZEOF_projectile_data)); return S; } uint8 *pack_projectile_data(uint8 *Stream, projectile_data* Objects, size_t Count) { uint8* S = Stream; projectile_data* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { ValueToStream(S,ObjPtr->type); ValueToStream(S,ObjPtr->object_index); ValueToStream(S,ObjPtr->target_index); ValueToStream(S,ObjPtr->elevation); ValueToStream(S,ObjPtr->owner_index); ValueToStream(S,ObjPtr->owner_type); ValueToStream(S,ObjPtr->flags); ValueToStream(S,ObjPtr->ticks_since_last_contrail); ValueToStream(S,ObjPtr->contrail_count); ValueToStream(S,ObjPtr->distance_travelled); ValueToStream(S,ObjPtr->gravity); ValueToStream(S,ObjPtr->damage_scale); ValueToStream(S,ObjPtr->permutation); S += 2*2; } assert((S - Stream) == static_cast(Count*SIZEOF_projectile_data)); return S; } uint8 *unpack_projectile_definition(uint8 *Stream, projectile_definition *Objects, size_t Count) { uint8* S = Stream; projectile_definition* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { StreamToValue(S,ObjPtr->collection); StreamToValue(S,ObjPtr->shape); StreamToValue(S,ObjPtr->detonation_effect); StreamToValue(S,ObjPtr->media_detonation_effect); StreamToValue(S,ObjPtr->contrail_effect); StreamToValue(S,ObjPtr->ticks_between_contrails); StreamToValue(S,ObjPtr->maximum_contrails); StreamToValue(S,ObjPtr->media_projectile_promotion); StreamToValue(S,ObjPtr->radius); StreamToValue(S,ObjPtr->area_of_effect); S = unpack_damage_definition(S,&ObjPtr->damage,1); StreamToValue(S,ObjPtr->flags); StreamToValue(S,ObjPtr->speed); StreamToValue(S,ObjPtr->maximum_range); StreamToValue(S,ObjPtr->sound_pitch); StreamToValue(S,ObjPtr->flyby_sound); StreamToValue(S,ObjPtr->rebound_sound); } assert((S - Stream) == static_cast(Count*SIZEOF_projectile_definition)); return S; } uint8 *unpack_projectile_definition(uint8 *Stream, size_t Count) { return unpack_projectile_definition(Stream,projectile_definitions,Count); } uint8 *pack_projectile_definition(uint8 *Stream, projectile_definition *Objects, size_t Count) { uint8* S = Stream; projectile_definition* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { ValueToStream(S,ObjPtr->collection); ValueToStream(S,ObjPtr->shape); ValueToStream(S,ObjPtr->detonation_effect); ValueToStream(S,ObjPtr->media_detonation_effect); ValueToStream(S,ObjPtr->contrail_effect); ValueToStream(S,ObjPtr->ticks_between_contrails); ValueToStream(S,ObjPtr->maximum_contrails); ValueToStream(S,ObjPtr->media_projectile_promotion); ValueToStream(S,ObjPtr->radius); ValueToStream(S,ObjPtr->area_of_effect); S = pack_damage_definition(S,&ObjPtr->damage,1); ValueToStream(S,ObjPtr->flags); ValueToStream(S,ObjPtr->speed); ValueToStream(S,ObjPtr->maximum_range); ValueToStream(S,ObjPtr->sound_pitch); ValueToStream(S,ObjPtr->flyby_sound); ValueToStream(S,ObjPtr->rebound_sound); } assert((S - Stream) == static_cast(Count*SIZEOF_projectile_definition)); return S; } uint8 *pack_projectile_definition(uint8 *Stream, size_t Count) { return pack_projectile_definition(Stream,projectile_definitions,Count); } void init_projectile_definitions() { memcpy(projectile_definitions, original_projectile_definitions, sizeof(projectile_definitions)); }