Descent3/Descent3/weapon.cpp
2024-05-24 20:57:17 -04:00

1430 lines
42 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/main/weapon.cpp $
* $Revision: 91 $
* $Date: 4/19/00 5:07p $
* $Author: Matt $
*
* $Log: /DescentIII/main/weapon.cpp $
*
* 91 4/19/00 5:07p Matt
* From Duane for 1.4
* Added checks, asserts, and fixes for bad return values
*
* 90 11/03/99 6:29p Chris
* Improved the auto-selection code for dual fire secondary weapons
*
* 89 10/21/99 9:30p Jeff
* B.A. Macintosh code merge
*
* 88 5/20/99 9:56p Jeff
* AddWeaponToPlayer checks for max ammo when adding it
*
* 87 5/17/99 12:20p Samir
* don't print out any poweup weapon pickup messages in AddWeaponToPlayer.
*
* 86 5/08/99 6:18p Chris
* Fixed up no hearing cases
*
* 85 5/08/99 4:12p Chris
* Added AI hearing noises... version 1
*
* 84 4/14/99 4:19a Jeff
* more case mismatch fixes in #includes
*
* 83 4/12/99 12:49p Jeff
* added recoil_force to weapon's page
*
* 82 3/31/99 10:27a Samir
* code to reset auto select states when ship is initializiaed and code to
* save and load these states from disk.
*
* 81 3/05/99 12:19p Matt
* Delete now-unused function AddWeaponAmmoToPlayer()
*
* 80 3/03/99 5:08p Samir
* AddWeaponAmmoToPlayer added.
*
* 79 2/25/99 1:29p Jason
* fixed zoom problem
*
* 78 2/20/99 7:06p Matt
* Changed a few HUD messages
*
* 77 2/10/99 3:48p Jason
* table filter changes
*
* 76 1/31/99 7:26p Matt
* Renamed a bunch of functions to have HUD capitalized
*
* 75 12/15/98 11:51a Samir
* fixed bugs with autoselect weapons when they're supposed to be skipped.
*
* 74 12/04/98 5:13p Jason
* took out extra mprintfs
*
* 73 10/22/98 2:41p Samir
* fixed autoselection for good.
*
* 72 10/21/98 11:52p Samir
* print different message if autoselecting a recently acquired weapon.
*
* 71 10/21/98 12:19p Samir
* fixed small bug in SwitchPlayerWeapon for skipped weapons.
*
* 70 10/21/98 11:14a Samir
* autoselect bug when selecting into a higher class without ammo/energy
* fixed.
*
* 69 10/19/98 11:31p Samir
* cycle up instead of down.
*
* 68 10/19/98 11:27p Samir
* fixed weapon cycling (switchplayerweapon)
*
* 67 10/07/98 9:46p Samir
* fixed small bug in SwitchPlayerWeapon
*
* 66 10/07/98 9:36p Samir
* added a function to switch a player's weapon to another in the given
* weapon class.
*
* 65 10/07/98 5:23p Jeff
* samir fixed an autoselect bug
*
* 64 10/07/98 4:13p Samir
* cleaned up weapon selection code.
*
* 63 9/30/98 4:31p Samir
* added functions to handle weapon select list.
*
* 62 7/31/98 5:17p Samir
* fixed weapon selection bug because secondaries weren't working properly
* and removed guided missile from autoselect list.
*
* 61 7/30/98 11:09a Jason
* added weapons that freeze and deform terrain
*
* 60 7/01/98 12:12p Jason
* added countermeasures
*
* 59 6/22/98 6:26p Jason
* added gravity field effect for weapons
*
* 58 6/19/98 12:04p Jason
*
* 57 6/17/98 3:27p Jeff
* Changes made for localization
*
* 56 6/16/98 10:54a Jeff
*
* 55 5/27/98 7:12p Samir
* yet another silly weapon selection(primary) bug fixed due to hasty
* checking in.
*
* 54 5/27/98 6:02p Samir
* foolishly, I don't check for primary weapon ammo when selecting
* weapons.
*
* 53 5/26/98 10:48p Samir
* weapon selection FIXED!
*
* 52 5/26/98 5:06p Samir
* changed name of weapon indices and added function to get weapon's icon
* image.
*
* 51 5/25/98 6:39p Jason
* got icons working for weapons
*
* 50 5/22/98 6:25p Samir
* fixed 'Rocker' bug.
*
* 49 5/22/98 11:59a Chris
* Fixed improper uses of FindSoundName and made the proper static sounds
*
* 48 5/14/98 12:56p Jason
* changes to help lower memory usage
*
* 47 4/24/98 5:32p Samir
* added reset reticle when setting a new weapon.
*
* 46 4/24/98 8:02a Samir
* added a int16_t weapon name array.
*
* 45 4/22/98 1:08p Chris
* Fixed auto-firing of weapons after auto-selection
*
* 44 4/08/98 12:16p Samir
* Weapon selection should be improved.
*
* 43 4/07/98 3:31p Jason
* got particle effects working with weapons
*
* 42 4/06/98 4:53p Jason
* got pageable polymodels working correctly with editor
*
* 41 4/02/98 3:54p Jason
* first pass in getting polymodel paging to work
*
* 40 3/23/98 3:52p Jason
* added weapon names
*
* 39 3/17/98 2:40p Samir
* reorg of hud/gauge system.
*
* 38 2/26/98 6:00p Jason
* fixed a bug where a OBJ_GHOST object (ie a dead player) could try to
* select a new weapon
*
* 37 2/17/98 11:33p Matt
* Use new function to stop player firing activity when switching weapons
*
* 36 2/17/98 5:04p Samir
* Fixed selection of secondary weapons.
*
* 35 2/17/98 4:06p Jason
* Fixed DrawAlphaBlendedScreen not zbuffering correctly
*
* 34 2/17/98 3:47p Matt
* Revamped weapon system and got sounds for spray and fusion weapons
* working. Still need to implements getting continuous & cutoff sounds
* from player ship.
*
* 33 2/15/98 1:18a Jason
* fixed zbuffer problem
*
* 32 2/14/98 10:48p Jason
* got preferred rendering working
*
* 31 2/11/98 2:04p Jason
* got spawning weapons working
*
* 30 2/05/98 7:06p Jason
* fixed weapon naming bug
*
* 29 2/05/98 6:53p Jason
* added new weapon slot
*
* 28 2/05/98 6:29p Jason
* added user settable explode time/size
*
* 27 2/04/98 9:28p Jason
* added some new weapons effects
*
* 26 2/02/98 4:07p Jason
* added a couple of weapons
*
* 25 1/29/98 6:06p Jason
* added new weapons
*
* 24 1/28/98 3:18p Jason
* Made AddWeaponToPlayer take a playernum
*
*
* 23 1/26/98 11:01a Jason
* incremental checkin for multiplayer
*
* 22 1/23/98 6:25p Jason
* Got spray weapons working
*
* 21 12/22/97 6:19p Chris
* Moved weapon battery firing sound off the projectile (weapon) and into
* the weapon battery.
*
* 20 11/14/97 6:57p Samir
* Maybe fixed selection of weapons <-> auto selection dependencies.
*
* 19 11/14/97 5:47p Mark
* Fixed some weapon selection hokeyness.
*
* 18 11/12/97 6:01p Samir
* Added omega and guided names to weapon list.
*
* 17 11/12/97 1:13p Jason
* added weapons that can ramp up
*
* 16 11/11/97 1:28p Samir
* Weapon selection should change hud, I think.
*
* 15 11/05/97 12:21p Chris
* Added weapon remap for player ship weapon batteries
*
* 14 11/04/97 5:52p Jason
* added DrawAplhaBlendedScreen function
*
* 13 10/30/97 4:02p Matt
* Added the flare
*
* 12 10/15/97 5:20p Jason
* did a HUGE overhaul of the bitmap system
*
* 11 9/16/97 3:03p Samir
* fixed autoselection of weapons when you don't have enough energy.
*
* 10 9/15/97 3:37p Samir
* Updated some more names for weapons.
*
* 9 9/10/97 11:45a Chris
* FIXED a major remapping bug
* Added support for weapon batteries
*
* 8 9/10/97 10:12a Samir
* Added weapon name text.
*
* 7 9/08/97 5:12p Samir
* Fixed weapon selection
*
* 6 9/05/97 12:25p Samir
* Added autoselection of weapons.
*
* 5 9/04/97 3:59p Samir
* Added code to switch weapons.
*
* 4 8/07/97 3:28p Jason
* Assign -1 as the default sound for weapons
*
* 3 8/07/97 1:01p Mark
* fixed a dumb bug
*
* 2 8/07/97 12:53p Mark
* FROM JASON: Made oof readable as discharge files
*
* 7 4/28/97 6:46p Jason
* made ships have multiple gun points
*
* 6 4/25/97 3:59p Jason
* fixed a bug that my last checkin caused
*
* 5 4/25/97 3:31p Jason
* implemented better memory management for vclips and bitmaps
*
* 4 4/17/97 2:54p Jason
* added clueless weapon picker upper
*
* 3 4/14/97 1:50p Jason
* first pass at getting weapons to fire
*
* 2 3/31/97 4:35p Jason
* added weapon page functionality
*
* 1 3/31/97 4:13p Jason
* weapons implementation file
*
*
* $NoKeywords: $
*/
#include "weapon.h"
#include "pstypes.h"
#include "pserror.h"
#include "object.h"
#include "3d.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "bitmap.h"
#include "vclip.h"
#include "game.h"
#include "polymodel.h"
#include "player.h"
#include "hud.h"
#include "hlsoundlib.h"
#include "soundload.h"
#include "objinfo.h"
#include "gametexture.h"
#include "ship.h"
#include "gauges.h"
#include "sounds.h"
#include "stringtable.h"
#include "Macros.h"
#include "cfile.h"
#include "AIMain.h"
#include <algorithm>
// #include "samirlog.h"
#define LOGFILE(_s)
weapon Weapons[MAX_WEAPONS];
int Num_weapons = 0;
const char *Static_weapon_names[] = {
// Primary weapons
"Laser",
"Vauss",
"Microwave",
"Plasma",
"Fusion",
"Super Laser",
"Mass Driver",
"Napalm",
"EMD Gun",
"Omega",
// Secondary weapons
"Concussion",
"Homing",
"Impact Mortar",
"Smart",
"Mega",
"Frag",
"Guided",
"Napalm Rocket",
"Cyclone",
"Black Shark",
// The flares
"Yellow Flare",
};
int Static_weapon_names_msg[] = {
// Primary weapons
TXI_WPN_LASER,
TXI_WPN_VAUSS,
TXI_WPN_MICROWAVE,
TXI_WPN_PLASMA,
TXI_WPN_FUSION,
TXI_WPN_SUPLASER,
TXI_WPN_MASSDRIVER,
TXI_WPN_NAPALM,
TXI_WPN_EMDGUN,
TXI_WPN_OMEGA,
// Secondary weapons
TXI_WPN_CONCUSSION,
TXI_WPN_HOMING,
TXI_WPN_IMPACT,
TXI_WPN_SMART,
TXI_WPN_MEGA,
TXI_WPN_FRAG,
TXI_WPN_GUIDED,
TXI_WPN_NAPALMR,
TXI_WPN_CYCLONE,
TXI_WPN_BLACKSHARK,
// The flares
TXI_WPN_YELL_FLARE,
};
int Static_weapon_ckpt_names[][2] = {
// Primary weapons
{TXI_WPNC_LASER_1, TXI_WPNC_LASER_2},
{TXI_WPNC_VAUSS_1, TXI_WPNC_VAUSS_2},
{TXI_WPNC_MICRO_1, TXI_WPNC_MICRO_2},
{TXI_WPNC_PLASMA_1, TXI_WPNC_PLASMA_2},
{TXI_WPNC_FUSION_1, TXI_WPNC_FUSION_2},
{TXI_WPNC_SUPLAS_1, TXI_WPNC_SUPLAS_2},
{TXI_WPNC_MASSD_1, TXI_WPNC_MASSD_2},
{TXI_WPNC_NAPALM_1, TXI_WPNC_NAPALM_2},
{TXI_WPNC_EMD_1, TXI_WPNC_EMD_2},
{TXI_WPNC_OMEGA_1, TXI_WPNC_OMEGA_2},
// Secondary weapons
{TXI_WPNC_CONC_1, TXI_WPNC_CONC_2},
{TXI_WPNC_HOMING_1, TXI_WPNC_HOMING_2},
{TXI_WPNC_IMPACT_1, TXI_WPNC_IMPACT_2},
{TXI_WPNC_SMART_1, TXI_WPNC_SMART_2},
{TXI_WPNC_MEGA_1, TXI_WPNC_MEGA_2},
{TXI_WPNC_FRAG_1, TXI_WPNC_FRAG_2},
{TXI_WPNC_GUID_1, TXI_WPNC_GUID_2},
{TXI_WPNC_NAPALMR_1, TXI_WPNC_NAPALMR_2},
{TXI_WPNC_CYCLONE_1, TXI_WPNC_CYCLONE_2},
{TXI_WPNC_BLKSHRK_1, TXI_WPNC_BLKSHRK_2},
// The flares
{TXI_WPNC_YELFLARE_1, TXI_WPNC_YELFLARE_2},
};
// Sets all weapons to unused
void InitWeapons() {
for (int i = 0; i < MAX_WEAPONS; i++) {
Weapons[i].used = 0;
Weapons[i].name[0] = 0;
}
Num_weapons = 0;
}
// Allocs a weapon for use, returns -1 if error, else index on success
int AllocWeapon() {
for (int i = MAX_STATIC_WEAPONS; i < MAX_WEAPONS; i++) {
if (Weapons[i].used == 0) {
memset(&Weapons[i], 0, sizeof(weapon));
for (int t = 0; t < MAX_WEAPON_SOUNDS; t++)
Weapons[i].sounds[t] = SOUND_NONE_INDEX;
Weapons[i].alpha = 1.0;
Weapons[i].spawn_handle = -1;
Weapons[i].alternate_spawn_handle = -1;
Weapons[i].icon_handle = -1;
Weapons[i].scorch_handle = -1;
Weapons[i].gravity_size = 1;
Weapons[i].gravity_time = 1;
Weapons[i].explode_size = 1;
Weapons[i].explode_time = 1;
Weapons[i].particle_count = 0;
Weapons[i].particle_size = 1;
Weapons[i].particle_life = 0;
Weapons[i].particle_handle = -1;
Weapons[i].robot_spawn_handle = -1;
Weapons[i].alternate_chance = 0;
Weapons[i].terrain_damage_size = 0;
Weapons[i].terrain_damage_depth = 0;
Weapons[i].used = 1;
Weapons[i].recoil_force = 0.0f;
Num_weapons++;
return i;
}
}
Int3(); // No weapons free!
return -1;
}
// Frees weapon index n and all associated images
void FreeWeapon(int n) {
ASSERT(Weapons[n].used > 0);
Weapons[n].used = 0;
Weapons[n].name[0] = 0;
Num_weapons--;
}
// Gets next weapon from n that has actually been alloced
int GetNextWeapon(int n) {
int i;
ASSERT(n >= 0 && n < MAX_WEAPONS);
if (Num_weapons == 0)
return -1;
for (i = n + 1; i < MAX_WEAPONS; i++)
if (Weapons[i].used)
return i;
for (i = 0; i < n; i++)
if (Weapons[i].used)
return i;
// this is the only one
return n;
}
// Gets previous weapon from n that has actually been alloced
int GetPrevWeapon(int n) {
int i;
ASSERT(n >= 0 && n < MAX_WEAPONS);
if (Num_weapons == 0)
return -1;
for (i = n - 1; i >= 0; i--) {
if (Weapons[i].used)
return i;
}
for (i = MAX_WEAPONS - 1; i > n; i--) {
if (Weapons[i].used)
return i;
}
// this is the only one
return n;
}
// Searches thru all weapons for a specific name, returns -1 if not found
// or index of weapon with name
int FindWeaponName(const char *name) {
int i;
ASSERT(name != NULL);
for (i = 0; i < MAX_WEAPONS; i++)
if (Weapons[i].used && !stricmp(name, Weapons[i].name))
return i;
return -1;
}
// Given a weapon handle, returns that weapons bitmap
// If the weapon is animated, returns framenum mod num_of_frames in the animation
// Also figures in gametime
int GetWeaponHudImage(int handle, int framenum) {
if (Weapons[handle].flags & WF_HUD_ANIMATED) {
float cur_frametime;
int int_frame;
vclip *vc = &GameVClips[Weapons[handle].hud_image_handle];
ASSERT(vc->used);
cur_frametime = Gametime / vc->frame_time;
int_frame = cur_frametime;
int_frame += framenum;
return (vc->frames[int_frame % vc->num_frames]);
} else
return (Weapons[handle].hud_image_handle);
}
// Given a filename, loads either the bitmap or vclip found in that file. If type
// is not NULL, sets it to 1 if file is animation, otherwise sets it to zero
int LoadWeaponHudImage(char *filename, int *type) {
int anim = 0, bm_handle;
char extension[10];
int len = strlen(filename);
if (len < 4)
return -1;
strncpy(extension, &filename[len - 3], 5);
if ((!strnicmp("oaf", extension, 3)) || (!strnicmp("ifl", extension, 3)) || (!strnicmp("abm", extension, 3)))
anim = 1;
if (type != NULL)
*type = anim;
if (anim)
bm_handle = AllocLoadVClip(IGNORE_TABLE(filename), NOT_TEXTURE, 0);
else
bm_handle = bm_AllocLoadFileBitmap(IGNORE_TABLE(filename), 0);
return bm_handle;
}
// Given a weapon handle, returns that weapons discharge bitmap
int GetWeaponFireImage(int handle, int frame) {
if (Weapons[handle].flags & WF_IMAGE_VCLIP) {
float cur_frametime;
int int_frame;
PageInVClip(Weapons[handle].fire_image_handle);
vclip *vc = &GameVClips[Weapons[handle].fire_image_handle];
ASSERT(vc->used >= 1);
cur_frametime = Gametime / vc->frame_time;
int_frame = cur_frametime;
int_frame += frame;
return (vc->frames[int_frame % vc->num_frames]);
} else
return (Weapons[handle].fire_image_handle);
}
// Given a filename, loads either the bitmap or model found in that file. If type
// is not NULL, sets it to 1 if file is model, otherwise sets it to zero
int LoadWeaponFireImage(char *filename, int *type, int *anim, int pageable) {
int model = 0, bm_handle;
int is_vclip = 0;
char extension[10];
int len = strlen(filename);
if (len < 4)
return -1;
strncpy(extension, &filename[len - 3], 5);
if ((!strnicmp("pof", extension, 3)) || (!strnicmp("oof", extension, 3)))
model = 1;
if (type != NULL)
*type = model;
if (anim != NULL)
*anim = is_vclip;
if (model)
bm_handle = LoadPolyModel(IGNORE_TABLE(filename), 0);
else {
bm_handle = LoadTextureImage(IGNORE_TABLE(filename), anim, NOT_TEXTURE, 0);
}
return bm_handle;
}
// Given a weapon name, assigns that weapon to a specific index into
// the Weapons array. Returns -1 if the named weapon is not found, 0 if the weapon
// is already in its place, or 1 if successfully moved
int MatchWeaponToIndex(char *name, int dest_index) {
ASSERT(dest_index >= 0 && dest_index < MAX_WEAPONS);
int cur_index = FindWeaponName(name);
int new_index;
if (cur_index == -1) {
// The weapon we are trying to find is not loaded or doesn't exist
Int3(); // Get Jason or Matt
return -1;
}
if (cur_index == dest_index)
return 0; // hurray, we're already matched up!
if (Weapons[dest_index].used) {
// This weapon is currently in use. Copy the present data
// into a new weapon and then assign our desired weapon to that index
new_index = AllocWeapon();
if (new_index >= 0) // DAJ -1FIX
memcpy(&Weapons[new_index], &Weapons[dest_index], sizeof(weapon));
// Now copy our new info over and free the old one
memcpy(&Weapons[dest_index], &Weapons[cur_index], sizeof(weapon));
FreeWeapon(cur_index);
} else {
// This slot is unused, so just take it
Weapons[dest_index].used = 1;
Num_weapons++;
memcpy(&Weapons[dest_index], &Weapons[cur_index], sizeof(weapon));
FreeWeapon(cur_index);
return 0;
}
return new_index; // we made it!
}
// Moves a weapon from a given index into a new one (above MAX_STATIC_WEAPONS)
// returns new index
int MoveWeaponFromIndex(int index) {
ASSERT(index >= 0 && index < MAX_STATIC_WEAPONS);
ASSERT(Weapons[index].used);
int new_index = AllocWeapon();
memcpy(&Weapons[new_index], &Weapons[index], sizeof(weapon));
FreeWeapon(index);
return new_index;
}
// This is a very confusing function. It takes all the weapons that we have loaded
// and remaps then into their proper places (if they are static).
void RemapWeapons() {
int limit = sizeof(Static_weapon_names) / sizeof(void *);
int i;
for (i = 0; i < MAX_STATIC_WEAPONS; i++) {
if (Weapons[i].used) {
int match = -1;
for (int t = 0; t < limit; t++)
if (!stricmp(Weapons[i].name, Static_weapon_names[t])) {
match = t;
break;
}
if (match == -1) // this weapon is not supposed to be in the static area
{
int new_index;
new_index = MoveWeaponFromIndex(i);
RemapAllWeaponObjects(i, new_index);
} else // this weapon is a static weapon, make sure its in its place
{
if (i != match) // its not where it belongs, move it
{
int new_index;
new_index = MatchWeaponToIndex(Weapons[i].name, match);
if (new_index != 0)
RemapAllWeaponObjects(match, new_index);
RemapAllWeaponObjects(i, match);
}
}
}
}
for (i = MAX_STATIC_WEAPONS; i < MAX_WEAPONS; i++) {
if (Weapons[i].used) {
int match = -1;
for (int t = 0; t < limit; t++)
if (!stricmp(Weapons[i].name, Static_weapon_names[t])) {
match = t;
break;
}
if (match != -1) // this is a static weapon that isn't supposed to be in the free area
{
int new_index;
new_index = MatchWeaponToIndex(Weapons[i].name, match);
if (new_index != 0)
RemapAllWeaponObjects(match, new_index);
RemapAllWeaponObjects(i, match);
}
}
}
}
// goes thru every entity that could possible have a weapon index (ie objects, weapons, etc)
// and changes the old index to the new index
void RemapAllWeaponObjects(int old_index, int new_index) {
int i, j, k;
// Remaps flying weapons
for (i = 0; i < MAX_OBJECTS; i++) {
if (Objects[i].type == OBJ_WEAPON) {
if (Objects[i].id == old_index) {
Objects[i].id = new_index;
}
}
}
// Remaps weapons contained by generic objects
for (i = 0; i < MAX_OBJECT_IDS; i++) {
if (Object_info[i].static_wb) {
for (j = 0; j < MAX_WBS_PER_OBJ; j++) {
for (k = 0; k < MAX_WB_GUNPOINTS; k++) {
if (Object_info[i].static_wb[j].gp_weapon_index[k] == old_index) {
Object_info[i].static_wb[j].gp_weapon_index[k] = new_index;
}
}
}
}
}
// Remaps weapons contained by Ships
for (i = 0; i < MAX_SHIPS; i++) {
for (j = 0; j < MAX_WBS_PER_OBJ; j++) {
for (k = 0; k < MAX_WB_FIRING_MASKS; k++) {
if (Ships[i].static_wb[j].gp_weapon_index[k] == old_index) {
Ships[i].static_wb[j].gp_weapon_index[k] = new_index;
}
}
}
}
// Do spawns
for (i = 0; i < MAX_WEAPONS; i++) {
if (!Weapons[i].used)
continue;
if (Weapons[i].spawn_handle == old_index)
Weapons[i].spawn_handle = new_index;
if (Weapons[i].alternate_spawn_handle == old_index)
Weapons[i].alternate_spawn_handle = new_index;
}
}
bool IsWeaponSecondary(int index) {
if (index < SECONDARY_INDEX)
return false;
else
return true;
}
///////////////////////////////////////////////////////////////////////////
// Weapon acquirement
// unconditionally adds a weapon and ammo to a player.
int AddWeaponToPlayer(int slot, int weap_index, int ammo) {
bool select_new = false;
LOGFILE((_logfp, "Adding weapon(%d,ammo=%d) to player.\n", weap_index, ammo));
Players[slot].weapon_flags |= HAS_FLAG(weap_index);
ship *ship = &Ships[Players[slot].ship_index];
otype_wb_info *wb = &ship->static_wb[weap_index];
ASSERT(wb != NULL);
// if secondary or primary that uses ammo, then use the ammo
if ((weap_index >= SECONDARY_INDEX) || wb->ammo_usage) {
// figure out much ammo to add
int added = std::min(ship->max_ammo[weap_index] - Players[slot].weapon_ammo[weap_index], ammo);
// now add it
Players[slot].weapon_ammo[weap_index] += (uint16_t)added;
}
if (slot == Player_num) {
if (weap_index < SECONDARY_INDEX)
select_new = AutoSelectWeapon(PW_PRIMARY, weap_index);
else
select_new = AutoSelectWeapon(PW_SECONDARY, weap_index);
// this should be done in multisafe code now.
// if (!select_new) {
// AddHUDMessage ("%s!",TXT(Static_weapon_names_msg[weap_index]));
//}
}
return 1;
}
///////////////////////////////////////////////////////////////////////////
// Weapon selection
// Note that we allocate five keys per category of weapon (primary, secondary)
// We essentially select the weapon in slot passed.
#define NUM_PRIMARY_SLOTS 5
#define NUM_SECONDARY_SLOTS 5
// This is NOT a mask of weapons available to the player. This is a mask of what CLASS of
// weapon this slot is currently in. The code below checks this mask to see if it should
// select the higher class weapon in that slot when switching to that slot.
static uint16_t Weapon_slot_mask = 0;
void SelectPrimaryWeapon(int slot);
void SelectSecondaryWeapon(int slot);
void SetPrimaryWeapon(int index, int slot);
void SetSecondaryWeapon(int index, int slot);
static inline bool is_weapon_available(unsigned player_weapon_flags, int new_weapon, uint16_t ammo = 0xffff) {
return ((player_weapon_flags & HAS_FLAG(new_weapon)) && ammo > 0) ? true : false;
}
// used for sequencing
void ResetWeaponSelectStates(uint16_t new_state) { Weapon_slot_mask = new_state; }
void SaveWeaponSelectStates(CFILE *fp) { cf_WriteShort(fp, Weapon_slot_mask); }
void LoadWeaponSelectStates(CFILE *fp) {
uint16_t state = (uint16_t)cf_ReadShort(fp);
ResetWeaponSelectStates(state);
}
void SelectWeapon(int slot) {
if (Player_object->type != OBJ_PLAYER)
return; // This can happen when a player is dead and tries to select a weapon
if (slot < NUM_PRIMARY_SLOTS)
SelectPrimaryWeapon(slot);
else
SelectSecondaryWeapon(slot);
}
// slot ranges 0-4
void SelectPrimaryWeapon(int slot) {
// get slot of currently selected weapon
int oldslot;
int nw_low, nw_high;
unsigned avail_flags;
player *plr = &Players[Player_num];
avail_flags = plr->weapon_flags;
oldslot = plr->weapon[PW_PRIMARY].index % NUM_PRIMARY_SLOTS;
// do selection. if we are selecting the same slot of weapon, then we select to the next
// level of weapon. when going from highest level, go to lowest
if (oldslot == slot) {
uint16_t nw_low = (plr->weapon[PW_PRIMARY].index + NUM_PRIMARY_SLOTS) % MAX_PRIMARY_WEAPONS;
if (is_weapon_available(avail_flags, nw_low)) {
// toggle class of weapon in specified slot (save for selection)
SetPrimaryWeapon(nw_low, slot);
} else {
AddHUDMessage(TXT_WPNNOTAVAIL);
Sound_system.Play2dSound(SOUND_DO_NOT_HAVE_IT);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
}
return;
} else {
// we are selecting a new weapon slot.
nw_low = slot % NUM_PRIMARY_SLOTS;
nw_high = nw_low + NUM_PRIMARY_SLOTS;
if (Weapon_slot_mask & (1 << slot)) {
// we think we have a higher class of weapon.
if (is_weapon_available(avail_flags, nw_high)) {
// if this slot had the higher class of weapon then check if we still have the higher class weapon.
// toggle class of weapon in specified slot (save for selection)
SetPrimaryWeapon(nw_high, slot);
} else if (is_weapon_available(avail_flags, nw_low)) {
// check if we have the lower class.
// toggle class of weapon in specified slot (save for selection)
SetPrimaryWeapon(nw_low, slot);
} else {
AddHUDMessage(TXT_WPNNOTAVAIL);
Sound_system.Play2dSound(SOUND_DO_NOT_HAVE_IT);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
}
} else {
// if this is a lower class of weapon, make sure this slot is flagged as having the lower version
if (is_weapon_available(avail_flags, nw_low)) {
// if this slot had the higher class of weapon then check if we still have the higher class weapon.
// toggle class of weapon in specified slot (save for selection)
SetPrimaryWeapon(nw_low, slot);
} else if (is_weapon_available(avail_flags, nw_high)) {
// check if we have the lower class.
// toggle class of weapon in specified slot (save for selection)
SetPrimaryWeapon(nw_high, slot);
} else {
AddHUDMessage(TXT_WPNNOTAVAIL);
Sound_system.Play2dSound(SOUND_DO_NOT_HAVE_IT);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
}
}
}
}
// slot ranges 5-9
void SelectSecondaryWeapon(int slot) {
// get slot of currently selected weapon
int oldslot;
int nw_low, nw_high;
unsigned avail_flags;
player *plr = &Players[Player_num];
avail_flags = plr->weapon_flags;
oldslot = (plr->weapon[PW_SECONDARY].index % NUM_SECONDARY_SLOTS) + NUM_PRIMARY_SLOTS;
// do selection. if we are selecting the same slot of weapon, then we select to the next
// level of weapon. when going from highest level, go to lowest
if (oldslot == slot) {
nw_low = SECONDARY_INDEX + ((plr->weapon[PW_SECONDARY].index + NUM_SECONDARY_SLOTS) % MAX_SECONDARY_WEAPONS);
if (is_weapon_available(avail_flags, nw_low, plr->weapon_ammo[nw_low])) {
// toggle class of weapon in specified slot (save for selection)
SetSecondaryWeapon(nw_low, slot);
} else {
AddHUDMessage(TXT_WPNNOTAVAIL);
Sound_system.Play2dSound(SOUND_DO_NOT_HAVE_IT);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
}
return;
}
// we are selecting a new weapon slot.
nw_low = (slot % NUM_SECONDARY_SLOTS) + SECONDARY_INDEX;
nw_high = nw_low + NUM_SECONDARY_SLOTS;
if (Weapon_slot_mask & (1 << slot)) {
// we think we have a higher class of weapon.
if (is_weapon_available(avail_flags, nw_high, plr->weapon_ammo[nw_high])) {
// if this slot had the higher class of weapon then check if we still have the higher class weapon.
SetSecondaryWeapon(nw_high, slot);
} else if (is_weapon_available(avail_flags, nw_low, plr->weapon_ammo[nw_low])) {
SetSecondaryWeapon(nw_low, slot);
} else {
AddHUDMessage(TXT_WPNNOTAVAIL);
Sound_system.Play2dSound(SOUND_DO_NOT_HAVE_IT);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
}
} else {
// if this is a lower class of weapon, make sure this slot is flagged as having the lower version
if (is_weapon_available(avail_flags, nw_low, plr->weapon_ammo[nw_low])) {
// if this slot had the higher class of weapon then check if we still have the higher class weapon.
SetSecondaryWeapon(nw_low, slot);
} else if (is_weapon_available(avail_flags, nw_high, plr->weapon_ammo[nw_high])) {
// check if we have the lower class.
SetSecondaryWeapon(nw_high, slot);
} else {
AddHUDMessage(TXT_WPNNOTAVAIL);
Sound_system.Play2dSound(SOUND_DO_NOT_HAVE_IT);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
}
}
}
void SetPrimaryWeapon(int index, int slot) {
dynamic_wb_info *p_dwb = &Player_object->dynamic_wb[index];
if (index < NUM_PRIMARY_SLOTS)
Weapon_slot_mask &= ~(1 << slot);
else
Weapon_slot_mask |= (1 << slot);
if (index != Players[Player_num].weapon[PW_PRIMARY].index) {
ClearPlayerFiring(Player_object, PW_PRIMARY);
Players[Player_num].weapon[PW_PRIMARY].index = index;
AddHUDMessage(TXT_WPNSELECT, TXT(Static_weapon_names_msg[index]));
// SetGaugeModified(GGF_PRIMARYLOAD, true);
Sound_system.Play2dSound(SOUND_CHANGE_PRIMARY);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.2f;
hear.curiosity_level = 0.5f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
// Stop any firing activity
p_dwb->last_fire_time = Gametime;
// resets reticle to current weapon.
ResetReticle();
}
}
void SetSecondaryWeapon(int index, int slot) {
dynamic_wb_info *p_dwb = &Player_object->dynamic_wb[index];
if (index < (NUM_SECONDARY_SLOTS + SECONDARY_INDEX))
Weapon_slot_mask &= ~(1 << slot);
else
Weapon_slot_mask |= (1 << slot);
// Stop any firing activity
ClearPlayerFiring(Player_object, PW_SECONDARY);
if (index != Players[Player_num].weapon[PW_SECONDARY].index) {
Players[Player_num].weapon[PW_SECONDARY].index = index;
AddHUDMessage(TXT_WPNSELECT, TXT(Static_weapon_names_msg[index]));
// SetGaugeModified(GGF_SECONDARYLOAD, true);
Sound_system.Play2dSound(SOUND_CHANGE_SECONDARY);
ain_hear hear;
hear.f_directly_player = true;
hear.hostile_level = 0.1f;
hear.curiosity_level = 0.3f;
hear.max_dist = AI_SOUND_SHORT_DIST;
AINotify(&Objects[Players[Player_num].objnum], AIN_HEAR_NOISE, (void *)&hear);
// Stop any firing activity
ClearPlayerFiring(Player_object, PW_SECONDARY);
p_dwb->last_fire_time = Gametime;
// resets reticle to current weapon.
ResetReticle();
}
}
///////////////////////////////////////////////////////////////////////////
// Weapon AUTO selection
const uint16_t SELLIST_START = 0x7ffe, SELLIST_END = 0x7fff;
static uint16_t PrimaryWpnSelectList[] = {SELLIST_START, LASER_INDEX, VAUSS_INDEX, MICROWAVE_INDEX,
PLASMA_INDEX, FUSION_INDEX, SUPER_LASER_INDEX, MASSDRIVER_INDEX,
NAPALM_INDEX, EMD_INDEX, OMEGA_INDEX, SELLIST_END};
static uint16_t SecondaryWpnSelectList[] = {
SELLIST_START, CONCUSSION_INDEX, HOMING_INDEX, IMPACTMORTAR_INDEX, SMART_INDEX, MEGA_INDEX,
FRAG_INDEX, GUIDED_INDEX + WPNSEL_SKIP, NAPALMROCKET_INDEX, CYCLONE_INDEX, BLACKSHARK_INDEX, SELLIST_END};
uint16_t GetAutoSelectPrimaryWpnIdx(int slot) {
int i = -1;
while (PrimaryWpnSelectList[i + 1] != SELLIST_END) {
if (slot == i)
return PrimaryWpnSelectList[i + 1];
i++;
}
return WPNSEL_INVALID;
}
uint16_t GetAutoSelectSecondaryWpnIdx(int slot) {
int i = -1;
while (SecondaryWpnSelectList[i + 1] != SELLIST_END) {
if (slot == i)
return SecondaryWpnSelectList[i + 1];
i++;
}
return WPNSEL_INVALID;
}
void SetAutoSelectPrimaryWpnIdx(int slot, uint16_t idx) {
if (slot < 0 || slot >= MAX_PRIMARY_WEAPONS)
Int3();
PrimaryWpnSelectList[slot + 1] = idx;
}
void SetAutoSelectSecondaryWpnIdx(int slot, uint16_t idx) {
if (slot < 0 || slot >= MAX_SECONDARY_WEAPONS)
Int3();
SecondaryWpnSelectList[slot + 1] = idx;
}
const uint16_t IWPNSEL_SKIP = (uint16_t) ((~WPNSEL_SKIP) & 0xFFFF);
#define WPNINDEX(_index) (sel_list[(_index)] & IWPNSEL_SKIP)
// automatically switches weapon up to next level in autoselect order to this value. and type.
int SwitchPlayerWeapon(int weapon_type) {
player *plr;
ship *ship;
int new_index;
void (*setwpnfunc)(int, int); // Call either primary or secondary set weapon function
uint16_t *sel_list;
int plr_wpn_index;
plr = &Players[Player_num];
ship = &Ships[plr->ship_index];
plr_wpn_index = plr->weapon[weapon_type].index;
setwpnfunc = (weapon_type == PW_SECONDARY) ? SetSecondaryWeapon : SetPrimaryWeapon;
sel_list = (weapon_type == PW_SECONDARY) ? SecondaryWpnSelectList : PrimaryWpnSelectList;
new_index = 0;
while (WPNINDEX(new_index) != plr_wpn_index && WPNINDEX(new_index) != SELLIST_END)
new_index++;
if (WPNINDEX(new_index) == SELLIST_END) {
Int3(); // every weapon should be in list that's in the game. bad
new_index = 1; // 1st item after SELLIST_START
}
// select weapon if we can. find a weapon we can select, if we go back to start, return.
// THIS CODE IS SIMILAR BUT NOT THE SAME AS THE AUTO SELECT CODE.
int old_index = new_index;
new_index++;
while (old_index != new_index) {
if (WPNINDEX(new_index) == SELLIST_END) {
new_index = 0;
} else if (WPNINDEX(new_index) == SELLIST_START) {
new_index++;
} else {
uint16_t wpn_index = WPNINDEX(new_index);
otype_wb_info *wb = &ship->static_wb[wpn_index];
int slot = (weapon_type == PW_SECONDARY)
? (((wpn_index - SECONDARY_INDEX) % NUM_SECONDARY_SLOTS) + NUM_PRIMARY_SLOTS)
: (wpn_index % NUM_PRIMARY_SLOTS);
// mprintf(0, "wpn_index = %d\n", wpn_index);
if ((Players[Player_num].weapon_flags & HAS_FLAG(wpn_index)) && !(sel_list[new_index] & WPNSEL_SKIP)) {
if (wpn_index >= SECONDARY_INDEX && wb->ammo_usage && (wb->ammo_usage <= plr->weapon_ammo[wpn_index])) {
// we've found a weapon to select to that uses ammo!
(*setwpnfunc)(wpn_index, slot);
LOGFILE((_logfp, "ammo wpn: switch to new index %d\n", wpn_index));
break;
} else if (wpn_index < SECONDARY_INDEX && wb->ammo_usage && plr->weapon_ammo[wpn_index]) {
// we've found a weapon to select to that uses ammo!
(*setwpnfunc)(wpn_index, slot);
LOGFILE((_logfp, "ammo wpn: switch to new index %d\n", wpn_index));
break;
} else if (!wb->ammo_usage && (plr->energy >= wb->energy_usage)) {
// we've found an energy weapon to select to!
(*setwpnfunc)(wpn_index, slot);
LOGFILE((_logfp, "energy wpn:switch to new index %d\n", wpn_index));
break;
}
}
new_index++;
// mprintf(0, "new_index = %d\n", new_index);
}
}
return sel_list[new_index] & (~WPNSEL_SKIP);
}
// Auto selects a weapon, usually the next best weapon.
// step_up = true if we're going to next higher in order.
// step_up = false if we're going to next lower in order.
bool AutoSelectWeapon(int weapon_type, int new_wpn) {
player *plr;
ship *ship;
uint16_t *sel_list; // a weapon selection list
int weapon_index; // the current weapon index
int list_index; // index into a selection list
int list_initial; // initial index in list.
int slot;
bool sel_new_wpn = false;
void (*setwpnfunc)(int, int); // Call either primary or secondary set weapon function
LOGFILE((_logfp, "Entering AutoSelect\n"));
ASSERT((weapon_type == PW_PRIMARY) || (weapon_type == PW_SECONDARY));
plr = &Players[Player_num];
weapon_index = plr->weapon[weapon_type].index;
ship = &Ships[plr->ship_index];
// choose primary or secondary list and select function
sel_list = (weapon_type == PW_SECONDARY) ? SecondaryWpnSelectList : PrimaryWpnSelectList;
setwpnfunc = (weapon_type == PW_SECONDARY) ? SetSecondaryWeapon : SetPrimaryWeapon;
list_index = 0;
list_initial = 0;
while (WPNINDEX(list_index) != SELLIST_END) {
if (WPNINDEX(list_index) == weapon_index)
list_initial = list_index;
list_index++;
}
list_index--;
// this code takes care of selecting a GIVEN new weapon over the existing.
if (new_wpn > -1) {
while (WPNINDEX(list_index) != SELLIST_START && WPNINDEX(list_index) != new_wpn)
list_index--;
if (!(sel_list[list_index] & WPNSEL_SKIP)) {
if (list_initial >= list_index) {
uint16_t index = WPNINDEX(list_initial);
otype_wb_info *wb = &ship->static_wb[index];
if (index >= SECONDARY_INDEX && wb->ammo_usage && (wb->ammo_usage <= plr->weapon_ammo[index])) {
LOGFILE((_logfp, "keep current ammo weapon...(ind=%d)\n", list_index));
return sel_new_wpn; // the current weapon supercedes the new weapon, (or is the same) so return
} else if (index < SECONDARY_INDEX && wb->ammo_usage && plr->weapon_ammo[index]) {
LOGFILE((_logfp, "keep current ammo weapon...(ind=%d)\n", list_index));
return sel_new_wpn; // the current weapon supercedes the new weapon, (or is the same) so return
} else if (!wb->ammo_usage && (plr->energy >= wb->energy_usage)) {
LOGFILE((_logfp, "keep current energy weapon...(ind=%d)\n", list_index));
return sel_new_wpn; // the current weapon supercedes the new weapon, (or is the same) so return
}
LOGFILE((_logfp, "tried to keep current weapon, but no ammo!...(ind=%d)\n", list_index));
}
} else {
LOGFILE((_logfp, "tried to autoselect skipped weapon, will not do...(ind=%d)\n", list_index));
return sel_new_wpn;
}
}
while (1) {
uint16_t index = sel_list[list_index];
otype_wb_info *wb = &ship->static_wb[index];
if (index == SELLIST_START) {
break;
} else {
// we have a real weapon coming up, lets see if we have it, and if we do, then do we have
// ammo
if (!(index & WPNSEL_SKIP)) {
if (plr->weapon_flags & HAS_FLAG(index)) {
slot = (weapon_type == PW_SECONDARY) ? (((index - SECONDARY_INDEX) % NUM_SECONDARY_SLOTS) + NUM_PRIMARY_SLOTS)
: (index % NUM_PRIMARY_SLOTS);
if (new_wpn == -1) {
// if no new weapon, then select to next best weapon that can be used
if (index >= SECONDARY_INDEX && wb->ammo_usage && (wb->ammo_usage <= plr->weapon_ammo[index])) {
// we've found a weapon to select to that uses ammo!
LOGFILE((_logfp, "ammo wpn: auto select to new index %d\n", index));
(*setwpnfunc)(index, slot);
break;
} else if (index <= SECONDARY_INDEX && wb->ammo_usage && plr->weapon_ammo[index]) {
// we've found a weapon to select to that uses ammo!
LOGFILE((_logfp, "ammo wpn: auto select to new index %d\n", index));
(*setwpnfunc)(index, slot);
break;
} else if (!wb->ammo_usage && (plr->energy >= wb->energy_usage)) {
// we've found an energy weapon to select to!
LOGFILE((_logfp, "energy wpn: auto select to new index %d\n", index));
(*setwpnfunc)(index, slot);
break;
}
} else {
if (index < SECONDARY_INDEX || !wb->ammo_usage ||
(index >= SECONDARY_INDEX && wb->ammo_usage && (wb->ammo_usage <= plr->weapon_ammo[index]))) {
// if new weapon, then always select that weapon (already assumes that new weapon is better.)
LOGFILE((_logfp, "auto select to new index with new weapon!! %d\n", index));
(*setwpnfunc)(index, slot);
sel_new_wpn = true;
break;
}
}
}
}
}
list_index--;
}
return sel_new_wpn;
}
// Draws an alpha blended polygon over the entire 3d rendering scene
// The r,g,b floats specify the color
void DrawAlphaBlendedScreen(float r, float g, float b, float alpha) {
g3Point *pntlist[4], points[4];
ddgr_color color;
int i;
color = GR_RGB(r * 255, g * 255, b * 255);
// Set our four corners to cover the screen
points[0].p3_sx = 0;
points[0].p3_sy = 0;
points[1].p3_sx = Game_window_w;
points[1].p3_sy = 0;
points[2].p3_sx = Game_window_w;
points[2].p3_sy = Game_window_h;
points[3].p3_sx = 0;
points[3].p3_sy = Game_window_h;
for (i = 0; i < 4; i++) {
points[i].p3_z = 0;
points[i].p3_flags = PF_PROJECTED;
pntlist[i] = &points[i];
}
rend_SetZBufferState(0);
rend_SetTextureType(TT_FLAT);
rend_SetAlphaType(AT_CONSTANT);
rend_SetAlphaValue(alpha * 255);
rend_SetLighting(LS_NONE);
rend_SetFlatColor(color);
rend_DrawPolygon2D(0, pntlist, 4);
rend_SetZBufferState(1);
}
// Retreives the weapon in the weapon array based off of an 'index' from 0-19 (non-mapped
// primary and secondaries)
weapon *GetWeaponFromIndex(int player, int index) {
ship *ship = &Ships[Players[player].ship_index];
otype_wb_info *wb = &ship->static_wb[index];
object *pobj = &Objects[Players[player].objnum];
poly_model *pm = &Poly_models[pobj->rtype.pobj_info.model_num];
dynamic_wb_info *dyn_wb = &pobj->dynamic_wb[index];
int cur_m_bit;
for (cur_m_bit = 0; cur_m_bit < pm->poly_wb[0].num_gps; cur_m_bit++) {
if (wb->gp_fire_masks[dyn_wb->cur_firing_mask] & (0x01 << cur_m_bit)) {
return &Weapons[wb->gp_weapon_index[cur_m_bit]];
}
}
return NULL;
}