dxx-rebirth/similar/main/laser.cpp

2415 lines
88 KiB
C++
Raw Normal View History

2006-03-20 17:12:09 +00:00
/*
2014-06-01 17:55:23 +00:00
* Portions of this file are copyright Rebirth contributors and licensed as
* described in COPYING.txt.
* Portions of this file are copyright Parallax Software and licensed
* according to the Parallax license below.
* See COPYING.txt for license details.
2006-03-20 17:12:09 +00:00
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
*/
/*
*
* This will contain the laser code
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "inferno.h"
#include "game.h"
#include "bm.h"
#include "object.h"
#include "laser.h"
#include "segment.h"
#include "fvi.h"
#include "dxxerror.h"
2013-12-26 04:18:28 +00:00
#include "gameseg.h"
2006-03-20 17:12:09 +00:00
#include "textures.h"
#include "fireball.h"
#include "polyobj.h"
#include "robot.h"
#include "weapon.h"
#include "newdemo.h"
#include "timer.h"
#include "player.h"
#include "sounds.h"
#include "ai.h"
#include "powerup.h"
#include "multi.h"
#include "physics.h"
#include "multi.h"
#include "fwd-wall.h"
#include "playsave.h"
#include "compiler-range_for.h"
#include "d_levelstate.h"
#include "d_underlying_value.h"
2015-02-14 22:48:27 +00:00
#include "partial_range.h"
2006-03-20 17:12:09 +00:00
namespace {
#ifdef NEWHOMER
#define HOMING_TRACKABLE_DOT_FRAME_TIME HOMING_TURN_TIME
static ubyte d_homer_tick_step = 0;
static fix d_homer_tick_count = 0;
#else
#define HOMING_TRACKABLE_DOT_FRAME_TIME FrameTime
#endif
2006-03-20 17:12:09 +00:00
2015-05-09 17:39:01 +00:00
static int Muzzle_queue_index;
}
2015-05-09 17:39:01 +00:00
namespace dsx {
namespace {
static imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2);
static imobjptridx_t find_homing_object(const vms_vector &curpos, vmobjptridx_t tracker);
}
2006-03-20 17:12:09 +00:00
//---------------------------------------------------------------------------------
// Called by render code.... determines if the laser is from a robot or the
// player and calls the appropriate routine.
2017-03-11 19:56:26 +00:00
void Laser_render(grs_canvas &canvas, const object_base &obj)
2006-03-20 17:12:09 +00:00
{
2014-11-30 22:09:23 +00:00
auto &wi = Weapon_info[get_weapon_id(obj)];
switch(wi.render)
2014-11-30 22:09:23 +00:00
{
2006-03-20 17:12:09 +00:00
case WEAPON_RENDER_LASER:
Int3(); // Not supported anymore!
//Laser_draw_one(obj-Objects, Weapon_info[obj->id].bitmap );
break;
case WEAPON_RENDER_BLOB:
2022-06-05 17:44:52 +00:00
draw_object_blob(GameBitmaps, *Viewer, canvas, obj, wi.bitmap);
2006-03-20 17:12:09 +00:00
break;
case WEAPON_RENDER_POLYMODEL:
break;
case WEAPON_RENDER_VCLIP:
Int3(); // Oops, not supported, type added by mk on 09/09/94, but not for lasers...
[[fallthrough]];
2006-03-20 17:12:09 +00:00
default:
Error( "Invalid weapon render type in Laser_render\n" );
}
}
//---------------------------------------------------------------------------------
// Draws a texture-mapped laser bolt
//void Laser_draw_one( int objnum, grs_bitmap * bmp )
//{
// int t1, t2, t3;
// g3s_point p1, p2;
// object *obj;
// vms_vector start_pos,end_pos;
//
// obj = &Objects[objnum];
//
// start_pos = obj->pos;
// vm_vec_scale_add(&end_pos,&start_pos,&obj->orient.fvec,-Laser_length);
//
// g3_rotate_point(&p1,&start_pos);
// g3_rotate_point(&p2,&end_pos);
//
// t1 = Lighting_on;
// t2 = Interpolation_method;
// t3 = Transparency_on;
//
// Lighting_on = 0;
// //Interpolation_method = 3; // Full perspective
// Interpolation_method = 1; // Linear
// Transparency_on = 1;
//
// //gr_setcolor( gr_getcolor(31,15,0));
// //g3_draw_line_ptrs(p1,p2);
// //g3_draw_rod(p1,0x2000,p2,0x2000);
// //g3_draw_rod(p1,Laser_width,p2,Laser_width);
// g3_draw_rod_tmap(bmp,&p2,Laser_width,&p1,Laser_width,0);
// Lighting_on = t1;
// Interpolation_method = t2;
// Transparency_on = t3;
//
//}
namespace {
static bool ignore_proximity_weapon(const object &o)
{
if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(o)))
return false;
#if defined(DXX_BUILD_DESCENT_I)
return GameTime64 > o.ctype.laser_info.creation_time + F1_0*2;
#elif defined(DXX_BUILD_DESCENT_II)
return GameTime64 > o.ctype.laser_info.creation_time + F1_0*4;
#endif
}
#if defined(DXX_BUILD_DESCENT_I)
static bool ignore_phoenix_weapon(const object &)
{
return false;
}
static bool ignore_guided_missile_weapon(const object &)
{
return false;
}
#elif defined(DXX_BUILD_DESCENT_II)
static bool ignore_phoenix_weapon(const object &o)
{
return get_weapon_id(o) == weapon_id_type::PHOENIX_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0/4;
}
static bool ignore_guided_missile_weapon(const object &o)
{
return get_weapon_id(o) == weapon_id_type::GUIDEDMISS_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0*2;
}
#endif
}
2006-03-20 17:12:09 +00:00
// Changed by MK on 09/07/94
// I want you to be able to blow up your own bombs.
// AND...Your proximity bombs can blow you up if they're 2.0 seconds or more old.
// Changed by MK on 06/06/95: Now must be 4.0 seconds old. Much valid Net-complaining.
2015-05-09 17:38:58 +00:00
bool laser_are_related(const vcobjptridx_t o1, const vcobjptridx_t o2)
2006-03-20 17:12:09 +00:00
{
// See if o2 is the parent of o1
2015-05-09 17:38:58 +00:00
if (o1->type == OBJ_WEAPON)
if (laser_parent_is_object(o1->ctype.laser_info, o2))
2006-03-20 17:12:09 +00:00
{
// 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
2015-05-09 17:38:58 +00:00
if (ignore_proximity_weapon(o1) || ignore_guided_missile_weapon(o1) || ignore_phoenix_weapon(o1))
2013-08-27 23:53:03 +00:00
{
2006-03-20 17:12:09 +00:00
return 0;
} else
return 1;
}
// See if o1 is the parent of o2
2015-05-09 17:38:58 +00:00
if (o2->type == OBJ_WEAPON)
2006-03-20 17:12:09 +00:00
{
if (laser_parent_is_object(o2->ctype.laser_info, o1))
2006-03-20 17:12:09 +00:00
{
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// 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
2015-05-09 17:38:58 +00:00
if (ignore_proximity_weapon(o2) || ignore_guided_missile_weapon(o2) || ignore_phoenix_weapon(o2))
{
2006-03-20 17:12:09 +00:00
return 0;
} else
#endif
2006-03-20 17:12:09 +00:00
return 1;
}
}
// They must both be weapons
2015-05-09 17:38:58 +00:00
if (o1->type != OBJ_WEAPON || o2->type != OBJ_WEAPON)
2006-03-20 17:12:09 +00:00
return 0;
// Here is the 09/07/94 change -- Siblings must be identical, others can hurt each other
// See if they're siblings...
// 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.
const auto o1id = get_weapon_id(o1);
const auto o2id = get_weapon_id(o2);
auto &o1li = o1->ctype.laser_info;
auto &o2li = o2->ctype.laser_info;
if (o1li.parent_num == o2li.parent_num && o1li.parent_signature == o2li.parent_signature)
2006-03-20 17:12:09 +00:00
{
if (is_proximity_bomb_or_player_smart_mine(o1id) || is_proximity_bomb_or_player_smart_mine(o2id))
{
2006-03-20 17:12:09 +00:00
// If neither is older than 1/2 second, then can't blow up!
#if defined(DXX_BUILD_DESCENT_II)
if (!(GameTime64 > o1li.creation_time + F1_0/2 || GameTime64 > o2li.creation_time + F1_0/2))
2006-03-20 17:12:09 +00:00
return 1;
else
#endif
return 0;
2006-03-20 17:12:09 +00:00
} else
return 1;
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Anything can cause a collision with a robot super prox mine.
2015-11-27 03:56:13 +00:00
if (!(
o1id == weapon_id_type::ROBOT_SUPERPROX_ID || o2id == weapon_id_type::ROBOT_SUPERPROX_ID ||
o1id == weapon_id_type::PROXIMITY_ID || o2id == weapon_id_type::PROXIMITY_ID ||
o1id == weapon_id_type::SUPERPROX_ID || o2id == weapon_id_type::SUPERPROX_ID ||
o1id == weapon_id_type::PMINE_ID || o2id == weapon_id_type::PMINE_ID
2015-11-27 03:56:13 +00:00
))
2013-08-27 23:53:03 +00:00
return 1;
#endif
2013-08-27 23:53:03 +00:00
return 0;
2006-03-20 17:12:09 +00:00
}
}
namespace dcx {
namespace {
constexpr vm_distance MAX_SMART_DISTANCE(F1_0*150);
constexpr vm_distance_squared MAX_SMART_DISTANCE_SQUARED = MAX_SMART_DISTANCE * MAX_SMART_DISTANCE;
2014-10-26 21:37:27 +00:00
static void do_muzzle_stuff(segnum_t segnum, const vms_vector &pos)
2006-03-20 17:12:09 +00:00
{
2016-07-23 04:10:42 +00:00
auto &m = Muzzle_data[Muzzle_queue_index];
2006-03-20 17:12:09 +00:00
Muzzle_queue_index++;
if (Muzzle_queue_index >= MUZZLE_QUEUE_MAX)
Muzzle_queue_index = 0;
2016-07-23 04:10:42 +00:00
m.segnum = segnum;
m.pos = pos;
m.create_time = timer_query();
2006-03-20 17:12:09 +00:00
}
[[noreturn]]
__attribute_cold
static void report_invalid_weapon_render_type(const int weapon_type, const weapon_info::render_type render)
{
char buf[96];
snprintf(buf, sizeof(buf), "invalid weapon render type %u on weapon %i", static_cast<unsigned>(render), weapon_type);
throw std::runtime_error(buf);
}
}
}
namespace dsx {
namespace {
2006-03-20 17:12:09 +00:00
//creates a weapon object
static imobjptridx_t create_weapon_object(int weapon_type,const vmsegptridx_t segnum, const vms_vector &position)
2006-03-20 17:12:09 +00:00
{
render_type_t rtype;
2006-03-20 17:12:09 +00:00
fix laser_radius = -1;
switch(Weapon_info[weapon_type].render)
{
2006-03-20 17:12:09 +00:00
case WEAPON_RENDER_BLOB:
rtype = RT_LASER; // Render as a laser even if blob (see render code above for explanation)
laser_radius = Weapon_info[weapon_type].blob_size;
break;
case WEAPON_RENDER_POLYMODEL:
laser_radius = 0; // Filled in below.
rtype = RT_POLYOBJ;
break;
case WEAPON_RENDER_LASER:
Int3(); // Not supported anymore
return object_none;
2006-03-20 17:12:09 +00:00
case WEAPON_RENDER_NONE:
rtype = RT_NONE;
laser_radius = F1_0;
break;
case WEAPON_RENDER_VCLIP:
rtype = RT_WEAPON_VCLIP;
laser_radius = Weapon_info[weapon_type].blob_size;
break;
default:
report_invalid_weapon_render_type(weapon_type, Weapon_info[weapon_type].render);
2006-03-20 17:12:09 +00:00
}
Assert(laser_radius != -1);
2022-07-02 18:10:45 +00:00
const auto &&obj = obj_weapon_create(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, Weapon_info, weapon_type, segnum, position, laser_radius, rtype);
if (obj == object_none)
return object_none;
2006-03-20 17:12:09 +00:00
if (Weapon_info[weapon_type].render == WEAPON_RENDER_POLYMODEL) {
auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
obj->rtype.pobj_info.model_num = Weapon_info[get_weapon_id(obj)].model_num;
obj->size = fixdiv(Polygon_models[obj->rtype.pobj_info.model_num].rad,Weapon_info[get_weapon_id(obj)].po_len_to_width_ratio);
2006-03-20 17:12:09 +00:00
}
obj->mtype.phys_info.mass = Weapon_info[weapon_type].mass;
obj->mtype.phys_info.drag = Weapon_info[weapon_type].drag;
2014-09-28 21:11:04 +00:00
vm_vec_zero(obj->mtype.phys_info.thrust);
2006-03-20 17:12:09 +00:00
2020-12-20 20:39:07 +00:00
const auto bounce = Weapon_info[weapon_type].bounce;
if (bounce == weapon_info::bounce_type::always)
2006-03-20 17:12:09 +00:00
obj->mtype.phys_info.flags |= PF_BOUNCE;
#if defined(DXX_BUILD_DESCENT_II)
2020-12-20 20:39:07 +00:00
if (bounce == weapon_info::bounce_type::twice || cheats.bouncyfire)
2006-03-20 17:12:09 +00:00
obj->mtype.phys_info.flags |= PF_BOUNCE+PF_BOUNCES_TWICE;
#endif
2006-03-20 17:12:09 +00:00
return obj;
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
namespace {
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------------------------------------------
// ***** HEY ARTISTS!! *****
// Here are the constants you're looking for! --MK
// Change the following constants to affect the look of the omega cannon.
// Changing these constants will not affect the damage done.
// WARNING: If you change DESIRED_OMEGA_DIST and MAX_OMEGA_BLOBS, you don't merely change the look of the cannon,
// you change its range. If you decrease DESIRED_OMEGA_DIST, you decrease how far the gun can fire.
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!
constexpr std::integral_constant<unsigned, 3> MIN_OMEGA_BLOBS{}; // No matter how close the obstruction, at this many blobs created.
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
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.
constexpr std::integral_constant<unsigned, 16> MAX_OMEGA_BLOBS{}; // No matter how far away the obstruction, this is the maximum number of blobs.
constexpr vm_distance MAX_OMEGA_DIST{MAX_OMEGA_BLOBS * DESIRED_OMEGA_DIST}; // Maximum extent of lightning blobs.
constexpr vm_distance_squared MAX_OMEGA_DIST_SQUARED{MAX_OMEGA_DIST * MAX_OMEGA_DIST};
2006-03-20 17:12:09 +00:00
// Additionally, several constants which apply to homing objects in general control the behavior of the Omega Cannon.
// They are defined in laser.h. They are copied here for reference. These values are valid on 1/10/96:
// If you want the Omega Cannon view cone to be different than the Homing Missile viewcone, contact MK to make the change.
// (Unless you are a programmer, in which case, do it yourself!)
#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.
constexpr vm_distance OMEGA_MAX_TRACKABLE_DIST = MAX_OMEGA_DIST; // An object must be at least this close to be tracked.
2006-03-20 17:12:09 +00:00
// Note, you don't need to change these constants. You can control damage and energy consumption by changing the
// usual bitmaps.tbl parameters.
#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.
#define OMEGA_ENERGY_CONSUMPTION 16 // Controls how much energy is consumed. This gets multiplied by the energy parameter from bitmaps.tbl.
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------------------------------------------
// Delete omega blobs further away than MAX_OMEGA_DIST
// 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.
static int omega_cleanup(fvcobjptr &vcobjptr, const vmobjptridx_t weapon)
2006-03-20 17:12:09 +00:00
{
2015-12-03 03:26:49 +00:00
if (weapon->type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID)
return 0;
auto &weapon_laser_info = weapon->ctype.laser_info;
auto &obj = *vcobjptr(weapon_laser_info.parent_num);
if (laser_parent_is_matching_signature(weapon_laser_info, obj))
if (vm_vec_dist2(weapon->pos, obj.pos) > MAX_OMEGA_DIST_SQUARED)
{
obj_delete(LevelUniqueObjectState, Segments, weapon);
return 1;
}
return 0;
}
}
// Return true if ok to do Omega damage. For Multiplayer games. See comment for omega_cleanup()
int ok_to_do_omega_damage(const object &weapon)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vcobjptr = Objects.vcptr;
if (weapon.type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID)
return 1;
if (!(Game_mode & GM_MULTI))
return 1;
auto &weapon_laser_info = weapon.ctype.laser_info;
auto &obj = *vcobjptr(weapon_laser_info.parent_num);
if (laser_parent_is_matching_signature(weapon_laser_info, obj))
if (vm_vec_dist2(obj.pos, weapon.pos) > MAX_OMEGA_DIST_SQUARED)
return 0;
return 1;
2006-03-20 17:12:09 +00:00
}
namespace {
2006-03-20 17:12:09 +00:00
// ---------------------------------------------------------------------------------
static bool create_omega_blobs(d_level_unique_object_state &LevelUniqueObjectState, const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const weapon_info_array &Weapon_info, const Difficulty_level_type Difficulty_level, const imsegptridx_t firing_segnum, const vms_vector &firing_pos, const vms_vector &goal_pos, const vmobjptridx_t parent_objp)
2006-03-20 17:12:09 +00:00
{
imobjptridx_t last_created_objnum = object_none;
2016-01-26 03:45:08 +00:00
fix dist_to_goal = 0, omega_blob_dist = 0;
2006-03-20 17:12:09 +00:00
2014-10-29 03:24:31 +00:00
auto vec_to_goal = vm_vec_sub(goal_pos, firing_pos);
2014-09-28 21:11:04 +00:00
dist_to_goal = vm_vec_normalize_quick(vec_to_goal);
2006-03-20 17:12:09 +00:00
2015-02-14 22:48:27 +00:00
unsigned num_omega_blobs = 0;
2006-03-20 17:12:09 +00:00
if (dist_to_goal < MIN_OMEGA_BLOBS * MIN_OMEGA_DIST) {
omega_blob_dist = MIN_OMEGA_DIST;
num_omega_blobs = dist_to_goal/omega_blob_dist;
if (num_omega_blobs == 0)
num_omega_blobs = 1;
} else {
omega_blob_dist = DESIRED_OMEGA_DIST;
num_omega_blobs = dist_to_goal / omega_blob_dist;
if (num_omega_blobs > MAX_OMEGA_BLOBS) {
num_omega_blobs = MAX_OMEGA_BLOBS;
omega_blob_dist = dist_to_goal / num_omega_blobs;
} else if (num_omega_blobs < MIN_OMEGA_BLOBS) {
num_omega_blobs = MIN_OMEGA_BLOBS;
omega_blob_dist = dist_to_goal / num_omega_blobs;
}
}
auto omega_delta_vector = vec_to_goal;
2014-09-28 21:11:05 +00:00
vm_vec_scale(omega_delta_vector, omega_blob_dist);
2006-03-20 17:12:09 +00:00
// Now, create all the blobs
auto blob_pos = firing_pos;
2014-11-26 03:39:21 +00:00
auto last_segnum = firing_segnum;
2006-03-20 17:12:09 +00:00
// If nearby, don't perturb vector. If not nearby, start halfway out.
std::array<fix, MAX_OMEGA_BLOBS> perturb_array;
2006-03-20 17:12:09 +00:00
if (dist_to_goal < MIN_OMEGA_DIST*4) {
perturb_array = {};
2006-03-20 17:12:09 +00:00
} else {
2014-09-28 21:43:14 +00:00
vm_vec_scale_add2(blob_pos, omega_delta_vector, F1_0/2); // Put first blob half way out.
for (int i=0; i<num_omega_blobs/2; i++) {
2006-03-20 17:12:09 +00:00
perturb_array[i] = F1_0*i + F1_0/4;
perturb_array[num_omega_blobs-1-i] = F1_0*i;
}
}
// Create random perturbation vector, but favor _not_ going up in player's reference.
auto perturb_vec = make_random_vector();
2014-09-28 21:43:14 +00:00
vm_vec_scale_add2(perturb_vec, parent_objp->orient.uvec, -F1_0/2);
2006-03-20 17:12:09 +00:00
Doing_lighting_hack_flag = 1; // Ugly, but prevents blobs which are probably outside the mine from killing framerate.
for (int i=0; i<num_omega_blobs; i++) {
2006-03-20 17:12:09 +00:00
// This will put the last blob right at the destination object, causing damage.
if (i == num_omega_blobs-1)
2014-09-28 21:43:14 +00:00
vm_vec_scale_add2(blob_pos, omega_delta_vector, 15*F1_0/32); // Move last blob another (almost) half section
2006-03-20 17:12:09 +00:00
// Every so often, re-perturb blobs
if ((i % 4) == 3) {
2016-07-24 04:04:25 +00:00
vm_vec_scale_add2(perturb_vec, make_random_vector(), F1_0/4);
2006-03-20 17:12:09 +00:00
}
const auto temp_pos = vm_vec_scale_add(blob_pos, perturb_vec, perturb_array[i]);
2006-03-20 17:12:09 +00:00
2018-09-19 02:13:30 +00:00
const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, temp_pos, last_segnum);
if (segnum != segment_none) {
2006-03-20 17:12:09 +00:00
last_segnum = segnum;
2022-07-02 18:10:45 +00:00
const auto &&objp = obj_weapon_create(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, Weapon_info, weapon_id_type::OMEGA_ID, segnum, temp_pos, 0, RT_WEAPON_VCLIP);
if (objp == object_none)
2006-03-20 17:12:09 +00:00
break;
last_created_objnum = objp;
2006-03-20 17:12:09 +00:00
objp->lifeleft = OMEGA_BASE_TIME+(d_rand()/8); // add little randomness so the lighting effect becomes a little more interesting
2006-03-20 17:12:09 +00:00
objp->mtype.phys_info.velocity = vec_to_goal;
// Only make the last one move fast, else multiple blobs might collide with target.
2014-09-28 21:11:05 +00:00
vm_vec_scale(objp->mtype.phys_info.velocity, F1_0*4);
2006-03-20 17:12:09 +00:00
2015-11-27 03:56:13 +00:00
const auto &weapon_info = Weapon_info[get_weapon_id(objp)];
objp->size = weapon_info.blob_size;
2006-03-20 17:12:09 +00:00
2015-11-27 03:56:13 +00:00
objp->shields = fixmul(OMEGA_DAMAGE_SCALE*OMEGA_BASE_TIME, weapon_info.strength[Difficulty_level]);
2006-03-20 17:12:09 +00:00
objp->ctype.laser_info.parent_type = parent_objp->type;
objp->ctype.laser_info.parent_signature = parent_objp->signature;
objp->ctype.laser_info.parent_num = parent_objp;
objp->movement_source = object::movement_type::None; // Only last one moves, that will get bashed below.
2006-03-20 17:12:09 +00:00
}
2014-09-28 21:43:00 +00:00
vm_vec_add2(blob_pos, omega_delta_vector);
2006-03-20 17:12:09 +00:00
}
Doing_lighting_hack_flag = 0;
2006-03-20 17:12:09 +00:00
// Make last one move faster, but it's already moving at speed = F1_0*4.
if (last_created_objnum != object_none) {
2015-12-03 03:26:49 +00:00
vm_vec_scale(last_created_objnum->mtype.phys_info.velocity, Weapon_info[weapon_id_type::OMEGA_ID].speed[Difficulty_level]/4);
last_created_objnum->movement_source = object::movement_type::physics;
return true;
2006-03-20 17:12:09 +00:00
}
return false;
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
#define MIN_OMEGA_CHARGE (MAX_OMEGA_CHARGE/8)
#define OMEGA_CHARGE_SCALE 4 // FrameTime / OMEGA_CHARGE_SCALE added to Omega_charge every frame.
fix get_omega_energy_consumption(const fix delta_charge)
{
const fix energy_used = fixmul(F1_0 * 190 / 17, delta_charge);
const auto Difficulty_level = GameUniqueState.Difficulty_level;
return (Difficulty_level == Difficulty_level_type::_0 || Difficulty_level == Difficulty_level_type::_1)
? fixmul(energy_used, i2f(underlying_value(Difficulty_level) + 2) / 4)
: energy_used;
}
2006-03-20 17:12:09 +00:00
// ---------------------------------------------------------------------------------
// Call this every frame to recharge the Omega Cannon.
void omega_charge_frame(player_info &player_info)
2006-03-20 17:12:09 +00:00
{
if (!(player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(primary_weapon_index_t::OMEGA_INDEX)))
return;
auto &Omega_charge = player_info.Omega_charge;
if (Omega_charge >= MAX_OMEGA_CHARGE)
2006-03-20 17:12:09 +00:00
return;
if (Player_dead_state != player_dead_state::no)
2006-03-20 17:12:09 +00:00
return;
// Don't charge while firing. Wait 1/3 second after firing before recharging
auto &Omega_recharge_delay = player_info.Omega_recharge_delay;
Track omega recharge delay as relative time This fixes two problems on systems where sizeof(int) < sizeof(fix64) (which applies on most systems). Normally, the omega cannon does not charge for ~1/3 of a second after firing stops. The first problem is that, if (Last_omega_fire_time + 1/3 second) exceeds 0x80000000 (about 9.1 hours), truncation issues confuse this rule into not applying, thus allowing the omega cannon to charge whenever energy is available, even while firing. The second problem is that, if GameTime64 exceeds 0x8000000000000000 (about 4459701.8 years), a sanity check that attempted to compensate for the incorrect tracking of Last_omega_fire_time would confuse the recharge rule into deciding that the user had always fired recently, which would prevent the omega cannon from ever recharging. The sanity check would reset Last_omega_fire_time when Last_omega_fire_time was in the future relative to GameTime64. Unfortunately, Last_omega_fire_time was only an int, so the reset truncates off the high bits of GameTime64. This truncation is harmless when GameTime64 is less than 0x80000000, causes the first problem when GameTime64 is not less than 0x80000000, but is positive, and causes the second problem when GameTime64 is negative. These problems were mitigated in prior releases by three factors. First, a hack resets GameTime64 when restoring from a saved game, so the affected game needs to run for ~9.1 hours (or ~4459701.8 years) without reloading. Second, starting a new level resets GameTime64, so the game needs to stay on a single level for ~9.1 hours (or ~4459701.8 years). Third, the omega cannon discharges faster than it can recharge, so even when the first bug allowed it to charge while firing, a player could still drain Omega_charge to zero by continuous firing. However, it would take longer to drain due to the bug-induced concurrent recharge, and would not be subject to the 1/3 second wait normally imposed between depleting Omega_charge and recharge beginning. Fix these problems by replacing Last_omega_fire_time with Omega_recharge_delay, which is 0 when recharging is allowed or a positive amount of frame time if recharging is disallowed due to recent firing. Set Omega_recharge_delay to 1/3 second when the user fires. Decrease it by up to FrameTime when omega_recharge_frame runs. This does not fix the preexisting problem that reloading a savegame does not update the recharge delay, which manifests two related problems. First, firing the omega cannon and then loading a game will bring the delay through into the save, whether or not the user had been firing just before saving. Second, not firing the omega cannon and then loading a game will allow the user to fire immediately on load, even if the user had been firing when the game was saved. Future work can address the first problem by clearing the delay, but a savegame file modification is required to address the second problem. ------------->8------------- #include <cstdio> int last_fire; long t64; /* The test program only shows the bug on 64-bit systems */ static_assert(sizeof(last_fire) == 4, "sizeof(int) != 4"); static_assert(sizeof(t64) == 8, "sizeof(long) != 8"); void frame(bool expect_recharge, const unsigned line) /* clang does not support __builtin_LINE, so fake it with a macro */ #define frame(E) frame(E, __LINE__) { if (last_fire > t64) { printf(__FILE__ ":%u:%u: last_fire=%8x t64=%16lx: last_fire > t64, resetting\n", __LINE__, line, last_fire, t64); last_fire = t64; } const int time_bias = 0x5555; if (last_fire + time_bias > t64) { printf(__FILE__ ":%u:%u: %5sexpect_recharge=%i last_fire=%8x t64=%16lx: last_fire recent (%16lx), refusing to recharge\n", __LINE__, line, expect_recharge ? "BUG: " : "", expect_recharge, last_fire, t64, static_cast<long>(last_fire + time_bias)); return; } printf(__FILE__ ":%u:%u: %5sexpect_recharge=%i last_fire=%8x t64=%16lx: last_fire old (%16lx), recharging\n", __LINE__, line, expect_recharge ? "" : "BUG: ", expect_recharge, last_fire, t64, static_cast<long>(last_fire + time_bias)); } int main(int, char **) { frame(false); t64 = 0x7fff; frame(true); last_fire = t64 - 4; frame(false); t64 = 0x7fffaaab; frame(true); last_fire = t64; ++t64; frame(false); t64 = 0x7ffffffffd; frame(true); last_fire = t64; ++t64; frame(false); t64 += 0x10000; frame(true); t64 = 0x7fffffffffffff00; frame(true); last_fire = t64; frame(false); t64 = 0x8000000000000000; frame(true); t64 += 0x800000000; frame(true); }
2016-11-12 18:10:08 +00:00
if (Omega_recharge_delay)
{
if (Omega_recharge_delay > FrameTime)
{
Omega_recharge_delay -= FrameTime;
return;
}
Omega_recharge_delay = 0;
}
2006-03-20 17:12:09 +00:00
if (auto &energy = player_info.energy)
{
const auto old_omega_charge = Omega_charge;
2006-03-20 17:12:09 +00:00
Omega_charge += FrameTime/OMEGA_CHARGE_SCALE;
if (Omega_charge > MAX_OMEGA_CHARGE)
Omega_charge = MAX_OMEGA_CHARGE;
const auto energy_used = get_omega_energy_consumption(Omega_charge - old_omega_charge);
2016-07-03 00:54:16 +00:00
energy -= energy_used;
if (energy < 0)
energy = 0;
2006-03-20 17:12:09 +00:00
}
}
namespace {
2006-03-20 17:12:09 +00:00
// -- fix Last_omega_muzzle_flash_time;
// ---------------------------------------------------------------------------------
// *objp is the object firing the omega cannon
// *pos is the location from which the omega bolt starts
Allow non-players to use OMEGA_ID weapon User TRUEpiiiicness reports that valptridx<player>::check_index_range traps when "The Apocalyptic Factor"[1] level 14 boss fires its Omega-based weapon. Code inspection shows that this is expected, since the original designers assumed OMEGA_ID would only ever be used by players, and coded various shortcuts accordingly. No one told the level author this. The boss is an interesting concept and not difficult to support, so adjust the code to handle this situation correctly: - Check that the shooter is a player before checking its player ID. Without this, a robot that happened to have the same ID as the player would interpret robot data as player data, likely causing corruption when it tried to update the omega cannon charge. Even if the robot ID does not match, this step causes a diagnostic[2] reporting that a robot ID is being misused as a player ID. - Remove the shortcut that assumes the shooter is a player. Store the shooter's actual type. - Remove the unnecessary and counterproductive path that: 1. Uses the object pointer to get the player's ID 2. Uses that player ID to get a player pointer 3. Uses that player pointer to get an object number 4. Uses that object number to get an object pointer When invariants are maintained, the pointer derived in step 4 is equal to the pointer used at the start of step 1. Use that pointer directly instead of rederiving it. The reported exception was due to step 2, which requires that the player ID is in range. When the shooter is a player, this is true. When the shooter is not a player, it may not be true. [1] http://www.enspiar.com/dmdb/viewMission.php?id=418 ``` sha1sum af.hog af.mn2 133c52fb4b4e5fd40bf7b2321789841b727d1d0b af.hog 0bb5f0dd1803b0fc1aac7c2023eacc97e9ab872b af.mn2 stat -c '%s %Y %n' af.hog af.mn2 4024838 1094445016 af.hog 566 1013524628 af.mn2 ``` [2] `similar/main/laser.cpp:560: BUG: object 0x555556078958 has type 2, expected 4` Reported-by: TRUEpiiiicness <https://forum.dxx-rebirth.com/showthread.php?tid=1038>
2018-01-29 01:56:40 +00:00
static void do_omega_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t parent_objp, const vms_vector &firing_pos, const vmobjptridx_t weapon_objp)
2006-03-20 17:12:09 +00:00
{
vms_vector goal_pos;
player_info *pl_info = nullptr;
Allow non-players to use OMEGA_ID weapon User TRUEpiiiicness reports that valptridx<player>::check_index_range traps when "The Apocalyptic Factor"[1] level 14 boss fires its Omega-based weapon. Code inspection shows that this is expected, since the original designers assumed OMEGA_ID would only ever be used by players, and coded various shortcuts accordingly. No one told the level author this. The boss is an interesting concept and not difficult to support, so adjust the code to handle this situation correctly: - Check that the shooter is a player before checking its player ID. Without this, a robot that happened to have the same ID as the player would interpret robot data as player data, likely causing corruption when it tried to update the omega cannon charge. Even if the robot ID does not match, this step causes a diagnostic[2] reporting that a robot ID is being misused as a player ID. - Remove the shortcut that assumes the shooter is a player. Store the shooter's actual type. - Remove the unnecessary and counterproductive path that: 1. Uses the object pointer to get the player's ID 2. Uses that player ID to get a player pointer 3. Uses that player pointer to get an object number 4. Uses that object number to get an object pointer When invariants are maintained, the pointer derived in step 4 is equal to the pointer used at the start of step 1. Use that pointer directly instead of rederiving it. The reported exception was due to step 2, which requires that the player ID is in range. When the shooter is a player, this is true. When the shooter is not a player, it may not be true. [1] http://www.enspiar.com/dmdb/viewMission.php?id=418 ``` sha1sum af.hog af.mn2 133c52fb4b4e5fd40bf7b2321789841b727d1d0b af.hog 0bb5f0dd1803b0fc1aac7c2023eacc97e9ab872b af.mn2 stat -c '%s %Y %n' af.hog af.mn2 4024838 1094445016 af.hog 566 1013524628 af.mn2 ``` [2] `similar/main/laser.cpp:560: BUG: object 0x555556078958 has type 2, expected 4` Reported-by: TRUEpiiiicness <https://forum.dxx-rebirth.com/showthread.php?tid=1038>
2018-01-29 01:56:40 +00:00
if (parent_objp->type == OBJ_PLAYER && get_player_id(parent_objp) == Player_num)
{
2006-03-20 17:12:09 +00:00
// If charge >= min, or (some charge and zero energy), allow to fire.
Track omega recharge delay as relative time This fixes two problems on systems where sizeof(int) < sizeof(fix64) (which applies on most systems). Normally, the omega cannon does not charge for ~1/3 of a second after firing stops. The first problem is that, if (Last_omega_fire_time + 1/3 second) exceeds 0x80000000 (about 9.1 hours), truncation issues confuse this rule into not applying, thus allowing the omega cannon to charge whenever energy is available, even while firing. The second problem is that, if GameTime64 exceeds 0x8000000000000000 (about 4459701.8 years), a sanity check that attempted to compensate for the incorrect tracking of Last_omega_fire_time would confuse the recharge rule into deciding that the user had always fired recently, which would prevent the omega cannon from ever recharging. The sanity check would reset Last_omega_fire_time when Last_omega_fire_time was in the future relative to GameTime64. Unfortunately, Last_omega_fire_time was only an int, so the reset truncates off the high bits of GameTime64. This truncation is harmless when GameTime64 is less than 0x80000000, causes the first problem when GameTime64 is not less than 0x80000000, but is positive, and causes the second problem when GameTime64 is negative. These problems were mitigated in prior releases by three factors. First, a hack resets GameTime64 when restoring from a saved game, so the affected game needs to run for ~9.1 hours (or ~4459701.8 years) without reloading. Second, starting a new level resets GameTime64, so the game needs to stay on a single level for ~9.1 hours (or ~4459701.8 years). Third, the omega cannon discharges faster than it can recharge, so even when the first bug allowed it to charge while firing, a player could still drain Omega_charge to zero by continuous firing. However, it would take longer to drain due to the bug-induced concurrent recharge, and would not be subject to the 1/3 second wait normally imposed between depleting Omega_charge and recharge beginning. Fix these problems by replacing Last_omega_fire_time with Omega_recharge_delay, which is 0 when recharging is allowed or a positive amount of frame time if recharging is disallowed due to recent firing. Set Omega_recharge_delay to 1/3 second when the user fires. Decrease it by up to FrameTime when omega_recharge_frame runs. This does not fix the preexisting problem that reloading a savegame does not update the recharge delay, which manifests two related problems. First, firing the omega cannon and then loading a game will bring the delay through into the save, whether or not the user had been firing just before saving. Second, not firing the omega cannon and then loading a game will allow the user to fire immediately on load, even if the user had been firing when the game was saved. Future work can address the first problem by clearing the delay, but a savegame file modification is required to address the second problem. ------------->8------------- #include <cstdio> int last_fire; long t64; /* The test program only shows the bug on 64-bit systems */ static_assert(sizeof(last_fire) == 4, "sizeof(int) != 4"); static_assert(sizeof(t64) == 8, "sizeof(long) != 8"); void frame(bool expect_recharge, const unsigned line) /* clang does not support __builtin_LINE, so fake it with a macro */ #define frame(E) frame(E, __LINE__) { if (last_fire > t64) { printf(__FILE__ ":%u:%u: last_fire=%8x t64=%16lx: last_fire > t64, resetting\n", __LINE__, line, last_fire, t64); last_fire = t64; } const int time_bias = 0x5555; if (last_fire + time_bias > t64) { printf(__FILE__ ":%u:%u: %5sexpect_recharge=%i last_fire=%8x t64=%16lx: last_fire recent (%16lx), refusing to recharge\n", __LINE__, line, expect_recharge ? "BUG: " : "", expect_recharge, last_fire, t64, static_cast<long>(last_fire + time_bias)); return; } printf(__FILE__ ":%u:%u: %5sexpect_recharge=%i last_fire=%8x t64=%16lx: last_fire old (%16lx), recharging\n", __LINE__, line, expect_recharge ? "" : "BUG: ", expect_recharge, last_fire, t64, static_cast<long>(last_fire + time_bias)); } int main(int, char **) { frame(false); t64 = 0x7fff; frame(true); last_fire = t64 - 4; frame(false); t64 = 0x7fffaaab; frame(true); last_fire = t64; ++t64; frame(false); t64 = 0x7ffffffffd; frame(true); last_fire = t64; ++t64; frame(false); t64 += 0x10000; frame(true); t64 = 0x7fffffffffffff00; frame(true); last_fire = t64; frame(false); t64 = 0x8000000000000000; frame(true); t64 += 0x800000000; frame(true); }
2016-11-12 18:10:08 +00:00
auto &player_info = parent_objp->ctype.player_info;
if (!(player_info.Omega_charge >= MIN_OMEGA_CHARGE || (player_info.Omega_charge && !player_info.energy)))
{
obj_delete(LevelUniqueObjectState, Segments, weapon_objp);
2006-03-20 17:12:09 +00:00
return;
}
pl_info = &player_info;
2006-03-20 17:12:09 +00:00
}
Allow non-players to use OMEGA_ID weapon User TRUEpiiiicness reports that valptridx<player>::check_index_range traps when "The Apocalyptic Factor"[1] level 14 boss fires its Omega-based weapon. Code inspection shows that this is expected, since the original designers assumed OMEGA_ID would only ever be used by players, and coded various shortcuts accordingly. No one told the level author this. The boss is an interesting concept and not difficult to support, so adjust the code to handle this situation correctly: - Check that the shooter is a player before checking its player ID. Without this, a robot that happened to have the same ID as the player would interpret robot data as player data, likely causing corruption when it tried to update the omega cannon charge. Even if the robot ID does not match, this step causes a diagnostic[2] reporting that a robot ID is being misused as a player ID. - Remove the shortcut that assumes the shooter is a player. Store the shooter's actual type. - Remove the unnecessary and counterproductive path that: 1. Uses the object pointer to get the player's ID 2. Uses that player ID to get a player pointer 3. Uses that player pointer to get an object number 4. Uses that object number to get an object pointer When invariants are maintained, the pointer derived in step 4 is equal to the pointer used at the start of step 1. Use that pointer directly instead of rederiving it. The reported exception was due to step 2, which requires that the player ID is in range. When the shooter is a player, this is true. When the shooter is not a player, it may not be true. [1] http://www.enspiar.com/dmdb/viewMission.php?id=418 ``` sha1sum af.hog af.mn2 133c52fb4b4e5fd40bf7b2321789841b727d1d0b af.hog 0bb5f0dd1803b0fc1aac7c2023eacc97e9ab872b af.mn2 stat -c '%s %Y %n' af.hog af.mn2 4024838 1094445016 af.hog 566 1013524628 af.mn2 ``` [2] `similar/main/laser.cpp:560: BUG: object 0x555556078958 has type 2, expected 4` Reported-by: TRUEpiiiicness <https://forum.dxx-rebirth.com/showthread.php?tid=1038>
2018-01-29 01:56:40 +00:00
weapon_objp->ctype.laser_info.parent_type = parent_objp->type;
weapon_objp->ctype.laser_info.parent_num = parent_objp.get_unchecked_index();
weapon_objp->ctype.laser_info.parent_signature = parent_objp->signature;
2006-03-20 17:12:09 +00:00
const auto &&lock_objnum = find_homing_object(firing_pos, weapon_objp);
2006-03-20 17:12:09 +00:00
2018-09-19 02:13:30 +00:00
const auto &&firing_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, firing_pos, Segments.vmptridx(parent_objp->segnum));
2006-03-20 17:12:09 +00:00
// -- if ((Last_omega_muzzle_flash_time + F1_0/4 < GameTime) || (Last_omega_muzzle_flash_time > GameTime)) {
// -- do_muzzle_stuff(firing_segnum, firing_pos);
// -- Last_omega_muzzle_flash_time = GameTime;
// -- }
// Delete the original object. Its only purpose in life was to determine which object to home in on.
obj_delete(LevelUniqueObjectState, Segments, weapon_objp);
2006-03-20 17:12:09 +00:00
// If couldn't lock on anything, fire straight ahead.
if (lock_objnum == object_none) {
2006-03-20 17:12:09 +00:00
fvi_info hit_data;
2016-07-24 04:04:25 +00:00
const auto &&perturbed_fvec = vm_vec_scale_add(parent_objp->orient.fvec, make_random_vector(), F1_0/16);
2014-10-26 21:37:27 +00:00
vm_vec_scale_add(goal_pos, firing_pos, perturbed_fvec, MAX_OMEGA_DIST);
if (firing_segnum == segment_none)
2006-03-20 17:12:09 +00:00
return;
const auto fate = find_vector_intersection(fvi_query{
firing_pos,
goal_pos,
fvi_query::unused_ignore_obj_list,
&LevelUniqueObjectState,
&LevelSharedRobotInfoState.Robot_info,
FQ_IGNORE_POWERUPS | FQ_TRANSPOINT, //what about trans walls???
parent_objp,
}, firing_segnum, 0, hit_data);
if (fate != fvi_hit_type::None)
{
Assert(hit_data.hit_seg != segment_none); // How can this be? We went from inside the mine to outside without hitting anything?
2006-03-20 17:12:09 +00:00
goal_pos = hit_data.hit_pnt;
}
} else
goal_pos = lock_objnum->pos;
2006-03-20 17:12:09 +00:00
// This is where we create a pile of omega blobs!
if (!create_omega_blobs(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, Weapon_info, GameUniqueState.Difficulty_level, firing_segnum, firing_pos, goal_pos, parent_objp))
return;
2006-03-20 17:12:09 +00:00
// Play sound.
{
const auto flash_sound = Weapon_info[get_weapon_id(weapon_objp)].flash_sound;
if (parent_objp == Viewer)
digi_play_sample(flash_sound, F1_0);
else
digi_link_sound_to_pos(flash_sound, vmsegptridx(weapon_objp->segnum), sidenum_t::WLEFT, weapon_objp->pos, 0, F1_0);
}
if (pl_info)
{
if (auto &Omega_charge = pl_info->Omega_charge; Omega_charge > OMEGA_BASE_TIME)
Omega_charge -= OMEGA_BASE_TIME;
else
Omega_charge = 0;
pl_info->Omega_recharge_delay = F1_0 / 3;
}
2006-03-20 17:12:09 +00:00
}
static int is_laser_weapon_type(const weapon_id_type weapon_type)
{
return weapon_type == weapon_id_type::LASER_ID_L1 ||
weapon_type == weapon_id_type::LASER_ID_L2 ||
weapon_type == weapon_id_type::LASER_ID_L3 ||
weapon_type == weapon_id_type::LASER_ID_L4 ||
weapon_type == weapon_id_type::LASER_ID_L5 ||
weapon_type == weapon_id_type::LASER_ID_L6;
}
}
#endif
2006-03-20 17:12:09 +00:00
// ---------------------------------------------------------------------------------
// Initializes a laser after Fire is pressed
2006-03-20 17:12:09 +00:00
// Returns object number.
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, const weapon_sound_flag make_sound)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
2006-03-20 17:12:09 +00:00
fix parent_speed, weapon_speed;
fix laser_length=0;
if (weapon_type >= N_weapon_types)
{
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());
weapon_type = weapon_id_type::LASER_ID_L1;
}
2006-03-20 17:12:09 +00:00
// Don't let homing blobs make muzzle flash.
2014-08-23 23:53:56 +00:00
if (parent->type == OBJ_ROBOT)
2006-03-20 17:12:09 +00:00
do_muzzle_stuff(segnum, position);
const imobjptridx_t obj = create_weapon_object(weapon_type,segnum,position);
2006-03-20 17:12:09 +00:00
if (obj == object_none)
{
return object_none;
2006-03-20 17:12:09 +00:00
}
2015-11-27 03:56:13 +00:00
const auto &weapon_info = Weapon_info[weapon_type];
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Do the special Omega Cannon stuff. Then return on account of everything that follows does
// not apply to the Omega Cannon.
2015-12-03 03:26:49 +00:00
if (weapon_type == weapon_id_type::OMEGA_ID) {
2006-03-20 17:12:09 +00:00
// Create orientation matrix for tracking purposes.
2014-10-26 21:37:27 +00:00
vm_vector_2_matrix( obj->orient, direction, &parent->orient.uvec ,nullptr);
2006-03-20 17:12:09 +00:00
2014-08-23 23:53:56 +00:00
if (parent != Viewer && parent->type != OBJ_WEAPON) {
// Muzzle flash
2015-11-27 03:56:13 +00:00
if (weapon_info.flash_vclip > -1 )
object_create_muzzle_flash(vmsegptridx(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip);
2006-03-20 17:12:09 +00:00
}
Allow non-players to use OMEGA_ID weapon User TRUEpiiiicness reports that valptridx<player>::check_index_range traps when "The Apocalyptic Factor"[1] level 14 boss fires its Omega-based weapon. Code inspection shows that this is expected, since the original designers assumed OMEGA_ID would only ever be used by players, and coded various shortcuts accordingly. No one told the level author this. The boss is an interesting concept and not difficult to support, so adjust the code to handle this situation correctly: - Check that the shooter is a player before checking its player ID. Without this, a robot that happened to have the same ID as the player would interpret robot data as player data, likely causing corruption when it tried to update the omega cannon charge. Even if the robot ID does not match, this step causes a diagnostic[2] reporting that a robot ID is being misused as a player ID. - Remove the shortcut that assumes the shooter is a player. Store the shooter's actual type. - Remove the unnecessary and counterproductive path that: 1. Uses the object pointer to get the player's ID 2. Uses that player ID to get a player pointer 3. Uses that player pointer to get an object number 4. Uses that object number to get an object pointer When invariants are maintained, the pointer derived in step 4 is equal to the pointer used at the start of step 1. Use that pointer directly instead of rederiving it. The reported exception was due to step 2, which requires that the player ID is in range. When the shooter is a player, this is true. When the shooter is not a player, it may not be true. [1] http://www.enspiar.com/dmdb/viewMission.php?id=418 ``` sha1sum af.hog af.mn2 133c52fb4b4e5fd40bf7b2321789841b727d1d0b af.hog 0bb5f0dd1803b0fc1aac7c2023eacc97e9ab872b af.mn2 stat -c '%s %Y %n' af.hog af.mn2 4024838 1094445016 af.hog 566 1013524628 af.mn2 ``` [2] `similar/main/laser.cpp:560: BUG: object 0x555556078958 has type 2, expected 4` Reported-by: TRUEpiiiicness <https://forum.dxx-rebirth.com/showthread.php?tid=1038>
2018-01-29 01:56:40 +00:00
do_omega_stuff(vmsegptridx, parent, position, obj);
2006-03-20 17:12:09 +00:00
2014-01-11 17:55:32 +00:00
return obj;
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
2014-08-23 23:53:56 +00:00
if (parent->type == OBJ_PLAYER) {
2015-12-03 03:26:49 +00:00
if (weapon_type == weapon_id_type::FUSION_ID) {
2013-08-27 23:53:03 +00:00
int fusion_scale;
#if defined(DXX_BUILD_DESCENT_I)
if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
fusion_scale = 2;
else
#endif
2013-08-27 23:53:03 +00:00
fusion_scale = 4;
2006-03-20 17:12:09 +00:00
auto &player_info = parent->ctype.player_info;
const auto Fusion_charge = player_info.Fusion_charge;
2006-03-20 17:12:09 +00:00
if (Fusion_charge <= 0)
obj->ctype.laser_info.multiplier = F1_0;
2013-08-27 23:53:03 +00:00
else if (Fusion_charge <= F1_0*fusion_scale)
2006-03-20 17:12:09 +00:00
obj->ctype.laser_info.multiplier = F1_0 + Fusion_charge/2;
else
2013-08-27 23:53:03 +00:00
obj->ctype.laser_info.multiplier = F1_0*fusion_scale;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_I)
// 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.
if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
obj->ctype.laser_info.multiplier /= 2;
#endif
2013-08-27 23:53:03 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
else if (!EMULATING_D1 && is_laser_weapon_type(weapon_type) && (parent->ctype.player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS))
2006-03-20 17:12:09 +00:00
obj->ctype.laser_info.multiplier = F1_0*3/4;
2015-12-03 03:26:49 +00:00
else if (weapon_type == weapon_id_type::GUIDEDMISS_ID) {
if (parent==get_local_player().objnum) {
LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(obj, Player_num);
2006-03-20 17:12:09 +00:00
if (Newdemo_state==ND_STATE_RECORDING)
newdemo_record_guided_start();
}
}
#endif
2006-03-20 17:12:09 +00:00
}
// Make children of smart bomb bounce so if they hit a wall right away, they
// won't detonate. The frame interval code will clear this bit after 1/2 second.
#if defined(DXX_BUILD_DESCENT_I)
2015-12-03 03:26:49 +00:00
if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID))
#elif defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
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))
#endif
2006-03-20 17:12:09 +00:00
obj->mtype.phys_info.flags |= PF_BOUNCE;
if (weapon_info.render == WEAPON_RENDER_POLYMODEL)
{
auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
2006-03-20 17:12:09 +00:00
laser_length = Polygon_models[obj->rtype.pobj_info.model_num].rad * 2;
}
2006-03-20 17:12:09 +00:00
2015-12-03 03:26:49 +00:00
if (weapon_type == weapon_id_type::FLARE_ID)
2006-03-20 17:12:09 +00:00
obj->mtype.phys_info.flags |= PF_STICK; //this obj sticks to walls
const auto Difficulty_level = GameUniqueState.Difficulty_level;
2015-11-27 03:56:13 +00:00
obj->shields = weapon_info.strength[Difficulty_level];
2006-03-20 17:12:09 +00:00
// Fill in laser-specific data
2015-11-27 03:56:13 +00:00
obj->lifeleft = weapon_info.lifetime;
2014-08-23 23:53:56 +00:00
obj->ctype.laser_info.parent_type = parent->type;
obj->ctype.laser_info.parent_signature = parent->signature;
2006-03-20 17:12:09 +00:00
obj->ctype.laser_info.parent_num = parent;
// Assign parent type to highest level creator. This propagates parent type down from
// the original creator through weapons which create children of their own (ie, smart missile)
2014-08-23 23:53:56 +00:00
if (parent->type == OBJ_WEAPON) {
2014-11-20 03:00:41 +00:00
auto highest_parent = parent;
2006-03-20 17:12:09 +00:00
int count;
count = 0;
2014-11-20 03:00:41 +00:00
while ((count++ < 10) && (highest_parent->type == OBJ_WEAPON)) {
const auto next_parent = highest_parent->ctype.laser_info.parent_num;
const auto &&parent_objp = parent.absolute_sibling(next_parent);
if (!laser_parent_is_object(highest_parent->ctype.laser_info, parent_objp))
2006-03-20 17:12:09 +00:00
break; // Probably means parent was killed. Just continue.
if (next_parent == highest_parent) {
Int3(); // Hmm, object is parent of itself. This would seem to be bad, no?
break;
}
highest_parent = parent_objp;
2006-03-20 17:12:09 +00:00
obj->ctype.laser_info.parent_num = highest_parent;
2014-11-20 03:00:41 +00:00
obj->ctype.laser_info.parent_type = highest_parent->type;
obj->ctype.laser_info.parent_signature = highest_parent->signature;
2006-03-20 17:12:09 +00:00
}
}
// Create orientation matrix so we can look from this pov
// Homing missiles also need an orientation matrix so they know if they can make a turn.
if ((weapon_info.homing_flag && (obj->ctype.laser_info.track_goal = object_none, true)) || obj->render_type == RT_POLYOBJ)
2014-10-26 21:37:27 +00:00
vm_vector_2_matrix(obj->orient, direction, &parent->orient.uvec, nullptr);
2006-03-20 17:12:09 +00:00
2014-08-23 23:53:56 +00:00
if (( parent != Viewer ) && (parent->type != OBJ_WEAPON)) {
// Muzzle flash
2015-11-27 03:56:13 +00:00
if (weapon_info.flash_vclip > -1 )
object_create_muzzle_flash(segnum.absolute_sibling(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip);
2006-03-20 17:12:09 +00:00
}
2015-11-27 03:56:13 +00:00
if (weapon_info.flash_sound > -1)
{
if (make_sound != weapon_sound_flag::silent)
{
if (parent == Viewer)
{
2021-06-28 03:37:51 +00:00
// Make your own vulcan gun 1/2 as loud.
digi_play_sample(weapon_info.flash_sound, weapon_type == weapon_id_type::VULCAN_ID ? (F0_5 / 2) : F0_5);
2006-03-20 17:12:09 +00:00
} else {
digi_link_sound_to_pos(weapon_info.flash_sound, segnum.absolute_sibling(obj->segnum), sidenum_t::WLEFT, obj->pos, 0, F0_5);
2006-03-20 17:12:09 +00:00
}
}
}
// Fire the laser from the gun tip so that the back end of the laser bolt is at the gun tip.
// Move 1 frame, so that the end-tip of the laser is touching the gun barrel.
// This also jitters the laser a bit so that it doesn't alias.
// Don't do for weapons created by weapons.
#if defined(DXX_BUILD_DESCENT_I)
if (parent->type != OBJ_WEAPON && weapon_info.render != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID)
#elif defined(DXX_BUILD_DESCENT_II)
if (parent->type == OBJ_PLAYER && weapon_info.render != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID)
#endif
2014-08-23 23:53:56 +00:00
{
const auto end_pos = vm_vec_scale_add(obj->pos, direction, (laser_length/2) );
2018-09-19 02:13:30 +00:00
const auto &&end_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, end_pos, Segments.vmptridx(obj->segnum));
2006-03-20 17:12:09 +00:00
if (end_segnum != obj->segnum) {
if (end_segnum != segment_none) {
2006-03-20 17:12:09 +00:00
obj->pos = end_pos;
2018-03-12 03:43:46 +00:00
obj_relink(vmobjptr, vmsegptr, obj, end_segnum);
}
2006-03-20 17:12:09 +00:00
} else
obj->pos = end_pos;
}
// Here's where to fix the problem with objects which are moving backwards imparting higher velocity to their weaponfire.
// Find out if moving backwards.
if (is_proximity_bomb_or_player_smart_mine(weapon_type)) {
2014-09-28 21:11:03 +00:00
parent_speed = vm_vec_mag_quick(parent->mtype.phys_info.velocity);
2014-09-28 21:11:48 +00:00
if (vm_vec_dot(parent->mtype.phys_info.velocity, parent->orient.fvec) < 0)
2006-03-20 17:12:09 +00:00
parent_speed = -parent_speed;
} else
parent_speed = 0;
2015-11-27 03:56:13 +00:00
weapon_speed = weapon_info.speed[Difficulty_level];
#if defined(DXX_BUILD_DESCENT_II)
2015-11-27 03:56:13 +00:00
if (weapon_info.speedvar != 128)
{
2006-03-20 17:12:09 +00:00
fix randval;
// Get a scale factor between speedvar% and 1.0.
2015-11-27 03:56:13 +00:00
randval = F1_0 - ((d_rand() * weapon_info.speedvar) >> 6);
2006-03-20 17:12:09 +00:00
weapon_speed = fixmul(weapon_speed, randval);
}
#endif
2006-03-20 17:12:09 +00:00
// Ugly hack (too bad we're on a deadline), for homing missiles dropped by smart bomb, start them out slower.
#if defined(DXX_BUILD_DESCENT_I)
2015-12-03 03:26:49 +00:00
if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID)
#elif defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
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)
#endif
2006-03-20 17:12:09 +00:00
weapon_speed /= 4;
2015-11-27 03:56:13 +00:00
if (weapon_info.thrust)
2006-03-20 17:12:09 +00:00
weapon_speed /= 2;
2014-10-26 21:37:27 +00:00
vm_vec_copy_scale(obj->mtype.phys_info.velocity, direction, weapon_speed + parent_speed );
2006-03-20 17:12:09 +00:00
// Set thrust
2015-11-27 03:56:13 +00:00
if (weapon_info.thrust)
{
2006-03-20 17:12:09 +00:00
obj->mtype.phys_info.thrust = obj->mtype.phys_info.velocity;
2015-11-27 03:56:13 +00:00
vm_vec_scale(obj->mtype.phys_info.thrust, fixdiv(weapon_info.thrust, weapon_speed+parent_speed));
2006-03-20 17:12:09 +00:00
}
2015-12-03 03:26:49 +00:00
if (obj->type == OBJ_WEAPON && weapon_type == weapon_id_type::FLARE_ID)
2006-03-20 17:12:09 +00:00
obj->lifeleft += (d_rand()-16384) << 2; // add in -2..2 seconds
2014-01-11 17:55:32 +00:00
return obj;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------------------------------------
// Calls Laser_create_new, but takes care of the segment and point computation for you.
imobjptridx_t Laser_create_new_easy(const d_robot_info_array &Robot_info, const vms_vector &direction, const vms_vector &position, const vmobjptridx_t parent, weapon_id_type weapon_type, const weapon_sound_flag make_sound)
2006-03-20 17:12:09 +00:00
{
fvi_info hit_data;
// Find segment containing laser fire position. If the robot is straddling a segment, the position from
// which it fires may be in a different segment, which is bad news for find_vector_intersection. So, cast
// a ray from the object center (whose segment we know) to the laser position. Then, in the call to Laser_create_new
// use the data returned from this call to find_vector_intersection.
// Note that while find_vector_intersection is pretty slow, it is not terribly slow if the destination point is
// in the same segment as the source point.
const auto fate = find_vector_intersection(fvi_query{
parent->pos,
position,
fvi_query::unused_ignore_obj_list,
&LevelUniqueObjectState,
&Robot_info,
FQ_TRANSWALL, //what about trans walls???
parent,
}, parent->segnum, 0, hit_data);
if (fate != fvi_hit_type::None || hit_data.hit_seg == segment_none)
{
return object_none;
2006-03-20 17:12:09 +00:00
}
return Laser_create_new(direction, hit_data.hit_pnt, vmsegptridx(hit_data.hit_seg), parent, weapon_type, make_sound);
2006-03-20 17:12:09 +00:00
}
}
namespace dcx {
2020-05-02 21:18:42 +00:00
std::array<muzzle_info, MUZZLE_QUEUE_MAX> Muzzle_data;
2006-03-20 17:12:09 +00:00
namespace {
static fix get_weapon_energy_usage_with_difficulty(const weapon_info &wi, const Difficulty_level_type Difficulty_level)
{
const auto energy_usage = wi.energy_usage;
if (Difficulty_level == Difficulty_level_type::_0 || Difficulty_level == Difficulty_level_type::_1)
return fixmul(energy_usage, i2f(underlying_value(Difficulty_level) + 2) / 4);
return energy_usage;
}
}
}
namespace d1x {
namespace {
static fix get_scaled_min_trackable_dot()
{
const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME;
if (curFT <= F1_0 / 16)
return (3 * (F1_0 - HOMING_MIN_TRACKABLE_DOT) / 4 + HOMING_MIN_TRACKABLE_DOT);
else if (curFT < F1_0 / 4)
return (fixmul(F1_0 - HOMING_MIN_TRACKABLE_DOT, F1_0 - 4 * curFT) + HOMING_MIN_TRACKABLE_DOT);
else
return (HOMING_MIN_TRACKABLE_DOT);
}
}
}
namespace dsx {
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------------
// Determine if two objects are on a line of sight. If so, return true, else return false.
// Calls fvi.
int object_to_object_visibility(const vcobjptridx_t obj1, const object_base &obj2, int trans_type)
2006-03-20 17:12:09 +00:00
{
fvi_info hit_data;
switch(const auto fate = find_vector_intersection(fvi_query{
obj1->pos,
obj2.pos,
fvi_query::unused_ignore_obj_list,
fvi_query::unused_LevelUniqueObjectState,
fvi_query::unused_Robot_info,
trans_type,
obj1,
}, obj1->segnum, 0x10, hit_data))
{
case fvi_hit_type::None:
return 1;
case fvi_hit_type::Wall:
return 0;
default:
con_printf(CON_VERBOSE, "object_to_object_visibility: fate=%u for object %hu{%hu/%i,%i,%i} to {%i,%i,%i}", underlying_value(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);
// Int3(); // Contact Mike: Oops, what happened? What is fate?
2006-03-20 17:12:09 +00:00
// 2 = hit object (impossible), 3 = bad starting point (bad)
break;
}
2006-03-20 17:12:09 +00:00
return 0;
}
namespace {
#if defined(DXX_BUILD_DESCENT_II)
static fix get_scaled_min_trackable_dot()
{
if (EMULATING_D1)
return ::d1x::get_scaled_min_trackable_dot();
const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME;
if (curFT <= F1_0/64)
return (HOMING_MIN_TRACKABLE_DOT);
else if (curFT < F1_0/32)
return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - 2*curFT);
else if (curFT < F1_0/4)
return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/16 - curFT);
else
return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/8);
}
#endif
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------------
// Return true if weapon *tracker is able to track object Objects[track_goal], else return false.
// In order for the object to be trackable, it must be within a reasonable turning radius for the missile
// and it must not be obstructed by a wall.
static int object_is_trackable(const imobjptridx_t objp, const vmobjptridx_t tracker, fix *dot)
2006-03-20 17:12:09 +00:00
{
2014-08-23 23:53:56 +00:00
if (objp == object_none)
2006-03-20 17:12:09 +00:00
return 0;
if (Game_mode & GM_MULTI_COOP)
return 0;
// Don't track player if he's cloaked.
if ((objp == get_local_player().objnum) && (objp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
2006-03-20 17:12:09 +00:00
return 0;
#if defined(DXX_BUILD_DESCENT_II)
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
#endif
2006-03-20 17:12:09 +00:00
// Can't track AI object if he's cloaked.
if (objp->type == OBJ_ROBOT) {
if (objp->ctype.ai_info.CLOAKED)
return 0;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Your missiles don't track your escort.
if (Robot_info[get_robot_id(objp)].companion)
2006-03-20 17:12:09 +00:00
if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER)
return 0;
#endif
2006-03-20 17:12:09 +00:00
}
auto vector_to_goal = vm_vec_normalized_quick(vm_vec_sub(objp->pos, tracker->pos));
2014-09-28 21:11:48 +00:00
*dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec);
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
if ((*dot < get_scaled_min_trackable_dot()) && (*dot > F1_0*9/10)) {
vm_vec_normalize(vector_to_goal);
*dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec);
}
#endif
if (*dot >= get_scaled_min_trackable_dot()) {
2006-03-20 17:12:09 +00:00
int rval;
// dot is in legal range, now see if object is visible
rval = object_to_object_visibility(tracker, objp, FQ_TRANSWALL);
return rval;
} else {
return 0;
}
}
// --------------------------------------------------------------------------------------------
static imobjptridx_t call_find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker)
2006-03-20 17:12:09 +00:00
{
if (Game_mode & GM_MULTI) {
if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) {
// It's fired by a player, so if robots present, track robot, else track player.
if (Game_mode & GM_MULTI_COOP)
return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1);
else
return find_homing_object_complete( curpos, tracker, OBJ_PLAYER, OBJ_ROBOT);
} else {
int goal2_type = -1;
#if defined(DXX_BUILD_DESCENT_II)
if (cheats.robotskillrobots)
2006-03-20 17:12:09 +00:00
goal2_type = OBJ_ROBOT;
#endif
2006-03-20 17:12:09 +00:00
Assert(tracker->ctype.laser_info.parent_type == OBJ_ROBOT);
return find_homing_object_complete(curpos, tracker, OBJ_PLAYER, goal2_type);
}
2006-03-20 17:12:09 +00:00
} else
return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1);
}
// --------------------------------------------------------------------------------------------
// Find object to home in on.
// Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance.
static imobjptridx_t find_homing_object(const vms_vector &curpos, const vmobjptridx_t tracker)
2006-03-20 17:12:09 +00:00
{
// Contact Mike: This is a bad and stupid thing. Who called this routine with an illegal laser type??
2015-11-27 03:56:13 +00:00
#ifndef NDEBUG
const auto tracker_id = get_weapon_id(tracker);
#if defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
if (tracker_id != weapon_id_type::OMEGA_ID)
2015-11-27 03:56:13 +00:00
#endif
assert(Weapon_info[tracker_id].homing_flag);
#endif
2006-03-20 17:12:09 +00:00
// Find an object to track based on game mode (eg, whether in network play) and who fired it.
2014-10-26 21:37:27 +00:00
return call_find_homing_object_complete(curpos, tracker);
2006-03-20 17:12:09 +00:00
}
// --------------------------------------------------------------------------------------------
// Find object to home in on.
// Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance.
// Can track two kinds of objects. If you are only interested in one type, set track_obj_type2 to NULL
// Always track proximity bombs. --MK, 06/14/95
// Make homing objects not track parent's prox bombs.
imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vcobjptr = Objects.vcptr;
auto &vmobjptridx = Objects.vmptridx;
2006-03-20 17:12:09 +00:00
fix max_dot = -F1_0*2;
2015-11-27 03:56:13 +00:00
const auto tracker_id = get_weapon_id(tracker);
2014-08-08 03:02:32 +00:00
#if defined(DXX_BUILD_DESCENT_II)
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
2015-12-03 03:26:49 +00:00
if (tracker_id != weapon_id_type::OMEGA_ID)
2006-03-20 17:12:09 +00:00
// Contact Mike: This is a bad and stupid thing. Who called this routine with an illegal laser type??
#endif
2014-08-08 03:02:32 +00:00
{
2015-11-27 03:56:13 +00:00
if (!Weapon_info[tracker_id].homing_flag)
2014-08-08 03:02:32 +00:00
throw std::logic_error("tracking without homing_flag");
}
2006-03-20 17:12:09 +00:00
2014-10-10 02:41:51 +00:00
const fix64 HOMING_MAX_TRACKABLE_DIST = F1_0*250;
const auto max_trackable_dist =
#if defined(DXX_BUILD_DESCENT_II)
(tracker_id == weapon_id_type::OMEGA_ID)
? vm_distance_squared{(OMEGA_MAX_TRACKABLE_DIST * OMEGA_MAX_TRACKABLE_DIST)}
:
#endif
vm_distance_squared{HOMING_MAX_TRACKABLE_DIST * HOMING_MAX_TRACKABLE_DIST};
const auto min_trackable_dot =
#if defined(DXX_BUILD_DESCENT_II)
(tracker_id == weapon_id_type::OMEGA_ID)
? OMEGA_MIN_TRACKABLE_DOT
:
#endif
HOMING_MIN_TRACKABLE_DOT;
2006-03-20 17:12:09 +00:00
imobjptridx_t best_objnum = object_none;
range_for (const auto &&curobjp, vmobjptridx)
2014-10-12 23:05:46 +00:00
{
2006-03-20 17:12:09 +00:00
int is_proximity = 0;
2014-10-10 02:41:51 +00:00
fix dot;
2006-03-20 17:12:09 +00:00
if ((curobjp->type != track_obj_type1) && (curobjp->type != track_obj_type2))
{
#if defined(DXX_BUILD_DESCENT_II)
if ((curobjp->type == OBJ_WEAPON) && (is_proximity_bomb_or_player_smart_mine(get_weapon_id(curobjp)))) {
auto &cur_laser_info = curobjp->ctype.laser_info;
auto &tracker_laser_info = tracker->ctype.laser_info;
if (cur_laser_info.parent_num != tracker_laser_info.parent_num || cur_laser_info.parent_signature != tracker_laser_info.parent_signature)
2006-03-20 17:12:09 +00:00
is_proximity = 1;
else
continue;
} else
#endif
2006-03-20 17:12:09 +00:00
continue;
}
if (curobjp == tracker->ctype.laser_info.parent_num) // Don't track shooter
2006-03-20 17:12:09 +00:00
continue;
// Don't track cloaked players.
if (curobjp->type == OBJ_PLAYER)
{
if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
2006-03-20 17:12:09 +00:00
continue;
// Don't track teammates in team games
2015-07-12 01:04:20 +00:00
if (Game_mode & GM_TEAM)
{
const auto &&objparent = vcobjptr(tracker->ctype.laser_info.parent_num);
if (objparent->type == OBJ_PLAYER && get_team(get_player_id(curobjp)) == get_team(get_player_id(objparent)))
continue;
}
2006-03-20 17:12:09 +00:00
}
// Can't track AI object if he's cloaked.
if (curobjp->type == OBJ_ROBOT) {
if (curobjp->ctype.ai_info.CLOAKED)
continue;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Your missiles don't track your escort.
2015-11-27 03:56:13 +00:00
if (Robot_info[get_robot_id(curobjp)].companion)
2006-03-20 17:12:09 +00:00
if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER)
continue;
#endif
2006-03-20 17:12:09 +00:00
}
2014-10-29 03:24:31 +00:00
auto vec_to_curobj = vm_vec_sub(curobjp->pos, curpos);
2014-10-10 02:41:51 +00:00
auto dist = vm_vec_mag2(vec_to_curobj);
2006-03-20 17:12:09 +00:00
if (dist < max_trackable_dist) {
2014-09-28 21:11:03 +00:00
vm_vec_normalize(vec_to_curobj);
2014-09-28 21:11:48 +00:00
dot = vm_vec_dot(vec_to_curobj, tracker->orient.fvec);
2006-03-20 17:12:09 +00:00
if (is_proximity)
dot = ((dot << 3) + dot) >> 3; // I suspect Watcom would be too stupid to figure out the obvious...
if (dot > min_trackable_dot) {
if (dot > max_dot) {
2014-08-23 23:53:56 +00:00
if (object_to_object_visibility(tracker, curobjp, FQ_TRANSWALL)) {
2006-03-20 17:12:09 +00:00
max_dot = dot;
2014-08-23 23:53:56 +00:00
best_objnum = curobjp;
2006-03-20 17:12:09 +00:00
}
}
}
}
2006-03-20 17:12:09 +00:00
}
return best_objnum;
}
}
#ifdef NEWHOMER
// Similar to calc_d_tick but made just for the homers.
// Causes d_homer_tick_step to be true in intervals dictated by HOMING_TURN_TIME
// and increments d_homer_tick_count accordingly
void calc_d_homer_tick()
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
static fix timer = 0;
auto t = timer + FrameTime;
d_homer_tick_step = t >= HOMING_TURN_TIME;
if (d_homer_tick_step)
{
d_homer_tick_count++;
if (d_homer_tick_count > F1_0)
d_homer_tick_count = 0;
t -= HOMING_TURN_TIME;
// Don't let slowdowns have a lasting impact; allow you to build up at most 3 frames worth
if (t > HOMING_TURN_TIME*3)
t = HOMING_TURN_TIME*3;
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.
}
timer = t;
}
#endif
namespace {
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------------------------------
// See if legal to keep tracking currently tracked object. If not, see if another object is trackable. If not, return -1,
// else return object number of tracking object.
// Computes and returns a fairly precise dot product.
static imobjptridx_t track_track_goal(fvcobjptr &vcobjptr, const imobjptridx_t track_goal, const vmobjptridx_t tracker, fix *dot, fix tick_count)
2006-03-20 17:12:09 +00:00
{
#if defined(DXX_BUILD_DESCENT_I)
if (object_is_trackable(track_goal, tracker, dot))
#elif defined(DXX_BUILD_DESCENT_II)
// Every 8 frames for each object, scan all objects.
if (object_is_trackable(track_goal, tracker, dot) && (((tracker ^ tick_count) % 8) != 0))
#endif
{
return track_goal;
} else if (((tracker ^ tick_count) % 4) == 0)
{
2015-11-15 22:30:41 +00:00
int goal_type, goal2_type;
2006-03-20 17:12:09 +00:00
// If player fired missile, then search for an object, if not, then give up.
2015-11-15 22:30:41 +00:00
if (vcobjptr(tracker->ctype.laser_info.parent_num)->type == OBJ_PLAYER)
{
2006-03-20 17:12:09 +00:00
if (track_goal == object_none)
2006-03-20 17:12:09 +00:00
{
if (Game_mode & GM_MULTI)
{
if (Game_mode & GM_MULTI_COOP)
2015-11-15 22:30:41 +00:00
goal_type = OBJ_ROBOT, goal2_type = -1;
else
{
goal_type = OBJ_PLAYER;
goal2_type = (Game_mode & GM_MULTI_ROBOTS)
? OBJ_ROBOT // Not cooperative, if robots, track either robot or player
: -1; // Not cooperative and no robots, track only a player
}
2006-03-20 17:12:09 +00:00
}
else
2015-11-15 22:30:41 +00:00
goal_type = OBJ_PLAYER, goal2_type = OBJ_ROBOT;
}
else
2006-03-20 17:12:09 +00:00
{
2015-11-15 22:30:41 +00:00
goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type;
2006-03-20 17:12:09 +00:00
if ((goal_type == OBJ_PLAYER) || (goal_type == OBJ_ROBOT))
2015-11-15 22:30:41 +00:00
goal2_type = -1;
2006-03-20 17:12:09 +00:00
else
2014-08-23 23:53:56 +00:00
return object_none;
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
else {
2015-11-15 22:30:41 +00:00
goal2_type = -1;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
if (cheats.robotskillrobots)
2006-03-20 17:12:09 +00:00
goal2_type = OBJ_ROBOT;
#endif
2006-03-20 17:12:09 +00:00
if (track_goal == object_none)
2015-11-15 22:30:41 +00:00
goal_type = OBJ_PLAYER;
2006-03-20 17:12:09 +00:00
else {
2015-11-15 22:30:41 +00:00
goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type;
assert(goal_type != OBJ_GHOST);
2006-03-20 17:12:09 +00:00
}
}
2015-11-15 22:30:41 +00:00
return find_homing_object_complete(tracker->pos, tracker, goal_type, goal2_type);
2006-03-20 17:12:09 +00:00
}
return object_none;
2006-03-20 17:12:09 +00:00
}
//-------------- Initializes a laser after Fire is pressed -----------------
static imobjptridx_t Laser_player_fire_spread_delay(const d_robot_info_array &Robot_info, fvmsegptridx &vmsegptridx, const vmobjptridx_t obj, const weapon_id_type laser_type, const gun_num_t gun_num, const fix spreadr, const fix spreadu, const fix delay_time, const weapon_sound_flag make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track)
2006-03-20 17:12:09 +00:00
{
2014-10-30 03:11:06 +00:00
vms_vector LaserDir;
2006-03-20 17:12:09 +00:00
fvi_info hit_data;
2014-11-04 01:31:22 +00:00
vms_vector *pnt;
2006-03-20 17:12:09 +00:00
// Find the initial position of the laser
pnt = &Player_ship->gun_points[gun_num];
vms_matrix m = vm_transposed_matrix(obj->orient);
2014-11-04 01:31:22 +00:00
const auto gun_point = vm_vec_rotate(*pnt,m);
2006-03-20 17:12:09 +00:00
2014-10-30 03:11:06 +00:00
auto LaserPos = vm_vec_add(obj->pos,gun_point);
2006-03-20 17:12:09 +00:00
// If supposed to fire at a delayed time (delay_time), then move this point backwards.
if (delay_time)
vm_vec_scale_add2(LaserPos, shot_orientation, -fixmul(delay_time, Weapon_info[laser_type].speed[GameUniqueState.Difficulty_level]));
2006-03-20 17:12:09 +00:00
// do_muzzle_stuff(obj, &Pos);
//--------------- Find LaserPos and LaserSeg ------------------
const auto Fate = find_vector_intersection(fvi_query{
obj->pos,
LaserPos,
fvi_query::unused_ignore_obj_list,
&LevelUniqueObjectState,
&Robot_info,
#if defined(DXX_BUILD_DESCENT_I)
0,
#elif defined(DXX_BUILD_DESCENT_II)
FQ_IGNORE_POWERUPS,
#endif
obj,
}, obj->segnum, 0x10, hit_data);
2006-03-20 17:12:09 +00:00
2014-11-20 03:00:36 +00:00
auto LaserSeg = hit_data.hit_seg;
2006-03-20 17:12:09 +00:00
if (LaserSeg == segment_none) //some sort of annoying error
return object_none;
2006-03-20 17:12:09 +00:00
//SORT OF HACK... IF ABOVE WAS CORRECT THIS WOULDNT BE NECESSARY.
2014-10-01 02:28:41 +00:00
if ( vm_vec_dist_quick(LaserPos, obj->pos) > 0x50000 )
return object_none;
if (Fate == fvi_hit_type::Wall)
return object_none;
2006-03-20 17:12:09 +00:00
if (Fate == fvi_hit_type::Object)
{
2006-03-20 17:12:09 +00:00
// if ( Objects[hit_data.hit_object].type == OBJ_ROBOT )
// Objects[hit_data.hit_object].flags |= OF_SHOULD_BE_DEAD;
// if ( Objects[hit_data.hit_object].type != OBJ_POWERUP )
// return;
2006-03-20 17:12:09 +00:00
//as of 12/6/94, we don't care if the laser is stuck in an object. We
//just fire away normally
}
// Now, make laser spread out.
LaserDir = shot_orientation;
2006-03-20 17:12:09 +00:00
if ((spreadr != 0) || (spreadu != 0)) {
2014-09-28 21:43:14 +00:00
vm_vec_scale_add2(LaserDir, obj->orient.rvec, spreadr);
vm_vec_scale_add2(LaserDir, obj->orient.uvec, spreadu);
2006-03-20 17:12:09 +00:00
}
const auto &&objnum = Laser_create_new(LaserDir, LaserPos, vmsegptridx(LaserSeg), obj, laser_type, make_sound);
2006-03-20 17:12:09 +00:00
if (objnum == object_none)
return object_none;
2013-08-27 23:53:03 +00:00
#if defined(DXX_BUILD_DESCENT_II)
create_awareness_event(obj, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState);
2006-03-20 17:12:09 +00:00
// Omega cannon is a hack, not surprisingly. Don't want to do the rest of this stuff.
2015-12-03 03:26:49 +00:00
if (laser_type == weapon_id_type::OMEGA_ID)
return objnum;
2006-03-20 17:12:09 +00:00
2015-12-03 03:26:49 +00:00
if (laser_type == weapon_id_type::GUIDEDMISS_ID && Multi_is_guided) {
LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(objnum, get_player_id(obj));
2006-03-20 17:12:09 +00:00
}
Multi_is_guided=0;
2015-12-03 03:26:49 +00:00
if (laser_type == weapon_id_type::CONCUSSION_ID ||
laser_type == weapon_id_type::HOMING_ID ||
laser_type == weapon_id_type::SMART_ID ||
laser_type == weapon_id_type::MEGA_ID ||
laser_type == weapon_id_type::FLASH_ID ||
2006-03-20 17:12:09 +00:00
//laser_type == GUIDEDMISS_ID ||
//laser_type == SUPERPROX_ID ||
2015-12-03 03:26:49 +00:00
laser_type == weapon_id_type::MERCURY_ID ||
laser_type == weapon_id_type::EARTHSHAKER_ID)
2015-02-14 22:48:28 +00:00
{
const auto need_new_missile_viewer = [obj]{
if (!Missile_viewer)
return true;
if (Missile_viewer->type != OBJ_WEAPON)
return true;
if (Missile_viewer->signature != Missile_viewer_sig)
return true;
2015-11-27 03:56:13 +00:00
if (get_player_id(obj) == Player_num && Missile_viewer->ctype.laser_info.parent_num != get_local_player().objnum)
2015-02-14 22:48:28 +00:00
/* New missile fired-by local player &&
* currently viewing missile not-fired-by local player
*/
return true;
return false;
};
const auto can_view_missile = [obj]{
2015-11-27 03:56:13 +00:00
const auto obj_id = get_player_id(obj);
if (obj_id == Player_num)
2015-02-14 22:48:28 +00:00
return true;
if (PlayerCfg.MissileViewEnabled != MissileViewMode::EnabledSelfAndAllies)
return false;
{
if (Game_mode & GM_MULTI_COOP)
return true;
if (Game_mode & GM_TEAM)
2015-11-27 03:56:13 +00:00
return get_team(Player_num) == get_team(obj_id);
}
2015-02-14 22:48:28 +00:00
return false;
};
if (need_new_missile_viewer() && can_view_missile())
{
2014-08-23 23:53:56 +00:00
Missile_viewer = objnum;
2015-02-14 22:48:28 +00:00
Missile_viewer_sig = objnum->signature;
}
}
#endif
2006-03-20 17:12:09 +00:00
// If this weapon is supposed to be silent, set that bit!
if (make_sound == weapon_sound_flag::silent)
2014-08-23 23:53:56 +00:00
objnum->flags |= OF_SILENT;
2006-03-20 17:12:09 +00:00
// If the object firing the laser is the player, then indicate the laser object so robots can dodge.
// New by MK on 6/8/95, don't let robots evade proximity bombs, thereby decreasing uselessness of bombs.
2013-08-27 23:53:03 +00:00
if (obj == ConsoleObject)
#if defined(DXX_BUILD_DESCENT_II)
if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(objnum)))
#endif
2006-03-20 17:12:09 +00:00
Player_fired_laser_this_frame = objnum;
if (Weapon_info[laser_type].homing_flag) {
if (obj == ConsoleObject)
{
2014-10-26 21:37:27 +00:00
objnum->ctype.laser_info.track_goal = find_homing_object(LaserPos, objnum);
2006-03-20 17:12:09 +00:00
}
else // Some other player shot the homing thing
{
Assert(Game_mode & GM_MULTI);
2014-08-23 23:53:56 +00:00
objnum->ctype.laser_info.track_goal = Network_laser_track;
2006-03-20 17:12:09 +00:00
}
}
return objnum;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------------------------------------
static imobjptridx_t Laser_player_fire_spread(const d_robot_info_array &Robot_info, const vmobjptridx_t obj, const weapon_id_type laser_type, const gun_num_t gun_num, const fix spreadr, const fix spreadu, const weapon_sound_flag make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track)
2006-03-20 17:12:09 +00:00
{
return Laser_player_fire_spread_delay(Robot_info, vmsegptridx, obj, laser_type, gun_num, spreadr, spreadu, 0, make_sound, shot_orientation, Network_laser_track);
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------------
imobjptridx_t Laser_player_fire(const d_robot_info_array &Robot_info, const vmobjptridx_t obj, const weapon_id_type laser_type, const gun_num_t gun_num, const weapon_sound_flag make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track)
2006-03-20 17:12:09 +00:00
{
return Laser_player_fire_spread(Robot_info, obj, laser_type, gun_num, 0, 0, make_sound, shot_orientation, Network_laser_track);
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------------------------------------
void Flare_create(const vmobjptridx_t obj)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
2006-03-20 17:12:09 +00:00
const auto energy_usage = get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_id_type::FLARE_ID], GameUniqueState.Difficulty_level);
2006-03-20 17:12:09 +00:00
// MK, 11/04/95: Allowed to fire flare even if no energy.
2013-08-27 23:53:03 +00:00
// -- if (Players[Player_num].energy >= energy_usage)
auto &player_info = get_local_plrobj().ctype.player_info;
auto &energy = player_info.energy;
#if defined(DXX_BUILD_DESCENT_I)
2016-07-03 00:54:16 +00:00
if (energy > 0)
#endif
2013-08-27 23:53:03 +00:00
{
const auto &&flare = Laser_player_fire(LevelSharedRobotInfoState.Robot_info, obj, weapon_id_type::FLARE_ID, gun_num_t::center, weapon_sound_flag::audible, get_local_plrobj().orient.fvec, object_none);
if (flare == object_none)
return;
2016-07-03 00:54:16 +00:00
energy -= energy_usage;
2006-03-20 17:12:09 +00:00
2016-07-03 00:54:16 +00:00
if (energy <= 0)
{
energy = 0;
#if defined(DXX_BUILD_DESCENT_I)
auto_select_primary_weapon(player_info);
#endif
2006-03-20 17:12:09 +00:00
}
if (Game_mode & GM_MULTI)
multi_send_fire(FLARE_ADJUST, laser_level::_1 /* unused */, 0, object_none, object_none);
2013-08-27 23:53:03 +00:00
}
2006-03-20 17:12:09 +00:00
}
namespace {
#if defined(DXX_BUILD_DESCENT_I)
#define HOMING_MISSILE_SCALE 8
#elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
#define HOMING_MISSILE_SCALE 16
static bool is_active_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const vcobjptridx_t obj)
{
if (obj->ctype.laser_info.parent_type != OBJ_PLAYER)
return false;
auto &vcobjptr = LevelUniqueObjectState.get_objects().vcptr;
auto &parent_obj = *vcobjptr(obj->ctype.laser_info.parent_num);
if (parent_obj.type != OBJ_PLAYER)
return false;
const auto pnum = get_player_id(parent_obj);
return LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(pnum) == obj;
}
#endif
2006-03-20 17:12:09 +00:00
//--------------------------------------------------------------------
// Set object *objp's orientation to (or towards if I'm ambitious) its velocity.
static void homing_missile_turn_towards_velocity(object_base &objp, const vms_vector &norm_vel, fix ft)
2006-03-20 17:12:09 +00:00
{
2014-10-26 21:37:27 +00:00
auto new_fvec = norm_vel;
vm_vec_scale(new_fvec, ft * HOMING_MISSILE_SCALE);
vm_vec_add2(new_fvec, objp.orient.fvec);
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick(new_fvec);
2006-03-20 17:12:09 +00:00
// if ((norm_vel->x == 0) && (norm_vel->y == 0) && (norm_vel->z == 0))
// return;
vm_vector_2_matrix(objp.orient, new_fvec, nullptr, nullptr);
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
//-------------------------------------------------------------------------------------------
//sequence this laser object for this _frame_ (underscores added here to aid MK in his searching!)
void Laser_do_weapon_sequence(const d_robot_info_array &Robot_info, const vmobjptridx_t obj)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &imobjptridx = Objects.imptridx;
auto &vcobjptr = Objects.vcptr;
auto &vmobjptr = Objects.vmptr;
assert(obj->control_source == object::control_type::weapon);
2006-03-20 17:12:09 +00:00
if (obj->lifeleft < 0 ) { // We died of old age
obj->flags |= OF_SHOULD_BE_DEAD;
if ( Weapon_info[get_weapon_id(obj)].damage_radius )
explode_badass_weapon(Robot_info, obj, obj->pos);
2006-03-20 17:12:09 +00:00
return;
}
#if defined(DXX_BUILD_DESCENT_II)
if (omega_cleanup(vcobjptr, obj))
return;
#endif
2006-03-20 17:12:09 +00:00
//delete weapons that are not moving
const auto Difficulty_level = GameUniqueState.Difficulty_level;
2020-08-10 03:45:13 +00:00
if ( !((d_tick_count ^ static_cast<uint16_t>(obj->signature)) & 3) &&
2015-12-03 03:26:49 +00:00
(get_weapon_id(obj) != weapon_id_type::FLARE_ID) &&
(Weapon_info[get_weapon_id(obj)].speed[Difficulty_level] > 0) &&
2014-09-28 21:11:03 +00:00
(vm_vec_mag_quick(obj->mtype.phys_info.velocity) < F2_0)) {
obj_delete(LevelUniqueObjectState, Segments, obj);
2006-03-20 17:12:09 +00:00
return;
}
2015-12-03 03:26:49 +00:00
if ( get_weapon_id(obj) == weapon_id_type::FUSION_ID ) { //always set fusion weapon to max vel
2006-03-20 17:12:09 +00:00
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick(obj->mtype.phys_info.velocity);
2006-03-20 17:12:09 +00:00
2014-09-28 21:11:05 +00:00
vm_vec_scale(obj->mtype.phys_info.velocity, Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]);
2006-03-20 17:12:09 +00:00
}
// For homing missiles, turn towards target. (unless it's the guided missile)
#if defined(DXX_BUILD_DESCENT_I)
if (Weapon_info[get_weapon_id(obj)].homing_flag)
#elif defined(DXX_BUILD_DESCENT_II)
if (Weapon_info[get_weapon_id(obj)].homing_flag && !is_active_guided_missile(LevelUniqueObjectState, obj))
#endif
{
2006-03-20 17:12:09 +00:00
vms_vector vector_to_object, temp_vec;
fix dot=F1_0;
fix speed, max_speed;
// For first 125ms of life, missile flies straight.
if (obj->ctype.laser_info.creation_time + HOMING_FLY_STRAIGHT_TIME < GameTime64)
{
2006-03-20 17:12:09 +00:00
// If it's time to do tracking, then it's time to grow up, stop bouncing and start exploding!.
#if defined(DXX_BUILD_DESCENT_I)
2015-12-03 03:26:49 +00:00
if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID))
#elif defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
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))
#endif
{
2006-03-20 17:12:09 +00:00
obj->mtype.phys_info.flags &= ~PF_BOUNCE;
}
#ifdef NEWHOMER
if (d_homer_tick_step)
{
const auto &&track_goal = track_track_goal(vcobjptr, imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_homer_tick_count);
if (track_goal == get_local_player().objnum) {
fix dist_to_player;
dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos);
auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist;
if (dist_to_player < homing_object_dist || homing_object_dist < 0)
homing_object_dist = dist_to_player;
}
if (track_goal != object_none)
{
vm_vec_sub(vector_to_object, track_goal->pos, obj->pos);
vm_vec_normalize_quick(vector_to_object);
temp_vec = obj->mtype.phys_info.velocity;
speed = vm_vec_normalize_quick(temp_vec);
max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level];
if (speed+F1_0 < max_speed) {
speed += fixmul(max_speed, HOMING_TURN_TIME/2);
if (speed > max_speed)
speed = max_speed;
}
#if defined(DXX_BUILD_DESCENT_I)
dot = vm_vec_dot(temp_vec, vector_to_object);
#endif
vm_vec_add2(temp_vec, vector_to_object);
// The boss' smart children track better...
if (Weapon_info[get_weapon_id(obj)].render != WEAPON_RENDER_POLYMODEL)
vm_vec_add2(temp_vec, vector_to_object);
vm_vec_normalize_quick(temp_vec);
#if defined(DXX_BUILD_DESCENT_I)
vm_vec_scale(temp_vec, speed);
obj->mtype.phys_info.velocity = temp_vec;
#elif defined(DXX_BUILD_DESCENT_II)
obj->mtype.phys_info.velocity = temp_vec;
vm_vec_scale(obj->mtype.phys_info.velocity, speed);
#endif
// Subtract off life proportional to amount turned.
// For hardest turn, it will lose 2 seconds per second.
{
fix lifelost, absdot;
absdot = abs(F1_0 - dot);
#if defined(DXX_BUILD_DESCENT_I)
if (absdot > F1_0/8) {
if (absdot > F1_0/4)
absdot = F1_0/4;
lifelost = fixmul(absdot*16, HOMING_TURN_TIME);
obj->lifeleft -= lifelost;
}
#elif defined(DXX_BUILD_DESCENT_II)
lifelost = fixmul(absdot*32, HOMING_TURN_TIME);
obj->lifeleft -= lifelost;
#endif
}
// Only polygon objects have visible orientation, so only they should turn.
if (Weapon_info[get_weapon_id(obj)].render == WEAPON_RENDER_POLYMODEL)
homing_missile_turn_towards_velocity(obj, temp_vec, HOMING_TURN_TIME); // temp_vec is normalized velocity.
}
}
#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.
2006-03-20 17:12:09 +00:00
// Make sure the object we are tracking is still trackable.
const auto &&track_goal = track_track_goalvcobjptr, (imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_tick_count);
2006-03-20 17:12:09 +00:00
if (track_goal == get_local_player().objnum) {
2006-03-20 17:12:09 +00:00
fix dist_to_player;
2014-10-01 02:28:41 +00:00
dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos);
auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist;
if (dist_to_player < homing_object_dist || homing_object_dist < 0)
homing_object_dist = dist_to_player;
2006-03-20 17:12:09 +00:00
}
if (track_goal != object_none)
{
vm_vec_sub(vector_to_object, track_goal->pos, obj->pos);
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick(vector_to_object);
temp_vec = obj->mtype.phys_info.velocity;
2014-09-28 21:11:04 +00:00
speed = vm_vec_normalize_quick(temp_vec);
max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level];
if (speed+F1_0 < max_speed) {
speed += fixmul(max_speed, FrameTime/2);
if (speed > max_speed)
speed = max_speed;
}
#if defined(DXX_BUILD_DESCENT_I)
2014-09-28 21:11:48 +00:00
dot = vm_vec_dot(temp_vec, vector_to_object);
#endif
2014-09-28 21:43:00 +00:00
vm_vec_add2(temp_vec, vector_to_object);
// The boss' smart children track better...
if (Weapon_info[get_weapon_id(obj)].render != WEAPON_RENDER_POLYMODEL)
2014-09-28 21:43:00 +00:00
vm_vec_add2(temp_vec, vector_to_object);
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick(temp_vec);
#if defined(DXX_BUILD_DESCENT_I)
2014-09-28 21:11:05 +00:00
vm_vec_scale(temp_vec, speed);
obj->mtype.phys_info.velocity = temp_vec;
#elif defined(DXX_BUILD_DESCENT_II)
obj->mtype.phys_info.velocity = temp_vec;
vm_vec_scale(obj->mtype.phys_info.velocity, speed);
#endif
// Subtract off life proportional to amount turned.
// For hardest turn, it will lose 2 seconds per second.
{
fix lifelost, absdot;
absdot = abs(F1_0 - dot);
#if defined(DXX_BUILD_DESCENT_I)
if (absdot > F1_0/8) {
if (absdot > F1_0/4)
absdot = F1_0/4;
lifelost = fixmul(absdot*16, FrameTime);
obj->lifeleft -= lifelost;
}
#elif defined(DXX_BUILD_DESCENT_II)
lifelost = fixmul(absdot*32, FrameTime);
obj->lifeleft -= lifelost;
#endif
2006-03-20 17:12:09 +00:00
}
// Only polygon objects have visible orientation, so only they should turn.
if (Weapon_info[get_weapon_id(obj)].render == WEAPON_RENDER_POLYMODEL)
homing_missile_turn_towards_velocity(obj, temp_vec, FrameTime); // temp_vec is normalized velocity.
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
}
}
// Make sure weapon is not moving faster than allowed speed.
2015-11-27 03:56:13 +00:00
const auto &weapon_info = Weapon_info[get_weapon_id(obj)];
#if defined(DXX_BUILD_DESCENT_I)
2015-11-27 03:56:13 +00:00
if (weapon_info.thrust != 0)
#endif
2006-03-20 17:12:09 +00:00
{
fix weapon_speed;
2014-09-28 21:11:03 +00:00
weapon_speed = vm_vec_mag_quick(obj->mtype.phys_info.velocity);
2015-11-27 03:56:13 +00:00
if (weapon_speed > weapon_info.speed[Difficulty_level]) {
2006-03-20 17:12:09 +00:00
// Only slow down if not allowed to move. Makes sense, huh? Allows proxbombs to get moved by physics force. --MK, 2/13/96
#if defined(DXX_BUILD_DESCENT_II)
2015-11-27 03:56:13 +00:00
if (weapon_info.speed[Difficulty_level])
#endif
2013-08-27 23:53:03 +00:00
{
2006-03-20 17:12:09 +00:00
fix scale_factor;
scale_factor = fixdiv(Weapon_info[get_weapon_id(obj)].speed[Difficulty_level], weapon_speed);
2014-09-28 21:11:05 +00:00
vm_vec_scale(obj->mtype.phys_info.velocity, scale_factor);
2006-03-20 17:12:09 +00:00
}
}
}
}
}
namespace dcx {
namespace {
constexpr std::integral_constant<unsigned, 30> MAX_OBJDISTS{};
2013-09-21 21:29:58 +00:00
static inline int sufficient_energy(int energy_used, fix energy)
{
return !energy_used || (energy >= energy_used);
}
static inline int sufficient_ammo(int ammo_used, int uses_vulcan_ammo, ushort vulcan_ammo)
{
return !ammo_used || (!uses_vulcan_ammo || vulcan_ammo >= ammo_used);
}
}
}
namespace dsx {
2006-03-20 17:12:09 +00:00
// --------------------------------------------------------------------------------------------------
// Assumption: This is only called by the actual console player, not for network players
2017-02-08 23:34:41 +00:00
void do_laser_firing_player(object &plrobj)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
2013-09-21 21:29:58 +00:00
int ammo_used;
2006-03-20 17:12:09 +00:00
int rval = 0;
if (Player_dead_state != player_dead_state::no)
2017-02-08 23:34:41 +00:00
return;
2006-03-20 17:12:09 +00:00
2016-08-28 22:41:49 +00:00
auto &player_info = plrobj.ctype.player_info;
const auto Primary_weapon = player_info.Primary_weapon;
const auto weapon_index = Primary_weapon_to_weapon_info[Primary_weapon];
2006-03-20 17:12:09 +00:00
2013-08-27 23:53:03 +00:00
ammo_used = Weapon_info[weapon_index].ammo_usage;
const auto uses_vulcan_ammo = weapon_index_uses_vulcan_ammo(Primary_weapon);
auto &pl_energy = player_info.energy;
const auto base_energy_used =
#if defined(DXX_BUILD_DESCENT_II)
(Primary_weapon == primary_weapon_index_t::OMEGA_INDEX)
? 0 // Omega consumes energy when recharging, not when firing.
:
#endif
get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_index], GameUniqueState.Difficulty_level);
const auto energy_used =
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// MK, 01/26/96, Helix use 2x energy in multiplayer. bitmaps.tbl parm should have been reduced for single player.
(weapon_index == weapon_id_type::HELIX_ID && (Game_mode & GM_MULTI))
? base_energy_used * 2
:
#endif
base_energy_used;
2006-03-20 17:12:09 +00:00
if (!(sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo)))
auto_select_primary_weapon(player_info); // Make sure the player can fire from this weapon.
2006-03-20 17:12:09 +00:00
auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
while (Next_laser_fire_time <= GameTime64) {
if (sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo)) {
int flags, fire_frame_overhead = 0;
if (GameTime64 - Next_laser_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that.
fire_frame_overhead = GameTime64 - Next_laser_fire_time;
auto laser_level = player_info.laser_level;
2006-03-20 17:12:09 +00:00
flags = 0;
2015-12-22 04:18:52 +00:00
switch (Primary_weapon)
{
case primary_weapon_index_t::LASER_INDEX:
if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
flags |= LASER_QUAD;
break;
case primary_weapon_index_t::SPREADFIRE_INDEX:
flags |= (player_info.Spreadfire_toggle ^= LASER_SPREADFIRE_TOGGLED) & LASER_SPREADFIRE_TOGGLED;
2015-12-22 04:18:52 +00:00
break;
case primary_weapon_index_t::VULCAN_INDEX:
case primary_weapon_index_t::PLASMA_INDEX:
case primary_weapon_index_t::FUSION_INDEX:
default:
break;
#if defined(DXX_BUILD_DESCENT_II)
2015-12-22 04:18:52 +00:00
case primary_weapon_index_t::HELIX_INDEX:
flags |= (player_info.Helix_orientation++ & LASER_HELIX_MASK);
2015-12-22 04:18:52 +00:00
break;
case primary_weapon_index_t::SUPER_LASER_INDEX:
case primary_weapon_index_t::GAUSS_INDEX:
case primary_weapon_index_t::PHOENIX_INDEX:
case primary_weapon_index_t::OMEGA_INDEX:
break;
#endif
2015-12-22 04:18:52 +00:00
}
2006-03-20 17:12:09 +00:00
const auto shot_fired = do_laser_firing(vmobjptridx(get_local_player().objnum), Primary_weapon, laser_level, flags, plrobj.orient.fvec, object_none);
rval += shot_fired;
if (!shot_fired)
break;
Next_laser_fire_time = GameTime64 - fire_frame_overhead + (unlikely(cheats.rapidfire)
? (F1_0 / 25)
: (
#if defined(DXX_BUILD_DESCENT_II)
weapon_index == weapon_id_type::OMEGA_ID
? OMEGA_BASE_TIME
:
#endif
Weapon_info[weapon_index].fire_wait
)
);
2006-03-20 17:12:09 +00:00
pl_energy -= (energy_used * rval) / Weapon_info[weapon_index].fire_count;
if (pl_energy < 0)
pl_energy = 0;
2006-03-20 17:12:09 +00:00
2013-09-21 21:29:58 +00:00
if (uses_vulcan_ammo) {
auto &v = player_info.vulcan_ammo;
if (v < ammo_used)
v = 0;
2006-03-20 17:12:09 +00:00
else
v -= ammo_used;
maybe_drop_net_powerup(POW_VULCAN_AMMO, 1, 0);
2006-03-20 17:12:09 +00:00
}
} else {
#if defined(DXX_BUILD_DESCENT_II)
Next_laser_fire_time = GameTime64; // Prevents shots-to-fire from building up.
#endif
2006-03-20 17:12:09 +00:00
break; // Couldn't fire weapon, so abort.
}
}
auto_select_primary_weapon(player_info); // Make sure the player can fire from this weapon.
2006-03-20 17:12:09 +00:00
}
// --------------------------------------------------------------------------------------------------
// Object "objnum" fires weapon "weapon_num" of level "level". (Right now (9/24/94) level is used only for type 0 laser.
// Flags are the player flags. For network mode, set to 0.
// It is assumed that this is a player object (as in multiplayer), and therefore the gun positions are known.
// Returns whether the weapon was fired. This is 1 on success, 0 on failure.
// More than one shot is fired with a pseudo-delay so that players on slow machines can fire (for themselves
2006-03-20 17:12:09 +00:00
// or other players) often enough for things like the vulcan cannon.
int do_laser_firing(vmobjptridx_t objp, int weapon_num, const laser_level level, int flags, vms_vector shot_orientation, const icobjidx_t Network_laser_track)
2006-03-20 17:12:09 +00:00
{
switch (weapon_num) {
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::LASER_INDEX: {
2015-12-03 03:26:48 +00:00
weapon_id_type weapon_type;
2006-03-20 17:12:09 +00:00
2013-10-09 01:54:12 +00:00
switch(level)
{
case laser_level::_1:
2015-12-03 03:26:49 +00:00
weapon_type = weapon_id_type::LASER_ID_L1;
2013-10-09 01:54:12 +00:00
break;
case laser_level::_2:
2015-12-03 03:26:49 +00:00
weapon_type = weapon_id_type::LASER_ID_L2;
2013-10-09 01:54:12 +00:00
break;
case laser_level::_3:
2015-12-03 03:26:49 +00:00
weapon_type = weapon_id_type::LASER_ID_L3;
2013-10-09 01:54:12 +00:00
break;
case laser_level::_4:
2015-12-03 03:26:49 +00:00
weapon_type = weapon_id_type::LASER_ID_L4;
2013-10-09 01:54:12 +00:00
break;
#if defined(DXX_BUILD_DESCENT_II)
case laser_level::_5:
2015-12-03 03:26:49 +00:00
weapon_type = weapon_id_type::LASER_ID_L5;
2013-10-09 01:54:12 +00:00
break;
case laser_level::_6:
2015-12-03 03:26:49 +00:00
weapon_type = weapon_id_type::LASER_ID_L6;
2013-10-09 01:54:12 +00:00
break;
#endif
2013-10-09 01:54:12 +00:00
default:
/* Invalid laser level. Cancel the shot. */
return 0;
2013-10-09 01:54:12 +00:00
}
if (Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_type, gun_num_t::_0, weapon_sound_flag::audible, shot_orientation, object_none) == object_none)
/* If the first one fails, assume all will fail. Tell the
* caller that no shots were fired, so that the player is not
* charged for the shot.
*/
return 0;
Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_type, gun_num_t::_1, weapon_sound_flag::silent, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
if (flags & LASER_QUAD) {
// hideous system to make quad laser 1.5x powerful as normal laser, make every other quad laser bolt harmless
Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_type, gun_num_t::_2, weapon_sound_flag::silent, shot_orientation, object_none);
Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_type, gun_num_t::_3, weapon_sound_flag::silent, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
}
break;
}
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::VULCAN_INDEX: {
if (Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::VULCAN_ID, gun_num_t::center, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, weapon_sound_flag::audible, shot_orientation, object_none) == object_none)
return 0;
2006-03-20 17:12:09 +00:00
break;
}
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::SPREADFIRE_INDEX:
{
fix spreadr0, spreadu0, spreadr1, spreadu1;
if (flags & LASER_SPREADFIRE_TOGGLED)
spreadr0 = F1_0 / 16, spreadr1 = -F1_0 / 16, spreadu0 = spreadu1 = 0;
else
spreadu0 = F1_0 / 16, spreadu1 = -F1_0 / 16, spreadr0 = spreadr1 = 0;
if (Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::SPREADFIRE_ID, gun_num_t::center, spreadr0, spreadu0, weapon_sound_flag::silent, shot_orientation, object_none) == object_none)
return 0;
Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::SPREADFIRE_ID, gun_num_t::center, spreadr1, spreadu1, weapon_sound_flag::silent, shot_orientation, object_none);
Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::SPREADFIRE_ID, gun_num_t::center, 0, 0, weapon_sound_flag::audible, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
}
break;
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::PLASMA_INDEX:
if (Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::PLASMA_ID, gun_num_t::_0, weapon_sound_flag::audible, shot_orientation, object_none) == object_none)
return 0;
Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::PLASMA_ID, gun_num_t::_1, weapon_sound_flag::silent, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
break;
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::FUSION_INDEX: {
2006-03-20 17:12:09 +00:00
vms_vector force_vec;
auto &&weapon_obj = Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::FUSION_ID, gun_num_t::_0, weapon_sound_flag::audible, shot_orientation, object_none);
Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::FUSION_ID, gun_num_t::_1, weapon_sound_flag::audible, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
assert(objp->type == OBJ_PLAYER);
auto &Fusion_charge = objp->ctype.player_info.Fusion_charge;
2016-09-04 00:02:53 +00:00
flags = static_cast<int8_t>(Fusion_charge >> 12);
2006-03-20 17:12:09 +00:00
Fusion_charge = 0;
if (weapon_obj == object_none)
/* Clear the fusion charge even if the shot fails. This is bad
* for the player, but it is safer than leaving Fusion_charge
* set while the cannon is not charging.
*/
return 0;
2006-03-20 17:12:09 +00:00
force_vec.x = -(objp->orient.fvec.x << 7);
force_vec.y = -(objp->orient.fvec.y << 7);
force_vec.z = -(objp->orient.fvec.z << 7);
phys_apply_force(objp, force_vec);
2006-03-20 17:12:09 +00:00
force_vec.x = (force_vec.x >> 4) + d_rand() - 16384;
force_vec.y = (force_vec.y >> 4) + d_rand() - 16384;
force_vec.z = (force_vec.z >> 4) + d_rand() - 16384;
phys_apply_rot(objp, force_vec);
2006-03-20 17:12:09 +00:00
}
break;
#if defined(DXX_BUILD_DESCENT_II)
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::GAUSS_INDEX: {
if (Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::GAUSS_ID, gun_num_t::center, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, weapon_sound_flag::audible, shot_orientation, object_none) == object_none)
return 0;
2006-03-20 17:12:09 +00:00
break;
}
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::HELIX_INDEX: {
2006-03-20 17:12:09 +00:00
fix spreadr,spreadu;
2017-11-01 02:01:21 +00:00
static const std::array<std::pair<fix, fix>, 8> spread{{
{ F1_0/16, 0}, // Vertical
{ F1_0/17, F1_0/42}, // 22.5 degrees
{ F1_0/22, F1_0/22}, // 45 degrees
{ F1_0/42, F1_0/17}, // 67.5 degrees
{ 0, F1_0/16}, // 90 degrees
{-F1_0/42, F1_0/17}, // 112.5 degrees
{-F1_0/22, F1_0/22}, // 135 degrees
{-F1_0/17, F1_0/42}, // 157.5 degrees
}};
const unsigned helix_orient = flags & LASER_HELIX_MASK;
if (helix_orient < spread.size())
{
auto &s = spread[helix_orient];
spreadr = s.first;
spreadu = s.second;
2006-03-20 17:12:09 +00:00
}
2017-11-01 02:01:21 +00:00
else
break;
if (Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::HELIX_ID, gun_num_t::center, 0, 0, weapon_sound_flag::audible, shot_orientation, object_none) == object_none)
return 0;
Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::HELIX_ID, gun_num_t::center, spreadr, spreadu, weapon_sound_flag::silent, shot_orientation, object_none);
Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::HELIX_ID, gun_num_t::center, -spreadr, -spreadu, weapon_sound_flag::silent, shot_orientation, object_none);
Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::HELIX_ID, gun_num_t::center, spreadr*2, spreadu*2, weapon_sound_flag::silent, shot_orientation, object_none);
Laser_player_fire_spread(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::HELIX_ID, gun_num_t::center, -spreadr*2, -spreadu*2, weapon_sound_flag::silent, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
break;
}
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::PHOENIX_INDEX:
if (Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::PHOENIX_ID, gun_num_t::_0, weapon_sound_flag::audible, shot_orientation, object_none) == object_none)
return 0;
Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::PHOENIX_ID, gun_num_t::_1, weapon_sound_flag::silent, shot_orientation, object_none);
2006-03-20 17:12:09 +00:00
break;
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::OMEGA_INDEX:
if (Laser_player_fire(LevelSharedRobotInfoState.Robot_info, objp, weapon_id_type::OMEGA_ID, gun_num_t::_1, weapon_sound_flag::audible, shot_orientation, Network_laser_track) == object_none)
return 0;
2006-03-20 17:12:09 +00:00
break;
2015-10-18 21:01:18 +00:00
case primary_weapon_index_t::SUPER_LASER_INDEX:
#endif
2006-03-20 17:12:09 +00:00
default:
Int3(); // Contact Yuan: Unknown Primary weapon type, setting to 0.
return 0;
2006-03-20 17:12:09 +00:00
}
// Set values to be recognized during comunication phase, if we are the
// one shooting
if ((Game_mode & GM_MULTI) && objp == get_local_player().objnum)
multi_send_fire(weapon_num, level, flags, Network_laser_track, object_none);
return 1;
2006-03-20 17:12:09 +00:00
}
2019-02-02 18:36:39 +00:00
}
namespace dcx {
uint_fast8_t laser_info::test_set_hitobj(const vcobjidx_t o)
{
if (const auto r = test_hitobj(o))
return r;
const std::size_t values_size = hitobj_values.size();
assert(hitobj_count <= values_size);
assert(hitobj_pos < values_size);
hitobj_values[hitobj_pos++] = o;
if (unlikely(hitobj_pos == values_size))
hitobj_pos = 0;
if (likely(hitobj_count != values_size))
++ hitobj_count;
return false;
}
2019-02-02 18:36:39 +00:00
uint_fast8_t laser_info::test_hitobj(const vcobjidx_t o) const
{
/* Search backward so that the highest added element is the first
* one considered. This preserves most of the benefit of tracking
* the most recent hit separately, without storing it separately or
* requiring expensive shift operations as new elements are added.
*
* This efficiency hack becomes ineffective (but not incorrect) if
* the list wraps. Wrapping should be very rare, and even a full
* search is relatively cheap, so it is not worth complicating the
* code to ensure that elements are always searched in
* most-recently-added order.
*/
const auto &&r = partial_range(hitobj_values, hitobj_count).reversed();
const auto &&e = r.end();
return std::find(r.begin(), e, o) != e;
}
2019-02-02 18:36:39 +00:00
}
namespace dsx {
namespace {
2019-02-02 18:36:39 +00:00
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------
// if goal_obj == -1, then create random vector
static imobjptridx_t create_homing_missile(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const imobjptridx_t goal_obj, weapon_id_type objtype, const weapon_sound_flag make_sound)
2006-03-20 17:12:09 +00:00
{
vms_vector vector_to_goal;
//vms_vector goal_pos;
if (goal_obj == object_none) {
2014-10-02 03:02:38 +00:00
make_random_vector(vector_to_goal);
2006-03-20 17:12:09 +00:00
} else {
vm_vec_normalized_dir_quick(vector_to_goal, goal_obj->pos, objp->pos);
2016-07-24 04:04:25 +00:00
vm_vec_scale_add2(vector_to_goal, make_random_vector(), F1_0/4);
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick(vector_to_goal);
}
2006-03-20 17:12:09 +00:00
// Create a vector towards the goal, then add some noise to it.
const auto &&objnum = Laser_create_new(vector_to_goal, objp->pos, vmsegptridx(objp->segnum), objp, objtype, make_sound);
if (objnum != object_none)
{
2006-03-20 17:12:09 +00:00
// Fixed to make sure the right person gets credit for the kill
// Objects[objnum].ctype.laser_info.parent_num = objp->ctype.laser_info.parent_num;
// Objects[objnum].ctype.laser_info.parent_type = objp->ctype.laser_info.parent_type;
// Objects[objnum].ctype.laser_info.parent_signature = objp->ctype.laser_info.parent_signature;
2014-08-23 23:53:56 +00:00
objnum->ctype.laser_info.track_goal = goal_obj;
}
2006-03-20 17:12:09 +00:00
return objnum;
}
2015-05-09 17:38:58 +00:00
struct miniparent
{
short type;
objnum_t num;
};
2006-03-20 17:12:09 +00:00
//-----------------------------------------------------------------------------
// Create the children of a smart bomb, which is a bunch of homing missiles.
static void create_smart_children(object_array &Objects, const vmobjptridx_t objp, const uint_fast32_t num_smart_children, const miniparent parent)
2006-03-20 17:12:09 +00:00
{
auto &vcobjptridx = Objects.vcptridx;
auto &vcobjptr = Objects.vcptr;
unsigned numobjs = 0;
2015-12-03 03:26:48 +00:00
weapon_id_type blob_id;
2006-03-20 17:12:09 +00:00
2020-05-02 21:18:42 +00:00
std::array<objnum_t, MAX_OBJDISTS> objlist;
#if defined(DXX_BUILD_DESCENT_II)
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
#endif
{
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
d_srand(8321L);
2016-02-12 04:02:28 +00:00
range_for (const auto &&curobjp, vcobjptridx)
2014-10-12 23:05:46 +00:00
{
if (((curobjp->type == OBJ_ROBOT && !curobjp->ctype.ai_info.CLOAKED) || curobjp->type == OBJ_PLAYER) && curobjp != parent.num)
2015-05-09 17:38:58 +00:00
{
2006-03-20 17:12:09 +00:00
if (curobjp->type == OBJ_PLAYER)
{
2015-05-09 17:38:58 +00:00
if (parent.type == OBJ_PLAYER && (Game_mode & GM_MULTI_COOP))
2006-03-20 17:12:09 +00:00
continue;
2015-05-09 17:38:58 +00:00
if ((Game_mode & GM_TEAM) && get_team(get_player_id(curobjp)) == get_team(get_player_id(vcobjptr(parent.num))))
2006-03-20 17:12:09 +00:00
continue;
if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
2006-03-20 17:12:09 +00:00
continue;
}
// Robot blobs can't track robots.
if (curobjp->type == OBJ_ROBOT) {
2015-05-09 17:38:58 +00:00
if (parent.type == OBJ_ROBOT)
2006-03-20 17:12:09 +00:00
continue;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Your shots won't track the buddy.
2015-05-09 17:38:58 +00:00
if (parent.type == OBJ_PLAYER)
2015-11-27 03:56:13 +00:00
if (Robot_info[get_robot_id(curobjp)].companion)
2006-03-20 17:12:09 +00:00
continue;
#endif
2006-03-20 17:12:09 +00:00
}
const auto &&dist = vm_vec_dist2(objp->pos, curobjp->pos);
if (dist < MAX_SMART_DISTANCE_SQUARED)
{
int oovis;
2006-03-20 17:12:09 +00:00
oovis = object_to_object_visibility(objp, curobjp, FQ_TRANSWALL);
2014-08-23 23:53:56 +00:00
if (oovis) {
objlist[numobjs] = curobjp;
2006-03-20 17:12:09 +00:00
numobjs++;
if (numobjs >= MAX_OBJDISTS) {
numobjs = MAX_OBJDISTS;
break;
}
}
}
}
}
// Get type of weapon for child from parent.
#if defined(DXX_BUILD_DESCENT_I)
2015-05-09 17:38:58 +00:00
if (parent.type == OBJ_PLAYER)
{
2015-12-03 03:26:49 +00:00
blob_id = weapon_id_type::PLAYER_SMART_HOMING_ID;
} else {
2015-12-03 03:26:49 +00:00
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..,
}
#elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if (objp->type == OBJ_WEAPON) {
2015-12-03 03:26:49 +00:00
blob_id = Weapon_info[get_weapon_id(objp)].children;
2015-12-03 03:26:48 +00:00
Assert(blob_id != weapon_none); // Hmm, missing data in bitmaps.tbl. Need "children=NN" parameter.
2006-03-20 17:12:09 +00:00
} else {
Assert(objp->type == OBJ_ROBOT);
2015-12-03 03:26:49 +00:00
blob_id = weapon_id_type::ROBOT_SMART_HOMING_ID;
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
objnum_t last_sel_objnum = object_none;
const auto get_random_different_object = [&]{
for (;;)
{
const auto r = objlist[(d_rand() * numobjs) >> 15];
if (last_sel_objnum != r)
return last_sel_objnum = r;
}
};
for (auto i = num_smart_children; i--;)
{
const auto &&sel_objnum = numobjs
? Objects.imptridx((numobjs == 1)
? objlist[0]
: get_random_different_object())
: object_none;
create_homing_missile(vmsegptridx, objp, sel_objnum, blob_id, (i==0) ? weapon_sound_flag::audible : weapon_sound_flag::silent);
2006-03-20 17:12:09 +00:00
}
}
}
}
void create_weapon_smart_children(const vmobjptridx_t objp)
2015-05-09 17:38:58 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
2015-05-09 17:38:58 +00:00
const auto wid = get_weapon_id(objp);
#if defined(DXX_BUILD_DESCENT_I)
2015-12-03 03:26:49 +00:00
if (wid != weapon_id_type::SMART_ID)
2015-05-09 17:38:58 +00:00
#elif defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
if (Weapon_info[wid].children == weapon_id_type::unspecified)
2015-05-09 17:38:58 +00:00
#endif
return;
#if defined(DXX_BUILD_DESCENT_II)
2015-12-03 03:26:49 +00:00
if (wid == weapon_id_type::EARTHSHAKER_ID)
blast_nearby_glass(objp, Weapon_info[weapon_id_type::EARTHSHAKER_ID].strength[GameUniqueState.Difficulty_level]);
2015-05-09 17:38:58 +00:00
#endif
create_smart_children(Objects, objp, NUM_SMART_CHILDREN, {objp->ctype.laser_info.parent_type, objp->ctype.laser_info.parent_num});
2015-05-09 17:38:58 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
void create_robot_smart_children(const vmobjptridx_t objp, const uint_fast32_t num_smart_children)
2015-05-09 17:38:58 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
create_smart_children(Objects, objp, num_smart_children, {OBJ_ROBOT, objp});
2015-05-09 17:38:58 +00:00
}
2006-03-20 17:12:09 +00:00
//give up control of the guided missile
void release_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const unsigned player_num)
2006-03-20 17:12:09 +00:00
{
if (player_num == Player_num)
{
const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, player_num);
if (gimobj == nullptr)
2006-03-20 17:12:09 +00:00
return;
Missile_viewer = gimobj;
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
multi_send_guided_info(gimobj, 1);
2006-03-20 17:12:09 +00:00
if (Newdemo_state==ND_STATE_RECORDING)
newdemo_record_guided_end();
}
LevelUniqueObjectState.Guided_missile.clear_player_active_guided_missile(player_num);
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------
//changed on 31/3/10 by kreatordxx to distinguish between drop bomb and secondary fire
void do_missile_firing(int drop_bomb)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
auto &vmobjptridx = Objects.vmptridx;
2006-03-20 17:12:09 +00:00
int gun_flag=0;
fix fire_frame_overhead = 0;
2006-03-20 17:12:09 +00:00
auto &plrobj = get_local_plrobj();
2016-08-28 22:41:48 +00:00
auto &player_info = plrobj.ctype.player_info;
const auto bomb = which_bomb(player_info);
2016-08-28 22:41:48 +00:00
const auto weapon = drop_bomb ? bomb : player_info.Secondary_weapon;
assert(weapon < MAX_SECONDARY_WEAPONS);
auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
if (GameTime64 - Next_missile_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that.
fire_frame_overhead = GameTime64 - Next_missile_fire_time;
#if defined(DXX_BUILD_DESCENT_II)
const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num);
if (gimobj != nullptr)
{
release_guided_missile(LevelUniqueObjectState, Player_num);
Next_missile_fire_time = GameTime64 + Weapon_info[Secondary_weapon_to_weapon_info[weapon]].fire_wait - fire_frame_overhead;
2006-03-20 17:12:09 +00:00
return;
}
#endif
2006-03-20 17:12:09 +00:00
if (Player_dead_state != player_dead_state::no)
2015-10-10 03:44:14 +00:00
return;
if (auto &secondary_weapon_ammo = player_info.secondary_ammo[weapon])
2015-10-10 03:44:14 +00:00
{
2015-12-03 03:26:49 +00:00
const auto weapon_index = Secondary_weapon_to_weapon_info[weapon];
const auto base_weapon_gun = Secondary_weapon_to_gun_num[weapon];
auto &plrobj = get_local_plrobj();
auto &Missile_gun = plrobj.ctype.player_info.missile_gun;
2022-07-09 13:39:29 +00:00
const auto weapon_gun = (base_weapon_gun == gun_num_t::_4)
? static_cast<gun_num_t>(static_cast<uint8_t>(base_weapon_gun) + (gun_flag = (Missile_gun & 1)))
: base_weapon_gun;
const auto &&objnum = Laser_player_fire(LevelSharedRobotInfoState.Robot_info, vmobjptridx(ConsoleObject), weapon_index, weapon_gun, weapon_sound_flag::audible, plrobj.orient.fvec, object_none);
if (objnum == object_none)
2022-07-09 13:39:29 +00:00
/* If the missile was not created, return early. Do not charge for
* it, and do not report it to other players.
*/
return;
2022-07-09 13:39:29 +00:00
/* Toggle between the left and right missile guns.
*/
if (base_weapon_gun == gun_num_t::_4)
Missile_gun++;
if (!cheats.rapidfire)
Next_missile_fire_time = GameTime64 + Weapon_info[weapon_index].fire_wait - fire_frame_overhead;
2006-03-20 17:12:09 +00:00
else
Next_missile_fire_time = GameTime64 + (F1_0/25) - fire_frame_overhead;
2006-03-20 17:12:09 +00:00
-- secondary_weapon_ammo;
if (weapon != CONCUSSION_INDEX)
maybe_drop_net_powerup(Secondary_weapon_to_powerup[weapon], 1, 0);
#if defined(DXX_BUILD_DESCENT_I)
if (weapon == MEGA_INDEX)
#elif defined(DXX_BUILD_DESCENT_II)
if (weapon == MEGA_INDEX || weapon == SMISSILE5_INDEX)
#endif
{
2006-03-20 17:12:09 +00:00
vms_vector force_vec;
const auto &&console = vmobjptr(ConsoleObject);
2006-03-20 17:12:09 +00:00
force_vec.x = -(ConsoleObject->orient.fvec.x << 7);
force_vec.y = -(ConsoleObject->orient.fvec.y << 7);
force_vec.z = -(ConsoleObject->orient.fvec.z << 7);
2015-07-12 01:04:20 +00:00
phys_apply_force(console, force_vec);
2006-03-20 17:12:09 +00:00
force_vec.x = (force_vec.x >> 4) + d_rand() - 16384;
force_vec.y = (force_vec.y >> 4) + d_rand() - 16384;
force_vec.z = (force_vec.z >> 4) + d_rand() - 16384;
2015-07-12 01:04:20 +00:00
phys_apply_rot(console, force_vec);
2006-03-20 17:12:09 +00:00
}
if (Game_mode & GM_MULTI)
{
multi_send_fire(weapon+MISSILE_ADJUST, laser_level::_1 /* unused */, gun_flag, objnum == object_none ? object_none : objnum->ctype.laser_info.track_goal, weapon_index_is_player_bomb(weapon) ? objnum : object_none);
}
2006-03-20 17:12:09 +00:00
// don't autoselect if dropping prox and prox not current weapon
2016-08-28 22:41:48 +00:00
if (!drop_bomb || player_info.Secondary_weapon == bomb)
auto_select_secondary_weapon(player_info); //select next missile, if this one out of ammo
2006-03-20 17:12:09 +00:00
}
}
}