Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 1 | /* |
| 2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
| 3 | * described in COPYING.txt. |
||
| 4 | * Portions of this file are copyright Parallax Software and licensed |
||
| 5 | * according to the Parallax license below. |
||
| 6 | * See COPYING.txt for license details. |
||
| 7 | |||
| 8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
| 9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
| 10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
| 11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
| 12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
| 13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
| 14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
| 15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
| 16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
| 17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
| 18 | */ |
||
| 19 | |||
| 20 | /* |
||
| 21 | * |
||
| 22 | * This will contain the laser code |
||
| 23 | * |
||
| 24 | */ |
||
| 25 | |||
| 26 | #include <stdlib.h> |
||
| 27 | #include <stdio.h> |
||
| 28 | #include <time.h> |
||
| 29 | |||
| 30 | #include "inferno.h" |
||
| 31 | #include "game.h" |
||
| 32 | #include "bm.h" |
||
| 33 | #include "object.h" |
||
| 34 | #include "laser.h" |
||
| 35 | #include "args.h" |
||
| 36 | #include "segment.h" |
||
| 37 | #include "fvi.h" |
||
| 38 | #include "segpoint.h" |
||
| 39 | #include "dxxerror.h" |
||
| 40 | #include "key.h" |
||
| 41 | #include "texmap.h" |
||
| 42 | #include "gameseg.h" |
||
| 43 | #include "textures.h" |
||
| 44 | #include "render.h" |
||
| 45 | #include "vclip.h" |
||
| 46 | #include "fireball.h" |
||
| 47 | #include "polyobj.h" |
||
| 48 | #include "robot.h" |
||
| 49 | #include "weapon.h" |
||
| 50 | #include "newdemo.h" |
||
| 51 | #include "timer.h" |
||
| 52 | #include "player.h" |
||
| 53 | #include "sounds.h" |
||
| 54 | #include "ai.h" |
||
| 55 | #include "powerup.h" |
||
| 56 | #include "multi.h" |
||
| 57 | #include "physics.h" |
||
| 58 | #include "multi.h" |
||
| 59 | #include "fwd-wall.h" |
||
| 60 | #include "playsave.h" |
||
| 61 | |||
| 62 | #include "compiler-range_for.h" |
||
| 63 | #include "partial_range.h" |
||
| 64 | |||
| 65 | #ifdef NEWHOMER |
||
| 66 | #define HOMING_TRACKABLE_DOT_FRAME_TIME HOMING_TURN_TIME |
||
| 67 | static ubyte d_homer_tick_step = 0; |
||
| 68 | static fix d_homer_tick_count = 0; |
||
| 69 | #else |
||
| 70 | #define HOMING_TRACKABLE_DOT_FRAME_TIME FrameTime |
||
| 71 | #endif |
||
| 72 | |||
| 73 | static int Muzzle_queue_index; |
||
| 74 | |||
| 75 | namespace dsx { |
||
| 76 | static imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2); |
||
| 77 | static imobjptridx_t find_homing_object(const vms_vector &curpos, vmobjptridx_t tracker); |
||
| 78 | |||
| 79 | //--------------------------------------------------------------------------------- |
||
| 80 | // Called by render code.... determines if the laser is from a robot or the |
||
| 81 | // player and calls the appropriate routine. |
||
| 82 | |||
| 83 | void Laser_render(grs_canvas &canvas, const object_base &obj) |
||
| 84 | { |
||
| 85 | auto &wi = Weapon_info[get_weapon_id(obj)]; |
||
| 86 | switch(wi.render_type) |
||
| 87 | { |
||
| 88 | case WEAPON_RENDER_LASER: |
||
| 89 | Int3(); // Not supported anymore! |
||
| 90 | //Laser_draw_one(obj-Objects, Weapon_info[obj->id].bitmap ); |
||
| 91 | break; |
||
| 92 | case WEAPON_RENDER_BLOB: |
||
| 93 | draw_object_blob(canvas, obj, wi.bitmap); |
||
| 94 | break; |
||
| 95 | case WEAPON_RENDER_POLYMODEL: |
||
| 96 | break; |
||
| 97 | case WEAPON_RENDER_VCLIP: |
||
| 98 | Int3(); // Oops, not supported, type added by mk on 09/09/94, but not for lasers... |
||
| 99 | DXX_BOOST_FALLTHROUGH; |
||
| 100 | default: |
||
| 101 | Error( "Invalid weapon render type in Laser_render\n" ); |
||
| 102 | } |
||
| 103 | } |
||
| 104 | |||
| 105 | //--------------------------------------------------------------------------------- |
||
| 106 | // Draws a texture-mapped laser bolt |
||
| 107 | |||
| 108 | //void Laser_draw_one( int objnum, grs_bitmap * bmp ) |
||
| 109 | //{ |
||
| 110 | // int t1, t2, t3; |
||
| 111 | // g3s_point p1, p2; |
||
| 112 | // object *obj; |
||
| 113 | // vms_vector start_pos,end_pos; |
||
| 114 | // |
||
| 115 | // obj = &Objects[objnum]; |
||
| 116 | // |
||
| 117 | // start_pos = obj->pos; |
||
| 118 | // vm_vec_scale_add(&end_pos,&start_pos,&obj->orient.fvec,-Laser_length); |
||
| 119 | // |
||
| 120 | // g3_rotate_point(&p1,&start_pos); |
||
| 121 | // g3_rotate_point(&p2,&end_pos); |
||
| 122 | // |
||
| 123 | // t1 = Lighting_on; |
||
| 124 | // t2 = Interpolation_method; |
||
| 125 | // t3 = Transparency_on; |
||
| 126 | // |
||
| 127 | // Lighting_on = 0; |
||
| 128 | // //Interpolation_method = 3; // Full perspective |
||
| 129 | // Interpolation_method = 1; // Linear |
||
| 130 | // Transparency_on = 1; |
||
| 131 | // |
||
| 132 | // //gr_setcolor( gr_getcolor(31,15,0)); |
||
| 133 | // //g3_draw_line_ptrs(p1,p2); |
||
| 134 | // //g3_draw_rod(p1,0x2000,p2,0x2000); |
||
| 135 | // //g3_draw_rod(p1,Laser_width,p2,Laser_width); |
||
| 136 | // g3_draw_rod_tmap(bmp,&p2,Laser_width,&p1,Laser_width,0); |
||
| 137 | // Lighting_on = t1; |
||
| 138 | // Interpolation_method = t2; |
||
| 139 | // Transparency_on = t3; |
||
| 140 | // |
||
| 141 | //} |
||
| 142 | |||
| 143 | static bool ignore_proximity_weapon(const object &o) |
||
| 144 | { |
||
| 145 | if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(o))) |
||
| 146 | return false; |
||
| 147 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 148 | return GameTime64 > o.ctype.laser_info.creation_time + F1_0*2; |
||
| 149 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 150 | return GameTime64 > o.ctype.laser_info.creation_time + F1_0*4; |
||
| 151 | #endif |
||
| 152 | } |
||
| 153 | |||
| 154 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 155 | static bool ignore_phoenix_weapon(const object &) |
||
| 156 | { |
||
| 157 | return false; |
||
| 158 | } |
||
| 159 | |||
| 160 | static bool ignore_guided_missile_weapon(const object &) |
||
| 161 | { |
||
| 162 | return false; |
||
| 163 | } |
||
| 164 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 165 | static bool ignore_phoenix_weapon(const object &o) |
||
| 166 | { |
||
| 167 | return get_weapon_id(o) == weapon_id_type::PHOENIX_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0/4; |
||
| 168 | } |
||
| 169 | |||
| 170 | static bool ignore_guided_missile_weapon(const object &o) |
||
| 171 | { |
||
| 172 | return get_weapon_id(o) == weapon_id_type::GUIDEDMISS_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0*2; |
||
| 173 | } |
||
| 174 | #endif |
||
| 175 | |||
| 176 | // Changed by MK on 09/07/94 |
||
| 177 | // I want you to be able to blow up your own bombs. |
||
| 178 | // AND...Your proximity bombs can blow you up if they're 2.0 seconds or more old. |
||
| 179 | // Changed by MK on 06/06/95: Now must be 4.0 seconds old. Much valid Net-complaining. |
||
| 180 | bool laser_are_related(const vcobjptridx_t o1, const vcobjptridx_t o2) |
||
| 181 | { |
||
| 182 | // See if o2 is the parent of o1 |
||
| 183 | if (o1->type == OBJ_WEAPON) |
||
| 184 | if (laser_parent_is_object(o1->ctype.laser_info, o2)) |
||
| 185 | { |
||
| 186 | // o1 is a weapon, o2 is the parent of 1, so if o1 is PROXIMITY_BOMB and o2 is player, they are related only if o1 < 2.0 seconds old |
||
| 187 | if (ignore_proximity_weapon(o1) || ignore_guided_missile_weapon(o1) || ignore_phoenix_weapon(o1)) |
||
| 188 | { |
||
| 189 | return 0; |
||
| 190 | } else |
||
| 191 | return 1; |
||
| 192 | } |
||
| 193 | |||
| 194 | // See if o1 is the parent of o2 |
||
| 195 | if (o2->type == OBJ_WEAPON) |
||
| 196 | { |
||
| 197 | if (laser_parent_is_object(o2->ctype.laser_info, o1)) |
||
| 198 | { |
||
| 199 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 200 | // o2 is a weapon, o1 is the parent of 2, so if o2 is PROXIMITY_BOMB and o1 is player, they are related only if o1 < 2.0 seconds old |
||
| 201 | if (ignore_proximity_weapon(o2) || ignore_guided_missile_weapon(o2) || ignore_phoenix_weapon(o2)) |
||
| 202 | { |
||
| 203 | return 0; |
||
| 204 | } else |
||
| 205 | #endif |
||
| 206 | return 1; |
||
| 207 | } |
||
| 208 | } |
||
| 209 | |||
| 210 | // They must both be weapons |
||
| 211 | if (o1->type != OBJ_WEAPON || o2->type != OBJ_WEAPON) |
||
| 212 | return 0; |
||
| 213 | |||
| 214 | // Here is the 09/07/94 change -- Siblings must be identical, others can hurt each other |
||
| 215 | // See if they're siblings... |
||
| 216 | // MK: 06/08/95, Don't allow prox bombs to detonate for 3/4 second. Else too likely to get toasted by your own bomb if hit by opponent. |
||
| 217 | const auto o1id = get_weapon_id(o1); |
||
| 218 | const auto o2id = get_weapon_id(o2); |
||
| 219 | auto &o1li = o1->ctype.laser_info; |
||
| 220 | auto &o2li = o2->ctype.laser_info; |
||
| 221 | if (o1li.parent_num == o2li.parent_num && o1li.parent_signature == o2li.parent_signature) |
||
| 222 | { |
||
| 223 | if (is_proximity_bomb_or_player_smart_mine(o1id) || is_proximity_bomb_or_player_smart_mine(o2id)) |
||
| 224 | { |
||
| 225 | // If neither is older than 1/2 second, then can't blow up! |
||
| 226 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 227 | if (!(GameTime64 > o1li.creation_time + F1_0/2 || GameTime64 > o2li.creation_time + F1_0/2)) |
||
| 228 | return 1; |
||
| 229 | else |
||
| 230 | #endif |
||
| 231 | return 0; |
||
| 232 | } else |
||
| 233 | return 1; |
||
| 234 | } |
||
| 235 | |||
| 236 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 237 | // Anything can cause a collision with a robot super prox mine. |
||
| 238 | if (!( |
||
| 239 | o1id == weapon_id_type::ROBOT_SUPERPROX_ID || o2id == weapon_id_type::ROBOT_SUPERPROX_ID || |
||
| 240 | o1id == weapon_id_type::PROXIMITY_ID || o2id == weapon_id_type::PROXIMITY_ID || |
||
| 241 | o1id == weapon_id_type::SUPERPROX_ID || o2id == weapon_id_type::SUPERPROX_ID || |
||
| 242 | o1id == weapon_id_type::PMINE_ID || o2id == weapon_id_type::PMINE_ID |
||
| 243 | )) |
||
| 244 | return 1; |
||
| 245 | #endif |
||
| 246 | return 0; |
||
| 247 | } |
||
| 248 | |||
| 249 | } |
||
| 250 | |||
| 251 | namespace dcx { |
||
| 252 | |||
| 253 | constexpr vm_distance MAX_SMART_DISTANCE(F1_0*150); |
||
| 254 | constexpr vm_distance_squared MAX_SMART_DISTANCE_SQUARED = MAX_SMART_DISTANCE * MAX_SMART_DISTANCE; |
||
| 255 | static void do_muzzle_stuff(segnum_t segnum, const vms_vector &pos) |
||
| 256 | { |
||
| 257 | auto &m = Muzzle_data[Muzzle_queue_index]; |
||
| 258 | Muzzle_queue_index++; |
||
| 259 | if (Muzzle_queue_index >= MUZZLE_QUEUE_MAX) |
||
| 260 | Muzzle_queue_index = 0; |
||
| 261 | m.segnum = segnum; |
||
| 262 | m.pos = pos; |
||
| 263 | m.create_time = timer_query(); |
||
| 264 | } |
||
| 265 | |||
| 266 | __attribute_noreturn |
||
| 267 | static void report_invalid_weapon_render_type(const int weapon_type, const unsigned render_type) |
||
| 268 | { |
||
| 269 | char buf[96]; |
||
| 270 | snprintf(buf, sizeof(buf), "invalid weapon render type %u on weapon %i", render_type, weapon_type); |
||
| 271 | throw std::runtime_error(buf); |
||
| 272 | } |
||
| 273 | |||
| 274 | } |
||
| 275 | |||
| 276 | namespace dsx { |
||
| 277 | |||
| 278 | //creates a weapon object |
||
| 279 | static imobjptridx_t create_weapon_object(int weapon_type,const vmsegptridx_t segnum, const vms_vector &position) |
||
| 280 | { |
||
| 281 | render_type_t rtype; |
||
| 282 | fix laser_radius = -1; |
||
| 283 | |||
| 284 | switch( Weapon_info[weapon_type].render_type ) { |
||
| 285 | |||
| 286 | case WEAPON_RENDER_BLOB: |
||
| 287 | rtype = RT_LASER; // Render as a laser even if blob (see render code above for explanation) |
||
| 288 | laser_radius = Weapon_info[weapon_type].blob_size; |
||
| 289 | break; |
||
| 290 | case WEAPON_RENDER_POLYMODEL: |
||
| 291 | laser_radius = 0; // Filled in below. |
||
| 292 | rtype = RT_POLYOBJ; |
||
| 293 | break; |
||
| 294 | case WEAPON_RENDER_LASER: |
||
| 295 | Int3(); // Not supported anymore |
||
| 296 | return object_none; |
||
| 297 | case WEAPON_RENDER_NONE: |
||
| 298 | rtype = RT_NONE; |
||
| 299 | laser_radius = F1_0; |
||
| 300 | break; |
||
| 301 | case WEAPON_RENDER_VCLIP: |
||
| 302 | rtype = RT_WEAPON_VCLIP; |
||
| 303 | laser_radius = Weapon_info[weapon_type].blob_size; |
||
| 304 | break; |
||
| 305 | default: |
||
| 306 | report_invalid_weapon_render_type(weapon_type, Weapon_info[weapon_type].render_type); |
||
| 307 | } |
||
| 308 | |||
| 309 | Assert(laser_radius != -1); |
||
| 310 | |||
| 311 | auto &&obj = obj_create( OBJ_WEAPON, weapon_type, segnum, position, NULL, laser_radius, CT_WEAPON, MT_PHYSICS, rtype); |
||
| 312 | if (obj == object_none) |
||
| 313 | return object_none; |
||
| 314 | |||
| 315 | if (Weapon_info[weapon_type].render_type == WEAPON_RENDER_POLYMODEL) { |
||
| 316 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
| 317 | obj->rtype.pobj_info.model_num = Weapon_info[get_weapon_id(obj)].model_num; |
||
| 318 | obj->size = fixdiv(Polygon_models[obj->rtype.pobj_info.model_num].rad,Weapon_info[get_weapon_id(obj)].po_len_to_width_ratio); |
||
| 319 | } |
||
| 320 | |||
| 321 | obj->mtype.phys_info.mass = Weapon_info[weapon_type].mass; |
||
| 322 | obj->mtype.phys_info.drag = Weapon_info[weapon_type].drag; |
||
| 323 | vm_vec_zero(obj->mtype.phys_info.thrust); |
||
| 324 | |||
| 325 | if (Weapon_info[weapon_type].bounce==1) |
||
| 326 | obj->mtype.phys_info.flags |= PF_BOUNCE; |
||
| 327 | |||
| 328 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 329 | if (Weapon_info[weapon_type].bounce==2 || cheats.bouncyfire) |
||
| 330 | obj->mtype.phys_info.flags |= PF_BOUNCE+PF_BOUNCES_TWICE; |
||
| 331 | #endif |
||
| 332 | |||
| 333 | |||
| 334 | return obj; |
||
| 335 | } |
||
| 336 | |||
| 337 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 338 | // ------------------------------------------------------------------------------------------------------------------------------- |
||
| 339 | // ***** HEY ARTISTS!! ***** |
||
| 340 | // Here are the constants you're looking for! --MK |
||
| 341 | |||
| 342 | // Change the following constants to affect the look of the omega cannon. |
||
| 343 | // Changing these constants will not affect the damage done. |
||
| 344 | // WARNING: If you change DESIRED_OMEGA_DIST and MAX_OMEGA_BLOBS, you don't merely change the look of the cannon, |
||
| 345 | // you change its range. If you decrease DESIRED_OMEGA_DIST, you decrease how far the gun can fire. |
||
| 346 | constexpr std::integral_constant<fix, F1_0/20> OMEGA_BASE_TIME{}; // How many blobs per second!! No FPS-based blob creation anymore, no FPS-based damage anymore! |
||
| 347 | constexpr std::integral_constant<unsigned, 3> MIN_OMEGA_BLOBS{}; // No matter how close the obstruction, at this many blobs created. |
||
| 348 | constexpr std::integral_constant<fix, F1_0*3> MIN_OMEGA_DIST{}; // At least this distance between blobs, unless doing so would violate MIN_OMEGA_BLOBS |
||
| 349 | constexpr std::integral_constant<fix, F1_0*5> DESIRED_OMEGA_DIST{}; // This is the desired distance between blobs. For distances > MIN_OMEGA_BLOBS*DESIRED_OMEGA_DIST, but not very large, this will apply. |
||
| 350 | constexpr std::integral_constant<unsigned, 16> MAX_OMEGA_BLOBS{}; // No matter how far away the obstruction, this is the maximum number of blobs. |
||
| 351 | constexpr vm_distance MAX_OMEGA_DIST{MAX_OMEGA_BLOBS * DESIRED_OMEGA_DIST}; // Maximum extent of lightning blobs. |
||
| 352 | constexpr vm_distance_squared MAX_OMEGA_DIST_SQUARED{MAX_OMEGA_DIST * MAX_OMEGA_DIST}; |
||
| 353 | |||
| 354 | // Additionally, several constants which apply to homing objects in general control the behavior of the Omega Cannon. |
||
| 355 | // They are defined in laser.h. They are copied here for reference. These values are valid on 1/10/96: |
||
| 356 | // If you want the Omega Cannon view cone to be different than the Homing Missile viewcone, contact MK to make the change. |
||
| 357 | // (Unless you are a programmer, in which case, do it yourself!) |
||
| 358 | #define OMEGA_MIN_TRACKABLE_DOT (15*F1_0/16) // Larger values mean narrower cone. F1_0 means damn near impossible. 0 means 180 degree field of view. |
||
| 359 | constexpr vm_distance OMEGA_MAX_TRACKABLE_DIST = MAX_OMEGA_DIST; // An object must be at least this close to be tracked. |
||
| 360 | |||
| 361 | // Note, you don't need to change these constants. You can control damage and energy consumption by changing the |
||
| 362 | // usual bitmaps.tbl parameters. |
||
| 363 | #define OMEGA_DAMAGE_SCALE 32 // Controls how much damage is done. This gets multiplied by the damage specified in bitmaps.tbl in the $WEAPON line. |
||
| 364 | #define OMEGA_ENERGY_CONSUMPTION 16 // Controls how much energy is consumed. This gets multiplied by the energy parameter from bitmaps.tbl. |
||
| 365 | // ------------------------------------------------------------------------------------------------------------------------------- |
||
| 366 | |||
| 367 | // Delete omega blobs further away than MAX_OMEGA_DIST |
||
| 368 | // Since last omega blob has VERY high velocity it's impossible to ensure a constant travel distance on varying FPS. So delete if they exceed their maximum distance. |
||
| 369 | static int omega_cleanup(fvcobjptr &vcobjptr, const vmobjptridx_t weapon) |
||
| 370 | { |
||
| 371 | if (weapon->type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID) |
||
| 372 | return 0; |
||
| 373 | auto &weapon_laser_info = weapon->ctype.laser_info; |
||
| 374 | auto &obj = *vcobjptr(weapon_laser_info.parent_num); |
||
| 375 | if (laser_parent_is_matching_signature(weapon_laser_info, obj)) |
||
| 376 | if (vm_vec_dist2(weapon->pos, obj.pos) > MAX_OMEGA_DIST_SQUARED) |
||
| 377 | { |
||
| 378 | obj_delete(LevelUniqueObjectState, Segments, weapon); |
||
| 379 | return 1; |
||
| 380 | } |
||
| 381 | |||
| 382 | return 0; |
||
| 383 | } |
||
| 384 | |||
| 385 | // Return true if ok to do Omega damage. For Multiplayer games. See comment for omega_cleanup() |
||
| 386 | int ok_to_do_omega_damage(const object &weapon) |
||
| 387 | { |
||
| 388 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 389 | auto &vcobjptr = Objects.vcptr; |
||
| 390 | if (weapon.type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID) |
||
| 391 | return 1; |
||
| 392 | if (!(Game_mode & GM_MULTI)) |
||
| 393 | return 1; |
||
| 394 | auto &weapon_laser_info = weapon.ctype.laser_info; |
||
| 395 | auto &obj = *vcobjptr(weapon_laser_info.parent_num); |
||
| 396 | if (laser_parent_is_matching_signature(weapon_laser_info, obj)) |
||
| 397 | if (vm_vec_dist2(obj.pos, weapon.pos) > MAX_OMEGA_DIST_SQUARED) |
||
| 398 | return 0; |
||
| 399 | |||
| 400 | return 1; |
||
| 401 | } |
||
| 402 | |||
| 403 | // --------------------------------------------------------------------------------- |
||
| 404 | static void create_omega_blobs(const imsegptridx_t firing_segnum, const vms_vector &firing_pos, const vms_vector &goal_pos, const vmobjptridx_t parent_objp) |
||
| 405 | { |
||
| 406 | imobjptridx_t last_created_objnum = object_none; |
||
| 407 | fix dist_to_goal = 0, omega_blob_dist = 0; |
||
| 408 | std::array<fix, MAX_OMEGA_BLOBS> perturb_array{}; |
||
| 409 | |||
| 410 | auto vec_to_goal = vm_vec_sub(goal_pos, firing_pos); |
||
| 411 | dist_to_goal = vm_vec_normalize_quick(vec_to_goal); |
||
| 412 | |||
| 413 | unsigned num_omega_blobs = 0; |
||
| 414 | if (dist_to_goal < MIN_OMEGA_BLOBS * MIN_OMEGA_DIST) { |
||
| 415 | omega_blob_dist = MIN_OMEGA_DIST; |
||
| 416 | num_omega_blobs = dist_to_goal/omega_blob_dist; |
||
| 417 | if (num_omega_blobs == 0) |
||
| 418 | num_omega_blobs = 1; |
||
| 419 | } else { |
||
| 420 | omega_blob_dist = DESIRED_OMEGA_DIST; |
||
| 421 | num_omega_blobs = dist_to_goal / omega_blob_dist; |
||
| 422 | if (num_omega_blobs > MAX_OMEGA_BLOBS) { |
||
| 423 | num_omega_blobs = MAX_OMEGA_BLOBS; |
||
| 424 | omega_blob_dist = dist_to_goal / num_omega_blobs; |
||
| 425 | } else if (num_omega_blobs < MIN_OMEGA_BLOBS) { |
||
| 426 | num_omega_blobs = MIN_OMEGA_BLOBS; |
||
| 427 | omega_blob_dist = dist_to_goal / num_omega_blobs; |
||
| 428 | } |
||
| 429 | } |
||
| 430 | |||
| 431 | vms_vector omega_delta_vector, blob_pos; |
||
| 432 | omega_delta_vector = vec_to_goal; |
||
| 433 | vm_vec_scale(omega_delta_vector, omega_blob_dist); |
||
| 434 | |||
| 435 | // Now, create all the blobs |
||
| 436 | blob_pos = firing_pos; |
||
| 437 | auto last_segnum = firing_segnum; |
||
| 438 | |||
| 439 | // If nearby, don't perturb vector. If not nearby, start halfway out. |
||
| 440 | if (dist_to_goal < MIN_OMEGA_DIST*4) { |
||
| 441 | } else { |
||
| 442 | vm_vec_scale_add2(blob_pos, omega_delta_vector, F1_0/2); // Put first blob half way out. |
||
| 443 | for (int i=0; i<num_omega_blobs/2; i++) { |
||
| 444 | perturb_array[i] = F1_0*i + F1_0/4; |
||
| 445 | perturb_array[num_omega_blobs-1-i] = F1_0*i; |
||
| 446 | } |
||
| 447 | } |
||
| 448 | |||
| 449 | // Create random perturbation vector, but favor _not_ going up in player's reference. |
||
| 450 | auto perturb_vec = make_random_vector(); |
||
| 451 | vm_vec_scale_add2(perturb_vec, parent_objp->orient.uvec, -F1_0/2); |
||
| 452 | |||
| 453 | Doing_lighting_hack_flag = 1; // Ugly, but prevents blobs which are probably outside the mine from killing framerate. |
||
| 454 | |||
| 455 | const auto &Difficulty_level = GameUniqueState.Difficulty_level; |
||
| 456 | for (int i=0; i<num_omega_blobs; i++) { |
||
| 457 | // This will put the last blob right at the destination object, causing damage. |
||
| 458 | if (i == num_omega_blobs-1) |
||
| 459 | vm_vec_scale_add2(blob_pos, omega_delta_vector, 15*F1_0/32); // Move last blob another (almost) half section |
||
| 460 | |||
| 461 | // Every so often, re-perturb blobs |
||
| 462 | if ((i % 4) == 3) { |
||
| 463 | vm_vec_scale_add2(perturb_vec, make_random_vector(), F1_0/4); |
||
| 464 | } |
||
| 465 | |||
| 466 | const auto temp_pos = vm_vec_scale_add(blob_pos, perturb_vec, perturb_array[i]); |
||
| 467 | |||
| 468 | const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, temp_pos, last_segnum); |
||
| 469 | if (segnum != segment_none) { |
||
| 470 | last_segnum = segnum; |
||
| 471 | auto objp = obj_create(OBJ_WEAPON, weapon_id_type::OMEGA_ID, segnum, temp_pos, NULL, 0, CT_WEAPON, MT_PHYSICS, RT_WEAPON_VCLIP ); |
||
| 472 | if (objp == object_none) |
||
| 473 | break; |
||
| 474 | |||
| 475 | last_created_objnum = objp; |
||
| 476 | |||
| 477 | objp->lifeleft = OMEGA_BASE_TIME+(d_rand()/8); // add little randomness so the lighting effect becomes a little more interesting |
||
| 478 | objp->mtype.phys_info.velocity = vec_to_goal; |
||
| 479 | |||
| 480 | // Only make the last one move fast, else multiple blobs might collide with target. |
||
| 481 | vm_vec_scale(objp->mtype.phys_info.velocity, F1_0*4); |
||
| 482 | |||
| 483 | const auto &weapon_info = Weapon_info[get_weapon_id(objp)]; |
||
| 484 | objp->size = weapon_info.blob_size; |
||
| 485 | |||
| 486 | objp->shields = fixmul(OMEGA_DAMAGE_SCALE*OMEGA_BASE_TIME, weapon_info.strength[Difficulty_level]); |
||
| 487 | |||
| 488 | objp->ctype.laser_info.parent_type = parent_objp->type; |
||
| 489 | objp->ctype.laser_info.parent_signature = parent_objp->signature; |
||
| 490 | objp->ctype.laser_info.parent_num = parent_objp; |
||
| 491 | objp->movement_type = MT_NONE; // Only last one moves, that will get bashed below. |
||
| 492 | |||
| 493 | } |
||
| 494 | vm_vec_add2(blob_pos, omega_delta_vector); |
||
| 495 | } |
||
| 496 | |||
| 497 | // Make last one move faster, but it's already moving at speed = F1_0*4. |
||
| 498 | if (last_created_objnum != object_none) { |
||
| 499 | vm_vec_scale(last_created_objnum->mtype.phys_info.velocity, Weapon_info[weapon_id_type::OMEGA_ID].speed[Difficulty_level]/4); |
||
| 500 | last_created_objnum->movement_type = MT_PHYSICS; |
||
| 501 | } |
||
| 502 | |||
| 503 | Doing_lighting_hack_flag = 0; |
||
| 504 | } |
||
| 505 | |||
| 506 | #define MIN_OMEGA_CHARGE (MAX_OMEGA_CHARGE/8) |
||
| 507 | #define OMEGA_CHARGE_SCALE 4 // FrameTime / OMEGA_CHARGE_SCALE added to Omega_charge every frame. |
||
| 508 | |||
| 509 | #define OMEGA_CHARGE_SCALE 4 |
||
| 510 | |||
| 511 | fix get_omega_energy_consumption(const fix delta_charge) |
||
| 512 | { |
||
| 513 | const fix energy_used = fixmul(F1_0 * 190 / 17, delta_charge); |
||
| 514 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
| 515 | return Difficulty_level < 2 |
||
| 516 | ? fixmul(energy_used, i2f(Difficulty_level + 2) / 4) |
||
| 517 | : energy_used; |
||
| 518 | } |
||
| 519 | |||
| 520 | // --------------------------------------------------------------------------------- |
||
| 521 | // Call this every frame to recharge the Omega Cannon. |
||
| 522 | void omega_charge_frame(player_info &player_info) |
||
| 523 | { |
||
| 524 | if (!(player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(primary_weapon_index_t::OMEGA_INDEX))) |
||
| 525 | return; |
||
| 526 | auto &Omega_charge = player_info.Omega_charge; |
||
| 527 | if (Omega_charge >= MAX_OMEGA_CHARGE) |
||
| 528 | return; |
||
| 529 | |||
| 530 | if (Player_dead_state != player_dead_state::no) |
||
| 531 | return; |
||
| 532 | |||
| 533 | // Don't charge while firing. Wait 1/3 second after firing before recharging |
||
| 534 | auto &Omega_recharge_delay = player_info.Omega_recharge_delay; |
||
| 535 | if (Omega_recharge_delay) |
||
| 536 | { |
||
| 537 | if (Omega_recharge_delay > FrameTime) |
||
| 538 | { |
||
| 539 | Omega_recharge_delay -= FrameTime; |
||
| 540 | return; |
||
| 541 | } |
||
| 542 | Omega_recharge_delay = 0; |
||
| 543 | } |
||
| 544 | |||
| 545 | if (auto &energy = player_info.energy) |
||
| 546 | { |
||
| 547 | const auto old_omega_charge = Omega_charge; |
||
| 548 | Omega_charge += FrameTime/OMEGA_CHARGE_SCALE; |
||
| 549 | if (Omega_charge > MAX_OMEGA_CHARGE) |
||
| 550 | Omega_charge = MAX_OMEGA_CHARGE; |
||
| 551 | |||
| 552 | const auto energy_used = get_omega_energy_consumption(Omega_charge - old_omega_charge); |
||
| 553 | energy -= energy_used; |
||
| 554 | if (energy < 0) |
||
| 555 | energy = 0; |
||
| 556 | } |
||
| 557 | |||
| 558 | |||
| 559 | } |
||
| 560 | |||
| 561 | // -- fix Last_omega_muzzle_flash_time; |
||
| 562 | |||
| 563 | // --------------------------------------------------------------------------------- |
||
| 564 | // *objp is the object firing the omega cannon |
||
| 565 | // *pos is the location from which the omega bolt starts |
||
| 566 | static void do_omega_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t parent_objp, const vms_vector &firing_pos, const vmobjptridx_t weapon_objp) |
||
| 567 | { |
||
| 568 | vms_vector goal_pos; |
||
| 569 | if (parent_objp->type == OBJ_PLAYER && get_player_id(parent_objp) == Player_num) |
||
| 570 | { |
||
| 571 | // If charge >= min, or (some charge and zero energy), allow to fire. |
||
| 572 | auto &player_info = parent_objp->ctype.player_info; |
||
| 573 | auto &Omega_charge = player_info.Omega_charge; |
||
| 574 | if (!((Omega_charge >= MIN_OMEGA_CHARGE) || (Omega_charge && !player_info.energy))) { |
||
| 575 | obj_delete(LevelUniqueObjectState, Segments, weapon_objp); |
||
| 576 | return; |
||
| 577 | } |
||
| 578 | |||
| 579 | Omega_charge -= OMEGA_BASE_TIME; |
||
| 580 | if (Omega_charge < 0) |
||
| 581 | Omega_charge = 0; |
||
| 582 | |||
| 583 | player_info.Omega_recharge_delay = F1_0 / 3; |
||
| 584 | } |
||
| 585 | |||
| 586 | weapon_objp->ctype.laser_info.parent_type = parent_objp->type; |
||
| 587 | weapon_objp->ctype.laser_info.parent_num = parent_objp.get_unchecked_index(); |
||
| 588 | weapon_objp->ctype.laser_info.parent_signature = parent_objp->signature; |
||
| 589 | |||
| 590 | const auto &&lock_objnum = find_homing_object(firing_pos, weapon_objp); |
||
| 591 | |||
| 592 | const auto &&firing_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, firing_pos, Segments.vmptridx(parent_objp->segnum)); |
||
| 593 | |||
| 594 | // Play sound. |
||
| 595 | { |
||
| 596 | const auto flash_sound = Weapon_info[get_weapon_id(weapon_objp)].flash_sound; |
||
| 597 | if ( parent_objp == Viewer ) |
||
| 598 | digi_play_sample(flash_sound, F1_0); |
||
| 599 | else |
||
| 600 | digi_link_sound_to_pos(flash_sound, vmsegptridx(weapon_objp->segnum), 0, weapon_objp->pos, 0, F1_0); |
||
| 601 | } |
||
| 602 | |||
| 603 | // -- if ((Last_omega_muzzle_flash_time + F1_0/4 < GameTime) || (Last_omega_muzzle_flash_time > GameTime)) { |
||
| 604 | // -- do_muzzle_stuff(firing_segnum, firing_pos); |
||
| 605 | // -- Last_omega_muzzle_flash_time = GameTime; |
||
| 606 | // -- } |
||
| 607 | |||
| 608 | // Delete the original object. Its only purpose in life was to determine which object to home in on. |
||
| 609 | obj_delete(LevelUniqueObjectState, Segments, weapon_objp); |
||
| 610 | |||
| 611 | // If couldn't lock on anything, fire straight ahead. |
||
| 612 | if (lock_objnum == object_none) { |
||
| 613 | fvi_query fq; |
||
| 614 | fvi_info hit_data; |
||
| 615 | int fate; |
||
| 616 | const auto &&perturbed_fvec = vm_vec_scale_add(parent_objp->orient.fvec, make_random_vector(), F1_0/16); |
||
| 617 | vm_vec_scale_add(goal_pos, firing_pos, perturbed_fvec, MAX_OMEGA_DIST); |
||
| 618 | fq.startseg = firing_segnum; |
||
| 619 | if (fq.startseg == segment_none) { |
||
| 620 | return; |
||
| 621 | } |
||
| 622 | fq.p0 = &firing_pos; |
||
| 623 | fq.p1 = &goal_pos; |
||
| 624 | fq.rad = 0; |
||
| 625 | fq.thisobjnum = parent_objp; |
||
| 626 | fq.ignore_obj_list.first = nullptr; |
||
| 627 | fq.flags = FQ_IGNORE_POWERUPS | FQ_TRANSPOINT | FQ_CHECK_OBJS; //what about trans walls??? |
||
| 628 | |||
| 629 | fate = find_vector_intersection(fq, hit_data); |
||
| 630 | if (fate != HIT_NONE) { |
||
| 631 | Assert(hit_data.hit_seg != segment_none); // How can this be? We went from inside the mine to outside without hitting anything? |
||
| 632 | goal_pos = hit_data.hit_pnt; |
||
| 633 | } |
||
| 634 | } else |
||
| 635 | goal_pos = lock_objnum->pos; |
||
| 636 | |||
| 637 | // This is where we create a pile of omega blobs! |
||
| 638 | create_omega_blobs(firing_segnum, firing_pos, goal_pos, parent_objp); |
||
| 639 | |||
| 640 | } |
||
| 641 | |||
| 642 | static int is_laser_weapon_type(const weapon_id_type weapon_type) |
||
| 643 | { |
||
| 644 | return weapon_type == weapon_id_type::LASER_ID_L1 || |
||
| 645 | weapon_type == weapon_id_type::LASER_ID_L2 || |
||
| 646 | weapon_type == weapon_id_type::LASER_ID_L3 || |
||
| 647 | weapon_type == weapon_id_type::LASER_ID_L4 || |
||
| 648 | weapon_type == weapon_id_type::LASER_ID_L5 || |
||
| 649 | weapon_type == weapon_id_type::LASER_ID_L6; |
||
| 650 | } |
||
| 651 | #endif |
||
| 652 | |||
| 653 | // --------------------------------------------------------------------------------- |
||
| 654 | // Initializes a laser after Fire is pressed |
||
| 655 | // Returns object number. |
||
| 656 | imobjptridx_t Laser_create_new(const vms_vector &direction, const vms_vector &position, const vmsegptridx_t segnum, const vmobjptridx_t parent, weapon_id_type weapon_type, int make_sound ) |
||
| 657 | { |
||
| 658 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 659 | auto &vmobjptr = Objects.vmptr; |
||
| 660 | fix parent_speed, weapon_speed; |
||
| 661 | fix volume; |
||
| 662 | fix laser_length=0; |
||
| 663 | |||
| 664 | if (weapon_type >= N_weapon_types) |
||
| 665 | { |
||
| 666 | con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "invalid weapon id %u fired by parent %hu (type %u) in segment %hu"), weapon_type, parent.get_unchecked_index(), parent->type, segnum.get_unchecked_index()); |
||
| 667 | weapon_type = weapon_id_type::LASER_ID_L1; |
||
| 668 | } |
||
| 669 | |||
| 670 | // Don't let homing blobs make muzzle flash. |
||
| 671 | if (parent->type == OBJ_ROBOT) |
||
| 672 | do_muzzle_stuff(segnum, position); |
||
| 673 | |||
| 674 | const imobjptridx_t obj = create_weapon_object(weapon_type,segnum,position); |
||
| 675 | |||
| 676 | if (obj == object_none) |
||
| 677 | { |
||
| 678 | return object_none; |
||
| 679 | } |
||
| 680 | const auto &weapon_info = Weapon_info[weapon_type]; |
||
| 681 | |||
| 682 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 683 | // Do the special Omega Cannon stuff. Then return on account of everything that follows does |
||
| 684 | // not apply to the Omega Cannon. |
||
| 685 | if (weapon_type == weapon_id_type::OMEGA_ID) { |
||
| 686 | // Create orientation matrix for tracking purposes. |
||
| 687 | vm_vector_2_matrix( obj->orient, direction, &parent->orient.uvec ,nullptr); |
||
| 688 | |||
| 689 | if (parent != Viewer && parent->type != OBJ_WEAPON) { |
||
| 690 | // Muzzle flash |
||
| 691 | if (weapon_info.flash_vclip > -1 ) |
||
| 692 | object_create_muzzle_flash(vmsegptridx(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip); |
||
| 693 | } |
||
| 694 | |||
| 695 | do_omega_stuff(vmsegptridx, parent, position, obj); |
||
| 696 | |||
| 697 | return obj; |
||
| 698 | } |
||
| 699 | #endif |
||
| 700 | |||
| 701 | if (parent->type == OBJ_PLAYER) { |
||
| 702 | if (weapon_type == weapon_id_type::FUSION_ID) { |
||
| 703 | int fusion_scale; |
||
| 704 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 705 | if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)) |
||
| 706 | fusion_scale = 2; |
||
| 707 | else |
||
| 708 | #endif |
||
| 709 | fusion_scale = 4; |
||
| 710 | |||
| 711 | auto &player_info = parent->ctype.player_info; |
||
| 712 | const auto Fusion_charge = player_info.Fusion_charge; |
||
| 713 | if (Fusion_charge <= 0) |
||
| 714 | obj->ctype.laser_info.multiplier = F1_0; |
||
| 715 | else if (Fusion_charge <= F1_0*fusion_scale) |
||
| 716 | obj->ctype.laser_info.multiplier = F1_0 + Fusion_charge/2; |
||
| 717 | else |
||
| 718 | obj->ctype.laser_info.multiplier = F1_0*fusion_scale; |
||
| 719 | |||
| 720 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 721 | // Fusion damage was boosted by mk on 3/27 (for reg 1.1 release), but we only want it to apply to single player games. |
||
| 722 | if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)) |
||
| 723 | obj->ctype.laser_info.multiplier /= 2; |
||
| 724 | #endif |
||
| 725 | } |
||
| 726 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 727 | else if (!EMULATING_D1 && is_laser_weapon_type(weapon_type) && (parent->ctype.player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)) |
||
| 728 | obj->ctype.laser_info.multiplier = F1_0*3/4; |
||
| 729 | else if (weapon_type == weapon_id_type::GUIDEDMISS_ID) { |
||
| 730 | if (parent==get_local_player().objnum) { |
||
| 731 | LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(obj, Player_num); |
||
| 732 | if (Newdemo_state==ND_STATE_RECORDING) |
||
| 733 | newdemo_record_guided_start(); |
||
| 734 | } |
||
| 735 | } |
||
| 736 | #endif |
||
| 737 | } |
||
| 738 | |||
| 739 | // Make children of smart bomb bounce so if they hit a wall right away, they |
||
| 740 | // won't detonate. The frame interval code will clear this bit after 1/2 second. |
||
| 741 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 742 | if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID)) |
||
| 743 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 744 | if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::SMART_MINE_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID) || (weapon_type == weapon_id_type::EARTHSHAKER_MEGA_ID)) |
||
| 745 | #endif |
||
| 746 | obj->mtype.phys_info.flags |= PF_BOUNCE; |
||
| 747 | |||
| 748 | if (weapon_info.render_type == WEAPON_RENDER_POLYMODEL) |
||
| 749 | { |
||
| 750 | auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models; |
||
| 751 | laser_length = Polygon_models[obj->rtype.pobj_info.model_num].rad * 2; |
||
| 752 | } |
||
| 753 | |||
| 754 | if (weapon_type == weapon_id_type::FLARE_ID) |
||
| 755 | obj->mtype.phys_info.flags |= PF_STICK; //this obj sticks to walls |
||
| 756 | |||
| 757 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
| 758 | obj->shields = weapon_info.strength[Difficulty_level]; |
||
| 759 | |||
| 760 | // Fill in laser-specific data |
||
| 761 | |||
| 762 | obj->lifeleft = weapon_info.lifetime; |
||
| 763 | obj->ctype.laser_info.parent_type = parent->type; |
||
| 764 | obj->ctype.laser_info.parent_signature = parent->signature; |
||
| 765 | obj->ctype.laser_info.parent_num = parent; |
||
| 766 | |||
| 767 | // Assign parent type to highest level creator. This propagates parent type down from |
||
| 768 | // the original creator through weapons which create children of their own (ie, smart missile) |
||
| 769 | if (parent->type == OBJ_WEAPON) { |
||
| 770 | auto highest_parent = parent; |
||
| 771 | int count; |
||
| 772 | |||
| 773 | count = 0; |
||
| 774 | while ((count++ < 10) && (highest_parent->type == OBJ_WEAPON)) { |
||
| 775 | const auto next_parent = highest_parent->ctype.laser_info.parent_num; |
||
| 776 | const auto &&parent_objp = parent.absolute_sibling(next_parent); |
||
| 777 | if (!laser_parent_is_object(highest_parent->ctype.laser_info, parent_objp)) |
||
| 778 | break; // Probably means parent was killed. Just continue. |
||
| 779 | |||
| 780 | if (next_parent == highest_parent) { |
||
| 781 | Int3(); // Hmm, object is parent of itself. This would seem to be bad, no? |
||
| 782 | break; |
||
| 783 | } |
||
| 784 | |||
| 785 | highest_parent = parent_objp; |
||
| 786 | |||
| 787 | obj->ctype.laser_info.parent_num = highest_parent; |
||
| 788 | obj->ctype.laser_info.parent_type = highest_parent->type; |
||
| 789 | obj->ctype.laser_info.parent_signature = highest_parent->signature; |
||
| 790 | } |
||
| 791 | } |
||
| 792 | |||
| 793 | // Create orientation matrix so we can look from this pov |
||
| 794 | // Homing missiles also need an orientation matrix so they know if they can make a turn. |
||
| 795 | if ((weapon_info.homing_flag && (obj->ctype.laser_info.track_goal = object_none, true)) || obj->render_type == RT_POLYOBJ) |
||
| 796 | vm_vector_2_matrix(obj->orient, direction, &parent->orient.uvec, nullptr); |
||
| 797 | |||
| 798 | if (( parent != Viewer ) && (parent->type != OBJ_WEAPON)) { |
||
| 799 | // Muzzle flash |
||
| 800 | if (weapon_info.flash_vclip > -1 ) |
||
| 801 | object_create_muzzle_flash(segnum.absolute_sibling(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip); |
||
| 802 | } |
||
| 803 | |||
| 804 | volume = F1_0; |
||
| 805 | if (weapon_info.flash_sound > -1) |
||
| 806 | { |
||
| 807 | if (make_sound) { |
||
| 808 | if (parent == Viewer) |
||
| 809 | { |
||
| 810 | if (weapon_type == weapon_id_type::VULCAN_ID) // Make your own vulcan gun 1/2 as loud. |
||
| 811 | volume = F1_0 / 2; |
||
| 812 | digi_play_sample(weapon_info.flash_sound, volume); |
||
| 813 | } else { |
||
| 814 | digi_link_sound_to_pos(weapon_info.flash_sound, segnum.absolute_sibling(obj->segnum), 0, obj->pos, 0, volume); |
||
| 815 | } |
||
| 816 | } |
||
| 817 | } |
||
| 818 | |||
| 819 | // Fire the laser from the gun tip so that the back end of the laser bolt is at the gun tip. |
||
| 820 | // Move 1 frame, so that the end-tip of the laser is touching the gun barrel. |
||
| 821 | // This also jitters the laser a bit so that it doesn't alias. |
||
| 822 | // Don't do for weapons created by weapons. |
||
| 823 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 824 | if (parent->type != OBJ_WEAPON && weapon_info.render_type != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID) |
||
| 825 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 826 | if (parent->type == OBJ_PLAYER && weapon_info.render_type != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID) |
||
| 827 | #endif |
||
| 828 | { |
||
| 829 | const auto end_pos = vm_vec_scale_add(obj->pos, direction, (laser_length/2) ); |
||
| 830 | const auto &&end_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, end_pos, Segments.vmptridx(obj->segnum)); |
||
| 831 | if (end_segnum != obj->segnum) { |
||
| 832 | if (end_segnum != segment_none) { |
||
| 833 | obj->pos = end_pos; |
||
| 834 | obj_relink(vmobjptr, vmsegptr, obj, end_segnum); |
||
| 835 | } |
||
| 836 | } else |
||
| 837 | obj->pos = end_pos; |
||
| 838 | } |
||
| 839 | |||
| 840 | // Here's where to fix the problem with objects which are moving backwards imparting higher velocity to their weaponfire. |
||
| 841 | // Find out if moving backwards. |
||
| 842 | if (is_proximity_bomb_or_player_smart_mine(weapon_type)) { |
||
| 843 | parent_speed = vm_vec_mag_quick(parent->mtype.phys_info.velocity); |
||
| 844 | if (vm_vec_dot(parent->mtype.phys_info.velocity, parent->orient.fvec) < 0) |
||
| 845 | parent_speed = -parent_speed; |
||
| 846 | } else |
||
| 847 | parent_speed = 0; |
||
| 848 | |||
| 849 | weapon_speed = weapon_info.speed[Difficulty_level]; |
||
| 850 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 851 | if (weapon_info.speedvar != 128) |
||
| 852 | { |
||
| 853 | fix randval; |
||
| 854 | |||
| 855 | // Get a scale factor between speedvar% and 1.0. |
||
| 856 | randval = F1_0 - ((d_rand() * weapon_info.speedvar) >> 6); |
||
| 857 | weapon_speed = fixmul(weapon_speed, randval); |
||
| 858 | } |
||
| 859 | #endif |
||
| 860 | |||
| 861 | // Ugly hack (too bad we're on a deadline), for homing missiles dropped by smart bomb, start them out slower. |
||
| 862 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 863 | if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID) |
||
| 864 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 865 | if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::SMART_MINE_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID || weapon_type == weapon_id_type::EARTHSHAKER_MEGA_ID) |
||
| 866 | #endif |
||
| 867 | weapon_speed /= 4; |
||
| 868 | |||
| 869 | if (weapon_info.thrust) |
||
| 870 | weapon_speed /= 2; |
||
| 871 | |||
| 872 | vm_vec_copy_scale(obj->mtype.phys_info.velocity, direction, weapon_speed + parent_speed ); |
||
| 873 | |||
| 874 | // Set thrust |
||
| 875 | if (weapon_info.thrust) |
||
| 876 | { |
||
| 877 | obj->mtype.phys_info.thrust = obj->mtype.phys_info.velocity; |
||
| 878 | vm_vec_scale(obj->mtype.phys_info.thrust, fixdiv(weapon_info.thrust, weapon_speed+parent_speed)); |
||
| 879 | } |
||
| 880 | |||
| 881 | if (obj->type == OBJ_WEAPON && weapon_type == weapon_id_type::FLARE_ID) |
||
| 882 | obj->lifeleft += (d_rand()-16384) << 2; // add in -2..2 seconds |
||
| 883 | |||
| 884 | return obj; |
||
| 885 | } |
||
| 886 | |||
| 887 | // ----------------------------------------------------------------------------------------------------------- |
||
| 888 | // Calls Laser_create_new, but takes care of the segment and point computation for you. |
||
| 889 | imobjptridx_t Laser_create_new_easy(const vms_vector &direction, const vms_vector &position, const vmobjptridx_t parent, weapon_id_type weapon_type, int make_sound ) |
||
| 890 | { |
||
| 891 | fvi_query fq; |
||
| 892 | fvi_info hit_data; |
||
| 893 | int fate; |
||
| 894 | |||
| 895 | // Find segment containing laser fire position. If the robot is straddling a segment, the position from |
||
| 896 | // which it fires may be in a different segment, which is bad news for find_vector_intersection. So, cast |
||
| 897 | // a ray from the object center (whose segment we know) to the laser position. Then, in the call to Laser_create_new |
||
| 898 | // use the data returned from this call to find_vector_intersection. |
||
| 899 | // Note that while find_vector_intersection is pretty slow, it is not terribly slow if the destination point is |
||
| 900 | // in the same segment as the source point. |
||
| 901 | |||
| 902 | fq.p0 = &parent->pos; |
||
| 903 | fq.startseg = parent->segnum; |
||
| 904 | fq.p1 = &position; |
||
| 905 | fq.rad = 0; |
||
| 906 | fq.thisobjnum = parent; |
||
| 907 | fq.ignore_obj_list.first = nullptr; |
||
| 908 | fq.flags = FQ_TRANSWALL | FQ_CHECK_OBJS; //what about trans walls??? |
||
| 909 | |||
| 910 | fate = find_vector_intersection(fq, hit_data); |
||
| 911 | if (fate != HIT_NONE || hit_data.hit_seg==segment_none) { |
||
| 912 | return object_none; |
||
| 913 | } |
||
| 914 | |||
| 915 | return Laser_create_new(direction, hit_data.hit_pnt, vmsegptridx(hit_data.hit_seg), parent, weapon_type, make_sound); |
||
| 916 | } |
||
| 917 | |||
| 918 | } |
||
| 919 | |||
| 920 | namespace dcx { |
||
| 921 | |||
| 922 | std::array<muzzle_info, MUZZLE_QUEUE_MAX> Muzzle_data; |
||
| 923 | |||
| 924 | static fix get_weapon_energy_usage_with_difficulty(const weapon_info &wi, const Difficulty_level_type Difficulty_level) |
||
| 925 | { |
||
| 926 | const auto energy_usage = wi.energy_usage; |
||
| 927 | if (Difficulty_level < 2) |
||
| 928 | return fixmul(energy_usage, i2f(Difficulty_level + 2) / 4); |
||
| 929 | return energy_usage; |
||
| 930 | } |
||
| 931 | |||
| 932 | } |
||
| 933 | |||
| 934 | namespace d1x { |
||
| 935 | |||
| 936 | static fix get_scaled_min_trackable_dot() |
||
| 937 | { |
||
| 938 | const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME; |
||
| 939 | if (curFT <= F1_0 / 16) |
||
| 940 | return (3 * (F1_0 - HOMING_MIN_TRACKABLE_DOT) / 4 + HOMING_MIN_TRACKABLE_DOT); |
||
| 941 | else if (curFT < F1_0 / 4) |
||
| 942 | return (fixmul(F1_0 - HOMING_MIN_TRACKABLE_DOT, F1_0 - 4 * curFT) + HOMING_MIN_TRACKABLE_DOT); |
||
| 943 | else |
||
| 944 | return (HOMING_MIN_TRACKABLE_DOT); |
||
| 945 | } |
||
| 946 | |||
| 947 | } |
||
| 948 | |||
| 949 | namespace dsx { |
||
| 950 | |||
| 951 | // ----------------------------------------------------------------------------------------------------------- |
||
| 952 | // Determine if two objects are on a line of sight. If so, return true, else return false. |
||
| 953 | // Calls fvi. |
||
| 954 | int object_to_object_visibility(const vcobjptridx_t obj1, const object_base &obj2, int trans_type) |
||
| 955 | { |
||
| 956 | fvi_query fq; |
||
| 957 | fvi_info hit_data; |
||
| 958 | |||
| 959 | fq.p0 = &obj1->pos; |
||
| 960 | fq.startseg = obj1->segnum; |
||
| 961 | fq.p1 = &obj2.pos; |
||
| 962 | fq.rad = 0x10; |
||
| 963 | fq.thisobjnum = obj1; |
||
| 964 | fq.ignore_obj_list.first = nullptr; |
||
| 965 | fq.flags = trans_type; |
||
| 966 | |||
| 967 | switch(const auto fate = find_vector_intersection(fq, hit_data)) |
||
| 968 | { |
||
| 969 | case HIT_NONE: |
||
| 970 | return 1; |
||
| 971 | case HIT_WALL: |
||
| 972 | return 0; |
||
| 973 | default: |
||
| 974 | con_printf(CON_VERBOSE, "object_to_object_visibility: fate=%u for object %hu{%hu/%i,%i,%i} to {%i,%i,%i}", fate, static_cast<vcobjptridx_t::integral_type>(obj1), obj1->segnum, obj1->pos.x, obj1->pos.y, obj1->pos.z, obj2.pos.x, obj2.pos.y, obj2.pos.z); |
||
| 975 | // Int3(); // Contact Mike: Oops, what happened? What is fate? |
||
| 976 | // 2 = hit object (impossible), 3 = bad starting point (bad) |
||
| 977 | break; |
||
| 978 | } |
||
| 979 | return 0; |
||
| 980 | } |
||
| 981 | |||
| 982 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 983 | static fix get_scaled_min_trackable_dot() |
||
| 984 | { |
||
| 985 | if (EMULATING_D1) |
||
| 986 | return ::d1x::get_scaled_min_trackable_dot(); |
||
| 987 | const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME; |
||
| 988 | if (curFT <= F1_0/64) |
||
| 989 | return (HOMING_MIN_TRACKABLE_DOT); |
||
| 990 | else if (curFT < F1_0/32) |
||
| 991 | return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - 2*curFT); |
||
| 992 | else if (curFT < F1_0/4) |
||
| 993 | return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/16 - curFT); |
||
| 994 | else |
||
| 995 | return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/8); |
||
| 996 | } |
||
| 997 | #endif |
||
| 998 | |||
| 999 | // ----------------------------------------------------------------------------------------------------------- |
||
| 1000 | // Return true if weapon *tracker is able to track object Objects[track_goal], else return false. |
||
| 1001 | // In order for the object to be trackable, it must be within a reasonable turning radius for the missile |
||
| 1002 | // and it must not be obstructed by a wall. |
||
| 1003 | static int object_is_trackable(const imobjptridx_t objp, const vmobjptridx_t tracker, fix *dot) |
||
| 1004 | { |
||
| 1005 | if (objp == object_none) |
||
| 1006 | return 0; |
||
| 1007 | if (Game_mode & GM_MULTI_COOP) |
||
| 1008 | return 0; |
||
| 1009 | // Don't track player if he's cloaked. |
||
| 1010 | if ((objp == get_local_player().objnum) && (objp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)) |
||
| 1011 | return 0; |
||
| 1012 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1013 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 1014 | #endif |
||
| 1015 | |||
| 1016 | // Can't track AI object if he's cloaked. |
||
| 1017 | if (objp->type == OBJ_ROBOT) { |
||
| 1018 | if (objp->ctype.ai_info.CLOAKED) |
||
| 1019 | return 0; |
||
| 1020 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1021 | // Your missiles don't track your escort. |
||
| 1022 | if (Robot_info[get_robot_id(objp)].companion) |
||
| 1023 | if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) |
||
| 1024 | return 0; |
||
| 1025 | #endif |
||
| 1026 | } |
||
| 1027 | auto vector_to_goal = vm_vec_normalized_quick(vm_vec_sub(objp->pos, tracker->pos)); |
||
| 1028 | *dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec); |
||
| 1029 | |||
| 1030 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1031 | if ((*dot < get_scaled_min_trackable_dot()) && (*dot > F1_0*9/10)) { |
||
| 1032 | vm_vec_normalize(vector_to_goal); |
||
| 1033 | *dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec); |
||
| 1034 | } |
||
| 1035 | #endif |
||
| 1036 | |||
| 1037 | if (*dot >= get_scaled_min_trackable_dot()) { |
||
| 1038 | int rval; |
||
| 1039 | // dot is in legal range, now see if object is visible |
||
| 1040 | rval = object_to_object_visibility(tracker, objp, FQ_TRANSWALL); |
||
| 1041 | return rval; |
||
| 1042 | } else { |
||
| 1043 | return 0; |
||
| 1044 | } |
||
| 1045 | } |
||
| 1046 | |||
| 1047 | // -------------------------------------------------------------------------------------------- |
||
| 1048 | static imobjptridx_t call_find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker) |
||
| 1049 | { |
||
| 1050 | if (Game_mode & GM_MULTI) { |
||
| 1051 | if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) { |
||
| 1052 | // It's fired by a player, so if robots present, track robot, else track player. |
||
| 1053 | if (Game_mode & GM_MULTI_COOP) |
||
| 1054 | return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1); |
||
| 1055 | else |
||
| 1056 | return find_homing_object_complete( curpos, tracker, OBJ_PLAYER, OBJ_ROBOT); |
||
| 1057 | } else { |
||
| 1058 | int goal2_type = -1; |
||
| 1059 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1060 | if (cheats.robotskillrobots) |
||
| 1061 | goal2_type = OBJ_ROBOT; |
||
| 1062 | #endif |
||
| 1063 | Assert(tracker->ctype.laser_info.parent_type == OBJ_ROBOT); |
||
| 1064 | return find_homing_object_complete(curpos, tracker, OBJ_PLAYER, goal2_type); |
||
| 1065 | } |
||
| 1066 | } else |
||
| 1067 | return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1); |
||
| 1068 | } |
||
| 1069 | |||
| 1070 | // -------------------------------------------------------------------------------------------- |
||
| 1071 | // Find object to home in on. |
||
| 1072 | // Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance. |
||
| 1073 | static imobjptridx_t find_homing_object(const vms_vector &curpos, const vmobjptridx_t tracker) |
||
| 1074 | { |
||
| 1075 | // Contact Mike: This is a bad and stupid thing. Who called this routine with an illegal laser type?? |
||
| 1076 | #ifndef NDEBUG |
||
| 1077 | const auto tracker_id = get_weapon_id(tracker); |
||
| 1078 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1079 | if (tracker_id != weapon_id_type::OMEGA_ID) |
||
| 1080 | #endif |
||
| 1081 | assert(Weapon_info[tracker_id].homing_flag); |
||
| 1082 | #endif |
||
| 1083 | |||
| 1084 | // Find an object to track based on game mode (eg, whether in network play) and who fired it. |
||
| 1085 | |||
| 1086 | return call_find_homing_object_complete(curpos, tracker); |
||
| 1087 | } |
||
| 1088 | |||
| 1089 | // -------------------------------------------------------------------------------------------- |
||
| 1090 | // Find object to home in on. |
||
| 1091 | // Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance. |
||
| 1092 | // Can track two kinds of objects. If you are only interested in one type, set track_obj_type2 to NULL |
||
| 1093 | // Always track proximity bombs. --MK, 06/14/95 |
||
| 1094 | // Make homing objects not track parent's prox bombs. |
||
| 1095 | imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2) |
||
| 1096 | { |
||
| 1097 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1098 | auto &vcobjptr = Objects.vcptr; |
||
| 1099 | auto &vmobjptridx = Objects.vmptridx; |
||
| 1100 | fix max_dot = -F1_0*2; |
||
| 1101 | |||
| 1102 | const auto tracker_id = get_weapon_id(tracker); |
||
| 1103 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1104 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 1105 | if (tracker_id != weapon_id_type::OMEGA_ID) |
||
| 1106 | // Contact Mike: This is a bad and stupid thing. Who called this routine with an illegal laser type?? |
||
| 1107 | #endif |
||
| 1108 | { |
||
| 1109 | if (!Weapon_info[tracker_id].homing_flag) |
||
| 1110 | throw std::logic_error("tracking without homing_flag"); |
||
| 1111 | } |
||
| 1112 | |||
| 1113 | const fix64 HOMING_MAX_TRACKABLE_DIST = F1_0*250; |
||
| 1114 | vm_distance_squared max_trackable_dist{HOMING_MAX_TRACKABLE_DIST * HOMING_MAX_TRACKABLE_DIST}; |
||
| 1115 | fix min_trackable_dot = HOMING_MIN_TRACKABLE_DOT; |
||
| 1116 | |||
| 1117 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1118 | if (tracker_id == weapon_id_type::OMEGA_ID) |
||
| 1119 | { |
||
| 1120 | max_trackable_dist = OMEGA_MAX_TRACKABLE_DIST * OMEGA_MAX_TRACKABLE_DIST; |
||
| 1121 | min_trackable_dot = OMEGA_MIN_TRACKABLE_DOT; |
||
| 1122 | } |
||
| 1123 | #endif |
||
| 1124 | |||
| 1125 | imobjptridx_t best_objnum = object_none; |
||
| 1126 | range_for (const auto &&curobjp, vmobjptridx) |
||
| 1127 | { |
||
| 1128 | int is_proximity = 0; |
||
| 1129 | fix dot; |
||
| 1130 | |||
| 1131 | if ((curobjp->type != track_obj_type1) && (curobjp->type != track_obj_type2)) |
||
| 1132 | { |
||
| 1133 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1134 | if ((curobjp->type == OBJ_WEAPON) && (is_proximity_bomb_or_player_smart_mine(get_weapon_id(curobjp)))) { |
||
| 1135 | auto &cur_laser_info = curobjp->ctype.laser_info; |
||
| 1136 | auto &tracker_laser_info = tracker->ctype.laser_info; |
||
| 1137 | if (cur_laser_info.parent_num != tracker_laser_info.parent_num || cur_laser_info.parent_signature != tracker_laser_info.parent_signature) |
||
| 1138 | is_proximity = 1; |
||
| 1139 | else |
||
| 1140 | continue; |
||
| 1141 | } else |
||
| 1142 | #endif |
||
| 1143 | continue; |
||
| 1144 | } |
||
| 1145 | |||
| 1146 | if (curobjp == tracker->ctype.laser_info.parent_num) // Don't track shooter |
||
| 1147 | continue; |
||
| 1148 | |||
| 1149 | // Don't track cloaked players. |
||
| 1150 | if (curobjp->type == OBJ_PLAYER) |
||
| 1151 | { |
||
| 1152 | if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) |
||
| 1153 | continue; |
||
| 1154 | // Don't track teammates in team games |
||
| 1155 | if (Game_mode & GM_TEAM) |
||
| 1156 | { |
||
| 1157 | const auto &&objparent = vcobjptr(tracker->ctype.laser_info.parent_num); |
||
| 1158 | if (objparent->type == OBJ_PLAYER && get_team(get_player_id(curobjp)) == get_team(get_player_id(objparent))) |
||
| 1159 | continue; |
||
| 1160 | } |
||
| 1161 | } |
||
| 1162 | |||
| 1163 | // Can't track AI object if he's cloaked. |
||
| 1164 | if (curobjp->type == OBJ_ROBOT) { |
||
| 1165 | if (curobjp->ctype.ai_info.CLOAKED) |
||
| 1166 | continue; |
||
| 1167 | |||
| 1168 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1169 | // Your missiles don't track your escort. |
||
| 1170 | if (Robot_info[get_robot_id(curobjp)].companion) |
||
| 1171 | if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) |
||
| 1172 | continue; |
||
| 1173 | #endif |
||
| 1174 | } |
||
| 1175 | |||
| 1176 | auto vec_to_curobj = vm_vec_sub(curobjp->pos, curpos); |
||
| 1177 | auto dist = vm_vec_mag2(vec_to_curobj); |
||
| 1178 | |||
| 1179 | if (dist < max_trackable_dist) { |
||
| 1180 | vm_vec_normalize(vec_to_curobj); |
||
| 1181 | dot = vm_vec_dot(vec_to_curobj, tracker->orient.fvec); |
||
| 1182 | if (is_proximity) |
||
| 1183 | dot = ((dot << 3) + dot) >> 3; // I suspect Watcom would be too stupid to figure out the obvious... |
||
| 1184 | |||
| 1185 | if (dot > min_trackable_dot) { |
||
| 1186 | if (dot > max_dot) { |
||
| 1187 | if (object_to_object_visibility(tracker, curobjp, FQ_TRANSWALL)) { |
||
| 1188 | max_dot = dot; |
||
| 1189 | best_objnum = curobjp; |
||
| 1190 | } |
||
| 1191 | } |
||
| 1192 | } |
||
| 1193 | } |
||
| 1194 | |||
| 1195 | } |
||
| 1196 | return best_objnum; |
||
| 1197 | } |
||
| 1198 | |||
| 1199 | #ifdef NEWHOMER |
||
| 1200 | // Similar to calc_d_tick but made just for the homers. |
||
| 1201 | // Causes d_homer_tick_step to be true in intervals dictated by HOMING_TURN_TIME |
||
| 1202 | // and increments d_homer_tick_count accordingly |
||
| 1203 | void calc_d_homer_tick() |
||
| 1204 | { |
||
| 1205 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1206 | auto &vmobjptr = Objects.vmptr; |
||
| 1207 | static fix timer = 0; |
||
| 1208 | auto t = timer + FrameTime; |
||
| 1209 | d_homer_tick_step = t >= HOMING_TURN_TIME; |
||
| 1210 | if (d_homer_tick_step) |
||
| 1211 | { |
||
| 1212 | d_homer_tick_count++; |
||
| 1213 | if (d_homer_tick_count > F1_0) |
||
| 1214 | d_homer_tick_count = 0; |
||
| 1215 | t -= HOMING_TURN_TIME; |
||
| 1216 | // Don't let slowdowns have a lasting impact; allow you to build up at most 3 frames worth |
||
| 1217 | if (t > HOMING_TURN_TIME*3) |
||
| 1218 | t = HOMING_TURN_TIME*3; |
||
| 1219 | |||
| 1220 | get_local_plrobj().ctype.player_info.homing_object_dist = -1; // Assume not being tracked. Laser_do_weapon_sequence modifies this. Let's do this here since the homers do not track every frame, we may not want to reset this ever frame. |
||
| 1221 | } |
||
| 1222 | timer = t; |
||
| 1223 | } |
||
| 1224 | #endif |
||
| 1225 | |||
| 1226 | // ------------------------------------------------------------------------------------------------------------ |
||
| 1227 | // See if legal to keep tracking currently tracked object. If not, see if another object is trackable. If not, return -1, |
||
| 1228 | // else return object number of tracking object. |
||
| 1229 | // Computes and returns a fairly precise dot product. |
||
| 1230 | static imobjptridx_t track_track_goal(fvcobjptr &vcobjptr, const imobjptridx_t track_goal, const vmobjptridx_t tracker, fix *dot, fix tick_count) |
||
| 1231 | { |
||
| 1232 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1233 | if (object_is_trackable(track_goal, tracker, dot)) |
||
| 1234 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1235 | // Every 8 frames for each object, scan all objects. |
||
| 1236 | if (object_is_trackable(track_goal, tracker, dot) && (((tracker ^ tick_count) % 8) != 0)) |
||
| 1237 | #endif |
||
| 1238 | { |
||
| 1239 | return track_goal; |
||
| 1240 | } else if (((tracker ^ tick_count) % 4) == 0) |
||
| 1241 | { |
||
| 1242 | int goal_type, goal2_type; |
||
| 1243 | // If player fired missile, then search for an object, if not, then give up. |
||
| 1244 | if (vcobjptr(tracker->ctype.laser_info.parent_num)->type == OBJ_PLAYER) |
||
| 1245 | { |
||
| 1246 | |||
| 1247 | if (track_goal == object_none) |
||
| 1248 | { |
||
| 1249 | if (Game_mode & GM_MULTI) |
||
| 1250 | { |
||
| 1251 | if (Game_mode & GM_MULTI_COOP) |
||
| 1252 | goal_type = OBJ_ROBOT, goal2_type = -1; |
||
| 1253 | else |
||
| 1254 | { |
||
| 1255 | goal_type = OBJ_PLAYER; |
||
| 1256 | goal2_type = (Game_mode & GM_MULTI_ROBOTS) |
||
| 1257 | ? OBJ_ROBOT // Not cooperative, if robots, track either robot or player |
||
| 1258 | : -1; // Not cooperative and no robots, track only a player |
||
| 1259 | } |
||
| 1260 | } |
||
| 1261 | else |
||
| 1262 | goal_type = OBJ_PLAYER, goal2_type = OBJ_ROBOT; |
||
| 1263 | } |
||
| 1264 | else |
||
| 1265 | { |
||
| 1266 | goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type; |
||
| 1267 | if ((goal_type == OBJ_PLAYER) || (goal_type == OBJ_ROBOT)) |
||
| 1268 | goal2_type = -1; |
||
| 1269 | else |
||
| 1270 | return object_none; |
||
| 1271 | } |
||
| 1272 | } |
||
| 1273 | else { |
||
| 1274 | goal2_type = -1; |
||
| 1275 | |||
| 1276 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1277 | if (cheats.robotskillrobots) |
||
| 1278 | goal2_type = OBJ_ROBOT; |
||
| 1279 | #endif |
||
| 1280 | |||
| 1281 | if (track_goal == object_none) |
||
| 1282 | goal_type = OBJ_PLAYER; |
||
| 1283 | else { |
||
| 1284 | goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type; |
||
| 1285 | assert(goal_type != OBJ_GHOST); |
||
| 1286 | } |
||
| 1287 | } |
||
| 1288 | return find_homing_object_complete(tracker->pos, tracker, goal_type, goal2_type); |
||
| 1289 | } |
||
| 1290 | |||
| 1291 | return object_none; |
||
| 1292 | } |
||
| 1293 | |||
| 1294 | //-------------- Initializes a laser after Fire is pressed ----------------- |
||
| 1295 | |||
| 1296 | static imobjptridx_t Laser_player_fire_spread_delay(fvmsegptridx &vmsegptridx, const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const fix spreadr, const fix spreadu, const fix delay_time, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track) |
||
| 1297 | { |
||
| 1298 | int Fate; |
||
| 1299 | vms_vector LaserDir; |
||
| 1300 | fvi_query fq; |
||
| 1301 | fvi_info hit_data; |
||
| 1302 | vms_vector *pnt; |
||
| 1303 | |||
| 1304 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1305 | create_awareness_event(obj, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState); |
||
| 1306 | #endif |
||
| 1307 | |||
| 1308 | // Find the initial position of the laser |
||
| 1309 | pnt = &Player_ship->gun_points[gun_num]; |
||
| 1310 | |||
| 1311 | vms_matrix m = vm_transposed_matrix(obj->orient); |
||
| 1312 | const auto gun_point = vm_vec_rotate(*pnt,m); |
||
| 1313 | |||
| 1314 | auto LaserPos = vm_vec_add(obj->pos,gun_point); |
||
| 1315 | |||
| 1316 | // If supposed to fire at a delayed time (delay_time), then move this point backwards. |
||
| 1317 | if (delay_time) |
||
| 1318 | vm_vec_scale_add2(LaserPos, shot_orientation, -fixmul(delay_time, Weapon_info[laser_type].speed[GameUniqueState.Difficulty_level])); |
||
| 1319 | |||
| 1320 | // do_muzzle_stuff(obj, &Pos); |
||
| 1321 | |||
| 1322 | //--------------- Find LaserPos and LaserSeg ------------------ |
||
| 1323 | fq.p0 = &obj->pos; |
||
| 1324 | fq.startseg = obj->segnum; |
||
| 1325 | fq.p1 = &LaserPos; |
||
| 1326 | fq.rad = 0x10; |
||
| 1327 | fq.thisobjnum = obj; |
||
| 1328 | fq.ignore_obj_list.first = nullptr; |
||
| 1329 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1330 | fq.flags = FQ_CHECK_OBJS; |
||
| 1331 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1332 | fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS; |
||
| 1333 | #endif |
||
| 1334 | |||
| 1335 | Fate = find_vector_intersection(fq, hit_data); |
||
| 1336 | |||
| 1337 | auto LaserSeg = hit_data.hit_seg; |
||
| 1338 | |||
| 1339 | if (LaserSeg == segment_none) //some sort of annoying error |
||
| 1340 | return object_none; |
||
| 1341 | |||
| 1342 | //SORT OF HACK... IF ABOVE WAS CORRECT THIS WOULDNT BE NECESSARY. |
||
| 1343 | if ( vm_vec_dist_quick(LaserPos, obj->pos) > 0x50000 ) |
||
| 1344 | return object_none; |
||
| 1345 | |||
| 1346 | if (Fate==HIT_WALL) { |
||
| 1347 | return object_none; |
||
| 1348 | } |
||
| 1349 | |||
| 1350 | if (Fate==HIT_OBJECT) { |
||
| 1351 | // if ( Objects[hit_data.hit_object].type == OBJ_ROBOT ) |
||
| 1352 | // Objects[hit_data.hit_object].flags |= OF_SHOULD_BE_DEAD; |
||
| 1353 | // if ( Objects[hit_data.hit_object].type != OBJ_POWERUP ) |
||
| 1354 | // return; |
||
| 1355 | //as of 12/6/94, we don't care if the laser is stuck in an object. We |
||
| 1356 | //just fire away normally |
||
| 1357 | } |
||
| 1358 | |||
| 1359 | // Now, make laser spread out. |
||
| 1360 | LaserDir = shot_orientation; |
||
| 1361 | if ((spreadr != 0) || (spreadu != 0)) { |
||
| 1362 | vm_vec_scale_add2(LaserDir, obj->orient.rvec, spreadr); |
||
| 1363 | vm_vec_scale_add2(LaserDir, obj->orient.uvec, spreadu); |
||
| 1364 | } |
||
| 1365 | |||
| 1366 | const auto &&objnum = Laser_create_new(LaserDir, LaserPos, vmsegptridx(LaserSeg), obj, laser_type, make_sound); |
||
| 1367 | |||
| 1368 | if (objnum == object_none) |
||
| 1369 | return object_none; |
||
| 1370 | |||
| 1371 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1372 | // Omega cannon is a hack, not surprisingly. Don't want to do the rest of this stuff. |
||
| 1373 | if (laser_type == weapon_id_type::OMEGA_ID) |
||
| 1374 | return objnum; |
||
| 1375 | |||
| 1376 | if (laser_type == weapon_id_type::GUIDEDMISS_ID && Multi_is_guided) { |
||
| 1377 | LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(objnum, get_player_id(obj)); |
||
| 1378 | } |
||
| 1379 | |||
| 1380 | Multi_is_guided=0; |
||
| 1381 | |||
| 1382 | if (laser_type == weapon_id_type::CONCUSSION_ID || |
||
| 1383 | laser_type == weapon_id_type::HOMING_ID || |
||
| 1384 | laser_type == weapon_id_type::SMART_ID || |
||
| 1385 | laser_type == weapon_id_type::MEGA_ID || |
||
| 1386 | laser_type == weapon_id_type::FLASH_ID || |
||
| 1387 | //laser_type == GUIDEDMISS_ID || |
||
| 1388 | //laser_type == SUPERPROX_ID || |
||
| 1389 | laser_type == weapon_id_type::MERCURY_ID || |
||
| 1390 | laser_type == weapon_id_type::EARTHSHAKER_ID) |
||
| 1391 | { |
||
| 1392 | const auto need_new_missile_viewer = [obj]{ |
||
| 1393 | if (!Missile_viewer) |
||
| 1394 | return true; |
||
| 1395 | if (Missile_viewer->type != OBJ_WEAPON) |
||
| 1396 | return true; |
||
| 1397 | if (Missile_viewer->signature != Missile_viewer_sig) |
||
| 1398 | return true; |
||
| 1399 | if (get_player_id(obj) == Player_num && Missile_viewer->ctype.laser_info.parent_num != get_local_player().objnum) |
||
| 1400 | /* New missile fired-by local player && |
||
| 1401 | * currently viewing missile not-fired-by local player |
||
| 1402 | */ |
||
| 1403 | return true; |
||
| 1404 | return false; |
||
| 1405 | }; |
||
| 1406 | const auto can_view_missile = [obj]{ |
||
| 1407 | const auto obj_id = get_player_id(obj); |
||
| 1408 | if (obj_id == Player_num) |
||
| 1409 | return true; |
||
| 1410 | if (PlayerCfg.MissileViewEnabled != MissileViewMode::EnabledSelfAndAllies) |
||
| 1411 | return false; |
||
| 1412 | { |
||
| 1413 | if (Game_mode & GM_MULTI_COOP) |
||
| 1414 | return true; |
||
| 1415 | if (Game_mode & GM_TEAM) |
||
| 1416 | return get_team(Player_num) == get_team(obj_id); |
||
| 1417 | } |
||
| 1418 | return false; |
||
| 1419 | }; |
||
| 1420 | if (need_new_missile_viewer() && can_view_missile()) |
||
| 1421 | { |
||
| 1422 | Missile_viewer = objnum; |
||
| 1423 | Missile_viewer_sig = objnum->signature; |
||
| 1424 | } |
||
| 1425 | } |
||
| 1426 | #endif |
||
| 1427 | |||
| 1428 | // If this weapon is supposed to be silent, set that bit! |
||
| 1429 | if (!make_sound) |
||
| 1430 | objnum->flags |= OF_SILENT; |
||
| 1431 | |||
| 1432 | // If the object firing the laser is the player, then indicate the laser object so robots can dodge. |
||
| 1433 | // New by MK on 6/8/95, don't let robots evade proximity bombs, thereby decreasing uselessness of bombs. |
||
| 1434 | if (obj == ConsoleObject) |
||
| 1435 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1436 | if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(objnum))) |
||
| 1437 | #endif |
||
| 1438 | Player_fired_laser_this_frame = objnum; |
||
| 1439 | |||
| 1440 | if (Weapon_info[laser_type].homing_flag) { |
||
| 1441 | if (obj == ConsoleObject) |
||
| 1442 | { |
||
| 1443 | objnum->ctype.laser_info.track_goal = find_homing_object(LaserPos, objnum); |
||
| 1444 | } |
||
| 1445 | else // Some other player shot the homing thing |
||
| 1446 | { |
||
| 1447 | Assert(Game_mode & GM_MULTI); |
||
| 1448 | objnum->ctype.laser_info.track_goal = Network_laser_track; |
||
| 1449 | } |
||
| 1450 | } |
||
| 1451 | |||
| 1452 | return objnum; |
||
| 1453 | } |
||
| 1454 | |||
| 1455 | // ----------------------------------------------------------------------------------------------------------- |
||
| 1456 | static imobjptridx_t Laser_player_fire_spread(const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const fix spreadr, const fix spreadu, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track) |
||
| 1457 | { |
||
| 1458 | return Laser_player_fire_spread_delay(vmsegptridx, obj, laser_type, gun_num, spreadr, spreadu, 0, make_sound, shot_orientation, Network_laser_track); |
||
| 1459 | } |
||
| 1460 | |||
| 1461 | |||
| 1462 | // ----------------------------------------------------------------------------------------------------------- |
||
| 1463 | imobjptridx_t Laser_player_fire(const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track) |
||
| 1464 | { |
||
| 1465 | return Laser_player_fire_spread(obj, laser_type, gun_num, 0, 0, make_sound, shot_orientation, Network_laser_track); |
||
| 1466 | } |
||
| 1467 | |||
| 1468 | // ----------------------------------------------------------------------------------------------------------- |
||
| 1469 | void Flare_create(const vmobjptridx_t obj) |
||
| 1470 | { |
||
| 1471 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1472 | auto &vmobjptr = Objects.vmptr; |
||
| 1473 | |||
| 1474 | const auto energy_usage = get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_id_type::FLARE_ID], GameUniqueState.Difficulty_level); |
||
| 1475 | |||
| 1476 | // MK, 11/04/95: Allowed to fire flare even if no energy. |
||
| 1477 | // -- if (Players[Player_num].energy >= energy_usage) |
||
| 1478 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
| 1479 | auto &energy = player_info.energy; |
||
| 1480 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1481 | if (energy > 0) |
||
| 1482 | #endif |
||
| 1483 | { |
||
| 1484 | const auto &&flare = Laser_player_fire(obj, weapon_id_type::FLARE_ID, 6, 1, get_local_plrobj().orient.fvec, object_none); |
||
| 1485 | if (flare == object_none) |
||
| 1486 | return; |
||
| 1487 | energy -= energy_usage; |
||
| 1488 | |||
| 1489 | if (energy <= 0) |
||
| 1490 | { |
||
| 1491 | energy = 0; |
||
| 1492 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1493 | auto_select_primary_weapon(player_info); |
||
| 1494 | #endif |
||
| 1495 | } |
||
| 1496 | |||
| 1497 | if (Game_mode & GM_MULTI) |
||
| 1498 | multi_send_fire(FLARE_ADJUST, 0, 0, 1, object_none, object_none); |
||
| 1499 | } |
||
| 1500 | |||
| 1501 | } |
||
| 1502 | |||
| 1503 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1504 | #define HOMING_MISSILE_SCALE 8 |
||
| 1505 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1506 | #define HOMING_MISSILE_SCALE 16 |
||
| 1507 | |||
| 1508 | static bool is_active_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const vcobjptridx_t obj) |
||
| 1509 | { |
||
| 1510 | if (obj->ctype.laser_info.parent_type != OBJ_PLAYER) |
||
| 1511 | return false; |
||
| 1512 | auto &vcobjptr = LevelUniqueObjectState.get_objects().vcptr; |
||
| 1513 | auto &parent_obj = *vcobjptr(obj->ctype.laser_info.parent_num); |
||
| 1514 | if (parent_obj.type != OBJ_PLAYER) |
||
| 1515 | return false; |
||
| 1516 | const auto pnum = get_player_id(parent_obj); |
||
| 1517 | return LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(pnum) == obj; |
||
| 1518 | } |
||
| 1519 | #endif |
||
| 1520 | |||
| 1521 | //-------------------------------------------------------------------- |
||
| 1522 | // Set object *objp's orientation to (or towards if I'm ambitious) its velocity. |
||
| 1523 | static void homing_missile_turn_towards_velocity(object_base &objp, const vms_vector &norm_vel, fix ft) |
||
| 1524 | { |
||
| 1525 | auto new_fvec = norm_vel; |
||
| 1526 | vm_vec_scale(new_fvec, ft * HOMING_MISSILE_SCALE); |
||
| 1527 | vm_vec_add2(new_fvec, objp.orient.fvec); |
||
| 1528 | vm_vec_normalize_quick(new_fvec); |
||
| 1529 | |||
| 1530 | // if ((norm_vel->x == 0) && (norm_vel->y == 0) && (norm_vel->z == 0)) |
||
| 1531 | // return; |
||
| 1532 | |||
| 1533 | vm_vector_2_matrix(objp.orient, new_fvec, nullptr, nullptr); |
||
| 1534 | } |
||
| 1535 | |||
| 1536 | |||
| 1537 | //------------------------------------------------------------------------------------------- |
||
| 1538 | //sequence this laser object for this _frame_ (underscores added here to aid MK in his searching!) |
||
| 1539 | void Laser_do_weapon_sequence(const vmobjptridx_t obj) |
||
| 1540 | { |
||
| 1541 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1542 | auto &imobjptridx = Objects.imptridx; |
||
| 1543 | auto &vcobjptr = Objects.vcptr; |
||
| 1544 | auto &vmobjptr = Objects.vmptr; |
||
| 1545 | Assert(obj->control_type == CT_WEAPON); |
||
| 1546 | |||
| 1547 | if (obj->lifeleft < 0 ) { // We died of old age |
||
| 1548 | obj->flags |= OF_SHOULD_BE_DEAD; |
||
| 1549 | if ( Weapon_info[get_weapon_id(obj)].damage_radius ) |
||
| 1550 | explode_badass_weapon(obj, obj->pos); |
||
| 1551 | return; |
||
| 1552 | } |
||
| 1553 | |||
| 1554 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1555 | if (omega_cleanup(vcobjptr, obj)) |
||
| 1556 | return; |
||
| 1557 | #endif |
||
| 1558 | |||
| 1559 | //delete weapons that are not moving |
||
| 1560 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
| 1561 | if ( !((d_tick_count ^ obj->signature.get()) & 3) && |
||
| 1562 | (get_weapon_id(obj) != weapon_id_type::FLARE_ID) && |
||
| 1563 | (Weapon_info[get_weapon_id(obj)].speed[Difficulty_level] > 0) && |
||
| 1564 | (vm_vec_mag_quick(obj->mtype.phys_info.velocity) < F2_0)) { |
||
| 1565 | obj_delete(LevelUniqueObjectState, Segments, obj); |
||
| 1566 | return; |
||
| 1567 | } |
||
| 1568 | |||
| 1569 | if ( get_weapon_id(obj) == weapon_id_type::FUSION_ID ) { //always set fusion weapon to max vel |
||
| 1570 | |||
| 1571 | vm_vec_normalize_quick(obj->mtype.phys_info.velocity); |
||
| 1572 | |||
| 1573 | vm_vec_scale(obj->mtype.phys_info.velocity, Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]); |
||
| 1574 | } |
||
| 1575 | |||
| 1576 | // For homing missiles, turn towards target. (unless it's the guided missile) |
||
| 1577 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1578 | if (Weapon_info[get_weapon_id(obj)].homing_flag) |
||
| 1579 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1580 | if (Weapon_info[get_weapon_id(obj)].homing_flag && !is_active_guided_missile(LevelUniqueObjectState, obj)) |
||
| 1581 | #endif |
||
| 1582 | { |
||
| 1583 | vms_vector vector_to_object, temp_vec; |
||
| 1584 | fix dot=F1_0; |
||
| 1585 | fix speed, max_speed; |
||
| 1586 | |||
| 1587 | // For first 125ms of life, missile flies straight. |
||
| 1588 | if (obj->ctype.laser_info.creation_time + HOMING_FLY_STRAIGHT_TIME < GameTime64) |
||
| 1589 | { |
||
| 1590 | |||
| 1591 | // If it's time to do tracking, then it's time to grow up, stop bouncing and start exploding!. |
||
| 1592 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1593 | if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID)) |
||
| 1594 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1595 | if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::SMART_MINE_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::EARTHSHAKER_MEGA_ID)) |
||
| 1596 | #endif |
||
| 1597 | { |
||
| 1598 | obj->mtype.phys_info.flags &= ~PF_BOUNCE; |
||
| 1599 | } |
||
| 1600 | |||
| 1601 | #ifdef NEWHOMER |
||
| 1602 | if (d_homer_tick_step) |
||
| 1603 | { |
||
| 1604 | const auto &&track_goal = track_track_goal(vcobjptr, imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_homer_tick_count); |
||
| 1605 | |||
| 1606 | if (track_goal == get_local_player().objnum) { |
||
| 1607 | fix dist_to_player; |
||
| 1608 | |||
| 1609 | dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos); |
||
| 1610 | auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist; |
||
| 1611 | if (dist_to_player < homing_object_dist || homing_object_dist < 0) |
||
| 1612 | homing_object_dist = dist_to_player; |
||
| 1613 | } |
||
| 1614 | |||
| 1615 | if (track_goal != object_none) |
||
| 1616 | { |
||
| 1617 | vm_vec_sub(vector_to_object, track_goal->pos, obj->pos); |
||
| 1618 | vm_vec_normalize_quick(vector_to_object); |
||
| 1619 | temp_vec = obj->mtype.phys_info.velocity; |
||
| 1620 | speed = vm_vec_normalize_quick(temp_vec); |
||
| 1621 | max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]; |
||
| 1622 | if (speed+F1_0 < max_speed) { |
||
| 1623 | speed += fixmul(max_speed, HOMING_TURN_TIME/2); |
||
| 1624 | if (speed > max_speed) |
||
| 1625 | speed = max_speed; |
||
| 1626 | } |
||
| 1627 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1628 | dot = vm_vec_dot(temp_vec, vector_to_object); |
||
| 1629 | #endif |
||
| 1630 | vm_vec_add2(temp_vec, vector_to_object); |
||
| 1631 | // The boss' smart children track better... |
||
| 1632 | if (Weapon_info[get_weapon_id(obj)].render_type != WEAPON_RENDER_POLYMODEL) |
||
| 1633 | vm_vec_add2(temp_vec, vector_to_object); |
||
| 1634 | vm_vec_normalize_quick(temp_vec); |
||
| 1635 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1636 | vm_vec_scale(temp_vec, speed); |
||
| 1637 | obj->mtype.phys_info.velocity = temp_vec; |
||
| 1638 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1639 | obj->mtype.phys_info.velocity = temp_vec; |
||
| 1640 | vm_vec_scale(obj->mtype.phys_info.velocity, speed); |
||
| 1641 | #endif |
||
| 1642 | |||
| 1643 | // Subtract off life proportional to amount turned. |
||
| 1644 | // For hardest turn, it will lose 2 seconds per second. |
||
| 1645 | { |
||
| 1646 | fix lifelost, absdot; |
||
| 1647 | |||
| 1648 | absdot = abs(F1_0 - dot); |
||
| 1649 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1650 | if (absdot > F1_0/8) { |
||
| 1651 | if (absdot > F1_0/4) |
||
| 1652 | absdot = F1_0/4; |
||
| 1653 | lifelost = fixmul(absdot*16, HOMING_TURN_TIME); |
||
| 1654 | obj->lifeleft -= lifelost; |
||
| 1655 | } |
||
| 1656 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1657 | lifelost = fixmul(absdot*32, HOMING_TURN_TIME); |
||
| 1658 | obj->lifeleft -= lifelost; |
||
| 1659 | #endif |
||
| 1660 | } |
||
| 1661 | |||
| 1662 | // Only polygon objects have visible orientation, so only they should turn. |
||
| 1663 | if (Weapon_info[get_weapon_id(obj)].render_type == WEAPON_RENDER_POLYMODEL) |
||
| 1664 | homing_missile_turn_towards_velocity(obj, temp_vec, HOMING_TURN_TIME); // temp_vec is normalized velocity. |
||
| 1665 | } |
||
| 1666 | } |
||
| 1667 | #else // old FPS-dependent homers - NOTE: I know this is very redundant but I want to keep the historical code 100% preserved to compare against potential changes in the above. |
||
| 1668 | // Make sure the object we are tracking is still trackable. |
||
| 1669 | const auto &&track_goal = track_track_goalvcobjptr, (imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_tick_count); |
||
| 1670 | |||
| 1671 | if (track_goal == get_local_player().objnum) { |
||
| 1672 | fix dist_to_player; |
||
| 1673 | |||
| 1674 | dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos); |
||
| 1675 | auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist; |
||
| 1676 | if (dist_to_player < homing_object_dist || homing_object_dist < 0) |
||
| 1677 | homing_object_dist = dist_to_player; |
||
| 1678 | } |
||
| 1679 | |||
| 1680 | if (track_goal != object_none) |
||
| 1681 | { |
||
| 1682 | vm_vec_sub(vector_to_object, track_goal->pos, obj->pos); |
||
| 1683 | vm_vec_normalize_quick(vector_to_object); |
||
| 1684 | temp_vec = obj->mtype.phys_info.velocity; |
||
| 1685 | speed = vm_vec_normalize_quick(temp_vec); |
||
| 1686 | max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]; |
||
| 1687 | if (speed+F1_0 < max_speed) { |
||
| 1688 | speed += fixmul(max_speed, FrameTime/2); |
||
| 1689 | if (speed > max_speed) |
||
| 1690 | speed = max_speed; |
||
| 1691 | } |
||
| 1692 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1693 | dot = vm_vec_dot(temp_vec, vector_to_object); |
||
| 1694 | #endif |
||
| 1695 | vm_vec_add2(temp_vec, vector_to_object); |
||
| 1696 | // The boss' smart children track better... |
||
| 1697 | if (Weapon_info[get_weapon_id(obj)].render_type != WEAPON_RENDER_POLYMODEL) |
||
| 1698 | vm_vec_add2(temp_vec, vector_to_object); |
||
| 1699 | vm_vec_normalize_quick(temp_vec); |
||
| 1700 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1701 | vm_vec_scale(temp_vec, speed); |
||
| 1702 | obj->mtype.phys_info.velocity = temp_vec; |
||
| 1703 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1704 | obj->mtype.phys_info.velocity = temp_vec; |
||
| 1705 | vm_vec_scale(obj->mtype.phys_info.velocity, speed); |
||
| 1706 | #endif |
||
| 1707 | |||
| 1708 | // Subtract off life proportional to amount turned. |
||
| 1709 | // For hardest turn, it will lose 2 seconds per second. |
||
| 1710 | { |
||
| 1711 | fix lifelost, absdot; |
||
| 1712 | |||
| 1713 | absdot = abs(F1_0 - dot); |
||
| 1714 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1715 | if (absdot > F1_0/8) { |
||
| 1716 | if (absdot > F1_0/4) |
||
| 1717 | absdot = F1_0/4; |
||
| 1718 | lifelost = fixmul(absdot*16, FrameTime); |
||
| 1719 | obj->lifeleft -= lifelost; |
||
| 1720 | } |
||
| 1721 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1722 | lifelost = fixmul(absdot*32, FrameTime); |
||
| 1723 | obj->lifeleft -= lifelost; |
||
| 1724 | #endif |
||
| 1725 | } |
||
| 1726 | |||
| 1727 | // Only polygon objects have visible orientation, so only they should turn. |
||
| 1728 | if (Weapon_info[get_weapon_id(obj)].render_type == WEAPON_RENDER_POLYMODEL) |
||
| 1729 | homing_missile_turn_towards_velocity(obj, temp_vec, FrameTime); // temp_vec is normalized velocity. |
||
| 1730 | } |
||
| 1731 | #endif |
||
| 1732 | } |
||
| 1733 | } |
||
| 1734 | |||
| 1735 | // Make sure weapon is not moving faster than allowed speed. |
||
| 1736 | const auto &weapon_info = Weapon_info[get_weapon_id(obj)]; |
||
| 1737 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1738 | if (weapon_info.thrust != 0) |
||
| 1739 | #endif |
||
| 1740 | { |
||
| 1741 | fix weapon_speed; |
||
| 1742 | |||
| 1743 | weapon_speed = vm_vec_mag_quick(obj->mtype.phys_info.velocity); |
||
| 1744 | if (weapon_speed > weapon_info.speed[Difficulty_level]) { |
||
| 1745 | // Only slow down if not allowed to move. Makes sense, huh? Allows proxbombs to get moved by physics force. --MK, 2/13/96 |
||
| 1746 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1747 | if (weapon_info.speed[Difficulty_level]) |
||
| 1748 | #endif |
||
| 1749 | { |
||
| 1750 | fix scale_factor; |
||
| 1751 | |||
| 1752 | scale_factor = fixdiv(Weapon_info[get_weapon_id(obj)].speed[Difficulty_level], weapon_speed); |
||
| 1753 | vm_vec_scale(obj->mtype.phys_info.velocity, scale_factor); |
||
| 1754 | } |
||
| 1755 | } |
||
| 1756 | } |
||
| 1757 | } |
||
| 1758 | |||
| 1759 | } |
||
| 1760 | |||
| 1761 | namespace dcx { |
||
| 1762 | |||
| 1763 | constexpr std::integral_constant<unsigned, 30> MAX_OBJDISTS{}; |
||
| 1764 | |||
| 1765 | static inline int sufficient_energy(int energy_used, fix energy) |
||
| 1766 | { |
||
| 1767 | return !energy_used || (energy >= energy_used); |
||
| 1768 | } |
||
| 1769 | |||
| 1770 | static inline int sufficient_ammo(int ammo_used, int uses_vulcan_ammo, ushort vulcan_ammo) |
||
| 1771 | { |
||
| 1772 | return !ammo_used || (!uses_vulcan_ammo || vulcan_ammo >= ammo_used); |
||
| 1773 | } |
||
| 1774 | |||
| 1775 | } |
||
| 1776 | |||
| 1777 | namespace dsx { |
||
| 1778 | |||
| 1779 | // -------------------------------------------------------------------------------------------------- |
||
| 1780 | // Assumption: This is only called by the actual console player, not for network players |
||
| 1781 | |||
| 1782 | void do_laser_firing_player(object &plrobj) |
||
| 1783 | { |
||
| 1784 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1785 | auto &vmobjptridx = Objects.vmptridx; |
||
| 1786 | int ammo_used; |
||
| 1787 | int rval = 0; |
||
| 1788 | int nfires = 1; |
||
| 1789 | |||
| 1790 | if (Player_dead_state != player_dead_state::no) |
||
| 1791 | return; |
||
| 1792 | |||
| 1793 | auto &player_info = plrobj.ctype.player_info; |
||
| 1794 | const auto Primary_weapon = player_info.Primary_weapon; |
||
| 1795 | const auto weapon_index = Primary_weapon_to_weapon_info[Primary_weapon]; |
||
| 1796 | |||
| 1797 | ammo_used = Weapon_info[weapon_index].ammo_usage; |
||
| 1798 | |||
| 1799 | const auto uses_vulcan_ammo = weapon_index_uses_vulcan_ammo(Primary_weapon); |
||
| 1800 | |||
| 1801 | auto &pl_energy = player_info.energy; |
||
| 1802 | const auto base_energy_used = |
||
| 1803 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1804 | (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX) |
||
| 1805 | ? 0 // Omega consumes energy when recharging, not when firing. |
||
| 1806 | : |
||
| 1807 | #endif |
||
| 1808 | get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_index], GameUniqueState.Difficulty_level); |
||
| 1809 | |||
| 1810 | const auto energy_used = |
||
| 1811 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1812 | // MK, 01/26/96, Helix use 2x energy in multiplayer. bitmaps.tbl parm should have been reduced for single player. |
||
| 1813 | (weapon_index == weapon_id_type::HELIX_ID && (Game_mode & GM_MULTI)) |
||
| 1814 | ? base_energy_used * 2 |
||
| 1815 | : |
||
| 1816 | #endif |
||
| 1817 | base_energy_used; |
||
| 1818 | |||
| 1819 | if (!(sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo))) |
||
| 1820 | auto_select_primary_weapon(player_info); // Make sure the player can fire from this weapon. |
||
| 1821 | |||
| 1822 | auto &Next_laser_fire_time = player_info.Next_laser_fire_time; |
||
| 1823 | while (Next_laser_fire_time <= GameTime64) { |
||
| 1824 | if (sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo)) { |
||
| 1825 | int laser_level, flags, fire_frame_overhead = 0; |
||
| 1826 | |||
| 1827 | if (GameTime64 - Next_laser_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that. |
||
| 1828 | fire_frame_overhead = GameTime64 - Next_laser_fire_time; |
||
| 1829 | |||
| 1830 | laser_level = player_info.laser_level; |
||
| 1831 | |||
| 1832 | flags = 0; |
||
| 1833 | |||
| 1834 | switch (Primary_weapon) |
||
| 1835 | { |
||
| 1836 | case primary_weapon_index_t::LASER_INDEX: |
||
| 1837 | if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS) |
||
| 1838 | flags |= LASER_QUAD; |
||
| 1839 | break; |
||
| 1840 | case primary_weapon_index_t::SPREADFIRE_INDEX: |
||
| 1841 | flags |= (player_info.Spreadfire_toggle ^= LASER_SPREADFIRE_TOGGLED) & LASER_SPREADFIRE_TOGGLED; |
||
| 1842 | break; |
||
| 1843 | case primary_weapon_index_t::VULCAN_INDEX: |
||
| 1844 | case primary_weapon_index_t::PLASMA_INDEX: |
||
| 1845 | case primary_weapon_index_t::FUSION_INDEX: |
||
| 1846 | default: |
||
| 1847 | break; |
||
| 1848 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1849 | case primary_weapon_index_t::HELIX_INDEX: |
||
| 1850 | flags |= (player_info.Helix_orientation++ & LASER_HELIX_MASK); |
||
| 1851 | break; |
||
| 1852 | case primary_weapon_index_t::SUPER_LASER_INDEX: |
||
| 1853 | case primary_weapon_index_t::GAUSS_INDEX: |
||
| 1854 | case primary_weapon_index_t::PHOENIX_INDEX: |
||
| 1855 | case primary_weapon_index_t::OMEGA_INDEX: |
||
| 1856 | break; |
||
| 1857 | #endif |
||
| 1858 | } |
||
| 1859 | |||
| 1860 | const auto shot_fired = do_laser_firing(vmobjptridx(get_local_player().objnum), Primary_weapon, laser_level, flags, nfires, plrobj.orient.fvec, object_none); |
||
| 1861 | rval += shot_fired; |
||
| 1862 | |||
| 1863 | if (!shot_fired) |
||
| 1864 | break; |
||
| 1865 | Next_laser_fire_time = GameTime64 - fire_frame_overhead + (unlikely(cheats.rapidfire) |
||
| 1866 | ? (F1_0 / 25) |
||
| 1867 | : ( |
||
| 1868 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1869 | weapon_index == weapon_id_type::OMEGA_ID |
||
| 1870 | ? OMEGA_BASE_TIME |
||
| 1871 | : |
||
| 1872 | #endif |
||
| 1873 | Weapon_info[weapon_index].fire_wait |
||
| 1874 | ) |
||
| 1875 | ); |
||
| 1876 | |||
| 1877 | pl_energy -= (energy_used * rval) / Weapon_info[weapon_index].fire_count; |
||
| 1878 | if (pl_energy < 0) |
||
| 1879 | pl_energy = 0; |
||
| 1880 | |||
| 1881 | if (uses_vulcan_ammo) { |
||
| 1882 | auto &v = player_info.vulcan_ammo; |
||
| 1883 | if (v < ammo_used) |
||
| 1884 | v = 0; |
||
| 1885 | else |
||
| 1886 | v -= ammo_used; |
||
| 1887 | maybe_drop_net_powerup(POW_VULCAN_AMMO, 1, 0); |
||
| 1888 | } |
||
| 1889 | } else { |
||
| 1890 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1891 | Next_laser_fire_time = GameTime64; // Prevents shots-to-fire from building up. |
||
| 1892 | #endif |
||
| 1893 | break; // Couldn't fire weapon, so abort. |
||
| 1894 | } |
||
| 1895 | } |
||
| 1896 | auto_select_primary_weapon(player_info); // Make sure the player can fire from this weapon. |
||
| 1897 | } |
||
| 1898 | |||
| 1899 | // -------------------------------------------------------------------------------------------------- |
||
| 1900 | // Object "objnum" fires weapon "weapon_num" of level "level". (Right now (9/24/94) level is used only for type 0 laser. |
||
| 1901 | // Flags are the player flags. For network mode, set to 0. |
||
| 1902 | // It is assumed that this is a player object (as in multiplayer), and therefore the gun positions are known. |
||
| 1903 | // Returns number of times a weapon was fired. This is typically 1, but might be more for low frame rates. |
||
| 1904 | // More than one shot is fired with a pseudo-delay so that players on slow machines can fire (for themselves |
||
| 1905 | // or other players) often enough for things like the vulcan cannon. |
||
| 1906 | int do_laser_firing(vmobjptridx_t objp, int weapon_num, int level, int flags, int nfires, vms_vector shot_orientation, const icobjidx_t Network_laser_track) |
||
| 1907 | { |
||
| 1908 | switch (weapon_num) { |
||
| 1909 | case primary_weapon_index_t::LASER_INDEX: { |
||
| 1910 | weapon_id_type weapon_type; |
||
| 1911 | |||
| 1912 | switch(level) |
||
| 1913 | { |
||
| 1914 | case LASER_LEVEL_1: |
||
| 1915 | weapon_type = weapon_id_type::LASER_ID_L1; |
||
| 1916 | break; |
||
| 1917 | case LASER_LEVEL_2: |
||
| 1918 | weapon_type = weapon_id_type::LASER_ID_L2; |
||
| 1919 | break; |
||
| 1920 | case LASER_LEVEL_3: |
||
| 1921 | weapon_type = weapon_id_type::LASER_ID_L3; |
||
| 1922 | break; |
||
| 1923 | case LASER_LEVEL_4: |
||
| 1924 | weapon_type = weapon_id_type::LASER_ID_L4; |
||
| 1925 | break; |
||
| 1926 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1927 | case LASER_LEVEL_5: |
||
| 1928 | weapon_type = weapon_id_type::LASER_ID_L5; |
||
| 1929 | break; |
||
| 1930 | case LASER_LEVEL_6: |
||
| 1931 | weapon_type = weapon_id_type::LASER_ID_L6; |
||
| 1932 | break; |
||
| 1933 | #endif |
||
| 1934 | default: |
||
| 1935 | Assert(0); |
||
| 1936 | return nfires; |
||
| 1937 | } |
||
| 1938 | Laser_player_fire(objp, weapon_type, 0, 1, shot_orientation, object_none); |
||
| 1939 | Laser_player_fire(objp, weapon_type, 1, 0, shot_orientation, object_none); |
||
| 1940 | |||
| 1941 | if (flags & LASER_QUAD) { |
||
| 1942 | // hideous system to make quad laser 1.5x powerful as normal laser, make every other quad laser bolt harmless |
||
| 1943 | Laser_player_fire(objp, weapon_type, 2, 0, shot_orientation, object_none); |
||
| 1944 | Laser_player_fire(objp, weapon_type, 3, 0, shot_orientation, object_none); |
||
| 1945 | } |
||
| 1946 | break; |
||
| 1947 | } |
||
| 1948 | case primary_weapon_index_t::VULCAN_INDEX: { |
||
| 1949 | // Only make sound for 1/4 of vulcan bullets. |
||
| 1950 | int make_sound = 1; |
||
| 1951 | //if (d_rand() > 24576) |
||
| 1952 | // make_sound = 1; |
||
| 1953 | Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, make_sound, shot_orientation, object_none); |
||
| 1954 | if (nfires > 1) { |
||
| 1955 | Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, 0, shot_orientation, object_none); |
||
| 1956 | if (nfires > 2) { |
||
| 1957 | Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, 0, shot_orientation, object_none); |
||
| 1958 | } |
||
| 1959 | } |
||
| 1960 | break; |
||
| 1961 | } |
||
| 1962 | case primary_weapon_index_t::SPREADFIRE_INDEX: |
||
| 1963 | { |
||
| 1964 | fix spreadr0, spreadu0, spreadr1, spreadu1; |
||
| 1965 | if (flags & LASER_SPREADFIRE_TOGGLED) |
||
| 1966 | spreadr0 = F1_0 / 16, spreadr1 = -F1_0 / 16, spreadu0 = spreadu1 = 0; |
||
| 1967 | else |
||
| 1968 | spreadu0 = F1_0 / 16, spreadu1 = -F1_0 / 16, spreadr0 = spreadr1 = 0; |
||
| 1969 | Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, spreadr0, spreadu0, 0, shot_orientation, object_none); |
||
| 1970 | Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, spreadr1, spreadu1, 0, shot_orientation, object_none); |
||
| 1971 | Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, 0, 0, 1, shot_orientation, object_none); |
||
| 1972 | } |
||
| 1973 | break; |
||
| 1974 | |||
| 1975 | case primary_weapon_index_t::PLASMA_INDEX: |
||
| 1976 | Laser_player_fire(objp, weapon_id_type::PLASMA_ID, 0, 1, shot_orientation, object_none); |
||
| 1977 | Laser_player_fire(objp, weapon_id_type::PLASMA_ID, 1, 0, shot_orientation, object_none); |
||
| 1978 | if (nfires > 1) { |
||
| 1979 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PLASMA_ID, 0, 0, 0, FrameTime/2, 1, shot_orientation, object_none); |
||
| 1980 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PLASMA_ID, 1, 0, 0, FrameTime/2, 0, shot_orientation, object_none); |
||
| 1981 | } |
||
| 1982 | break; |
||
| 1983 | |||
| 1984 | case primary_weapon_index_t::FUSION_INDEX: { |
||
| 1985 | vms_vector force_vec; |
||
| 1986 | |||
| 1987 | Laser_player_fire(objp, weapon_id_type::FUSION_ID, 0, 1, shot_orientation, object_none); |
||
| 1988 | Laser_player_fire(objp, weapon_id_type::FUSION_ID, 1, 1, shot_orientation, object_none); |
||
| 1989 | |||
| 1990 | assert(objp->type == OBJ_PLAYER); |
||
| 1991 | auto &Fusion_charge = objp->ctype.player_info.Fusion_charge; |
||
| 1992 | flags = static_cast<int8_t>(Fusion_charge >> 12); |
||
| 1993 | |||
| 1994 | Fusion_charge = 0; |
||
| 1995 | |||
| 1996 | force_vec.x = -(objp->orient.fvec.x << 7); |
||
| 1997 | force_vec.y = -(objp->orient.fvec.y << 7); |
||
| 1998 | force_vec.z = -(objp->orient.fvec.z << 7); |
||
| 1999 | phys_apply_force(objp, force_vec); |
||
| 2000 | |||
| 2001 | force_vec.x = (force_vec.x >> 4) + d_rand() - 16384; |
||
| 2002 | force_vec.y = (force_vec.y >> 4) + d_rand() - 16384; |
||
| 2003 | force_vec.z = (force_vec.z >> 4) + d_rand() - 16384; |
||
| 2004 | phys_apply_rot(objp, force_vec); |
||
| 2005 | |||
| 2006 | } |
||
| 2007 | break; |
||
| 2008 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 2009 | case primary_weapon_index_t::GAUSS_INDEX: { |
||
| 2010 | // Only make sound for 1/4 of vulcan bullets. |
||
| 2011 | int make_sound = 1; |
||
| 2012 | //if (d_rand() > 24576) |
||
| 2013 | // make_sound = 1; |
||
| 2014 | |||
| 2015 | Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, make_sound, shot_orientation, object_none); |
||
| 2016 | if (nfires > 1) { |
||
| 2017 | Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, 0, shot_orientation, object_none); |
||
| 2018 | if (nfires > 2) { |
||
| 2019 | Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, 0, shot_orientation, object_none); |
||
| 2020 | } |
||
| 2021 | } |
||
| 2022 | break; |
||
| 2023 | } |
||
| 2024 | case primary_weapon_index_t::HELIX_INDEX: { |
||
| 2025 | fix spreadr,spreadu; |
||
| 2026 | static const std::array<std::pair<fix, fix>, 8> spread{{ |
||
| 2027 | { F1_0/16, 0}, // Vertical |
||
| 2028 | { F1_0/17, F1_0/42}, // 22.5 degrees |
||
| 2029 | { F1_0/22, F1_0/22}, // 45 degrees |
||
| 2030 | { F1_0/42, F1_0/17}, // 67.5 degrees |
||
| 2031 | { 0, F1_0/16}, // 90 degrees |
||
| 2032 | {-F1_0/42, F1_0/17}, // 112.5 degrees |
||
| 2033 | {-F1_0/22, F1_0/22}, // 135 degrees |
||
| 2034 | {-F1_0/17, F1_0/42}, // 157.5 degrees |
||
| 2035 | }}; |
||
| 2036 | const unsigned helix_orient = flags & LASER_HELIX_MASK; |
||
| 2037 | if (helix_orient < spread.size()) |
||
| 2038 | { |
||
| 2039 | auto &s = spread[helix_orient]; |
||
| 2040 | spreadr = s.first; |
||
| 2041 | spreadu = s.second; |
||
| 2042 | } |
||
| 2043 | else |
||
| 2044 | break; |
||
| 2045 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, 0, 0, 1, shot_orientation, object_none); |
||
| 2046 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, spreadr, spreadu, 0, shot_orientation, object_none); |
||
| 2047 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, -spreadr, -spreadu, 0, shot_orientation, object_none); |
||
| 2048 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, spreadr*2, spreadu*2, 0, shot_orientation, object_none); |
||
| 2049 | Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, -spreadr*2, -spreadu*2, 0, shot_orientation, object_none); |
||
| 2050 | break; |
||
| 2051 | } |
||
| 2052 | |||
| 2053 | case primary_weapon_index_t::PHOENIX_INDEX: |
||
| 2054 | Laser_player_fire(objp, weapon_id_type::PHOENIX_ID, 0, 1, shot_orientation, object_none); |
||
| 2055 | Laser_player_fire(objp, weapon_id_type::PHOENIX_ID, 1, 0, shot_orientation, object_none); |
||
| 2056 | if (nfires > 1) { |
||
| 2057 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PHOENIX_ID, 0, 0, 0, FrameTime/2, 1, shot_orientation, object_none); |
||
| 2058 | Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PHOENIX_ID, 1, 0, 0, FrameTime/2, 0, shot_orientation, object_none); |
||
| 2059 | } |
||
| 2060 | break; |
||
| 2061 | |||
| 2062 | case primary_weapon_index_t::OMEGA_INDEX: |
||
| 2063 | Laser_player_fire(objp, weapon_id_type::OMEGA_ID, 1, 1, shot_orientation, Network_laser_track); |
||
| 2064 | break; |
||
| 2065 | |||
| 2066 | case primary_weapon_index_t::SUPER_LASER_INDEX: |
||
| 2067 | #endif |
||
| 2068 | default: |
||
| 2069 | Int3(); // Contact Yuan: Unknown Primary weapon type, setting to 0. |
||
| 2070 | } |
||
| 2071 | |||
| 2072 | // Set values to be recognized during comunication phase, if we are the |
||
| 2073 | // one shooting |
||
| 2074 | if ((Game_mode & GM_MULTI) && objp == get_local_player().objnum) |
||
| 2075 | multi_send_fire(weapon_num, level, flags, nfires, Network_laser_track, object_none); |
||
| 2076 | |||
| 2077 | return nfires; |
||
| 2078 | } |
||
| 2079 | |||
| 2080 | } |
||
| 2081 | |||
| 2082 | namespace dcx { |
||
| 2083 | |||
| 2084 | uint_fast8_t laser_info::test_set_hitobj(const vcobjidx_t o) |
||
| 2085 | { |
||
| 2086 | if (const auto r = test_hitobj(o)) |
||
| 2087 | return r; |
||
| 2088 | const std::size_t values_size = hitobj_values.size(); |
||
| 2089 | assert(hitobj_count <= values_size); |
||
| 2090 | assert(hitobj_pos < values_size); |
||
| 2091 | hitobj_values[hitobj_pos++] = o; |
||
| 2092 | if (unlikely(hitobj_pos == values_size)) |
||
| 2093 | hitobj_pos = 0; |
||
| 2094 | if (likely(hitobj_count != values_size)) |
||
| 2095 | ++ hitobj_count; |
||
| 2096 | return false; |
||
| 2097 | } |
||
| 2098 | |||
| 2099 | uint_fast8_t laser_info::test_hitobj(const vcobjidx_t o) const |
||
| 2100 | { |
||
| 2101 | /* Search backward so that the highest added element is the first |
||
| 2102 | * one considered. This preserves most of the benefit of tracking |
||
| 2103 | * the most recent hit separately, without storing it separately or |
||
| 2104 | * requiring expensive shift operations as new elements are added. |
||
| 2105 | * |
||
| 2106 | * This efficiency hack becomes ineffective (but not incorrect) if |
||
| 2107 | * the list wraps. Wrapping should be very rare, and even a full |
||
| 2108 | * search is relatively cheap, so it is not worth complicating the |
||
| 2109 | * code to ensure that elements are always searched in |
||
| 2110 | * most-recently-added order. |
||
| 2111 | */ |
||
| 2112 | const auto &&r = partial_range(hitobj_values, hitobj_count).reversed(); |
||
| 2113 | const auto &&e = r.end(); |
||
| 2114 | return std::find(r.begin(), e, o) != e; |
||
| 2115 | } |
||
| 2116 | |||
| 2117 | } |
||
| 2118 | |||
| 2119 | namespace dsx { |
||
| 2120 | |||
| 2121 | // ------------------------------------------------------------------------------------------- |
||
| 2122 | // if goal_obj == -1, then create random vector |
||
| 2123 | static imobjptridx_t create_homing_missile(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const imobjptridx_t goal_obj, weapon_id_type objtype, int make_sound) |
||
| 2124 | { |
||
| 2125 | vms_vector vector_to_goal; |
||
| 2126 | //vms_vector goal_pos; |
||
| 2127 | |||
| 2128 | if (goal_obj == object_none) { |
||
| 2129 | make_random_vector(vector_to_goal); |
||
| 2130 | } else { |
||
| 2131 | vm_vec_normalized_dir_quick(vector_to_goal, goal_obj->pos, objp->pos); |
||
| 2132 | vm_vec_scale_add2(vector_to_goal, make_random_vector(), F1_0/4); |
||
| 2133 | vm_vec_normalize_quick(vector_to_goal); |
||
| 2134 | } |
||
| 2135 | |||
| 2136 | // Create a vector towards the goal, then add some noise to it. |
||
| 2137 | const auto &&objnum = Laser_create_new(vector_to_goal, objp->pos, vmsegptridx(objp->segnum), objp, objtype, make_sound); |
||
| 2138 | if (objnum != object_none) |
||
| 2139 | { |
||
| 2140 | // Fixed to make sure the right person gets credit for the kill |
||
| 2141 | |||
| 2142 | // Objects[objnum].ctype.laser_info.parent_num = objp->ctype.laser_info.parent_num; |
||
| 2143 | // Objects[objnum].ctype.laser_info.parent_type = objp->ctype.laser_info.parent_type; |
||
| 2144 | // Objects[objnum].ctype.laser_info.parent_signature = objp->ctype.laser_info.parent_signature; |
||
| 2145 | objnum->ctype.laser_info.track_goal = goal_obj; |
||
| 2146 | } |
||
| 2147 | return objnum; |
||
| 2148 | } |
||
| 2149 | |||
| 2150 | namespace { |
||
| 2151 | |||
| 2152 | struct miniparent |
||
| 2153 | { |
||
| 2154 | short type; |
||
| 2155 | objnum_t num; |
||
| 2156 | }; |
||
| 2157 | |||
| 2158 | } |
||
| 2159 | |||
| 2160 | //----------------------------------------------------------------------------- |
||
| 2161 | // Create the children of a smart bomb, which is a bunch of homing missiles. |
||
| 2162 | static void create_smart_children(object_array &Objects, const vmobjptridx_t objp, const uint_fast32_t num_smart_children, const miniparent parent) |
||
| 2163 | { |
||
| 2164 | auto &vcobjptridx = Objects.vcptridx; |
||
| 2165 | auto &vcobjptr = Objects.vcptr; |
||
| 2166 | unsigned numobjs = 0; |
||
| 2167 | weapon_id_type blob_id; |
||
| 2168 | |||
| 2169 | std::array<objnum_t, MAX_OBJDISTS> objlist; |
||
| 2170 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 2171 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 2172 | #endif |
||
| 2173 | { |
||
| 2174 | if (Game_mode & GM_MULTI) |
||
| 2175 | d_srand(8321L); |
||
| 2176 | |||
| 2177 | range_for (const auto &&curobjp, vcobjptridx) |
||
| 2178 | { |
||
| 2179 | if (((curobjp->type == OBJ_ROBOT && !curobjp->ctype.ai_info.CLOAKED) || curobjp->type == OBJ_PLAYER) && curobjp != parent.num) |
||
| 2180 | { |
||
| 2181 | if (curobjp->type == OBJ_PLAYER) |
||
| 2182 | { |
||
| 2183 | if (parent.type == OBJ_PLAYER && (Game_mode & GM_MULTI_COOP)) |
||
| 2184 | continue; |
||
| 2185 | if ((Game_mode & GM_TEAM) && get_team(get_player_id(curobjp)) == get_team(get_player_id(vcobjptr(parent.num)))) |
||
| 2186 | continue; |
||
| 2187 | if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) |
||
| 2188 | continue; |
||
| 2189 | } |
||
| 2190 | |||
| 2191 | // Robot blobs can't track robots. |
||
| 2192 | if (curobjp->type == OBJ_ROBOT) { |
||
| 2193 | if (parent.type == OBJ_ROBOT) |
||
| 2194 | continue; |
||
| 2195 | |||
| 2196 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 2197 | // Your shots won't track the buddy. |
||
| 2198 | if (parent.type == OBJ_PLAYER) |
||
| 2199 | if (Robot_info[get_robot_id(curobjp)].companion) |
||
| 2200 | continue; |
||
| 2201 | #endif |
||
| 2202 | } |
||
| 2203 | |||
| 2204 | const auto &&dist = vm_vec_dist2(objp->pos, curobjp->pos); |
||
| 2205 | if (dist < MAX_SMART_DISTANCE_SQUARED) |
||
| 2206 | { |
||
| 2207 | int oovis; |
||
| 2208 | |||
| 2209 | oovis = object_to_object_visibility(objp, curobjp, FQ_TRANSWALL); |
||
| 2210 | |||
| 2211 | if (oovis) { |
||
| 2212 | objlist[numobjs] = curobjp; |
||
| 2213 | numobjs++; |
||
| 2214 | if (numobjs >= MAX_OBJDISTS) { |
||
| 2215 | numobjs = MAX_OBJDISTS; |
||
| 2216 | break; |
||
| 2217 | } |
||
| 2218 | } |
||
| 2219 | } |
||
| 2220 | } |
||
| 2221 | } |
||
| 2222 | |||
| 2223 | // Get type of weapon for child from parent. |
||
| 2224 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 2225 | if (parent.type == OBJ_PLAYER) |
||
| 2226 | { |
||
| 2227 | blob_id = weapon_id_type::PLAYER_SMART_HOMING_ID; |
||
| 2228 | } else { |
||
| 2229 | blob_id = ((N_weapon_types<weapon_id_type::ROBOT_SMART_HOMING_ID)?(weapon_id_type::PLAYER_SMART_HOMING_ID):(weapon_id_type::ROBOT_SMART_HOMING_ID)); // NOTE: Shareware & reg 1.0 do not have their own Smart structure for bots. It was introduced in 1.4 to make Smart blobs from lvl 7 boss easier to dodge. So if we do not have this type, revert to player's Smart behaviour.., |
||
| 2230 | } |
||
| 2231 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 2232 | if (objp->type == OBJ_WEAPON) { |
||
| 2233 | blob_id = Weapon_info[get_weapon_id(objp)].children; |
||
| 2234 | Assert(blob_id != weapon_none); // Hmm, missing data in bitmaps.tbl. Need "children=NN" parameter. |
||
| 2235 | } else { |
||
| 2236 | Assert(objp->type == OBJ_ROBOT); |
||
| 2237 | blob_id = weapon_id_type::ROBOT_SMART_HOMING_ID; |
||
| 2238 | } |
||
| 2239 | #endif |
||
| 2240 | |||
| 2241 | objnum_t last_sel_objnum = object_none; |
||
| 2242 | const auto get_random_different_object = [&]{ |
||
| 2243 | for (;;) |
||
| 2244 | { |
||
| 2245 | const auto r = objlist[(d_rand() * numobjs) >> 15]; |
||
| 2246 | if (last_sel_objnum != r) |
||
| 2247 | return last_sel_objnum = r; |
||
| 2248 | } |
||
| 2249 | }; |
||
| 2250 | for (auto i = num_smart_children; i--;) |
||
| 2251 | { |
||
| 2252 | const auto &&sel_objnum = numobjs |
||
| 2253 | ? Objects.imptridx((numobjs == 1) |
||
| 2254 | ? objlist[0] |
||
| 2255 | : get_random_different_object()) |
||
| 2256 | : object_none; |
||
| 2257 | create_homing_missile(vmsegptridx, objp, sel_objnum, blob_id, (i==0)?1:0); |
||
| 2258 | } |
||
| 2259 | } |
||
| 2260 | } |
||
| 2261 | |||
| 2262 | void create_weapon_smart_children(const vmobjptridx_t objp) |
||
| 2263 | { |
||
| 2264 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 2265 | const auto wid = get_weapon_id(objp); |
||
| 2266 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 2267 | if (wid != weapon_id_type::SMART_ID) |
||
| 2268 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 2269 | if (Weapon_info[wid].children == weapon_id_type::unspecified) |
||
| 2270 | #endif |
||
| 2271 | return; |
||
| 2272 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 2273 | if (wid == weapon_id_type::EARTHSHAKER_ID) |
||
| 2274 | blast_nearby_glass(objp, Weapon_info[weapon_id_type::EARTHSHAKER_ID].strength[GameUniqueState.Difficulty_level]); |
||
| 2275 | #endif |
||
| 2276 | create_smart_children(Objects, objp, NUM_SMART_CHILDREN, {objp->ctype.laser_info.parent_type, objp->ctype.laser_info.parent_num}); |
||
| 2277 | } |
||
| 2278 | |||
| 2279 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 2280 | |||
| 2281 | void create_robot_smart_children(const vmobjptridx_t objp, const uint_fast32_t num_smart_children) |
||
| 2282 | { |
||
| 2283 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 2284 | create_smart_children(Objects, objp, num_smart_children, {OBJ_ROBOT, objp}); |
||
| 2285 | } |
||
| 2286 | |||
| 2287 | //give up control of the guided missile |
||
| 2288 | void release_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const unsigned player_num) |
||
| 2289 | { |
||
| 2290 | if (player_num == Player_num) |
||
| 2291 | { |
||
| 2292 | const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, player_num); |
||
| 2293 | if (gimobj == nullptr) |
||
| 2294 | return; |
||
| 2295 | |||
| 2296 | Missile_viewer = gimobj; |
||
| 2297 | if (Game_mode & GM_MULTI) |
||
| 2298 | multi_send_guided_info(gimobj, 1); |
||
| 2299 | if (Newdemo_state==ND_STATE_RECORDING) |
||
| 2300 | newdemo_record_guided_end(); |
||
| 2301 | } |
||
| 2302 | LevelUniqueObjectState.Guided_missile.clear_player_active_guided_missile(player_num); |
||
| 2303 | } |
||
| 2304 | #endif |
||
| 2305 | |||
| 2306 | // ------------------------------------------------------------------------------------------- |
||
| 2307 | //changed on 31/3/10 by kreatordxx to distinguish between drop bomb and secondary fire |
||
| 2308 | void do_missile_firing(int drop_bomb) |
||
| 2309 | { |
||
| 2310 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 2311 | auto &vmobjptr = Objects.vmptr; |
||
| 2312 | auto &vmobjptridx = Objects.vmptridx; |
||
| 2313 | int gun_flag=0; |
||
| 2314 | fix fire_frame_overhead = 0; |
||
| 2315 | |||
| 2316 | auto &plrobj = get_local_plrobj(); |
||
| 2317 | const auto bomb = which_bomb(); |
||
| 2318 | auto &player_info = plrobj.ctype.player_info; |
||
| 2319 | const auto weapon = drop_bomb ? bomb : player_info.Secondary_weapon; |
||
| 2320 | assert(weapon < MAX_SECONDARY_WEAPONS); |
||
| 2321 | auto &Next_missile_fire_time = player_info.Next_missile_fire_time; |
||
| 2322 | if (GameTime64 - Next_missile_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that. |
||
| 2323 | fire_frame_overhead = GameTime64 - Next_missile_fire_time; |
||
| 2324 | |||
| 2325 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 2326 | const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num); |
||
| 2327 | if (gimobj != nullptr) |
||
| 2328 | { |
||
| 2329 | release_guided_missile(LevelUniqueObjectState, Player_num); |
||
| 2330 | Next_missile_fire_time = GameTime64 + Weapon_info[Secondary_weapon_to_weapon_info[weapon]].fire_wait - fire_frame_overhead; |
||
| 2331 | return; |
||
| 2332 | } |
||
| 2333 | #endif |
||
| 2334 | |||
| 2335 | if (Player_dead_state != player_dead_state::no) |
||
| 2336 | return; |
||
| 2337 | if (auto &secondary_weapon_ammo = player_info.secondary_ammo[weapon]) |
||
| 2338 | { |
||
| 2339 | |||
| 2340 | int weapon_gun; |
||
| 2341 | |||
| 2342 | const auto weapon_index = Secondary_weapon_to_weapon_info[weapon]; |
||
| 2343 | |||
| 2344 | if (!cheats.rapidfire) |
||
| 2345 | Next_missile_fire_time = GameTime64 + Weapon_info[weapon_index].fire_wait - fire_frame_overhead; |
||
| 2346 | else |
||
| 2347 | Next_missile_fire_time = GameTime64 + (F1_0/25) - fire_frame_overhead; |
||
| 2348 | |||
| 2349 | weapon_gun = Secondary_weapon_to_gun_num[weapon]; |
||
| 2350 | |||
| 2351 | if (weapon_gun==4) { //alternate left/right |
||
| 2352 | auto &Missile_gun = get_local_plrobj().ctype.player_info.missile_gun; |
||
| 2353 | weapon_gun += (gun_flag = (Missile_gun & 1)); |
||
| 2354 | Missile_gun++; |
||
| 2355 | } |
||
| 2356 | |||
| 2357 | const auto &&objnum = Laser_player_fire(vmobjptridx(ConsoleObject), weapon_index, weapon_gun, 1, get_local_plrobj().orient.fvec, object_none); |
||
| 2358 | if (objnum != object_none) |
||
| 2359 | -- secondary_weapon_ammo; |
||
| 2360 | |||
| 2361 | if (weapon != CONCUSSION_INDEX) |
||
| 2362 | maybe_drop_net_powerup(Secondary_weapon_to_powerup[weapon], 1, 0); |
||
| 2363 | |||
| 2364 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 2365 | if (weapon == MEGA_INDEX) |
||
| 2366 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 2367 | if (weapon == MEGA_INDEX || weapon == SMISSILE5_INDEX) |
||
| 2368 | #endif |
||
| 2369 | { |
||
| 2370 | vms_vector force_vec; |
||
| 2371 | |||
| 2372 | const auto &&console = vmobjptr(ConsoleObject); |
||
| 2373 | force_vec.x = -(ConsoleObject->orient.fvec.x << 7); |
||
| 2374 | force_vec.y = -(ConsoleObject->orient.fvec.y << 7); |
||
| 2375 | force_vec.z = -(ConsoleObject->orient.fvec.z << 7); |
||
| 2376 | phys_apply_force(console, force_vec); |
||
| 2377 | |||
| 2378 | force_vec.x = (force_vec.x >> 4) + d_rand() - 16384; |
||
| 2379 | force_vec.y = (force_vec.y >> 4) + d_rand() - 16384; |
||
| 2380 | force_vec.z = (force_vec.z >> 4) + d_rand() - 16384; |
||
| 2381 | phys_apply_rot(console, force_vec); |
||
| 2382 | } |
||
| 2383 | |||
| 2384 | if (Game_mode & GM_MULTI) |
||
| 2385 | { |
||
| 2386 | multi_send_fire(weapon+MISSILE_ADJUST, 0, gun_flag, 1, objnum == object_none ? object_none : objnum->ctype.laser_info.track_goal, weapon_index_is_player_bomb(weapon) ? objnum : object_none); |
||
| 2387 | } |
||
| 2388 | |||
| 2389 | // don't autoselect if dropping prox and prox not current weapon |
||
| 2390 | if (!drop_bomb || player_info.Secondary_weapon == bomb) |
||
| 2391 | auto_select_secondary_weapon(player_info); //select next missile, if this one out of ammo |
||
| 2392 | } |
||
| 2393 | } |
||
| 2394 | } |